diff --git a/l10n_it_reverse_charge/models/account_move.py b/l10n_it_reverse_charge/models/account_move.py index 3eb7d884f729..d00d80597694 100644 --- a/l10n_it_reverse_charge/models/account_move.py +++ b/l10n_it_reverse_charge/models/account_move.py @@ -3,6 +3,7 @@ # Copyright 2017 Lorenzo Battistini - Agile Business Group # Copyright 2017 Marco Calcagni - Dinamiche Aziendali srl # Copyright 2023 Simone Rubino - TAKOBI +# Copyright 2024 Nextev Srl from odoo import api, fields, models from odoo.exceptions import UserError @@ -180,16 +181,17 @@ def compute_rc_amount_tax_main_currency(self): The result is converted and rounded based on Company Currency because this value is used for credit/debit. """ - rc_tax_amount = self.get_tax_amount_added_for_rc() + rc_tax_amount_ic = self.get_tax_amount_added_for_rc() + rc_tax_amount_cc = rc_tax_amount_ic invoice_currency = self.currency_id company_currency = self.company_currency_id if invoice_currency != company_currency: - rc_tax_amount = invoice_currency._convert( - rc_tax_amount, company_currency, self.company_id, self.invoice_date + rc_tax_amount_cc = invoice_currency._convert( + rc_tax_amount_ic, company_currency, self.company_id, self.invoice_date ) - return rc_tax_amount + return rc_tax_amount_ic, rc_tax_amount_cc def rc_payment_vals(self, rc_type): """Values for the RC Payment Move.""" @@ -199,15 +201,20 @@ def rc_payment_vals(self, rc_type): "date": self.date, } - def _rc_line_values(self, account, credit, debit): + def _rc_line_values(self, account, credit, debit, amount_currency): """Base Values for the RC Payment Move lines.""" - return { + values = { "name": self.name, "credit": credit, "debit": debit, "account_id": account.id, "currency_id": self.currency_id.id, } + if amount_currency: + sign = 1 if debit else -1 + amount_currency = abs(amount_currency) * sign + values["amount_currency"] = amount_currency + return values def _rc_credit_line_amounts(self, amount): if self.is_inbound(): @@ -230,7 +237,9 @@ def rc_payment_credit_line_vals(self, line_to_reconcile): ) account = line_to_reconcile.account_id - line_values = self._rc_line_values(account, credit, debit) + line_values = self._rc_line_values( + account, credit, debit, line_to_reconcile.amount_currency + ) line_values.update( { "partner_id": self.partner_id.id, @@ -244,16 +253,18 @@ def rc_payment_debit_line_vals(self, line_to_reconcile, account): abs(line_to_reconcile.balance), ) - line_values = self._rc_line_values(account, credit, debit) + line_values = self._rc_line_values( + account, credit, debit, line_to_reconcile.amount_currency + ) return line_values - def rc_credit_line_vals(self, account, amount): - credit, debit = self._rc_credit_line_amounts(amount) - return self._rc_line_values(account, credit, debit) + def rc_credit_line_vals(self, account, amount_ic, amount_cc): + credit, debit = self._rc_credit_line_amounts(amount_cc) + return self._rc_line_values(account, credit, debit, amount_ic) - def rc_debit_line_vals(self, account, amount): - credit, debit = self._rc_debit_line_amounts(amount) - line_values = self._rc_line_values(account, credit, debit) + def rc_debit_line_vals(self, account, amount_ic, amount_cc): + credit, debit = self._rc_debit_line_amounts(amount_cc) + line_values = self._rc_line_values(account, credit, debit, amount_ic) line_values.update( { "partner_id": self.partner_id.id, @@ -277,6 +288,7 @@ def _prepare_rc_supplier_invoice_payment(self, rc_invoice, rc_type): line_to_reconcile = self._rc_get_move_line_to_reconcile() payment_debit_line_data = self.rc_debit_line_vals( line_to_reconcile.account_id, + payment_credit_line_data["amount_currency"], payment_credit_line_data["credit"], ) rc_payment_data["line_ids"] = [ @@ -318,15 +330,17 @@ def _prepare_rc_invoice_payment(self, rc_invoice, rc_type): ) # Lines to be reconciled with the original supplier Invoice (self) - rc_tax_amount = self.compute_rc_amount_tax_main_currency() + rc_tax_amount_ic, rc_tax_amount_cc = self.compute_rc_amount_tax_main_currency() payment_credit_line_data = self.rc_credit_line_vals( rc_type.transitory_account_id, - rc_tax_amount, + rc_tax_amount_ic, + rc_tax_amount_cc, ) line_to_reconcile = self._rc_get_move_line_to_reconcile() payment_debit_line_data = self.rc_debit_line_vals( line_to_reconcile.account_id, - rc_tax_amount, + rc_tax_amount_ic, + rc_tax_amount_cc, ) rc_payment_data["line_ids"] = [ diff --git a/l10n_it_reverse_charge/tests/rc_common.py b/l10n_it_reverse_charge/tests/rc_common.py index e2d5458d6042..0d94e8f5e5ae 100644 --- a/l10n_it_reverse_charge/tests/rc_common.py +++ b/l10n_it_reverse_charge/tests/rc_common.py @@ -35,6 +35,7 @@ def setUpClass(cls, chart_template_ref=None): cls._create_rc_types() cls._create_rc_type_taxes() cls._create_fiscal_position() + cls._create_currency_rate(cls.env.ref("base.EUR").id, 1.1) cls.supplier_extraEU = cls.partner_model.create( { @@ -42,6 +43,13 @@ def setUpClass(cls, chart_template_ref=None): "property_account_position_id": cls.fiscal_position_extra.id, } ) + cls.supplier_extraEU_EUR = cls.partner_model.create( + { + "name": "Extra EU Euro supplier", + "property_account_position_id": cls.fiscal_position_extra_no_si.id, + "currency_id": cls.env.ref("base.EUR"), + } + ) cls.supplier_intraEU = cls.partner_model.create( { "name": "Intra EU supplier", @@ -102,9 +110,14 @@ def setUpClass(cls, chart_template_ref=None): ) @classmethod - def create_invoice(cls, partner, amounts, taxes=None, post=True): + def create_invoice(cls, partner, amounts, taxes=None, post=True, currency=None): invoice = cls.init_invoice( - "in_invoice", partner=partner, post=post, amounts=amounts, taxes=taxes + "in_invoice", + partner=partner, + post=post, + amounts=amounts, + taxes=taxes, + currency=currency, ) for line in invoice.invoice_line_ids: line.account_id = cls.invoice_line_account.id @@ -224,6 +237,20 @@ def _create_rc_types(cls): } ) + cls.rc_type_eeu_no_selfinvoice_extra = rc_type_model.create( + { + "name": "Extra EU (selfinvoice)", + "method": "selfinvoice", + "partner_type": "other", + "with_supplier_self_invoice": False, + "partner_id": cls.env.ref("base.main_partner").id, + "journal_id": cls.journal_selfinvoice_extra.id, + "supplier_journal_id": cls.journal_cee_extra.id, + "payment_journal_id": cls.journal_reconciliation.id, + "transitory_account_id": cls.account_selfinvoice.id, + } + ) + cls.rc_type_exempt = rc_type_model.create( { "name": "Intra EU (exempt)", @@ -255,6 +282,14 @@ def _create_rc_type_taxes(cls): } ) + cls.rc_type_tax_eeu_no_selfinvoice_extra = rc_type_tax_model.create( + { + "rc_type_id": cls.rc_type_eeu_no_selfinvoice_extra.id, + "purchase_tax_id": cls.tax_22ae.id, + "sale_tax_id": cls.tax_22ve.id, + } + ) + cls.rc_type_tax_exempt = rc_type_tax_model.create( { "rc_type_id": cls.rc_type_exempt.id, @@ -274,6 +309,24 @@ def _create_fiscal_position(cls): {"name": "Extra EU", "rc_type_id": cls.rc_type_eeu.id} ) + cls.fiscal_position_extra_no_si = model_fiscal_position.create( + { + "name": "Extra EU no extra self invoice", + "rc_type_id": cls.rc_type_eeu_no_selfinvoice_extra.id, + } + ) + cls.fiscal_position_exempt = model_fiscal_position.create( {"name": "Intra EU exempt", "rc_type_id": cls.rc_type_exempt.id} ) + + @classmethod + def _create_currency_rate(cls, currency_id, rate, date="2016-01-01"): + cls.env["res.currency.rate"].create( + { + "name": date, + "currency_id": currency_id, + "rate": rate, + "company_id": cls.env.company.id, + } + ) diff --git a/l10n_it_reverse_charge/tests/test_rc.py b/l10n_it_reverse_charge/tests/test_rc.py index f2865cb7805e..6e96ceeada76 100644 --- a/l10n_it_reverse_charge/tests/test_rc.py +++ b/l10n_it_reverse_charge/tests/test_rc.py @@ -171,6 +171,53 @@ def test_supplier_extraEU_no_outstanding_payment(self): payments_lines = (self_purchase_payment | self_purchase_rc_payment).line_ids self.assertTrue(all(payments_lines.mapped("reconciled"))) + def test_supplier_extraEU_no_outstanding_payment_different_currencies(self): + """ + Self invoice from Extra EU partner in a different currency + """ + invoice = self.create_invoice( + self.supplier_extraEU_EUR, + amounts=[100], + taxes=self.tax_22ae, + currency=self.env.ref("base.EUR"), + ) + + self_invoice = invoice.rc_self_invoice_id + self_payment = self_invoice.rc_payment_move_id + # check self payment creation + self.assertTrue(self_payment) + # check self payment amount total + self.assertEqual(self_payment.amount_total, 144.0) + # check self payment amount total in currency + self.assertEqual(self_payment.amount_total_signed, 130.91) + # check self payment lines amount currency setting + self.assertTrue(all(self_payment.line_ids.mapped("amount_currency"))) + + # check self invoice amount + invoices_amounts_sum = ( + invoice.amount_untaxed_signed + self_invoice.amount_untaxed_signed + ) + self.assertEqual(invoices_amounts_sum, 0.0) + + # check amount conversion + invoice_amount_untaxed_usd = invoice.currency_id._convert( + invoice.amount_untaxed, + invoice.company_id.currency_id, + invoice.company_id, + invoice.invoice_date, + ) + self_invoice_test_line = self_invoice.line_ids.filtered( + lambda x: x.name == "test line" + ) + self.assertEqual( + invoice_amount_untaxed_usd, abs(self_invoice_test_line.balance) + ) + + # check amount_currency setting + self.assertEqual( + invoice.amount_untaxed, abs(self_invoice_test_line.amount_currency) + ) + def test_extra_EU_draft_and_reconfirm(self): """Check that an invoice with RC Self Purchase Invoice can be reset to draft and confirmed again."""