diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 3e17a07c5c18..c1f930e5dd41 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -357,7 +357,7 @@ erpnext.patches.v14_0.clear_reconciliation_values_from_singles erpnext.patches.v14_0.update_total_asset_cost_field erpnext.patches.v14_0.create_accounting_dimensions_in_reconciliation_tool erpnext.patches.v14_0.update_flag_for_return_invoices #2024-03-22 -erpnext.patches.v14_0.update_pos_return_ledger_entries +erpnext.patches.v14_0.update_pos_return_ledger_entries #2024-08-16 # 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/v14_0/update_pos_return_ledger_entries.py b/erpnext/patches/v14_0/update_pos_return_ledger_entries.py index 60e867d1bcb5..6a6d15e0b34a 100644 --- a/erpnext/patches/v14_0/update_pos_return_ledger_entries.py +++ b/erpnext/patches/v14_0/update_pos_return_ledger_entries.py @@ -1,8 +1,64 @@ import frappe from frappe import qb +from erpnext.accounts.utils import update_voucher_outstanding -def execute(): + +def get_valid_against_voucher_ref(pos_returns): + sinv = qb.DocType("Sales Invoice") + res = ( + qb.from_(sinv) + .select(sinv.name, sinv.return_against) + .where(sinv.name.isin(pos_returns) & sinv.return_against.notnull()) + .orderby(sinv.name) + .run(as_dict=True) + ) + return res + + +def build_dict_of_valid_against_reference(pos_returns): + _against_ref_dict = frappe._dict() + res = get_valid_against_voucher_ref(pos_returns) + for x in res: + _against_ref_dict[x.name] = x.return_against + return _against_ref_dict + + +def fix_incorrect_against_voucher_ref(affected_pos_returns): + if affected_pos_returns: + valid_against_voucher_dict = build_dict_of_valid_against_reference(affected_pos_returns) + + gle = qb.DocType("GL Entry") + gles_with_invalid_against = ( + qb.from_(gle) + .select(gle.name, gle.voucher_no) + .where( + gle.voucher_no.isin(affected_pos_returns) + & gle.against_voucher.notnull() + & gle.against_voucher.eq(gle.voucher_no) + & gle.is_cancelled.eq(0) + ) + .run(as_dict=True) + ) + # Update GL + if gles_with_invalid_against: + for gl in gles_with_invalid_against: + frappe.db.set_value( + "GL Entry", + gl.name, + "against_voucher", + valid_against_voucher_dict[gl.voucher_no], + ) + + # Update Payment Ledger + ple = qb.DocType("Payment Ledger Entry") + for x in affected_pos_returns: + qb.update(ple).set(ple.against_voucher_no, valid_against_voucher_dict[x]).where( + ple.voucher_no.eq(x) & ple.delinked.eq(0) + ).run() + + +def get_pos_returns_with_invalid_against_ref(): sinv = qb.DocType("Sales Invoice") pos_returns_without_self = ( qb.from_(sinv) @@ -32,30 +88,40 @@ def execute(): .run() ) - _vouchers = list(set([x[0] for x in gl_against_references])) - invoice_return_against = ( + if gl_against_references: + _vouchers = list(set([x[0] for x in gl_against_references])) + invoice_return_against = ( + qb.from_(sinv) + .select(sinv.name, sinv.return_against) + .where(sinv.name.isin(_vouchers) & sinv.return_against.notnull()) + .orderby(sinv.name) + .run() + ) + + valid_references = set(invoice_return_against) + actual_references = set(gl_against_references) + + invalid_references = actual_references.difference(valid_references) + if invalid_references: + return [x[0] for x in invalid_references] + return None + + +def update_outstanding_for_affected(affected_pos_returns): + if affected_pos_returns: + sinv = qb.DocType("Sales Invoice") + pos_with_accounts = ( qb.from_(sinv) - .select(sinv.name, sinv.return_against) - .where(sinv.name.isin(_vouchers) & sinv.return_against.notnull()) - .orderby(sinv.name) - .run() + .select(sinv.return_against, sinv.debit_to, sinv.customer) + .where(sinv.name.isin(affected_pos_returns)) + .run(as_dict=True) ) - valid_references = set(invoice_return_against) - actual_references = set(gl_against_references) + for x in pos_with_accounts: + update_voucher_outstanding("Sales Invoice", x.return_against, x.debit_to, "Customer", x.customer) - invalid_references = actual_references.difference(valid_references) - if invalid_references: - # Repost Accounting Ledger - pos_for_reposting = ( - qb.from_(sinv) - .select(sinv.company, sinv.name) - .where(sinv.name.isin([x[0] for x in invalid_references])) - .run(as_dict=True) - ) - for x in pos_for_reposting: - ral = frappe.new_doc("Repost Accounting Ledger") - ral.company = x.company - ral.append("vouchers", {"voucher_type": "Sales Invoice", "voucher_no": x.name}) - ral.save().submit() +def execute(): + affected_pos_returns = get_pos_returns_with_invalid_against_ref() + fix_incorrect_against_voucher_ref(affected_pos_returns) + update_outstanding_for_affected(affected_pos_returns)