From c7bacddd2d992a1a46512d2037e0a77b9fd22d2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jose=CC=81=20Arturo=20Garci=CC=81a?= Date: Wed, 30 Sep 2026 23:34:18 -0400 Subject: [PATCH] Add configurable fiscal invoice defaults --- configuration.py | 49 +++++++++++++- fiscal.py | 38 +++++++++++ invoice.py | 124 ++++++++++++++++++++++++------------ view/configuration_form.xml | 11 +++- 4 files changed, 179 insertions(+), 43 deletions(-) create mode 100644 fiscal.py diff --git a/configuration.py b/configuration.py index e43644e..9f3118f 100644 --- a/configuration.py +++ b/configuration.py @@ -7,6 +7,8 @@ from trytond.model import fields, ModelSQL from trytond.modules.company.model import CompanyValueMixin from trytond.pool import PoolMeta, Pool from trytond.transaction import Transaction +from trytond.modules.ncf_manager.fiscal import ( + EXPENSE_TYPE, PAYMENT_METHOD, REVENUE_TYPE) from trytond.modules.ncf_manager.ncf_manager import NCF_TYPE @@ -36,10 +38,37 @@ class Configuration(metaclass=PoolMeta): help="Default supplier NCF type for new parties." )) + default_customer_revenue_type = fields.MultiValue(fields.Selection( + REVENUE_TYPE, + "Default Customer Revenue Type", + sort=False, + help="Default revenue type for new customer invoices." + )) + + default_supplier_expense_type = fields.MultiValue(fields.Selection( + EXPENSE_TYPE, + "Default Supplier Expense Type", + sort=False, + help="Default expense type for new supplier invoices." + )) + + default_payment_method = fields.MultiValue(fields.Selection( + PAYMENT_METHOD, + "Default Payment Method", + sort=False, + help="Default payment method for new invoices." + )) + @classmethod def multivalue_model(cls, field): pool = Pool() - if field in {'default_customer_ncf_type', 'default_supplier_ncf_type'}: + if field in { + 'default_customer_ncf_type', + 'default_supplier_ncf_type', + 'default_customer_revenue_type', + 'default_supplier_expense_type', + 'default_payment_method', + }: return pool.get('account.configuration.default_ncf_type') return super(Configuration, cls).multivalue_model(field) @@ -60,6 +89,24 @@ class DefaultNcfType(ModelSQL, CompanyValueMixin): sort=False ) + default_customer_revenue_type = fields.Selection( + REVENUE_TYPE, + "Default Customer Revenue Type", + sort=False + ) + + default_supplier_expense_type = fields.Selection( + EXPENSE_TYPE, + "Default Supplier Expense Type", + sort=False + ) + + default_payment_method = fields.Selection( + PAYMENT_METHOD, + "Default Payment Method", + sort=False + ) + @classmethod def __register__(cls, module_name): """ diff --git a/fiscal.py b/fiscal.py new file mode 100644 index 0000000..e0a2139 --- /dev/null +++ b/fiscal.py @@ -0,0 +1,38 @@ +# This file is part of Tryton. The COPYRIGHT file at the top level of +# this repository contains the full copyright notices and license terms. + +REVENUE_TYPE = [ + ('', ''), + ('01', "01 - Ingresos por Operaciones (No Financieros)"), + ('02', "02 - Ingresos Financieros"), + ('03', "03 - Ingresos Extraordinarios"), + ('04', "04 - Ingresos por Arrendamientos"), + ('05', "05 - Ingresos por Venta de Activo Depreciable"), + ('06', "06 - Otros Ingresos"), +] + +EXPENSE_TYPE = [ + ('', ''), + ('01', "01 - Gastos de Personal"), + ('02', "02 - Gastos por Trabajos, Suministros y Servicios"), + ('03', "03 - Arrendamientos"), + ('04', "04 - Gastos de Activos Fijo"), + ('05', "05 - Gastos de Representación"), + ('06', "06 - Otras Deducciones Admitidas"), + ('07', "07 - Gastos Financieros"), + ('08', "08 - Gastos Extraordinarios"), + ('09', "09 - Compras y Gastos que forman parte del Costo de Venta"), + ('10', "10 - Adquisiciones de Activos"), + ('11', "11 - Gastos de Seguros"), +] + +PAYMENT_METHOD = [ + ('', ''), + ('01', "01 - Efectivo"), + ('02', "02 - Cheques/Transferencias/Depósito"), + ('03', "03 - Tarjeta Crédito/Débito"), + ('04', "04 - Compra a Crédito"), + ('05', "05 - Permuta"), + ('06', "06 - Nota de Crédito"), + ('07', "07 - Mixto"), +] diff --git a/invoice.py b/invoice.py index 715fd21..50ae30a 100755 --- a/invoice.py +++ b/invoice.py @@ -15,6 +15,8 @@ from trytond.transaction import Transaction from trytond.exceptions import UserError from trytond.i18n import gettext from trytond.modules.ncf_manager.ncf_manager import NCF_TYPE +from trytond.modules.ncf_manager.fiscal import ( + EXPENSE_TYPE, PAYMENT_METHOD, REVENUE_TYPE) from trytond.modules.currency.fields import Monetary import logging logger = logging.getLogger(__name__) @@ -25,43 +27,6 @@ def _backend_name(): return name() if callable(name) else name -_REVENUE_TYPE = [ - ('', ''), - ('01', "01 - Ingresos por Operaciones (No Financieros)"), - ('02', "02 - Ingresos Financieros"), - ('03', "03 - Ingresos Extraordinarios"), - ('04', "04 - Ingresos por Arrendamientos"), - ('05', "05 - Ingresos por Venta de Activo Depreciable"), - ('06', "06 - Otros Ingresos"), -] - -_EXPENSE_TYPE = [ - ('', ''), - ('01', "01 - Gastos de Personal"), - ('02', "02 - Gastos por Trabajos, Suministros y Servicios"), - ('03', "03 - Arrendamientos"), - ('04', "04 - Gastos de Activos Fijo"), - ('05', "05 - Gastos de Representación"), - ('06', "06 - Otras Deducciones Admitidas"), - ('07', "07 - Gastos Financieros"), - ('08', "08 - Gastos Extraordinarios"), - ('09', "09 - Compras y Gastos que forman parte del Costo de Venta"), - ('10', "10 - Adquisiciones de Activos"), - ('11', "11 - Gastos de Seguros"), -] - -_PAYMENT_METHOD = [ - ('', ''), - ('01', "01 - Efectivo"), - ('02', "02 - Cheques/Transferencias/Depósito"), - ('03', "03 - Tarjeta Crédito/Débito"), - ('04', "04 - Compra a Crédito"), - ('05', "05 - Permuta"), - ('06', "06 - Nota de Crédito"), - ('07', "07 - Mixto"), -] - - _ISR_WITHHOLDING_TYPE = [ ('', ''), ('01', "01 - Alquileres"), @@ -230,7 +195,7 @@ class Invoice(metaclass=PoolMeta): "Amount Invoiced in Goods", currency='currency', digits='currency'), 'get_amount_invoiced_goods', searcher='search_amount_invoiced_goods') - revenue_type = fields.Selection(_REVENUE_TYPE, 'Revenue Type', + revenue_type = fields.Selection(REVENUE_TYPE, 'Revenue Type', states={ 'readonly': Eval('state').in_(['posted', 'paid', 'cancelled']), 'invisible': Eval('type') == 'in', @@ -239,7 +204,7 @@ class Invoice(metaclass=PoolMeta): revenue_type_string = revenue_type.translated('revenue_type') - expense_type = fields.Selection(_EXPENSE_TYPE, 'Expense Type', + expense_type = fields.Selection(EXPENSE_TYPE, 'Expense Type', states={ 'readonly': Eval('state').in_(['posted', 'paid', 'cancelled']), 'invisible': Eval('type') == 'out', @@ -248,7 +213,7 @@ class Invoice(metaclass=PoolMeta): expense_type_string = expense_type.translated('expense_type') - payment_method = fields.Selection(_PAYMENT_METHOD, 'Method of Payment', + payment_method = fields.Selection(PAYMENT_METHOD, 'Method of Payment', states={ 'readonly': Eval('state').in_(['posted', 'paid', 'cancelled']), }, @@ -323,6 +288,32 @@ class Invoice(metaclass=PoolMeta): }, depends=['state', 'type', 'ncf_number']) + @classmethod + def _ncf_get_configuration_default(cls, field_name, **pattern): + "Return a default value from account configuration." + Configuration = Pool().get('account.configuration') + config = Configuration(1) + return config.get_multivalue(field_name, **pattern) or None + + @classmethod + def default_revenue_type(cls, **pattern): + if Transaction().context.get('type') == 'in': + return None + return cls._ncf_get_configuration_default( + 'default_customer_revenue_type', **pattern) + + @classmethod + def default_expense_type(cls, **pattern): + if Transaction().context.get('type') == 'out': + return None + return cls._ncf_get_configuration_default( + 'default_supplier_expense_type', **pattern) + + @classmethod + def default_payment_method(cls, **pattern): + return cls._ncf_get_configuration_default( + 'default_payment_method', **pattern) + @classmethod def __setup__(cls): super().__setup__() @@ -804,6 +795,39 @@ class Invoice(metaclass=PoolMeta): else: self.expiration_date = None + def _ncf_get_configuration_default_invoice_field(self, field_name): + "Return an invoice default configured on account configuration." + Configuration = Pool().get('account.configuration') + config = Configuration(1) + company_id = ( + self.company.id if self.company + else Transaction().context.get('company')) + + if company_id: + with Transaction().set_context(company=company_id): + return config.get_multivalue(field_name) or None + + return config.get_multivalue(field_name) or None + + def _ncf_apply_configuration_invoice_defaults(self): + """ + Apply configured fiscal reporting defaults without replacing values + already selected by the user or copied from another invoice. + """ + if self.type == 'out' and not self.revenue_type: + self.revenue_type = ( + self._ncf_get_configuration_default_invoice_field( + 'default_customer_revenue_type')) + elif self.type == 'in' and not self.expense_type: + self.expense_type = ( + self._ncf_get_configuration_default_invoice_field( + 'default_supplier_expense_type')) + + if not self.payment_method: + self.payment_method = ( + self._ncf_get_configuration_default_invoice_field( + 'default_payment_method')) + @fields.depends( 'party', 'company', @@ -811,6 +835,9 @@ class Invoice(metaclass=PoolMeta): 'ncf_type', 'ncf_sequence', 'expiration_date', + 'revenue_type', + 'expense_type', + 'payment_method', '_parent_party.customer_ncf_type', '_parent_party.supplier_ncf_type', ) @@ -827,9 +854,19 @@ class Invoice(metaclass=PoolMeta): """ super().on_change_party() self._ncf_apply_party_default_ncf_type() + self._ncf_apply_configuration_invoice_defaults() - @fields.depends('company', 'type', 'ncf_type', 'ncf_sequence', 'expiration_date') + @fields.depends( + 'company', + 'type', + 'ncf_type', + 'ncf_sequence', + 'expiration_date', + 'revenue_type', + 'expense_type', + 'payment_method', + ) def on_change_company(self): if self.ncf_type: sequence = self._ncf_get_active_sequence() @@ -841,6 +878,7 @@ class Invoice(metaclass=PoolMeta): self.ncf_sequence = sequence if sequence.ncf_sequences: self.expiration_date = sequence.ncf_sequences.expiration_date + self._ncf_apply_configuration_invoice_defaults() @fields.depends( 'company', @@ -854,6 +892,9 @@ class Invoice(metaclass=PoolMeta): 'ncf_type', 'ncf_sequence', 'expiration_date', + 'revenue_type', + 'expense_type', + 'payment_method', methods=['set_journal', 'on_change_party'], ) def on_change_type(self): @@ -870,6 +911,7 @@ class Invoice(metaclass=PoolMeta): the new invoice type. """ super().on_change_type() + self._ncf_apply_configuration_invoice_defaults() if not self.ncf_sequence: self.ncf_type = None diff --git a/view/configuration_form.xml b/view/configuration_form.xml index 949de48..28fa2fe 100644 --- a/view/configuration_form.xml +++ b/view/configuration_form.xml @@ -9,5 +9,14 @@ this repository contains the full copyright notices and license terms. -->