[FIX] account_reconcile_oca : Fix multi currency management

Fix the case of payment with a foreign currency set on bank statement line

improve tests around multi-currency
pull/808/head
Florian da Costa 2024-09-17 11:35:58 +02:00 committed by Jordi Ballester Alomar
parent 82677beb24
commit 7d92a34370
3 changed files with 148 additions and 90 deletions

View File

@ -178,6 +178,18 @@ class AccountBankStatementLine(models.Model):
)._default_reconcile_data()
self.can_reconcile = self.reconcile_data_info.get("can_reconcile", False)
def _get_amount_currency(self, line, dest_curr):
if line["line_currency_id"] == dest_curr.id:
amount = line["currency_amount"]
else:
amount = self.company_id.currency_id._convert(
line["amount"],
dest_curr,
self.company_id,
self.date,
)
return amount
@api.onchange("add_account_move_line_id")
def _onchange_add_account_move_line_id(self):
if self.add_account_move_line_id:
@ -185,16 +197,10 @@ class AccountBankStatementLine(models.Model):
new_data = []
is_new_line = True
pending_amount = 0.0
currency = self._get_reconcile_currency()
for line in data:
if line["kind"] != "suspense":
pending_amount += currency._convert(
line["currency_amount"],
self.env["res.currency"].browse(
line.get("line_currency_id", currency.id)
),
self.company_id,
self.date,
pending_amount += self._get_amount_currency(
line, self._get_reconcile_currency()
)
if self.add_account_move_line_id.id in line.get(
"counterpart_line_ids", []
@ -226,6 +232,7 @@ class AccountBankStatementLine(models.Model):
new_data = []
suspense_line = False
counterparts = []
suspense_currency = self.foreign_currency_id or self.currency_id
for line in data:
if line.get("counterpart_line_ids"):
counterparts += line["counterpart_line_ids"]
@ -237,24 +244,25 @@ class AccountBankStatementLine(models.Model):
if line["kind"] != "suspense":
new_data.append(line)
total_amount += line["amount"]
if line.get("currency_amount"):
currency_amount += (
self.env["res.currency"]
.browse(line["line_currency_id"])
._convert(
line["currency_amount"],
self._get_reconcile_currency(),
if not line.get("is_exchange_counterpart"):
# case of statement line with foreign_currency
if (
line["kind"] == "liquidity"
and line["line_currency_id"] != suspense_currency.id
):
currency_amount += self.amount_currency
elif (
line.get("currency_amount")
and line.get("line_currency_id") == suspense_currency.id
):
currency_amount += line.get("currency_amount")
else:
currency_amount += self.company_id.currency_id._convert(
line["amount"],
suspense_currency,
self.company_id,
self.date,
)
)
else:
currency_amount += self.company_id.currency_id._convert(
line["amount"],
self._get_reconcile_currency(),
self.company_id,
self.date,
)
else:
suspense_line = line
if not float_is_zero(
@ -267,6 +275,7 @@ class AccountBankStatementLine(models.Model):
"amount": -total_amount,
"credit": total_amount if total_amount > 0 else 0.0,
"debit": -total_amount if total_amount < 0 else 0.0,
"currency_amount": -currency_amount,
}
)
else:
@ -292,7 +301,7 @@ class AccountBankStatementLine(models.Model):
"debit": -total_amount if total_amount < 0 else 0.0,
"kind": "suspense",
"currency_id": self.company_id.currency_id.id,
"line_currency_id": self.currency_id.id,
"line_currency_id": suspense_currency.id,
"currency_amount": -currency_amount,
}
reconcile_auxiliary_id += 1
@ -396,7 +405,7 @@ class AccountBankStatementLine(models.Model):
if self.manual_line_id.exists() and self.manual_line_id:
self.manual_amount = self.manual_in_currency_id._convert(
self.manual_amount_in_currency,
self._get_reconcile_currency(),
self.company_id.currency_id,
self.company_id,
self.manual_line_id.date,
)
@ -585,6 +594,7 @@ class AccountBankStatementLine(models.Model):
self.manual_reference,
)
elif res and res.get("amls"):
# TODO should be signed in currency get_reconcile_currency
amount = self.amount_total_signed
for line in res.get("amls", []):
reconcile_auxiliary_id, line_data = self._get_reconcile_line(
@ -898,7 +908,7 @@ class AccountBankStatementLine(models.Model):
self.manual_reference,
)
elif res.get("amls"):
amount = self.amount
amount = self.amount_currency or self.amount
for line in res.get("amls", []):
reconcile_auxiliary_id, line_datas = record._get_reconcile_line(
line, "other", is_counterpart=True, max_amount=amount, move=True
@ -1023,26 +1033,38 @@ class AccountBankStatementLine(models.Model):
)
rates = []
for vals in new_vals:
rate = False
if vals["partner_id"] is False:
vals["partner_id"] = (False, self.partner_name)
reconcile_auxiliary_id, rate = self._compute_exchange_rate(
vals, line, reconcile_auxiliary_id
)
if vals.get("kind") not in ("suspense", "liquidity"):
reconcile_auxiliary_id, rate = self._compute_exchange_rate(
vals, line, reconcile_auxiliary_id
)
if rate:
rates.append(rate)
new_vals += rates
return reconcile_auxiliary_id, new_vals
def _get_exchange_rate_amount(self, amount, currency_amount, currency, line):
return (
currency._convert(
if self.foreign_currency_id:
# take real rate of statement line to compute the exchange rate gain/loss
real_rate = self.amount / self.amount_currency
to_amount_journal_currency = currency_amount * real_rate
to_amount_company_currency = self.currency_id._convert(
to_amount_journal_currency,
self.company_id.currency_id,
self.company_id,
self.date,
)
to_amount = self.company_id.currency_id.round(to_amount_company_currency)
else:
to_amount = currency._convert(
currency_amount,
self.company_id.currency_id,
self.company_id,
self.date,
)
- amount
)
return self.company_id.currency_id.round(to_amount - amount)
def _compute_exchange_rate(
self,
@ -1112,7 +1134,7 @@ class AccountBankStatementLine(models.Model):
def _get_reconcile_currency(self):
return (
self.currency_id
self.foreign_currency_id
or self.journal_id.currency_id
or self.company_id._currency_id
or self.company_id.currency_id
)

View File

@ -47,18 +47,25 @@ class AccountReconcileAbstract(models.AbstractModel):
):
date = self.date if "date" in self._fields else line.date
original_amount = amount = net_amount = line.debit - line.credit
line_currency = line.currency_id
if is_counterpart:
currency_amount = -line.amount_residual_currency or line.amount_residual
amount = -line.amount_residual
currency = line.currency_id or line.company_id.currency_id
original_amount = net_amount = -line.amount_residual
if max_amount:
real_currency_amount = currency._convert(
currency_amount,
self._get_reconcile_currency(),
self.company_id,
date,
)
dest_currency = self._get_reconcile_currency()
if currency == dest_currency:
real_currency_amount = currency_amount
elif self.company_id.currency_id == dest_currency:
real_currency_amount = amount
else:
real_currency_amount = self.company_id.currency_id._convert(
amount,
dest_currency,
self.company_id,
date,
)
if (
-real_currency_amount > max_amount > 0
or -real_currency_amount < max_amount < 0
@ -76,7 +83,8 @@ class AccountReconcileAbstract(models.AbstractModel):
date,
)
else:
currency_amount = line.amount_currency
currency_amount = self.amount_currency or self.amount
line_currency = self._get_reconcile_currency()
vals = {
"move_id": move and line.move_id.id,
"move": move and line.move_id.name,
@ -93,7 +101,7 @@ class AccountReconcileAbstract(models.AbstractModel):
"amount": amount,
"net_amount": amount - net_amount,
"currency_id": self.company_id.currency_id.id,
"line_currency_id": line.currency_id.id,
"line_currency_id": line_currency.id,
"currency_amount": currency_amount,
"analytic_distribution": line.analytic_distribution,
"kind": kind,

View File

@ -91,7 +91,6 @@ class TestReconciliationWidget(TestAccountReconciliationCommon):
inv1 = self.create_invoice(currency_id=self.currency_usd_id, invoice_amount=100)
bank_stmt = self.acc_bank_stmt_model.create(
{
"company_id": self.env.ref("base.main_company").id,
"journal_id": self.bank_journal_euro.id,
"date": time.strftime("%Y-07-15"),
"name": "test",
@ -119,6 +118,42 @@ class TestReconciliationWidget(TestAccountReconciliationCommon):
self.assertFalse(f.add_account_move_line_id)
self.assertTrue(f.can_reconcile)
def test_manual_line_with_currency(self):
bank_stmt = self.acc_bank_stmt_model.create(
{
"journal_id": self.bank_journal_euro.id,
"date": time.strftime("%Y-07-15"),
"name": "test",
}
)
bank_stmt_line = self.acc_bank_stmt_line_model.create(
{
"name": "testLine",
"journal_id": self.bank_journal_euro.id,
"statement_id": bank_stmt.id,
"amount": 50,
"amount_currency": 100,
"foreign_currency_id": self.currency_usd_id,
"date": time.strftime("%Y-07-15"),
}
)
receivable_acc = self.company_data["default_account_receivable"]
with Form(
bank_stmt_line,
view="account_reconcile_oca.bank_statement_line_form_reconcile_view",
) as f:
self.assertFalse(f.can_reconcile)
f.manual_reference = "reconcile_auxiliary;1"
f.manual_account_id = receivable_acc
self.assertTrue(f.can_reconcile)
bank_stmt_line.reconcile_bank_line()
receivable_line = bank_stmt_line.line_ids.filtered(
lambda line: line.account_id == receivable_acc
)
self.assertEqual(receivable_line.currency_id.id, self.currency_usd_id)
self.assertEqual(receivable_line.amount_currency, -100)
self.assertEqual(receivable_line.balance, -50)
def test_reconcile_invoice_reconcile_full(self):
"""
We want to test the reconcile widget for bank statements on invoices.
@ -130,7 +165,6 @@ class TestReconciliationWidget(TestAccountReconciliationCommon):
)
bank_stmt = self.acc_bank_stmt_model.create(
{
"company_id": self.env.ref("base.main_company").id,
"journal_id": self.bank_journal_euro.id,
"date": time.strftime("%Y-07-15"),
"name": "test",
@ -179,7 +213,6 @@ class TestReconciliationWidget(TestAccountReconciliationCommon):
)
bank_stmt = self.acc_bank_stmt_model.create(
{
"company_id": self.env.ref("base.main_company").id,
"journal_id": self.bank_journal_euro.id,
"date": time.strftime("%Y-07-15"),
"name": "test",
@ -242,7 +275,6 @@ class TestReconciliationWidget(TestAccountReconciliationCommon):
)
bank_stmt = self.acc_bank_stmt_model.create(
{
"company_id": self.env.ref("base.main_company").id,
"journal_id": self.bank_journal_euro.id,
"date": time.strftime("%Y-07-15"),
"name": "test",
@ -306,7 +338,6 @@ class TestReconciliationWidget(TestAccountReconciliationCommon):
)
bank_stmt = self.acc_bank_stmt_model.create(
{
"company_id": self.env.ref("base.main_company").id,
"journal_id": self.bank_journal_euro.id,
"date": time.strftime("%Y-07-15"),
"name": "test",
@ -360,7 +391,6 @@ class TestReconciliationWidget(TestAccountReconciliationCommon):
"""
bank_stmt = self.acc_bank_stmt_model.create(
{
"company_id": self.env.ref("base.main_company").id,
"journal_id": self.bank_journal_euro.id,
"date": time.strftime("%Y-07-15"),
"name": "test",
@ -404,7 +434,6 @@ class TestReconciliationWidget(TestAccountReconciliationCommon):
)
bank_stmt = self.acc_bank_stmt_model.create(
{
"company_id": self.env.ref("base.main_company").id,
"journal_id": self.bank_journal_euro.id,
"date": time.strftime("%Y-07-15"),
"name": "test",
@ -459,7 +488,6 @@ class TestReconciliationWidget(TestAccountReconciliationCommon):
)
bank_stmt = self.acc_bank_stmt_model.create(
{
"company_id": self.env.ref("base.main_company").id,
"journal_id": self.bank_journal_euro.id,
"date": time.strftime("%Y-07-15"),
"name": "test",
@ -519,7 +547,6 @@ class TestReconciliationWidget(TestAccountReconciliationCommon):
bank_stmt = self.acc_bank_stmt_model.create(
{
"company_id": self.env.ref("base.main_company").id,
"journal_id": self.bank_journal_euro.id,
"date": time.strftime("%Y-07-15"),
"name": "test",
@ -549,7 +576,6 @@ class TestReconciliationWidget(TestAccountReconciliationCommon):
)
bank_stmt = self.acc_bank_stmt_model.create(
{
"company_id": self.env.ref("base.main_company").id,
"journal_id": self.bank_journal_euro.id,
"date": time.strftime("%Y-07-15"),
"name": "test",
@ -607,7 +633,6 @@ class TestReconciliationWidget(TestAccountReconciliationCommon):
)
bank_stmt = self.acc_bank_stmt_model.create(
{
"company_id": self.env.ref("base.main_company").id,
"journal_id": self.bank_journal_euro.id,
"date": time.strftime("%Y-07-15"),
"name": "test",
@ -649,7 +674,6 @@ class TestReconciliationWidget(TestAccountReconciliationCommon):
"""
bank_stmt = self.acc_bank_stmt_model.create(
{
"company_id": self.env.ref("base.main_company").id,
"journal_id": self.bank_journal_euro.id,
"date": time.strftime("%Y-07-15"),
"name": "test",
@ -684,7 +708,6 @@ class TestReconciliationWidget(TestAccountReconciliationCommon):
)
bank_stmt = self.acc_bank_stmt_model.create(
{
"company_id": self.env.ref("base.main_company").id,
"journal_id": self.bank_journal_euro.id,
"date": time.strftime("%Y-07-15"),
"name": "test",
@ -722,7 +745,6 @@ class TestReconciliationWidget(TestAccountReconciliationCommon):
)
bank_stmt = self.acc_bank_stmt_model.create(
{
"company_id": self.env.ref("base.main_company").id,
"journal_id": self.bank_journal_euro.id,
"date": time.strftime("%Y-07-15"),
"name": "test",
@ -763,7 +785,6 @@ class TestReconciliationWidget(TestAccountReconciliationCommon):
)
bank_stmt = self.acc_bank_stmt_model.create(
{
"company_id": self.env.ref("base.main_company").id,
"journal_id": self.bank_journal_euro.id,
"date": time.strftime("%Y-07-15"),
"name": "test",
@ -804,7 +825,6 @@ class TestReconciliationWidget(TestAccountReconciliationCommon):
)
bank_stmt = self.acc_bank_stmt_model.create(
{
"company_id": self.env.ref("base.main_company").id,
"journal_id": self.bank_journal_euro.id,
"date": time.strftime("%Y-07-15"),
"name": "test",
@ -840,7 +860,6 @@ class TestReconciliationWidget(TestAccountReconciliationCommon):
"""
bank_stmt = self.acc_bank_stmt_model.create(
{
"company_id": self.env.ref("base.main_company").id,
"journal_id": self.bank_journal_euro.id,
"date": time.strftime("%Y-07-15"),
"name": "test",
@ -891,7 +910,6 @@ class TestReconciliationWidget(TestAccountReconciliationCommon):
"""
bank_stmt = self.acc_bank_stmt_model.create(
{
"company_id": self.env.ref("base.main_company").id,
"journal_id": self.bank_journal_euro.id,
"date": time.strftime("%Y-07-15"),
"name": "test",
@ -934,7 +952,6 @@ class TestReconciliationWidget(TestAccountReconciliationCommon):
bank_stmt = self.acc_bank_stmt_model.create(
{
"company_id": self.env.ref("base.main_company").id,
"journal_id": self.bank_journal_euro.id,
"date": time.strftime("%Y-07-15"),
"name": "test",
@ -998,7 +1015,6 @@ class TestReconciliationWidget(TestAccountReconciliationCommon):
bank_stmt = self.acc_bank_stmt_model.create(
{
"company_id": self.env.ref("base.main_company").id,
"journal_id": self.bank_journal_euro.id,
"date": time.strftime("%Y-07-15"),
"name": "test",
@ -1032,7 +1048,6 @@ class TestReconciliationWidget(TestAccountReconciliationCommon):
inv1 = self.create_invoice(currency_id=self.currency_usd_id, invoice_amount=100)
bank_stmt = self.acc_bank_stmt_model.create(
{
"company_id": self.env.ref("base.main_company").id,
"journal_id": self.bank_journal_usd.id,
"date": time.strftime("%Y-07-15"),
"name": "test",
@ -1071,30 +1086,51 @@ class TestReconciliationWidget(TestAccountReconciliationCommon):
)
def test_journal_foreign_currency_change(self):
cny = self.env.ref("base.CNY")
cny.write({"active": True})
cny_journal = self.env["account.journal"].create(
{
"name": "Bank CNY",
"type": "bank",
"currency_id": cny.id,
}
)
self.env["res.currency.rate"].create(
{
"currency_id": self.env.ref("base.EUR").id,
"name": time.strftime("%Y-07-14"),
"rate": 1.15,
"name": time.strftime("%Y-09-10"),
"currency_id": cny.id,
"inverse_company_rate": 0.125989013758,
}
)
self.env["res.currency.rate"].create(
{
"name": time.strftime("%Y-09-09"),
"currency_id": cny.id,
"inverse_company_rate": 0.126225969731,
}
)
bank_stmt = self.acc_bank_stmt_model.create(
{
"company_id": self.env.ref("base.main_company").id,
"journal_id": self.bank_journal_usd.id,
"date": time.strftime("%Y-07-15"),
"journal_id": cny_journal.id,
"date": time.strftime("%Y-09-10"),
"name": "test",
}
)
bank_stmt_line = self.acc_bank_stmt_line_model.create(
{
"name": "testLine",
"journal_id": self.bank_journal_usd.id,
"journal_id": cny_journal.id,
"statement_id": bank_stmt.id,
"amount": 100,
"date": time.strftime("%Y-07-15"),
"amount": 259200,
"date": time.strftime("%Y-09-10"),
}
)
inv1 = self._create_invoice(
currency_id=cny.id,
invoice_amount=259200,
date_invoice=time.strftime("%Y-09-09"),
auto_validate=True,
)
with Form(
bank_stmt_line,
view="account_reconcile_oca.bank_statement_line_form_reconcile_view",
@ -1102,24 +1138,17 @@ class TestReconciliationWidget(TestAccountReconciliationCommon):
line = f.reconcile_data_info["data"][0]
self.assertEqual(
line["currency_amount"],
100,
259200,
)
self.env["res.currency.rate"].create(
{
"currency_id": self.env.ref("base.EUR").id,
"name": time.strftime("%Y-07-15"),
"rate": 1.2,
}
)
with Form(
bank_stmt_line,
view="account_reconcile_oca.bank_statement_line_form_reconcile_view",
) as f:
line = f.reconcile_data_info["data"][0]
self.assertEqual(
line["currency_amount"],
100,
f.add_account_move_line_id = inv1.line_ids.filtered(
lambda l: l.account_id.account_type == "asset_receivable"
)
self.assertTrue(f.can_reconcile)
self.assertEqual(len(bank_stmt_line.reconcile_data_info["data"]), 3)
exchange_line = bank_stmt_line.reconcile_data_info["data"][-1]
self.assertEqual(exchange_line["amount"], 61.42)
bank_stmt_line.reconcile_bank_line()
self.assertEqual(inv1.payment_state, "paid")
def test_invoice_foreign_currency_change(self):
self.env["res.currency.rate"].create(
@ -1144,7 +1173,6 @@ class TestReconciliationWidget(TestAccountReconciliationCommon):
)
bank_stmt = self.acc_bank_stmt_model.create(
{
"company_id": self.env.ref("base.main_company").id,
"journal_id": self.bank_journal_usd.id,
"date": time.strftime("%Y-07-15"),
"name": "test",