Report issued invoices to SII
This commit is contained in:
parent
a764c976fe
commit
4df9df692c
81
aeat.py
81
aeat.py
|
@ -1,16 +1,26 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# 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.
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
'SIIReport',
|
||||||
|
'SIIReportLine',
|
||||||
|
]
|
||||||
|
|
||||||
import unicodedata
|
import unicodedata
|
||||||
from logging import getLogger
|
from logging import getLogger
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
|
|
||||||
from trytond.model import ModelSQL, ModelView, fields, Workflow
|
from trytond.model import ModelSQL, ModelView, fields, Workflow
|
||||||
from trytond.pyson import Eval
|
from trytond.pyson import Eval
|
||||||
from trytond.pool import Pool, PoolMeta
|
from trytond.pool import Pool
|
||||||
from trytond.transaction import Transaction
|
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__)
|
_logger = getLogger(__name__)
|
||||||
_ZERO = Decimal('0.0')
|
_ZERO = Decimal('0.0')
|
||||||
|
|
||||||
|
@ -55,7 +65,8 @@ PARTY_IDENTIFIER_TYPE = [
|
||||||
('04', 'Official Document Emmited by the Country of Residence'),
|
('04', 'Official Document Emmited by the Country of Residence'),
|
||||||
('05', 'Certificate of fiscal resident'),
|
('05', 'Certificate of fiscal resident'),
|
||||||
('06', 'Other proving document'),
|
('06', 'Other proving document'),
|
||||||
]
|
('07', 'Not on the Census'),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
SEND_SPECIAL_REGIME_KEY = [ # L3.1
|
SEND_SPECIAL_REGIME_KEY = [ # L3.1
|
||||||
|
@ -105,16 +116,16 @@ RECEIVE_SPECIAL_REGIME_KEY = [
|
||||||
|
|
||||||
AEAT_COMMUNICATION_STATE = [
|
AEAT_COMMUNICATION_STATE = [
|
||||||
(None, ''),
|
(None, ''),
|
||||||
('CORRECTO', 'Accepted'),
|
('Correcto', 'Accepted'),
|
||||||
('PARCIALMENTECORRECTO', 'Partial Accepted'),
|
('ParcialmenteCorrecto', 'Partially Accepted'),
|
||||||
('INCORRECTO', 'Rejected')
|
('Incorrecto', 'Rejected')
|
||||||
]
|
]
|
||||||
|
|
||||||
AEAT_INVOICE_STATE = [
|
AEAT_INVOICE_STATE = [
|
||||||
(None, ''),
|
(None, ''),
|
||||||
('CORRECTO', 'Accepted'),
|
('Correcto', 'Accepted'),
|
||||||
('ACEPTADOCONERRORES', 'Accepted with Errors'),
|
('AceptadoConErrores', 'Accepted with Errors'),
|
||||||
('INCORRECTO', 'Rejected')
|
('Incorrecto', 'Rejected')
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -237,8 +248,9 @@ class SIIReport(Workflow, ModelSQL, ModelView):
|
||||||
('draft', 'Draft'),
|
('draft', 'Draft'),
|
||||||
('confirmed', 'Confirmed'),
|
('confirmed', 'Confirmed'),
|
||||||
('done', 'Done'),
|
('done', 'Done'),
|
||||||
('cancelled', 'Cancelled')
|
('cancelled', 'Cancelled'),
|
||||||
], 'State', readonly=True)
|
('sent', 'Sent'),
|
||||||
|
], 'State', readonly=True)
|
||||||
|
|
||||||
communication_state = fields.Selection( AEAT_COMMUNICATION_STATE,
|
communication_state = fields.Selection( AEAT_COMMUNICATION_STATE,
|
||||||
'Communication State', readonly=True)
|
'Communication State', readonly=True)
|
||||||
|
@ -273,7 +285,7 @@ class SIIReport(Workflow, ModelSQL, ModelView):
|
||||||
'icon': 'tryton-ok',
|
'icon': 'tryton-ok',
|
||||||
},
|
},
|
||||||
'cancel': {
|
'cancel': {
|
||||||
'invisible': Eval('state').in_(['cancelled']),
|
'invisible': Eval('state').in_(['cancelled', 'sent']),
|
||||||
'icon': 'tryton-cancel',
|
'icon': 'tryton-cancel',
|
||||||
},
|
},
|
||||||
'load_invoices': {
|
'load_invoices': {
|
||||||
|
@ -336,12 +348,39 @@ class SIIReport(Workflow, ModelSQL, ModelView):
|
||||||
@ModelView.button
|
@ModelView.button
|
||||||
@Workflow.transition('sent')
|
@Workflow.transition('sent')
|
||||||
def send(cls, reports):
|
def send(cls, reports):
|
||||||
_logger.info(
|
|
||||||
'Sending reports (%s) to AEAT SII',
|
def call_aeat(headers, invoices):
|
||||||
','.join(str(r.id) for r in reports))
|
|
||||||
for report in reports:
|
|
||||||
with report.company.tmp_ssl_credentials() as (crt, key):
|
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
|
@classmethod
|
||||||
@ModelView.button
|
@ModelView.button
|
||||||
|
@ -388,8 +427,10 @@ class SIIReportLine(ModelSQL, ModelView):
|
||||||
'aeat.sii.report', 'Issued Report', ondelete='CASCADE')
|
'aeat.sii.report', 'Issued Report', ondelete='CASCADE')
|
||||||
invoice = fields.Many2One('account.invoice', 'Invoice')
|
invoice = fields.Many2One('account.invoice', 'Invoice')
|
||||||
state = fields.Selection(AEAT_INVOICE_STATE, 'State')
|
state = fields.Selection(AEAT_INVOICE_STATE, 'State')
|
||||||
communication_msg = fields.Selection(
|
communication_code = fields.Selection(
|
||||||
aeat_errors.AEAT_ERRORS, 'Communication Message', readonly=True)
|
aeat_errors.AEAT_ERRORS, 'Communication Code', readonly=True)
|
||||||
|
communication_msg = fields.Char(
|
||||||
|
'Communication Message', readonly=True)
|
||||||
company = fields.Many2One(
|
company = fields.Many2One(
|
||||||
'company.company', 'Company', required=True, select=True)
|
'company.company', 'Company', required=True, select=True)
|
||||||
|
|
||||||
|
|
229
aeat_errors.py
229
aeat_errors.py
|
@ -1,116 +1,119 @@
|
||||||
AEAT_ERRORS = [
|
AEAT_ERRORS = [
|
||||||
(None, ''),
|
(None, ''),
|
||||||
(3501, 'Technical error of BBDD'),
|
('3501', 'Technical error of BBDD'),
|
||||||
(3500, 'Technical error of BBDD. Error in the Integrity of the Information'),
|
('3500', 'Technical error of BBDD. Error in the Integrity of the Information'),
|
||||||
(3502, 'Technical error. Failed to get invoice data'),
|
('3502', 'Technical error. Failed to get invoice data'),
|
||||||
(3503, 'Invoice consulted for the provision of Payments / Collections does not exist'),
|
('3503', 'Invoice consulted for the provision of Payments / Collections does not exist'),
|
||||||
(3504, 'Technical error. Failed to get Metall Collection data'),
|
('3504', 'Technical error. Failed to get Metall Collection data'),
|
||||||
(3505, 'Technical error. Error obtaining the data of the Insurance Operation'),
|
('3505', 'Technical error. Error obtaining the data of the Insurance Operation'),
|
||||||
(4100, 'Error in header. The contents of the IDVersionSii field are invalid.'),
|
('4100', 'Error in header. The contents of the IDVersionSii field are invalid.'),
|
||||||
(4101, 'Error in header. The content of theCommunication field is invalid.'),
|
('4101', 'Error in header. The content of theCommunication field is invalid.'),
|
||||||
(4102, 'XML does not meet the schema. Required field is missing: XXXX'),
|
('4102', 'XML does not meet the schema. Required field is missing: XXXX'),
|
||||||
(4103, 'Unexpected error parsing the XML'),
|
('4103', 'Unexpected error parsing the XML'),
|
||||||
(4104, 'Error in header. The value of the NIF field of the Holder block is not identified'),
|
('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'),
|
('4105', 'Error in the header. The value of the NIFRepresentator field of the Holder block is not identified'),
|
||||||
(4106, 'Error in date format'),
|
('4106', 'Error in date format'),
|
||||||
(4107, 'Technical error when obtaining the CSV.'),
|
('4107', 'Technical error when obtaining the CSV.'),
|
||||||
(4108, 'The XML root tag does not match the defined schema'),
|
('4108', 'The XML root tag does not match the defined schema'),
|
||||||
(4109, 'NIF is not identified. NIF: XXXX'),
|
('4109', 'NIF is not identified. NIF: XXXX'),
|
||||||
(4110, 'Failed to get the certificate.'),
|
('4110', 'Failed to get the certificate.'),
|
||||||
(4111, 'The NIF is in the wrong format.'),
|
('4111', 'The NIF is in the wrong format.'),
|
||||||
(4112, 'Technical error checking powers.'),
|
('4112', 'Technical error checking powers.'),
|
||||||
(4113, 'Technical error when creating the process.'),
|
('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'),
|
('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.'),
|
('4115', 'Technical error checking Social Collaboration.'),
|
||||||
(4116, 'The allowed limit of registrations for the block DatosInmueble / DetalleIVA has been exceeded'),
|
('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.'),
|
('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"),
|
('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."),
|
('4122', "Error in the header. The holder's NIF has an incorrect format."),
|
||||||
(4123, 'Error in header. The NIFRepresentant 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.'),
|
('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.'),
|
('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"),
|
('1100', "Incorrect value or field type: XXXX"),
|
||||||
(1101, "Incorrect field code value"),
|
('1101', "Incorrect field code value"),
|
||||||
(1102, "Field Value Incorrect Period"),
|
('1102', "Field Value Incorrect Period"),
|
||||||
(1103, "Incorrect IDType field value"),
|
('1103', "Incorrect IDType field value"),
|
||||||
(1104, "Incorrect ID field value"),
|
('1104', "Incorrect ID field value"),
|
||||||
(1105, "Field Value NumberFactory Incorrect Enumerator"),
|
('1105', "Field Value NumberFactory Incorrect Enumerator"),
|
||||||
(1106, "Field Value DateExpeditionFactorMissor Incorrect"),
|
('1106', "Field Value DateExpeditionInvoiceIssuer Incorrect"),
|
||||||
(1107, "Field Value Type Invoice Incorrect"),
|
('1107', "Field Value Type Invoice Incorrect"),
|
||||||
(1108, "Field Value Location Property Incorrect"),
|
('1108', "Field Value Location Property Incorrect"),
|
||||||
(1109, "Field Value Key Special Regime or Incorrect Transcendence"),
|
('1109', "Field Value Key Special Regime or Incorrect Transcendence"),
|
||||||
(1110, "Field Value Payment Medium or Incorrect Collection"),
|
('1110', "Field Value Payment Medium or Incorrect Collection"),
|
||||||
(1111, "Field Value Type Incorrect Amending"),
|
('1111', "Field Value Type Incorrect Amending"),
|
||||||
(1112, "The invoice NIF must be the same as the NIF of the record holder"),
|
('1112', "The invoice NIF must be the same as the NIF of the record holder"),
|
||||||
(1113, "Field Value CauseExcluded Incorrect"),
|
('1113', "Field Value CauseExcluded Incorrect"),
|
||||||
(1114, "Incorrect type field value"),
|
('1114', "Incorrect type field value"),
|
||||||
(1115, "If the invoice has part No Subject must report at least one of the two amounts not subject"),
|
('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"),
|
('1116', "NIF is not identified. NIF: XXXXX"),
|
||||||
(1117, "NIF is not identified. NIF: XXXXX. NAME_RACE: YYYYY"),
|
('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"),
|
('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"),
|
('1119', "The IdType of the Issuer and the Counterpart do not match"),
|
||||||
(1120, "Issuer and Counterparty ID do not match"),
|
('1120', "Issuer and Counterparty ID do not match"),
|
||||||
(1121, "The Issuer's and Counterpart's NIF do not overlap"),
|
('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"),
|
('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"),
|
('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"),
|
('1124', "Country Code is mandatory when Type Identification is different from VAT NIF"),
|
||||||
(1125, "The Expedition Date is greater than the current date"),
|
('1125', "The Expedition Date is greater than the current date"),
|
||||||
(1126, "Field value Incorrect exercise. This should be the current or previous year"),
|
('1126', "Field value Incorrect exercise. This should be the current or previous year"),
|
||||||
(1127, "Invoice Type of Seats Summary, NumberStockFactorEmisterSummaryFin is undeclared"),
|
('1127', "Invoice Type of Seats Summary, NumberStockFactorEmisterSummaryFin is undeclared"),
|
||||||
(1128, "Invoice type is not Seats Summary and has NumSeriesFactorSamplingResumedFin declared"),
|
('1128', "Invoice type is not Seats Summary and has NumSeriesFactorSamplingResumedFin declared"),
|
||||||
(1129, "The IssuedForColors field only accepts N or S values"),
|
('1129', "The IssuedForColors field only accepts N or S values"),
|
||||||
(1130, "Field Value TypeCommunication Incorrect."),
|
('1130', "Field Value TypeCommunication Incorrect."),
|
||||||
(1131, "Field value IncorrectKeyword."),
|
('1131', "Field value IncorrectKeyword."),
|
||||||
(1132, "Field value StateMember is incorrect."),
|
('1132', "Field value StateMember is incorrect."),
|
||||||
(1133, "Incorrect IDType field value. Must have value 02"),
|
('1133', "Incorrect IDType field value. Must have value 02"),
|
||||||
(1134, "If the invoice is of the rectifying type, the TypeRectificative field must have value."),
|
('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."),
|
('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."),
|
('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."),
|
('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"),
|
('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"),
|
('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)"),
|
('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)"),
|
('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"),
|
('1142', "Field Value NumberNumberFactorEmisterRemand Incorrect"),
|
||||||
(1143, "Field value NIF of block IDFacture with incorrect type"),
|
('1143', "Field value NIF of block IDFacture with incorrect type"),
|
||||||
(1144, "The invoice ID and CounterSource fields of the invoice are different"),
|
('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"),
|
('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"),
|
('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."),
|
('1147', "Error in the IDFactura block. IncorrectNameName field value."),
|
||||||
(1148, "BreakdownTypeOperation needs at least ProvisionServices or Delivery or both"),
|
('1148', "BreakdownTypeOperation needs at least ProvisionServices or Delivery or both"),
|
||||||
(1149, "The field ID is not identified"),
|
('1149', "The field ID is not identified"),
|
||||||
(1150, "The field CodePais indicated does not match the first two digits of the identifier"),
|
('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"),
|
('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"),
|
('1152', "If the field Average has 03 value the field Account_O_Medium can not have value"),
|
||||||
(1153, "NIF is formatted incorrectly"),
|
('1153', "NIF is formatted incorrectly"),
|
||||||
(1154, "The field MiscellaneousDisitors only accepts N or S values"),
|
('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"),
|
('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"),
|
('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"),
|
('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"),
|
('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"),
|
('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"),
|
('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"),
|
('1166', ""),
|
||||||
(3001, "Registration is already unsubscribed"),
|
('1177', "The value of the field XXX is not among the allowed values"),
|
||||||
(3002, "There is no record"),
|
('3000', "Duplicate Invoice"),
|
||||||
(3003, "Can not Include Charges for Unsubscribed Bills"),
|
('3001', "Registration is already unsubscribed"),
|
||||||
(3004, "Maximum field size exceeded"),
|
('3002', "There is no record"),
|
||||||
(3005, "Collections can not be included if the field KeyRegimenSpecialTotal of the invoice Has a value other than 07"),
|
('3003', "Can not Include Charges for Unsubscribed Bills"),
|
||||||
(3006, "Payment of unsubscribed invoices can not be included"),
|
('3004', "Maximum field size exceeded"),
|
||||||
(3007, "You can not include payments if the fieldTypeRegistryType of the invoice field Has a value other than 07"),
|
('3005', "Collections can not be included if the field KeyRegimenSpecialTotal of the invoice Has a value other than 07"),
|
||||||
(3008, "There is already a Metallic Collection with this Counterpart"),
|
('3006', "Payment of unsubscribed invoices can not be included"),
|
||||||
(3009, "Duplicate intra-Community operation"),
|
('3007', "You can not include payments if the fieldTypeRegistryType of the invoice field Has a value other than 07"),
|
||||||
(3010, "The Presenter does not have the necessary permissions to update this invoice"),
|
('3008', "There is already a Metallic Collection with this Counterpart"),
|
||||||
(3011, "It is not allowed to modify the Special Regime Key in invoices that contain Collections or Payments"),
|
('3009', "Duplicate intra-Community operation"),
|
||||||
(3012, "There is already an Insurance Operation with this Counterparty"),
|
('3010', "The Presenter does not have the necessary permissions to update this invoice"),
|
||||||
(2000, "If the fieldTypeRegistryType has a value of 12 or 13 the block of DatosInmueble must be informed"),
|
('3011', "It is not allowed to modify the Special Regime Key in invoices that contain Collections or Payments"),
|
||||||
(2001, "The base fieldAvailableAvailable on invoices issued should not be reported if the field ClaveRegimenEspecialTrascendencia has a value other than 06"),
|
('3012', "There is already an Insurance Operation with this Counterparty"),
|
||||||
(2002, "Error if SpecialRegimenKeyTransfer different from 12, 13 and the block of Estate"),
|
('2000', "If the fieldTypeRegistryType has a value of 12 or 13 the block of DatosInmueble must be informed"),
|
||||||
(2003, "Some of the rectified invoices do not exist in the system"),
|
('2001', "The base fieldAvailableAvailable on invoices issued should not be reported if the field ClaveRegimenEspecialTrascendencia has a value other than 06"),
|
||||||
(2004, "Technical Error when consulting the list of rectified invoices"),
|
('2002', "Error if SpecialRegimenKeyTransfer different from 12, 13 and the block of Estate"),
|
||||||
(2005, "The BaseAvailableAccount field should not be reported on received invoices if the field ClaveRegimenEspecialTrascendencia has a value other than 06"),
|
('2003', "Some of the rectified invoices do not exist in the system"),
|
||||||
(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"),
|
('2004', "Technical Error when consulting the list of rectified invoices"),
|
||||||
(2007, "The Total Amount field is not more than 6,000"),
|
('2005', "The BaseAvailableAccount field should not be reported on received invoices if the field ClaveRegimenEspecialTrascendencia has a value other than 06"),
|
||||||
(2008, "The field PercentDeliveryREAGYP should not be reported if the field KeyRegimenSpecialTrading on invoices received has a value other than 02"),
|
('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"),
|
||||||
(2009, "Do not enter the field PaymentCompensationREAGYP if the field KeyRegimenSpecialTrading on invoices received has a value other than 02"),
|
('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
|
# The COPYRIGHT file at the top level of this repository contains the full
|
||||||
# copyright notices and license terms.
|
# copyright notices and license terms.
|
||||||
|
|
||||||
|
from operator import attrgetter
|
||||||
|
|
||||||
from trytond import backend
|
from trytond import backend
|
||||||
from trytond.model import ModelSQL, ModelView, fields
|
from trytond.model import ModelSQL, ModelView, fields
|
||||||
from trytond.wizard import Wizard, StateView, StateTransition, Button
|
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,
|
RECEIVE_SPECIAL_REGIME_KEY, AEAT_INVOICE_STATE, IVA_SUBJECTED,
|
||||||
EXCEMPTION_CAUSE, INTRACOMUNITARY_TYPE)
|
EXCEMPTION_CAUSE, INTRACOMUNITARY_TYPE)
|
||||||
|
|
||||||
|
from .pyAEATsii import mapping
|
||||||
|
|
||||||
__all__ = ['Invoice', 'ReasignSIIRecord', 'ReasignSIIRecordStart',
|
__all__ = ['Invoice', 'ReasignSIIRecord', 'ReasignSIIRecordStart',
|
||||||
'ReasignSIIRecordEnd']
|
'ReasignSIIRecordEnd']
|
||||||
|
|
||||||
|
@ -107,6 +112,30 @@ class Invoice:
|
||||||
|
|
||||||
return result
|
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):
|
class ReasignSIIRecordStart(ModelView):
|
||||||
|
|
|
@ -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),
|
||||||
|
}
|
|
@ -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
|
|
@ -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)
|
major_version = int(major_version)
|
||||||
minor_version = int(minor_version)
|
minor_version = int(minor_version)
|
||||||
|
|
||||||
requires = ['cryptography', 'pyOpenSSL']
|
requires = ['cryptography', 'pyOpenSSL', 'zeep', 'vatnumber']
|
||||||
for dep in info.get('depends', []):
|
for dep in info.get('depends', []):
|
||||||
if not re.match(r'(ir|res|webdav)(\W|$)', dep):
|
if not re.match(r'(ir|res|webdav)(\W|$)', dep):
|
||||||
prefix = MODULE2PREFIX.get(dep, 'trytond')
|
prefix = MODULE2PREFIX.get(dep, 'trytond')
|
||||||
|
@ -59,6 +59,7 @@ setup(name='%s_%s' % (PREFIX, MODULE),
|
||||||
packages=[
|
packages=[
|
||||||
'trytond.modules.%s' % MODULE,
|
'trytond.modules.%s' % MODULE,
|
||||||
'trytond.modules.%s.tests' % MODULE,
|
'trytond.modules.%s.tests' % MODULE,
|
||||||
|
'trytond.modules.%s.pyAEATsii' % MODULE,
|
||||||
],
|
],
|
||||||
package_data={
|
package_data={
|
||||||
'trytond.modules.%s' % MODULE: (info.get('xml', [])
|
'trytond.modules.%s' % MODULE: (info.get('xml', [])
|
||||||
|
|
|
@ -5,5 +5,6 @@
|
||||||
<field name="report"/>
|
<field name="report"/>
|
||||||
<field name="invoice"/>
|
<field name="invoice"/>
|
||||||
<field name="state"/>
|
<field name="state"/>
|
||||||
|
<field name="communication_code"/>
|
||||||
<field name="communication_msg"/>
|
<field name="communication_msg"/>
|
||||||
</tree>
|
</tree>
|
||||||
|
|
Loading…
Reference in New Issue