diff --git a/l10n_es_aeat_sii_oca/data/aeat_sii_map_data.xml b/l10n_es_aeat_sii_oca/data/aeat_sii_map_data.xml index 3ae451e989f..8ddd160fb7b 100644 --- a/l10n_es_aeat_sii_oca/data/aeat_sii_map_data.xml +++ b/l10n_es_aeat_sii_oca/data/aeat_sii_map_data.xml @@ -356,4 +356,25 @@ Bases no incluidas en ImporteTotal + + DUA + + + DUA + diff --git a/l10n_es_aeat_sii_oca/data/l10n.es.aeat.map.tax.line.tax.csv b/l10n_es_aeat_sii_oca/data/l10n.es.aeat.map.tax.line.tax.csv index 203eab818c4..ee99db6d88c 100644 --- a/l10n_es_aeat_sii_oca/data/l10n.es.aeat.map.tax.line.tax.csv +++ b/l10n_es_aeat_sii_oca/data/l10n.es.aeat.map.tax.line.tax.csv @@ -64,6 +64,7 @@ p_iva7-5_ibc,account_tax_template_p_iva7-5_ibc p_iva10_ibc,account_tax_template_p_iva10_ibc p_iva21_ibc,account_tax_template_p_iva21_ibc p_iva4_ibi,account_tax_template_p_iva4_ibi +p_iva5_ibi,account_tax_template_p_iva5_ibi p_iva10_ibi,account_tax_template_p_iva10_ibi p_iva21_ibi,account_tax_template_p_iva21_ibi p_iva12_agr,account_tax_template_p_iva12_agr diff --git a/l10n_es_aeat_sii_oca/models/account_move.py b/l10n_es_aeat_sii_oca/models/account_move.py index 4a4186a784a..eb68b89fb2e 100644 --- a/l10n_es_aeat_sii_oca/models/account_move.py +++ b/l10n_es_aeat_sii_oca/models/account_move.py @@ -91,6 +91,7 @@ def _default_sii_refund_type(self): string="Cron Triggers", copy=False, ) + sii_dua_invoice = fields.Boolean(compute="_compute_dua_invoice") @api.depends("move_type") def _compute_sii_refund_type(self): @@ -112,6 +113,21 @@ def _compute_sii_registration_key(self): def _compute_macrodata(self): return super()._compute_macrodata() + @api.depends("company_id", "fiscal_position_id", "invoice_line_ids.tax_ids") + def _compute_dua_invoice(self): + for invoice in self: + taxes = self.env["account.tax"] + for template in [ + "account_tax_template_p_iva4_ibc_group", + "account_tax_template_p_iva10_ibc_group", + "account_tax_template_p_iva21_ibc_group", + ]: + tax_id = invoice.company_id._get_tax_id_from_xmlid(template) + taxes |= self.env["account.tax"].browse(tax_id) + invoice.sii_dua_invoice = invoice.line_ids.filtered( + lambda x, taxes=taxes: any([tax in taxes for tax in x.tax_ids]) + ) + def _aeat_get_partner(self): return self.commercial_partner_id @@ -516,18 +532,27 @@ def _get_aeat_invoice_dict_in(self, cancel=False): {"NombreRazon": partner.name[0:120]} ) else: - amount_total = -self.amount_total_signed - not_in_amount_total + invoice_type = self._get_sii_invoice_type() + company_name = partner.name[0:120] + if self.sii_dua_invoice: + company_name = self.company_id.name + if not self.sii_lc_operation: + invoice_type = "F5" + inv_dict["FacturaRecibida"] = { # TODO: Incluir los 5 tipos de facturas rectificativas - "TipoFactura": self._get_sii_invoice_type(), + "TipoFactura": invoice_type, "ClaveRegimenEspecialOTrascendencia": self.sii_registration_key.code, "DescripcionOperacion": self.sii_description, "DesgloseFactura": desglose_factura, - "Contraparte": {"NombreRazon": partner.name[0:120]}, + "Contraparte": {"NombreRazon": company_name}, "FechaRegContable": reg_date, - "ImporteTotal": amount_total, "CuotaDeducible": tax_amount, } + if not self.sii_dua_invoice: + inv_dict["FacturaRecibida"]["ImporteTotal"] = ( + -self.amount_total_signed - not_in_amount_total + ) if self.sii_macrodata: inv_dict["FacturaRecibida"].update(Macrodato="S") if self.sii_registration_key_additional1: @@ -559,6 +584,13 @@ def _get_aeat_invoice_dict_in(self, cancel=False): ), "CuotaRectificada": refund_tax_amount, } + + if self.sii_dua_invoice: + inv_dict["FacturaRecibida"].pop("FechaOperacion", None) + nif = self.company_id.partner_id._parse_aeat_vat_info()[2] + inv_dict["FacturaRecibida"]["IDEmisorFactura"] = {"NIF": nif} + inv_dict["IDFactura"]["IDEmisorFactura"] = {"NIF": nif} + inv_dict["FacturaRecibida"]["Contraparte"]["NIF"] = nif return inv_dict def _get_cancel_sii_invoice_dict(self): @@ -780,6 +812,7 @@ def _compute_sii_description(self): "move_type", "fiscal_position_id", "fiscal_position_id.aeat_active", + "invoice_line_ids", ) def _compute_sii_enabled(self): """Compute if the invoice is enabled for the SII""" @@ -791,9 +824,19 @@ def _compute_sii_enabled(self): and invoice.is_invoice() ): invoice.sii_enabled = ( - invoice.fiscal_position_id - and invoice.fiscal_position_id.aeat_active - ) or not invoice.fiscal_position_id + ( + invoice.fiscal_position_id + and invoice.fiscal_position_id.aeat_active + ) + or not invoice.fiscal_position_id + ) and ( + not dua_sii_exempt_taxes + or not invoice.invoice_line_ids.filtered( + lambda x, dua_taxes=dua_sii_exempt_taxes: any( + [tax.id in dua_taxes for tax in x.tax_ids] + ) + ) + ) else: invoice.sii_enabled = False diff --git a/l10n_es_aeat_sii_oca/models/sii_mixin.py b/l10n_es_aeat_sii_oca/models/sii_mixin.py index ff666142a26..50336d6a814 100644 --- a/l10n_es_aeat_sii_oca/models/sii_mixin.py +++ b/l10n_es_aeat_sii_oca/models/sii_mixin.py @@ -210,6 +210,16 @@ def _get_aeat_taxes_map(self, codes, date): taxes |= self.env["account.tax"].browse(tax_id) return taxes + def _get_dua_sii_exempt_taxes(self): + self.ensure_one() + taxes = [] + dua_exempt_tax = self.company_id._get_tax_id_from_xmlid( + "account_tax_template_p_dua_exempt" + ) + if dua_exempt_tax: + taxes.append(dua_exempt_tax) + return taxes + def _get_aeat_header(self, tipo_comunicacion=False, cancellation=False): """Builds SII send header diff --git a/l10n_es_aeat_sii_oca/tests/json/sii_in_invoice_p_iva21_ibc_group_dict.json b/l10n_es_aeat_sii_oca/tests/json/sii_in_invoice_p_iva21_ibc_group_dict.json new file mode 100644 index 00000000000..75a0e872cc4 --- /dev/null +++ b/l10n_es_aeat_sii_oca/tests/json/sii_in_invoice_p_iva21_ibc_group_dict.json @@ -0,0 +1,24 @@ +{ + "IDFactura": { + "FechaExpedicionFacturaEmisor": "01-01-2020", + "NumSerieFacturaEmisor": "sup0001", + "IDEmisorFactura": {"NIF": "U2687761C"} + }, + "FacturaRecibida": { + "TipoFactura": "F5", + "Contraparte": {"NombreRazon": "Spanish test company", "NIF": "U2687761C"}, + "IDEmisorFactura": {"NIF": "U2687761C"}, + "DescripcionOperacion": "/", + "ClaveRegimenEspecialOTrascendencia": "01", + "FechaRegContable": "01-10-2020", + "DesgloseFactura": { + "DesgloseIVA": { + "DetalleIVA": [ + {"BaseImponible": 83.33, "CuotaSoportada": 17.5, "TipoImpositivo": "21.0"} + ] + } + }, + "CuotaDeducible": 17.5 + }, + "PeriodoLiquidacion": {"Periodo": "01", "Ejercicio": 2020} +} diff --git a/l10n_es_aeat_sii_oca/tests/test_l10n_es_aeat_sii.py b/l10n_es_aeat_sii_oca/tests/test_l10n_es_aeat_sii.py index 796f303f9aa..530b5274779 100644 --- a/l10n_es_aeat_sii_oca/tests/test_l10n_es_aeat_sii.py +++ b/l10n_es_aeat_sii_oca/tests/test_l10n_es_aeat_sii.py @@ -246,22 +246,24 @@ def test_partner_sii_enabled(self): def test_get_invoice_data(self): mapping = [ - ("out_invoice", [(100, ["s_iva10b"]), (200, ["s_iva21s"])], {}), - ("out_invoice", [(100, ["s_iva10b"]), (200, ["s_iva0_ns"])], {}), + ("out_invoice", [(100, ["s_iva10b"]), (200, ["s_iva21s"])], {}, False), + ("out_invoice", [(100, ["s_iva10b"]), (200, ["s_iva0_ns"])], {}, False), ( "out_invoice", [(100, ["s_iva10b", "s_req014"]), (200, ["s_iva21s", "s_req52"])], {}, + False, ), ( "out_refund", [(100, ["s_iva10b"]), (100, ["s_iva10b"]), (200, ["s_iva21s"])], {}, + False, ), - ("out_invoice", [(100, ["s_iva0_sp_i"]), (200, ["s_iva0_ic"])], {}), - ("out_refund", [(100, ["s_iva0_sp_i"]), (200, ["s_iva0_ic"])], {}), - ("out_invoice", [(100, ["s_iva_e"]), (200, ["s_iva0_e"])], {}), - ("out_refund", [(100, ["s_iva_e"]), (200, ["s_iva0_e"])], {}), + ("out_invoice", [(100, ["s_iva0_sp_i"]), (200, ["s_iva0_ic"])], {}, False), + ("out_refund", [(100, ["s_iva0_sp_i"]), (200, ["s_iva0_ic"])], {}, False), + ("out_invoice", [(100, ["s_iva_e"]), (200, ["s_iva0_e"])], {}, False), + ("out_refund", [(100, ["s_iva_e"]), (200, ["s_iva0_e"])], {}, False), ( "in_invoice", [(100, ["p_iva10_bc", "p_irpf19"]), (200, ["p_iva21_sc", "p_irpf19"])], @@ -270,34 +272,40 @@ def test_get_invoice_data(self): "date": "2020-02-01", "sii_account_registration_date": "2020-10-01", }, + False, ), ( "in_refund", [(100, ["p_iva10_bc"])], {"ref": "sup0002", "sii_account_registration_date": "2020-10-01"}, + False, ), ( "in_invoice", [(100, ["p_iva10_bc", "p_req014"]), (200, ["p_iva21_sc", "p_req52"])], {"ref": "sup0003", "sii_account_registration_date": "2020-10-01"}, + False, ), ( "in_invoice", [(100, ["p_iva21_sp_ex"])], {"ref": "sup0004", "sii_account_registration_date": "2020-10-01"}, + False, ), ( "in_invoice", [(100, ["p_iva0_ns"]), (200, ["p_iva10_bc"])], {"ref": "sup0005", "sii_account_registration_date": "2020-10-01"}, + False, ), # Out invoice with currency - ("out_invoice", [(100, ["s_iva10b"])], {"currency_id": self.usd.id}), + ("out_invoice", [(100, ["s_iva10b"])], {"currency_id": self.usd.id}, False), # Out invoice with currency and with not included in total amount ( "out_invoice", [(100, ["s_iva10b", "s_irpf1"])], {"currency_id": self.usd.id}, + False, ), # In invoice with currency ( @@ -308,6 +316,7 @@ def test_get_invoice_data(self): "sii_account_registration_date": "2020-10-01", "currency_id": self.usd.id, }, + False, ), # In invoice with currency and with not included in total amount ( @@ -318,16 +327,34 @@ def test_get_invoice_data(self): "sii_account_registration_date": "2020-10-01", "currency_id": self.usd.id, }, + False, ), # Intra-community supplier refund with ImporteTotal with "one side" ( "in_refund", [(100, ["p_iva21_sp_in"])], {"ref": "sup0008", "sii_account_registration_date": "2020-10-01"}, + False, + ), + ( + "in_invoice", + [(100, ["p_iva21_ibc_group"])], + { + "ref": "sup0001", + "sii_account_registration_date": "2020-10-01", + "currency_id": self.usd.id, + }, + True, ), ] - for inv_type, lines, extra_vals in mapping: - self._create_and_test_invoice_sii_dict(inv_type, lines, extra_vals) + for inv_type, lines, extra_vals, is_dua in mapping: + invoice = self._create_and_test_invoice_sii_dict( + inv_type, lines, extra_vals + ) + if is_dua: + self.assertTrue(invoice.sii_dua_invoice) + else: + self.assertFalse(invoice.sii_dua_invoice) return def test_sii_description(self): @@ -505,3 +532,26 @@ def test_account_move_reversal_out_invoice(self): self.assertEqual(reversal.sii_refund_type, "I") self.assertTrue(reversal.sii_refund_type_required) self.assertFalse(reversal.supplier_invoice_number_refund_required) + + def test_dua_exempt_invoice(self): + self.partner.write({"country_id": self.env.ref("base.us").id}) + invoice = self.env["account.move"].create( + { + "partner_id": self.partner.id, + "invoice_date": "2018-02-01", + "move_type": "in_invoice", + "invoice_line_ids": [ + ( + 0, + 0, + { + "product_id": self.product.id, + "name": "Test line", + "quantity": 1.0, + "price_unit": 100.00, + }, + ) + ], + } + ) + self.assertFalse(invoice.sii_enabled) diff --git a/l10n_es_aeat_sii_oca/views/account_move_views.xml b/l10n_es_aeat_sii_oca/views/account_move_views.xml index 52d9d5896b3..e8beb17e1b9 100644 --- a/l10n_es_aeat_sii_oca/views/account_move_views.xml +++ b/l10n_es_aeat_sii_oca/views/account_move_views.xml @@ -79,7 +79,7 @@ groups="account.group_account_invoice" invisible="1" /> - +