# 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. 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()) 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 = _NCF_TYPE_MAP.get(self.sequence_type.name) 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 = _NCF_TYPE_MAP.get(self.sequence_type.name) 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