423 lines
15 KiB
Python
423 lines
15 KiB
Python
# The COPYRIGHT file at the top level of this repository contains the full
|
|
# copyright notices and license terms.
|
|
import hashlib
|
|
from decimal import Decimal
|
|
from trytond.model import ModelView, fields, Workflow, dualmethod
|
|
from trytond.pool import Pool, PoolMeta
|
|
from trytond.pyson import Eval
|
|
from trytond.transaction import Transaction
|
|
from trytond.i18n import gettext
|
|
from trytond.exceptions import UserError, UserWarning
|
|
from trytond.wizard import Wizard, StateView, StateTransition, Button
|
|
from .aeat import (
|
|
OPERATION_KEY, BOOK_KEY, SEND_SPECIAL_REGIME_KEY, COMMUNICATION_TYPE,
|
|
RECEIVE_SPECIAL_REGIME_KEY, AEAT_INVOICE_STATE)
|
|
|
|
|
|
__all__ = ['Invoice', 'ResetSIIKeysStart', 'ResetSIIKeys', 'ResetSIIKeysEnd']
|
|
|
|
_SII_INVOICE_KEYS = ['sii_book_key', 'sii_operation_key', 'sii_issued_key',
|
|
'sii_received_key']
|
|
|
|
|
|
class Invoice(metaclass=PoolMeta):
|
|
__name__ = 'account.invoice'
|
|
|
|
sii_book_key = fields.Selection(BOOK_KEY, 'SII Book Key',
|
|
states={
|
|
'required': Eval('state').in_(['posted', 'paid'])
|
|
}, depends=['state'])
|
|
sii_operation_key = fields.Selection(OPERATION_KEY, 'SII Operation Key')
|
|
sii_issued_key = fields.Selection(SEND_SPECIAL_REGIME_KEY,
|
|
'SII Issued Key',
|
|
states={
|
|
'invisible': ~Eval('sii_book_key').in_(['E']),
|
|
}, depends=['sii_book_key'])
|
|
sii_received_key = fields.Selection(RECEIVE_SPECIAL_REGIME_KEY,
|
|
'SII Recived Key',
|
|
states={
|
|
'invisible': ~Eval('sii_book_key').in_(['R']),
|
|
}, depends=['sii_book_key'])
|
|
sii_records = fields.One2Many('aeat.sii.report.lines', 'invoice',
|
|
"SII Report Lines")
|
|
sii_state = fields.Selection(AEAT_INVOICE_STATE,
|
|
'SII State', readonly=True)
|
|
sii_communication_type = fields.Selection(
|
|
COMMUNICATION_TYPE, 'SII Communication Type', readonly=True)
|
|
sii_pending_sending = fields.Boolean('SII Pending Sending Pending',
|
|
readonly=True)
|
|
sii_header = fields.Text('Header')
|
|
is_reagyp = fields.Function(fields.Boolean('Is REAGYP'), 'get_is_reagyp')
|
|
|
|
@classmethod
|
|
def __setup__(cls):
|
|
super(Invoice, cls).__setup__()
|
|
sii_fields = {'sii_book_key', 'sii_operation_key', 'sii_received_key',
|
|
'sii_issued_key', 'sii_state', 'sii_pending_sending',
|
|
'sii_communication_type', 'sii_header'}
|
|
cls._check_modify_exclude |= sii_fields
|
|
if hasattr(cls, '_intercompany_excluded_fields'):
|
|
cls._intercompany_excluded_fields += sii_fields
|
|
cls._intercompany_excluded_fields += ['sii_records']
|
|
|
|
@classmethod
|
|
def __register__(cls, module_name):
|
|
table = cls.__table_handler__(module_name)
|
|
|
|
exist_sii_intracomunity_key = table.column_exist('sii_intracomunity_key')
|
|
exist_sii_subjected_key = table.column_exist('sii_subjected_key')
|
|
exist_sii_excemption_key = table.column_exist('sii_excemption_key')
|
|
|
|
super(Invoice, cls).__register__(module_name)
|
|
|
|
if exist_sii_intracomunity_key:
|
|
table.drop_column('sii_intracomunity_key')
|
|
if exist_sii_subjected_key:
|
|
table.drop_column('sii_subjected_key')
|
|
if exist_sii_excemption_key:
|
|
table.drop_column('sii_excemption_key')
|
|
|
|
@staticmethod
|
|
def default_sii_pending_sending():
|
|
return False
|
|
|
|
def _credit(self, **values):
|
|
credit = super(Invoice, self)._credit(**values)
|
|
for field in _SII_INVOICE_KEYS:
|
|
setattr(credit, field, getattr(self, field))
|
|
|
|
credit.sii_operation_key = 'R1'
|
|
return credit
|
|
|
|
def _set_sii_keys(self):
|
|
tax = None
|
|
for t in self.taxes:
|
|
if t.tax and t.tax.sii_book_key:
|
|
tax = t.tax
|
|
break
|
|
if not tax:
|
|
return
|
|
for field in _SII_INVOICE_KEYS:
|
|
setattr(self, field, getattr(tax, field))
|
|
|
|
@fields.depends(*_SII_INVOICE_KEYS)
|
|
def _on_change_lines_taxes(self):
|
|
super(Invoice, self)._on_change_lines_taxes()
|
|
for field in _SII_INVOICE_KEYS:
|
|
if getattr(self, field):
|
|
return
|
|
self._set_sii_keys()
|
|
|
|
@dualmethod
|
|
def update_taxes(cls, invoices, exception=False):
|
|
super().update_taxes(invoices, exception=exception)
|
|
to_save = []
|
|
for invoice in invoices:
|
|
set_keys = True
|
|
for field in _SII_INVOICE_KEYS:
|
|
if getattr(invoice, field):
|
|
set_keys = False
|
|
break
|
|
if set_keys:
|
|
invoice._set_sii_keys()
|
|
to_save.append(invoice)
|
|
if to_save:
|
|
cls.save(to_save)
|
|
|
|
@classmethod
|
|
def copy(cls, records, default=None):
|
|
if default is None:
|
|
default = {}
|
|
default = default.copy()
|
|
default.setdefault('sii_records')
|
|
default.setdefault('sii_state')
|
|
default.setdefault('sii_communication_type')
|
|
default.setdefault('sii_operation_key')
|
|
default.setdefault('sii_pending_sending')
|
|
default.setdefault('sii_header')
|
|
return super(Invoice, cls).copy(records, default=default)
|
|
|
|
def _get_sii_operation_key(self):
|
|
keys = ((self.party_tax_identifier or self.party.tax_identifier)
|
|
and ('R1', 'F1') or ('R5', 'F2'))
|
|
return self.untaxed_amount < Decimal('0.0') and keys[0] or keys[1]
|
|
|
|
@classmethod
|
|
def reset_sii_keys(cls, invoices):
|
|
to_write = []
|
|
for invoice in invoices:
|
|
if invoice.sii_state in ('Correcto', 'Correcta'):
|
|
continue
|
|
for field in _SII_INVOICE_KEYS:
|
|
setattr(invoice, field, None)
|
|
invoice._set_sii_keys()
|
|
if not invoice.sii_operation_key:
|
|
invoice.sii_operation_key = invoice._get_sii_operation_key()
|
|
to_write.extend(([invoice], invoice._save_values))
|
|
|
|
if to_write:
|
|
cls.write(*to_write)
|
|
|
|
@classmethod
|
|
def process(cls, invoices):
|
|
super(Invoice, cls).process(invoices)
|
|
invoices_sii = ''
|
|
for invoice in invoices:
|
|
if invoice.state != 'draft':
|
|
continue
|
|
if invoice.sii_state:
|
|
invoices_sii += '\n%s: %s' % (invoice.number, invoice.sii_state)
|
|
if invoices_sii:
|
|
raise UserError(gettext('aeat_sii.msg_invoices_sii',
|
|
invoices=invoices_sii))
|
|
|
|
@classmethod
|
|
def draft(cls, invoices):
|
|
pool = Pool()
|
|
Warning = pool.get('res.user.warning')
|
|
super(Invoice, cls).draft(invoices)
|
|
invoices_sii = []
|
|
to_write = []
|
|
for invoice in invoices:
|
|
to_write.extend(([invoice], {'sii_pending_sending': False}))
|
|
|
|
if invoice.sii_state:
|
|
invoices_sii.append('%s: %s' % (
|
|
invoice.number, invoice.sii_state))
|
|
for record in invoice.sii_records:
|
|
if record.report.state == 'draft':
|
|
raise UserError(gettext('aeat_sii.invoices_sii_pending'))
|
|
|
|
if invoices_sii:
|
|
warning_name = 'invoices_sii.' + hashlib.md5(
|
|
''.join(invoices_sii).encode('utf-8')).hexdigest()
|
|
if Warning.check(warning_name):
|
|
raise UserWarning(warning_name,
|
|
gettext('aeat_sii.msg_invoices_sii',
|
|
invoices='\n'.join(invoices_sii)))
|
|
|
|
if to_write:
|
|
cls.write(*to_write)
|
|
|
|
@classmethod
|
|
def post(cls, invoices):
|
|
to_write = []
|
|
|
|
invoices2checksii = []
|
|
for invoice in invoices:
|
|
if not invoice.move or invoice.move.state == 'draft':
|
|
invoices2checksii.append(invoice)
|
|
|
|
super(Invoice, cls).post(invoices)
|
|
|
|
#TODO:
|
|
# OUT invoice, check that all tax have the same TipoNoExenta and(or the same Exenta
|
|
# Suejta-Exenta --> Can only be one
|
|
# NoSujeta --> Can only be one
|
|
|
|
for invoice in invoices2checksii:
|
|
values = {}
|
|
if invoice.sii_book_key:
|
|
if not invoice.sii_operation_key:
|
|
values['sii_operation_key'] =\
|
|
invoice._get_sii_operation_key()
|
|
values['sii_pending_sending'] = True
|
|
values['sii_header'] = str(cls.get_sii_header(invoice, False))
|
|
to_write.extend(([invoice], values))
|
|
for tax in invoice.taxes:
|
|
if (tax.tax.sii_subjected_key in ('S2', 'S3') and
|
|
not invoice.sii_operation_key in (
|
|
'F1', 'R1', 'R2', 'R3', 'R4')):
|
|
raise UserError(gettext('aeat_sii.msg_sii_operation_key_wrong',
|
|
invoice=invoice))
|
|
if to_write:
|
|
cls.write(*to_write)
|
|
|
|
@classmethod
|
|
def cancel(cls, invoices):
|
|
cls.write(invoices, {'sii_pending_sending': False})
|
|
return super(Invoice, cls).cancel(invoices)
|
|
|
|
@classmethod
|
|
def get_sii_header(cls, invoice, delete):
|
|
pool = Pool()
|
|
IssuedMapper = pool.get('aeat.sii.issued.invoice.mapper')
|
|
ReceivedMapper = pool.get('aeat.sii.recieved.invoice.mapper')
|
|
|
|
if delete:
|
|
rline = [x for x in invoice.sii_records if x.state == 'Correcto'
|
|
and x.sii_header != None]
|
|
if rline:
|
|
return rline[0].sii_header
|
|
if invoice.type == 'out':
|
|
mapper = IssuedMapper()
|
|
header = mapper.build_delete_request(invoice)
|
|
else:
|
|
mapper = ReceivedMapper()
|
|
header = mapper.build_delete_request(invoice)
|
|
return header
|
|
|
|
@classmethod
|
|
def set_number(cls, invoices):
|
|
super().set_number(invoices)
|
|
|
|
is_reagyp = cls.get_is_reagyp(invoices)
|
|
for invoice in invoices:
|
|
if not invoice.reference and is_reagyp[invoice.id]:
|
|
invoice.reference = invoice.number
|
|
cls.save(invoices)
|
|
|
|
@classmethod
|
|
def get_is_reagyp(cls, invoices, name=None):
|
|
pool = Pool()
|
|
Modeldata = pool.get('ir.model.data')
|
|
|
|
res = {}
|
|
try:
|
|
reagyp_ids = [
|
|
Modeldata.get_id('account_es', 'iva_reagp_12_normal'),
|
|
Modeldata.get_id('account_es', 'iva_reagp_12_pyme'),
|
|
]
|
|
except AttributeError:
|
|
reagyp_ids = [
|
|
Modeldata.get_id('account_es', 'iva_reagp_compras_12_1')
|
|
]
|
|
for invoice in invoices:
|
|
res[invoice.id] = False
|
|
if not invoice.taxes or invoice.type != 'in':
|
|
continue
|
|
for tax in invoice.taxes:
|
|
if tax.tax.template and tax.tax.template.id in reagyp_ids:
|
|
res[invoice.id] = True
|
|
break
|
|
return res
|
|
|
|
|
|
class ResetSIIKeysStart(ModelView):
|
|
"""
|
|
Reset to default SII Keys Start
|
|
"""
|
|
__name__ = "aeat.sii.reset.keys.start"
|
|
|
|
|
|
class ResetSIIKeysEnd(ModelView):
|
|
"""
|
|
Reset to default SII Keys End
|
|
"""
|
|
__name__ = "aeat.sii.reset.keys.end"
|
|
|
|
|
|
class ResetSIIKeys(Wizard):
|
|
"""
|
|
Reset to default SII Keys
|
|
"""
|
|
__name__ = "aeat.sii.reset.keys"
|
|
|
|
start = StateView('aeat.sii.reset.keys.start',
|
|
'aeat_sii.aeat_sii_reset_keys_start_view', [
|
|
Button('Cancel', 'end', 'tryton-cancel'),
|
|
Button('Reset', 'reset', 'tryton-ok', default=True),
|
|
])
|
|
reset = StateTransition()
|
|
done = StateView('aeat.sii.reset.keys.end',
|
|
'aeat_sii.aeat_sii_reset_keys_end_view', [
|
|
Button('Ok', 'end', 'tryton-ok', default=True),
|
|
])
|
|
|
|
def transition_reset(self):
|
|
pool = Pool()
|
|
Invoice = pool.get('account.invoice')
|
|
invoices = Invoice.browse(Transaction().context['active_ids'])
|
|
Invoice.reset_sii_keys(invoices)
|
|
return 'done'
|
|
|
|
|
|
class Invoice2(metaclass=PoolMeta):
|
|
__name__ = 'account.invoice'
|
|
|
|
@classmethod
|
|
def __setup__(cls):
|
|
super().__setup__()
|
|
cls._sii_state_deny_draft = {
|
|
'Correcto', 'Correcta', 'AceptadoConErrores', 'AceptadaConErrores'
|
|
}
|
|
cls._deny_modify_sii_fields = {
|
|
'reference', 'party', 'invoice_date', 'party_tax_identifier'
|
|
}
|
|
|
|
@classmethod
|
|
@ModelView.button
|
|
@Workflow.transition('draft')
|
|
def draft(cls, invoices):
|
|
for invoice in invoices:
|
|
invoice.check_sent_sii()
|
|
super().draft(invoices)
|
|
|
|
@classmethod
|
|
@ModelView.button
|
|
@Workflow.transition('posted')
|
|
def post(cls, invoices):
|
|
for invoice in invoices:
|
|
if invoice.state in ('draft', 'validated'):
|
|
invoice.check_sent_sii()
|
|
super().post(invoices)
|
|
|
|
@classmethod
|
|
def write(cls, *args):
|
|
actions = iter(args)
|
|
args = []
|
|
for records, values in zip(actions, actions):
|
|
sii_vals = set(values) & cls._deny_modify_sii_fields
|
|
if sii_vals:
|
|
for record in records:
|
|
if record.type == 'in':
|
|
record.check_sent_sii(list(sii_vals))
|
|
args.extend((records, values))
|
|
super().write(*args)
|
|
|
|
def check_sent_sii(self, fields=[]):
|
|
pool = Pool()
|
|
ModelData = pool.get('ir.model.data')
|
|
User = pool.get('res.user')
|
|
Group = pool.get('res.group')
|
|
Warning = pool.get('res.user.warning')
|
|
|
|
if self.sii_state in self._sii_state_deny_draft and \
|
|
self.sii_communication_type != 'D0':
|
|
|
|
# check group
|
|
def in_group():
|
|
group = Group(ModelData.get_id(
|
|
'aeat_sii',
|
|
'group_invoice_sent_sii_posted2draft'))
|
|
transaction = Transaction()
|
|
user_id = transaction.user
|
|
if user_id == 0:
|
|
user_id = transaction.context.get('user', user_id)
|
|
if user_id == 0:
|
|
return True
|
|
user = User(user_id)
|
|
return group in user.groups
|
|
|
|
if fields:
|
|
raise UserError(gettext(
|
|
'aeat_sii.msg_invoice_deny_modify_sii_pk',
|
|
fields=', '.join(
|
|
[f['string'] for f in self.__class__.fields_get(
|
|
fields_names=fields).values()]),
|
|
invoice=self.rec_name,
|
|
sii_state=self.sii_state))
|
|
elif not in_group():
|
|
raise UserError(gettext(
|
|
'aeat_sii.msg_invoice_deny_draft_invalid_sii_state',
|
|
invoice=self.rec_name,
|
|
sii_state=self.sii_state))
|
|
else:
|
|
warning_key = ('aeat_sii.'
|
|
'msg_invoice_modify_invalid_sii_state_%s' % self.id)
|
|
if Warning.check(warning_key):
|
|
raise UserWarning(warning_key, gettext(
|
|
'aeat_sii.msg_invoice_modify_invalid_sii_state',
|
|
invoice=self.rec_name,
|
|
sii_state=self.sii_state))
|