mirror of
https://github.com/frappe/erpnext.git
synced 2026-10-29 22:57:42 +00:00
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:
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user