Report issued invoices to SII

This commit is contained in:
Daniel Möller 2017-05-22 18:08:28 +02:00
parent a764c976fe
commit 4df9df692c
9 changed files with 374 additions and 134 deletions

81
aeat.py
View File

@ -1,16 +1,26 @@
# -*- coding: utf-8 -*-
# The COPYRIGHT file at the top level of this repository contains the full
# copyright notices and license terms.
__all__ = [
'SIIReport',
'SIIReportLine',
]
import unicodedata
from logging import getLogger
from decimal import Decimal
from trytond.model import ModelSQL, ModelView, fields, Workflow
from trytond.pyson import Eval
from trytond.pool import Pool, PoolMeta
from trytond.pool import Pool
from trytond.transaction import Transaction
from . import aeat_errors
__all__ = ['SIIReport', 'SIIReportLine']
from . import aeat_errors
from .pyAEATsii import service
from .pyAEATsii import mapping
_logger = getLogger(__name__)
_ZERO = Decimal('0.0')
@ -55,7 +65,8 @@ PARTY_IDENTIFIER_TYPE = [
('04', 'Official Document Emmited by the Country of Residence'),
('05', 'Certificate of fiscal resident'),
('06', 'Other proving document'),
]
('07', 'Not on the Census'),
]
SEND_SPECIAL_REGIME_KEY = [ # L3.1
@ -105,16 +116,16 @@ RECEIVE_SPECIAL_REGIME_KEY = [
AEAT_COMMUNICATION_STATE = [
(None, ''),
('CORRECTO', 'Accepted'),
('PARCIALMENTECORRECTO', 'Partial Accepted'),
('INCORRECTO', 'Rejected')
('Correcto', 'Accepted'),
('ParcialmenteCorrecto', 'Partially Accepted'),
('Incorrecto', 'Rejected')
]
AEAT_INVOICE_STATE = [
(None, ''),
('CORRECTO', 'Accepted'),
('ACEPTADOCONERRORES', 'Accepted with Errors'),
('INCORRECTO', 'Rejected')
('Correcto', 'Accepted'),
('AceptadoConErrores', 'Accepted with Errors'),
('Incorrecto', 'Rejected')
]
@ -237,8 +248,9 @@ class SIIReport(Workflow, ModelSQL, ModelView):
('draft', 'Draft'),
('confirmed', 'Confirmed'),
('done', 'Done'),
('cancelled', 'Cancelled')
], 'State', readonly=True)
('cancelled', 'Cancelled'),
('sent', 'Sent'),
], 'State', readonly=True)
communication_state = fields.Selection( AEAT_COMMUNICATION_STATE,
'Communication State', readonly=True)
@ -273,7 +285,7 @@ class SIIReport(Workflow, ModelSQL, ModelView):
'icon': 'tryton-ok',
},
'cancel': {
'invisible': Eval('state').in_(['cancelled']),
'invisible': Eval('state').in_(['cancelled', 'sent']),
'icon': 'tryton-cancel',
},
'load_invoices': {
@ -336,12 +348,39 @@ class SIIReport(Workflow, ModelSQL, ModelView):
@ModelView.button
@Workflow.transition('sent')
def send(cls, reports):
_logger.info(
'Sending reports (%s) to AEAT SII',
','.join(str(r.id) for r in reports))
for report in reports:
def call_aeat(headers, invoices):
with report.company.tmp_ssl_credentials() as (crt, key):
raise NotImplementedError
_logger.debug('Service invoices request: %s', invoices)
srv = service.bind_SuministroFactEmitidas(crt, key, test=True)
res = srv.SuministroLRFacturasEmitidas(headers, invoices)
_logger.debug('Service response: %s', res)
return res
pool = Pool()
Company = pool.get('company.company')
Invoice = pool.get('account.invoice')
company = Company(Transaction().context.get('company'))
headers = mapping.get_headers(
name=company.party.name, vat=company.party.vat_number,
comm_kind='A0')
for report in reports:
_logger.info('Sending report %s to AEAT SII', report.id)
invoices = Invoice.map_to_aeat_sii(
line.invoice for line in report.lines)
res = call_aeat(headers, invoices)
# TODO: assert response lines order matches report line order
for (report_line, response_line) in zip(
report.lines, res.RespuestaLinea):
report_line.state = response_line.EstadoRegistro
report_line.communication_code = \
response_line.CodigoErrorRegistro
report_line.communication_msg = \
response_line.DescripcionErrorRegistro
report_line.save()
report.communication_state = res.EstadoEnvio
report.save()
_logger.debug('Done sending reports to AEAT SII')
@classmethod
@ModelView.button
@ -388,8 +427,10 @@ class SIIReportLine(ModelSQL, ModelView):
'aeat.sii.report', 'Issued Report', ondelete='CASCADE')
invoice = fields.Many2One('account.invoice', 'Invoice')
state = fields.Selection(AEAT_INVOICE_STATE, 'State')
communication_msg = fields.Selection(
aeat_errors.AEAT_ERRORS, 'Communication Message', readonly=True)
communication_code = fields.Selection(
aeat_errors.AEAT_ERRORS, 'Communication Code', readonly=True)
communication_msg = fields.Char(
'Communication Message', readonly=True)
company = fields.Many2One(
'company.company', 'Company', required=True, select=True)

