parent
b8c3b39a8b
commit
d952a9b8be
35
account.py
35
account.py
|
@ -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
|
||||
|
|
167
aeat_mapping.py
167
aeat_mapping.py
|
@ -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)
|
||||
|
|
|
@ -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')
|
||||
]
|
||||
|
|
24
locale/es.po
24
locale/es.po
|
@ -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"
|
||||
|
|
2
sii.xml
2
sii.xml
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Reference in New Issue