Skip to content

Commit e53fabf

Browse files
[FIX] account_reconcile_oca: When doing manual reconciliations, allow to set the amount to reconcile in the invoice currency.
Before this fix, when attempting to reconcile a statement line in EUR with an invoice in USD, upon indicating the amount to reconcile in the currency invoice the results would be incorrect. This fix also includes an improvement on the message shown in the bank statement line on the effect of the reconciliation to the invoice.
1 parent 8698b3c commit e53fabf

File tree

4 files changed

+217
-26
lines changed

4 files changed

+217
-26
lines changed

account_reconcile_oca/models/account_bank_statement_line.py

Lines changed: 94 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from odoo.exceptions import UserError
1212
from odoo.fields import first
1313
from odoo.tools import LazyTranslate, float_compare, float_is_zero, groupby
14+
from odoo.tools.misc import formatLang
1415

1516
_lt = LazyTranslate(__name__, default_lang="en_US")
1617

@@ -85,6 +86,11 @@ class AccountBankStatementLine(models.Model):
8586
prefetch=False,
8687
currency_field="manual_in_currency_id",
8788
)
89+
changed_manual_amount_in_currency = fields.Boolean(
90+
store=False,
91+
default=False,
92+
prefetch=False,
93+
)
8894
manual_exchange_counterpart = fields.Boolean(
8995
store=False,
9096
)
@@ -107,8 +113,16 @@ class AccountBankStatementLine(models.Model):
107113
manual_currency_id = fields.Many2one(
108114
"res.currency", readonly=True, store=False, prefetch=False
109115
)
110-
manual_original_amount = fields.Monetary(
111-
default=False, store=False, prefetch=False, readonly=True
116+
manual_original_amount_in_currency = fields.Monetary(
117+
default=False,
118+
store=False,
119+
prefetch=False,
120+
readonly=True,
121+
currency_field="manual_in_currency_id",
122+
)
123+
manual_amount_message = fields.Char(compute="_compute_manual_amount_message")
124+
show_full_reconcile_button = fields.Boolean(
125+
compute="_compute_manual_amount_message"
112126
)
113127
manual_move_type = fields.Selection(
114128
lambda r: r.env["account.move"]._fields["move_type"].selection,
@@ -125,6 +139,34 @@ class AccountBankStatementLine(models.Model):
125139
aggregate_id = fields.Integer(compute="_compute_reconcile_aggregate")
126140
aggregate_name = fields.Char(compute="_compute_reconcile_aggregate")
127141

142+
@api.depends(
143+
"manual_original_amount_in_currency",
144+
"manual_currency_id",
145+
"manual_amount_in_currency",
146+
"manual_move_id",
147+
)
148+
def _compute_manual_amount_message(self):
149+
for rec in self:
150+
rec.show_full_reconcile_button = True
151+
rec.manual_amount_message = ""
152+
if not rec.manual_currency_id:
153+
continue
154+
resulting_amt = (
155+
rec.manual_original_amount_in_currency - rec.manual_amount_in_currency
156+
)
157+
if rec.manual_currency_id.is_zero(resulting_amt):
158+
msg = _("will be fully paid.")
159+
rec.show_full_reconcile_button = False
160+
else:
161+
msg = _("will be reduced by %s") % (
162+
formatLang(
163+
self.env,
164+
rec.manual_amount_in_currency,
165+
currency_obj=rec.manual_currency_id,
166+
)
167+
)
168+
rec.manual_amount_message = msg
169+
128170
@api.model
129171
def _reconcile_aggregate_map(self):
130172
lang = self.env["res.lang"]._lang_get(self.env.user.lang)
@@ -382,7 +424,7 @@ def _get_manual_delete_vals(self):
382424
"manual_move_id": False,
383425
"manual_move_type": False,
384426
"manual_kind": False,
385-
"manual_original_amount": False,
427+
"manual_original_amount_in_currency": False,
386428
"manual_currency_id": False,
387429
"analytic_distribution": False,
388430
}
@@ -407,7 +449,9 @@ def _process_manual_reconcile_from_line(self, line):
407449
self.manual_move_id = self.manual_line_id.move_id
408450
self.manual_move_type = self.manual_line_id.move_id.move_type
409451
self.manual_kind = line["kind"]
410-
self.manual_original_amount = line.get("original_amount", 0.0)
452+
self.manual_original_amount_in_currency = line.get(
453+
"original_amount_currency", 0.0
454+
)
411455

412456
@api.onchange("manual_reference", "manual_delete")
413457
def _onchange_manual_reconcile_reference(self):
@@ -438,6 +482,39 @@ def _onchange_manual_reconcile_reference(self):
438482
)
439483
self.can_reconcile = self.reconcile_data_info.get("can_reconcile", False)
440484

