Skip to content

Commit

Permalink
Merge pull request #41023 from frappe/mergify/bp/version-15-hotfix/pr…
Browse files Browse the repository at this point in the history
…-41020

fix: incorrect exc gain/loss for PE against JE for payable accounts (backport #41020)
  • Loading branch information
ruthra-kumar committed Apr 16, 2024
2 parents 80adafc + 5995029 commit ec337d3
Show file tree
Hide file tree
Showing 2 changed files with 103 additions and 2 deletions.
11 changes: 10 additions & 1 deletion erpnext/controllers/accounts_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -1437,7 +1437,8 @@ def make_exchange_gain_loss_journal(

dr_or_cr = "debit" if d.exchange_gain_loss > 0 else "credit"

if d.reference_doctype == "Purchase Invoice":
# Inverse debit/credit for payable accounts
if self.is_payable_account(d.reference_doctype, party_account):
dr_or_cr = "debit" if dr_or_cr == "credit" else "credit"

reverse_dr_or_cr = "debit" if dr_or_cr == "credit" else "credit"
Expand Down Expand Up @@ -1471,6 +1472,14 @@ def make_exchange_gain_loss_journal(
)
)

def is_payable_account(self, reference_doctype, account):
if reference_doctype == "Purchase Invoice" or (
reference_doctype == "Journal Entry"
and frappe.get_cached_value("Account", account, "account_type") == "Payable"
):
return True
return False

def update_against_document_in_jv(self):
"""
Links invoice and advance voucher:
Expand Down
94 changes: 93 additions & 1 deletion erpnext/controllers/tests/test_accounts_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,27 @@ def create_account(self):
acc = frappe.get_doc("Account", name)
self.debtors_usd = acc.name

account_name = "Creditors USD"
if not frappe.db.get_value(
"Account", filters={"account_name": account_name, "company": self.company}
):
acc = frappe.new_doc("Account")
acc.account_name = account_name
acc.parent_account = "Accounts Payable - " + self.company_abbr
acc.company = self.company
acc.account_currency = "USD"
acc.account_type = "Payable"
acc.insert()
else:
name = frappe.db.get_value(
"Account",
filters={"account_name": account_name, "company": self.company},
fieldname="name",
pluck=True,
)
acc = frappe.get_doc("Account", name)
self.creditors_usd = acc.name

def create_sales_invoice(
self,
qty=1,
Expand Down Expand Up @@ -174,7 +195,9 @@ def create_sales_invoice(
)
return sinv

def create_payment_entry(self, amount=1, source_exc_rate=75, posting_date=None, customer=None):
def create_payment_entry(
self, amount=1, source_exc_rate=75, posting_date=None, customer=None, submit=True
):
"""
Helper function to populate default values in payment entry
"""
Expand Down Expand Up @@ -1606,3 +1629,72 @@ def test_60_payment_entry_against_journal(self):
exc_je_for_je2 = self.get_journals_for(je2.doctype, je2.name)
self.assertEqual(exc_je_for_je1, [])
self.assertEqual(exc_je_for_je2, [])

def test_61_payment_entry_against_journal_for_payable_accounts(self):
# Invoices
exc_rate1 = 75
exc_rate2 = 77
amount = 1
je1 = self.create_journal_entry(
acc1=self.creditors_usd,
acc1_exc_rate=exc_rate1,
acc2=self.cash,
acc1_amount=-amount,
acc2_amount=(-amount * 75),
acc2_exc_rate=1,
)
je1.accounts[0].party_type = "Supplier"
je1.accounts[0].party = self.supplier
je1 = je1.save().submit()

# Payment
pe = create_payment_entry(
company=self.company,
payment_type="Pay",
party_type="Supplier",
party=self.supplier,
paid_from=self.cash,
paid_to=self.creditors_usd,
paid_amount=amount,
)
pe.target_exchange_rate = exc_rate2
pe.received_amount = amount
pe.paid_amount = amount * exc_rate2
pe.save().submit()

pr = frappe.get_doc(
{
"doctype": "Payment Reconciliation",
"company": self.company,
"party_type": "Supplier",
"party": self.supplier,
"receivable_payable_account": get_party_account("Supplier", self.supplier, self.company),
}
)
pr.from_invoice_date = pr.to_invoice_date = pr.from_payment_date = pr.to_payment_date = nowdate()
pr.get_unreconciled_entries()
self.assertEqual(len(pr.invoices), 1)
self.assertEqual(len(pr.payments), 1)
invoices = [x.as_dict() for x in pr.invoices]
payments = [x.as_dict() for x in pr.payments]
pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
pr.reconcile()
self.assertEqual(len(pr.invoices), 0)
self.assertEqual(len(pr.payments), 0)

# There should be no outstanding in both currencies
self.assert_ledger_outstanding(je1.doctype, je1.name, 0.0, 0.0)

# Exchange Gain/Loss Journal should've been created
exc_je_for_je1 = self.get_journals_for(je1.doctype, je1.name)
self.assertEqual(len(exc_je_for_je1), 1)

# Cancel Payment
pe.reload()
pe.cancel()

self.assert_ledger_outstanding(je1.doctype, je1.name, (amount * exc_rate1), amount)

# Exchange Gain/Loss Journal should've been cancelled
exc_je_for_je1 = self.get_journals_for(je1.doctype, je1.name)
self.assertEqual(exc_je_for_je1, [])

0 comments on commit ec337d3

Please sign in to comment.