Added operation product type.

This commit refs #23717
This commit is contained in:
Sergio Morillo 2022-07-21 12:56:46 +02:00
parent b8c3b39a8b
commit d952a9b8be
7 changed files with 154 additions and 80 deletions

View File

@ -1,10 +1,9 @@
# The COPYRIGHT file at the top level of this repository contains the full # The COPYRIGHT file at the top level of this repository contains the full
# copyright notices and license terms. # copyright notices and license terms.
from trytond.model import fields from trytond.model import fields
from trytond.pool import PoolMeta from trytond.pool import PoolMeta, Pool
from trytond.pyson import Eval from trytond.pyson import Eval
from trytond.transaction import Transaction from trytond.transaction import Transaction
from trytond.tools import cached_property
from .aeat import (BOOK_KEY, OPERATION_KEY, SEND_SPECIAL_REGIME_KEY, from .aeat import (BOOK_KEY, OPERATION_KEY, SEND_SPECIAL_REGIME_KEY,
RECEIVE_SPECIAL_REGIME_KEY, IVA_SUBJECTED, EXEMPTION_CAUSE, RECEIVE_SPECIAL_REGIME_KEY, IVA_SUBJECTED, EXEMPTION_CAUSE,
IVA_NOT_SUBJECTED) IVA_NOT_SUBJECTED)
@ -66,6 +65,13 @@ class TemplateTax(metaclass=PoolMeta):
sii_exemption_cause = fields.Selection(EXEMPTION_CAUSE, 'Exemption Cause') sii_exemption_cause = fields.Selection(EXEMPTION_CAUSE, 'Exemption Cause')
sii_not_subjected_key = fields.Selection(IVA_NOT_SUBJECTED, sii_not_subjected_key = fields.Selection(IVA_NOT_SUBJECTED,
'Not Subjected Key') 'Not Subjected Key')
sii_product_type = fields.Selection([
(None, ''),
('goods', 'Goods'),
('services', 'Services')], 'SII Product Type',
states={
'invisible': Eval('_parent_group', {}).get('kind') == 'purchase'
}, depends=['group'])
tax_used = fields.Boolean('Used in Tax') tax_used = fields.Boolean('Used in Tax')
invoice_used = fields.Boolean('Used in invoice Total') invoice_used = fields.Boolean('Used in invoice Total')
@ -100,6 +106,10 @@ class TemplateTax(metaclass=PoolMeta):
return res return res
@staticmethod
def default_sii_product_type():
return 'goods'
class Tax(metaclass=PoolMeta): class Tax(metaclass=PoolMeta):
__name__ = 'account.tax' __name__ = 'account.tax'
@ -113,6 +123,12 @@ class Tax(metaclass=PoolMeta):
sii_not_subjected_key = fields.Selection(IVA_NOT_SUBJECTED, sii_not_subjected_key = fields.Selection(IVA_NOT_SUBJECTED,
'Not Subjected Key') 'Not Subjected Key')
sii_exemption_cause = fields.Selection(EXEMPTION_CAUSE, 'Exemption Cause') sii_exemption_cause = fields.Selection(EXEMPTION_CAUSE, 'Exemption Cause')
sii_product_type = fields.Selection([
('goods', 'Goods'),
('services', 'Services')], 'SII Product Type',
states={
'invisible': Eval('_parent_group', {}).get('kind') == 'purchase'
}, depends=['group'])
tax_used = fields.Boolean('Used in Tax') tax_used = fields.Boolean('Used in Tax')
invoice_used = fields.Boolean('Used in invoice Total') invoice_used = fields.Boolean('Used in invoice Total')
recargo_equivalencia = fields.Boolean('Recargo Equivalencia', recargo_equivalencia = fields.Boolean('Recargo Equivalencia',
@ -150,3 +166,18 @@ class Tax(metaclass=PoolMeta):
@staticmethod @staticmethod
def default_deducible(): def default_deducible():
return True return True
@staticmethod
def default_sii_product_type():
return 'goods'
@property
def sii_product_type_used(self):
ModelData = Pool().get('ir.model.data')
try:
if self.group.id == ModelData.get_id(
'account_es', 'tax_group_sale_service'):
return 'services'
return 'goods'
except KeyError:
return self.sii_product_type

View File

@ -2,6 +2,7 @@
# The COPYRIGHT file at the top level of this repository contains the full # The COPYRIGHT file at the top level of this repository contains the full
# copyright notices and license terms. # copyright notices and license terms.
from decimal import Decimal from decimal import Decimal
from itertools import groupby
from logging import getLogger from logging import getLogger
from operator import attrgetter from operator import attrgetter
from datetime import date from datetime import date
@ -317,89 +318,101 @@ class IssuedInvoiceMapper(BaseInvoiceMapper):
'IDOtro' in ret['Contraparte'] or ('NIF' in ret['Contraparte'] and 'IDOtro' in ret['Contraparte'] or ('NIF' in ret['Contraparte'] and
ret['Contraparte']['NIF'].startswith('N'))) ret['Contraparte']['NIF'].startswith('N')))
) )
detail = {
'Sujeta': {},
'NoSujeta': {}
}
if must_detail_op:
ret['TipoDesglose'].update({
'DesgloseTipoOperacion': {
'Entrega': detail,
# 'PrestacionDeServicios': {},
}
})
else:
ret['TipoDesglose'].update({
'DesgloseFactura': detail
})
for tax in self.taxes(invoice): def get_tax_grouping(tax):
exempt_kind = self.exempt_kind(tax.tax) # TODO: add all fields to group taxes once
not_exempt_kind = self.not_exempt_kind(tax.tax) if not must_detail_op:
not_subject_kind = self.not_subject_kind(tax.tax) return ''
if (not_exempt_kind in ('S2', 'S3') and return tax.tax.sii_product_type_used
'NIF' not in ret.get('Contraparte', {})):
raise UserError(gettext('aeat_sii.msg_missing_nif',
invoice=invoice))
if not_exempt_kind: taxes = sorted(self.taxes(invoice), key=get_tax_grouping)
if not_exempt_kind == 'S2': for product_type, grouped_taxes in groupby(taxes,
# inv. subj. pass. key=get_tax_grouping):
tax_detail = { grouped_taxes = list(grouped_taxes)
'TipoImpositivo': 0, detail = {
'BaseImponible': self.get_tax_base(tax), 'Sujeta': {},
'CuotaRepercutida': 0 'NoSujeta': {}
} }
else:
tax_detail = self.build_taxes(tax) for tax in grouped_taxes:
if tax_detail: exempt_kind = self.exempt_kind(tax.tax)
if not detail['Sujeta']: not_exempt_kind = self.not_exempt_kind(tax.tax)
detail['Sujeta'].update({ not_subject_kind = self.not_subject_kind(tax.tax)
'NoExenta': { if (not_exempt_kind in ('S2', 'S3') and
'TipoNoExenta': not_exempt_kind, 'NIF' not in ret.get('Contraparte', {})):
'DesgloseIVA': { raise UserError(gettext('aeat_sii.msg_missing_nif',
'DetalleIVA': [tax_detail] invoice=invoice))
}
} if not_exempt_kind:
}) if not_exempt_kind == 'S2':
else: # inv. subj. pass.
detail['Sujeta']['NoExenta']['DesgloseIVA'][ tax_detail = {
'DetalleIVA'].append(tax_detail) 'TipoImpositivo': 0,
elif exempt_kind: 'BaseImponible': self.get_tax_base(tax),
baseimponible = self.get_tax_base(tax) 'CuotaRepercutida': 0
if detail['Sujeta'].get('Exenta', {}).get(
'DetalleExenta', {}).get(
'CausaExencion', None) == exempt_kind:
baseimponible += detail['Sujeta'].get('Exenta').get(
'DetalleExenta').get('BaseImponible', 0)
detail['Sujeta'].update({
'Exenta': {
'DetalleExenta': {
'CausaExencion': exempt_kind,
'BaseImponible': baseimponible
} }
} else:
}) tax_detail = self.build_taxes(tax)
elif not_subject_kind: if tax_detail:
if self.art_7_14(tax.tax): if not detail['Sujeta']:
detail['NoSujeta'].setdefault( detail['Sujeta'].update({
'ImportePorArticulos7_14_Otros', 0) 'NoExenta': {
detail['NoSujeta']['ImportePorArticulos7_14_Otros' 'TipoNoExenta': not_exempt_kind,
] += self.get_tax_base(tax) 'DesgloseIVA': {
elif self.location_rules(tax.tax, must_detail_op): 'DetalleIVA': [tax_detail]
detail['NoSujeta'].setdefault( }
'ImporteTAIReglasLocalizacion', 0) }
detail['NoSujeta']['ImporteTAIReglasLocalizacion' })
] += self.get_tax_base(tax) else:
detail['Sujeta']['NoExenta']['DesgloseIVA'][
'DetalleIVA'].append(tax_detail)
elif exempt_kind:
baseimponible = self.get_tax_base(tax)
if detail['Sujeta'].get('Exenta', {}).get(
'DetalleExenta', {}).get(
'CausaExencion', None) == exempt_kind:
baseimponible += detail['Sujeta'].get('Exenta').get(
'DetalleExenta').get('BaseImponible', 0)
detail['Sujeta'].update({
'Exenta': {
'DetalleExenta': {
'CausaExencion': exempt_kind,
'BaseImponible': baseimponible
}
}
})
elif not_subject_kind:
if self.art_7_14(tax.tax):
detail['NoSujeta'].setdefault(
'ImportePorArticulos7_14_Otros', 0)
detail['NoSujeta']['ImportePorArticulos7_14_Otros'
] += self.get_tax_base(tax)
elif self.location_rules(tax.tax, must_detail_op):
detail['NoSujeta'].setdefault(
'ImporteTAIReglasLocalizacion', 0)
detail['NoSujeta']['ImporteTAIReglasLocalizacion'
] += self.get_tax_base(tax)
else:
raise NotImplementedError()
else: else:
raise NotImplementedError() raise NotImplementedError()
else:
raise NotImplementedError()
# remove unused key # remove unused key
for key in ('Sujeta', 'NoSujeta'): for key in ('Sujeta', 'NoSujeta'):
if not detail[key]: if not detail[key]:
detail.pop(key) detail.pop(key)
detail_type = 'PrestacionServicios' \
if product_type == 'services' else 'Entrega'
if must_detail_op:
ret['TipoDesglose'].setdefault('DesgloseTipoOperacion', {})
ret['TipoDesglose']['DesgloseTipoOperacion'].update({
detail_type: detail.copy(),
})
else:
ret['TipoDesglose'].update({
'DesgloseFactura': detail.copy()
})
self._update_total_amount(ret, invoice) self._update_total_amount(ret, invoice)
self._update_rectified_invoice(ret, invoice) self._update_rectified_invoice(ret, invoice)

View File

@ -278,7 +278,7 @@ class Invoice(metaclass=PoolMeta):
Modeldata.get_id('account_es', 'iva_reagp_12_normal'), Modeldata.get_id('account_es', 'iva_reagp_12_normal'),
Modeldata.get_id('account_es', 'iva_reagp_12_pyme'), Modeldata.get_id('account_es', 'iva_reagp_12_pyme'),
] ]
except AttributeError: except KeyError:
reagyp_ids = [ reagyp_ids = [
Modeldata.get_id('account_es', 'iva_reagp_compras_12_1') Modeldata.get_id('account_es', 'iva_reagp_compras_12_1')
] ]

