From 888b880bd67b255f1e294a1388f38fe38bc02fa3 Mon Sep 17 00:00:00 2001 From: laurentbarontini Date: Mon, 6 Apr 2026 15:17:17 +0200 Subject: [PATCH] Add template management --- modules/purchase_trade/__init__.py | 2 + modules/purchase_trade/configuration.py | 4 + modules/purchase_trade/configuration.xml | 21 +++++ modules/purchase_trade/invoice.py | 80 +++++++++++++------ modules/purchase_trade/tests/test_module.py | 60 ++++++++++++++ .../view/configuration_form.xml | 6 -- .../view/template_configuration_form.xml | 22 +++++ 7 files changed, 166 insertions(+), 29 deletions(-) create mode 100644 modules/purchase_trade/view/template_configuration_form.xml diff --git a/modules/purchase_trade/__init__.py b/modules/purchase_trade/__init__.py index abb308e..2076a30 100755 --- a/modules/purchase_trade/__init__.py +++ b/modules/purchase_trade/__init__.py @@ -276,5 +276,7 @@ def register(): module='sale', type_='wizard') Pool.register( invoice.InvoiceReport, + invoice.SaleReport, + invoice.PurchaseReport, module='purchase_trade', type_='report') diff --git a/modules/purchase_trade/configuration.py b/modules/purchase_trade/configuration.py index 73b8acb..538e225 100644 --- a/modules/purchase_trade/configuration.py +++ b/modules/purchase_trade/configuration.py @@ -6,6 +6,10 @@ class Configuration(ModelSingleton, ModelSQL, ModelView): __name__ = 'purchase_trade.configuration' pricing_rule = fields.Text("Pricing Rule") + sale_report_template = fields.Char("Sale Template") + sale_bill_report_template = fields.Char("Sale Bill Template") + sale_final_report_template = fields.Char("Sale Final Template") invoice_report_template = fields.Char("Invoice Template") invoice_cndn_report_template = fields.Char("CN/DN Template") invoice_prepayment_report_template = fields.Char("Prepayment Template") + purchase_report_template = fields.Char("Purchase Template") diff --git a/modules/purchase_trade/configuration.xml b/modules/purchase_trade/configuration.xml index 68a49cd..edcccc9 100644 --- a/modules/purchase_trade/configuration.xml +++ b/modules/purchase_trade/configuration.xml @@ -5,6 +5,11 @@ form configuration_form + + purchase_trade.configuration + form + template_configuration_form + Pricing Configuration @@ -15,6 +20,15 @@ + + Document Templates + purchase_trade.configuration + + + + + + + diff --git a/modules/purchase_trade/invoice.py b/modules/purchase_trade/invoice.py index f3adbbb..72f6cd0 100644 --- a/modules/purchase_trade/invoice.py +++ b/modules/purchase_trade/invoice.py @@ -5,6 +5,9 @@ from trytond.modules.purchase_trade.numbers_to_words import amount_to_currency_w from trytond.exceptions import UserError from trytond.modules.account_invoice.invoice import ( InvoiceReport as BaseInvoiceReport) +from trytond.modules.sale.sale import SaleReport as BaseSaleReport +from trytond.modules.purchase.purchase import ( + PurchaseReport as BasePurchaseReport) class Invoice(metaclass=PoolMeta): @@ -670,9 +673,7 @@ class InvoiceLine(metaclass=PoolMeta): return round(Decimal(net) * Decimal('2204.62'),2) -class InvoiceReport(BaseInvoiceReport): - __name__ = 'account.invoice' - +class ReportTemplateMixin: @classmethod def _get_purchase_trade_configuration(cls): Configuration = Pool().get('purchase_trade.configuration') @@ -686,31 +687,20 @@ class InvoiceReport(BaseInvoiceReport): return getattr(action, 'name', '') or '' @classmethod - def _resolve_configured_report_path(cls, action): + def _get_action_report_path(cls, action): + if isinstance(action, dict): + return action.get('report') or '' + return getattr(action, 'report', '') or '' + + @classmethod + def _resolve_template_path(cls, action, field_name, default_prefix): 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 = getattr(config, field_name, '') 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 f'{default_prefix}/{template}' return template @classmethod @@ -727,3 +717,47 @@ class InvoiceReport(BaseInvoiceReport): def _execute(cls, records, header, data, action): resolved_action = cls._get_resolved_action(action) return super()._execute(records, header, data, resolved_action) + + +class InvoiceReport(ReportTemplateMixin, BaseInvoiceReport): + __name__ = 'account.invoice' + + @classmethod + def _resolve_configured_report_path(cls, action): + 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'): + field_name = 'invoice_prepayment_report_template' + elif (report_path.endswith('/invoice_ict_final.fodt') + or action_name == 'CN/DN'): + field_name = 'invoice_cndn_report_template' + else: + field_name = 'invoice_report_template' + return cls._resolve_template_path(action, field_name, 'account_invoice') + + +class SaleReport(ReportTemplateMixin, BaseSaleReport): + __name__ = 'sale.sale' + + @classmethod + def _resolve_configured_report_path(cls, action): + report_path = cls._get_action_report_path(action) + action_name = cls._get_action_name(action) + if report_path.endswith('/bill.fodt') or action_name == 'Bill': + field_name = 'sale_bill_report_template' + elif report_path.endswith('/sale_final.fodt') or action_name == 'Sale (final)': + field_name = 'sale_final_report_template' + else: + field_name = 'sale_report_template' + return cls._resolve_template_path(action, field_name, 'sale') + + +class PurchaseReport(ReportTemplateMixin, BasePurchaseReport): + __name__ = 'purchase.purchase' + + @classmethod + def _resolve_configured_report_path(cls, action): + return cls._resolve_template_path( + action, 'purchase_report_template', 'purchase') diff --git a/modules/purchase_trade/tests/test_module.py b/modules/purchase_trade/tests/test_module.py index 62b2a19..a58f4d4 100644 --- a/modules/purchase_trade/tests/test_module.py +++ b/modules/purchase_trade/tests/test_module.py @@ -307,9 +307,13 @@ class PurchaseTradeTestCase(ModuleTestCase): config_model = Mock() config_model.search.return_value = [ Mock( + sale_report_template='sale_melya.fodt', + sale_bill_report_template='bill_melya.fodt', + sale_final_report_template='sale_final_melya.fodt', invoice_report_template='invoice_melya.fodt', invoice_cndn_report_template='invoice_ict_final.fodt', invoice_prepayment_report_template='prepayment.fodt', + purchase_report_template='purchase_melya.fodt', ) ] @@ -359,6 +363,62 @@ class PurchaseTradeTestCase(ModuleTestCase): 'report': 'account_invoice/invoice.fodt', }) + def test_sale_report_uses_templates_from_configuration(self): + 'sale report paths are resolved from purchase_trade configuration' + report_class = Pool().get('sale.sale', type='report') + config_model = Mock() + config_model.search.return_value = [ + Mock( + sale_report_template='sale_melya.fodt', + sale_bill_report_template='bill_melya.fodt', + sale_final_report_template='sale_final_melya.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': 'Sale', + 'report': 'sale/sale.fodt', + }), + 'sale/sale_melya.fodt') + self.assertEqual( + report_class._resolve_configured_report_path({ + 'name': 'Bill', + 'report': 'sale/bill.fodt', + }), + 'sale/bill_melya.fodt') + self.assertEqual( + report_class._resolve_configured_report_path({ + 'name': 'Sale (final)', + 'report': 'sale/sale_final.fodt', + }), + 'sale/sale_final_melya.fodt') + + def test_purchase_report_uses_template_from_configuration(self): + 'purchase report path is resolved from purchase_trade configuration' + report_class = Pool().get('purchase.purchase', type='report') + config_model = Mock() + config_model.search.return_value = [ + Mock(purchase_report_template='purchase_melya.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': 'Purchase', + 'report': 'purchase/purchase.fodt', + }), + 'purchase/purchase_melya.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 a0ed7e6..af617d4 100644 --- a/modules/purchase_trade/view/configuration_form.xml +++ b/modules/purchase_trade/view/configuration_form.xml @@ -2,10 +2,4 @@