View File

@ -1,116 +1,119 @@
AEAT_ERRORS = [
(None, ''),
(3501, 'Technical error of BBDD'),
(3500, 'Technical error of BBDD. Error in the Integrity of the Information'),
(3502, 'Technical error. Failed to get invoice data'),
(3503, 'Invoice consulted for the provision of Payments / Collections does not exist'),
(3504, 'Technical error. Failed to get Metall Collection data'),
(3505, 'Technical error. Error obtaining the data of the Insurance Operation'),
(4100, 'Error in header. The contents of the IDVersionSii field are invalid.'),
(4101, 'Error in header. The content of theCommunication field is invalid.'),
(4102, 'XML does not meet the schema. Required field is missing: XXXX'),
(4103, 'Unexpected error parsing the XML'),
(4104, 'Error in header. The value of the NIF field of the Holder block is not identified'),
(4105, 'Error in the header. The value of the NIFRepresentator field of the Holder block is not identified'),
(4106, 'Error in date format'),
(4107, 'Technical error when obtaining the CSV.'),
(4108, 'The XML root tag does not match the defined schema'),
(4109, 'NIF is not identified. NIF: XXXX'),
(4110, 'Failed to get the certificate.'),
(4111, 'The NIF is in the wrong format.'),
(4112, 'Technical error checking powers.'),
(4113, 'Technical error when creating the process.'),
(4114, 'The holder of the certificate must be the holder of the book of registry, social worker or proxy'),
(4115, 'Technical error checking Social Collaboration.'),
(4116, 'The allowed limit of registrations for the block DatosInmueble / DetalleIVA has been exceeded'),
(4117, 'XML does not meet the schema. The maximum allowable invoice threshold has been exceeded to register.'),
(4118, "The holder's NIF is not authorized to send information to the system. You must register previously"),
(4122, "Error in the header. The holder's NIF has an incorrect format."),
(4123, 'Error in header. The NIFRepresentant has an incorrect format.'),
(4124, 'Error The address does not correspond to the input file.'),
(4125, 'XML does not meet the schema. The maximum allowable amount of operations has been exceeded to register.'),
(1100, "Incorrect value or field type: XXXX"),
(1101, "Incorrect field code value"),
(1102, "Field Value Incorrect Period"),
(1103, "Incorrect IDType field value"),
(1104, "Incorrect ID field value"),
(1105, "Field Value NumberFactory Incorrect Enumerator"),
(1106, "Field Value DateExpeditionFactorMissor Incorrect"),
(1107, "Field Value Type Invoice Incorrect"),
(1108, "Field Value Location Property Incorrect"),
(1109, "Field Value Key Special Regime or Incorrect Transcendence"),
(1110, "Field Value Payment Medium or Incorrect Collection"),
(1111, "Field Value Type Incorrect Amending"),
(1112, "The invoice NIF must be the same as the NIF of the record holder"),
(1113, "Field Value CauseExcluded Incorrect"),
(1114, "Incorrect type field value"),
(1115, "If the invoice has part No Subject must report at least one of the two amounts not subject"),
(1116, "NIF is not identified. NIF: XXXXX"),
(1117, "NIF is not identified. NIF: XXXXX. NAME_RACE: YYYYY"),
(1118, "The Issuer's Country Code and the Counterparty's Country Code do not match"),
(1119, "The IdType of the Issuer and the Counterpart do not match"),
(1120, "Issuer and Counterparty ID do not match"),
(1121, "The Issuer's and Counterpart's NIF do not overlap"),
(1122, "In the case of a minor, the NIF of the representative must contain value"),
(1123, "In case of a minor, the NIF of the representative can not coindidir with the NIF of the holder"),
(1124, "Country Code is mandatory when Type Identification is different from VAT NIF"),
(1125, "The Expedition Date is greater than the current date"),
(1126, "Field value Incorrect exercise. This should be the current or previous year"),
(1127, "Invoice Type of Seats Summary, NumberStockFactorEmisterSummaryFin is undeclared"),
(1128, "Invoice type is not Seats Summary and has NumSeriesFactorSamplingResumedFin declared"),
(1129, "The IssuedForColors field only accepts N or S values"),
(1130, "Field Value TypeCommunication Incorrect."),
(1131, "Field value IncorrectKeyword."),
(1132, "Field value StateMember is incorrect."),
(1133, "Incorrect IDType field value. Must have value 02"),
(1134, "If the invoice is of the rectifying type, the TypeRectificative field must have value."),
(1135, "If the invoice is not an amendment type, the TypeRectificative field must have no value."),
(1136, "The field Invoices should be reported only if the invoice is invoice type issued in Replacement of invoiced and declared simplified invoices."),
(1137, "If the invoice is not of the Amending or Summary Invoice type, the field Refunded Bills can not be informed."),
(1138, "If the invoice is an Amending amendment, the rectification amount is mandatory"),
(1139, "If the invoice is not an Amending Amendment or an Invoice Summary ReCotification block must have no value"),
(1140, "The transactions may have within the subject part, exempt part and / or non exempt part. So, Only one block or both may appear, but at least one (Exempt and / or Exempt)"),
(1141, "The operations may have part subject and part not subject. Therefore, only one Block or both, but at least one must appear (Subject and / or No subject)"),
(1142, "Field Value NumberNumberFactorEmisterRemand Incorrect"),
(1143, "Field value NIF of block IDFacture with incorrect type"),
(1144, "The invoice ID and CounterSource fields of the invoice are different"),
(1145, "Field value Incorrect period. This must be less than or equal to the current period"),
(1146, "The CodigoPais field indicated for the identification of NIF-IVA does not coincide with the two First characters of ID"),
(1147, "Error in the IDFactura block. IncorrectNameName field value."),
(1148, "BreakdownTypeOperation needs at least ProvisionServices or Delivery or both"),
(1149, "The field ID is not identified"),
(1150, "The field CodePais indicated does not match the first two digits of the identifier"),
(1151, "The value 03 can only be entered in the Medium field when the Date of payment / payment '31 -12 'for the year"),
(1152, "If the field Average has 03 value the field Account_O_Medium can not have value"),
(1153, "NIF is formatted incorrectly"),
(1154, "The field MiscellaneousDisitors only accepts N or S values"),
(1155, "The Cadastral Reference field must be informed whenever the FieldInput field does not Has value 3"),
(1156, "Field value KeyOperation is not included in the list of allowed values"),
(1157, "The BreakdownFactory block must have reported at least one of the two blocks InvestmentPayment or Deferred Transfer"),
(1158, "The Counterpart field must be informed as long as the TypeFactor field has no value F2 or F4"),
(1159, "The Coupon field only accepts N or S values"),
(1160, "If the invoice is not of the type Invoice Amending in simplified invoices or summary entry, the Coupon field should have no value"),
(3000, "Duplicate Invoice"),
(3001, "Registration is already unsubscribed"),
(3002, "There is no record"),
(3003, "Can not Include Charges for Unsubscribed Bills"),
(3004, "Maximum field size exceeded"),
(3005, "Collections can not be included if the field KeyRegimenSpecialTotal of the invoice Has a value other than 07"),
(3006, "Payment of unsubscribed invoices can not be included"),
(3007, "You can not include payments if the fieldTypeRegistryType of the invoice field Has a value other than 07"),
(3008, "There is already a Metallic Collection with this Counterpart"),
(3009, "Duplicate intra-Community operation"),
(3010, "The Presenter does not have the necessary permissions to update this invoice"),
(3011, "It is not allowed to modify the Special Regime Key in invoices that contain Collections or Payments"),
(3012, "There is already an Insurance Operation with this Counterparty"),
(2000, "If the fieldTypeRegistryType has a value of 12 or 13 the block of DatosInmueble must be informed"),
(2001, "The base fieldAvailableAvailable on invoices issued should not be reported if the field ClaveRegimenEspecialTrascendencia has a value other than 06"),
(2002, "Error if SpecialRegimenKeyTransfer different from 12, 13 and the block of Estate"),
(2003, "Some of the rectified invoices do not exist in the system"),
(2004, "Technical Error when consulting the list of rectified invoices"),
(2005, "The BaseAvailableAccount field should not be reported on received invoices if the field ClaveRegimenEspecialTrascendencia has a value other than 06"),
(2006, "The invoice contains a breakdown at the invoice level when it corresponds to a breakdown at the Transaction, since it is a non-simplified invoice or summary statement and the counterpart contains a IdOtro or a NIF beginning with N"),
(2007, "The Total Amount field is not more than 6,000"),
(2008, "The field PercentDeliveryREAGYP should not be reported if the field KeyRegimenSpecialTrading on invoices received has a value other than 02"),
(2009, "Do not enter the field PaymentCompensationREAGYP if the field KeyRegimenSpecialTrading on invoices received has a value other than 02"),
('3501', 'Technical error of BBDD'),
('3500', 'Technical error of BBDD. Error in the Integrity of the Information'),
('3502', 'Technical error. Failed to get invoice data'),
('3503', 'Invoice consulted for the provision of Payments / Collections does not exist'),
('3504', 'Technical error. Failed to get Metall Collection data'),
('3505', 'Technical error. Error obtaining the data of the Insurance Operation'),
('4100', 'Error in header. The contents of the IDVersionSii field are invalid.'),
('4101', 'Error in header. The content of theCommunication field is invalid.'),
('4102', 'XML does not meet the schema. Required field is missing: XXXX'),
('4103', 'Unexpected error parsing the XML'),
('4104', 'Error in header. The value of the NIF field of the Holder block is not identified'),
('4105', 'Error in the header. The value of the NIFRepresentator field of the Holder block is not identified'),
('4106', 'Error in date format'),
('4107', 'Technical error when obtaining the CSV.'),
('4108', 'The XML root tag does not match the defined schema'),
('4109', 'NIF is not identified. NIF: XXXX'),
('4110', 'Failed to get the certificate.'),
('4111', 'The NIF is in the wrong format.'),
('4112', 'Technical error checking powers.'),
('4113', 'Technical error when creating the process.'),
('4114', 'The holder of the certificate must be the holder of the book of registry, social worker or proxy'),
('4115', 'Technical error checking Social Collaboration.'),
('4116', 'The allowed limit of registrations for the block DatosInmueble / DetalleIVA has been exceeded'),
('4117', 'XML does not meet the schema. The maximum allowable invoice threshold has been exceeded to register.'),
('4118', "The holder's NIF is not authorized to send information to the system. You must register previously"),
('4122', "Error in the header. The holder's NIF has an incorrect format."),
('4123', 'Error in header. The NIFRepresentant has an incorrect format.'),
('4124', 'Error The address does not correspond to the input file.'),
('4125', 'XML does not meet the schema. The maximum allowable amount of operations has been exceeded to register.'),
('1100', "Incorrect value or field type: XXXX"),
('1101', "Incorrect field code value"),
('1102', "Field Value Incorrect Period"),
('1103', "Incorrect IDType field value"),
('1104', "Incorrect ID field value"),
('1105', "Field Value NumberFactory Incorrect Enumerator"),
('1106', "Field Value DateExpeditionInvoiceIssuer Incorrect"),
('1107', "Field Value Type Invoice Incorrect"),
('1108', "Field Value Location Property Incorrect"),
('1109', "Field Value Key Special Regime or Incorrect Transcendence"),
('1110', "Field Value Payment Medium or Incorrect Collection"),
('1111', "Field Value Type Incorrect Amending"),
('1112', "The invoice NIF must be the same as the NIF of the record holder"),
('1113', "Field Value CauseExcluded Incorrect"),
('1114', "Incorrect type field value"),
('1115', "If the invoice has part No Subject must report at least one of the two amounts not subject"),
('1116', "NIF is not identified. NIF: XXXXX"),
('1117', "NIF is not identified. NIF: XXXXX. NAME_RACE: YYYYY"),
('1118', "The Issuer's Country Code and the Counterparty's Country Code do not match"),
('1119', "The IdType of the Issuer and the Counterpart do not match"),
('1120', "Issuer and Counterparty ID do not match"),
('1121', "The Issuer's and Counterpart's NIF do not overlap"),
('1122', "In the case of a minor, the NIF of the representative must contain value"),
('1123', "In case of a minor, the NIF of the representative can not coindidir with the NIF of the holder"),
('1124', "Country Code is mandatory when Type Identification is different from VAT NIF"),
('1125', "The Expedition Date is greater than the current date"),
('1126', "Field value Incorrect exercise. This should be the current or previous year"),
('1127', "Invoice Type of Seats Summary, NumberStockFactorEmisterSummaryFin is undeclared"),
('1128', "Invoice type is not Seats Summary and has NumSeriesFactorSamplingResumedFin declared"),
('1129', "The IssuedForColors field only accepts N or S values"),
('1130', "Field Value TypeCommunication Incorrect."),
('1131', "Field value IncorrectKeyword."),
('1132', "Field value StateMember is incorrect."),
('1133', "Incorrect IDType field value. Must have value 02"),
('1134', "If the invoice is of the rectifying type, the TypeRectificative field must have value."),
('1135', "If the invoice is not an amendment type, the TypeRectificative field must have no value."),
('1136', "The field Invoices should be reported only if the invoice is invoice type issued in Replacement of invoiced and declared simplified invoices."),
('1137', "If the invoice is not of the Amending or Summary Invoice type, the field Refunded Bills can not be informed."),
('1138', "If the invoice is an Amending amendment, the rectification amount is mandatory"),
('1139', "If the invoice is not an Amending Amendment or an Invoice Summary ReCotification block must have no value"),
('1140', "The transactions may have within the subject part, exempt part and / or non exempt part. So, Only one block or both may appear, but at least one (Exempt and / or Exempt)"),
('1141', "The operations may have part subject and part not subject. Therefore, only one Block or both, but at least one must appear (Subject and / or No subject)"),
('1142', "Field Value NumberNumberFactorEmisterRemand Incorrect"),
('1143', "Field value NIF of block IDFacture with incorrect type"),
('1144', "The invoice ID and CounterSource fields of the invoice are different"),
('1145', "Field value Incorrect period. This must be less than or equal to the current period"),
('1146', "The CodigoPais field indicated for the identification of NIF-IVA does not coincide with the two First characters of ID"),
('1147', "Error in the IDFactura block. IncorrectNameName field value."),
('1148', "BreakdownTypeOperation needs at least ProvisionServices or Delivery or both"),
('1149', "The field ID is not identified"),
('1150', "The field CodePais indicated does not match the first two digits of the identifier"),
('1151', "The value 03 can only be entered in the Medium field when the Date of payment / payment '31 -12 'for the year"),
('1152', "If the field Average has 03 value the field Account_O_Medium can not have value"),
('1153', "NIF is formatted incorrectly"),
('1154', "The field MiscellaneousDisitors only accepts N or S values"),
('1155', "The Cadastral Reference field must be informed whenever the FieldInput field does not Has value 3"),
('1156', "Field value KeyOperation is not included in the list of allowed values"),
('1157', "The BreakdownFactory block must have reported at least one of the two blocks InvestmentPayment or Deferred Transfer"),
('1158', "The Counterpart field must be informed as long as the TypeFactor field has no value F2 or F4"),
('1159', "The Coupon field only accepts N or S values"),
('1160', "If the invoice is not of the type Invoice Amending in simplified invoices or summary entry, the Coupon field should have no value"),
('1166', ""),
('1177', "The value of the field XXX is not among the allowed values"),
('3000', "Duplicate Invoice"),
('3001', "Registration is already unsubscribed"),
('3002', "There is no record"),
('3003', "Can not Include Charges for Unsubscribed Bills"),
('3004', "Maximum field size exceeded"),
('3005', "Collections can not be included if the field KeyRegimenSpecialTotal of the invoice Has a value other than 07"),
('3006', "Payment of unsubscribed invoices can not be included"),
('3007', "You can not include payments if the fieldTypeRegistryType of the invoice field Has a value other than 07"),
('3008', "There is already a Metallic Collection with this Counterpart"),
('3009', "Duplicate intra-Community operation"),
('3010', "The Presenter does not have the necessary permissions to update this invoice"),
('3011', "It is not allowed to modify the Special Regime Key in invoices that contain Collections or Payments"),
('3012', "There is already an Insurance Operation with this Counterparty"),
('2000', "If the fieldTypeRegistryType has a value of 12 or 13 the block of DatosInmueble must be informed"),
('2001', "The base fieldAvailableAvailable on invoices issued should not be reported if the field ClaveRegimenEspecialTrascendencia has a value other than 06"),
('2002', "Error if SpecialRegimenKeyTransfer different from 12, 13 and the block of Estate"),
('2003', "Some of the rectified invoices do not exist in the system"),
('2004', "Technical Error when consulting the list of rectified invoices"),
('2005', "The BaseAvailableAccount field should not be reported on received invoices if the field ClaveRegimenEspecialTrascendencia has a value other than 06"),
('2006', "The invoice contains a breakdown at the invoice level when it corresponds to a breakdown at the Transaction, since it is a non-simplified invoice or summary statement and the counterpart contains a IdOtro or a NIF beginning with N"),
('2007', "The Total Amount field is not more than 6,000"),
('2008', "The field PercentDeliveryREAGYP should not be reported if the field KeyRegimenSpecialTrading on invoices received has a value other than 02"),
('2009', "Do not enter the field PaymentCompensationREAGYP if the field KeyRegimenSpecialTrading on invoices received has a value other than 02"),
('2011', "Counterpart NIF is not in the Census"),
]