485+
@api.onchange("manual_amount_in_currency")
486+
def _onchange_manual_amount_in_currency(self):
487+
if (
488+
self.manual_line_id.exists()
489+
and self.manual_line_id
490+
and self.manual_kind != "liquidity"
491+
):
492+
self.manual_amount = self.manual_in_currency_id._convert(
493+
self.manual_amount_in_currency,
494+
self.company_id.currency_id,
495+
self.company_id,
496+
self.manual_line_id.date,
497+
)
498+
self.changed_manual_amount_in_currency = True
499+
self._onchange_manual_reconcile_vals()
500+
501+
@api.onchange("manual_amount")
502+
def _onchange_manual_amount(self):
503+
if (
504+
self.manual_line_id.exists()
505+
and self.manual_line_id
506+
and self.manual_kind != "liquidity"
507+
and not self.changed_manual_amount_in_currency
508+
):
509+
self.manual_amount_in_currency = self.company_id.currency_id._convert(
510+
self.manual_amount,
511+
self.manual_in_currency_id,
512+
self.company_id,
513+
self.manual_line_id.date,
514+
)
515+
self.changed_manual_amount_in_currency = False
516+
self._onchange_manual_reconcile_vals()
517+
441518
def _get_manual_reconcile_vals(self):
442519
vals = {
443520
"name": self.manual_name,
@@ -460,16 +537,19 @@ def _get_manual_reconcile_vals(self):
460537
}
461538
liquidity_lines, _suspense_lines, _other_lines = self._seek_for_lines()
462539
if self.manual_line_id and self.manual_line_id.id not in liquidity_lines.ids:
463-
vals.update(
464-
{
465-
"currency_amount": self.manual_currency_id._convert(
466-
self.manual_amount,
467-
self.manual_in_currency_id,
468-
self.company_id,
469-
self.manual_line_id.date,
470-
),
471-
}
472-
)
540+
if self.manual_in_currency_id and self.manual_amount_in_currency:
541+
vals.update({"currency_amount": self.manual_amount_in_currency})
542+
else:
543+
vals.update(
544+
{
545+
"currency_amount": self.manual_currency_id._convert(
546+
self.manual_amount,
547+
self.manual_in_currency_id,
548+
self.company_id,
549+
self.manual_line_id.date,
550+
),
551+
}
552+
)
473553
return vals
474554

