From a3994867899f7f815b83f6ba373f072615089b6b Mon Sep 17 00:00:00 2001 From: ljain112 Date: Tue, 1 Jul 2025 19:14:52 +0530 Subject: [PATCH 01/35] fix: manage advance payment allocation form Advance Payment Ledger Entry --- .../advance_payment_ledger_entry.json | 16 ++- .../advance_payment_ledger_entry.py | 1 + .../doctype/payment_entry/payment_entry.py | 21 +--- .../unreconcile_payment.py | 107 ++++++++++++------ erpnext/accounts/utils.py | 39 +++---- erpnext/controllers/accounts_controller.py | 19 ++++ erpnext/public/js/utils/unreconcile.js | 14 +-- 7 files changed, 133 insertions(+), 84 deletions(-) diff --git a/erpnext/accounts/doctype/advance_payment_ledger_entry/advance_payment_ledger_entry.json b/erpnext/accounts/doctype/advance_payment_ledger_entry/advance_payment_ledger_entry.json index 290ed11c98ee..18e55a5c6111 100644 --- a/erpnext/accounts/doctype/advance_payment_ledger_entry/advance_payment_ledger_entry.json +++ b/erpnext/accounts/doctype/advance_payment_ledger_entry/advance_payment_ledger_entry.json @@ -12,7 +12,8 @@ "against_voucher_no", "amount", "currency", - "event" + "event", + "delinked" ], "fields": [ { @@ -68,12 +69,20 @@ "label": "Company", "options": "Company", "read_only": 1 + }, + { + "default": "0", + "fieldname": "delinked", + "fieldtype": "Check", + "label": "DeLinked", + "read_only": 1 } ], + "grid_page_length": 50, "in_create": 1, "index_web_pages_for_search": 1, "links": [], - "modified": "2024-11-05 10:31:28.736671", + "modified": "2025-07-01 18:43:34.964275", "modified_by": "Administrator", "module": "Accounts", "name": "Advance Payment Ledger Entry", @@ -107,7 +116,8 @@ "share": 1 } ], + "row_format": "Dynamic", "sort_field": "creation", "sort_order": "DESC", "states": [] -} \ No newline at end of file +} diff --git a/erpnext/accounts/doctype/advance_payment_ledger_entry/advance_payment_ledger_entry.py b/erpnext/accounts/doctype/advance_payment_ledger_entry/advance_payment_ledger_entry.py index 0ec2d4117610..a952af9aad4b 100644 --- a/erpnext/accounts/doctype/advance_payment_ledger_entry/advance_payment_ledger_entry.py +++ b/erpnext/accounts/doctype/advance_payment_ledger_entry/advance_payment_ledger_entry.py @@ -19,6 +19,7 @@ class AdvancePaymentLedgerEntry(Document): amount: DF.Currency company: DF.Link | None currency: DF.Link | None + delinked: DF.Check event: DF.Data | None voucher_no: DF.DynamicLink | None voucher_type: DF.Link | None diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index a9e890c947cd..1f9c29b0754a 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -1384,16 +1384,15 @@ def add_party_gl_entries(self, gl_entries): 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: against_account = self.paid_from party_account_type = frappe.db.get_value("Party Type", self.party_type, "account_type") + advance_payment_doctypes = frappe.get_hooks("advance_payment_receivable_doctypes") + frappe.get_hooks( + "advance_payment_payable_doctypes" + ) party_gl_dict = self.get_gl_dict( { @@ -1448,18 +1447,8 @@ def add_party_gl_entries(self, gl_entries): ) ) - 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, - "against_voucher": d.reference_name, - } - ) - else: - # Do not reference Invoices while Advance is in separate party account - gle.update({"against_voucher_type": self.doctype, "against_voucher": self.name}) + if d.reference_doctype in advance_payment_doctypes: + gle.update({"against_voucher_type": self.doctype, "against_voucher": self.name}) else: gle.update( { diff --git a/erpnext/accounts/doctype/unreconcile_payment/unreconcile_payment.py b/erpnext/accounts/doctype/unreconcile_payment/unreconcile_payment.py index aa5e6d323b36..396f8e98ea4d 100644 --- a/erpnext/accounts/doctype/unreconcile_payment/unreconcile_payment.py +++ b/erpnext/accounts/doctype/unreconcile_payment/unreconcile_payment.py @@ -44,31 +44,12 @@ def validate(self): @frappe.whitelist() def get_allocations_from_payment(self): - allocated_references = [] - ple = qb.DocType("Payment Ledger Entry") - allocated_references = ( - qb.from_(ple) - .select( - ple.account, - ple.party_type, - ple.party, - ple.against_voucher_type.as_("reference_doctype"), - ple.against_voucher_no.as_("reference_name"), - Abs(Sum(ple.amount_in_account_currency)).as_("allocated_amount"), - ple.account_currency, - ) - .where( - (ple.docstatus == 1) - & (ple.voucher_type == self.voucher_type) - & (ple.voucher_no == self.voucher_no) - & (ple.voucher_no != ple.against_voucher_no) - ) - .groupby(ple.against_voucher_type, ple.against_voucher_no) - .run(as_dict=True) + return get_linked_payments_for_doc( + company=self.company, + doctype=self.voucher_type, + docname=self.voucher_no, ) - return allocated_references - def add_references(self): allocations = self.get_allocations_from_payment() @@ -77,34 +58,52 @@ def add_references(self): def on_submit(self): # todo: more granular unreconciliation + advance_payment_doctypes = frappe.get_hooks("advance_payment_payable_doctypes") + frappe.get_hooks( + "advance_payment_receivable_doctypes" + ) + for alloc in self.allocations: doc = frappe.get_doc(alloc.reference_doctype, alloc.reference_name) unlink_ref_doc_from_payment_entries(doc, self.voucher_no) cancel_exchange_gain_loss_journal(doc, self.voucher_type, self.voucher_no) - update_voucher_outstanding( - alloc.reference_doctype, alloc.reference_name, alloc.account, alloc.party_type, alloc.party - ) - if doc.doctype in frappe.get_hooks("advance_payment_payable_doctypes") + frappe.get_hooks( - "advance_payment_receivable_doctypes" - ): + + # update outstanding amounts + if doc.doctype in advance_payment_doctypes: doc.set_total_advance_paid() + else: + update_voucher_outstanding( + alloc.reference_doctype, + alloc.reference_name, + alloc.account, + alloc.party_type, + alloc.party, + ) frappe.db.set_value("Unreconcile Payment Entries", alloc.name, "unlinked", True) @frappe.whitelist() def doc_has_references(doctype: str | None = None, docname: str | None = None): + count = 0 if doctype in ["Sales Invoice", "Purchase Invoice"]: - return frappe.db.count( + count = frappe.db.count( "Payment Ledger Entry", filters={"delinked": 0, "against_voucher_no": docname, "amount": ["<", 0]}, ) else: - return frappe.db.count( + count = frappe.db.count( "Payment Ledger Entry", filters={"delinked": 0, "voucher_no": docname, "against_voucher_no": ["!=", docname]}, ) + if doctype == "Payment Entry": + count += frappe.db.count( + "Advance Payment Ledger Entry", + filters={"delinked": 0, "voucher_no": docname, "voucher_type": doctype}, + ) + + return count + @frappe.whitelist() def get_linked_payments_for_doc( @@ -125,9 +124,12 @@ def get_linked_payments_for_doc( res = ( qb.from_(ple) .select( + ple.account, + ple.party_type, + ple.party, ple.company, - ple.voucher_type, - ple.voucher_no, + ple.voucher_type.as_("reference_doctype"), + ple.voucher_no.as_("reference_name"), Abs(Sum(ple.amount_in_account_currency)).as_("allocated_amount"), ple.account_currency, ) @@ -149,8 +151,11 @@ def get_linked_payments_for_doc( qb.from_(ple) .select( ple.company, - ple.against_voucher_type.as_("voucher_type"), - ple.against_voucher_no.as_("voucher_no"), + ple.account, + ple.party_type, + ple.party, + ple.against_voucher_type.as_("reference_doctype"), + ple.against_voucher_no.as_("reference_name"), Abs(Sum(ple.amount_in_account_currency)).as_("allocated_amount"), ple.account_currency, ) @@ -158,10 +163,42 @@ def get_linked_payments_for_doc( .groupby(ple.against_voucher_no) ) res = query.run(as_dict=True) + + if _dt == "Payment Entry": + # Add advance payments linked to this payment entry + res += get_advance_linked_payments_for_doc(company, doctype, docname) + return res + return [] +def get_advance_linked_payments_for_doc( + company: str | None = None, doctype: str | None = None, docname: str | None = None +) -> list: + if not (doctype and docname): + return [] + + ple = qb.DocType("Advance Payment Ledger Entry") + query = ( + qb.from_(ple) + .select( + ple.company, + ple.against_voucher_type.as_("reference_doctype"), + ple.against_voucher_no.as_("reference_name"), + Abs(Sum(ple.amount)).as_("allocated_amount"), + ple.currency.as_("account_currency"), + ) + .where(ple.delinked == 0) + .where(ple.voucher_no == docname) + .where(ple.voucher_type == doctype) + .where(ple.company == company) + .groupby(ple.against_voucher_no) + ) + + return query.run(as_dict=True) + + @frappe.whitelist() def create_unreconcile_doc_for_selection(selections=None): if selections: diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index 9d73681cf95a..f072f2afaa2e 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -486,17 +486,10 @@ def reconcile_against_document( doc = frappe.get_doc(voucher_type, voucher_no) frappe.flags.ignore_party_validation = True - # 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: - doc.make_gl_entries(cancel=1) - else: - doc.make_advance_gl_entries(cancel=1) - else: + if not (voucher_type == "Payment Entry" and doc.book_advance_payments_in_separate_party_account): _delete_pl_entries(voucher_type, voucher_no) + reposting_rows = [] for entry in entries: check_if_advance_entry_modified(entry) validate_allocated_amount(entry) @@ -521,20 +514,15 @@ def reconcile_against_document( skip_ref_details_update_for_pe=skip_ref_details_update_for_pe, dimensions_dict=dimensions_dict, ) + reposting_rows.append(referenced_row) doc.save(ignore_permissions=True) # re-submit advance entry doc = frappe.get_doc(entry.voucher_type, entry.voucher_no) if voucher_type == "Payment Entry" and doc.book_advance_payments_in_separate_party_account: - # 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() + for row in reposting_rows: + doc.make_advance_gl_entries(entry=row) else: gl_map = doc.build_gl_map() # Make sure there is no overallocation @@ -765,12 +753,14 @@ def update_reference_in_payment_entry( new_row = payment_entry.append("references") new_row.docstatus = 1 + new_row.set_new_name() for field in list(reference_details): new_row.set(field, reference_details[field]) row = new_row else: new_row = payment_entry.append("references") new_row.docstatus = 1 + new_row.set_new_name() new_row.update(reference_details) row = new_row @@ -1029,6 +1019,9 @@ def remove_ref_doc_link_from_pe( # remove reference only from specified payment linked_pe = [x for x in linked_pe if x == payment_name] if payment_name else linked_pe + advance_payment_doctypes = (frappe.get_hooks("advance_payment_receivable_doctypes") or []) + ( + frappe.get_hooks("advance_payment_payable_doctypes") or [] + ) if linked_pe: update_query = ( qb.update(per) @@ -1049,12 +1042,12 @@ def remove_ref_doc_link_from_pe( pe_doc.set_amounts() # Call cancel on only removed reference - references = [ - x - for x in pe_doc.references - if x.reference_doctype == ref_type and x.reference_name == ref_no - ] - [pe_doc.make_advance_gl_entries(x, cancel=1) for x in references] + for refrences in pe_doc.references: + if refrences.reference_doctype == ref_type and refrences.reference_name == ref_no: + if refrences.reference_doctype in advance_payment_doctypes: + pe_doc.mark_advance_payment_ledger_as_delinked(refrences) + else: + pe_doc.make_advance_gl_entries(refrences, cancel=1) pe_doc.clear_unallocated_reference_document_rows() pe_doc.validate_payment_type_with_outstanding() diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index c5e31b46612a..02406eec0acb 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -2210,9 +2210,12 @@ def calculate_total_advance_from_ledger(self): (adv.against_voucher_type == self.doctype) & (adv.against_voucher_no == self.name) & (adv.company == self.company) + & (adv.delinked == 0) ) + .groupby(adv.against_voucher_no, adv.against_voucher_type, adv.company) .run(as_dict=True) ) + return advance def set_total_advance_paid(self): @@ -2981,6 +2984,22 @@ def make_advance_payment_ledger_for_payment(self): doc.flags.ignore_permissions = 1 doc.save() + def mark_advance_payment_ledger_as_delinked(self, references): + if not (references.get("reference_doctype") and references.get("reference_name")): + return + + adv = qb.DocType("Advance Payment Ledger Entry") + + ( + qb.update(adv) + .set(adv.delinked, 1) + .where(adv.voucher_type == self.doctype) + .where(adv.voucher_no == self.name) + .where(adv.against_voucher_type == references.reference_doctype) + .where(adv.against_voucher_no == references.reference_name) + .run() + ) + def make_advance_payment_ledger_entries(self): if self.docstatus != 0: if self.doctype == "Journal Entry": diff --git a/erpnext/public/js/utils/unreconcile.js b/erpnext/public/js/utils/unreconcile.js index 072b541753d5..4ccbf0106d7f 100644 --- a/erpnext/public/js/utils/unreconcile.js +++ b/erpnext/public/js/utils/unreconcile.js @@ -42,8 +42,8 @@ erpnext.accounts.unreconcile_payment = { selection_map = selections.map(function (elem) { return { company: elem.company, - voucher_type: elem.voucher_type, - voucher_no: elem.voucher_no, + voucher_type: elem.reference_doctype, + voucher_no: elem.reference_name, against_voucher_type: frm.doc.doctype, against_voucher_no: frm.doc.name, }; @@ -54,8 +54,8 @@ erpnext.accounts.unreconcile_payment = { company: elem.company, voucher_type: frm.doc.doctype, voucher_no: frm.doc.name, - against_voucher_type: elem.voucher_type, - against_voucher_no: elem.voucher_no, + against_voucher_type: elem.reference_doctype, + against_voucher_no: elem.reference_name, }; }); } @@ -69,7 +69,7 @@ erpnext.accounts.unreconcile_payment = { let child_table_fields = [ { label: __("Voucher Type"), - fieldname: "voucher_type", + fieldname: "reference_doctype", fieldtype: "Link", options: "DocType", in_list_view: 1, @@ -77,9 +77,9 @@ erpnext.accounts.unreconcile_payment = { }, { label: __("Voucher No"), - fieldname: "voucher_no", + fieldname: "reference_name", fieldtype: "Dynamic Link", - options: "voucher_type", + options: "reference_doctype", in_list_view: 1, read_only: 1, }, From ea4c75a74f24cd86d5d87f9bd5fb3308a7a7565f Mon Sep 17 00:00:00 2001 From: ljain112 Date: Tue, 1 Jul 2025 22:56:19 +0530 Subject: [PATCH 02/35] chore: fix test case for expected gl entry in advance adjustment --- erpnext/accounts/doctype/payment_entry/test_payment_entry.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py index ff653ec89713..355aa46ea05d 100644 --- a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py @@ -52,7 +52,7 @@ def test_payment_entry_against_order(self): self.assertEqual(pe.paid_to_account_type, "Cash") expected_gle = dict( - (d[0], d) for d in [["Debtors - _TC", 0, 1000, so.name], ["_Test Cash - _TC", 1000.0, 0, None]] + (d[0], d) for d in [["Debtors - _TC", 0, 1000, pe.name], ["_Test Cash - _TC", 1000.0, 0, None]] ) self.validate_gl_entries(pe.name, expected_gle) @@ -84,7 +84,7 @@ def test_payment_against_sales_order_usd_to_inr(self): expected_gle = dict( (d[0], d) - for d in [["_Test Receivable USD - _TC", 0, 5500, so.name], [pe.paid_to, 5500.0, 0, None]] + for d in [["_Test Receivable USD - _TC", 0, 5500, pe.name], [pe.paid_to, 5500.0, 0, None]] ) self.validate_gl_entries(pe.name, expected_gle) From 0b0f4b024f6b31c22c2492e17666c2a47c61f844 Mon Sep 17 00:00:00 2001 From: ljain112 Date: Wed, 2 Jul 2025 23:18:44 +0530 Subject: [PATCH 03/35] chore: removed redundant code --- .../doctype/payment_entry/payment_entry.py | 18 +-- erpnext/accounts/utils.py | 127 ++++++++---------- 2 files changed, 59 insertions(+), 86 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 1f9c29b0754a..9d66dcff53dd 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -46,6 +46,7 @@ from erpnext.accounts.utils import ( cancel_exchange_gain_loss_journal, get_account_currency, + get_advance_reconciliation_date, get_outstanding_invoices, ) from erpnext.controllers.accounts_controller import ( @@ -1559,23 +1560,10 @@ def add_advance_gl_for_reference(self, gl_entries, invoice): else: # For backwards compatibility # Supporting reposting on payment entries reconciled before select field introduction - reconciliation_takes_effect_on = frappe.get_cached_value( - "Company", self.company, "reconciliation_takes_effect_on" + posting_date = get_advance_reconciliation_date( + self, invoice.reference_doctype, invoice.reference_name ) - if reconciliation_takes_effect_on == "Advance Payment Date": - posting_date = self.posting_date - elif reconciliation_takes_effect_on == "Oldest Of Invoice Or Advance": - date_field = "posting_date" - if invoice.reference_doctype in ["Sales Order", "Purchase Order"]: - date_field = "transaction_date" - posting_date = frappe.db.get_value( - invoice.reference_doctype, invoice.reference_name, date_field - ) - if getdate(posting_date) < getdate(self.posting_date): - posting_date = self.posting_date - elif reconciliation_takes_effect_on == "Reconciliation Date": - posting_date = nowdate() frappe.db.set_value("Payment Entry Reference", invoice.name, "reconcile_effect_on", posting_date) dr_or_cr, account = self.get_dr_and_account_for_advances(invoice) diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index f072f2afaa2e..1170c46a7750 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -2,6 +2,7 @@ # License: GNU General Public License v3. See license.txt +from collections import defaultdict from json import loads from typing import TYPE_CHECKING, Optional @@ -471,43 +472,44 @@ def reconcile_against_document( Cancel PE or JV, Update against document, split if required and resubmit """ # To optimize making GL Entry for PE or JV with multiple references - reconciled_entries = {} - for row in args: - if not reconciled_entries.get((row.voucher_type, row.voucher_no)): - reconciled_entries[(row.voucher_type, row.voucher_no)] = [] + reconciled_entries = defaultdict(list) + docs_to_update = set() + for row in args: reconciled_entries[(row.voucher_type, row.voucher_no)].append(row) for key, entries in reconciled_entries.items(): - voucher_type = key[0] - voucher_no = key[1] + voucher_type, voucher_no = key - # cancel advance entry doc = frappe.get_doc(voucher_type, voucher_no) frappe.flags.ignore_party_validation = True - if not (voucher_type == "Payment Entry" and doc.book_advance_payments_in_separate_party_account): - _delete_pl_entries(voucher_type, voucher_no) - reposting_rows = [] for entry in entries: + # ensure correct sequence of args + docs_to_update.add( + ( + entry.against_voucher_type, + entry.against_voucher, + entry.account, + entry.party_type, + entry.party, + ) + ) check_if_advance_entry_modified(entry) validate_allocated_amount(entry) dimensions_dict = _build_dimensions_dict_for_exc_gain_loss(entry, active_dimensions) - # update ref in advance entry if voucher_type == "Journal Entry": - referenced_row, update_advance_paid = update_reference_in_journal_entry( - entry, doc, do_not_save=False - ) + referenced_row = update_reference_in_journal_entry(entry, doc, do_not_save=False) # advance section in sales/purchase invoice and reconciliation tool,both pass on exchange gain/loss # amount and account in args # referenced_row is used to deduplicate gain/loss journal - entry.update({"referenced_row": referenced_row}) + entry.update({"referenced_row": referenced_row.name}) doc.make_exchange_gain_loss_journal([entry], dimensions_dict) else: - referenced_row, update_advance_paid = update_reference_in_payment_entry( + referenced_row = update_reference_in_payment_entry( entry, doc, do_not_save=True, @@ -517,13 +519,12 @@ def reconcile_against_document( reposting_rows.append(referenced_row) doc.save(ignore_permissions=True) - # re-submit advance entry - doc = frappe.get_doc(entry.voucher_type, entry.voucher_no) if voucher_type == "Payment Entry" and doc.book_advance_payments_in_separate_party_account: for row in reposting_rows: doc.make_advance_gl_entries(entry=row) else: + _delete_pl_entries(voucher_type, voucher_no) gl_map = doc.build_gl_map() # Make sure there is no overallocation from erpnext.accounts.general_ledger import process_debit_credit_difference @@ -531,22 +532,12 @@ def reconcile_against_document( process_debit_credit_difference(gl_map) create_payment_ledger_entry(gl_map, update_outstanding="No", cancel=0, adv_adj=1) - # Only update outstanding for newly linked vouchers - for entry in entries: - update_voucher_outstanding( - entry.against_voucher_type, - entry.against_voucher, - entry.account, - entry.party_type, - entry.party, - ) - # update advance paid in Advance Receivable/Payable doctypes - if update_advance_paid: - for t, n in update_advance_paid: - frappe.get_lazy_doc(t, n).set_total_advance_paid() - frappe.flags.ignore_party_validation = False + # Only update outstanding for newly linked vouchers + for entry in docs_to_update: + update_voucher_outstanding(*entry) + def check_if_advance_entry_modified(args): """ @@ -630,14 +621,6 @@ def update_reference_in_journal_entry(d, journal_entry, do_not_save=False): """ jv_detail = journal_entry.get("accounts", {"name": d["voucher_detail_no"]})[0] - # Update Advance Paid in SO/PO since they might be getting unlinked - update_advance_paid = [] - advance_payment_doctypes = frappe.get_hooks("advance_payment_receivable_doctypes") + frappe.get_hooks( - "advance_payment_payable_doctypes" - ) - if jv_detail.get("reference_type") in advance_payment_doctypes: - update_advance_paid.append((jv_detail.reference_type, jv_detail.reference_name)) - rev_dr_or_cr = ( "debit_in_account_currency" if d["dr_or_cr"] == "credit_in_account_currency" @@ -697,7 +680,7 @@ def update_reference_in_journal_entry(d, journal_entry, do_not_save=False): if not do_not_save: journal_entry.save(ignore_permissions=True) - return new_row.name, update_advance_paid + return new_row def update_reference_in_payment_entry( @@ -716,38 +699,17 @@ def update_reference_in_payment_entry( "account": d.account, "dimensions": d.dimensions, } - update_advance_paid = [] # Update Reconciliation effect date in reference - reconciliation_takes_effect_on = frappe.get_cached_value( - "Company", payment_entry.company, "reconciliation_takes_effect_on" - ) if payment_entry.book_advance_payments_in_separate_party_account: - if reconciliation_takes_effect_on == "Advance Payment Date": - reconcile_on = payment_entry.posting_date - elif reconciliation_takes_effect_on == "Oldest Of Invoice Or Advance": - date_field = "posting_date" - if d.against_voucher_type in ["Sales Order", "Purchase Order"]: - date_field = "transaction_date" - reconcile_on = frappe.db.get_value(d.against_voucher_type, d.against_voucher, date_field) - - if getdate(reconcile_on) < getdate(payment_entry.posting_date): - reconcile_on = payment_entry.posting_date - elif reconciliation_takes_effect_on == "Reconciliation Date": - reconcile_on = nowdate() - + reconcile_on = get_advance_reconciliation_date( + payment_entry, d.against_voucher_type, d.against_voucher + ) reference_details.update({"reconcile_effect_on": reconcile_on}) if d.voucher_detail_no: existing_row = payment_entry.get("references", {"name": d["voucher_detail_no"]})[0] - # Update Advance Paid in SO/PO since they are getting unlinked - advance_payment_doctypes = frappe.get_hooks("advance_payment_receivable_doctypes") + frappe.get_hooks( - "advance_payment_payable_doctypes" - ) - if existing_row.get("reference_doctype") in advance_payment_doctypes: - update_advance_paid.append((existing_row.reference_doctype, existing_row.reference_name)) - if d.allocated_amount <= existing_row.allocated_amount: existing_row.allocated_amount -= d.allocated_amount @@ -792,7 +754,30 @@ def update_reference_in_payment_entry( payment_entry.flags.ignore_reposting_on_reconciliation = True if not do_not_save: payment_entry.save(ignore_permissions=True) - return row, update_advance_paid + + return row + + +def get_advance_reconciliation_date(doc, against_voucher_type, against_voucher): + reconciliation_takes_effect_on = frappe.get_cached_value( + "Company", doc.company, "reconciliation_takes_effect_on" + ) + posting_date = doc.posting_date + reconcile_on = posting_date + + if reconciliation_takes_effect_on == "Advance Payment Date": + reconcile_on = posting_date + elif reconciliation_takes_effect_on == "Oldest Of Invoice Or Advance": + date_field = "posting_date" + if against_voucher_type in ["Sales Order", "Purchase Order"]: + date_field = "transaction_date" + reconcile_on = frappe.db.get_value(against_voucher_type, against_voucher, date_field) + if getdate(reconcile_on) < getdate(posting_date): + reconcile_on = posting_date + elif reconciliation_takes_effect_on == "Reconciliation Date": + reconcile_on = nowdate() + + return reconcile_on def cancel_exchange_gain_loss_journal( @@ -1042,12 +1027,12 @@ def remove_ref_doc_link_from_pe( pe_doc.set_amounts() # Call cancel on only removed reference - for refrences in pe_doc.references: - if refrences.reference_doctype == ref_type and refrences.reference_name == ref_no: - if refrences.reference_doctype in advance_payment_doctypes: - pe_doc.mark_advance_payment_ledger_as_delinked(refrences) + for reference in pe_doc.references: + if reference.reference_doctype == ref_type and reference.reference_name == ref_no: + if reference.reference_doctype in advance_payment_doctypes: + pe_doc.mark_advance_payment_ledger_as_delinked(reference) else: - pe_doc.make_advance_gl_entries(refrences, cancel=1) + pe_doc.make_advance_gl_entries(reference, cancel=1) pe_doc.clear_unallocated_reference_document_rows() pe_doc.validate_payment_type_with_outstanding() From d4cc07a0644eb117f08ad8604d0b02bc83a8dcdd Mon Sep 17 00:00:00 2001 From: ljain112 Date: Thu, 3 Jul 2025 23:20:55 +0530 Subject: [PATCH 04/35] fix: reposting for advance payment ledger entry --- .../advance_payment_ledger_entry.py | 12 +- .../doctype/journal_entry/journal_entry.py | 18 --- .../doctype/payment_entry/payment_entry.py | 77 +++------- .../payment_entry/test_payment_entry.py | 4 +- .../repost_payment_ledger.py | 3 +- .../unreconcile_payment.py | 30 ++-- erpnext/accounts/utils.py | 143 +++++++++++++----- erpnext/controllers/accounts_controller.py | 15 +- 8 files changed, 163 insertions(+), 139 deletions(-) diff --git a/erpnext/accounts/doctype/advance_payment_ledger_entry/advance_payment_ledger_entry.py b/erpnext/accounts/doctype/advance_payment_ledger_entry/advance_payment_ledger_entry.py index a952af9aad4b..a9392fc9367f 100644 --- a/erpnext/accounts/doctype/advance_payment_ledger_entry/advance_payment_ledger_entry.py +++ b/erpnext/accounts/doctype/advance_payment_ledger_entry/advance_payment_ledger_entry.py @@ -1,9 +1,11 @@ # Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -# import frappe +import frappe from frappe.model.document import Document +from erpnext.accounts.utils import update_voucher_outstanding + class AdvancePaymentLedgerEntry(Document): # begin: auto-generated types @@ -25,4 +27,10 @@ class AdvancePaymentLedgerEntry(Document): voucher_type: DF.Link | None # end: auto-generated types - pass + def on_update(self): + if ( + self.against_voucher_type in ["Purchase Order", "Sales Order"] + and self.flags.update_outstanding == "Yes" + and not frappe.flags.is_reverse_depr_entry + ): + update_voucher_outstanding(self.against_voucher_type, self.against_voucher_no, None, None, None) diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py index ddf6c70d8389..4fcb627ccd94 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py @@ -195,8 +195,6 @@ def on_submit(self): self.validate_cheque_info() self.check_credit_limit() self.make_gl_entries() - self.make_advance_payment_ledger_entries() - self.update_advance_paid() self.update_asset_value() self.update_inter_company_jv() self.update_invoice_discounting() @@ -298,8 +296,6 @@ def on_cancel(self): "Advance Payment Ledger Entry", ) self.make_gl_entries(1) - self.make_advance_payment_ledger_entries() - self.update_advance_paid() self.unlink_advance_entry_reference() self.unlink_asset_reference() self.unlink_inter_company_jv() @@ -309,20 +305,6 @@ def on_cancel(self): def get_title(self): return self.pay_to_recd_from or self.accounts[0].account - def update_advance_paid(self): - advance_paid = frappe._dict() - advance_payment_doctypes = frappe.get_hooks("advance_payment_receivable_doctypes") + frappe.get_hooks( - "advance_payment_payable_doctypes" - ) - for d in self.get("accounts"): - if d.is_advance: - if d.reference_type in advance_payment_doctypes: - advance_paid.setdefault(d.reference_type, []).append(d.reference_name) - - for voucher_type, order_list in advance_paid.items(): - for voucher_no in list(set(order_list)): - frappe.get_doc(voucher_type, voucher_no).set_total_advance_paid() - def validate_inter_company_accounts(self): if self.voucher_type == "Inter Company Journal Entry" and self.inter_company_journal_entry_reference: doc = frappe.db.get_value( diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 9d66dcff53dd..85a83a8cd7e8 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -202,8 +202,6 @@ def on_submit(self): self.update_outstanding_amounts() self.update_payment_schedule() self.update_payment_requests() - self.make_advance_payment_ledger_entries() - self.update_advance_paid() # advance_paid_status depends on the payment request amount self.set_status() def validate_for_repost(self): @@ -308,8 +306,6 @@ def on_cancel(self): self.delink_advance_entry_references() self.update_payment_schedule(cancel=1) self.update_payment_requests(cancel=True) - self.make_advance_payment_ledger_entries() - self.update_advance_paid() # advance_paid_status depends on the payment request amount self.set_status() def update_payment_requests(self, cancel=False): @@ -1103,36 +1099,23 @@ def calculate_base_allocated_amount_for_reference(self, d) -> float: advance_payment_doctypes = frappe.get_hooks("advance_payment_receivable_doctypes") + frappe.get_hooks( "advance_payment_payable_doctypes" ) - if d.reference_doctype in advance_payment_doctypes: - # When referencing Sales/Purchase Order, use the source/target exchange rate depending on payment type. - # This is so there are no Exchange Gain/Loss generated for such doctypes - exchange_rate = 1 - if self.payment_type == "Receive": - exchange_rate = self.source_exchange_rate - elif self.payment_type == "Pay": - exchange_rate = self.target_exchange_rate - - base_allocated_amount += flt( - flt(d.allocated_amount) * flt(exchange_rate), self.precision("base_paid_amount") - ) - else: - # Use source/target exchange rate, so no difference amount is calculated. - # then update exchange gain/loss amount in reference table - # if there is an exchange gain/loss amount in reference table, submit a JE for that + exchange_rate = 1 + if self.payment_type == "Receive": + exchange_rate = self.source_exchange_rate + elif self.payment_type == "Pay": + exchange_rate = self.target_exchange_rate - exchange_rate = 1 - if self.payment_type == "Receive": - exchange_rate = self.source_exchange_rate - elif self.payment_type == "Pay": - exchange_rate = self.target_exchange_rate + base_allocated_amount += flt( + flt(d.allocated_amount) * flt(exchange_rate), self.precision("base_paid_amount") + ) - base_allocated_amount += flt( - flt(d.allocated_amount) * flt(exchange_rate), self.precision("base_paid_amount") - ) + # When referencing Sales/Purchase Order, use the source/target exchange rate depending on payment type. + # on rare case, when `exchange_rate` is unset, gain/loss amount is incorrectly calculated + # for base currency transactions - # on rare case, when `exchange_rate` is unset, gain/loss amount is incorrectly calculated - # for base currency transactions + # This is so there are no Exchange Gain/Loss generated for such doctypes + if d.reference_doctype not in advance_payment_doctypes: if d.exchange_rate is None: d.exchange_rate = 1 @@ -1140,6 +1123,7 @@ def calculate_base_allocated_amount_for_reference(self, d) -> float: flt(d.allocated_amount) * flt(d.exchange_rate), self.precision("base_paid_amount") ) d.exchange_gain_loss = base_allocated_amount - allocated_amount_in_ref_exchange_rate + return base_allocated_amount def set_total_allocated_amount(self): @@ -1391,9 +1375,6 @@ def add_party_gl_entries(self, gl_entries): against_account = self.paid_from party_account_type = frappe.db.get_value("Party Type", self.party_type, "account_type") - advance_payment_doctypes = frappe.get_hooks("advance_payment_receivable_doctypes") + frappe.get_hooks( - "advance_payment_payable_doctypes" - ) party_gl_dict = self.get_gl_dict( { @@ -1419,7 +1400,7 @@ def add_party_gl_entries(self, gl_entries): allocated_amount_in_company_currency = self.calculate_base_allocated_amount_for_reference(d) if ( - d.reference_doctype in ["Sales Invoice", "Purchase Invoice"] + d.reference_doctype in ["Sales Invoice", "Purchase Invoice", "Sales Order", "Purchase Order"] and d.allocated_amount < 0 and ( (party_account_type == "Receivable" and self.payment_type == "Pay") @@ -1448,15 +1429,12 @@ def add_party_gl_entries(self, gl_entries): ) ) - if d.reference_doctype in advance_payment_doctypes: - 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, - } - ) + gle.update( + { + "against_voucher_type": d.reference_doctype, + "against_voucher": d.reference_name, + } + ) gl_entries.append(gle) @@ -1753,19 +1731,6 @@ def get_value_in_transaction_currency(self, account_currency, gl_dict, field): return flt(gl_dict.get(field, 0) / (conversion_rate or 1)) - def update_advance_paid(self): - if self.payment_type not in ("Receive", "Pay") or not self.party: - return - - advance_payment_doctypes = frappe.get_hooks("advance_payment_receivable_doctypes") + frappe.get_hooks( - "advance_payment_payable_doctypes" - ) - for d in self.get("references"): - if d.allocated_amount and d.reference_doctype in advance_payment_doctypes: - frappe.get_lazy_doc( - d.reference_doctype, d.reference_name, for_update=True - ).set_total_advance_paid() - def on_recurring(self, reference_doc, auto_repeat_doc): self.reference_no = reference_doc.name self.reference_date = nowdate() diff --git a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py index 355aa46ea05d..ff653ec89713 100644 --- a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py @@ -52,7 +52,7 @@ def test_payment_entry_against_order(self): self.assertEqual(pe.paid_to_account_type, "Cash") expected_gle = dict( - (d[0], d) for d in [["Debtors - _TC", 0, 1000, pe.name], ["_Test Cash - _TC", 1000.0, 0, None]] + (d[0], d) for d in [["Debtors - _TC", 0, 1000, so.name], ["_Test Cash - _TC", 1000.0, 0, None]] ) self.validate_gl_entries(pe.name, expected_gle) @@ -84,7 +84,7 @@ def test_payment_against_sales_order_usd_to_inr(self): expected_gle = dict( (d[0], d) - for d in [["_Test Receivable USD - _TC", 0, 5500, pe.name], [pe.paid_to, 5500.0, 0, None]] + for d in [["_Test Receivable USD - _TC", 0, 5500, so.name], [pe.paid_to, 5500.0, 0, None]] ) self.validate_gl_entries(pe.name, expected_gle) diff --git a/erpnext/accounts/doctype/repost_payment_ledger/repost_payment_ledger.py b/erpnext/accounts/doctype/repost_payment_ledger/repost_payment_ledger.py index 6b90300a8991..6fd1b0f2bf2e 100644 --- a/erpnext/accounts/doctype/repost_payment_ledger/repost_payment_ledger.py +++ b/erpnext/accounts/doctype/repost_payment_ledger/repost_payment_ledger.py @@ -8,7 +8,7 @@ from frappe.model.document import Document from frappe.query_builder.custom import ConstantColumn -from erpnext.accounts.utils import _delete_pl_entries, create_payment_ledger_entry +from erpnext.accounts.utils import _delete_adv_pl_entries, _delete_pl_entries, create_payment_ledger_entry VOUCHER_TYPES = ["Sales Invoice", "Purchase Invoice", "Payment Entry", "Journal Entry"] @@ -16,6 +16,7 @@ def repost_ple_for_voucher(voucher_type, voucher_no, gle_map=None): if voucher_type and voucher_no and gle_map: _delete_pl_entries(voucher_type, voucher_no) + _delete_adv_pl_entries(voucher_type, voucher_no) create_payment_ledger_entry(gle_map, cancel=0) diff --git a/erpnext/accounts/doctype/unreconcile_payment/unreconcile_payment.py b/erpnext/accounts/doctype/unreconcile_payment/unreconcile_payment.py index 396f8e98ea4d..366cbad90c08 100644 --- a/erpnext/accounts/doctype/unreconcile_payment/unreconcile_payment.py +++ b/erpnext/accounts/doctype/unreconcile_payment/unreconcile_payment.py @@ -58,26 +58,19 @@ def add_references(self): def on_submit(self): # todo: more granular unreconciliation - advance_payment_doctypes = frappe.get_hooks("advance_payment_payable_doctypes") + frappe.get_hooks( - "advance_payment_receivable_doctypes" - ) - for alloc in self.allocations: doc = frappe.get_doc(alloc.reference_doctype, alloc.reference_name) unlink_ref_doc_from_payment_entries(doc, self.voucher_no) cancel_exchange_gain_loss_journal(doc, self.voucher_type, self.voucher_no) # update outstanding amounts - if doc.doctype in advance_payment_doctypes: - doc.set_total_advance_paid() - else: - update_voucher_outstanding( - alloc.reference_doctype, - alloc.reference_name, - alloc.account, - alloc.party_type, - alloc.party, - ) + update_voucher_outstanding( + alloc.reference_doctype, + alloc.reference_name, + alloc.account, + alloc.party_type, + alloc.party, + ) frappe.db.set_value("Unreconcile Payment Entries", alloc.name, "unlinked", True) @@ -96,11 +89,10 @@ def doc_has_references(doctype: str | None = None, docname: str | None = None): filters={"delinked": 0, "voucher_no": docname, "against_voucher_no": ["!=", docname]}, ) - if doctype == "Payment Entry": - count += frappe.db.count( - "Advance Payment Ledger Entry", - filters={"delinked": 0, "voucher_no": docname, "voucher_type": doctype}, - ) + count += frappe.db.count( + "Advance Payment Ledger Entry", + filters={"delinked": 0, "voucher_no": docname, "voucher_type": doctype}, + ) return count diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index 1170c46a7750..83489681b9ed 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -932,6 +932,22 @@ def update_accounting_ledgers_after_reference_removal( ple_update_query = ple_update_query.where(ple.voucher_no == payment_name) ple_update_query.run() + # Advance Payment + adv = qb.DocType("Advance Payment Ledger Entry") + adv_ple_update_query = ( + qb.update(adv) + .set(adv.delinked, 1) + .set(adv.modified, now()) + .set(adv.modified_by, frappe.session.user) + .where( + (adv.against_voucher_type == ref_type) & (adv.against_voucher_no == ref_no) & (adv.delinked == 0) + ) + ) + + if payment_name: + adv_ple_update_query = adv_ple_update_query.where(adv.voucher_no == payment_name) + adv_ple_update_query.run() + def remove_ref_from_advance_section(ref_doc: object = None): # TODO: this might need some testing @@ -1004,9 +1020,6 @@ def remove_ref_doc_link_from_pe( # remove reference only from specified payment linked_pe = [x for x in linked_pe if x == payment_name] if payment_name else linked_pe - advance_payment_doctypes = (frappe.get_hooks("advance_payment_receivable_doctypes") or []) + ( - frappe.get_hooks("advance_payment_payable_doctypes") or [] - ) if linked_pe: update_query = ( qb.update(per) @@ -1029,10 +1042,7 @@ def remove_ref_doc_link_from_pe( # Call cancel on only removed reference for reference in pe_doc.references: if reference.reference_doctype == ref_type and reference.reference_name == ref_no: - if reference.reference_doctype in advance_payment_doctypes: - pe_doc.mark_advance_payment_ledger_as_delinked(reference) - else: - pe_doc.make_advance_gl_entries(reference, cancel=1) + pe_doc.make_advance_gl_entries(reference, cancel=1) pe_doc.clear_unallocated_reference_document_rows() pe_doc.validate_payment_type_with_outstanding() @@ -1473,6 +1483,11 @@ def _delete_pl_entries(voucher_type, voucher_no): qb.from_(ple).delete().where((ple.voucher_type == voucher_type) & (ple.voucher_no == voucher_no)).run() +def _delete_adv_pl_entries(voucher_type, voucher_no): + adv = qb.DocType("Advance Payment Ledger Entry") + qb.from_(adv).delete().where((adv.voucher_type == voucher_type) & (adv.voucher_no == voucher_no)).run() + + def _delete_gl_entries(voucher_type, voucher_no): gle = qb.DocType("GL Entry") qb.from_(gle).delete().where((gle.voucher_type == voucher_type) & (gle.voucher_no == voucher_no)).run() @@ -1778,6 +1793,9 @@ def get_account_type(account): dr_or_cr = 0 account_type = None + advance_payment_doctypes = frappe.get_hooks("advance_payment_receivable_doctypes") + frappe.get_hooks( + "advance_payment_payable_doctypes" + ) for gle in gl_entries: if gle.account in receivable_or_payable_accounts: account_type = get_account_type(gle.account) @@ -1792,6 +1810,17 @@ def get_account_type(account): dr_or_cr *= -1 dr_or_cr_account_currency *= -1 + is_advance_doctype = gle.against_voucher_type in advance_payment_doctypes + + against_voucher_type = ( + gle.against_voucher_type if gle.against_voucher_type else gle.voucher_type + ) + against_voucher_no = gle.against_voucher if gle.against_voucher else gle.voucher_no + + if is_advance_doctype: + against_voucher_type = gle.voucher_type + against_voucher_no = gle.voucher_no + ple = frappe._dict( doctype="Payment Ledger Entry", posting_date=gle.posting_date, @@ -1806,14 +1835,12 @@ def get_account_type(account): voucher_type=gle.voucher_type, voucher_no=gle.voucher_no, voucher_detail_no=gle.voucher_detail_no, - against_voucher_type=gle.against_voucher_type - if gle.against_voucher_type - else gle.voucher_type, - against_voucher_no=gle.against_voucher if gle.against_voucher else gle.voucher_no, + against_voucher_type=against_voucher_type, + against_voucher_no=against_voucher_no, account_currency=gle.account_currency, amount=dr_or_cr, amount_in_account_currency=dr_or_cr_account_currency, - delinked=True if cancel else False, + delinked=cancel, remarks=gle.remarks, ) @@ -1822,7 +1849,27 @@ def get_account_type(account): for dimension in dimensions_and_defaults[0]: ple[dimension.fieldname] = gle.get(dimension.fieldname) + if is_advance_doctype: + # create advance entry + adv = frappe._dict( + doctype="Advance Payment Ledger Entry", + company=gle.company, + voucher_type=gle.voucher_type, + voucher_no=gle.voucher_no, + voucher_detail_no=gle.voucher_detail_no, + against_voucher_type=gle.against_voucher_type, + against_voucher_no=gle.against_voucher, + currency=gle.account_currency, + amount=dr_or_cr_account_currency, + event="Cancel" if cancel else "Submit", + delinked=cancel, + remarks=gle.remarks, + ) + + ple_map.append(adv) + ple_map.append(ple) + return ple_map @@ -1846,6 +1893,17 @@ def create_payment_ledger_entry( def update_voucher_outstanding(voucher_type, voucher_no, account, party_type, party): + if not voucher_type or not voucher_no: + return + + if voucher_type in ["Purchase Order", "Sales Order"]: + ref_doc = frappe.get_lazy_doc(voucher_type, voucher_no) + ref_doc.set_total_advance_paid() + return + + if not (voucher_type in ["Sales Invoice", "Purchase Invoice", "Fees"] and party_type and party): + return + ple = frappe.qb.DocType("Payment Ledger Entry") vouchers = [frappe._dict({"voucher_type": voucher_type, "voucher_no": voucher_no})] common_filter = [] @@ -1862,33 +1920,50 @@ def update_voucher_outstanding(voucher_type, voucher_no, account, party_type, pa # on cancellation outstanding can be an empty list voucher_outstanding = ple_query.get_voucher_outstandings(vouchers, common_filter=common_filter) - if ( - voucher_type in ["Sales Invoice", "Purchase Invoice", "Fees"] - and party_type - and party - and voucher_outstanding - ): - outstanding = voucher_outstanding[0] - ref_doc = frappe.get_lazy_doc(voucher_type, voucher_no) - outstanding_amount = flt( - outstanding["outstanding_in_account_currency"], ref_doc.precision("outstanding_amount") - ) - # Didn't use db_set for optimisation purpose - ref_doc.outstanding_amount = outstanding_amount - frappe.db.set_value( - voucher_type, - voucher_no, - "outstanding_amount", - outstanding_amount, - ) + if not voucher_outstanding: + return - ref_doc.set_status(update=True) - ref_doc.notify_update() + outstanding = voucher_outstanding[0] + ref_doc = frappe.get_lazy_doc(voucher_type, voucher_no) + outstanding_amount = flt( + outstanding["outstanding_in_account_currency"], ref_doc.precision("outstanding_amount") + ) + + # Didn't use db_set for optimisation purpose + ref_doc.outstanding_amount = outstanding_amount + frappe.db.set_value( + voucher_type, + voucher_no, + "outstanding_amount", + outstanding_amount, + ) + + ref_doc.set_status(update=True) + ref_doc.notify_update() def delink_original_entry(pl_entry, partial_cancel=False): - if pl_entry: + if not pl_entry: + return + + if pl_entry.doctype == "Advance Payment Ledger Entry": + adv = qb.DocType("Advance Payment Ledger Entry") + + ( + qb.update(adv) + .set(adv.delinked, 1) + .set(adv.event, "Cancel") + .set(adv.modified, now()) + .set(adv.modified_by, frappe.session.user) + .where(adv.voucher_type == pl_entry.voucher_type) + .where(adv.voucher_no == pl_entry.voucher_no) + .where(adv.against_voucher_type == pl_entry.against_voucher_type) + .where(adv.against_voucher_no == pl_entry.against_voucher_no) + .run() + ) + + else: ple = qb.DocType("Payment Ledger Entry") query = ( qb.update(ple) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 02406eec0acb..a0d3d0f1b831 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -8,7 +8,7 @@ import frappe from frappe import _, bold, qb, throw from frappe.model.workflow import get_workflow_name, is_transition_condition_satisfied -from frappe.query_builder import Criterion, DocType +from frappe.query_builder import Case, Criterion, DocType from frappe.query_builder.custom import ConstantColumn from frappe.query_builder.functions import Abs, Sum from frappe.utils import ( @@ -394,7 +394,6 @@ def _remove_advance_payment_ledger_entries(self): def on_trash(self): from erpnext.accounts.utils import delete_exchange_gain_loss_journal - self._remove_advance_payment_ledger_entries() self._remove_references_in_repost_doctypes() self._remove_references_in_unreconcile() self.remove_serial_and_batch_bundle() @@ -423,6 +422,8 @@ def on_trash(self): (sle.voucher_type == self.doctype) & (sle.voucher_no == self.name) ).run() + self._remove_advance_payment_ledger_entries() + def remove_serial_and_batch_bundle(self): bundles = frappe.get_all( "Serial and Batch Bundle", @@ -2203,21 +2204,21 @@ def get_stock_items(self): def calculate_total_advance_from_ledger(self): adv = frappe.qb.DocType("Advance Payment Ledger Entry") - advance = ( + return ( frappe.qb.from_(adv) - .select(adv.currency.as_("account_currency"), Abs(Sum(adv.amount)).as_("amount")) + .select( + Abs(Sum(Case().when(adv.delinked == 0, adv.amount).else_(0))).as_("amount"), + adv.currency.as_("account_currency"), + ) .where( (adv.against_voucher_type == self.doctype) & (adv.against_voucher_no == self.name) & (adv.company == self.company) - & (adv.delinked == 0) ) .groupby(adv.against_voucher_no, adv.against_voucher_type, adv.company) .run(as_dict=True) ) - return advance - def set_total_advance_paid(self): advance = self.calculate_total_advance_from_ledger() advance_paid, order_total = None, None From c7b533381e02e6ce3a134364cc9a5d1d3bfefc97 Mon Sep 17 00:00:00 2001 From: ljain112 Date: Thu, 3 Jul 2025 23:29:18 +0530 Subject: [PATCH 05/35] fix: ignore linked doctypes `Advance Payment Ledger Entry` on cancel --- erpnext/buying/doctype/purchase_order/purchase_order.py | 1 + erpnext/selling/doctype/sales_order/sales_order.py | 1 + 2 files changed, 2 insertions(+) diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.py b/erpnext/buying/doctype/purchase_order/purchase_order.py index 37b2830c084a..f294dd70588e 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.py +++ b/erpnext/buying/doctype/purchase_order/purchase_order.py @@ -512,6 +512,7 @@ def on_cancel(self): self.ignore_linked_doctypes = ( "GL Entry", "Payment Ledger Entry", + "Advance Payment Ledger Entry", "Unreconcile Payment", "Unreconcile Payment Entries", ) diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py index e27dc6910930..910f9e9bc4e3 100755 --- a/erpnext/selling/doctype/sales_order/sales_order.py +++ b/erpnext/selling/doctype/sales_order/sales_order.py @@ -457,6 +457,7 @@ def on_cancel(self): "GL Entry", "Stock Ledger Entry", "Payment Ledger Entry", + "Advance Payment Ledger Entry", "Unreconcile Payment", "Unreconcile Payment Entries", ) From 86d41d4237650070d05486fc6b0634d82387362b Mon Sep 17 00:00:00 2001 From: ljain112 Date: Thu, 3 Jul 2025 23:53:39 +0530 Subject: [PATCH 06/35] fix: update payment request before making gl entries --- erpnext/accounts/doctype/payment_entry/payment_entry.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 85a83a8cd7e8..3eac3c0ac5ec 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -198,10 +198,10 @@ def before_save(self): def on_submit(self): if self.difference_amount: frappe.throw(_("Difference Amount must be zero")) + self.update_payment_requests() # To be called before advance ledger entry creation self.make_gl_entries() self.update_outstanding_amounts() self.update_payment_schedule() - self.update_payment_requests() self.set_status() def validate_for_repost(self): @@ -301,11 +301,11 @@ def on_cancel(self): "Advance Payment Ledger Entry", ) super().on_cancel() + self.update_payment_requests(cancel=True) # To be called before advance ledger entry creation self.make_gl_entries(cancel=1) self.update_outstanding_amounts() self.delink_advance_entry_references() self.update_payment_schedule(cancel=1) - self.update_payment_requests(cancel=True) self.set_status() def update_payment_requests(self, cancel=False): From 27d3e7c1f1132c258097a641d9ddba40d88eee4d Mon Sep 17 00:00:00 2001 From: ljain112 Date: Fri, 4 Jul 2025 00:43:46 +0530 Subject: [PATCH 07/35] fix(test): on unreconcile of order advance paid amount will get updated --- .../doctype/unreconcile_payment/test_unreconcile_payment.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/accounts/doctype/unreconcile_payment/test_unreconcile_payment.py b/erpnext/accounts/doctype/unreconcile_payment/test_unreconcile_payment.py index 3466b2733ac7..875a496cb0e7 100644 --- a/erpnext/accounts/doctype/unreconcile_payment/test_unreconcile_payment.py +++ b/erpnext/accounts/doctype/unreconcile_payment/test_unreconcile_payment.py @@ -364,13 +364,13 @@ def test_05_unreconcile_order(self): # Assert 'Advance Paid' so.reload() pe.reload() - self.assertEqual(so.advance_paid, 100) + self.assertEqual(so.advance_paid, 0) self.assertEqual(len(pe.references), 0) self.assertEqual(pe.unallocated_amount, 100) pe.cancel() so.reload() - self.assertEqual(so.advance_paid, 100) + self.assertEqual(so.advance_paid, 0) def test_06_unreconcile_advance_from_payment_entry(self): self.enable_advance_as_liability() @@ -417,7 +417,7 @@ def test_06_unreconcile_advance_from_payment_entry(self): so2.reload() pe.reload() self.assertEqual(so1.advance_paid, 150) - self.assertEqual(so2.advance_paid, 110) + self.assertEqual(so2.advance_paid, 0) self.assertEqual(len(pe.references), 1) self.assertEqual(pe.unallocated_amount, 110) From 5844892bdd2162fbae3fc1e742552b39909fe873 Mon Sep 17 00:00:00 2001 From: ljain112 Date: Fri, 4 Jul 2025 00:57:52 +0530 Subject: [PATCH 08/35] fix: set advance_payment_status for advance_doctypes --- .../doctype/journal_entry/journal_entry.py | 16 ++++++++++++++ .../doctype/payment_entry/payment_entry.py | 21 ++++++++++++++++--- erpnext/controllers/accounts_controller.py | 2 -- 3 files changed, 34 insertions(+), 5 deletions(-) diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py index 4fcb627ccd94..8bfd30d91749 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py @@ -195,6 +195,7 @@ def on_submit(self): self.validate_cheque_info() self.check_credit_limit() self.make_gl_entries() + self.set_advance_payment_status_for_advance_doctypes() self.update_asset_value() self.update_inter_company_jv() self.update_invoice_discounting() @@ -296,6 +297,7 @@ def on_cancel(self): "Advance Payment Ledger Entry", ) self.make_gl_entries(1) + self.set_advance_payment_status_for_advance_doctypes() self.unlink_advance_entry_reference() self.unlink_asset_reference() self.unlink_inter_company_jv() @@ -305,6 +307,20 @@ def on_cancel(self): def get_title(self): return self.pay_to_recd_from or self.accounts[0].account + def set_advance_payment_status_for_advance_doctypes(self): + advance_paid = frappe._dict() + advance_payment_doctypes = frappe.get_hooks("advance_payment_receivable_doctypes") + frappe.get_hooks( + "advance_payment_payable_doctypes" + ) + for d in self.get("accounts"): + if d.is_advance: + if d.reference_type in advance_payment_doctypes: + advance_paid.setdefault(d.reference_type, []).append(d.reference_name) + + for voucher_type, order_list in advance_paid.items(): + for voucher_no in list(set(order_list)): + frappe.get_doc(voucher_type, voucher_no).set_advance_payment_status() + def validate_inter_company_accounts(self): if self.voucher_type == "Inter Company Journal Entry" and self.inter_company_journal_entry_reference: doc = frappe.db.get_value( diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 3eac3c0ac5ec..f1a87130965a 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -198,10 +198,11 @@ def before_save(self): def on_submit(self): if self.difference_amount: frappe.throw(_("Difference Amount must be zero")) - self.update_payment_requests() # To be called before advance ledger entry creation self.make_gl_entries() self.update_outstanding_amounts() self.update_payment_schedule() + self.update_payment_requests() + self.set_advance_payment_status_for_advance_doctypes() # advance_paid_status depends on the payment request amount self.set_status() def validate_for_repost(self): @@ -301,11 +302,12 @@ def on_cancel(self): "Advance Payment Ledger Entry", ) super().on_cancel() - self.update_payment_requests(cancel=True) # To be called before advance ledger entry creation self.make_gl_entries(cancel=1) self.update_outstanding_amounts() - self.delink_advance_entry_references() self.update_payment_schedule(cancel=1) + self.update_payment_requests(cancel=True) + self.set_advance_payment_status_for_advance_doctypes() + self.delink_advance_entry_references() self.set_status() def update_payment_requests(self, cancel=False): @@ -1731,6 +1733,19 @@ def get_value_in_transaction_currency(self, account_currency, gl_dict, field): return flt(gl_dict.get(field, 0) / (conversion_rate or 1)) + def set_advance_payment_status_for_advance_doctypes(self): + if self.payment_type not in ("Receive", "Pay") or not self.party: + return + + advance_payment_doctypes = frappe.get_hooks("advance_payment_receivable_doctypes") + frappe.get_hooks( + "advance_payment_payable_doctypes" + ) + for d in self.get("references"): + if d.allocated_amount and d.reference_doctype in advance_payment_doctypes: + frappe.get_lazy_doc( + d.reference_doctype, d.reference_name, for_update=True + ).set_advance_payment_status() + def on_recurring(self, reference_doc, auto_repeat_doc): self.reference_no = reference_doc.name self.reference_date = nowdate() diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index a0d3d0f1b831..5aa7edd98c87 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -2256,8 +2256,6 @@ def set_total_advance_paid(self): self.db_set("advance_paid", advance_paid) - self.set_advance_payment_status() - def set_advance_payment_status(self): new_status = None From b35a14c0f325fae17f98a13efed3ca03e4569f9f Mon Sep 17 00:00:00 2001 From: ljain112 Date: Fri, 4 Jul 2025 12:47:55 +0530 Subject: [PATCH 09/35] fix(test): for orders also outstanding is now updated and remove redundant code --- .../doctype/journal_entry/journal_entry.py | 16 ---------------- .../doctype/payment_entry/payment_entry.py | 5 ++--- .../payment_request/test_payment_request.py | 2 +- erpnext/controllers/accounts_controller.py | 16 ---------------- 4 files changed, 3 insertions(+), 36 deletions(-) diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py index 8bfd30d91749..4fcb627ccd94 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py @@ -195,7 +195,6 @@ def on_submit(self): self.validate_cheque_info() self.check_credit_limit() self.make_gl_entries() - self.set_advance_payment_status_for_advance_doctypes() self.update_asset_value() self.update_inter_company_jv() self.update_invoice_discounting() @@ -297,7 +296,6 @@ def on_cancel(self): "Advance Payment Ledger Entry", ) self.make_gl_entries(1) - self.set_advance_payment_status_for_advance_doctypes() self.unlink_advance_entry_reference() self.unlink_asset_reference() self.unlink_inter_company_jv() @@ -307,20 +305,6 @@ def on_cancel(self): def get_title(self): return self.pay_to_recd_from or self.accounts[0].account - def set_advance_payment_status_for_advance_doctypes(self): - advance_paid = frappe._dict() - advance_payment_doctypes = frappe.get_hooks("advance_payment_receivable_doctypes") + frappe.get_hooks( - "advance_payment_payable_doctypes" - ) - for d in self.get("accounts"): - if d.is_advance: - if d.reference_type in advance_payment_doctypes: - advance_paid.setdefault(d.reference_type, []).append(d.reference_name) - - for voucher_type, order_list in advance_paid.items(): - for voucher_no in list(set(order_list)): - frappe.get_doc(voucher_type, voucher_no).set_advance_payment_status() - def validate_inter_company_accounts(self): if self.voucher_type == "Inter Company Journal Entry" and self.inter_company_journal_entry_reference: doc = frappe.db.get_value( diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index f1a87130965a..92d541b327f0 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -202,7 +202,6 @@ def on_submit(self): self.update_outstanding_amounts() self.update_payment_schedule() self.update_payment_requests() - self.set_advance_payment_status_for_advance_doctypes() # advance_paid_status depends on the payment request amount self.set_status() def validate_for_repost(self): @@ -306,7 +305,6 @@ def on_cancel(self): self.update_outstanding_amounts() self.update_payment_schedule(cancel=1) self.update_payment_requests(cancel=True) - self.set_advance_payment_status_for_advance_doctypes() self.delink_advance_entry_references() self.set_status() @@ -316,6 +314,7 @@ def update_payment_requests(self, cancel=False): ) update_payment_requests_as_per_pe_references(self.references, cancel=cancel) + self.set_advance_payment_status_for_advance_doctypes() def update_outstanding_amounts(self): self.set_missing_ref_details(force=True) @@ -1402,7 +1401,7 @@ def add_party_gl_entries(self, gl_entries): allocated_amount_in_company_currency = self.calculate_base_allocated_amount_for_reference(d) if ( - d.reference_doctype in ["Sales Invoice", "Purchase Invoice", "Sales Order", "Purchase Order"] + d.reference_doctype in ["Sales Invoice", "Purchase Invoice"] and d.allocated_amount < 0 and ( (party_account_type == "Receivable" and self.payment_type == "Pay") diff --git a/erpnext/accounts/doctype/payment_request/test_payment_request.py b/erpnext/accounts/doctype/payment_request/test_payment_request.py index cd50a4efc50a..cbec88cdf8fe 100644 --- a/erpnext/accounts/doctype/payment_request/test_payment_request.py +++ b/erpnext/accounts/doctype/payment_request/test_payment_request.py @@ -471,7 +471,7 @@ def test_multiple_payment_if_partially_paid_for_same_currency(self): self.assertEqual(pe.paid_amount, 800) # paid amount set from pr's outstanding amount self.assertEqual(pe.references[0].allocated_amount, 800) - self.assertEqual(pe.references[0].outstanding_amount, 800) # for Orders it is not zero + self.assertEqual(pe.references[0].outstanding_amount, 0) # Also for orders it will zero self.assertEqual(pe.references[0].payment_request, pr.name) so.load_from_db() diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 5aa7edd98c87..9bcfbfdaaf52 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -2983,22 +2983,6 @@ def make_advance_payment_ledger_for_payment(self): doc.flags.ignore_permissions = 1 doc.save() - def mark_advance_payment_ledger_as_delinked(self, references): - if not (references.get("reference_doctype") and references.get("reference_name")): - return - - adv = qb.DocType("Advance Payment Ledger Entry") - - ( - qb.update(adv) - .set(adv.delinked, 1) - .where(adv.voucher_type == self.doctype) - .where(adv.voucher_no == self.name) - .where(adv.against_voucher_type == references.reference_doctype) - .where(adv.against_voucher_no == references.reference_name) - .run() - ) - def make_advance_payment_ledger_entries(self): if self.docstatus != 0: if self.doctype == "Journal Entry": From 90b4ca3c6a2ce30eba506e9118f1ce5a491dc298 Mon Sep 17 00:00:00 2001 From: ljain112 Date: Fri, 4 Jul 2025 13:23:27 +0530 Subject: [PATCH 10/35] chore: revert unnecessary changes --- .../doctype/payment_entry/payment_entry.py | 60 +++++++++++++------ erpnext/accounts/utils.py | 50 +++++++--------- 2 files changed, 64 insertions(+), 46 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 92d541b327f0..ca958695a18a 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -46,7 +46,6 @@ from erpnext.accounts.utils import ( cancel_exchange_gain_loss_journal, get_account_currency, - get_advance_reconciliation_date, get_outstanding_invoices, ) from erpnext.controllers.accounts_controller import ( @@ -303,9 +302,9 @@ def on_cancel(self): super().on_cancel() self.make_gl_entries(cancel=1) self.update_outstanding_amounts() + self.delink_advance_entry_references() self.update_payment_schedule(cancel=1) self.update_payment_requests(cancel=True) - self.delink_advance_entry_references() self.set_status() def update_payment_requests(self, cancel=False): @@ -1100,23 +1099,36 @@ def calculate_base_allocated_amount_for_reference(self, d) -> float: advance_payment_doctypes = frappe.get_hooks("advance_payment_receivable_doctypes") + frappe.get_hooks( "advance_payment_payable_doctypes" ) + if d.reference_doctype in advance_payment_doctypes: + # When referencing Sales/Purchase Order, use the source/target exchange rate depending on payment type. + # This is so there are no Exchange Gain/Loss generated for such doctypes - exchange_rate = 1 - if self.payment_type == "Receive": - exchange_rate = self.source_exchange_rate - elif self.payment_type == "Pay": - exchange_rate = self.target_exchange_rate + exchange_rate = 1 + if self.payment_type == "Receive": + exchange_rate = self.source_exchange_rate + elif self.payment_type == "Pay": + exchange_rate = self.target_exchange_rate - base_allocated_amount += flt( - flt(d.allocated_amount) * flt(exchange_rate), self.precision("base_paid_amount") - ) + base_allocated_amount += flt( + flt(d.allocated_amount) * flt(exchange_rate), self.precision("base_paid_amount") + ) + else: + # Use source/target exchange rate, so no difference amount is calculated. + # then update exchange gain/loss amount in reference table + # if there is an exchange gain/loss amount in reference table, submit a JE for that - # When referencing Sales/Purchase Order, use the source/target exchange rate depending on payment type. - # on rare case, when `exchange_rate` is unset, gain/loss amount is incorrectly calculated - # for base currency transactions + exchange_rate = 1 + if self.payment_type == "Receive": + exchange_rate = self.source_exchange_rate + elif self.payment_type == "Pay": + exchange_rate = self.target_exchange_rate - # This is so there are no Exchange Gain/Loss generated for such doctypes - if d.reference_doctype not in advance_payment_doctypes: + base_allocated_amount += flt( + flt(d.allocated_amount) * flt(exchange_rate), self.precision("base_paid_amount") + ) + + # on rare case, when `exchange_rate` is unset, gain/loss amount is incorrectly calculated + # for base currency transactions if d.exchange_rate is None: d.exchange_rate = 1 @@ -1124,7 +1136,6 @@ def calculate_base_allocated_amount_for_reference(self, d) -> float: flt(d.allocated_amount) * flt(d.exchange_rate), self.precision("base_paid_amount") ) d.exchange_gain_loss = base_allocated_amount - allocated_amount_in_ref_exchange_rate - return base_allocated_amount def set_total_allocated_amount(self): @@ -1539,10 +1550,23 @@ def add_advance_gl_for_reference(self, gl_entries, invoice): else: # For backwards compatibility # Supporting reposting on payment entries reconciled before select field introduction - posting_date = get_advance_reconciliation_date( - self, invoice.reference_doctype, invoice.reference_name + reconciliation_takes_effect_on = frappe.get_cached_value( + "Company", self.company, "reconciliation_takes_effect_on" ) + if reconciliation_takes_effect_on == "Advance Payment Date": + posting_date = self.posting_date + elif reconciliation_takes_effect_on == "Oldest Of Invoice Or Advance": + date_field = "posting_date" + if invoice.reference_doctype in ["Sales Order", "Purchase Order"]: + date_field = "transaction_date" + posting_date = frappe.db.get_value( + invoice.reference_doctype, invoice.reference_name, date_field + ) + if getdate(posting_date) < getdate(self.posting_date): + posting_date = self.posting_date + elif reconciliation_takes_effect_on == "Reconciliation Date": + posting_date = nowdate() frappe.db.set_value("Payment Entry Reference", invoice.name, "reconcile_effect_on", posting_date) dr_or_cr, account = self.get_dr_and_account_for_advances(invoice) diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index 83489681b9ed..22ec4037eac7 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -701,10 +701,23 @@ def update_reference_in_payment_entry( } # Update Reconciliation effect date in reference + reconciliation_takes_effect_on = frappe.get_cached_value( + "Company", payment_entry.company, "reconciliation_takes_effect_on" + ) if payment_entry.book_advance_payments_in_separate_party_account: - reconcile_on = get_advance_reconciliation_date( - payment_entry, d.against_voucher_type, d.against_voucher - ) + if reconciliation_takes_effect_on == "Advance Payment Date": + reconcile_on = payment_entry.posting_date + elif reconciliation_takes_effect_on == "Oldest Of Invoice Or Advance": + date_field = "posting_date" + if d.against_voucher_type in ["Sales Order", "Purchase Order"]: + date_field = "transaction_date" + reconcile_on = frappe.db.get_value(d.against_voucher_type, d.against_voucher, date_field) + + if getdate(reconcile_on) < getdate(payment_entry.posting_date): + reconcile_on = payment_entry.posting_date + elif reconciliation_takes_effect_on == "Reconciliation Date": + reconcile_on = nowdate() + reference_details.update({"reconcile_effect_on": reconcile_on}) if d.voucher_detail_no: @@ -758,28 +771,6 @@ def update_reference_in_payment_entry( return row -def get_advance_reconciliation_date(doc, against_voucher_type, against_voucher): - reconciliation_takes_effect_on = frappe.get_cached_value( - "Company", doc.company, "reconciliation_takes_effect_on" - ) - posting_date = doc.posting_date - reconcile_on = posting_date - - if reconciliation_takes_effect_on == "Advance Payment Date": - reconcile_on = posting_date - elif reconciliation_takes_effect_on == "Oldest Of Invoice Or Advance": - date_field = "posting_date" - if against_voucher_type in ["Sales Order", "Purchase Order"]: - date_field = "transaction_date" - reconcile_on = frappe.db.get_value(against_voucher_type, against_voucher, date_field) - if getdate(reconcile_on) < getdate(posting_date): - reconcile_on = posting_date - elif reconciliation_takes_effect_on == "Reconciliation Date": - reconcile_on = nowdate() - - return reconcile_on - - def cancel_exchange_gain_loss_journal( parent_doc: dict | object, referenced_dt: str | None = None, referenced_dn: str | None = None ) -> None: @@ -1040,9 +1031,12 @@ def remove_ref_doc_link_from_pe( pe_doc.set_amounts() # Call cancel on only removed reference - for reference in pe_doc.references: - if reference.reference_doctype == ref_type and reference.reference_name == ref_no: - pe_doc.make_advance_gl_entries(reference, cancel=1) + references = [ + x + for x in pe_doc.references + if x.reference_doctype == ref_type and x.reference_name == ref_no + ] + [pe_doc.make_advance_gl_entries(x, cancel=1) for x in references] pe_doc.clear_unallocated_reference_document_rows() pe_doc.validate_payment_type_with_outstanding() From cd43b8256c89298f285d1d117cf667c3795850da Mon Sep 17 00:00:00 2001 From: ljain112 Date: Fri, 4 Jul 2025 16:55:03 +0530 Subject: [PATCH 11/35] fix: delete linked Advance payment ledger entries on reposting --- .../repost_accounting_ledger/repost_accounting_ledger.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.py b/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.py index 800f16474711..aec26b280c4f 100644 --- a/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.py +++ b/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.py @@ -169,6 +169,10 @@ def start_repost(account_repost_doc=str) -> None: frappe.db.delete( "Payment Ledger Entry", filters={"voucher_type": doc.doctype, "voucher_no": doc.name} ) + frappe.db.delete( + "Advance Payment Ledger Entry", + filters={"voucher_type": doc.doctype, "voucher_no": doc.name}, + ) if doc.doctype in ["Sales Invoice", "Purchase Invoice"]: if not repost_doc.delete_cancelled_entries: From c57339929588bb03ed0b6139be1b12cf189ac0f8 Mon Sep 17 00:00:00 2001 From: ljain112 Date: Fri, 4 Jul 2025 17:11:40 +0530 Subject: [PATCH 12/35] fix: update payment request before creating gl entries --- .../doctype/payment_entry/payment_entry.py | 18 ++---------------- erpnext/controllers/accounts_controller.py | 2 ++ 2 files changed, 4 insertions(+), 16 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index ca958695a18a..fb1595a90c81 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -197,10 +197,10 @@ def before_save(self): def on_submit(self): if self.difference_amount: frappe.throw(_("Difference Amount must be zero")) + self.update_payment_requests() self.make_gl_entries() self.update_outstanding_amounts() self.update_payment_schedule() - self.update_payment_requests() self.set_status() def validate_for_repost(self): @@ -300,11 +300,11 @@ def on_cancel(self): "Advance Payment Ledger Entry", ) super().on_cancel() + self.update_payment_requests(cancel=True) self.make_gl_entries(cancel=1) self.update_outstanding_amounts() self.delink_advance_entry_references() self.update_payment_schedule(cancel=1) - self.update_payment_requests(cancel=True) self.set_status() def update_payment_requests(self, cancel=False): @@ -313,7 +313,6 @@ def update_payment_requests(self, cancel=False): ) update_payment_requests_as_per_pe_references(self.references, cancel=cancel) - self.set_advance_payment_status_for_advance_doctypes() def update_outstanding_amounts(self): self.set_missing_ref_details(force=True) @@ -1756,19 +1755,6 @@ def get_value_in_transaction_currency(self, account_currency, gl_dict, field): return flt(gl_dict.get(field, 0) / (conversion_rate or 1)) - def set_advance_payment_status_for_advance_doctypes(self): - if self.payment_type not in ("Receive", "Pay") or not self.party: - return - - advance_payment_doctypes = frappe.get_hooks("advance_payment_receivable_doctypes") + frappe.get_hooks( - "advance_payment_payable_doctypes" - ) - for d in self.get("references"): - if d.allocated_amount and d.reference_doctype in advance_payment_doctypes: - frappe.get_lazy_doc( - d.reference_doctype, d.reference_name, for_update=True - ).set_advance_payment_status() - def on_recurring(self, reference_doc, auto_repeat_doc): self.reference_no = reference_doc.name self.reference_date = nowdate() diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 9bcfbfdaaf52..f73193fd5395 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -2256,6 +2256,8 @@ def set_total_advance_paid(self): self.db_set("advance_paid", advance_paid) + self.set_advance_payment_status() + def set_advance_payment_status(self): new_status = None From 3b2b8713904af2806fd613e64b5c0da9a433b66c Mon Sep 17 00:00:00 2001 From: ljain112 Date: Fri, 4 Jul 2025 19:04:17 +0530 Subject: [PATCH 13/35] fix: patch to create Advance Payment Ledger Entry --- erpnext/controllers/accounts_controller.py | 58 ------ erpnext/patches.txt | 2 +- .../create_advance_payment_ledger_records.py | 166 ++++++++++++------ 3 files changed, 110 insertions(+), 116 deletions(-) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index f73193fd5395..352d93509fa1 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -2934,64 +2934,6 @@ def get_advance_payment_doctypes(self) -> list: "advance_payment_payable_doctypes" ) - def make_advance_payment_ledger_for_journal(self): - advance_payment_doctypes = self.get_advance_payment_doctypes() - advance_doctype_references = [ - x for x in self.accounts if x.reference_type in advance_payment_doctypes - ] - - for x in advance_doctype_references: - # Looking for payments - dr_or_cr = ( - "credit_in_account_currency" - if x.account_type == "Receivable" - else "debit_in_account_currency" - ) - - amount = x.get(dr_or_cr) - if amount > 0: - doc = frappe.new_doc("Advance Payment Ledger Entry") - doc.company = self.company - doc.voucher_type = self.doctype - doc.voucher_no = self.name - doc.against_voucher_type = x.reference_type - doc.against_voucher_no = x.reference_name - doc.amount = amount if self.docstatus == 1 else -1 * amount - doc.event = "Submit" if self.docstatus == 1 else "Cancel" - doc.currency = x.account_currency - doc.flags.ignore_permissions = 1 - doc.save() - - def make_advance_payment_ledger_for_payment(self): - advance_payment_doctypes = self.get_advance_payment_doctypes() - advance_doctype_references = [ - x for x in self.references if x.reference_doctype in advance_payment_doctypes - ] - currency = ( - self.paid_from_account_currency - if self.payment_type == "Receive" - else self.paid_to_account_currency - ) - for x in advance_doctype_references: - doc = frappe.new_doc("Advance Payment Ledger Entry") - doc.company = self.company - doc.voucher_type = self.doctype - doc.voucher_no = self.name - doc.against_voucher_type = x.reference_doctype - doc.against_voucher_no = x.reference_name - doc.amount = x.allocated_amount if self.docstatus == 1 else -1 * x.allocated_amount - doc.currency = currency - doc.event = "Submit" if self.docstatus == 1 else "Cancel" - doc.flags.ignore_permissions = 1 - doc.save() - - def make_advance_payment_ledger_entries(self): - if self.docstatus != 0: - if self.doctype == "Journal Entry": - self.make_advance_payment_ledger_for_journal() - elif self.doctype == "Payment Entry": - self.make_advance_payment_ledger_for_payment() - def set_transaction_currency_and_rate_in_gl_map(self, gl_entries): for x in gl_entries: x["transaction_currency"] = self.currency diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 3561e147d598..3f7c161628a0 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -362,7 +362,7 @@ erpnext.patches.v14_0.create_accounting_dimensions_in_reconciliation_tool erpnext.patches.v14_0.update_flag_for_return_invoices #2024-03-22 erpnext.patches.v15_0.create_accounting_dimensions_in_payment_request erpnext.patches.v14_0.update_pos_return_ledger_entries #2024-08-16 -erpnext.patches.v15_0.create_advance_payment_ledger_records +erpnext.patches.v15_0.create_advance_payment_ledger_records #2025-07-04 # below migration patch should always run last erpnext.patches.v14_0.migrate_gl_to_payment_ledger erpnext.stock.doctype.delivery_note.patches.drop_unused_return_against_index # 2023-12-20 diff --git a/erpnext/patches/v15_0/create_advance_payment_ledger_records.py b/erpnext/patches/v15_0/create_advance_payment_ledger_records.py index 13b4d95c760e..3706de2957f0 100644 --- a/erpnext/patches/v15_0/create_advance_payment_ledger_records.py +++ b/erpnext/patches/v15_0/create_advance_payment_ledger_records.py @@ -1,75 +1,127 @@ import frappe -from frappe import qb -from frappe.query_builder.custom import ConstantColumn +from frappe.model.naming import _generate_random_string +from frappe.query_builder import Case +from frappe.utils import now_datetime +DOCTYPE = "Advance Payment Ledger Entry" -def get_advance_doctypes() -> list: - return frappe.get_hooks("advance_payment_receivable_doctypes") + frappe.get_hooks( +FIELDS = [ + "name", + "creation", + "modified", + "owner", + "modified_by", + "company", + "voucher_type", + "voucher_no", + "against_voucher_type", + "against_voucher_no", + "amount", + "currency", + "event", + "delinked", +] + + +def execute(): + """ + Description: + Create Advance Payment Ledger Entry for all Payments made against Sales / Purchase Orders + """ + frappe.db.truncate(DOCTYPE) + advance_doctpyes = frappe.get_hooks("advance_payment_receivable_doctypes") + frappe.get_hooks( "advance_payment_payable_doctypes" ) + make_advance_ledger_entries_for_payment_entries(advance_doctpyes) + make_advance_ledger_entries_for_journal_entries(advance_doctpyes) -def get_payments_with_so_po_reference() -> list: - advance_payment_entries = [] - advance_doctypes = get_advance_doctypes() - per = qb.DocType("Payment Entry Reference") - payments_with_reference = ( - qb.from_(per) - .select(per.parent) - .distinct() - .where(per.reference_doctype.isin(advance_doctypes) & per.docstatus.eq(1)) - .run() - ) - if payments_with_reference: - pe = qb.DocType("Payment Entry") - advance_payment_entries = ( - qb.from_(pe) - .select(ConstantColumn("Payment Entry").as_("doctype")) - .select(pe.name) - .where(pe.name.isin(payments_with_reference) & pe.docstatus.eq(1)) - .run(as_dict=True) +def make_advance_ledger_entries_for_payment_entries(advance_doctpyes) -> list: + pe = frappe.qb.DocType("Payment Entry") + per = frappe.qb.DocType("Payment Entry Reference") + + entries = ( + frappe.qb.from_(per) + .inner_join(pe) + .on(pe.name == per.parent) + .select( + pe.company, + per.parenttype.as_("voucher_type"), + per.parent.as_("voucher_no"), + per.reference_doctype.as_("against_voucher_type"), + per.reference_name.as_("against_voucher_no"), + per.allocated_amount.as_("amount"), + Case() + .when(pe.payment_type == "Receive", pe.paid_from_account_currency) + .else_(pe.paid_to_account_currency) + .as_("currency"), ) + .where(per.reference_doctype.isin(advance_doctpyes) & per.docstatus.eq(1)) + .run(as_dict=True) + ) - return advance_payment_entries + if not entries: + return + bulk_insert_advance_entries(entries) -def get_journals_with_so_po_reference() -> list: - advance_journal_entries = [] - advance_doctypes = get_advance_doctypes() - jea = qb.DocType("Journal Entry Account") - journals_with_reference = ( - qb.from_(jea) - .select(jea.parent) - .distinct() - .where(jea.reference_type.isin(advance_doctypes) & jea.docstatus.eq(1)) - .run() - ) - if journals_with_reference: - je = qb.DocType("Journal Entry") - advance_journal_entries = ( - qb.from_(je) - .select(ConstantColumn("Journal Entry").as_("doctype")) - .select(je.name) - .where(je.name.isin(journals_with_reference) & je.docstatus.eq(1)) - .run(as_dict=True) + +def make_advance_ledger_entries_for_journal_entries(advance_doctpyes) -> list: + je = frappe.qb.DocType("Journal Entry") + jea = frappe.qb.DocType("Journal Entry Account") + + entries = ( + frappe.qb.from_(jea) + .inner_join(je) + .on(je.name == jea.parent) + .select( + je.company, + jea.parenttype.as_("voucher_type"), + jea.parent.as_("voucher_no"), + jea.reference_type.as_("against_voucher_type"), + jea.reference_name.as_("against_voucher_no"), + Case() + .when(jea.account_type == "Receivable", jea.credit_in_account_currency) + .else_(jea.debit_in_account_currency) + .as_("amount"), + jea.account_currency.as_("currency"), ) + .where(jea.reference_type.isin(advance_doctpyes) & jea.docstatus.eq(1)) + .run(as_dict=True) + ) - return advance_journal_entries + if not entries: + return + bulk_insert_advance_entries(entries) -def make_advance_ledger_entries(vouchers: list): - for x in vouchers: - frappe.get_doc(x.doctype, x.name).make_advance_payment_ledger_entries() +def bulk_insert_advance_entries(entries): + details = [] + user = frappe.session.user + now = now_datetime() + for entry in entries: + if entry.amount < 0: + continue + details.append(get_values(user, now, entry)) -def execute(): - """ - Description: - Create Advance Payment Ledger Entry for all Payments made against Sales / Purchase Orders - """ - frappe.db.truncate("Advance Payment Ledger Entry") - payment_entries = get_payments_with_so_po_reference() - make_advance_ledger_entries(payment_entries) + frappe.db.bulk_insert(DOCTYPE, fields=FIELDS, values=details) - journals = get_journals_with_so_po_reference() - make_advance_ledger_entries(journals) + +def get_values(user, now, entry): + return ( + _generate_random_string(10), + now, + now, + user, + user, + entry.company, + entry.voucher_type, + entry.voucher_no, + entry.against_voucher_type, + entry.against_voucher_no, + entry.amount * -1, + entry.currency, + "Submit", + 0, + ) From dd00f082a25c237567039e868550700bcc470532 Mon Sep 17 00:00:00 2001 From: ljain112 Date: Sat, 5 Jul 2025 16:23:03 +0530 Subject: [PATCH 14/35] fix: update unadjusted amount in Advance Payment Ledger Entry --- .../advance_payment_ledger_entry.json | 10 ++- .../advance_payment_ledger_entry.py | 1 + .../doctype/payment_entry/payment_entry.py | 29 ++++++-- .../unreconcile_payment.py | 8 ++- erpnext/accounts/utils.py | 67 +++++++++++++++---- 5 files changed, 93 insertions(+), 22 deletions(-) diff --git a/erpnext/accounts/doctype/advance_payment_ledger_entry/advance_payment_ledger_entry.json b/erpnext/accounts/doctype/advance_payment_ledger_entry/advance_payment_ledger_entry.json index 18e55a5c6111..781d5b66c4c2 100644 --- a/erpnext/accounts/doctype/advance_payment_ledger_entry/advance_payment_ledger_entry.json +++ b/erpnext/accounts/doctype/advance_payment_ledger_entry/advance_payment_ledger_entry.json @@ -11,6 +11,7 @@ "against_voucher_type", "against_voucher_no", "amount", + "unadjusted_amount", "currency", "event", "delinked" @@ -76,13 +77,20 @@ "fieldtype": "Check", "label": "DeLinked", "read_only": 1 + }, + { + "description": "Amount which is yet to be adjusted", + "fieldname": "unadjusted_amount", + "fieldtype": "Currency", + "label": "Unadjusted Amount", + "read_only": 1 } ], "grid_page_length": 50, "in_create": 1, "index_web_pages_for_search": 1, "links": [], - "modified": "2025-07-01 18:43:34.964275", + "modified": "2025-07-05 14:30:09.030494", "modified_by": "Administrator", "module": "Accounts", "name": "Advance Payment Ledger Entry", diff --git a/erpnext/accounts/doctype/advance_payment_ledger_entry/advance_payment_ledger_entry.py b/erpnext/accounts/doctype/advance_payment_ledger_entry/advance_payment_ledger_entry.py index a9392fc9367f..8349d633ac5f 100644 --- a/erpnext/accounts/doctype/advance_payment_ledger_entry/advance_payment_ledger_entry.py +++ b/erpnext/accounts/doctype/advance_payment_ledger_entry/advance_payment_ledger_entry.py @@ -23,6 +23,7 @@ class AdvancePaymentLedgerEntry(Document): currency: DF.Link | None delinked: DF.Check event: DF.Data | None + unadjusted_amount: DF.Currency voucher_no: DF.DynamicLink | None voucher_type: DF.Link | None # end: auto-generated types diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index fb1595a90c81..913971147bd9 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -1380,6 +1380,10 @@ def add_party_gl_entries(self, gl_entries): 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: @@ -1440,12 +1444,25 @@ def add_party_gl_entries(self, gl_entries): ) ) - gle.update( - { - "against_voucher_type": d.reference_doctype, - "against_voucher": d.reference_name, - } - ) + 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, + "against_voucher": d.reference_name, + } + ) + 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( + { + "against_voucher_type": d.reference_doctype, + "against_voucher": d.reference_name, + } + ) gl_entries.append(gle) diff --git a/erpnext/accounts/doctype/unreconcile_payment/unreconcile_payment.py b/erpnext/accounts/doctype/unreconcile_payment/unreconcile_payment.py index 366cbad90c08..9e222d82da64 100644 --- a/erpnext/accounts/doctype/unreconcile_payment/unreconcile_payment.py +++ b/erpnext/accounts/doctype/unreconcile_payment/unreconcile_payment.py @@ -91,7 +91,12 @@ def doc_has_references(doctype: str | None = None, docname: str | None = None): count += frappe.db.count( "Advance Payment Ledger Entry", - filters={"delinked": 0, "voucher_no": docname, "voucher_type": doctype}, + filters={ + "delinked": 0, + "voucher_no": docname, + "voucher_type": doctype, + "unadjusted_amount": ["<", 0], + }, ) return count @@ -185,6 +190,7 @@ def get_advance_linked_payments_for_doc( .where(ple.voucher_no == docname) .where(ple.voucher_type == doctype) .where(ple.company == company) + .where(ple.unadjusted_amount < 0) .groupby(ple.against_voucher_no) ) diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index 22ec4037eac7..05e869c8830c 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -619,6 +619,8 @@ def update_reference_in_journal_entry(d, journal_entry, do_not_save=False): """ Updates against document, if partial amount splits into rows """ + + # TODO: Update unadjusted amount for advance doctype jv_detail = journal_entry.get("accounts", {"name": d["voucher_detail_no"]})[0] rev_dr_or_cr = ( @@ -700,6 +702,10 @@ def update_reference_in_payment_entry( "dimensions": d.dimensions, } + advance_payment_doctypes = frappe.get_hooks("advance_payment_receivable_doctypes") + frappe.get_hooks( + "advance_payment_payable_doctypes" + ) + # Update Reconciliation effect date in reference reconciliation_takes_effect_on = frappe.get_cached_value( "Company", payment_entry.company, "reconciliation_takes_effect_on" @@ -732,6 +738,16 @@ def update_reference_in_payment_entry( for field in list(reference_details): new_row.set(field, reference_details[field]) row = new_row + + if existing_row.reference_doctype in advance_payment_doctypes: + update_unadjusted_amount_in_advance_entry( + existing_row.parenttype, + existing_row.parent, + existing_row.reference_doctype, + existing_row.reference_name, + d.allocated_amount, + ) + else: new_row = payment_entry.append("references") new_row.docstatus = 1 @@ -771,6 +787,24 @@ def update_reference_in_payment_entry( return row +def update_unadjusted_amount_in_advance_entry( + voucher_type, voucher_no, against_voucher_type, against_voucher_no, adj_amount +): + adv = frappe.qb.DocType("Advance Payment Ledger Entry") + + ( + frappe.qb.update(adv) + .set(adv.unadjusted_amount, adv.unadjusted_amount + adj_amount) + .where( + (adv.voucher_type == voucher_type) + & (adv.voucher_no == voucher_no) + & (adv.against_voucher_type == against_voucher_type) + & (adv.against_voucher_no == against_voucher_no) + ) + .run() + ) + + def cancel_exchange_gain_loss_journal( parent_doc: dict | object, referenced_dt: str | None = None, referenced_dn: str | None = None ) -> None: @@ -1845,20 +1879,7 @@ def get_account_type(account): if is_advance_doctype: # create advance entry - adv = frappe._dict( - doctype="Advance Payment Ledger Entry", - company=gle.company, - voucher_type=gle.voucher_type, - voucher_no=gle.voucher_no, - voucher_detail_no=gle.voucher_detail_no, - against_voucher_type=gle.against_voucher_type, - against_voucher_no=gle.against_voucher, - currency=gle.account_currency, - amount=dr_or_cr_account_currency, - event="Cancel" if cancel else "Submit", - delinked=cancel, - remarks=gle.remarks, - ) + adv = get_advance_ledger_entry(gle, cancel, dr_or_cr_account_currency) ple_map.append(adv) @@ -1867,6 +1888,24 @@ def get_account_type(account): return ple_map +def get_advance_ledger_entry(gle, cancel, amount): + return frappe._dict( + doctype="Advance Payment Ledger Entry", + company=gle.company, + voucher_type=gle.voucher_type, + voucher_no=gle.voucher_no, + voucher_detail_no=gle.voucher_detail_no, + against_voucher_type=gle.against_voucher_type, + against_voucher_no=gle.against_voucher, + currency=gle.account_currency, + amount=amount, + unadjusted_amount=amount, + event="Cancel" if cancel else "Submit", + delinked=cancel, + remarks=gle.remarks, + ) + + def create_payment_ledger_entry( gl_entries, cancel=0, adv_adj=0, update_outstanding="Yes", from_repost=0, partial_cancel=False ): From e5f9baf7aa0e972f732c4a81c3833f55d6e9477a Mon Sep 17 00:00:00 2001 From: ljain112 Date: Thu, 10 Jul 2025 15:20:03 +0530 Subject: [PATCH 15/35] fix: patch for updating existing payment ledger entries --- erpnext/patches.txt | 3 ++- ...ledger_entries_against_advance_doctypes.py | 25 +++++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) create mode 100644 erpnext/patches/v15_0/update_payment_ledger_entries_against_advance_doctypes.py diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 855c0777d47b..2f87110561ad 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -424,4 +424,5 @@ execute:frappe.db.set_single_value("Accounts Settings", "confirm_before_resettin erpnext.patches.v15_0.rename_pos_closing_entry_fields #2025-06-13 erpnext.patches.v15_0.update_pegged_currencies erpnext.patches.v15_0.set_status_cancelled_on_cancelled_pos_opening_entry_and_pos_closing_entry -erpnext.patches.v15_0.set_company_on_pos_inv_merge_log \ No newline at end of file +erpnext.patches.v15_0.set_company_on_pos_inv_merge_log +erpnext.patches.v15_0.update_payment_ledger_entries_against_advance_doctypes \ No newline at end of file diff --git a/erpnext/patches/v15_0/update_payment_ledger_entries_against_advance_doctypes.py b/erpnext/patches/v15_0/update_payment_ledger_entries_against_advance_doctypes.py new file mode 100644 index 000000000000..f5a20803c6eb --- /dev/null +++ b/erpnext/patches/v15_0/update_payment_ledger_entries_against_advance_doctypes.py @@ -0,0 +1,25 @@ +import frappe + +from erpnext.accounts.utils import get_advance_payment_doctypes + +DOCTYPE = "Payment Ledger Entry" + + +def execute(): + """ + Description: + Set against_voucher as entry for Payment Ledger Entry against advance vouchers. + """ + advance_payment_doctypes = get_advance_payment_doctypes() + + if not advance_payment_doctypes: + return + ple = frappe.qb.DocType(DOCTYPE) + + ( + frappe.qb.update(ple) + .set(ple.against_voucher_type, ple.voucher_type) + .set(ple.against_voucher_no, ple.voucher_no) + .where(ple.against_voucher_type.isin(advance_payment_doctypes)) + .run() + ) From f5beda48dcb984cf7df0d09d6360d2bb2a229164 Mon Sep 17 00:00:00 2001 From: ravibharathi656 Date: Wed, 23 Jul 2025 08:34:52 +0530 Subject: [PATCH 16/35] fix(pick list): make warehouse editable --- erpnext/stock/doctype/pick_list/pick_list.js | 17 +++++++++++++++++ erpnext/stock/doctype/pick_list/pick_list.json | 4 ++-- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/doctype/pick_list/pick_list.js b/erpnext/stock/doctype/pick_list/pick_list.js index dea835604946..c72fa8649609 100644 --- a/erpnext/stock/doctype/pick_list/pick_list.js +++ b/erpnext/stock/doctype/pick_list/pick_list.js @@ -21,6 +21,14 @@ frappe.ui.form.on("Pick List", { "Stock Entry": "Stock Entry", }; + frm.set_query("warehouse", "locations", () => { + return { + filters: { + company: frm.doc.company, + }, + }; + }); + frm.set_query("parent_warehouse", () => { return { filters: { @@ -91,6 +99,15 @@ frappe.ui.form.on("Pick List", { }); } }, + + pick_manually: function (frm) { + frm.fields_dict.locations.grid.update_docfield_property( + "warehouse", + "read_only", + !frm.doc.pick_manually + ); + }, + get_item_locations: (frm) => { // Button on the form frm.events.set_item_locations(frm, false); diff --git a/erpnext/stock/doctype/pick_list/pick_list.json b/erpnext/stock/doctype/pick_list/pick_list.json index e64494769719..69a1482d6d9f 100644 --- a/erpnext/stock/doctype/pick_list/pick_list.json +++ b/erpnext/stock/doctype/pick_list/pick_list.json @@ -201,7 +201,7 @@ }, { "default": "0", - "description": "If enabled then system won't override the picked qty / batches / serial numbers.", + "description": "If enabled then system won't override the picked qty / batches / serial numbers / warehouse.", "fieldname": "pick_manually", "fieldtype": "Check", "label": "Pick Manually" @@ -247,7 +247,7 @@ ], "is_submittable": 1, "links": [], - "modified": "2025-05-31 19:18:30.860044", + "modified": "2025-07-23 08:34:32.099673", "modified_by": "Administrator", "module": "Stock", "name": "Pick List", From d3253d7d064859829be1de18908b1275d8872fd5 Mon Sep 17 00:00:00 2001 From: ravibharathi656 Date: Wed, 23 Jul 2025 16:13:20 +0530 Subject: [PATCH 17/35] revert: do not save when set email campaign status --- erpnext/crm/doctype/email_campaign/email_campaign.py | 1 - 1 file changed, 1 deletion(-) diff --git a/erpnext/crm/doctype/email_campaign/email_campaign.py b/erpnext/crm/doctype/email_campaign/email_campaign.py index 9390573773c0..6bfa4f8b3cb2 100644 --- a/erpnext/crm/doctype/email_campaign/email_campaign.py +++ b/erpnext/crm/doctype/email_campaign/email_campaign.py @@ -144,4 +144,3 @@ def set_email_campaign_status(): for entry in email_campaigns: email_campaign = frappe.get_doc("Email Campaign", entry.name) email_campaign.update_status() - email_campaign.save() From dc841fe6618ba3e967b91b14758aa830ed93f185 Mon Sep 17 00:00:00 2001 From: ljain112 Date: Sat, 26 Jul 2025 12:56:10 +0530 Subject: [PATCH 18/35] fix: attribute error in payment entry --- .../doctype/payment_entry/payment_entry.py | 5 +++-- erpnext/accounts/utils.py | 15 +++++++++------ 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 8a0bd527e1cf..0cf91dceb164 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -1560,13 +1560,14 @@ def add_advance_gl_for_reference(self, gl_entries, invoice): "voucher_no": self.name, "voucher_detail_no": invoice.name, } - if invoice.reconcile_effect_on: posting_date = invoice.reconcile_effect_on else: # For backwards compatibility # Supporting reposting on payment entries reconciled before select field introduction - posting_date = get_reconciliation_effect_date(invoice, self.company, self.posting_date) + posting_date = get_reconciliation_effect_date( + invoice.reference_doctype, invoice.reference_name, self.company, self.posting_date + ) frappe.db.set_value("Payment Entry Reference", invoice.name, "reconcile_effect_on", posting_date) dr_or_cr, account = self.get_dr_and_account_for_advances(invoice) diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index dcb05d7040aa..5aca7dd04ccc 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -732,7 +732,9 @@ def update_reference_in_payment_entry( # Update Reconciliation effect date in reference if payment_entry.book_advance_payments_in_separate_party_account: - reconcile_on = get_reconciliation_effect_date(d, payment_entry.company, payment_entry.posting_date) + reconcile_on = get_reconciliation_effect_date( + d.against_voucher_type, d.against_voucher, payment_entry.company, payment_entry.posting_date + ) reference_details.update({"reconcile_effect_on": reconcile_on}) if d.voucher_detail_no: @@ -787,20 +789,21 @@ def update_reference_in_payment_entry( return row, update_advance_paid -def get_reconciliation_effect_date(reference, company, posting_date): +def get_reconciliation_effect_date(against_voucher_type, against_voucher, company, posting_date): reconciliation_takes_effect_on = frappe.get_cached_value( "Company", company, "reconciliation_takes_effect_on" ) + # default + reconcile_on = posting_date + if reconciliation_takes_effect_on == "Advance Payment Date": reconcile_on = posting_date elif reconciliation_takes_effect_on == "Oldest Of Invoice Or Advance": date_field = "posting_date" - if reference.against_voucher_type in ["Sales Order", "Purchase Order"]: + if against_voucher_type in ["Sales Order", "Purchase Order"]: date_field = "transaction_date" - reconcile_on = frappe.db.get_value( - reference.against_voucher_type, reference.against_voucher, date_field - ) + reconcile_on = frappe.db.get_value(against_voucher_type, against_voucher, date_field) if getdate(reconcile_on) < getdate(posting_date): reconcile_on = posting_date elif reconciliation_takes_effect_on == "Reconciliation Date": From b5a87ed236406a95f797ab8d43f0623ddb9ce152 Mon Sep 17 00:00:00 2001 From: ljain112 Date: Tue, 29 Jul 2025 12:49:13 +0530 Subject: [PATCH 19/35] fix: update advance doctype outstanding --- .../advance_payment_ledger_entry.json | 10 +- .../advance_payment_ledger_entry.py | 1 - .../doctype/journal_entry/journal_entry.py | 86 +++++++------ .../journal_entry_account.json | 23 +++- .../journal_entry_account.py | 4 +- .../doctype/payment_entry/payment_entry.py | 32 +++-- .../payment_entry/test_payment_entry.py | 4 +- .../payment_entry_reference.json | 25 +++- .../payment_entry_reference.py | 3 +- .../payment_ledger_entry.json | 2 +- .../unreconcile_payment.py | 52 ++++---- erpnext/accounts/general_ledger.py | 2 + erpnext/accounts/utils.py | 116 +++++++----------- erpnext/controllers/accounts_controller.py | 57 +++------ 14 files changed, 208 insertions(+), 209 deletions(-) diff --git a/erpnext/accounts/doctype/advance_payment_ledger_entry/advance_payment_ledger_entry.json b/erpnext/accounts/doctype/advance_payment_ledger_entry/advance_payment_ledger_entry.json index 781d5b66c4c2..5ad2479e858f 100644 --- a/erpnext/accounts/doctype/advance_payment_ledger_entry/advance_payment_ledger_entry.json +++ b/erpnext/accounts/doctype/advance_payment_ledger_entry/advance_payment_ledger_entry.json @@ -11,7 +11,6 @@ "against_voucher_type", "against_voucher_no", "amount", - "unadjusted_amount", "currency", "event", "delinked" @@ -77,20 +76,13 @@ "fieldtype": "Check", "label": "DeLinked", "read_only": 1 - }, - { - "description": "Amount which is yet to be adjusted", - "fieldname": "unadjusted_amount", - "fieldtype": "Currency", - "label": "Unadjusted Amount", - "read_only": 1 } ], "grid_page_length": 50, "in_create": 1, "index_web_pages_for_search": 1, "links": [], - "modified": "2025-07-05 14:30:09.030494", + "modified": "2025-07-29 11:37:42.678556", "modified_by": "Administrator", "module": "Accounts", "name": "Advance Payment Ledger Entry", diff --git a/erpnext/accounts/doctype/advance_payment_ledger_entry/advance_payment_ledger_entry.py b/erpnext/accounts/doctype/advance_payment_ledger_entry/advance_payment_ledger_entry.py index 8349d633ac5f..a9392fc9367f 100644 --- a/erpnext/accounts/doctype/advance_payment_ledger_entry/advance_payment_ledger_entry.py +++ b/erpnext/accounts/doctype/advance_payment_ledger_entry/advance_payment_ledger_entry.py @@ -23,7 +23,6 @@ class AdvancePaymentLedgerEntry(Document): currency: DF.Link | None delinked: DF.Check event: DF.Data | None - unadjusted_amount: DF.Currency voucher_no: DF.DynamicLink | None voucher_type: DF.Link | None # end: auto-generated types diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py index 7869a00bc342..15def2d3df27 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py @@ -1179,49 +1179,65 @@ def build_gl_map(self): self.transaction_exchange_rate = row.exchange_rate break + advance_doctypes = get_advance_payment_doctypes() + for d in self.get("accounts"): if d.debit or d.credit or (self.voucher_type == "Exchange Gain Or Loss"): r = [d.user_remark, self.remark] r = [x for x in r if x] remarks = "\n".join(r) + row = { + "account": d.account, + "party_type": d.party_type, + "due_date": self.due_date, + "party": d.party, + "against": d.against_account, + "debit": flt(d.debit, d.precision("debit")), + "credit": flt(d.credit, d.precision("credit")), + "account_currency": d.account_currency, + "debit_in_account_currency": flt( + d.debit_in_account_currency, d.precision("debit_in_account_currency") + ), + "credit_in_account_currency": flt( + d.credit_in_account_currency, d.precision("credit_in_account_currency") + ), + "transaction_currency": self.transaction_currency, + "transaction_exchange_rate": self.transaction_exchange_rate, + "debit_in_transaction_currency": flt( + d.debit_in_account_currency, d.precision("debit_in_account_currency") + ) + if self.transaction_currency == d.account_currency + else flt(d.debit, d.precision("debit")) / self.transaction_exchange_rate, + "credit_in_transaction_currency": flt( + d.credit_in_account_currency, d.precision("credit_in_account_currency") + ) + if self.transaction_currency == d.account_currency + else flt(d.credit, d.precision("credit")) / self.transaction_exchange_rate, + "against_voucher_type": d.reference_type, + "against_voucher": d.reference_name, + "remarks": remarks, + "voucher_detail_no": d.reference_detail_no, + "cost_center": d.cost_center, + "project": d.project, + "finance_book": self.finance_book, + "advance_voucher_type": d.advance_voucher_type, + "advance_voucher_no": d.advance_voucher_no, + } + + if d.reference_type in advance_doctypes: + row.update( + { + "against_voucher_type": self.doctype, + "against_voucher": self.name, + "advance_voucher_type": d.reference_type, + "advance_voucher_no": d.reference_name, + } + ) + gl_map.append( self.get_gl_dict( - { - "account": d.account, - "party_type": d.party_type, - "due_date": self.due_date, - "party": d.party, - "against": d.against_account, - "debit": flt(d.debit, d.precision("debit")), - "credit": flt(d.credit, d.precision("credit")), - "account_currency": d.account_currency, - "debit_in_account_currency": flt( - d.debit_in_account_currency, d.precision("debit_in_account_currency") - ), - "credit_in_account_currency": flt( - d.credit_in_account_currency, d.precision("credit_in_account_currency") - ), - "transaction_currency": self.transaction_currency, - "transaction_exchange_rate": self.transaction_exchange_rate, - "debit_in_transaction_currency": flt( - d.debit_in_account_currency, d.precision("debit_in_account_currency") - ) - if self.transaction_currency == d.account_currency - else flt(d.debit, d.precision("debit")) / self.transaction_exchange_rate, - "credit_in_transaction_currency": flt( - d.credit_in_account_currency, d.precision("credit_in_account_currency") - ) - if self.transaction_currency == d.account_currency - else flt(d.credit, d.precision("credit")) / self.transaction_exchange_rate, - "against_voucher_type": d.reference_type, - "against_voucher": d.reference_name, - "remarks": remarks, - "voucher_detail_no": d.reference_detail_no, - "cost_center": d.cost_center, - "project": d.project, - "finance_book": self.finance_book, - }, + row, item=d, ) ) diff --git a/erpnext/accounts/doctype/journal_entry_account/journal_entry_account.json b/erpnext/accounts/doctype/journal_entry_account/journal_entry_account.json index e3dbcf51fa12..45dc903c0681 100644 --- a/erpnext/accounts/doctype/journal_entry_account/journal_entry_account.json +++ b/erpnext/accounts/doctype/journal_entry_account/journal_entry_account.json @@ -32,6 +32,8 @@ "reference_name", "reference_due_date", "reference_detail_no", + "advance_voucher_type", + "advance_voucher_no", "col_break3", "is_advance", "user_remark", @@ -262,20 +264,37 @@ "hidden": 1, "label": "Reference Detail No", "no_copy": 1 + }, + { + "fieldname": "advance_voucher_type", + "fieldtype": "Link", + "label": "Advance Voucher Type", + "no_copy": 1, + "options": "DocType", + "read_only": 1 + }, + { + "fieldname": "advance_voucher_no", + "fieldtype": "Dynamic Link", + "label": "Advance Voucher No", + "no_copy": 1, + "options": "advance_voucher_type", + "read_only": 1 } ], "idx": 1, "istable": 1, "links": [], - "modified": "2024-03-27 13:09:58.647732", + "modified": "2025-07-25 04:45:28.117715", "modified_by": "Administrator", "module": "Accounts", "name": "Journal Entry Account", "naming_rule": "Random", "owner": "Administrator", "permissions": [], + "row_format": "Dynamic", "sort_field": "creation", "sort_order": "DESC", "states": [], "track_changes": 1 -} \ No newline at end of file +} diff --git a/erpnext/accounts/doctype/journal_entry_account/journal_entry_account.py b/erpnext/accounts/doctype/journal_entry_account/journal_entry_account.py index 00c9dcb374b1..b801ac8c9a58 100644 --- a/erpnext/accounts/doctype/journal_entry_account/journal_entry_account.py +++ b/erpnext/accounts/doctype/journal_entry_account/journal_entry_account.py @@ -17,8 +17,9 @@ class JournalEntryAccount(Document): account: DF.Link account_currency: DF.Link | None account_type: DF.Data | None + advance_voucher_no: DF.DynamicLink | None + advance_voucher_type: DF.Link | None against_account: DF.Text | None - balance: DF.Currency bank_account: DF.Link | None cost_center: DF.Link | None credit: DF.Currency @@ -31,7 +32,6 @@ class JournalEntryAccount(Document): parentfield: DF.Data parenttype: DF.Data party: DF.DynamicLink | None - party_balance: DF.Currency party_type: DF.Link | None project: DF.Link | None reference_detail_no: DF.Data | None diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index b690e0de52f9..8b526e0b0df8 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -1435,23 +1435,27 @@ def add_party_gl_entries(self, gl_entries): dr_or_cr + "_in_transaction_currency": d.allocated_amount if self.transaction_currency == self.party_account_currency else allocated_amount_in_company_currency / self.transaction_exchange_rate, + "advance_voucher_type": d.advance_voucher_type, + "advance_voucher_no": d.advance_voucher_no, }, item=self, ) ) - 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, - "against_voucher": d.reference_name, - } - ) - else: - # Do not reference Invoices while Advance is in separate party account - gle.update({"against_voucher_type": self.doctype, "against_voucher": self.name}) + if d.reference_doctype in advance_payment_doctypes: + # advance reference + gle.update( + { + "against_voucher_type": self.doctype, + "against_voucher": self.name, + "advance_voucher_type": d.reference_doctype, + "advance_voucher_no": d.reference_name, + } + ) + + elif self.book_advance_payments_in_separate_party_account: + # 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( { @@ -1580,6 +1584,8 @@ def add_advance_gl_for_reference(self, gl_entries, invoice): { "against_voucher_type": invoice.reference_doctype, "against_voucher": invoice.reference_name, + "advance_voucher_type": invoice.advance_voucher_type, + "advance_voucher_no": invoice.advance_voucher_no, "posting_date": posting_date, } ) @@ -1604,6 +1610,8 @@ def add_advance_gl_for_reference(self, gl_entries, invoice): { "against_voucher_type": "Payment Entry", "against_voucher": self.name, + "advance_voucher_type": invoice.advance_voucher_type, + "advance_voucher_no": invoice.advance_voucher_no, } ) gle = self.get_gl_dict( diff --git a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py index ff653ec89713..355aa46ea05d 100644 --- a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py @@ -52,7 +52,7 @@ def test_payment_entry_against_order(self): self.assertEqual(pe.paid_to_account_type, "Cash") expected_gle = dict( - (d[0], d) for d in [["Debtors - _TC", 0, 1000, so.name], ["_Test Cash - _TC", 1000.0, 0, None]] + (d[0], d) for d in [["Debtors - _TC", 0, 1000, pe.name], ["_Test Cash - _TC", 1000.0, 0, None]] ) self.validate_gl_entries(pe.name, expected_gle) @@ -84,7 +84,7 @@ def test_payment_against_sales_order_usd_to_inr(self): expected_gle = dict( (d[0], d) - for d in [["_Test Receivable USD - _TC", 0, 5500, so.name], [pe.paid_to, 5500.0, 0, None]] + for d in [["_Test Receivable USD - _TC", 0, 5500, pe.name], [pe.paid_to, 5500.0, 0, None]] ) self.validate_gl_entries(pe.name, expected_gle) diff --git a/erpnext/accounts/doctype/payment_entry_reference/payment_entry_reference.json b/erpnext/accounts/doctype/payment_entry_reference/payment_entry_reference.json index fccbbb72f3ce..77abc195ebc3 100644 --- a/erpnext/accounts/doctype/payment_entry_reference/payment_entry_reference.json +++ b/erpnext/accounts/doctype/payment_entry_reference/payment_entry_reference.json @@ -22,7 +22,9 @@ "exchange_gain_loss", "account", "payment_request", - "payment_request_outstanding" + "payment_request_outstanding", + "advance_voucher_type", + "advance_voucher_no" ], "fields": [ { @@ -151,20 +153,37 @@ "fieldtype": "Date", "label": "Reconcile Effect On", "read_only": 1 + }, + { + "columns": 2, + "fieldname": "advance_voucher_type", + "fieldtype": "Link", + "label": "Advance Voucher Type", + "options": "DocType", + "read_only": 1 + }, + { + "columns": 2, + "fieldname": "advance_voucher_no", + "fieldtype": "Dynamic Link", + "label": "Advance Voucher No", + "options": "advance_voucher_type", + "read_only": 1 } ], "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2025-01-13 15:56:18.895082", + "modified": "2025-07-25 04:32:11.040025", "modified_by": "Administrator", "module": "Accounts", "name": "Payment Entry Reference", "owner": "Administrator", "permissions": [], "quick_entry": 1, + "row_format": "Dynamic", "sort_field": "creation", "sort_order": "DESC", "states": [], "track_changes": 1 -} \ No newline at end of file +} diff --git a/erpnext/accounts/doctype/payment_entry_reference/payment_entry_reference.py b/erpnext/accounts/doctype/payment_entry_reference/payment_entry_reference.py index 1d869b927157..a5e0b21a9af3 100644 --- a/erpnext/accounts/doctype/payment_entry_reference/payment_entry_reference.py +++ b/erpnext/accounts/doctype/payment_entry_reference/payment_entry_reference.py @@ -16,6 +16,8 @@ class PaymentEntryReference(Document): account: DF.Link | None account_type: DF.Data | None + advance_voucher_no: DF.DynamicLink | None + advance_voucher_type: DF.Link | None allocated_amount: DF.Float bill_no: DF.Data | None due_date: DF.Date | None @@ -26,7 +28,6 @@ class PaymentEntryReference(Document): parentfield: DF.Data parenttype: DF.Data payment_request: DF.Link | None - payment_request_outstanding: DF.Float payment_term: DF.Link | None payment_term_outstanding: DF.Float payment_type: DF.Data | None diff --git a/erpnext/accounts/doctype/payment_ledger_entry/payment_ledger_entry.json b/erpnext/accounts/doctype/payment_ledger_entry/payment_ledger_entry.json index 9f882ce4542f..c03439cb1578 100644 --- a/erpnext/accounts/doctype/payment_ledger_entry/payment_ledger_entry.json +++ b/erpnext/accounts/doctype/payment_ledger_entry/payment_ledger_entry.json @@ -197,4 +197,4 @@ "sort_field": "creation", "sort_order": "DESC", "states": [] -} \ No newline at end of file +} diff --git a/erpnext/accounts/doctype/unreconcile_payment/unreconcile_payment.py b/erpnext/accounts/doctype/unreconcile_payment/unreconcile_payment.py index 732ab6d673b1..fc6961f7a938 100644 --- a/erpnext/accounts/doctype/unreconcile_payment/unreconcile_payment.py +++ b/erpnext/accounts/doctype/unreconcile_payment/unreconcile_payment.py @@ -7,12 +7,11 @@ from frappe import _, qb from frappe.model.document import Document from frappe.query_builder import Criterion -from frappe.query_builder.functions import Abs, Sum +from frappe.query_builder.functions import Abs, IfNull, Sum from frappe.utils.data import comma_and from erpnext.accounts.utils import ( cancel_exchange_gain_loss_journal, - get_advance_payment_doctypes, unlink_ref_doc_from_payment_entries, update_voucher_outstanding, ) @@ -89,14 +88,14 @@ def doc_has_references(doctype: str | None = None, docname: str | None = None): "Payment Ledger Entry", filters={"delinked": 0, "voucher_no": docname, "against_voucher_no": ["!=", docname]}, ) - count += frappe.db.count( "Advance Payment Ledger Entry", filters={ "delinked": 0, "voucher_no": docname, "voucher_type": doctype, - "unadjusted_amount": ["<", 0], + "amount": ["<", 0], + "event": ["=", "Submit"], }, ) @@ -160,43 +159,40 @@ def get_linked_payments_for_doc( .where(Criterion.all(criteria)) .groupby(ple.against_voucher_no) ) + res = query.run(as_dict=True) - if _dt == "Payment Entry": - # Add advance payments linked to this payment entry - res += get_advance_linked_payments_for_doc(company, doctype, docname) + res += get_linked_advances(company, _dn, ple) return res return [] -def get_advance_linked_payments_for_doc( - company: str | None = None, doctype: str | None = None, docname: str | None = None -) -> list: - if not (doctype and docname): - return [] +def get_linked_advances(company, _dn, adv): + adv = qb.DocType("Advance Payment Ledger Entry") + criteria = [ + (adv.company == company), + (adv.delinked == 0), + (adv.voucher_no == _dn), + (adv.event == "Submit"), + ] - ple = qb.DocType("Advance Payment Ledger Entry") - query = ( - qb.from_(ple) + return ( + qb.from_(adv) .select( - ple.company, - ple.against_voucher_type.as_("reference_doctype"), - ple.against_voucher_no.as_("reference_name"), - Abs(Sum(ple.amount)).as_("allocated_amount"), - ple.currency.as_("account_currency"), + adv.company, + adv.against_voucher_type.as_("reference_doctype"), + adv.against_voucher_no.as_("reference_name"), + Abs(Sum(adv.amount)).as_("allocated_amount"), + adv.currency, ) - .where(ple.delinked == 0) - .where(ple.voucher_no == docname) - .where(ple.voucher_type == doctype) - .where(ple.company == company) - .where(ple.unadjusted_amount < 0) - .groupby(ple.against_voucher_no) + .where(Criterion.all(criteria)) + .having(qb.Field("allocated_amount") > 0) + .groupby(adv.against_voucher_no) + .run(as_dict=True) ) - return query.run(as_dict=True) - @frappe.whitelist() def create_unreconcile_doc_for_selection(selections=None): diff --git a/erpnext/accounts/general_ledger.py b/erpnext/accounts/general_ledger.py index 8c7ec2795e0e..57c8b964c235 100644 --- a/erpnext/accounts/general_ledger.py +++ b/erpnext/accounts/general_ledger.py @@ -316,6 +316,8 @@ def get_merge_properties(dimensions=None): "project", "finance_book", "voucher_no", + "advance_voucher_type", + "advance_voucher_no", ] if dimensions: merge_properties.extend(dimensions) diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index ba336ed978b1..f05d86d108b1 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -2,7 +2,6 @@ # License: GNU General Public License v3. See license.txt -from collections import defaultdict from json import loads from typing import TYPE_CHECKING, Optional @@ -12,7 +11,7 @@ from frappe.desk.reportview import build_match_conditions from frappe.model.meta import get_field_precision from frappe.query_builder import AliasedQuery, Case, Criterion, Table -from frappe.query_builder.functions import Count, Max, Round, Sum +from frappe.query_builder.functions import Count, IfNull, Max, Round, Sum from frappe.query_builder.utils import DocType from frappe.utils import ( add_days, @@ -473,10 +472,11 @@ def reconcile_against_document( Cancel PE or JV, Update against document, split if required and resubmit """ # To optimize making GL Entry for PE or JV with multiple references - reconciled_entries = defaultdict(list) - docs_to_update = set() - + reconciled_entries = {} for row in args: + if not reconciled_entries.get((row.voucher_type, row.voucher_no)): + reconciled_entries[(row.voucher_type, row.voucher_no)] = [] + reconciled_entries[(row.voucher_type, row.voucher_no)].append(row) for key, entries in reconciled_entries.items(): @@ -487,16 +487,6 @@ def reconcile_against_document( reposting_rows = [] for entry in entries: - # ensure correct sequence of args - docs_to_update.add( - ( - entry.against_voucher_type, - entry.against_voucher, - entry.account, - entry.party_type, - entry.party, - ) - ) check_if_advance_entry_modified(entry) validate_allocated_amount(entry) @@ -536,12 +526,17 @@ def reconcile_against_document( process_debit_credit_difference(gl_map) create_payment_ledger_entry(gl_map, update_outstanding="No", cancel=0, adv_adj=1) + # Only update outstanding for newly linked vouchers + for entry in entries: + update_voucher_outstanding( + entry.against_voucher_type, + entry.against_voucher, + entry.account, + entry.party_type, + entry.party, + ) frappe.flags.ignore_party_validation = False - # Only update outstanding for newly linked vouchers - for entry in docs_to_update: - update_voucher_outstanding(*entry) - def check_if_advance_entry_modified(args): """ @@ -623,8 +618,6 @@ def update_reference_in_journal_entry(d, journal_entry, do_not_save=False): """ Updates against document, if partial amount splits into rows """ - - # TODO: Update unadjusted amount for advance doctype jv_detail = journal_entry.get("accounts", {"name": d["voucher_detail_no"]})[0] rev_dr_or_cr = ( @@ -679,6 +672,10 @@ def update_reference_in_journal_entry(d, journal_entry, do_not_save=False): new_row.is_advance = cstr(jv_detail.is_advance) new_row.docstatus = 1 + if jv_detail.get("reference_type") in get_advance_payment_doctypes(): + new_row.advance_voucher_type = jv_detail.get("reference_type") + new_row.advance_voucher_no = jv_detail.get("reference_name") + # will work as update after submit journal_entry.flags.ignore_validate_update_after_submit = True # Ledgers will be reposted by Reconciliation tool @@ -721,24 +718,18 @@ def update_reference_in_payment_entry( new_row = payment_entry.append("references") new_row.docstatus = 1 - new_row.set_new_name() for field in list(reference_details): new_row.set(field, reference_details[field]) - row = new_row if existing_row.reference_doctype in advance_payment_doctypes: - update_unadjusted_amount_in_advance_entry( - existing_row.parenttype, - existing_row.parent, - existing_row.reference_doctype, - existing_row.reference_name, - d.allocated_amount, - ) + new_row.advance_voucher_type = existing_row.reference_doctype + new_row.advance_voucher_no = existing_row.reference_name + + row = new_row else: new_row = payment_entry.append("references") new_row.docstatus = 1 - new_row.set_new_name() new_row.update(reference_details) row = new_row @@ -774,24 +765,6 @@ def update_reference_in_payment_entry( return row -def update_unadjusted_amount_in_advance_entry( - voucher_type, voucher_no, against_voucher_type, against_voucher_no, adj_amount -): - adv = frappe.qb.DocType("Advance Payment Ledger Entry") - - ( - frappe.qb.update(adv) - .set(adv.unadjusted_amount, adv.unadjusted_amount + adj_amount) - .where( - (adv.voucher_type == voucher_type) - & (adv.voucher_no == voucher_no) - & (adv.against_voucher_type == against_voucher_type) - & (adv.against_voucher_no == against_voucher_no) - ) - .run() - ) - - def get_reconciliation_effect_date(reference, company, posting_date): reconciliation_takes_effect_on = frappe.get_cached_value( "Company", company, "reconciliation_takes_effect_on" @@ -968,7 +941,7 @@ def update_accounting_ledgers_after_reference_removal( # Advance Payment adv = qb.DocType("Advance Payment Ledger Entry") - adv_ple_update_query = ( + adv_ple = ( qb.update(adv) .set(adv.delinked, 1) .set(adv.modified, now()) @@ -979,8 +952,9 @@ def update_accounting_ledgers_after_reference_removal( ) if payment_name: - adv_ple_update_query = adv_ple_update_query.where(adv.voucher_no == payment_name) - adv_ple_update_query.run() + adv_ple = adv_ple.where(adv.voucher_no == payment_name) + + adv_ple.run() def remove_ref_from_advance_section(ref_doc: object = None): @@ -1018,6 +992,8 @@ def remove_ref_doc_link_from_jv( qb.update(jea) .set(jea.reference_type, None) .set(jea.reference_name, None) + .set(jea.advance_voucher_type, None) + .set(jea.advance_voucher_no, None) .set(jea.modified, now()) .set(jea.modified_by, frappe.session.user) .where((jea.reference_type == ref_type) & (jea.reference_name == ref_no)) @@ -1851,9 +1827,6 @@ def get_account_type(account): dr_or_cr = 0 account_type = None - advance_payment_doctypes = frappe.get_hooks("advance_payment_receivable_doctypes") + frappe.get_hooks( - "advance_payment_payable_doctypes" - ) for gle in gl_entries: if gle.account in receivable_or_payable_accounts: account_type = get_account_type(gle.account) @@ -1868,17 +1841,11 @@ def get_account_type(account): dr_or_cr *= -1 dr_or_cr_account_currency *= -1 - is_advance_doctype = gle.against_voucher_type in advance_payment_doctypes - against_voucher_type = ( gle.against_voucher_type if gle.against_voucher_type else gle.voucher_type ) against_voucher_no = gle.against_voucher if gle.against_voucher else gle.voucher_no - if is_advance_doctype: - against_voucher_type = gle.voucher_type - against_voucher_no = gle.voucher_no - ple = frappe._dict( doctype="Payment Ledger Entry", posting_date=gle.posting_date, @@ -1907,9 +1874,11 @@ def get_account_type(account): for dimension in dimensions_and_defaults[0]: ple[dimension.fieldname] = gle.get(dimension.fieldname) - if is_advance_doctype: + if gle.advance_voucher_no: # create advance entry - adv = get_advance_ledger_entry(gle, cancel, dr_or_cr_account_currency) + adv = get_advance_ledger_entry( + gle, against_voucher_type, against_voucher_no, dr_or_cr_account_currency, cancel + ) ple_map.append(adv) @@ -1918,21 +1887,27 @@ def get_account_type(account): return ple_map -def get_advance_ledger_entry(gle, cancel, amount): +def get_advance_ledger_entry(gle, against_voucher_type, against_voucher_no, amount, cancel): + event = ( + "Submit" + if (against_voucher_type == gle.voucher_type and against_voucher_no == gle.voucher_no) + else "Adjustment" + ) + if cancel: + event = "Cancel" + return frappe._dict( doctype="Advance Payment Ledger Entry", company=gle.company, voucher_type=gle.voucher_type, voucher_no=gle.voucher_no, voucher_detail_no=gle.voucher_detail_no, - against_voucher_type=gle.against_voucher_type, - against_voucher_no=gle.against_voucher, - currency=gle.account_currency, + against_voucher_type=gle.advance_voucher_type, + against_voucher_no=gle.advance_voucher_no, amount=amount, - unadjusted_amount=amount, - event="Cancel" if cancel else "Submit", + currency=gle.account_currency, + event=event, delinked=cancel, - remarks=gle.remarks, ) @@ -2023,6 +1998,7 @@ def delink_original_entry(pl_entry, partial_cancel=False): .where(adv.voucher_no == pl_entry.voucher_no) .where(adv.against_voucher_type == pl_entry.against_voucher_type) .where(adv.against_voucher_no == pl_entry.against_voucher_no) + .where(adv.event == pl_entry.event) .run() ) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index ab63e28874d4..4d0d08de95d9 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -8,7 +8,7 @@ import frappe from frappe import _, bold, qb, throw from frappe.model.workflow import get_workflow_name, is_transition_condition_satisfied -from frappe.query_builder import Case, Criterion, DocType +from frappe.query_builder import Criterion, DocType from frappe.query_builder.custom import ConstantColumn from frappe.query_builder.functions import Abs, Sum from frappe.utils import ( @@ -2212,59 +2212,30 @@ def get_stock_items(self): return stock_items def calculate_total_advance_from_ledger(self): - adv = frappe.qb.DocType("Advance Payment Ledger Entry") - return ( - frappe.qb.from_(adv) + ple = frappe.qb.DocType("Advance Payment Ledger Entry") + query = ( + qb.from_(ple) .select( - Abs(Sum(Case().when(adv.delinked == 0, adv.amount).else_(0))).as_("amount"), - adv.currency.as_("account_currency"), + Abs(Sum(ple.amount)).as_("amount"), ) - .where( - (adv.against_voucher_type == self.doctype) - & (adv.against_voucher_no == self.name) - & (adv.company == self.company) - ) - .groupby(adv.against_voucher_no, adv.against_voucher_type, adv.company) - .run(as_dict=True) + .where(ple.company == self.company) + .where(ple.delinked == 0) + .where(ple.against_voucher_type == self.doctype) + .where(ple.against_voucher_no == self.name) + .groupby(ple.against_voucher_no) ) + return query.run(as_dict=True) + def set_total_advance_paid(self): advance = self.calculate_total_advance_from_ledger() - advance_paid, order_total = None, None + advance_paid = 0 if advance: advance = advance[0] - advance_paid = flt(advance.amount, self.precision("advance_paid")) - formatted_advance_paid = fmt_money( - advance_paid, precision=self.precision("advance_paid"), currency=advance.account_currency - ) - - if advance.account_currency: - frappe.db.set_value( - self.doctype, self.name, "party_account_currency", advance.account_currency - ) - - if advance.account_currency == self.currency: - order_total = self.get("rounded_total") or self.grand_total - precision = "rounded_total" if self.get("rounded_total") else "grand_total" - else: - order_total = self.get("base_rounded_total") or self.base_grand_total - precision = "base_rounded_total" if self.get("base_rounded_total") else "base_grand_total" - - formatted_order_total = fmt_money( - order_total, precision=self.precision(precision), currency=advance.account_currency - ) - - if self.currency == self.company_currency and advance_paid > order_total: - frappe.throw( - _( - "Total advance ({0}) against Order {1} cannot be greater than the Grand Total ({2})" - ).format(formatted_advance_paid, self.name, formatted_order_total) - ) - - self.db_set("advance_paid", advance_paid) + self.db_set("advance_paid", advance_paid) self.set_advance_payment_status() def set_advance_payment_status(self): From c59b97d912e86a656ff13aa292e818210d677891 Mon Sep 17 00:00:00 2001 From: ljain112 Date: Tue, 29 Jul 2025 13:21:17 +0530 Subject: [PATCH 20/35] fix: delink payment entry on cancellation of payment entry --- erpnext/accounts/utils.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index 867189e848be..9c669c089994 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -947,11 +947,12 @@ def update_accounting_ledgers_after_reference_removal( .set(adv.delinked, 1) .set(adv.modified, now()) .set(adv.modified_by, frappe.session.user) + .where(adv.delinked == 0) .where( - (adv.against_voucher_type == ref_type) & (adv.against_voucher_no == ref_no) & (adv.delinked == 0) + ((adv.against_voucher_type == ref_type) & (adv.against_voucher_no == ref_no)) + | ((adv.voucher_type == ref_type) & (adv.voucher_no == ref_no)) ) ) - if payment_name: adv_ple = adv_ple.where(adv.voucher_no == payment_name) From e6b6452be552e6e5371b5718e11fcdaaf0e20eac Mon Sep 17 00:00:00 2001 From: ljain112 Date: Tue, 29 Jul 2025 13:21:34 +0530 Subject: [PATCH 21/35] refactor: use function to get advance doctypes --- .../patches/v15_0/create_advance_payment_ledger_records.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/patches/v15_0/create_advance_payment_ledger_records.py b/erpnext/patches/v15_0/create_advance_payment_ledger_records.py index 3706de2957f0..dc849191de8a 100644 --- a/erpnext/patches/v15_0/create_advance_payment_ledger_records.py +++ b/erpnext/patches/v15_0/create_advance_payment_ledger_records.py @@ -3,6 +3,8 @@ from frappe.query_builder import Case from frappe.utils import now_datetime +from erpnext.accounts.utils import get_advance_payment_doctypes + DOCTYPE = "Advance Payment Ledger Entry" FIELDS = [ @@ -29,9 +31,7 @@ def execute(): Create Advance Payment Ledger Entry for all Payments made against Sales / Purchase Orders """ frappe.db.truncate(DOCTYPE) - advance_doctpyes = frappe.get_hooks("advance_payment_receivable_doctypes") + frappe.get_hooks( - "advance_payment_payable_doctypes" - ) + advance_doctpyes = get_advance_payment_doctypes() make_advance_ledger_entries_for_payment_entries(advance_doctpyes) make_advance_ledger_entries_for_journal_entries(advance_doctpyes) From 2a95f3a2a933aeb727a430eedc2fd8acb1bfb92c Mon Sep 17 00:00:00 2001 From: ljain112 Date: Tue, 29 Jul 2025 14:17:37 +0530 Subject: [PATCH 22/35] fix: set party currency in advance doctype and remove unused import --- erpnext/accounts/utils.py | 2 +- erpnext/controllers/accounts_controller.py | 56 ++++++++++++++++------ 2 files changed, 42 insertions(+), 16 deletions(-) diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index 9c669c089994..eff28eeca2a3 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -11,7 +11,7 @@ from frappe.desk.reportview import build_match_conditions from frappe.model.meta import get_field_precision from frappe.query_builder import AliasedQuery, Case, Criterion, Table -from frappe.query_builder.functions import Count, IfNull, Max, Round, Sum +from frappe.query_builder.functions import Count, Max, Round, Sum from frappe.query_builder.utils import DocType from frappe.utils import ( add_days, diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 4d0d08de95d9..88baffbbdb4c 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -2212,31 +2212,57 @@ def get_stock_items(self): return stock_items def calculate_total_advance_from_ledger(self): - ple = frappe.qb.DocType("Advance Payment Ledger Entry") - query = ( - qb.from_(ple) - .select( - Abs(Sum(ple.amount)).as_("amount"), - ) - .where(ple.company == self.company) - .where(ple.delinked == 0) - .where(ple.against_voucher_type == self.doctype) - .where(ple.against_voucher_no == self.name) - .groupby(ple.against_voucher_no) + adv = frappe.qb.DocType("Advance Payment Ledger Entry") + return ( + qb.from_(adv) + .select(Abs(Sum(adv.amount)).as_("amount"), adv.currency.as_("account_currency")) + .where(adv.company == self.company) + .where(adv.delinked == 0) + .where(adv.against_voucher_type == self.doctype) + .where(adv.against_voucher_no == self.name) + .run(as_dict=True) ) - return query.run(as_dict=True) - def set_total_advance_paid(self): advance = self.calculate_total_advance_from_ledger() - advance_paid = 0 + advance_paid, order_total = 0, 0 if advance: advance = advance[0] + advance_paid = flt(advance.amount, self.precision("advance_paid")) + formatted_advance_paid = fmt_money( + advance_paid, precision=self.precision("advance_paid"), currency=advance.account_currency + ) + + if advance.account_currency: + frappe.db.set_value( + self.doctype, self.name, "party_account_currency", advance.account_currency + ) + + if advance.account_currency == self.currency: + order_total = self.get("rounded_total") or self.grand_total + + precision = "rounded_total" if self.get("rounded_total") else "grand_total" + + else: + order_total = self.get("base_rounded_total") or self.base_grand_total + + precision = "base_rounded_total" if self.get("base_rounded_total") else "base_grand_total" + + formatted_order_total = fmt_money( + order_total, precision=self.precision(precision), currency=advance.account_currency + ) + + if self.currency == self.company_currency and advance_paid > order_total: + frappe.throw( + _( + "Total advance ({0}) against Order {1} cannot be greater than the Grand Total ({2})" + ).format(formatted_advance_paid, self.name, formatted_order_total) + ) + self.db_set("advance_paid", advance_paid) - self.set_advance_payment_status() def set_advance_payment_status(self): new_status = None From f13d98fc7c77e03761efee90b3ad15e28efb7719 Mon Sep 17 00:00:00 2001 From: l0gesh29 Date: Tue, 29 Jul 2025 16:03:10 +0530 Subject: [PATCH 23/35] fix: include empty values in user permission --- erpnext/accounts/utils.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index 7097fbb5190b..2057717ab1c6 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -27,6 +27,7 @@ nowdate, ) from pypika import Order +from pypika.functions import Coalesce from pypika.terms import ExistsCriterion import erpnext @@ -2385,6 +2386,8 @@ def sync_auto_reconcile_config(auto_reconciliation_job_trigger: int = 15): def build_qb_match_conditions(doctype, user=None) -> list: match_filters = build_match_conditions(doctype, user, False) criterion = [] + apply_strict_user_permissions = frappe.get_system_settings("apply_strict_user_permissions") + if match_filters: from frappe import qb @@ -2393,6 +2396,12 @@ def build_qb_match_conditions(doctype, user=None) -> list: for filter in match_filters: for d, names in filter.items(): fieldname = d.lower().replace(" ", "_") - criterion.append(_dt[fieldname].isin(names)) + field = _dt[fieldname] + + cond = field.isin(names) + if not apply_strict_user_permissions: + cond = (Coalesce(field, "") == "") | field.isin(names) + + criterion.append(cond) return criterion From f7ee9ee967408f3355446bc474dd4a25f19a106d Mon Sep 17 00:00:00 2001 From: ljain112 Date: Tue, 29 Jul 2025 16:11:46 +0530 Subject: [PATCH 24/35] chore: added test case for reconciliation_effect_date --- .../test_payment_reconciliation.py | 61 +++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py index 57231ec5bdd7..a7f763619d1a 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py +++ b/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py @@ -1714,6 +1714,67 @@ def test_advance_payment_reconciliation_date(self): ) self.assertEqual(len(pl_entries), 3) + def test_advance_payment_reconciliation_date_for_older_date(self): + old_settings = frappe.db.get_value( + "Company", + self.company, + [ + "reconciliation_takes_effect_on", + "default_advance_paid_account", + "book_advance_payments_in_separate_party_account", + ], + as_dict=True, + ) + frappe.db.set_value( + "Company", + self.company, + { + "book_advance_payments_in_separate_party_account": 1, + "default_advance_paid_account": self.advance_payable_account, + "reconciliation_takes_effect_on": "Oldest Of Invoice Or Advance", + }, + ) + + self.supplier = "_Test Supplier" + + pi1 = self.create_purchase_invoice(qty=10, rate=100) + po = self.create_purchase_order(qty=10, rate=100) + + pay = get_payment_entry(po.doctype, po.name) + pay.paid_amount = 1000 + pay.save().submit() + + pr = frappe.new_doc("Payment Reconciliation") + pr.company = self.company + pr.party_type = "Supplier" + pr.party = self.supplier + pr.receivable_payable_account = get_party_account(pr.party_type, pr.party, pr.company) + pr.default_advance_account = self.advance_payable_account + pr.get_unreconciled_entries() + invoices = [x.as_dict() for x in pr.invoices] + payments = [x.as_dict() for x in pr.payments] + pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments})) + pr.allocation[0].allocated_amount = 100 + pr.reconcile() + + pay.reload() + self.assertEqual(getdate(pay.references[0].reconcile_effect_on), getdate(pi1.posting_date)) + + # test setting of date if not available + frappe.db.set_value("Payment Entry Reference", pay.references[1].name, "reconcile_effect_on", None) + pay.reload() + pay.cancel() + + pay.reload() + pi1.reload() + po.reload() + + self.assertEqual(getdate(pay.references[0].reconcile_effect_on), getdate(pi1.posting_date)) + pi1.cancel() + po.cancel() + + frappe.db.set_value("Company", self.company, old_settings) + def test_advance_payment_reconciliation_against_journal_for_customer(self): frappe.db.set_value( "Company", From cf70147c0d8e7324cc5261a59c398c34039f09dc Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 29 Jul 2025 16:26:57 +0530 Subject: [PATCH 25/35] fix: ignore is overridden by transaction.js upon clicking cancel which overrides with 'Serial and Batch Bundle' --- erpnext/buying/doctype/purchase_order/purchase_order.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.js b/erpnext/buying/doctype/purchase_order/purchase_order.js index b7d6c8d5ae00..c86600bee9b9 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.js +++ b/erpnext/buying/doctype/purchase_order/purchase_order.js @@ -12,7 +12,6 @@ erpnext.buying.setup_buying_controller(); frappe.ui.form.on("Purchase Order", { setup: function (frm) { - frm.ignore_doctypes_on_cancel_all = ["Unreconcile Payment", "Unreconcile Payment Entries"]; if (frm.doc.is_old_subcontracting_flow) { frm.set_query("reserve_warehouse", "supplied_items", function () { return { @@ -154,6 +153,10 @@ frappe.ui.form.on("Purchase Order", { }, onload: function (frm) { + var ignore_list = ["Unreconcile Payment", "Unreconcile Payment Entries"]; + frm.ignore_doctypes_on_cancel_all = Object.hasOwn(frm, "ignore_doctypes_on_cancel_all") + ? frm.ignore_doctypes_on_cancel_all.concat(ignore_list) + : ignore_list; set_schedule_date(frm); if (!frm.doc.transaction_date) { frm.set_value("transaction_date", frappe.datetime.get_today()); From c18d565d3e021d6eb58d48d6a73cc2461eb58192 Mon Sep 17 00:00:00 2001 From: Ayush Chaudhari Date: Tue, 29 Jul 2025 17:13:26 +0530 Subject: [PATCH 26/35] fix: change modified timestamp so migrations work --- .../doctype/job_card_scrap_item/job_card_scrap_item.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/manufacturing/doctype/job_card_scrap_item/job_card_scrap_item.json b/erpnext/manufacturing/doctype/job_card_scrap_item/job_card_scrap_item.json index 37fff696af9b..fdb8ec44bdc5 100644 --- a/erpnext/manufacturing/doctype/job_card_scrap_item/job_card_scrap_item.json +++ b/erpnext/manufacturing/doctype/job_card_scrap_item/job_card_scrap_item.json @@ -70,7 +70,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2024-03-27 13:09:57.323835", + "modified": "2025-07-29 13:09:57.323835", "modified_by": "Administrator", "module": "Manufacturing", "name": "Job Card Scrap Item", From 1deedc766c78b7ef2179d831df7d667860e9b4fa Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Tue, 29 Jul 2025 17:49:46 +0530 Subject: [PATCH 27/35] fix: serial no warehouse for backdated stock reco --- .../test_stock_reconciliation.py | 76 +++++++++++++++++++ erpnext/stock/serial_batch_bundle.py | 2 +- 2 files changed, 77 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py index e7aa5988f06e..4974fbb50633 100644 --- a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py @@ -1515,6 +1515,82 @@ def test_stock_reco_recalculate_qty_for_backdated_entry(self): self.assertTrue(len(stock_ledgers) == 2) + def test_serial_no_backdated_stock_reco(self): + from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry + + serial_item = self.make_item( + "Test Serial Item Stock Reco Backdated", + { + "is_stock_item": 1, + "has_serial_no": 1, + "serial_no_series": "TSISRB.####", + }, + ).name + + warehouse = "_Test Warehouse - _TC" + + se = make_stock_entry( + item_code=serial_item, + target=warehouse, + qty=1, + basic_rate=100, + use_serial_batch_fields=1, + ) + + serial_no = get_serial_nos_from_bundle(se.items[0].serial_and_batch_bundle)[0] + status = frappe.get_value( + "Serial No", + serial_no, + "status", + ) + + self.assertTrue(status == "Active") + + sr = create_stock_reconciliation( + item_code=serial_item, + warehouse=warehouse, + qty=1, + rate=200, + use_serial_batch_fields=1, + serial_no=serial_no, + ) + + sr.reload() + + status = frappe.get_value( + "Serial No", + serial_no, + "status", + ) + + self.assertTrue(status == "Active") + + make_stock_entry( + item_code=serial_item, + source=warehouse, + qty=1, + basic_rate=100, + use_serial_batch_fields=1, + ) + + status = frappe.get_value( + "Serial No", + serial_no, + "status", + ) + + self.assertFalse(status == "Active") + + sr.cancel() + + status = frappe.get_value( + "Serial No", + serial_no, + "status", + ) + + self.assertFalse(status == "Active") + def create_batch_item_with_batch(item_name, batch_id): batch_item_doc = create_item(item_name, is_stock_item=1) diff --git a/erpnext/stock/serial_batch_bundle.py b/erpnext/stock/serial_batch_bundle.py index b578ceea4c04..9b5dcffb1f7b 100644 --- a/erpnext/stock/serial_batch_bundle.py +++ b/erpnext/stock/serial_batch_bundle.py @@ -391,7 +391,7 @@ def set_warehouse_and_status_in_serial_nos(self): self.update_serial_no_status_warehouse(self.sle, serial_nos) def update_serial_no_status_warehouse(self, sle, serial_nos): - warehouse = self.warehouse if sle.actual_qty > 0 else None + warehouse = sle.warehouse if sle.actual_qty > 0 else None if isinstance(serial_nos, str): serial_nos = [serial_nos] From f6212f7b5154807dde59211759ae4c5e6b67b598 Mon Sep 17 00:00:00 2001 From: mithili Date: Tue, 29 Jul 2025 18:20:50 +0530 Subject: [PATCH 28/35] fix: set mandatory field for pos search fields --- .../doctype/pos_search_fields/pos_search_fields.json | 7 ++++--- erpnext/selling/page/point_of_sale/point_of_sale.py | 2 ++ 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/erpnext/accounts/doctype/pos_search_fields/pos_search_fields.json b/erpnext/accounts/doctype/pos_search_fields/pos_search_fields.json index 44555b562a2a..9f6b95ffef5c 100644 --- a/erpnext/accounts/doctype/pos_search_fields/pos_search_fields.json +++ b/erpnext/accounts/doctype/pos_search_fields/pos_search_fields.json @@ -19,13 +19,14 @@ "fieldname": "field", "fieldtype": "Select", "in_list_view": 1, - "label": "Field" + "label": "Field", + "reqd": 1 } ], "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2024-03-27 13:10:16.969895", + "modified": "2025-07-29 18:08:40.323579", "modified_by": "Administrator", "module": "Accounts", "name": "POS Search Fields", @@ -35,4 +36,4 @@ "sort_order": "DESC", "states": [], "track_changes": 1 -} \ No newline at end of file +} diff --git a/erpnext/selling/page/point_of_sale/point_of_sale.py b/erpnext/selling/page/point_of_sale/point_of_sale.py index fc8e834a4bdb..9b2b2ce9a61c 100644 --- a/erpnext/selling/page/point_of_sale/point_of_sale.py +++ b/erpnext/selling/page/point_of_sale/point_of_sale.py @@ -268,6 +268,8 @@ def add_search_fields_condition(search_term): search_fields = frappe.get_all("POS Search Fields", fields=["fieldname"]) if search_fields: for field in search_fields: + if not field.get("fieldname"): + continue condition += " or item.`{}` like {}".format( field["fieldname"], frappe.db.escape("%" + search_term + "%") ) From 3eda12ba4690c5b01309dbeda0ec70d09ed6dd24 Mon Sep 17 00:00:00 2001 From: ljain112 Date: Tue, 29 Jul 2025 18:34:27 +0530 Subject: [PATCH 29/35] fix: do not set cancel as event --- erpnext/accounts/utils.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index eff28eeca2a3..c218feda642d 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -1895,9 +1895,6 @@ def get_advance_ledger_entry(gle, against_voucher_type, against_voucher_no, amou if (against_voucher_type == gle.voucher_type and against_voucher_no == gle.voucher_no) else "Adjustment" ) - if cancel: - event = "Cancel" - return frappe._dict( doctype="Advance Payment Ledger Entry", company=gle.company, From 45323f40d5534ab447fc41ce9a19e335fcd2fd5e Mon Sep 17 00:00:00 2001 From: ljain112 Date: Tue, 29 Jul 2025 18:52:10 +0530 Subject: [PATCH 30/35] fix: remove validation and set_advance_payment_status --- erpnext/controllers/accounts_controller.py | 29 ++-------------------- 1 file changed, 2 insertions(+), 27 deletions(-) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 88baffbbdb4c..2d2703dec297 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -2225,44 +2225,19 @@ def calculate_total_advance_from_ledger(self): def set_total_advance_paid(self): advance = self.calculate_total_advance_from_ledger() - advance_paid, order_total = 0, 0 + advance_paid = 0 if advance: advance = advance[0] advance_paid = flt(advance.amount, self.precision("advance_paid")) - - formatted_advance_paid = fmt_money( - advance_paid, precision=self.precision("advance_paid"), currency=advance.account_currency - ) - if advance.account_currency: frappe.db.set_value( self.doctype, self.name, "party_account_currency", advance.account_currency ) - if advance.account_currency == self.currency: - order_total = self.get("rounded_total") or self.grand_total - - precision = "rounded_total" if self.get("rounded_total") else "grand_total" - - else: - order_total = self.get("base_rounded_total") or self.base_grand_total - - precision = "base_rounded_total" if self.get("base_rounded_total") else "base_grand_total" - - formatted_order_total = fmt_money( - order_total, precision=self.precision(precision), currency=advance.account_currency - ) - - if self.currency == self.company_currency and advance_paid > order_total: - frappe.throw( - _( - "Total advance ({0}) against Order {1} cannot be greater than the Grand Total ({2})" - ).format(formatted_advance_paid, self.name, formatted_order_total) - ) - self.db_set("advance_paid", advance_paid) + self.set_advance_payment_status() def set_advance_payment_status(self): new_status = None From 140698d676139d812d2a704be8aaeca0e45c7ca6 Mon Sep 17 00:00:00 2001 From: diptanilsaha Date: Tue, 29 Jul 2025 18:16:58 +0530 Subject: [PATCH 31/35] feat: list view on item selector in pos --- erpnext/public/scss/point-of-sale.scss | 107 ++++++++++++++++-- .../page/point_of_sale/pos_item_selector.js | 33 +++++- 2 files changed, 128 insertions(+), 12 deletions(-) diff --git a/erpnext/public/scss/point-of-sale.scss b/erpnext/public/scss/point-of-sale.scss index 790822ab8886..7a42f989bf7f 100644 --- a/erpnext/public/scss/point-of-sale.scss +++ b/erpnext/public/scss/point-of-sale.scss @@ -113,13 +113,108 @@ > .items-container { display: grid; - grid-template-columns: repeat(4, minmax(0, 1fr)); - gap: var(--margin-lg); - padding: var(--padding-lg); padding-top: var(--padding-xs); overflow-y: scroll; overflow-x: hidden; + &.show-item-image { + grid-template-columns: repeat(4, minmax(0, 1fr)); + gap: var(--margin-lg); + padding: var(--padding-lg); + + > .item-wrapper { + border-radius: var(--border-radius-md); + box-shadow: var(--shadow-base); + + &:hover { + transform: scale(1.02, 1.02); + } + } + } + + &.hide-item-image { + display: flex; + flex-direction: column; + padding-left: 1.5em; + padding-right: 1.5em; + padding-top: 0; + + > .list-column { + display: flex; + position: sticky; + top: 0; + z-index: 1; + background-color: var(--fg-color); + font-weight: bold; + flex-direction: row; + gap: 2rem; + align-items: center; + min-height: 3rem; + height: 3rem; + border-bottom: 1px solid var(--gray-300); + + > .column-name { + width: 55%; + text-align: left; + white-space: initial; + } + + > .column-price { + width: 15%; + } + + > .column-uom { + width: 15%; + text-align: center; + } + + > .column-qty-available { + width: 15%; + text-align: right; + } + } + + > .item-wrapper { + border-top: 1px solid var(--gray-300); + + &:hover { + background-color: var(--control-bg); + } + + .item-detail { + flex-direction: row; + gap: 2rem; + align-items: center; + min-height: 3rem; + height: 3rem; + + > .item-name, + .column-name { + width: 55%; + text-align: left; + white-space: initial; + } + + > .item-price, + .column-price { + width: 15%; + } + + > .item-uom, + .column-uom { + width: 15%; + text-align: center; + } + + > .item-qty-available, + .column-qty-available { + width: 15%; + text-align: right; + } + } + } + } + &:after { content: ""; display: block; @@ -128,14 +223,8 @@ > .item-wrapper { @extend .pointer-no-select; - border-radius: var(--border-radius-md); - box-shadow: var(--shadow-base); position: relative; - &:hover { - transform: scale(1.02, 1.02); - } - .item-qty-pill { position: absolute; display: flex; diff --git a/erpnext/selling/page/point_of_sale/pos_item_selector.js b/erpnext/selling/page/point_of_sale/pos_item_selector.js index b355b02f83a2..8d0641f85ed7 100644 --- a/erpnext/selling/page/point_of_sale/pos_item_selector.js +++ b/erpnext/selling/page/point_of_sale/pos_item_selector.js @@ -34,6 +34,9 @@ erpnext.PointOfSale.ItemSelector = class { this.$component = this.wrapper.find(".items-selector"); this.$items_container = this.$component.find(".items-container"); + + const show_hide_images = this.hide_images ? "hide-item-image" : "show-item-image"; + this.$items_container.addClass(show_hide_images); } async load_items_data() { @@ -73,12 +76,25 @@ erpnext.PointOfSale.ItemSelector = class { render_item_list(items) { this.$items_container.html(""); + if (this.hide_images) { + this.$items_container.append(this.render_item_list_column_header()); + } + items.forEach((item) => { const item_html = this.get_item_html(item); this.$items_container.append(item_html); }); } + render_item_list_column_header() { + return `
+
Name
+
Price
+
UOM
+
Quantity Available
+
`; + } + get_item_html(item) { const me = this; // eslint-disable-next-line no-unused-vars @@ -100,7 +116,8 @@ erpnext.PointOfSale.ItemSelector = class { } function get_item_image_html() { - if (!me.hide_images && item_image) { + if (me.hide_images) return ""; + if (item_image) { return `
${qty_to_display}
@@ -130,9 +147,19 @@ erpnext.PointOfSale.ItemSelector = class {
- ${frappe.ellipsis(item.item_name, 18)} + ${!me.hide_images ? frappe.ellipsis(item.item_name, 18) : item.item_name}
-
${format_currency(price_list_rate, item.currency, precision) || 0} / ${uom}
+ ${ + !me.hide_images + ? `
+ ${format_currency(price_list_rate, item.currency, precision) || 0} / ${uom} +
` + : ` +
${format_currency(price_list_rate, item.currency, precision) || 0}
+
${uom}
+
${qty_to_display || "Non stock item"}
+ ` + }
`; } From 99f7eb38d383c0e5c42d048235d4d324f34bb84d Mon Sep 17 00:00:00 2001 From: Ravibharathi <131471282+ravibharathi656@users.noreply.github.com> Date: Tue, 29 Jul 2025 20:35:12 +0530 Subject: [PATCH 32/35] fix: update advance paid amount on unreconcile --- .../test_unreconcile_payment.py | 61 ++++++++++++++++++- .../unreconcile_payment.py | 15 +++++ 2 files changed, 73 insertions(+), 3 deletions(-) diff --git a/erpnext/accounts/doctype/unreconcile_payment/test_unreconcile_payment.py b/erpnext/accounts/doctype/unreconcile_payment/test_unreconcile_payment.py index 3466b2733ac7..c3fd64d6dc16 100644 --- a/erpnext/accounts/doctype/unreconcile_payment/test_unreconcile_payment.py +++ b/erpnext/accounts/doctype/unreconcile_payment/test_unreconcile_payment.py @@ -9,6 +9,7 @@ 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.buying.doctype.purchase_order.test_purchase_order import create_purchase_order 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 @@ -17,6 +18,7 @@ class TestUnreconcilePayment(AccountsTestMixin, IntegrationTestCase): def setUp(self): self.create_company() self.create_customer() + self.create_supplier() self.create_usd_receivable_account() self.create_item() self.clear_old_entries() @@ -364,13 +366,13 @@ def test_05_unreconcile_order(self): # Assert 'Advance Paid' so.reload() pe.reload() - self.assertEqual(so.advance_paid, 100) + self.assertEqual(so.advance_paid, 0) self.assertEqual(len(pe.references), 0) self.assertEqual(pe.unallocated_amount, 100) pe.cancel() so.reload() - self.assertEqual(so.advance_paid, 100) + self.assertEqual(so.advance_paid, 0) def test_06_unreconcile_advance_from_payment_entry(self): self.enable_advance_as_liability() @@ -417,7 +419,7 @@ def test_06_unreconcile_advance_from_payment_entry(self): so2.reload() pe.reload() self.assertEqual(so1.advance_paid, 150) - self.assertEqual(so2.advance_paid, 110) + self.assertEqual(so2.advance_paid, 0) self.assertEqual(len(pe.references), 1) self.assertEqual(pe.unallocated_amount, 110) @@ -468,3 +470,56 @@ def test_07_adv_from_so_to_invoice(self): self.assertEqual(so.advance_paid, 1000) self.disable_advance_as_liability() + + def test_unreconcile_advance_from_journal_entry(self): + po = create_purchase_order( + company=self.company, + supplier=self.supplier, + item=self.item, + qty=1, + rate=100, + transaction_date=today(), + do_not_submit=False, + ) + + je = frappe.get_doc( + { + "doctype": "Journal Entry", + "company": self.company, + "voucher_type": "Journal Entry", + "posting_date": po.transaction_date, + "multi_currency": True, + "accounts": [ + { + "account": "Creditors - _TC", + "party_type": "Supplier", + "party": po.supplier, + "debit_in_account_currency": 100, + "is_advance": "Yes", + "reference_type": po.doctype, + "reference_name": po.name, + }, + {"account": "Cash - _TC", "credit_in_account_currency": 100}, + ], + } + ) + je.save().submit() + po.reload() + self.assertEqual(po.advance_paid, 100) + + unreconcile = frappe.get_doc( + { + "doctype": "Unreconcile Payment", + "company": self.company, + "voucher_type": je.doctype, + "voucher_no": je.name, + } + ) + unreconcile.add_references() + self.assertEqual(len(unreconcile.allocations), 1) + allocations = [x.reference_name for x in unreconcile.allocations] + self.assertEqual([po.name], allocations) + unreconcile.save().submit() + + po.reload() + self.assertEqual(po.advance_paid, 0) diff --git a/erpnext/accounts/doctype/unreconcile_payment/unreconcile_payment.py b/erpnext/accounts/doctype/unreconcile_payment/unreconcile_payment.py index e57b90f11f75..f7c826bf3fc2 100644 --- a/erpnext/accounts/doctype/unreconcile_payment/unreconcile_payment.py +++ b/erpnext/accounts/doctype/unreconcile_payment/unreconcile_payment.py @@ -86,10 +86,25 @@ def on_submit(self): alloc.reference_doctype, alloc.reference_name, alloc.account, alloc.party_type, alloc.party ) if doc.doctype in get_advance_payment_doctypes(): + self.make_advance_payment_ledger(alloc) doc.set_total_advance_paid() frappe.db.set_value("Unreconcile Payment Entries", alloc.name, "unlinked", True) + def make_advance_payment_ledger(self, alloc): + if alloc.allocated_amount > 0: + doc = frappe.new_doc("Advance Payment Ledger Entry") + doc.company = self.company + doc.voucher_type = self.voucher_type + doc.voucher_no = self.voucher_no + doc.against_voucher_type = alloc.reference_doctype + doc.against_voucher_no = alloc.reference_name + doc.amount = -1 * alloc.allocated_amount + doc.event = "Unreconcile" + doc.currency = alloc.account_currency + doc.flags.ignore_permissions = 1 + doc.save() + @frappe.whitelist() def doc_has_references(doctype: str | None = None, docname: str | None = None): From b9b3302b69b5d15bb9c4033893d526e67ddb13be Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Tue, 29 Jul 2025 21:58:14 +0530 Subject: [PATCH 33/35] Update CODEOWNERS --- CODEOWNERS | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/CODEOWNERS b/CODEOWNERS index 88802410520a..a0c68b877893 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -8,16 +8,16 @@ erpnext/assets/ @khushi8112 erpnext/regional @ruthra-kumar erpnext/selling @ruthra-kumar erpnext/support/ @ruthra-kumar -pos* +pos* @diptanilsaha -erpnext/buying/ @rohitwaghchaure +erpnext/buying/ @rohitwaghchaure @mihir-kandoi erpnext/maintenance/ @rohitwaghchaure -erpnext/manufacturing/ @rohitwaghchaure +erpnext/manufacturing/ @rohitwaghchaure @mihir-kandoi erpnext/quality_management/ @rohitwaghchaure -erpnext/stock/ @rohitwaghchaure -erpnext/subcontracting @rohitwaghchaure +erpnext/stock/ @rohitwaghchaure @mihir-kandoi +erpnext/subcontracting @mihir-kandoi -erpnext/controllers/ @ruthra-kumar @rohitwaghchaure +erpnext/controllers/ @ruthra-kumar @rohitwaghchaure @mihir-kandoi erpnext/patches/ @ruthra-kumar .github/ @ruthra-kumar From 6da0e1e48ae0ba28bdf400333ae70386e3abd5d2 Mon Sep 17 00:00:00 2001 From: ljain112 Date: Wed, 30 Jul 2025 11:50:06 +0530 Subject: [PATCH 34/35] chore: remove redundant code --- .../unreconcile_payment/unreconcile_payment.py | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/erpnext/accounts/doctype/unreconcile_payment/unreconcile_payment.py b/erpnext/accounts/doctype/unreconcile_payment/unreconcile_payment.py index 6c7951c09ae5..fc6961f7a938 100644 --- a/erpnext/accounts/doctype/unreconcile_payment/unreconcile_payment.py +++ b/erpnext/accounts/doctype/unreconcile_payment/unreconcile_payment.py @@ -74,20 +74,6 @@ def on_submit(self): frappe.db.set_value("Unreconcile Payment Entries", alloc.name, "unlinked", True) - def make_advance_payment_ledger(self, alloc): - if alloc.allocated_amount > 0: - doc = frappe.new_doc("Advance Payment Ledger Entry") - doc.company = self.company - doc.voucher_type = self.voucher_type - doc.voucher_no = self.voucher_no - doc.against_voucher_type = alloc.reference_doctype - doc.against_voucher_no = alloc.reference_name - doc.amount = -1 * alloc.allocated_amount - doc.event = "Unreconcile" - doc.currency = alloc.account_currency - doc.flags.ignore_permissions = 1 - doc.save() - @frappe.whitelist() def doc_has_references(doctype: str | None = None, docname: str | None = None): From b2bd5808ac6da344ec53781c77e52dda3921f433 Mon Sep 17 00:00:00 2001 From: ljain112 Date: Wed, 30 Jul 2025 11:51:39 +0530 Subject: [PATCH 35/35] chore: remove unused import --- .../accounts/doctype/unreconcile_payment/unreconcile_payment.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/unreconcile_payment/unreconcile_payment.py b/erpnext/accounts/doctype/unreconcile_payment/unreconcile_payment.py index fc6961f7a938..d136768f0260 100644 --- a/erpnext/accounts/doctype/unreconcile_payment/unreconcile_payment.py +++ b/erpnext/accounts/doctype/unreconcile_payment/unreconcile_payment.py @@ -7,7 +7,7 @@ from frappe import _, qb from frappe.model.document import Document from frappe.query_builder import Criterion -from frappe.query_builder.functions import Abs, IfNull, Sum +from frappe.query_builder.functions import Abs, Sum from frappe.utils.data import comma_and from erpnext.accounts.utils import (