View File

@ -1,5 +1,8 @@
# The COPYRIGHT file at the top level of this repository contains the full
# copyright notices and license terms.
from operator import attrgetter
from trytond import backend
from trytond.model import ModelSQL, ModelView, fields
from trytond.wizard import Wizard, StateView, StateTransition, Button
@ -12,6 +15,8 @@ from .aeat import (OPERATION_KEY, BOOK_KEY, SEND_SPECIAL_REGIME_KEY,
RECEIVE_SPECIAL_REGIME_KEY, AEAT_INVOICE_STATE, IVA_SUBJECTED,
EXCEMPTION_CAUSE, INTRACOMUNITARY_TYPE)
from .pyAEATsii import mapping
__all__ = ['Invoice', 'ReasignSIIRecord', 'ReasignSIIRecordStart',
'ReasignSIIRecordEnd']
@ -107,6 +112,30 @@ class Invoice:
return result
@classmethod
def map_to_aeat_sii(cls, invoices):
mapper = IssuedTrytonInvoiceMapper()
return map(mapper.build_request, invoices)
class IssuedTrytonInvoiceMapper(mapping.OutInvoiceMapper):
year = attrgetter('move.period.fiscalyear.name')
period = attrgetter('move.period.start_date.month')
nif = attrgetter('company.party.vat_number')
serial_number = attrgetter('number')
issue_date = attrgetter('invoice_date')
invoice_kind = attrgetter('sii_operation_key')
specialkey_or_trascendence = attrgetter('sii_issued_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')
taxes = attrgetter('taxes')
tax_rate = attrgetter('tax.rate')
tax_base = attrgetter('base')
tax_amount = attrgetter('amount')
class ReasignSIIRecordStart(ModelView):

