Prepare NCF manager for production on Tryton 8.0
Publicar paquete PyPI / publish (push) Successful in 1m17s

This commit is contained in:
José Arturo García
2026-08-16 13:36:51 -04:00
parent e272bb5cfa
commit b96639f3e8
10 changed files with 204 additions and 192 deletions
+53 -2
View File
@@ -3,6 +3,7 @@
# this repository contains the full copyright notices and license terms.
from decimal import Decimal
from datetime import date
import re
from sql.aggregate import Sum
from sql.conditionals import Coalesce
from sql.functions import Round
@@ -1563,7 +1564,7 @@ class Invoice(metaclass=PoolMeta):
ncf_code = invoice.ncf_type
if invoice.type == 'in' and ncf_code == '99':
if ncf_code == '99':
continue
ncf_sequence = invoice.ncf_sequence or cls._ncf_find_active_sequence(
@@ -1712,6 +1713,53 @@ class Invoice(metaclass=PoolMeta):
raise UserError(gettext(
'ncf_manager.msg_incorrect_lenght_ncf_number_modified'))
@staticmethod
def _ncf_normalize_number(number):
return (number or '').strip().upper()
@classmethod
def _ncf_validate_number(cls, invoice):
number = cls._ncf_normalize_number(invoice.ncf_number)
if not number:
return
legacy_supplier_ncf = (
invoice.type == 'in' and re.fullmatch(r'[A-Z]\d{18}', number))
if not legacy_supplier_ncf and not re.fullmatch(
r'(?:B\d{10}|E\d{12})', number):
raise UserError(gettext(
'ncf_manager.msg_invalid_ncf_structure', number=number))
if legacy_supplier_ncf:
invoice.ncf_number = number
return
expected = ('E' if invoice.ncf_type in _ECF_CODES else 'B') + \
invoice.ncf_type.zfill(2)
if invoice.ncf_type != '99' and not number.startswith(expected):
raise UserError(gettext(
'ncf_manager.msg_invalid_ncf_type_prefix',
number=number, prefix=expected))
invoice.ncf_number = number
@classmethod
def _ncf_check_duplicate(cls, invoice):
if not invoice.ncf_number:
return
domain = [
('id', '!=', invoice.id),
('company', '=', invoice.company.id),
('ncf_number', '=', invoice.ncf_number),
]
# Supplier NCF sequences are unique per issuer, not globally.
if invoice.type == 'in':
domain.extend([
('type', '=', 'in'),
('party', '=', invoice.party.id),
])
else:
domain.append(('type', '=', 'out'))
if cls.search(domain, limit=1):
raise UserError(gettext(
'ncf_manager.msg_duplicated_ncf_number_invoice'))
@staticmethod
def _ncf_is_fiscal_cancellation_required(invoice):
return (
@@ -1782,6 +1830,8 @@ class Invoice(metaclass=PoolMeta):
cls.set_ncf_number(invoices)
for invoice in invoices:
cls._ncf_validate_number(invoice)
cls._ncf_check_duplicate(invoice)
ncf_code = invoice.ncf_type
is_debit_note = cls._ncf_is_debit_note(invoice)
is_credit_note = cls._ncf_is_credit_note(invoice)
@@ -1789,7 +1839,8 @@ class Invoice(metaclass=PoolMeta):
is_without_ncf = ncf_code == '99'
if invoice.type == 'out':
invoice._ncf_validate_selected_sequence()
if not is_without_ncf:
invoice._ncf_validate_selected_sequence()
if is_credit_note:
invoice.check_credit_note()
invoice.check_ncf_number_modified()