[IMP] account_reconcile_oca: Fix multi currency computation

pull/668/head
Enric Tobella 2024-06-19 19:33:58 +02:00
parent 41549a63e8
commit 0b69f38cac
4 changed files with 179 additions and 108 deletions

View File

@ -151,11 +151,11 @@ class AccountAccountReconcile(models.Model):
counterparts = data["counterparts"] counterparts = data["counterparts"]
amount = 0.0 amount = 0.0
for line_id in counterparts: for line_id in counterparts:
line = self._get_reconcile_line( lines = self._get_reconcile_line(
self.env["account.move.line"].browse(line_id), "other", True, amount self.env["account.move.line"].browse(line_id), "other", True, amount
) )
new_data["data"].append(line) new_data["data"] += lines
amount += line["amount"] amount += sum(line["amount"] for line in lines)
return new_data return new_data
def clean_reconcile(self): def clean_reconcile(self):

View File

@ -163,7 +163,7 @@ class AccountBankStatementLine(models.Model):
self.manual_model_id, self.manual_model_id,
self.reconcile_data_info["reconcile_auxiliary_id"], self.reconcile_data_info["reconcile_auxiliary_id"],
), ),
self.manual_reference self.manual_reference,
) )
else: else:
# Refreshing data # Refreshing data
@ -189,26 +189,19 @@ class AccountBankStatementLine(models.Model):
else: else:
new_data.append(line) new_data.append(line)
if is_new_line: if is_new_line:
new_data.append( reconcile_auxiliary_id, lines = self._get_reconcile_line(
self._get_reconcile_line(
self.add_account_move_line_id, "other", True, pending_amount self.add_account_move_line_id, "other", True, pending_amount
) )
) new_data += lines
self.reconcile_data_info = self._recompute_suspense_line( self.reconcile_data_info = self._recompute_suspense_line(
new_data, new_data,
self.reconcile_data_info["reconcile_auxiliary_id"], self.reconcile_data_info["reconcile_auxiliary_id"],
self.manual_reference, self.manual_reference,
exchange_recompute=True,
) )
self.can_reconcile = self.reconcile_data_info.get("can_reconcile", False) self.can_reconcile = self.reconcile_data_info.get("can_reconcile", False)
self.add_account_move_line_id = False self.add_account_move_line_id = False
def _recompute_suspense_line( def _recompute_suspense_line(self, data, reconcile_auxiliary_id, manual_reference):
self, data, reconcile_auxiliary_id, manual_reference, exchange_recompute=False
):
reconcile_auxiliary_id = self._compute_exchange_rate(
data, reconcile_auxiliary_id, exchange_recompute=exchange_recompute
)
can_reconcile = True can_reconcile = True
total_amount = 0 total_amount = 0
new_data = [] new_data = []
@ -450,49 +443,15 @@ class AccountBankStatementLine(models.Model):
new_data.append(new_line) new_data.append(new_line)
return new_data, reconcile_auxiliary_id return new_data, reconcile_auxiliary_id
def _compute_exchange_rate(
self, data, reconcile_auxiliary_id, exchange_recompute=False
):
if not exchange_recompute:
return reconcile_auxiliary_id
foreign_currency = (
self.currency_id != self.company_id.currency_id
or self.foreign_currency_id
or any(line["currency_id"] != line["line_currency_id"] for line in data)
)
if not foreign_currency or self.is_reconciled:
return reconcile_auxiliary_id
currency = self.journal_id.currency_id or self.company_id.currency_id
amount = sum(d.get("net_amount", 0) for d in data)
if not currency.is_zero(amount):
account = self.company_id.expense_currency_exchange_account_id
if amount > 0:
account = self.company_id.income_currency_exchange_account_id
data.append(
{
"reference": "reconcile_auxiliary;%s" % reconcile_auxiliary_id,
"id": False,
"account_id": account.name_get()[0],
"partner_id": False,
"date": fields.Date.to_string(self.date),
"name": self.payment_ref or self.name,
"amount": -amount,
"net_amount": -amount,
"credit": amount if amount > 0 else 0.0,
"debit": -amount if amount < 0 else 0.0,
"kind": "other",
"currency_id": self.currency_id.id,
"line_currency_id": self.currency_id.id,
"currency_amount": -amount,
}
)
reconcile_auxiliary_id += 1
return reconcile_auxiliary_id
def _default_reconcile_data(self, from_unreconcile=False): def _default_reconcile_data(self, from_unreconcile=False):
liquidity_lines, suspense_lines, other_lines = self._seek_for_lines() liquidity_lines, suspense_lines, other_lines = self._seek_for_lines()
data = [self._get_reconcile_line(line, "liquidity") for line in liquidity_lines] data = []
reconcile_auxiliary_id = 1 reconcile_auxiliary_id = 1
for line in liquidity_lines:
reconcile_auxiliary_id, lines = self._get_reconcile_line(
line, "liquidity", reconcile_auxiliary_id=reconcile_auxiliary_id
)
data += lines
if not from_unreconcile: if not from_unreconcile:
res = ( res = (
self.env["account.reconcile.model"] self.env["account.reconcile.model"]
@ -507,30 +466,31 @@ class AccountBankStatementLine(models.Model):
data, res["model"], reconcile_auxiliary_id data, res["model"], reconcile_auxiliary_id
), ),
self.manual_reference, self.manual_reference,
exchange_recompute=True
) )
elif res and res.get("amls"): elif res and res.get("amls"):
amount = self.amount_total_signed amount = self.amount_total_signed
for line in res.get("amls", []): for line in res.get("amls", []):
line_data = self._get_reconcile_line( reconcile_auxiliary_id, line_data = self._get_reconcile_line(
line, "other", is_counterpart=True, max_amount=amount line,
"other",
is_counterpart=True,
max_amount=amount,
reconcile_auxiliary_id=reconcile_auxiliary_id,
) )
amount -= line_data.get("amount") amount -= sum(line.get("amount") for line in line_data)
data.append(line_data) data += line_data
return self._recompute_suspense_line( return self._recompute_suspense_line(
data, data,
reconcile_auxiliary_id, reconcile_auxiliary_id,
self.manual_reference, self.manual_reference,
exchange_recompute=True,
) )
return self._recompute_suspense_line( for line in other_lines:
data reconcile_auxiliary_id, lines = self._get_reconcile_line(
+ [
self._get_reconcile_line(
line, "other", from_unreconcile=from_unreconcile line, "other", from_unreconcile=from_unreconcile
) )
for line in other_lines data += lines
], return self._recompute_suspense_line(
data,
reconcile_auxiliary_id, reconcile_auxiliary_id,
self.manual_reference, self.manual_reference,
) )
@ -699,10 +659,12 @@ class AccountBankStatementLine(models.Model):
if not res: if not res:
continue continue
liquidity_lines, suspense_lines, other_lines = record._seek_for_lines() liquidity_lines, suspense_lines, other_lines = record._seek_for_lines()
data = [ data = []
record._get_reconcile_line(line, "liquidity") for line in liquidity_lines:
for line in liquidity_lines reconcile_auxiliary_id, lines = record._get_reconcile_line(
] line, "liquidity"
)
data += lines
reconcile_auxiliary_id = 1 reconcile_auxiliary_id = 1
if res.get("status", "") == "write_off": if res.get("status", "") == "write_off":
data = record._recompute_suspense_line( data = record._recompute_suspense_line(
@ -710,21 +672,19 @@ class AccountBankStatementLine(models.Model):
data, res["model"], reconcile_auxiliary_id data, res["model"], reconcile_auxiliary_id
), ),
self.manual_reference, self.manual_reference,
exchange_recompute=True
) )
elif res.get("amls"): elif res.get("amls"):
amount = self.amount amount = self.amount
for line in res.get("amls", []): for line in res.get("amls", []):
line_data = record._get_reconcile_line( reconcile_auxiliary_id, line_datas = record._get_reconcile_line(
line, "other", is_counterpart=True, max_amount=amount line, "other", is_counterpart=True, max_amount=amount
) )
amount -= line_data.get("amount") amount -= sum(line_data.get("amount") for line_data in line_datas)
data.append(line_data) data += line_datas
data = record._recompute_suspense_line( data = record._recompute_suspense_line(
data, data,
reconcile_auxiliary_id, reconcile_auxiliary_id,
self.manual_reference, self.manual_reference,
exchange_recompute=True,
) )
if not data.get("can_reconcile"): if not data.get("can_reconcile"):
continue continue
@ -745,14 +705,14 @@ class AccountBankStatementLine(models.Model):
if line["reference"] == manual_reference and line.get("id"): if line["reference"] == manual_reference and line.get("id"):
total_amount = -line["amount"] + line["original_amount_unsigned"] total_amount = -line["amount"] + line["original_amount_unsigned"]
original_amount = line["original_amount_unsigned"] original_amount = line["original_amount_unsigned"]
new_data.append( reconcile_auxiliary_id, lines = self._get_reconcile_line(
self._get_reconcile_line(
self.env["account.move.line"].browse(line["id"]), self.env["account.move.line"].browse(line["id"]),
"other", "other",
is_counterpart=True, is_counterpart=True,
reconcile_auxiliary_id=reconcile_auxiliary_id,
max_amount=original_amount, max_amount=original_amount,
) )
) new_data += lines
new_data.append( new_data.append(
{ {
"reference": "reconcile_auxiliary;%s" % reconcile_auxiliary_id, "reference": "reconcile_auxiliary;%s" % reconcile_auxiliary_id,
@ -777,7 +737,6 @@ class AccountBankStatementLine(models.Model):
new_data, new_data,
reconcile_auxiliary_id, reconcile_auxiliary_id,
self.manual_reference, self.manual_reference,
exchange_recompute=True,
) )
self.can_reconcile = self.reconcile_data_info.get("can_reconcile", False) self.can_reconcile = self.reconcile_data_info.get("can_reconcile", False)
@ -792,18 +751,76 @@ class AccountBankStatementLine(models.Model):
self.move_id.to_check = False self.move_id.to_check = False
def _get_reconcile_line( def _get_reconcile_line(
self, line, kind, is_counterpart=False, max_amount=False, from_unreconcile=False self,
line,
kind,
is_counterpart=False,
max_amount=False,
from_unreconcile=False,
reconcile_auxiliary_id=False,
): ):
vals = super()._get_reconcile_line( new_vals = super()._get_reconcile_line(
line, line,
kind, kind,
is_counterpart=is_counterpart, is_counterpart=is_counterpart,
max_amount=max_amount, max_amount=max_amount,
from_unreconcile=from_unreconcile, from_unreconcile=from_unreconcile,
) )
rates = []
for vals in new_vals:
if vals["partner_id"] is False: if vals["partner_id"] is False:
vals["partner_id"] = (False, self.partner_name) vals["partner_id"] = (False, self.partner_name)
return vals 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 _compute_exchange_rate(
self,
vals,
line,
reconcile_auxiliary_id,
):
foreign_currency = (
self.currency_id != self.company_id.currency_id
or self.foreign_currency_id
or vals["currency_id"] != vals["line_currency_id"]
)
if not foreign_currency or self.is_reconciled:
return reconcile_auxiliary_id, False
currency = self.env["res.currency"].browse(vals["line_currency_id"])
amount = currency._convert(
vals["currency_amount"],
self.company_id.currency_id,
self.company_id,
self.date,
) - vals.get("amount", 0)
if currency.is_zero(amount):
return reconcile_auxiliary_id, False
account = self.company_id.expense_currency_exchange_account_id
if amount < 0:
account = self.company_id.income_currency_exchange_account_id
data = {
"reference": "reconcile_auxiliary;%s" % reconcile_auxiliary_id,
"id": False,
"account_id": account.name_get()[0],
"partner_id": False,
"date": fields.Date.to_string(self.date),
"name": self.payment_ref or self.name,
"amount": amount,
"net_amount": amount,
"credit": -amount if amount < 0 else 0.0,
"debit": amount if amount > 0 else 0.0,
"kind": "other",
"currency_id": self.company_id.currency_id.id,
"line_currency_id": self.company_id.currency_id.id,
"currency_amount": amount,
}
reconcile_auxiliary_id += 1
return reconcile_auxiliary_id, data
def add_statement(self): def add_statement(self):
self.ensure_one() self.ensure_one()

View File

@ -38,29 +38,29 @@ class AccountReconcileAbstract(models.AbstractModel):
): ):
date = self.date if "date" in self._fields else line.date date = self.date if "date" in self._fields else line.date
original_amount = amount = net_amount = line.debit - line.credit original_amount = amount = net_amount = line.debit - line.credit
amount_currency = self.company_id.currency_id
if is_counterpart: if is_counterpart:
amount = line.amount_residual_currency or line.amount_residual currency_amount = -line.amount_residual_currency or line.amount_residual
amount_currency = line.currency_id or self.company_id.currency_id amount = -line.amount_residual
original_amount = net_amount = line.amount_residual currency = line.currency_id or self.company_id.currency_id
original_amount = net_amount = -line.amount_residual
if max_amount: if max_amount:
currency_max_amount = self.company_id.currency_id._convert( currency_max_amount = self.company_id.currency_id._convert(
max_amount, amount_currency, self.company_id, line.date max_amount, currency, self.company_id, date
) )
if amount > currency_max_amount > 0: if (
-currency_amount > currency_max_amount > 0
or -currency_amount < currency_max_amount < 0
):
amount = currency_max_amount amount = currency_max_amount
net_amount = max_amount net_amount = -max_amount
if amount < currency_max_amount < 0:
amount = currency_max_amount
net_amount = max_amount
currency_amount = -amount currency_amount = -amount
original_amount = -original_amount amount = currency._convert(
net_amount = -net_amount currency_amount,
amount = amount_currency._convert( self.company_id.currency_id,
currency_amount, self.company_id.currency_id, self.company_id, date self.company_id,
date,
) )
else: else:
amount_currency = line.currency_id
currency_amount = line.amount_currency currency_amount = line.amount_currency
vals = { vals = {
"reference": "account.move.line;%s" % line.id, "reference": "account.move.line;%s" % line.id,
@ -96,4 +96,4 @@ class AccountReconcileAbstract(models.AbstractModel):
vals["original_amount_unsigned"] = original_amount vals["original_amount_unsigned"] = original_amount
if is_counterpart: if is_counterpart:
vals["counterpart_line_ids"] = line.ids vals["counterpart_line_ids"] = line.ids
return vals return [vals]

View File

@ -1058,3 +1058,57 @@ class TestReconciliationWidget(TestAccountReconciliationCommon):
line["currency_amount"], line["currency_amount"],
100, 100,
) )
def test_invoice_foreign_currency_change(self):
self.env["res.currency.rate"].create(
{
"currency_id": self.env.ref("base.EUR").id,
"name": time.strftime("%Y-07-14"),
"rate": 1.15,
}
)
self.env["res.currency.rate"].create(
{
"currency_id": self.env.ref("base.EUR").id,
"name": time.strftime("%Y-07-15"),
"rate": 1.2,
}
)
inv1 = self._create_invoice(
currency_id=self.currency_usd_id,
invoice_amount=100,
date_invoice="2021-07-14",
auto_validate=True,
)
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",
}
)
bank_stmt_line = self.acc_bank_stmt_line_model.create(
{
"name": "testLine",
"journal_id": self.bank_journal_usd.id,
"statement_id": bank_stmt.id,
"amount": 100,
"date": time.strftime("%Y-07-15"),
}
)
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.assertFalse(f.add_account_move_line_id)
self.assertTrue(f.can_reconcile)
self.assertEqual(3, len(f.reconcile_data_info["data"]))