diff --git a/modules/purchase_trade/__init__.py b/modules/purchase_trade/__init__.py index c22cc52..abb308e 100755 --- a/modules/purchase_trade/__init__.py +++ b/modules/purchase_trade/__init__.py @@ -270,8 +270,11 @@ def register(): valuation.ValuationProcess, derivative.DerivativeMatchWizard, module='purchase', type_='wizard') - Pool.register( - sale.SaleCreatePurchase, - sale.SaleAllocationsWizard, - module='sale', type_='wizard') + Pool.register( + sale.SaleCreatePurchase, + sale.SaleAllocationsWizard, + module='sale', type_='wizard') + Pool.register( + invoice.InvoiceReport, + module='purchase_trade', type_='report') diff --git a/modules/purchase_trade/configuration.py b/modules/purchase_trade/configuration.py index 2eda67c..73b8acb 100644 --- a/modules/purchase_trade/configuration.py +++ b/modules/purchase_trade/configuration.py @@ -6,3 +6,6 @@ class Configuration(ModelSingleton, ModelSQL, ModelView): __name__ = 'purchase_trade.configuration' pricing_rule = fields.Text("Pricing Rule") + invoice_report_template = fields.Char("Invoice Template") + invoice_cndn_report_template = fields.Char("CN/DN Template") + invoice_prepayment_report_template = fields.Char("Prepayment Template") diff --git a/modules/purchase_trade/docs/template-rules.md b/modules/purchase_trade/docs/template-rules.md index c37eb95..0683068 100644 --- a/modules/purchase_trade/docs/template-rules.md +++ b/modules/purchase_trade/docs/template-rules.md @@ -98,6 +98,9 @@ Derniere mise a jour: `2026-04-02` - verifier si le probleme vient du cache avant de modifier le `.fodt` - pour un report alternatif, ne pas reutiliser le cache du report standard `account_invoice/invoice.fodt` - si besoin, bypasser la lecture/ecriture du cache pour les templates alternatifs + - pour les clients multi-templates, preferer une configuration metier qui + stocke le nom du template par action (`Invoice`, `CN/DN`, `Prepayment`) + plutot qu'une modification manuelle de `ir_action_report.report` ### TR-007 - Pour une facture trade, privilegier le lot physique comme chemin de navigation diff --git a/modules/purchase_trade/invoice.py b/modules/purchase_trade/invoice.py index 630e8a4..f3adbbb 100644 --- a/modules/purchase_trade/invoice.py +++ b/modules/purchase_trade/invoice.py @@ -2,6 +2,9 @@ from decimal import Decimal, ROUND_HALF_UP from trytond.pool import Pool, PoolMeta from trytond.modules.purchase_trade.numbers_to_words import amount_to_currency_words +from trytond.exceptions import UserError +from trytond.modules.account_invoice.invoice import ( + InvoiceReport as BaseInvoiceReport) class Invoice(metaclass=PoolMeta): @@ -665,3 +668,62 @@ class InvoiceLine(metaclass=PoolMeta): if net == '': return '' return round(Decimal(net) * Decimal('2204.62'),2) + + +class InvoiceReport(BaseInvoiceReport): + __name__ = 'account.invoice' + + @classmethod + def _get_purchase_trade_configuration(cls): + Configuration = Pool().get('purchase_trade.configuration') + configurations = Configuration.search([], limit=1) + return configurations[0] if configurations else None + + @classmethod + def _get_action_name(cls, action): + if isinstance(action, dict): + return action.get('name') or '' + return getattr(action, 'name', '') or '' + + @classmethod + def _resolve_configured_report_path(cls, action): + config = cls._get_purchase_trade_configuration() + report_path = cls._get_action_report_path(action) or '' + action_name = cls._get_action_name(action) + + if (report_path.endswith('/prepayment.fodt') + or action_name == 'Prepayment'): + template = ( + getattr(config, 'invoice_prepayment_report_template', '') + if config else '') + elif (report_path.endswith('/invoice_ict_final.fodt') + or action_name == 'CN/DN'): + template = ( + getattr(config, 'invoice_cndn_report_template', '') + if config else '') + else: + template = ( + getattr(config, 'invoice_report_template', '') + if config else '') + + template = (template or '').strip() + if not template: + raise UserError('No template found') + if '/' not in template: + return f'account_invoice/{template}' + return template + + @classmethod + def _get_resolved_action(cls, action): + report_path = cls._resolve_configured_report_path(action) + if isinstance(action, dict): + resolved = dict(action) + resolved['report'] = report_path + return resolved + setattr(action, 'report', report_path) + return action + + @classmethod + def _execute(cls, records, header, data, action): + resolved_action = cls._get_resolved_action(action) + return super()._execute(records, header, data, resolved_action) diff --git a/modules/purchase_trade/tests/test_module.py b/modules/purchase_trade/tests/test_module.py index a7ea2fe..62b2a19 100644 --- a/modules/purchase_trade/tests/test_module.py +++ b/modules/purchase_trade/tests/test_module.py @@ -301,6 +301,64 @@ class PurchaseTradeTestCase(ModuleTestCase): with self.assertRaises(UserError): report.validate_remote_weight_report_context(shipment) + def test_invoice_report_uses_invoice_template_from_configuration(self): + 'invoice report path is resolved from purchase_trade configuration' + report_class = Pool().get('account.invoice', type='report') + config_model = Mock() + config_model.search.return_value = [ + Mock( + invoice_report_template='invoice_melya.fodt', + invoice_cndn_report_template='invoice_ict_final.fodt', + invoice_prepayment_report_template='prepayment.fodt', + ) + ] + + with patch( + 'trytond.modules.purchase_trade.invoice.Pool' + ) as PoolMock: + PoolMock.return_value.get.return_value = config_model + + self.assertEqual( + report_class._resolve_configured_report_path({ + 'name': 'Invoice', + 'report': 'account_invoice/invoice.fodt', + }), + 'account_invoice/invoice_melya.fodt') + self.assertEqual( + report_class._resolve_configured_report_path({ + 'name': 'Prepayment', + 'report': 'account_invoice/prepayment.fodt', + }), + 'account_invoice/prepayment.fodt') + self.assertEqual( + report_class._resolve_configured_report_path({ + 'name': 'CN/DN', + 'report': 'account_invoice/invoice_ict_final.fodt', + }), + 'account_invoice/invoice_ict_final.fodt') + + def test_invoice_report_raises_when_template_is_missing(self): + 'invoice report must fail clearly when no template is configured' + report_class = Pool().get('account.invoice', type='report') + config_model = Mock() + config_model.search.return_value = [ + Mock( + invoice_report_template='', + invoice_cndn_report_template='', + invoice_prepayment_report_template='', + ) + ] + + with patch( + 'trytond.modules.purchase_trade.invoice.Pool' + ) as PoolMock: + PoolMock.return_value.get.return_value = config_model + with self.assertRaises(UserError): + report_class._resolve_configured_report_path({ + 'name': 'Invoice', + 'report': 'account_invoice/invoice.fodt', + }) + def test_sale_report_multi_line_helpers_aggregate_all_lines(self): 'sale report helpers aggregate quantity, price lines and shipment periods' Sale = Pool().get('sale.sale') diff --git a/modules/purchase_trade/view/configuration_form.xml b/modules/purchase_trade/view/configuration_form.xml index af617d4..a0ed7e6 100644 --- a/modules/purchase_trade/view/configuration_form.xml +++ b/modules/purchase_trade/view/configuration_form.xml @@ -2,4 +2,10 @@