Skip to content

Commit 86968d4

Browse files
cav-adhocrov-adhoc
authored andcommitted
[FIX] account_payment_pro: fix write-off amount_currency calculation
- Modified `account_payment_pro/models/account_payment.py`: restore minimal write-off branch while making sign explicit; keep original behaviour of computing `amount_currency` by dividing `write_off_amount` by `exchange_rate` when payment and company currencies differ; preserve `currency_id` and compute `balance` in company currency. - Extended `account_payment_pro/tests/test_account_paymet_pro_unit_test.py` with a new test `test_write_off_line_amounts_company_vs_payment_currency` that sets up a scenario with a company in ARS and a payment in USD, then creates and validates a payment with write-off, checking that the write-off line shows the correct amounts in both currencies. Change note: Ahora, al crear y validar un pago con write-off en otra moneda, la línea de write-off muestra correctamente el importe en la moneda del pago (por ejemplo 1000/1300) y el saldo en la moneda de la compañía (por ejemplo 1000). Se añadió una prueba automática que valida este comportamiento para evitar regresiones. closes #1017 Signed-off-by: rov-adhoc <rov@adhoc.com.ar>
1 parent 9ad1754 commit 86968d4

4 files changed

Lines changed: 88 additions & 3 deletions

File tree

account_payment_pro/models/account_move.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ def pay_now(self):
7272
payment.payment_method_id = pay_journal._get_manual_payment_method_id(payment_type).id
7373

7474
payment.amount = abs(difference)
75+
payment.amount_exact = abs(difference)
7576
payment.action_post()
7677
rec.write({"matched_payment_ids": [(4, payment.id)]})
7778

account_payment_pro/models/account_move_line.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ def action_register_payment(self, ctx=None):
7272
"default_partner_type": partner_type,
7373
"default_partner_id": to_pay_partner_id,
7474
"default_amount": abs(to_pay_amount),
75+
"default_amount_exact": abs(to_pay_amount),
7576
"default_to_pay_move_line_ids": to_pay_move_lines.ids,
7677
# We set this because if became from other view and in the context has 'create=False'
7778
# you can't crate payment lines (for ej: subscription)

account_payment_pro/models/account_payment.py

Lines changed: 83 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,33 @@ class AccountPayment(models.Model):
132132
use_payment_pro = fields.Boolean(compute="_compute_use_payment_pro")
133133

134134
open_move_line_ids = fields.One2many(related="move_id.open_move_line_ids")
135+
# Campo técnico para round-trip del onchange: el cliente lo devuelve en cada llamada,
136+
# evitando depender de _origin (que no se actualiza entre onchanges y no existe en registros nuevos).
137+
previous_currency_id = fields.Many2one(
138+
"res.currency",
139+
store=True,
140+
copy=False,
141+
)
142+
amount_exact = fields.Float(
143+
string="Amount (Exact)",
144+
digits=0,
145+
copy=False,
146+
help="Exact value of amount with full precision, used internally for conversions to avoid rounding errors.",
147+
)
148+
149+
@api.model
150+
def default_get(self, fields_list):
151+
res = super().default_get(fields_list)
152+
if "previous_currency_id" in fields_list and "previous_currency_id" not in res:
153+
currency_id = res.get("currency_id")
154+
if not currency_id:
155+
journal_id = res.get("journal_id") or self._context.get("default_journal_id")
156+
if journal_id:
157+
journal = self.env["account.journal"].browse(journal_id)
158+
currency_id = (journal.currency_id or journal.company_id.currency_id).id
159+
if currency_id:
160+
res["previous_currency_id"] = currency_id
161+
return res
135162

136163
@api.depends("journal_id")
137164
def _compute_counterpart_currency_id(self):
@@ -167,7 +194,16 @@ def action_draft(self):
167194
self.move_id.posted_before = False
168195
super().action_draft()
169196

