From 5f15297f28d02289746946f46366044bc2b18c8e Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Sat, 2 Mar 2024 19:29:58 +0530 Subject: [PATCH 1/4] refactor: support payment against reverse payment reconciliation --- .../accounts/doctype/payment_entry/payment_entry.py | 11 +++++++---- .../payment_reconciliation/payment_reconciliation.py | 7 ++++++- erpnext/accounts/utils.py | 4 ++-- 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 95bb1884009..8a5d2c6c690 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -611,9 +611,9 @@ class PaymentEntry(AccountsController): def get_valid_reference_doctypes(self): if self.party_type == "Customer": - return ("Sales Order", "Sales Invoice", "Journal Entry", "Dunning") + return ("Sales Order", "Sales Invoice", "Journal Entry", "Dunning", "Payment Entry") elif self.party_type == "Supplier": - return ("Purchase Order", "Purchase Invoice", "Journal Entry") + return ("Purchase Order", "Purchase Invoice", "Journal Entry", "Payment Entry") elif self.party_type == "Shareholder": return ("Journal Entry",) elif self.party_type == "Employee": @@ -1279,6 +1279,7 @@ class PaymentEntry(AccountsController): "Journal Entry", "Sales Order", "Purchase Order", + "Payment Entry", ): self.add_advance_gl_for_reference(gl_entries, ref) @@ -1301,7 +1302,9 @@ class PaymentEntry(AccountsController): if getdate(posting_date) < getdate(self.posting_date): posting_date = self.posting_date - dr_or_cr = "credit" if invoice.reference_doctype in ["Sales Invoice", "Sales Order"] else "debit" + dr_or_cr = ( + "credit" if invoice.reference_doctype in ["Sales Invoice", "Payment Entry"] else "debit" + ) args_dict["account"] = invoice.account args_dict[dr_or_cr] = invoice.allocated_amount args_dict[dr_or_cr + "_in_account_currency"] = invoice.allocated_amount @@ -1751,7 +1754,7 @@ def get_outstanding_reference_documents(args, validate=False): outstanding_invoices = get_outstanding_invoices( args.get("party_type"), args.get("party"), - party_account, + [party_account], common_filter=common_filter, posting_date=posting_and_due_date, min_outstanding=args.get("outstanding_amt_greater_than"), diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py index 972ce26f34a..dcb1a16dba4 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py +++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py @@ -340,10 +340,15 @@ class PaymentReconciliation(Document): self.build_qb_filter_conditions(get_invoices=True) + accounts = [self.receivable_payable_account] + + if self.default_advance_account: + accounts.append(self.default_advance_account) + non_reconciled_invoices = get_outstanding_invoices( self.party_type, self.party, - self.receivable_payable_account, + accounts, common_filter=self.common_filter_conditions, posting_date=self.ple_posting_date_filter, min_outstanding=self.minimum_invoice_amount if self.minimum_invoice_amount else None, diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index 0755f2e9e84..02012ad09e4 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -1027,7 +1027,7 @@ def get_outstanding_invoices( if account: root_type, account_type = frappe.get_cached_value( - "Account", account, ["root_type", "account_type"] + "Account", account[0], ["root_type", "account_type"] ) party_account_type = "Receivable" if root_type == "Asset" else "Payable" party_account_type = account_type or party_account_type @@ -1038,7 +1038,7 @@ def get_outstanding_invoices( common_filter = common_filter or [] common_filter.append(ple.account_type == party_account_type) - common_filter.append(ple.account == account) + common_filter.append(ple.account.isin(account)) common_filter.append(ple.party_type == party_type) common_filter.append(ple.party == party) From 6d9074d5858bd893d24a973dc3f86efbd328f33a Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Sun, 10 Mar 2024 19:51:41 +0530 Subject: [PATCH 2/4] test: reverse payment reconciliation --- .../payment_entry/test_payment_entry.py | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py index 5a014b85c99..3ab0c20e3be 100644 --- a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py @@ -1514,6 +1514,44 @@ class TestPaymentEntry(FrappeTestCase): for field in ["account", "debit", "credit"]: self.assertEqual(self.expected_gle[row][field], gl_entries[row][field]) + def test_reverse_payment_reconciliation(self): + pe = create_payment_entry( + party_type="Customer", + party="_Test Customer", + payment_type="Receive", + paid_from="Debtors - _TC", + paid_to="_Test Cash - _TC", + ) + pe.submit() + + reverse_pe = create_payment_entry( + party_type="Customer", + party="_Test Customer", + payment_type="Pay", + paid_from="_Test Cash - _TC", + paid_to="Debtors - _TC", + ) + reverse_pe.submit() + + pr = frappe.get_doc("Payment Reconciliation") + pr.company = "_Test Company" + pr.party_type = "Customer" + pr.party = "_Test Customer" + pr.receivable_payable_account = "Debtors - _TC" + pr.get_unreconciled_entries() + self.assertEqual(len(pr.invoices), 1) + self.assertEqual(len(pr.payments), 1) + + self.assertEqual(reverse_pe.name, pr.invoices[0].invoice_number) + self.assertEqual(pe.name, pr.payments[0].reference_name) + + invoices = [x.as_dict() for x in pr.invoices] + payments = [pr.payments[0].as_dict()] + pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments})) + pr.reconcile() + self.assertEqual(len(pr.invoices), 0) + self.assertEqual(len(pr.payments), 0) + def create_payment_entry(**args): payment_entry = frappe.new_doc("Payment Entry") From 2a08072443caaccc2798464f0a375db29543e9c1 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 11 Mar 2024 12:32:31 +0530 Subject: [PATCH 3/4] test: advance payment reconciliation against payment 'advance' payments booked in separate party account --- .../payment_entry/test_payment_entry.py | 122 ++++++++++++++++++ 1 file changed, 122 insertions(+) diff --git a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py index 3ab0c20e3be..be0868727cd 100644 --- a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py @@ -1552,6 +1552,128 @@ class TestPaymentEntry(FrappeTestCase): self.assertEqual(len(pr.invoices), 0) self.assertEqual(len(pr.payments), 0) + def test_advance_reverse_payment_reconciliation(self): + from erpnext.accounts.doctype.account.test_account import create_account + + company = "_Test Company" + advance_account = create_account( + parent_account="Current Assets - _TC", + account_name="Advances Received", + company=company, + account_type="Receivable", + ) + + frappe.db.set_value( + "Company", + company, + { + "book_advance_payments_in_separate_party_account": 1, + "default_advance_received_account": advance_account, + }, + ) + # Reverse Payment(essentially an Invoice) + reverse_pe = create_payment_entry( + party_type="Customer", + party="_Test Customer", + payment_type="Pay", + paid_from="_Test Cash - _TC", + paid_to=advance_account, + ) + reverse_pe.save() # use save() to trigger set_liability_account() + reverse_pe.submit() + + # Advance Payment + pe = create_payment_entry( + party_type="Customer", + party="_Test Customer", + payment_type="Receive", + paid_from=advance_account, + paid_to="_Test Cash - _TC", + ) + pe.save() # use save() to trigger set_liability_account() + pe.submit() + + # Partially reconcile advance against invoice + pr = frappe.get_doc("Payment Reconciliation") + pr.company = company + pr.party_type = "Customer" + pr.party = "_Test Customer" + pr.receivable_payable_account = "Debtors - _TC" + pr.default_advance_account = advance_account + pr.get_unreconciled_entries() + + self.assertEqual(len(pr.invoices), 1) + self.assertEqual(len(pr.payments), 1) + + invoices = [x.as_dict() for x in pr.get("invoices")] + payments = [x.as_dict() for x in pr.get("payments")] + pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments})) + pr.allocation[0].allocated_amount = 400 + pr.reconcile() + + # assert General and Payment Ledger entries post partial reconciliation + self.expected_gle = [ + {"account": "Debtors - _TC", "debit": 0.0, "credit": 400.0}, + {"account": advance_account, "debit": 400.0, "credit": 0.0}, + {"account": advance_account, "debit": 0.0, "credit": 1000.0}, + {"account": "_Test Cash - _TC", "debit": 1000.0, "credit": 0.0}, + ] + self.expected_ple = [ + { + "account": advance_account, + "voucher_no": pe.name, + "against_voucher_no": pe.name, + "amount": -1000.0, + }, + { + "account": "Debtors - _TC", + "voucher_no": pe.name, + "against_voucher_no": reverse_pe.name, + "amount": -400.0, + }, + { + "account": advance_account, + "voucher_no": pe.name, + "against_voucher_no": pe.name, + "amount": 400.0, + }, + ] + self.voucher_no = pe.name + self.check_gl_entries() + self.check_pl_entries() + + # Unreconcile + unrecon = ( + frappe.get_doc( + { + "doctype": "Unreconcile Payment", + "company": company, + "voucher_type": pe.doctype, + "voucher_no": pe.name, + "allocations": [{"reference_doctype": reverse_pe.doctype, "reference_name": reverse_pe.name}], + } + ) + .save() + .submit() + ) + + # assert General and Payment Ledger entries post unreconciliation + self.expected_gle = [ + {"account": advance_account, "debit": 0.0, "credit": 1000.0}, + {"account": "_Test Cash - _TC", "debit": 1000.0, "credit": 0.0}, + ] + self.expected_ple = [ + { + "account": advance_account, + "voucher_no": pe.name, + "against_voucher_no": pe.name, + "amount": -1000.0, + }, + ] + self.voucher_no = pe.name + self.check_gl_entries() + self.check_pl_entries() + def create_payment_entry(**args): payment_entry = frappe.new_doc("Payment Entry") From a3d4aff29c61f41629136d93a13b74687ed13d36 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 15 Mar 2024 12:08:32 +0530 Subject: [PATCH 4/4] refactor(test): generate and use unique party --- .../doctype/payment_entry/test_payment_entry.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py index be0868727cd..6323e4c42a1 100644 --- a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py @@ -1515,9 +1515,10 @@ class TestPaymentEntry(FrappeTestCase): self.assertEqual(self.expected_gle[row][field], gl_entries[row][field]) def test_reverse_payment_reconciliation(self): + customer = create_customer(frappe.generate_hash(length=10), "INR") pe = create_payment_entry( party_type="Customer", - party="_Test Customer", + party=customer, payment_type="Receive", paid_from="Debtors - _TC", paid_to="_Test Cash - _TC", @@ -1526,7 +1527,7 @@ class TestPaymentEntry(FrappeTestCase): reverse_pe = create_payment_entry( party_type="Customer", - party="_Test Customer", + party=customer, payment_type="Pay", paid_from="_Test Cash - _TC", paid_to="Debtors - _TC", @@ -1536,7 +1537,7 @@ class TestPaymentEntry(FrappeTestCase): pr = frappe.get_doc("Payment Reconciliation") pr.company = "_Test Company" pr.party_type = "Customer" - pr.party = "_Test Customer" + pr.party = customer pr.receivable_payable_account = "Debtors - _TC" pr.get_unreconciled_entries() self.assertEqual(len(pr.invoices), 1) @@ -1556,6 +1557,7 @@ class TestPaymentEntry(FrappeTestCase): from erpnext.accounts.doctype.account.test_account import create_account company = "_Test Company" + customer = create_customer(frappe.generate_hash(length=10), "INR") advance_account = create_account( parent_account="Current Assets - _TC", account_name="Advances Received", @@ -1574,7 +1576,7 @@ class TestPaymentEntry(FrappeTestCase): # Reverse Payment(essentially an Invoice) reverse_pe = create_payment_entry( party_type="Customer", - party="_Test Customer", + party=customer, payment_type="Pay", paid_from="_Test Cash - _TC", paid_to=advance_account, @@ -1585,7 +1587,7 @@ class TestPaymentEntry(FrappeTestCase): # Advance Payment pe = create_payment_entry( party_type="Customer", - party="_Test Customer", + party=customer, payment_type="Receive", paid_from=advance_account, paid_to="_Test Cash - _TC", @@ -1597,7 +1599,7 @@ class TestPaymentEntry(FrappeTestCase): pr = frappe.get_doc("Payment Reconciliation") pr.company = company pr.party_type = "Customer" - pr.party = "_Test Customer" + pr.party = customer pr.receivable_payable_account = "Debtors - _TC" pr.default_advance_account = advance_account pr.get_unreconciled_entries()