mirror of
https://github.com/frappe/erpnext.git
synced 2026-08-29 06:38:24 +00:00
feat(accounts): split bank charges from exchange gain/loss on multi-currency transfers (#58071)
In a multi-currency Internal Transfer, the paid-vs-received difference was booked entirely to Exchange Gain/Loss, so a bank charge entered as a deduction pushed the Difference Amount non-zero and blocked submission. The exchange gain/loss row now absorbs only the residual after user-entered deductions, letting a Bank Charges row and the Exchange Gain/Loss row coexist and net to zero.
This commit is contained in:
@@ -1279,8 +1279,14 @@ frappe.ui.form.on("Payment Entry", {
|
|||||||
await frappe.after_ajax();
|
await frappe.after_ajax();
|
||||||
const base_paid_amount = frm.doc.base_paid_amount || 0;
|
const base_paid_amount = frm.doc.base_paid_amount || 0;
|
||||||
const base_received_amount = frm.doc.base_received_amount || 0;
|
const base_received_amount = frm.doc.base_received_amount || 0;
|
||||||
|
let other_deductions = 0;
|
||||||
|
if (frm.doc.payment_type === "Internal Transfer") {
|
||||||
|
other_deductions = (frm.doc.deductions || [])
|
||||||
|
.filter((row) => !row.is_exchange_gain_loss)
|
||||||
|
.reduce((sum, row) => sum + flt(row.amount), 0);
|
||||||
|
}
|
||||||
const exchange_gain_loss = flt(
|
const exchange_gain_loss = flt(
|
||||||
base_paid_amount - base_received_amount,
|
base_paid_amount - base_received_amount - other_deductions,
|
||||||
get_deduction_amount_precision()
|
get_deduction_amount_precision()
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -1857,11 +1863,19 @@ frappe.ui.form.on("Payment Entry Deduction", {
|
|||||||
},
|
},
|
||||||
|
|
||||||
amount: function (frm) {
|
amount: function (frm) {
|
||||||
frm.events.set_unallocated_amount(frm);
|
if (frm.doc.payment_type === "Internal Transfer") {
|
||||||
|
frm.events.set_exchange_gain_loss_deduction(frm);
|
||||||
|
} else {
|
||||||
|
frm.events.set_unallocated_amount(frm);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
deductions_remove: function (frm) {
|
deductions_remove: function (frm) {
|
||||||
frm.events.set_unallocated_amount(frm);
|
if (frm.doc.payment_type === "Internal Transfer") {
|
||||||
|
frm.events.set_exchange_gain_loss_deduction(frm);
|
||||||
|
} else {
|
||||||
|
frm.events.set_unallocated_amount(frm);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1118,8 +1118,14 @@ class PaymentEntry(AccountsController):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def set_exchange_gain_loss(self):
|
def set_exchange_gain_loss(self):
|
||||||
|
other_deductions = 0
|
||||||
|
if self.payment_type == "Internal Transfer":
|
||||||
|
other_deductions = sum(
|
||||||
|
flt(row.amount) for row in self.get("deductions") if not row.is_exchange_gain_loss
|
||||||
|
)
|
||||||
|
|
||||||
exchange_gain_loss = flt(
|
exchange_gain_loss = flt(
|
||||||
self.base_paid_amount - self.base_received_amount,
|
self.base_paid_amount - self.base_received_amount - other_deductions,
|
||||||
self.precision("amount", "deductions"),
|
self.precision("amount", "deductions"),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -870,6 +870,64 @@ class TestPaymentEntry(ERPNextTestSuite):
|
|||||||
|
|
||||||
self.validate_gl_entries(pe.name, expected_gle)
|
self.validate_gl_entries(pe.name, expected_gle)
|
||||||
|
|
||||||
|
def test_cross_currency_transfer_splits_bank_charge_and_exchange_gain_loss(self):
|
||||||
|
exchange_gain_loss_account = frappe.db.get_value(
|
||||||
|
"Company", "_Test Company", "exchange_gain_loss_account"
|
||||||
|
)
|
||||||
|
bank_charges_account = create_account(
|
||||||
|
parent_account="Indirect Expenses - _TC",
|
||||||
|
account_name="_Test Bank Charges",
|
||||||
|
company="_Test Company",
|
||||||
|
)
|
||||||
|
|
||||||
|
pe = frappe.new_doc("Payment Entry")
|
||||||
|
pe.payment_type = "Internal Transfer"
|
||||||
|
pe.company = "_Test Company"
|
||||||
|
pe.paid_from = "_Test Bank USD - _TC"
|
||||||
|
pe.paid_to = "_Test Bank - _TC"
|
||||||
|
pe.paid_amount = 100
|
||||||
|
pe.source_exchange_rate = 50
|
||||||
|
pe.received_amount = 4500
|
||||||
|
pe.reference_no = "6"
|
||||||
|
pe.reference_date = nowdate()
|
||||||
|
pe.append(
|
||||||
|
"deductions",
|
||||||
|
{
|
||||||
|
"account": bank_charges_account,
|
||||||
|
"cost_center": "_Test Cost Center - _TC",
|
||||||
|
"amount": 100,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
pe.setup_party_account_field()
|
||||||
|
pe.set_missing_values()
|
||||||
|
pe.set_exchange_rate()
|
||||||
|
pe.set_amounts()
|
||||||
|
|
||||||
|
deductions = {d.account: d for d in pe.deductions}
|
||||||
|
self.assertEqual(deductions[bank_charges_account].amount, 100)
|
||||||
|
self.assertEqual(deductions[exchange_gain_loss_account].amount, 400)
|
||||||
|
self.assertTrue(deductions[exchange_gain_loss_account].is_exchange_gain_loss)
|
||||||
|
self.assertEqual(pe.difference_amount, 0)
|
||||||
|
|
||||||
|
for d in pe.deductions:
|
||||||
|
d.cost_center = "_Test Cost Center - _TC"
|
||||||
|
|
||||||
|
pe.insert()
|
||||||
|
pe.submit()
|
||||||
|
|
||||||
|
expected_gle = dict(
|
||||||
|
(d[0], d)
|
||||||
|
for d in [
|
||||||
|
["_Test Bank USD - _TC", 0, 5000, None],
|
||||||
|
["_Test Bank - _TC", 4500, 0, None],
|
||||||
|
[exchange_gain_loss_account, 400.0, 0, None],
|
||||||
|
[bank_charges_account, 100.0, 0, None],
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
self.validate_gl_entries(pe.name, expected_gle)
|
||||||
|
|
||||||
def test_payment_against_negative_sales_invoice(self):
|
def test_payment_against_negative_sales_invoice(self):
|
||||||
si1 = create_sales_invoice()
|
si1 = create_sales_invoice()
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user