Merge pull request #3013 from neilLasrado/expense
Expense Claim - 'Total Amount Reimbursed' feild added
diff --git a/.travis.yml b/.travis.yml
index 74cf0d5..598c503 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -18,11 +18,10 @@
script:
- cd ~/frappe-bench
- bench get-app erpnext $TRAVIS_BUILD_DIR
- - bench set-default-site test_site
- - bench frappe --reinstall
- - bench frappe --build_website
- - bench frappe --serve_test &
- - bench frappe --verbose --run_tests
+ - bench use test_site
+ - bench reinstall
+ - bench build-website
+ - bench --verbose run-tests
before_script:
- mysql -e 'create database test_frappe'
diff --git a/erpnext/accounts/doctype/fiscal_year/fiscal_year.py b/erpnext/accounts/doctype/fiscal_year/fiscal_year.py
index 3361e1b..5d0c20d 100644
--- a/erpnext/accounts/doctype/fiscal_year/fiscal_year.py
+++ b/erpnext/accounts/doctype/fiscal_year/fiscal_year.py
@@ -4,7 +4,7 @@
from __future__ import unicode_literals
import frappe
from frappe import msgprint, _
-from frappe.utils import getdate, add_days, add_years
+from frappe.utils import getdate, add_days, add_years, cstr
from datetime import timedelta
from frappe.model.document import Document
@@ -48,17 +48,17 @@
@frappe.whitelist()
def auto_create_fiscal_year():
- for d in frappe.db.sql("""select name from `tabFiscal Year` where year_end_date =(current_date + 3)"""):
+ for d in frappe.db.sql("""select name from `tabFiscal Year` where year_end_date = date_add(current_date, interval 3 day)"""):
try:
current_fy = frappe.get_doc("Fiscal Year", d[0])
- new_fy = frappe.copy_doc(current_fy)
+ new_fy = frappe.copy_doc(current_fy, ignore_no_copy=False)
new_fy.year_start_date = add_days(current_fy.year_end_date, 1)
new_fy.year_end_date = add_years(current_fy.year_end_date, 1)
- start_year = new_fy.year_start_date[:4]
- end_year = new_fy.year_end_date[:4]
+ start_year = cstr(new_fy.year_start_date.year)
+ end_year = cstr(new_fy.year_end_date.year)
new_fy.year = start_year if start_year==end_year else (start_year + "-" + end_year)
new_fy.insert()
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
index b3b35f9..ade0d32 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
@@ -174,8 +174,12 @@
frappe.model.round_floats_in(this.frm.doc, ["base_grand_total", "paid_amount"]);
// this will make outstanding amount 0
this.frm.set_value("write_off_amount",
- flt(this.frm.doc.base_grand_total - this.frm.doc.paid_amount, precision("write_off_amount"))
+ flt(this.frm.doc.base_grand_total - this.frm.doc.paid_amount - this.frm.doc.total_advance, precision("write_off_amount"))
);
+ this.frm.toggle_enable("write_off_amount", false);
+
+ } else {
+ this.frm.toggle_enable("write_off_amount", true);
}
this.calculate_outstanding_amount(false);
@@ -219,10 +223,8 @@
if(cint(doc.is_pos) == 1) {
hide_field(par_flds);
- unhide_field('payments_section');
cur_frm.fields_dict['items'].grid.set_column_disp(item_flds_normal, false);
} else {
- hide_field('payments_section');
for (i in par_flds) {
var docfield = frappe.meta.docfield_map[doc.doctype][par_flds[i]];
if(!docfield.hidden) unhide_field(par_flds[i]);
@@ -238,6 +240,8 @@
if (frappe.boot.sysdefaults.country == 'India') unhide_field(['c_form_applicable', 'c_form_no']);
else hide_field(['c_form_applicable', 'c_form_no']);
+ this.frm.toggle_enable("write_off_amount", !!!cint(doc.write_off_outstanding_amount_automatically));
+
cur_frm.refresh_fields();
}
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json
index bb9f08f..11ae3b5 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json
@@ -654,7 +654,7 @@
"read_only": 0
},
{
- "depends_on": "is_pos",
+ "depends_on": "eval:doc.is_pos===1||(doc.advances && doc.advances.length>0)",
"fieldname": "payments_section",
"fieldtype": "Section Break",
"label": "Payments",
@@ -664,14 +664,6 @@
},
{
"depends_on": "is_pos",
- "fieldname": "column_break3",
- "fieldtype": "Column Break",
- "permlevel": 0,
- "read_only": 0,
- "width": "50%"
- },
- {
- "depends_on": "is_pos",
"fieldname": "paid_amount",
"fieldtype": "Currency",
"label": "Paid Amount",
@@ -685,6 +677,14 @@
},
{
"depends_on": "is_pos",
+ "fieldname": "column_break3",
+ "fieldtype": "Column Break",
+ "permlevel": 0,
+ "read_only": 0,
+ "width": "50%"
+ },
+ {
+ "depends_on": "is_pos",
"fieldname": "cash_bank_account",
"fieldtype": "Link",
"label": "Cash/Bank Account",
@@ -696,24 +696,16 @@
"read_only": 0
},
{
- "depends_on": "is_pos",
+ "depends_on": "grand_total",
"fieldname": "column_break4",
- "fieldtype": "Column Break",
+ "fieldtype": "Section Break",
+ "label": "Write Off",
"permlevel": 0,
"read_only": 0,
"width": "50%"
},
{
- "depends_on": "is_pos",
- "fieldname": "write_off_outstanding_amount_automatically",
- "fieldtype": "Check",
- "label": "Write Off Outstanding Amount",
- "permlevel": 0,
- "print_hide": 1,
- "read_only": 0
- },
- {
- "depends_on": "is_pos",
+ "depends_on": "",
"fieldname": "write_off_amount",
"fieldtype": "Currency",
"label": "Write Off Amount",
@@ -724,7 +716,23 @@
"read_only": 0
},
{
- "depends_on": "is_pos",
+ "depends_on": "",
+ "fieldname": "write_off_outstanding_amount_automatically",
+ "fieldtype": "Check",
+ "label": "Write Off Outstanding Amount",
+ "permlevel": 0,
+ "print_hide": 1,
+ "read_only": 0
+ },
+ {
+ "depends_on": "eval:doc.is_pos===1||(doc.advances && doc.advances.length>0)",
+ "fieldname": "column_break_74",
+ "fieldtype": "Column Break",
+ "permlevel": 0,
+ "precision": ""
+ },
+ {
+ "depends_on": "",
"fieldname": "write_off_account",
"fieldtype": "Link",
"label": "Write Off Account",
@@ -734,7 +742,7 @@
"read_only": 0
},
{
- "depends_on": "is_pos",
+ "depends_on": "",
"fieldname": "write_off_cost_center",
"fieldtype": "Link",
"label": "Write Off Cost Center",
@@ -1246,8 +1254,8 @@
"icon": "icon-file-text",
"idx": 1,
"is_submittable": 1,
- "modified": "2015-03-23 14:46:49.412013",
- "modified_by": "Administrator",
+ "modified": "2015-04-02 13:42:22.985078",
+ "modified_by": "anand@erpnext.com",
"module": "Accounts",
"name": "Sales Invoice",
"owner": "Administrator",
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
index 9cb63b2..2a4c331 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
@@ -48,10 +48,10 @@
self.clear_unallocated_advances("Sales Invoice Advance", "advances")
self.validate_advance_jv("advances", "sales_order")
self.add_remarks()
+ self.validate_write_off_account()
if cint(self.is_pos):
self.validate_pos()
- self.validate_write_off_account()
if cint(self.update_stock):
self.validate_item_code()
@@ -453,8 +453,10 @@
if gl_entries:
from erpnext.accounts.general_ledger import make_gl_entries
+ # if POS and amount is written off, there's no outstanding and hence no need to update it
update_outstanding = cint(self.is_pos) and self.write_off_account \
and 'No' or 'Yes'
+
make_gl_entries(gl_entries, cancel=(self.docstatus == 2),
update_outstanding=update_outstanding, merge_entries=False)
@@ -483,6 +485,8 @@
self.make_pos_gl_entries(gl_entries)
+ self.make_write_off_gl_entry(gl_entries)
+
return gl_entries
def make_customer_gl_entry(self, gl_entries):
@@ -555,29 +559,31 @@
"remarks": self.remarks,
})
)
- # write off entries, applicable if only pos
- if self.write_off_account and self.write_off_amount:
- gl_entries.append(
- self.get_gl_dict({
- "account": self.debit_to,
- "party_type": "Customer",
- "party": self.customer,
- "against": self.write_off_account,
- "credit": self.write_off_amount,
- "remarks": self.remarks,
- "against_voucher": self.name,
- "against_voucher_type": self.doctype,
- })
- )
- gl_entries.append(
- self.get_gl_dict({
- "account": self.write_off_account,
- "against": self.debit_to,
- "debit": self.write_off_amount,
- "remarks": self.remarks,
- "cost_center": self.write_off_cost_center
- })
- )
+
+ def make_write_off_gl_entry(self, gl_entries):
+ # write off entries, applicable if only pos
+ if self.write_off_account and self.write_off_amount:
+ gl_entries.append(
+ self.get_gl_dict({
+ "account": self.debit_to,
+ "party_type": "Customer",
+ "party": self.customer,
+ "against": self.write_off_account,
+ "credit": self.write_off_amount,
+ "remarks": self.remarks,
+ "against_voucher": self.name,
+ "against_voucher_type": self.doctype,
+ })
+ )
+ gl_entries.append(
+ self.get_gl_dict({
+ "account": self.write_off_account,
+ "against": self.debit_to,
+ "debit": self.write_off_amount,
+ "remarks": self.remarks,
+ "cost_center": self.write_off_cost_center
+ })
+ )
def get_list_context(context=None):
from erpnext.controllers.website_list_for_contact import get_list_context
diff --git a/erpnext/accounts/party.py b/erpnext/accounts/party.py
index 8b4e6cb..7ea0c66 100644
--- a/erpnext/accounts/party.py
+++ b/erpnext/accounts/party.py
@@ -14,6 +14,9 @@
def get_party_details(party=None, account=None, party_type="Customer", company=None,
posting_date=None, price_list=None, currency=None, doctype=None):
+ if not party:
+ return {}
+
return _get_party_details(party, account, party_type,
company, posting_date, price_list, currency, doctype)
@@ -161,6 +164,9 @@
return due_date
def get_credit_days(party_type, party, company):
+ if not party:
+ return None
+
party_group_doctype = "Customer Group" if party_type=="Customer" else "Supplier Type"
credit_days, party_group = frappe.db.get_value(party_type, party, ["credit_days", frappe.scrub(party_group_doctype)])
diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.py b/erpnext/buying/doctype/purchase_order/purchase_order.py
index 9e2d34e..15a6fd1 100644
--- a/erpnext/buying/doctype/purchase_order/purchase_order.py
+++ b/erpnext/buying/doctype/purchase_order/purchase_order.py
@@ -3,6 +3,7 @@
from __future__ import unicode_literals
import frappe
+import json
from frappe.utils import cstr, flt
from frappe import msgprint, _, throw
from frappe.model.mapper import get_mapped_doc
@@ -217,6 +218,25 @@
def on_update(self):
pass
+@frappe.whitelist()
+def stop_or_unstop_purchase_orders(names, status):
+ if not frappe.has_permission("Purchase Order", "write"):
+ frappe.throw(_("Not permitted"), frappe.PermissionError)
+
+ names = json.loads(names)
+ for name in names:
+ po = frappe.get_doc("Purchase Order", name)
+ if po.docstatus == 1:
+ if status=="Stopped":
+ if po.status not in ("Stopped", "Cancelled") and (po.per_received < 100 or po.per_billed < 100):
+ po.update_status("Stopped")
+ else:
+ if po.status == "Stopped":
+ po.update_status("Submitted")
+
+ frappe.local.message_log = []
+
+
def set_missing_values(source, target):
target.ignore_pricing_rule = 1
target.run_method("set_missing_values")
diff --git a/erpnext/buying/doctype/purchase_order/purchase_order_list.js b/erpnext/buying/doctype/purchase_order/purchase_order_list.js
index 2c28eec..08bf0fe 100644
--- a/erpnext/buying/doctype/purchase_order/purchase_order_list.js
+++ b/erpnext/buying/doctype/purchase_order/purchase_order_list.js
@@ -3,7 +3,7 @@
"supplier_name", "per_received", "per_billed", "status"],
get_indicator: function(doc) {
if(doc.status==="Stopped") {
- return [__("Stopped"), "red", "status,=,Stopped"];
+ return [__("Stopped"), "darkgrey", "status,=,Stopped"];
} else if(flt(doc.per_received) < 100 && doc.status!=="Stopped") {
return [__("Not Received"), "orange", "per_received,<,100|status,!=,Stopped"];
} else if(flt(doc.per_received) == 100 && flt(doc.per_billed) < 100 && doc.status!=="Stopped") {
@@ -12,5 +12,17 @@
return [__("Completed"), "green", "per_received,=,100|per_billed,=,100|status,!=,Stopped"];
}
},
- order_by: "per_received asc, modified desc"
+ order_by: "per_received asc, modified desc",
+ onload: function(listview) {
+ var method = "erpnext.buying.doctype.purchase_order.purchase_order.stop_or_unstop_purchase_orders";
+
+ listview.page.add_menu_item(__("Set as Stopped"), function() {
+ listview.call_for_selected_items(method, {"status": "Stopped"});
+ });
+
+ listview.page.add_menu_item(__("Set as Unstopped"), function() {
+ listview.call_for_selected_items(method, {"status": "Submitted"});
+ });
+
+ }
};
diff --git a/erpnext/crm/doctype/lead/lead.json b/erpnext/crm/doctype/lead/lead.json
index 2f5be34..7a02ff1 100644
--- a/erpnext/crm/doctype/lead/lead.json
+++ b/erpnext/crm/doctype/lead/lead.json
@@ -64,7 +64,7 @@
"fieldname": "status",
"fieldtype": "Select",
"in_filter": 1,
- "in_list_view": 0,
+ "in_list_view": 1,
"label": "Status",
"no_copy": 1,
"oldfieldname": "status",
@@ -332,7 +332,7 @@
],
"icon": "icon-user",
"idx": 1,
- "modified": "2015-03-18 07:06:34.330943",
+ "modified": "2015-03-30 21:50:08.852355",
"modified_by": "Administrator",
"module": "CRM",
"name": "Lead",
@@ -343,7 +343,6 @@
"cancel": 0,
"create": 0,
"delete": 0,
- "match": "",
"permlevel": 1,
"read": 1,
"report": 1,
@@ -401,7 +400,6 @@
"cancel": 0,
"create": 0,
"delete": 0,
- "match": "",
"permlevel": 1,
"read": 1,
"report": 1,
@@ -414,7 +412,6 @@
"cancel": 0,
"create": 0,
"delete": 0,
- "match": "",
"permlevel": 1,
"read": 1,
"report": 1,
diff --git a/erpnext/crm/doctype/lead/lead.py b/erpnext/crm/doctype/lead/lead.py
index 49a4e3d..009cfc2 100644
--- a/erpnext/crm/doctype/lead/lead.py
+++ b/erpnext/crm/doctype/lead/lead.py
@@ -64,7 +64,7 @@
self.email_id)
if len(email_list) > 1:
items = [e[0] for e in email_list if e[0]!=self.name]
- frappe.throw(_("Email id must be unique, already exists for {0}").format(comma_and(items)))
+ frappe.throw(_("Email id must be unique, already exists for {0}").format(comma_and(items)), frappe.DuplicateEntryError)
def on_trash(self):
frappe.db.sql("""update `tabIssue` set lead='' where lead=%s""",
@@ -124,7 +124,7 @@
}}, target_doc)
return target_doc
-
+
@frappe.whitelist()
def make_quotation(source_name, target_doc=None):
target_doc = get_mapped_doc("Lead", source_name,
diff --git a/erpnext/crm/doctype/newsletter/newsletter.py b/erpnext/crm/doctype/newsletter/newsletter.py
index b1a4bd0..f7f42a0 100644
--- a/erpnext/crm/doctype/newsletter/newsletter.py
+++ b/erpnext/crm/doctype/newsletter/newsletter.py
@@ -59,7 +59,7 @@
subject = self.subject, message = self.message,
reference_doctype = self.doctype, reference_name = self.name,
unsubscribe_method = "/api/method/erpnext.crm.doctype.newsletter.newsletter.unsubscribe",
- unsubscribe_params = {"name": self.name})
+ unsubscribe_params = {"name": self.newsletter_list})
if not frappe.flags.in_test:
frappe.db.auto_commit_on_many_writes = False
@@ -85,9 +85,7 @@
@frappe.whitelist(allow_guest=True)
def unsubscribe(email, name):
- from frappe.email.bulk import return_unsubscribed_page
from frappe.utils.verified_command import verify_request
-
if not verify_request():
return
@@ -97,8 +95,13 @@
subscriber.unsubscribed = 1
subscriber.save(ignore_permissions=True)
+ frappe.db.commit()
+
return_unsubscribed_page(email)
+def return_unsubscribed_page(email):
+ frappe.respond_as_web_page(_("Unsubscribed"), _("{0} has been successfully unsubscribed from this list.").format(email))
+
def create_lead(email_id):
"""create a lead if it does not exist"""
from email.utils import parseaddr
diff --git a/erpnext/hooks.py b/erpnext/hooks.py
index dee46aa..79a3ffe 100644
--- a/erpnext/hooks.py
+++ b/erpnext/hooks.py
@@ -90,8 +90,11 @@
]
}
-default_mail_footer = """<div style="padding: 7px; text-align: right;">
- <a style="color: #888; font-size: 80%;" href="https://erpnext.com">Sent via ERPNext</a></div>"""
+default_mail_footer = """<div style="padding: 7px; margin-top: 7px;">
+ <a style="color: #8D99A6; font-size: 85%; text-decoration: none;" href="https://erpnext.com" target="_blank">
+ Sent via ERPNext
+ </a>
+</div>"""
get_translated_dict = {
("page", "setup-wizard"): "frappe.geo.country_info.get_translated_dict",
diff --git a/erpnext/hr/doctype/employee/employee.py b/erpnext/hr/doctype/employee/employee.py
index 4ba3e96..5a9bad29 100644
--- a/erpnext/hr/doctype/employee/employee.py
+++ b/erpnext/hr/doctype/employee/employee.py
@@ -131,7 +131,7 @@
employee = frappe.db.sql_list("""select name from `tabEmployee` where
user_id=%s and status='Active' and name!=%s""", (self.user_id, self.name))
if employee:
- throw(_("User {0} is already assigned to Employee {1}").format(self.user_id, employee[0]))
+ throw(_("User {0} is already assigned to Employee {1}").format(self.user_id, employee[0]), frappe.DuplicateEntryError)
def validate_employee_leave_approver(self):
for l in self.get("leave_approvers")[:]:
diff --git a/erpnext/hr/doctype/job_applicant/job_applicant.py b/erpnext/hr/doctype/job_applicant/job_applicant.py
index eca767a..1bdd8fc 100644
--- a/erpnext/hr/doctype/job_applicant/job_applicant.py
+++ b/erpnext/hr/doctype/job_applicant/job_applicant.py
@@ -7,6 +7,7 @@
from frappe.model.document import Document
import frappe
from frappe import _
+from frappe.utils import comma_and
class JobApplicant(Document):
def autoname(self):
@@ -15,5 +16,16 @@
frappe.throw(_("Name or Email is mandatory"), frappe.NameError)
self.name = " - ".join(keys)
+ def validate(self):
+ self.check_email_id_is_unique()
+
+ def check_email_id_is_unique(self):
+ if self.email_id:
+ names = frappe.db.sql_list("""select name from `tabJob Applicant`
+ where email_id=%s and name!=%s""", (self.email_id, self.name))
+
+ if names:
+ frappe.throw(_("Email id must be unique, already exists for {0}").format(comma_and(names)), frappe.DuplicateEntryError)
+
def set_sender(self, sender):
self.email_id = sender
diff --git a/erpnext/manufacturing/doctype/production_order/production_order.py b/erpnext/manufacturing/doctype/production_order/production_order.py
index 524d11e..ead4b4f 100644
--- a/erpnext/manufacturing/doctype/production_order/production_order.py
+++ b/erpnext/manufacturing/doctype/production_order/production_order.py
@@ -56,7 +56,7 @@
def validate_warehouse(self):
from erpnext.stock.utils import validate_warehouse_company
-
+
for w in [self.fg_warehouse, self.wip_warehouse]:
validate_warehouse_company(w, self.company)
@@ -176,7 +176,7 @@
operations = frappe.db.sql("""select operation, description, workstation,
hour_rate, time_in_mins, operating_cost as "planned_operating_cost", "Pending" as status
- from `tabBOM Operation` where parent = %s""", self.bom_no, as_dict=1)
+ from `tabBOM Operation` where parent = %s order by idx""", self.bom_no, as_dict=1)
self.set('operations', operations)
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index de51782..14ec93f 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -85,6 +85,7 @@
erpnext.patches.v4_2.recalculate_bom_cost
erpnext.patches.v4_2.fix_gl_entries_for_stock_transactions
erpnext.patches.v4_2.update_requested_and_ordered_qty
+execute:frappe.rename_doc("DocType", "Support Ticket", "Issue", force=True)
erpnext.patches.v4_4.make_email_accounts
execute:frappe.delete_doc("DocType", "Contact Control")
erpnext.patches.v4_2.discount_amount
@@ -94,7 +95,6 @@
execute:frappe.db.sql("update `tabMaterial Request` set material_request_type = 'Material Transfer' where material_request_type = 'Transfer'")
execute:frappe.reload_doc('stock', 'doctype', 'item')
execute:frappe.db.sql("update `tabItem` i set apply_warehouse_wise_reorder_level=1, re_order_level=0, re_order_qty=0 where exists(select name from `tabItem Reorder` where parent=i.name)")
-execute:frappe.rename_doc("DocType", "Support Ticket", "Issue", force=True)
erpnext.patches.v5_0.set_default_company_in_bom
erpnext.patches.v5_0.capacity_planning
execute:frappe.reload_doc('crm', 'doctype', 'lead')
diff --git a/erpnext/patches/v4_4/make_email_accounts.py b/erpnext/patches/v4_4/make_email_accounts.py
index 3ca5fe9..09d1ccb 100644
--- a/erpnext/patches/v4_4/make_email_accounts.py
+++ b/erpnext/patches/v4_4/make_email_accounts.py
@@ -21,7 +21,6 @@
account.enable_outgoing = 1
account.enable_incoming = 0
- account.is_global = 1
account.insert()
@@ -44,7 +43,7 @@
account.set(target_fieldname, support.get(source_fieldname))
account.enable_outgoing = 0
- account.is_global = 1
+ account.append_to = "Issue"
account.insert()
@@ -65,7 +64,6 @@
account.set(target_fieldname, source.get(source_fieldname))
account.enable_outgoing = 0
- account.is_global = 1
account.append_to = "Lead" if doctype=="Sales Email Settings" else "Job Applicant"
account.insert()
diff --git a/erpnext/patches/v5_0/newsletter.py b/erpnext/patches/v5_0/newsletter.py
index 1969c3c..bd95937 100644
--- a/erpnext/patches/v5_0/newsletter.py
+++ b/erpnext/patches/v5_0/newsletter.py
@@ -5,6 +5,7 @@
import frappe.permissions
def execute():
+ frappe.reload_doc("core", "doctype", "block_module")
frappe.reload_doctype("User")
frappe.reload_doctype("Lead")
frappe.reload_doctype("Contact")
diff --git a/erpnext/patches/v5_0/rename_table_fieldnames.py b/erpnext/patches/v5_0/rename_table_fieldnames.py
index 499ef0c..ca92c28 100644
--- a/erpnext/patches/v5_0/rename_table_fieldnames.py
+++ b/erpnext/patches/v5_0/rename_table_fieldnames.py
@@ -92,21 +92,12 @@
"C-Form": [
["invoice_details", "invoices"]
],
- "Customize Form": [
- ["customize_form_fields", "fields"]
- ],
- "Email Alert": [
- ["email_alert_recipients", "recipients"]
- ],
"Employee": [
["employee_leave_approvers", "leave_approvers"],
["educational_qualification_details", "education"],
["previous_experience_details", "external_work_history"],
["experience_in_company_details", "internal_work_history"]
],
- "Event": [
- ["event_roles", "roles"]
- ],
"Expense Claim": [
["expense_voucher_details", "expenses"]
],
@@ -201,10 +192,6 @@
"Time Log Batch": [
["time_log_batch_details", "time_logs"]
],
- "Workflow": [
- ["workflow_document_states", "states"],
- ["workflow_transitions", "transitions"]
- ],
"Workstation": [
["workstation_operation_hours", "working_hours"]
],
@@ -241,7 +228,6 @@
frappe.reload_doc("stock", "doctype", "item_variant")
frappe.reload_doc("accounts", "doctype", "party_account")
frappe.reload_doc("accounts", "doctype", "fiscal_year_company")
- frappe.reload_doc("workflow", "doctype", "workflow")
#rename table fieldnames
for dn in rename_map:
diff --git a/erpnext/patches/v5_0/rename_total_fields.py b/erpnext/patches/v5_0/rename_total_fields.py
index 04b191e..cf95caa 100644
--- a/erpnext/patches/v5_0/rename_total_fields.py
+++ b/erpnext/patches/v5_0/rename_total_fields.py
@@ -41,12 +41,12 @@
def execute():
for doctypes, fields in [[selling_doctypes, selling_renamed_fields], [buying_doctypes, buying_renamed_fields]]:
for dt in doctypes:
- meta = frappe.get_meta(dt)
frappe.reload_doc(get_doctype_module(dt), "doctype", scrub(dt))
+ table_columns = frappe.db.get_table_columns(dt)
base_net_total = frappe.db.sql("select sum(ifnull({0}, 0)) from `tab{1}`".format(fields[0][1], dt))[0][0]
if not base_net_total:
for f in fields:
- if meta.get_field(f[0]):
+ if f[0] in table_columns:
rename_field(dt, f[0], f[1])
# Added new field "total_taxes_and_charges" in buying cycle, updating value
diff --git a/erpnext/projects/doctype/task/task.py b/erpnext/projects/doctype/task/task.py
index a51a495..acd0877 100644
--- a/erpnext/projects/doctype/task/task.py
+++ b/erpnext/projects/doctype/task/task.py
@@ -85,3 +85,12 @@
limit %(start)s, %(page_len)s """ % {'key': searchfield,
'txt': "%%%s%%" % txt, 'mcond':get_match_cond(doctype),
'start': start, 'page_len': page_len})
+
+
+@frappe.whitelist()
+def set_multiple_status(names, status):
+ names = json.loads(names)
+ for name in names:
+ task = frappe.get_doc("Task", name)
+ task.status = status
+ task.save()
diff --git a/erpnext/projects/doctype/task/task_list.js b/erpnext/projects/doctype/task/task_list.js
index 4406085..9150501 100644
--- a/erpnext/projects/doctype/task/task_list.js
+++ b/erpnext/projects/doctype/task/task_list.js
@@ -1,4 +1,15 @@
frappe.listview_settings['Task'] = {
add_fields: ["project", "status", "priority", "exp_end_date"],
- filters:[["status","=", "Open"]]
+ onload: function(listview) {
+ var method = "erpnext.projects.doctype.task.task.set_multiple_status";
+
+ listview.page.add_menu_item(__("Set as Open"), function() {
+ listview.call_for_selected_items(method, {"status": "Open"});
+ });
+
+ listview.page.add_menu_item(__("Set as Closed"), function() {
+ listview.call_for_selected_items(method, {"status": "Closed"});
+ });
+ }
+
};
diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py
index a06f3d7..890f39a 100644
--- a/erpnext/selling/doctype/sales_order/sales_order.py
+++ b/erpnext/selling/doctype/sales_order/sales_order.py
@@ -3,6 +3,7 @@
from __future__ import unicode_literals
import frappe
+import json
import frappe.utils
from frappe.utils import cstr, flt, getdate, comma_and
from frappe import _
@@ -238,6 +239,24 @@
return list_context
@frappe.whitelist()
+def stop_or_unstop_sales_orders(names, status):
+ if not frappe.has_permission("Sales Order", "write"):
+ frappe.throw(_("Not permitted"), frappe.PermissionError)
+
+ names = json.loads(names)
+ for name in names:
+ so = frappe.get_doc("Sales Order", name)
+ if so.docstatus == 1:
+ if status=="Stop":
+ if so.status not in ("Stopped", "Cancelled") and (so.per_delivered < 100 or so.per_billed < 100):
+ so.stop_sales_order()
+ else:
+ if so.status == "Stopped":
+ so.unstop_sales_order()
+
+ frappe.local.message_log = []
+
+@frappe.whitelist()
def make_material_request(source_name, target_doc=None):
def postprocess(source, doc):
doc.material_request_type = "Purchase"
diff --git a/erpnext/selling/doctype/sales_order/sales_order_list.js b/erpnext/selling/doctype/sales_order/sales_order_list.js
index 702c300..085d0e8 100644
--- a/erpnext/selling/doctype/sales_order/sales_order_list.js
+++ b/erpnext/selling/doctype/sales_order/sales_order_list.js
@@ -3,7 +3,7 @@
"status"],
get_indicator: function(doc) {
if(doc.status==="Stopped") {
- return [__("Stopped"), "red", "status,=,Stopped"];
+ return [__("Stopped"), "darkgrey", "status,=,Stopped"];
} else if(flt(doc.per_delivered) < 100 && frappe.datetime.get_diff(doc.delivery_date) < 0) {
return [__("Overdue"), "red", "per_delivered,<,100|delivery_date,<,Today|status,!=,Stopped"];
} else if(flt(doc.per_delivered) < 100 && doc.status!=="Stopped") {
@@ -14,5 +14,17 @@
return [__("Completed"), "green", "per_delivered,=,100|per_billed,=,100|status,!=,Stopped"];
}
},
- order_by: "per_delivered asc, modified desc"
+ order_by: "per_delivered asc, modified desc",
+ onload: function(listview) {
+ var method = "erpnext.selling.doctype.sales_order.sales_order.stop_or_unstop_sales_orders";
+
+ listview.page.add_menu_item(__("Set as Stopped"), function() {
+ listview.call_for_selected_items(method, {"status": "Stop"});
+ });
+
+ listview.page.add_menu_item(__("Set as Unstopped"), function() {
+ listview.call_for_selected_items(method, {"status": "Unstop"});
+ });
+
+ }
};
diff --git a/erpnext/selling/report/customer_acquisition_and_loyalty/customer_acquisition_and_loyalty.py b/erpnext/selling/report/customer_acquisition_and_loyalty/customer_acquisition_and_loyalty.py
index 3360ddb..2b88a50 100644
--- a/erpnext/selling/report/customer_acquisition_and_loyalty/customer_acquisition_and_loyalty.py
+++ b/erpnext/selling/report/customer_acquisition_and_loyalty/customer_acquisition_and_loyalty.py
@@ -18,11 +18,11 @@
company_condition = ' and company=%(company)s'
for si in frappe.db.sql("""select posting_date, customer, base_grand_total from `tabSales Invoice`
- where docstatus=1 and posting_date <= %(to_date)s
- {company_condition} order by posting_date""".format(company_condition=company_condition),
+ where docstatus=1 and posting_date <= %(to_date)s
+ {company_condition} order by posting_date""".format(company_condition=company_condition),
filters, as_dict=1):
-
- key = si.posting_date[:7]
+
+ key = si.posting_date.strftime("%Y-%m-%d")
if not si.customer in customers:
new_customers_in.setdefault(key, [0, 0.0])
new_customers_in[key][0] += 1
@@ -32,14 +32,14 @@
repeat_customers_in.setdefault(key, [0, 0.0])
repeat_customers_in[key][0] += 1
repeat_customers_in[key][1] += si.base_grand_total
-
+
# time series
from_year, from_month, temp = filters.get("from_date").split("-")
to_year, to_month, temp = filters.get("to_date").split("-")
-
+
from_year, from_month, to_year, to_month = \
cint(from_year), cint(from_month), cint(to_year), cint(to_month)
-
+
out = []
for year in xrange(from_year, to_year+1):
for month in xrange(from_month if year==from_year else 1, (to_month+1) if year==to_year else 13):
@@ -48,19 +48,18 @@
new = new_customers_in.get(key, [0,0.0])
repeat = repeat_customers_in.get(key, [0,0.0])
- out.append([year, calendar.month_name[month],
+ out.append([year, calendar.month_name[month],
new[0], repeat[0], new[0] + repeat[0],
new[1], repeat[1], new[1] + repeat[1]])
-
+
return [
- _("Year"), _("Month"),
- _("New Customers") + ":Int",
- _("Repeat Customers") + ":Int",
+ _("Year"), _("Month"),
+ _("New Customers") + ":Int",
+ _("Repeat Customers") + ":Int",
_("Total") + ":Int",
- _("New Customer Revenue") + ":Currency:150",
- _("Repeat Customer Revenue") + ":Currency:150",
+ _("New Customer Revenue") + ":Currency:150",
+ _("Repeat Customer Revenue") + ":Currency:150",
_("Total Revenue") + ":Currency:150"
], out
-
-
-
\ No newline at end of file
+
+
diff --git a/erpnext/setup/doctype/sales_person/sales_person.py b/erpnext/setup/doctype/sales_person/sales_person.py
index 9098aae..5f70b23 100644
--- a/erpnext/setup/doctype/sales_person/sales_person.py
+++ b/erpnext/setup/doctype/sales_person/sales_person.py
@@ -27,7 +27,7 @@
frappe.throw(_("User ID not set for Employee {0}").format(self.employee))
else:
return frappe.db.get_value("User", user, "email") or user
-
+
def validate_employee_id(self):
if frappe.db.exists({"doctype": "Sales Person","employee": self.employee}):
- frappe.throw("Another sales person with the same employee id exists.")
+ frappe.throw("Another sales person with the same employee id exists.", frappe.DuplicateEntryError)
diff --git a/erpnext/setup/page/setup_wizard/emails/email-1.md b/erpnext/setup/page/setup_wizard/emails/email-1.md
new file mode 100644
index 0000000..56ad94f
--- /dev/null
+++ b/erpnext/setup/page/setup_wizard/emails/email-1.md
@@ -0,0 +1,15 @@
+Dear {{ fullname }},
+
+If you aren't a pro, setting me up, is not an easy job. Here are some tips:
+
+1. Try and make dummy cycles: Run your dummy quotes, invoices, payments, deliveries in the system so that you can get familiar with what I can do for you.
+1. Data Import Tool: You can import bulk data into me using the data import tool: via Setup > Data > Data Import Tool
+1. Help me make more friends: Add more users via Setup > Users and Permissions > User
+
+If you need help or are stuck, [head to the user forum](https://discuss.frappe.io) or [read my manual](https://manual.erpnext.com).
+
+Best of luck!
+
+Your ERPNext Account\*
+
+\* This is an automated messaged sent by your ERPNext installation to keep you motivated. You will receive one more such message in another 3 days.
diff --git a/erpnext/setup/page/setup_wizard/emails/email-2.md b/erpnext/setup/page/setup_wizard/emails/email-2.md
new file mode 100644
index 0000000..f2b5d6a
--- /dev/null
+++ b/erpnext/setup/page/setup_wizard/emails/email-2.md
@@ -0,0 +1,16 @@
+Dear {{ fullname }},
+
+Hey, how's it going?
+
+If you still need some help, there is a large community out there to help you. Some places you can start:
+
+1. [Ask your questions in the user forum](https://discuss.frappe.io)
+1. [Buy support from the ERPNext Team](https://erpnext.com/pricing)
+1. [Connect with a Service Provider](https://community.erpnext.com/service-providers)
+1. [Hangout on the community chat (gitter.im)](https://gitter.im/frappe/erpnext)
+
+Thanks!
+
+Your ERPNext Account\*
+
+\* That's it from me. Have fun!
diff --git a/erpnext/setup/page/setup_wizard/install_fixtures.py b/erpnext/setup/page/setup_wizard/install_fixtures.py
index 32fb6a5..6f3f9e1 100644
--- a/erpnext/setup/page/setup_wizard/install_fixtures.py
+++ b/erpnext/setup/page/setup_wizard/install_fixtures.py
@@ -179,4 +179,3 @@
pass
else:
raise
-
diff --git a/erpnext/setup/page/setup_wizard/setup_wizard.js b/erpnext/setup/page/setup_wizard/setup_wizard.js
index ef39b03..eb47e4a 100644
--- a/erpnext/setup/page/setup_wizard/setup_wizard.js
+++ b/erpnext/setup/page/setup_wizard/setup_wizard.js
@@ -50,7 +50,7 @@
this.slides = this.slides;
this.slide_dict = {};
this.welcomed = true;
- frappe.set_route(this.page_name, "0");
+ frappe.set_route("setup-wizard/0");
},
make: function() {
this.parent = $('<div class="setup-wizard-wrapper">').appendTo(this.parent);
@@ -565,10 +565,12 @@
placeholder:__("A Product or Service")},
{fieldtype:"Select", label:__("Group"), fieldname:"item_group_" + i,
options:[__("Products"), __("Services"),
- __("Raw Material"), __("Consumable"), __("Sub Assemblies")]},
+ __("Raw Material"), __("Consumable"), __("Sub Assemblies")],
+ "default": __("Products")},
{fieldtype:"Select", fieldname:"item_uom_" + i, label:__("UOM"),
options:[__("Unit"), __("Nos"), __("Box"), __("Pair"), __("Kg"), __("Set"),
- __("Hour"), __("Minute")]},
+ __("Hour"), __("Minute")],
+ "default": __("Unit")},
{fieldtype: "Check", fieldname: "is_sales_item_" + i, label:__("We sell this Item"), default: 1},
{fieldtype: "Check", fieldname: "is_purchase_item_" + i, label:__("We buy this Item")},
{fieldtype:"Column Break"},
diff --git a/erpnext/setup/page/setup_wizard/setup_wizard.py b/erpnext/setup/page/setup_wizard/setup_wizard.py
index 4df21b5..11f3269 100644
--- a/erpnext/setup/page/setup_wizard/setup_wizard.py
+++ b/erpnext/setup/page/setup_wizard/setup_wizard.py
@@ -11,8 +11,9 @@
get_lang_dict, send_translations, get_language_from_code)
from frappe.geo.country_info import get_country_info
from frappe.utils.nestedset import get_root_of
-from default_website import website_maker
+from .default_website import website_maker
import install_fixtures
+from .welcome_emails import setup_welcome_emails
@frappe.whitelist()
def setup_account(args=None):
@@ -73,8 +74,12 @@
website_maker(args.company_name.strip(), args.company_tagline, args.name)
create_logo(args)
+ frappe.db.commit()
+
login_as_first_user(args)
+ setup_welcome_emails()
+
frappe.db.commit()
frappe.clear_cache()
diff --git a/erpnext/setup/page/setup_wizard/welcome_emails.py b/erpnext/setup/page/setup_wizard/welcome_emails.py
new file mode 100644
index 0000000..03a9717
--- /dev/null
+++ b/erpnext/setup/page/setup_wizard/welcome_emails.py
@@ -0,0 +1,20 @@
+# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
+# License: GNU General Public License v3. See license.txt
+
+from __future__ import unicode_literals
+import frappe
+from frappe.utils import add_days, nowdate, get_fullname
+import markdown2
+
+def setup_welcome_emails():
+ for email in (
+ {"fname": "email-1.md", "subject": "Setting up ERPNext", "after": 1},
+ {"fname": "email-2.md", "subject": "Getting ERPNext Help", "after": 3},
+ ):
+ content = frappe.get_template("setup/page/setup_wizard/emails/" \
+ + email["fname"]).render({"fullname": get_fullname()})
+
+ frappe.sendmail(recipients = frappe.session.user, subject = email["subject"],
+ sender = "hello@erpnext.com",
+ content=markdown2.markdown(content), as_bulk = True,
+ send_after= add_days(nowdate(), email["after"]))
diff --git a/erpnext/stock/report/stock_balance/stock_balance.py b/erpnext/stock/report/stock_balance/stock_balance.py
index 4f456fe..45f5ccc 100644
--- a/erpnext/stock/report/stock_balance/stock_balance.py
+++ b/erpnext/stock/report/stock_balance/stock_balance.py
@@ -4,7 +4,7 @@
from __future__ import unicode_literals
import frappe
from frappe import _
-from frappe.utils import flt
+from frappe.utils import flt, getdate
def execute(filters=None):
if not filters: filters = {}
@@ -88,11 +88,11 @@
qty_diff = flt(d.actual_qty)
value_diff = flt(d.stock_value_difference)
-
- if d.posting_date < filters["from_date"]:
+
+ if d.posting_date < getdate(filters["from_date"]):
qty_dict.opening_qty += qty_diff
qty_dict.opening_val += value_diff
- elif d.posting_date >= filters["from_date"] and d.posting_date <= filters["to_date"]:
+ elif d.posting_date >= getdate(filters["from_date"]) and d.posting_date <= getdate(filters["to_date"]):
qty_dict.val_rate = d.valuation_rate
if qty_diff > 0:
qty_dict.in_qty += qty_diff
diff --git a/erpnext/support/doctype/issue/issue.js b/erpnext/support/doctype/issue/issue.js
new file mode 100644
index 0000000..f92e4e6
--- /dev/null
+++ b/erpnext/support/doctype/issue/issue.js
@@ -0,0 +1,15 @@
+frappe.ui.form.on("Issue", {
+ "refresh": function(frm) {
+ if(frm.doc.status==="Open") {
+ frm.add_custom_button("Close", function() {
+ frm.set_value("status", "Closed");
+ frm.save();
+ });
+ } else {
+ frm.add_custom_button("Reopen", function() {
+ frm.set_value("status", "Open");
+ frm.save();
+ });
+ }
+ }
+});
diff --git a/erpnext/support/doctype/issue/issue.py b/erpnext/support/doctype/issue/issue.py
index 841d905..dc6809f 100644
--- a/erpnext/support/doctype/issue/issue.py
+++ b/erpnext/support/doctype/issue/issue.py
@@ -3,6 +3,7 @@
from __future__ import unicode_literals
import frappe
+import json
from frappe import _
from frappe.model.document import Document
@@ -75,3 +76,9 @@
frappe.db.sql("""update `tabIssue` set status = 'Closed'
where status = 'Replied'
and date_sub(curdate(),interval 15 Day) > modified""")
+
+@frappe.whitelist()
+def set_multiple_status(names, status):
+ names = json.loads(names)
+ for name in names:
+ set_status(name, status)
diff --git a/erpnext/support/doctype/issue/issue_list.js b/erpnext/support/doctype/issue/issue_list.js
index 02fd40c..7198b0c 100644
--- a/erpnext/support/doctype/issue/issue_list.js
+++ b/erpnext/support/doctype/issue/issue_list.js
@@ -1,3 +1,14 @@
frappe.listview_settings['Issue'] = {
- colwidths: {"subject": 6}
+ colwidths: {"subject": 6},
+ onload: function(listview) {
+ var method = "erpnext.support.doctype.issue.issue.set_multiple_status";
+
+ listview.page.add_menu_item(__("Set as Open"), function() {
+ listview.call_for_selected_items(method, {"status": "Open"});
+ });
+
+ listview.page.add_menu_item(__("Set as Closed"), function() {
+ listview.call_for_selected_items(method, {"status": "Closed"});
+ });
+ }
}
diff --git a/erpnext/utilities/doctype/note/note.py b/erpnext/utilities/doctype/note/note.py
index 757684c..d81363c 100644
--- a/erpnext/utilities/doctype/note/note.py
+++ b/erpnext/utilities/doctype/note/note.py
@@ -24,10 +24,7 @@
if user == "Administrator":
return ""
- return """(`tabNote`.public=1 or `tabNote`.owner="{user}" or exists (
- select name from `tabNote User`
- where `tabNote User`.parent=`tabNote`.name
- and `tabNote User`.user="{user}"))""".format(user=frappe.db.escape(user))
+ return "`tabNote`.public=1"
def has_permission(doc, ptype, user):
if doc.public == 1 or user == "Administrator":