0
pyAEATsii/__init__.py Normal file
View File

91
pyAEATsii/mapping.py Normal file
View File

@ -0,0 +1,91 @@
__all__ = [
'get_headers',
'OutInvoiceMapper',
]
def get_headers(name=None, vat=None, comm_kind=None, version='0.6'):
return {
'IDVersionSii': version,
'Titular': {
'NombreRazon': name,
'NIF': vat,
},
'TipoComunicacion': comm_kind,
}
class OutInvoiceMapper(object):
def __init__(self):
pass
def build_request(self, invoice):
return {
'PeriodoImpositivo': self.build_period(invoice),
'IDFactura': self.build_invoice_id(invoice),
'FacturaExpedida': self.build_issued_invoice(invoice),
}
def build_period(self, invoice):
return {
'Ejercicio': self.year(invoice),
'Periodo': str(self.period(invoice)).zfill(2),
}
def build_invoice_id(self, invoice):
return {
'IDEmisorFactura': {
'NIF': self.nif(invoice),
},
'NumSerieFacturaEmisor': self.serial_number(invoice),
'FechaExpedicionFacturaEmisor':
self.issue_date(invoice).strftime('%d/%m/%Y'),
}
def build_issued_invoice(self, invoice):
ret = {
'TipoFactura': self.invoice_kind(invoice),
'ClaveRegimenEspecialOTrascendencia':
self.specialkey_or_trascendence(invoice),
'DescripcionOperacion': self.description(invoice),
'TipoDesglose': {
'DesgloseFactura': {
'Sujeta': {
# 'Exenta': {
# 'BaseImponible': '0.00',
# },
'NoExenta': {
'TipoNoExenta': self.not_exempt_kind(invoice),
'DesgloseIVA': {
'DetalleIVA':
map(self.build_taxes, self.taxes(invoice)),
}
},
},
# 'NoSujeta': {
# },
},
},
}
if ret['TipoFactura'] not in {'F2', 'F4', 'R5'}:
ret['Contraparte'] = self.build_counterpart(invoice)
return ret
def build_counterpart(self, invoice):
return {
'NombreRazon': self.counterpart_name(invoice),
# 'NIF': self.counterpart_nif(invoice),
'IDOtro': {
'IDType': self.counterpart_id_type(invoice),
'CodigoPais': self.counterpart_country(invoice),
'ID': self.counterpart_nif(invoice),
},
}
def build_taxes(self, tax):
return {
'TipoImpositivo': int(100 * self.tax_rate(tax)),
'BaseImponible': self.tax_base(tax),
'CuotaRepercutida': self.tax_amount(tax),
}

