From 1b9a4483d419217d964f3b3ec9ef6281ffa25422 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Fri, 1 Jul 2022 15:44:18 +0530 Subject: [PATCH 01/16] chore(meta): update CODEOWNERS --- CODEOWNERS | 36 +++++++++++++++++++----------------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/CODEOWNERS b/CODEOWNERS index a4a14de1b8e..b52062d2371 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -3,33 +3,35 @@ # These owners will be the default owners for everything in # the repo. Unless a later match takes precedence, -erpnext/accounts/ @nextchamp-saqib @deepeshgarg007 -erpnext/assets/ @nextchamp-saqib @deepeshgarg007 +erpnext/accounts/ @nextchamp-saqib @deepeshgarg007 @ruthra-kumar +erpnext/assets/ @nextchamp-saqib @deepeshgarg007 @ruthra-kumar erpnext/erpnext_integrations/ @nextchamp-saqib erpnext/loan_management/ @nextchamp-saqib @deepeshgarg007 -erpnext/regional @nextchamp-saqib @deepeshgarg007 -erpnext/selling @nextchamp-saqib @deepeshgarg007 +erpnext/regional @nextchamp-saqib @deepeshgarg007 @ruthra-kumar +erpnext/selling @nextchamp-saqib @deepeshgarg007 @ruthra-kumar erpnext/support/ @nextchamp-saqib @deepeshgarg007 pos* @nextchamp-saqib -erpnext/buying/ @marination @rohitwaghchaure @ankush +erpnext/buying/ @marination @rohitwaghchaure @s-aga-r erpnext/e_commerce/ @marination -erpnext/maintenance/ @marination @rohitwaghchaure -erpnext/manufacturing/ @marination @rohitwaghchaure @ankush +erpnext/maintenance/ @marination @rohitwaghchaure @s-aga-r +erpnext/manufacturing/ @marination @rohitwaghchaure @s-aga-r erpnext/portal/ @marination -erpnext/quality_management/ @marination @rohitwaghchaure +erpnext/quality_management/ @marination @rohitwaghchaure @s-aga-r erpnext/shopping_cart/ @marination -erpnext/stock/ @marination @rohitwaghchaure @ankush +erpnext/stock/ @marination @rohitwaghchaure @s-aga-r -erpnext/crm/ @ruchamahabal @pateljannat -erpnext/education/ @ruchamahabal @pateljannat -erpnext/healthcare/ @ruchamahabal @pateljannat @chillaranand -erpnext/hr/ @ruchamahabal @pateljannat +erpnext/crm/ @NagariaHussain +erpnext/education/ @rutwikhdev +erpnext/healthcare/ @chillaranand +erpnext/hr/ @ruchamahabal erpnext/non_profit/ @ruchamahabal -erpnext/payroll @ruchamahabal @pateljannat -erpnext/projects/ @ruchamahabal @pateljannat +erpnext/payroll @ruchamahabal +erpnext/projects/ @ruchamahabal erpnext/controllers @deepeshgarg007 @nextchamp-saqib @rohitwaghchaure @marination +erpnext/patches/ @deepeshgarg007 @nextchamp-saqib @marination rohitwaghchaure +erpnext/public/ @nextchamp-saqib @marination -.github/ @surajshetty3416 @ankush -requirements.txt @gavindsouza +.github/ @ankush +requirements.txt @gavindsouza @ankush From 8727a6c5da9a37ac2ec1d8b4c7c9c074ecf04b6d Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Thu, 30 Jun 2022 21:29:54 +0530 Subject: [PATCH 02/16] fix: Internal PI link in Sales Invoice (cherry picked from commit 536e768ba96a20c519f19dfd4c6b65acedd03aef) --- .../doctype/sales_invoice/sales_invoice.py | 29 +++++++++++++++++++ .../sales_invoice/sales_invoice_dashboard.py | 2 ++ 2 files changed, 31 insertions(+) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 069148c1199..ba15523c87a 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -2163,6 +2163,8 @@ def make_inter_company_transaction(doctype, source_name, target_doc=None): source_document_warehouse_field = "from_warehouse" target_document_warehouse_field = "target_warehouse" + received_items = get_received_items(source_name, target_doctype, target_detail_field) + validate_inter_company_transaction(source_doc, doctype) details = get_inter_company_details(source_doc, doctype) @@ -2227,12 +2229,17 @@ def make_inter_company_transaction(doctype, source_name, target_doc=None): shipping_address_name=target_doc.shipping_address_name, ) + def update_item(source, target, source_parent): + target.qty = flt(source.qty) - received_items.get(source.name, 0.0) + item_field_map = { "doctype": target_doctype + " Item", "field_no_map": ["income_account", "expense_account", "cost_center", "warehouse"], "field_map": { "rate": "rate", }, + "postprocess": update_item, + "condition": lambda doc: doc.qty > 0, } if doctype in ["Sales Invoice", "Sales Order"]: @@ -2270,6 +2277,28 @@ def make_inter_company_transaction(doctype, source_name, target_doc=None): return doclist +def get_received_items(reference_name, doctype, reference_fieldname): + target_doctypes = frappe.get_all( + doctype, + filters={"inter_company_invoice_reference": reference_name, "docstatus": 1}, + as_list=True, + ) + + if target_doctypes: + target_doctypes = list(target_doctypes[0]) + + received_items_map = frappe._dict( + frappe.get_all( + doctype + " Item", + filters={"parent": ("in", target_doctypes)}, + fields=[reference_fieldname, "qty"], + as_list=1, + ) + ) + + return received_items_map + + def set_purchase_references(doc): # add internal PO or PR links if any if doc.is_internal_transfer(): diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice_dashboard.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice_dashboard.py index c0005f78cfd..0a765f3f46f 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice_dashboard.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice_dashboard.py @@ -11,6 +11,7 @@ def get_data(): "Payment Request": "reference_name", "Sales Invoice": "return_against", "Auto Repeat": "reference_document", + "Purchase Invoice": "inter_company_invoice_reference", }, "internal_links": { "Sales Order": ["items", "sales_order"], @@ -30,5 +31,6 @@ def get_data(): {"label": _("Reference"), "items": ["Timesheet", "Delivery Note", "Sales Order"]}, {"label": _("Returns"), "items": ["Sales Invoice"]}, {"label": _("Subscription"), "items": ["Auto Repeat"]}, + {"label": _("Internal Transfers"), "items": ["Purchase Invoice"]}, ], } From fd2ec25588d1c9dbfcda37c07ddbcd01f9d4b6cc Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sun, 19 Jun 2022 21:18:12 +0530 Subject: [PATCH 03/16] feat: Cash and Non trade discounts in Sales Invoice (cherry picked from commit 169ff5a0dd074bc197376d94a977a9ab3f4bb495) # Conflicts: # erpnext/accounts/doctype/sales_invoice/sales_invoice.js --- .../doctype/sales_invoice/sales_invoice.js | 11 +++++++++++ .../doctype/sales_invoice/sales_invoice.json | 14 ++++++++++---- .../doctype/sales_invoice/sales_invoice.py | 18 +++++++++++++++++- erpnext/controllers/taxes_and_totals.py | 7 +++++++ 4 files changed, 45 insertions(+), 5 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js index a36872fb234..cdfb631284c 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js @@ -477,7 +477,18 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte this.frm.trigger("calculate_timesheet_totals"); } } +<<<<<<< HEAD }); +======= + + is_cash_or_non_trade_discount() { + this.frm.set_df_property("additional_discount_account", "hidden", 1 - this.frm.doc.is_cash_or_non_trade_discount); + if (!this.frm.doc.is_cash_or_non_trade_discount) { + this.frm.set_value("additional_discount_account", ""); + } + } +}; +>>>>>>> 169ff5a0dd (feat: Cash and Non trade discounts in Sales Invoice) // for backward compatibility: combine new and previous states $.extend(cur_frm.cscript, new erpnext.accounts.SalesInvoiceController({frm: cur_frm})); diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json index 327545aa54e..499377d4263 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json @@ -106,6 +106,7 @@ "loyalty_redemption_cost_center", "section_break_49", "apply_discount_on", + "is_cash_or_non_trade_discount", "base_discount_amount", "additional_discount_account", "column_break_51", @@ -1790,8 +1791,6 @@ "width": "50%" }, { - "fetch_from": "sales_partner.commission_rate", - "fetch_if_empty": 1, "fieldname": "commission_rate", "fieldtype": "Float", "hide_days": 1, @@ -1990,7 +1989,7 @@ { "fieldname": "additional_discount_account", "fieldtype": "Link", - "label": "Additional Discount Account", + "label": "Discount Account", "options": "Account" }, { @@ -2028,6 +2027,13 @@ "fieldtype": "Currency", "label": "Amount Eligible for Commission", "read_only": 1 + }, + { + "default": "0", + "depends_on": "eval: doc.apply_discount_on == \"Grand Total\"", + "fieldname": "is_cash_or_non_trade_discount", + "fieldtype": "Check", + "label": "Is Cash or Non Trade Discount" } ], "icon": "fa fa-file-text", @@ -2040,7 +2046,7 @@ "link_fieldname": "consolidated_invoice" } ], - "modified": "2022-06-10 03:52:51.409913", + "modified": "2022-06-16 16:22:44.870575", "modified_by": "Administrator", "module": "Accounts", "name": "Sales Invoice", diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index ba15523c87a..b8f1e314e82 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -1040,7 +1040,7 @@ class SalesInvoice(SellingController): ) if grand_total and not self.is_internal_transfer(): - # Didnot use base_grand_total to book rounding loss gle + # Did not use base_grand_total to book rounding loss gle gl_entries.append( self.get_gl_dict( { @@ -1065,6 +1065,22 @@ class SalesInvoice(SellingController): ) ) + if self.apply_discount_on == "Grand Total" and self.get("is_cash_or_discount_account"): + gl_entries.append( + self.get_gl_dict( + { + "account": self.additional_discount_account, + "against": self.debit_to, + "debit": self.base_discount_amount, + "debit_in_account_currency": self.discount_amount, + "cost_center": self.cost_center, + "project": self.project, + }, + self.currency, + item=self, + ) + ) + def make_tax_gl_entries(self, gl_entries): for tax in self.get("taxes"): amount, base_amount = self.get_tax_amounts(tax, self.enable_discount_accounting) diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py index 2afba91b379..4801315c977 100644 --- a/erpnext/controllers/taxes_and_totals.py +++ b/erpnext/controllers/taxes_and_totals.py @@ -500,6 +500,9 @@ class calculate_taxes_and_totals(object): else: self.doc.grand_total = flt(self.doc.net_total) + if self.doc.apply_discount_on == "Grand Total" and self.doc.get("is_cash_or_non_trade_discount"): + self.doc.grand_total -= self.doc.discount_amount + if self.doc.get("taxes"): self.doc.total_taxes_and_charges = flt( self.doc.grand_total - self.doc.net_total - flt(self.doc.rounding_adjustment), @@ -594,6 +597,10 @@ class calculate_taxes_and_totals(object): if not self.doc.apply_discount_on: frappe.throw(_("Please select Apply Discount On")) + if self.doc.apply_discount_on == "Grand Total" and self.doc.is_cash_or_non_trade_discount: + self.discount_amount_applied = True + return + self.doc.base_discount_amount = flt( self.doc.discount_amount * self.doc.conversion_rate, self.doc.precision("base_discount_amount") ) From 9ba7290dc9c4643429c8c435c667f80b714bf3e7 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sun, 19 Jun 2022 21:19:02 +0530 Subject: [PATCH 04/16] fix(India): Discounts in E-Invoicing (cherry picked from commit f337213f33384aea9d80ef97d96d0097d436bd68) --- erpnext/regional/india/e_invoice/utils.py | 27 ++++++++++++++++------- erpnext/regional/india/utils.py | 12 ++++++++-- 2 files changed, 29 insertions(+), 10 deletions(-) diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py index 0d53e702d8e..a3abc28104a 100644 --- a/erpnext/regional/india/e_invoice/utils.py +++ b/erpnext/regional/india/e_invoice/utils.py @@ -272,14 +272,18 @@ def get_item_list(invoice): item.description = sanitize_for_json(d.item_name) item.qty = abs(item.qty) - if flt(item.qty) != 0.0: - item.unit_rate = abs(item.taxable_value / item.qty) - else: - item.unit_rate = abs(item.taxable_value) - item.gross_amount = abs(item.taxable_value) - item.taxable_value = abs(item.taxable_value) - item.discount_amount = 0 + if invoice.get("apply_discount_on"): + item.discount_amount = item.base_amount - item.base_net_amount + elif item.discount_amount > 0: + item.discount_amount = item.discount_amount + else: + item.discount_amount = 0 + + item.unit_rate = abs(item.taxable_value - item.discount_amount) / item.qty + + item.gross_amount = abs(item.taxable_value) + item.discount_amount + item.taxable_value = abs(item.taxable_value) item.is_service_item = "Y" if item.gst_hsn_code and item.gst_hsn_code[:2] == "99" else "N" item.serial_no = "" @@ -353,7 +357,14 @@ def update_item_taxes(invoice, item): def get_invoice_value_details(invoice): invoice_value_details = frappe._dict(dict()) invoice_value_details.base_total = abs(sum([i.taxable_value for i in invoice.get("items")])) - invoice_value_details.invoice_discount_amt = 0 + if ( + invoice.apply_discount_on == "Grand Total" + and invoice.discount_amount + and invoice.get("is_cash_or_non_trade_discount") + ): + invoice_value_details.invoice_discount_amt = invoice.base_discount_amount + else: + invoice_value_details.invoice_discount_amt = 0 invoice_value_details.round_off = invoice.base_rounding_adjustment invoice_value_details.base_grand_total = abs(invoice.base_rounded_total) or abs( diff --git a/erpnext/regional/india/utils.py b/erpnext/regional/india/utils.py index eb9d6743e13..378f39a7e21 100644 --- a/erpnext/regional/india/utils.py +++ b/erpnext/regional/india/utils.py @@ -1061,8 +1061,16 @@ def update_taxable_values(doc, method): considered_rows.append(prev_row_id) for item in doc.get("items"): - proportionate_value = item.base_net_amount if doc.base_net_total else item.qty - total_value = doc.base_net_total if doc.base_net_total else doc.total_qty + if ( + doc.apply_discount_on == "Grand Total" + and doc.discount_amount + and doc.get("is_cash_or_non_trade_discount") + ): + proportionate_value = item.base_amount if doc.base_total else item.qty + total_value = doc.base_total if doc.base_total else doc.total_qty + else: + proportionate_value = item.base_net_amount if doc.base_net_total else item.qty + total_value = doc.base_net_total if doc.base_net_total else doc.total_qty applicable_charges = flt( flt( From 57dc1026c873099b414e3d5756d8bc94f132dada Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sat, 2 Jul 2022 22:27:20 +0530 Subject: [PATCH 05/16] test: Add test for einvoice discounts (cherry picked from commit 38352b3e46fb18435c780e5775bbc886491eac96) --- .../sales_invoice/test_sales_invoice.py | 57 +++++++++++++++++++ erpnext/regional/india/e_invoice/utils.py | 6 +- 2 files changed, 58 insertions(+), 5 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index 16210e78bdd..2ff1b34e35e 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -2620,6 +2620,63 @@ class TestSalesInvoice(unittest.TestCase): einvoice = make_einvoice(si) validate_totals(einvoice) + def test_einvoice_discounts(self): + from erpnext.regional.india.e_invoice.utils import make_einvoice, validate_totals + + # Normal Itemized Discount + si = get_sales_invoice_for_e_invoice() + si.apply_discount_on = "" + si.items[0].discount_amount = 4000 + si.items[1].discount_amount = 300 + si.save() + + einvoice = make_einvoice(si) + validate_totals(einvoice) + + self.assertEqual(einvoice["ItemList"][0]["Discount"], 4000) + self.assertEqual(einvoice["ItemList"][1]["Discount"], 300) + self.assertEqual(einvoice["ValDtls"]["Discount"], 0) + + # Invoice Discount on net total + si = get_sales_invoice_for_e_invoice() + si.apply_discount_on = "Net Total" + si.discount_amount = 400 + si.save() + + einvoice = make_einvoice(si) + validate_totals(einvoice) + + self.assertEqual(einvoice["ItemList"][0]["Discount"], 316.83) + self.assertEqual(einvoice["ItemList"][1]["Discount"], 83.17) + self.assertEqual(einvoice["ValDtls"]["Discount"], 0) + + # Invoice Discount on grand total (Itemized Discount) + si = get_sales_invoice_for_e_invoice() + si.apply_discount_on = "Grand Total" + si.discount_amount = 400 + si.save() + + einvoice = make_einvoice(si) + validate_totals(einvoice) + + self.assertEqual(einvoice["ItemList"][0]["Discount"], 268.5) + self.assertEqual(einvoice["ItemList"][1]["Discount"], 70.48) + self.assertEqual(einvoice["ValDtls"]["Discount"], 0) + + # Invoice Discount on grand total (Cash/Non-Trade Discount) + si = get_sales_invoice_for_e_invoice() + si.apply_discount_on = "Grand Total" + si.is_cash_or_non_trade_discount = 1 + si.discount_amount = 400 + si.save() + + einvoice = make_einvoice(si) + validate_totals(einvoice) + + self.assertEqual(einvoice["ItemList"][0]["Discount"], 0) + self.assertEqual(einvoice["ItemList"][1]["Discount"], 0) + self.assertEqual(einvoice["ValDtls"]["Discount"], 400) + def test_item_tax_net_range(self): item = create_item("T Shirt") diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py index a3abc28104a..d4a2e026453 100644 --- a/erpnext/regional/india/e_invoice/utils.py +++ b/erpnext/regional/india/e_invoice/utils.py @@ -275,10 +275,6 @@ def get_item_list(invoice): if invoice.get("apply_discount_on"): item.discount_amount = item.base_amount - item.base_net_amount - elif item.discount_amount > 0: - item.discount_amount = item.discount_amount - else: - item.discount_amount = 0 item.unit_rate = abs(item.taxable_value - item.discount_amount) / item.qty @@ -362,7 +358,7 @@ def get_invoice_value_details(invoice): and invoice.discount_amount and invoice.get("is_cash_or_non_trade_discount") ): - invoice_value_details.invoice_discount_amt = invoice.base_discount_amount + invoice_value_details.invoice_discount_amt = invoice.discount_amount else: invoice_value_details.invoice_discount_amt = 0 From 4a5d681c77f2a4c61538146318daffc3562e83ab Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sun, 3 Jul 2022 11:02:21 +0530 Subject: [PATCH 06/16] chore: use get instead of . operator (cherry picked from commit e54ec4b9b6280bf1d12ebd3f0fc8c704551f9652) --- erpnext/controllers/taxes_and_totals.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py index 4801315c977..589c75783fe 100644 --- a/erpnext/controllers/taxes_and_totals.py +++ b/erpnext/controllers/taxes_and_totals.py @@ -597,7 +597,9 @@ class calculate_taxes_and_totals(object): if not self.doc.apply_discount_on: frappe.throw(_("Please select Apply Discount On")) - if self.doc.apply_discount_on == "Grand Total" and self.doc.is_cash_or_non_trade_discount: + if self.doc.apply_discount_on == "Grand Total" and self.doc.get( + "is_cash_or_non_trade_discount" + ): self.discount_amount_applied = True return From c72ae178fa9e0435d97791d027533602abcdf5a0 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sun, 3 Jul 2022 14:05:34 +0530 Subject: [PATCH 07/16] chore: resolve conflicts --- erpnext/accounts/doctype/sales_invoice/sales_invoice.js | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js index cdfb631284c..8f7038df30e 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js @@ -476,10 +476,7 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte }); this.frm.trigger("calculate_timesheet_totals"); } - } -<<<<<<< HEAD -}); -======= + }, is_cash_or_non_trade_discount() { this.frm.set_df_property("additional_discount_account", "hidden", 1 - this.frm.doc.is_cash_or_non_trade_discount); @@ -487,8 +484,8 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte this.frm.set_value("additional_discount_account", ""); } } -}; ->>>>>>> 169ff5a0dd (feat: Cash and Non trade discounts in Sales Invoice) + +}); // for backward compatibility: combine new and previous states $.extend(cur_frm.cscript, new erpnext.accounts.SalesInvoiceController({frm: cur_frm})); From 5fc93076f47cd92f48c10c7cf0e2f62a264959cc Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Mon, 4 Jul 2022 13:18:34 +0530 Subject: [PATCH 08/16] chore: ignore late binding warnings Most are false positives [skip ci] --- .github/helper/.flake8_strict | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/helper/.flake8_strict b/.github/helper/.flake8_strict index a79137d7c32..198ec7bfe54 100644 --- a/.github/helper/.flake8_strict +++ b/.github/helper/.flake8_strict @@ -66,6 +66,7 @@ ignore = F841, E713, E712, + B023 max-line-length = 200 From 31fd263825a811942d20e6a48b8d92c07b642f7d Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 4 Jul 2022 17:02:11 +0530 Subject: [PATCH 09/16] fix(UX): dont apply price list when changing batch on mapped docs (backport #31503) (#31504) fix(UX): dont apply price list when changing batch on mapped docs (#31503) fix(UX): dont apply price list batch change on mapped docs (cherry picked from commit 7e40c86c56c81abbbfa23ef79b7e14b9a6a47090) Co-authored-by: Ankush Menat --- erpnext/public/js/controllers/transaction.js | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 3a51dd1a261..b83d5a8d7ae 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -1223,9 +1223,25 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ } }, + is_a_mapped_document(item) { + const mapped_item_field_map = { + "Delivery Note Item": ["si_detail", "so_detail", "dn_detail"], + "Sales Invoice Item": ["dn_detail", "so_detail", "sales_invoice_item"], + "Purchase Receipt Item": ["purchase_order_item", "purchase_invoice_item", "purchase_receipt_item"], + "Purchase Invoice Item": ["purchase_order_item", "pr_detail", "po_detail"], + }; + const mappped_fields = mapped_item_field_map[item.doctype] || []; + + return mappped_fields + .map((field) => item[field]) + .filter(Boolean).length > 0; + }, + batch_no: function(doc, cdt, cdn) { let item = frappe.get_doc(cdt, cdn); - this.apply_price_list(item, true); + if (!this.is_a_mapped_document(item)) { + this.apply_price_list(item, true); + } }, toggle_conversion_factor: function(item) { From d182137ed17bd288f76318fdfe1aeb443d5e5b2c Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 4 Jul 2022 18:38:26 +0530 Subject: [PATCH 10/16] fix: Incorrect provisional expense booking while reposting (cherry picked from commit 60aad31162aedb7be484cde53d7ca069065b2002) --- erpnext/stock/doctype/purchase_receipt/purchase_receipt.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index cca3f05270c..f859791de62 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -468,6 +468,7 @@ class PurchaseReceipt(BuyingController): and not d.is_fixed_asset and flt(d.qty) and provisional_accounting_for_non_stock_items + and d.get("provisional_expense_account") ): self.add_provisional_gl_entry( d, gl_entries, self.posting_date, d.get("provisional_expense_account") From 6bda2a086515c24c7d0593d1d419eb13aaa7fa5a Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Tue, 5 Jul 2022 21:20:51 +0530 Subject: [PATCH 11/16] chore: deprecation warning for remove-india --- erpnext/patches.txt | 1 + ...show_india_localisation_deprecation_warning.py | 15 +++++++++++++++ 2 files changed, 16 insertions(+) create mode 100644 erpnext/patches/v13_0/show_india_localisation_deprecation_warning.py diff --git a/erpnext/patches.txt b/erpnext/patches.txt index f639dc7d380..bdc43dd094a 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -368,3 +368,4 @@ erpnext.patches.v13_0.set_per_billed_in_return_delivery_note erpnext.patches.v13_0.update_employee_advance_status erpnext.patches.v13_0.job_card_status_on_hold erpnext.patches.v13_0.add_cost_center_in_loans +erpnext.patches.v13_0.show_india_localisation_deprecation_warning \ No newline at end of file diff --git a/erpnext/patches/v13_0/show_india_localisation_deprecation_warning.py b/erpnext/patches/v13_0/show_india_localisation_deprecation_warning.py new file mode 100644 index 00000000000..1d76b252192 --- /dev/null +++ b/erpnext/patches/v13_0/show_india_localisation_deprecation_warning.py @@ -0,0 +1,15 @@ +import click +import frappe + + +def execute(): + if not frappe.db.exists("Company", {"country": "India"}): + return + + click.secho( + "India-specific regional features have been moved to a separate app" + " and will be removed from ERPNext in Version 14." + " Please install India Compliance after upgrading to Version 14:\n" + "https://github.com/resilient-tech/india-compliance", + fg="yellow", + ) From 2a432c22d4c1891518cfd23a1a3028ccfc21fcfc Mon Sep 17 00:00:00 2001 From: marination Date: Tue, 5 Jul 2022 19:43:02 +0530 Subject: [PATCH 12/16] fix: Use fallback conversion factor while setting incoming rate for petty purchase - PIs for petty items (that do not need an Item record) are allowed using Item Name field - If a different UOM is used in this case, conversion factor stays 0 and causes an error - Fallback to 1 in `set_incoming_rate` for buying - Selling will need a proper item, so this change is not needed there (cherry picked from commit aa043fe9617e15ff6f6999e968ca09ec2c68e66f) --- erpnext/controllers/buying_controller.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py index 0a605345662..64e70b1aef5 100644 --- a/erpnext/controllers/buying_controller.py +++ b/erpnext/controllers/buying_controller.py @@ -299,7 +299,7 @@ class BuyingController(StockController, Subcontracting): raise_error_if_no_rate=False, ) - rate = flt(outgoing_rate * d.conversion_factor, d.precision("rate")) + rate = flt(outgoing_rate * (d.conversion_factor or 1), d.precision("rate")) else: rate = frappe.db.get_value(ref_doctype, d.get(frappe.scrub(ref_doctype)), "rate") From 07b80c295d4f81143b48a0a666b38c45f7e41be3 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Mon, 4 Jul 2022 20:24:18 +0530 Subject: [PATCH 13/16] fix: timeout error while reposting (cherry picked from commit 78c8bb251eff8450ece23a312bae3e82962a2beb) # Conflicts: # erpnext/stock/doctype/stock_entry/test_stock_entry.py --- .../doctype/stock_entry/test_stock_entry.py | 136 ++++++++++++++++++ erpnext/stock/stock_ledger.py | 19 +-- 2 files changed, 142 insertions(+), 13 deletions(-) diff --git a/erpnext/stock/doctype/stock_entry/test_stock_entry.py b/erpnext/stock/doctype/stock_entry/test_stock_entry.py index 8703aefdda7..686c914652b 100644 --- a/erpnext/stock/doctype/stock_entry/test_stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/test_stock_entry.py @@ -5,8 +5,12 @@ import frappe from frappe.permissions import add_user_permission, remove_user_permission from frappe.tests.utils import FrappeTestCase, change_settings +<<<<<<< HEAD from frappe.utils import flt, nowdate, nowtime from six import iteritems +======= +from frappe.utils import add_days, flt, nowdate, nowtime +>>>>>>> 78c8bb251e (fix: timeout error while reposting) from erpnext.accounts.doctype.account.test_account import get_inventory_account from erpnext.stock.doctype.item.test_item import ( @@ -1414,6 +1418,138 @@ class TestStockEntry(FrappeTestCase): self.assertEqual(se.items[0].item_name, item.item_name) self.assertEqual(se.items[0].stock_uom, item.stock_uom) + def test_reposting_for_depedent_warehouse(self): + from erpnext.stock.doctype.repost_item_valuation.repost_item_valuation import repost_sl_entries + from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse + + # Inward at WH1 warehouse (Component) + # 1st Repack (Component (WH1) - Subcomponent (WH2)) + # 2nd Repack (Subcomponent (WH2) - FG Item (WH3)) + # Material Transfer of FG Item -> WH 3 -> WH2 -> Wh1 (Two transfer entries) + # Backdated transction which should update valuation rate in repack as well trasfer entries + + for item_code in ["FG Item 1", "Sub Component 1", "Component 1"]: + create_item(item_code) + + for warehouse in ["WH 1", "WH 2", "WH 3"]: + create_warehouse(warehouse) + + make_stock_entry( + item_code="Component 1", + rate=100, + purpose="Material Receipt", + qty=10, + to_warehouse="WH 1 - _TC", + posting_date=add_days(nowdate(), -10), + ) + + repack1 = make_stock_entry( + item_code="Component 1", + purpose="Repack", + do_not_save=True, + qty=10, + from_warehouse="WH 1 - _TC", + posting_date=add_days(nowdate(), -9), + ) + + repack1.append( + "items", + { + "item_code": "Sub Component 1", + "qty": 10, + "t_warehouse": "WH 2 - _TC", + "transfer_qty": 10, + "uom": "Nos", + "stock_uom": "Nos", + "conversion_factor": 1.0, + }, + ) + + repack1.save() + repack1.submit() + + self.assertEqual(repack1.items[1].basic_rate, 100) + self.assertEqual(repack1.items[1].amount, 1000) + + repack2 = make_stock_entry( + item_code="Sub Component 1", + purpose="Repack", + do_not_save=True, + qty=10, + from_warehouse="WH 2 - _TC", + posting_date=add_days(nowdate(), -8), + ) + + repack2.append( + "items", + { + "item_code": "FG Item 1", + "qty": 10, + "t_warehouse": "WH 3 - _TC", + "transfer_qty": 10, + "uom": "Nos", + "stock_uom": "Nos", + "conversion_factor": 1.0, + }, + ) + + repack2.save() + repack2.submit() + + self.assertEqual(repack2.items[1].basic_rate, 100) + self.assertEqual(repack2.items[1].amount, 1000) + + transfer1 = make_stock_entry( + item_code="FG Item 1", + purpose="Material Transfer", + qty=10, + from_warehouse="WH 3 - _TC", + to_warehouse="WH 2 - _TC", + posting_date=add_days(nowdate(), -7), + ) + + self.assertEqual(transfer1.items[0].basic_rate, 100) + self.assertEqual(transfer1.items[0].amount, 1000) + + transfer2 = make_stock_entry( + item_code="FG Item 1", + purpose="Material Transfer", + qty=10, + from_warehouse="WH 2 - _TC", + to_warehouse="WH 1 - _TC", + posting_date=add_days(nowdate(), -6), + ) + + self.assertEqual(transfer2.items[0].basic_rate, 100) + self.assertEqual(transfer2.items[0].amount, 1000) + + # Backdated transaction + receipt2 = make_stock_entry( + item_code="Component 1", + rate=200, + purpose="Material Receipt", + qty=10, + to_warehouse="WH 1 - _TC", + posting_date=add_days(nowdate(), -15), + ) + + self.assertEqual(receipt2.items[0].basic_rate, 200) + self.assertEqual(receipt2.items[0].amount, 2000) + + repost_name = frappe.db.get_value( + "Repost Item Valuation", {"voucher_no": receipt2.name, "docstatus": 1}, "name" + ) + + doc = frappe.get_doc("Repost Item Valuation", repost_name) + repost_sl_entries(doc) + + for obj in [repack1, repack2, transfer1, transfer2]: + obj.load_from_db() + + index = 1 if obj.purpose == "Repack" else 0 + self.assertEqual(obj.items[index].basic_rate, 200) + self.assertEqual(obj.items[index].basic_amount, 2000) + def make_serialized_item(**args): args = frappe._dict(args) diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index da57ba054dc..068cc12ed91 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -247,16 +247,11 @@ def repost_future_sle( data.sle_changed = False i += 1 - if doc and i % 2 == 0: + if doc: update_args_in_repost_item_valuation( doc, i, args, distinct_item_warehouses, affected_transactions ) - if doc and args: - update_args_in_repost_item_valuation( - doc, i, args, distinct_item_warehouses, affected_transactions - ) - def update_args_in_repost_item_valuation( doc, index, args, distinct_item_warehouses, affected_transactions @@ -491,7 +486,8 @@ class update_entries_after(object): elif dependant_sle.item_code == self.item_code and dependant_sle.warehouse in self.data: return entries_to_fix else: - return self.append_future_sle_for_dependant(dependant_sle, entries_to_fix) + self.append_future_sle_for_dependant(dependant_sle, entries_to_fix) + return entries_to_fix def update_distinct_item_warehouses(self, dependant_sle): key = (dependant_sle.item_code, dependant_sle.warehouse) @@ -510,14 +506,11 @@ class update_entries_after(object): def append_future_sle_for_dependant(self, dependant_sle, entries_to_fix): self.initialize_previous_data(dependant_sle) - - args = self.data[dependant_sle.warehouse].previous_sle or frappe._dict( - {"item_code": self.item_code, "warehouse": dependant_sle.warehouse} + self.distinct_item_warehouses[(self.item_code, dependant_sle.warehouse)] = frappe._dict( + {"sle": dependant_sle} ) - future_sle_for_dependant = list(self.get_sle_after_datetime(args)) - entries_to_fix.extend(future_sle_for_dependant) - return sorted(entries_to_fix, key=lambda k: k["timestamp"]) + self.new_items_found = True def process_sle(self, sle): from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos From 2045df19f99eb7886b20645fac9a210223d663b0 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Wed, 6 Jul 2022 12:34:10 +0530 Subject: [PATCH 14/16] fix: conflicts --- erpnext/stock/doctype/stock_entry/test_stock_entry.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/erpnext/stock/doctype/stock_entry/test_stock_entry.py b/erpnext/stock/doctype/stock_entry/test_stock_entry.py index 686c914652b..6156a6458b6 100644 --- a/erpnext/stock/doctype/stock_entry/test_stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/test_stock_entry.py @@ -5,12 +5,8 @@ import frappe from frappe.permissions import add_user_permission, remove_user_permission from frappe.tests.utils import FrappeTestCase, change_settings -<<<<<<< HEAD -from frappe.utils import flt, nowdate, nowtime -from six import iteritems -======= from frappe.utils import add_days, flt, nowdate, nowtime ->>>>>>> 78c8bb251e (fix: timeout error while reposting) +from six import iteritems from erpnext.accounts.doctype.account.test_account import get_inventory_account from erpnext.stock.doctype.item.test_item import ( From b0e17dea2aa9abb1e57f03680b7799011ef9eb55 Mon Sep 17 00:00:00 2001 From: marination Date: Thu, 7 Jul 2022 16:59:23 +0530 Subject: [PATCH 15/16] fix: Use Contact Name instead of Supplier in RFQ Email (cherry picked from commit 88ac519b2439603a65b720f935887fd7287005be) --- .../request_for_quotation/request_for_quotation.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py index 1122e7fc573..8b5bf6f660f 100644 --- a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py +++ b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py @@ -181,12 +181,19 @@ class RequestforQuotation(BuyingController): doc_args = self.as_dict() doc_args.update({"supplier": data.get("supplier"), "supplier_name": data.get("supplier_name")}) + # Get Contact Full Name + if data.get("contact"): + contact_name = frappe.db.get_value( + "Contact", data.get("contact"), ["first_name", "middle_name", "last_name"] + ) + supplier_name = (" ").join(x for x in contact_name if x) # remove any blank values + args = { "update_password_link": update_password_link, "message": frappe.render_template(self.message_for_supplier, doc_args), "rfq_link": rfq_link, "user_fullname": full_name, - "supplier_name": data.get("supplier_name"), + "supplier_name": supplier_name or data.get("supplier_name"), "supplier_salutation": self.salutation or "Dear Mx.", } From b331c462efcb62e9558b00cb5ab1447f744f55bf Mon Sep 17 00:00:00 2001 From: marination Date: Fri, 8 Jul 2022 15:38:44 +0530 Subject: [PATCH 16/16] chore: Instantiate variable unconditionally (cherry picked from commit 300e812a1f2b8752a66c009560ec99e34a07abd1) --- .../doctype/request_for_quotation/request_for_quotation.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py index 8b5bf6f660f..bc32b1d1971 100644 --- a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py +++ b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py @@ -182,6 +182,7 @@ class RequestforQuotation(BuyingController): doc_args.update({"supplier": data.get("supplier"), "supplier_name": data.get("supplier_name")}) # Get Contact Full Name + supplier_name = None if data.get("contact"): contact_name = frappe.db.get_value( "Contact", data.get("contact"), ["first_name", "middle_name", "last_name"]