diff --git a/erpnext/accounts/doctype/payment_ledger_entry/test_payment_ledger_entry.py b/erpnext/accounts/doctype/payment_ledger_entry/test_payment_ledger_entry.py index dd3a936f220..ef3daf6bde0 100644 --- a/erpnext/accounts/doctype/payment_ledger_entry/test_payment_ledger_entry.py +++ b/erpnext/accounts/doctype/payment_ledger_entry/test_payment_ledger_entry.py @@ -3,8 +3,9 @@ import frappe from frappe import qb +from frappe.query_builder.functions import Count, Sum from frappe.tests.utils import FrappeTestCase, change_settings -from frappe.utils import nowdate +from frappe.utils import add_days, nowdate from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry from erpnext.accounts.doctype.payment_entry.test_payment_entry import create_payment_entry @@ -94,6 +95,7 @@ class TestPaymentLedgerEntry(FrappeTestCase): posting_date = nowdate() sinv = create_sales_invoice( + posting_date=posting_date, qty=qty, rate=rate, company=self.company, @@ -535,3 +537,82 @@ class TestPaymentLedgerEntry(FrappeTestCase): # with references removed, deletion should be possible so.delete() self.assertRaises(frappe.DoesNotExistError, frappe.get_doc, so.doctype, so.name) + + @change_settings( + "Accounts Settings", + {"enable_immutable_ledger": 1}, + ) + def test_reverse_entries_on_cancel_for_immutable_ledger(self): + invoice_posting_date = add_days(nowdate(), -5) + gle = qb.DocType("GL Entry") + ple = qb.DocType("Payment Ledger Entry") + + si = self.create_sales_invoice(qty=1, rate=100, posting_date=invoice_posting_date) + + gles_before = ( + qb.from_(gle) + .select( + Count(gle.name), + ) + .where((gle.voucher_type == si.doctype) & (gle.voucher_no == si.name) & (gle.is_cancelled == 0)) + .run()[0][0] + ) + ples_before = ( + qb.from_(ple) + .select( + Count(ple.name), + ) + .where((ple.voucher_type == si.doctype) & (ple.voucher_no == si.name) & (ple.delinked.eq(0))) + .run()[0][0] + ) + + si.cancel() + + gles_after = ( + qb.from_(gle) + .select(Count(gle.account)) + .where((gle.voucher_type == si.doctype) & (gle.voucher_no == si.name) & (gle.is_cancelled == 0)) + .run()[0][0] + ) + self.assertEqual(gles_after, gles_before * 2) + + ples_after = ( + qb.from_(ple) + .select( + Count(ple.name), + ) + .where((ple.voucher_type == si.doctype) & (ple.voucher_no == si.name) & (ple.delinked.eq(0))) + .run()[0][0] + ) + self.assertEqual(ples_after, ples_before * 2) + + # assert debit/credit are reversed + gl_entries = ( + qb.from_(gle) + .select(gle.account, Sum(gle.debit).as_("total_debit"), Sum(gle.credit).as_("total_credit")) + .where((gle.voucher_type == si.doctype) & (gle.voucher_no == si.name) & (gle.is_cancelled == 0)) + .groupby(gle.account) + .run(as_dict=True) + ) + for gl in gl_entries: + with self.subTest(gl=gl): + self.assertEqual(gl.total_debit, gl.total_credit) + + # assert amounts are reversed + pl_entries = ( + qb.from_(ple) + .select(ple.account, Sum(ple.amount).as_("total_amount")) + .where((ple.voucher_type == si.doctype) & (ple.voucher_no == si.name) & (ple.delinked == 0)) + .groupby(ple.account) + .run(as_dict=True) + ) + for pl in pl_entries: + with self.subTest(pl=pl): + self.assertEqual(pl.total_amount, 0) + + self.assertFalse( + frappe.db.exists( + "Payment Ledger Entry", + {"voucher_type": si.doctype, "voucher_no": si.name, "delinked": 1}, + ) + ) diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index 930f632fbf7..557afc865fd 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -1934,8 +1934,9 @@ def create_payment_ledger_entry( ple = frappe.get_doc(entry) if cancel: - delink_original_entry(ple, partial_cancel=partial_cancel) - if is_immutable_ledger_enabled(): + if not is_immutable_ledger_enabled(): + delink_original_entry(ple, partial_cancel=partial_cancel) + else: ple.delinked = 0 ple.posting_date = frappe.form_dict.get("posting_date") or getdate() @@ -2027,6 +2028,7 @@ def delink_original_entry(pl_entry, partial_cancel=False): qb.update(ple) .set(ple.modified, now()) .set(ple.modified_by, frappe.session.user) + .set(ple.delinked, True) .where( (ple.company == pl_entry.company) & (ple.account_type == pl_entry.account_type) @@ -2043,9 +2045,6 @@ def delink_original_entry(pl_entry, partial_cancel=False): if partial_cancel: query = query.where(ple.voucher_detail_no == pl_entry.voucher_detail_no) - if not is_immutable_ledger_enabled(): - query = query.set(ple.delinked, True) - query.run()