27
pyAEATsii/plugins.py Normal file
View File

@ -0,0 +1,27 @@
__all__ = [
'LoggingPlugin',
]
from logging import getLogger
from lxml import etree
from zeep import Plugin
_logger = getLogger(__name__)
class LoggingPlugin(Plugin):
def ingress(self, envelope, http_headers, operation):
_logger.debug('http_headers: %s', http_headers)
_logger.debug('operation: %s', operation)
_logger.debug('envelope: %s', etree.tostring(
envelope, pretty_print=True))
return envelope, http_headers
def egress(self, envelope, http_headers, operation, binding_options):
_logger.debug('http_headers: %s', http_headers)
_logger.debug('operation: %s', operation)
_logger.debug('envelope: %s', etree.tostring(
envelope, pretty_print=True))
return envelope, http_headers

47
pyAEATsii/service.py Normal file
View File

@ -0,0 +1,47 @@
__all__ = [
'bind_SuministroFactEmitidas',
]
from requests import Session
from zeep import Client
from zeep.transports import Transport
from zeep.plugins import HistoryPlugin
from .plugins import LoggingPlugin
def _get_client(wsdl, public_crt, private_key, test=False):
session = Session()
session.cert = (public_crt, private_key)
transport = Transport(session=session)
plugins = [HistoryPlugin()]
# TODO: manually handle sessionId? Not mandatory yet recommended...
# http://www.agenciatributaria.es/AEAT.internet/Inicio/Ayuda/Modelos__Procedimientos_y_Servicios/Ayuda_P_G417____IVA__Llevanza_de_libros_registro__SII_/Ayuda_tecnica/Informacion_tecnica_SII/Preguntas_tecnicas_frecuentes/1__Cuestiones_Generales/16___Como_se_debe_utilizar_el_dato_sesionId__.shtml
if test:
plugins.append(LoggingPlugin())
client = Client(wsdl=wsdl, transport=transport, plugins=plugins)
return client
def bind_SuministroFactEmitidas(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_06/'
'SuministroFactEmitidas.wsdl'
)
port_name = 'SuministroFactEmitidas'
if test:
port_name += 'Pruebas'
cli = _get_client(wsdl, crt, pkey, test)
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')

View File

@ -36,7 +36,7 @@ major_version, minor_version, _ = version.split('.', 2)
major_version = int(major_version)
minor_version = int(minor_version)
requires = ['cryptography', 'pyOpenSSL']
requires = ['cryptography', 'pyOpenSSL', 'zeep', 'vatnumber']
for dep in info.get('depends', []):
if not re.match(r'(ir|res|webdav)(\W|$)', dep):
prefix = MODULE2PREFIX.get(dep, 'trytond')
@ -59,6 +59,7 @@ setup(name='%s_%s' % (PREFIX, MODULE),
packages=[
'trytond.modules.%s' % MODULE,
'trytond.modules.%s.tests' % MODULE,
'trytond.modules.%s.pyAEATsii' % MODULE,
],
package_data={
'trytond.modules.%s' % MODULE: (info.get('xml', [])

View File

@ -5,5 +5,6 @@
<field name="report"/>
<field name="invoice"/>
<field name="state"/>
<field name="communication_code"/>
<field name="communication_msg"/>
</tree>