From eb1f1255ebb7f1424e982864bd2b08ceb54e86a2 Mon Sep 17 00:00:00 2001 From: Syed Mujeer Hashmi Date: Mon, 7 Oct 2024 14:12:43 +0000 Subject: [PATCH 01/34] fix: Link opportunity from RFQ to supplier quotation --- .../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 c8ef833e0e9..e123822f386 100644 --- a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py +++ b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py @@ -390,6 +390,7 @@ def make_supplier_quotation_from_rfq(source_name, target_doc=None, for_supplier= "Request for Quotation": { "doctype": "Supplier Quotation", "validation": {"docstatus": ["=", 1]}, + "field_map": {"opportunity": "opportunity"}, }, "Request for Quotation Item": { "doctype": "Supplier Quotation Item", From d604b12d5111e9dd5e0fcd3bc35f06c87284c349 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Fri, 11 Oct 2024 16:22:22 +0530 Subject: [PATCH 02/34] fix: show incorrect entries filter in Stock Ledger Invariant Check report (backport #43619) (#43622) fix: show incorrect entries filter in Stock Ledger Invariant Check report (#43619) fix: show incorrect entry filter in Stock Ledger Invariant Check report (cherry picked from commit 8beee1982f7e04cba7a4887d74ac77469a9648cb) Co-authored-by: rohitwaghchaure --- .../stock_ledger_invariant_check.js | 6 ++++ .../stock_ledger_invariant_check.py | 29 +++++++++++++++++-- 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/erpnext/stock/report/stock_ledger_invariant_check/stock_ledger_invariant_check.js b/erpnext/stock/report/stock_ledger_invariant_check/stock_ledger_invariant_check.js index 1b05d3a65e4..df65654e36b 100644 --- a/erpnext/stock/report/stock_ledger_invariant_check/stock_ledger_invariant_check.js +++ b/erpnext/stock/report/stock_ledger_invariant_check/stock_ledger_invariant_check.js @@ -32,6 +32,12 @@ frappe.query_reports["Stock Ledger Invariant Check"] = { mandatory: 1, options: "Warehouse", }, + { + fieldname: "show_incorrect_entries", + fieldtype: "Check", + label: "Show Incorrect Entries", + default: 0, + }, ], formatter(value, row, column, data, default_formatter) { diff --git a/erpnext/stock/report/stock_ledger_invariant_check/stock_ledger_invariant_check.py b/erpnext/stock/report/stock_ledger_invariant_check/stock_ledger_invariant_check.py index 6fc109f1c6e..5cf653e3851 100644 --- a/erpnext/stock/report/stock_ledger_invariant_check/stock_ledger_invariant_check.py +++ b/erpnext/stock/report/stock_ledger_invariant_check/stock_ledger_invariant_check.py @@ -5,7 +5,7 @@ import json import frappe from frappe import _ -from frappe.utils import get_link_to_form, parse_json +from frappe.utils import cint, flt, get_link_to_form, parse_json SLE_FIELDS = ( "name", @@ -36,7 +36,7 @@ def execute(filters=None): def get_data(filters): sles = get_stock_ledger_entries(filters) - return add_invariant_check_fields(sles) + return add_invariant_check_fields(sles, filters) def get_stock_ledger_entries(filters): @@ -48,9 +48,12 @@ def get_stock_ledger_entries(filters): ) -def add_invariant_check_fields(sles): +def add_invariant_check_fields(sles, filters): balance_qty = 0.0 balance_stock_value = 0.0 + + incorrect_idx = 0 + precision = frappe.get_precision("Stock Ledger Entry", "actual_qty") for idx, sle in enumerate(sles): queue = json.loads(sle.stock_queue) if sle.stock_queue else [] @@ -95,6 +98,12 @@ def add_invariant_check_fields(sles): ) sle.diff_value_diff = sle.stock_value_from_diff - sle.stock_value + if not incorrect_idx and filters.get("show_incorrect_entries"): + if is_sle_has_correct_data(sle, precision): + continue + else: + incorrect_idx = idx + if idx > 0: sle.fifo_stock_diff = sle.fifo_stock_value - sles[idx - 1].fifo_stock_value sle.fifo_difference_diff = sle.fifo_stock_diff - sle.stock_value_difference @@ -104,9 +113,23 @@ def add_invariant_check_fields(sles): "Batch", sle.batch_no, "use_batchwise_valuation", cache=True ) + if filters.get("show_incorrect_entries"): + if incorrect_idx > 0: + sles = sles[cint(incorrect_idx) - 1 :] + + return [] + return sles +def is_sle_has_correct_data(sle, precision): + if flt(sle.difference_in_qty, precision) != 0.0 or flt(sle.diff_value_diff, precision) != 0: + print(flt(sle.difference_in_qty, precision), flt(sle.diff_value_diff, precision)) + return False + + return True + + def get_columns(): return [ { From b72906a7a1de57d9344384226cf8e9061ab47cfa Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Sat, 12 Oct 2024 14:52:35 +0530 Subject: [PATCH 03/34] fix: don't update reference to SI / PI on advances (cherry picked from commit b409f7462070f301469e958be1764b90352298b6) --- .../doctype/payment_entry/payment_entry.py | 16 ++++++++++++++-- erpnext/accounts/utils.py | 11 +++++++++-- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 9424d722cf5..34179b0b104 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -1211,11 +1211,23 @@ class PaymentEntry(AccountsController): { dr_or_cr: allocated_amount_in_company_currency, dr_or_cr + "_in_account_currency": d.allocated_amount, - "against_voucher_type": d.reference_doctype, - "against_voucher": d.reference_name, "cost_center": cost_center, } ) + + if self.book_advance_payments_in_separate_party_account and d.reference_doctype in [ + "Sales Order", + "Purchase Order", + ]: + gle.update( + { + "against_voucher_type": d.reference_doctype, + "against_voucher": d.reference_name, + } + ) + else: + gle.update({"against_voucher_type": self.doctype, "against_voucher": self.name}) + gl_entries.append(gle) if self.unallocated_amount: diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index 8e47ddc3652..cf25b201129 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -476,8 +476,12 @@ def reconcile_against_document( # For payments with `Advance` in separate account feature enabled, only new ledger entries are posted for each reference. # No need to cancel/delete payment ledger entries + repost_whole_ledger = any([x.voucher_detail_no for x in entries]) if voucher_type == "Payment Entry" and doc.book_advance_payments_in_separate_party_account: - doc.make_advance_gl_entries(cancel=1) + if repost_whole_ledger: + doc.make_gl_entries(cancel=1) + else: + doc.make_advance_gl_entries(cancel=1) else: _delete_pl_entries(voucher_type, voucher_no) @@ -513,7 +517,10 @@ def reconcile_against_document( if voucher_type == "Payment Entry" and doc.book_advance_payments_in_separate_party_account: # both ledgers must be posted to for `Advance` in separate account feature # TODO: find a more efficient way post only for the new linked vouchers - doc.make_advance_gl_entries() + if repost_whole_ledger: + doc.make_gl_entries() + else: + doc.make_advance_gl_entries() else: gl_map = doc.build_gl_map() # Make sure there is no overallocation From ca0a962870c2be7007d6453f851b5eee571f4e73 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Sat, 12 Oct 2024 15:29:48 +0530 Subject: [PATCH 04/34] refactor: reference update logic in advance (cherry picked from commit a112581acd77b41f9140b0cee082db3b00cd1e22) --- .../doctype/payment_entry/payment_entry.py | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 34179b0b104..f031bda045e 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -1215,18 +1215,26 @@ class PaymentEntry(AccountsController): } ) - if self.book_advance_payments_in_separate_party_account and d.reference_doctype in [ - "Sales Order", - "Purchase Order", - ]: + if self.book_advance_payments_in_separate_party_account: + if d.reference_doctype in [ + "Sales Order", + "Purchase Order", + ]: + gle.update( + { + "against_voucher_type": d.reference_doctype, + "against_voucher": d.reference_name, + } + ) + else: + gle.update({"against_voucher_type": self.doctype, "against_voucher": self.name}) + else: gle.update( { "against_voucher_type": d.reference_doctype, "against_voucher": d.reference_name, } ) - else: - gle.update({"against_voucher_type": self.doctype, "against_voucher": self.name}) gl_entries.append(gle) From 5ce2d73692f09b70784da47db52e02c7eb773c79 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Sat, 12 Oct 2024 15:54:21 +0530 Subject: [PATCH 05/34] refactor: use hooks to identify advance doctypes (cherry picked from commit e7bb960bb557a0cc05570b248962ba519a3278db) --- erpnext/accounts/doctype/payment_entry/payment_entry.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index f031bda045e..eddcdf56fb2 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -1166,6 +1166,10 @@ class PaymentEntry(AccountsController): if not self.party_account: return + advance_payment_doctypes = frappe.get_hooks("advance_payment_receivable_doctypes") + frappe.get_hooks( + "advance_payment_payable_doctypes" + ) + if self.payment_type == "Receive": against_account = self.paid_to else: @@ -1216,10 +1220,7 @@ class PaymentEntry(AccountsController): ) if self.book_advance_payments_in_separate_party_account: - if d.reference_doctype in [ - "Sales Order", - "Purchase Order", - ]: + if d.reference_doctype in advance_payment_doctypes: gle.update( { "against_voucher_type": d.reference_doctype, From 9c26093a51effc459a7f5f7520ec176423b028c5 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Sat, 12 Oct 2024 15:55:51 +0530 Subject: [PATCH 06/34] refactor(test): utility methods for enabling advance in separate acc (cherry picked from commit a21a406d0409c28c0880b91577343f24db6e510a) # Conflicts: # erpnext/accounts/test/accounts_mixin.py --- erpnext/accounts/test/accounts_mixin.py | 41 +++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/erpnext/accounts/test/accounts_mixin.py b/erpnext/accounts/test/accounts_mixin.py index d503f7bc4af..b67e190f431 100644 --- a/erpnext/accounts/test/accounts_mixin.py +++ b/erpnext/accounts/test/accounts_mixin.py @@ -87,6 +87,22 @@ class AccountsTestMixin: "parent_account": "Bank Accounts - " + abbr, } ), + frappe._dict( + { + "attribute_name": "advance_received", + "account_name": "Advance Received", + "parent_account": "Current Liabilities - " + abbr, + "account_type": "Receivable", + } + ), + frappe._dict( + { + "attribute_name": "advance_paid", + "account_name": "Advance Paid", + "parent_account": "Current Assets - " + abbr, + "account_type": "Payable", + } + ), ] for acc in other_accounts: acc_name = acc.account_name + " - " + abbr @@ -101,9 +117,34 @@ class AccountsTestMixin: "company": self.company, } ) + new_acc.account_type = acc.get("account_type", None) new_acc.save() setattr(self, acc.attribute_name, new_acc.name) +<<<<<<< HEAD +======= + self.identify_default_warehouses() + + def enable_advance_as_liability(self): + company = frappe.get_doc("Company", self.company) + company.book_advance_payments_in_separate_party_account = True + company.default_advance_received_account = self.advance_received + company.default_advance_paid_account = self.advance_paid + company.save() + + def disable_advance_as_liability(self): + company = frappe.get_doc("Company", self.company) + company.book_advance_payments_in_separate_party_account = False + company.default_advance_paid_account = company.default_advance_received_account = None + company.save() + + def identify_default_warehouses(self): + for w in frappe.db.get_all( + "Warehouse", filters={"company": self.company}, fields=["name", "warehouse_name"] + ): + setattr(self, "warehouse_" + w.warehouse_name.lower().strip().replace(" ", "_"), w.name) + +>>>>>>> a21a406d04 (refactor(test): utility methods for enabling advance in separate acc) def create_usd_receivable_account(self): account_name = "Debtors USD" if not frappe.db.get_value( From e37a88fdb647afc11e54bd00692ab87027a84c3e Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Sat, 12 Oct 2024 15:56:53 +0530 Subject: [PATCH 07/34] test: unreconciliation of individual SO from Advance Payment (cherry picked from commit 8a6978e55000efda874931dcf4a11403bf795e84) --- .../test_unreconcile_payment.py | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/erpnext/accounts/doctype/unreconcile_payment/test_unreconcile_payment.py b/erpnext/accounts/doctype/unreconcile_payment/test_unreconcile_payment.py index 43dfbfaef60..eb5530706b6 100644 --- a/erpnext/accounts/doctype/unreconcile_payment/test_unreconcile_payment.py +++ b/erpnext/accounts/doctype/unreconcile_payment/test_unreconcile_payment.py @@ -363,3 +363,54 @@ class TestUnreconcilePayment(AccountsTestMixin, FrappeTestCase): self.assertEqual(so.advance_paid, 0) self.assertEqual(len(pe.references), 0) self.assertEqual(pe.unallocated_amount, 100) + + def test_06_unreconcile_advance_from_payment_entry(self): + self.enable_advance_as_liability() + so1 = self.create_sales_order() + so2 = self.create_sales_order() + + pe = self.create_payment_entry() + # Allocation payment against Sales Order + pe.paid_amount = 260 + pe.append( + "references", + {"reference_doctype": so1.doctype, "reference_name": so1.name, "allocated_amount": 150}, + ) + pe.append( + "references", + {"reference_doctype": so2.doctype, "reference_name": so2.name, "allocated_amount": 110}, + ) + pe.save().submit() + + # Assert 'Advance Paid' + so1.reload() + self.assertEqual(so1.advance_paid, 150) + so2.reload() + self.assertEqual(so2.advance_paid, 110) + + unreconcile = frappe.get_doc( + { + "doctype": "Unreconcile Payment", + "company": self.company, + "voucher_type": pe.doctype, + "voucher_no": pe.name, + } + ) + unreconcile.add_references() + self.assertEqual(len(unreconcile.allocations), 2) + allocations = [(x.reference_name, x.allocated_amount) for x in unreconcile.allocations] + self.assertListEqual(allocations, [(so1.name, 150), (so2.name, 110)]) + # unreconcile so2 + unreconcile.remove(unreconcile.allocations[0]) + unreconcile.save().submit() + + # Assert 'Advance Paid' + so1.reload() + so2.reload() + pe.reload() + self.assertEqual(so1.advance_paid, 150) + self.assertEqual(so2.advance_paid, 0) + self.assertEqual(len(pe.references), 1) + self.assertEqual(pe.unallocated_amount, 110) + + self.disable_advance_as_liability() From 2c2ca22d1288d2a771e292d5d1fa84dd99194b93 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Sat, 12 Oct 2024 18:12:03 +0530 Subject: [PATCH 08/34] test: reconciled Invoice should not showup in tool Scenario should be tested on 'Advance in separate party account' (cherry picked from commit f1ec61c19ec8275804958e4eaf131906a2cffc90) --- .../test_unreconcile_payment.py | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/erpnext/accounts/doctype/unreconcile_payment/test_unreconcile_payment.py b/erpnext/accounts/doctype/unreconcile_payment/test_unreconcile_payment.py index eb5530706b6..13e5294aa78 100644 --- a/erpnext/accounts/doctype/unreconcile_payment/test_unreconcile_payment.py +++ b/erpnext/accounts/doctype/unreconcile_payment/test_unreconcile_payment.py @@ -7,7 +7,9 @@ from frappe.utils import today from erpnext.accounts.doctype.payment_entry.test_payment_entry import create_payment_entry from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice +from erpnext.accounts.party import get_party_account from erpnext.accounts.test.accounts_mixin import AccountsTestMixin +from erpnext.selling.doctype.sales_order.sales_order import make_sales_invoice from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order @@ -414,3 +416,49 @@ class TestUnreconcilePayment(AccountsTestMixin, FrappeTestCase): self.assertEqual(pe.unallocated_amount, 110) self.disable_advance_as_liability() + + def test_07_adv_from_so_to_invoice(self): + self.enable_advance_as_liability() + so = self.create_sales_order() + pe = self.create_payment_entry() + pe.paid_amount = 1000 + pe.append( + "references", + {"reference_doctype": so.doctype, "reference_name": so.name, "allocated_amount": 1000}, + ) + pe.save().submit() + + # Assert 'Advance Paid' + so.reload() + self.assertEqual(so.advance_paid, 1000) + + si = make_sales_invoice(so.name) + si.insert().submit() + + pr = frappe.get_doc( + { + "doctype": "Payment Reconciliation", + "company": self.company, + "party_type": "Customer", + "party": so.customer, + } + ) + accounts = get_party_account("Customer", so.customer, so.company, True) + pr.receivable_payable_account = accounts[0] + pr.default_advance_account = accounts[1] + pr.get_unreconciled_entries() + self.assertEqual(len(pr.get("invoices")), 1) + self.assertEqual(len(pr.get("payments")), 1) + invoices = [x.as_dict() for x in pr.get("invoices")] + payments = [x.as_dict() for x in pr.get("payments")] + pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments})) + pr.reconcile() + + self.assertEqual(len(pr.get("invoices")), 0) + self.assertEqual(len(pr.get("payments")), 0) + + # Assert 'Advance Paid' + so.reload() + self.assertEqual(so.advance_paid, 0) + + self.disable_advance_as_liability() From ae73d9c6216c19db829f32d45cee73ce689813d7 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Sun, 13 Oct 2024 08:08:54 +0530 Subject: [PATCH 09/34] chore: better comments for context (cherry picked from commit e7505e92c9b4200d52214d2dc97f7052b2333095) --- .../accounts/doctype/payment_entry/payment_entry.py | 2 ++ erpnext/accounts/utils.py | 10 ++++++---- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index eddcdf56fb2..7e3aa25f358 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -1221,6 +1221,7 @@ class PaymentEntry(AccountsController): if self.book_advance_payments_in_separate_party_account: if d.reference_doctype in advance_payment_doctypes: + # Upon reconciliation, whole ledger will be reposted. So, reference to SO/PO is fine gle.update( { "against_voucher_type": d.reference_doctype, @@ -1228,6 +1229,7 @@ class PaymentEntry(AccountsController): } ) else: + # Do not reference Invoices while Advance is in separate party account gle.update({"against_voucher_type": self.doctype, "against_voucher": self.name}) else: gle.update( diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index cf25b201129..1d75fafc1a9 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -474,8 +474,8 @@ def reconcile_against_document( doc = frappe.get_doc(voucher_type, voucher_no) frappe.flags.ignore_party_validation = True - # For payments with `Advance` in separate account feature enabled, only new ledger entries are posted for each reference. - # No need to cancel/delete payment ledger entries + # When Advance is allocated from an Order to an Invoice + # whole ledger must be reposted repost_whole_ledger = any([x.voucher_detail_no for x in entries]) if voucher_type == "Payment Entry" and doc.book_advance_payments_in_separate_party_account: if repost_whole_ledger: @@ -515,11 +515,13 @@ def reconcile_against_document( doc = frappe.get_doc(entry.voucher_type, entry.voucher_no) if voucher_type == "Payment Entry" and doc.book_advance_payments_in_separate_party_account: - # both ledgers must be posted to for `Advance` in separate account feature - # TODO: find a more efficient way post only for the new linked vouchers + # When Advance is allocated from an Order to an Invoice + # whole ledger must be reposted if repost_whole_ledger: doc.make_gl_entries() else: + # both ledgers must be posted to for `Advance` in separate account feature + # TODO: find a more efficient way post only for the new linked vouchers doc.make_advance_gl_entries() else: gl_map = doc.build_gl_map() From 361836e7350ec5210979f6cbc9434f69aa0583bc Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Sun, 13 Oct 2024 08:45:15 +0530 Subject: [PATCH 10/34] chore: resolve conflict --- erpnext/accounts/test/accounts_mixin.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/erpnext/accounts/test/accounts_mixin.py b/erpnext/accounts/test/accounts_mixin.py index b67e190f431..e526e07c734 100644 --- a/erpnext/accounts/test/accounts_mixin.py +++ b/erpnext/accounts/test/accounts_mixin.py @@ -121,8 +121,6 @@ class AccountsTestMixin: new_acc.save() setattr(self, acc.attribute_name, new_acc.name) -<<<<<<< HEAD -======= self.identify_default_warehouses() def enable_advance_as_liability(self): @@ -144,7 +142,6 @@ class AccountsTestMixin: ): setattr(self, "warehouse_" + w.warehouse_name.lower().strip().replace(" ", "_"), w.name) ->>>>>>> a21a406d04 (refactor(test): utility methods for enabling advance in separate acc) def create_usd_receivable_account(self): account_name = "Debtors USD" if not frappe.db.get_value( From 6265582e53d1cec3b22a6fbb5444ac596d03c88e Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Sun, 13 Oct 2024 09:50:41 +0530 Subject: [PATCH 11/34] refactor: use correct hook for identifying advance doctypes --- erpnext/accounts/doctype/payment_entry/payment_entry.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 7e3aa25f358..7885d825129 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -1166,9 +1166,7 @@ class PaymentEntry(AccountsController): if not self.party_account: return - advance_payment_doctypes = frappe.get_hooks("advance_payment_receivable_doctypes") + frappe.get_hooks( - "advance_payment_payable_doctypes" - ) + advance_payment_doctypes = frappe.get_hooks("advance_payment_doctypes") if self.payment_type == "Receive": against_account = self.paid_to From 928b6b1510b13fbf15e7d3a76fe204a3bcd12f30 Mon Sep 17 00:00:00 2001 From: Abdeali Chharchhoda Date: Sun, 13 Oct 2024 19:58:20 +0530 Subject: [PATCH 12/34] fix: Use `ref_doc.get()` for `party_account_currency` (cherry picked from commit b79549422aff1c9b404e759a0f14efc01823c1b5) --- erpnext/accounts/doctype/payment_request/payment_request.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/payment_request/payment_request.py b/erpnext/accounts/doctype/payment_request/payment_request.py index 8c370ba9d8c..fd07551897a 100644 --- a/erpnext/accounts/doctype/payment_request/payment_request.py +++ b/erpnext/accounts/doctype/payment_request/payment_request.py @@ -571,7 +571,7 @@ def make_payment_request(**args): ) party_type = args.get("party_type") or "Customer" - party_account_currency = ref_doc.party_account_currency + party_account_currency = ref_doc.get("party_account_currency") if not party_account_currency: party_account = get_party_account(party_type, ref_doc.get(party_type.lower()), ref_doc.company) From f3ceb4ac7d50969d2dcece661078e1ab0af575ac Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 14 Oct 2024 15:03:35 +0530 Subject: [PATCH 13/34] fix: quotation to so frappe crm (backport #43644) (#43646) fix: quotation to so frappe crm (#43644) (cherry picked from commit d57624b18242f2a3da0d6ee4d1fdfba4a157e8b2) Co-authored-by: Nihantra C. Patel <141945075+Nihantra-Patel@users.noreply.github.com> --- .../selling/doctype/quotation/quotation.py | 25 ++++++++++--------- 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/erpnext/selling/doctype/quotation/quotation.py b/erpnext/selling/doctype/quotation/quotation.py index 2d7fef2d6e0..a5994756c46 100644 --- a/erpnext/selling/doctype/quotation/quotation.py +++ b/erpnext/selling/doctype/quotation/quotation.py @@ -369,24 +369,25 @@ def _make_sales_order(source_name, target_doc=None, ignore_permissions=False): if customer: target.customer = customer.name target.customer_name = customer.customer_name + + # sales team + if not target.get("sales_team"): + for d in customer.get("sales_team") or []: + target.append( + "sales_team", + { + "sales_person": d.sales_person, + "allocated_percentage": d.allocated_percentage or None, + "commission_rate": d.commission_rate, + }, + ) + if source.referral_sales_partner: target.sales_partner = source.referral_sales_partner target.commission_rate = frappe.get_value( "Sales Partner", source.referral_sales_partner, "commission_rate" ) - # sales team - if not target.get("sales_team"): - for d in customer.get("sales_team") or []: - target.append( - "sales_team", - { - "sales_person": d.sales_person, - "allocated_percentage": d.allocated_percentage or None, - "commission_rate": d.commission_rate, - }, - ) - target.flags.ignore_permissions = ignore_permissions target.run_method("set_missing_values") target.run_method("calculate_taxes_and_totals") From e5aaa5b6e5ebdb340666123e86e3c3b78b57d6ce Mon Sep 17 00:00:00 2001 From: Ninad1306 Date: Fri, 11 Oct 2024 11:33:27 +0530 Subject: [PATCH 14/34] fix: ignore free item when qty is zero (cherry picked from commit 7ae98f77eeee9b44d0afb9899c826a5acd4eb126) --- erpnext/accounts/doctype/pricing_rule/utils.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/erpnext/accounts/doctype/pricing_rule/utils.py b/erpnext/accounts/doctype/pricing_rule/utils.py index b848fde08d8..a74dfea2cca 100644 --- a/erpnext/accounts/doctype/pricing_rule/utils.py +++ b/erpnext/accounts/doctype/pricing_rule/utils.py @@ -657,6 +657,9 @@ def get_product_discount_rule(pricing_rule, item_details, args=None, doc=None): if pricing_rule.round_free_qty: qty = math.floor(qty) + if not qty: + return + free_item_data_args = { "item_code": free_item, "qty": qty, From 389ee909a52280f84a88509e0596cdd1a72afaf0 Mon Sep 17 00:00:00 2001 From: Ninad1306 Date: Fri, 11 Oct 2024 14:02:50 +0530 Subject: [PATCH 15/34] test: test case to validate free item is ignored when qty is zero (cherry picked from commit a2b41a0c16c9d95c6ca609e1be100d8924d8028b) --- erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py b/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py index 3fece4aeeab..b4c47a26eb1 100644 --- a/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py +++ b/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py @@ -1131,6 +1131,12 @@ class TestPricingRule(FrappeTestCase): self.assertEqual(so.items[1].item_code, "_Test Item") self.assertEqual(so.items[1].qty, 3) + so = make_sales_order(item_code="_Test Item", qty=5, do_not_submit=1) + so.items[0].qty = 1 + del so.items[-1] + so.save() + self.assertEqual(len(so.items), 1) + def test_apply_multiple_pricing_rules_for_discount_percentage_and_amount(self): frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule 1") frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule 2") From 9d05a6ebc04a38d985b0626aa4af26d3638f9cc4 Mon Sep 17 00:00:00 2001 From: venkat102 Date: Thu, 10 Oct 2024 12:33:12 +0530 Subject: [PATCH 16/34] refactor: remove 'format:' based naming (cherry picked from commit e8e1ec0e851496a8096184a2dddebea8efcd6551) # Conflicts: # erpnext/accounts/doctype/unreconcile_payment/unreconcile_payment.json --- .../doctype/unreconcile_payment/unreconcile_payment.json | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/erpnext/accounts/doctype/unreconcile_payment/unreconcile_payment.json b/erpnext/accounts/doctype/unreconcile_payment/unreconcile_payment.json index f906dc6cec6..8ba79581d50 100644 --- a/erpnext/accounts/doctype/unreconcile_payment/unreconcile_payment.json +++ b/erpnext/accounts/doctype/unreconcile_payment/unreconcile_payment.json @@ -1,7 +1,5 @@ { "actions": [], - "allow_rename": 1, - "autoname": "format:UNREC-{#####}", "creation": "2023-08-22 10:26:34.421423", "default_view": "List", "doctype": "DocType", @@ -58,11 +56,14 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], +<<<<<<< HEAD "modified": "2023-08-28 17:42:50.261377", +======= + "modified": "2024-10-10 12:03:50.022444", +>>>>>>> e8e1ec0e85 (refactor: remove 'format:' based naming) "modified_by": "Administrator", "module": "Accounts", "name": "Unreconcile Payment", - "naming_rule": "Expression", "owner": "Administrator", "permissions": [ { From 1fac17b36f536d3b2995a983774e035c4acae4be Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 15 Oct 2024 06:00:12 +0530 Subject: [PATCH 17/34] chore: resolve conflict --- .../doctype/unreconcile_payment/unreconcile_payment.json | 4 ---- 1 file changed, 4 deletions(-) diff --git a/erpnext/accounts/doctype/unreconcile_payment/unreconcile_payment.json b/erpnext/accounts/doctype/unreconcile_payment/unreconcile_payment.json index 8ba79581d50..f2c2f380fe9 100644 --- a/erpnext/accounts/doctype/unreconcile_payment/unreconcile_payment.json +++ b/erpnext/accounts/doctype/unreconcile_payment/unreconcile_payment.json @@ -56,11 +56,7 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], -<<<<<<< HEAD - "modified": "2023-08-28 17:42:50.261377", -======= "modified": "2024-10-10 12:03:50.022444", ->>>>>>> e8e1ec0e85 (refactor: remove 'format:' based naming) "modified_by": "Administrator", "module": "Accounts", "name": "Unreconcile Payment", From 930e79c3510ea7e92edf669f864024c02dbaf058 Mon Sep 17 00:00:00 2001 From: Bhavan23 Date: Tue, 8 Oct 2024 16:18:31 +0530 Subject: [PATCH 18/34] fix: update item details with actual quantity. (cherry picked from commit 9dbdfec9b7a4bcc38399a4d849dcc32b54bf9d4a) --- erpnext/selling/doctype/sales_order/sales_order.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py index 4804080be38..53663de1981 100755 --- a/erpnext/selling/doctype/sales_order/sales_order.py +++ b/erpnext/selling/doctype/sales_order/sales_order.py @@ -14,6 +14,7 @@ from frappe.model.mapper import get_mapped_doc from frappe.model.utils import get_fetch_values from frappe.query_builder.functions import Sum from frappe.utils import add_days, cint, cstr, flt, get_link_to_form, getdate, nowdate, strip_html +from erpnext.stock.get_item_details import get_bin_details from erpnext.accounts.doctype.sales_invoice.sales_invoice import ( unlink_inter_company_doc, @@ -838,6 +839,7 @@ def make_material_request(source_name, target_doc=None): target.project = source_parent.project target.qty = get_remaining_qty(source) target.stock_qty = flt(target.qty) * flt(target.conversion_factor) + target.actual_qty = get_bin_details(target.item_code, target.warehouse, source_parent.company, True).get("actual_qty", 0) args = target.as_dict().copy() args.update( From 86017b223ac8b75802c83c12a964a81258dd6391 Mon Sep 17 00:00:00 2001 From: Bhavan23 Date: Tue, 8 Oct 2024 16:24:28 +0530 Subject: [PATCH 19/34] test: Validate the actual quantity when creating a material request from the sales order (cherry picked from commit 17fdd426455aa3daab828444a2ecde837936faa2) --- erpnext/selling/doctype/sales_order/test_sales_order.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/erpnext/selling/doctype/sales_order/test_sales_order.py b/erpnext/selling/doctype/sales_order/test_sales_order.py index 53c629a90b4..0e031cc7b51 100644 --- a/erpnext/selling/doctype/sales_order/test_sales_order.py +++ b/erpnext/selling/doctype/sales_order/test_sales_order.py @@ -8,6 +8,8 @@ import frappe.permissions from frappe.core.doctype.user_permission.test_user_permission import create_user from frappe.tests.utils import FrappeTestCase, change_settings from frappe.utils import add_days, flt, getdate, nowdate, today +from erpnext.stock.get_item_details import get_bin_details + from erpnext.accounts.test.accounts_mixin import AccountsTestMixin from erpnext.controllers.accounts_controller import update_child_qty_rate @@ -96,6 +98,10 @@ class TestSalesOrder(AccountsTestMixin, FrappeTestCase): self.assertEqual(mr.material_request_type, "Purchase") self.assertEqual(len(mr.get("items")), len(so.get("items"))) + for item in mr.get("items"): + actual_qty = get_bin_details(item.item_code, item.warehouse, mr.company, True).get("actual_qty", 0) + self.assertEqual(flt(item.actual_qty), actual_qty) + def test_make_delivery_note(self): so = make_sales_order(do_not_submit=True) From a70181e0258ae209eb7012a507ce513def51f311 Mon Sep 17 00:00:00 2001 From: Bhavan23 Date: Tue, 8 Oct 2024 17:49:12 +0530 Subject: [PATCH 20/34] fix: update formatings (cherry picked from commit 5f4a523340209b29ebd9932228b0715eb1035103) --- erpnext/selling/doctype/sales_order/sales_order.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py index 53663de1981..a87c1352471 100755 --- a/erpnext/selling/doctype/sales_order/sales_order.py +++ b/erpnext/selling/doctype/sales_order/sales_order.py @@ -14,7 +14,6 @@ from frappe.model.mapper import get_mapped_doc from frappe.model.utils import get_fetch_values from frappe.query_builder.functions import Sum from frappe.utils import add_days, cint, cstr, flt, get_link_to_form, getdate, nowdate, strip_html -from erpnext.stock.get_item_details import get_bin_details from erpnext.accounts.doctype.sales_invoice.sales_invoice import ( unlink_inter_company_doc, @@ -36,7 +35,7 @@ from erpnext.stock.doctype.stock_reservation_entry.stock_reservation_entry impor get_sre_reserved_qty_details_for_voucher, has_reserved_stock, ) -from erpnext.stock.get_item_details import get_default_bom, get_price_list_rate +from erpnext.stock.get_item_details import get_bin_details, get_default_bom, get_price_list_rate from erpnext.stock.stock_balance import get_reserved_qty, update_bin_qty form_grid_templates = {"items": "templates/form_grid/item_grid.html"} @@ -839,7 +838,9 @@ def make_material_request(source_name, target_doc=None): target.project = source_parent.project target.qty = get_remaining_qty(source) target.stock_qty = flt(target.qty) * flt(target.conversion_factor) - target.actual_qty = get_bin_details(target.item_code, target.warehouse, source_parent.company, True).get("actual_qty", 0) + target.actual_qty = get_bin_details( + target.item_code, target.warehouse, source_parent.company, True + ).get("actual_qty", 0) args = target.as_dict().copy() args.update( From c2c6d27625a82fe836668773d0b8fe41426c5161 Mon Sep 17 00:00:00 2001 From: Bhavan23 Date: Tue, 8 Oct 2024 18:14:00 +0530 Subject: [PATCH 21/34] fix: update formatings (cherry picked from commit 50442973219cd246f07d6178ca3465c59778808b) --- erpnext/selling/doctype/sales_order/test_sales_order.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/erpnext/selling/doctype/sales_order/test_sales_order.py b/erpnext/selling/doctype/sales_order/test_sales_order.py index 0e031cc7b51..244a6b1ddad 100644 --- a/erpnext/selling/doctype/sales_order/test_sales_order.py +++ b/erpnext/selling/doctype/sales_order/test_sales_order.py @@ -8,8 +8,6 @@ import frappe.permissions from frappe.core.doctype.user_permission.test_user_permission import create_user from frappe.tests.utils import FrappeTestCase, change_settings from frappe.utils import add_days, flt, getdate, nowdate, today -from erpnext.stock.get_item_details import get_bin_details - from erpnext.accounts.test.accounts_mixin import AccountsTestMixin from erpnext.controllers.accounts_controller import update_child_qty_rate @@ -32,6 +30,7 @@ from erpnext.selling.doctype.sales_order.sales_order import ( ) from erpnext.stock.doctype.item.test_item import make_item from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry +from erpnext.stock.get_item_details import get_bin_details class TestSalesOrder(AccountsTestMixin, FrappeTestCase): @@ -99,7 +98,9 @@ class TestSalesOrder(AccountsTestMixin, FrappeTestCase): self.assertEqual(len(mr.get("items")), len(so.get("items"))) for item in mr.get("items"): - actual_qty = get_bin_details(item.item_code, item.warehouse, mr.company, True).get("actual_qty", 0) + actual_qty = get_bin_details(item.item_code, item.warehouse, mr.company, True).get( + "actual_qty", 0 + ) self.assertEqual(flt(item.actual_qty), actual_qty) def test_make_delivery_note(self): From 85088e4aff41306bb132a1f4e22e7e4c85eb70b4 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Tue, 15 Oct 2024 11:57:02 +0530 Subject: [PATCH 22/34] fix: zero incoming rate for delivery note return (#43642) (cherry picked from commit 6087a57b0c84b4a35a4e0797a321f13f07fa8d43) --- erpnext/controllers/selling_controller.py | 10 +++++ .../delivery_note/test_delivery_note.py | 41 +++++++++++++++++++ erpnext/stock/stock_ledger.py | 9 ++++ 3 files changed, 60 insertions(+) diff --git a/erpnext/controllers/selling_controller.py b/erpnext/controllers/selling_controller.py index 7ad12b54eff..49710de06f6 100644 --- a/erpnext/controllers/selling_controller.py +++ b/erpnext/controllers/selling_controller.py @@ -473,6 +473,16 @@ class SellingController(StockController): raise_error_if_no_rate=False, ) + if ( + not d.incoming_rate + and self.get("return_against") + and self.get("is_return") + and get_valuation_method(d.item_code) == "Moving Average" + ): + d.incoming_rate = get_rate_for_return( + self.doctype, self.name, d.item_code, self.return_against, item_row=d + ) + # For internal transfers use incoming rate as the valuation rate if self.is_internal_transfer(): if self.doctype == "Delivery Note" or self.get("update_stock"): diff --git a/erpnext/stock/doctype/delivery_note/test_delivery_note.py b/erpnext/stock/doctype/delivery_note/test_delivery_note.py index cfe550f2f27..0cfb427c670 100644 --- a/erpnext/stock/doctype/delivery_note/test_delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/test_delivery_note.py @@ -2039,6 +2039,47 @@ class TestDeliveryNote(FrappeTestCase): self.assertEqual(sn.status, "Delivered") self.assertEqual(sn.warranty_period, 100) + def test_batch_return_dn(self): + from erpnext.stock.doctype.delivery_note.delivery_note import make_sales_return + + item_code = make_item( + "Test Batch Return DN Item 1", + properties={ + "has_batch_no": 1, + "valuation_method": "Moving Average", + "create_new_batch": 1, + "batch_number_series": "TBRDN1-.#####", + "is_stock_item": 1, + }, + ).name + + se = make_stock_entry(item_code=item_code, target="_Test Warehouse - _TC", qty=5, basic_rate=100) + + batch_no = get_batch_from_bundle(se.items[0].serial_and_batch_bundle) + dn = create_delivery_note( + item_code=item_code, + qty=5, + rate=500, + use_serial_batch_fields=1, + batch_no=batch_no, + ) + + dn_return = make_sales_return(dn.name) + dn_return.save().submit() + + self.assertEqual(dn_return.items[0].qty, 5 * -1) + + returned_batch_no = get_batch_from_bundle(dn_return.items[0].serial_and_batch_bundle) + self.assertEqual(batch_no, returned_batch_no) + + stock_value_difference = frappe.db.get_value( + "Stock Ledger Entry", + {"voucher_no": dn_return.name, "voucher_type": "Delivery Note"}, + "stock_value_difference", + ) + + self.assertEqual(stock_value_difference, 100.0 * 5) + def create_delivery_note(**args): dn = frappe.new_doc("Delivery Note") diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index b167ccf6df8..dfa60cad96e 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -1074,6 +1074,15 @@ class update_entries_after: } ) + if not rate and sle.voucher_type in ["Delivery Note", "Sales Invoice"]: + rate = get_rate_for_return( + sle.voucher_type, + sle.voucher_no, + sle.item_code, + voucher_detail_no=sle.voucher_detail_no, + sle=sle, + ) + else: rate = get_rate_for_return( sle.voucher_type, From 8668ae92d830a2eb1168739f9a8bd55281f5c451 Mon Sep 17 00:00:00 2001 From: ljain112 Date: Tue, 15 Oct 2024 11:52:44 +0530 Subject: [PATCH 23/34] fix: removed unused query (cherry picked from commit 5f590ddfa2901d483e0ee73fa5e35ff7dd369b1e) # Conflicts: # erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py --- .../tax_withholding_category/tax_withholding_category.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py index 67a33faf9bf..3f9bdea5faf 100644 --- a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py +++ b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py @@ -610,8 +610,11 @@ def get_tcs_amount(parties, inv, tax_details, vouchers, adv_vouchers): conditions.append(ple.voucher_no == ple.against_voucher_no) conditions.append(ple.company == inv.company) +<<<<<<< HEAD (qb.from_(ple).select(Abs(Sum(ple.amount))).where(Criterion.all(conditions)).run(as_list=1)) +======= +>>>>>>> 5f590ddfa2 (fix: removed unused query) advance_amt = ( qb.from_(ple).select(Abs(Sum(ple.amount))).where(Criterion.all(conditions)).run()[0][0] or 0.0 ) From 105f9ec2e1d73671cea1ccb8b2e9ab40439a5eb4 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 15 Oct 2024 13:32:29 +0530 Subject: [PATCH 24/34] chore: resolve conflict --- .../tax_withholding_category/tax_withholding_category.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py index 3f9bdea5faf..a56a7f045c8 100644 --- a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py +++ b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py @@ -610,11 +610,6 @@ def get_tcs_amount(parties, inv, tax_details, vouchers, adv_vouchers): conditions.append(ple.voucher_no == ple.against_voucher_no) conditions.append(ple.company == inv.company) -<<<<<<< HEAD - (qb.from_(ple).select(Abs(Sum(ple.amount))).where(Criterion.all(conditions)).run(as_list=1)) - -======= ->>>>>>> 5f590ddfa2 (fix: removed unused query) advance_amt = ( qb.from_(ple).select(Abs(Sum(ple.amount))).where(Criterion.all(conditions)).run()[0][0] or 0.0 ) From 5d6fc71556ba70adec566c0711a401cfadbaf432 Mon Sep 17 00:00:00 2001 From: ljain112 Date: Tue, 8 Oct 2024 12:54:28 +0530 Subject: [PATCH 25/34] fix: delete invalid pricing rule on change of applicable_for (cherry picked from commit 42746fc630eb3b4707ee62f16bf73268799deb2b) --- .../promotional_scheme/promotional_scheme.py | 52 +++++++++++++++---- 1 file changed, 41 insertions(+), 11 deletions(-) diff --git a/erpnext/accounts/doctype/promotional_scheme/promotional_scheme.py b/erpnext/accounts/doctype/promotional_scheme/promotional_scheme.py index 86bd2135515..4cc87394b4f 100644 --- a/erpnext/accounts/doctype/promotional_scheme/promotional_scheme.py +++ b/erpnext/accounts/doctype/promotional_scheme/promotional_scheme.py @@ -5,6 +5,8 @@ import frappe from frappe import _ from frappe.model.document import Document +from frappe.query_builder import Criterion +from frappe.query_builder.functions import IfNull pricing_rule_fields = [ "apply_on", @@ -162,22 +164,50 @@ class PromotionalScheme(Document): if self.is_new(): return - transaction_exists = False - docnames = [] + invalid_pricing_rule = self.get_invalid_pricing_rules() - # If user has changed applicable for - if self.get_doc_before_save() and self.get_doc_before_save().applicable_for == self.applicable_for: + if not invalid_pricing_rule: return - docnames = frappe.get_all("Pricing Rule", filters={"promotional_scheme": self.name}) + if frappe.db.exists( + "Pricing Rule Detail", + { + "pricing_rule": ["in", invalid_pricing_rule], + "docstatus": ["<", 2], + }, + ): + raise_for_transaction_exists(self.name) - for docname in docnames: - if frappe.db.exists("Pricing Rule Detail", {"pricing_rule": docname.name, "docstatus": ("<", 2)}): - raise_for_transaction_exists(self.name) + for doc in invalid_pricing_rule: + frappe.delete_doc("Pricing Rule", doc) - if docnames and not transaction_exists: - for docname in docnames: - frappe.delete_doc("Pricing Rule", docname.name) + frappe.msgprint( + _("The following invalid Pricing Rules are deleted:") + + "

  • " + + "
  • ".join(invalid_pricing_rule) + + "
" + ) + + def get_invalid_pricing_rules(self): + pr = frappe.qb.DocType("Pricing Rule") + conditions = [] + conditions.append(pr.promotional_scheme == self.name) + + if self.applicable_for: + applicable_for = frappe.scrub(self.applicable_for) + applicable_for_list = [d.get(applicable_for) for d in self.get(applicable_for)] + + conditions.append( + (IfNull(pr.applicable_for, "") != self.applicable_for) + | ( + (IfNull(pr.applicable_for, "") == self.applicable_for) + & IfNull(pr[applicable_for], "").notin(applicable_for_list) + ) + ) + else: + conditions.append(IfNull(pr.applicable_for, "") != "") + + return frappe.qb.from_(pr).select(pr.name).where(Criterion.all(conditions)).run(pluck=True) def on_update(self): self.validate() From 4dbee00b82b5eed04e350281b54251801686ba30 Mon Sep 17 00:00:00 2001 From: ljain112 Date: Tue, 8 Oct 2024 17:06:05 +0530 Subject: [PATCH 26/34] test: added test for change in applicable_for_value in promotional scheme (cherry picked from commit 2613bdd868ac22bb2165dda439a3d9d0d0a29cea) --- .../test_promotional_scheme.py | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/erpnext/accounts/doctype/promotional_scheme/test_promotional_scheme.py b/erpnext/accounts/doctype/promotional_scheme/test_promotional_scheme.py index 74ba6cf923c..a4ea81f0d9f 100644 --- a/erpnext/accounts/doctype/promotional_scheme/test_promotional_scheme.py +++ b/erpnext/accounts/doctype/promotional_scheme/test_promotional_scheme.py @@ -90,6 +90,31 @@ class TestPromotionalScheme(unittest.TestCase): price_rules = frappe.get_all("Pricing Rule", filters={"promotional_scheme": ps.name}) self.assertEqual(price_rules, []) + def test_change_applicable_for_values_in_promotional_scheme(self): + ps = make_promotional_scheme(applicable_for="Customer", customer="_Test Customer") + ps.append("customer", {"customer": "_Test Customer 2"}) + ps.save() + + price_rules = frappe.get_all( + "Pricing Rule", filters={"promotional_scheme": ps.name, "applicable_for": "Customer"} + ) + self.assertTrue(len(price_rules), 2) + + ps.set("customer", []) + ps.append("customer", {"customer": "_Test Customer 2"}) + ps.save() + + price_rules = frappe.get_all( + "Pricing Rule", + filters={ + "promotional_scheme": ps.name, + "applicable_for": "Customer", + "customer": "_Test Customer", + }, + ) + self.assertEqual(price_rules, []) + frappe.delete_doc("Promotional Scheme", ps.name) + def test_min_max_amount_configuration(self): ps = make_promotional_scheme() ps.price_discount_slabs[0].min_amount = 10 From c490a6654090b30de9e2aa9398859fbb6d2bd9da Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 15 Oct 2024 16:04:36 +0530 Subject: [PATCH 27/34] fix: incorrect warehouse in the serial no selector for rejection (backport #43671) (#43673) fix: incorrect warehouse in the serial no selector for rejection (#43671) (cherry picked from commit 29ff682ecaddad9450a735e02f7a631f555b145e) Co-authored-by: rohitwaghchaure --- erpnext/public/js/utils/serial_no_batch_selector.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/erpnext/public/js/utils/serial_no_batch_selector.js b/erpnext/public/js/utils/serial_no_batch_selector.js index 1045965e43d..8329e5c9d55 100644 --- a/erpnext/public/js/utils/serial_no_batch_selector.js +++ b/erpnext/public/js/utils/serial_no_batch_selector.js @@ -678,6 +678,10 @@ erpnext.SerialBatchPackageSelector = class SerialNoBatchBundleUpdate { } get_warehouse() { + if (this.item?.is_rejected) { + return this.item.rejected_warehouse; + } + return this.item?.type_of_transaction === "Outward" ? this.item.warehouse || this.item.s_warehouse : this.item.warehouse || this.item.t_warehouse; From b2deb8982646deccd4a4b0bef4c67796a64c09d8 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 15 Oct 2024 16:04:48 +0530 Subject: [PATCH 28/34] fix: conversion factor issue (backport #43645) (#43674) fix: conversion factor issue (#43645) (cherry picked from commit a52756f1d471ff313b0c932aeb5d0634be2113d4) Co-authored-by: rohitwaghchaure --- .../request_for_quotation.py | 1 + erpnext/controllers/stock_controller.py | 15 +++++++++++++++ .../purchase_receipt/test_purchase_receipt.py | 15 +++++++++++++++ 3 files changed, 31 insertions(+) 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 e123822f386..3a71733a003 100644 --- a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py +++ b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py @@ -456,6 +456,7 @@ def create_rfq_items(sq_doc, supplier, data): "material_request", "material_request_item", "stock_qty", + "uom", ]: args[field] = data.get(field) diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index 537b37facf4..0714bdd3a63 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -63,6 +63,21 @@ class StockController(AccountsController): self.set_rate_of_stock_uom() self.validate_internal_transfer() self.validate_putaway_capacity() + self.reset_conversion_factor() + + def reset_conversion_factor(self): + for row in self.get("items"): + if row.uom != row.stock_uom: + continue + + if row.conversion_factor != 1.0: + row.conversion_factor = 1.0 + frappe.msgprint( + _( + "Conversion factor for item {0} has been reset to 1.0 as the uom {1} is same as stock uom {2}." + ).format(bold(row.item_code), bold(row.uom), bold(row.stock_uom)), + alert=True, + ) def validate_items_exist(self): if not self.get("items"): diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index 4cb53e753e7..6d0fe27033f 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -3657,6 +3657,21 @@ class TestPurchaseReceipt(FrappeTestCase): self.assertEqual(data[0].get("bal_qty"), 50.0) + def test_same_stock_and_transaction_uom_conversion_factor(self): + item_code = "Test Item for Same Stock and Transaction UOM Conversion Factor" + create_item(item_code) + + pr = make_purchase_receipt( + item_code=item_code, + qty=10, + rate=100, + stock_uom="Nos", + transaction_uom="Nos", + conversion_factor=10, + ) + + self.assertEqual(pr.items[0].conversion_factor, 1.0) + def prepare_data_for_internal_transfer(): from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_internal_supplier From e10a58074f2f58c1fbcca7a85a41deca1f6db076 Mon Sep 17 00:00:00 2001 From: ljain112 Date: Thu, 10 Oct 2024 11:27:26 +0530 Subject: [PATCH 29/34] fix: added string for translation in bank reconciliation statement (cherry picked from commit c99d9f70373d3c833229e828eb9596f93140c3b2) --- .../bank_reconciliation_statement.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/report/bank_reconciliation_statement/bank_reconciliation_statement.js b/erpnext/accounts/report/bank_reconciliation_statement/bank_reconciliation_statement.js index 9335a8cd65a..2684c87a22a 100644 --- a/erpnext/accounts/report/bank_reconciliation_statement/bank_reconciliation_statement.js +++ b/erpnext/accounts/report/bank_reconciliation_statement/bank_reconciliation_statement.js @@ -47,7 +47,7 @@ frappe.query_reports["Bank Reconciliation Statement"] = { }, ], formatter: function (value, row, column, data, default_formatter, filter) { - if (column.fieldname == "payment_entry" && value == "Cheques and Deposits incorrectly cleared") { + if (column.fieldname == "payment_entry" && value == __("Cheques and Deposits incorrectly cleared")) { column.link_onclick = "frappe.query_reports['Bank Reconciliation Statement'].open_utility_report()"; } From 21a7dd43a9f1fee1871e7962924330c03d7949fa Mon Sep 17 00:00:00 2001 From: ljain112 Date: Fri, 11 Oct 2024 11:07:19 +0530 Subject: [PATCH 30/34] fix: added parentheses for correct query formation for logical OR condition (cherry picked from commit c0da8f11f77c6d3bcd543e70aea6a9754427538e) --- .../period_closing_voucher/period_closing_voucher.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 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 c68cd292523..f5619f8088f 100644 --- a/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py +++ b/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py @@ -353,9 +353,10 @@ class PeriodClosingVoucher(AccountsController): if get_opening_entries: query = query.where( - gl_entry.posting_date.between(self.get("year_start_date"), self.posting_date) - | gl_entry.is_opening - == "Yes" + ( # noqa: UP034 + (gl_entry.posting_date.between(self.get("year_start_date"), self.posting_date)) + | (gl_entry.is_opening == "Yes") + ) ) else: query = query.where( From 492ba539e803b2b13abaf41443812d8ac172f858 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 15 Oct 2024 16:47:40 +0530 Subject: [PATCH 31/34] =?UTF-8?q?fix:=20refetch=20items=20from=20BOM=20if?= =?UTF-8?q?=20'Use=20Multi-Level=20BOM'=20has=20changed=20usin=E2=80=A6=20?= =?UTF-8?q?(backport=20#43672)=20(#43676)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fix: refetch items from BOM if 'Use Multi-Level BOM' has changed usin… (#43672) fix: refetch items from BOM if 'Use Multi-Level BOM' has changed using api (cherry picked from commit 05915415de3b13545cab4e2d89c33b526ae7db39) Co-authored-by: rohitwaghchaure --- erpnext/manufacturing/doctype/work_order/work_order.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py index 218ab2f2bf8..d13cd27a095 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.py +++ b/erpnext/manufacturing/doctype/work_order/work_order.py @@ -158,11 +158,20 @@ class WorkOrder(Document): self.validate_operation_time() self.status = self.get_status() self.validate_workstation_type() + self.reset_use_multi_level_bom() validate_uom_is_integer(self, "stock_uom", ["qty", "produced_qty"]) self.set_required_items(reset_only_qty=len(self.get("required_items"))) + def reset_use_multi_level_bom(self): + if self.is_new(): + return + + before_save_obj = self.get_doc_before_save() + if before_save_obj.use_multi_level_bom != self.use_multi_level_bom: + self.get_items_and_operations_from_bom() + def validate_workstation_type(self): for row in self.operations: if not row.workstation and not row.workstation_type: From ea12897ce96b8ba36f1a8db8ddbb192526f78141 Mon Sep 17 00:00:00 2001 From: ljain112 Date: Tue, 15 Oct 2024 12:42:48 +0530 Subject: [PATCH 32/34] fix: run gl_entries and closing voucher processes in same function (cherry picked from commit af4daa5b0f68338fee20ac15c35b6f721d78a459) --- .../period_closing_voucher.py | 54 +++++++------------ 1 file changed, 20 insertions(+), 34 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 f5619f8088f..dd8a5ff16c3 100644 --- a/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py +++ b/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py @@ -60,7 +60,7 @@ class PeriodClosingVoucher(AccountsController): ) if gle_count > 5000: frappe.enqueue( - make_reverse_gl_entries, + process_cancellation, voucher_type="Period Closing Voucher", voucher_no=self.name, queue="long", @@ -71,9 +71,7 @@ class PeriodClosingVoucher(AccountsController): alert=True, ) else: - make_reverse_gl_entries(voucher_type="Period Closing Voucher", voucher_no=self.name) - - self.delete_closing_entries() + process_cancellation(voucher_type="Period Closing Voucher", voucher_no=self.name) def validate_future_closing_vouchers(self): if frappe.db.exists( @@ -86,12 +84,6 @@ class PeriodClosingVoucher(AccountsController): ) ) - def delete_closing_entries(self): - closing_balance = frappe.qb.DocType("Account Closing Balance") - frappe.qb.from_(closing_balance).delete().where( - closing_balance.period_closing_voucher == self.name - ).run() - def validate_account_head(self): closing_account_type = frappe.get_cached_value("Account", self.closing_account_head, "root_type") @@ -166,14 +158,7 @@ class PeriodClosingVoucher(AccountsController): closing_entries = self.get_grouped_gl_entries(get_opening_entries=get_opening_entries) if len(gl_entries + closing_entries) > 3000: frappe.enqueue( - process_gl_entries, - gl_entries=gl_entries, - voucher_name=self.name, - timeout=3000, - ) - - frappe.enqueue( - process_closing_entries, + process_gl_and_closing_entries, gl_entries=gl_entries, closing_entries=closing_entries, voucher_name=self.name, @@ -187,8 +172,9 @@ class PeriodClosingVoucher(AccountsController): alert=True, ) else: - process_gl_entries(gl_entries, self.name) - process_closing_entries(gl_entries, closing_entries, self.name, self.company, self.posting_date) + process_gl_and_closing_entries( + gl_entries, closing_entries, self.name, self.company, self.posting_date + ) def get_grouped_gl_entries(self, get_opening_entries=False): closing_entries = [] @@ -374,12 +360,16 @@ class PeriodClosingVoucher(AccountsController): return query.run(as_dict=1) -def process_gl_entries(gl_entries, voucher_name): +def process_gl_and_closing_entries(gl_entries, closing_entries, voucher_name, company, closing_date): + from erpnext.accounts.doctype.account_closing_balance.account_closing_balance import ( + make_closing_entries, + ) from erpnext.accounts.general_ledger import make_gl_entries try: if gl_entries: make_gl_entries(gl_entries, merge_entries=False) + make_closing_entries(gl_entries + closing_entries, voucher_name, company, closing_date) frappe.db.set_value("Period Closing Voucher", voucher_name, "gle_processing_status", "Completed") except Exception as e: frappe.db.rollback() @@ -387,25 +377,21 @@ def process_gl_entries(gl_entries, voucher_name): frappe.db.set_value("Period Closing Voucher", voucher_name, "gle_processing_status", "Failed") -def process_closing_entries(gl_entries, closing_entries, voucher_name, company, closing_date): - from erpnext.accounts.doctype.account_closing_balance.account_closing_balance import ( - make_closing_entries, - ) - - try: - make_closing_entries(gl_entries + closing_entries, voucher_name, company, closing_date) - except Exception as e: - frappe.db.rollback() - frappe.log_error(e) - - -def make_reverse_gl_entries(voucher_type, voucher_no): +def process_cancellation(voucher_type, voucher_no): from erpnext.accounts.general_ledger import make_reverse_gl_entries try: make_reverse_gl_entries(voucher_type=voucher_type, voucher_no=voucher_no) + delete_closing_entries(voucher_no) frappe.db.set_value("Period Closing Voucher", voucher_no, "gle_processing_status", "Completed") except Exception as e: frappe.db.rollback() frappe.log_error(e) frappe.db.set_value("Period Closing Voucher", voucher_no, "gle_processing_status", "Failed") + + +def delete_closing_entries(voucher_no): + closing_balance = frappe.qb.DocType("Account Closing Balance") + frappe.qb.from_(closing_balance).delete().where( + closing_balance.period_closing_voucher == voucher_no + ).run() From 4db12fe2da4a3bdacf786c8a6c2ab47711ffecef Mon Sep 17 00:00:00 2001 From: ljain112 Date: Tue, 15 Oct 2024 11:41:00 +0530 Subject: [PATCH 33/34] fix: missing child company accounts in consolidated balance sheet (cherry picked from commit 7fae9d57d24de1a075a4c6bcca73ed95888603d9) --- .../consolidated_financial_statement.py | 72 +++++++++---------- 1 file changed, 35 insertions(+), 37 deletions(-) diff --git a/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py b/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py index e6aa215924d..d287b30bf0b 100644 --- a/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py +++ b/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py @@ -469,10 +469,13 @@ def update_parent_account_names(accounts): for d in accounts: if d.account_number: - account_name = d.account_number + " - " + d.account_name + account_key = d.account_number + " - " + d.account_name else: - account_name = d.account_name - name_to_account_map[d.name] = account_name + account_key = d.account_name + + d.account_key = account_key + + name_to_account_map[d.name] = account_key for account in accounts: if account.parent_account: @@ -505,33 +508,26 @@ def get_subsidiary_companies(company): def get_accounts(root_type, companies): accounts = [] - added_accounts = [] for company in companies: - for account in frappe.get_all( - "Account", - fields=[ - "name", - "is_group", - "company", - "parent_account", - "lft", - "rgt", - "root_type", - "report_type", - "account_name", - "account_number", - ], - filters={"company": company, "root_type": root_type}, - ): - if account.account_number: - account_key = account.account_number + "-" + account.account_name - else: - account_key = account.account_name - - if account_key not in added_accounts: - accounts.append(account) - added_accounts.append(account_key) + accounts.extend( + frappe.get_all( + "Account", + fields=[ + "name", + "is_group", + "company", + "parent_account", + "lft", + "rgt", + "root_type", + "report_type", + "account_name", + "account_number", + ], + filters={"company": company, "root_type": root_type}, + ) + ) return accounts @@ -770,15 +766,17 @@ def add_total_row(out, root_type, balance_must_be, companies, company_currency): def filter_accounts(accounts, depth=10): parent_children_map = {} accounts_by_name = {} - for d in accounts: - if d.account_number: - account_name = d.account_number + " - " + d.account_name - else: - account_name = d.account_name - d["company_wise_opening_bal"] = defaultdict(float) - accounts_by_name[account_name] = d + added_accounts = [] - parent_children_map.setdefault(d.parent_account or None, []).append(d) + for d in accounts: + if d.account_key in added_accounts: + continue + + added_accounts.append(d.account_key) + d["company_wise_opening_bal"] = defaultdict(float) + accounts_by_name[d.account_key] = d + + parent_children_map.setdefault(d.parent_account_name or None, []).append(d) filtered_accounts = [] @@ -790,7 +788,7 @@ def filter_accounts(accounts, depth=10): for child in children: child.indent = level filtered_accounts.append(child) - add_to_list(child.name, level + 1) + add_to_list(child.account_key, level + 1) add_to_list(None, 0) From cd9f949b1288708876dd908403616ce0c2e7b6b8 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 15 Oct 2024 17:45:35 +0530 Subject: [PATCH 34/34] fix(stock): Grab posting date/time from SABB (backport #43493) (#43502) fix(stock): Grab posting date/time from SABB (#43493) (cherry picked from commit ade121dac614612405f976014707045ed97181a9) Co-authored-by: Corentin Forler <10946971+cogk@users.noreply.github.com> --- erpnext/stock/stock_ledger.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index dfa60cad96e..0f058d0a64b 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -1740,6 +1740,9 @@ def get_valuation_rate( # Get moving average rate of a specific batch number if warehouse and serial_and_batch_bundle: + sabb = frappe.db.get_value( + "Serial and Batch Bundle", serial_and_batch_bundle, ["posting_date", "posting_time"], as_dict=True + ) batch_obj = BatchNoValuation( sle=frappe._dict( { @@ -1747,6 +1750,8 @@ def get_valuation_rate( "warehouse": warehouse, "actual_qty": -1, "serial_and_batch_bundle": serial_and_batch_bundle, + "posting_date": sabb.posting_date, + "posting_time": sabb.posting_time, } ) )