From 38dabdf584c54b7a5bb1a7b57f4dc0278bb186ec Mon Sep 17 00:00:00 2001 From: Sugesh G <73237300+Sugesh393@users.noreply.github.com> Date: Wed, 12 Mar 2025 16:13:35 +0530 Subject: [PATCH 01/44] fix: use shipping_address_name for address validation in sales invoice (#46473) * fix: validate address and contact related to party * fix: solve unboundlocal error * refactor: improve variable scope * refactor: translatable strings * fix: use shipping_address_name for address validation in sales invoice * test: add new unit test for address and contact validation * chore: to avoid keyerror --------- Co-authored-by: ruthra kumar (cherry picked from commit 0bdb81db53136bea54e1e703be5a3c80ccb29be4) --- erpnext/controllers/accounts_controller.py | 40 +++++++++++++ .../tests/test_accounts_controller.py | 56 +++++++++++++++++++ 2 files changed, 96 insertions(+) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 55889c3b4c3..21f339891bf 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -271,6 +271,7 @@ class AccountsController(TransactionBase): self.set_total_in_words() self.set_default_letter_head() self.validate_company_in_accounting_dimension() + self.validate_party_address_and_contact() def set_default_letter_head(self): if hasattr(self, "letter_head") and not self.letter_head: @@ -441,6 +442,45 @@ class AccountsController(TransactionBase): ) ) + def validate_party_address_and_contact(self): + party, party_type = None, None + if self.get("customer"): + party, party_type = self.customer, "Customer" + billing_address, shipping_address = ( + self.get("customer_address"), + self.get("shipping_address_name"), + ) + self.validate_party_address(party, party_type, billing_address, shipping_address) + elif self.get("supplier"): + party, party_type = self.supplier, "Supplier" + billing_address = self.get("supplier_address") + self.validate_party_address(party, party_type, billing_address) + + if party and party_type: + self.validate_party_contact(party, party_type) + + def validate_party_address(self, party, party_type, billing_address, shipping_address=None): + if billing_address or shipping_address: + party_address = frappe.get_list( + "Dynamic Link", + {"link_doctype": party_type, "link_name": party, "parenttype": "Address"}, + pluck="parent", + ) + if billing_address and billing_address not in party_address: + frappe.throw(_("Billing Address does not belong to the {0}").format(party)) + elif shipping_address and shipping_address not in party_address: + frappe.throw(_("Shipping Address does not belong to the {0}").format(party)) + + def validate_party_contact(self, party, party_type): + if self.get("contact_person"): + contact = frappe.get_list( + "Dynamic Link", + {"link_doctype": party_type, "link_name": party, "parenttype": "Contact"}, + pluck="parent", + ) + if self.contact_person and self.contact_person not in contact: + frappe.throw(_("Contact Person does not belong to the {0}").format(party)) + def validate_return_against_account(self): if self.doctype in ["Sales Invoice", "Purchase Invoice"] and self.is_return and self.return_against: cr_dr_account_field = "debit_to" if self.doctype == "Sales Invoice" else "credit_to" diff --git a/erpnext/controllers/tests/test_accounts_controller.py b/erpnext/controllers/tests/test_accounts_controller.py index f959cbd0488..28536893ab5 100644 --- a/erpnext/controllers/tests/test_accounts_controller.py +++ b/erpnext/controllers/tests/test_accounts_controller.py @@ -2175,3 +2175,59 @@ class TestAccountsController(FrappeTestCase): si_1 = create_sales_invoice(do_not_submit=True) si_1.items[0].project = project.name self.assertRaises(frappe.ValidationError, si_1.save) + + def test_party_billing_and_shipping_address(self): + from erpnext.crm.doctype.prospect.test_prospect import make_address + + customer_billing = make_address(address_title="Customer") + customer_billing.append("links", {"link_doctype": "Customer", "link_name": "_Test Customer"}) + customer_billing.save() + supplier_billing = make_address(address_title="Supplier", address_line1="2", city="Ahmedabad") + supplier_billing.append("links", {"link_doctype": "Supplier", "link_name": "_Test Supplier"}) + supplier_billing.save() + + customer_shipping = make_address( + address_title="Customer", address_type="Shipping", address_line1="10" + ) + customer_shipping.append("links", {"link_doctype": "Customer", "link_name": "_Test Customer"}) + customer_shipping.save() + supplier_shipping = make_address( + address_title="Supplier", address_type="Shipping", address_line1="20", city="Ahmedabad" + ) + supplier_shipping.append("links", {"link_doctype": "Supplier", "link_name": "_Test Supplier"}) + supplier_shipping.save() + + si = create_sales_invoice(do_not_save=True) + si.customer_address = supplier_billing.name + self.assertRaises(frappe.ValidationError, si.save) + si.customer_address = customer_billing.name + si.save() + + si.shipping_address_name = supplier_shipping.name + self.assertRaises(frappe.ValidationError, si.save) + si.shipping_address_name = customer_shipping.name + si.reload() + si.save() + + pi = make_purchase_invoice(do_not_save=True) + pi.supplier_address = customer_shipping.name + self.assertRaises(frappe.ValidationError, pi.save) + pi.supplier_address = supplier_shipping.name + pi.save() + + def test_party_contact(self): + from frappe.contacts.doctype.contact.test_contact import create_contact + + customer_contact = create_contact(name="Customer", salutation="Mr", save=False) + customer_contact.append("links", {"link_doctype": "Customer", "link_name": "_Test Customer"}) + customer_contact.save() + + supplier_contact = create_contact(name="Supplier", salutation="Mr", save=False) + supplier_contact.append("links", {"link_doctype": "Supplier", "link_name": "_Test Supplier"}) + supplier_contact.save() + + si = create_sales_invoice(do_not_save=True) + si.contact_person = supplier_contact.name + self.assertRaises(frappe.ValidationError, si.save) + si.contact_person = customer_contact.name + si.save() From 950656d6f7b5350059a28a1f31285b1879141475 Mon Sep 17 00:00:00 2001 From: Sanket Shah <113279972+Sanket322@users.noreply.github.com> Date: Wed, 12 Mar 2025 22:37:08 +0530 Subject: [PATCH 02/44] fix: using `in` for lookup in list instead of directly assigning (#46492) fix: using in for lookup in list instead of assigning Co-authored-by: Sanket322 (cherry picked from commit 38955af802197cf1a49b63a0dbb2e1decc730cfb) --- .../accounts/report/accounts_receivable/accounts_receivable.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py index c7a0da5afe9..e2260876490 100644 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py @@ -745,7 +745,8 @@ class ReceivablePayableReport: for party_type in self.party_type: party_field = scrub(party_type) if self.filters.get(party_field): - or_filters.update({party_field: self.filters.get(party_field)}) + or_filters.update({party_field: ["in", self.filters.get(party_field)]}) + self.return_entries = frappe._dict( frappe.get_all( doctype, filters=filters, or_filters=or_filters, fields=["name", "return_against"], as_list=1 From 0223651b5be6130826bb3b6b6e46d9d0a67e19c6 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 10 Mar 2025 16:24:43 +0530 Subject: [PATCH 03/44] Revert "fix: Show Credit Note amount in credit note column" (cherry picked from commit 5a9767ca67217a962c99332180aa17afa2e734dc) --- .../accounts_payable/test_accounts_payable.py | 17 ----------------- .../accounts_receivable/accounts_receivable.py | 16 ++-------------- .../test_accounts_receivable.py | 12 +++--------- 3 files changed, 5 insertions(+), 40 deletions(-) diff --git a/erpnext/accounts/report/accounts_payable/test_accounts_payable.py b/erpnext/accounts/report/accounts_payable/test_accounts_payable.py index 69f332d9800..8971dc3d37b 100644 --- a/erpnext/accounts/report/accounts_payable/test_accounts_payable.py +++ b/erpnext/accounts/report/accounts_payable/test_accounts_payable.py @@ -38,23 +38,6 @@ class TestAccountsPayable(AccountsTestMixin, FrappeTestCase): self.assertEqual(data[1][0].get("outstanding"), 300) self.assertEqual(data[1][0].get("currency"), "USD") - def test_account_payable_for_debit_note(self): - pi = self.create_purchase_invoice(do_not_submit=True) - pi.is_return = 1 - pi.items[0].qty = -1 - pi = pi.save().submit() - - filters = { - "company": self.company, - "party_type": "Supplier", - "party": [self.supplier], - "report_date": today(), - "range": "30, 60, 90, 120", - } - - data = execute(filters) - self.assertEqual(data[1][0].get("invoiced"), 300) - def create_purchase_invoice(self, do_not_submit=False): frappe.set_user("Administrator") pi = make_purchase_invoice( diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py index c7a0da5afe9..1ddf9bce06f 100644 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py @@ -267,18 +267,6 @@ class ReceivablePayableReport: row.invoiced_in_account_currency += amount_in_account_currency else: if self.is_invoice(ple): - # when invoice has is_return marked - if self.invoice_details.get(row.voucher_no, {}).get("is_return"): - # for Credit Note - if row.voucher_type == "Sales Invoice": - row.credit_note -= amount - row.credit_note_in_account_currency -= amount_in_account_currency - # for Debit Note - else: - row.invoiced -= amount - row.invoiced_in_account_currency -= amount_in_account_currency - return - if row.voucher_no == ple.voucher_no == ple.against_voucher_no: row.paid -= amount row.paid_in_account_currency -= amount_in_account_currency @@ -433,7 +421,7 @@ class ReceivablePayableReport: # nosemgrep si_list = frappe.db.sql( """ - select name, due_date, po_no, is_return + select name, due_date, po_no from `tabSales Invoice` where posting_date <= %s and company = %s @@ -465,7 +453,7 @@ class ReceivablePayableReport: # nosemgrep for pi in frappe.db.sql( """ - select name, due_date, bill_no, bill_date, is_return + select name, due_date, bill_no, bill_date from `tabPurchase Invoice` where posting_date <= %s diff --git a/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py index f3513286c9e..39ca78153c3 100644 --- a/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py @@ -204,7 +204,7 @@ class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase): expected_data_after_credit_note = [ [100.0, 100.0, 40.0, 0.0, 60.0, si.name], - [0, 0, 0, 100.0, -100.0, cr_note.name], + [0, 0, 100.0, 0.0, -100.0, cr_note.name], ] self.assertEqual(len(report[1]), 2) si_row = next( @@ -478,19 +478,13 @@ class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase): report = execute(filters)[1] self.assertEqual(len(report), 2) - expected_data = {sr.name: [0.0, 10.0, -10.0, 0.0, -10], si.name: [100.0, 0.0, 100.0, 10.0, 90.0]} + expected_data = {sr.name: [10.0, -10.0, 0.0, -10], si.name: [100.0, 100.0, 10.0, 90.0]} rows = report[:2] for row in rows: self.assertEqual( expected_data[row.voucher_no], - [ - row.invoiced or row.paid, - row.credit_note, - row.outstanding, - row.remaining_balance, - row.future_amount, - ], + [row.invoiced or row.paid, row.outstanding, row.remaining_balance, row.future_amount], ) pe.cancel() From 32335da8398876fde80da12e3079ad3a23de6d0e Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 13 Mar 2025 12:03:46 +0530 Subject: [PATCH 04/44] fix: add parenttype condition to payment schedule query in accounts receivable report (backport #46370) (#46499) Fix: add parenttype condition to payment schedule query in accounts receivable report (#46370) fix: add parenttype condition to payment schedule query in accounts receivable report (cherry picked from commit f311a0fc1ca80be3bda349175d1cb36f3009bb12) Co-authored-by: Shanuka Hewage <89955436+Shanuka-98@users.noreply.github.com> --- .../accounts/report/accounts_receivable/accounts_receivable.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py index 54b2c604bf4..2fcae9fa778 100644 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py @@ -520,7 +520,7 @@ class ReceivablePayableReport: ps.description, ps.paid_amount, ps.discounted_amount from `tab{row.voucher_type}` si, `tabPayment Schedule` ps where - si.name = ps.parent and + si.name = ps.parent and ps.parenttype = '{row.voucher_type}' and si.name = %s and si.is_return = 0 order by ps.paid_amount desc, due_date From 2a788a4fb162ca582a043811dd94d197ae871d39 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 13 Mar 2025 14:30:38 +0530 Subject: [PATCH 05/44] refactor: print receipt on order complete on pos (backport #46501) (#46507) * refactor: print receipt on order complete on pos (#46501) (cherry picked from commit 0552209310c7f3b17d02508a12b7547068a906cc) # Conflicts: # erpnext/selling/page/point_of_sale/pos_past_order_summary.js * chore: resolve conflict --------- Co-authored-by: Diptanil Saha --- .../page/point_of_sale/pos_controller.js | 2 +- .../point_of_sale/pos_past_order_summary.js | 20 ++++--------------- 2 files changed, 5 insertions(+), 17 deletions(-) diff --git a/erpnext/selling/page/point_of_sale/pos_controller.js b/erpnext/selling/page/point_of_sale/pos_controller.js index e7208c41dde..b7062abecba 100644 --- a/erpnext/selling/page/point_of_sale/pos_controller.js +++ b/erpnext/selling/page/point_of_sale/pos_controller.js @@ -449,6 +449,7 @@ erpnext.PointOfSale.Controller = class { init_order_summary() { this.order_summary = new erpnext.PointOfSale.PastOrderSummary({ wrapper: this.$components_wrapper, + settings: this.settings, events: { get_frm: () => this.frm, @@ -485,7 +486,6 @@ erpnext.PointOfSale.Controller = class { ]); }, }, - pos_profile: this.pos_profile, }); } diff --git a/erpnext/selling/page/point_of_sale/pos_past_order_summary.js b/erpnext/selling/page/point_of_sale/pos_past_order_summary.js index d4b5562c218..cf775176c07 100644 --- a/erpnext/selling/page/point_of_sale/pos_past_order_summary.js +++ b/erpnext/selling/page/point_of_sale/pos_past_order_summary.js @@ -1,8 +1,8 @@ erpnext.PointOfSale.PastOrderSummary = class { - constructor({ wrapper, events, pos_profile }) { + constructor({ wrapper, settings, events }) { this.wrapper = wrapper; this.events = events; - this.pos_profile = pos_profile; + this.print_receipt_on_order_complete = settings.print_receipt_on_order_complete; this.init_component(); } @@ -357,8 +357,8 @@ erpnext.PointOfSale.PastOrderSummary = class { this.add_summary_btns(condition_btns_map); - if (after_submission) { - this.print_receipt_on_order_complete(); + if (after_submission && this.print_receipt_on_order_complete) { + this.print_receipt(); } } @@ -426,16 +426,4 @@ erpnext.PointOfSale.PastOrderSummary = class { toggle_component(show) { show ? this.$component.css("display", "flex") : this.$component.css("display", "none"); } - - async print_receipt_on_order_complete() { - const res = await frappe.db.get_value( - "POS Profile", - this.pos_profile, - "print_receipt_on_order_complete" - ); - - if (res.message.print_receipt_on_order_complete) { - this.print_receipt(); - } - } }; From 6202e302b1f15809be9bd1be9238fa1eae71807f Mon Sep 17 00:00:00 2001 From: Bhavan23 Date: Thu, 13 Mar 2025 16:45:18 +0530 Subject: [PATCH 06/44] fix: set stock adjustment account in difference account --- .../stock/doctype/stock_entry/stock_entry.py | 46 +++++++++++++++---- 1 file changed, 36 insertions(+), 10 deletions(-) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index f362f9d3da9..3cf19577ef5 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -1614,17 +1614,38 @@ class StockEntry(StockController): @frappe.whitelist() def get_item_details(self, args=None, for_update=False): - item = frappe.db.sql( - """select i.name, i.stock_uom, i.description, i.image, i.item_name, i.item_group, - i.has_batch_no, i.sample_quantity, i.has_serial_no, i.allow_alternative_item, - id.expense_account, id.buying_cost_center - from `tabItem` i LEFT JOIN `tabItem Default` id ON i.name=id.parent and id.company=%s - where i.name=%s - and i.disabled=0 - and (i.end_of_life is null or i.end_of_life<'1900-01-01' or i.end_of_life > %s)""", - (self.company, args.get("item_code"), nowdate()), - as_dict=1, + item = frappe.qb.DocType("Item") + item_default = frappe.qb.DocType("Item Default") + + query = ( + frappe.qb.from_(item) + .left_join(item_default) + .on((item.name == item_default.parent) & (item_default.company == self.company)) + .select( + item.name, + item.stock_uom, + item.description, + item.image, + item.item_name, + item.item_group, + item.has_batch_no, + item.sample_quantity, + item.has_serial_no, + item.allow_alternative_item, + item_default.expense_account, + item_default.buying_cost_center, + ) + .where( + (item.name == args.get("item_code")) + & (item.disabled == 0) + & ( + (item.end_of_life.isnull()) + | (item.end_of_life < "1900-01-01") + | (item.end_of_life > nowdate()) + ) + ) ) + item = query.run(as_dict=True) if not item: frappe.throw( @@ -1667,6 +1688,11 @@ class StockEntry(StockController): if self.purpose == "Material Issue": ret["expense_account"] = item.get("expense_account") or item_group_defaults.get("expense_account") + if self.purpose == "Manufacture": + ret["expense_account"] = frappe.get_cached_value( + "Company", self.company, "stock_adjustment_account" + ) + for company_field, field in { "stock_adjustment_account": "expense_account", "cost_center": "cost_center", From 3737b4a30028bfa0b080c471b41d0700659d9738 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Wed, 12 Mar 2025 17:34:23 +0530 Subject: [PATCH 07/44] refactor(test): unset billing address --- erpnext/selling/doctype/sales_order/test_sales_order.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/erpnext/selling/doctype/sales_order/test_sales_order.py b/erpnext/selling/doctype/sales_order/test_sales_order.py index 003ffd5ac82..16c7431c315 100644 --- a/erpnext/selling/doctype/sales_order/test_sales_order.py +++ b/erpnext/selling/doctype/sales_order/test_sales_order.py @@ -2136,7 +2136,7 @@ class TestSalesOrder(AccountsTestMixin, FrappeTestCase): self.assertEqual(dn.items[0].rate, 90) - def test_credit_limit_on_so_reopning(self): + def test_credit_limit_on_so_reopening(self): # set credit limit company = "_Test Company" customer = frappe.get_doc("Customer", self.customer) @@ -2148,12 +2148,14 @@ class TestSalesOrder(AccountsTestMixin, FrappeTestCase): so1 = make_sales_order(qty=9, rate=100, do_not_submit=True) so1.customer = self.customer + so1.customer_address = so1.shipping_address_name = None so1.save().submit() so1.update_status("Closed") so2 = make_sales_order(qty=9, rate=100, do_not_submit=True) so2.customer = self.customer + so2.customer_address = so2.shipping_address_name = None so2.save().submit() self.assertRaises(frappe.ValidationError, so1.update_status, "Draft") From 7fb26f802c99bd3c640b22fd2e7170cc446144f5 Mon Sep 17 00:00:00 2001 From: Sugesh393 Date: Wed, 12 Mar 2025 17:54:39 +0530 Subject: [PATCH 08/44] refactor: replace get_list with get_all for dynamic link child access (cherry picked from commit 8f7f0b81f6be2e4d8c320c5c7d07c7f8c5c08b02) --- erpnext/controllers/accounts_controller.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 21f339891bf..94b553e2f99 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -461,7 +461,7 @@ class AccountsController(TransactionBase): def validate_party_address(self, party, party_type, billing_address, shipping_address=None): if billing_address or shipping_address: - party_address = frappe.get_list( + party_address = frappe.get_all( "Dynamic Link", {"link_doctype": party_type, "link_name": party, "parenttype": "Address"}, pluck="parent", @@ -473,7 +473,7 @@ class AccountsController(TransactionBase): def validate_party_contact(self, party, party_type): if self.get("contact_person"): - contact = frappe.get_list( + contact = frappe.get_all( "Dynamic Link", {"link_doctype": party_type, "link_name": party, "parenttype": "Contact"}, pluck="parent", From 5dd5784716e4ff5a2d8b82ee0f3750dd5e2defb9 Mon Sep 17 00:00:00 2001 From: Sanket322 Date: Thu, 13 Mar 2025 11:29:20 +0530 Subject: [PATCH 09/44] fix: use party explicitly (cherry picked from commit 5057e3fe30256a34d7e0f4da3e4ca1e01bcff933) --- .../report/accounts_receivable/accounts_receivable.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py index 2fcae9fa778..5a140ec3f2d 100644 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py @@ -729,11 +729,12 @@ class ReceivablePayableReport: "company": self.filters.company, "update_outstanding_for_self": 0, } + or_filters = {} - for party_type in self.party_type: + if party_type := self.filters.party_type: party_field = scrub(party_type) - if self.filters.get(party_field): - or_filters.update({party_field: ["in", self.filters.get(party_field)]}) + if parties := self.filters.get("party"): + or_filters.update({party_field: ["in", parties]}) self.return_entries = frappe._dict( frappe.get_all( From 08525337516ad9432f570c99bc76fd7022889ab0 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Fri, 14 Mar 2025 12:33:37 +0530 Subject: [PATCH 10/44] Revert "fix: error when creating delivery note from pick list (backport #46417)" --- erpnext/stock/doctype/pick_list/pick_list.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/erpnext/stock/doctype/pick_list/pick_list.py b/erpnext/stock/doctype/pick_list/pick_list.py index a80a59a822d..31bff657fd1 100644 --- a/erpnext/stock/doctype/pick_list/pick_list.py +++ b/erpnext/stock/doctype/pick_list/pick_list.py @@ -1189,6 +1189,7 @@ def create_delivery_note(source_name, target_doc=None): if not all(item.sales_order for item in pick_list.locations): delivery_note = create_dn_wo_so(pick_list) + frappe.msgprint(_("Delivery Note(s) created for the Pick List")) return delivery_note @@ -1205,6 +1206,7 @@ def create_dn_wo_so(pick_list): }, } map_pl_locations(pick_list, item_table_mapper_without_so, delivery_note) + delivery_note.insert(ignore_mandatory=True) return delivery_note @@ -1232,7 +1234,10 @@ def create_dn_with_so(sales_dict, pick_list): # map all items of all sales orders of that customer for so in sales_dict[customer]: map_pl_locations(pick_list, item_table_mapper, delivery_note, so) + delivery_note.flags.ignore_mandatory = True + delivery_note.insert() update_packed_item_details(pick_list, delivery_note) + delivery_note.save() return delivery_note From 62feec5cc3b1d108717bca1590b65ca98cae915b Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Thu, 13 Mar 2025 17:28:48 +0530 Subject: [PATCH 11/44] fix: hide subcontracted qty field if PO is not subcontracted (cherry picked from commit 6e8521d7615352f244e1a3cb4f100ce7b5ca122d) --- .../doctype/purchase_order_item/purchase_order_item.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json b/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json index 894c705dc96..4473d021635 100644 --- a/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json +++ b/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json @@ -913,6 +913,7 @@ }, { "allow_on_submit": 1, + "depends_on": "eval:parent.is_subcontracted && !parent.is_old_subcontracting_flow", "fieldname": "subcontracted_quantity", "fieldtype": "Float", "label": "Subcontracted Quantity", @@ -926,7 +927,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2025-03-02 16:58:26.059601", + "modified": "2025-03-13 17:27:43.468602", "modified_by": "Administrator", "module": "Buying", "name": "Purchase Order Item", @@ -940,4 +941,4 @@ "sort_order": "DESC", "states": [], "track_changes": 1 -} \ No newline at end of file +} From 2f3dcc2137ab9b122992c40f8faa07c8361b151d Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Thu, 13 Mar 2025 18:33:39 +0530 Subject: [PATCH 12/44] fix: UOM conversion error when creating pick list from material transfer request (cherry picked from commit 840ea070a9514a88e83b51b3446c7ae9724062fe) --- erpnext/stock/doctype/pick_list/pick_list.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/pick_list/pick_list.py b/erpnext/stock/doctype/pick_list/pick_list.py index a80a59a822d..f7bd4657973 100644 --- a/erpnext/stock/doctype/pick_list/pick_list.py +++ b/erpnext/stock/doctype/pick_list/pick_list.py @@ -838,7 +838,7 @@ def get_items_with_location_and_quantity(item_doc, item_location_map, docstatus) item_location = frappe._dict(item_location) stock_qty = remaining_stock_qty if item_location.qty >= remaining_stock_qty else item_location.qty - qty = stock_qty / (item_doc.conversion_factor or 1) + qty = stock_qty * (item_doc.conversion_factor or 1) uom_must_be_whole_number = frappe.get_cached_value("UOM", item_doc.uom, "must_be_whole_number") if uom_must_be_whole_number: From be3e083e7d07ef460ddfe7918511b5f5ac6d6b1a Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Thu, 13 Mar 2025 20:19:47 +0530 Subject: [PATCH 13/44] fix: wrong field mapping (cherry picked from commit 8411e2e01f066b8b49049a94f3859079124f4019) --- erpnext/stock/doctype/material_request/material_request.py | 2 +- erpnext/stock/doctype/pick_list/pick_list.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/doctype/material_request/material_request.py b/erpnext/stock/doctype/material_request/material_request.py index 69572e661f8..59634cb9f7c 100644 --- a/erpnext/stock/doctype/material_request/material_request.py +++ b/erpnext/stock/doctype/material_request/material_request.py @@ -813,7 +813,7 @@ def create_pick_list(source_name, target_doc=None): }, "Material Request Item": { "doctype": "Pick List Item", - "field_map": {"name": "material_request_item", "qty": "stock_qty"}, + "field_map": {"name": "material_request_item", "stock_qty": "stock_qty"}, }, }, target_doc, diff --git a/erpnext/stock/doctype/pick_list/pick_list.py b/erpnext/stock/doctype/pick_list/pick_list.py index f7bd4657973..a80a59a822d 100644 --- a/erpnext/stock/doctype/pick_list/pick_list.py +++ b/erpnext/stock/doctype/pick_list/pick_list.py @@ -838,7 +838,7 @@ def get_items_with_location_and_quantity(item_doc, item_location_map, docstatus) item_location = frappe._dict(item_location) stock_qty = remaining_stock_qty if item_location.qty >= remaining_stock_qty else item_location.qty - qty = stock_qty * (item_doc.conversion_factor or 1) + qty = stock_qty / (item_doc.conversion_factor or 1) uom_must_be_whole_number = frappe.get_cached_value("UOM", item_doc.uom, "must_be_whole_number") if uom_must_be_whole_number: From e2418101aba602123735d81053c7c3f374243058 Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Thu, 13 Mar 2025 18:11:20 +0530 Subject: [PATCH 14/44] fix: get bom_no from sales order item and material request item (cherry picked from commit ac354505ef09079b2481421a53420736b45e923b) --- erpnext/manufacturing/doctype/production_plan/production_plan.py | 1 + erpnext/selling/doctype/sales_order/sales_order.py | 1 + 2 files changed, 2 insertions(+) diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py index 7d134adb3cc..165e8746c5a 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py @@ -425,6 +425,7 @@ class ProductionPlan(Document): mr_item.item_code, mr_item.warehouse, mr_item.description, + mr_item.bom_no, ((mr_item.qty - mr_item.ordered_qty) * mr_item.conversion_factor).as_("pending_qty"), ) .distinct() diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py index 74e2328be24..4a026395425 100755 --- a/erpnext/selling/doctype/sales_order/sales_order.py +++ b/erpnext/selling/doctype/sales_order/sales_order.py @@ -876,6 +876,7 @@ def make_material_request(source_name, target_doc=None): "name": "sales_order_item", "parent": "sales_order", "delivery_date": "required_by", + "bom_no": "bom_no", }, "condition": lambda item: not frappe.db.exists( "Product Bundle", {"name": item.item_code, "disabled": 0} From 65a80cffe7082fe1dd13fd124228a8a66411e326 Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Fri, 14 Mar 2025 15:54:03 +0530 Subject: [PATCH 15/44] fix: also consider CRM Deal as party type for ERPNext CRM Integration (cherry picked from commit 04edbf7efe164c71afaa58f7a066ada72db03223) --- erpnext/public/js/utils/party.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/public/js/utils/party.js b/erpnext/public/js/utils/party.js index 623941755d1..a85423b8340 100644 --- a/erpnext/public/js/utils/party.js +++ b/erpnext/public/js/utils/party.js @@ -17,7 +17,7 @@ erpnext.utils.get_party_details = function (frm, method, args, callback) { (frm.doc.party_name && ["Quotation", "Opportunity"].includes(frm.doc.doctype)) ) { let party_type = "Customer"; - if (frm.doc.quotation_to && ["Lead", "Prospect"].includes(frm.doc.quotation_to)) { + if (frm.doc.quotation_to && ["Lead", "Prospect", "CRM Deal"].includes(frm.doc.quotation_to)) { party_type = frm.doc.quotation_to; } From b638aed7588b2237648a6c109819e6dfb489c99e Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Fri, 14 Mar 2025 23:51:30 +0530 Subject: [PATCH 16/44] fix: exclude current doc when checking for duplicate (cherry picked from commit d8ef5e4d580f3e5764a2c9e00fe051975c5a7e82) --- .../manufacturing/doctype/work_order/test_work_order.py | 1 + erpnext/stock/doctype/stock_entry/stock_entry.py | 7 ++++++- 2 files changed, 7 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 205ca1dbdb4..84f2b75432c 100644 --- a/erpnext/manufacturing/doctype/work_order/test_work_order.py +++ b/erpnext/manufacturing/doctype/work_order/test_work_order.py @@ -42,6 +42,7 @@ class TestWorkOrder(FrappeTestCase): prepare_data_for_backflush_based_on_materials_transferred() def tearDown(self): + frappe.local.future_sle = {} frappe.db.rollback() def check_planned_qty(self): diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index 9b5f9dc9810..3f548a1324d 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -913,7 +913,12 @@ class StockEntry(StockController): if frappe.db.exists( "Stock Entry", - {"docstatus": 1, "work_order": self.work_order, "purpose": "Manufacture"}, + { + "docstatus": 1, + "work_order": self.work_order, + "purpose": "Manufacture", + "name": ("!=", self.name), + }, ): frappe.throw( _("Only one {0} entry can be created against the Work Order {1}").format( From 426222d8e000f028e4a31165c4f07488db26fbfe Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Thu, 13 Mar 2025 13:38:54 +0530 Subject: [PATCH 17/44] fix: dashboard link for QC from PR (cherry picked from commit 551f89f14b9f4e02dc164ce346a09c0a58c2ce07) --- erpnext/public/js/controllers/transaction.js | 2 +- .../doctype/purchase_receipt/purchase_receipt_dashboard.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 2fab91cbf88..5440fe8588e 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -335,7 +335,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe let d = locals[cdt][cdn]; return { filters: { - docstatus: 1, + docstatus: ("<", 2), inspection_type: inspection_type, reference_name: doc.name, item_code: d.item_code diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt_dashboard.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt_dashboard.py index 71489fbb494..b1b0a962246 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt_dashboard.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt_dashboard.py @@ -11,12 +11,12 @@ def get_data(): "Auto Repeat": "reference_document", "Purchase Receipt": "return_against", "Stock Reservation Entry": "from_voucher_no", + "Quality Inspection": "reference_name", }, "internal_links": { "Material Request": ["items", "material_request"], "Purchase Order": ["items", "purchase_order"], "Project": ["items", "project"], - "Quality Inspection": ["items", "quality_inspection"], }, "transactions": [ { From d071a6c9001303d7a6c5eef600fd29e170e0b0b1 Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Mon, 17 Mar 2025 16:06:54 +0530 Subject: [PATCH 18/44] fix: incorrect production item and bom no in job card --- erpnext/manufacturing/doctype/job_card/job_card.json | 5 ++--- erpnext/manufacturing/doctype/job_card/job_card.py | 6 ++---- erpnext/manufacturing/doctype/work_order/work_order.js | 7 +++++++ erpnext/manufacturing/doctype/work_order/work_order.py | 2 +- 4 files changed, 12 insertions(+), 8 deletions(-) diff --git a/erpnext/manufacturing/doctype/job_card/job_card.json b/erpnext/manufacturing/doctype/job_card/job_card.json index 0f01704eb0f..39e7aac38ae 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.json +++ b/erpnext/manufacturing/doctype/job_card/job_card.json @@ -86,7 +86,6 @@ "search_index": 1 }, { - "fetch_from": "work_order.bom_no", "fieldname": "bom_no", "fieldtype": "Link", "label": "BOM No", @@ -281,7 +280,7 @@ "fieldtype": "Column Break" }, { - "fetch_from": "work_order.production_item", + "fetch_from": "bom_no.item", "fieldname": "production_item", "fieldtype": "Link", "label": "Production Item", @@ -511,7 +510,7 @@ ], "is_submittable": 1, "links": [], - "modified": "2023-06-28 19:23:14.345214", + "modified": "2025-03-17 15:55:11.143456", "modified_by": "Administrator", "module": "Manufacturing", "name": "Job Card", diff --git a/erpnext/manufacturing/doctype/job_card/job_card.py b/erpnext/manufacturing/doctype/job_card/job_card.py index 90ccff720e8..659bb01b4d4 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.py +++ b/erpnext/manufacturing/doctype/job_card/job_card.py @@ -64,9 +64,7 @@ class JobCard(Document): from erpnext.manufacturing.doctype.job_card_scheduled_time.job_card_scheduled_time import ( JobCardScheduledTime, ) - from erpnext.manufacturing.doctype.job_card_scrap_item.job_card_scrap_item import ( - JobCardScrapItem, - ) + from erpnext.manufacturing.doctype.job_card_scrap_item.job_card_scrap_item import JobCardScrapItem from erpnext.manufacturing.doctype.job_card_time_log.job_card_time_log import JobCardTimeLog actual_end_date: DF.Datetime | None @@ -91,7 +89,7 @@ class JobCard(Document): naming_series: DF.Literal["PO-JOB.#####"] operation: DF.Link operation_id: DF.Data | None - operation_row_number: DF.Literal + operation_row_number: DF.Literal[None] posting_date: DF.Date | None process_loss_qty: DF.Float production_item: DF.Link | None diff --git a/erpnext/manufacturing/doctype/work_order/work_order.js b/erpnext/manufacturing/doctype/work_order/work_order.js index df5027172ff..67233dc206c 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.js +++ b/erpnext/manufacturing/doctype/work_order/work_order.js @@ -301,6 +301,12 @@ frappe.ui.form.on("Work Order", { label: __("Sequence Id"), read_only: 1, }, + { + fieldtype: "Link", + fieldname: "bom", + label: __("BOM"), + read_only: 1, + }, ], data: operations_data, in_place_edit: true, @@ -341,6 +347,7 @@ frappe.ui.form.on("Work Order", { qty: pending_qty, pending_qty: pending_qty, sequence_id: data.sequence_id, + bom: data.bom, }); } } diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py index cd987099eb8..885978deece 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.py +++ b/erpnext/manufacturing/doctype/work_order/work_order.py @@ -1616,7 +1616,7 @@ def create_job_card(work_order, row, enable_capacity_planning=False, auto_create "posting_date": nowdate(), "for_quantity": row.job_card_qty or work_order.get("qty", 0), "operation_id": row.get("name"), - "bom_no": work_order.bom_no, + "bom_no": row.get("bom"), "project": work_order.project, "company": work_order.company, "sequence_id": row.get("sequence_id"), From 59c653ef3fcce994b5840619cb4fc90b80587bfe Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Mon, 17 Mar 2025 13:00:32 +0530 Subject: [PATCH 19/44] fix: not able to select the item in the BOM (cherry picked from commit 96d0cd23f1c4660fbcddd93ede669ffad41f2a75) --- erpnext/manufacturing/doctype/bom/bom.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py index 61f1de798e2..7f2d2acc00d 100644 --- a/erpnext/manufacturing/doctype/bom/bom.py +++ b/erpnext/manufacturing/doctype/bom/bom.py @@ -440,8 +440,8 @@ class BOM(WebsiteGenerator): "description": item and args["description"] or "", "image": item and args["image"] or "", "stock_uom": item and args["stock_uom"] or "", - "uom": args["uom"] if hasattr(args, "uom") else item and args["stock_uom"] or "", - "conversion_factor": args["conversion_factor"] if hasattr(args, "conversion_factor") else 1, + "uom": args["uom"] if args.get("uom") else item and args["stock_uom"] or "", + "conversion_factor": args["conversion_factor"] if args.get("conversion_factor") else 1, "bom_no": args["bom_no"], "rate": rate, "qty": args.get("qty") or args.get("stock_qty") or 1, From 46b6e621c2e4384e3650d7d3b32efcc0a5db3edc Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Mon, 17 Mar 2025 12:57:44 +0530 Subject: [PATCH 20/44] fix: Debit and Credit not equal for Purchase Invoice (cherry picked from commit ecb31b7c9f7ca644d8bbec4af6c549524584b2aa) --- .../purchase_invoice/purchase_invoice.py | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index 51b10932557..c32c62afc23 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -1330,6 +1330,37 @@ class PurchaseInvoice(BuyingController): warehouse_debit_amount = stock_amount + elif self.is_return and self.update_stock and self.is_internal_supplier and warehouse_debit_amount: + net_rate = item.base_net_amount + if item.sales_incoming_rate: # for internal transfer + net_rate = item.qty * item.sales_incoming_rate + + stock_amount = ( + net_rate + + item.item_tax_amount + + flt(item.landed_cost_voucher_amount) + + flt(item.get("amount_difference_with_purchase_invoice")) + ) + + if flt(stock_amount, net_amt_precision) != flt(warehouse_debit_amount, net_amt_precision): + cost_of_goods_sold_account = self.get_company_default("default_expense_account") + stock_adjustment_amt = stock_amount - warehouse_debit_amount + + gl_entries.append( + self.get_gl_dict( + { + "account": cost_of_goods_sold_account, + "against": item.expense_account, + "debit": stock_adjustment_amt, + "remarks": self.get("remarks") or _("Stock Adjustment"), + "cost_center": item.cost_center, + "project": item.project or self.project, + }, + account_currency, + item=item, + ) + ) + return warehouse_debit_amount def make_tax_gl_entries(self, gl_entries): From 34d6e4bdaa5780da8d8432ac0ed9b55008a78baf Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Mon, 17 Mar 2025 13:48:01 +0530 Subject: [PATCH 21/44] fix: performance issue for item list view (cherry picked from commit d758fde881dc5bba0c50faf587ad89b021855268) # Conflicts: # erpnext/stock/doctype/item_default/item_default.json --- erpnext/stock/doctype/item_default/item_default.json | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/item_default/item_default.json b/erpnext/stock/doctype/item_default/item_default.json index 28956612762..61d9ef49dec 100644 --- a/erpnext/stock/doctype/item_default/item_default.json +++ b/erpnext/stock/doctype/item_default/item_default.json @@ -40,7 +40,8 @@ "fieldtype": "Link", "in_list_view": 1, "label": "Default Warehouse", - "options": "Warehouse" + "options": "Warehouse", + "search_index": 1 }, { "fieldname": "column_break_3", @@ -139,7 +140,11 @@ ], "istable": 1, "links": [], +<<<<<<< HEAD "modified": "2023-09-04 12:33:14.607267", +======= + "modified": "2025-03-17 13:46:09.719105", +>>>>>>> d758fde881 (fix: performance issue for item list view) "modified_by": "Administrator", "module": "Stock", "name": "Item Default", From ea68caec7d1c4210f1111b19baa93408b7e3c3a9 Mon Sep 17 00:00:00 2001 From: venkat102 Date: Mon, 17 Mar 2025 16:13:42 +0530 Subject: [PATCH 22/44] fix(Transaction Deletion Record): sql syntax error while fetching lead address (cherry picked from commit af0d6eeae8fdcc7f033e8378fbbca73ee5998e4c) --- .../transaction_deletion_record.py | 49 +++++++++++-------- 1 file changed, 28 insertions(+), 21 deletions(-) diff --git a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py index ce3f918f7eb..edb55f5dc46 100644 --- a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py +++ b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py @@ -220,35 +220,42 @@ class TransactionDeletionRecord(Document): """Delete addresses to which leads are linked""" self.validate_doc_status() if not self.delete_leads_and_addresses: - leads = frappe.get_all("Lead", filters={"company": self.company}) - leads = ["'%s'" % row.get("name") for row in leads] + leads = frappe.db.get_all("Lead", filters={"company": self.company}, pluck="name") addresses = [] if leads: - addresses = frappe.db.sql_list( - """select parent from `tabDynamic Link` where link_name - in ({leads})""".format(leads=",".join(leads)) + addresses = frappe.db.get_all( + "Dynamic Link", filters={"link_name": ("in", leads)}, pluck="parent" ) - if addresses: addresses = ["%s" % frappe.db.escape(addr) for addr in addresses] - frappe.db.sql( - """delete from `tabAddress` where name in ({addresses}) and - name not in (select distinct dl1.parent from `tabDynamic Link` dl1 - inner join `tabDynamic Link` dl2 on dl1.parent=dl2.parent - and dl1.link_doctype<>dl2.link_doctype)""".format(addresses=",".join(addresses)) - ) + address = qb.DocType("Address") + dl1 = qb.DocType("Dynamic Link") + dl2 = qb.DocType("Dynamic Link") - frappe.db.sql( - """delete from `tabDynamic Link` where link_doctype='Lead' - and parenttype='Address' and link_name in ({leads})""".format(leads=",".join(leads)) - ) + qb.from_(address).delete().where( + (address.name.isin(addresses)) + & ( + address.name.notin( + qb.from_(dl1) + .join(dl2) + .on((dl1.parent == dl2.parent) & (dl1.link_doctype != dl2.link_doctype)) + .select(dl1.parent) + .distinct() + ) + ) + ).run() + + dynamic_link = qb.DocType("Dynamic Link") + qb.from_(dynamic_link).delete().where( + (dynamic_link.link_doctype == "Lead") + & (dynamic_link.parenttype == "Address") + & (dynamic_link.link_name.isin(leads)) + ).run() + + customer = qb.DocType("Customer") + qb.update(customer).set(customer.lead_name, None).where(customer.lead_name.isin(leads)).run() - frappe.db.sql( - """update `tabCustomer` set lead_name=NULL where lead_name in ({leads})""".format( - leads=",".join(leads) - ) - ) self.db_set("delete_leads_and_addresses", 1) self.enqueue_task(task="Reset Company Values") From 7cfd7e6539404be3087838bae04569f8f76da332 Mon Sep 17 00:00:00 2001 From: Ejaaz Khan Date: Mon, 17 Mar 2025 19:34:04 +0530 Subject: [PATCH 23/44] refactor: remove default print format from sales invoice (cherry picked from commit f10d1f2b1f64e6b65ba07a9009635c0fef4bd40f) --- erpnext/accounts/doctype/sales_invoice/sales_invoice.json | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json index e2c5fd965e8..0c19e79ccd4 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json @@ -3,7 +3,6 @@ "allow_import": 1, "autoname": "naming_series:", "creation": "2022-01-25 10:29:57.771398", - "default_print_format": "Sales Invoice Print", "doctype": "DocType", "engine": "InnoDB", "field_order": [ @@ -2189,7 +2188,7 @@ "link_fieldname": "consolidated_invoice" } ], - "modified": "2025-03-05 17:06:59.720616", + "modified": "2025-03-17 19:32:31.809658", "modified_by": "Administrator", "module": "Accounts", "name": "Sales Invoice", @@ -2245,4 +2244,4 @@ "title_field": "title", "track_changes": 1, "track_seen": 1 -} \ No newline at end of file +} From 01bab8f22be42a1ff2c5237c186a2d860f5b5ca8 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 17 Mar 2025 21:23:30 +0530 Subject: [PATCH 24/44] perf: faster count estimation (backport #46550) (#46551) perf: faster count estimation (#46550) These count queries themselves take quite a long time. `estimate_count` uses info_schema stats to guess the time. Co-authored-by: Nabin Hait (cherry picked from commit e47a87839bcd6ff78298bec017dd1a57b39228e3) Co-authored-by: Ankush Menat --- .../period_closing_voucher.py | 12 +----------- .../batch_wise_balance_history.py | 15 ++------------- 2 files changed, 3 insertions(+), 24 deletions(-) diff --git a/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py b/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py index 7e0145e91a4..790ada3f63e 100644 --- a/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py +++ b/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py @@ -139,7 +139,7 @@ class PeriodClosingVoucher(AccountsController): self.cancel_gl_entries() def make_gl_entries(self): - if self.get_gle_count_in_selected_period() > 5000: + if frappe.db.estimate_count("GL Entry") > 100_000: frappe.enqueue( process_gl_and_closing_entries, doc=self, @@ -154,16 +154,6 @@ class PeriodClosingVoucher(AccountsController): else: process_gl_and_closing_entries(self) - def get_gle_count_in_selected_period(self): - return frappe.db.count( - "GL Entry", - { - "posting_date": ["between", [self.period_start_date, self.period_end_date]], - "company": self.company, - "is_cancelled": 0, - }, - ) - def get_pcv_gl_entries(self): self.pl_accounts_reverse_gle = [] self.closing_account_gle = [] diff --git a/erpnext/stock/report/batch_wise_balance_history/batch_wise_balance_history.py b/erpnext/stock/report/batch_wise_balance_history/batch_wise_balance_history.py index df466ed8436..dabd3b1c6e4 100644 --- a/erpnext/stock/report/batch_wise_balance_history/batch_wise_balance_history.py +++ b/erpnext/stock/report/batch_wise_balance_history/batch_wise_balance_history.py @@ -10,25 +10,14 @@ from pypika import functions as fn from erpnext.stock.doctype.warehouse.warehouse import apply_warehouse_filter -SLE_COUNT_LIMIT = 10_000 - - -def _estimate_table_row_count(doctype: str): - table = get_table_name(doctype) - return cint( - frappe.db.sql( - f"""select table_rows - from information_schema.tables - where table_name = '{table}' ;""" - )[0][0] - ) +SLE_COUNT_LIMIT = 100_000 def execute(filters=None): if not filters: filters = {} - sle_count = _estimate_table_row_count("Stock Ledger Entry") + sle_count = frappe.db.estimate_count("Stock Ledger Entry") if ( sle_count > SLE_COUNT_LIMIT From 3e2749d6d516e9686567bdce79f77309b2a40c07 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 13 Mar 2025 14:23:51 +0530 Subject: [PATCH 25/44] fix: use base currency total (cherry picked from commit 46f4babcd081d6d4b61dde714a48737d0f13609f) --- erpnext/regional/report/uae_vat_201/uae_vat_201.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/erpnext/regional/report/uae_vat_201/uae_vat_201.py b/erpnext/regional/report/uae_vat_201/uae_vat_201.py index 1b77b3c0dd1..1a68d7dec6a 100644 --- a/erpnext/regional/report/uae_vat_201/uae_vat_201.py +++ b/erpnext/regional/report/uae_vat_201/uae_vat_201.py @@ -179,7 +179,7 @@ def get_reverse_charge_total(filters): try: return ( frappe.db.get_all( - "Purchase Invoice", filters=query_filters, fields=["sum(total)"], as_list=True, limit=1 + "Purchase Invoice", filters=query_filters, fields=["sum(base_total)"], as_list=True, limit=1 )[0][0] or 0 ) @@ -219,7 +219,7 @@ def get_reverse_charge_recoverable_total(filters): try: return ( frappe.db.get_all( - "Purchase Invoice", filters=query_filters, fields=["sum(total)"], as_list=True, limit=1 + "Purchase Invoice", filters=query_filters, fields=["sum(base_total)"], as_list=True, limit=1 )[0][0] or 0 ) @@ -274,7 +274,7 @@ def get_standard_rated_expenses_total(filters): try: return ( frappe.db.get_all( - "Purchase Invoice", filters=query_filters, fields=["sum(total)"], as_list=True, limit=1 + "Purchase Invoice", filters=query_filters, fields=["sum(base_total)"], as_list=True, limit=1 )[0][0] or 0 ) @@ -310,7 +310,7 @@ def get_tourist_tax_return_total(filters): try: return ( frappe.db.get_all( - "Sales Invoice", filters=query_filters, fields=["sum(total)"], as_list=True, limit=1 + "Sales Invoice", filters=query_filters, fields=["sum(base_total)"], as_list=True, limit=1 )[0][0] or 0 ) From ec43ca97cb74cff00222f72f091e5c5d917de58b Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 14 Mar 2025 11:50:16 +0530 Subject: [PATCH 26/44] test: report ouput on foreign currency PI (cherry picked from commit e80129627af1382fffffe54298b0945a21c7c646) --- .../report/uae_vat_201/test_uae_vat_201.py | 34 +++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/erpnext/regional/report/uae_vat_201/test_uae_vat_201.py b/erpnext/regional/report/uae_vat_201/test_uae_vat_201.py index 2966b0657a4..cab84024417 100644 --- a/erpnext/regional/report/uae_vat_201/test_uae_vat_201.py +++ b/erpnext/regional/report/uae_vat_201/test_uae_vat_201.py @@ -39,11 +39,10 @@ class TestUaeVat201(TestCase): make_item("_Test UAE VAT Zero Rated Item", properties={"is_zero_rated": 1, "is_exempt": 0}) make_item("_Test UAE VAT Exempt Item", properties={"is_zero_rated": 0, "is_exempt": 1}) + def test_uae_vat_201_report(self): make_sales_invoices() - create_purchase_invoices() - def test_uae_vat_201_report(self): filters = {"company": "_Test Company UAE VAT"} total_emiratewise = get_total_emiratewise(filters) amounts_by_emirate = {} @@ -64,6 +63,37 @@ class TestUaeVat201(TestCase): self.assertEqual(get_standard_rated_expenses_total(filters), 250) self.assertEqual(get_standard_rated_expenses_tax(filters), 1) + def test_uae_vat_201_report_with_foreign_transaction(self): + pi = make_purchase_invoice( + company="_Test Company UAE VAT", + supplier="_Test UAE Supplier", + supplier_warehouse="_Test UAE VAT Supplier Warehouse - _TCUV", + warehouse="_Test UAE VAT Supplier Warehouse - _TCUV", + currency="USD", + conversion_rate=3.67, + cost_center="Main - _TCUV", + expense_account="Cost of Goods Sold - _TCUV", + item="_Test UAE VAT Item", + do_not_save=1, + uom="Nos", + ) + pi.append( + "taxes", + { + "charge_type": "On Net Total", + "account_head": "VAT 5% - _TCUV", + "cost_center": "Main - _TCUV", + "description": "VAT 5% @ 5.0", + "rate": 5.0, + }, + ) + pi.recoverable_standard_rated_expenses = 50 + pi.save().submit() + + filters = {"company": "_Test Company UAE VAT"} + self.assertEqual(get_standard_rated_expenses_total(filters), 917.5) + self.assertEqual(get_standard_rated_expenses_tax(filters), 50) + def make_company(company_name, abbr): if not frappe.db.exists("Company", company_name): From e6dd3f3e647b59d04f83aba7ddd0a760c7f13896 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 17 Mar 2025 20:28:34 +0530 Subject: [PATCH 27/44] fix: set correct currency for offset account gl entries (cherry picked from commit c32e11e69dc1bfe8b433a4b4c63dfcb4439a4d6c) --- erpnext/accounts/general_ledger.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/erpnext/accounts/general_ledger.py b/erpnext/accounts/general_ledger.py index cb267972c70..c5ee7400473 100644 --- a/erpnext/accounts/general_ledger.py +++ b/erpnext/accounts/general_ledger.py @@ -81,6 +81,10 @@ def make_acc_dimensions_offsetting_entry(gl_map): "credit_in_account_currency": credit, "remarks": _("Offsetting for Accounting Dimension") + f" - {dimension.name}", "against_voucher": None, + "account_currency": dimension.account_currency, + # Party Type and Party are restricted to Receivable and Payable accounts + "party_type": None, + "party": None, } ) offsetting_entry["against_voucher_type"] = None @@ -108,6 +112,9 @@ def get_accounting_dimensions_for_offsetting_entry(gl_map, company): accounting_dimensions_to_offset = [] for acc_dimension in acc_dimensions: values = set([entry.get(acc_dimension.fieldname) for entry in gl_map]) + acc_dimension.account_currency = frappe.get_cached_value( + "Account", acc_dimension.offsetting_account, "account_currency" + ) if len(values) > 1: accounting_dimensions_to_offset.append(acc_dimension) From e8047ab2cab4b7cd694dabdbb984a54f09b27c74 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Tue, 18 Mar 2025 12:01:11 +0530 Subject: [PATCH 28/44] chore: fix conflicts --- erpnext/stock/doctype/item_default/item_default.json | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/erpnext/stock/doctype/item_default/item_default.json b/erpnext/stock/doctype/item_default/item_default.json index 61d9ef49dec..04212b31795 100644 --- a/erpnext/stock/doctype/item_default/item_default.json +++ b/erpnext/stock/doctype/item_default/item_default.json @@ -140,11 +140,7 @@ ], "istable": 1, "links": [], -<<<<<<< HEAD - "modified": "2023-09-04 12:33:14.607267", -======= "modified": "2025-03-17 13:46:09.719105", ->>>>>>> d758fde881 (fix: performance issue for item list view) "modified_by": "Administrator", "module": "Stock", "name": "Item Default", @@ -155,4 +151,4 @@ "sort_order": "DESC", "states": [], "track_changes": 1 -} \ No newline at end of file +} From 8162fb3e5d3beeae5bc0b3d0ecc1e1e0686da20e Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Thu, 13 Mar 2025 18:33:22 +0530 Subject: [PATCH 29/44] fix: ensure qty conversion when creating production plan from SO (cherry picked from commit 75882cc81c222adddf48b48973634527408bb5b3) --- .../manufacturing/doctype/production_plan/production_plan.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py index 165e8746c5a..cfd3f789e78 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py @@ -363,8 +363,8 @@ class ProductionPlan(Document): for item in items: item.pending_qty = ( - flt(item.qty) - max(item.work_order_qty, item.delivered_qty, 0) * item.conversion_factor - ) + flt(item.qty) - max(item.work_order_qty, item.delivered_qty, 0) + ) * item.conversion_factor pi = frappe.qb.DocType("Packed Item") From cd0abbae51049a9603f51367364b326073897f5b Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Tue, 18 Mar 2025 13:08:48 +0530 Subject: [PATCH 30/44] fix: fetch quality inspection parameter group (cherry picked from commit 0a482c7ea85ed0e8a2f0f6259c05b9f2e4d66667) --- erpnext/stock/doctype/quality_inspection/quality_inspection.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/erpnext/stock/doctype/quality_inspection/quality_inspection.py b/erpnext/stock/doctype/quality_inspection/quality_inspection.py index 714db645ca6..7c6b892416b 100644 --- a/erpnext/stock/doctype/quality_inspection/quality_inspection.py +++ b/erpnext/stock/doctype/quality_inspection/quality_inspection.py @@ -181,6 +181,9 @@ class QualityInspection(Document): child = self.append("readings", {}) child.update(d) child.status = "Accepted" + child.parameter_group = frappe.get_value( + "Quality Inspection Parameter", d.specification, "parameter_group" + ) @frappe.whitelist() def get_quality_inspection_template(self): From bc408d979a0ea7fbc3c1bacc6200bb8197233ddb Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Mon, 17 Mar 2025 13:11:46 +0530 Subject: [PATCH 31/44] fix: add validation to rename_subcontracting_fields patch (cherry picked from commit 6c3117dc0de3bb76ba23612bca47c654c0e5a9f5) --- erpnext/patches/v15_0/rename_subcontracting_fields.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/erpnext/patches/v15_0/rename_subcontracting_fields.py b/erpnext/patches/v15_0/rename_subcontracting_fields.py index d18d6149cac..a32b4fcb660 100644 --- a/erpnext/patches/v15_0/rename_subcontracting_fields.py +++ b/erpnext/patches/v15_0/rename_subcontracting_fields.py @@ -3,5 +3,12 @@ from frappe.model.utils.rename_field import rename_field def execute(): - rename_field("Purchase Order Item", "sco_qty", "subcontracted_quantity") - rename_field("Subcontracting Order Item", "sc_conversion_factor", "subcontracting_conversion_factor") + if frappe.db.table_exists("Purchase Order Item") and frappe.db.has_column( + "Purchase Order Item", "sco_qty" + ): + rename_field("Purchase Order Item", "sco_qty", "subcontracted_quantity") + + if frappe.db.table_exists("Subcontracting Order Item") and frappe.db.has_column( + "Subcontracting Order Item", "sc_conversion_factor" + ): + rename_field("Subcontracting Order Item", "sc_conversion_factor", "subcontracting_conversion_factor") From 2144f8962478840729218c5aa65934a70e414d03 Mon Sep 17 00:00:00 2001 From: Khushi Rawat <142375893+khushi8112@users.noreply.github.com> Date: Tue, 18 Mar 2025 15:59:58 +0530 Subject: [PATCH 32/44] fix: repost future sle and gle after capitalization (#46576) (cherry picked from commit 29d77aa19f1a514bfbb29e517791a91ca1c1f088) --- .../assets/doctype/asset_capitalization/asset_capitalization.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py index f4addb66eb9..1d36eae1d61 100644 --- a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py +++ b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py @@ -123,6 +123,7 @@ class AssetCapitalization(StockController): self.make_bundle_using_old_serial_batch_fields() self.update_stock_ledger() self.make_gl_entries() + self.repost_future_sle_and_gle() self.update_target_asset() def on_cancel(self): @@ -136,6 +137,7 @@ class AssetCapitalization(StockController): ) self.update_stock_ledger() self.make_gl_entries() + self.repost_future_sle_and_gle() self.restore_consumed_asset_items() def set_title(self): From 5f1bb1f1ba12671bb2e04ce80ce8b6b706183d9c Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Tue, 18 Mar 2025 14:42:40 +0530 Subject: [PATCH 33/44] fix: valuation for moving average with batches (cherry picked from commit cdfbc73f4cbafee83dcd2cc1ba72a162546c7855) --- erpnext/stock/deprecated_serial_batch.py | 4 ---- erpnext/stock/doctype/batch/batch.py | 6 ++++++ .../stock/doctype/purchase_receipt/test_purchase_receipt.py | 2 +- .../doctype/stock_ledger_entry/test_stock_ledger_entry.py | 2 +- erpnext/stock/serial_batch_bundle.py | 6 ++++++ 5 files changed, 14 insertions(+), 6 deletions(-) diff --git a/erpnext/stock/deprecated_serial_batch.py b/erpnext/stock/deprecated_serial_batch.py index f946f459c07..359027ec6be 100644 --- a/erpnext/stock/deprecated_serial_batch.py +++ b/erpnext/stock/deprecated_serial_batch.py @@ -228,7 +228,6 @@ class DeprecatedBatchNoValuation: (sle.item_code == self.sle.item_code) & (sle.warehouse == self.sle.warehouse) & (sle.batch_no.isnotnull()) - & (batch.use_batchwise_valuation == 0) & (sle.is_cancelled == 0) & (sle.batch_no.isin(self.non_batchwise_valuation_batches)) ) @@ -278,7 +277,6 @@ class DeprecatedBatchNoValuation: (sle.item_code == self.sle.item_code) & (sle.warehouse == self.sle.warehouse) & (sle.batch_no.isnotnull()) - & (batch.use_batchwise_valuation == 0) & (sle.is_cancelled == 0) ) .where(timestamp_condition) @@ -318,7 +316,6 @@ class DeprecatedBatchNoValuation: (sabb.item_code == self.sle.item_code) & (sabb.warehouse == self.sle.warehouse) & (sabb_entry.batch_no.isnotnull()) - & (batch.use_batchwise_valuation == 0) & (sabb.is_cancelled == 0) & (sabb.docstatus == 1) ) @@ -378,7 +375,6 @@ class DeprecatedBatchNoValuation: (bundle.item_code == self.sle.item_code) & (bundle.warehouse == self.sle.warehouse) & (bundle_child.batch_no.isnotnull()) - & (batch.use_batchwise_valuation == 0) & (bundle.is_cancelled == 0) & (bundle.docstatus == 1) & (bundle.type_of_transaction.isin(["Inward", "Outward"])) diff --git a/erpnext/stock/doctype/batch/batch.py b/erpnext/stock/doctype/batch/batch.py index ae77672b730..4aeb165c7b5 100644 --- a/erpnext/stock/doctype/batch/batch.py +++ b/erpnext/stock/doctype/batch/batch.py @@ -157,7 +157,13 @@ class Batch(Document): frappe.throw(_("The selected item cannot have Batch")) def set_batchwise_valuation(self): + from erpnext.stock.utils import get_valuation_method + if self.is_new(): + if get_valuation_method(self.item) != "FIFO": + self.use_batchwise_valuation = 0 + return + if frappe.db.get_single_value("Stock Settings", "do_not_use_batchwise_valuation"): self.use_batchwise_valuation = 0 return diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index 9cf9f4d4958..4f22d83244e 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -3327,7 +3327,7 @@ class TestPurchaseReceipt(FrappeTestCase): bundle = dn.items[0].serial_and_batch_bundle valuation_rate = frappe.db.get_value("Serial and Batch Bundle", bundle, "avg_rate") - self.assertEqual(valuation_rate, 100) + self.assertEqual(valuation_rate, 150) doc = frappe.get_doc("Stock Settings") doc.do_not_use_batchwise_valuation = 1 diff --git a/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py b/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py index 42e402e0005..c0171b7a710 100644 --- a/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py +++ b/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py @@ -484,7 +484,7 @@ class TestStockLedgerEntry(FrappeTestCase, StockTestMixin): dns = create_delivery_note_entries_for_batchwise_item_valuation_test(dn_entry_list) sle_details = fetch_sle_details_for_doc_list(dns, ["stock_value_difference"]) svd_list = [-1 * d["stock_value_difference"] for d in sle_details] - expected_incoming_rates = expected_abs_svd = [75.0, 125.0, 75.0, 125.0] + expected_incoming_rates = expected_abs_svd = [100.0, 100.0, 100.0, 100.0] self.assertEqual(expected_abs_svd, svd_list, "Incorrect 'Stock Value Difference' values") for dn, _incoming_rate in zip(dns, expected_incoming_rates, strict=False): diff --git a/erpnext/stock/serial_batch_bundle.py b/erpnext/stock/serial_batch_bundle.py index c88df01665f..8e64b4e856f 100644 --- a/erpnext/stock/serial_batch_bundle.py +++ b/erpnext/stock/serial_batch_bundle.py @@ -683,6 +683,8 @@ class BatchNoValuation(DeprecatedBatchNoValuation): return query.run(as_dict=True) def prepare_batches(self): + from erpnext.stock.utils import get_valuation_method + self.batches = self.batch_nos if isinstance(self.batch_nos, dict): self.batches = list(self.batch_nos.keys()) @@ -690,6 +692,10 @@ class BatchNoValuation(DeprecatedBatchNoValuation): self.batchwise_valuation_batches = [] self.non_batchwise_valuation_batches = [] + if get_valuation_method(self.sle.item_code) == "Moving Average": + self.non_batchwise_valuation_batches = self.batches + return + batches = frappe.get_all( "Batch", filters={"name": ("in", self.batches), "use_batchwise_valuation": 1}, fields=["name"] ) From 2d6626e9063489b7d46a5cdf5952e93580b41782 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Tue, 18 Mar 2025 16:28:31 +0530 Subject: [PATCH 34/44] fix: SABB validation for packed items (cherry picked from commit 3756bf231b496fff01706d76eaa5a8b3afee5d70) --- .../serial_and_batch_bundle/serial_and_batch_bundle.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py index d30cf84d93e..8502c0cc3cc 100644 --- a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py +++ b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py @@ -145,6 +145,11 @@ class SerialandBatchBundle(Document): ) elif not frappe.db.exists("Stock Ledger Entry", {"voucher_detail_no": self.voucher_detail_no}): + if self.voucher_type == "Delivery Note" and frappe.db.exists( + "Packed Item", self.voucher_detail_no + ): + return + frappe.throw( _("The serial and batch bundle {0} not linked to {1} {2}").format( bold(self.name), self.voucher_type, bold(self.voucher_no) From 95718acc9a9a24ee5982313bc3f9481277a0f93d Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Tue, 18 Mar 2025 18:41:57 +0530 Subject: [PATCH 35/44] test: test case for FIFO batch valuation (cherry picked from commit ad9ac1f058703b6e7b9b2930a355787bed149448) --- erpnext/stock/doctype/batch/batch.py | 2 +- .../test_stock_ledger_entry.py | 39 +++++++++++++++++++ 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/batch/batch.py b/erpnext/stock/doctype/batch/batch.py index 4aeb165c7b5..47acebb6634 100644 --- a/erpnext/stock/doctype/batch/batch.py +++ b/erpnext/stock/doctype/batch/batch.py @@ -160,7 +160,7 @@ class Batch(Document): from erpnext.stock.utils import get_valuation_method if self.is_new(): - if get_valuation_method(self.item) != "FIFO": + if get_valuation_method(self.item) == "Moving Average": self.use_batchwise_valuation = 0 return diff --git a/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py b/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py index c0171b7a710..8658316ad90 100644 --- a/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py +++ b/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py @@ -456,6 +456,45 @@ class TestStockLedgerEntry(FrappeTestCase, StockTestMixin): frappe.set_user("Administrator") user.remove_roles("Stock Manager") + def test_batchwise_item_valuation_fifo(self): + item, warehouses, batches = setup_item_valuation_test(valuation_method="FIFO") + + # Incoming Entries for Stock Value check + pr_entry_list = [ + (item, warehouses[0], batches[0], 1, 100), + (item, warehouses[0], batches[1], 1, 50), + (item, warehouses[0], batches[0], 1, 150), + (item, warehouses[0], batches[1], 1, 100), + ] + prs = create_purchase_receipt_entries_for_batchwise_item_valuation_test(pr_entry_list) + sle_details = fetch_sle_details_for_doc_list(prs, ["stock_value"]) + sv_list = [d["stock_value"] for d in sle_details] + expected_sv = [100, 150, 300, 400] + self.assertEqual(expected_sv, sv_list, "Incorrect 'Stock Value' values") + + # Outgoing Entries for Stock Value Difference check + dn_entry_list = [ + (item, warehouses[0], batches[1], 1, 200), + (item, warehouses[0], batches[0], 1, 200), + (item, warehouses[0], batches[1], 1, 200), + (item, warehouses[0], batches[0], 1, 200), + ] + + frappe.flags.use_serial_and_batch_fields = True + dns = create_delivery_note_entries_for_batchwise_item_valuation_test(dn_entry_list) + sle_details = fetch_sle_details_for_doc_list(dns, ["stock_value_difference"]) + svd_list = [-1 * d["stock_value_difference"] for d in sle_details] + expected_incoming_rates = expected_abs_svd = [75.0, 125.0, 75.0, 125.0] + + self.assertEqual(expected_abs_svd, svd_list, "Incorrect 'Stock Value Difference' values") + for dn, _incoming_rate in zip(dns, expected_incoming_rates, strict=False): + self.assertTrue( + dn.items[0].incoming_rate in expected_abs_svd, + "Incorrect 'Incoming Rate' values fetched for DN items", + ) + + frappe.flags.use_serial_and_batch_fields = False + def test_batchwise_item_valuation_moving_average(self): item, warehouses, batches = setup_item_valuation_test(valuation_method="Moving Average") From 8e19b46bd93f4353f4922c628ccfc9de8b83c94c Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Tue, 18 Mar 2025 21:26:25 +0530 Subject: [PATCH 36/44] fix: debit in transaction currency (cherry picked from commit e4acf20a62727ea94c439ec81a877fff8950198e) --- erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index c32c62afc23..98d1850bd8f 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -1318,7 +1318,7 @@ class PurchaseInvoice(BuyingController): "account": cost_of_goods_sold_account, "against": item.expense_account, "debit": stock_adjustment_amt, - "debit_in_transaction_currency": item.net_amount, + "debit_in_transaction_currency": stock_adjustment_amt / self.conversion_rate, "remarks": self.get("remarks") or _("Stock Adjustment"), "cost_center": item.cost_center, "project": item.project or self.project, @@ -1352,6 +1352,7 @@ class PurchaseInvoice(BuyingController): "account": cost_of_goods_sold_account, "against": item.expense_account, "debit": stock_adjustment_amt, + "debit_in_transaction_currency": stock_adjustment_amt / self.conversion_rate, "remarks": self.get("remarks") or _("Stock Adjustment"), "cost_center": item.cost_center, "project": item.project or self.project, From d62960e9253d6b1d0aec57e257f60f10c3ea2f14 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Tue, 18 Mar 2025 21:20:40 +0530 Subject: [PATCH 37/44] fix: not able to make PR against stand alone Debit Note (cherry picked from commit 6a52c30591b8202a73ff87672a54fe9ea21c6ff5) --- erpnext/public/js/controllers/buying.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/public/js/controllers/buying.js b/erpnext/public/js/controllers/buying.js index a705ce62f2b..cbc59867e46 100644 --- a/erpnext/public/js/controllers/buying.js +++ b/erpnext/public/js/controllers/buying.js @@ -194,7 +194,7 @@ erpnext.buying = { } qty(doc, cdt, cdn) { - if ((doc.doctype == "Purchase Receipt") || (doc.doctype == "Purchase Invoice" && (doc.update_stock || doc.is_return))) { + if ((doc.doctype == "Purchase Receipt") || (doc.doctype == "Purchase Invoice" && doc.update_stock)) { this.calculate_received_qty(doc, cdt, cdn) } super.qty(doc, cdt, cdn); From 56bc26aeccf8a208dd7adf4607c364acd1e2fb48 Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Tue, 18 Mar 2025 11:22:46 +0530 Subject: [PATCH 38/44] fix: set landed cost based on purchase invoice rate (cherry picked from commit 75ab5f2bd01bdac8ef14755fb9645495dacd0175) # Conflicts: # erpnext/patches.txt --- erpnext/patches.txt | 4 +++ .../purchase_receipt/purchase_receipt.py | 25 ++++++------------- 2 files changed, 11 insertions(+), 18 deletions(-) diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 5e657df44dc..dda1b11859d 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -395,9 +395,13 @@ execute:frappe.db.set_single_value("Accounts Settings", "exchange_gain_loss_post erpnext.patches.v14_0.disable_add_row_in_gross_profit erpnext.patches.v15_0.set_difference_amount_in_asset_value_adjustment erpnext.patches.v14_0.update_posting_datetime +<<<<<<< HEAD erpnext.stock.doctype.stock_ledger_entry.patches.ensure_sle_indexes erpnext.patches.v15_0.update_query_report erpnext.patches.v15_0.rename_field_from_rate_difference_to_amount_difference +======= +erpnext.patches.v15_0.rename_field_from_rate_difference_to_amount_difference #2025-03-18 +>>>>>>> 75ab5f2bd0 (fix: set landed cost based on purchase invoice rate) erpnext.patches.v15_0.recalculate_amount_difference_field erpnext.patches.v15_0.rename_sla_fields #2025-03-12 erpnext.patches.v15_0.set_purchase_receipt_row_item_to_capitalization_stock_item diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index 7f2c04316e9..168713a66da 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -1102,16 +1102,10 @@ def update_billing_percentage(pr_doc, update_modified=True, adjust_incoming_rate if adjust_incoming_rate: adjusted_amt = 0.0 - item_wise_billed_qty = get_billed_qty_against_purchase_receipt(pr_doc) + billed_qty = get_billed_qty_against_item(item.name) - if ( - item.billed_amt is not None - and item.amount is not None - and item_wise_billed_qty.get(item.name) - ): - adjusted_amt = ( - flt(item.billed_amt / item_wise_billed_qty.get(item.name)) - flt(item.rate) - ) * item.qty + if item.billed_amt is not None and item.amount is not None and billed_qty: + adjusted_amt = (flt(item.billed_amt / billed_qty) - flt(item.rate)) * item.qty adjusted_amt = flt(adjusted_amt * flt(pr_doc.conversion_rate), item.precision("amount")) item.db_set("amount_difference_with_purchase_invoice", adjusted_amt, update_modified=False) @@ -1127,19 +1121,14 @@ def update_billing_percentage(pr_doc, update_modified=True, adjust_incoming_rate adjust_incoming_rate_for_pr(pr_doc) -def get_billed_qty_against_purchase_receipt(pr_doc): - pr_names = [d.name for d in pr_doc.items] +def get_billed_qty_against_item(name): table = frappe.qb.DocType("Purchase Invoice Item") query = ( frappe.qb.from_(table) - .select(table.pr_detail, fn.Sum(table.qty).as_("qty")) - .where((table.pr_detail.isin(pr_names)) & (table.docstatus == 1)) + .select(fn.Sum(table.qty).as_("qty")) + .where((table.pr_detail == name) & (table.docstatus == 1)) ) - invoice_data = query.run(as_list=1) - - if not invoice_data: - return frappe._dict() - return frappe._dict(invoice_data) + return query.run(as_dict=True)[0].get("qty", 0) def adjust_incoming_rate_for_pr(doc): From 36ffc2ee67096d726933b6f54ee3f6448593ee69 Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Tue, 18 Mar 2025 11:43:16 +0530 Subject: [PATCH 39/44] fix: patch (cherry picked from commit 7e669c07281a1275a2c254072009af31b626ea86) --- .../patches/v15_0/recalculate_amount_difference_field.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/erpnext/patches/v15_0/recalculate_amount_difference_field.py b/erpnext/patches/v15_0/recalculate_amount_difference_field.py index 2891ac16c44..0b7ab2ec4fe 100644 --- a/erpnext/patches/v15_0/recalculate_amount_difference_field.py +++ b/erpnext/patches/v15_0/recalculate_amount_difference_field.py @@ -27,11 +27,7 @@ def execute(): table.qty, parent.conversion_rate, ) - .where( - (table.amount_difference_with_purchase_invoice != 0) - & (table.docstatus == 1) - & (parent.company == company) - ) + .where((table.docstatus == 1) & (parent.company == company)) ) posting_date = "2024-04-01" @@ -121,6 +117,7 @@ def get_billed_qty_against_purchase_receipt(pr_names): frappe.qb.from_(table) .select(table.pr_detail, Sum(table.qty).as_("qty")) .where((table.pr_detail.isin(pr_names)) & (table.docstatus == 1)) + .groupby(table.pr_detail) ) invoice_data = query.run(as_list=1) From ec1a3a1e6b4fcecda6e89abb46dca065cc33fdef Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Wed, 19 Mar 2025 13:04:08 +0530 Subject: [PATCH 40/44] fix: take function call outside loop (cherry picked from commit b3c400f998ca030b8d3e705bbd0a676603e133fa) --- .../purchase_receipt/purchase_receipt.py | 28 ++++++++++++++----- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index 168713a66da..48e161980db 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -1083,6 +1083,9 @@ def update_billing_percentage(pr_doc, update_modified=True, adjust_incoming_rate total_amount, total_billed_amount = 0, 0 item_wise_returned_qty = get_item_wise_returned_qty(pr_doc) + if adjust_incoming_rate: + item_wise_billed_qty = get_billed_qty_against_purchase_receipt(pr_doc) + for item in pr_doc.items: returned_qty = flt(item_wise_returned_qty.get(item.name)) returned_amount = flt(returned_qty) * flt(item.rate) @@ -1102,10 +1105,15 @@ def update_billing_percentage(pr_doc, update_modified=True, adjust_incoming_rate if adjust_incoming_rate: adjusted_amt = 0.0 - billed_qty = get_billed_qty_against_item(item.name) - if item.billed_amt is not None and item.amount is not None and billed_qty: - adjusted_amt = (flt(item.billed_amt / billed_qty) - flt(item.rate)) * item.qty + if ( + item.billed_amt is not None + and item.amount is not None + and item_wise_billed_qty.get(item.name) + ): + adjusted_amt = ( + flt(item.billed_amt / item_wise_billed_qty.get(item.name)) - flt(item.rate) + ) * item.qty adjusted_amt = flt(adjusted_amt * flt(pr_doc.conversion_rate), item.precision("amount")) item.db_set("amount_difference_with_purchase_invoice", adjusted_amt, update_modified=False) @@ -1121,14 +1129,20 @@ def update_billing_percentage(pr_doc, update_modified=True, adjust_incoming_rate adjust_incoming_rate_for_pr(pr_doc) -def get_billed_qty_against_item(name): +def get_billed_qty_against_purchase_receipt(pr_doc): + pr_names = [d.name for d in pr_doc.items] table = frappe.qb.DocType("Purchase Invoice Item") query = ( frappe.qb.from_(table) - .select(fn.Sum(table.qty).as_("qty")) - .where((table.pr_detail == name) & (table.docstatus == 1)) + .select(table.pr_detail, fn.Sum(table.qty).as_("qty")) + .where((table.pr_detail.isin(pr_names)) & (table.docstatus == 1)) + .groupby(table.pr_detail) ) - return query.run(as_dict=True)[0].get("qty", 0) + invoice_data = query.run(as_list=1) + + if not invoice_data: + return frappe._dict() + return frappe._dict(invoice_data) def adjust_incoming_rate_for_pr(doc): From 5b802ae527e8e1fe70b893be9d4c5de068ce6833 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Wed, 19 Mar 2025 15:35:26 +0530 Subject: [PATCH 41/44] chore: fix conflicts --- erpnext/patches.txt | 4 ---- 1 file changed, 4 deletions(-) diff --git a/erpnext/patches.txt b/erpnext/patches.txt index dda1b11859d..835ee5b6133 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -395,13 +395,9 @@ execute:frappe.db.set_single_value("Accounts Settings", "exchange_gain_loss_post erpnext.patches.v14_0.disable_add_row_in_gross_profit erpnext.patches.v15_0.set_difference_amount_in_asset_value_adjustment erpnext.patches.v14_0.update_posting_datetime -<<<<<<< HEAD erpnext.stock.doctype.stock_ledger_entry.patches.ensure_sle_indexes erpnext.patches.v15_0.update_query_report -erpnext.patches.v15_0.rename_field_from_rate_difference_to_amount_difference -======= erpnext.patches.v15_0.rename_field_from_rate_difference_to_amount_difference #2025-03-18 ->>>>>>> 75ab5f2bd0 (fix: set landed cost based on purchase invoice rate) erpnext.patches.v15_0.recalculate_amount_difference_field erpnext.patches.v15_0.rename_sla_fields #2025-03-12 erpnext.patches.v15_0.set_purchase_receipt_row_item_to_capitalization_stock_item From 41d8b26dd247858271d2994be348ede8709f1f67 Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Wed, 19 Mar 2025 12:52:34 +0530 Subject: [PATCH 42/44] fix: fetch bom_no when updating items in sales order (cherry picked from commit 508727a57a7248fae0a72b9c521dba7e7dbfdaec) # Conflicts: # erpnext/public/js/utils.js --- erpnext/controllers/accounts_controller.py | 3 +++ erpnext/public/js/utils.js | 16 ++++++++++++++++ erpnext/stock/get_item_details.py | 1 + 3 files changed, 20 insertions(+) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 94b553e2f99..eb483bdb868 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -3706,6 +3706,9 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil if d.get("schedule_date") and parent_doctype == "Purchase Order": child_item.schedule_date = d.get("schedule_date") + if d.get("bom_no") and parent_doctype == "Sales Order": + child_item.bom_no = d.get("bom_no") + if flt(child_item.price_list_rate): if flt(child_item.rate) > flt(child_item.price_list_rate): # if rate is greater than price_list_rate, set margin diff --git a/erpnext/public/js/utils.js b/erpnext/public/js/utils.js index cd851b57972..36320c787b6 100755 --- a/erpnext/public/js/utils.js +++ b/erpnext/public/js/utils.js @@ -692,7 +692,18 @@ erpnext.utils.update_child_items = function (opts) { }, callback: function (r) { if (r.message) { +<<<<<<< HEAD const { qty, price_list_rate: rate, uom, conversion_factor } = r.message; +======= + const { + qty, + price_list_rate: rate, + uom, + conversion_factor, + item_name, + default_bom, + } = r.message; +>>>>>>> 508727a57a (fix: fetch bom_no when updating items in sales order) const row = dialog.fields_dict.trans_items.df.data.find( (doc) => doc.idx == me.doc.idx @@ -703,6 +714,11 @@ erpnext.utils.update_child_items = function (opts) { uom: me.doc.uom || uom, qty: me.doc.qty || qty, rate: me.doc.rate || rate, +<<<<<<< HEAD +======= + item_name: item_name, + bom_no: default_bom, +>>>>>>> 508727a57a (fix: fetch bom_no when updating items in sales order) }); dialog.fields_dict.trans_items.grid.refresh(); } diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index ff87d159e36..2bed71a3e96 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -472,6 +472,7 @@ def get_basic_details(args, item, overwrite_warehouse=True): "weight_per_unit": args.get("weight_per_unit") or item.get("weight_per_unit"), "weight_uom": args.get("weight_uom") or item.get("weight_uom"), "grant_commission": item.get("grant_commission"), + "default_bom": item.default_bom, } ) From e5b28018304ecb4e50dc5392f0f2332e4f5486b4 Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Wed, 19 Mar 2025 15:25:50 +0530 Subject: [PATCH 43/44] fix: remove duplicate (cherry picked from commit 386df968c2647bfed9a388833e2f9cf7618228f2) # Conflicts: # erpnext/public/js/utils.js --- erpnext/public/js/utils.js | 6 +++++- erpnext/stock/get_item_details.py | 1 - 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/erpnext/public/js/utils.js b/erpnext/public/js/utils.js index 36320c787b6..d1cc69c6cda 100755 --- a/erpnext/public/js/utils.js +++ b/erpnext/public/js/utils.js @@ -701,7 +701,7 @@ erpnext.utils.update_child_items = function (opts) { uom, conversion_factor, item_name, - default_bom, + bom_no, } = r.message; >>>>>>> 508727a57a (fix: fetch bom_no when updating items in sales order) @@ -717,8 +717,12 @@ erpnext.utils.update_child_items = function (opts) { <<<<<<< HEAD ======= item_name: item_name, +<<<<<<< HEAD bom_no: default_bom, >>>>>>> 508727a57a (fix: fetch bom_no when updating items in sales order) +======= + bom_no: bom_no, +>>>>>>> 386df968c2 (fix: remove duplicate) }); dialog.fields_dict.trans_items.grid.refresh(); } diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index 2bed71a3e96..ff87d159e36 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -472,7 +472,6 @@ def get_basic_details(args, item, overwrite_warehouse=True): "weight_per_unit": args.get("weight_per_unit") or item.get("weight_per_unit"), "weight_uom": args.get("weight_uom") or item.get("weight_uom"), "grant_commission": item.get("grant_commission"), - "default_bom": item.default_bom, } ) From 38213b31dad108e8881aa48c9f731786a05f25ee Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Wed, 19 Mar 2025 15:47:43 +0530 Subject: [PATCH 44/44] chore: fix conflicts --- erpnext/public/js/utils.js | 21 +-------------------- 1 file changed, 1 insertion(+), 20 deletions(-) diff --git a/erpnext/public/js/utils.js b/erpnext/public/js/utils.js index d1cc69c6cda..c0126f2c6ee 100755 --- a/erpnext/public/js/utils.js +++ b/erpnext/public/js/utils.js @@ -692,18 +692,7 @@ erpnext.utils.update_child_items = function (opts) { }, callback: function (r) { if (r.message) { -<<<<<<< HEAD - const { qty, price_list_rate: rate, uom, conversion_factor } = r.message; -======= - const { - qty, - price_list_rate: rate, - uom, - conversion_factor, - item_name, - bom_no, - } = r.message; ->>>>>>> 508727a57a (fix: fetch bom_no when updating items in sales order) + const { qty, price_list_rate: rate, uom, conversion_factor, bom_no } = r.message; const row = dialog.fields_dict.trans_items.df.data.find( (doc) => doc.idx == me.doc.idx @@ -714,15 +703,7 @@ erpnext.utils.update_child_items = function (opts) { uom: me.doc.uom || uom, qty: me.doc.qty || qty, rate: me.doc.rate || rate, -<<<<<<< HEAD -======= - item_name: item_name, -<<<<<<< HEAD - bom_no: default_bom, ->>>>>>> 508727a57a (fix: fetch bom_no when updating items in sales order) -======= bom_no: bom_no, ->>>>>>> 386df968c2 (fix: remove duplicate) }); dialog.fields_dict.trans_items.grid.refresh(); }