From cb2d8943ea968c51b430832071b9d2b7bf44b594 Mon Sep 17 00:00:00 2001 From: Jorge Pinna Puissant Date: Fri, 27 Dec 2019 12:59:50 +0000 Subject: [PATCH] [FIX] account: compute of amount residual in foreign currency When having an invoice in foreign currency and a partial payment in domestic currency (in a different date with a different exchange rate), the computed amount due is incorrect. When computing the amount_residual_currency the payment (in domestic currency) is converted to the foreign currency using the exchange rate of the invoice date, and not the one of the payment date (see 56d92213b82d417e48cb5a3432eb57dcba706b6e). If the account.partial.reconcile line will have a currency_id and a amount_currency the amount_residual_currency will not be converted but simply added. This behaviour was introduced with e1306066026649acd388b49d7429337d87cf819a and backported to version 11.0 with 3363d7235137f286458c734a1a0417beeb1f66bb. In version 12.0 this behaviour was partially reverted in d3d26120614139fd7d7e888bd66d21de5158a034. opw-2156001 closes odoo/odoo#44181 X-original-commit: 94d34e052f3757c21884fd5a57cc68ea8c51aafb Signed-off-by: Jorge Pinna Puissant (jpp) --- addons/account/models/account_move.py | 8 +++ addons/account/tests/test_payment.py | 59 +++++++++++++++++++++ addons/account/tests/test_reconciliation.py | 13 +++-- 3 files changed, 73 insertions(+), 7 deletions(-) diff --git a/addons/account/models/account_move.py b/addons/account/models/account_move.py index c729a0f9933..e8d61edd853 100644 --- a/addons/account/models/account_move.py +++ b/addons/account/models/account_move.py @@ -3653,6 +3653,14 @@ class AccountMoveLine(models.Model): currency = credit_move.currency_id.id amount_reconcile_currency = temp_amount_residual_currency amount_reconcile = temp_amount_residual + elif bool(debit_move.currency_id) != bool(credit_move.currency_id): + # If only one of debit_move or credit_move has a secondary currency, also record the converted amount + # in that secondary currency in the partial reconciliation. That allows the exchange difference entry + # to be created, in case it is needed. It also allows to compute the amount residual in foreign currency. + currency = debit_move.currency_id or credit_move.currency_id + currency_date = debit_move.currency_id and credit_move.date or debit_move.date + amount_reconcile_currency = company_currency._convert(amount_reconcile, currency, debit_move.company_id, currency_date) + currency = currency.id if cash_basis: tmp_set = debit_move | credit_move diff --git a/addons/account/tests/test_payment.py b/addons/account/tests/test_payment.py index 1c9da598ffd..08235a14369 100644 --- a/addons/account/tests/test_payment.py +++ b/addons/account/tests/test_payment.py @@ -568,3 +568,62 @@ class TestPayment(AccountTestCommon): self.assertEqual(name, move.name) self.assertTrue(transfer_move.name) self.assertNotEqual(name, transfer_move.name) + + def test_partial_payment_inv_foreign_payment_domestic(self): + """ + Invoice of 1000$ (foreign $) at 01/01 with foreign exchange rate of 0.50000 + Payment of 500 (domestic €) at 15/01 with foreign exchange rate of 1.00000 + The residuals should be 500 in foreign and 1500 in domestic. + """ + company = self.env.ref('base.main_company') + self.env['res.currency.rate'].search([]).unlink() + self.env['res.currency.rate'].create({ + 'name': time.strftime('%Y') + '-01-01', + 'rate': 1.0, + 'currency_id': self.currency_eur_id, + 'company_id': company.id + }) + self.env['res.currency.rate'].create({ + 'name': time.strftime('%Y') + '-01-01', + 'rate': 0.5, # Don't change this ! + 'currency_id': self.currency_usd_id, + 'company_id': self.env.ref('base.main_company').id + }) + self.env['res.currency.rate'].create({ + 'name': time.strftime('%Y') + '-01-15', + 'rate': 1.0, # Don't change this ! + 'currency_id': self.currency_usd_id, + 'company_id': self.env.ref('base.main_company').id + }) + inv1 = self.env['account.move'].create({ + 'type': 'out_invoice', + 'partner_id': self.partner_agrolait.id, + 'currency_id': self.currency_usd_id, + 'invoice_date': time.strftime('%Y') + '-01-01', + 'date': time.strftime('%Y') + '-01-01', + 'invoice_line_ids': [ + (0, 0, {'product_id': self.product.id, 'quantity': 1, 'price_unit': 1000}) + ], + }) + inv1.post() + payment = self.payment_model.create({ + 'payment_date': time.strftime('%Y') + '-01-15', + 'payment_method_id': self.payment_method_manual_in.id, + 'payment_type': 'inbound', + 'partner_type': 'customer', + 'partner_id': inv1.partner_id.id, + 'amount': 500, + 'journal_id': self.bank_journal_euro.id, + 'company_id': company.id, + 'currency_id': self.currency_eur_id, + }) + payment.post() + inv1_receivable = inv1.line_ids.filtered(lambda l: l.account_id.internal_type == 'receivable') + pay_receivable = payment.move_line_ids.filtered(lambda l: l.account_id.internal_type == 'receivable') + + self.assertEqual(inv1_receivable.balance, 2000) + self.assertEqual(pay_receivable.balance, -500) + + (inv1_receivable + pay_receivable).reconcile() + self.assertEqual(inv1.amount_residual, 500) + self.assertEqual(inv1.amount_residual_signed, 1500) diff --git a/addons/account/tests/test_reconciliation.py b/addons/account/tests/test_reconciliation.py index 8d5c5500416..d28a673c0e7 100644 --- a/addons/account/tests/test_reconciliation.py +++ b/addons/account/tests/test_reconciliation.py @@ -1855,6 +1855,7 @@ class TestReconciliationExec(TestAccountReconciliationCommon): 'partner_type': 'customer', 'partner_id': inv1.partner_id.id, 'amount': 372.10, + 'payment_date': time.strftime('%Y') + '-07-01', 'journal_id': self.bank_journal_euro.id, 'company_id': company.id, }) @@ -1884,9 +1885,7 @@ class TestReconciliationExec(TestAccountReconciliationCommon): self.assertEqual(inv1_receivable.full_reconcile_id, pay_receivable.full_reconcile_id) self.assertEqual(inv1_receivable.full_reconcile_id, move_balance_receiv.full_reconcile_id) - # This one may be due rounding errors - exchange_rcv = inv1_receivable.full_reconcile_id.exchange_move_id.line_ids.filtered(lambda l: l.account_id.internal_type == 'receivable') - self.assertEqual(exchange_rcv.amount_currency, 0.01) + self.assertFalse(inv1_receivable.full_reconcile_id.exchange_move_id) self.assertEqual(inv1.payment_state, 'paid') self.assertEqual(inv2.payment_state, 'paid') @@ -1967,8 +1966,6 @@ class TestReconciliationExec(TestAccountReconciliationCommon): self.assertEqual(inv1_receivable.full_reconcile_id, pay_receivable.full_reconcile_id) self.assertEqual(inv1_receivable.full_reconcile_id, move_balance_receiv.full_reconcile_id) - self.assertFalse(inv1_receivable.full_reconcile_id.exchange_move_id) - self.assertEqual(inv1.payment_state, 'paid') self.assertEqual(inv2.payment_state, 'paid') @@ -2099,7 +2096,8 @@ class TestReconciliationExec(TestAccountReconciliationCommon): self.assertEqual(inv1_receivable.full_reconcile_id, inv2_receivable.full_reconcile_id) self.assertEqual(inv1_receivable.full_reconcile_id, payment_receivable.full_reconcile_id) - self.assertFalse(inv1_receivable.full_reconcile_id.exchange_move_id) + exchange_rcv = inv1_receivable.full_reconcile_id.exchange_move_id.line_ids.filtered(lambda l: l.account_id.internal_type == 'receivable') + self.assertEqual(exchange_rcv.amount_currency, 0.01) self.assertEqual(inv1.payment_state, 'paid') self.assertEqual(inv2.payment_state, 'paid') @@ -2296,6 +2294,7 @@ class TestReconciliationExec(TestAccountReconciliationCommon): self.assertEqual(inv1_receivable.full_reconcile_id, payment_receivable.full_reconcile_id) self.assertEqual(move_balance_receiv.full_reconcile_id, inv1_receivable.full_reconcile_id) - self.assertFalse(inv1_receivable.full_reconcile_id.exchange_move_id) + exchange_rcv = inv1_receivable.full_reconcile_id.exchange_move_id.line_ids.filtered(lambda l: l.account_id.internal_type == 'receivable') + self.assertEqual(exchange_rcv.amount_currency, 0.01) self.assertEqual(inv1.payment_state, 'paid')