diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 4482831f60e..cc64fd059b6 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -1485,6 +1485,10 @@ class SalesInvoice(SellingController): if skip_change_gl_entries and payment_mode.account == self.account_for_change_amount: payment_mode.base_amount -= flt(self.change_amount) + against_voucher = self.name + if self.is_return and self.return_against and not self.update_outstanding_for_self: + against_voucher = self.return_against + if payment_mode.base_amount: # POS, make payment entries gl_entries.append( @@ -1498,7 +1502,7 @@ class SalesInvoice(SellingController): "credit_in_account_currency": payment_mode.base_amount if self.party_account_currency == self.company_currency else payment_mode.amount, - "against_voucher": self.name, + "against_voucher": against_voucher, "against_voucher_type": self.doctype, "cost_center": self.cost_center, }, diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index ca3091fb79a..e94b27b0545 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -5,6 +5,7 @@ import copy import json import frappe +from frappe import qb from frappe.model.dynamic_links import get_dynamic_link_map from frappe.tests.utils import FrappeTestCase, change_settings from frappe.utils import add_days, flt, getdate, nowdate, today @@ -3849,6 +3850,40 @@ class TestSalesInvoice(FrappeTestCase): ] self.assertEqual(expected, actual) + def test_pos_returns_without_update_outstanding_for_self(self): + from erpnext.accounts.doctype.sales_invoice.sales_invoice import make_sales_return + + pos_profile = make_pos_profile() + pos_profile.payments = [] + pos_profile.append("payments", {"default": 1, "mode_of_payment": "Cash"}) + pos_profile.save() + + pos = create_sales_invoice(qty=10, do_not_save=True) + pos.is_pos = 1 + pos.pos_profile = pos_profile.name + pos.append( + "payments", {"mode_of_payment": "Bank Draft", "account": "_Test Bank - _TC", "amount": 500} + ) + pos.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 500}) + pos.save().submit() + + pos_return = make_sales_return(pos.name) + pos_return.update_outstanding_for_self = False + pos_return.save().submit() + + gle = qb.DocType("GL Entry") + res = ( + qb.from_(gle) + .select(gle.against_voucher) + .distinct() + .where( + gle.is_cancelled.eq(0) & gle.voucher_no.eq(pos_return.name) & gle.against_voucher.notnull() + ) + .run(as_list=1) + ) + self.assertEqual(len(res), 1) + self.assertEqual(res[0][0], pos_return.return_against) + def set_advance_flag(company, flag, default_account): frappe.db.set_value( diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 0268e155957..c29344c81b0 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -359,6 +359,7 @@ erpnext.patches.v15_0.allow_on_submit_dimensions_for_repostable_doctypes 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 # 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 new file mode 100644 index 00000000000..60e867d1bcb --- /dev/null +++ b/erpnext/patches/v14_0/update_pos_return_ledger_entries.py @@ -0,0 +1,61 @@ +import frappe +from frappe import qb + + +def execute(): + sinv = qb.DocType("Sales Invoice") + pos_returns_without_self = ( + qb.from_(sinv) + .select(sinv.name) + .where( + sinv.docstatus.eq(1) + & sinv.is_pos.eq(1) + & sinv.is_return.eq(1) + & sinv.return_against.notnull() + & sinv.update_outstanding_for_self.eq(0) + ) + .run() + ) + if pos_returns_without_self: + pos_returns_without_self = [x[0] for x in pos_returns_without_self] + + gle = qb.DocType("GL Entry") + gl_against_references = ( + qb.from_(gle) + .select(gle.voucher_no, gle.against_voucher) + .where( + gle.voucher_no.isin(pos_returns_without_self) + & gle.against_voucher.notnull() + & gle.against_voucher.eq(gle.voucher_no) + & gle.is_cancelled.eq(0) + ) + .run() + ) + + _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: + # 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()