From dc448c2f51210e4b7481ad229cf9c339e81ffd69 Mon Sep 17 00:00:00 2001 From: Anupam Date: Thu, 17 Jun 2021 00:28:03 +0530 Subject: [PATCH 01/54] refactor: lead --- erpnext/crm/doctype/lead/lead.js | 5 - erpnext/crm/doctype/lead/lead.json | 186 ++++++++++++++++------------- erpnext/crm/doctype/lead/lead.py | 84 +------------ 3 files changed, 107 insertions(+), 168 deletions(-) diff --git a/erpnext/crm/doctype/lead/lead.js b/erpnext/crm/doctype/lead/lead.js index ebe85241d28..815bb41a76e 100644 --- a/erpnext/crm/doctype/lead/lead.js +++ b/erpnext/crm/doctype/lead/lead.js @@ -68,11 +68,6 @@ erpnext.LeadController = class LeadController extends frappe.ui.form.Controller }) } - organization_lead () { - this.frm.toggle_reqd("lead_name", !this.frm.doc.organization_lead); - this.frm.toggle_reqd("company_name", this.frm.doc.organization_lead); - } - company_name () { if (this.frm.doc.organization_lead && !this.frm.doc.lead_name) { this.frm.set_value("lead_name", this.frm.doc.company_name); diff --git a/erpnext/crm/doctype/lead/lead.json b/erpnext/crm/doctype/lead/lead.json index 1b33fd73acf..ed33d896f8a 100644 --- a/erpnext/crm/doctype/lead/lead.json +++ b/erpnext/crm/doctype/lead/lead.json @@ -9,30 +9,32 @@ "email_append_to": 1, "engine": "InnoDB", "field_order": [ - "organization_lead", "lead_details", "naming_series", - "lead_name", - "company_name", - "email_id", - "col_break123", - "lead_owner", - "status", "salutation", + "first_name", + "middle_name", + "last_name", + "lead_name", + "email_id", + "mobile_no", + "phone", + "col_break123", + "status", + "company_name", "designation", "gender", - "source", - "customer", - "campaign_name", - "image", - "section_break_12", - "contact_by", - "column_break_14", - "contact_date", - "ends_on", - "notes_section", - "notes", - "address_info", + "additional_information_section", + "no_of_employees", + "industry", + "market_segment", + "type", + "request_type", + "column_break_22", + "whatsapp_no", + "fax", + "website", + "address_section", "address_html", "address_type", "address_title", @@ -45,35 +47,33 @@ "state", "country", "pincode", - "contact_section", - "phone", - "mobile_no", - "fax", - "website", - "more_info", - "type", - "market_segment", - "industry", - "request_type", - "column_break3", + "section_break_12", + "lead_owner", + "ends_on", + "column_break_14", + "contact_by", + "contact_date", + "lead_source_details_section", "company", "territory", "language", + "column_break_50", + "source", + "campaign_name", "unsubscribed", "blog_subscriber", + "notes_section", + "notes", + "other_information_section", + "customer", + "image", "title" ], "fields": [ - { - "default": "0", - "fieldname": "organization_lead", - "fieldtype": "Check", - "label": "Lead is an Organization", - "set_only_once": 1 - }, { "fieldname": "lead_details", "fieldtype": "Section Break", + "label": "Lead Details", "options": "fa fa-user" }, { @@ -90,7 +90,8 @@ "fieldname": "lead_name", "fieldtype": "Data", "in_global_search": 1, - "label": "Person Name", + "label": "Full Name", + "mandatory_depends_on": "eval: !(doc.company_name)", "oldfieldname": "lead_name", "oldfieldtype": "Data", "search_index": 1 @@ -99,7 +100,9 @@ "fieldname": "company_name", "fieldtype": "Data", "in_list_view": 1, + "in_standard_filter": 1, "label": "Organization Name", + "mandatory_depends_on": "eval: !(doc.lead_name)", "oldfieldname": "company_name", "oldfieldtype": "Data" }, @@ -121,7 +124,6 @@ "default": "__user", "fieldname": "lead_owner", "fieldtype": "Link", - "in_list_view": 1, "label": "Lead Owner", "oldfieldname": "lead_owner", "oldfieldtype": "Link", @@ -241,46 +243,39 @@ "read_only": 1 }, { - "depends_on": "eval: doc.__islocal", "description": "Home, Work, etc.", "fieldname": "address_title", "fieldtype": "Data", "label": "Address Title" }, { - "depends_on": "eval: doc.__islocal", "fieldname": "address_line1", "fieldtype": "Data", "label": "Address Line 1", "mandatory_depends_on": "eval: doc.address_title && doc.address_type" }, { - "depends_on": "eval: doc.__islocal", "fieldname": "address_line2", "fieldtype": "Data", "label": "Address Line 2" }, { - "depends_on": "eval: doc.__islocal", "fieldname": "city", "fieldtype": "Data", "label": "City/Town", "mandatory_depends_on": "eval: doc.address_title && doc.address_type" }, { - "depends_on": "eval: doc.__islocal", "fieldname": "county", "fieldtype": "Data", "label": "County" }, { - "depends_on": "eval: doc.__islocal", "fieldname": "state", "fieldtype": "Data", "label": "State" }, { - "depends_on": "eval: doc.__islocal", "fieldname": "country", "fieldtype": "Link", "label": "Country", @@ -288,7 +283,7 @@ "options": "Country" }, { - "depends_on": "eval: doc.__islocal", + "collapsible_depends_on": "eval: doc.__islocal", "fieldname": "pincode", "fieldtype": "Data", "label": "Postal Code" @@ -329,14 +324,6 @@ "oldfieldname": "fax", "oldfieldtype": "Data" }, - { - "collapsible": 1, - "fieldname": "more_info", - "fieldtype": "Section Break", - "label": "More Information", - "oldfieldtype": "Section Break", - "options": "fa fa-file-text" - }, { "fieldname": "type", "fieldtype": "Select", @@ -369,12 +356,6 @@ "oldfieldtype": "Select", "options": "\nProduct Enquiry\nRequest for Information\nSuggestions\nOther" }, - { - "fieldname": "column_break3", - "fieldtype": "Column Break", - "oldfieldtype": "Column Break", - "width": "50%" - }, { "fieldname": "company", "fieldtype": "Link", @@ -389,11 +370,14 @@ "fieldtype": "Data", "label": "Website", "oldfieldname": "website", - "oldfieldtype": "Data" + "oldfieldtype": "Data", + "options": "URL" }, { "fieldname": "territory", "fieldtype": "Link", + "in_list_view": 1, + "in_standard_filter": 1, "label": "Territory", "oldfieldname": "territory", "oldfieldtype": "Link", @@ -422,28 +406,13 @@ { "fieldname": "designation", "fieldtype": "Link", + "in_list_view": 1, + "in_standard_filter": 1, "label": "Designation", "options": "Designation" }, - { - "collapsible": 1, - "collapsible_depends_on": "eval: doc.__islocal", - "fieldname": "address_info", - "fieldtype": "Section Break", - "label": "Address & Contact", - "oldfieldtype": "Column Break", - "options": "fa fa-map-marker" - }, - { - "collapsible": 1, - "collapsible_depends_on": "eval: doc.__islocal", - "fieldname": "contact_section", - "fieldtype": "Section Break", - "label": "Contact" - }, { "default": "Billing", - "depends_on": "eval: doc.__islocal", "fieldname": "address_type", "fieldtype": "Select", "label": "Address Type", @@ -454,13 +423,70 @@ "fieldtype": "Link", "label": "Print Language", "options": "Language" + }, + { + "fieldname": "first_name", + "fieldtype": "Data", + "label": "First Name" + }, + { + "fieldname": "middle_name", + "fieldtype": "Data", + "label": "Middle Name" + }, + { + "fieldname": "last_name", + "fieldtype": "Data", + "label": "Last Name" + }, + { + "collapsible": 1, + "fieldname": "additional_information_section", + "fieldtype": "Section Break", + "label": "Additional Information" + }, + { + "fieldname": "no_of_employees", + "fieldtype": "Int", + "label": "No. of Employees" + }, + { + "fieldname": "column_break_22", + "fieldtype": "Column Break" + }, + { + "fieldname": "whatsapp_no", + "fieldtype": "Data", + "label": "WhatsApp No.", + "options": "Phone" + }, + { + "collapsible": 1, + "depends_on": "eval:!doc.__islocal", + "fieldname": "address_section", + "fieldtype": "Section Break", + "label": "Address" + }, + { + "fieldname": "lead_source_details_section", + "fieldtype": "Section Break", + "label": "Lead Source Details" + }, + { + "fieldname": "column_break_50", + "fieldtype": "Column Break" + }, + { + "fieldname": "other_information_section", + "fieldtype": "Section Break", + "label": "Other Information" } ], "icon": "fa fa-user", "idx": 5, "image_field": "image", "links": [], - "modified": "2021-01-06 19:39:58.748978", + "modified": "2021-06-17 00:20:37.768449", "modified_by": "Administrator", "module": "CRM", "name": "Lead", diff --git a/erpnext/crm/doctype/lead/lead.py b/erpnext/crm/doctype/lead/lead.py index d1d096843bf..b18129b14fc 100644 --- a/erpnext/crm/doctype/lead/lead.py +++ b/erpnext/crm/doctype/lead/lead.py @@ -21,14 +21,6 @@ class Lead(SellingController): self.get("__onload").is_customer = customer load_address_and_contact(self) - def before_insert(self): - if self.address_title and self.address_type: - self.address_doc = self.create_address() - self.contact_doc = self.create_contact() - - def after_insert(self): - self.update_links() - def validate(self): self.set_lead_name() self.set_title() @@ -120,85 +112,11 @@ class Lead(SellingController): self.lead_name = self.email_id.split("@")[0] def set_title(self): - if self.organization_lead: + if self.company_name: self.title = self.company_name else: self.title = self.lead_name - def create_address(self): - address_fields = ["address_type", "address_title", "address_line1", "address_line2", - "city", "county", "state", "country", "pincode"] - info_fields = ["email_id", "phone", "fax"] - - # do not create an address if no fields are available, - # skipping country since the system auto-sets it from system defaults - address = frappe.new_doc("Address") - - address.update({addr_field: self.get(addr_field) for addr_field in address_fields}) - address.update({info_field: self.get(info_field) for info_field in info_fields}) - address.insert() - - return address - - def create_contact(self): - if not self.lead_name: - self.set_lead_name() - - names = self.lead_name.strip().split(" ") - if len(names) > 1: - first_name, last_name = names[0], " ".join(names[1:]) - else: - first_name, last_name = self.lead_name, None - - contact = frappe.new_doc("Contact") - contact.update({ - "first_name": first_name, - "last_name": last_name, - "salutation": self.salutation, - "gender": self.gender, - "designation": self.designation, - }) - - if self.email_id: - contact.append("email_ids", { - "email_id": self.email_id, - "is_primary": 1 - }) - - if self.phone: - contact.append("phone_nos", { - "phone": self.phone, - "is_primary": 1 - }) - - if self.mobile_no: - contact.append("phone_nos", { - "phone": self.mobile_no - }) - - contact.insert(ignore_permissions=True) - - return contact - - def update_links(self): - # update address links - if hasattr(self, 'address_doc'): - self.address_doc.append("links", { - "link_doctype": "Lead", - "link_name": self.name, - "link_title": self.lead_name - }) - self.address_doc.save() - - # update contact links - if self.contact_doc: - self.contact_doc.append("links", { - "link_doctype": "Lead", - "link_name": self.name, - "link_title": self.lead_name - }) - self.contact_doc.save() - @frappe.whitelist() def make_customer(source_name, target_doc=None): return _make_customer(source_name, target_doc) From 23b1c25ff5652b120585bab11d1a0e33a3ba24a2 Mon Sep 17 00:00:00 2001 From: Subin Tom Date: Mon, 19 Jul 2021 20:09:37 +0530 Subject: [PATCH 02/54] fix:Ignore mandatory fields while creating payment reconciliation Journal Entry --- .../doctype/payment_reconciliation/payment_reconciliation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py index 6635128f9ef..d788d91855e 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py +++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py @@ -306,5 +306,5 @@ def reconcile_dr_cr_note(dr_cr_notes, company): } ] }) - + jv.flags.ignore_mandatory = True jv.submit() \ No newline at end of file From eecfc4c0aef57eb8146ff15d1b9210c62c73c489 Mon Sep 17 00:00:00 2001 From: marination Date: Thu, 22 Jul 2021 13:23:54 +0530 Subject: [PATCH 03/54] fix: Clean Serial No input on Server Side --- erpnext/controllers/stock_controller.py | 7 +++++++ erpnext/public/js/controllers/transaction.js | 2 +- erpnext/stock/doctype/serial_no/serial_no.py | 10 ++++++++-- erpnext/stock/doctype/stock_entry/stock_entry.py | 1 + .../stock_reconciliation/stock_reconciliation.py | 1 + 5 files changed, 18 insertions(+), 3 deletions(-) diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index 2526e6df0ef..f15af0a6974 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -27,6 +27,7 @@ class StockController(AccountsController): if not self.get('is_return'): self.validate_inspection() self.validate_serialized_batch() + self.clean_serial_nos() self.validate_customer_provided_item() self.set_rate_of_stock_uom() self.validate_internal_transfer() @@ -67,6 +68,12 @@ class StockController(AccountsController): frappe.throw(_("Row #{0}: The batch {1} has already expired.") .format(d.idx, get_link_to_form("Batch", d.get("batch_no")))) + def clean_serial_nos(self): + for row in self.get("items"): + if hasattr(row, "serial_no") and row.serial_no: + # replace commas by linefeed and remove all spaces in string + row.serial_no = row.serial_no.replace(",", "\n").replace(" ", "") + def get_gl_entries(self, warehouse_account=None, default_expense_account=None, default_cost_center=None): diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 8360337ef73..e75e0a32fc6 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -752,7 +752,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe this.frm.trigger("item_code", cdt, cdn); } else { - // Replacing all occurences of comma with carriage return + // Replace all occurences of comma with line feed item.serial_no = item.serial_no.replace(/,/g, '\n'); item.conversion_factor = item.conversion_factor || 1; refresh_field("serial_no", item.name, item.parentfield); diff --git a/erpnext/stock/doctype/serial_no/serial_no.py b/erpnext/stock/doctype/serial_no/serial_no.py index bad7b608acf..70312bc543b 100644 --- a/erpnext/stock/doctype/serial_no/serial_no.py +++ b/erpnext/stock/doctype/serial_no/serial_no.py @@ -165,8 +165,14 @@ class SerialNo(StockController): ) ORDER BY posting_date desc, posting_time desc, creation desc""", - (self.item_code, self.company, - serial_no, serial_no+'\n%', '%\n'+serial_no, '%\n'+serial_no+'\n%'), as_dict=1): + ( + self.item_code, self.company, + serial_no, + serial_no+'\n%', + '%\n'+serial_no, + '%\n'+serial_no+'\n%' + ), + as_dict=1): if serial_no.upper() in get_serial_nos(sle.serial_no): if cint(sle.actual_qty) > 0: sle_dict.setdefault("incoming", []).append(sle) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index fcb6f0f4c2f..395f3397870 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -76,6 +76,7 @@ class StockEntry(StockController): self.validate_difference_account() self.set_job_card_data() self.set_purpose_for_stock_entry() + self.clean_serial_nos() self.validate_duplicate_serial_no() if not self.from_bom: diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py index 98754915939..07c32a943d6 100644 --- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py @@ -31,6 +31,7 @@ class StockReconciliation(StockController): self.validate_expense_account() self.validate_customer_provided_item() self.set_zero_value_for_customer_provided_items() + self.clean_serial_nos() self.set_total_qty_and_amount() self.validate_putaway_capacity() From 03a6c38f06034383b57593145f8c9e77c42d6370 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Mon, 26 Jul 2021 22:24:25 +0530 Subject: [PATCH 04/54] test: fix test due to rename change --- erpnext/selling/doctype/sales_order/test_sales_order.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/erpnext/selling/doctype/sales_order/test_sales_order.py b/erpnext/selling/doctype/sales_order/test_sales_order.py index 974648d6d44..1de1e000975 100644 --- a/erpnext/selling/doctype/sales_order/test_sales_order.py +++ b/erpnext/selling/doctype/sales_order/test_sales_order.py @@ -673,6 +673,8 @@ class TestSalesOrder(unittest.TestCase): so.cancel() + dn.load_from_db() + self.assertRaises(frappe.CancelledLinkError, dn.submit) def test_service_type_product_bundle(self): From 49cfac0ef0eb2333e5ff6291a60550d26faf3930 Mon Sep 17 00:00:00 2001 From: Anupam Date: Wed, 28 Jul 2021 17:51:35 +0530 Subject: [PATCH 05/54] feat: added basic info. of lead in header part --- erpnext/crm/doctype/lead/lead.js | 40 ++++++++++++++++++++++++++++++ erpnext/crm/doctype/lead/lead.json | 2 +- 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/erpnext/crm/doctype/lead/lead.js b/erpnext/crm/doctype/lead/lead.js index 815bb41a76e..bd1639bb12f 100644 --- a/erpnext/crm/doctype/lead/lead.js +++ b/erpnext/crm/doctype/lead/lead.js @@ -42,6 +42,7 @@ erpnext.LeadController = class LeadController extends frappe.ui.form.Controller if (!this.frm.is_new()) { frappe.contacts.render_address_and_contact(this.frm); + cur_frm.trigger('render_basic_info_html'); } else { frappe.contacts.clear_address_and_contact(this.frm); } @@ -81,6 +82,45 @@ erpnext.LeadController = class LeadController extends frappe.ui.form.Controller this.frm.set_value("ends_on", d.format(frappe.defaultDatetimeFormat)); } } + + render_basic_info_html() { + let html=''; + if (cur_frm.doc.lead_owner){ + html += `
+ Lead Owner +
+
+ ${cur_frm.doc.lead_owner} +
` ; + } + + if (cur_frm.doc.email_id){ + html += `
+ Email +
+
+ ${cur_frm.doc.email_id} +
` ; + } + + if (cur_frm.doc.mobile_no){ + html += `
+ Mobile +
+
+ ${cur_frm.doc.mobile_no} +
` ; + } + + html += `
+ Status +
+
+ ${cur_frm.doc.status} +
` ; + html = `
${html}
`; + cur_frm.dashboard.set_headline_alert(html); + } }; extend_cscript(cur_frm.cscript, new erpnext.LeadController({ frm: cur_frm })); diff --git a/erpnext/crm/doctype/lead/lead.json b/erpnext/crm/doctype/lead/lead.json index ed33d896f8a..dc030fe8197 100644 --- a/erpnext/crm/doctype/lead/lead.json +++ b/erpnext/crm/doctype/lead/lead.json @@ -486,7 +486,7 @@ "idx": 5, "image_field": "image", "links": [], - "modified": "2021-06-17 00:20:37.768449", + "modified": "2021-07-28 00:20:37.768449", "modified_by": "Administrator", "module": "CRM", "name": "Lead", From 47a651a80fe5be8ec9100b68f33097f2e349a086 Mon Sep 17 00:00:00 2001 From: Anupam Date: Wed, 28 Jul 2021 18:21:19 +0530 Subject: [PATCH 06/54] fix: removing depends_on for contact fields --- erpnext/crm/doctype/lead/lead.json | 5 ----- 1 file changed, 5 deletions(-) diff --git a/erpnext/crm/doctype/lead/lead.json b/erpnext/crm/doctype/lead/lead.json index dc030fe8197..f9a500fa974 100644 --- a/erpnext/crm/doctype/lead/lead.json +++ b/erpnext/crm/doctype/lead/lead.json @@ -145,7 +145,6 @@ "search_index": 1 }, { - "depends_on": "eval: doc.__islocal", "fieldname": "salutation", "fieldtype": "Link", "label": "Salutation", @@ -283,7 +282,6 @@ "options": "Country" }, { - "collapsible_depends_on": "eval: doc.__islocal", "fieldname": "pincode", "fieldtype": "Data", "label": "Postal Code" @@ -299,7 +297,6 @@ "read_only": 1 }, { - "depends_on": "eval: doc.__islocal", "fieldname": "phone", "fieldtype": "Data", "label": "Phone", @@ -308,7 +305,6 @@ "options": "Phone" }, { - "depends_on": "eval: doc.__islocal", "fieldname": "mobile_no", "fieldtype": "Data", "label": "Mobile No.", @@ -317,7 +313,6 @@ "options": "Phone" }, { - "depends_on": "eval: doc.__islocal", "fieldname": "fax", "fieldtype": "Data", "label": "Fax", From a6ce1244a07279d601702ece31243d9d6e857557 Mon Sep 17 00:00:00 2001 From: Anupam Date: Thu, 29 Jul 2021 10:43:21 +0530 Subject: [PATCH 07/54] fix: sider issues --- erpnext/crm/doctype/lead/lead.js | 6 +++--- erpnext/crm/doctype/lead/lead.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/erpnext/crm/doctype/lead/lead.js b/erpnext/crm/doctype/lead/lead.js index bd1639bb12f..ad24a6e2225 100644 --- a/erpnext/crm/doctype/lead/lead.js +++ b/erpnext/crm/doctype/lead/lead.js @@ -85,7 +85,7 @@ erpnext.LeadController = class LeadController extends frappe.ui.form.Controller render_basic_info_html() { let html=''; - if (cur_frm.doc.lead_owner){ + if (cur_frm.doc.lead_owner) { html += `
Lead Owner
@@ -94,7 +94,7 @@ erpnext.LeadController = class LeadController extends frappe.ui.form.Controller ` ; } - if (cur_frm.doc.email_id){ + if (cur_frm.doc.email_id) { html += `
Email
@@ -103,7 +103,7 @@ erpnext.LeadController = class LeadController extends frappe.ui.form.Controller ` ; } - if (cur_frm.doc.mobile_no){ + if (cur_frm.doc.mobile_no) { html += `
Mobile
diff --git a/erpnext/crm/doctype/lead/lead.py b/erpnext/crm/doctype/lead/lead.py index ce36069052e..f09a8145401 100644 --- a/erpnext/crm/doctype/lead/lead.py +++ b/erpnext/crm/doctype/lead/lead.py @@ -95,7 +95,7 @@ class Lead(SellingController): if d.link_doctype == self.doctype and d.link_name == self.name: to_remove = d if to_remove: - link_doctype.remove(to_remove) + linked_doc.remove(to_remove) def has_customer(self): return frappe.db.get_value("Customer", {"lead_name": self.name}) From 493029195c81dd5317de23649486dd61aa8e8765 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Sun, 1 Aug 2021 19:47:42 +0530 Subject: [PATCH 08/54] fix: Additional salary processing --- .../doctype/salary_slip/salary_slip.py | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/erpnext/payroll/doctype/salary_slip/salary_slip.py b/erpnext/payroll/doctype/salary_slip/salary_slip.py index b39cef8bbd4..f0ca64fdf26 100644 --- a/erpnext/payroll/doctype/salary_slip/salary_slip.py +++ b/erpnext/payroll/doctype/salary_slip/salary_slip.py @@ -647,10 +647,13 @@ class SalarySlip(TransactionBase): continue if ( - (not d.additional_salary - and (not additional_salary or additional_salary.overwrite)) - or (additional_salary - and additional_salary.name == d.additional_salary) + ( + not d.additional_salary + and (not additional_salary or additional_salary.overwrite) + ) or ( + additional_salary + and additional_salary.name == d.additional_salary + ) ): component_row = d break @@ -679,8 +682,12 @@ class SalarySlip(TransactionBase): if additional_salary: component_row.is_recurring_additional_salary = is_recurring - component_row.default_amount = 0 - component_row.additional_amount = amount + if additional_salary.overwrite: + component_row.additional_amount = flt(flt(amount) - flt(component_row.get("default_amount", 0)), + component_row.precision("additional_amount")) + else: + component_row.default_amount = 0 + component_row.additional_amount = amount component_row.additional_salary = additional_salary.name component_row.deduct_full_tax_on_selected_payroll_date = \ additional_salary.deduct_full_tax_on_selected_payroll_date From e5d8ba65ca1c982ff4a8db0352e81939ae64665f Mon Sep 17 00:00:00 2001 From: Mohammed Yusuf Shaikh <49878143+mohammedyusufshaikh@users.noreply.github.com> Date: Wed, 4 Aug 2021 16:35:38 +0530 Subject: [PATCH 09/54] fix: trigger lost reason dialog when status is changed to lost (#26811) --- erpnext/crm/doctype/opportunity/opportunity.js | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/erpnext/crm/doctype/opportunity/opportunity.js b/erpnext/crm/doctype/opportunity/opportunity.js index 43e1b99f3ad..e9a7a95fc7d 100644 --- a/erpnext/crm/doctype/opportunity/opportunity.js +++ b/erpnext/crm/doctype/opportunity/opportunity.js @@ -53,6 +53,13 @@ frappe.ui.form.on("Opportunity", { frm.get_field("items").grid.set_multiple_add("item_code", "qty"); }, + status:function(frm){ + if (frm.doc.status == "Lost"){ + frm.trigger('set_as_lost_dialog'); + } + + }, + customer_address: function(frm, cdt, cdn) { erpnext.utils.get_address_display(frm, 'customer_address', 'address_display', false); }, @@ -91,11 +98,6 @@ frappe.ui.form.on("Opportunity", { frm.add_custom_button(__('Quotation'), cur_frm.cscript.create_quotation, __('Create')); - if(doc.status!=="Quotation") { - frm.add_custom_button(__('Lost'), () => { - frm.trigger('set_as_lost_dialog'); - }); - } } if(!frm.doc.__islocal && frm.perm[0].write && frm.doc.docstatus==0) { From 005291e6dd4088fca23fa36b85f1755510939823 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20de=20Ryckel?= Date: Wed, 4 Aug 2021 19:29:18 +0300 Subject: [PATCH 10/54] fix: typo in error message (#26816) --- erpnext/accounts/doctype/account/account.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/account/account.py b/erpnext/accounts/doctype/account/account.py index 1be2fbf5c81..f763df0852b 100644 --- a/erpnext/accounts/doctype/account/account.py +++ b/erpnext/accounts/doctype/account/account.py @@ -230,7 +230,7 @@ class Account(NestedSet): if self.check_gle_exists(): throw(_("Account with existing transaction can not be converted to group.")) elif self.account_type and not self.flags.exclude_account_type_check: - throw(_("Cannot covert to Group because Account Type is selected.")) + throw(_("Cannot convert to Group because Account Type is selected.")) else: self.is_group = 1 self.save() From 2732490cbefd0e8af56ae4e473e6a2fb9c72b1a3 Mon Sep 17 00:00:00 2001 From: Anupam Date: Thu, 5 Aug 2021 00:30:01 +0530 Subject: [PATCH 11/54] fix: review changes --- erpnext/crm/doctype/lead/lead.js | 47 +++++--------------- erpnext/crm/doctype/lead/lead.json | 70 +++++++++++++----------------- erpnext/crm/doctype/lead/lead.py | 18 +++++--- 3 files changed, 54 insertions(+), 81 deletions(-) diff --git a/erpnext/crm/doctype/lead/lead.js b/erpnext/crm/doctype/lead/lead.js index ad24a6e2225..10e3f7da6be 100644 --- a/erpnext/crm/doctype/lead/lead.js +++ b/erpnext/crm/doctype/lead/lead.js @@ -12,7 +12,8 @@ erpnext.LeadController = class LeadController extends frappe.ui.form.Controller 'Opportunity': this.make_opportunity }; - this.frm.toggle_reqd("lead_name", !this.frm.doc.organization_lead); + // For avoiding integration issues. + this.frm.set_df_property('first_name', 'reqd', true); } onload () { @@ -84,42 +85,16 @@ erpnext.LeadController = class LeadController extends frappe.ui.form.Controller } render_basic_info_html() { - let html=''; - if (cur_frm.doc.lead_owner) { - html += `
- Lead Owner -
-
- ${cur_frm.doc.lead_owner} -
` ; + if (cur_frm.doc.contact_date) { + let contact_date = frappe.datetime.obj_to_str(cur_frm.doc.contact_date) + let diff_days = frappe.datetime.get_day_diff(contact_date, frappe.datetime.get_today()); + let color = diff_days > 0 ? "orange" : "green"; + let message = diff_days > 0 ? __("Next Contact Date") : __("Last Contact Date"); + let html = `
+ ${message} : ${contact_date} +
` ; + cur_frm.dashboard.set_headline_alert(html); } - - if (cur_frm.doc.email_id) { - html += `
- Email -
-
- ${cur_frm.doc.email_id} -
` ; - } - - if (cur_frm.doc.mobile_no) { - html += `
- Mobile -
-
- ${cur_frm.doc.mobile_no} -
` ; - } - - html += `
- Status -
-
- ${cur_frm.doc.status} -
` ; - html = `
${html}
`; - cur_frm.dashboard.set_headline_alert(html); } }; diff --git a/erpnext/crm/doctype/lead/lead.json b/erpnext/crm/doctype/lead/lead.json index f9a500fa974..542977e689b 100644 --- a/erpnext/crm/doctype/lead/lead.json +++ b/erpnext/crm/doctype/lead/lead.json @@ -16,37 +16,36 @@ "middle_name", "last_name", "lead_name", - "email_id", - "mobile_no", - "phone", "col_break123", "status", "company_name", "designation", "gender", + "contact_details_section", + "email_id", + "mobile_no", + "whatsapp_no", + "column_break_16", + "phone", + "phone_ext", "additional_information_section", "no_of_employees", "industry", "market_segment", - "type", - "request_type", "column_break_22", - "whatsapp_no", "fax", "website", + "type", + "request_type", "address_section", "address_html", - "address_type", - "address_title", - "address_line1", - "address_line2", "city", + "pincode", "county", "column_break2", "contact_html", "state", "country", - "pincode", "section_break_12", "lead_owner", "ends_on", @@ -91,9 +90,9 @@ "fieldtype": "Data", "in_global_search": 1, "label": "Full Name", - "mandatory_depends_on": "eval: !(doc.company_name)", "oldfieldname": "lead_name", "oldfieldtype": "Data", + "read_only": 1, "search_index": 1 }, { @@ -102,7 +101,7 @@ "in_list_view": 1, "in_standard_filter": 1, "label": "Organization Name", - "mandatory_depends_on": "eval: !(doc.lead_name)", + "mandatory_depends_on": "eval: !(doc.first_name)", "oldfieldname": "company_name", "oldfieldtype": "Data" }, @@ -241,23 +240,6 @@ "label": "Address HTML", "read_only": 1 }, - { - "description": "Home, Work, etc.", - "fieldname": "address_title", - "fieldtype": "Data", - "label": "Address Title" - }, - { - "fieldname": "address_line1", - "fieldtype": "Data", - "label": "Address Line 1", - "mandatory_depends_on": "eval: doc.address_title && doc.address_type" - }, - { - "fieldname": "address_line2", - "fieldtype": "Data", - "label": "Address Line 2" - }, { "fieldname": "city", "fieldtype": "Data", @@ -406,13 +388,6 @@ "label": "Designation", "options": "Designation" }, - { - "default": "Billing", - "fieldname": "address_type", - "fieldtype": "Select", - "label": "Address Type", - "options": "Billing\nShipping\nOffice\nPersonal\nPlant\nPostal\nShop\nSubsidiary\nWarehouse\nCurrent\nPermanent\nOther" - }, { "fieldname": "language", "fieldtype": "Link", @@ -422,7 +397,8 @@ { "fieldname": "first_name", "fieldtype": "Data", - "label": "First Name" + "label": "First Name", + "mandatory_depends_on": "eval: !(doc.company_name)" }, { "fieldname": "middle_name", @@ -457,7 +433,7 @@ }, { "collapsible": 1, - "depends_on": "eval:!doc.__islocal", + "depends_on": "eval: !doc.__islocal", "fieldname": "address_section", "fieldtype": "Section Break", "label": "Address" @@ -475,13 +451,27 @@ "fieldname": "other_information_section", "fieldtype": "Section Break", "label": "Other Information" + }, + { + "fieldname": "contact_details_section", + "fieldtype": "Section Break", + "label": "Contact Details" + }, + { + "fieldname": "column_break_16", + "fieldtype": "Column Break" + }, + { + "fieldname": "phone_ext", + "fieldtype": "Data", + "label": "Phone Ext." } ], "icon": "fa fa-user", "idx": 5, "image_field": "image", "links": [], - "modified": "2021-07-28 00:20:37.768449", + "modified": "2021-08-04 00:24:57.208590", "modified_by": "Administrator", "module": "CRM", "name": "Lead", diff --git a/erpnext/crm/doctype/lead/lead.py b/erpnext/crm/doctype/lead/lead.py index f09a8145401..33fda89f28b 100644 --- a/erpnext/crm/doctype/lead/lead.py +++ b/erpnext/crm/doctype/lead/lead.py @@ -21,18 +21,24 @@ class Lead(SellingController): self.get("__onload").is_customer = customer load_address_and_contact(self) + def set_full_name(self): + self.lead_name = " ".join(filter(None, [self.first_name, self.middle_name, self.last_name])) + def validate(self): + self.set_full_name() self.set_lead_name() self.set_title() + self.set_status() + self.check_email_id_is_unique() + self.validate_email_id() + self.validate_contact_date() self._prev = frappe._dict({ "contact_date": frappe.db.get_value("Lead", self.name, "contact_date") if (not cint(self.is_new())) else None, "ends_on": frappe.db.get_value("Lead", self.name, "ends_on") if (not cint(self.is_new())) else None, "contact_by": frappe.db.get_value("Lead", self.name, "contact_by") if (not cint(self.is_new())) else None, }) - - self.set_status() - self.check_email_id_is_unique() - + + def validate_email_id(self): if self.email_id: if not self.flags.ignore_email_validation: validate_email_address(self.email_id, throw=True) @@ -46,6 +52,7 @@ class Lead(SellingController): if self.is_new() or not self.image: self.image = has_gravatar(self.email_id) + def validate_contact_date(self): if self.contact_date and getdate(self.contact_date) < getdate(nowdate()): frappe.throw(_("Next Contact Date cannot be in the past")) @@ -88,7 +95,7 @@ class Lead(SellingController): linked_doc = frappe.get_doc(link['parenttype'], link['parent']) if len(linked_doc.get('links')) == 1: - linked_doc.delete() + linked_doc.delete(ignore_permissions=True) else: to_remove = None for d in linked_doc.get('links'): @@ -96,6 +103,7 @@ class Lead(SellingController): to_remove = d if to_remove: linked_doc.remove(to_remove) + linked_doc.save(ignore_permissions=True) def has_customer(self): return frappe.db.get_value("Customer", {"lead_name": self.name}) From 884d8cf0653794af68b377e2f02ac60968a7ead1 Mon Sep 17 00:00:00 2001 From: Ganga Manoj Date: Thu, 5 Aug 2021 11:14:46 +0530 Subject: [PATCH 12/54] fix: Let all System Managers be able to delete Company transactions (#26815) --- .../transaction_deletion_record.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.json b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.json index 9313f955167..23e59472a6d 100644 --- a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.json +++ b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.json @@ -54,7 +54,7 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2021-05-08 23:13:48.049879", + "modified": "2021-08-04 20:15:59.071493", "modified_by": "Administrator", "module": "Setup", "name": "Transaction Deletion Record", @@ -70,6 +70,7 @@ "report": 1, "role": "System Manager", "share": 1, + "submit": 1, "write": 1 } ], From c35a526dd826e91e764194ad45a1b6ac52b98b2a Mon Sep 17 00:00:00 2001 From: Anupam Date: Thu, 5 Aug 2021 14:42:15 +0530 Subject: [PATCH 13/54] fix: adding test cases --- erpnext/crm/doctype/lead/lead.js | 6 ++-- erpnext/crm/doctype/lead/lead.py | 6 ++-- erpnext/crm/doctype/lead/test_lead.py | 49 +++++++++++++++++++++++++++ 3 files changed, 55 insertions(+), 6 deletions(-) diff --git a/erpnext/crm/doctype/lead/lead.js b/erpnext/crm/doctype/lead/lead.js index 10e3f7da6be..3363d8c0232 100644 --- a/erpnext/crm/doctype/lead/lead.js +++ b/erpnext/crm/doctype/lead/lead.js @@ -43,7 +43,7 @@ erpnext.LeadController = class LeadController extends frappe.ui.form.Controller if (!this.frm.is_new()) { frappe.contacts.render_address_and_contact(this.frm); - cur_frm.trigger('render_basic_info_html'); + cur_frm.trigger('render_contact_day_html'); } else { frappe.contacts.clear_address_and_contact(this.frm); } @@ -84,14 +84,14 @@ erpnext.LeadController = class LeadController extends frappe.ui.form.Controller } } - render_basic_info_html() { + render_contact_day_html() { if (cur_frm.doc.contact_date) { let contact_date = frappe.datetime.obj_to_str(cur_frm.doc.contact_date) let diff_days = frappe.datetime.get_day_diff(contact_date, frappe.datetime.get_today()); let color = diff_days > 0 ? "orange" : "green"; let message = diff_days > 0 ? __("Next Contact Date") : __("Last Contact Date"); let html = `
- ${message} : ${contact_date} + ${message} : ${frappe.datetime.global_date_format(contact_date)}
` ; cur_frm.dashboard.set_headline_alert(html); } diff --git a/erpnext/crm/doctype/lead/lead.py b/erpnext/crm/doctype/lead/lead.py index 33fda89f28b..49b682c12fd 100644 --- a/erpnext/crm/doctype/lead/lead.py +++ b/erpnext/crm/doctype/lead/lead.py @@ -21,9 +21,6 @@ class Lead(SellingController): self.get("__onload").is_customer = customer load_address_and_contact(self) - def set_full_name(self): - self.lead_name = " ".join(filter(None, [self.first_name, self.middle_name, self.last_name])) - def validate(self): self.set_full_name() self.set_lead_name() @@ -38,6 +35,9 @@ class Lead(SellingController): "contact_by": frappe.db.get_value("Lead", self.name, "contact_by") if (not cint(self.is_new())) else None, }) + def set_full_name(self): + self.lead_name = " ".join(filter(None, [self.first_name, self.middle_name, self.last_name])) + def validate_email_id(self): if self.email_id: if not self.flags.ignore_email_validation: diff --git a/erpnext/crm/doctype/lead/test_lead.py b/erpnext/crm/doctype/lead/test_lead.py index d428a453f9f..174b1c98bd4 100644 --- a/erpnext/crm/doctype/lead/test_lead.py +++ b/erpnext/crm/doctype/lead/test_lead.py @@ -4,6 +4,7 @@ from __future__ import unicode_literals import frappe +from frappe.utils import random_string import unittest test_records = frappe.get_test_records('Lead') @@ -32,3 +33,51 @@ class TestLead(unittest.TestCase): customer.company = "_Test Company" customer.customer_group = "_Test Customer Group" customer.insert() + + def test_create_lead_and_unlinking_dynamic_links(self): + lead_doc = make_lead(first_name = "Lorem", last_name="Ipsum") + lead_doc_1 = make_lead() + address = frappe.get_doc({ + "doctype": "Address", + "address_type": "Billing", + "city": "Mumbai", + "address_line1": "Vidya Vihar West", + "country": "India", + "links": [{ + "link_doctype": "Lead", + "link_name": lead_doc.name + }] + }).insert() + + address_1 = frappe.get_doc({ + "doctype": "Address", + "address_type": "Billing", + "address_line1": "Baner", + "city": "Pune", + "country": "India", + "links": [{ + "link_doctype": "Lead", + "link_name": lead_doc.name + }, + { + "link_doctype": "Lead", + "link_name": lead_doc_1.name + }] + }).insert() + + lead_doc.delete() + address_1.reload() + self.assertEqual(frappe.db.exists("Lead",lead_doc.name), None) + self.assertEqual(len(address_1.get('links')), 1) + +def make_lead(**args): + args = frappe._dict(args) + + lead_doc = frappe.get_doc({ + "doctype": "Lead", + "first_name": args.first_name or "Test", + "last_name": args.last_name or "Lead", + "email_id": args.email_id or "new_lead{}@example.com".format(random_string(5)), + }).insert() + + return lead_doc \ No newline at end of file From 1d90f7684e0fce8227fb566fa4110b96003d9ee5 Mon Sep 17 00:00:00 2001 From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Date: Thu, 5 Aug 2021 19:17:32 +0530 Subject: [PATCH 14/54] fix: Do not fetch fully return issued purchase receipts (#26809) --- erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js index 7562418fd2f..bda9f419a98 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js @@ -134,7 +134,7 @@ erpnext.accounts.PurchaseInvoice = class PurchaseInvoice extends erpnext.buying. }, get_query_filters: { docstatus: 1, - status: ["not in", ["Closed", "Completed"]], + status: ["not in", ["Closed", "Completed", "Return Issued"]], company: me.frm.doc.company, is_return: 0 } From e7fa2e582649edc141800645bc364bf3d74edf13 Mon Sep 17 00:00:00 2001 From: Saqib Date: Fri, 6 Aug 2021 11:03:57 +0530 Subject: [PATCH 15/54] fix(e-invoicing): cannot generate IRNs for standalone credit notes (#26824) --- erpnext/regional/india/e_invoice/utils.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py index f8a230d0b87..f4976138ac8 100644 --- a/erpnext/regional/india/e_invoice/utils.py +++ b/erpnext/regional/india/e_invoice/utils.py @@ -316,10 +316,6 @@ def get_payment_details(invoice): )) def get_return_doc_reference(invoice): - if not invoice.return_against: - frappe.throw(_('For generating IRN, reference to the original invoice is mandatory for a credit note. Please set {} field to generate e-invoice.') - .format(frappe.bold('Return Against')), title=_('Missing Field')) - invoice_date = frappe.db.get_value('Sales Invoice', invoice.return_against, 'posting_date') return frappe._dict(dict( invoice_name=invoice.return_against, invoice_date=format_date(invoice_date, 'dd/mm/yyyy') @@ -438,7 +434,7 @@ def make_einvoice(invoice): if invoice.is_pos and invoice.base_paid_amount: payment_details = get_payment_details(invoice) - if invoice.is_return: + if invoice.is_return and invoice.return_against: prev_doc_details = get_return_doc_reference(invoice) if invoice.transporter and not invoice.is_return: From 4723e18f9e3dec0846fd13240dd7594095b7cbc4 Mon Sep 17 00:00:00 2001 From: Anupam Date: Thu, 5 Aug 2021 19:44:00 +0530 Subject: [PATCH 16/54] fix: sider issue --- erpnext/crm/doctype/lead/lead.js | 2 +- erpnext/crm/doctype/lead/test_lead.py | 20 +++++++++++--------- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/erpnext/crm/doctype/lead/lead.js b/erpnext/crm/doctype/lead/lead.js index 3363d8c0232..97e6315eff5 100644 --- a/erpnext/crm/doctype/lead/lead.js +++ b/erpnext/crm/doctype/lead/lead.js @@ -86,7 +86,7 @@ erpnext.LeadController = class LeadController extends frappe.ui.form.Controller render_contact_day_html() { if (cur_frm.doc.contact_date) { - let contact_date = frappe.datetime.obj_to_str(cur_frm.doc.contact_date) + let contact_date = frappe.datetime.obj_to_str(cur_frm.doc.contact_date); let diff_days = frappe.datetime.get_day_diff(contact_date, frappe.datetime.get_today()); let color = diff_days > 0 ? "orange" : "green"; let message = diff_days > 0 ? __("Next Contact Date") : __("Last Contact Date"); diff --git a/erpnext/crm/doctype/lead/test_lead.py b/erpnext/crm/doctype/lead/test_lead.py index 174b1c98bd4..18e0692c5e9 100644 --- a/erpnext/crm/doctype/lead/test_lead.py +++ b/erpnext/crm/doctype/lead/test_lead.py @@ -37,7 +37,7 @@ class TestLead(unittest.TestCase): def test_create_lead_and_unlinking_dynamic_links(self): lead_doc = make_lead(first_name = "Lorem", last_name="Ipsum") lead_doc_1 = make_lead() - address = frappe.get_doc({ + frappe.get_doc({ "doctype": "Address", "address_type": "Billing", "city": "Mumbai", @@ -55,14 +55,16 @@ class TestLead(unittest.TestCase): "address_line1": "Baner", "city": "Pune", "country": "India", - "links": [{ - "link_doctype": "Lead", - "link_name": lead_doc.name - }, - { - "link_doctype": "Lead", - "link_name": lead_doc_1.name - }] + "links": [ + { + "link_doctype": "Lead", + "link_name": lead_doc.name + }, + { + "link_doctype": "Lead", + "link_name": lead_doc_1.name + } + ] }).insert() lead_doc.delete() From 01a0585ba0bbfe68b650a1090edb20f6071bfaf0 Mon Sep 17 00:00:00 2001 From: Anupam Date: Fri, 6 Aug 2021 11:47:07 +0530 Subject: [PATCH 17/54] fix: removing organization_lead traceback --- erpnext/crm/doctype/lead/lead.js | 2 +- erpnext/crm/doctype/lead/test_lead.py | 4 ++-- erpnext/crm/doctype/lead/test_records.json | 1 - erpnext/crm/doctype/lead/tests/test_lead_organization.js | 1 - erpnext/selling/doctype/customer/customer.py | 4 ++-- 5 files changed, 5 insertions(+), 7 deletions(-) diff --git a/erpnext/crm/doctype/lead/lead.js b/erpnext/crm/doctype/lead/lead.js index 97e6315eff5..75af9379900 100644 --- a/erpnext/crm/doctype/lead/lead.js +++ b/erpnext/crm/doctype/lead/lead.js @@ -71,7 +71,7 @@ erpnext.LeadController = class LeadController extends frappe.ui.form.Controller } company_name () { - if (this.frm.doc.organization_lead && !this.frm.doc.lead_name) { + if (!this.frm.doc.lead_name) { this.frm.set_value("lead_name", this.frm.doc.company_name); } } diff --git a/erpnext/crm/doctype/lead/test_lead.py b/erpnext/crm/doctype/lead/test_lead.py index 18e0692c5e9..cadc1a28a0e 100644 --- a/erpnext/crm/doctype/lead/test_lead.py +++ b/erpnext/crm/doctype/lead/test_lead.py @@ -35,7 +35,7 @@ class TestLead(unittest.TestCase): customer.insert() def test_create_lead_and_unlinking_dynamic_links(self): - lead_doc = make_lead(first_name = "Lorem", last_name="Ipsum") + lead_doc = make_lead(first_name = "Lorem", last_name="Ipsum", email_id="lorem_ipsum@example.com") lead_doc_1 = make_lead() frappe.get_doc({ "doctype": "Address", @@ -79,7 +79,7 @@ def make_lead(**args): "doctype": "Lead", "first_name": args.first_name or "Test", "last_name": args.last_name or "Lead", - "email_id": args.email_id or "new_lead{}@example.com".format(random_string(5)), + "email_id": args.email_id or "new_lead_{}@example.com".format(random_string(5)), }).insert() return lead_doc \ No newline at end of file diff --git a/erpnext/crm/doctype/lead/test_records.json b/erpnext/crm/doctype/lead/test_records.json index 39864e2e3e2..3158add0f23 100644 --- a/erpnext/crm/doctype/lead/test_records.json +++ b/erpnext/crm/doctype/lead/test_records.json @@ -27,7 +27,6 @@ { "doctype": "Lead", "email_id": "test_lead4@example.com", - "organization_lead": 1, "lead_name": "_Test Lead 4", "company_name": "_Test Lead 4", "status": "Open" diff --git a/erpnext/crm/doctype/lead/tests/test_lead_organization.js b/erpnext/crm/doctype/lead/tests/test_lead_organization.js index 43959356b14..7fb957370b0 100644 --- a/erpnext/crm/doctype/lead/tests/test_lead_organization.js +++ b/erpnext/crm/doctype/lead/tests/test_lead_organization.js @@ -9,7 +9,6 @@ QUnit.test("test: lead", function (assert) { () => frappe.set_route("List", "Lead"), () => frappe.new_doc("Lead"), () => frappe.timeout(1), - () => cur_frm.set_value("organization_lead", "1"), () => cur_frm.set_value("company_name", lead_name), () => cur_frm.save(), () => frappe.timeout(1), diff --git a/erpnext/selling/doctype/customer/customer.py b/erpnext/selling/doctype/customer/customer.py index 3b62081e24c..66edcd01886 100644 --- a/erpnext/selling/doctype/customer/customer.py +++ b/erpnext/selling/doctype/customer/customer.py @@ -176,12 +176,12 @@ class Customer(TransactionBase): address.append('links', dict(link_doctype='Customer', link_name=self.name)) address.save(ignore_permissions=self.flags.ignore_permissions) - lead = frappe.db.get_value("Lead", self.lead_name, ["organization_lead", "lead_name", "email_id", "phone", "mobile_no", "gender", "salutation"], as_dict=True) + lead = frappe.db.get_value("Lead", self.lead_name, ["company_name", "lead_name", "email_id", "phone", "mobile_no", "gender", "salutation"], as_dict=True) if not lead.lead_name: frappe.throw(_("Please mention the Lead Name in Lead {0}").format(self.lead_name)) - if lead.organization_lead: + if lead.company_name: contact_names = frappe.get_all('Dynamic Link', filters={ "parenttype":"Contact", "link_doctype":"Lead", From 59c971015abf1e90452da2e8752d47b34ed76ee4 Mon Sep 17 00:00:00 2001 From: Anupam Date: Fri, 6 Aug 2021 14:02:57 +0530 Subject: [PATCH 18/54] fix: test case --- erpnext/crm/doctype/lead/test_lead.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/crm/doctype/lead/test_lead.py b/erpnext/crm/doctype/lead/test_lead.py index cadc1a28a0e..d4886d35067 100644 --- a/erpnext/crm/doctype/lead/test_lead.py +++ b/erpnext/crm/doctype/lead/test_lead.py @@ -77,7 +77,7 @@ def make_lead(**args): lead_doc = frappe.get_doc({ "doctype": "Lead", - "first_name": args.first_name or "Test", + "first_name": args.first_name or "_Test", "last_name": args.last_name or "Lead", "email_id": args.email_id or "new_lead_{}@example.com".format(random_string(5)), }).insert() From b4e720f8ecd8ad5966fb8c7f399c1347f11a84d2 Mon Sep 17 00:00:00 2001 From: Anupam Date: Fri, 6 Aug 2021 14:39:13 +0530 Subject: [PATCH 19/54] fix: test case --- erpnext/crm/doctype/appointment/test_appointment.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/crm/doctype/appointment/test_appointment.py b/erpnext/crm/doctype/appointment/test_appointment.py index 50c98c59de6..bcab7bd7081 100644 --- a/erpnext/crm/doctype/appointment/test_appointment.py +++ b/erpnext/crm/doctype/appointment/test_appointment.py @@ -9,7 +9,7 @@ import datetime def create_test_lead(): - test_lead = frappe.db.exists({'doctype': 'Lead', 'lead_name': 'Test Lead'}) + test_lead = frappe.db.exists({'doctype': 'Lead', 'email':'test@example.com'}) if test_lead: return frappe.get_doc('Lead', test_lead[0][0]) test_lead = frappe.get_doc({ From f1141e775642e501f8547d01050001d2e020aac9 Mon Sep 17 00:00:00 2001 From: Saqib Date: Fri, 6 Aug 2021 17:37:17 +0530 Subject: [PATCH 20/54] fix: failing budget test due to project naming (#26834) --- erpnext/accounts/doctype/budget/test_budget.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/budget/test_budget.py b/erpnext/accounts/doctype/budget/test_budget.py index 603e21ea248..6c25f0024d5 100644 --- a/erpnext/accounts/doctype/budget/test_budget.py +++ b/erpnext/accounts/doctype/budget/test_budget.py @@ -249,7 +249,7 @@ class TestBudget(unittest.TestCase): def set_total_expense_zero(posting_date, budget_against_field=None, budget_against_CC=None): if budget_against_field == "project": - budget_against = "_Test Project" + budget_against = frappe.db.get_value("Project", {"project_name": "_Test Project"}) else: budget_against = budget_against_CC or "_Test Cost Center - _TC" @@ -275,7 +275,7 @@ def set_total_expense_zero(posting_date, budget_against_field=None, budget_again "_Test Bank - _TC", -existing_expense, "_Test Cost Center - _TC", posting_date=nowdate(), submit=True) elif budget_against_field == "project": make_journal_entry("_Test Account Cost for Goods Sold - _TC", - "_Test Bank - _TC", -existing_expense, "_Test Cost Center - _TC", submit=True, project="_Test Project", posting_date=nowdate()) + "_Test Bank - _TC", -existing_expense, "_Test Cost Center - _TC", submit=True, project=budget_against, posting_date=nowdate()) def make_budget(**args): args = frappe._dict(args) From 614336fe1d2f2d136809e29d394800994e7ae4c9 Mon Sep 17 00:00:00 2001 From: Ankush Date: Fri, 6 Aug 2021 19:36:21 +0530 Subject: [PATCH 21/54] test: use item that allows fractional UOM in test (#26837) --- .../stock/doctype/purchase_receipt/test_purchase_receipt.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index 82461cb843c..bb4a710a046 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -23,9 +23,7 @@ class TestPurchaseReceipt(unittest.TestCase): def test_reverse_purchase_receipt_sle(self): - frappe.db.set_value('UOM', '_Test UOM', 'must_be_whole_number', 0) - - pr = make_purchase_receipt(qty=0.5) + pr = make_purchase_receipt(qty=0.5, item_code="_Test Item Home Desktop 200") sl_entry = frappe.db.get_all("Stock Ledger Entry", {"voucher_type": "Purchase Receipt", "voucher_no": pr.name}, ['actual_qty']) @@ -41,8 +39,6 @@ class TestPurchaseReceipt(unittest.TestCase): self.assertEqual(len(sl_entry_cancelled), 2) self.assertEqual(sl_entry_cancelled[1].actual_qty, -0.5) - frappe.db.set_value('UOM', '_Test UOM', 'must_be_whole_number', 1) - def test_make_purchase_invoice(self): if not frappe.db.exists('Payment Terms Template', '_Test Payment Terms Template For Purchase Invoice'): frappe.get_doc({ From 27a29eb6bc67fc8dce482c0b38239929cf3fc6fb Mon Sep 17 00:00:00 2001 From: Ankush Date: Fri, 6 Aug 2021 21:34:44 +0530 Subject: [PATCH 22/54] test: fix pricelist tests (#26839) problem: exchange rate API is returning exchange rates for "_Test currency". These tests were relying on failure of that function. --- erpnext/stock/doctype/batch/test_batch.py | 9 ++++++--- erpnext/stock/doctype/item/test_item.py | 9 ++++++--- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/erpnext/stock/doctype/batch/test_batch.py b/erpnext/stock/doctype/batch/test_batch.py index cbd272df4b8..a85a0222b55 100644 --- a/erpnext/stock/doctype/batch/test_batch.py +++ b/erpnext/stock/doctype/batch/test_batch.py @@ -269,11 +269,14 @@ class TestBatch(unittest.TestCase): batch2 = create_batch('_Test Batch Price Item', 300, 1) batch3 = create_batch('_Test Batch Price Item', 400, 0) + company = "_Test Company with perpetual inventory" + currency = frappe.get_cached_value("Company", company, "default_currency") + args = frappe._dict({ "item_code": "_Test Batch Price Item", - "company": "_Test Company with perpetual inventory", + "company": company, "price_list": "_Test Price List", - "currency": "_Test Currency", + "currency": currency, "doctype": "Sales Invoice", "conversion_rate": 1, "price_list_currency": "_Test Currency", @@ -333,4 +336,4 @@ def make_new_batch(**args): except frappe.DuplicateEntryError: batch = frappe.get_doc("Batch", args.batch_id) - return batch \ No newline at end of file + return batch diff --git a/erpnext/stock/doctype/item/test_item.py b/erpnext/stock/doctype/item/test_item.py index 922049f1447..7a9985d7f07 100644 --- a/erpnext/stock/doctype/item/test_item.py +++ b/erpnext/stock/doctype/item/test_item.py @@ -83,14 +83,17 @@ class TestItem(unittest.TestCase): make_test_objects("Item Price") + company = "_Test Company" + currency = frappe.get_cached_value("Company", company, "default_currency") + details = get_item_details({ "item_code": "_Test Item", - "company": "_Test Company", + "company": company, "price_list": "_Test Price List", - "currency": "_Test Currency", + "currency": currency, "doctype": "Sales Order", "conversion_rate": 1, - "price_list_currency": "_Test Currency", + "price_list_currency": currency, "plc_conversion_rate": 1, "order_type": "Sales", "customer": "_Test Customer", From 07e65ab5895f166b6dceb38dfbea1cb90f6015b9 Mon Sep 17 00:00:00 2001 From: HENRY Florian Date: Mon, 9 Aug 2021 07:02:31 +0200 Subject: [PATCH 23/54] feat: add french address template (#26316) * add french address template Co-authored-by: Ankush --- erpnext/regional/address_template/templates/france.html | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 erpnext/regional/address_template/templates/france.html diff --git a/erpnext/regional/address_template/templates/france.html b/erpnext/regional/address_template/templates/france.html new file mode 100644 index 00000000000..752331eeec9 --- /dev/null +++ b/erpnext/regional/address_template/templates/france.html @@ -0,0 +1,5 @@ +{% if address_line1 %}{{ address_line1 }}{% endif -%} +{% if address_line2 %}
{{ address_line2 }}{% endif -%} +{% if pincode %}
{{ pincode }}{% endif -%} +{% if city %} {{ city }}{% endif -%} +{% if country %}
{{ country }}{% endif -%} From 16d4de5130097bc2dfdc7e073f1e13f0a22481d1 Mon Sep 17 00:00:00 2001 From: Ankush Date: Mon, 9 Aug 2021 10:41:24 +0530 Subject: [PATCH 24/54] fix: price list with 0 value are ignored (#26655) * fix: price list with 0 value are ignored Steps to reproduce: 1. Create 2 item price for two different supplier. One of them should be zero. 2. Create PO 3. Add supplier with non-zero price and add item. 4. change supplier. Price won't change. If price was non-zero it would've changed. Root cause: falsiness check instead of null value check is used for checking if price list value exists. 0 is evaluated as false. * refactor: make get_price_list_rate function pure --- erpnext/manufacturing/doctype/bom/bom.py | 5 ++--- erpnext/stock/get_item_details.py | 21 ++++++++++++--------- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py index 4e93fc67997..0ba85078ead 100644 --- a/erpnext/manufacturing/doctype/bom/bom.py +++ b/erpnext/manufacturing/doctype/bom/bom.py @@ -717,9 +717,8 @@ def get_bom_item_rate(args, bom_doc): "ignore_conversion_rate": True }) item_doc = frappe.get_cached_doc("Item", args.get("item_code")) - out = frappe._dict() - get_price_list_rate(bom_args, item_doc, out) - rate = out.price_list_rate + price_list_data = get_price_list_rate(bom_args, item_doc) + rate = price_list_data.price_list_rate return rate diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index 2ed7a04ba80..be8508a000b 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -74,8 +74,7 @@ def get_item_details(args, doc=None, for_validate=False, overwrite_warehouse=Tru update_party_blanket_order(args, out) - - get_price_list_rate(args, item, out) + out.update(get_price_list_rate(args, item)) if args.customer and cint(args.is_pos): out.update(get_pos_profile_item_details(args.company, args, update_data=True)) @@ -638,7 +637,10 @@ def get_default_supplier(args, item, item_group, brand): or item_group.get("default_supplier") or brand.get("default_supplier")) -def get_price_list_rate(args, item_doc, out): +def get_price_list_rate(args, item_doc, out=None): + if out is None: + out = frappe._dict() + meta = frappe.get_meta(args.parenttype or args.doctype) if meta.get_field("currency") or args.get('currency'): @@ -651,17 +653,17 @@ def get_price_list_rate(args, item_doc, out): if meta.get_field("currency"): validate_conversion_rate(args, meta) - price_list_rate = get_price_list_rate_for(args, item_doc.name) or 0 + price_list_rate = get_price_list_rate_for(args, item_doc.name) # variant - if not price_list_rate and item_doc.variant_of: + if price_list_rate is None and item_doc.variant_of: price_list_rate = get_price_list_rate_for(args, item_doc.variant_of) # insert in database - if not price_list_rate: + if price_list_rate is None: if args.price_list and args.rate: insert_item_price(args) - return {} + return out out.price_list_rate = flt(price_list_rate) * flt(args.plc_conversion_rate) \ / flt(args.conversion_rate) @@ -671,6 +673,8 @@ def get_price_list_rate(args, item_doc, out): out.update(get_last_purchase_details(item_doc.name, args.name, args.conversion_rate)) + return out + def insert_item_price(args): """Insert Item Price if Price List and Price List Rate are specified and currency is the same""" if frappe.db.get_value("Price List", args.price_list, "currency", cache=True) == args.currency \ @@ -1073,9 +1077,8 @@ def apply_price_list(args, as_doc=False): } def apply_price_list_on_item(args): - item_details = frappe._dict() item_doc = frappe.get_doc("Item", args.item_code) - get_price_list_rate(args, item_doc, item_details) + item_details = get_price_list_rate(args, item_doc) item_details.update(get_pricing_rule_for_item(args, item_details.price_list_rate)) From 7e0c57fa3fe62417ad3be75412e0c031d6486bb8 Mon Sep 17 00:00:00 2001 From: Ankush Date: Mon, 9 Aug 2021 10:46:29 +0530 Subject: [PATCH 25/54] fix: allow alternative items when using job card (#26724) --- erpnext/manufacturing/doctype/job_card/job_card.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/erpnext/manufacturing/doctype/job_card/job_card.py b/erpnext/manufacturing/doctype/job_card/job_card.py index 69c7f5c614b..66e2394b847 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.py +++ b/erpnext/manufacturing/doctype/job_card/job_card.py @@ -608,6 +608,11 @@ def make_stock_entry(source_name, target_doc=None): target.set_missing_values() target.set_stock_entry_type() + wo_allows_alternate_item = frappe.db.get_value("Work Order", target.work_order, "allow_alternative_item") + for item in target.items: + item.allow_alternative_item = int(wo_allows_alternate_item and + frappe.get_cached_value("Item", item.item_code, "allow_alternative_item")) + doclist = get_mapped_doc("Job Card", source_name, { "Job Card": { "doctype": "Stock Entry", @@ -698,4 +703,4 @@ def make_corrective_job_card(source_name, operation=None, for_operation=None, ta } }, target_doc, set_missing_values) - return doclist \ No newline at end of file + return doclist From ab8f0cab4d0a934c200665a6916ecb7de032519b Mon Sep 17 00:00:00 2001 From: Marica Date: Mon, 9 Aug 2021 12:24:04 +0530 Subject: [PATCH 26/54] fix: Faulty Gl Entry for Asset LCVs (#26803) * fix: Faulty Gl Entry for Asset LCVs - Both Gl entries were crediting in their respective accounts - Asset Account must be debited into * fix: Use keyword arguments instead of positional for better readability * chore: Test for LCV for draft asset created via Purchase Receipt --- .../test_landed_cost_voucher.py | 35 +++- .../purchase_receipt/purchase_receipt.py | 179 ++++++++++++++---- 2 files changed, 181 insertions(+), 33 deletions(-) diff --git a/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py b/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py index 32b08f60c4a..128a2ab62ff 100644 --- a/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py +++ b/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py @@ -11,6 +11,7 @@ from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt \ from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice from erpnext.accounts.doctype.account.test_account import get_inventory_account from erpnext.accounts.doctype.account.test_account import create_account +from erpnext.assets.doctype.asset.test_asset import create_asset_category, create_fixed_asset_item class TestLandedCostVoucher(unittest.TestCase): def test_landed_cost_voucher(self): @@ -250,6 +251,38 @@ class TestLandedCostVoucher(unittest.TestCase): self.assertEqual(entry.credit, amounts[0]) self.assertEqual(entry.credit_in_account_currency, amounts[1]) + def test_asset_lcv(self): + "Check if LCV for an Asset updates the Assets Gross Purchase Amount correctly." + if not frappe.db.exists("Asset Category", "Computers"): + create_asset_category() + + if not frappe.db.exists("Item", "Macbook Pro"): + create_fixed_asset_item() + + pr = make_purchase_receipt(item_code="Macbook Pro", qty=1, rate=50000) + + # check if draft asset was created + assets = frappe.db.get_all('Asset', filters={'purchase_receipt': pr.name}) + self.assertEqual(len(assets), 1) + + frappe.db.set_value("Company", pr.company, "capital_work_in_progress_account", "CWIP Account - _TC") + lcv = make_landed_cost_voucher( + company = pr.company, + receipt_document_type = "Purchase Receipt", + receipt_document=pr.name, + charges=80, + expense_account="Expenses Included In Valuation - _TC") + + lcv.save() + lcv.submit() + + # lcv updates amount in draft asset + self.assertEqual(frappe.db.get_value("Asset", assets[0].name, "gross_purchase_amount"), 50080) + + # tear down + lcv.cancel() + pr.cancel() + def make_landed_cost_voucher(** args): args = frappe._dict(args) ref_doc = frappe.get_doc(args.receipt_document_type, args.receipt_document) @@ -268,7 +301,7 @@ def make_landed_cost_voucher(** args): lcv.set("taxes", [{ "description": "Shipping Charges", - "expense_account": "Expenses Included In Valuation - TCP1", + "expense_account": args.expense_account or "Expenses Included In Valuation - TCP1", "amount": args.charges }]) diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index 899d7e8e666..bcf605288f6 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -290,8 +290,16 @@ class PurchaseReceipt(BuyingController): and warehouse_account_name == supplier_warehouse_account: continue - self.add_gl_entry(gl_entries, warehouse_account_name, d.cost_center, stock_value_diff, 0.0, remarks, - stock_rbnb, account_currency=warehouse_account_currency, item=d) + self.add_gl_entry( + gl_entries=gl_entries, + account=warehouse_account_name, + cost_center=d.cost_center, + debit=stock_value_diff, + credit=0.0, + remarks=remarks, + against_account=stock_rbnb, + account_currency=warehouse_account_currency, + item=d) # GL Entry for from warehouse or Stock Received but not billed # Intentionally passed negative debit amount to avoid incorrect GL Entry validation @@ -304,9 +312,17 @@ class PurchaseReceipt(BuyingController): account = warehouse_account[d.from_warehouse]['account'] \ if d.from_warehouse else stock_rbnb - self.add_gl_entry(gl_entries, account, d.cost_center, - -1 * flt(d.base_net_amount, d.precision("base_net_amount")), 0.0, remarks, warehouse_account_name, - debit_in_account_currency=-1 * credit_amount, account_currency=credit_currency, item=d) + self.add_gl_entry( + gl_entries=gl_entries, + account=account, + cost_center=d.cost_center, + debit=-1 * flt(d.base_net_amount, d.precision("base_net_amount")), + credit=0.0, + remarks=remarks, + against_account=warehouse_account_name, + debit_in_account_currency=-1 * credit_amount, + account_currency=credit_currency, + item=d) # check if the exchange rate has changed if d.get('purchase_invoice'): @@ -317,13 +333,29 @@ class PurchaseReceipt(BuyingController): discrepancy_caused_by_exchange_rate_difference = (d.qty * d.net_rate) * \ (exchange_rate_map[d.purchase_invoice] - self.conversion_rate) - self.add_gl_entry(gl_entries, account, d.cost_center, 0.0, discrepancy_caused_by_exchange_rate_difference, - remarks, self.supplier, debit_in_account_currency=-1 * discrepancy_caused_by_exchange_rate_difference, - account_currency=credit_currency, item=d) + self.add_gl_entry( + gl_entries=gl_entries, + account=account, + cost_center=d.cost_center, + debit=0.0, + credit=discrepancy_caused_by_exchange_rate_difference, + remarks=remarks, + against_account=self.supplier, + debit_in_account_currency=-1 * discrepancy_caused_by_exchange_rate_difference, + account_currency=credit_currency, + item=d) - self.add_gl_entry(gl_entries, self.get_company_default("exchange_gain_loss_account"), d.cost_center, discrepancy_caused_by_exchange_rate_difference, 0.0, - remarks, self.supplier, debit_in_account_currency=-1 * discrepancy_caused_by_exchange_rate_difference, - account_currency=credit_currency, item=d) + self.add_gl_entry( + gl_entries=gl_entries, + account=self.get_company_default("exchange_gain_loss_account"), + cost_center=d.cost_center, + debit=discrepancy_caused_by_exchange_rate_difference, + credit=0.0, + remarks=remarks, + against_account=self.supplier, + debit_in_account_currency=-1 * discrepancy_caused_by_exchange_rate_difference, + account_currency=credit_currency, + item=d) # Amount added through landed-cos-voucher if d.landed_cost_voucher_amount and landed_cost_entries: @@ -332,14 +364,31 @@ class PurchaseReceipt(BuyingController): credit_amount = (flt(amount["base_amount"]) if (amount["base_amount"] or account_currency!=self.company_currency) else flt(amount["amount"])) - self.add_gl_entry(gl_entries, account, d.cost_center, 0.0, credit_amount, remarks, - warehouse_account_name, credit_in_account_currency=flt(amount["amount"]), - account_currency=account_currency, project=d.project, item=d) + self.add_gl_entry( + gl_entries=gl_entries, + account=account, + cost_center=d.cost_center, + debit=0.0, + credit=credit_amount, + remarks=remarks, + against_account=warehouse_account_name, + credit_in_account_currency=flt(amount["amount"]), + account_currency=account_currency, + project=d.project, + item=d) # sub-contracting warehouse if flt(d.rm_supp_cost) and warehouse_account.get(self.supplier_warehouse): - self.add_gl_entry(gl_entries, supplier_warehouse_account, d.cost_center, 0.0, flt(d.rm_supp_cost), - remarks, warehouse_account_name, account_currency=supplier_warehouse_account_currency, item=d) + self.add_gl_entry( + gl_entries=gl_entries, + account=supplier_warehouse_account, + cost_center=d.cost_center, + debit=0.0, + credit=flt(d.rm_supp_cost), + remarks=remarks, + against_account=warehouse_account_name, + account_currency=supplier_warehouse_account_currency, + item=d) # divisional loss adjustment valuation_amount_as_per_doc = flt(d.base_net_amount, d.precision("base_net_amount")) + \ @@ -356,8 +405,17 @@ class PurchaseReceipt(BuyingController): cost_center = d.cost_center or frappe.get_cached_value("Company", self.company, "cost_center") - self.add_gl_entry(gl_entries, loss_account, cost_center, divisional_loss, 0.0, remarks, - warehouse_account_name, account_currency=credit_currency, project=d.project, item=d) + self.add_gl_entry( + gl_entries=gl_entries, + account=loss_account, + cost_center=cost_center, + debit=divisional_loss, + credit=0.0, + remarks=remarks, + against_account=warehouse_account_name, + account_currency=credit_currency, + project=d.project, + item=d) elif d.warehouse not in warehouse_with_no_account or \ d.rejected_warehouse not in warehouse_with_no_account: @@ -368,12 +426,30 @@ class PurchaseReceipt(BuyingController): debit_currency = get_account_currency(d.expense_account) remarks = self.get("remarks") or _("Accounting Entry for Service") - self.add_gl_entry(gl_entries, service_received_but_not_billed_account, d.cost_center, 0.0, d.amount, - remarks, d.expense_account, account_currency=credit_currency, project=d.project, + self.add_gl_entry( + gl_entries=gl_entries, + account=service_received_but_not_billed_account, + cost_center=d.cost_center, + debit=0.0, + credit=d.amount, + remarks=remarks, + against_account=d.expense_account, + account_currency=credit_currency, + project=d.project, voucher_detail_no=d.name, item=d) - self.add_gl_entry(gl_entries, d.expense_account, d.cost_center, d.amount, 0.0, remarks, service_received_but_not_billed_account, - account_currency = debit_currency, project=d.project, voucher_detail_no=d.name, item=d) + self.add_gl_entry( + gl_entries=gl_entries, + account=d.expense_account, + cost_center=d.cost_center, + debit=d.amount, + credit=0.0, + remarks=remarks, + against_account=service_received_but_not_billed_account, + account_currency = debit_currency, + project=d.project, + voucher_detail_no=d.name, + item=d) if warehouse_with_no_account: frappe.msgprint(_("No accounting entries for the following warehouses") + ": \n" + @@ -423,8 +499,15 @@ class PurchaseReceipt(BuyingController): applicable_amount = negative_expense_to_be_booked * (valuation_tax[tax.name] / total_valuation_amount) amount_including_divisional_loss -= applicable_amount - self.add_gl_entry(gl_entries, account, tax.cost_center, 0.0, applicable_amount, self.remarks or _("Accounting Entry for Stock"), - against_account, item=tax) + self.add_gl_entry( + gl_entries=gl_entries, + account=account, + cost_center=tax.cost_center, + debit=0.0, + credit=applicable_amount, + remarks=self.remarks or _("Accounting Entry for Stock"), + against_account=against_account, + item=tax) i += 1 @@ -477,15 +560,31 @@ class PurchaseReceipt(BuyingController): # debit cwip account debit_in_account_currency = (base_asset_amount if cwip_account_currency == self.company_currency else asset_amount) - self.add_gl_entry(gl_entries, cwip_account, item.cost_center, base_asset_amount, 0.0, remarks, - arbnb_account, debit_in_account_currency=debit_in_account_currency, item=item) + self.add_gl_entry( + gl_entries=gl_entries, + account=cwip_account, + cost_center=item.cost_center, + debit=base_asset_amount, + credit=0.0, + remarks=remarks, + against_account=arbnb_account, + debit_in_account_currency=debit_in_account_currency, + item=item) asset_rbnb_currency = get_account_currency(arbnb_account) # credit arbnb account credit_in_account_currency = (base_asset_amount if asset_rbnb_currency == self.company_currency else asset_amount) - self.add_gl_entry(gl_entries, arbnb_account, item.cost_center, 0.0, base_asset_amount, remarks, - cwip_account, credit_in_account_currency=credit_in_account_currency, item=item) + self.add_gl_entry( + gl_entries=gl_entries, + account=arbnb_account, + cost_center=item.cost_center, + debit=0.0, + credit=base_asset_amount, + remarks=remarks, + against_account=cwip_account, + credit_in_account_currency=credit_in_account_currency, + item=item) def add_lcv_gl_entries(self, item, gl_entries): expenses_included_in_asset_valuation = self.get_company_default("expenses_included_in_asset_valuation") @@ -498,11 +597,27 @@ class PurchaseReceipt(BuyingController): remarks = self.get("remarks") or _("Accounting Entry for Stock") - self.add_gl_entry(gl_entries, expenses_included_in_asset_valuation, item.cost_center, 0.0, flt(item.landed_cost_voucher_amount), - remarks, asset_account, project=item.project, item=item) + self.add_gl_entry( + gl_entries=gl_entries, + account=expenses_included_in_asset_valuation, + cost_center=item.cost_center, + debit=0.0, + credit=flt(item.landed_cost_voucher_amount), + remarks=remarks, + against_account=asset_account, + project=item.project, + item=item) - self.add_gl_entry(gl_entries, asset_account, item.cost_center, 0.0, flt(item.landed_cost_voucher_amount), - remarks, expenses_included_in_asset_valuation, project=item.project, item=item) + self.add_gl_entry( + gl_entries=gl_entries, + account=asset_account, + cost_center=item.cost_center, + debit=flt(item.landed_cost_voucher_amount), + credit=0.0, + remarks=remarks, + against_account=expenses_included_in_asset_valuation, + project=item.project, + item=item) def update_assets(self, item, valuation_rate): assets = frappe.db.get_all('Asset', From ef8539fd606b1fc00b0d5b3ea124b11df765e32e Mon Sep 17 00:00:00 2001 From: Anupam Kumar Date: Mon, 9 Aug 2021 12:38:14 +0530 Subject: [PATCH 27/54] refactor: Selling Settings form cleanup (#26841) --- .../selling_settings/selling_settings.json | 61 +++++++++++++++---- 1 file changed, 50 insertions(+), 11 deletions(-) diff --git a/erpnext/selling/doctype/selling_settings/selling_settings.json b/erpnext/selling/doctype/selling_settings/selling_settings.json index f01934b7e6b..717fd9b92e3 100644 --- a/erpnext/selling/doctype/selling_settings/selling_settings.json +++ b/erpnext/selling/doctype/selling_settings/selling_settings.json @@ -6,24 +6,31 @@ "document_type": "Other", "engine": "InnoDB", "field_order": [ + "customer_defaults_section", "cust_master_name", - "campaign_naming_by", "customer_group", + "column_break_4", "territory", - "selling_price_list", - "close_opportunity_after_days", + "crm_settings_section", + "campaign_naming_by", "default_valid_till", - "column_break_5", + "column_break_9", + "close_opportunity_after_days", + "item_price_settings_section", + "selling_price_list", + "column_break_15", + "maintain_same_sales_rate", + "maintain_same_rate_action", + "editable_price_list_rate", + "validate_selling_price", + "sales_transactions_settings_section", "so_required", "dn_required", "sales_update_frequency", - "maintain_same_sales_rate", - "maintain_same_rate_action", + "column_break_5", "role_to_override_stop_action", - "editable_price_list_rate", "allow_multiple_items", "allow_against_multiple_purchase_orders", - "validate_selling_price", "hide_tax_id" ], "fields": [ @@ -116,7 +123,7 @@ "default": "0", "fieldname": "allow_multiple_items", "fieldtype": "Check", - "label": "Allow Item to Be Added Multiple Times in a Transaction" + "label": "Allow Item to be Added Multiple Times in a Transaction" }, { "default": "0", @@ -142,7 +149,7 @@ "description": "Configure the action to stop the transaction or just warn if the same rate is not maintained.", "fieldname": "maintain_same_rate_action", "fieldtype": "Select", - "label": "Action If Same Rate is Not Maintained", + "label": "Action if Same Rate is Not Maintained", "mandatory_depends_on": "maintain_same_sales_rate", "options": "Stop\nWarn" }, @@ -152,6 +159,38 @@ "fieldtype": "Link", "label": "Role Allowed to Override Stop Action", "options": "Role" + }, + { + "fieldname": "customer_defaults_section", + "fieldtype": "Section Break", + "label": "Customer Defaults" + }, + { + "fieldname": "column_break_4", + "fieldtype": "Column Break" + }, + { + "fieldname": "crm_settings_section", + "fieldtype": "Section Break", + "label": "CRM Settings" + }, + { + "fieldname": "column_break_9", + "fieldtype": "Column Break" + }, + { + "fieldname": "item_price_settings_section", + "fieldtype": "Section Break", + "label": "Item Price Settings" + }, + { + "fieldname": "column_break_15", + "fieldtype": "Column Break" + }, + { + "fieldname": "sales_transactions_settings_section", + "fieldtype": "Section Break", + "label": "Transaction Settings" } ], "icon": "fa fa-cog", @@ -159,7 +198,7 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2021-04-04 20:18:12.814624", + "modified": "2021-08-06 22:25:50.119458", "modified_by": "Administrator", "module": "Selling", "name": "Selling Settings", From 07337d5c78c14119ae9fc5d010080a1db61d0bdd Mon Sep 17 00:00:00 2001 From: Ankush Date: Mon, 9 Aug 2021 12:38:40 +0530 Subject: [PATCH 28/54] fix: validate python expressions (#26835) --- erpnext/accounts/doctype/pricing_rule/pricing_rule.json | 5 +++-- .../item_quality_inspection_parameter.json | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/erpnext/accounts/doctype/pricing_rule/pricing_rule.json b/erpnext/accounts/doctype/pricing_rule/pricing_rule.json index 428989aa965..0be41b40635 100644 --- a/erpnext/accounts/doctype/pricing_rule/pricing_rule.json +++ b/erpnext/accounts/doctype/pricing_rule/pricing_rule.json @@ -558,7 +558,8 @@ "description": "Simple Python Expression, Example: territory != 'All Territories'", "fieldname": "condition", "fieldtype": "Code", - "label": "Condition" + "label": "Condition", + "options": "PythonExpression" }, { "fieldname": "column_break_42", @@ -575,7 +576,7 @@ "icon": "fa fa-gift", "idx": 1, "links": [], - "modified": "2021-03-06 22:01:24.840422", + "modified": "2021-08-06 15:10:04.219321", "modified_by": "Administrator", "module": "Accounts", "name": "Pricing Rule", diff --git a/erpnext/stock/doctype/item_quality_inspection_parameter/item_quality_inspection_parameter.json b/erpnext/stock/doctype/item_quality_inspection_parameter/item_quality_inspection_parameter.json index 9b1a47eed6c..5de45cbcad9 100644 --- a/erpnext/stock/doctype/item_quality_inspection_parameter/item_quality_inspection_parameter.json +++ b/erpnext/stock/doctype/item_quality_inspection_parameter/item_quality_inspection_parameter.json @@ -47,7 +47,8 @@ "description": "Simple Python formula applied on Reading fields.
Numeric eg. 1: reading_1 > 0.2 and reading_1 < 0.5
\nNumeric eg. 2: mean > 3.5 (mean of populated fields)
\nValue based eg.: reading_value in (\"A\", \"B\", \"C\")", "fieldname": "acceptance_formula", "fieldtype": "Code", - "label": "Acceptance Criteria Formula" + "label": "Acceptance Criteria Formula", + "options": "PythonExpression" }, { "default": "0", @@ -89,7 +90,7 @@ "idx": 1, "istable": 1, "links": [], - "modified": "2021-02-04 18:50:02.056173", + "modified": "2021-08-06 15:08:20.911338", "modified_by": "Administrator", "module": "Stock", "name": "Item Quality Inspection Parameter", From b0f3c0f3fd86ed6d75e0234075402362b1941681 Mon Sep 17 00:00:00 2001 From: harshpwctech <84438948+harshpwctech@users.noreply.github.com> Date: Mon, 9 Aug 2021 13:42:05 +0530 Subject: [PATCH 29/54] fix: JWT decoding error (#26624) --- erpnext/regional/india/e_invoice/utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py index f4976138ac8..4276946ebb3 100644 --- a/erpnext/regional/india/e_invoice/utils.py +++ b/erpnext/regional/india/e_invoice/utils.py @@ -928,7 +928,7 @@ class GSPConnector(): def set_einvoice_data(self, res): enc_signed_invoice = res.get('SignedInvoice') - dec_signed_invoice = jwt.decode(enc_signed_invoice, verify=False)['data'] + dec_signed_invoice = jwt.decode(enc_signed_invoice, options={"verify_signature": False})['data'] self.invoice.irn = res.get('Irn') self.invoice.ewaybill = res.get('EwbNo') @@ -1126,4 +1126,4 @@ def check_scheduler_status(): def job_already_enqueued(job_name): enqueued_jobs = [d.get("job_name") for d in get_info()] if job_name in enqueued_jobs: - return True \ No newline at end of file + return True From 9c04c297b8906978c5931223f1f56c75246e7709 Mon Sep 17 00:00:00 2001 From: Anupam Kumar Date: Mon, 9 Aug 2021 14:01:02 +0530 Subject: [PATCH 30/54] refactor: Accounts Settings form cleanup (#26842) --- .../doctype/accounts_settings/accounts_settings.json | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json index 703e93c0757..7bcc6ee53b3 100644 --- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json +++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json @@ -10,6 +10,7 @@ "accounts_transactions_settings_section", "over_billing_allowance", "role_allowed_to_over_bill", + "credit_controller", "make_payment_via_journal_entry", "column_break_11", "check_supplier_invoice_uniqueness", @@ -27,7 +28,6 @@ "acc_frozen_upto", "frozen_accounts_modifier", "column_break_4", - "credit_controller", "deferred_accounting_settings_section", "book_deferred_entries_based_on", "column_break_18", @@ -73,11 +73,10 @@ "fieldtype": "Column Break" }, { - "description": "This role is allowed to submit transactions that exceed credit limits", "fieldname": "credit_controller", "fieldtype": "Link", "in_list_view": 1, - "label": "Credit Controller", + "label": "Role allowed to bypass Credit Limit", "options": "Role" }, { @@ -268,7 +267,7 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2021-06-17 20:26:03.721202", + "modified": "2021-08-09 13:08:01.335416", "modified_by": "Administrator", "module": "Accounts", "name": "Accounts Settings", From b3bbebd27c73827d5a88ff47d0d16fb73dcf6de2 Mon Sep 17 00:00:00 2001 From: Afshan <33727827+AfshanKhan@users.noreply.github.com> Date: Mon, 9 Aug 2021 14:39:32 +0530 Subject: [PATCH 31/54] fix: add parameter for db insert while adding item tax (#26855) --- erpnext/controllers/accounts_controller.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index bf4ab1a848f..9e2e5968c6d 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -1507,7 +1507,7 @@ def set_child_tax_template_and_map(item, child_item, parent_doc): if child_item.get("item_tax_template"): child_item.item_tax_rate = get_item_tax_map(parent_doc.get('company'), child_item.item_tax_template, as_json=True) -def add_taxes_from_tax_template(child_item, parent_doc): +def add_taxes_from_tax_template(child_item, parent_doc, db_insert=True): add_taxes_from_item_tax_template = frappe.db.get_single_value("Accounts Settings", "add_taxes_from_item_tax_template") if child_item.get("item_tax_rate") and add_taxes_from_item_tax_template: @@ -1530,7 +1530,8 @@ def add_taxes_from_tax_template(child_item, parent_doc): "category" : "Total", "add_deduct_tax" : "Add" }) - tax_row.db_insert() + if db_insert: + tax_row.db_insert() def set_order_defaults(parent_doctype, parent_doctype_name, child_doctype, child_docname, trans_item): """ From 42bb77bf80a494e6ed4fae35e1e283dbadf2c581 Mon Sep 17 00:00:00 2001 From: Anupam Date: Mon, 9 Aug 2021 15:11:13 +0530 Subject: [PATCH 32/54] fix: creating contact on creation of lead --- erpnext/crm/doctype/lead/lead.py | 58 +++++++++++++++++++++++++++++--- 1 file changed, 53 insertions(+), 5 deletions(-) diff --git a/erpnext/crm/doctype/lead/lead.py b/erpnext/crm/doctype/lead/lead.py index 49b682c12fd..7f028cb3160 100644 --- a/erpnext/crm/doctype/lead/lead.py +++ b/erpnext/crm/doctype/lead/lead.py @@ -63,6 +63,22 @@ class Lead(SellingController): def on_update(self): self.add_calendar_event() + def before_insert(self): + self.contact_doc = self.create_contact() + + def after_insert(self): + self.update_links() + + def update_links(self): + # update contact links + if self.contact_doc: + self.contact_doc.append("links", { + "link_doctype": "Lead", + "link_name": self.name, + "link_title": self.lead_name + }) + self.contact_doc.save() + def add_calendar_event(self, opts=None, force=False): super(Lead, self).add_calendar_event({ "owner": self.lead_owner, @@ -116,7 +132,6 @@ class Lead(SellingController): "party_name": self.name, "docstatus": 1, "status": ["!=", "Lost"] - }) def has_lost_quotation(self): @@ -137,10 +152,43 @@ class Lead(SellingController): self.lead_name = self.email_id.split("@")[0] def set_title(self): - if self.company_name: - self.title = self.company_name - else: - self.title = self.lead_name + self.title = self.company_name or self.lead_name + + def create_contact(self): + if not self.lead_name: + self.set_full_name() + self.set_lead_name() + + contact = frappe.new_doc("Contact") + contact.update({ + "first_name": self.first_name or self.lead_name, + "last_name": self.last_name, + "salutation": self.salutation, + "gender": self.gender, + "designation": self.designation, + }) + + if self.email_id: + contact.append("email_ids", { + "email_id": self.email_id, + "is_primary": 1 + }) + + if self.phone: + contact.append("phone_nos", { + "phone": self.phone, + "is_primary_phone": 1 + }) + + if self.mobile_no: + contact.append("phone_nos", { + "phone": self.mobile_no, + "is_primary_mobile_no":1 + }) + + contact.insert(ignore_permissions=True) + + return contact @frappe.whitelist() def make_customer(source_name, target_doc=None): From 7ba8c821a1659185df524c466fd8906d44649876 Mon Sep 17 00:00:00 2001 From: Ankush Date: Mon, 9 Aug 2021 15:14:26 +0530 Subject: [PATCH 33/54] test: fix flaky purchase receipt test (#26859) --- .../purchase_receipt/test_purchase_receipt.py | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index bb4a710a046..d40d78184d5 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -324,21 +324,8 @@ class TestPurchaseReceipt(unittest.TestCase): pr1.submit() self.assertRaises(frappe.ValidationError, pr2.submit) + frappe.db.rollback() - pr1.cancel() - se.cancel() - se1.cancel() - se2.cancel() - se3.cancel() - po.reload() - pr2.load_from_db() - - if pr2.docstatus == 1 and frappe.db.get_value('Stock Ledger Entry', - {'voucher_no': pr2.name, 'is_cancelled': 0}, 'name'): - pr2.cancel() - - po.load_from_db() - po.cancel() def test_serial_no_supplier(self): pr = make_purchase_receipt(item_code="_Test Serialized Item With Series", qty=1) From db3ee314138ee40653c562d89d5aff0764a03ad7 Mon Sep 17 00:00:00 2001 From: Anupam Date: Mon, 9 Aug 2021 15:34:50 +0530 Subject: [PATCH 34/54] fix: test case --- erpnext/crm/doctype/appointment/test_appointment.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/crm/doctype/appointment/test_appointment.py b/erpnext/crm/doctype/appointment/test_appointment.py index bcab7bd7081..c7563e9d15b 100644 --- a/erpnext/crm/doctype/appointment/test_appointment.py +++ b/erpnext/crm/doctype/appointment/test_appointment.py @@ -9,7 +9,7 @@ import datetime def create_test_lead(): - test_lead = frappe.db.exists({'doctype': 'Lead', 'email':'test@example.com'}) + test_lead = frappe.db.exists({'doctype': 'Lead', 'email_id':'test@example.com'}) if test_lead: return frappe.get_doc('Lead', test_lead[0][0]) test_lead = frappe.get_doc({ From 458f7f119416d380a63fe69e8a2d74b25234c468 Mon Sep 17 00:00:00 2001 From: Ganga Manoj Date: Mon, 9 Aug 2021 17:16:48 +0530 Subject: [PATCH 35/54] patch: delete all orphaned tables docs (#26743) --- erpnext/patches.txt | 1 + .../patches/v13_0/delete_orphaned_tables.py | 69 +++++++++++++++++++ 2 files changed, 70 insertions(+) create mode 100644 erpnext/patches/v13_0/delete_orphaned_tables.py diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 04f05eda13d..30486242c1b 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -294,6 +294,7 @@ erpnext.patches.v13_0.update_level_in_bom #1234sswef erpnext.patches.v13_0.add_missing_fg_item_for_stock_entry erpnext.patches.v13_0.update_subscription_status_in_memberships erpnext.patches.v13_0.update_amt_in_work_order_required_items +erpnext.patches.v13_0.delete_orphaned_tables erpnext.patches.v13_0.update_export_type_for_gst erpnext.patches.v13_0.update_tds_check_field #3 erpnext.patches.v13_0.add_custom_field_for_south_africa diff --git a/erpnext/patches/v13_0/delete_orphaned_tables.py b/erpnext/patches/v13_0/delete_orphaned_tables.py new file mode 100644 index 00000000000..1d6eebe0398 --- /dev/null +++ b/erpnext/patches/v13_0/delete_orphaned_tables.py @@ -0,0 +1,69 @@ +# Copyright (c) 2019, Frappe and Contributors +# License: GNU General Public License v3. See license.txt + +from __future__ import unicode_literals + +import frappe +from frappe.utils import getdate + +def execute(): + frappe.reload_doc('setup', 'doctype', 'transaction_deletion_record') + + if has_deleted_company_transactions(): + child_doctypes = get_child_doctypes_whose_parent_doctypes_were_affected() + + for doctype in child_doctypes: + docs = frappe.get_all(doctype, fields=['name', 'parent', 'parenttype', 'creation']) + + for doc in docs: + if not frappe.db.exists(doc['parenttype'], doc['parent']): + frappe.db.delete(doctype, {'name': doc['name']}) + + elif check_for_new_doc_with_same_name_as_deleted_parent(doc): + frappe.db.delete(doctype, {'name': doc['name']}) + +def has_deleted_company_transactions(): + return frappe.get_all('Transaction Deletion Record') + +def get_child_doctypes_whose_parent_doctypes_were_affected(): + parent_doctypes = get_affected_doctypes() + child_doctypes = frappe.get_all( + 'DocField', + filters={ + 'fieldtype': 'Table', + 'parent':['in', parent_doctypes] + }, pluck='options') + + return child_doctypes + +def get_affected_doctypes(): + affected_doctypes = [] + tdr_docs = frappe.get_all('Transaction Deletion Record', pluck="name") + + for tdr in tdr_docs: + tdr_doc = frappe.get_doc("Transaction Deletion Record", tdr) + + for doctype in tdr_doc.doctypes: + if is_not_child_table(doctype.doctype_name): + affected_doctypes.append(doctype.doctype_name) + + affected_doctypes = remove_duplicate_items(affected_doctypes) + return affected_doctypes + +def is_not_child_table(doctype): + return not bool(frappe.get_value('DocType', doctype, 'istable')) + +def remove_duplicate_items(affected_doctypes): + return list(set(affected_doctypes)) + +def check_for_new_doc_with_same_name_as_deleted_parent(doc): + """ + Compares creation times of parent and child docs. + Since Transaction Deletion Record resets the naming series after deletion, + it allows the creation of new docs with the same names as the deleted ones. + """ + + parent_creation_time = frappe.db.get_value(doc['parenttype'], doc['parent'], 'creation') + child_creation_time = doc['creation'] + + return getdate(parent_creation_time) > getdate(child_creation_time) \ No newline at end of file From 08ae49cd118bf24beff9908c162ae2abcb544337 Mon Sep 17 00:00:00 2001 From: Anuja Pawar <60467153+Anuja-pawar@users.noreply.github.com> Date: Mon, 9 Aug 2021 17:58:52 +0530 Subject: [PATCH 36/54] fix(regional): add permissions for VAT Audit report (#26851) fix(regional): add permissions for VAT Audit report --- erpnext/patches.txt | 2 +- .../add_custom_field_for_south_africa.py | 3 ++- .../vat_audit_report/vat_audit_report.json | 12 +--------- .../vat_audit_report/vat_audit_report.py | 22 ++++++++++++++++--- erpnext/regional/south_africa/setup.py | 20 ++++++++++++++--- 5 files changed, 40 insertions(+), 19 deletions(-) diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 30486242c1b..35b248c08ed 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -297,5 +297,5 @@ erpnext.patches.v13_0.update_amt_in_work_order_required_items erpnext.patches.v13_0.delete_orphaned_tables erpnext.patches.v13_0.update_export_type_for_gst erpnext.patches.v13_0.update_tds_check_field #3 -erpnext.patches.v13_0.add_custom_field_for_south_africa +erpnext.patches.v13_0.add_custom_field_for_south_africa #2 erpnext.patches.v13_0.shopify_deprecation_warning diff --git a/erpnext/patches/v13_0/add_custom_field_for_south_africa.py b/erpnext/patches/v13_0/add_custom_field_for_south_africa.py index f882fdedf38..73ff1cad5b6 100644 --- a/erpnext/patches/v13_0/add_custom_field_for_south_africa.py +++ b/erpnext/patches/v13_0/add_custom_field_for_south_africa.py @@ -3,7 +3,7 @@ from __future__ import unicode_literals import frappe -from erpnext.regional.south_africa.setup import make_custom_fields +from erpnext.regional.south_africa.setup import make_custom_fields, add_permissions def execute(): company = frappe.get_all('Company', filters = {'country': 'South Africa'}) @@ -11,3 +11,4 @@ def execute(): return make_custom_fields() + add_permissions() diff --git a/erpnext/regional/report/vat_audit_report/vat_audit_report.json b/erpnext/regional/report/vat_audit_report/vat_audit_report.json index 8917e8f3c7e..a8be7bf64c0 100644 --- a/erpnext/regional/report/vat_audit_report/vat_audit_report.json +++ b/erpnext/regional/report/vat_audit_report/vat_audit_report.json @@ -18,15 +18,5 @@ "ref_doctype": "GL Entry", "report_name": "VAT Audit Report", "report_type": "Script Report", - "roles": [ - { - "role": "Accounts User" - }, - { - "role": "Accounts Manager" - }, - { - "role": "Auditor" - } - ] + "roles": [] } \ No newline at end of file diff --git a/erpnext/regional/report/vat_audit_report/vat_audit_report.py b/erpnext/regional/report/vat_audit_report/vat_audit_report.py index f45ba01dea5..292605ef13d 100644 --- a/erpnext/regional/report/vat_audit_report/vat_audit_report.py +++ b/erpnext/regional/report/vat_audit_report/vat_audit_report.py @@ -189,6 +189,8 @@ class VATAuditReport(object): row["posting_date"] = formatdate(inv_data.get("posting_date"), "dd-mm-yyyy") row["voucher_type"] = doctype row["voucher_no"] = inv + row["party_type"] = "Customer" if doctype == "Sales Invoice" else "Supplier" + row["party"] = inv_data.get("party") row["remarks"] = inv_data.get("remarks") row["gross_amount"]= item_details[0].get("gross_amount") row["tax_amount"]= item_details[0].get("tax_amount") @@ -226,6 +228,20 @@ class VATAuditReport(object): "options": "voucher_type", "width": 150 }, + { + "fieldname": "party_type", + "label": "Party Type", + "fieldtype": "Data", + "width": 140, + "hidden": 1 + }, + { + "fieldname": "party", + "label": "Party", + "fieldtype": "Dynamic Link", + "options": "party_type", + "width": 150 + }, { "fieldname": "remarks", "label": "Details", @@ -236,18 +252,18 @@ class VATAuditReport(object): "fieldname": "net_amount", "label": "Net Amount", "fieldtype": "Currency", - "width": 150 + "width": 130 }, { "fieldname": "tax_amount", "label": "Tax Amount", "fieldtype": "Currency", - "width": 150 + "width": 130 }, { "fieldname": "gross_amount", "label": "Gross Amount", "fieldtype": "Currency", - "width": 150 + "width": 130 }, ] diff --git a/erpnext/regional/south_africa/setup.py b/erpnext/regional/south_africa/setup.py index ac783b84884..4657ff833dd 100644 --- a/erpnext/regional/south_africa/setup.py +++ b/erpnext/regional/south_africa/setup.py @@ -3,11 +3,12 @@ from __future__ import unicode_literals -# import frappe, os, json +import frappe from frappe.custom.doctype.custom_field.custom_field import create_custom_fields from frappe.permissions import add_permission, update_permission_property def setup(company=None, patch=True): + make_custom_fields() add_permissions() def make_custom_fields(update=True): @@ -27,10 +28,23 @@ def make_custom_fields(update=True): create_custom_fields(custom_fields, update=update) def add_permissions(): - """Add Permissions for South Africa VAT Settings and South Africa VAT Account""" + """Add Permissions for South Africa VAT Settings and South Africa VAT Account + and VAT Audit Report""" for doctype in ('South Africa VAT Settings', 'South Africa VAT Account'): add_permission(doctype, 'All', 0) for role in ('Accounts Manager', 'Accounts User', 'System Manager'): add_permission(doctype, role, 0) update_permission_property(doctype, role, 0, 'write', 1) - update_permission_property(doctype, role, 0, 'create', 1) \ No newline at end of file + update_permission_property(doctype, role, 0, 'create', 1) + + + if not frappe.db.get_value('Custom Role', dict(report="VAT Audit Report")): + frappe.get_doc(dict( + doctype='Custom Role', + report="VAT Audit Report", + roles= [ + dict(role='Accounts User'), + dict(role='Accounts Manager'), + dict(role='Auditor') + ] + )).insert() \ No newline at end of file From 7e3dd9e8ee05611f5192a674a8ed6f87545ad389 Mon Sep 17 00:00:00 2001 From: Francisco Roldan Date: Mon, 9 Aug 2021 11:21:41 -0300 Subject: [PATCH 37/54] fix: depends_on in price list field --- .../accounts/doctype/subscription_plan/subscription_plan.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/subscription_plan/subscription_plan.json b/erpnext/accounts/doctype/subscription_plan/subscription_plan.json index 46ce0939e4f..771611a7860 100644 --- a/erpnext/accounts/doctype/subscription_plan/subscription_plan.json +++ b/erpnext/accounts/doctype/subscription_plan/subscription_plan.json @@ -78,7 +78,7 @@ "label": "Cost" }, { - "depends_on": "eval:doc.price_determination==\"Based on price list\"", + "depends_on": "eval:doc.price_determination==\"Based On Price List\"", "fieldname": "price_list", "fieldtype": "Link", "label": "Price List", @@ -147,7 +147,7 @@ } ], "links": [], - "modified": "2020-06-25 10:53:44.205774", + "modified": "2021-08-09 10:53:44.205774", "modified_by": "Administrator", "module": "Accounts", "name": "Subscription Plan", From 5e428f0447e2b0de930efe0f7924090e54543045 Mon Sep 17 00:00:00 2001 From: Ankush Date: Mon, 9 Aug 2021 22:15:49 +0530 Subject: [PATCH 38/54] perf(cache): fix active SLA doctype caching (#26861) If no SLA is configured then this query runs on EVERY `validate` call. Root cause: if not active SLA doctypes exist then `not []` evalutes to true and causes query to run again. --- .../service_level_agreement/service_level_agreement.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/erpnext/support/doctype/service_level_agreement/service_level_agreement.py b/erpnext/support/doctype/service_level_agreement/service_level_agreement.py index 8739cb2364c..cfa264feb54 100644 --- a/erpnext/support/doctype/service_level_agreement/service_level_agreement.py +++ b/erpnext/support/doctype/service_level_agreement/service_level_agreement.py @@ -281,15 +281,18 @@ def get_repeated(values): def get_documents_with_active_service_level_agreement(): - if not frappe.cache().hget("service_level_agreement", "active"): - set_documents_with_active_service_level_agreement() + sla_doctypes = frappe.cache().hget("service_level_agreement", "active") - return frappe.cache().hget("service_level_agreement", "active") + if sla_doctypes is None: + return set_documents_with_active_service_level_agreement() + + return sla_doctypes def set_documents_with_active_service_level_agreement(): active = [sla.document_type for sla in frappe.get_all("Service Level Agreement", fields=["document_type"])] frappe.cache().hset("service_level_agreement", "active", active) + return active def apply(doc, method=None): From 8db0f07115676c63c40c38d092ca352a794d0f67 Mon Sep 17 00:00:00 2001 From: Subin Tom <36098155+nemesis189@users.noreply.github.com> Date: Tue, 10 Aug 2021 11:43:43 +0530 Subject: [PATCH 39/54] fix: Missing method reset_issue_metrics added back to Issue doctype (#26573) --- erpnext/support/doctype/issue/issue.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/erpnext/support/doctype/issue/issue.py b/erpnext/support/doctype/issue/issue.py index b9a65b6749e..24dadd5a8c6 100644 --- a/erpnext/support/doctype/issue/issue.py +++ b/erpnext/support/doctype/issue/issue.py @@ -116,6 +116,10 @@ class Issue(Document): }).insert(ignore_permissions=True) return replicated_issue.name + + def reset_issue_metrics(self): + self.db_set("resolution_time", None) + self.db_set("user_resolution_time", None) def get_list_context(context=None): return { From 9ca2febd880c62be7f009b4c29ff456155636ca9 Mon Sep 17 00:00:00 2001 From: marination Date: Tue, 10 Aug 2021 13:10:46 +0530 Subject: [PATCH 40/54] fix: Set CWIP Account in company at the start to avoid flaky test --- .../doctype/landed_cost_voucher/test_landed_cost_voucher.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py b/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py index 128a2ab62ff..cb09d933801 100644 --- a/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py +++ b/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py @@ -253,6 +253,8 @@ class TestLandedCostVoucher(unittest.TestCase): def test_asset_lcv(self): "Check if LCV for an Asset updates the Assets Gross Purchase Amount correctly." + frappe.db.set_value("Company", "_Test Company", "capital_work_in_progress_account", "CWIP Account - _TC") + if not frappe.db.exists("Asset Category", "Computers"): create_asset_category() @@ -265,7 +267,6 @@ class TestLandedCostVoucher(unittest.TestCase): assets = frappe.db.get_all('Asset', filters={'purchase_receipt': pr.name}) self.assertEqual(len(assets), 1) - frappe.db.set_value("Company", pr.company, "capital_work_in_progress_account", "CWIP Account - _TC") lcv = make_landed_cost_voucher( company = pr.company, receipt_document_type = "Purchase Receipt", From 94beda65ca01724fb3d0d2b57190672083740c61 Mon Sep 17 00:00:00 2001 From: Anurag Mishra <32095923+Anurag810@users.noreply.github.com> Date: Tue, 10 Aug 2021 13:16:46 +0530 Subject: [PATCH 41/54] fix: updating lead status while customer creation (#26606) * fix: updating lead status while customer creation * fix: changes requested --- erpnext/selling/doctype/customer/customer.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/erpnext/selling/doctype/customer/customer.py b/erpnext/selling/doctype/customer/customer.py index 66edcd01886..abf146c43f4 100644 --- a/erpnext/selling/doctype/customer/customer.py +++ b/erpnext/selling/doctype/customer/customer.py @@ -157,9 +157,7 @@ class Customer(TransactionBase): '''If Customer created from Lead, update lead status to "Converted" update Customer link in Quotation, Opportunity''' if self.lead_name: - lead = frappe.get_doc('Lead', self.lead_name) - lead.status = 'Converted' - lead.save() + frappe.db.set_value("Lead", self.lead_name, "status", "Converted") def create_lead_address_contact(self): if self.lead_name: From 948386d85db8fea500443f055d89b75c837b8285 Mon Sep 17 00:00:00 2001 From: marination Date: Tue, 10 Aug 2021 14:00:55 +0530 Subject: [PATCH 42/54] test: Serial no sanitation --- .../stock/doctype/serial_no/test_serial_no.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/erpnext/stock/doctype/serial_no/test_serial_no.py b/erpnext/stock/doctype/serial_no/test_serial_no.py index cde7fe07c63..b9a58cf43e4 100644 --- a/erpnext/stock/doctype/serial_no/test_serial_no.py +++ b/erpnext/stock/doctype/serial_no/test_serial_no.py @@ -174,5 +174,23 @@ class TestSerialNo(unittest.TestCase): self.assertEqual(sn_doc.warehouse, "_Test Warehouse - _TC") self.assertEqual(sn_doc.purchase_document_no, se.name) + def test_serial_no_sanitation(self): + "Test if Serial No input is sanitised before entering the DB." + item_code = "_Test Serialized Item" + test_records = frappe.get_test_records('Stock Entry') + + se = frappe.copy_doc(test_records[0]) + se.get("items")[0].item_code = item_code + se.get("items")[0].qty = 3 + se.get("items")[0].serial_no = " _TS1, _TS2 , _TS3 " + se.get("items")[0].transfer_qty = 3 + se.set_stock_entry_type() + se.insert() + se.submit() + + self.assertEqual(se.get("items")[0].serial_no, "_TS1\n_TS2\n_TS3") + + frappe.db.rollback() + def tearDown(self): frappe.db.rollback() \ No newline at end of file From af2f5277d5352ab2fc710200e152f4f9c468439c Mon Sep 17 00:00:00 2001 From: Afshan <33727827+AfshanKhan@users.noreply.github.com> Date: Tue, 10 Aug 2021 14:16:16 +0530 Subject: [PATCH 43/54] fix: pos profile not mandatory for Sales Invoice (#26853) --- erpnext/accounts/doctype/sales_invoice/sales_invoice.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 88899130a24..4e4e5b5aa78 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -480,7 +480,7 @@ class SalesInvoice(SellingController): if not self.pos_profile: pos_profile = get_pos_profile(self.company) or {} if not pos_profile: - frappe.throw(_("No POS Profile found. Please create a New POS Profile first")) + return self.pos_profile = pos_profile.get('name') pos = {} From 2ae2580706774135813a158db989ca74d7dc69de Mon Sep 17 00:00:00 2001 From: Saqib Date: Tue, 10 Aug 2021 16:37:48 +0530 Subject: [PATCH 44/54] fix(asset): incorrect date difference calculation (#26793) --- erpnext/assets/doctype/asset/test_asset.py | 10 +++++----- erpnext/regional/india/utils.py | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py index 59fbe3b0301..e23a7154524 100644 --- a/erpnext/assets/doctype/asset/test_asset.py +++ b/erpnext/assets/doctype/asset/test_asset.py @@ -639,7 +639,7 @@ class TestAsset(unittest.TestCase): asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') asset = frappe.get_doc('Asset', asset_name) asset.calculate_depreciation = 1 - asset.available_for_use_date = '2030-06-12' + asset.available_for_use_date = '2030-07-12' asset.purchase_date = '2030-01-01' asset.append("finance_books", { "expected_value_after_useful_life": 1000, @@ -653,10 +653,10 @@ class TestAsset(unittest.TestCase): self.assertEqual(asset.finance_books[0].rate_of_depreciation, 50.0) expected_schedules = [ - ["2030-12-31", 1106.85, 1106.85], - ["2031-12-31", 3446.58, 4553.43], - ["2032-12-31", 1723.29, 6276.72], - ["2033-06-12", 723.28, 7000.00] + ["2030-12-31", 942.47, 942.47], + ["2031-12-31", 3528.77, 4471.24], + ["2032-12-31", 1764.38, 6235.62], + ["2033-07-12", 764.38, 7000.00] ] schedules = [[cstr(d.schedule_date), flt(d.depreciation_amount, 2), flt(d.accumulated_depreciation_amount, 2)] diff --git a/erpnext/regional/india/utils.py b/erpnext/regional/india/utils.py index 88c350ac899..a152797a5d4 100644 --- a/erpnext/regional/india/utils.py +++ b/erpnext/regional/india/utils.py @@ -851,7 +851,7 @@ def get_depreciation_amount(asset, depreciable_value, row): # if its the first depreciation if depreciable_value == asset.gross_purchase_amount: # as per IT act, if the asset is purchased in the 2nd half of fiscal year, then rate is divided by 2 - diff = date_diff(asset.available_for_use_date, row.depreciation_start_date) + diff = date_diff(row.depreciation_start_date, asset.available_for_use_date) if diff <= 180: rate_of_depreciation = rate_of_depreciation / 2 frappe.msgprint( From 84c4161c001b446dda25b8070d8da16203eec07d Mon Sep 17 00:00:00 2001 From: Saqib Date: Tue, 10 Aug 2021 16:46:44 +0530 Subject: [PATCH 45/54] fix(e-invoicing): cannot cancel invoice if IRN cancelled on portal (#26638) --- erpnext/patches.txt | 1 + .../v12_0/show_einvoice_irn_cancelled_field.py | 12 ++++++++++++ erpnext/regional/india/setup.py | 4 ++-- 3 files changed, 15 insertions(+), 2 deletions(-) create mode 100644 erpnext/patches/v12_0/show_einvoice_irn_cancelled_field.py diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 35b248c08ed..86356e30269 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -294,6 +294,7 @@ erpnext.patches.v13_0.update_level_in_bom #1234sswef erpnext.patches.v13_0.add_missing_fg_item_for_stock_entry erpnext.patches.v13_0.update_subscription_status_in_memberships erpnext.patches.v13_0.update_amt_in_work_order_required_items +erpnext.patches.v12_0.show_einvoice_irn_cancelled_field erpnext.patches.v13_0.delete_orphaned_tables erpnext.patches.v13_0.update_export_type_for_gst erpnext.patches.v13_0.update_tds_check_field #3 diff --git a/erpnext/patches/v12_0/show_einvoice_irn_cancelled_field.py b/erpnext/patches/v12_0/show_einvoice_irn_cancelled_field.py new file mode 100644 index 00000000000..2319c17b34c --- /dev/null +++ b/erpnext/patches/v12_0/show_einvoice_irn_cancelled_field.py @@ -0,0 +1,12 @@ +from __future__ import unicode_literals +import frappe + +def execute(): + company = frappe.get_all('Company', filters = {'country': 'India'}) + if not company: + return + + irn_cancelled_field = frappe.db.exists('Custom Field', {'dt': 'Sales Invoice', 'fieldname': 'irn_cancelled'}) + if irn_cancelled_field: + frappe.db.set_value('Custom Field', irn_cancelled_field, 'depends_on', 'eval: doc.irn') + frappe.db.set_value('Custom Field', irn_cancelled_field, 'read_only', 0) diff --git a/erpnext/regional/india/setup.py b/erpnext/regional/india/setup.py index e9372f9b8fc..b4f146ce57e 100644 --- a/erpnext/regional/india/setup.py +++ b/erpnext/regional/india/setup.py @@ -457,7 +457,7 @@ def make_custom_fields(update=True): depends_on='eval:in_list(["Registered Regular", "SEZ", "Overseas", "Deemed Export"], doc.gst_category) && doc.irn_cancelled === 0'), dict(fieldname='irn_cancelled', label='IRN Cancelled', fieldtype='Check', no_copy=1, print_hide=1, - depends_on='eval:(doc.irn_cancelled === 1)', read_only=1, allow_on_submit=1, insert_after='customer'), + depends_on='eval: doc.irn', allow_on_submit=1, insert_after='customer'), dict(fieldname='eway_bill_validity', label='E-Way Bill Validity', fieldtype='Data', no_copy=1, print_hide=1, depends_on='ewaybill', read_only=1, allow_on_submit=1, insert_after='ewaybill'), @@ -985,4 +985,4 @@ def create_gratuity_rule(): def update_accounts_settings_for_taxes(): if frappe.db.count('Company') == 1: - frappe.db.set_value('Accounts Settings', None, "add_taxes_from_item_tax_template", 0) \ No newline at end of file + frappe.db.set_value('Accounts Settings', None, "add_taxes_from_item_tax_template", 0) From 1cba77cfbd1e3684603795eabd52d6af4284167a Mon Sep 17 00:00:00 2001 From: Anuja Pawar <60467153+Anuja-pawar@users.noreply.github.com> Date: Tue, 10 Aug 2021 17:25:12 +0530 Subject: [PATCH 46/54] fix: Sales Return cancellation if linked with Payment Entry (#26551) --- .../purchase_invoice/purchase_invoice.py | 4 +- .../doctype/sales_invoice/sales_invoice.py | 46 +++++++++++++++++-- 2 files changed, 46 insertions(+), 4 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index 863c104dff4..25f42bce845 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -22,7 +22,7 @@ from erpnext.assets.doctype.asset.asset import get_asset_account, is_cwip_accoun from frappe.model.mapper import get_mapped_doc from six import iteritems from erpnext.accounts.doctype.sales_invoice.sales_invoice import validate_inter_company_party, update_linked_doc,\ - unlink_inter_company_doc + unlink_inter_company_doc, check_if_return_invoice_linked_with_payment_entry from erpnext.accounts.doctype.tax_withholding_category.tax_withholding_category import get_party_tax_withholding_details from erpnext.accounts.deferred_revenue import validate_service_stop_date from erpnext.stock.doctype.purchase_receipt.purchase_receipt import get_item_account_wise_additional_cost @@ -1014,6 +1014,8 @@ class PurchaseInvoice(BuyingController): }, item=self)) def on_cancel(self): + check_if_return_invoice_linked_with_payment_entry(self) + super(PurchaseInvoice, self).on_cancel() self.check_on_hold_or_closed_status() diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 4e4e5b5aa78..cecc1a18df4 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -290,6 +290,8 @@ class SalesInvoice(SellingController): self.update_time_sheet(None) def on_cancel(self): + check_if_return_invoice_linked_with_payment_entry(self) + super(SalesInvoice, self).on_cancel() self.check_sales_order_on_hold_or_close("sales_order") @@ -922,7 +924,7 @@ class SalesInvoice(SellingController): asset = frappe.get_doc("Asset", item.asset) else: frappe.throw(_( - "Row #{0}: You must select an Asset for Item {1}.").format(item.idx, item.item_name), + "Row #{0}: You must select an Asset for Item {1}.").format(item.idx, item.item_name), title=_("Missing Asset") ) if (len(asset.finance_books) > 1 and not item.finance_book @@ -944,7 +946,7 @@ class SalesInvoice(SellingController): gl_entries.append(self.get_gl_dict(gle, item=item)) self.set_asset_status(asset) - + else: # Do not book income for transfer within same company if not self.is_internal_transfer(): @@ -973,7 +975,7 @@ class SalesInvoice(SellingController): def set_asset_status(self, asset): if self.is_return: asset.set_status() - else: + else: asset.set_status("Sold" if self.docstatus==1 else None) def make_loyalty_point_redemption_gle(self, gl_entries): @@ -1941,3 +1943,41 @@ def create_dunning(source_name, target_doc=None): } }, target_doc, set_missing_values) return doclist + +def check_if_return_invoice_linked_with_payment_entry(self): + # If a Return invoice is linked with payment entry along with other invoices, + # the cancellation of the Return causes allocated amount to be greater than paid + + if not frappe.db.get_single_value('Accounts Settings', 'unlink_payment_on_cancellation_of_invoice'): + return + + payment_entries = [] + if self.is_return and self.return_against: + invoice = self.return_against + else: + invoice = self.name + + payment_entries = frappe.db.sql_list(""" + SELECT + t1.name + FROM + `tabPayment Entry` t1, `tabPayment Entry Reference` t2 + WHERE + t1.name = t2.parent + and t1.docstatus = 1 + and t2.reference_name = %s + and t2.allocated_amount < 0 + """, invoice) + + links_to_pe = [] + if payment_entries: + for payment in payment_entries: + payment_entry = frappe.get_doc("Payment Entry", payment) + if len(payment_entry.references) > 1: + links_to_pe.append(payment_entry.name) + if links_to_pe: + payment_entries_link = [get_link_to_form('Payment Entry', name, label=name) for name in links_to_pe] + message = _("Please cancel and amend the Payment Entry") + message += " " + ", ".join(payment_entries_link) + " " + message += _("to unallocate the amount of this Return Invoice before cancelling it.") + frappe.throw(message) From 0e337be06573d40ad1f239a38e95953761fe497d Mon Sep 17 00:00:00 2001 From: Anuja Pawar <60467153+Anuja-pawar@users.noreply.github.com> Date: Tue, 10 Aug 2021 17:26:35 +0530 Subject: [PATCH 47/54] fix: cost center & account validation in Sales/Purchase Taxes and Charges (#25929) --- .../sales_taxes_and_charges_template.py | 4 +++- .../test_records.json | 8 +++++++ erpnext/controllers/accounts_controller.py | 21 +++++++++++++++++++ erpnext/public/js/controllers/accounts.js | 8 +++++++ erpnext/setup/doctype/company/company.py | 6 +++--- .../setup_wizard/operations/taxes_setup.py | 3 ++- 6 files changed, 45 insertions(+), 5 deletions(-) diff --git a/erpnext/accounts/doctype/sales_taxes_and_charges_template/sales_taxes_and_charges_template.py b/erpnext/accounts/doctype/sales_taxes_and_charges_template/sales_taxes_and_charges_template.py index 52d19d54a8b..8f9eb6577b8 100644 --- a/erpnext/accounts/doctype/sales_taxes_and_charges_template/sales_taxes_and_charges_template.py +++ b/erpnext/accounts/doctype/sales_taxes_and_charges_template/sales_taxes_and_charges_template.py @@ -6,7 +6,7 @@ import frappe from frappe import _ from frappe.utils import flt from frappe.model.document import Document -from erpnext.controllers.accounts_controller import validate_taxes_and_charges, validate_inclusive_tax +from erpnext.controllers.accounts_controller import validate_taxes_and_charges, validate_inclusive_tax, validate_cost_center, validate_account_head class SalesTaxesandChargesTemplate(Document): def validate(self): @@ -39,6 +39,8 @@ def valdiate_taxes_and_charges_template(doc): for tax in doc.get("taxes"): validate_taxes_and_charges(tax) + validate_account_head(tax, doc) + validate_cost_center(tax, doc) validate_inclusive_tax(tax, doc) def validate_disabled(doc): diff --git a/erpnext/accounts/doctype/sales_taxes_and_charges_template/test_records.json b/erpnext/accounts/doctype/sales_taxes_and_charges_template/test_records.json index 2b737b98048..74db08d5b86 100644 --- a/erpnext/accounts/doctype/sales_taxes_and_charges_template/test_records.json +++ b/erpnext/accounts/doctype/sales_taxes_and_charges_template/test_records.json @@ -8,6 +8,7 @@ "charge_type": "On Net Total", "description": "VAT", "doctype": "Sales Taxes and Charges", + "cost_center": "Main - _TC", "parentfield": "taxes", "rate": 6 }, @@ -16,6 +17,7 @@ "charge_type": "On Net Total", "description": "Service Tax", "doctype": "Sales Taxes and Charges", + "cost_center": "Main - _TC", "parentfield": "taxes", "rate": 6.36 } @@ -114,6 +116,7 @@ "charge_type": "On Net Total", "description": "VAT", "doctype": "Sales Taxes and Charges", + "cost_center": "Main - _TC", "parentfield": "taxes", "rate": 12 }, @@ -122,6 +125,7 @@ "charge_type": "On Net Total", "description": "Service Tax", "doctype": "Sales Taxes and Charges", + "cost_center": "Main - _TC", "parentfield": "taxes", "rate": 4 } @@ -137,6 +141,7 @@ "charge_type": "On Net Total", "description": "VAT", "doctype": "Sales Taxes and Charges", + "cost_center": "Main - _TC", "parentfield": "taxes", "rate": 12 }, @@ -145,6 +150,7 @@ "charge_type": "On Net Total", "description": "Service Tax", "doctype": "Sales Taxes and Charges", + "cost_center": "Main - _TC", "parentfield": "taxes", "rate": 4 } @@ -160,6 +166,7 @@ "charge_type": "On Net Total", "description": "VAT", "doctype": "Sales Taxes and Charges", + "cost_center": "Main - _TC", "parentfield": "taxes", "rate": 12 }, @@ -168,6 +175,7 @@ "charge_type": "On Net Total", "description": "Service Tax", "doctype": "Sales Taxes and Charges", + "cost_center": "Main - _TC", "parentfield": "taxes", "rate": 4 } diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 9e2e5968c6d..bc8e4cea2d7 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -1288,6 +1288,27 @@ def validate_taxes_and_charges(tax): tax.rate = None +def validate_account_head(tax, doc): + company = frappe.get_cached_value('Account', + tax.account_head, 'company') + + if company != doc.company: + frappe.throw(_('Row {0}: Account {1} does not belong to Company {2}') + .format(tax.idx, frappe.bold(tax.account_head), frappe.bold(doc.company)), title=_('Invalid Account')) + + +def validate_cost_center(tax, doc): + if not tax.cost_center: + return + + company = frappe.get_cached_value('Cost Center', + tax.cost_center, 'company') + + if company != doc.company: + frappe.throw(_('Row {0}: Cost Center {1} does not belong to Company {2}') + .format(tax.idx, frappe.bold(tax.cost_center), frappe.bold(doc.company)), title=_('Invalid Cost Center')) + + def validate_inclusive_tax(tax, doc): def _on_previous_row_error(row_range): throw(_("To include tax in row {0} in Item rate, taxes in rows {1} must also be included").format(tax.idx, row_range)) diff --git a/erpnext/public/js/controllers/accounts.js b/erpnext/public/js/controllers/accounts.js index 7b997a11530..84c717676c7 100644 --- a/erpnext/public/js/controllers/accounts.js +++ b/erpnext/public/js/controllers/accounts.js @@ -31,6 +31,14 @@ frappe.ui.form.on(cur_frm.doctype, { } } }); + frm.set_query("cost_center", "taxes", function(doc) { + return { + filters: { + "company": doc.company, + "is_group": 0 + } + }; + }); } }, validate: function(frm) { diff --git a/erpnext/setup/doctype/company/company.py b/erpnext/setup/doctype/company/company.py index 8755125c810..95cbf5150cd 100644 --- a/erpnext/setup/doctype/company/company.py +++ b/erpnext/setup/doctype/company/company.py @@ -108,6 +108,9 @@ class Company(NestedSet): frappe.flags.country_change = True self.create_default_accounts() self.create_default_warehouses() + + if not frappe.db.get_value("Cost Center", {"is_group": 0, "company": self.name}): + self.create_default_cost_center() if frappe.flags.country_change: install_country_fixtures(self.name, self.country) @@ -117,9 +120,6 @@ class Company(NestedSet): from erpnext.setup.setup_wizard.operations.install_fixtures import install_post_company_fixtures install_post_company_fixtures(frappe._dict({'company_name': self.name})) - if not frappe.db.get_value("Cost Center", {"is_group": 0, "company": self.name}): - self.create_default_cost_center() - if not frappe.local.flags.ignore_chart_of_accounts: self.set_default_accounts() if self.default_cash_account: diff --git a/erpnext/setup/setup_wizard/operations/taxes_setup.py b/erpnext/setup/setup_wizard/operations/taxes_setup.py index cbb3dc881fb..bacada9f5cc 100644 --- a/erpnext/setup/setup_wizard/operations/taxes_setup.py +++ b/erpnext/setup/setup_wizard/operations/taxes_setup.py @@ -124,7 +124,8 @@ def make_taxes_and_charges_template(company_name, doctype, template): account_data = tax_row.get('account_head') tax_row_defaults = { 'category': 'Total', - 'charge_type': 'On Net Total' + 'charge_type': 'On Net Total', + 'cost_center': frappe.db.get_value('Company', company_name, 'cost_center') } if doctype == 'Purchase Taxes and Charges Template': From b614834efedbef572e0567828f0d9d82e81331ee Mon Sep 17 00:00:00 2001 From: Afshan <33727827+AfshanKhan@users.noreply.github.com> Date: Tue, 10 Aug 2021 21:33:58 +0530 Subject: [PATCH 48/54] fix: unseting of payment if no pos profile found (#26884) --- erpnext/controllers/taxes_and_totals.py | 6 +----- erpnext/public/js/controllers/taxes_and_totals.js | 2 -- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py index 099c7d43463..05edb2530c2 100644 --- a/erpnext/controllers/taxes_and_totals.py +++ b/erpnext/controllers/taxes_and_totals.py @@ -679,17 +679,13 @@ class calculate_taxes_and_totals(object): default_mode_of_payment = frappe.db.get_value('POS Payment Method', {'parent': self.doc.pos_profile, 'default': 1}, ['mode_of_payment'], as_dict=1) - self.doc.payments = [] - if default_mode_of_payment: + self.doc.payments = [] self.doc.append('payments', { 'mode_of_payment': default_mode_of_payment.mode_of_payment, 'amount': total_amount_to_pay, 'default': 1 }) - else: - self.doc.is_pos = 0 - self.doc.pos_profile = '' self.calculate_paid_amount() diff --git a/erpnext/public/js/controllers/taxes_and_totals.js b/erpnext/public/js/controllers/taxes_and_totals.js index a495a9b0c11..84697e0f008 100644 --- a/erpnext/public/js/controllers/taxes_and_totals.js +++ b/erpnext/public/js/controllers/taxes_and_totals.js @@ -751,8 +751,6 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments { this.frm.doc.payments.find(pay => { if (pay.default) { pay.amount = total_amount_to_pay; - } else { - pay.amount = 0.0 } }); this.frm.refresh_fields(); From 9152715f9049585e8d59967dd5c4fef15f04428c Mon Sep 17 00:00:00 2001 From: Ankush Date: Wed, 11 Aug 2021 11:17:50 +0530 Subject: [PATCH 49/54] perf: various minor perf fixes for ledger postings (#26775) * perf: only validate if voucher is journal entry * perf: optimize merge GLE - Order fields such that comparison will fail faster - Break out of loops if not matched * perf: don't try to match SLE if count mismatch * refactor: simplify initialize_previous_data * perf: use cache for fetching valuation_method These are set only once fields * refactor: simplify get_future_stock_vouchers * refactor: simplify get_voucherwise_gl_entries * perf: fetch only required fields for GL comparison `select *` fetches all fields, output of this function is only used for comparing. * perf: reorder conditions in PL cost center check * perf: reduce query while validating new gle * perf: use cache for validating warehouse props These properties don't change often, no need to query everytime. * perf: use cached stock settings to validate SLE * docs: update misleading docstring Co-authored-by: Marica --- erpnext/accounts/doctype/gl_entry/gl_entry.py | 22 ++++++----- erpnext/accounts/general_ledger.py | 25 ++++++++----- erpnext/accounts/utils.py | 37 +++++++++++++------ .../stock_ledger_entry/stock_ledger_entry.py | 4 +- erpnext/stock/stock_ledger.py | 12 +++--- erpnext/stock/utils.py | 8 ++-- 6 files changed, 65 insertions(+), 43 deletions(-) diff --git a/erpnext/accounts/doctype/gl_entry/gl_entry.py b/erpnext/accounts/doctype/gl_entry/gl_entry.py index 11465b711e3..0844995f296 100644 --- a/erpnext/accounts/doctype/gl_entry/gl_entry.py +++ b/erpnext/accounts/doctype/gl_entry/gl_entry.py @@ -58,8 +58,8 @@ class GLEntry(Document): if not self.get(k): frappe.throw(_("{0} is required").format(_(self.meta.get_label(k)))) - account_type = frappe.get_cached_value("Account", self.account, "account_type") if not (self.party_type and self.party): + account_type = frappe.get_cached_value("Account", self.account, "account_type") if account_type == "Receivable": frappe.throw(_("{0} {1}: Customer is required against Receivable account {2}") .format(self.voucher_type, self.voucher_no, self.account)) @@ -73,15 +73,19 @@ class GLEntry(Document): .format(self.voucher_type, self.voucher_no, self.account)) def pl_must_have_cost_center(self): - if frappe.get_cached_value("Account", self.account, "report_type") == "Profit and Loss": - if not self.cost_center and self.voucher_type != 'Period Closing Voucher': - msg = _("{0} {1}: Cost Center is required for 'Profit and Loss' account {2}.").format( - self.voucher_type, self.voucher_no, self.account) - msg += " " - msg += _("Please set the cost center field in {0} or setup a default Cost Center for the Company.").format( - self.voucher_type) + """Validate that profit and loss type account GL entries have a cost center.""" - frappe.throw(msg, title=_("Missing Cost Center")) + if self.cost_center or self.voucher_type == 'Period Closing Voucher': + return + + if frappe.get_cached_value("Account", self.account, "report_type") == "Profit and Loss": + msg = _("{0} {1}: Cost Center is required for 'Profit and Loss' account {2}.").format( + self.voucher_type, self.voucher_no, self.account) + msg += " " + msg += _("Please set the cost center field in {0} or setup a default Cost Center for the Company.").format( + self.voucher_type) + + frappe.throw(msg, title=_("Missing Cost Center")) def validate_dimensions_for_pl_and_bs(self): account_type = frappe.db.get_value("Account", self.account, "report_type") diff --git a/erpnext/accounts/general_ledger.py b/erpnext/accounts/general_ledger.py index 25d2cf10bd4..4c7c567b42a 100644 --- a/erpnext/accounts/general_ledger.py +++ b/erpnext/accounts/general_ledger.py @@ -100,8 +100,8 @@ def merge_similar_entries(gl_map, precision=None): return merged_gl_map def check_if_in_list(gle, gl_map, dimensions=None): - account_head_fieldnames = ['party_type', 'party', 'against_voucher', 'against_voucher_type', - 'cost_center', 'project', 'voucher_detail_no'] + account_head_fieldnames = ['voucher_detail_no', 'party', 'against_voucher', + 'cost_center', 'against_voucher_type', 'party_type', 'project'] if dimensions: account_head_fieldnames = account_head_fieldnames + dimensions @@ -110,10 +110,12 @@ def check_if_in_list(gle, gl_map, dimensions=None): same_head = True if e.account != gle.account: same_head = False + continue for fieldname in account_head_fieldnames: if cstr(e.get(fieldname)) != cstr(gle.get(fieldname)): same_head = False + break if same_head: return e @@ -143,16 +145,19 @@ def make_entry(args, adv_adj, update_outstanding, from_repost=False): validate_expense_against_budget(args) def validate_cwip_accounts(gl_map): - cwip_enabled = any(cint(ac.enable_cwip_accounting) for ac in frappe.db.get_all("Asset Category","enable_cwip_accounting")) + """Validate that CWIP account are not used in Journal Entry""" + if gl_map and gl_map[0].voucher_type != "Journal Entry": + return - if cwip_enabled and gl_map[0].voucher_type == "Journal Entry": - cwip_accounts = [d[0] for d in frappe.db.sql("""select name from tabAccount - where account_type = 'Capital Work in Progress' and is_group=0""")] + cwip_enabled = any(cint(ac.enable_cwip_accounting) for ac in frappe.db.get_all("Asset Category", "enable_cwip_accounting")) + if cwip_enabled: + cwip_accounts = [d[0] for d in frappe.db.sql("""select name from tabAccount + where account_type = 'Capital Work in Progress' and is_group=0""")] - for entry in gl_map: - if entry.account in cwip_accounts: - frappe.throw( - _("Account: {0} is capital Work in progress and can not be updated by Journal Entry").format(entry.account)) + for entry in gl_map: + if entry.account in cwip_accounts: + frappe.throw( + _("Account: {0} is capital Work in progress and can not be updated by Journal Entry").format(entry.account)) def round_off_debit_credit(gl_map): precision = get_field_precision(frappe.get_meta("GL Entry").get_field("debit"), diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index 9272bc4fcee..5b58e874fed 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -920,7 +920,6 @@ def repost_gle_for_stock_vouchers(stock_vouchers, posting_date, company=None, wa _delete_gl_entries(voucher_type, voucher_no) def get_future_stock_vouchers(posting_date, posting_time, for_warehouses=None, for_items=None, company=None): - future_stock_vouchers = [] values = [] condition = "" @@ -936,30 +935,46 @@ def get_future_stock_vouchers(posting_date, posting_time, for_warehouses=None, f condition += " and company = %s" values.append(company) - for d in frappe.db.sql("""select distinct sle.voucher_type, sle.voucher_no + future_stock_vouchers = frappe.db.sql("""select distinct sle.voucher_type, sle.voucher_no from `tabStock Ledger Entry` sle where timestamp(sle.posting_date, sle.posting_time) >= timestamp(%s, %s) and is_cancelled = 0 {condition} order by timestamp(sle.posting_date, sle.posting_time) asc, creation asc for update""".format(condition=condition), - tuple([posting_date, posting_time] + values), as_dict=True): - future_stock_vouchers.append([d.voucher_type, d.voucher_no]) + tuple([posting_date, posting_time] + values), as_dict=True) - return future_stock_vouchers + return [(d.voucher_type, d.voucher_no) for d in future_stock_vouchers] def get_voucherwise_gl_entries(future_stock_vouchers, posting_date): + """ Get voucherwise list of GL entries. + + Only fetches GLE fields required for comparing with new GLE. + Check compare_existing_and_expected_gle function below. + """ gl_entries = {} - if future_stock_vouchers: - for d in frappe.db.sql("""select * from `tabGL Entry` - where posting_date >= %s and voucher_no in (%s)""" % - ('%s', ', '.join(['%s']*len(future_stock_vouchers))), - tuple([posting_date] + [d[1] for d in future_stock_vouchers]), as_dict=1): - gl_entries.setdefault((d.voucher_type, d.voucher_no), []).append(d) + if not future_stock_vouchers: + return gl_entries + + voucher_nos = [d[1] for d in future_stock_vouchers] + + gles = frappe.db.sql(""" + select name, account, credit, debit, cost_center, project + from `tabGL Entry` + where + posting_date >= %s and voucher_no in (%s)""" % + ('%s', ', '.join(['%s'] * len(voucher_nos))), + tuple([posting_date] + voucher_nos), as_dict=1) + + for d in gles: + gl_entries.setdefault((d.voucher_type, d.voucher_no), []).append(d) return gl_entries def compare_existing_and_expected_gle(existing_gle, expected_gle, precision): + if len(existing_gle) != len(expected_gle): + return False + matched = True for entry in expected_gle: account_existed = False diff --git a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py index b4f458388b3..be1f00e37fa 100644 --- a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py +++ b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py @@ -55,8 +55,8 @@ class StockLedgerEntry(Document): "sum(actual_qty)") or 0 frappe.db.set_value("Batch", self.batch_no, "batch_qty", batch_qty) - #check for item quantity available in stock def actual_amt_check(self): + """Validate that qty at warehouse for selected batch is >=0""" if self.batch_no and not self.get("allow_negative_stock"): batch_bal_after_transaction = flt(frappe.db.sql("""select sum(actual_qty) from `tabStock Ledger Entry` @@ -107,7 +107,7 @@ class StockLedgerEntry(Document): self.stock_uom = item_det.stock_uom def check_stock_frozen_date(self): - stock_settings = frappe.get_doc('Stock Settings', 'Stock Settings') + stock_settings = frappe.get_cached_doc('Stock Settings') if stock_settings.stock_frozen_upto: if (getdate(self.posting_date) <= getdate(stock_settings.stock_frozen_upto) diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index f990ce06be5..eddd048c74e 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -279,15 +279,13 @@ class update_entries_after(object): } """ - self.data.setdefault(args.warehouse, frappe._dict()) - warehouse_dict = self.data[args.warehouse] previous_sle = get_previous_sle_of_current_voucher(args) - warehouse_dict.previous_sle = previous_sle - for key in ("qty_after_transaction", "valuation_rate", "stock_value"): - setattr(warehouse_dict, key, flt(previous_sle.get(key))) - - warehouse_dict.update({ + self.data[args.warehouse] = frappe._dict({ + "previous_sle": previous_sle, + "qty_after_transaction": flt(previous_sle.qty_after_transaction), + "valuation_rate": flt(previous_sle.valuation_rate), + "stock_value": flt(previous_sle.stock_value), "prev_stock_value": previous_sle.stock_value or 0.0, "stock_queue": json.loads(previous_sle.stock_queue or "[]"), "stock_value_difference": 0.0 diff --git a/erpnext/stock/utils.py b/erpnext/stock/utils.py index b57b2aa6b8f..9f6d0a8addd 100644 --- a/erpnext/stock/utils.py +++ b/erpnext/stock/utils.py @@ -224,7 +224,7 @@ def get_avg_purchase_rate(serial_nos): def get_valuation_method(item_code): """get valuation method from item or default""" - val_method = frappe.db.get_value('Item', item_code, 'valuation_method') + val_method = frappe.db.get_value('Item', item_code, 'valuation_method', cache=True) if not val_method: val_method = frappe.db.get_value("Stock Settings", None, "valuation_method") or "FIFO" return val_method @@ -275,17 +275,17 @@ def get_valid_serial_nos(sr_nos, qty=0, item_code=''): return valid_serial_nos def validate_warehouse_company(warehouse, company): - warehouse_company = frappe.db.get_value("Warehouse", warehouse, "company") + warehouse_company = frappe.db.get_value("Warehouse", warehouse, "company", cache=True) if warehouse_company and warehouse_company != company: frappe.throw(_("Warehouse {0} does not belong to company {1}").format(warehouse, company), InvalidWarehouseCompany) def is_group_warehouse(warehouse): - if frappe.db.get_value("Warehouse", warehouse, "is_group"): + if frappe.db.get_value("Warehouse", warehouse, "is_group", cache=True): frappe.throw(_("Group node warehouse is not allowed to select for transactions")) def validate_disabled_warehouse(warehouse): - if frappe.db.get_value("Warehouse", warehouse, "disabled"): + if frappe.db.get_value("Warehouse", warehouse, "disabled", cache=True): frappe.throw(_("Disabled Warehouse {0} cannot be used for this transaction.").format(get_link_to_form('Warehouse', warehouse))) def update_included_uom_in_report(columns, result, include_uom, conversion_factors): From e5b02216933a71c06457920e5f3a58a06ffa5bbc Mon Sep 17 00:00:00 2001 From: marination Date: Wed, 11 Aug 2021 13:02:49 +0530 Subject: [PATCH 50/54] test: fix attendance request tests - Use `frappe.db.get_value` instead of `get_doc` for asserting values - Get values after cancellation as reloading attendance doc breaks due to stale doc (primary key changed after cancel of attendance request) - rollback everything on tearDown --- .../test_attendance_request.py | 68 ++++++++++++++----- 1 file changed, 52 insertions(+), 16 deletions(-) diff --git a/erpnext/hr/doctype/attendance_request/test_attendance_request.py b/erpnext/hr/doctype/attendance_request/test_attendance_request.py index 3c42bd9fc35..0898476edf4 100644 --- a/erpnext/hr/doctype/attendance_request/test_attendance_request.py +++ b/erpnext/hr/doctype/attendance_request/test_attendance_request.py @@ -15,6 +15,9 @@ class TestAttendanceRequest(unittest.TestCase): for doctype in ["Attendance Request", "Attendance"]: frappe.db.sql("delete from `tab{doctype}`".format(doctype=doctype)) + def tearDown(self): + frappe.db.rollback() + def test_on_duty_attendance_request(self): today = nowdate() employee = get_employee() @@ -26,15 +29,33 @@ class TestAttendanceRequest(unittest.TestCase): attendance_request.company = "_Test Company" attendance_request.insert() attendance_request.submit() - attendance = frappe.get_doc('Attendance', { - 'employee': employee.name, - 'attendance_date': date(date.today().year, 1, 1), - 'docstatus': 1 - }) - self.assertEqual(attendance.status, 'Present') + + attendance = frappe.db.get_value( + "Attendance", + filters={ + "attendance_request": attendance_request.name, + "attendance_date": date(date.today().year, 1, 1) + }, + fieldname=["status", "docstatus"], + as_dict=True + ) + self.assertEqual(attendance.status, "Present") + self.assertEqual(attendance.docstatus, 1) + + # cancelling attendance request cancels linked attendances attendance_request.cancel() - attendance.reload() - self.assertEqual(attendance.docstatus, 2) + + # cancellation alters docname + # fetch attendance value again to avoid stale docname + attendance_docstatus = frappe.db.get_value( + "Attendance", + filters={ + "attendance_request": attendance_request.name, + "attendance_date": date(date.today().year, 1, 1) + }, + fieldname="docstatus" + ) + self.assertEqual(attendance_docstatus, 2) def test_work_from_home_attendance_request(self): today = nowdate() @@ -47,15 +68,30 @@ class TestAttendanceRequest(unittest.TestCase): attendance_request.company = "_Test Company" attendance_request.insert() attendance_request.submit() - attendance = frappe.get_doc('Attendance', { - 'employee': employee.name, - 'attendance_date': date(date.today().year, 1, 1), - 'docstatus': 1 - }) - self.assertEqual(attendance.status, 'Work From Home') + + attendance_status = frappe.db.get_value( + "Attendance", + filters={ + "attendance_request": attendance_request.name, + "attendance_date": date(date.today().year, 1, 1) + }, + fieldname="status" + ) + self.assertEqual(attendance_status, 'Work From Home') + attendance_request.cancel() - attendance.reload() - self.assertEqual(attendance.docstatus, 2) + + # cancellation alters docname + # fetch attendance value again to avoid stale docname + attendance_docstatus = frappe.db.get_value( + "Attendance", + filters={ + "attendance_request": attendance_request.name, + "attendance_date": date(date.today().year, 1, 1) + }, + fieldname="docstatus" + ) + self.assertEqual(attendance_docstatus, 2) def get_employee(): return frappe.get_doc("Employee", "_T-Employee-00001") From bca30d6101f539b4f68c536e83a82c1cd29b1bb7 Mon Sep 17 00:00:00 2001 From: marination Date: Wed, 11 Aug 2021 13:29:44 +0530 Subject: [PATCH 51/54] test: fix Shift Request test - Use `get_value` instead of `get_doc` - Remove unnecessary loop, only one shift assignment is made against a shift request - Get value after cancel again. Get doc is not reliable since primary key changed after cancel --- .../test_attendance_request.py | 2 ++ .../shift_request/test_shift_request.py | 33 ++++++++++++------- 2 files changed, 24 insertions(+), 11 deletions(-) diff --git a/erpnext/hr/doctype/attendance_request/test_attendance_request.py b/erpnext/hr/doctype/attendance_request/test_attendance_request.py index 0898476edf4..9e668aa72fb 100644 --- a/erpnext/hr/doctype/attendance_request/test_attendance_request.py +++ b/erpnext/hr/doctype/attendance_request/test_attendance_request.py @@ -19,6 +19,7 @@ class TestAttendanceRequest(unittest.TestCase): frappe.db.rollback() def test_on_duty_attendance_request(self): + "Test creation/updation of Attendace from Attendance Request, on duty." today = nowdate() employee = get_employee() attendance_request = frappe.new_doc("Attendance Request") @@ -58,6 +59,7 @@ class TestAttendanceRequest(unittest.TestCase): self.assertEqual(attendance_docstatus, 2) def test_work_from_home_attendance_request(self): + "Test creation/updation of Attendace from Attendance Request, work from home." today = nowdate() employee = get_employee() attendance_request = frappe.new_doc("Attendance Request") diff --git a/erpnext/hr/doctype/shift_request/test_shift_request.py b/erpnext/hr/doctype/shift_request/test_shift_request.py index 9c0d8e31985..3525540cdfd 100644 --- a/erpnext/hr/doctype/shift_request/test_shift_request.py +++ b/erpnext/hr/doctype/shift_request/test_shift_request.py @@ -15,24 +15,35 @@ class TestShiftRequest(unittest.TestCase): for doctype in ["Shift Request", "Shift Assignment"]: frappe.db.sql("delete from `tab{doctype}`".format(doctype=doctype)) + def tearDown(self): + frappe.db.rollback() + def test_make_shift_request(self): + "Test creation/updation of Shift Assignment from Shift Request." department = frappe.get_value("Employee", "_T-Employee-00001", 'department') set_shift_approver(department) approver = frappe.db.sql("""select approver from `tabDepartment Approver` where parent= %s and parentfield = 'shift_request_approver'""", (department))[0][0] shift_request = make_shift_request(approver) - shift_assignments = frappe.db.sql(''' - SELECT shift_request, employee - FROM `tabShift Assignment` - WHERE shift_request = '{0}' - '''.format(shift_request.name), as_dict=1) - for d in shift_assignments: - employee = d.get('employee') - self.assertEqual(shift_request.employee, employee) - shift_request.cancel() - shift_assignment_doc = frappe.get_doc("Shift Assignment", {"shift_request": d.get('shift_request')}) - self.assertEqual(shift_assignment_doc.docstatus, 2) + # Only one shift assignment is created against a shift request + shift_assignment = frappe.db.get_value( + "Shift Assignment", + filters={"shift_request": shift_request.name}, + fieldname=["employee", "docstatus"], + as_dict=True + ) + self.assertEqual(shift_request.employee, shift_assignment.employee) + self.assertEqual(shift_assignment.docstatus, 1) + + shift_request.cancel() + + shift_assignment_docstatus = frappe.db.get_value( + "Shift Assignment", + filters={"shift_request": shift_request.name}, + fieldname="docstatus" + ) + self.assertEqual(shift_assignment_docstatus, 2) def test_shift_request_approver_perms(self): employee = frappe.get_doc("Employee", "_T-Employee-00001") From 8f1a3aef2e3f14e179f4ac91718e9f376b34198c Mon Sep 17 00:00:00 2001 From: marination Date: Wed, 11 Aug 2021 15:17:06 +0530 Subject: [PATCH 52/54] test: fix POS Closing Entry Test - Separated into two tests, one checks if SI cancelling is blocked, the other checks PCE cancel impact - This is done because after cancel via assertRaises, damage done by cancel still exists or is partially comitted - Dont use this partially cancelled doc for any assertions further, end test at exception assertion - Use `get_value` to check SI docstatus, as its primary key changes after cancel --- .../test_pos_closing_entry.py | 43 +++++++++++++++++-- 1 file changed, 39 insertions(+), 4 deletions(-) diff --git a/erpnext/accounts/doctype/pos_closing_entry/test_pos_closing_entry.py b/erpnext/accounts/doctype/pos_closing_entry/test_pos_closing_entry.py index b596c0cf25a..0265f43476f 100644 --- a/erpnext/accounts/doctype/pos_closing_entry/test_pos_closing_entry.py +++ b/erpnext/accounts/doctype/pos_closing_entry/test_pos_closing_entry.py @@ -19,6 +19,7 @@ class TestPOSClosingEntry(unittest.TestCase): def tearDown(self): frappe.set_user("Administrator") frappe.db.sql("delete from `tabPOS Profile`") + frappe.db.rollback() def test_pos_closing_entry(self): test_user, pos_profile = init_user_and_profile() @@ -50,7 +51,8 @@ class TestPOSClosingEntry(unittest.TestCase): self.assertEqual(pcv_doc.total_quantity, 2) self.assertEqual(pcv_doc.net_total, 6700) - def test_cancelling_of_pos_closing_entry(self): + def test_cancelling_of_consolidated_sales_invoice(self): + "Check if cancelling consolidated Sales Invoice with submitted POS Closing Entry is blocked." test_user, pos_profile = init_user_and_profile() opening_entry = create_opening_entry(pos_profile, test_user.name) @@ -83,13 +85,46 @@ class TestPOSClosingEntry(unittest.TestCase): si_doc = frappe.get_doc("Sales Invoice", pos_inv1.consolidated_invoice) self.assertRaises(frappe.ValidationError, si_doc.cancel) + def test_cancelling_of_pos_closing_entry(self): + "Check impact of cancelling POS Closing Entry." + test_user, pos_profile = init_user_and_profile() + opening_entry = create_opening_entry(pos_profile, test_user.name) + + pos_inv1 = create_pos_invoice(rate=3500, do_not_submit=1) + pos_inv1.append('payments', { + 'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 3500 + }) + pos_inv1.submit() + + pos_inv2 = create_pos_invoice(rate=3200, do_not_submit=1) + pos_inv2.append('payments', { + 'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 3200 + }) + pos_inv2.submit() + + pcv_doc = make_closing_entry_from_opening(opening_entry) + payment = pcv_doc.payment_reconciliation[0] + + for d in pcv_doc.payment_reconciliation: + if d.mode_of_payment == 'Cash': + d.closing_amount = 6700 + + pcv_doc.submit() + + pos_inv1.load_from_db() + si_name = pos_inv1.consolidated_invoice + pcv_doc.load_from_db() pcv_doc.cancel() - si_doc.load_from_db() pos_inv1.load_from_db() - self.assertEqual(si_doc.docstatus, 2) - self.assertEqual(pos_inv1.status, 'Paid') + # After POS Closing Entry cancel, SI doc gets cancelled, unlinked and renamed + # There's no reference doc to fetch SI with new cancelled name + # which is why we are hardcoding suffix + si_docstatus = frappe.db.get_value("Sales Invoice", si_name+"-CANC-0", "docstatus") + self.assertEqual(si_docstatus, 2) + self.assertEqual(pos_inv1.status, 'Paid') + self.assertIsNone(pos_inv1.consolidated_invoice) def init_user_and_profile(**args): user = 'test@example.com' From 867939fcae12c689c145c0d8610a469508f1b900 Mon Sep 17 00:00:00 2001 From: marination Date: Wed, 11 Aug 2021 16:40:07 +0530 Subject: [PATCH 53/54] test: fixed asset movement tests - set cwip account in company to avoid value missing - removed unused statement - removed trailing spaces --- .../test_pos_closing_entry.py | 1 - .../asset_movement/test_asset_movement.py | 19 ++++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/erpnext/accounts/doctype/pos_closing_entry/test_pos_closing_entry.py b/erpnext/accounts/doctype/pos_closing_entry/test_pos_closing_entry.py index 0265f43476f..c585f310b16 100644 --- a/erpnext/accounts/doctype/pos_closing_entry/test_pos_closing_entry.py +++ b/erpnext/accounts/doctype/pos_closing_entry/test_pos_closing_entry.py @@ -103,7 +103,6 @@ class TestPOSClosingEntry(unittest.TestCase): pos_inv2.submit() pcv_doc = make_closing_entry_from_opening(opening_entry) - payment = pcv_doc.payment_reconciliation[0] for d in pcv_doc.payment_reconciliation: if d.mode_of_payment == 'Cash': diff --git a/erpnext/assets/doctype/asset_movement/test_asset_movement.py b/erpnext/assets/doctype/asset_movement/test_asset_movement.py index cddee5fa0f1..985bca9d0de 100644 --- a/erpnext/assets/doctype/asset_movement/test_asset_movement.py +++ b/erpnext/assets/doctype/asset_movement/test_asset_movement.py @@ -15,6 +15,7 @@ from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_pu class TestAssetMovement(unittest.TestCase): def setUp(self): + frappe.db.set_value("Company", "_Test Company", "capital_work_in_progress_account", "CWIP Account - _TC") create_asset_data() make_location() @@ -45,12 +46,12 @@ class TestAssetMovement(unittest.TestCase): 'location_name': 'Test Location 2' }).insert() - movement1 = create_asset_movement(purpose = 'Transfer', company = asset.company, + movement1 = create_asset_movement(purpose = 'Transfer', company = asset.company, assets = [{ 'asset': asset.name , 'source_location': 'Test Location', 'target_location': 'Test Location 2'}], reference_doctype = 'Purchase Receipt', reference_name = pr.name) self.assertEqual(frappe.db.get_value("Asset", asset.name, "location"), "Test Location 2") - movement2 = create_asset_movement(purpose = 'Transfer', company = asset.company, + movement2 = create_asset_movement(purpose = 'Transfer', company = asset.company, assets = [{ 'asset': asset.name , 'source_location': 'Test Location 2', 'target_location': 'Test Location'}], reference_doctype = 'Purchase Receipt', reference_name = pr.name) self.assertEqual(frappe.db.get_value("Asset", asset.name, "location"), "Test Location") @@ -59,18 +60,18 @@ class TestAssetMovement(unittest.TestCase): self.assertEqual(frappe.db.get_value("Asset", asset.name, "location"), "Test Location") employee = make_employee("testassetmovemp@example.com", company="_Test Company") - movement3 = create_asset_movement(purpose = 'Issue', company = asset.company, + movement3 = create_asset_movement(purpose = 'Issue', company = asset.company, assets = [{ 'asset': asset.name , 'source_location': 'Test Location', 'to_employee': employee}], reference_doctype = 'Purchase Receipt', reference_name = pr.name) - + # after issuing asset should belong to an employee not at a location self.assertEqual(frappe.db.get_value("Asset", asset.name, "location"), None) self.assertEqual(frappe.db.get_value("Asset", asset.name, "custodian"), employee) - + def test_last_movement_cancellation(self): pr = make_purchase_receipt(item_code="Macbook Pro", qty=1, rate=100000.0, location="Test Location") - + asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') asset = frappe.get_doc('Asset', asset_name) asset.calculate_depreciation = 1 @@ -85,17 +86,17 @@ class TestAssetMovement(unittest.TestCase): }) if asset.docstatus == 0: asset.submit() - + if not frappe.db.exists("Location", "Test Location 2"): frappe.get_doc({ 'doctype': 'Location', 'location_name': 'Test Location 2' }).insert() - + movement = frappe.get_doc({'doctype': 'Asset Movement', 'reference_name': pr.name }) self.assertRaises(frappe.ValidationError, movement.cancel) - movement1 = create_asset_movement(purpose = 'Transfer', company = asset.company, + movement1 = create_asset_movement(purpose = 'Transfer', company = asset.company, assets = [{ 'asset': asset.name , 'source_location': 'Test Location', 'target_location': 'Test Location 2'}], reference_doctype = 'Purchase Receipt', reference_name = pr.name) self.assertEqual(frappe.db.get_value("Asset", asset.name, "location"), "Test Location 2") From 455d300fca044643eb37a78568e123a62a94ef38 Mon Sep 17 00:00:00 2001 From: marination Date: Wed, 11 Aug 2021 17:00:46 +0530 Subject: [PATCH 54/54] Revert "test: fix POS Closing Entry Test" This reverts commit 8f1a3aef2e3f14e179f4ac91718e9f376b34198c. --- .../test_pos_closing_entry.py | 42 ++----------------- .../asset_movement/test_asset_movement.py | 4 +- 2 files changed, 6 insertions(+), 40 deletions(-) diff --git a/erpnext/accounts/doctype/pos_closing_entry/test_pos_closing_entry.py b/erpnext/accounts/doctype/pos_closing_entry/test_pos_closing_entry.py index c585f310b16..b596c0cf25a 100644 --- a/erpnext/accounts/doctype/pos_closing_entry/test_pos_closing_entry.py +++ b/erpnext/accounts/doctype/pos_closing_entry/test_pos_closing_entry.py @@ -19,7 +19,6 @@ class TestPOSClosingEntry(unittest.TestCase): def tearDown(self): frappe.set_user("Administrator") frappe.db.sql("delete from `tabPOS Profile`") - frappe.db.rollback() def test_pos_closing_entry(self): test_user, pos_profile = init_user_and_profile() @@ -51,8 +50,7 @@ class TestPOSClosingEntry(unittest.TestCase): self.assertEqual(pcv_doc.total_quantity, 2) self.assertEqual(pcv_doc.net_total, 6700) - def test_cancelling_of_consolidated_sales_invoice(self): - "Check if cancelling consolidated Sales Invoice with submitted POS Closing Entry is blocked." + def test_cancelling_of_pos_closing_entry(self): test_user, pos_profile = init_user_and_profile() opening_entry = create_opening_entry(pos_profile, test_user.name) @@ -85,45 +83,13 @@ class TestPOSClosingEntry(unittest.TestCase): si_doc = frappe.get_doc("Sales Invoice", pos_inv1.consolidated_invoice) self.assertRaises(frappe.ValidationError, si_doc.cancel) - def test_cancelling_of_pos_closing_entry(self): - "Check impact of cancelling POS Closing Entry." - test_user, pos_profile = init_user_and_profile() - opening_entry = create_opening_entry(pos_profile, test_user.name) - - pos_inv1 = create_pos_invoice(rate=3500, do_not_submit=1) - pos_inv1.append('payments', { - 'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 3500 - }) - pos_inv1.submit() - - pos_inv2 = create_pos_invoice(rate=3200, do_not_submit=1) - pos_inv2.append('payments', { - 'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 3200 - }) - pos_inv2.submit() - - pcv_doc = make_closing_entry_from_opening(opening_entry) - - for d in pcv_doc.payment_reconciliation: - if d.mode_of_payment == 'Cash': - d.closing_amount = 6700 - - pcv_doc.submit() - - pos_inv1.load_from_db() - si_name = pos_inv1.consolidated_invoice - pcv_doc.load_from_db() pcv_doc.cancel() + si_doc.load_from_db() pos_inv1.load_from_db() - - # After POS Closing Entry cancel, SI doc gets cancelled, unlinked and renamed - # There's no reference doc to fetch SI with new cancelled name - # which is why we are hardcoding suffix - si_docstatus = frappe.db.get_value("Sales Invoice", si_name+"-CANC-0", "docstatus") - self.assertEqual(si_docstatus, 2) + self.assertEqual(si_doc.docstatus, 2) self.assertEqual(pos_inv1.status, 'Paid') - self.assertIsNone(pos_inv1.consolidated_invoice) + def init_user_and_profile(**args): user = 'test@example.com' diff --git a/erpnext/assets/doctype/asset_movement/test_asset_movement.py b/erpnext/assets/doctype/asset_movement/test_asset_movement.py index 985bca9d0de..2b2d2b44004 100644 --- a/erpnext/assets/doctype/asset_movement/test_asset_movement.py +++ b/erpnext/assets/doctype/asset_movement/test_asset_movement.py @@ -51,7 +51,7 @@ class TestAssetMovement(unittest.TestCase): reference_doctype = 'Purchase Receipt', reference_name = pr.name) self.assertEqual(frappe.db.get_value("Asset", asset.name, "location"), "Test Location 2") - movement2 = create_asset_movement(purpose = 'Transfer', company = asset.company, + create_asset_movement(purpose = 'Transfer', company = asset.company, assets = [{ 'asset': asset.name , 'source_location': 'Test Location 2', 'target_location': 'Test Location'}], reference_doctype = 'Purchase Receipt', reference_name = pr.name) self.assertEqual(frappe.db.get_value("Asset", asset.name, "location"), "Test Location") @@ -60,7 +60,7 @@ class TestAssetMovement(unittest.TestCase): self.assertEqual(frappe.db.get_value("Asset", asset.name, "location"), "Test Location") employee = make_employee("testassetmovemp@example.com", company="_Test Company") - movement3 = create_asset_movement(purpose = 'Issue', company = asset.company, + create_asset_movement(purpose = 'Issue', company = asset.company, assets = [{ 'asset': asset.name , 'source_location': 'Test Location', 'to_employee': employee}], reference_doctype = 'Purchase Receipt', reference_name = pr.name)