mirror of
https://gitlab.com/datalifeit/trytond-aeat_sii
synced 2023-12-13 20:30:37 +01:00
Report issued invoices to SII
This commit is contained in:
parent
5075f5c2d2
commit
e8f4d665d5
9 changed files with 374 additions and 134 deletions
81
aeat.py
81
aeat.py
|
@ -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)
|
||||
|
||||
|
|
229
aeat_errors.py
229
aeat_errors.py
|
@ -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"),
|
||||
]
|
||||
|
|
29
invoice.py
29
invoice.py
|
@ -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
0
pyAEATsii/__init__.py
Normal file
91
pyAEATsii/mapping.py
Normal file
91
pyAEATsii/mapping.py
Normal 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
27
pyAEATsii/plugins.py
Normal 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
47
pyAEATsii/service.py
Normal 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')
|
3
setup.py
3
setup.py
|
@ -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', [])
|
||||
|
|
|
@ -5,5 +5,6 @@
|
|||
<field name="report"/>
|
||||
<field name="invoice"/>
|
||||
<field name="state"/>
|
||||
<field name="communication_code"/>
|
||||
<field name="communication_msg"/>
|
||||
</tree>
|
||||
|
|
Loading…
Reference in a new issue