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
|
# 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
|
||||||
|
|
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
|
# 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)
|
||||||
|
|
|
@ -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')
|
||||||
]
|
]
|
||||||
|
|
24
locale/es.po
24
locale/es.po
|
@ -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"
|
||||||
|
|
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="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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
Loading…
Reference in New Issue