From 17fb287cd978416dcaf498b80b0d733dd4e66cab Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Sat, 2 Mar 2024 19:29:58 +0530 Subject: [PATCH 1/5] refactor: support payment against reverse payment reconciliation (cherry picked from commit 5f15297f28d02289746946f46366044bc2b18c8e) --- .../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 c031be53d65..87fb8c16263 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -526,9 +526,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": @@ -1191,6 +1191,7 @@ class PaymentEntry(AccountsController): "Journal Entry", "Sales Order", "Purchase Order", + "Payment Entry", ): self.add_advance_gl_for_reference(gl_entries, ref) @@ -1213,7 +1214,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 @@ -1660,7 +1663,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 1bf1acee70d..2c4952a0c66 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 558eeaa6d35..0e6c041d24d 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -1015,7 +1015,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 @@ -1026,7 +1026,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 1762f9919a6168f4f6f9d97189ae2ad34986a38e Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Sun, 10 Mar 2024 19:51:41 +0530 Subject: [PATCH 2/5] test: reverse payment reconciliation (cherry picked from commit 6d9074d5858bd893d24a973dc3f86efbd328f33a) # Conflicts: # erpnext/accounts/doctype/payment_entry/test_payment_entry.py --- .../payment_entry/test_payment_entry.py | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py index 04066666c9e..754aa09845c 100644 --- a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py @@ -1475,6 +1475,7 @@ class TestPaymentEntry(FrappeTestCase): for field in ["account", "debit", "credit"]: self.assertEqual(self.expected_gle[row][field], gl_entries[row][field]) +<<<<<<< HEAD def test_outstanding_invoices_api(self): """ Test if `get_outstanding_reference_documents` fetches invoices in the right order. @@ -1513,6 +1514,45 @@ class TestPaymentEntry(FrappeTestCase): self.assertEqual(references[2].voucher_no, si2.name) self.assertEqual(references[1].payment_term, "Basic Amount Receivable") self.assertEqual(references[2].payment_term, "Tax Receivable") +======= + 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) +>>>>>>> 6d9074d585 (test: reverse payment reconciliation) def create_payment_entry(**args): From 7ea1edc4ffb4b0b382509a2f9a6b1124062f2cae Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 11 Mar 2024 12:32:31 +0530 Subject: [PATCH 3/5] test: advance payment reconciliation against payment 'advance' payments booked in separate party account (cherry picked from commit 2a08072443caaccc2798464f0a375db29543e9c1) --- .../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 754aa09845c..3979a6d52cf 100644 --- a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py @@ -1554,6 +1554,128 @@ class TestPaymentEntry(FrappeTestCase): self.assertEqual(len(pr.payments), 0) >>>>>>> 6d9074d585 (test: reverse payment reconciliation) + 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 d82b1fc6009f0d69b1e04d4ba9eda1ea0aae777d Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 15 Mar 2024 12:08:32 +0530 Subject: [PATCH 4/5] refactor(test): generate and use unique party (cherry picked from commit a3d4aff29c61f41629136d93a13b74687ed13d36) --- .../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 3979a6d52cf..c0e425a912a 100644 --- a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py @@ -1516,9 +1516,10 @@ class TestPaymentEntry(FrappeTestCase): self.assertEqual(references[2].payment_term, "Tax Receivable") ======= 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", @@ -1527,7 +1528,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", @@ -1537,7 +1538,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) @@ -1558,6 +1559,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", @@ -1576,7 +1578,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, @@ -1587,7 +1589,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", @@ -1599,7 +1601,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() From ed4c8461d380f25187a3896a2b44e9eef4dca32c Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 15 Mar 2024 20:16:28 +0530 Subject: [PATCH 5/5] chore: resolve conflict --- erpnext/accounts/doctype/payment_entry/test_payment_entry.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py index c0e425a912a..2b226e1b241 100644 --- a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py @@ -1475,7 +1475,6 @@ class TestPaymentEntry(FrappeTestCase): for field in ["account", "debit", "credit"]: self.assertEqual(self.expected_gle[row][field], gl_entries[row][field]) -<<<<<<< HEAD def test_outstanding_invoices_api(self): """ Test if `get_outstanding_reference_documents` fetches invoices in the right order. @@ -1514,7 +1513,7 @@ class TestPaymentEntry(FrappeTestCase): self.assertEqual(references[2].voucher_no, si2.name) self.assertEqual(references[1].payment_term, "Basic Amount Receivable") self.assertEqual(references[2].payment_term, "Tax Receivable") -======= + def test_reverse_payment_reconciliation(self): customer = create_customer(frappe.generate_hash(length=10), "INR") pe = create_payment_entry( @@ -1553,7 +1552,6 @@ class TestPaymentEntry(FrappeTestCase): pr.reconcile() self.assertEqual(len(pr.invoices), 0) self.assertEqual(len(pr.payments), 0) ->>>>>>> 6d9074d585 (test: reverse payment reconciliation) def test_advance_reverse_payment_reconciliation(self): from erpnext.accounts.doctype.account.test_account import create_account