Files
ncf_manager/sequence.py
T
José Arturo García 3b5d900221
Publicar paquete PyPI / publish (push) Successful in 1m37s
Homologate shared fiscal behavior with Tryton 7.0
2026-08-30 18:28:13 -04:00

155 lines
7.2 KiB
Python

# This file is part of ncf_manager module for Tryton. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
import unicodedata
from trytond.exceptions import UserError
from trytond.model import fields
from trytond.pyson import Eval
from trytond.pool import PoolMeta
from trytond.i18n import gettext
# Maps sequence_type.name → (prefix, padding)
_NCF_TYPE_MAP = {
'Factura de Crédito Fiscal': ('B01', 8),
'Factura de Consumo': ('B02', 8),
'Nota de Débito': ('B03', 8),
'Nota de Crédito': ('B04', 8),
'Comprobante de Compras': ('B11', 8),
'Registro Único de Ingreso': ('B12', 8),
'Comprobante para Gasto Menor': ('B13', 8),
'Comprobante de Regímen Especial': ('B14', 8),
'Comprobante Gubernamental': ('B15', 8),
'Comprobante para exportaciones': ('B16', 8),
'Comprobante para Pagos al Exterior': ('B17', 8),
'Factura de Crédito Fiscal Electrónica': ('E31', 10),
'Factura de Consumo Electrónica': ('E32', 10),
'Nota de Débito Electrónica': ('E33', 10),
'Nota de Crédito Electrónica': ('E34', 10),
'Compras Electrónico': ('E41', 10),
'Gastos Menores Electrónico': ('E43', 10),
'Regímenes Especiales Electrónico': ('E44', 10),
'Gubernamental Electrónico': ('E45', 10),
'Exportación Electrónico': ('E46', 10),
'Pagos al Exterior Electrónico': ('E47', 10),
}
NCF_PREFIXES = tuple(prefix for prefix, _padding in _NCF_TYPE_MAP.values())
def _sequence_type_entry(sequence_type):
name = unicodedata.normalize('NFC', sequence_type.name or '')
return _NCF_TYPE_MAP.get(name)
class Sequence(metaclass=PoolMeta):
__name__ = 'ir.sequence'
from_number = fields.Integer('From Number',
states={
'required': Eval('prefix').in_(['B01', 'B02', 'B03', 'B04',
'B11', 'B12', 'B13', 'B14', 'B15', 'B16', 'B17', 'E31',
'E32', 'E33', 'E34', 'E41', 'E43', 'E44', 'E45', 'E46', 'E47']),
'invisible': ~Eval('prefix').in_(['B01', 'B02', 'B03', 'B04',
'B11', 'B12', 'B13', 'B14', 'B15', 'B16', 'B17', 'E31',
'E32', 'E33', 'E34', 'E41', 'E43', 'E44', 'E45', 'E46', 'E47'])
}, depends=['prefix'])
until_number = fields.Integer('Until Number',
states={
'required': Eval('prefix').in_(['B01', 'B02', 'B03', 'B04',
'B11', 'B12', 'B13', 'B14', 'B15', 'B16', 'B17', 'E31',
'E32', 'E33', 'E34', 'E41', 'E43', 'E44', 'E45', 'E46', 'E47']),
'invisible': ~Eval('prefix').in_(['B01', 'B02', 'B03', 'B04',
'B11', 'B12', 'B13', 'B14', 'B15', 'B16', 'B17', 'E31',
'E32', 'E33', 'E34', 'E41', 'E43', 'E44', 'E45', 'E46', 'E47'])
}, depends=['prefix'])
approved_qty = fields.Function(fields.Integer('Approved Quantity',
states={
'invisible': ~Eval('prefix').in_(['B01', 'B02', 'B03', 'B04',
'B11', 'B12', 'B13', 'B14', 'B15', 'B16', 'B17', 'E31',
'E32', 'E33', 'E34', 'E41', 'E43', 'E44', 'E45', 'E46', 'E47'])
}), 'on_change_with_approved_qty')
expiration_date = fields.Date('Expiration Date',
states={
'required': Eval('prefix').in_(['B01', 'B03',
'B11', 'B12', 'B13', 'B14', 'B15', 'B16', 'B17', 'E31',
'E33', 'E41', 'E43', 'E44', 'E45', 'E46', 'E47']),
'invisible': ~Eval('prefix').in_(['B01', 'B02', 'B03', 'B04',
'B11', 'B12', 'B13', 'B14', 'B15', 'B16', 'B17', 'E31',
'E32', 'E33', 'E34', 'E41', 'E43', 'E44', 'E45', 'E46', 'E47'])
}, depends=['prefix'])
remaining_qty = fields.Function(fields.Integer('Remaining Quantity',
states={
'invisible': ~Eval('prefix').in_(['B01', 'B02', 'B03', 'B04',
'B11', 'B12', 'B13', 'B14', 'B15', 'B16', 'B17', 'E31',
'E32', 'E33', 'E34', 'E41', 'E43', 'E44', 'E45', 'E46', 'E47'])
}), 'on_change_with_remaining_qty')
@fields.depends('sequence_type')
def on_change_with_name(self, name=None):
if self.sequence_type:
return self.sequence_type.name
return None
@fields.depends('sequence_type')
def on_change_with_prefix(self, name=None):
if not self.sequence_type or not self.sequence_type.name:
return None
entry = _sequence_type_entry(self.sequence_type)
return entry[0] if entry else None
@fields.depends('sequence_type')
def on_change_with_padding(self, name=None):
if not self.sequence_type or not self.sequence_type.name:
return 0
entry = _sequence_type_entry(self.sequence_type)
return entry[1] if entry else 0
@fields.depends('from_number', 'until_number')
def on_change_with_approved_qty(self, name=None):
if self.from_number and self.until_number:
return self.until_number - self.from_number + 1
@fields.depends('number_next', 'until_number')
def on_change_with_remaining_qty(self, name=None):
if self.until_number is not None and self.number_next is not None:
return self.until_number - self.number_next + 1
@classmethod
def validate_fields(cls, records, field_names):
super().validate_fields(records, field_names)
if field_names & {
'prefix', 'padding', 'from_number', 'until_number',
'number_next'}:
for record in records:
record._validate_ncf_range()
def _validate_ncf_range(self):
if self.prefix not in NCF_PREFIXES:
return
expected_padding = 10 if self.prefix.startswith('E') else 8
if self.padding != expected_padding:
raise UserError(gettext(
'ncf_manager.msg_ncf_invalid_padding',
padding=expected_padding))
if self.from_number is None or self.until_number is None:
return
if self.from_number < 1 or self.until_number < self.from_number:
raise UserError(gettext('ncf_manager.msg_ncf_invalid_range'))
if (self.number_next is not None
and self.number_next < self.from_number):
raise UserError(gettext('ncf_manager.msg_ncf_next_before_range'))
class SequenceStrict(Sequence):
"Sequence Strict"
__name__ = 'ir.sequence.strict'
_table = None # Needed to reset Sequence._table
_strict = True
ncf_authorization_lines = fields.One2Many(
'ncf.manager.sequence', 'ncf_sequences', 'NCF Authorizations',
readonly=True)