Merge pull request #62650 from aerele/fix/cost-center-allocation-transaction-currency

fix(accounts): allocate transaction currency amounts by cost center
This commit is contained in:
mergify[bot]
2026-10-29 10:29:13 +00:00
committed by GitHub
2 changed files with 122 additions and 6 deletions
@@ -14,6 +14,7 @@ from erpnext.accounts.doctype.cost_center_allocation.cost_center_allocation impo
WrongPercentageAllocation,
)
from erpnext.accounts.doctype.journal_entry.test_journal_entry import make_journal_entry
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
from erpnext.tests.utils import ERPNextTestSuite
@@ -192,8 +193,6 @@ class TestCostCenterAllocation(ERPNextTestSuite):
@ERPNextTestSuite.change_settings("System Settings", {"rounding_method": "Commercial Rounding"})
def test_debit_credit_on_cost_center_allocation_for_commercial_rounding(self):
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
cca = create_cost_center_allocation(
"_Test Company",
"Main Cost Center 1 - _TC",
@@ -215,6 +214,93 @@ class TestCostCenterAllocation(ERPNextTestSuite):
si.cancel()
cca.cancel()
@ERPNextTestSuite.change_settings(
"Accounts Settings", {"allow_multi_currency_invoices_against_single_party_account": 1}
)
@ERPNextTestSuite.change_settings(
"System Settings", {"currency_precision": "", "use_number_format_from_currency": 1}
)
def test_transaction_currency_allocation_preserves_precision_and_totals(self):
for main_cost_center, percentage in (
("Main Cost Center 1 - _TC", 60),
("Main Cost Center 2 - _TC", 50),
):
create_cost_center_allocation(
"_Test Company",
main_cost_center,
{"Sub Cost Center 1 - _TC": percentage, "Sub Cost Center 2 - _TC": 100 - percentage},
)
for currency, number_format in (
("INR", "#,###.##"),
("USD", "#,###.##"),
("JPY", "#,###"),
("KWD", "#,###.###"),
):
currency_doc = frappe.get_doc("Currency", currency)
currency_doc.number_format = number_format
currency_doc.save()
for rounding_method in ("Commercial Rounding", "Banker's Rounding"):
with self.change_settings("System Settings", {"rounding_method": rounding_method}):
for currency, amount, cost_center, expected in (
("USD", 100, "Main Cost Center 1 - _TC", [60.0, 40.0]),
("USD", 100.01, "Main Cost Center 2 - _TC", [50.0, 50.01]),
("JPY", 1, "Main Cost Center 1 - _TC", [1.0, 0.0]),
("KWD", 1.001, "Main Cost Center 1 - _TC", [0.601, 0.4]),
):
with self.subTest(rounding_method=rounding_method, currency=currency, amount=amount):
invoice = create_sales_invoice(
currency=currency,
conversion_rate=50,
rate=amount,
cost_center=cost_center,
do_not_save=True,
)
invoice.disable_rounded_total = 1
invoice.insert().submit()
credit_note = create_sales_invoice(
currency=currency,
conversion_rate=50,
rate=amount,
qty=-1,
cost_center=cost_center,
is_return=1,
return_against=invoice.name,
do_not_save=True,
)
credit_note.disable_rounded_total = 1
credit_note.insert().submit()
for voucher, field in (
(invoice, "credit_in_transaction_currency"),
(credit_note, "debit_in_transaction_currency"),
):
entries = frappe.get_all(
"GL Entry",
filters={"voucher_type": voucher.doctype, "voucher_no": voucher.name},
fields=[
"account",
"debit",
"credit",
"debit_in_transaction_currency",
"credit_in_transaction_currency",
],
)
self.assertCountEqual(
[row[field] for row in entries if row.account == "Sales - _TC"], expected
)
self.assertAlmostEqual(sum(row.debit - row.credit for row in entries), 0)
self.assertAlmostEqual(
sum(row.debit_in_transaction_currency for row in entries), amount
)
self.assertAlmostEqual(
sum(row.credit_in_transaction_currency for row in entries), amount
)
credit_note.cancel()
invoice.reload().cancel()
def create_cost_center_allocation(
company,
+34 -4
View File
@@ -186,16 +186,43 @@ def distribute_gl_based_on_cost_center_allocation(gl_map, precision=None, from_r
new_gl_map.append(d)
continue
transaction_precision = get_transaction_currency_precision(d, precision)
allocated_percentage = 0
allocated_transaction_amounts = {
"debit_in_transaction_currency": 0,
"credit_in_transaction_currency": 0,
}
for sub_cost_center, percentage in cost_center_allocation:
gle = copy.deepcopy(d)
gle.cost_center = sub_cost_center
for field in ("debit", "credit", "debit_in_account_currency", "credit_in_account_currency"):
for field in (
"debit",
"credit",
"debit_in_account_currency",
"credit_in_account_currency",
):
gle[field] = flt(flt(d.get(field)) * percentage / 100, precision)
# Round cumulative amounts so each allocation carries forward the rounding remainder.
allocated_percentage += percentage
for field, allocated_amount in allocated_transaction_amounts.items():
cumulative_amount = flt(flt(d.get(field)) * allocated_percentage / 100, transaction_precision)
gle[field] = flt(cumulative_amount - allocated_amount, transaction_precision)
allocated_transaction_amounts[field] = cumulative_amount
new_gl_map.append(gle)
return new_gl_map
def get_transaction_currency_precision(entry, default_precision):
if not entry.get("transaction_currency"):
return default_precision
return get_field_precision(
frappe.get_meta("GL Entry").get_field("debit_in_transaction_currency"),
currency=entry.transaction_currency,
)
@request_cache
def get_cost_center_allocation_data(company, posting_date, cost_center):
cost_center_allocation = frappe.db.get_value(
@@ -431,20 +458,23 @@ def process_debit_credit_difference(gl_map):
def get_debit_credit_difference(gl_map, precision):
debit_credit_diff = 0.0
trx_cur_debit_credit_diff = 0
transaction_precision = 0
for entry in gl_map:
entry.debit = flt(entry.debit, precision)
entry.credit = flt(entry.credit, precision)
debit_credit_diff += entry.debit - entry.credit
entry.debit_in_transaction_currency = flt(entry.debit_in_transaction_currency, precision)
entry.credit_in_transaction_currency = flt(entry.credit_in_transaction_currency, precision)
entry_precision = get_transaction_currency_precision(entry, precision)
transaction_precision = max(transaction_precision, entry_precision)
entry.debit_in_transaction_currency = flt(entry.debit_in_transaction_currency, entry_precision)
entry.credit_in_transaction_currency = flt(entry.credit_in_transaction_currency, entry_precision)
trx_cur_debit_credit_diff += (
entry.debit_in_transaction_currency - entry.credit_in_transaction_currency
)
debit_credit_diff = flt(debit_credit_diff, precision)
trx_cur_debit_credit_diff = flt(trx_cur_debit_credit_diff, precision)
trx_cur_debit_credit_diff = flt(trx_cur_debit_credit_diff, transaction_precision)
return debit_credit_diff, trx_cur_debit_credit_diff