From 49d3e8e68dcf1c90c69ad83a89398af03c06d3aa Mon Sep 17 00:00:00 2001 From: Laurent Smet Date: Mon, 4 Apr 2022 16:52:03 +0000 Subject: [PATCH] [FIX] account: Fix reconciliation rounding issue MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Steps to reproduce: - Create an invoice: select a product; set the price to 23.00$; change the currency to €; confirm the invoice. - Create a receiving payment from the same customer: select the $ currency and put 100$ - Go back to the invoice and select the payment/reconcile the invoice and the payment. Issue: The payment is partial with 0.01€ to be paid. Cause: (conversion rate: 1.5289$/1€) 1) When the currency is changed or the price assigned, we convert the price EXCL. and then we convert the tax amount. https://github.com/odoo/odoo/blob/582b43bdf46c485be8219e1cbeb60dbed033f1e7/addons/account/models/account_move.py#L3953-L3954 In our example: - price EXCL.: 23€ * 1.5289 = 35.1647$ price._convert() -> 35.16$ - Tax amount: 3.45€ * 1.5289 = 5.274705$ tax_amount._convert() -> 5.27$ Real Sum = 40,439405$ Sum returned after convertion = 40.43$ != 40.44$ 2) To compute what is left to be paid, we use the converted amount and convert it back to the currency of the invoice. Double conversion, double mistake. https://github.com/odoo/odoo/blob/582b43bdf46c485be8219e1cbeb60dbed033f1e7/addons/account/models/account_move.py#L4873-L4878 In our example: 3) We convert back the 40.43$ to know the residual: - 40.43$ / 1.5289 = 26.443848519€ => rounded = 26.44€ != 26.45 We then compute the `debit_amount_residual_currency` (26.45€) by substracting from it the `min_debit_amount_residual_currency`(26.44€). https://github.com/odoo/odoo/blob/582b43bdf46c485be8219e1cbeb60dbed033f1e7/addons/account/models/account_move.py#L4886-L4887 Which lefts us with a `debit_amount_residual_currency` 0.01€ (0.0099999..801). https://github.com/odoo/odoo/blob/582b43bdf46c485be8219e1cbeb60dbed033f1e7/addons/account/models/account_move.py#L1421-L1437 Ticket: 2765223 X-original-commit: ce1d64cdcf160e596dba97b29f970a8b9d87b348 Part-of: odoo/odoo#88265 --- .../tests/test_account_move_reconcile.py | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/addons/account/tests/test_account_move_reconcile.py b/addons/account/tests/test_account_move_reconcile.py index 3063e0329e1..a459ffcaaa1 100644 --- a/addons/account/tests/test_account_move_reconcile.py +++ b/addons/account/tests/test_account_move_reconcile.py @@ -2237,6 +2237,39 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon): {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True}, ]) + def test_reconcile_rounding_issue(self): + rate = 1/1.5289 + currency = self.setup_multi_currency_data(default_values={ + 'name': 'XXX', + 'symbol': 'XXX', + 'currency_unit_label': 'XX', + 'currency_subunit_label': 'X', + 'rounding': 0.01, + }, rate2016=rate, rate2017=rate)['currency'] + + # Create an invoice 26.45 XXX = 40.43 USD + invoice = self.env['account.move'].create({ + 'move_type': 'out_invoice', + 'partner_id': self.partner_a.id, + 'currency_id': currency.id, + 'date': '2017-01-01', + 'invoice_date': '2017-01-01', + 'invoice_line_ids': [(0, 0, { + 'product_id': self.product_a.id, + 'price_unit': 23.0, + 'tax_ids': [(6, 0, self.company_data['default_tax_sale'].ids)], + })], + }) + invoice.action_post() + + # Pay it with 100.0 USD + self.env['account.payment.register']\ + .with_context(active_model='account.move', active_ids=invoice.ids)\ + .create({'amount': 100.0, 'currency_id': self.company_data['currency'].id})\ + ._create_payments() + + self.assertTrue(invoice.payment_state in ('in_payment', 'paid')) + # ------------------------------------------------------------------------- # Test creation of extra journal entries during the reconciliation to # deal with taxes that are exigible on payment (cash basis).