mirror of
https://gitlab.com/datalifeit/trytond-aeat_sii
synced 2023-12-13 20:30:37 +01:00
716 lines
25 KiB
Python
716 lines
25 KiB
Python
# -*- coding: utf-8 -*-
|
||
# The COPYRIGHT file at the top level of this repository contains the full
|
||
# copyright notices and license terms.
|
||
|
||
__all__ = [
|
||
'SIIReport',
|
||
'SIIReportLine',
|
||
'IssuedTrytonInvoiceMapper',
|
||
'RecievedTrytonInvoiceMapper',
|
||
]
|
||
|
||
import unicodedata
|
||
from logging import getLogger
|
||
from decimal import Decimal
|
||
from operator import attrgetter
|
||
|
||
from pyAEATsii import service
|
||
from pyAEATsii import mapping
|
||
from pyAEATsii import callback_utils
|
||
|
||
from trytond.model import ModelSQL, ModelView, fields, Workflow
|
||
from trytond.model import Model
|
||
from trytond.pyson import Eval
|
||
from trytond.pool import Pool
|
||
from trytond.transaction import Transaction
|
||
|
||
|
||
_logger = getLogger(__name__)
|
||
_ZERO = Decimal('0.0')
|
||
|
||
|
||
COMMUNICATION_TYPE = [ # L0
|
||
('A0', 'New Invoices'),
|
||
('A1', 'Modify Invoices'),
|
||
# ('A4', 'Modify (Travelers)'), # Not suported
|
||
('C0', 'Query Invoices'), # Not in L0
|
||
('D0', 'Delete Invoices'), # Not In L0
|
||
]
|
||
|
||
BOOK_KEY = [
|
||
('E', 'Issued Invoices'),
|
||
('I', 'Investment Goods'),
|
||
('R', 'Received Invoices'),
|
||
('U', 'Particular Intracommunity Operations'),
|
||
('F', 'IGIC Issued Invoices'),
|
||
('J', 'IGIC Investment Goods'),
|
||
('S', 'IGIC Received Invoices'),
|
||
]
|
||
|
||
OPERATION_KEY = [ # L2_EMI - L2_RECI
|
||
('F1', 'Invoice'),
|
||
('F2', 'Simplified Invoice'),
|
||
('R1', 'Credit Note (Art 80.1 y 80.2)'),
|
||
('R2', 'Credit Note (Art 80.3)'),
|
||
('R3', 'Credit Note (Art 80.4)'),
|
||
('R4', 'Credit Note'),
|
||
('R5', 'Credit Note on simplified Invoices'),
|
||
('F3', 'Invoice issued as a substitute for simplified invoices'
|
||
'Billed and declared'),
|
||
('F4', 'Invoice Summary Account Move'),
|
||
('F5', 'Import (DUA)'),
|
||
('F6', 'Other accounting documents'),
|
||
|
||
]
|
||
|
||
PARTY_IDENTIFIER_TYPE = [
|
||
('02', 'NIF'),
|
||
('03', 'Passport'),
|
||
('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
|
||
('01', 'Common System Operation'),
|
||
('02', 'Export'),
|
||
('03', 'Operations to which the special arrangements for second-hand goods,'
|
||
' art objects, antiques and collectors articles apply (135-139 LIVA)'),
|
||
('04', 'Special investment gold regime'),
|
||
('05', 'Special travel agencies'),
|
||
('06', 'Special group of entities in VAT (Advanced Level)'),
|
||
('07', 'Special scheme for cash'),
|
||
('08', 'Operations subject to IPSI / IGIC'),
|
||
('09', 'Invoicing of travel agency services acting as mediators in the '
|
||
'name and for the account of others (D.A.4a RD1619 2012)'),
|
||
# ('10', 'Collection on behalf of third parties of professional fees or '
|
||
# 'rights derived from industrial property, author or others...'),
|
||
# ('11', 'Business premises lease transactions subject to withholding'),
|
||
# ('12', 'Non-retention business lease operations'),
|
||
# ('13', 'Lease transactions of business premises subject to and not subject '
|
||
# ' to withholding'),
|
||
# ('14', 'Invoice with tax pending of accrual (certifications of works whose'
|
||
# ' addressee is a Public Administration)'),
|
||
# ('15', 'Invoice with VAT pending accrual - operations of successive tract'),
|
||
]
|
||
|
||
RECEIVE_SPECIAL_REGIME_KEY = [
|
||
('01', 'Common system operation'),
|
||
('02', 'Operations by which employers satisfy REAGYP compensation'),
|
||
('03', 'Operations to which the special arrangements for second-hand goods,'
|
||
' art objects, antiques and collectors articles apply (135-139 LIVA)'),
|
||
('04', 'Special investment gold regime'),
|
||
('05', 'Special travel agencies'),
|
||
('06', 'Special group of entities in VAT (Advanced Level)'),
|
||
('07', 'Special scheme for cash'),
|
||
('08', 'Operations subject to IPSI / IGIC'),
|
||
('09', 'Intra-Community acquisitions of goods and services'),
|
||
('10', 'Purchase of travel agencies: mediation operations in the name and '
|
||
'for the account of others in transport services provided to the '
|
||
'recipient of the services in accordance with section 3 '
|
||
'of D.A.4a RD1619 / 2012'),
|
||
('11', 'Billing of travel agency services acting as mediators in the name '
|
||
'and for the account of others (D.A.4a RD1619 / 2012)'),
|
||
('12', 'Business premises lease operations'),
|
||
('13', 'Invoice corresponding to an import '
|
||
'(reported without associating with a DUA)')
|
||
]
|
||
|
||
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 cadastral reference located at any point in the '
|
||
'Spanish territory, except the Basque Country and Navarra.'),
|
||
('2',
|
||
'2. Property located in the Autonomous Community of the Basque '
|
||
'Country or in the Comunidad Foral de Navarra.'),
|
||
('3',
|
||
'3. Property in any of the above situations but without cadastral '
|
||
'reference.'),
|
||
('4', '4. Property located in the foreign country.'),
|
||
]
|
||
|
||
|
||
# L7 - Iva Subjected
|
||
IVA_SUBJECTED = [
|
||
('S1', 'Subjected - Not Excempt'),
|
||
('S2', 'Subjected - Not Excempt , Inv. Suj. Pass')
|
||
]
|
||
|
||
# L9 - Excemption cause
|
||
EXCEMPTION_CAUSE = [
|
||
('E1', 'Excempt. Article 20'),
|
||
('E2', 'Excempt. Article 21'),
|
||
('E3', 'Excempt. Article 22'),
|
||
('E4', 'Excempt. Article 24'),
|
||
('E5', 'Excempt. Article 25'),
|
||
('E6', 'Excempt. Other'),
|
||
]
|
||
|
||
# L11 Payment Type
|
||
PAYMENT_TYPE = [
|
||
('01', 'Transference'),
|
||
('02', 'Check'),
|
||
('03', 'Not Paid (ERE)'),
|
||
('04', 'Other')
|
||
]
|
||
|
||
# L12
|
||
INTRACOMUNITARY_TYPE = [
|
||
('A', 'The sending or receiving of goods for the execution of the partial\n'
|
||
'reports or works Mentioned in article 70, paragraph one, number 7,\n'
|
||
'of the Tax Law (Law 37/1992).'),
|
||
('B', 'Transfers of goods and intra-Community acquisitions of goods \n'
|
||
'covered by In articles 9, paragraph 3, and 16, section 2, of the \n'
|
||
'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, unicode):
|
||
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 (u'\u0327', u'\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'])
|
||
currency = fields.Function(fields.Many2One('currency.currency',
|
||
'Currency'), 'get_currency')
|
||
fiscalyear = fields.Many2One('account.fiscalyear', 'Fiscal Year',
|
||
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'])
|
||
|
||
period = fields.Many2One('account.period', 'Period', required=True,
|
||
domain = [('fiscalyear','=', Eval('fiscalyear'))],
|
||
states={
|
||
'readonly': Eval('state') != 'draft',
|
||
}, depends=['state'])
|
||
|
||
operation_type = fields.Selection(COMMUNICATION_TYPE, 'Operation Type',
|
||
required=True,
|
||
states={
|
||
'readonly': ~Eval('state').in_(['draft', 'confirmed']),
|
||
}, depends=['state'])
|
||
|
||
book = fields.Selection(BOOK_KEY, 'Book', required=True,
|
||
states={
|
||
'readonly': ~Eval('state').in_(['draft', 'confirmed']),
|
||
}, 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'),
|
||
], 'Version', required=True, states={
|
||
}, depends=['state'])
|
||
|
||
lines = fields.One2Many('aeat.sii.report.lines', 'report',
|
||
'Lines', states={
|
||
'readonly': ~Eval('state').in_(['draft']),
|
||
}, 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._transitions |= set((
|
||
('draft', 'confirmed'),
|
||
('draft', 'cancelled'),
|
||
('confirmed', 'draft'),
|
||
('confirmed', 'sent'),
|
||
('confirmed', 'cancelled'),
|
||
('cancelled', 'draft'),
|
||
))
|
||
|
||
|
||
|
||
@staticmethod
|
||
def default_company():
|
||
|
||
return Transaction().context.get('company')
|
||
|
||
def get_currency(self, name):
|
||
return self.company.currency.id
|
||
|
||
@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 '0.6'
|
||
|
||
@fields.depends('company')
|
||
def on_change_with_company_vat(self):
|
||
if self.company:
|
||
return self.company.party.vat_number
|
||
|
||
@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('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
|
||
_logger.debug('Done sending reports to AEAT SII')
|
||
|
||
@classmethod
|
||
@ModelView.button
|
||
@Workflow.transition('cancelled')
|
||
def cancel(cls, reports):
|
||
pass
|
||
|
||
@classmethod
|
||
@ModelView.button
|
||
def load_invoices(cls, reports):
|
||
pool = Pool()
|
||
Invoice = pool.get('account.invoice')
|
||
ReportLine = pool.get('aeat.sii.report.lines')
|
||
|
||
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', '=', None))
|
||
elif report.operation_type in ('A1', 'A4'):
|
||
domain.append(('sii_state', 'in', [
|
||
'ACEPTADOCONERRORES', 'INCORRECTO']))
|
||
|
||
_logger.debug('Searching invoices for SII report: %s', domain)
|
||
invoices = Invoice.search(domain)
|
||
report.lines = [
|
||
ReportLine(invoice=invoice, report=report)
|
||
for invoice in invoices
|
||
]
|
||
report.save()
|
||
|
||
def submit_issued_invoices(self):
|
||
_logger.info('Sending report %s to AEAT SII', self.id)
|
||
headers = mapping.get_headers(
|
||
name=self.company.party.name,
|
||
vat=self.company.party.vat_number,
|
||
comm_kind=self.operation_type)
|
||
pool = Pool()
|
||
mapper = pool.get('aeat.sii.issued.invoice.mapper')(pool=pool)
|
||
res = None
|
||
with self.company.tmp_ssl_credentials() as (crt, key):
|
||
srv = service.bind_issued_invoices_service(crt, key, test=True)
|
||
res = srv.submit(
|
||
headers, (line.invoice for line in self.lines),
|
||
mapper=mapper)
|
||
|
||
# TODO: assert response order matches report order
|
||
for (report_line, response_line) in zip(
|
||
self.lines, res.RespuestaLinea):
|
||
report_line.write([report_line], {
|
||
'state': response_line.EstadoRegistro,
|
||
'communication_code': response_line.CodigoErrorRegistro,
|
||
'communication_msg': response_line.DescripcionErrorRegistro,
|
||
})
|
||
self.write([self], {
|
||
'communication_state': res.EstadoEnvio,
|
||
'csv': res.CSV,
|
||
})
|
||
|
||
def delete_issued_invoices(self):
|
||
headers = mapping.get_headers(
|
||
name=self.company.party.name,
|
||
vat=self.company.party.vat_number,
|
||
comm_kind=self.operation_type)
|
||
pool = Pool()
|
||
mapper = pool.get('aeat.sii.issued.invoice.mapper')(pool=pool)
|
||
res = None
|
||
with self.company.tmp_ssl_credentials() as (crt, key):
|
||
srv = service.bind_issued_invoices_service(crt, key, test=True)
|
||
res = srv.cancel(
|
||
headers, (line.invoice for line in self.lines),
|
||
mapper=mapper)
|
||
|
||
# TODO: assert response order matches report order
|
||
for (report_line, response_line) in zip(
|
||
self.lines, res.RespuestaLinea):
|
||
report_line.write([report_line], {
|
||
'state': response_line.EstadoRegistro,
|
||
'communication_code': response_line.CodigoErrorRegistro,
|
||
'communication_msg': response_line.DescripcionErrorRegistro,
|
||
})
|
||
self.write([self], {
|
||
'communication_state': res.EstadoEnvio,
|
||
'csv': res.CSV,
|
||
})
|
||
|
||
def query_issued_invoices(self):
|
||
res = None
|
||
pool = Pool()
|
||
Invoice = pool.get('account.invoice')
|
||
headers = mapping.get_headers(
|
||
name=self.company.party.name,
|
||
vat=self.company.party.vat_number,
|
||
comm_kind=self.operation_type)
|
||
|
||
with self.company.tmp_ssl_credentials() as (crt, key):
|
||
srv = service.bind_issued_invoices_service(
|
||
crt, key, test=True)
|
||
res = srv.query(
|
||
headers,
|
||
year=self.fiscalyear.name,
|
||
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
|
||
}
|
||
lines_to_create = [
|
||
{
|
||
'invoice':
|
||
invoices_ids.get(
|
||
reg.IDFactura.NumSerieFacturaEmisor),
|
||
'state':
|
||
reg.EstadoFactura.EstadoRegistro,
|
||
'communication_code':
|
||
reg.EstadoFactura.CodigoErrorRegistro,
|
||
'communication_msg':
|
||
reg.EstadoFactura.DescripcionErrorRegistro,
|
||
# FIXME: store any other info from the response
|
||
}
|
||
for reg in registers
|
||
]
|
||
self.write([self], {
|
||
'lines': [('create', lines_to_create)]
|
||
})
|
||
|
||
def submit_recieved_invoices(self):
|
||
_logger.info('Sending report %s to AEAT SII', self.id)
|
||
headers = mapping.get_headers(
|
||
name=self.company.party.name,
|
||
vat=self.company.party.vat_number,
|
||
comm_kind=self.operation_type)
|
||
pool = Pool()
|
||
mapper = pool.get('aeat.sii.recieved.invoice.mapper')(pool=pool)
|
||
res = None
|
||
with self.company.tmp_ssl_credentials() as (crt, key):
|
||
srv = service.bind_recieved_invoices_service(crt, key, test=True)
|
||
res = srv.submit(
|
||
headers, (line.invoice for line in self.lines),
|
||
mapper=mapper)
|
||
|
||
# TODO: assert response order matches report order
|
||
for (report_line, response_line) in zip(
|
||
self.lines, res.RespuestaLinea):
|
||
report_line.write([report_line], {
|
||
'state': response_line.EstadoRegistro,
|
||
'communication_code': response_line.CodigoErrorRegistro,
|
||
'communication_msg': response_line.DescripcionErrorRegistro,
|
||
})
|
||
self.write([self], {
|
||
'communication_state': res.EstadoEnvio,
|
||
'csv': res.CSV,
|
||
})
|
||
|
||
def delete_recieved_invoices(self):
|
||
headers = mapping.get_headers(
|
||
name=self.company.party.name,
|
||
vat=self.company.party.vat_number,
|
||
comm_kind=self.operation_type)
|
||
pool = Pool()
|
||
mapper = pool.get('aeat.sii.recieved.invoice.mapper')(pool=pool)
|
||
res = None
|
||
with self.company.tmp_ssl_credentials() as (crt, key):
|
||
srv = service.bind_recieved_invoices_service(crt, key, test=True)
|
||
res = srv.cancel(
|
||
headers, (line.invoice for line in self.lines),
|
||
mapper=mapper)
|
||
|
||
# TODO: assert response order matches report order
|
||
for (report_line, response_line) in zip(
|
||
self.lines, res.RespuestaLinea):
|
||
report_line.write([report_line], {
|
||
'state': response_line.EstadoRegistro,
|
||
'communication_code': response_line.CodigoErrorRegistro,
|
||
'communication_msg': response_line.DescripcionErrorRegistro,
|
||
})
|
||
self.write([self], {
|
||
'communication_state': res.EstadoEnvio,
|
||
'csv': res.CSV,
|
||
})
|
||
|
||
def query_recieved_invoices(self):
|
||
res = None
|
||
pool = Pool()
|
||
Invoice = pool.get('account.invoice')
|
||
headers = mapping.get_headers(
|
||
name=self.company.party.name,
|
||
vat=self.company.party.vat_number,
|
||
comm_kind=self.operation_type)
|
||
|
||
with self.company.tmp_ssl_credentials() as (crt, key):
|
||
srv = service.bind_recieved_invoices_service(
|
||
crt, key, test=True)
|
||
res = srv.query(
|
||
headers,
|
||
year=self.fiscalyear.name,
|
||
period=self.period.start_date.month)
|
||
|
||
_logger.debug(res)
|
||
registers = \
|
||
res.RegistroRespuestaConsultaLRFacturasRecibidas
|
||
# FIXME: the reference is not forced to be unique
|
||
invoices_list = Invoice.search([
|
||
('reference', 'in', [
|
||
reg.IDFactura.NumSerieFacturaEmisor
|
||
for reg in registers
|
||
])
|
||
])
|
||
invoices_ids = {
|
||
invoice.reference: invoice.id
|
||
for invoice in invoices_list
|
||
}
|
||
lines_to_create = [
|
||
{
|
||
'invoice':
|
||
invoices_ids.get(
|
||
reg.IDFactura.NumSerieFacturaEmisor),
|
||
'state':
|
||
reg.EstadoFactura.EstadoRegistro,
|
||
'communication_code':
|
||
reg.EstadoFactura.CodigoErrorRegistro,
|
||
'communication_msg':
|
||
reg.EstadoFactura.DescripcionErrorRegistro,
|
||
# FIXME: store any other info from the response
|
||
}
|
||
for reg in registers
|
||
]
|
||
self.write([self], {
|
||
'lines': [('create', lines_to_create)]
|
||
})
|
||
|
||
|
||
class BaseTrytonInvoiceMapper(Model):
|
||
|
||
def __init__(self, *args, **kwargs):
|
||
super(BaseTrytonInvoiceMapper, self).__init__(*args, **kwargs)
|
||
self.pool = Pool()
|
||
|
||
year = attrgetter('move.period.fiscalyear.name')
|
||
period = attrgetter('move.period.start_date.month')
|
||
nif = attrgetter('company.party.vat_number')
|
||
issue_date = attrgetter('invoice_date')
|
||
invoice_kind = attrgetter('sii_operation_key')
|
||
rectified_invoice_kind = callback_utils.fixed_value('I')
|
||
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')
|
||
counterpart_id = counterpart_nif
|
||
taxes = attrgetter('taxes')
|
||
tax_rate = attrgetter('tax.rate')
|
||
tax_base = attrgetter('base')
|
||
tax_amount = attrgetter('amount')
|
||
tax_equivalence_surcharge_rate = callback_utils.fixed_value(None)
|
||
tax_equivalence_surcharge_amount = callback_utils.fixed_value(None)
|
||
|
||
def description(self, invoice):
|
||
return (
|
||
invoice.description or
|
||
invoice.lines[0].description or
|
||
self.serial_number(invoice)
|
||
)
|
||
|
||
def final_serial_number(self, invoice):
|
||
try:
|
||
SaleLine = self.pool.get('sale.line')
|
||
except KeyError:
|
||
SaleLine = None
|
||
if SaleLine is not None:
|
||
return max([
|
||
line.origin.number
|
||
for line in invoice.lines
|
||
if isinstance(line.origin, SaleLine)
|
||
])
|
||
|
||
|
||
class IssuedTrytonInvoiceMapper(
|
||
mapping.IssuedInvoiceMapper, BaseTrytonInvoiceMapper
|
||
):
|
||
"""
|
||
Tryton Issued Invoice to AEAT mapper
|
||
"""
|
||
__name__ = 'aeat.sii.issued.invoice.mapper'
|
||
serial_number = attrgetter('number')
|
||
specialkey_or_trascendence = attrgetter('sii_issued_key')
|
||
|
||
|
||
class RecievedTrytonInvoiceMapper(
|
||
mapping.RecievedInvoiceMapper, BaseTrytonInvoiceMapper
|
||
):
|
||
"""
|
||
Tryton Recieved Invoice to AEAT mapper
|
||
"""
|
||
__name__ = 'aeat.sii.recieved.invoice.mapper'
|
||
serial_number = attrgetter('reference')
|
||
specialkey_or_trascendence = attrgetter('sii_received_key')
|
||
move_date = attrgetter('move.date')
|
||
deductible_amount = attrgetter('tax_amount') # most of the times
|
||
|
||
|
||
class SIIReportLine(ModelSQL, ModelView):
|
||
'''
|
||
AEAT SII Issued
|
||
'''
|
||
__name__ = 'aeat.sii.report.lines'
|
||
|
||
report = fields.Many2One(
|
||
'aeat.sii.report', 'Issued Report', ondelete='CASCADE')
|
||
invoice = fields.Many2One('account.invoice', 'Invoice')
|
||
state = fields.Selection(AEAT_INVOICE_STATE, 'State')
|
||
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)
|
||
|
||
@staticmethod
|
||
def default_company():
|
||
return Transaction().context.get('company')
|