View File

@ -90,6 +90,18 @@ msgctxt "field:account.tax,sii_not_subjected_key:"
msgid "Not Subjected Key" msgid "Not Subjected Key"
msgstr "Clave no sujeto" msgstr "Clave no sujeto"
msgctxt "field:account.tax,sii_product_type:"
msgid "SII Product Type"
msgstr "Tipo operación SII"
msgctxt "selection:account.tax,sii_product_type:"
msgid "Goods"
msgstr "Entrega de bienes"
msgctxt "selection:account.tax,sii_product_type:"
msgid "Services"
msgstr "Prestación de servicios"
msgctxt "field:account.tax.template,invoice_used:" msgctxt "field:account.tax.template,invoice_used:"
msgid "Used in invoice Total" msgid "Used in invoice Total"
msgstr "Usado en total de factura" msgstr "Usado en total de factura"
@ -126,6 +138,18 @@ msgctxt "field:account.tax.template,tax_used:"
msgid "Used in Tax" msgid "Used in Tax"
msgstr "Usado en impuestos" msgstr "Usado en impuestos"
msgctxt "field:account.tax.template,sii_product_type:"
msgid "SII Product Type"
msgstr "Tipo operación SII"
msgctxt "selection:account.tax.template,sii_product_type:"
msgid "Goods"
msgstr "Entrega de bienes"
msgctxt "selection:account.tax.template,sii_product_type:"
msgid "Services"
msgstr "Prestación de servicios"
msgctxt "field:aeat.sii.load_pkcs12.start,password:" msgctxt "field:aeat.sii.load_pkcs12.start,password:"
msgid "Password" msgid "Password"
msgstr "Contraseña" msgstr "Contraseña"

