@@ -76,8 +76,14 @@ class AccountPayment(models.Model):
7676 # evitando depender de _origin (que no se actualiza entre onchanges y no existe en registros nuevos).
7777 previous_currency_id = fields .Many2one (
7878 "res.currency" ,
79- store = False ,
79+ store = True ,
80+ copy = False ,
81+ )
82+ amount_exact = fields .Float (
83+ string = "Amount (Exact)" ,
84+ digits = 0 ,
8085 copy = False ,
86+ help = "Exact value of amount with full precision, used internally for conversions to avoid rounding errors." ,
8187 )
8288 journal_currency_id = fields .Many2one (related = "journal_id.currency_id" , string = "Journal Currency" )
8389 destination_journal_currency_id = fields .Many2one (
@@ -165,6 +171,20 @@ class AccountPayment(models.Model):
165171 compute = "_compute_multi_currency_debt" ,
166172 )
167173
174+ @api .model
175+ def default_get (self , fields_list ):
176+ res = super ().default_get (fields_list )
177+ if "previous_currency_id" in fields_list and "previous_currency_id" not in res :
178+ currency_id = res .get ("currency_id" )
179+ if not currency_id :
180+ journal_id = res .get ("journal_id" )
181+ if journal_id :
182+ journal = self .env ["account.journal" ].browse (journal_id )
183+ currency_id = (journal .currency_id or journal .company_id .currency_id ).id
184+ if currency_id :
185+ res ["previous_currency_id" ] = currency_id
186+ return res
187+
168188 @api .depends ("to_pay_move_line_ids" , "company_id.reconcile_on_company_currency" )
169189 def _compute_multi_currency_debt (self ):
170190 for rec in self :
@@ -359,6 +379,12 @@ def _compute_write_off_available(self):
359379 rec .env ["account.write_off.type" ].search ([("company_ids" , "=" , rec .company_id .id )], limit = 1 )
360380 )
361381
382+ @api .onchange ("amount" )
383+ def _onchange_amount_update_exact (self ):
384+ for rec in self :
385+ if not rec .currency_id .is_zero (rec .amount - rec .amount_exact ):
386+ rec .amount_exact = rec .amount
387+
362388 @api .onchange ("currency_id" )
363389 def _onchange_currency_recompute_amount (self ):
364390 """Al cambiar la moneda del diario, reconvertir amount a la nueva moneda A."""
@@ -367,21 +393,34 @@ def _onchange_currency_recompute_amount(self):
367393 # previous_currency_id se round-tripea desde el cliente en cada onchange,
368394 # por eso refleja la moneda real anterior (funciona en registros nuevos y
369395 # en cambios consecutivos A→B→C sin guardar, donde _origin no sirve).
370- old_currency = rec .previous_currency_id
396+ old_currency = rec .previous_currency_id or rec ._origin .currency_id
397+ if not old_currency :
398+ old_currency = rec .company_currency_id
371399 # Actualizar para el próximo onchange antes de cualquier continue
372400 rec .previous_currency_id = new_currency
373401 if rec .state != "draft" or not rec .amount :
374402 continue
375- if not old_currency or old_currency == new_currency :
376- continue
377- rec .amount = abs (
403+
404+ old_amount = rec .amount_exact or rec .amount
405+ if not old_amount :
406+ old_amount = rec .env .context .get ("default_amount" , 0.0 )
407+ amount = abs (
378408 old_currency ._convert (
379- rec . amount ,
409+ old_amount ,
380410 new_currency ,
381411 rec .company_id ,
382412 rec .date or fields .Date .context_today (rec ),
413+ False ,
383414 )
384415 )
416+ if (
417+ rec .env .context .get ("default_amount" )
418+ and rec .currency_id == rec .company_currency_id
419+ and rec .amount_exact == rec ._origin .amount_exact
420+ and not rec .currency_id .is_zero (amount - rec .env .context .get ("default_amount" ))
421+ ):
422+ amount = rec .env .context .get ("default_amount" )
423+ rec .update ({"amount_exact" : amount , "amount" : amount })
385424
386425 @api .constrains ("to_pay_move_line_ids" )
387426 def _check_to_pay_lines_account (self ):
@@ -421,7 +460,16 @@ def action_draft(self):
421460
422461 super ().action_draft ()
423462
463+ @api .model_create_multi
464+ def create (self , vals_list ):
465+ for vals in vals_list :
466+ if "amount" in vals and "amount_exact" not in vals :
467+ vals ["amount_exact" ] = vals ["amount" ]
468+ return super ().create (vals_list )
469+
424470 def write (self , vals ):
471+ if "amount" in vals and "amount_exact" not in vals :
472+ vals ["amount_exact" ] = vals ["amount" ]
425473 for rec in self :
426474 if rec .company_id .use_payment_pro or (
427475 "company_id" in vals and rec .env ["res.company" ].browse (vals ["company_id" ]).use_payment_pro
@@ -476,23 +524,33 @@ def _compute_available_journal_ids(self):
476524 @api .depends ("amount" , "counterpart_rate" , "counterpart_currency_id" , "currency_id" )
477525 def _compute_counterpart_currency_amount (self ):
478526 for rec in self :
527+ amount = rec .amount_exact or rec .amount
479528 if rec .counterpart_currency_id and rec .counterpart_currency_id != rec .currency_id :
480529 if rec .counterpart_rate :
481530 # amount está en A, convertir a B1 usando counterpart_rate
482- rec .counterpart_currency_amount = rec . amount * rec .counterpart_rate
531+ rec .counterpart_currency_amount = amount * rec .counterpart_rate
483532 else :
484533 rec .counterpart_currency_amount = 0.0
485534 else :
486535 # A == B1, son la misma moneda
487- rec .counterpart_currency_amount = rec . amount
536+ rec .counterpart_currency_amount = amount
488537
489538 @api .onchange ("counterpart_currency_amount" )
490539 def _inverse_counterpart_currency_amount (self ):
491540 for rec in self :
541+ # Usar amount_exact para comparación precisa
542+ amount_to_compare = rec .amount_exact if rec .amount_exact else rec .amount
492543 if rec .counterpart_currency_id and not rec .counterpart_currency_id .is_zero (
493- rec . amount * rec .counterpart_rate - rec .counterpart_currency_amount
544+ amount_to_compare * rec .counterpart_rate - rec .counterpart_currency_amount
494545 ):
495- rec .amount = rec .counterpart_currency_amount / rec .counterpart_rate if rec .counterpart_rate else 0
546+ # Usar amount_exact para cálculo preciso sin pérdida de decimales
547+ if rec .counterpart_rate :
548+ exact_amount = rec .counterpart_currency_amount / rec .counterpart_rate
549+ rec .amount_exact = exact_amount
550+ rec .amount = exact_amount
551+ else :
552+ rec .amount_exact = 0
553+ rec .amount = 0
496554
497555 @api .depends (
498556 "accounting_rate" , "counterpart_currency_id" , "currency_id" , "company_currency_id" , "company_id" , "date"
@@ -552,7 +610,8 @@ def _prepare_move_lines_per_type(self, write_off_line_vals=None, force_balance=N
552610 # Para pagos sin ppro que tengan accounting rate, forzamos el balance
553611 # para que no haya diferencias en el asiento
554612 if self .accounting_rate and self .currency_id != self .company_currency_id and force_balance is None :
555- force_balance = self .amount / self .accounting_rate # A/C → monto en C
613+ amount_for_calc = self .amount_exact if self .amount_exact else self .amount
614+ force_balance = amount_for_calc / self .accounting_rate # A/C → monto en C
556615 return super ()._prepare_move_lines_per_type (
557616 write_off_line_vals = write_off_line_vals , force_balance = force_balance
558617 )
@@ -599,8 +658,17 @@ def _prepare_move_lines_per_type(self, write_off_line_vals=None, force_balance=N
599658 # Cuando force_balance está definido, el balance ya fue forzado por base Odoo
600659 # (ej: paired payment de transferencia interna) y NO debe recalcularse.
601660 if self .accounting_rate and self .currency_id != self .company_currency_id and force_balance is None :
602- for liq_line in liquidity_lines :
603- liq_line ["balance" ] = liq_line ["amount_currency" ] / self .accounting_rate
661+ # Usar amount_exact si está disponible para evitar desbalances por redondeo.
662+ # Cuando hay una sola línea de liquidez, usamos amount_exact directamente.
663+ # Cuando hay múltiples líneas (cheques), usamos el amount_currency de cada una.
664+ if len (liquidity_lines ) == 1 and self .amount_exact and self .amount_exact != self .amount :
665+ amount_for_balance = self .amount_exact
666+ liq_sign = 1 if liquidity_lines [0 ]["amount_currency" ] >= 0 else - 1
667+ liquidity_lines [0 ]["amount_currency" ] = liq_sign * abs (amount_for_balance )
668+ liquidity_lines [0 ]["balance" ] = liquidity_lines [0 ]["amount_currency" ] / self .accounting_rate
669+ else :
670+ for liq_line in liquidity_lines :
671+ liq_line ["balance" ] = liq_line ["amount_currency" ] / self .accounting_rate
604672
605673 # ── Recalcular balance de CONTRAPARTIDA para cerrar el asiento ────────────
606674 write_off_balance = sum (line ["balance" ] for line in res .get ("write_off_lines" , []))
@@ -663,10 +731,12 @@ def _prepare_paired_payment_values(self):
663731 dest_currency = self .destination_journal_id .currency_id or self .company_currency_id
664732 if dest_currency != self .currency_id :
665733 # balance_in_c: monto en moneda de compañía (ARS)
734+ # Usar amount_exact para cálculos precisos
735+ amount_for_calc = self .amount_exact if self .amount_exact else self .amount
666736 if self .accounting_rate and self .currency_id != self .company_currency_id :
667- balance_in_c = self . amount / self .accounting_rate
737+ balance_in_c = amount_for_calc / self .accounting_rate
668738 else :
669- balance_in_c = self . amount
739+ balance_in_c = amount_for_calc
670740
671741 if dest_currency == self .counterpart_currency_id and self .counterpart_currency_amount :
672742 # counterpart_currency_id coincide con la moneda destino (caso habitual
@@ -685,13 +755,14 @@ def _prepare_paired_payment_values(self):
685755 )
686756 paired_amount = dest_currency .round (balance_in_c * dest_rate )
687757
758+ vals ["amount_exact" ] = paired_amount
688759 vals ["amount" ] = paired_amount
689760
690761 # counterpart_currency_amount del paired = monto original (cuánto
691762 # salió del journal original). Lo pasamos explícitamente porque copy()
692763 # copia el valor del original y al tener inverse= el ORM ejecuta el
693764 # inverse durante create, sobreescribiendo amount.
694- vals ["counterpart_currency_amount" ] = self .amount
765+ vals ["counterpart_currency_amount" ] = self .amount_exact if self . amount_exact else self . amount
695766
696767 # Fijar accounting_rate del paired para que refleje la tasa implícita
697768 # real de la operación (balance_in_c / paired_amount), no la del día.
@@ -702,7 +773,7 @@ def _prepare_paired_payment_values(self):
702773 # moneda del journal original (B1_paired = self.currency_id).
703774 # rate = original_amount / paired_amount = B1/A del paired.
704775 if paired_amount :
705- vals ["counterpart_rate" ] = self .amount / paired_amount
776+ vals ["counterpart_rate" ] = ( self .amount_exact if self . amount_exact else self . amount ) / paired_amount
706777
707778 return vals
708779
@@ -844,11 +915,16 @@ def _compute_has_outstanding(self):
844915 def _compute_payment_total (self ):
845916 for rec in self :
846917 if rec .counterpart_currency_id == rec .destination_currency_id :
847- # B1 == B2 (caso normal sin reconcile): cca ya está en B2
918+ # B1 == B2 (caso normal sin reconcile): cca ya está en B2.
919+ # Aplica tanto si A != C (journal en moneda extranjera) como si A == C
920+ # (journal en moneda de compañía), porque counterpart_currency_amount
921+ # siempre está expresado en B1 = B2 = destination_currency_id.
848922 base_amount = rec .counterpart_currency_amount
849923 else :
850924 # B1 != B2 (reconcile_on_company_currency): B2 = C siempre
851- # Convertir A → C = amount / accounting_rate
925+ # Convertir A → C = amount_exact / accounting_rate (usar amount_exact para evitar redondeos)
926+ amount_for_calc = rec .amount_exact if rec .amount_exact else rec .amount
927+ base_amount = amount_for_calc / rec .accounting_rate if rec .accounting_rate else amount_for_calc
852928 base_amount = rec .amount / rec .accounting_rate if rec .accounting_rate else rec .amount
853929 rec .payment_total = base_amount + rec .write_off_amount
854930
@@ -875,8 +951,11 @@ def action_adjust_amount_for_difference(self):
875951 if not rec .payment_difference :
876952 continue
877953 diff_in_a = rec ._get_payment_difference_in_currency_a ()
878- amount = rec .amount + diff_in_a
879- rec .amount = amount if amount > 0 else 0
954+ amount = rec .amount_exact + diff_in_a
955+ # No permitir valores negativos, pero mantener el valor actual si el ajuste resulta negativo
956+ if amount > 0 :
957+ rec .amount_exact = amount
958+ rec .amount = amount
880959
881960 def action_adjust_writeoff_for_difference (self ):
882961 """Ajusta write_off_amount para que payment_difference quede en cero."""
0 commit comments