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
# copyright notices and license terms.
from trytond.model import fields
from trytond.pool import PoolMeta
from trytond.pool import PoolMeta, Pool
from trytond.pyson import Eval
from trytond.transaction import Transaction
from trytond.tools import cached_property
from .aeat import (BOOK_KEY, OPERATION_KEY, SEND_SPECIAL_REGIME_KEY,
RECEIVE_SPECIAL_REGIME_KEY, IVA_SUBJECTED, EXEMPTION_CAUSE,
IVA_NOT_SUBJECTED)
@ -66,6 +65,13 @@ class TemplateTax(metaclass=PoolMeta):
sii_exemption_cause = fields.Selection(EXEMPTION_CAUSE, 'Exemption Cause')
sii_not_subjected_key = fields.Selection(IVA_NOT_SUBJECTED,
'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')
invoice_used = fields.Boolean('Used in invoice Total')
@ -100,6 +106,10 @@ class TemplateTax(metaclass=PoolMeta):
return res
@staticmethod
def default_sii_product_type():
return 'goods'
class Tax(metaclass=PoolMeta):
__name__ = 'account.tax'
@ -113,6 +123,12 @@ class Tax(metaclass=PoolMeta):
sii_not_subjected_key = fields.Selection(IVA_NOT_SUBJECTED,
'Not Subjected Key')
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')
invoice_used = fields.Boolean('Used in invoice Total')
recargo_equivalencia = fields.Boolean('Recargo Equivalencia',
@ -150,3 +166,18 @@ class Tax(metaclass=PoolMeta):
@staticmethod
def default_deducible():
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
# copyright notices and license terms.
from decimal import Decimal
from itertools import groupby
from logging import getLogger
from operator import attrgetter
from datetime import date
@ -317,89 +318,101 @@ class IssuedInvoiceMapper(BaseInvoiceMapper):
'IDOtro' in ret['Contraparte'] or ('NIF' in ret['Contraparte'] and
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):
exempt_kind = self.exempt_kind(tax.tax)
not_exempt_kind = self.not_exempt_kind(tax.tax)
not_subject_kind = self.not_subject_kind(tax.tax)
if (not_exempt_kind in ('S2', 'S3') and
'NIF' not in ret.get('Contraparte', {})):
raise UserError(gettext('aeat_sii.msg_missing_nif',
invoice=invoice))
def get_tax_grouping(tax):
# TODO: add all fields to group taxes once
if not must_detail_op:
return ''
return tax.tax.sii_product_type_used
if not_exempt_kind:
if not_exempt_kind == 'S2':
# inv. subj. pass.
tax_detail = {
'TipoImpositivo': 0,
'BaseImponible': self.get_tax_base(tax),
'CuotaRepercutida': 0
}
else:
tax_detail = self.build_taxes(tax)
if tax_detail:
if not detail['Sujeta']:
detail['Sujeta'].update({
'NoExenta': {
'TipoNoExenta': not_exempt_kind,
'DesgloseIVA': {
'DetalleIVA': [tax_detail]
}
}
})
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
taxes = sorted(self.taxes(invoice), key=get_tax_grouping)
for product_type, grouped_taxes in groupby(taxes,
key=get_tax_grouping):
grouped_taxes = list(grouped_taxes)
detail = {
'Sujeta': {},
'NoSujeta': {}
}
for tax in grouped_taxes:
exempt_kind = self.exempt_kind(tax.tax)
not_exempt_kind = self.not_exempt_kind(tax.tax)
not_subject_kind = self.not_subject_kind(tax.tax)
if (not_exempt_kind in ('S2', 'S3') and
'NIF' not in ret.get('Contraparte', {})):
raise UserError(gettext('aeat_sii.msg_missing_nif',
invoice=invoice))
if not_exempt_kind:
if not_exempt_kind == 'S2':
# inv. subj. pass.
tax_detail = {
'TipoImpositivo': 0,
'BaseImponible': self.get_tax_base(tax),
'CuotaRepercutida': 0
}
}
})
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:
tax_detail = self.build_taxes(tax)
if tax_detail:
if not detail['Sujeta']:
detail['Sujeta'].update({
'NoExenta': {
'TipoNoExenta': not_exempt_kind,
'DesgloseIVA': {
'DetalleIVA': [tax_detail]
}
}
})
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:
raise NotImplementedError()
else:
raise NotImplementedError()
# remove unused key
for key in ('Sujeta', 'NoSujeta'):
if not detail[key]:
detail.pop(key)
# remove unused key
for key in ('Sujeta', 'NoSujeta'):
if not detail[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_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_pyme'),
]
except AttributeError:
except KeyError:
reagyp_ids = [
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"
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:"
msgid "Used in invoice Total"
msgstr "Usado en total de factura"
@ -126,6 +138,18 @@ msgctxt "field:account.tax.template,tax_used:"
msgid "Used in Tax"
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:"
msgid "Password"
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="tax_used" eval="True"/>
<field name="invoice_used" eval="True"/>
<field name="sii_product_type">services</field>
</record>
<record model="account.tax.template" id="account_es.iva_dev_AI">
<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="tax_used" eval="True"/>
<field name="invoice_used" eval="True"/>
<field name="sii_product_type">services</field>
</record>
<record model="account.tax.template" id="account_es.iva_rep_0_ter_rus">
<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"/>
<label name="sii_not_subjected_key"/>
<field name="sii_not_subjected_key"/>
<label name="sii_product_type"/>
<field name="sii_product_type"/>
</page>
</xpath>
</data>

View File

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