# -*- coding: utf-8 -*- # The COPYRIGHT file at the top level of this repository contains the full # copyright notices and license terms. import unicodedata from logging import getLogger from decimal import Decimal from datetime import datetime from pyAEATsii import service from pyAEATsii import mapping from trytond.model import ModelSQL, ModelView, fields, Workflow from trytond.pyson import Eval, Bool from trytond.pool import Pool from trytond.transaction import Transaction from trytond.config import config from . import tools __all__ = [ 'SIIReport', 'SIIReportLine', 'SIIReportLineTax', ] _logger = getLogger(__name__) _ZERO = Decimal('0.0') # AEAT SII test SII_TEST = config.getboolean('aeat', 'sii_test', default=True) def _decimal(x): return Decimal(x) if x is not None else None def _date(x): return datetime.strptime(x, "%d-%m-%Y").date() def _datetime(x): return datetime.strptime(x, "%d-%m-%Y %H:%M:%S") COMMUNICATION_TYPE = [ # L0 ('A0', 'Registration of invoices/records'), ('A1', 'Amendment of invoices/records (registration errors)'), # ('A4', 'Amendment of Invoice for Travellers'), # Not supported # ('A5', 'Travellers registration'), # Not supported # ('A6', 'Amendment of travellers tax devolutions'), # Not supported ('C0', 'Query Invoices'), # Not in L0 ('D0', 'Delete Invoices'), # Not In L0 ] BOOK_KEY = [ (None, ''), ('E', 'Issued Invoices'), ('I', 'Investment Goods'), ('R', 'Received Invoices'), ('U', 'Particular Intracommunity Operations'), ] OPERATION_KEY = [ # L2_EMI - L2_RECI (None, ''), ('F1', 'Invoice (Art 6.7.3 y 7.3 of RD1619/2012)'), ('F2', 'Simplified Invoice (ticket) and Invoices without destination ' 'identidication (Art 6.1.d of RD1619/2012)'), ('R1', 'Corrected Invoice ' '(Art 80.1, 80.2 and 80.6 and error grounded in law)'), ('R2', 'Corrected Invoice (Art. 80.3)'), ('R3', 'Credit Note (Art 80.4)'), ('R4', 'Corrected Invoice (Other)'), ('R5', 'Corrected Invoice in simplified invoices'), ('F3', 'Invoice issued to replace simplified invoices issued and filed'), ('F4', 'Invoice summary entry'), ('F5', 'Import (DUA)'), ('F6', 'Other accounting documents'), ('LC', 'Duty - Complementary clearing'), # Not supported ] PARTY_IDENTIFIER_TYPE = [ (None, ''), ('02', 'NIF-VAT'), ('03', 'Passport'), ('04', 'Official identification document issued by the country ' 'or region of residence'), ('05', 'Residence certificate'), ('06', 'Other supporting document'), ('07', 'Not registered'), ] SEND_SPECIAL_REGIME_KEY = [ # L3.1 (None, ''), ('01', 'General tax regime activity'), ('02', 'Export'), ('03', 'Activities to which the special scheme of used goods, ' 'works of art, antiquities and collectables (135-139 of the VAT Law)'), ('04', 'Special scheme for investment gold'), ('05', 'Special scheme for travel agencies'), ('06', 'Special scheme applicable to groups of entities, VAT (Advanced)'), ('07', 'Special cash basis scheme'), ('08', 'Activities subject to Canary Islands General Indirect Tax/Tax on ' 'Production, Services and Imports'), ('09', 'Invoicing of the provision of travel agency services acting as ' 'intermediaries in the name of and on behalf of other persons ' '(Additional Provision 4, Royal Decree 1619/2012)'), ('10', 'Collections on behalf of third parties of professional fees or ' 'industrial property, copyright or other such rights by partners, ' 'associates or members undertaken by companies, associations, ' 'professional organisations or other entities that, amongst their ' 'functions, undertake collections'), ('11', 'Business premises lease activities subject to withholding'), ('12', 'Business premises lease activities not subject to withholding'), ('13', 'Business premises lease activities subject and not subject ' 'to withholding'), ('14', 'Invoice with VAT pending accrual (work certifications with Public ' 'Administration recipients)'), ('15', 'Invoice with VAT pending accrual - ' 'operations of successive tract'), ('16', 'First semester 2017 and other invoices before the SII'), ] RECEIVE_SPECIAL_REGIME_KEY = [ (None, ''), ('01', 'General tax regime activity'), ('02', 'Activities through which businesses pay compensation for special ' 'VAT arrangements for agriculture and fisheries'), ('03', 'Activities to which the special scheme of used goods, ' 'works of art, antiquities and collectables (135-139 of the VAT Law)'), ('04', 'Special scheme for investment gold'), ('05', 'Special scheme for travel agencies'), ('06', 'Special scheme applicable to groups of entities, VAT (Advanced)'), ('07', 'Special cash basis scheme'), ('08', 'Activities subject to Canary Islands General Indirect Tax/Tax ' 'on Production, Services and Imports'), ('09', 'Intra-Community acquisition of assets and provisions of services'), ('12', 'Business premises lease activities'), ('13', 'Invoice corresponding to an import ' '(reported without been associated with a DUA)'), ('14', 'First semester 2017 and other invoices before the SII'), ] AEAT_COMMUNICATION_STATE = [ (None, ''), ('Correcto', 'Accepted'), ('ParcialmenteCorrecto', 'Partially Accepted'), ('Incorrecto', 'Rejected') ] AEAT_INVOICE_STATE = [ (None, ''), ('Correcto', 'Accepted '), ('Correcta', 'Accepted'), # You guys are disgusting ('AceptadoConErrores', 'Accepted with Errors '), ('AceptadaConErrores', 'Accepted with Errors'), # Shame on AEAT ('Anulada', 'Deleted'), ('Incorrecto', 'Rejected') ] PROPERTY_STATE = [ # L6 ('0', ''), ('1', '1. Property with a land register reference located in any part ' 'of Spain, with the exception of the Basque Country and Navarre'), ('2', '2. Property located in the Autonomous Community of the Basque ' 'Country or the Chartered Community of Navarre.'), ('3', '3. Property in any of the foregoing locations ' 'with no land register reference'), ('4', '4. Property located abroad'), ] # L7 - Iva Subjected IVA_SUBJECTED = [ (None, ''), ('S1', 'Subject - Not exempt. Non VAT reverse charge'), ('S2', 'Subject - Not exempt. VAT reverse charge'), ('S3', 'Subject - Not exempt. Both non VAT reverse charge ' 'and VAT reverse charge') ] # L9 - Excemption cause EXCEMPTION_CAUSE = [ (None, ''), ('E1', 'Exempt on account of Article 20'), ('E2', 'Exempt on account of Article 21'), ('E3', 'Exempt on account of Article 22'), ('E4', 'Exempt on account of Article 23 and Article 24'), ('E5', 'Exempt on account of Article 25'), ('E6', 'Exempt on other grounds'), ] # L11 Payment Type PAYMENT_TYPE = [ ('01', 'Transfer'), ('02', 'Cheque'), ('03', 'Not to be collected/paid (deadline for accrual/forced accrual ' 'as part of insolvency proceedings)'), ('04', 'Other methods of collection/payment'), ('05', 'Bank debit order'), ] # L12 INTRACOMUNITARY_TYPE = [ (None, ''), ('A', 'The transmission or receipt of goods to undertake partial reports ' 'or works stipulated in Article 70, section one, Number 7 ' 'of the Tax Law (Law 37/1992)'), ('B', 'Transfers of goods or intra-Community acquisitions of goods listed ' 'in Article 9.3 and Article 16.2 of the Tax Law (Law 37/1992)'), ] def remove_accents(unicode_string): if isinstance(unicode_string, str): unicode_string_bak = unicode_string try: unicode_string = unicode_string_bak.decode('iso-8859-1') except UnicodeDecodeError: try: unicode_string = unicode_string_bak.decode('utf-8') except UnicodeDecodeError: return unicode_string_bak if not isinstance(unicode_string, str): return unicode_string # From http://www.leccionespracticas.com/uncategorized/eliminar-tildes-con-python-solucionado unicode_string_nfd = ''.join( (c for c in unicodedata.normalize('NFD', unicode_string) if (unicodedata.category(c) != 'Mn' or c in ('\\u0327', '\\u0303')) # ç or ñ )) # It converts nfd to nfc to allow unicode.decode() return unicodedata.normalize('NFC', unicode_string_nfd) _STATES = { 'readonly': Eval('state') != 'draft', } _DEPENDS = ['state'] class SIIReport(Workflow, ModelSQL, ModelView): ''' SII Report ''' __name__ = 'aeat.sii.report' company = fields.Many2One('company.company', 'Company', required=True, states={ 'readonly': Eval('state') != 'draft', }, depends=['state']) company_vat = fields.Char('VAT', size=9, states={ 'required': Eval('state').in_(['confirmed', 'done']), 'readonly': ~Eval('state').in_(['draft', 'confirmed']), }, depends=['state']) currency = fields.Function(fields.Many2One('currency.currency', 'Currency'), 'on_change_with_currency') fiscalyear = fields.Many2One('account.fiscalyear', 'Fiscal Year', required=True, states={ 'readonly': ((Eval('state') != 'draft') | (Eval('lines', [0]) & Eval('fiscalyear'))), }, depends=['state']) period = fields.Many2One('account.period', 'Period', required=True, domain=[('fiscalyear', '=', Eval('fiscalyear'))], states={ 'readonly': ((Eval('state') != 'draft') | (Eval('lines', [0]) & Eval('period'))), }, depends=['state', 'fiscalyear']) load_date = fields.Date('Load Date', domain=['OR', [ ('load_date', '=', None), ], [ ('load_date', '>=', Eval('load_date_start')), ('load_date', '<=', Eval('load_date_end')), ] ], depends=['load_date_start', 'load_date_end'], help='Filter invoices to the date whitin the period.') load_date_start = fields.Function(fields.Date('Load Date Start'), 'on_change_with_load_date_start') load_date_end = fields.Function(fields.Date('Load Date End'), 'on_change_with_load_date_end') operation_type = fields.Selection(COMMUNICATION_TYPE, 'Operation Type', required=True, states={ 'readonly': ((~Eval('state').in_(['draft', 'confirmed'])) | (Eval('lines', [0]) & Eval('operation_type'))), }, depends=['state']) book = fields.Selection(BOOK_KEY, 'Book', required=True, states={ 'readonly': ((~Eval('state').in_(['draft', 'confirmed'])) | (Eval('lines', [0]) & Eval('book'))), }, depends=['state']) state = fields.Selection([ ('draft', 'Draft'), ('confirmed', 'Confirmed'), ('done', 'Done'), ('cancelled', 'Cancelled'), ('sent', 'Sent'), ], 'State', readonly=True) communication_state = fields.Selection(AEAT_COMMUNICATION_STATE, 'Communication State', readonly=True) csv = fields.Char('CSV', readonly=True) version = fields.Selection([ ('0.7', '0.7'), ('1.0', '1.0'), ('1.1', '1.1'), ], 'Version', required=True, readonly=True) lines = fields.One2Many('aeat.sii.report.lines', 'report', 'Lines', states={ 'readonly': Eval('state') != 'draft', }, depends=['state']) # TODO crash GTK client 4.x with widget date in XML view and attribute # readonly = True. At the moment, use PYSON to readonly field in XML views. send_date = fields.DateTime('Send date', states={ 'invisible': Eval('state') != 'sent', 'readonly': Bool(Eval('state') == 'sent'), }, depends=['state']) @classmethod def __setup__(cls): super(SIIReport, cls).__setup__() cls._buttons.update({ 'draft': { 'invisible': ~Eval('state').in_(['confirmed', 'cancelled']), 'icon': 'tryton-go-previous', }, 'confirm': { 'invisible': ~Eval('state').in_(['draft']), 'icon': 'tryton-go-next', }, 'send': { 'invisible': ~Eval('state').in_(['confirmed']), 'icon': 'tryton-ok', }, 'cancel': { 'invisible': Eval('state').in_(['cancelled', 'sent']), 'icon': 'tryton-cancel', }, 'load_invoices': { 'invisible': ~(Eval('state').in_(['draft']) & Eval('operation_type').in_(['A0', 'A1'])), } }) cls._error_messages.update({ 'delete_cancel': ('Report "%s" must be cancelled before ' 'deletion.'), }) cls._transitions |= set(( ('draft', 'confirmed'), ('draft', 'cancelled'), ('confirmed', 'draft'), ('confirmed', 'sent'), ('confirmed', 'cancelled'), ('cancelled', 'draft'), )) @staticmethod def default_company(): return Transaction().context.get('company') @staticmethod def default_fiscalyear(): FiscalYear = Pool().get('account.fiscalyear') return FiscalYear.find( Transaction().context.get('company'), exception=False) @staticmethod def default_state(): return 'draft' @staticmethod def default_version(): return '1.1' @fields.depends('period') def on_change_period(self): if not self.period: self.load_date = None @fields.depends('company') def on_change_with_company_vat(self): if self.company: return self.company.party.sii_vat_code @fields.depends('company') def on_change_with_currency(self, name=None): if self.company: return self.company.currency.id @fields.depends('period') def on_change_with_load_date_start(self, name=None): return self.period.start_date if self.period else None @fields.depends('period') def on_change_with_load_date_end(self, name=None): return self.period.end_date if self.period else None @classmethod def copy(cls, records, default=None): if default is None: default = {} else: default = default.copy() default['communication_state'] = None default['csv'] = None default['send_date'] = None return super(SIIReport, cls).copy(records, default=default) @classmethod def delete(cls, reports): # Cancel before delete for report in reports: if report.state != 'cancelled': cls.raise_user_error('delete_cancel', (report.rec_name,)) super(SIIReport, cls).delete(reports) @classmethod @ModelView.button @Workflow.transition('draft') def draft(cls, reports): pass @classmethod @ModelView.button @Workflow.transition('confirmed') def confirm(cls, reports): pass @classmethod @ModelView.button @Workflow.transition('cancelled') def cancel(cls, reports): pass @classmethod @ModelView.button @Workflow.transition('sent') def send(cls, reports): for report in reports: if report.book == 'E': # issued invoices if report.operation_type in {'A0', 'A1'}: report.submit_issued_invoices() elif report.operation_type == 'C0': report.query_issued_invoices() elif report.operation_type == 'D0': report.delete_issued_invoices() else: raise NotImplementedError elif report.book == 'R': if report.operation_type in {'A0', 'A1'}: report.submit_recieved_invoices() elif report.operation_type == 'C0': report.query_recieved_invoices() elif report.operation_type == 'D0': report.delete_recieved_invoices() else: raise NotImplementedError else: raise NotImplementedError cls.write(reports, { 'send_date': datetime.now()}) _logger.debug('Done sending reports to AEAT SII') @classmethod @ModelView.button def load_invoices(cls, reports): pool = Pool() Invoice = pool.get('account.invoice') ReportLine = pool.get('aeat.sii.report.lines') to_create = [] for report in reports: domain = [ ('sii_book_key', '=', report.book), ('move.period', '=', report.period.id), ('state', 'in', ['posted', 'paid']), ] if report.operation_type == 'A0': domain.append(('sii_state', 'in', [None, 'Incorrecto'])) elif report.operation_type in ('A1', 'A4'): domain.append(('sii_state', 'in', [ 'AceptadoConErrores', 'AceptadaConErrores'])) if report.load_date: domain.append(['OR', [ ('invoice_date', '<=', report.load_date), ('accounting_date', '=', None), ], [ ('accounting_date', '<=', report.load_date), ]]) _logger.debug('Searching invoices for SII report: %s', domain) for invoice in Invoice.search(domain): to_create.append({ 'report': report, 'invoice': invoice, }) if to_create: ReportLine.create(to_create) def submit_issued_invoices(self): pool = Pool() mapper = pool.get('aeat.sii.issued.invoice.mapper')(pool=pool) _logger.info('Sending report %s to AEAT SII', self.id) headers = mapping.get_headers( name=tools.unaccent(self.company.party.name), vat=self.company_vat, comm_kind=self.operation_type, version=self.version) with self.company.tmp_ssl_credentials() as (crt, key): srv = service.bind_issued_invoices_service( crt, key, test=SII_TEST) try: res = srv.submit( headers, (line.invoice for line in self.lines), mapper=mapper) except Exception as e: self.raise_user_error(tools.unaccent(str(e))) self._save_response(res) def delete_issued_invoices(self): pool = Pool() mapper = pool.get('aeat.sii.issued.invoice.mapper')(pool=pool) headers = mapping.get_headers( name=tools.unaccent(self.company.party.name), vat=self.company_vat, comm_kind=self.operation_type, version=self.version) with self.company.tmp_ssl_credentials() as (crt, key): srv = service.bind_issued_invoices_service( crt, key, test=SII_TEST) try: res = srv.cancel( headers, (line.invoice for line in self.lines), mapper=mapper) except Exception as e: self.raise_user_error(tools.unaccent(str(e))) self._save_response(res) def query_issued_invoices(self, last_invoice=None): pool = Pool() Invoice = pool.get('account.invoice') mapper = pool.get('aeat.sii.issued.invoice.mapper')(pool=pool) SIIReportLine = pool.get('aeat.sii.report.lines') SIIReportLineTax = pool.get('aeat.sii.report.line.tax') headers = mapping.get_headers( name=tools.unaccent(self.company.party.name), vat=self.company_vat, comm_kind=self.operation_type, version=self.version) with self.company.tmp_ssl_credentials() as (crt, key): srv = service.bind_issued_invoices_service( crt, key, test=SII_TEST) res = srv.query( headers, year=self.period.start_date.year, period=self.period.start_date.month) registers = \ res.RegistroRespuestaConsultaLRFacturasEmitidas # FIXME: the number can be repeated over time invoices_list = Invoice.search([ ('number', 'in', [ reg.IDFactura.NumSerieFacturaEmisor for reg in registers ]) ]) invoices_ids = { invoice.number: invoice.id for invoice in invoices_list } pagination = res.IndicadorPaginacion last_invoice = registers[-1].IDFactura lines_to_create = [] for reg in registers: taxes_to_create = [] taxes = None exemption = '' tipo_desglose = reg.DatosFacturaEmitida.TipoDesglose if tipo_desglose.DesgloseFactura: sujeta = tipo_desglose.DesgloseFactura.Sujeta else: if tipo_desglose.DesgloseTipoOperacion.PrestacionServicios: sujeta = tipo_desglose.DesgloseTipoOperacion.\ PrestacionServicios.Sujeta else: sujeta = tipo_desglose.DesgloseTipoOperacion.Entrega.Sujeta if sujeta.NoExenta: for detail in sujeta.NoExenta.DesgloseIVA.DetalleIVA: taxes_to_create.append({ 'base': _decimal(detail.BaseImponible), 'rate': _decimal(detail.TipoImpositivo), 'amount': _decimal(detail.CuotaRepercutida), 'surcharge_rate': _decimal( detail.TipoRecargoEquivalencia), 'surcharge_amount': _decimal( detail.CuotaRecargoEquivalencia), }) taxes = SIIReportLineTax.create(taxes_to_create) elif sujeta.Exenta: exemption = sujeta.Exenta.DetalleExenta[0].CausaExencion for exempt in EXCEMPTION_CAUSE: if exempt[0] == exemption: exemption = exempt[1] break sii_report_line = { 'report': self.id, 'invoice': invoices_ids.get( reg.IDFactura.NumSerieFacturaEmisor), 'state': reg.EstadoFactura.EstadoRegistro, 'last_modify_date': _datetime( reg.EstadoFactura.TimestampUltimaModificacion), 'communication_code': reg.EstadoFactura.CodigoErrorRegistro, 'communication_msg': reg.EstadoFactura.DescripcionErrorRegistro, 'issuer_vat_number': ( reg.IDFactura.IDEmisorFactura.NIF or reg.IDFactura.IDEmisorFactura.IDOtro.ID), 'serial_number': reg.IDFactura.NumSerieFacturaEmisor, 'final_serial_number': ( reg.IDFactura.NumSerieFacturaEmisorResumenFin), 'issue_date': _date( reg.IDFactura.FechaExpedicionFacturaEmisor), 'invoice_kind': reg.DatosFacturaEmitida.TipoFactura, 'special_key': ( reg.DatosFacturaEmitida.ClaveRegimenEspecialOTrascendencia), 'total_amount': _decimal(reg.DatosFacturaEmitida.ImporteTotal), 'taxes': [('add', [t.id for t in taxes])] if taxes else [], 'exemption_key': exemption, 'counterpart_name': ( reg.DatosFacturaEmitida.Contraparte.NombreRazon if reg.DatosFacturaEmitida.Contraparte else None), 'counterpart_id': ( ( reg.DatosFacturaEmitida.Contraparte.NIF or reg.DatosFacturaEmitida.Contraparte.IDOtro.ID) if reg.DatosFacturaEmitida.Contraparte else None), 'presenter': reg.DatosPresentacion.NIFPresentador, 'presentation_date': _datetime( reg.DatosPresentacion.TimestampPresentacion), 'csv': reg.DatosPresentacion.CSV, 'balance_state': reg.DatosPresentacion.CSV, 'aeat_register': str(reg), } lines_to_create.append(sii_report_line) SIIReportLine.create(lines_to_create) if pagination == 'S': self.query_issued_invoices(last_invoice=last_invoice) def submit_recieved_invoices(self): pool = Pool() mapper = pool.get('aeat.sii.recieved.invoice.mapper')(pool=pool) _logger.info('Sending report %s to AEAT SII', self.id) headers = mapping.get_headers( name=tools.unaccent(self.company.party.name), vat=self.company_vat, comm_kind=self.operation_type, version=self.version) with self.company.tmp_ssl_credentials() as (crt, key): srv = service.bind_recieved_invoices_service( crt, key, test=SII_TEST) try: res = srv.submit( headers, (line.invoice for line in self.lines), mapper=mapper) except Exception as e: self.raise_user_error(tools.unaccent(str(e))) self._save_response(res) def delete_recieved_invoices(self): pool = Pool() mapper = pool.get('aeat.sii.recieved.invoice.mapper')(pool=pool) headers = mapping.get_headers( name=tools.unaccent(self.company.party.name), vat=self.company_vat, comm_kind=self.operation_type, version=self.version) with self.company.tmp_ssl_credentials() as (crt, key): srv = service.bind_recieved_invoices_service( crt, key, test=SII_TEST) try: res = srv.cancel( headers, (line.invoice for line in self.lines), mapper=mapper) except Exception as e: self.raise_user_error(tools.unaccent(str(e))) self._save_response(res) def _save_response(self, response): for (report_line, response_line) in zip( self.lines, response.RespuestaLinea): report_line.state = response_line.EstadoRegistro report_line.communication_code = response_line.CodigoErrorRegistro report_line.communication_msg = ( response_line.DescripcionErrorRegistro) report_line.save() self.communication_state = response.EstadoEnvio self.csv = response.CSV self.save() def query_recieved_invoices(self, last_invoice=None): pool = Pool() Invoice = pool.get('account.invoice') mapper = pool.get('aeat.sii.recieved.invoice.mapper')(pool=pool) SIIReportLine = pool.get('aeat.sii.report.lines') SIIReportLineTax = pool.get('aeat.sii.report.line.tax') headers = mapping.get_headers( name=tools.unaccent(self.company.party.name), vat=self.company_vat, comm_kind=self.operation_type, version=self.version) with self.company.tmp_ssl_credentials() as (crt, key): srv = service.bind_recieved_invoices_service( crt, key, test=SII_TEST) res = srv.query( headers, year=self.period.start_date.year, period=self.period.start_date.month) _logger.debug(res) registers = res.RegistroRespuestaConsultaLRFacturasRecibidas pagination = res.IndicadorPaginacion last_invoice = registers[-1].IDFactura # FIXME: the reference is not forced to be unique lines_to_create = [] for reg in registers: invoice_date = _date(reg.IDFactura.FechaExpedicionFacturaEmisor) taxes_to_create = [] for detail in reg.DatosFacturaRecibida.DesgloseFactura.DesgloseIVA.\ DetalleIVA: taxes_to_create.append({ 'base': _decimal(detail.BaseImponible), 'rate': _decimal(detail.TipoImpositivo), 'amount': _decimal(detail.CuotaSoportada), 'surcharge_rate': _decimal( detail.TipoRecargoEquivalencia), 'surcharge_amount': _decimal( detail.CuotaRecargoEquivalencia), 'reagyp_rate': _decimal( detail.PorcentCompensacionREAGYP), 'reagyp_amount': _decimal( detail.ImporteCompensacionREAGYP), }) taxes = SIIReportLineTax.create(taxes_to_create) sii_report_line = { 'report': self.id, 'state': reg.EstadoFactura.EstadoRegistro, 'last_modify_date': _date( reg.EstadoFactura.TimestampUltimaModificacion), 'communication_code': ( reg.EstadoFactura.CodigoErrorRegistro), 'communication_msg': ( reg.EstadoFactura.DescripcionErrorRegistro), 'issuer_vat_number': ( reg.IDFactura.IDEmisorFactura.NIF or reg.IDFactura.IDEmisorFactura.IDOtro.ID), 'serial_number': reg.IDFactura.NumSerieFacturaEmisor, 'final_serial_number': ( reg.IDFactura.NumSerieFacturaEmisorResumenFin), 'issue_date': invoice_date, 'invoice_kind': reg.DatosFacturaRecibida.TipoFactura, 'special_key': (reg.DatosFacturaRecibida. ClaveRegimenEspecialOTrascendencia), 'total_amount': _decimal( reg.DatosFacturaRecibida.ImporteTotal), 'taxes': [('add', [t.id for t in taxes])], 'counterpart_name': ( reg.DatosFacturaRecibida.Contraparte.NombreRazon), 'counterpart_id': ( reg.DatosFacturaRecibida.Contraparte.NIF or reg.DatosFacturaRecibida.Contraparte.IDOtro.ID), 'presenter': reg.DatosPresentacion.NIFPresentador, 'presentation_date': _datetime( reg.DatosPresentacion.TimestampPresentacion), 'csv': reg.DatosPresentacion.CSV, 'balance_state': reg.DatosPresentacion.CSV, 'aeat_register': str(reg), } domain = [ ('reference', '=', reg.IDFactura.NumSerieFacturaEmisor), ('invoice_date', '=', invoice_date), ] vat = True if reg.IDFactura.IDEmisorFactura.NIF: vat = reg.IDFactura.IDEmisorFactura.NIF if not vat.startswith('ES'): vat = 'ES' + vat domain.append( ('party.vat_code', '=', vat) ) elif reg.IDFactura.IDEmisorFactura.IDOtro.IDType == '02': domain.append( ('party.vat_code', '=', reg.IDFactura.IDEmisorFactura.IDOtro.ID) ) else: vat = False invoices = Invoice.search(domain) if not vat and len(invoices) > 1: invoice_ok = [] for invoice in invoices: for register in registers: if register == reg.IDFactura.IDEmisorFactura.IDOtro.ID: invoice_ok.append(invoice) break invoices = invoice_ok if invoice_ok: break if invoices: sii_report_line['invoice'] = invoices[0].id lines_to_create.append(sii_report_line) SIIReportLine.create(lines_to_create) if pagination == 'S': self.query_recieved_invoices(last_invoice=last_invoice) class SIIReportLine(ModelSQL, ModelView): ''' AEAT SII Issued ''' __name__ = 'aeat.sii.report.lines' report = fields.Many2One( 'aeat.sii.report', 'Issued Report', ondelete='CASCADE', select=True) invoice = fields.Many2One('account.invoice', 'Invoice', select=True, states={ 'required': Eval('_parent_report', {}).get( 'operation_type') != 'C0', }) state = fields.Selection(AEAT_INVOICE_STATE, 'State') last_modify_date = fields.DateTime('Last Modification Date', readonly=True) communication_code = fields.Integer( 'Communication Code', readonly=True) communication_msg = fields.Char( 'Communication Message', readonly=True) company = fields.Many2One( 'company.company', 'Company', required=True, select=True) issuer_vat_number = fields.Char('Issuer VAT Number', readonly=True) serial_number = fields.Char('Serial Number', readonly=True) final_serial_number = fields.Char('Final Serial Number', readonly=True) issue_date = fields.Date('Issued Date', readonly=True) invoice_kind = fields.Char('Invoice Kind', readonly=True) special_key = fields.Char('Special Key', readonly=True) total_amount = fields.Numeric('Total Amount', readonly=True) counterpart_name = fields.Char('Counterpart Name', readonly=True) counterpart_id = fields.Char('Counterpart ID', readonly=True) taxes = fields.One2Many( 'aeat.sii.report.line.tax', 'line', 'Tax Lines', readonly=True) presenter = fields.Char('Presenter', readonly=True) presentation_date = fields.DateTime('Presentation Date', readonly=True) csv = fields.Char('CSV', readonly=True) balance_state = fields.Char('Balance State', readonly=True) # TODO counterpart balance data vat_code = fields.Function(fields.Char('VAT Code'), 'get_vat_code') identifier_type = fields.Function( fields.Selection(PARTY_IDENTIFIER_TYPE, 'Identifier Type'), 'get_identifier_type') invoice_operation_key = fields.Function( fields.Selection(OPERATION_KEY, 'SII Operation Key'), 'get_invoice_operation_key') exemption_key = fields.Char('Exemption Cause', readonly=True) aeat_register = fields.Text('Register from AEAT Webservice', readonly=True) def get_invoice_operation_key(self, name): return self.invoice.sii_operation_key if self.invoice else None def get_vat_code(self, name): return self.invoice.party.sii_vat_code if self.invoice else None def get_identifier_type(self, name): return self.invoice.party.sii_identifier_type if self.invoice else None @staticmethod def default_company(): return Transaction().context.get('company') @classmethod def copy(cls, records, default=None): if default is None: default = {} else: default = default.copy() default['state'] = None default['communication_code'] = None default['communication_msg'] = None default['issuer_vat_number'] = None default['serial_number'] = None default['final_serial_number'] = None default['issue_date'] = None default['invoice_kind'] = None default['special_key'] = None default['total_amount'] = None default['taxes'] = None default['counterpart_name'] = None default['counterpart_id'] = None default['presenter'] = None default['presentation_date'] = None default['csv'] = None default['balance_state'] = None return super(SIIReportLine, cls).copy(records, default=default) class SIIReportLineTax(ModelSQL, ModelView): ''' SII Report Line Tax ''' __name__ = 'aeat.sii.report.line.tax' line = fields.Many2One( 'aeat.sii.report.lines', 'Report Line', ondelete='CASCADE', select=True) base = fields.Numeric('Base', readonly=True) rate = fields.Numeric('Rate', readonly=True) amount = fields.Numeric('Amount', readonly=True) surcharge_rate = fields.Numeric('Surcharge Rate', readonly=True) surcharge_amount = fields.Numeric('Surcharge Amount', readonly=True) reagyp_rate = fields.Numeric('REAGYP Rate', readonly=True) reagyp_amount = fields.Numeric('REAGYP Amount', readonly=True)