[FIX] account: Fix reconciliation rounding issue

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
This commit is contained in:
Laurent Smet
2022-04-19 09:47:41 +02:00
parent a915535746
commit 49d3e8e68d
@@ -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).