From 3a28f69598d074dea94f1f33bb41a11ef843a1a7 Mon Sep 17 00:00:00 2001 From: Abhinav A P Date: Fri, 5 Jun 2026 23:29:17 +0530 Subject: [PATCH 1/2] fix(expense-claim): convert advance amounts to the claim currency When an Employee Advance in a currency different from the Expense Claim's currency was fetched (e.g. a USD advance pulled into an INR claim), its amounts were appended in the advance's own currency. Those row fields are interpreted in the claim currency, so the values were never converted and the advance/grand totals came out wrong. get_expense_claim_advances now converts advance_paid / unclaimed_amount / allocated_amount / return_amount into the claim currency (using advance_rate / claim_rate) when the currencies differ, and stores the row exchange rate as the claim rate so base-amount calculation stays consistent. Same-currency advances (incl. the existing exchange gain/loss flow) are unchanged. Adds a test for fetching a foreign-currency advance into a base-currency claim. Closes #4402 --- .../hr/doctype/expense_claim/expense_claim.py | 21 +++++++--- .../expense_claim/test_expense_claim.py | 40 +++++++++++++++++++ 2 files changed, 55 insertions(+), 6 deletions(-) diff --git a/hrms/hr/doctype/expense_claim/expense_claim.py b/hrms/hr/doctype/expense_claim/expense_claim.py index 8b4cc9d309..1280db2ec6 100644 --- a/hrms/hr/doctype/expense_claim/expense_claim.py +++ b/hrms/hr/doctype/expense_claim/expense_claim.py @@ -689,6 +689,7 @@ def get_advances(expense_claim: str | dict | Document, advance_id: str | None = advance.claimed_amount, advance.return_amount, advance.advance_account, + advance.currency, ) if not advance_id: @@ -775,27 +776,35 @@ def get_expense_claim_advances(expense_claim, employee_advance): adjustment_entry["amount"] ) + advance_currency = employee_advance.get("currency") + claim_currency = expense_claim.get("currency") + claim_exchange_rate = flt(expense_claim.get("exchange_rate")) or 1.0 + convert = bool(advance_currency and claim_currency and advance_currency != claim_currency) + for advance in advance_payments: paid_amount = flt(advance["amount"]) claimed_amount = adjustment_map.get((advance["voucher_type"], advance["voucher_no"]), 0) unclaimed_amount = paid_amount - claimed_amount return_amount = flt(employee_advance.return_amount) allocated_amount = get_allocation_amount( - paid_amount=(paid_amount), claimed_amount=(claimed_amount), return_amount=(return_amount) + paid_amount=paid_amount, claimed_amount=claimed_amount, return_amount=return_amount ) + conversion_rate = flt(advance["exchange_rate"]) / claim_exchange_rate if convert else 1.0 + row_exchange_rate = claim_exchange_rate if convert else flt(advance["exchange_rate"]) + expense_claim.append( "advances", { "advance_account": employee_advance.advance_account, "employee_advance": employee_advance.name, "posting_date": employee_advance.posting_date, - "advance_paid": paid_amount, + "advance_paid": flt(paid_amount * conversion_rate), "base_advance_paid": flt(advance["base_amount"]), - "unclaimed_amount": unclaimed_amount, - "allocated_amount": allocated_amount, - "return_amount": return_amount, - "exchange_rate": advance["exchange_rate"], + "unclaimed_amount": flt(unclaimed_amount * conversion_rate), + "allocated_amount": flt(allocated_amount * conversion_rate), + "return_amount": flt(return_amount * conversion_rate), + "exchange_rate": row_exchange_rate, "reference_type": advance["voucher_type"], "reference_name": advance["voucher_no"], }, diff --git a/hrms/hr/doctype/expense_claim/test_expense_claim.py b/hrms/hr/doctype/expense_claim/test_expense_claim.py index 90873c10f2..8abfcdd0dd 100644 --- a/hrms/hr/doctype/expense_claim/test_expense_claim.py +++ b/hrms/hr/doctype/expense_claim/test_expense_claim.py @@ -753,6 +753,46 @@ def test_self_expense_approval_not_allowed(self): self.assertEqual(1, expense_claim.docstatus) + def test_advance_in_different_currency_converted_in_claim(self): + """An advance in a currency different from the claim must be converted (#4402).""" + from hrms.hr.doctype.employee_advance.test_employee_advance import ( + create_advance_account, + get_advances_for_claim, + make_employee_advance, + make_payment_entry, + ) + + company = "_Test Company" + company_currency = get_company_currency(company) + advance_account = create_advance_account("Employee Advance (USD)", "USD") + employee = make_employee( + "test_adv_cross_currency@example.com", + company, + salary_currency="USD", + employee_advance_account=advance_account, + ) + advance = make_employee_advance(employee) + make_payment_entry(advance, advance.advance_amount) + advance.reload() + self.assertNotEqual(advance.currency, company_currency) + + claim = make_expense_claim( + get_payable_account(company), + advance.advance_amount, + advance.advance_amount, + company, + "Travel Expenses - _TC", + args={"currency": company_currency, "exchange_rate": 1}, + employee=employee, + do_not_submit=True, + ) + claim = get_advances_for_claim(claim, advance.name) + claim_advance = claim.advances[0] + + # converted into the claim (company) currency == the advance's base paid amount + self.assertNotEqual(flt(claim_advance.advance_paid, 2), flt(advance.advance_amount, 2)) + self.assertEqual(flt(claim_advance.advance_paid, 2), flt(advance.base_paid_amount, 2)) + def test_multicurrency_claim(self): from hrms.hr.doctype.employee_advance.test_employee_advance import ( create_advance_account, From e7970db751bc3508fdbbc0f5e924b52eb6624c85 Mon Sep 17 00:00:00 2001 From: Abhinav A P Date: Fri, 19 Jun 2026 15:57:20 +0530 Subject: [PATCH 2/2] fix(expense-claim): filter advances by claim currency GL entries against an Employee Advance must be in the advance's own currency, so a cross-currency advance cannot be adjusted against a claim and breaks on submit. Exclude mismatched-currency advances from the fetch (query + form filter) instead of converting their amounts. --- .../hr/doctype/expense_claim/expense_claim.js | 1 + .../hr/doctype/expense_claim/expense_claim.py | 23 ++++++++----------- .../expense_claim/test_expense_claim.py | 13 ++++------- 3 files changed, 15 insertions(+), 22 deletions(-) diff --git a/hrms/hr/doctype/expense_claim/expense_claim.js b/hrms/hr/doctype/expense_claim/expense_claim.js index 99e4a4b482..8be97dfc9b 100644 --- a/hrms/hr/doctype/expense_claim/expense_claim.js +++ b/hrms/hr/doctype/expense_claim/expense_claim.js @@ -13,6 +13,7 @@ frappe.ui.form.on("Expense Claim", { ["employee", "=", frm.doc.employee], ["paid_amount", ">", 0], ["status", "not in", ["Claimed", "Returned", "Partly Claimed and Returned"]], + ["currency", "=", frm.doc.currency], ], }; }); diff --git a/hrms/hr/doctype/expense_claim/expense_claim.py b/hrms/hr/doctype/expense_claim/expense_claim.py index 1280db2ec6..a5e749ed1d 100644 --- a/hrms/hr/doctype/expense_claim/expense_claim.py +++ b/hrms/hr/doctype/expense_claim/expense_claim.py @@ -689,7 +689,6 @@ def get_advances(expense_claim: str | dict | Document, advance_id: str | None = advance.claimed_amount, advance.return_amount, advance.advance_account, - advance.currency, ) if not advance_id: @@ -702,6 +701,10 @@ def get_advances(expense_claim: str | dict | Document, advance_id: str | None = else: query = query.where((advance.name == advance_id) & (advance.employee == expense_claim_doc.employee)) + # advance can only be adjusted in its own currency + if expense_claim_doc.currency: + query = query.where(advance.currency == expense_claim_doc.currency) + advances = query.run(as_dict=True) for advance in advances: @@ -776,11 +779,6 @@ def get_expense_claim_advances(expense_claim, employee_advance): adjustment_entry["amount"] ) - advance_currency = employee_advance.get("currency") - claim_currency = expense_claim.get("currency") - claim_exchange_rate = flt(expense_claim.get("exchange_rate")) or 1.0 - convert = bool(advance_currency and claim_currency and advance_currency != claim_currency) - for advance in advance_payments: paid_amount = flt(advance["amount"]) claimed_amount = adjustment_map.get((advance["voucher_type"], advance["voucher_no"]), 0) @@ -790,21 +788,18 @@ def get_expense_claim_advances(expense_claim, employee_advance): paid_amount=paid_amount, claimed_amount=claimed_amount, return_amount=return_amount ) - conversion_rate = flt(advance["exchange_rate"]) / claim_exchange_rate if convert else 1.0 - row_exchange_rate = claim_exchange_rate if convert else flt(advance["exchange_rate"]) - expense_claim.append( "advances", { "advance_account": employee_advance.advance_account, "employee_advance": employee_advance.name, "posting_date": employee_advance.posting_date, - "advance_paid": flt(paid_amount * conversion_rate), + "advance_paid": paid_amount, "base_advance_paid": flt(advance["base_amount"]), - "unclaimed_amount": flt(unclaimed_amount * conversion_rate), - "allocated_amount": flt(allocated_amount * conversion_rate), - "return_amount": flt(return_amount * conversion_rate), - "exchange_rate": row_exchange_rate, + "unclaimed_amount": unclaimed_amount, + "allocated_amount": allocated_amount, + "return_amount": return_amount, + "exchange_rate": advance["exchange_rate"], "reference_type": advance["voucher_type"], "reference_name": advance["voucher_no"], }, diff --git a/hrms/hr/doctype/expense_claim/test_expense_claim.py b/hrms/hr/doctype/expense_claim/test_expense_claim.py index 8abfcdd0dd..a2d80c159a 100644 --- a/hrms/hr/doctype/expense_claim/test_expense_claim.py +++ b/hrms/hr/doctype/expense_claim/test_expense_claim.py @@ -12,6 +12,7 @@ from hrms.hr.doctype.expense_claim.expense_claim import ( MismatchError, + get_advances, get_outstanding_amount_for_claim, make_expense_claim_for_delivery_trip, ) @@ -753,11 +754,9 @@ def test_self_expense_approval_not_allowed(self): self.assertEqual(1, expense_claim.docstatus) - def test_advance_in_different_currency_converted_in_claim(self): - """An advance in a currency different from the claim must be converted (#4402).""" + def test_advance_in_different_currency_excluded_from_claim(self): from hrms.hr.doctype.employee_advance.test_employee_advance import ( create_advance_account, - get_advances_for_claim, make_employee_advance, make_payment_entry, ) @@ -786,12 +785,10 @@ def test_advance_in_different_currency_converted_in_claim(self): employee=employee, do_not_submit=True, ) - claim = get_advances_for_claim(claim, advance.name) - claim_advance = claim.advances[0] - # converted into the claim (company) currency == the advance's base paid amount - self.assertNotEqual(flt(claim_advance.advance_paid, 2), flt(advance.advance_amount, 2)) - self.assertEqual(flt(claim_advance.advance_paid, 2), flt(advance.base_paid_amount, 2)) + # mismatched-currency advance is excluded from both the bulk and explicit fetch + self.assertEqual(get_advances(claim), []) + self.assertEqual(get_advances(claim, advance.name), []) def test_multicurrency_claim(self): from hrms.hr.doctype.employee_advance.test_employee_advance import (