From 9525bffcd9fcd2e4725e45a1bda93a8fdc9e1e27 Mon Sep 17 00:00:00 2001 From: Marica Date: Fri, 23 Oct 2020 18:15:55 +0530 Subject: [PATCH 01/57] fix: Hide Ex-Employees from Employee Tree and minor message UX * fix: Hide Ex-Employees from Employee Tree and minor message UX * fix: translation syntax --- erpnext/hr/doctype/employee/employee.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/erpnext/hr/doctype/employee/employee.py b/erpnext/hr/doctype/employee/employee.py index 2f88e1e3631..a746a9c3a8b 100755 --- a/erpnext/hr/doctype/employee/employee.py +++ b/erpnext/hr/doctype/employee/employee.py @@ -172,8 +172,11 @@ class Employee(NestedSet): ) if reports_to: link_to_employees = [frappe.utils.get_link_to_form('Employee', employee.name, label=employee.employee_name) for employee in reports_to] - throw(_("Employee status cannot be set to 'Left' as following employees are currently reporting to this employee: ") - + ', '.join(link_to_employees), EmployeeLeftValidationError) + message = _("The following employees are currently still reporting to {0}:").format(frappe.bold(self.employee_name)) + message += "


" + message += _("Please make sure the employees above report to another Active employee.") + throw(message, EmployeeLeftValidationError, _("Cannot Relieve Employee")) if not self.relieving_date: throw(_("Please enter relieving date.")) @@ -206,7 +209,7 @@ class Employee(NestedSet): def validate_preferred_email(self): if self.prefered_contact_email and not self.get(scrub(self.prefered_contact_email)): - frappe.msgprint(_("Please enter " + self.prefered_contact_email)) + frappe.msgprint(_("Please enter {0}").format(self.prefered_contact_email)) def validate_onboarding_process(self): employee_onboarding = frappe.get_all("Employee Onboarding", @@ -407,7 +410,11 @@ def get_employee_emails(employee_list): @frappe.whitelist() def get_children(doctype, parent=None, company=None, is_root=False, is_tree=False): - filters = [['company', '=', company]] + + filters = [['status', '!=', 'Left']] + if company and company != 'All Companies': + filters.append(['company', '=', company]) + fields = ['name as value', 'employee_name as title'] if is_root: From 2c29933118d02ceead60a8e0f1dcc3c0441a1203 Mon Sep 17 00:00:00 2001 From: Marica Date: Fri, 23 Oct 2020 19:26:43 +0530 Subject: [PATCH 02/57] fix: Sequence Matcher shouldn't get None input (#23539) Co-authored-by: Nabin Hait --- .../page/bank_reconciliation/bank_reconciliation.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/page/bank_reconciliation/bank_reconciliation.py b/erpnext/accounts/page/bank_reconciliation/bank_reconciliation.py index b4fffec7d40..fb409daacda 100644 --- a/erpnext/accounts/page/bank_reconciliation/bank_reconciliation.py +++ b/erpnext/accounts/page/bank_reconciliation/bank_reconciliation.py @@ -260,7 +260,11 @@ def check_amount_vs_description(amount_matching, description_matching): continue if "reference_no" in am_match and "reference_no" in des_match: - if difflib.SequenceMatcher(lambda x: x == " ", am_match["reference_no"], des_match["reference_no"]).ratio() > 70: + # Sequence Matcher does not handle None as input + am_reference = am_match["reference_no"] or "" + des_reference = des_match["reference_no"] or "" + + if difflib.SequenceMatcher(lambda x: x == " ", am_reference, des_reference).ratio() > 70: if am_match not in result: result.append(am_match) if result: From 7e18df31bf1a822d330159547b1ca22698696d73 Mon Sep 17 00:00:00 2001 From: marination Date: Fri, 9 Oct 2020 21:44:23 +0530 Subject: [PATCH 03/57] fix: Check if list view standard filter exists in Payment Entry --- .../doctype/payment_entry/payment_entry_list.js | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry_list.js b/erpnext/accounts/doctype/payment_entry/payment_entry_list.js index 7ea60bb48ed..e6d83b9f683 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry_list.js +++ b/erpnext/accounts/doctype/payment_entry/payment_entry_list.js @@ -1,12 +1,14 @@ frappe.listview_settings['Payment Entry'] = { onload: function(listview) { - listview.page.fields_dict.party_type.get_query = function() { - return { - "filters": { - "name": ["in", Object.keys(frappe.boot.party_account_types)], - } + if (listview.page.fields_dict.party_type) { + listview.page.fields_dict.party_type.get_query = function() { + return { + "filters": { + "name": ["in", Object.keys(frappe.boot.party_account_types)], + } + }; }; - }; + } } }; \ No newline at end of file From 5ea276d29d84c93889b0ee1dff5e4aa3a03e7deb Mon Sep 17 00:00:00 2001 From: marination Date: Fri, 16 Oct 2020 12:36:13 +0530 Subject: [PATCH 04/57] fix: Payment Reconciliation client side validations --- .../payment_reconciliation.js | 57 +++++++++++-------- erpnext/accounts/party.py | 2 +- 2 files changed, 33 insertions(+), 26 deletions(-) diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js index 355fe96c967..118e28970c4 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js +++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js @@ -37,6 +37,11 @@ frappe.ui.form.on("Payment Reconciliation Payment", { erpnext.accounts.PaymentReconciliationController = frappe.ui.form.Controller.extend({ onload: function() { var me = this; + + this.frm.set_query("party", function() { + check_mandatory(me.frm); + }); + this.frm.set_query("party_type", function() { return { "filters": { @@ -46,37 +51,39 @@ erpnext.accounts.PaymentReconciliationController = frappe.ui.form.Controller.ext }); this.frm.set_query('receivable_payable_account', function() { - if(!me.frm.doc.company || !me.frm.doc.party_type) { - frappe.msgprint(__("Please select Company and Party Type first")); - } else { - return{ - filters: { - "company": me.frm.doc.company, - "is_group": 0, - "account_type": frappe.boot.party_account_types[me.frm.doc.party_type] - } - }; - } - + check_mandatory(me.frm); + return { + filters: { + "company": me.frm.doc.company, + "is_group": 0, + "account_type": frappe.boot.party_account_types[me.frm.doc.party_type] + } + }; }); this.frm.set_query('bank_cash_account', function() { - if(!me.frm.doc.company) { - frappe.msgprint(__("Please select Company first")); - } else { - return{ - filters:[ - ['Account', 'company', '=', me.frm.doc.company], - ['Account', 'is_group', '=', 0], - ['Account', 'account_type', 'in', ['Bank', 'Cash']] - ] - }; - } + check_mandatory(me.frm, true); + return { + filters:[ + ['Account', 'company', '=', me.frm.doc.company], + ['Account', 'is_group', '=', 0], + ['Account', 'account_type', 'in', ['Bank', 'Cash']] + ] + }; }); this.frm.set_value('party_type', ''); this.frm.set_value('party', ''); this.frm.set_value('receivable_payable_account', ''); + + var check_mandatory = (frm, only_company=false) => { + var title = __("Mandatory"); + if (only_company && !frm.doc.company) { + frappe.throw({message: __("Please Select a Company First"), title: title}); + } else if (!frm.doc.company || !frm.doc.party_type) { + frappe.throw({message: __("Please Select Both Company and Party Type First"), title: title}); + } + } }, refresh: function() { @@ -90,7 +97,7 @@ erpnext.accounts.PaymentReconciliationController = frappe.ui.form.Controller.ext party: function() { var me = this - if(!me.frm.doc.receivable_payable_account && me.frm.doc.party_type && me.frm.doc.party) { + if (!me.frm.doc.receivable_payable_account && me.frm.doc.party_type && me.frm.doc.party) { return frappe.call({ method: "erpnext.accounts.party.get_party_account", args: { @@ -99,7 +106,7 @@ erpnext.accounts.PaymentReconciliationController = frappe.ui.form.Controller.ext party: me.frm.doc.party }, callback: function(r) { - if(!r.exc && r.message) { + if (!r.exc && r.message) { me.frm.set_value("receivable_payable_account", r.message); } } diff --git a/erpnext/accounts/party.py b/erpnext/accounts/party.py index 4e7922c6fbe..cf47daeceac 100644 --- a/erpnext/accounts/party.py +++ b/erpnext/accounts/party.py @@ -204,7 +204,7 @@ def set_account_and_due_date(party, account, party_type, company, posting_date, return out @frappe.whitelist() -def get_party_account(party_type, party, company): +def get_party_account(party_type, party, company=None): """Returns the account for the given `party`. Will first search in party (Customer / Supplier) record, if not found, will search in group (Customer Group / Supplier Group), From 2e23050338bce12778b6ef2b39d20ec46eaf0fda Mon Sep 17 00:00:00 2001 From: marination Date: Fri, 16 Oct 2020 16:04:56 +0530 Subject: [PATCH 05/57] fix: Missing semicolon --- .../doctype/payment_reconciliation/payment_reconciliation.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js index 118e28970c4..6b07197ec10 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js +++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js @@ -83,7 +83,7 @@ erpnext.accounts.PaymentReconciliationController = frappe.ui.form.Controller.ext } else if (!frm.doc.company || !frm.doc.party_type) { frappe.throw({message: __("Please Select Both Company and Party Type First"), title: title}); } - } + }; }, refresh: function() { From 3bbed22f04bbaf6d6e9645c780bed6bfaa67da56 Mon Sep 17 00:00:00 2001 From: marination Date: Mon, 12 Oct 2020 20:08:03 +0530 Subject: [PATCH 06/57] fix: Item Link Formatter Behaviour --- erpnext/public/js/utils.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/erpnext/public/js/utils.js b/erpnext/public/js/utils.js index 264344ca94c..6e2409cf875 100755 --- a/erpnext/public/js/utils.js +++ b/erpnext/public/js/utils.js @@ -665,9 +665,13 @@ erpnext.utils.map_current_doc = function(opts) { } frappe.form.link_formatters['Item'] = function(value, doc) { - if(doc && doc.item_name && doc.item_name !== value) { - return value? value + ': ' + doc.item_name: doc.item_name; + if (doc && value && doc.item_name && doc.item_name !== value) { + return value + ': ' + doc.item_name; + } else if (!value && doc.doctype && doc.item_name) { + // format blank value in child table + return doc.item_name; } else { + // if value is blank in report view or item code and name are the same, return as is return value; } } From f041af53dbfea8cd8b11444fd134b78ecea3c230 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Wed, 18 Nov 2020 01:15:35 +0530 Subject: [PATCH 07/57] fix: stock ledger entries for stock reco --- .../stock_reconciliation.py | 37 ++++++++++------ .../test_stock_reconciliation.py | 43 ++++++++++++++++--- 2 files changed, 61 insertions(+), 19 deletions(-) diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py index 2b4780437f4..ed29316030f 100644 --- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py @@ -191,7 +191,7 @@ class StockReconciliation(StockController): sl_entries = [] - serialized_items = False + serialized_items = [] for row in self.items: item = frappe.get_cached_doc("Item", row.item_code) if not (item.has_serial_no): @@ -229,27 +229,29 @@ class StockReconciliation(StockController): sl_entries.append(sle_data) else: - serialized_items = True + serialized_items.append(row.item_code) if serialized_items: - self.get_sle_for_serialized_items(sl_entries) + self.get_sle_for_serialized_items(sl_entries, serialized_items) if sl_entries: allow_negative_stock = frappe.get_cached_value("Stock Settings", None, "allow_negative_stock") self.make_sl_entries(sl_entries, allow_negative_stock=allow_negative_stock) - def get_sle_for_serialized_items(self, sl_entries): - self.issue_existing_serial_and_batch(sl_entries) - self.add_new_serial_and_batch(sl_entries) - self.update_valuation_rate_for_serial_no() + def get_sle_for_serialized_items(self, sl_entries, serialized_items=[]): + self.issue_existing_serial_and_batch(sl_entries, serialized_items) + self.add_new_serial_and_batch(sl_entries, serialized_items) + self.update_valuation_rate_for_serial_no(serialized_items) if sl_entries: sl_entries = self.merge_similar_item_serial_nos(sl_entries) - def issue_existing_serial_and_batch(self, sl_entries): + def issue_existing_serial_and_batch(self, sl_entries, serialized_items=[]): from erpnext.stock.stock_ledger import get_stock_ledger_entries for row in self.items: + if row.item_code not in serialized_items: continue + serial_nos = get_serial_nos(row.serial_no) or [] # To issue existing serial nos @@ -303,8 +305,10 @@ class StockReconciliation(StockController): sl_entries.append(new_args) - def add_new_serial_and_batch(self, sl_entries): + def add_new_serial_and_batch(self, sl_entries, serialized_items=[]): for row in self.items: + if row.item_code not in serialized_items: continue + if row.qty: args = self.get_sle_for_items(row) @@ -316,9 +320,9 @@ class StockReconciliation(StockController): sl_entries.append(args) - def update_valuation_rate_for_serial_no(self): + def update_valuation_rate_for_serial_no(self, serialized_items=[]): for d in self.items: - if not d.serial_no: continue + if d.item_code not in serialized_items: continue serial_nos = get_serial_nos(d.serial_no) self.update_valuation_rate_for_serial_nos(d, serial_nos) @@ -372,7 +376,16 @@ class StockReconciliation(StockController): where voucher_type=%s and voucher_no=%s""", (self.doctype, self.name)) sl_entries = [] - self.get_sle_for_serialized_items(sl_entries) + + serialized_items = [] + + for row in self.items: + has_serial_no = frappe.get_cached_value("Item", row.item_code, "has_serial_no") + if has_serial_no: + serialized_items.append(row.item_code) + + if serialized_items: + self.get_sle_for_serialized_items(sl_entries, serialized_items) if sl_entries: sl_entries.reverse() diff --git a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py index 27908016407..7c55fd6da1c 100644 --- a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py @@ -207,9 +207,9 @@ class TestStockReconciliation(unittest.TestCase): def test_stock_reco_for_serial_and_batch_item(self): set_perpetual_inventory() - item = frappe.db.exists("Item", {'item_name': 'Batched and Serialised Item'}) + item = frappe.db.exists("Item", {'item_name': 'Batched and Serialised Item 1'}) if not item: - item = create_item("Batched and Serialised Item") + item = create_item("Batched and Serialised Item 1") item.has_batch_no = 1 item.create_new_batch = 1 item.has_serial_no = 1 @@ -217,7 +217,7 @@ class TestStockReconciliation(unittest.TestCase): item.serial_no_series = "S-.####" item.save() else: - item = frappe.get_doc("Item", {'item_name': 'Batched and Serialised Item'}) + item = frappe.get_doc("Item", {'item_name': 'Batched and Serialised Item 1'}) warehouse = "_Test Warehouse for Stock Reco2 - _TC" @@ -236,7 +236,7 @@ class TestStockReconciliation(unittest.TestCase): self.assertEqual(frappe.db.exists("Batch", batch_no), None) if frappe.db.exists("Serial No", serial_nos[0]): - frappe.delete_doc("Serial No", serial_nos[0]) + frappe.delete_doc("Serial No", serial_nos[0]) def test_stock_reco_for_serial_and_batch_item_with_future_dependent_entry(self): """ @@ -255,9 +255,9 @@ class TestStockReconciliation(unittest.TestCase): set_perpetual_inventory() - item = frappe.db.exists("Item", {'item_name': 'Batched and Serialised Item'}) + item = frappe.db.exists("Item", {'item_name': 'Batched and Serialised Item 1'}) if not item: - item = create_item("Batched and Serialised Item") + item = create_item("Batched and Serialised Item 1") item.has_batch_no = 1 item.create_new_batch = 1 item.has_serial_no = 1 @@ -265,7 +265,7 @@ class TestStockReconciliation(unittest.TestCase): item.serial_no_series = "S-.####" item.save() else: - item = frappe.get_doc("Item", {'item_name': 'Batched and Serialised Item'}) + item = frappe.get_doc("Item", {'item_name': 'Batched and Serialised Item 1'}) warehouse = "_Test Warehouse for Stock Reco2 - _TC" @@ -392,6 +392,35 @@ class TestStockReconciliation(unittest.TestCase): doc.cancel() frappe.delete_doc(doc.doctype, doc.name) + def test_stock_reco_with_serial_and_batch(self): + from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry + + warehouse = "_Test Warehouse for Stock Reco1 - _TC" + ste1=make_stock_entry(item_code="Stock-Reco-Serial-Item-1", + target=warehouse, qty=2, basic_rate=100) + + ste2=make_stock_entry(item_code="Stock-Reco-batch-Item-1", + target=warehouse, qty=2, basic_rate=100) + + sr = create_stock_reconciliation(item_code="Stock-Reco-Serial-Item-1", + warehouse = warehouse, rate=200, do_not_submit=True) + + sr.append("items", { + "item_code": "Stock-Reco-batch-Item-1", + "warehouse": warehouse, + "batch_no": ste2.items[0].batch_no, + "valuation_rate": 200 + }) + + sr.submit() + sle = frappe.get_all("Stock Ledger Entry", filters={"item_code": "Stock-Reco-batch-Item-1", + "warehouse": warehouse, "voucher_no": sr.name, "voucher_type": sr.doctype}) + + self.assertEquals(len(sle), 1) + + for doc in [sr, ste2, ste1]: + doc.cancel() + def insert_existing_sle(warehouse): from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry From 36706b7cb05a8b614fe1c37646f5d29e043369a5 Mon Sep 17 00:00:00 2001 From: Marica Date: Wed, 18 Nov 2020 20:54:33 +0530 Subject: [PATCH 08/57] chore: Revert "fix: Received/Delivered Items to Billed Logic" (#23948) This reverts commit 4f008f59fc54be84ced14cfe26c4a4f58c9df603. --- .../delivered_items_to_be_billed.py | 18 +++++------------ erpnext/accounts/report/non_billed_report.py | 20 ++++++------------- .../received_items_to_be_billed.py | 18 +++++------------ 3 files changed, 16 insertions(+), 40 deletions(-) diff --git a/erpnext/accounts/report/delivered_items_to_be_billed/delivered_items_to_be_billed.py b/erpnext/accounts/report/delivered_items_to_be_billed/delivered_items_to_be_billed.py index 2aea3f64239..3ffb3ac1df4 100644 --- a/erpnext/accounts/report/delivered_items_to_be_billed/delivered_items_to_be_billed.py +++ b/erpnext/accounts/report/delivered_items_to_be_billed/delivered_items_to_be_billed.py @@ -14,19 +14,11 @@ def execute(filters=None): def get_column(): return [ - _("Delivery Note") + ":Link/Delivery Note:160", - _("Date") + ":Date:100", - _("Customer") + ":Link/Customer:120", - _("Customer Name") + "::120", - _("Item Code") + ":Link/Item:120", - _("Amount") + ":Currency:100", - _("Billed Amount") + ":Currency:100", - _("Returned Amount") + ":Currency:120", - _("Pending Amount") + ":Currency:100", - _("Item Name") + "::120", - _("Description") + "::120", - _("Project") + ":Link/Project:120", - _("Company") + ":Link/Company:120", + _("Delivery Note") + ":Link/Delivery Note:120", _("Status") + "::120", _("Date") + ":Date:100", + _("Suplier") + ":Link/Customer:120", _("Customer Name") + "::120", + _("Project") + ":Link/Project:120", _("Item Code") + ":Link/Item:120", + _("Amount") + ":Currency:100", _("Billed Amount") + ":Currency:100", _("Pending Amount") + ":Currency:100", + _("Item Name") + "::120", _("Description") + "::120", _("Company") + ":Link/Company:120", ] def get_args(): diff --git a/erpnext/accounts/report/non_billed_report.py b/erpnext/accounts/report/non_billed_report.py index 2e18ce11ddc..a9e25bc25bf 100644 --- a/erpnext/accounts/report/non_billed_report.py +++ b/erpnext/accounts/report/non_billed_report.py @@ -17,26 +17,18 @@ def get_ordered_to_be_billed_data(args): return frappe.db.sql(""" Select - `{parent_tab}`.name, `{parent_tab}`.{date_field}, - `{parent_tab}`.{party}, `{parent_tab}`.{party}_name, - `{child_tab}`.item_code, - `{child_tab}`.base_amount, + `{parent_tab}`.name, `{parent_tab}`.status, `{parent_tab}`.{date_field}, `{parent_tab}`.{party}, `{parent_tab}`.{party}_name, + {project_field}, `{child_tab}`.item_code, `{child_tab}`.base_amount, (`{child_tab}`.billed_amt * ifnull(`{parent_tab}`.conversion_rate, 1)), - (`{child_tab}`.base_rate * ifnull(`{child_tab}`.returned_qty, 0)), - (`{child_tab}`.base_amount - - (`{child_tab}`.billed_amt * ifnull(`{parent_tab}`.conversion_rate, 1)) - - (`{child_tab}`.base_rate * ifnull(`{child_tab}`.returned_qty, 0))), - `{child_tab}`.item_name, `{child_tab}`.description, - {project_field}, `{parent_tab}`.company + (`{child_tab}`.base_amount - (`{child_tab}`.billed_amt * ifnull(`{parent_tab}`.conversion_rate, 1))), + `{child_tab}`.item_name, `{child_tab}`.description, `{parent_tab}`.company from `{parent_tab}`, `{child_tab}` where `{parent_tab}`.name = `{child_tab}`.parent and `{parent_tab}`.docstatus = 1 and `{parent_tab}`.status not in ('Closed', 'Completed') - and `{child_tab}`.amount > 0 - and (`{child_tab}`.base_amount - - round(`{child_tab}`.billed_amt * ifnull(`{parent_tab}`.conversion_rate, 1), {precision}) - - (`{child_tab}`.base_rate * ifnull(`{child_tab}`.returned_qty, 0))) > 0 + and `{child_tab}`.amount > 0 and round(`{child_tab}`.billed_amt * + ifnull(`{parent_tab}`.conversion_rate, 1), {precision}) < `{child_tab}`.base_amount order by `{parent_tab}`.{order} {order_by} """.format(parent_tab = 'tab' + doctype, child_tab = 'tab' + child_tab, precision= precision, party = party, diff --git a/erpnext/accounts/report/received_items_to_be_billed/received_items_to_be_billed.py b/erpnext/accounts/report/received_items_to_be_billed/received_items_to_be_billed.py index c7d4384a734..5e8d7730b76 100644 --- a/erpnext/accounts/report/received_items_to_be_billed/received_items_to_be_billed.py +++ b/erpnext/accounts/report/received_items_to_be_billed/received_items_to_be_billed.py @@ -14,19 +14,11 @@ def execute(filters=None): def get_column(): return [ - _("Purchase Receipt") + ":Link/Purchase Receipt:160", - _("Date") + ":Date:100", - _("Supplier") + ":Link/Supplier:120", - _("Supplier Name") + "::120", - _("Item Code") + ":Link/Item:120", - _("Amount") + ":Currency:100", - _("Billed Amount") + ":Currency:100", - _("Returned Amount") + ":Currency:120", - _("Pending Amount") + ":Currency:120", - _("Item Name") + "::120", - _("Description") + "::120", - _("Project") + ":Link/Project:120", - _("Company") + ":Link/Company:120", + _("Purchase Receipt") + ":Link/Purchase Receipt:120", _("Status") + "::120", _("Date") + ":Date:100", + _("Supplier") + ":Link/Supplier:120", _("Supplier Name") + "::120", + _("Project") + ":Link/Project:120", _("Item Code") + ":Link/Item:120", + _("Amount") + ":Currency:100", _("Billed Amount") + ":Currency:100", _("Amount to Bill") + ":Currency:100", + _("Item Name") + "::120", _("Description") + "::120", _("Company") + ":Link/Company:120", ] def get_args(): From 3e36d774d2a4a56a15a5037424ae7c04e16ba739 Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Wed, 18 Nov 2020 21:03:45 +0530 Subject: [PATCH 09/57] fix(regional): set proper state code in ewaybill JSON when GST category is SEZ --- erpnext/regional/india/utils.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/erpnext/regional/india/utils.py b/erpnext/regional/india/utils.py index bc182382802..5872c69fd16 100644 --- a/erpnext/regional/india/utils.py +++ b/erpnext/regional/india/utils.py @@ -500,6 +500,9 @@ def get_address_details(data, doc, company_address, billing_address): data.transType = 1 data.actualToStateCode = data.toStateCode shipping_address = billing_address + + if doc.gst_category == 'SEZ': + data.toStateCode = 99 return data From 0433fa58f341d1428705a8a4d60a94840d5edfb5 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Wed, 18 Nov 2020 20:17:52 +0530 Subject: [PATCH 10/57] fix: Table 'tabStock Entry Detail' is specified twice --- erpnext/controllers/status_updater.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/erpnext/controllers/status_updater.py b/erpnext/controllers/status_updater.py index da99f1267f6..1615e901350 100644 --- a/erpnext/controllers/status_updater.py +++ b/erpnext/controllers/status_updater.py @@ -246,22 +246,26 @@ class StatusUpdater(Document): if not args.get("second_source_extra_cond"): args["second_source_extra_cond"] = "" - args['second_source_condition'] = """ + ifnull((select sum(%(second_source_field)s) + args['second_source_condition'] = frappe.db.sql(""" select ifnull((select sum(%(second_source_field)s) from `tab%(second_source_dt)s` where `%(second_join_field)s`="%(detail_id)s" - and (`tab%(second_source_dt)s`.docstatus=1) %(second_source_extra_cond)s FOR UPDATE), 0) """ % args + and (`tab%(second_source_dt)s`.docstatus=1) + %(second_source_extra_cond)s), 0) """ % args)[0][0] if args['detail_id']: if not args.get("extra_cond"): args["extra_cond"] = "" - frappe.db.sql("""update `tab%(target_dt)s` - set %(target_field)s = ( + args["source_dt_value"] = frappe.db.sql(""" (select ifnull(sum(%(source_field)s), 0) from `tab%(source_dt)s` where `%(join_field)s`="%(detail_id)s" and (docstatus=1 %(cond)s) %(extra_cond)s) - %(second_source_condition)s - ) - %(update_modified)s + """ % args)[0][0] or 0.0 + + if args['second_source_condition']: + args["source_dt_value"] += flt(args['second_source_condition']) + + frappe.db.sql("""update `tab%(target_dt)s` + set %(target_field)s = %(source_dt_value)s %(update_modified)s where name='%(detail_id)s'""" % args) def _update_percent_field_in_targets(self, args, update_modified=True): From 224006aebd824004c0dea6d82b3f95104252bf5a Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Thu, 19 Nov 2020 19:39:08 +0530 Subject: [PATCH 11/57] fix: Invoice generation for Unpaid subscriptions --- erpnext/accounts/doctype/subscription/subscription.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/erpnext/accounts/doctype/subscription/subscription.py b/erpnext/accounts/doctype/subscription/subscription.py index 4ec2e65b2a3..1eeed1f2faf 100644 --- a/erpnext/accounts/doctype/subscription/subscription.py +++ b/erpnext/accounts/doctype/subscription/subscription.py @@ -408,6 +408,15 @@ class Subscription(Document): else: self.set_status_grace_period() + if getdate() > getdate(self.current_invoice_end): + self.update_subscription_period(add_days(self.current_invoice_end, 1)) + + # Generate invoices periodically even if current invoice are unpaid + if not self.is_current_invoice_generated() and (self.is_postpaid_to_invoice() + or self.is_prepaid_to_invoice()): + prorate = frappe.db.get_single_value('Subscription Settings', 'prorate') + self.generate_invoice(prorate) + @staticmethod def is_not_outstanding(invoice): """ From 77b9fa0d06edea0ebee45fff7ef35cf21d526c48 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Wed, 18 Nov 2020 17:57:35 +0530 Subject: [PATCH 12/57] fix: incorrect delink serial no and batch --- erpnext/controllers/stock_controller.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index 8cad82c3e25..712fd3a51f5 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -227,9 +227,9 @@ class StockController(AccountsController): def check_expense_account(self, item): if not item.get("expense_account"): - frappe.throw(_("Row #{0}: Expense Account not set for Item {1}. Please set an Expense \ - Account in the Items table").format(item.idx, frappe.bold(item.item_code)), - title=_("Expense Account Missing")) + msg = _("Please set an Expense Account in the Items table") + frappe.throw(_("Row #{0}: Expense Account not set for the Item {1}. {2}") + .format(item.idx, frappe.bold(item.item_code), msg), title=_("Expense Account Missing")) else: is_expense_account = frappe.db.get_value("Account", @@ -242,11 +242,12 @@ class StockController(AccountsController): _(self.doctype), self.name, item.get("item_code"))) def delete_auto_created_batches(self): - from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos for d in self.items: if not d.batch_no: continue - serial_nos = get_serial_nos(d.serial_no) + serial_nos = [sr.name for sr in frappe.get_all("Serial No", + {'batch_no': d.batch_no, 'status': 'Inactive'})] + if serial_nos: frappe.db.set_value("Serial No", { 'name': ['in', serial_nos] }, "batch_no", None) From 0cb6ce3a8963f5edc98c25645896ace52509eca5 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Mon, 23 Nov 2020 13:47:49 +0530 Subject: [PATCH 13/57] fix: bom stock report color issue --- .../report/bom_stock_report/bom_stock_report.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/manufacturing/report/bom_stock_report/bom_stock_report.js b/erpnext/manufacturing/report/bom_stock_report/bom_stock_report.js index 2ac6fa073bf..45331c6af82 100644 --- a/erpnext/manufacturing/report/bom_stock_report/bom_stock_report.js +++ b/erpnext/manufacturing/report/bom_stock_report/bom_stock_report.js @@ -25,11 +25,11 @@ frappe.query_reports["BOM Stock Report"] = { ], "formatter": function(value, row, column, data, default_formatter) { value = default_formatter(value, row, column, data); - if (column.id == "Item"){ + if (column.id == "item"){ if (data["Enough Parts to Build"] > 0){ - value = `${data['Item']}` + value = `${data['item']}`; } else { - value = `${data['Item']}` + value = `${data['item']}`; } } return value From 3d84d324d7f53cc6f96fb87f3ddfd94dd4a22fea Mon Sep 17 00:00:00 2001 From: Jannat Patel <31363128+pateljannat@users.noreply.github.com> Date: Mon, 23 Nov 2020 16:29:42 +0530 Subject: [PATCH 14/57] fix: purchase receipt to purchase invoice bill date mapping (#23968) Co-authored-by: pateljannat Co-authored-by: Rucha Mahabal --- erpnext/stock/doctype/purchase_receipt/purchase_receipt.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index 730a1d0c076..85b6d578bbd 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -539,7 +539,8 @@ def make_purchase_invoice(source_name, target_doc=None): "doctype": "Purchase Invoice", "field_map": { "supplier_warehouse":"supplier_warehouse", - "is_return": "is_return" + "is_return": "is_return", + "bill_date": "bill_date" }, "validation": { "docstatus": ["=", 1], From 2b3a20ed5b8d4a55433b94e17fc768792fbcad08 Mon Sep 17 00:00:00 2001 From: Afshan <33727827+AfshanKhan@users.noreply.github.com> Date: Tue, 24 Nov 2020 08:01:43 +0530 Subject: [PATCH 15/57] fix: clear error message when approval not availab (#23972) le --- .../department_approver/department_approver.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/erpnext/hr/doctype/department_approver/department_approver.py b/erpnext/hr/doctype/department_approver/department_approver.py index 70a0aa217f7..1200ae1de16 100644 --- a/erpnext/hr/doctype/department_approver/department_approver.py +++ b/erpnext/hr/doctype/department_approver/department_approver.py @@ -20,7 +20,7 @@ def get_approvers(doctype, txt, searchfield, start, page_len, filters): approvers = [] department_details = {} department_list = [] - employee = frappe.get_value("Employee", filters.get("employee"), ["department", "leave_approver"], as_dict=True) + employee = frappe.get_value("Employee", filters.get("employee"), ["employee_name","department", "leave_approver"], as_dict=True) employee_department = filters.get("department") or employee.department if employee_department: @@ -36,8 +36,10 @@ def get_approvers(doctype, txt, searchfield, start, page_len, filters): if filters.get("doctype") == "Leave Application": parentfield = "leave_approvers" - else: + field_name = "Leave Approver" + elif filters.get("doctype") == "Expense Claim": parentfield = "expense_approvers" + field_name = "Expense Approver" if department_list: for d in department_list: approvers += frappe.db.sql("""select user.name, user.first_name, user.last_name from @@ -47,4 +49,10 @@ def get_approvers(doctype, txt, searchfield, start, page_len, filters): and approver.parentfield = %s and approver.approver=user.name""",(d, "%" + txt + "%", parentfield), as_list=True) + if len(approvers) == 0: + error_msg = _("Please set {0} for the Employee: {1}").format(field_name, frappe.bold(employee.employee_name)) + if department_list: + error_msg += _(" or for Department: {0}").format(frappe.bold(employee_department)) + frappe.throw(error_msg, title=_(field_name + " Missing")) + return set(tuple(approver) for approver in approvers) From f9e62b74d06b3e4c0772d0cedcaf4b56f08b75db Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Tue, 24 Nov 2020 16:56:20 +0530 Subject: [PATCH 16/57] fix: bom stock report color showing always red --- .../manufacturing/report/bom_stock_report/bom_stock_report.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/manufacturing/report/bom_stock_report/bom_stock_report.js b/erpnext/manufacturing/report/bom_stock_report/bom_stock_report.js index 45331c6af82..8cd016461cc 100644 --- a/erpnext/manufacturing/report/bom_stock_report/bom_stock_report.js +++ b/erpnext/manufacturing/report/bom_stock_report/bom_stock_report.js @@ -25,8 +25,8 @@ frappe.query_reports["BOM Stock Report"] = { ], "formatter": function(value, row, column, data, default_formatter) { value = default_formatter(value, row, column, data); - if (column.id == "item"){ - if (data["Enough Parts to Build"] > 0){ + if (column.id == "item") { + if (data["enough_parts_to_build"] > 0) { value = `${data['item']}`; } else { value = `${data['item']}`; From ca05945e9272b78de50400c680e60d2b90663d70 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Wed, 25 Nov 2020 08:58:58 +0530 Subject: [PATCH 17/57] fix: incorrect balance value in stock balance report (#23997) --- erpnext/stock/report/stock_balance/stock_balance.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/report/stock_balance/stock_balance.py b/erpnext/stock/report/stock_balance/stock_balance.py index 042087a4a77..145562cdc1b 100644 --- a/erpnext/stock/report/stock_balance/stock_balance.py +++ b/erpnext/stock/report/stock_balance/stock_balance.py @@ -164,7 +164,7 @@ def get_stock_ledger_entries(filters, items): select sle.item_code, warehouse, sle.posting_date, sle.actual_qty, sle.valuation_rate, sle.company, sle.voucher_type, sle.qty_after_transaction, sle.stock_value_difference, - sle.item_code as name, sle.voucher_no + sle.item_code as name, sle.voucher_no, sle.stock_value from `tabStock Ledger Entry` sle force index (posting_sort_index) where sle.docstatus < 2 %s %s @@ -196,7 +196,7 @@ def get_item_warehouse_map(filters, sle): else: qty_diff = flt(d.actual_qty) - value_diff = flt(d.stock_value_difference) + value_diff = flt(d.stock_value) - flt(qty_dict.bal_val) if d.posting_date < from_date: qty_dict.opening_qty += qty_diff From 42b24cc9eb936dde64ed5ebc2d3e62d3f84ee363 Mon Sep 17 00:00:00 2001 From: Jannat Patel <31363128+pateljannat@users.noreply.github.com> Date: Wed, 25 Nov 2020 09:07:57 +0530 Subject: [PATCH 18/57] fix: job card error handling for operations field (#23996) Co-authored-by: pateljannat --- .../doctype/job_card/job_card.py | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/erpnext/manufacturing/doctype/job_card/job_card.py b/erpnext/manufacturing/doctype/job_card/job_card.py index c7443d911ce..161f277c168 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.py +++ b/erpnext/manufacturing/doctype/job_card/job_card.py @@ -224,17 +224,19 @@ def get_operation_details(work_order, operation): @frappe.whitelist() def get_operations(doctype, txt, searchfield, start, page_len, filters): - if filters.get("work_order"): - args = {"parent": filters.get("work_order")} - if txt: - args["operation"] = ("like", "%{0}%".format(txt)) + if not filters.get("work_order"): + frappe.msgprint(_("Please select a Work Order first.")) + return [] + args = {"parent": filters.get("work_order")} + if txt: + args["operation"] = ("like", "%{0}%".format(txt)) - return frappe.get_all("Work Order Operation", - filters = args, - fields = ["distinct operation as operation"], - limit_start = start, - limit_page_length = page_len, - order_by="idx asc", as_list=1) + return frappe.get_all("Work Order Operation", + filters = args, + fields = ["distinct operation as operation"], + limit_start = start, + limit_page_length = page_len, + order_by="idx asc", as_list=1) @frappe.whitelist() def make_material_request(source_name, target_doc=None): From 75a54361d71c9a890536573991576c4eea0afae3 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Thu, 26 Nov 2020 12:55:27 +0530 Subject: [PATCH 19/57] fix: shipping chanrges not sync in erpnext from shopify --- .../connectors/shopify_connection.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/erpnext/erpnext_integrations/connectors/shopify_connection.py b/erpnext/erpnext_integrations/connectors/shopify_connection.py index 215f12c2c2f..45dafd3ab38 100644 --- a/erpnext/erpnext_integrations/connectors/shopify_connection.py +++ b/erpnext/erpnext_integrations/connectors/shopify_connection.py @@ -244,6 +244,15 @@ def update_taxes_with_shipping_lines(taxes, shipping_lines, shopify_settings): """Shipping lines represents the shipping details, each such shipping detail consists of a list of tax_lines""" for shipping_charge in shipping_lines: + if shipping_charge.get("price"): + taxes.append({ + "charge_type": _("Actual"), + "account_head": get_tax_account_head(shipping_charge), + "description": shipping_charge["title"], + "tax_amount": shipping_charge["price"], + "cost_center": shopify_settings.cost_center + }) + for tax in shipping_charge.get("tax_lines"): taxes.append({ "charge_type": _("Actual"), From 3ef60dbfdb0bba649a7954a8f96d5e0212e619f5 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Thu, 26 Nov 2020 21:55:04 +0530 Subject: [PATCH 20/57] fix: incoming rate for finished good --- .../doctype/work_order/test_work_order.py | 23 +++++++++++++++++++ .../stock/doctype/stock_entry/stock_entry.py | 2 +- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/erpnext/manufacturing/doctype/work_order/test_work_order.py b/erpnext/manufacturing/doctype/work_order/test_work_order.py index 0263102bac0..bdf1a8e854f 100644 --- a/erpnext/manufacturing/doctype/work_order/test_work_order.py +++ b/erpnext/manufacturing/doctype/work_order/test_work_order.py @@ -319,6 +319,29 @@ class TestWorkOrder(unittest.TestCase): allow_overproduction("overproduction_percentage_for_work_order", 0) + def test_finished_good_valuation_rate(self): + allow_overproduction("overproduction_percentage_for_work_order", 0) + wo_order = make_wo_order_test_record(planned_start_date=now(), qty=2) + test_stock_entry.make_stock_entry(item_code="_Test Item", + target="_Test Warehouse - _TC", qty=10, basic_rate=5000.0) + test_stock_entry.make_stock_entry(item_code="_Test Item Home Desktop 100", + target="_Test Warehouse - _TC", qty=10, basic_rate=1000.0) + + ste_doc = frappe.get_doc(make_stock_entry(wo_order.name, "Material Transfer for Manufacture", 2)) + ste_doc.submit() + + ste_doc = frappe.get_doc(make_stock_entry(wo_order.name, "Manufacture", 2)) + ste_doc.save() + + self.assertEquals(ste_doc.total_incoming_value, ste_doc.total_outgoing_value) + + for row in ste_doc.items: + if row.t_warehouse and not row.s_warehouse: + row.valuation_rate = 120 + ste_doc.save() + + self.assertEquals(ste_doc.total_incoming_value, ste_doc.total_outgoing_value) + def test_over_production_for_sales_order(self): so = make_sales_order(item_code="_Test FG Item", qty=2) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index 1dd022fce00..0148c165f62 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -83,7 +83,7 @@ class StockEntry(StockController): self.set_incoming_rate() self.validate_serialized_batch() self.set_actual_qty() - self.calculate_rate_and_amount(update_finished_item_rate=False) + self.calculate_rate_and_amount() def on_submit(self): From 02feab9f3cc4c8b21c8e5bc25e9cd21c792af144 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Fri, 27 Nov 2020 21:55:02 +0530 Subject: [PATCH 21/57] fix: Opening invoices in GSTR-1 report --- erpnext/regional/report/gstr_1/gstr_1.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/regional/report/gstr_1/gstr_1.py b/erpnext/regional/report/gstr_1/gstr_1.py index 9368d8fa9ac..28b77c5b694 100644 --- a/erpnext/regional/report/gstr_1/gstr_1.py +++ b/erpnext/regional/report/gstr_1/gstr_1.py @@ -151,6 +151,7 @@ class Gstr1Report(object): {select_columns} from `tab{doctype}` where docstatus = 1 {where_conditions} + and is_opening = 'No' order by posting_date desc """.format(select_columns=self.select_columns, doctype=self.doctype, where_conditions=conditions), self.filters, as_dict=1) From d8b4044ee175bd63b1f340d0eed8e8615130ee5c Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sun, 29 Nov 2020 22:48:05 +0530 Subject: [PATCH 22/57] fix: Add check to generate new invoices --- .../accounts/doctype/subscription/subscription.json | 13 ++++++++++--- .../accounts/doctype/subscription/subscription.py | 2 +- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/erpnext/accounts/doctype/subscription/subscription.json b/erpnext/accounts/doctype/subscription/subscription.json index c17f3aeb846..fb5bb4839c4 100644 --- a/erpnext/accounts/doctype/subscription/subscription.json +++ b/erpnext/accounts/doctype/subscription/subscription.json @@ -13,6 +13,7 @@ "cancelation_date", "trial_period_start", "trial_period_end", + "generate_new_invoices_past_due_date", "column_break_11", "current_invoice_start", "current_invoice_end", @@ -183,8 +184,7 @@ "fieldname": "invoices", "fieldtype": "Table", "label": "Invoices", - "options": "Subscription Invoice", - "read_only": 1 + "options": "Subscription Invoice" }, { "collapsible": 1, @@ -195,9 +195,16 @@ { "fieldname": "dimension_col_break", "fieldtype": "Column Break" + }, + { + "default": "0", + "description": "New invoices will be generated as per schedule even if current invoices are unpaid or past due date", + "fieldname": "generate_new_invoices_past_due_date", + "fieldtype": "Check", + "label": "Generate New Invoices Past Due Date" } ], - "modified": "2020-08-27 23:30:02.504042", + "modified": "2020-11-29 22:46:14.879289", "modified_by": "Administrator", "module": "Accounts", "name": "Subscription", diff --git a/erpnext/accounts/doctype/subscription/subscription.py b/erpnext/accounts/doctype/subscription/subscription.py index 1eeed1f2faf..ae0059cd3cd 100644 --- a/erpnext/accounts/doctype/subscription/subscription.py +++ b/erpnext/accounts/doctype/subscription/subscription.py @@ -412,7 +412,7 @@ class Subscription(Document): self.update_subscription_period(add_days(self.current_invoice_end, 1)) # Generate invoices periodically even if current invoice are unpaid - if not self.is_current_invoice_generated() and (self.is_postpaid_to_invoice() + if self.generate_new_invoices_past_due_date and not self.is_current_invoice_generated() and (self.is_postpaid_to_invoice() or self.is_prepaid_to_invoice()): prorate = frappe.db.get_single_value('Subscription Settings', 'prorate') self.generate_invoice(prorate) From 39fa5e3266d3afac73c1d3347a6721615e29c475 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Mon, 30 Nov 2020 15:49:00 +0530 Subject: [PATCH 23/57] fix: get formatted value in 'taxes' print template --- erpnext/templates/print_formats/includes/taxes.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/templates/print_formats/includes/taxes.html b/erpnext/templates/print_formats/includes/taxes.html index 6e984f39016..304e845287c 100644 --- a/erpnext/templates/print_formats/includes/taxes.html +++ b/erpnext/templates/print_formats/includes/taxes.html @@ -20,10 +20,10 @@ {%- if (charge.tax_amount or doc.flags.print_taxes_with_zero_amount) and (not charge.included_in_print_rate or doc.flags.show_inclusive_tax_in_print) -%}
-
+ +
- {{ frappe.format_value(frappe.utils.flt(charge.tax_amount), - table_meta.get_field("tax_amount"), doc, currency=doc.currency) }} + {{ charge.get_formatted('tax_amount', doc) }}
{%- endif -%} From e5d4b4c1eda6a643bd5e114370ac057da9ed989b Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Mon, 30 Nov 2020 17:20:51 +0530 Subject: [PATCH 24/57] fix: Depreciation Posting Date is mandatory even if Calculate Depreciation has disabled --- erpnext/assets/doctype/asset/asset.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/erpnext/assets/doctype/asset/asset.js b/erpnext/assets/doctype/asset/asset.js index cb8e439dd02..78d36f14d82 100644 --- a/erpnext/assets/doctype/asset/asset.js +++ b/erpnext/assets/doctype/asset/asset.js @@ -323,7 +323,10 @@ frappe.ui.form.on('Asset', { calculate_depreciation: function(frm) { frm.toggle_reqd("finance_books", frm.doc.calculate_depreciation); - frm.trigger('set_finance_book'); + + if (frm.doc.calculate_depreciation) { + frm.trigger('set_finance_book'); + } }, gross_purchase_amount: function(frm) { From 785579e67b953ed4a5359f932592f7259581f383 Mon Sep 17 00:00:00 2001 From: Afshan Date: Thu, 3 Dec 2020 15:11:34 +0530 Subject: [PATCH 25/57] fix: payroll attendance error --- .../hr/doctype/payroll_entry/payroll_entry.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/erpnext/hr/doctype/payroll_entry/payroll_entry.py b/erpnext/hr/doctype/payroll_entry/payroll_entry.py index f8aad316ed9..868617bc5ec 100644 --- a/erpnext/hr/doctype/payroll_entry/payroll_entry.py +++ b/erpnext/hr/doctype/payroll_entry/payroll_entry.py @@ -379,9 +379,13 @@ class PayrollEntry(Document): employees_to_mark_attendance = [] days_in_payroll, days_holiday, days_attendance_marked = 0, 0, 0 for employee_detail in self.employees: - days_holiday = self.get_count_holidays_of_employee(employee_detail.employee) - days_attendance_marked = self.get_count_employee_attendance(employee_detail.employee) - days_in_payroll = date_diff(self.end_date, self.start_date) + 1 + employee_joining_date = frappe.db.get_value("Employee", employee_detail.employee, 'date_of_joining') + start_date = self.start_date + if employee_joining_date > getdate(self.start_date): + start_date = employee_joining_date + days_holiday = self.get_count_holidays_of_employee(employee_detail.employee, start_date) + days_attendance_marked = self.get_count_employee_attendance(employee_detail.employee, start_date) + days_in_payroll = date_diff(self.end_date, start_date) + 1 if days_in_payroll > days_holiday + days_attendance_marked: employees_to_mark_attendance.append({ "employee": employee_detail.employee, @@ -389,22 +393,22 @@ class PayrollEntry(Document): }) return employees_to_mark_attendance - def get_count_holidays_of_employee(self, employee): + def get_count_holidays_of_employee(self, employee, start_date): holiday_list = get_holiday_list_for_employee(employee) holidays = 0 if holiday_list: days = frappe.db.sql("""select count(*) from tabHoliday where parent=%s and holiday_date between %s and %s""", (holiday_list, - self.start_date, self.end_date)) + start_date, self.end_date)) if days and days[0][0]: holidays = days[0][0] return holidays - def get_count_employee_attendance(self, employee): + def get_count_employee_attendance(self, employee, start_date): marked_days = 0 attendances = frappe.db.sql("""select count(*) from tabAttendance where employee=%s and docstatus=1 and attendance_date between %s and %s""", - (employee, self.start_date, self.end_date)) + (employee, start_date, self.end_date)) if attendances and attendances[0][0]: marked_days = attendances[0][0] return marked_days From 482996f8d11b09f90a3235f81367db0ee61a6779 Mon Sep 17 00:00:00 2001 From: Afshan Date: Thu, 3 Dec 2020 15:14:30 +0530 Subject: [PATCH 26/57] fix: using ORM --- erpnext/hr/doctype/payroll_entry/payroll_entry.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/erpnext/hr/doctype/payroll_entry/payroll_entry.py b/erpnext/hr/doctype/payroll_entry/payroll_entry.py index 868617bc5ec..8fce244e904 100644 --- a/erpnext/hr/doctype/payroll_entry/payroll_entry.py +++ b/erpnext/hr/doctype/payroll_entry/payroll_entry.py @@ -406,9 +406,12 @@ class PayrollEntry(Document): def get_count_employee_attendance(self, employee, start_date): marked_days = 0 - attendances = frappe.db.sql("""select count(*) from tabAttendance where - employee=%s and docstatus=1 and attendance_date between %s and %s""", - (employee, start_date, self.end_date)) + attendances = frappe.get_all("Attendance", + fields = ["count(*)"], + filters = { + "employee": employee, + "attendance_date": ('between', [start_date, self.end_date]) + }, as_list=1) if attendances and attendances[0][0]: marked_days = attendances[0][0] return marked_days From b09da9bcfc0e0e79fcb1c6a734ecaf2c09c4c44a Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Mon, 30 Nov 2020 13:05:28 +0530 Subject: [PATCH 27/57] fix: don't cancel job card if manufacturing entry has made --- .../doctype/job_card/job_card.py | 58 ++++++++++++------- .../doctype/work_order/test_work_order.py | 46 ++++++++++++--- 2 files changed, 74 insertions(+), 30 deletions(-) diff --git a/erpnext/manufacturing/doctype/job_card/job_card.py b/erpnext/manufacturing/doctype/job_card/job_card.py index c7443d911ce..e8e88b15b1e 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.py +++ b/erpnext/manufacturing/doctype/job_card/job_card.py @@ -10,6 +10,7 @@ from frappe.model.mapper import get_mapped_doc from frappe.model.document import Document class OperationMismatchError(frappe.ValidationError): pass +class JobCardCancelError(frappe.ValidationError): pass class JobCard(Document): def validate(self): @@ -110,39 +111,54 @@ class JobCard(Document): for_quantity, time_in_mins = 0, 0 from_time_list, to_time_list = [], [] - field = "operation_id" data = frappe.get_all('Job Card', fields = ["sum(total_time_in_mins) as time_in_mins", "sum(total_completed_qty) as completed_qty"], - filters = {"docstatus": 1, "work_order": self.work_order, field: self.get(field)}) + filters = {"docstatus": 1, "work_order": self.work_order, "operation_id": self.operation_id}) if data and len(data) > 0: - for_quantity = data[0].completed_qty - time_in_mins = data[0].time_in_mins + for_quantity = flt(data[0].completed_qty) + time_in_mins = flt(data[0].time_in_mins) - if self.get(field): - time_data = frappe.db.sql(""" + wo = frappe.get_doc('Work Order', self.work_order) + if self.operation_id: + self.validate_produced_quantity(for_quantity, wo) + self.update_work_order_data(for_quantity, time_in_mins, wo) + + def validate_produced_quantity(self, for_quantity, wo): + if self.docstatus < 2: return + + if wo.produced_qty > for_quantity: + first_part_msg = (_("The {0} {1} is used to calculate the valuation cost for the finished good {2}.") + .format(frappe.bold(_("Job Card")), frappe.bold(self.name), frappe.bold(self.production_item))) + + second_part_msg = (_("Kindly cancel the Manufacturing Entries first against the work order {0}.") + .format(frappe.bold(get_link_to_form("Work Order", self.work_order)))) + + frappe.throw(_("{0} {1}").format(first_part_msg, second_part_msg), + JobCardCancelError, title = _("Error")) + + def update_work_order_data(self, for_quantity, time_in_mins, wo): + time_data = frappe.db.sql(""" SELECT min(from_time) as start_time, max(to_time) as end_time FROM `tabJob Card` jc, `tabJob Card Time Log` jctl WHERE jctl.parent = jc.name and jc.work_order = %s - and jc.{0} = %s and jc.docstatus = 1 - """.format(field), (self.work_order, self.get(field)), as_dict=1) + and jc.operation_id = %s and jc.docstatus = 1 + """, (self.work_order, self.operation_id), as_dict=1) - wo = frappe.get_doc('Work Order', self.work_order) + for data in wo.operations: + if data.get("name") == self.operation_id: + data.completed_qty = for_quantity + data.actual_operation_time = time_in_mins + data.actual_start_time = time_data[0].start_time if time_data else None + data.actual_end_time = time_data[0].end_time if time_data else None - for data in wo.operations: - if data.get("name") == self.get(field): - data.completed_qty = for_quantity - data.actual_operation_time = time_in_mins - data.actual_start_time = time_data[0].start_time if time_data else None - data.actual_end_time = time_data[0].end_time if time_data else None - - wo.flags.ignore_validate_update_after_submit = True - wo.update_operation_status() - wo.calculate_operating_cost() - wo.set_actual_dates() - wo.save() + wo.flags.ignore_validate_update_after_submit = True + wo.update_operation_status() + wo.calculate_operating_cost() + wo.set_actual_dates() + wo.save() def set_transferred_qty(self, update_status=False): if not self.items: diff --git a/erpnext/manufacturing/doctype/work_order/test_work_order.py b/erpnext/manufacturing/doctype/work_order/test_work_order.py index 0263102bac0..c33035ec9af 100644 --- a/erpnext/manufacturing/doctype/work_order/test_work_order.py +++ b/erpnext/manufacturing/doctype/work_order/test_work_order.py @@ -5,16 +5,17 @@ from __future__ import unicode_literals import unittest import frappe -from frappe.utils import flt, time_diff_in_hours, now, add_days, cint +from frappe.utils import flt, now, cint, add_to_date from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import set_perpetual_inventory -from erpnext.manufacturing.doctype.work_order.work_order \ - import make_stock_entry, ItemHasVariantError, stop_unstop, StockOverProductionError, OverProductionError from erpnext.stock.doctype.stock_entry import test_stock_entry from erpnext.stock.utils import get_bin from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order from erpnext.stock.doctype.item.test_item import make_item from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse +from erpnext.manufacturing.doctype.work_order.work_order import (make_stock_entry, + ItemHasVariantError, stop_unstop, StockOverProductionError, OverProductionError) +from erpnext.manufacturing.doctype.job_card.job_card import JobCardCancelError class TestWorkOrder(unittest.TestCase): def setUp(self): @@ -374,14 +375,41 @@ class TestWorkOrder(unittest.TestCase): data = frappe.get_cached_value('BOM', {'docstatus': 1, 'with_operations': 1, 'company': '_Test Company'}, ['name', 'item']) - if data: - bom, bom_item = data + bom, bom_item = data - bom_doc = frappe.get_doc('BOM', bom) - work_order = make_wo_order_test_record(item=bom_item, qty=1, bom_no=bom) + bom_doc = frappe.get_doc('BOM', bom) + work_order = make_wo_order_test_record(item=bom_item, qty=1, + bom_no=bom, source_warehouse="_Test Warehouse - _TC") - job_cards = frappe.get_all('Job Card', filters = {'work_order': work_order.name}) - self.assertEqual(len(job_cards), len(bom_doc.operations)) + for row in work_order.required_items: + test_stock_entry.make_stock_entry(item_code=row.item_code, + target="_Test Warehouse - _TC", qty=row.required_qty, basic_rate=100) + + ste = frappe.get_doc(make_stock_entry(work_order.name, "Material Transfer for Manufacture", 1)) + ste.submit() + + job_cards = frappe.get_all('Job Card', filters = {'work_order': work_order.name}) + self.assertEqual(len(job_cards), len(bom_doc.operations)) + + for i, job_card in enumerate(job_cards): + doc = frappe.get_doc("Job Card", job_card) + doc.append("time_logs", { + "from_time": now(), + "hours": i, + "to_time": add_to_date(now(), i), + "completed_qty": doc.for_quantity + }) + doc.submit() + + ste1 = frappe.get_doc(make_stock_entry(work_order.name, "Manufacture", 1)) + ste1.submit() + + for job_card in job_cards: + doc = frappe.get_doc("Job Card", job_card) + self.assertRaises(JobCardCancelError, doc.cancel) + + ste1.cancel() + ste.cancel() def test_work_order_with_non_transfer_item(self): items = {'Finished Good Transfer Item': 1, '_Test FG Item': 1, '_Test FG Item 1': 0} From 29f92e0405c63dce8ffbd632aa874b289d5c8467 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Fri, 4 Dec 2020 00:47:46 +0530 Subject: [PATCH 28/57] fix: pricing rule with transaction not working for additional product --- .../doctype/pricing_rule/pricing_rule.json | 5 ++- .../doctype/pricing_rule/test_pricing_rule.py | 34 ++++++++++++++++--- .../accounts/doctype/pricing_rule/utils.py | 29 +++++++++++----- 3 files changed, 54 insertions(+), 14 deletions(-) diff --git a/erpnext/accounts/doctype/pricing_rule/pricing_rule.json b/erpnext/accounts/doctype/pricing_rule/pricing_rule.json index 29d83783d07..925fe7fcc36 100644 --- a/erpnext/accounts/doctype/pricing_rule/pricing_rule.json +++ b/erpnext/accounts/doctype/pricing_rule/pricing_rule.json @@ -404,6 +404,7 @@ "fieldtype": "Column Break" }, { + "default": "0", "depends_on": "eval:doc.rate_or_discount==\"Rate\"", "fieldname": "rate", "fieldtype": "Currency", @@ -467,6 +468,7 @@ "options": "UOM" }, { + "description": "If rate is zero them item will be treated as \"Free Item\"", "fieldname": "free_item_rate", "fieldtype": "Currency", "label": "Rate" @@ -554,7 +556,8 @@ ], "icon": "fa fa-gift", "idx": 1, - "modified": "2019-12-18 17:29:22.957077", + "links": [], + "modified": "2020-12-04 00:36:24.698219", "modified_by": "Administrator", "module": "Accounts", "name": "Pricing Rule", diff --git a/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py b/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py index 1e706a8099c..8e819d634cb 100644 --- a/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py +++ b/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py @@ -467,6 +467,22 @@ class TestPricingRule(unittest.TestCase): frappe.get_doc("Item Price", {"item_code": "Water Flask"}).delete() item.delete() + def test_pricing_rule_for_transaction(self): + make_item("Water Flask 1") + frappe.delete_doc_if_exists('Pricing Rule', '_Test Pricing Rule') + make_pricing_rule(selling=1, min_qty=5, price_or_product_discount="Product", + apply_on="Transaction", free_item="Water Flask 1", free_qty=1, free_item_rate=10) + + si = create_sales_invoice(qty=5, do_not_submit=True) + self.assertEquals(len(si.items), 2) + self.assertEquals(si.items[1].rate, 10) + + si1 = create_sales_invoice(qty=2, do_not_submit=True) + self.assertEquals(len(si1.items), 1) + + for doc in [si, si1]: + doc.delete() + def make_pricing_rule(**args): args = frappe._dict(args) @@ -484,15 +500,23 @@ def make_pricing_rule(**args): "rate_or_discount": args.rate_or_discount or "Discount Percentage", "discount_percentage": args.discount_percentage or 0.0, "rate": args.rate or 0.0, - "margin_type": args.margin_type, - "margin_rate_or_amount": args.margin_rate_or_amount or 0.0 + "margin_rate_or_amount": args.margin_rate_or_amount or 0.0, + "condition": args.condition or '', + "apply_multiple_pricing_rules": args.apply_multiple_pricing_rules or 0 }) + for field in ["free_item", "free_qty", "free_item_rate", "priority", + "margin_type", "price_or_product_discount"]: + if args.get(field): + doc.set(field, args.get(field)) + apply_on = doc.apply_on.replace(' ', '_').lower() child_table = {'Item Code': 'items', 'Item Group': 'item_groups', 'Brand': 'brands'} - doc.append(child_table.get(doc.apply_on), { - apply_on: args.get(apply_on) or "_Test Item" - }) + + if doc.apply_on != "Transaction": + doc.append(child_table.get(doc.apply_on), { + apply_on: args.get(apply_on) or "_Test Item" + }) doc.insert(ignore_permissions=True) if args.get(apply_on) and apply_on != "item_code": diff --git a/erpnext/accounts/doctype/pricing_rule/utils.py b/erpnext/accounts/doctype/pricing_rule/utils.py index 27541670a89..fb2f9ff8991 100644 --- a/erpnext/accounts/doctype/pricing_rule/utils.py +++ b/erpnext/accounts/doctype/pricing_rule/utils.py @@ -453,6 +453,9 @@ def apply_pricing_rule_on_transaction(doc): pricing_rules = filter_pricing_rules_for_qty_amount(doc.total_qty, doc.total, pricing_rules) + if not pricing_rules: + remove_free_item(doc) + for d in pricing_rules: if d.price_or_product_discount == 'Price': if d.apply_discount_on: @@ -476,6 +479,12 @@ def apply_pricing_rule_on_transaction(doc): get_product_discount_rule(d, item_details, doc=doc) apply_pricing_rule_for_free_items(doc, item_details.free_item_data) doc.set_missing_values() + doc.calculate_taxes_and_totals() + +def remove_free_item(doc): + for d in doc.items: + if d.is_free_item: + doc.remove(d) def get_applied_pricing_rules(pricing_rules): if pricing_rules: @@ -488,7 +497,7 @@ def get_applied_pricing_rules(pricing_rules): def get_product_discount_rule(pricing_rule, item_details, args=None, doc=None): free_item = pricing_rule.free_item - if pricing_rule.same_item: + if pricing_rule.same_item and pricing_rule.get("apply_on") != 'Transaction': free_item = item_details.item_code or args.item_code if not free_item: @@ -517,13 +526,17 @@ def get_product_discount_rule(pricing_rule, item_details, args=None, doc=None): if item_details.get("parenttype") == 'Sales Order': item_details.free_item_data['delivery_date'] = doc.delivery_date if doc else today() - company = args.get('company') or doc.company - item_details.free_item_data['income_account'] = get_default_income_account( - args=args, - item=get_item_defaults(free_item, company), - item_group=get_item_group_defaults(free_item, company), - brand=get_brand_defaults(free_item, company), - ) + company = doc.company + if args and args.get("company"): + company = args.get("company") + + if args: + item_details.free_item_data['income_account'] = get_default_income_account( + args=args, + item=get_item_defaults(free_item, company), + item_group=get_item_group_defaults(free_item, company), + brand=get_brand_defaults(free_item, company), + ) def apply_pricing_rule_for_free_items(doc, pricing_rule_args, set_missing_values=False): if pricing_rule_args.get('item_code'): From 26723d10af172dcf031580cdfce8b40cd52c9ecb Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Sun, 6 Dec 2020 17:21:30 +0530 Subject: [PATCH 29/57] fix: delete Receive at Warehouse entry on cancellation of Send to Warehouse entry --- erpnext/stock/doctype/stock_entry/stock_entry.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index 0148c165f62..b33b4be1979 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -117,6 +117,7 @@ class StockEntry(StockController): self.update_transferred_qty() self.update_quality_inspection() self.delete_auto_created_batches() + self.delete_linked_stock_entry() def set_job_card_data(self): if self.job_card and not self.work_order: @@ -160,6 +161,12 @@ class StockEntry(StockController): frappe.throw(_("For job card {0}, you can only make the 'Material Transfer for Manufacture' type stock entry") .format(self.job_card)) + def delete_linked_stock_entry(self): + if self.purpose == "Send to Warehouse": + for d in frappe.get_all("Stock Entry", filters={"docstatus": 0, + "outgoing_stock_entry": self.name, "purpose": "Receive at Warehouse"}): + frappe.delete_doc("Stock Entry", d.name) + def set_transfer_qty(self): for item in self.get("items"): if not flt(item.qty): From 5b08241dc61709fc0467b6752625714fa59b25b6 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sun, 6 Dec 2020 18:28:30 +0530 Subject: [PATCH 30/57] fix: Show tax amount in base currencies --- .../doctype/gstr_3b_report/gstr_3b_report.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py b/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py index 25c30acba76..9e841483554 100644 --- a/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py +++ b/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py @@ -192,19 +192,20 @@ class GSTR3BReport(Document): for d in self.report_dict["itc_elg"]["itc_avl"]: itc_type = itc_type_map.get(d["ty"]) - gst_category = ["Registered Regular"] if d["ty"] == 'ISRC': - reverse_charge = "Y" + reverse_charge = ["Y"] itc_type = 'All Other ITC' gst_category = ['Unregistered', 'Overseas', 'Registered Regular'] else: - reverse_charge = "N" + gst_category = ['Unregistered', 'Overseas', 'Registered Regular'] + reverse_charge = ["N", "Y"] for account_head in self.account_heads: for category in gst_category: - for key in [['iamt', 'igst_account'], ['camt', 'cgst_account'], ['samt', 'sgst_account'], ['csamt', 'cess_account']]: - d[key[0]] += flt(itc_details.get((category, itc_type, reverse_charge, account_head.get(key[1])), {}).get("amount"), 2) + for charge_type in reverse_charge: + for key in [['iamt', 'igst_account'], ['camt', 'cgst_account'], ['samt', 'sgst_account'], ['csamt', 'cess_account']]: + d[key[0]] += flt(itc_details.get((category, itc_type, charge_type, account_head.get(key[1])), {}).get("amount"), 2) for key in ['iamt', 'camt', 'samt', 'csamt']: net_itc[key] += flt(d[key], 2) @@ -264,7 +265,8 @@ class GSTR3BReport(Document): def get_itc_details(self): itc_amount = frappe.db.sql(""" - select s.gst_category, sum(t.tax_amount_after_discount_amount) as tax_amount, t.account_head, s.eligibility_for_itc, s.reverse_charge + select s.gst_category, sum(t.base_tax_amount_after_discount_amount) as tax_amount, + t.account_head, s.eligibility_for_itc, s.reverse_charge from `tabPurchase Invoice` s , `tabPurchase Taxes and Charges` t where s.docstatus = 1 and t.parent = s.name and month(s.posting_date) = %s and year(s.posting_date) = %s and s.company = %s @@ -388,7 +390,7 @@ class GSTR3BReport(Document): tax_template = 'Purchase Taxes and Charges' tax_amounts = frappe.db.sql(""" - select s.gst_category, sum(t.tax_amount_after_discount_amount) as tax_amount, t.account_head + select s.gst_category, sum(t.base_tax_amount_after_discount_amount) as tax_amount, t.account_head from `tab{doctype}` s , `tab{template}` t where s.docstatus = 1 and t.parent = s.name and s.reverse_charge = %s and month(s.posting_date) = %s and year(s.posting_date) = %s and s.company = %s From 7459dc410544436d7bb8e895602a90888a3f9df3 Mon Sep 17 00:00:00 2001 From: Afshan <33727827+AfshanKhan@users.noreply.github.com> Date: Tue, 8 Dec 2020 09:28:01 +0530 Subject: [PATCH 31/57] fix: po orverride (#24023) * fix: po orverride * fix: po * fix: spelling error * fix: refactor condition * fix: condition --- erpnext/controllers/selling_controller.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/erpnext/controllers/selling_controller.py b/erpnext/controllers/selling_controller.py index b2cc723ced5..5b7d76d0f08 100644 --- a/erpnext/controllers/selling_controller.py +++ b/erpnext/controllers/selling_controller.py @@ -42,7 +42,7 @@ class SellingController(StockController): self.validate_max_discount() self.validate_selling_price() self.set_qty_as_per_stock_uom() - self.set_po_nos() + self.set_po_nos(for_validate=True) self.set_gross_profit() set_default_income_account_for_item(self) self.set_customer_address() @@ -364,20 +364,28 @@ class SellingController(StockController): })) self.make_sl_entries(sl_entries) - def set_po_nos(self): + def set_po_nos(self, for_validate=False): if self.doctype == 'Sales Invoice' and hasattr(self, "items"): + if for_validate and self.po_no: + return self.set_pos_for_sales_invoice() if self.doctype == 'Delivery Note' and hasattr(self, "items"): + if for_validate and self.po_no: + return self.set_pos_for_delivery_note() def set_pos_for_sales_invoice(self): po_nos = [] + if self.po_no: + po_nos.append(self.po_no) self.get_po_nos('Sales Order', 'sales_order', po_nos) self.get_po_nos('Delivery Note', 'delivery_note', po_nos) self.po_no = ', '.join(list(set(x.strip() for x in ','.join(po_nos).split(',')))) def set_pos_for_delivery_note(self): po_nos = [] + if self.po_no: + po_nos.append(self.po_no) self.get_po_nos('Sales Order', 'against_sales_order', po_nos) self.get_po_nos('Sales Invoice', 'against_sales_invoice', po_nos) self.po_no = ', '.join(list(set(x.strip() for x in ','.join(po_nos).split(',')))) From e39dd079c4719cb6fc1de9268eaa09c59c779a3c Mon Sep 17 00:00:00 2001 From: Afshan <33727827+AfshanKhan@users.noreply.github.com> Date: Tue, 8 Dec 2020 09:32:08 +0530 Subject: [PATCH 32/57] fix: double exception in payroll (#24080) --- erpnext/hr/doctype/payroll_entry/payroll_entry.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/erpnext/hr/doctype/payroll_entry/payroll_entry.py b/erpnext/hr/doctype/payroll_entry/payroll_entry.py index 8fce244e904..49ec828c979 100644 --- a/erpnext/hr/doctype/payroll_entry/payroll_entry.py +++ b/erpnext/hr/doctype/payroll_entry/payroll_entry.py @@ -302,7 +302,9 @@ class PayrollEntry(Document): jv_name = journal_entry.name self.update_salary_slip_status(jv_name = jv_name) except Exception as e: - frappe.msgprint(e) + if type(e) in (str, list, tuple): + frappe.msgprint(e) + raise return jv_name From 51015178dad662d05be3cb6feb04e25b41b1305d Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Tue, 8 Dec 2020 09:34:22 +0530 Subject: [PATCH 33/57] fix(POS): do not fetch items until POS Profile is set (#24076) --- erpnext/selling/page/point_of_sale/point_of_sale.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/erpnext/selling/page/point_of_sale/point_of_sale.js b/erpnext/selling/page/point_of_sale/point_of_sale.js index f1728b7afda..a4a39ca4d03 100644 --- a/erpnext/selling/page/point_of_sale/point_of_sale.js +++ b/erpnext/selling/page/point_of_sale/point_of_sale.js @@ -1515,6 +1515,9 @@ class POSItems { } get_items({start = 0, page_length = 40, search_value='', item_group=this.parent_item_group}={}) { + if (!this.frm.doc.pos_profile) + return; + const price_list = this.frm.doc.selling_price_list; return new Promise(res => { frappe.call({ From b83e131b1f0eef6a9bb1ac91adfa56c627c242ff Mon Sep 17 00:00:00 2001 From: Afshan <33727827+AfshanKhan@users.noreply.github.com> Date: Tue, 8 Dec 2020 09:45:14 +0530 Subject: [PATCH 34/57] fix: throw an error when no pos profile exist (#24026) * fix: throw an error when no pos profile exist * fix: tests * fix: tests * fix:test --- .../doctype/bank_transaction/test_bank_transaction.py | 5 +++++ erpnext/accounts/doctype/sales_invoice/sales_invoice.py | 2 ++ .../accounts/doctype/sales_invoice/test_sales_invoice.py | 9 ++++++--- .../healthcare/doctype/fee_validity/test_fee_validity.py | 2 ++ 4 files changed, 15 insertions(+), 3 deletions(-) diff --git a/erpnext/accounts/doctype/bank_transaction/test_bank_transaction.py b/erpnext/accounts/doctype/bank_transaction/test_bank_transaction.py index 0fe57c32399..1aee0fd2b00 100644 --- a/erpnext/accounts/doctype/bank_transaction/test_bank_transaction.py +++ b/erpnext/accounts/doctype/bank_transaction/test_bank_transaction.py @@ -9,11 +9,13 @@ from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sal from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry from erpnext.accounts.page.bank_reconciliation.bank_reconciliation import reconcile, get_linked_payments +from erpnext.accounts.doctype.pos_profile.test_pos_profile import make_pos_profile test_dependencies = ["Item", "Cost Center"] class TestBankTransaction(unittest.TestCase): def setUp(self): + make_pos_profile() add_transactions() add_payments() @@ -27,6 +29,9 @@ class TestBankTransaction(unittest.TestCase): frappe.db.sql("""delete from `tabPayment Entry Reference`""") frappe.db.sql("""delete from `tabPayment Entry`""") + # Delete POS Profile + frappe.db.sql("delete from `tabPOS Profile`") + frappe.flags.test_bank_transactions_created = False frappe.flags.test_payments_created = False diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 2f8b782356d..1243145e982 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -398,6 +398,8 @@ class SalesInvoice(SellingController): from erpnext.stock.get_item_details import get_pos_profile_item_details, get_pos_profile 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")) self.pos_profile = pos_profile.get('name') pos = {} diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index 26f488d8c9a..4a6a771d099 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -690,7 +690,8 @@ class TestSalesInvoice(unittest.TestCase): self.assertFalse(gle) def test_pos_gl_entry_with_perpetual_inventory(self): - make_pos_profile() + make_pos_profile(company="_Test Company with perpetual inventory", income_account = "Sales - TCP1", + expense_account = "Cost of Goods Sold - TCP1", warehouse="Stores - TCP1", cost_center = "Main - TCP1", write_off_account="_Test Write Off - TCP1") pr = make_purchase_receipt(company= "_Test Company with perpetual inventory",supplier_warehouse= "Work In Progress - TCP1", item_code= "_Test FG Item",warehouse= "Stores - TCP1",cost_center= "Main - TCP1") @@ -773,7 +774,8 @@ class TestSalesInvoice(unittest.TestCase): def test_pos_change_amount(self): - make_pos_profile() + make_pos_profile(company="_Test Company with perpetual inventory", income_account = "Sales - TCP1", + expense_account = "Cost of Goods Sold - TCP1", warehouse="Stores - TCP1", cost_center = "Main - TCP1", write_off_account="_Test Write Off - TCP1") pr = make_purchase_receipt(company= "_Test Company with perpetual inventory",supplier_warehouse= "Work In Progress - TCP1", item_code= "_Test FG Item",warehouse= "Stores - TCP1",cost_center= "Main - TCP1") @@ -795,7 +797,8 @@ class TestSalesInvoice(unittest.TestCase): def test_make_pos_invoice(self): from erpnext.accounts.doctype.sales_invoice.pos import make_invoice - pos_profile = make_pos_profile() + pos_profile = make_pos_profile(company="_Test Company with perpetual inventory", income_account = "Sales - TCP1", + expense_account = "Cost of Goods Sold - TCP1", warehouse="Stores - TCP1", cost_center = "Main - TCP1", write_off_account="_Test Write Off - TCP1") pr = make_purchase_receipt(company= "_Test Company with perpetual inventory",supplier_warehouse= "Work In Progress - TCP1", item_code= "_Test FG Item",warehouse= "Stores - TCP1",cost_center= "Main - TCP1") pos = create_sales_invoice(company= "_Test Company with perpetual inventory", debit_to="Debtors - TCP1", item_code= "_Test FG Item", warehouse="Stores - TCP1", income_account = "Sales - TCP1", expense_account = "Cost of Goods Sold - TCP1", cost_center = "Main - TCP1", do_not_save=True) diff --git a/erpnext/healthcare/doctype/fee_validity/test_fee_validity.py b/erpnext/healthcare/doctype/fee_validity/test_fee_validity.py index 26b14504630..b2828cf857f 100644 --- a/erpnext/healthcare/doctype/fee_validity/test_fee_validity.py +++ b/erpnext/healthcare/doctype/fee_validity/test_fee_validity.py @@ -7,6 +7,7 @@ import frappe import unittest from frappe.utils.make_random import get_random from frappe.utils import nowdate, add_days, getdate +from erpnext.accounts.doctype.pos_profile.test_pos_profile import make_pos_profile test_dependencies = ["Company"] @@ -14,6 +15,7 @@ class TestFeeValidity(unittest.TestCase): def test_fee_validity(self): frappe.db.sql("""delete from `tabPatient Appointment`""") frappe.db.sql("""delete from `tabFee Validity`""") + make_pos_profile() patient = get_random("Patient") practitioner = get_random("Healthcare Practitioner") department = get_random("Medical Department") From 4fe48a30c4136017ad7587f59f63865e0cfd1cb3 Mon Sep 17 00:00:00 2001 From: pateljannat Date: Tue, 8 Dec 2020 12:04:56 +0530 Subject: [PATCH 35/57] fix: sales invoice add button on sales order dashboard --- erpnext/selling/doctype/sales_order/sales_order.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/selling/doctype/sales_order/sales_order.js b/erpnext/selling/doctype/sales_order/sales_order.js index 6a9e43e273a..d19caec1929 100644 --- a/erpnext/selling/doctype/sales_order/sales_order.js +++ b/erpnext/selling/doctype/sales_order/sales_order.js @@ -8,7 +8,7 @@ frappe.ui.form.on("Sales Order", { frm.custom_make_buttons = { 'Delivery Note': 'Delivery Note', 'Pick List': 'Pick List', - 'Sales Invoice': 'Invoice', + 'Sales Invoice': 'Sales Invoice', 'Material Request': 'Material Request', 'Purchase Order': 'Purchase Order', 'Project': 'Project', From cfb6e3aa30aadb425f0655548e53b174953af0ed Mon Sep 17 00:00:00 2001 From: prssanna Date: Tue, 8 Dec 2020 16:38:16 +0530 Subject: [PATCH 36/57] fix: allow add to cart for any item if allow_items_not_in_stock is enabled --- erpnext/portal/product_configurator/utils.py | 4 +++- erpnext/templates/generators/item/item_configure.js | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/erpnext/portal/product_configurator/utils.py b/erpnext/portal/product_configurator/utils.py index a3fe31b465d..ea4d4f44109 100644 --- a/erpnext/portal/product_configurator/utils.py +++ b/erpnext/portal/product_configurator/utils.py @@ -249,6 +249,7 @@ def get_next_attribute_and_values(item_code, selected_attributes): optional_attributes = item_cache.get_optional_attributes() exact_match = [] + allow_items_not_in_stock = False # search for exact match if all selected attributes are required attributes if len(selected_attributes.keys()) >= (len(attribute_list) - len(optional_attributes)): item_attribute_value_map = item_cache.get_item_attribute_value_map() @@ -263,7 +264,7 @@ def get_next_attribute_and_values(item_code, selected_attributes): if exact_match: data = get_product_info_for_website(exact_match[0]) product_info = data.product_info - product_info["allow_items_not_in_stock"] = cint(data.cart_settings.allow_items_not_in_stock) + allow_items_not_in_stock = cint(data.cart_settings.allow_items_not_in_stock) if not data.cart_settings.show_price: product_info = None else: @@ -275,6 +276,7 @@ def get_next_attribute_and_values(item_code, selected_attributes): 'filtered_items_count': filtered_items_count, 'filtered_items': filtered_items if filtered_items_count < 10 else [], 'exact_match': exact_match, + 'allow_items_not_in_stock': allow_items_not_in_stock, 'product_info': product_info } diff --git a/erpnext/templates/generators/item/item_configure.js b/erpnext/templates/generators/item/item_configure.js index 163c955c566..868437a7dcb 100644 --- a/erpnext/templates/generators/item/item_configure.js +++ b/erpnext/templates/generators/item/item_configure.js @@ -186,7 +186,7 @@ class ItemConfigure { this.dialog.$status_area.empty(); } - get_html_for_item_found({ filtered_items_count, filtered_items, exact_match, product_info }) { + get_html_for_item_found({ filtered_items_count, filtered_items, exact_match, product_info, allow_items_not_in_stock }) { const exact_match_message = __('1 exact match.'); const one_item = exact_match.length === 1 ? exact_match[0] : @@ -194,7 +194,7 @@ class ItemConfigure { filtered_items[0] : ''; // Allow Add to Cart if adding out of stock items enabled in Shopping Cart else check stock. - const in_stock = product_info.allow_items_not_in_stock ? 1 : product_info.in_stock; + const in_stock = allow_items_not_in_stock ? 1 : product_info && product_info.in_stock; const add_to_cart = `${__('Add to cart')}`; const product_action = in_stock ? add_to_cart : `${__('Not in Stock')}`; From e7476914cd5d3635be92210ed98cc1b4525f1fc7 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Tue, 24 Nov 2020 23:23:00 +0530 Subject: [PATCH 37/57] fix: incorrect stock quantity if 'Allow Multiple Material Consumption' has enabled --- .../doctype/work_order/test_work_order.py | 33 +++++++++++++++++++ .../doctype/work_order/work_order.js | 3 +- .../doctype/work_order/work_order.py | 2 +- .../stock/doctype/stock_entry/stock_entry.js | 5 +++ .../stock/doctype/stock_entry/stock_entry.py | 30 ++++++++--------- 5 files changed, 55 insertions(+), 18 deletions(-) diff --git a/erpnext/manufacturing/doctype/work_order/test_work_order.py b/erpnext/manufacturing/doctype/work_order/test_work_order.py index 0263102bac0..e4edac56bb1 100644 --- a/erpnext/manufacturing/doctype/work_order/test_work_order.py +++ b/erpnext/manufacturing/doctype/work_order/test_work_order.py @@ -455,6 +455,39 @@ class TestWorkOrder(unittest.TestCase): work_order1.save() self.assertEqual(work_order1.operations[0].time_in_mins, 40.0) + def test_partial_material_consumption(self): + frappe.db.set_value("Manufacturing Settings", None, "material_consumption", 1) + wo_order = make_wo_order_test_record(planned_start_date=now(), qty=4) + + ste_cancel_list = [] + ste1 = test_stock_entry.make_stock_entry(item_code="_Test Item", + target="_Test Warehouse - _TC", qty=20, basic_rate=5000.0) + ste2 = test_stock_entry.make_stock_entry(item_code="_Test Item Home Desktop 100", + target="_Test Warehouse - _TC", qty=20, basic_rate=1000.0) + + ste_cancel_list.extend([ste1, ste2]) + + s = frappe.get_doc(make_stock_entry(wo_order.name, "Material Transfer for Manufacture", 4)) + s.submit() + ste_cancel_list.append(s) + + ste1 = frappe.get_doc(make_stock_entry(wo_order.name, "Manufacture", 2)) + ste1.submit() + ste_cancel_list.append(ste1) + + print(wo_order.name) + ste3 = frappe.get_doc(make_stock_entry(wo_order.name, "Material Consumption for Manufacture", 2)) + self.assertEquals(ste3.fg_completed_qty, 2) + + expected_qty = {"_Test Item": 2, "_Test Item Home Desktop 100": 4} + for row in ste3.items: + self.assertEquals(row.qty, expected_qty.get(row.item_code)) + + for ste_doc in ste_cancel_list: + ste_doc.cancel() + + frappe.db.set_value("Manufacturing Settings", None, "material_consumption", 0) + def get_scrap_item_details(bom_no): scrap_items = {} for item in frappe.db.sql("""select item_code, stock_qty from `tabBOM Scrap Item` diff --git a/erpnext/manufacturing/doctype/work_order/work_order.js b/erpnext/manufacturing/doctype/work_order/work_order.js index f9c028563bb..13aceaa3601 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.js +++ b/erpnext/manufacturing/doctype/work_order/work_order.js @@ -521,7 +521,8 @@ erpnext.work_order = { var tbl = frm.doc.required_items || []; var tbl_lenght = tbl.length; for (var i = 0, len = tbl_lenght; i < len; i++) { - if (flt(frm.doc.required_items[i].required_qty) > flt(frm.doc.required_items[i].consumed_qty)) { + let wo_item_qty = frm.doc.required_items[i].transferred_qty || frm.doc.required_items[i].required_qty; + if (flt(wo_item_qty) > flt(frm.doc.required_items[i].consumed_qty)) { counter += 1; } } diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py index 603c8d4928c..05c2e26b9ca 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.py +++ b/erpnext/manufacturing/doctype/work_order/work_order.py @@ -529,7 +529,7 @@ class WorkOrder(Document): and (entry.purpose = "Material Consumption for Manufacture" or entry.purpose = "Manufacture") and entry.docstatus = 1 - and detail.parent = entry.name + and detail.parent = entry.name and IFNULL(t_warehouse, "") = "" and (detail.item_code = %(item)s or detail.original_item = %(item)s)''', { 'name': self.name, 'item': d.item_code diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.js b/erpnext/stock/doctype/stock_entry/stock_entry.js index fd921cef4e9..8178782c006 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.js +++ b/erpnext/stock/doctype/stock_entry/stock_entry.js @@ -736,6 +736,10 @@ erpnext.stock.StockEntry = erpnext.stock.StockController.extend({ } }, + fg_completed_qty: function() { + this.get_items(); + }, + get_items: function() { var me = this; if(!this.frm.doc.fg_completed_qty || !this.frm.doc.bom_no) @@ -745,6 +749,7 @@ erpnext.stock.StockEntry = erpnext.stock.StockController.extend({ // if work order / bom is mentioned, get items return this.frm.call({ doc: me.frm.doc, + freeze: true, method: "get_items", callback: function(r) { if(!r.exc) refresh_field("items"); diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index 1dd022fce00..fb776cc2e63 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -1003,26 +1003,22 @@ class StockEntry(StockController): wo = frappe.get_doc("Work Order", self.work_order) wo_items = frappe.get_all('Work Order Item', filters={'parent': self.work_order}, - fields=["item_code", "required_qty", "consumed_qty"] + fields=["item_code", "required_qty", "consumed_qty", "transferred_qty"] ) + work_order_qty = wo.material_transferred_for_manufacturing or wo.qty for item in wo_items: - qty = item.required_qty - item_account_details = get_item_defaults(item.item_code, self.company) # Take into account consumption if there are any. - if self.purpose == 'Manufacture': - req_qty_each = flt(item.required_qty / wo.qty) - if (flt(item.consumed_qty) != 0): - remaining_qty = flt(item.consumed_qty) - (flt(wo.produced_qty) * req_qty_each) - exhaust_qty = req_qty_each * wo.produced_qty - if remaining_qty > exhaust_qty : - if (remaining_qty/(req_qty_each * flt(self.fg_completed_qty))) >= 1: - qty =0 - else: - qty = (req_qty_each * flt(self.fg_completed_qty)) - remaining_qty - else: - qty = req_qty_each * flt(self.fg_completed_qty) + + wo_item_qty = item.transferred_qty or item.required_qty + + req_qty_each = ( + (flt(wo_item_qty) - flt(item.consumed_qty)) / + (flt(work_order_qty) - flt(wo.produced_qty)) + ) + + qty = req_qty_each * flt(self.fg_completed_qty) if qty > 0: self.add_to_stock_entry_detail({ @@ -1108,13 +1104,15 @@ class StockEntry(StockController): else: qty = req_qty_each * flt(self.fg_completed_qty) - elif backflushed_materials.get(item.item_code): for d in backflushed_materials.get(item.item_code): if d.get(item.warehouse): if (qty > req_qty): qty = (qty/trans_qty) * flt(self.fg_completed_qty) + if consumed_qty: + qty -= consumed_qty + if cint(frappe.get_cached_value('UOM', item.stock_uom, 'must_be_whole_number')): qty = frappe.utils.ceil(qty) From eed12444e1d1b94d06894b16e829a4f3d4ff8735 Mon Sep 17 00:00:00 2001 From: prssanna Date: Thu, 10 Dec 2020 12:19:14 +0530 Subject: [PATCH 38/57] fix: function imports in account_balance_timeline.py --- .../account_balance_timeline/account_balance_timeline.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/dashboard_chart_source/account_balance_timeline/account_balance_timeline.py b/erpnext/accounts/dashboard_chart_source/account_balance_timeline/account_balance_timeline.py index a9b3d7c4cc2..71a23d30b57 100644 --- a/erpnext/accounts/dashboard_chart_source/account_balance_timeline/account_balance_timeline.py +++ b/erpnext/accounts/dashboard_chart_source/account_balance_timeline/account_balance_timeline.py @@ -6,8 +6,8 @@ import frappe, json from frappe import _ from frappe.utils import add_to_date, date_diff, getdate, nowdate, get_last_day, formatdate, get_link_to_form from erpnext.accounts.report.general_ledger.general_ledger import execute -from frappe.core.page.dashboard.dashboard import cache_source, get_from_date_from_timespan -from frappe.desk.doctype.dashboard_chart.dashboard_chart import get_period_ending +from frappe.core.page.dashboard.dashboard import cache_source +from frappe.utils.dateutils import get_from_date_from_timespan, get_period_ending from frappe.utils.nestedset import get_descendants_of From 5062b787185101190fd13bf3e8118d74ecb0780d Mon Sep 17 00:00:00 2001 From: Saqib Date: Thu, 10 Dec 2020 12:33:43 +0530 Subject: [PATCH 39/57] fix: asset with value zero doesn't show up in fixed asset register (#24098) --- .../fixed_asset_register.py | 35 +++++++++---------- 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/erpnext/assets/report/fixed_asset_register/fixed_asset_register.py b/erpnext/assets/report/fixed_asset_register/fixed_asset_register.py index fa2fe7b4a3c..96e38a9b779 100644 --- a/erpnext/assets/report/fixed_asset_register/fixed_asset_register.py +++ b/erpnext/assets/report/fixed_asset_register/fixed_asset_register.py @@ -148,24 +148,23 @@ def get_data(filters): for asset in assets_record: asset_value = asset.gross_purchase_amount - flt(asset.opening_accumulated_depreciation) \ - flt(depreciation_amount_map.get(asset.name)) - if asset_value: - row = { - "asset_id": asset.name, - "asset_name": asset.asset_name, - "status": asset.status, - "department": asset.department, - "cost_center": asset.cost_center, - "vendor_name": pr_supplier_map.get(asset.purchase_receipt) or pi_supplier_map.get(asset.purchase_invoice), - "gross_purchase_amount": asset.gross_purchase_amount, - "opening_accumulated_depreciation": asset.opening_accumulated_depreciation, - "depreciated_amount": depreciation_amount_map.get(asset.name) or 0.0, - "available_for_use_date": asset.available_for_use_date, - "location": asset.location, - "asset_category": asset.asset_category, - "purchase_date": asset.purchase_date, - "asset_value": asset_value - } - data.append(row) + row = { + "asset_id": asset.asset_id, + "asset_name": asset.asset_name, + "status": asset.status, + "department": asset.department, + "cost_center": asset.cost_center, + "vendor_name": pr_supplier_map.get(asset.purchase_receipt) or pi_supplier_map.get(asset.purchase_invoice), + "gross_purchase_amount": asset.gross_purchase_amount, + "opening_accumulated_depreciation": asset.opening_accumulated_depreciation, + "depreciated_amount": depreciation_amount_map.get(asset.asset_id) or 0.0, + "available_for_use_date": asset.available_for_use_date, + "location": asset.location, + "asset_category": asset.asset_category, + "purchase_date": asset.purchase_date, + "asset_value": asset_value + } + data.append(row) return data From 6a718a3fc61f345410885145e3af2e95b1ef884c Mon Sep 17 00:00:00 2001 From: prssanna Date: Thu, 10 Dec 2020 12:39:53 +0530 Subject: [PATCH 40/57] fix: get value of allow_items_in_stock even if not an exact match --- erpnext/portal/product_configurator/utils.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/erpnext/portal/product_configurator/utils.py b/erpnext/portal/product_configurator/utils.py index ea4d4f44109..dccbc59b2f1 100644 --- a/erpnext/portal/product_configurator/utils.py +++ b/erpnext/portal/product_configurator/utils.py @@ -2,6 +2,7 @@ import frappe import numpy as np from frappe.utils import cint from erpnext.portal.product_configurator.item_variants_cache import ItemVariantsCacheManager +from erpnext.shopping_cart.doctype.shopping_cart_settings.shopping_cart_settings import get_shopping_cart_settings def get_field_filter_data(): product_settings = get_product_settings() @@ -249,7 +250,8 @@ def get_next_attribute_and_values(item_code, selected_attributes): optional_attributes = item_cache.get_optional_attributes() exact_match = [] - allow_items_not_in_stock = False + shopping_cart_settings = get_shopping_cart_settings() + allow_items_not_in_stock = cint(shopping_cart_settings.allow_items_not_in_stock) # search for exact match if all selected attributes are required attributes if len(selected_attributes.keys()) >= (len(attribute_list) - len(optional_attributes)): item_attribute_value_map = item_cache.get_item_attribute_value_map() @@ -264,7 +266,6 @@ def get_next_attribute_and_values(item_code, selected_attributes): if exact_match: data = get_product_info_for_website(exact_match[0]) product_info = data.product_info - allow_items_not_in_stock = cint(data.cart_settings.allow_items_not_in_stock) if not data.cart_settings.show_price: product_info = None else: From 96946fa4a0a88d771bcd08e06a5b2848c7bf5ce3 Mon Sep 17 00:00:00 2001 From: marination Date: Thu, 8 Oct 2020 19:08:27 +0530 Subject: [PATCH 41/57] fix: Handle missing Account and Item in Opening Invoice Creation Tool --- .../opening_invoice_creation_tool.py | 3 ++- .../doctype/purchase_invoice/purchase_invoice.py | 5 +++++ .../doctype/sales_invoice/sales_invoice.py | 5 +++++ erpnext/controllers/accounts_controller.py | 15 +++++++++++++++ 4 files changed, 27 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py b/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py index a53417eedf9..3653a881678 100644 --- a/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py +++ b/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py @@ -181,7 +181,8 @@ class OpeningInvoiceCreationTool(Document): "due_date": row.due_date, "posting_date": row.posting_date, frappe.scrub(party_type): row.party, - "doctype": "Sales Invoice" if self.invoice_type == "Sales" else "Purchase Invoice" + "doctype": "Sales Invoice" if self.invoice_type == "Sales" else "Purchase Invoice", + "update_stock": 0 }) accounting_dimension = get_accounting_dimensions() diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index 155bfd4416a..4db2460dc11 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -142,6 +142,11 @@ class PurchaseInvoice(BuyingController): throw(_("Conversion rate cannot be 0 or 1")) def validate_credit_to_acc(self): + if not self.credit_to: + self.credit_to = get_party_account("Supplier", self.supplier, self.company) + if not self.credit_to: + self.raise_missing_debit_credit_account_error("Supplier", self.supplier) + account = frappe.db.get_value("Account", self.credit_to, ["account_type", "report_type", "account_currency"], as_dict=True) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 1243145e982..184f98bd2ef 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -469,6 +469,11 @@ class SalesInvoice(SellingController): return frappe.db.sql("select abbr from tabCompany where name=%s", self.company)[0][0] def validate_debit_to_acc(self): + if not self.debit_to: + self.debit_to = get_party_account("Customer", self.customer, self.company) + if not self.debit_to: + self.raise_missing_debit_credit_account_error("Customer", self.customer) + account = frappe.get_cached_value("Account", self.debit_to, ["account_type", "report_type", "account_currency"], as_dict=True) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 58c7e847910..5cd9a1b7096 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -711,6 +711,21 @@ class AccountsController(TransactionBase): return self._abbr + def raise_missing_debit_credit_account_error(self, party_type, party): + """Raise an error if debit to/credit to account does not exist""" + db_or_cr = frappe.bold("Debit To") if self.doctype == "Sales Invoice" else frappe.bold("Credit To") + rec_or_pay = "Receivable" if self.doctype == "Sales Invoice" else "Payable" + + link_to_party = frappe.utils.get_link_to_form(party_type, party) + link_to_company = frappe.utils.get_link_to_form("Company", self.company) + + message = _("{0} Account not found against Customer {1}.").format(db_or_cr, frappe.bold(party) or '') + message += "
" + _("Please set one of the following:") + "
" + message += "
  • " + _("'Account' in the Accounting section of Customer {0}").format(link_to_party) + "
  • " + message += "
  • " + _("'Default {0} Account' in Company {1}").format(rec_or_pay, link_to_company) + "
" + + frappe.throw(message, title=_("Account Missing")) + def validate_party(self): party_type, party = self.get_party() validate_party_frozen_disabled(party_type, party) From 74bcb2977f92923ee9185f7a185a22e1ccaaf675 Mon Sep 17 00:00:00 2001 From: marination Date: Tue, 3 Nov 2020 15:45:25 +0530 Subject: [PATCH 42/57] chore: Add Test for missing debit account --- .../test_opening_invoice_creation_tool.py | 53 +++++++++++++++++-- erpnext/controllers/accounts_controller.py | 6 ++- 2 files changed, 54 insertions(+), 5 deletions(-) diff --git a/erpnext/accounts/doctype/opening_invoice_creation_tool/test_opening_invoice_creation_tool.py b/erpnext/accounts/doctype/opening_invoice_creation_tool/test_opening_invoice_creation_tool.py index 3bfc10dda55..71e3c24f0a4 100644 --- a/erpnext/accounts/doctype/opening_invoice_creation_tool/test_opening_invoice_creation_tool.py +++ b/erpnext/accounts/doctype/opening_invoice_creation_tool/test_opening_invoice_creation_tool.py @@ -7,16 +7,18 @@ import frappe import unittest test_dependencies = ["Customer", "Supplier"] +from frappe.custom.doctype.property_setter.property_setter import make_property_setter from erpnext.accounts.doctype.opening_invoice_creation_tool.opening_invoice_creation_tool import get_temporary_opening_account class TestOpeningInvoiceCreationTool(unittest.TestCase): - def make_invoices(self, invoice_type="Sales"): + def make_invoices(self, invoice_type="Sales", company=None): doc = frappe.get_single("Opening Invoice Creation Tool") - args = get_opening_invoice_creation_dict(invoice_type=invoice_type) + args = get_opening_invoice_creation_dict(invoice_type=invoice_type, company=company) doc.update(args) return doc.make_invoices() def test_opening_sales_invoice_creation(self): + property_setter = make_property_setter("Sales Invoice", "update_stock", "default", 1, "Check") invoices = self.make_invoices() self.assertEqual(len(invoices), 2) @@ -27,6 +29,13 @@ class TestOpeningInvoiceCreationTool(unittest.TestCase): } self.check_expected_values(invoices, expected_value) + si = frappe.get_doc("Sales Invoice", invoices[0]) + + # Check if update stock is not enabled + self.assertEqual(si.update_stock, 0) + + property_setter.delete() + def check_expected_values(self, invoices, expected_value, invoice_type="Sales"): doctype = "Sales Invoice" if invoice_type == "Sales" else "Purchase Invoice" @@ -46,6 +55,32 @@ class TestOpeningInvoiceCreationTool(unittest.TestCase): } self.check_expected_values(invoices, expected_value, invoice_type="Purchase", ) + def test_opening_sales_invoice_creation_with_missing_debit_account(self): + company = make_company() + old_default_receivable_account = frappe.db.get_value("Company", company.name, "default_receivable_account") + frappe.db.set_value("Company", company.name, "default_receivable_account", "") + + if not frappe.db.exists("Cost Center", "_Test Opening Invoice Company - _TOIC"): + cc = frappe.get_doc({"doctype": "Cost Center", "cost_center_name": "_Test Opening Invoice Company", + "is_group": 1, "company": "_Test Opening Invoice Company"}) + cc.insert(ignore_mandatory=True) + cc2 = frappe.get_doc({"doctype": "Cost Center", "cost_center_name": "Main", "is_group": 0, + "company": "_Test Opening Invoice Company", "parent_cost_center": cc.name}) + cc2.insert() + + frappe.db.set_value("Company", company.name, "cost_center", "Main - _TOIC") + + self.make_invoices(company="_Test Opening Invoice Company") + + # Check if missing debit account error raised + error_log = frappe.db.exists("Error Log", {"error": ["like", "%erpnext.controllers.accounts_controller.AccountMissingError%"]}) + self.assertTrue(error_log) + + # teardown + frappe.db.set_value("Company", company.name, "default_receivable_account", old_default_receivable_account) + company.delete() + frappe.get_doc("Error Log", error_log).delete() + def get_opening_invoice_creation_dict(**args): party = "Customer" if args.get("invoice_type", "Sales") == "Sales" else "Supplier" company = args.get("company", "_Test Company") @@ -76,4 +111,16 @@ def get_opening_invoice_creation_dict(**args): }) invoice_dict.update(args) - return invoice_dict \ No newline at end of file + return invoice_dict + +def make_company(): + if frappe.db.exists("Company", "_Test Opening Invoice Company"): + return frappe.get_doc("Company", "_Test Opening Invoice Company") + + company = frappe.new_doc("Company") + company.company_name = "_Test Opening Invoice Company" + company.abbr = "_TOIC" + company.default_currency = "INR" + company.country = "India" + company.insert() + return company \ No newline at end of file diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 5cd9a1b7096..9f582de90e6 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -23,6 +23,8 @@ from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import g from erpnext.stock.get_item_details import get_item_warehouse, _get_item_tax_template, get_item_tax_map from erpnext.stock.doctype.packed_item.packed_item import make_packing_list +class AccountMissingError(frappe.ValidationError): pass + force_item_fields = ("item_group", "brand", "stock_uom", "is_fixed_asset", "item_tax_rate", "pricing_rules") class AccountsController(TransactionBase): @@ -712,7 +714,7 @@ class AccountsController(TransactionBase): return self._abbr def raise_missing_debit_credit_account_error(self, party_type, party): - """Raise an error if debit to/credit to account does not exist""" + """Raise an error if debit to/credit to account does not exist.""" db_or_cr = frappe.bold("Debit To") if self.doctype == "Sales Invoice" else frappe.bold("Credit To") rec_or_pay = "Receivable" if self.doctype == "Sales Invoice" else "Payable" @@ -724,7 +726,7 @@ class AccountsController(TransactionBase): message += "
  • " + _("'Account' in the Accounting section of Customer {0}").format(link_to_party) + "
  • " message += "
  • " + _("'Default {0} Account' in Company {1}").format(rec_or_pay, link_to_company) + "
" - frappe.throw(message, title=_("Account Missing")) + frappe.throw(message, title=_("Account Missing"), exc=AccountMissingError) def validate_party(self): party_type, party = self.get_party() From a63b5c1a9ef8d9e3a9123b2b72b8b0d1770927a7 Mon Sep 17 00:00:00 2001 From: marination Date: Fri, 4 Dec 2020 13:31:36 +0530 Subject: [PATCH 43/57] fix: Make new Customers for account missing test and set company --- .../test_opening_invoice_creation_tool.py | 50 +++++++++++++------ 1 file changed, 35 insertions(+), 15 deletions(-) diff --git a/erpnext/accounts/doctype/opening_invoice_creation_tool/test_opening_invoice_creation_tool.py b/erpnext/accounts/doctype/opening_invoice_creation_tool/test_opening_invoice_creation_tool.py index 71e3c24f0a4..f71a896f599 100644 --- a/erpnext/accounts/doctype/opening_invoice_creation_tool/test_opening_invoice_creation_tool.py +++ b/erpnext/accounts/doctype/opening_invoice_creation_tool/test_opening_invoice_creation_tool.py @@ -11,15 +11,20 @@ from frappe.custom.doctype.property_setter.property_setter import make_property_ from erpnext.accounts.doctype.opening_invoice_creation_tool.opening_invoice_creation_tool import get_temporary_opening_account class TestOpeningInvoiceCreationTool(unittest.TestCase): - def make_invoices(self, invoice_type="Sales", company=None): + def setUp(self): + if not frappe.db.exists("Company", "_Test Opening Invoice Company"): + make_company() + + def make_invoices(self, invoice_type="Sales", company=None, party_1=None, party_2=None): doc = frappe.get_single("Opening Invoice Creation Tool") - args = get_opening_invoice_creation_dict(invoice_type=invoice_type, company=company) + args = get_opening_invoice_creation_dict(invoice_type=invoice_type, company=company, + party_1=party_1, party_2=party_2) doc.update(args) return doc.make_invoices() def test_opening_sales_invoice_creation(self): property_setter = make_property_setter("Sales Invoice", "update_stock", "default", 1, "Check") - invoices = self.make_invoices() + invoices = self.make_invoices(company="_Test Opening Invoice Company") self.assertEqual(len(invoices), 2) expected_value = { @@ -45,7 +50,7 @@ class TestOpeningInvoiceCreationTool(unittest.TestCase): self.assertEqual(si.get(field, ""), expected_value[invoice_idx][field_idx]) def test_opening_purchase_invoice_creation(self): - invoices = self.make_invoices(invoice_type="Purchase") + invoices = self.make_invoices(invoice_type="Purchase", company="_Test Opening Invoice Company") self.assertEqual(len(invoices), 2) expected_value = { @@ -56,9 +61,11 @@ class TestOpeningInvoiceCreationTool(unittest.TestCase): self.check_expected_values(invoices, expected_value, invoice_type="Purchase", ) def test_opening_sales_invoice_creation_with_missing_debit_account(self): - company = make_company() - old_default_receivable_account = frappe.db.get_value("Company", company.name, "default_receivable_account") - frappe.db.set_value("Company", company.name, "default_receivable_account", "") + company = "_Test Opening Invoice Company" + party_1, party_2 = make_customer("Customer A"), make_customer("Customer B") + + old_default_receivable_account = frappe.db.get_value("Company", company, "default_receivable_account") + frappe.db.set_value("Company", company, "default_receivable_account", "") if not frappe.db.exists("Cost Center", "_Test Opening Invoice Company - _TOIC"): cc = frappe.get_doc({"doctype": "Cost Center", "cost_center_name": "_Test Opening Invoice Company", @@ -68,18 +75,16 @@ class TestOpeningInvoiceCreationTool(unittest.TestCase): "company": "_Test Opening Invoice Company", "parent_cost_center": cc.name}) cc2.insert() - frappe.db.set_value("Company", company.name, "cost_center", "Main - _TOIC") + frappe.db.set_value("Company", company, "cost_center", "Main - _TOIC") - self.make_invoices(company="_Test Opening Invoice Company") + self.make_invoices(company="_Test Opening Invoice Company", party_1=party_1, party_2=party_2) # Check if missing debit account error raised error_log = frappe.db.exists("Error Log", {"error": ["like", "%erpnext.controllers.accounts_controller.AccountMissingError%"]}) self.assertTrue(error_log) # teardown - frappe.db.set_value("Company", company.name, "default_receivable_account", old_default_receivable_account) - company.delete() - frappe.get_doc("Error Log", error_log).delete() + frappe.db.set_value("Company", company, "default_receivable_account", old_default_receivable_account) def get_opening_invoice_creation_dict(**args): party = "Customer" if args.get("invoice_type", "Sales") == "Sales" else "Supplier" @@ -92,7 +97,7 @@ def get_opening_invoice_creation_dict(**args): { "qty": 1.0, "outstanding_amount": 300, - "party": "_Test {0}".format(party), + "party": args.get("party_1") or "_Test {0}".format(party), "item_name": "Opening Item", "due_date": "2016-09-10", "posting_date": "2016-09-05", @@ -101,7 +106,7 @@ def get_opening_invoice_creation_dict(**args): { "qty": 2.0, "outstanding_amount": 250, - "party": "_Test {0} 1".format(party), + "party": args.get("party_2") or "_Test {0} 1".format(party), "item_name": "Opening Item", "due_date": "2016-09-10", "posting_date": "2016-09-05", @@ -123,4 +128,19 @@ def make_company(): company.default_currency = "INR" company.country = "India" company.insert() - return company \ No newline at end of file + return company + +def make_customer(customer=None): + customer_name = customer or "Opening Customer" + customer = frappe.get_doc({ + "doctype": "Customer", + "customer_name": customer_name, + "customer_group": "All Customer Groups", + "customer_type": "Company", + "territory": "All Territories" + }) + if not frappe.db.exists("Customer", customer_name): + customer.insert(ignore_permissions=True) + return customer.name + else: + return frappe.db.exists("Customer", customer_name) \ No newline at end of file From 1fcd1e4dc09e4fb478b9353333bc8116ed5cd959 Mon Sep 17 00:00:00 2001 From: marination Date: Thu, 10 Dec 2020 17:34:30 +0530 Subject: [PATCH 44/57] fix: add assertRaises for error --- .../test_opening_invoice_creation_tool.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/erpnext/accounts/doctype/opening_invoice_creation_tool/test_opening_invoice_creation_tool.py b/erpnext/accounts/doctype/opening_invoice_creation_tool/test_opening_invoice_creation_tool.py index f71a896f599..b62228e5043 100644 --- a/erpnext/accounts/doctype/opening_invoice_creation_tool/test_opening_invoice_creation_tool.py +++ b/erpnext/accounts/doctype/opening_invoice_creation_tool/test_opening_invoice_creation_tool.py @@ -9,6 +9,7 @@ import unittest test_dependencies = ["Customer", "Supplier"] from frappe.custom.doctype.property_setter.property_setter import make_property_setter from erpnext.accounts.doctype.opening_invoice_creation_tool.opening_invoice_creation_tool import get_temporary_opening_account +from erpnext.controllers.accounts_controller import AccountMissingError class TestOpeningInvoiceCreationTool(unittest.TestCase): def setUp(self): @@ -77,11 +78,7 @@ class TestOpeningInvoiceCreationTool(unittest.TestCase): frappe.db.set_value("Company", company, "cost_center", "Main - _TOIC") - self.make_invoices(company="_Test Opening Invoice Company", party_1=party_1, party_2=party_2) - - # Check if missing debit account error raised - error_log = frappe.db.exists("Error Log", {"error": ["like", "%erpnext.controllers.accounts_controller.AccountMissingError%"]}) - self.assertTrue(error_log) + self.assertRaises(AccountMissingError, self.make_invoices, company="_Test Opening Invoice Company", party_1=party_1, party_2=party_2) # teardown frappe.db.set_value("Company", company, "default_receivable_account", old_default_receivable_account) From 3f399ee8061d032411b67f42cd529083f61cefae Mon Sep 17 00:00:00 2001 From: Mohammad Hasnain Mohsin Rajan Date: Thu, 10 Dec 2020 19:15:53 +0530 Subject: [PATCH 45/57] fix(acc recv report): columns mismatch (#24085) --- .../accounts_receivable.html | 60 ++++++++++++------- 1 file changed, 39 insertions(+), 21 deletions(-) diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.html b/erpnext/accounts/report/accounts_receivable/accounts_receivable.html index bb0d0a132a5..79a6aabd987 100644 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.html +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.html @@ -42,11 +42,13 @@ {% if(filters.show_future_payments) { %} {% var balance_row = data.slice(-1).pop(); - var range1 = report.columns[11].label; - var range2 = report.columns[12].label; - var range3 = report.columns[13].label; - var range4 = report.columns[14].label; - var range5 = report.columns[15].label; + var start = filters.based_on_payment_terms ? 13 : 11; + var range1 = report.columns[start].label; + var range2 = report.columns[start+1].label; + var range3 = report.columns[start+2].label; + var range4 = report.columns[start+3].label; + var range5 = report.columns[start+4].label; + var range6 = report.columns[start+5].label; %} {% if(balance_row) { %} @@ -70,20 +72,34 @@ + - - - - - + + + + + + + @@ -91,6 +107,7 @@ + @@ -101,6 +118,7 @@ + @@ -218,15 +236,15 @@ + {%= format_currency(data[i]["invoiced"], data[i]["currency"] ) %} {% if(!filters.show_future_payments) { %} - + {%= format_currency(data[i]["paid"], data[i]["currency"]) %} + {% } %} + {%= format_currency(data[i]["outstanding"], data[i]["currency"]) %} {% if(filters.show_future_payments) { %} {% if(report.report_name === "Accounts Receivable") { %} @@ -234,8 +252,8 @@ {%= data[i]["po_no"] %} {% } %} - - + + {% } %} {% } %} {% } else { %} @@ -256,10 +274,10 @@ {% } else { %} {% } %} - - - - + + + + {% } %} {% } %} From 599d7a686e3b8c4a5972497f3107f716f420c3e8 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Fri, 4 Dec 2020 18:07:46 +0530 Subject: [PATCH 46/57] fix: Tax template update on supplier --- erpnext/regional/india/taxes.js | 1 + erpnext/regional/india/utils.py | 31 +++++++++++++++++++++++++++++-- 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/erpnext/regional/india/taxes.js b/erpnext/regional/india/taxes.js index 44891a76a0b..950ba069fb1 100644 --- a/erpnext/regional/india/taxes.js +++ b/erpnext/regional/india/taxes.js @@ -16,6 +16,7 @@ erpnext.setup_auto_gst_taxation = (doctype) => { 'shipping_address': frm.doc.shipping_address || '', 'shipping_address_name': frm.doc.shipping_address_name || '', 'customer_address': frm.doc.customer_address || '', + 'supplier_address': frm.doc.supplier_address, 'customer': frm.doc.customer, 'supplier': frm.doc.supplier, 'supplier_gstin': frm.doc.supplier_gstin, diff --git a/erpnext/regional/india/utils.py b/erpnext/regional/india/utils.py index 1968076c53d..741c9533b18 100644 --- a/erpnext/regional/india/utils.py +++ b/erpnext/regional/india/utils.py @@ -12,6 +12,7 @@ from erpnext.regional.india import number_state_mapping from six import string_types from erpnext.accounts.general_ledger import make_gl_entries from erpnext.accounts.utils import get_account_currency +from frappe.model.utils import get_fetch_values def validate_gstin_for_india(doc, method): if hasattr(doc, 'gst_state') and doc.gst_state: @@ -155,7 +156,15 @@ def get_regional_address_details(party_details, doctype, company, return_taxes=N party_details = json.loads(party_details) party_details = frappe._dict(party_details) + update_party_details(party_details, doctype) + party_details.place_of_supply = get_place_of_supply(party_details, doctype) + + if is_internal_transfer(party_details, doctype): + party_details.taxes_and_charges = '' + party_details.taxes = '' + return party_details + if doctype in ("Sales Invoice", "Delivery Note", "Sales Order"): master_doctype = "Sales Taxes and Charges Template" @@ -196,8 +205,23 @@ def get_regional_address_details(party_details, doctype, company, return_taxes=N party_details["taxes_and_charges"] = default_tax party_details.taxes = get_taxes_and_charges(master_doctype, default_tax) - if return_taxes: - return party_details + return party_details + +def update_party_details(party_details, doctype): + for address_field in ['shipping_address', 'company_address', 'supplier_address', 'shipping_address_name', 'customer_address']: + if party_details.get(address_field): + party_details.update(get_fetch_values(doctype, address_field, party_details.get(address_field))) + +def is_internal_transfer(party_details, doctype): + if doctype in ("Sales Invoice", "Delivery Note", "Sales Order"): + destination_gstin = party_details.company_gstin + elif doctype in ("Purchase Invoice", "Purchase Order", "Purchase Receipt"): + destination_gstin = party_details.supplier_gstin + + if party_details.gstin == destination_gstin: + return True + else: + False def get_tax_template_based_on_category(master_doctype, company, party_details): if not party_details.get('tax_category'): @@ -502,6 +526,9 @@ def get_address_details(data, doc, company_address, billing_address): data.actualToStateCode = data.toStateCode shipping_address = billing_address + if doc.gst_category == 'SEZ': + data.toStateCode = 99 + return data def get_item_list(data, doc): From 8cfde675ac0703059fa111d2eeb732d04a322adb Mon Sep 17 00:00:00 2001 From: pateljannat Date: Tue, 17 Nov 2020 09:47:10 +0530 Subject: [PATCH 47/57] fix: place of supply change when address changes --- erpnext/public/js/utils.js | 15 +++++++++++++++ erpnext/regional/india/utils.py | 1 + erpnext/selling/sales_common.js | 1 + 3 files changed, 17 insertions(+) diff --git a/erpnext/public/js/utils.js b/erpnext/public/js/utils.js index 9b1c94e5ba0..139d5593705 100755 --- a/erpnext/public/js/utils.js +++ b/erpnext/public/js/utils.js @@ -304,6 +304,21 @@ $.extend(erpnext.utils, { } frappe.ui.form.make_quick_entry(doctype, null, null, new_doc); }); + }, + + set_place_of_supply: function(frm){ + frappe.call({ + method: "erpnext.regional.india.utils.get_place_of_supply", + args: { + "party_details": frm.doc, + "doctype": frm.doc.doctype + }, + callback: function(r){ + if(r.message){ + frm.set_value("place_of_supply", r.message) + } + } + }) } }); diff --git a/erpnext/regional/india/utils.py b/erpnext/regional/india/utils.py index 741c9533b18..06154ebef9b 100644 --- a/erpnext/regional/india/utils.py +++ b/erpnext/regional/india/utils.py @@ -136,6 +136,7 @@ def test_method(): '''test function''' return 'overridden' +@frappe.whitelist() def get_place_of_supply(party_details, doctype): if not frappe.get_meta('Address').has_field('gst_state'): return diff --git a/erpnext/selling/sales_common.js b/erpnext/selling/sales_common.js index 15a9a1ab882..802fb5661db 100644 --- a/erpnext/selling/sales_common.js +++ b/erpnext/selling/sales_common.js @@ -117,6 +117,7 @@ erpnext.selling.SellingController = erpnext.TransactionController.extend({ customer_address: function() { erpnext.utils.get_address_display(this.frm, "customer_address"); erpnext.utils.set_taxes_from_address(this.frm, "customer_address", "customer_address", "shipping_address_name"); + erpnext.utils.set_place_of_supply(this.frm) }, shipping_address_name: function() { From f226927b6a857254054f513c69c0e6577742f7eb Mon Sep 17 00:00:00 2001 From: pateljannat Date: Tue, 17 Nov 2020 20:34:51 +0530 Subject: [PATCH 48/57] fix: place of supply change on address change --- erpnext/public/js/controllers/buying.js | 1 + erpnext/public/js/utils.js | 28 ++++++++++++------------- erpnext/regional/india/utils.py | 3 +++ 3 files changed, 17 insertions(+), 15 deletions(-) diff --git a/erpnext/public/js/controllers/buying.js b/erpnext/public/js/controllers/buying.js index 802cc056c6a..e4369ffe96b 100644 --- a/erpnext/public/js/controllers/buying.js +++ b/erpnext/public/js/controllers/buying.js @@ -133,6 +133,7 @@ erpnext.buying.BuyingController = erpnext.TransactionController.extend({ supplier_address: function() { erpnext.utils.get_address_display(this.frm); erpnext.utils.set_taxes_from_address(this.frm, "supplier_address", "supplier_address", "supplier_address"); + erpnext.utils.set_place_of_supply(this.frm) }, buying_price_list: function() { diff --git a/erpnext/public/js/utils.js b/erpnext/public/js/utils.js index 139d5593705..baac95798e2 100755 --- a/erpnext/public/js/utils.js +++ b/erpnext/public/js/utils.js @@ -116,6 +116,19 @@ $.extend(erpnext.utils, { } }, + set_place_of_supply: function(frm){ + frappe.call({ + method: "erpnext.regional.india.utils.get_place_of_supply", + args: { + "party_details": frm.doc, + "doctype": frm.doc.doctype + }, + callback: function(r){ + frm.set_value("place_of_supply", r.message) + } + }) + }, + add_indicator_for_multicompany: function(frm, info) { frm.dashboard.stats_area.removeClass('hidden'); frm.dashboard.stats_area_row.addClass('flex'); @@ -304,21 +317,6 @@ $.extend(erpnext.utils, { } frappe.ui.form.make_quick_entry(doctype, null, null, new_doc); }); - }, - - set_place_of_supply: function(frm){ - frappe.call({ - method: "erpnext.regional.india.utils.get_place_of_supply", - args: { - "party_details": frm.doc, - "doctype": frm.doc.doctype - }, - callback: function(r){ - if(r.message){ - frm.set_value("place_of_supply", r.message) - } - } - }) } }); diff --git a/erpnext/regional/india/utils.py b/erpnext/regional/india/utils.py index 06154ebef9b..9da835224b0 100644 --- a/erpnext/regional/india/utils.py +++ b/erpnext/regional/india/utils.py @@ -139,6 +139,9 @@ def test_method(): @frappe.whitelist() def get_place_of_supply(party_details, doctype): if not frappe.get_meta('Address').has_field('gst_state'): return + if isinstance(party_details, string_types): + party_details = json.loads(party_details) + party_details = frappe._dict(party_details) if doctype in ("Sales Invoice", "Delivery Note", "Sales Order"): address_name = party_details.shipping_address_name or party_details.customer_address From dafbb80ec53d383a37f2f65cd507e4627e22fbac Mon Sep 17 00:00:00 2001 From: pateljannat Date: Wed, 18 Nov 2020 12:51:13 +0530 Subject: [PATCH 49/57] fix: reversing previous commits and adding condition in regional controller --- erpnext/public/js/controllers/buying.js | 1 - erpnext/public/js/utils.js | 13 ------------- erpnext/regional/india/taxes.js | 4 ++++ erpnext/regional/india/utils.py | 18 +++++++----------- erpnext/selling/sales_common.js | 1 - 5 files changed, 11 insertions(+), 26 deletions(-) diff --git a/erpnext/public/js/controllers/buying.js b/erpnext/public/js/controllers/buying.js index e4369ffe96b..802cc056c6a 100644 --- a/erpnext/public/js/controllers/buying.js +++ b/erpnext/public/js/controllers/buying.js @@ -133,7 +133,6 @@ erpnext.buying.BuyingController = erpnext.TransactionController.extend({ supplier_address: function() { erpnext.utils.get_address_display(this.frm); erpnext.utils.set_taxes_from_address(this.frm, "supplier_address", "supplier_address", "supplier_address"); - erpnext.utils.set_place_of_supply(this.frm) }, buying_price_list: function() { diff --git a/erpnext/public/js/utils.js b/erpnext/public/js/utils.js index baac95798e2..9b1c94e5ba0 100755 --- a/erpnext/public/js/utils.js +++ b/erpnext/public/js/utils.js @@ -116,19 +116,6 @@ $.extend(erpnext.utils, { } }, - set_place_of_supply: function(frm){ - frappe.call({ - method: "erpnext.regional.india.utils.get_place_of_supply", - args: { - "party_details": frm.doc, - "doctype": frm.doc.doctype - }, - callback: function(r){ - frm.set_value("place_of_supply", r.message) - } - }) - }, - add_indicator_for_multicompany: function(frm, info) { frm.dashboard.stats_area.removeClass('hidden'); frm.dashboard.stats_area_row.addClass('flex'); diff --git a/erpnext/regional/india/taxes.js b/erpnext/regional/india/taxes.js index 950ba069fb1..33de01adad8 100644 --- a/erpnext/regional/india/taxes.js +++ b/erpnext/regional/india/taxes.js @@ -35,6 +35,10 @@ erpnext.setup_auto_gst_taxation = (doctype) => { callback: function(r) { if(r.message) { frm.set_value('taxes_and_charges', r.message.taxes_and_charges); + frm.set_value('place_of_supply', r.message.place_of_supply); + } else if (frm.doc.is_internal_supplier || frm.doc.is_internal_customer) { + frm.set_value('taxes_and_charges', ''); + frm.set_value('taxes', []); } } }); diff --git a/erpnext/regional/india/utils.py b/erpnext/regional/india/utils.py index 9da835224b0..d2a6c2dbcfa 100644 --- a/erpnext/regional/india/utils.py +++ b/erpnext/regional/india/utils.py @@ -136,12 +136,8 @@ def test_method(): '''test function''' return 'overridden' -@frappe.whitelist() def get_place_of_supply(party_details, doctype): if not frappe.get_meta('Address').has_field('gst_state'): return - if isinstance(party_details, string_types): - party_details = json.loads(party_details) - party_details = frappe._dict(party_details) if doctype in ("Sales Invoice", "Delivery Note", "Sales Order"): address_name = party_details.shipping_address_name or party_details.customer_address @@ -175,11 +171,11 @@ def get_regional_address_details(party_details, doctype, company, return_taxes=N get_tax_template_for_sez(party_details, master_doctype, company, 'Customer') get_tax_template_based_on_category(master_doctype, company, party_details) - if party_details.get('taxes_and_charges') and return_taxes: + if party_details.get('taxes_and_charges'): return party_details if not party_details.company_gstin: - return + return party_details elif doctype in ("Purchase Invoice", "Purchase Order", "Purchase Receipt"): master_doctype = "Purchase Taxes and Charges Template" @@ -187,15 +183,15 @@ def get_regional_address_details(party_details, doctype, company, return_taxes=N get_tax_template_for_sez(party_details, master_doctype, company, 'Supplier') get_tax_template_based_on_category(master_doctype, company, party_details) - if party_details.get('taxes_and_charges') and return_taxes: + if party_details.get('taxes_and_charges'): return party_details if not party_details.supplier_gstin: - return + return party_details - if not party_details.place_of_supply: return + if not party_details.place_of_supply: return party_details - if not party_details.company_gstin: return + if not party_details.company_gstin: return party_details if ((doctype in ("Sales Invoice", "Delivery Note", "Sales Order") and party_details.company_gstin and party_details.company_gstin[:2] != party_details.place_of_supply[:2]) or (doctype in ("Purchase Invoice", @@ -205,7 +201,7 @@ def get_regional_address_details(party_details, doctype, company, return_taxes=N default_tax = get_tax_template(master_doctype, company, 0, party_details.company_gstin[:2]) if not default_tax: - return + return party_details party_details["taxes_and_charges"] = default_tax party_details.taxes = get_taxes_and_charges(master_doctype, default_tax) diff --git a/erpnext/selling/sales_common.js b/erpnext/selling/sales_common.js index 802fb5661db..15a9a1ab882 100644 --- a/erpnext/selling/sales_common.js +++ b/erpnext/selling/sales_common.js @@ -117,7 +117,6 @@ erpnext.selling.SellingController = erpnext.TransactionController.extend({ customer_address: function() { erpnext.utils.get_address_display(this.frm, "customer_address"); erpnext.utils.set_taxes_from_address(this.frm, "customer_address", "customer_address", "shipping_address_name"); - erpnext.utils.set_place_of_supply(this.frm) }, shipping_address_name: function() { From 56d5b6804698aef496bdcbc5c0b75d67ec5d13b8 Mon Sep 17 00:00:00 2001 From: pateljannat Date: Wed, 18 Nov 2020 15:57:16 +0530 Subject: [PATCH 50/57] fix: linter issue for translation syntax --- erpnext/regional/india/utils.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/erpnext/regional/india/utils.py b/erpnext/regional/india/utils.py index d2a6c2dbcfa..13a07a054cb 100644 --- a/erpnext/regional/india/utils.py +++ b/erpnext/regional/india/utils.py @@ -87,7 +87,7 @@ def validate_gstin_check_digit(gstin, label='GSTIN'): factor = 2 if factor == 1 else 1 if gstin[-1] != code_point_chars[((mod - (total % mod)) % mod)]: frappe.throw(_("""Invalid {0}! The check digit validation has failed. - Please ensure you've typed the {0} correctly.""".format(label))) + Please ensure you've typed the {0} correctly.""").format(label)) def get_itemised_tax_breakup_header(item_doctype, tax_accounts): if frappe.get_meta(item_doctype).has_field('gst_hsn_code'): @@ -163,7 +163,7 @@ def get_regional_address_details(party_details, doctype, company, return_taxes=N if is_internal_transfer(party_details, doctype): party_details.taxes_and_charges = '' party_details.taxes = '' - return party_details + return if doctype in ("Sales Invoice", "Delivery Note", "Sales Order"): master_doctype = "Sales Taxes and Charges Template" @@ -171,11 +171,11 @@ def get_regional_address_details(party_details, doctype, company, return_taxes=N get_tax_template_for_sez(party_details, master_doctype, company, 'Customer') get_tax_template_based_on_category(master_doctype, company, party_details) - if party_details.get('taxes_and_charges'): + if party_details.get('taxes_and_charges') and return_taxes: return party_details if not party_details.company_gstin: - return party_details + return elif doctype in ("Purchase Invoice", "Purchase Order", "Purchase Receipt"): master_doctype = "Purchase Taxes and Charges Template" @@ -183,15 +183,15 @@ def get_regional_address_details(party_details, doctype, company, return_taxes=N get_tax_template_for_sez(party_details, master_doctype, company, 'Supplier') get_tax_template_based_on_category(master_doctype, company, party_details) - if party_details.get('taxes_and_charges'): + if party_details.get('taxes_and_charges') and return_taxes: return party_details if not party_details.supplier_gstin: - return party_details + return - if not party_details.place_of_supply: return party_details + if not party_details.place_of_supply: return - if not party_details.company_gstin: return party_details + if not party_details.company_gstin: return if ((doctype in ("Sales Invoice", "Delivery Note", "Sales Order") and party_details.company_gstin and party_details.company_gstin[:2] != party_details.place_of_supply[:2]) or (doctype in ("Purchase Invoice", @@ -201,11 +201,12 @@ def get_regional_address_details(party_details, doctype, company, return_taxes=N default_tax = get_tax_template(master_doctype, company, 0, party_details.company_gstin[:2]) if not default_tax: - return party_details + return party_details["taxes_and_charges"] = default_tax party_details.taxes = get_taxes_and_charges(master_doctype, default_tax) - return party_details + if return_taxes: + return party_details def update_party_details(party_details, doctype): for address_field in ['shipping_address', 'company_address', 'supplier_address', 'shipping_address_name', 'customer_address']: @@ -245,7 +246,6 @@ def get_tax_template(master_doctype, company, is_inter_state, state_code): (not default_tax and not tax_category.gst_state): default_tax = frappe.db.get_value(master_doctype, {'disabled': 0, 'tax_category': tax_category.name}, 'name') - return default_tax def get_tax_template_for_sez(party_details, master_doctype, company, party_type): From 45d8204e1e1183196e2324ef712a8748c0314330 Mon Sep 17 00:00:00 2001 From: pateljannat Date: Thu, 19 Nov 2020 11:37:08 +0530 Subject: [PATCH 51/57] fix: company filter added again --- erpnext/regional/india/utils.py | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/erpnext/regional/india/utils.py b/erpnext/regional/india/utils.py index 13a07a054cb..8c764b7e8aa 100644 --- a/erpnext/regional/india/utils.py +++ b/erpnext/regional/india/utils.py @@ -163,7 +163,7 @@ def get_regional_address_details(party_details, doctype, company, return_taxes=N if is_internal_transfer(party_details, doctype): party_details.taxes_and_charges = '' party_details.taxes = '' - return + return party_details if doctype in ("Sales Invoice", "Delivery Note", "Sales Order"): master_doctype = "Sales Taxes and Charges Template" @@ -171,11 +171,11 @@ def get_regional_address_details(party_details, doctype, company, return_taxes=N get_tax_template_for_sez(party_details, master_doctype, company, 'Customer') get_tax_template_based_on_category(master_doctype, company, party_details) - if party_details.get('taxes_and_charges') and return_taxes: + if party_details.get('taxes_and_charges'): return party_details if not party_details.company_gstin: - return + return party_details elif doctype in ("Purchase Invoice", "Purchase Order", "Purchase Receipt"): master_doctype = "Purchase Taxes and Charges Template" @@ -183,15 +183,15 @@ def get_regional_address_details(party_details, doctype, company, return_taxes=N get_tax_template_for_sez(party_details, master_doctype, company, 'Supplier') get_tax_template_based_on_category(master_doctype, company, party_details) - if party_details.get('taxes_and_charges') and return_taxes: + if party_details.get('taxes_and_charges'): return party_details if not party_details.supplier_gstin: - return + return party_details - if not party_details.place_of_supply: return + if not party_details.place_of_supply: return party_details - if not party_details.company_gstin: return + if not party_details.company_gstin: return party_details if ((doctype in ("Sales Invoice", "Delivery Note", "Sales Order") and party_details.company_gstin and party_details.company_gstin[:2] != party_details.place_of_supply[:2]) or (doctype in ("Purchase Invoice", @@ -201,12 +201,11 @@ def get_regional_address_details(party_details, doctype, company, return_taxes=N default_tax = get_tax_template(master_doctype, company, 0, party_details.company_gstin[:2]) if not default_tax: - return + return party_details party_details["taxes_and_charges"] = default_tax party_details.taxes = get_taxes_and_charges(master_doctype, default_tax) - if return_taxes: - return party_details + return party_details def update_party_details(party_details, doctype): for address_field in ['shipping_address', 'company_address', 'supplier_address', 'shipping_address_name', 'customer_address']: @@ -245,7 +244,7 @@ def get_tax_template(master_doctype, company, is_inter_state, state_code): if tax_category.gst_state == number_state_mapping[state_code] or \ (not default_tax and not tax_category.gst_state): default_tax = frappe.db.get_value(master_doctype, - {'disabled': 0, 'tax_category': tax_category.name}, 'name') + {'company': company, 'disabled': 0, 'tax_category': tax_category.name}, 'name') return default_tax def get_tax_template_for_sez(party_details, master_doctype, company, party_type): From af60e854004b87db3f800e54cda4b6e10de26662 Mon Sep 17 00:00:00 2001 From: pateljannat Date: Thu, 19 Nov 2020 20:11:45 +0530 Subject: [PATCH 52/57] fix: removing return_taxes condition --- erpnext/regional/india/taxes.js | 3 +-- erpnext/regional/india/utils.py | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/erpnext/regional/india/taxes.js b/erpnext/regional/india/taxes.js index 33de01adad8..b3da21c2bb3 100644 --- a/erpnext/regional/india/taxes.js +++ b/erpnext/regional/india/taxes.js @@ -29,8 +29,7 @@ erpnext.setup_auto_gst_taxation = (doctype) => { args: { party_details: JSON.stringify(party_details), doctype: frm.doc.doctype, - company: frm.doc.company, - return_taxes: 1 + company: frm.doc.company }, callback: function(r) { if(r.message) { diff --git a/erpnext/regional/india/utils.py b/erpnext/regional/india/utils.py index 8c764b7e8aa..1bd1f6c0c77 100644 --- a/erpnext/regional/india/utils.py +++ b/erpnext/regional/india/utils.py @@ -150,8 +150,7 @@ def get_place_of_supply(party_details, doctype): return cstr(address.gst_state_number) + "-" + cstr(address.gst_state) @frappe.whitelist() -def get_regional_address_details(party_details, doctype, company, return_taxes=None): - +def get_regional_address_details(party_details, doctype, company): if isinstance(party_details, string_types): party_details = json.loads(party_details) party_details = frappe._dict(party_details) From 2a92d1b94a26ce77024e7d35ff491ee11c54c3df Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 16 Dec 2020 13:00:55 +0530 Subject: [PATCH 53/57] fix: Tax template update on customer address change --- erpnext/regional/india/taxes.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/erpnext/regional/india/taxes.js b/erpnext/regional/india/taxes.js index b3da21c2bb3..455879294a8 100644 --- a/erpnext/regional/india/taxes.js +++ b/erpnext/regional/india/taxes.js @@ -9,6 +9,9 @@ erpnext.setup_auto_gst_taxation = (doctype) => { tax_category: function(frm) { frm.trigger('get_tax_template'); }, + customer_address: function(frm) { + frm.trigger('get_tax_template'); + }, get_tax_template: function(frm) { if (!frm.doc.company) return; From 71349d5a49a4fd5355ace8e0b21d69891c9cd938 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sat, 21 Nov 2020 21:36:44 +0530 Subject: [PATCH 54/57] fix: Validation for duplicate Tax Category --- erpnext/hooks.py | 3 +++ erpnext/regional/india/utils.py | 9 ++++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/erpnext/hooks.py b/erpnext/hooks.py index 5270e7beea2..1dc6b6b4170 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -239,6 +239,9 @@ doc_events = { "Website Settings": { "validate": "erpnext.portal.doctype.products_settings.products_settings.home_page_is_products" }, + "Tax Category": { + "validate": "erpnext.regional.india.utils.validate_tax_category" + }, "Sales Invoice": { "on_submit": ["erpnext.regional.create_transaction_log", "erpnext.regional.italy.utils.sales_invoice_on_submit"], "on_cancel": "erpnext.regional.italy.utils.sales_invoice_on_cancel", diff --git a/erpnext/regional/india/utils.py b/erpnext/regional/india/utils.py index 7ceb90fca98..2ef55440464 100644 --- a/erpnext/regional/india/utils.py +++ b/erpnext/regional/india/utils.py @@ -52,6 +52,13 @@ def validate_gstin_for_india(doc, method): frappe.throw(_("Invalid GSTIN! First 2 digits of GSTIN should match with State number {0}.") .format(doc.gst_state_number)) +def validate_tax_category(doc, method): + if doc.get('gst_state') and frappe.db.get_value('Tax category', {'gst_state': doc.gst_state, 'is_inter_state': doc.is_inter_state}): + if doc.is_inter_state: + frappe.throw(_("Inter State tax category for GST State {0} already exists").format(doc.gst_state)) + else: + frappe.throw(_("Intra State tax category for GST State {0} already exists").format(doc.gst_state)) + def update_gst_category(doc, method): for link in doc.links: if link.link_doctype in ['Customer', 'Supplier']: @@ -523,7 +530,7 @@ def get_address_details(data, doc, company_address, billing_address): data.transType = 1 data.actualToStateCode = data.toStateCode shipping_address = billing_address - + if doc.gst_category == 'SEZ': data.toStateCode = 99 From 5ee6417228f263237e8bf74aaddd02a60eeef0cd Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Tue, 24 Nov 2020 08:08:47 +0530 Subject: [PATCH 55/57] fix: Translation issue --- erpnext/regional/india/utils.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/erpnext/regional/india/utils.py b/erpnext/regional/india/utils.py index 2ef55440464..87bd5f70a27 100644 --- a/erpnext/regional/india/utils.py +++ b/erpnext/regional/india/utils.py @@ -53,7 +53,7 @@ def validate_gstin_for_india(doc, method): .format(doc.gst_state_number)) def validate_tax_category(doc, method): - if doc.get('gst_state') and frappe.db.get_value('Tax category', {'gst_state': doc.gst_state, 'is_inter_state': doc.is_inter_state}): + if doc.get('gst_state') and frappe.db.get_value('Tax Category', {'gst_state': doc.gst_state, 'is_inter_state': doc.is_inter_state}): if doc.is_inter_state: frappe.throw(_("Inter State tax category for GST State {0} already exists").format(doc.gst_state)) else: @@ -93,8 +93,7 @@ def validate_gstin_check_digit(gstin, label='GSTIN'): total += digit factor = 2 if factor == 1 else 1 if gstin[-1] != code_point_chars[((mod - (total % mod)) % mod)]: - frappe.throw(_("""Invalid {0}! The check digit validation has failed. - Please ensure you've typed the {0} correctly.""").format(label)) + frappe.throw(_("""Invalid {0}! The check digit validation has failed. Please ensure you've typed the {0} correctly.""").format(label)) def get_itemised_tax_breakup_header(item_doctype, tax_accounts): if frappe.get_meta(item_doctype).has_field('gst_hsn_code'): @@ -770,4 +769,4 @@ def make_regional_gl_entries(gl_entries, doc): }, account_currency, item=tax) ) - return gl_entries \ No newline at end of file + return gl_entries From 53d98ff635ab7af6fc89b62ad7c9a6b0fa5092e2 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Mon, 21 Dec 2020 14:31:43 +0530 Subject: [PATCH 56/57] chore: Added change log --- erpnext/change_log/v12/v12_15_0.md | 40 ++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 erpnext/change_log/v12/v12_15_0.md diff --git a/erpnext/change_log/v12/v12_15_0.md b/erpnext/change_log/v12/v12_15_0.md new file mode 100644 index 00000000000..54082c01ac8 --- /dev/null +++ b/erpnext/change_log/v12/v12_15_0.md @@ -0,0 +1,40 @@ +## ERPNext v12.15.0 Release Note + +### Fixes and Enhancements + +- BOM stock report color showing always red ([#23993](https://github.com/frappe/erpnext/pull/23993)) +- Clear error message when approval not availab ([#23972](https://github.com/frappe/erpnext/pull/23972)) +- Show tax amount in base currencies ([#24071](https://github.com/frappe/erpnext/pull/24071)) +- Depreciation Posting Date is mandatory even if Calculate Depreciation is not checked ([#24037](https://github.com/frappe/erpnext/pull/24037)) +- Handle Account and Item None not found in Opening Invoice Creation Tool ([#24103](https://github.com/frappe/erpnext/pull/24103)) +- Opening invoices in GSTR-1 report ([#24020](https://github.com/frappe/erpnext/pull/24020)) +- Incorrect balance value in stock balance report ([#23997](https://github.com/frappe/erpnext/pull/23997)) +- Columns mismatch in AR report([#24085](https://github.com/frappe/erpnext/pull/24085)) +- Job card error handling for operations field ([#23996](https://github.com/frappe/erpnext/pull/23996)) +- Set proper state code in ewaybill JSON when GST category is SEZ ([#23954](https://github.com/frappe/erpnext/pull/23954)) +- PO orverride ([#24023](https://github.com/frappe/erpnext/pull/24023)) +- Invoice generation for Unpaid subscriptions ([#23966](https://github.com/frappe/erpnext/pull/23966)) +- Throw an error when no pos profile exist ([#24026](https://github.com/frappe/erpnext/pull/24026)) +- Purchase receipt to purchase invoice bill date mapping ([#23968](https://github.com/frappe/erpnext/pull/23968)) +- Validation for duplicate Tax Category ([#24175](https://github.com/frappe/erpnext/pull/24175)) +- Double exception in payroll ([#24080](https://github.com/frappe/erpnext/pull/24080)) +- Sales invoice add button on sales order dashboard ([#24081](https://github.com/frappe/erpnext/pull/24081)) +- Hide Ex-Employees from Employee Tree and minor message UX ([#23927](https://github.com/frappe/erpnext/pull/23927)) +- Get value of allow_items_in_stock even if not an exact match ([#24099](https://github.com/frappe/erpnext/pull/24099)) +- Incorrect delink serial no and batch ([#23958](https://github.com/frappe/erpnext/pull/23958)) +- Pricing rule with transaction not working for additional product ([#24064](https://github.com/frappe/erpnext/pull/24064)) +- Check if list view standard filter exists in Payment Entry ([#23929](https://github.com/frappe/erpnext/pull/23929)) +- Do not fetch items until POS Profile is set ([#24076](https://github.com/frappe/erpnext/pull/24076)) +- Taxation fixes for India ([#24162](https://github.com/frappe/erpnext/pull/24162)) +- Don't cancel job card if manufacturing entry has made ([#24034](https://github.com/frappe/erpnext/pull/24034)) +- Payment Reconciliation client side validations ([#23930](https://github.com/frappe/erpnext/pull/23930)) +- Item Link Formatter Behaviour ([#23931](https://github.com/frappe/erpnext/pull/23931)) +- Asset with value zero doesn't show up in fixed asset register ([#24098](https://github.com/frappe/erpnext/pull/24098)) +- Allow add to cart for any item if allow_items_not_in_stock is enabled ([#24084](https://github.com/frappe/erpnext/pull/24084)) +- Incoming rate for finished good ([#24013](https://github.com/frappe/erpnext/pull/24013)) +- Incorrect stock ledger entries for stock reco ([#23938](https://github.com/frappe/erpnext/pull/23938)) +- Function imports in account_balance_timeline.py ([#24097](https://github.com/frappe/erpnext/pull/24097)) +- Sequence Matcher error in Bank Reconciliation ([#23539](https://github.com/frappe/erpnext/pull/23539)) +- Shipping charges not sync from shopify ([#24009](https://github.com/frappe/erpnext/pull/24009)) +- Delete Receive at Warehouse entry on cancellation of Send to War… ([#24068](https://github.com/frappe/erpnext/pull/24068)) +- Get formatted value in 'taxes' print template ([#24036](https://github.com/frappe/erpnext/pull/24036)) \ No newline at end of file From 00e095278d6873423ce7dcf9e46c02b25ab427cc Mon Sep 17 00:00:00 2001 From: Saurabh Date: Mon, 21 Dec 2020 16:25:38 +0550 Subject: [PATCH 57/57] bumped to version 12.15.0 --- erpnext/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/__init__.py b/erpnext/__init__.py index c719b74e687..65c4eacd549 100644 --- a/erpnext/__init__.py +++ b/erpnext/__init__.py @@ -5,7 +5,7 @@ import frappe from erpnext.hooks import regional_overrides from frappe.utils import getdate -__version__ = '12.14.0' +__version__ = '12.15.0' def get_default_company(user=None): '''Get default company for user'''
{%= __(range3) %} {%= __(range4) %} {%= __(range5) %}{%= __(range6) %} {%= __("Total") %}
{%= __("Total Outstanding") %}{%= format_number(balance_row["range1"], null, 2) %}{%= format_currency(balance_row["range2"]) %}{%= format_currency(balance_row["range3"]) %}{%= format_currency(balance_row["range4"]) %}{%= format_currency(balance_row["range5"]) %} + {%= format_number(balance_row["age"], null, 2) %} + + {%= format_currency(balance_row["range1"], data[data.length-1]["currency"]) %} + + {%= format_currency(balance_row["range2"], data[data.length-1]["currency"]) %} + + {%= format_currency(balance_row["range3"], data[data.length-1]["currency"]) %} + + {%= format_currency(balance_row["range4"], data[data.length-1]["currency"]) %} + + {%= format_currency(balance_row["range5"], data[data.length-1]["currency"]) %} + {%= format_currency(flt(balance_row["outstanding"]), data[data.length-1]["currency"]) %} -
{%= __("Future Payments") %} {%= format_currency(flt(balance_row[("future_amount")]), data[data.length-1]["currency"]) %} {%= format_currency(flt(balance_row["outstanding"] - balance_row[("future_amount")]), data[data.length-1]["currency"]) %}
{%= __("Total") %} - {%= format_currency(data[i]["invoiced"], data[0]["currency"] ) %} - {%= format_currency(data[i]["paid"], data[0]["currency"]) %}{%= format_currency(data[i]["credit_note"], data[0]["currency"]) %} {%= format_currency(data[i]["credit_note"], data[i]["currency"]) %} - {%= format_currency(data[i]["outstanding"], data[0]["currency"]) %}{%= data[i]["future_ref"] %}{%= format_currency(data[i]["future_amount"], data[0]["currency"]) %}{%= format_currency(data[i]["remaining_balance"], data[0]["currency"]) %}{%= format_currency(data[i]["future_amount"], data[i]["currency"]) %}{%= format_currency(data[i]["remaining_balance"], data[i]["currency"]) %}{%= __("Total") %}{%= format_currency(data[i]["invoiced"], data[0]["currency"]) %}{%= format_currency(data[i]["paid"], data[0]["currency"]) %}{%= format_currency(data[i]["credit_note"], data[0]["currency"]) %}{%= format_currency(data[i]["outstanding"], data[0]["currency"]) %}{%= format_currency(data[i]["invoiced"], data[i]["currency"]) %}{%= format_currency(data[i]["paid"], data[i]["currency"]) %}{%= format_currency(data[i]["credit_note"], data[i]["currency"]) %}{%= format_currency(data[i]["outstanding"], data[i]["currency"]) %}