Submit and modify Recieved invoices to SII

This commit is contained in:
Daniel Möller 2017-05-24 17:01:55 +02:00
parent 3cdda9406a
commit 6436cacacd
3 changed files with 159 additions and 12 deletions

61
aeat.py
View File

@ -362,6 +362,11 @@ class SIIReport(Workflow, ModelSQL, ModelView):
report.delete_issued_invoices()
else:
raise NotImplementedError
elif report.book == 'R':
if report.operation_type in {'A0', 'A1'}:
report.submit_recieved_invoices()
else:
raise NotImplementedError
else:
raise NotImplementedError
_logger.debug('Done sending reports to AEAT SII')
@ -411,7 +416,7 @@ class SIIReport(Workflow, ModelSQL, ModelView):
(line.invoice for line in self.lines))
res = None
with self.company.tmp_ssl_credentials() as (crt, key):
srv = service.bind_SuministroFactEmitidas(crt, key, test=True)
srv = service.bind_issued_invoices_service(crt, key, test=True)
res = srv.SuministroLRFacturasEmitidas(headers, invoices)
# TODO: assert response order matches report order
for (report_line, response_line) in zip(
@ -436,7 +441,7 @@ class SIIReport(Workflow, ModelSQL, ModelView):
(line.invoice for line in self.lines))
res = None
with self.company.tmp_ssl_credentials() as (crt, key):
srv = service.bind_SuministroFactEmitidas(crt, key, test=True)
srv = service.bind_issued_invoices_service(crt, key, test=True)
res = srv.AnulacionLRFacturasEmitidas(headers, invoices)
# TODO: assert response order matches report order
for (report_line, response_line) in zip(
@ -470,7 +475,7 @@ class SIIReport(Workflow, ModelSQL, ModelView):
# EstadoCuadre, ClavePaginacion
}
with self.company.tmp_ssl_credentials() as (crt, key):
srv = service.bind_SuministroFactEmitidas(
srv = service.bind_issued_invoices_service(
crt, key, test=True)
res = srv.ConsultaLRFacturasEmitidas(
headers, filter_)
@ -506,6 +511,34 @@ class SIIReport(Workflow, ModelSQL, ModelView):
'lines': [('create', lines_to_create)]
})
def submit_recieved_invoices(self):
_logger.info('Sending report %s to AEAT SII', self.id)
headers = mapping.get_headers(
name=self.company.party.name,
vat=self.company.party.vat_number,
comm_kind=self.operation_type)
invoices = map(
RecievedTrytonInvoiceMapper.build_submit_request,
(line.invoice for line in self.lines))
res = None
_logger.debug(invoices)
with self.company.tmp_ssl_credentials() as (crt, key):
srv = service.bind_recieved_invoices_service(crt, key, test=True)
res = srv.SuministroLRFacturasRecibidas(headers, invoices)
_logger.debug(res)
# TODO: assert response order matches report order
for (report_line, response_line) in zip(
self.lines, res.RespuestaLinea):
report_line.write([report_line], {
'state': response_line.EstadoRegistro,
'communication_code': response_line.CodigoErrorRegistro,
'communication_msg': response_line.DescripcionErrorRegistro,
})
self.write([self], {
'communication_state': res.EstadoEnvio,
'csv': res.CSV,
})
class IssuedTrytonInvoiceMapper(mapping.OutInvoiceMapper):
year = attrgetter('move.period.fiscalyear.name')
@ -527,6 +560,28 @@ class IssuedTrytonInvoiceMapper(mapping.OutInvoiceMapper):
tax_amount = attrgetter('amount')
class RecievedTrytonInvoiceMapper(mapping.RecievedInvoiceMapper):
year = attrgetter('move.period.fiscalyear.name')
period = attrgetter('move.period.start_date.month')
nif = attrgetter('company.party.vat_number')
serial_number = attrgetter('reference')
issue_date = attrgetter('invoice_date')
invoice_kind = attrgetter('sii_operation_key')
specialkey_or_trascendence = attrgetter('sii_received_key')
description = attrgetter('description')
not_exempt_kind = attrgetter('sii_subjected')
counterpart_name = attrgetter('party.name')
counterpart_nif = attrgetter('party.vat_number')
counterpart_id_type = attrgetter('party.identifier_type')
counterpart_country = attrgetter('party.vat_country')
move_date = attrgetter('move.date')
deductible_amount = attrgetter('tax_amount') # most of the times
taxes = attrgetter('taxes')
tax_rate = attrgetter('tax.rate')
tax_base = attrgetter('base')
tax_amount = attrgetter('amount')
class SIIReportLine(ModelSQL, ModelView):
'''
AEAT SII Issued

View File

@ -2,8 +2,11 @@
__all__ = [
'get_headers',
'OutInvoiceMapper',
'RecievedInvoiceMapper',
]
_DATE_FMT = '%d-%m-%Y'
def get_headers(name=None, vat=None, comm_kind=None, version='0.7'):
return {
@ -46,7 +49,7 @@ class OutInvoiceMapper(object):
},
'NumSerieFacturaEmisor': cls.serial_number(invoice),
'FechaExpedicionFacturaEmisor':
cls.issue_date(invoice).strftime('%d/%m/%Y'),
cls.issue_date(invoice).strftime(_DATE_FMT),
}
@classmethod
@ -108,3 +111,83 @@ class OutInvoiceMapper(object):
'CuotaRepercutida': cls.tax_amount(tax),
# TODO: TipoRecargoEquivalencia, CuotaRecargoEquivalencia
}
class RecievedInvoiceMapper(object):
@classmethod
def build_delete_request(cls, invoice):
return {
'PeriodoImpositivo': cls.build_period(invoice),
'IDFactura': cls.build_invoice_id(invoice),
}
@classmethod
def build_submit_request(cls, invoice):
request = cls.build_delete_request(invoice)
request['FacturaRecibida'] = cls.build_invoice(invoice)
return request
@classmethod
def build_period(cls, invoice):
return {
'Ejercicio': cls.year(invoice),
'Periodo': str(cls.period(invoice)).zfill(2),
}
@classmethod
def build_invoice_id(cls, invoice):
return {
'IDEmisorFactura': {
'NIF': cls.counterpart_nif(invoice),
},
'NumSerieFacturaEmisor': cls.serial_number(invoice),
'FechaExpedicionFacturaEmisor':
cls.issue_date(invoice).strftime(_DATE_FMT),
}
@classmethod
def build_invoice(cls, invoice):
ret = {
'TipoFactura': cls.invoice_kind(invoice),
'ClaveRegimenEspecialOTrascendencia':
cls.specialkey_or_trascendence(invoice),
'DescripcionOperacion': cls.description(invoice),
'DesgloseFactura': {
# 'InversionSujetoPasivo': {
# 'DetalleIVA':
# map(cls.build_taxes, cls.taxes(invoice)),
# },
'DesgloseIVA': {
'DetalleIVA':
map(cls.build_taxes, cls.taxes(invoice)),
}
},
'FechaRegContable': cls.move_date(invoice).strftime(_DATE_FMT),
'CuotaDeducible': cls.deductible_amount(invoice),
}
if ret['TipoFactura'] not in {'F2', 'F4', 'R5'}:
ret['Contraparte'] = cls.build_counterpart(invoice)
return ret
@classmethod
def build_counterpart(cls, invoice):
return {
'NombreRazon': cls.counterpart_name(invoice),
'NIF': cls.counterpart_nif(invoice),
# 'IDOtro': {
# 'IDType': cls.counterpart_id_type(invoice),
# 'CodigoPais': cls.counterpart_country(invoice),
# # 'ID': cls.counterpart_nif(invoice),
# },
}
@classmethod
def build_taxes(cls, tax):
return {
'TipoImpositivo': int(100 * cls.tax_rate(tax)),
'BaseImponible': cls.tax_base(tax),
'CuotaSoportada': cls.tax_amount(tax),
# TODO: TipoRecargoEquivalencia, CuotaRecargoEquivalencia
# TODO: PorcentCompensacionREAGYP, ImporteCompensacionREAGYP
}

View File

@ -1,6 +1,7 @@
__all__ = [
'bind_SuministroFactEmitidas',
'bind_issued_invoices_service',
'bind_recieved_invoices_service',
]
from requests import Session
@ -25,7 +26,7 @@ def _get_client(wsdl, public_crt, private_key, test=False):
return client
def bind_SuministroFactEmitidas(crt, pkey, test=False):
def bind_issued_invoices_service(crt, pkey, test=False):
wsdl = (
'http://www.agenciatributaria.es/static_files/AEAT/'
'Contenidos_Comunes/La_Agencia_Tributaria/Modelos_y_formularios/'
@ -39,9 +40,17 @@ def bind_SuministroFactEmitidas(crt, pkey, test=False):
service = cli.bind('siiService', port_name)
return service
# wsdl_in = fields.Char(
# string='WSDL Invoice In', required=True,
# default='http://www.agenciatributaria.es/static_files/AEAT/'
# 'Contenidos_Comunes/La_Agencia_Tributaria/Modelos_y_formularios/'
# 'Suministro_inmediato_informacion/FicherosSuministros/V_06/'
# 'SuministroFactRecibidas.wsdl')
def bind_recieved_invoices_service(crt, pkey, test=False):
wsdl = (
'http://www.agenciatributaria.es/static_files/AEAT/'
'Contenidos_Comunes/La_Agencia_Tributaria/Modelos_y_formularios/'
'Suministro_inmediato_informacion/FicherosSuministros/V_07/'
'SuministroFactRecibidas.wsdl'
)
port_name = 'SuministroFactRecibidas'
if test:
port_name += 'Pruebas'
cli = _get_client(wsdl, crt, pkey, test)
service = cli.bind('siiService', port_name)
return service