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"
/>
-
+