197+
@api.model_create_multi
198+
def create(self, vals_list):
199+
for vals in vals_list:
200+
if "amount" in vals and "amount_exact" not in vals:
201+
vals["amount_exact"] = vals["amount"]
202+
return super().create(vals_list)
203+
170204
def write(self, vals):
205+
if "amount" in vals and "amount_exact" not in vals:
206+
vals["amount_exact"] = vals["amount"]
171207
for rec in self:
172208
if rec.company_id.use_payment_pro or (
173209
"company_id" in vals and rec.env["res.company"].browse(vals["company_id"]).use_payment_pro
@@ -290,21 +326,65 @@ def _onchange_company_id(self):
290326
if self._origin.company_id and self.company_id != self._origin.company_id and self.state == "draft":
291327
self.remove_all()
292328

293-
@api.depends("amount", "other_currency", "force_amount_company_currency")
329+
@api.onchange("amount")
330+
def _onchange_amount_update_exact(self):
331+
for rec in self:
332+
if not rec.currency_id.is_zero(rec.amount - rec.amount_exact):
333+
rec.amount_exact = rec.amount
334+
335+
@api.onchange("currency_id")
336+
def _onchange_currency_recompute_amount(self):
337+
"""Al cambiar la moneda del diario, reconvertir amount a la nueva moneda A."""
338+
for rec in self:
339+
new_currency = rec.currency_id
340+
# previous_currency_id se round-tripea desde el cliente en cada onchange,
341+
# por eso refleja la moneda real anterior (funciona en registros nuevos y
342+
# en cambios consecutivos A→B→C sin guardar, donde _origin no sirve).
343+
old_currency = rec.previous_currency_id or rec._origin.currency_id
344+
if not old_currency:
345+
old_currency = rec.company_currency_id
346+
# Actualizar para el próximo onchange antes de cualquier continue
347+
rec.previous_currency_id = new_currency
348+
if rec.state != "draft" or not rec.amount:
349+
continue
350+
351+
old_amount = rec.amount_exact or rec.amount
352+
if not old_amount:
353+
old_amount = rec.env.context.get("default_amount", 0.0)
354+
amount = abs(
355+
old_currency._convert(
356+
old_amount,
357+
new_currency,
358+
rec.company_id,
359+
rec.date or fields.Date.context_today(rec),
360+
False,
361+
)
362+
)
363+
if (
364+
rec.env.context.get("default_amount")
365+
and rec.currency_id == rec.company_currency_id
366+
and rec.amount_exact == rec._origin.amount_exact
367+
and not rec.currency_id.is_zero(amount - rec.env.context.get("default_amount"))
368+
):
369+
amount = rec.env.context.get("default_amount")
370+
rec.update({"amount_exact": amount, "amount": amount})
371+
372+
@api.depends("amount", "amount_exact", "other_currency", "force_amount_company_currency")
294373
def _compute_amount_company_currency(self):
295374
"""
296375
* Si las monedas son iguales devuelve 1
297376
* si no, si hay force_amount_company_currency, devuelve ese valor
298377
* sino, devuelve el amount convertido a la moneda de la cia
299378
"""
300379
for rec in self:
380+
amount = rec.amount_exact or rec.amount
301381
if not rec.other_currency:
302-
amount_company_currency = rec.amount
382+
amount_company_currency = amount
303383
elif rec.force_amount_company_currency:
304384
amount_company_currency = rec.force_amount_company_currency
305385
else:
306386
amount_company_currency = rec.currency_id._convert(
307-
rec.amount, rec.company_id.currency_id, rec.company_id, rec.date
387+
amount, rec.company_id.currency_id, rec.company_id, rec.date
308388
)
309389
rec.amount_company_currency = amount_company_currency
310390

account_payment_pro/views/account_payment_view.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,9 @@
5757

5858
<div name="amount_div" position="after">
5959
<field name="company_currency_id" invisible="True"/>
60+
<!-- Campo técnico: round-trip de la moneda anterior para _onchange_currency_recompute_amount -->
61+
<field name="previous_currency_id" invisible="True"/>
62+
<field name="amount_exact" invisible="True"/>
6063
<field name="other_currency" invisible="True"/>
6164
<field name="force_amount_company_currency" invisible="True"/>
6265
<div class="o_row" name="exchange_rate_div" invisible="not other_currency">

0 commit comments

Comments
 (0)