Integrate fiscal cancellation reasons

This commit is contained in:
José Arturo García
2026-07-22 21:52:32 -04:00
parent 288aea39a5
commit c1969f254f
5 changed files with 209 additions and 4 deletions
+101 -4
View File
@@ -7,9 +7,9 @@ from sql.aggregate import Sum
from sql.conditionals import Coalesce
from sql.functions import Round
from trytond import backend
from trytond.model import fields
from trytond.model import ModelView, Workflow, fields
from trytond.pool import Pool, PoolMeta
from trytond.pyson import Eval, If, And
from trytond.pyson import Eval, If, And, Bool
from trytond.transaction import Transaction
from trytond.exceptions import UserError
from trytond.i18n import gettext
@@ -293,6 +293,43 @@ class Invoice(metaclass=PoolMeta):
isr_withholding_type_string = isr_withholding_type.translated(
'isr_withholding_type')
ncf_cancellation_type = fields.Selection(
_CANCELLATION_TYPE, 'NCF Cancellation Type',
states={
'required': (
(Eval('state') == 'cancelled')
& (Eval('type') == 'out')
& Bool(Eval('ncf_number'))
& ~Eval('context', {}).get('invoice_force_cancel', False)),
'readonly': Eval('state') == 'cancelled',
'invisible': Eval('type') == 'in',
},
depends=['state', 'type', 'ncf_number'])
ncf_cancellation_type_string = ncf_cancellation_type.translated(
'ncf_cancellation_type')
ncf_cancellation_reason = fields.Text(
'NCF Cancellation Reason',
states={
'required': (
(Eval('state') == 'cancelled')
& (Eval('type') == 'out')
& Bool(Eval('ncf_number'))
& ~Eval('context', {}).get('invoice_force_cancel', False)),
'readonly': Eval('state') == 'cancelled',
'invisible': Eval('type') == 'in',
},
depends=['state', 'type', 'ncf_number'])
@classmethod
def __setup__(cls):
super().__setup__()
cls._check_modify_exclude.update({
'ncf_cancellation_type',
'ncf_cancellation_reason',
})
@classmethod
def __register__(cls, module_name):
"""
@@ -308,6 +345,7 @@ class Invoice(metaclass=PoolMeta):
cls._ncf_prepare_legacy_invoice_ncf_type_column()
super().__register__(module_name)
cls._ncf_migrate_invoice_ncf_type_from_legacy_column()
cls._ncf_migrate_legacy_cancellation_fields()
@classmethod
def _ncf_column_exists(cls, cursor, table_name, column_name):
@@ -447,6 +485,35 @@ class Invoice(metaclass=PoolMeta):
unmatched,
)
@classmethod
def _ncf_migrate_legacy_cancellation_fields(cls):
"""
Copy data from the former account_invoice_cancel_reason module.
The old module used generic cancellation_type/cancellation_reason
columns. ncf_manager stores those values as fiscal DGII cancellation
data so they can later feed the NCF annulment report.
"""
cursor = Transaction().connection.cursor()
table_name = 'account_invoice'
field_pairs = (
('cancellation_type', 'ncf_cancellation_type'),
('cancellation_reason', 'ncf_cancellation_reason'),
)
for old_column, new_column in field_pairs:
if not cls._ncf_column_exists(cursor, table_name, old_column):
continue
if not cls._ncf_column_exists(cursor, table_name, new_column):
continue
cursor.execute(f"""
UPDATE {table_name}
SET {new_column} = {old_column}
WHERE {old_column} IS NOT NULL
AND {old_column} <> ''
AND ({new_column} IS NULL OR {new_column} = '')
""")
@classmethod
def search_rec_name(cls, name, clause):
domain = super().search_rec_name(name, clause)
@@ -1645,6 +1712,25 @@ class Invoice(metaclass=PoolMeta):
raise UserError(gettext(
'ncf_manager.msg_incorrect_lenght_ncf_number_modified'))
@staticmethod
def _ncf_is_fiscal_cancellation_required(invoice):
return (
invoice.type == 'out'
and bool(getattr(invoice, 'ncf_number', None)))
@classmethod
def _ncf_check_cancellation_information(cls, invoice):
if not cls._ncf_is_fiscal_cancellation_required(invoice):
return
if not invoice.ncf_cancellation_type:
raise UserError(gettext(
'ncf_manager.msg_missing_ncf_cancellation_type',
invoice=invoice.rec_name))
if not invoice.ncf_cancellation_reason:
raise UserError(gettext(
'ncf_manager.msg_missing_ncf_cancellation_reason',
invoice=invoice.rec_name))
@classmethod
def copy(cls, invoices, default=None):
if default is None:
@@ -1658,6 +1744,8 @@ class Invoice(metaclass=PoolMeta):
default.setdefault('ncf_number_modified', None)
default.setdefault('expiration_date', None)
default.setdefault('security_code', None)
default.setdefault('ncf_cancellation_type', None)
default.setdefault('ncf_cancellation_reason', None)
return super(Invoice, cls).copy(invoices, default=default)
@@ -1674,11 +1762,20 @@ class Invoice(metaclass=PoolMeta):
('/form/notebook/page[@id="tax_information"]/separator[@id="expense"]', 'states', {
'invisible': Eval('type') == 'out',
}),
('/form/notebook/page[@id="cancel_reason"]', 'states', {
'invisible': Eval('type') == 'in',
('/form/notebook/page[@id="ncf_cancellation"]', 'states', {
'invisible': (
(Eval('type') == 'in') | ~Bool(Eval('ncf_number'))),
}),
]
@classmethod
@ModelView.button
@Workflow.transition('cancelled')
def cancel(cls, invoices):
if not Transaction().context.get('invoice_force_cancel'):
for invoice in invoices:
cls._ncf_check_cancellation_information(invoice)
super().cancel(invoices)
@classmethod
def _post(cls, invoices):