diff --git a/addons/account/models/account_move_line.py b/addons/account/models/account_move_line.py index 33df5cc5103..fd8f50c0b33 100644 --- a/addons/account/models/account_move_line.py +++ b/addons/account/models/account_move_line.py @@ -1663,23 +1663,29 @@ class AccountMoveLine(models.Model): return self[field] @api.model - def _prepare_move_line_residual_amounts(self, aml_values, counterpart_currency, shadowed_aml_values=None): + def _prepare_move_line_residual_amounts(self, aml_values, counterpart_currency, shadowed_aml_values=None, other_aml_values=None): """ Prepare the available residual amounts for each currency. :param aml_values: The values of account.move.line to consider. :param counterpart_currency: The currency of the opposite line this line will be reconciled with. :param shadowed_aml_values: A mapping aml -> dictionary to replace some original aml values to something else. This is usefull if you want to preview the reconciliation before doing some changes on amls like changing a date or an account. + :param other_aml_values: The other aml values to be reconciled with the current one. :return: A mapping currency -> dictionary containing: * residual: The residual amount left for this currency. * rate: The rate applied regarding the company's currency. """ - def get_odoo_rate(aml, currency): + def is_payment(aml): + return aml.move_id.payment_id or aml.move_id.statement_line_id + + def get_odoo_rate(aml, other_aml, currency): if aml.move_id.is_invoice(include_receipts=True): exchange_rate_date = aml.move_id.invoice_date else: exchange_rate_date = aml._get_reconciliation_aml_field_value('date', shadowed_aml_values) + if other_aml and not is_payment(aml) and is_payment(other_aml): + exchange_rate_date = other_aml._get_reconciliation_aml_field_value('date', shadowed_aml_values) return currency._get_conversion_rate(aml.company_currency_id, currency, aml.company_id, exchange_rate_date) def get_accounting_rate(aml, currency): @@ -1689,6 +1695,7 @@ class AccountMoveLine(models.Model): return abs(amount_currency / balance) aml = aml_values['aml'] + other_aml = (other_aml_values or {}).get('aml') remaining_amount_curr = aml_values['amount_residual_currency'] remaining_amount = aml_values['amount_residual'] company_currency = aml.company_currency_id @@ -1715,7 +1722,7 @@ class AccountMoveLine(models.Model): and is_rec_pay_account \ and not has_zero_residual \ and counterpart_currency != company_currency: - rate = get_odoo_rate(aml, counterpart_currency) + rate = get_odoo_rate(aml, other_aml, counterpart_currency) residual_in_foreign_curr = counterpart_currency.round(remaining_amount * rate) if not counterpart_currency.is_zero(residual_in_foreign_curr): available_residual_per_currency[counterpart_currency] = { @@ -1768,11 +1775,13 @@ class AccountMoveLine(models.Model): debit_values, credit_currency, shadowed_aml_values=shadowed_aml_values, + other_aml_values=credit_values, ) credit_available_residual_amounts = self._prepare_move_line_residual_amounts( credit_values, debit_currency, shadowed_aml_values=shadowed_aml_values, + other_aml_values=debit_values, ) if debit_currency != company_currency \ diff --git a/addons/account/tests/test_account_move_reconcile.py b/addons/account/tests/test_account_move_reconcile.py index b7ccc48fe8e..f8a11433493 100644 --- a/addons/account/tests/test_account_move_reconcile.py +++ b/addons/account/tests/test_account_move_reconcile.py @@ -1800,6 +1800,42 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon): {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True}, ]) + def test_reconcile_invoice_company_curr_payment_foreign_curr(self): + """ Test we always use the payment rate in priority when performing a reconciliation. """ + comp_curr = self.company_data['currency'] + foreign_curr = self.currency_data['currency'] + + invoice = self.env['account.move'].create({ + 'move_type': 'out_invoice', + 'invoice_date': '2017-01-01', + 'date': '2017-01-01', + 'partner_id': self.partner_a.id, + 'currency_id': comp_curr.id, + 'invoice_line_ids': [Command.create({ + 'product_id': self.product_a.id, + 'price_unit': 60.0, + 'tax_ids': [], + })], + }) + invoice.action_post() + + payment = self.env['account.payment.register']\ + .with_context(active_model=invoice._name, active_ids=invoice.ids)\ + .create({ + 'payment_date': '2016-01-01', + 'amount': 90.0, + 'currency_id': foreign_curr.id, + })\ + ._create_payments() + + lines = (invoice + payment.move_id).line_ids\ + .filtered(lambda x: x.account_id.account_type == 'asset_receivable') + self.assertRecordValues(lines, [ + # pylint: disable=bad-whitespace + {'amount_residual': 30.0, 'amount_residual_currency': 30.0, 'reconciled': False}, + {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True}, + ]) + def test_reverse_with_multiple_lines(self): """ Test if all lines from a reversed entry are fully reconciled diff --git a/addons/account/tests/test_account_payment_register.py b/addons/account/tests/test_account_payment_register.py index 819bfe8169f..4b504348046 100644 --- a/addons/account/tests/test_account_payment_register.py +++ b/addons/account/tests/test_account_payment_register.py @@ -1123,7 +1123,7 @@ class TestAccountPaymentRegister(AccountTestInvoicingCommon): ]) def test_register_payment_invoice_comp_curr_payment_foreign_curr(self): - # Invoice of 400 USD (equivalent to 1200 Gol in 2016). + # Invoice of 600 USD (equivalent to 1200 Gol in 2017). invoice = self.env['account.move'].create({ 'move_type': 'out_invoice', 'date': '2016-01-01', @@ -1132,7 +1132,7 @@ class TestAccountPaymentRegister(AccountTestInvoicingCommon): 'currency_id': self.company_data['currency'].id, 'invoice_line_ids': [Command.create({ 'product_id': self.product_a.id, - 'price_unit': 400.0, + 'price_unit': 600.0, 'tax_ids': [], })], }) diff --git a/addons/account/wizard/account_payment_register.py b/addons/account/wizard/account_payment_register.py index 3ca43914c2b..6d6c4662790 100644 --- a/addons/account/wizard/account_payment_register.py +++ b/addons/account/wizard/account_payment_register.py @@ -532,15 +532,19 @@ class AccountPaymentRegister(models.TransientModel): ), False elif self.source_currency_id == comp_curr and self.currency_id != comp_curr: # Company currency on source line but a foreign currency one on the opposite line. - return abs(sum( - comp_curr._convert( + residual_amount = 0.0 + for aml in batch_result['lines']: + if not aml.move_id.payment_id and not aml.move_id.statement_line_id: + conversion_date = self.payment_date + else: + conversion_date = aml.date + residual_amount += comp_curr._convert( aml.amount_residual, self.currency_id, self.company_id, - aml.date, + conversion_date, ) - for aml in batch_result['lines'] - )), False + return abs(residual_amount), False else: # Foreign currency on payment different than the one set on the journal entries. return comp_curr._convert( diff --git a/addons/account_check_printing/tests/test_print_check.py b/addons/account_check_printing/tests/test_print_check.py index fa100ffc7a0..4f746084500 100644 --- a/addons/account_check_printing/tests/test_print_check.py +++ b/addons/account_check_printing/tests/test_print_check.py @@ -119,7 +119,7 @@ class TestPrintCheck(AccountTestInvoicingCommon): 'invoice_date': '2016-01-01', 'invoice_line_ids': [Command.create({ 'product_id': self.product_a.id, - 'price_unit': 100.0, + 'price_unit': 150.0, 'tax_ids': [] })] }) @@ -138,8 +138,8 @@ class TestPrintCheck(AccountTestInvoicingCommon): self.assertEqual(stub_pages, [[{ 'due_date': '01/01/2016', 'number': invoice.name, - 'amount_total': f'${NON_BREAKING_SPACE}100.00', - 'amount_residual': f'${NON_BREAKING_SPACE}50.00', + 'amount_total': f'${NON_BREAKING_SPACE}150.00', + 'amount_residual': f'${NON_BREAKING_SPACE}75.00', 'amount_paid': f'150.000{NON_BREAKING_SPACE}☺', 'currency': invoice.currency_id, }]])