From 74838394183432a4aa2cf839e844aa1735727e35 Mon Sep 17 00:00:00 2001 From: ljain112 Date: Mon, 18 Nov 2024 20:08:11 +0530 Subject: [PATCH 1/2] fix: payment reco for jv with negative dr or cr amount (cherry picked from commit fee79b944575f6797ae3846e7394158a1daaac2d) --- .../payment_reconciliation.py | 16 +++++++++------- erpnext/accounts/utils.py | 10 ++++++++++ 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py index bbbb3c978ff..68e9eef711a 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py +++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py @@ -211,12 +211,14 @@ class PaymentReconciliation(Document): if self.get("cost_center"): conditions.append(jea.cost_center == self.cost_center) - dr_or_cr = ( - "credit_in_account_currency" - if erpnext.get_party_account_type(self.party_type) == "Receivable" - else "debit_in_account_currency" - ) - conditions.append(jea[dr_or_cr].gt(0)) + account_type = erpnext.get_party_account_type(self.party_type) + + if account_type == "Receivable": + dr_or_cr = jea.credit_in_account_currency - jea.debit_in_account_currency + elif account_type == "Payable": + dr_or_cr = jea.debit_in_account_currency - jea.credit_in_account_currency + + conditions.append(dr_or_cr.gt(0)) if self.bank_cash_account: conditions.append(jea.against_account.like(f"%%{self.bank_cash_account}%%")) @@ -231,7 +233,7 @@ class PaymentReconciliation(Document): je.posting_date, je.remark.as_("remarks"), jea.name.as_("reference_row"), - jea[dr_or_cr].as_("amount"), + dr_or_cr.as_("amount"), jea.is_advance, jea.exchange_rate, jea.account_currency.as_("currency"), diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index 37dbaef51a8..144039b794f 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -630,6 +630,16 @@ def update_reference_in_journal_entry(d, journal_entry, do_not_save=False): if jv_detail.get("reference_type") in ["Sales Order", "Purchase Order"]: update_advance_paid.append((jv_detail.reference_type, jv_detail.reference_name)) + rev_dr_or_cr = ( + "debit_in_account_currency" + if d["dr_or_cr"] == "credit_in_account_currency" + else "credit_in_account_currency" + ) + if jv_detail.get(rev_dr_or_cr): + d["dr_or_cr"] = rev_dr_or_cr + d["allocated_amount"] = d["allocated_amount"] * -1 + d["unadjusted_amount"] = d["unadjusted_amount"] * -1 + if flt(d["unadjusted_amount"]) - flt(d["allocated_amount"]) != 0: # adjust the unreconciled balance amount_in_account_currency = flt(d["unadjusted_amount"]) - flt(d["allocated_amount"]) From 234741f35f6575e8f61d3a22d00774b203254f8a Mon Sep 17 00:00:00 2001 From: ljain112 Date: Tue, 19 Nov 2024 12:17:35 +0530 Subject: [PATCH 2/2] fix: added test cases (cherry picked from commit 6f9ea6422d9b03dea918267f6272b7bd36f96375) --- .../test_payment_reconciliation.py | 130 ++++++++++++++++++ 1 file changed, 130 insertions(+) diff --git a/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py index 1b19949bb7e..3f0fb29d671 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py +++ b/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py @@ -632,6 +632,42 @@ class TestPaymentReconciliation(FrappeTestCase): self.assertEqual(len(pr.get("invoices")), 0) self.assertEqual(len(pr.get("payments")), 0) + def test_negative_debit_or_credit_journal_against_invoice(self): + transaction_date = nowdate() + amount = 100 + si = self.create_sales_invoice(qty=1, rate=amount, posting_date=transaction_date) + + # credit debtors account to record a payment + je = self.create_journal_entry(self.bank, self.debit_to, amount, transaction_date) + je.accounts[1].party_type = "Customer" + je.accounts[1].party = self.customer + je.accounts[1].credit_in_account_currency = 0 + je.accounts[1].debit_in_account_currency = -1 * amount + je.save() + je.submit() + + pr = self.create_payment_reconciliation() + + pr.get_unreconciled_entries() + 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})) + + # Difference amount should not be calculated for base currency accounts + for row in pr.allocation: + self.assertEqual(flt(row.get("difference_amount")), 0.0) + + pr.reconcile() + + # assert outstanding + si.reload() + self.assertEqual(si.status, "Paid") + self.assertEqual(si.outstanding_amount, 0) + + # check PR tool output + self.assertEqual(len(pr.get("invoices")), 0) + self.assertEqual(len(pr.get("payments")), 0) + def test_journal_against_journal(self): transaction_date = nowdate() sales = "Sales - _PR" @@ -954,6 +990,100 @@ class TestPaymentReconciliation(FrappeTestCase): frappe.db.get_value("Journal Entry", jea_parent.parent, "voucher_type"), "Exchange Gain Or Loss" ) + def test_difference_amount_via_negative_debit_or_credit_journal_entry(self): + # Make Sale Invoice + si = self.create_sales_invoice( + qty=1, rate=100, posting_date=nowdate(), do_not_save=True, do_not_submit=True + ) + si.customer = self.customer4 + si.currency = "EUR" + si.conversion_rate = 85 + si.debit_to = self.debtors_eur + si.save().submit() + + # Make payment using Journal Entry + je1 = self.create_journal_entry("HDFC - _PR", self.debtors_eur, 100, nowdate()) + je1.multi_currency = 1 + je1.accounts[0].exchange_rate = 1 + je1.accounts[0].credit_in_account_currency = -8000 + je1.accounts[0].credit = -8000 + je1.accounts[0].debit_in_account_currency = 0 + je1.accounts[0].debit = 0 + je1.accounts[1].party_type = "Customer" + je1.accounts[1].party = self.customer4 + je1.accounts[1].exchange_rate = 80 + je1.accounts[1].credit_in_account_currency = 100 + je1.accounts[1].credit = 8000 + je1.accounts[1].debit_in_account_currency = 0 + je1.accounts[1].debit = 0 + je1.save() + je1.submit() + + je2 = self.create_journal_entry("HDFC - _PR", self.debtors_eur, 200, nowdate()) + je2.multi_currency = 1 + je2.accounts[0].exchange_rate = 1 + je2.accounts[0].credit_in_account_currency = -16000 + je2.accounts[0].credit = -16000 + je2.accounts[0].debit_in_account_currency = 0 + je2.accounts[0].debit = 0 + je2.accounts[1].party_type = "Customer" + je2.accounts[1].party = self.customer4 + je2.accounts[1].exchange_rate = 80 + je2.accounts[1].credit_in_account_currency = 200 + je1.accounts[1].credit = 16000 + je1.accounts[1].debit_in_account_currency = 0 + je1.accounts[1].debit = 0 + je2.save() + je2.submit() + + pr = self.create_payment_reconciliation() + pr.party = self.customer4 + pr.receivable_payable_account = self.debtors_eur + pr.get_unreconciled_entries() + + self.assertEqual(len(pr.invoices), 1) + self.assertEqual(len(pr.payments), 2) + + # Test exact payment allocation + invoices = [x.as_dict() for x in pr.invoices] + payments = [pr.payments[0].as_dict()] + pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments})) + + self.assertEqual(pr.allocation[0].allocated_amount, 100) + self.assertEqual(pr.allocation[0].difference_amount, -500) + + # Test partial payment allocation (with excess payment entry) + pr.set("allocation", []) + pr.get_unreconciled_entries() + invoices = [x.as_dict() for x in pr.invoices] + payments = [pr.payments[1].as_dict()] + pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments})) + pr.allocation[0].difference_account = "Exchange Gain/Loss - _PR" + + self.assertEqual(pr.allocation[0].allocated_amount, 100) + self.assertEqual(pr.allocation[0].difference_amount, -500) + + # Check if difference journal entry gets generated for difference amount after reconciliation + pr.reconcile() + total_credit_amount = frappe.db.get_all( + "Journal Entry Account", + {"account": self.debtors_eur, "docstatus": 1, "reference_name": si.name}, + "sum(credit) as amount", + group_by="reference_name", + )[0].amount + + # total credit includes the exchange gain/loss amount + self.assertEqual(flt(total_credit_amount, 2), 8500) + + jea_parent = frappe.db.get_all( + "Journal Entry Account", + filters={"account": self.debtors_eur, "docstatus": 1, "reference_name": si.name, "credit": 500}, + fields=["parent"], + )[0] + self.assertEqual( + frappe.db.get_value("Journal Entry", jea_parent.parent, "voucher_type"), "Exchange Gain Or Loss" + ) + def test_difference_amount_via_payment_entry(self): # Make Sale Invoice si = self.create_sales_invoice(