diff --git a/account_payment_pro/models/account_payment.py b/account_payment_pro/models/account_payment.py index 7931d2521..4cabe723e 100644 --- a/account_payment_pro/models/account_payment.py +++ b/account_payment_pro/models/account_payment.py @@ -149,6 +149,18 @@ class AccountPayment(models.Model): @api.model def default_get(self, fields_list): res = super().default_get(fields_list) + # Si se pasa company_id explícitamente por contexto, evitamos que journal_id + # proveniente de ir.default (valores predeterminados del usuario) y perteneciente + # a otra compañía dispare el precompute _compute_company_id y sobreescriba la + # compañía correcta del pago por la compañía principal del entorno. + default_company_id = self._context.get("default_company_id") + if default_company_id and "journal_id" in res: + journal = self.env["account.journal"].browse(res["journal_id"]) + if journal.company_id.id != default_company_id: + res.pop("journal_id") + ir_defaults = self.env["ir.default"].with_company(default_company_id)._get_model_defaults(self._name) + if "journal_id" in ir_defaults: + res["journal_id"] = self.env["account.journal"].browse(ir_defaults["journal_id"]).id if "previous_currency_id" in fields_list and "previous_currency_id" not in res: currency_id = res.get("currency_id") if not currency_id: @@ -378,11 +390,14 @@ def _onchange_currency_recompute_amount(self): amount = rec.env.context.get("default_amount") rec.update({"amount_exact": amount, "amount": amount}) - @api.depends("amount", "amount_exact", "other_currency", "force_amount_company_currency") + @api.depends( + "amount", "amount_exact", "other_currency", "force_amount_company_currency", "amount_company_currency_signed" + ) def _compute_amount_company_currency(self): """ * Si las monedas son iguales devuelve 1 * si no, si hay force_amount_company_currency, devuelve ese valor + * si ya hay asiento, usa el importe contable nativo sin signo * sino, devuelve el amount convertido a la moneda de la cia """ for rec in self: @@ -391,9 +406,14 @@ def _compute_amount_company_currency(self): amount_company_currency = amount elif rec.force_amount_company_currency: amount_company_currency = rec.force_amount_company_currency + elif rec.move_id: + amount_company_currency = abs(rec.amount_company_currency_signed) else: amount_company_currency = rec.currency_id._convert( - amount, rec.company_id.currency_id, rec.company_id, rec.date + amount, + rec.company_id.currency_id, + rec.company_id, + rec.date, ) rec.amount_company_currency = amount_company_currency diff --git a/account_payment_pro/tests/test_account_paymet_pro_unit_test.py b/account_payment_pro/tests/test_account_paymet_pro_unit_test.py index 99a565bac..2f0c93ca7 100644 --- a/account_payment_pro/tests/test_account_paymet_pro_unit_test.py +++ b/account_payment_pro/tests/test_account_paymet_pro_unit_test.py @@ -169,6 +169,36 @@ def test_force_amount_company_currency_without_payment_pro(self): msg="Liquidity line balance should still use forced amount after synchronization", ) + def test_posted_payment_without_payment_pro_keeps_accounting_rate_after_rate_change(self): + self.company.use_payment_pro = False + + payment = self.env["account.payment"].create( + { + "payment_type": "inbound", + "partner_type": "customer", + "partner_id": self.partner_ri.id, + "journal_id": self.company_bank_journal.id, + "amount": 100.0, + "currency_id": self.eur_currency.id, + "date": self.today, + } + ) + payment.action_post() + + liquidity_lines = payment.move_id.line_ids.filtered( + lambda line: line.account_id == payment.outstanding_account_id + ) + accounting_amount = abs(sum(liquidity_lines.mapped("balance"))) + self.assertAlmostEqual(accounting_amount, 100000.0, places=2) + self.assertAlmostEqual(payment.amount_company_currency, accounting_amount, places=2) + self.assertAlmostEqual(payment.exchange_rate, 1000.0, places=2) + + self.rates[1].inverse_company_rate = 2000 + payment.invalidate_recordset(["amount_company_currency", "exchange_rate"]) + + self.assertAlmostEqual(payment.amount_company_currency, accounting_amount, places=2) + self.assertAlmostEqual(payment.exchange_rate, 1000.0, places=2) + def test_write_off_line_amounts_company_vs_payment_currency(self): """Minimal test: company currency vs payment currency, force company amount and check write-off line balance""" # Use existing company and ensure we have a different currency for the payment diff --git a/l10n_ar_payment_bundle/models/__init__.py b/l10n_ar_payment_bundle/models/__init__.py index bb2a9536b..839ada5fc 100644 --- a/l10n_ar_payment_bundle/models/__init__.py +++ b/l10n_ar_payment_bundle/models/__init__.py @@ -4,3 +4,4 @@ from . import account_payment_method from . import account_journal from . import account_payment_register +from . import account_move_line diff --git a/l10n_ar_payment_bundle/models/account_move_line.py b/l10n_ar_payment_bundle/models/account_move_line.py new file mode 100644 index 000000000..59662f649 --- /dev/null +++ b/l10n_ar_payment_bundle/models/account_move_line.py @@ -0,0 +1,22 @@ +from odoo import models + + +class AccountMoveLine(models.Model): + _inherit = "account.move.line" + + def action_register_payment(self, ctx=None): + action = super().action_register_payment(ctx=ctx) + # Si el diario por defecto es bundle + journal = self.env["account.journal"].search( + [ + *self.env["account.journal"]._check_company_domain(self.company_id), + ("type", "in", ["bank", "cash", "credit"]), + ], + limit=1, + ) + + if "payment_bundle" in ( + journal.inbound_payment_method_line_ids + journal.outbound_payment_method_line_ids + ).mapped("code"): + action.setdefault("context", {})["default_amount"] = 0 + return action diff --git a/l10n_ar_payment_bundle/models/account_payment.py b/l10n_ar_payment_bundle/models/account_payment.py index 0aed3f5ac..f9bbb0b42 100644 --- a/l10n_ar_payment_bundle/models/account_payment.py +++ b/l10n_ar_payment_bundle/models/account_payment.py @@ -355,4 +355,5 @@ def _compute_warnings(self): @api.onchange("to_pay_move_line_ids") def _onchange_amount(self): - super(AccountPayment, self.filtered(lambda r: r.payment_method_code != "payment_bundle"))._onchange_amount() + payments = self.filtered(lambda r: r.payment_method_code != "payment_bundle" and not r.main_payment_id) + super(AccountPayment, payments)._onchange_amount() diff --git a/l10n_ar_payment_bundle/tests/test_payment_difference.py b/l10n_ar_payment_bundle/tests/test_payment_difference.py index 9a632b133..deab4eb17 100644 --- a/l10n_ar_payment_bundle/tests/test_payment_difference.py +++ b/l10n_ar_payment_bundle/tests/test_payment_difference.py @@ -357,3 +357,57 @@ def test_payment_difference_zero_when_exact(self): places=2, msg="Payment difference should be 0 when amounts match exactly", ) + + def test_link_payment_onchange_does_not_override_remaining_amount(self): + """Linked payments should keep the remaining amount suggested by the x2many context.""" + invoice = self._create_invoice(10511.35) + invoice.action_post() + + payment = self._create_payment_bundle() + payment.to_pay_move_line_ids = invoice.line_ids.filtered( + lambda l: l.account_id.account_type in ("asset_receivable", "liability_payable") + ) + + self.env["account.payment"].create( + { + "payment_type": "inbound", + "partner_type": "customer", + "partner_id": self.partner.id, + "journal_id": self.bank_journal.id, + "amount": 500.0, + "main_payment_id": payment.id, + } + ) + + virtual_payment = self.env["account.payment"].new( + { + "payment_type": "inbound", + "partner_type": "customer", + "partner_id": self.partner.id, + "journal_id": self.bundle_journal.id, + "payment_method_line_id": self.payment_method_line.id, + "amount": 0.0, + "to_pay_move_line_ids": [ + Command.set( + invoice.line_ids.filtered( + lambda l: l.account_id.account_type in ("asset_receivable", "liability_payable") + ).ids + ) + ], + } + ) + linked_payment = self.env["account.payment"].new( + { + "payment_type": "inbound", + "partner_type": "customer", + "partner_id": self.partner.id, + "journal_id": self.bank_journal.id, + "amount": 10011.35, + "main_payment_id": virtual_payment, + } + ) + self.assertFalse(linked_payment.main_payment_id._origin) + + linked_payment._onchange_amount() + + self.assertAlmostEqual(linked_payment.amount, 10011.35, places=2) diff --git a/l10n_latam_check_ux/models/account_payment.py b/l10n_latam_check_ux/models/account_payment.py index f72b39946..79c1a1ddc 100644 --- a/l10n_latam_check_ux/models/account_payment.py +++ b/l10n_latam_check_ux/models/account_payment.py @@ -112,3 +112,43 @@ def action_draft(self): ) super().action_draft() + + def _is_latam_check_transfer(self): + self.ensure_one() + return super()._is_latam_check_transfer() or ( + self.is_internal_transfer + and bool(self.l10n_latam_move_check_ids) + and self.destination_account_id == self.company_id.transfer_account_id + ) + + @api.constrains( + "is_internal_transfer", + "payment_type", + "payment_method_line_id", + "destination_journal_id", + "l10n_latam_move_check_ids", + ) + def _check_inbound_transfer_checks_current_journal(self): + """Keep server-side behavior aligned with the wizard domain in Odoo. + + For inbound internal transfers receiving third-party checks, all selected checks + must come from the same current journal: the source journal (`destination_journal_id`). + """ + for rec in self.filtered( + lambda x: ( + x.state == "draft" + and x.is_internal_transfer + and x.payment_type == "inbound" + and x.payment_method_line_id.code == "in_third_party_checks" + and x.destination_journal_id + and x.l10n_latam_move_check_ids + ) + ): + invalid_checks = rec.l10n_latam_move_check_ids.filtered( + lambda c: c.current_journal_id != rec.destination_journal_id + ) + if invalid_checks: + raise ValidationError( + "All selected checks must belong to the source journal (%s)." + % rec.destination_journal_id.display_name + )