475555
@api.onchange(

account_reconcile_oca/models/account_reconcile_abstract.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,11 @@ def _get_reconcile_line(
4949
date = self.date if "date" in self._fields else line.date
5050
original_amount = amount = net_amount = line.debit - line.credit
5151
line_currency = line.currency_id
52+
original_amount_currency = line.amount_currency
5253
if is_counterpart:
53-
currency_amount = -line.amount_residual_currency or line.amount_residual
54+
currency_amount = original_amount_currency = (
55+
-line.amount_residual_currency or line.amount_residual
56+
)
5457
amount = -line.amount_residual
5558
currency = line.currency_id or line.company_id.currency_id
5659
original_amount = net_amount = -line.amount_residual
@@ -87,6 +90,7 @@ def _get_reconcile_line(
8790
currency_amount = line.amount_currency
8891
else:
8992
currency_amount = self.amount_currency or self.amount
93+
original_amount_currency = self.amount_currency
9094
line_currency = self._get_reconcile_currency()
9195
vals = {
9296
"move_id": move and line.move_id.id,
@@ -108,6 +112,7 @@ def _get_reconcile_line(
108112
"currency_amount": currency_amount,
109113
"analytic_distribution": line.analytic_distribution,
110114
"kind": kind,
115+
"original_amount_currency": original_amount_currency,
111116
}
112117
if from_unreconcile:
113118
vals.update(

account_reconcile_oca/tests/test_bank_account_reconcile.py

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,105 @@ def test_reconcile_invoice_reconcile_full(self):
267267
f.manual_reference = f"account.move.line;{receivable1.id}"
268268
self.assertEqual(-100, f.manual_amount)
269269

270+
def test_reconcile_invoice_reconcile_with_currency(self):
271+
"""
272+
We want to test the reconcile widget for bank statements on invoices
273+
with a different currency. We want to indicate manually in the bank
274+
statement line the amount that we want to reconcile, expressed in
275+
the invoice currency.
276+
"""
277+
new_rate = 1.6299
278+
self.env["res.currency.rate"].create(
279+
{
280+
"name": time.strftime("%Y-07-01"),
281+
"rate": new_rate,
282+
"currency_id": self.currency_usd_id,
283+
"company_id": self.bank_journal_euro.company_id.id,
284+
}
285+
)
286+
inv1 = self.create_invoice(currency_id=self.currency_usd_id, invoice_amount=100)
287+
bank_stmt = self.acc_bank_stmt_model.create(
288+
{
289+
"journal_id": self.bank_journal_euro.id,
290+
"date": time.strftime("%Y-07-15"),
291+
"name": "test",
292+
}
293+
)
294+
bank_stmt_line = self.acc_bank_stmt_line_model.create(
295+
{
296+
"name": "testLine",
297+
"journal_id": self.bank_journal_euro.id,
298+
"statement_id": bank_stmt.id,
299+
"amount": 50,
300+
"date": time.strftime("%Y-07-15"),
301+
}
302+
)
303+
receivable1 = inv1.line_ids.filtered(
304+
lambda line: line.account_id.account_type == "asset_receivable"
305+
)
306+
with Form(
307+
bank_stmt_line,
308+
view="account_reconcile_oca.bank_statement_line_form_reconcile_view",
309+
) as f:
310+
self.assertFalse(f.can_reconcile)
311+
f.add_account_move_line_id = receivable1
312+
self.assertFalse(f.add_account_move_line_id)
313+
self.assertTrue(f.can_reconcile)
314+
f.manual_reference = f"account.move.line;{receivable1.id}"
315+
f.manual_amount_in_currency = -100
316+
bank_stmt_line.reconcile_bank_line()
317+
self.assertEqual(inv1.amount_residual_signed, 0)
318+
self.assertEqual(inv1.payment_state, "paid")
319+
320+
def test_reconcile_invoice_reconcile_full_with_currency(self):
321+
"""
322+
We want to test the reconcile widget for bank statements on invoices
323+
with a different currency. We want to press the button on the statement
324+
line to force the full reconciliation.
325+
"""
326+
new_rate = 1.6299
327+
self.env["res.currency.rate"].create(
328+
{
329+
"name": time.strftime("%Y-07-01"),
330+
"rate": new_rate,
331+
"currency_id": self.currency_usd_id,
332+
"company_id": self.bank_journal_euro.company_id.id,
333+
}
334+
)
335+
inv1 = self.create_invoice(currency_id=self.currency_usd_id, invoice_amount=100)
336+
bank_stmt = self.acc_bank_stmt_model.create(
337+
{
338+
"journal_id": self.bank_journal_euro.id,
339+
"date": time.strftime("%Y-07-15"),
340+
"name": "test",
341+
}
342+
)
343+
bank_stmt_line = self.acc_bank_stmt_line_model.create(
344+
{
345+
"name": "testLine",
346+
"journal_id": self.bank_journal_euro.id,
347+
"statement_id": bank_stmt.id,
348+
"amount": 50,
349+
"date": time.strftime("%Y-07-15"),
350+
}
351+
)
352+
receivable1 = inv1.line_ids.filtered(
353+
lambda line: line.account_id.account_type == "asset_receivable"
354+
)
355+
with Form(
356+
bank_stmt_line,
357+
view="account_reconcile_oca.bank_statement_line_form_reconcile_view",
358+
) as f:
359+
self.assertFalse(f.can_reconcile)
360+
f.add_account_move_line_id = receivable1
361+
self.assertFalse(f.add_account_move_line_id)
362+
self.assertTrue(f.can_reconcile)
363+
f.manual_reference = f"account.move.line;{receivable1.id}"
364+
bank_stmt_line.button_manual_reference_full_paid()
365+
bank_stmt_line.reconcile_bank_line()
366+
self.assertEqual(inv1.amount_residual_signed, 0)
367+
self.assertEqual(inv1.payment_state, "paid")
368+
270369
def test_reconcile_invoice_unreconcile(self):
271370
"""
272371
We want to test the reconcile widget for bank statements on invoices.

account_reconcile_oca/views/account_bank_statement_line.xml

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -295,34 +295,41 @@
295295
modifiers="{'readonly': ['|', '|', ('manual_exchange_counterpart', '=', True), ('manual_reference', '=', False), ('is_reconciled', '=', True)]}"
296296
/>
297297
<field name="manual_currency_id" invisible="1" />
298-
<field name="manual_original_amount" invisible="1" />
298+
<field
299+
name="manual_original_amount_in_currency"
300+
invisible="1"
301+
/>
299302
<field name="manual_move_type" invisible="1" />
303+
<field
304+
name="show_full_reconcile_button"
305+
invisible="1"
306+
/>
300307
<label
301308
for="manual_move_id"
302309
string=""
303-
insible="manual_move_type not in ['in_invoice', 'in_refund', 'out_invoice', 'out_refund'] or manual_original_amount == 0"
310+
insible="manual_move_type not in ['in_invoice', 'in_refund', 'out_invoice', 'out_refund'] or manual_original_amount_in_currency == 0"
304311
/>
312+
305313
<div
306-
insible="manual_move_type not in ['in_invoice', 'in_refund', 'out_invoice', 'out_refund'] or manual_original_amount == 0"
314+
invisible="manual_move_type not in ['in_invoice', 'in_refund', 'out_invoice', 'out_refund'] or not manual_original_amount_in_currency == 0"
307315
>
308316
Invoice <field
309317
class="oe_inline"
310318
name="manual_move_id"
311319
/>
312320
with an open amount <field
313321
class="oe_inline"
314-
name="manual_original_amount"
315-
/> will be reduced by <field
316-
class="oe_inline"
317-
name="manual_amount"
318-
readonly="1"
319-
/>.
320-
<br />
321-
You might want to set the invoice as <button
322+
name="manual_original_amount_in_currency"
323+
/>
324+
<field name="manual_amount_message" />
325+
<div invisible="not show_full_reconcile_button">
326+
<br />
327+
You might want to set the invoice as <button
322328
name="button_manual_reference_full_paid"
323329
type="object"
324330
method_args="[1]"
325331
>fully paid</button>.
332+
</div>
326333
</div>
327334
</group>
328335
</group>

0 commit comments

Comments
 (0)