View File

@ -111,6 +111,7 @@ this repository contains the full copyright notices and license terms. -->
<field name="sii_exemption_cause">E6</field> <field name="sii_exemption_cause">E6</field>
<field name="tax_used" eval="True"/> <field name="tax_used" eval="True"/>
<field name="invoice_used" eval="True"/> <field name="invoice_used" eval="True"/>
<field name="sii_product_type">services</field>
</record> </record>
<record model="account.tax.template" id="account_es.iva_dev_AI"> <record model="account.tax.template" id="account_es.iva_dev_AI">
<field name="sii_book_key">E</field> <field name="sii_book_key">E</field>
@ -459,6 +460,7 @@ this repository contains the full copyright notices and license terms. -->
<field name="sii_exemption_cause">E1</field> <field name="sii_exemption_cause">E1</field>
<field name="tax_used" eval="True"/> <field name="tax_used" eval="True"/>
<field name="invoice_used" eval="True"/> <field name="invoice_used" eval="True"/>
<field name="sii_product_type">services</field>
</record> </record>
<record model="account.tax.template" id="account_es.iva_rep_0_ter_rus"> <record model="account.tax.template" id="account_es.iva_rep_0_ter_rus">
<field name="sii_book_key">E</field> <field name="sii_book_key">E</field>

View File

@ -22,6 +22,8 @@ contains the full copyright notices and license terms. -->
<field name="sii_exemption_cause"/> <field name="sii_exemption_cause"/>
<label name="sii_not_subjected_key"/> <label name="sii_not_subjected_key"/>
<field name="sii_not_subjected_key"/> <field name="sii_not_subjected_key"/>
<label name="sii_product_type"/>
<field name="sii_product_type"/>
</page> </page>
</xpath> </xpath>
</data> </data>

View File

@ -22,6 +22,8 @@ contains the full copyright notices and license terms. -->
<field name="sii_exemption_cause"/> <field name="sii_exemption_cause"/>
<label name="sii_not_subjected_key"/> <label name="sii_not_subjected_key"/>
<field name="sii_not_subjected_key"/> <field name="sii_not_subjected_key"/>
<label name="sii_product_type"/>
<field name="sii_product_type"/>
</page> </page>
</xpath> </xpath>
</data> </data>