From 06f3709b7c993777dc8b6da104a1aec1dc35adab Mon Sep 17 00:00:00 2001 From: andrea Date: Tue, 15 Apr 2025 10:09:59 +0200 Subject: [PATCH 1/2] [IMP] account_mass_reconcile: add test for reconcile with writeoff currency and opposite signs --- .../tests/test_scenario_reconcile.py | 126 ++++++++++++++++++ 1 file changed, 126 insertions(+) diff --git a/account_mass_reconcile/tests/test_scenario_reconcile.py b/account_mass_reconcile/tests/test_scenario_reconcile.py index cafa5270..714a761e 100644 --- a/account_mass_reconcile/tests/test_scenario_reconcile.py +++ b/account_mass_reconcile/tests/test_scenario_reconcile.py @@ -451,3 +451,129 @@ class TestScenarioReconcile(AccountTestInvoicingCommon): ) self.assertEqual(len(writeoff_line), 1) self.assertEqual(writeoff_line.date, fields.Date.today()) + + def test_reconcile_with_writeoff_currency_opposite_signs(self): + """Test write-off when company and foreign residuals have opposite signs.""" + self.env.ref("base.group_multi_currency").users |= self.env.user + self.company.currency_id = self.env.ref("base.EUR") + usd = self.env.ref("base.USD") + usd.active = True + + inv_date = fields.Date.today() - timedelta(days=10) + pay_date = fields.Date.today() - timedelta(days=1) + + # Create currency rates + self.env["res.currency.rate"].create( + [ + { + "name": inv_date, + "currency_id": usd.id, + "rate": 1.2, + "company_id": self.company.id, + }, + { + "name": pay_date, + "rate": 1.25, + "currency_id": usd.id, + "company_id": self.company.id, + }, + { + "name": fields.Date.today(), + "rate": 1.25, + "currency_id": usd.id, + "company_id": self.company.id, + }, + ] + ) + + # Create invoice and payment with opposing residual signs + common_ref = "INV-PAY-OPPOSITE-SIGNS" + invoice = self.init_invoice( + move_type="out_invoice", + partner=self.partner_a, + currency=usd, + amounts=[100], + invoice_date=inv_date, + post=True, + ) + invoice.ref = common_ref + + payment = self.env["account.payment"].create( + { + "partner_type": "customer", + "payment_type": "inbound", + "partner_id": self.partner_a.id, + "destination_account_id": self.company_data[ + "default_account_receivable" + ].id, + "amount": 100.10, + "currency_id": usd.id, + "journal_id": self.bank_journal.id, + "date": pay_date, + "ref": common_ref, + } + ) + payment.action_post() + + # Create and run mass reconciliation + mass_rec = self.mass_rec_obj.create( + { + "name": "mass_reconcile_currency_writeoff_opposite", + "account": self.company_data["default_account_receivable"].id, + "reconcile_method": [ + ( + 0, + 0, + { + "name": "mass.reconcile.advanced.ref", + "account_lost_id": self.company_data[ + "default_account_expense" + ].id, + "account_profit_id": self.company_data[ + "default_account_revenue" + ].id, + "journal_id": self.company_data["default_journal_misc"].id, + "write_off": 3.50, + "date_base_on": "newest", + }, + ) + ], + } + ) + existing_moves = self.env["account.move"].search( + [("journal_id", "=", self.company_data["default_journal_misc"].id)] + ) + mass_rec.run_reconcile() + + # Validate write-off move + writeoff_move = self.env["account.move"].search( + [ + ("journal_id", "=", self.company_data["default_journal_misc"].id), + ("id", "not in", existing_moves.ids), + ] + ) + self.assertEqual( + len(writeoff_move), 1, "Exactly one write-off move should exist" + ) + self.assertEqual( + writeoff_move.currency_id, usd, "USD should be the write-off currency" + ) + + pl_line = writeoff_move.line_ids.filtered( + lambda ml: ml.account_id == self.company_data["default_account_expense"] + ) + receivable_line = writeoff_move.line_ids.filtered( + lambda ml: ml.account_id == self.company_data["default_account_receivable"] + ) + + # Check amounts (0.10 USD residual, 3.25 EUR residual) + self.assertAlmostEqual(pl_line.debit, 3.25, 2, "Expense line debit (EUR)") + self.assertAlmostEqual( + pl_line.amount_currency, 0.10, 2, "Expense line USD amount" + ) + self.assertAlmostEqual( + receivable_line.credit, 3.25, 2, "Receivable line credit (EUR)" + ) + self.assertAlmostEqual( + receivable_line.amount_currency, -0.10, 2, "Receivable line USD amount" + ) From 9c0bfd0418b066e4f13e6a58d97d2d3b3e645633 Mon Sep 17 00:00:00 2001 From: andrea Date: Tue, 15 Apr 2025 10:25:11 +0200 Subject: [PATCH 2/2] [FIX] account_mass_reconcile: reconcile with writeoff currency and opposite signs --- .../models/base_reconciliation.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/account_mass_reconcile/models/base_reconciliation.py b/account_mass_reconcile/models/base_reconciliation.py index 3f3f7950..433d8b93 100644 --- a/account_mass_reconcile/models/base_reconciliation.py +++ b/account_mass_reconcile/models/base_reconciliation.py @@ -158,9 +158,20 @@ class MassReconcileBase(models.AbstractModel): currency = same_curr and lines[0].currency_id or lines[0].company_id.currency_id journal = self.journal_id partners = lines.mapped("partner_id") + + # Adjust amount_currency to match the sign of the company's residual (amount) + if same_curr: + if amount != 0: + sign = 1 if amount > 0 else -1 + adjusted_amount_curr = abs(amount_curr) * sign + else: + adjusted_amount_curr = 0.0 + else: + adjusted_amount_curr = amount + write_off_vals = { "name": _("Automatic writeoff"), - "amount_currency": same_curr and amount_curr or amount, + "amount_currency": adjusted_amount_curr if same_curr else amount, "debit": amount > 0.0 and amount or 0.0, "credit": amount < 0.0 and -amount or 0.0, "partner_id": len(partners) == 1 and partners.id or False,