From 4458871423bf428a323502cde90233fcb09e3d23 Mon Sep 17 00:00:00 2001 From: laurentbarontini Date: Wed, 22 Apr 2026 21:09:54 +0200 Subject: [PATCH] add COO --- modules/purchase_trade/__init__.py | 1 + modules/purchase_trade/configuration.py | 4 + modules/purchase_trade/stock.py | 89 +++++ modules/purchase_trade/stock.xml | 12 + modules/purchase_trade/tests/test_module.py | 15 +- .../view/template_configuration_form.xml | 4 + modules/stock/coo.fodt | 328 ++++++++++++++++++ 7 files changed, 452 insertions(+), 1 deletion(-) create mode 100644 modules/stock/coo.fodt diff --git a/modules/purchase_trade/__init__.py b/modules/purchase_trade/__init__.py index 2374193..afa8840 100755 --- a/modules/purchase_trade/__init__.py +++ b/modules/purchase_trade/__init__.py @@ -280,6 +280,7 @@ def register(): invoice.PurchaseReport, stock.ShipmentShippingReport, stock.ShipmentInsuranceReport, + stock.ShipmentCOOReport, stock.ShipmentPackingListReport, module='purchase_trade', type_='report') diff --git a/modules/purchase_trade/configuration.py b/modules/purchase_trade/configuration.py index 8e9d024..503fc2a 100644 --- a/modules/purchase_trade/configuration.py +++ b/modules/purchase_trade/configuration.py @@ -25,6 +25,8 @@ class Configuration(ModelSingleton, ModelSQL, ModelView): 'report_shipment_in_shipping', 'Shipping instructions'), ('shipment_insurance_report_label', 'purchase_trade', 'report_shipment_in_insurance', 'Insurance'), + ('shipment_coo_report_label', 'purchase_trade', + 'report_shipment_in_coo', 'COO'), ('shipment_packing_list_report_label', 'purchase_trade', 'report_shipment_in_packing_list', 'Packing List'), ) @@ -52,6 +54,8 @@ class Configuration(ModelSingleton, ModelSQL, ModelView): shipment_shipping_report_label = fields.Char("Shipping Menu Label") shipment_insurance_report_template = fields.Char("Insurance Template") shipment_insurance_report_label = fields.Char("Insurance Menu Label") + shipment_coo_report_template = fields.Char("COO Template") + shipment_coo_report_label = fields.Char("COO Menu Label") shipment_packing_list_report_template = fields.Char("Packing List Template") shipment_packing_list_report_label = fields.Char( "Packing List Menu Label") diff --git a/modules/purchase_trade/stock.py b/modules/purchase_trade/stock.py index 2a4c259..64d9ec1 100755 --- a/modules/purchase_trade/stock.py +++ b/modules/purchase_trade/stock.py @@ -792,6 +792,86 @@ class ShipmentIn(metaclass=PoolMeta): def report_packing_net_weight(self): net, _ = self._get_report_weight_totals() return self._format_report_quantity(net) + + @property + def report_coo_exporter(self): + company = getattr(self, 'company', None) + party = getattr(company, 'party', None) if company else None + if not party: + return '' + address = party.address_get() if hasattr(party, 'address_get') else None + lines = [getattr(party, 'rec_name', None) or getattr(party, 'name', None) or ''] + if address and getattr(address, 'full_address', None): + lines.append(address.full_address) + return '\n'.join(filter(None, lines)) + + @property + def report_coo_consignee(self): + trade = self._get_report_trade() + party = getattr(trade, 'party', None) if trade else None + if not party: + return '' + address = party.address_get() if hasattr(party, 'address_get') else None + lines = [getattr(party, 'rec_name', None) or getattr(party, 'name', None) or ''] + if address and getattr(address, 'full_address', None): + lines.append(address.full_address) + return '\n'.join(filter(None, lines)) + + @property + def report_coo_number(self): + return getattr(self, 'reference', None) or self.number or '' + + @property + def report_coo_transport(self): + parts = [] + if self.bl_number: + parts.append(f"B/L {self.bl_number}") + ship_name = self.report_packing_ship_name + if ship_name: + parts.append(ship_name) + if self.booking: + parts.append(f"Booking {self.booking}") + return ' - '.join(parts) + + @property + def report_coo_origin_country(self): + return self.report_packing_origin or '' + + @property + def report_coo_observations(self): + parts = [] + contract = self.report_packing_contract_number + if contract: + parts.append(f"Contract: {contract}") + if self.note: + parts.append(self.note) + return '\n'.join(filter(None, parts)) + + @property + def report_coo_goods_description(self): + parts = [self.report_product_name, self.report_product_description] + if self.container: + container_numbers = ', '.join( + filter(None, (getattr(c, 'container_no', None) or '' for c in self.container))) + if container_numbers: + parts.append(f"Container(s): {container_numbers}") + return '\n'.join(filter(None, parts)) + + @property + def report_coo_net_weight(self): + return self.report_packing_net_weight + + @property + def report_coo_gross_weight(self): + return self.report_packing_gross_weight + + @property + def report_coo_issue_date(self): + Date = Pool().get('ir.date') + today = Date.today() + if not today: + return '' + return today.strftime('%d-%m-%Y') def get_rec_name(self, name=None): if self.number: @@ -2310,3 +2390,12 @@ class ShipmentPackingListReport(ShipmentTemplateReportMixin, BaseSupplierShippin def _resolve_configured_report_path(cls, action): return cls._resolve_template_path( 'shipment_packing_list_report_template', 'stock') + + +class ShipmentCOOReport(ShipmentTemplateReportMixin, BaseSupplierShipping): + __name__ = 'stock.shipment.in.coo' + + @classmethod + def _resolve_configured_report_path(cls, action): + return cls._resolve_template_path( + 'shipment_coo_report_template', 'stock') diff --git a/modules/purchase_trade/stock.xml b/modules/purchase_trade/stock.xml index 99a2eec..8a0e02d 100755 --- a/modules/purchase_trade/stock.xml +++ b/modules/purchase_trade/stock.xml @@ -78,6 +78,18 @@ this repository contains the full copyright notices and license terms. --> + + COO + stock.shipment.in + stock.shipment.in.coo + stock/coo.fodt + + + form_print + stock.shipment.in,-1 + + + Packing List stock.shipment.in diff --git a/modules/purchase_trade/tests/test_module.py b/modules/purchase_trade/tests/test_module.py index adb3a4a..3afdcbc 100644 --- a/modules/purchase_trade/tests/test_module.py +++ b/modules/purchase_trade/tests/test_module.py @@ -1077,12 +1077,14 @@ class PurchaseTradeTestCase(ModuleTestCase): 'shipment report paths are resolved from purchase_trade configuration' shipping_report = Pool().get('stock.shipment.in.shipping', type='report') insurance_report = Pool().get('stock.shipment.in.insurance', type='report') + coo_report = Pool().get('stock.shipment.in.coo', type='report') packing_report = Pool().get('stock.shipment.in.packing_list', type='report') config_model = Mock() config_model.search.return_value = [ Mock( shipment_shipping_report_template='si_custom.fodt', shipment_insurance_report_template='insurance_custom.fodt', + shipment_coo_report_template='coo_custom.fodt', shipment_packing_list_report_template='packing_list_custom.fodt', ) ] @@ -1104,6 +1106,12 @@ class PurchaseTradeTestCase(ModuleTestCase): 'report': 'stock/insurance.fodt', }), 'stock/insurance_custom.fodt') + self.assertEqual( + coo_report._resolve_configured_report_path({ + 'name': 'COO', + 'report': 'stock/coo.fodt', + }), + 'stock/coo_custom.fodt') self.assertEqual( packing_report._resolve_configured_report_path({ 'name': 'Packing List', @@ -1125,6 +1133,7 @@ class PurchaseTradeTestCase(ModuleTestCase): purchase_report_label='', shipment_shipping_report_label='', shipment_insurance_report_label='', + shipment_coo_report_label='Certificate of Origin', shipment_packing_list_report_label='', ) action_sale = Mock(spec=['name']) @@ -1147,6 +1156,8 @@ class PurchaseTradeTestCase(ModuleTestCase): action_shipping.name = 'Shipping instructions' action_insurance = Mock(spec=['name']) action_insurance.name = 'Insurance' + action_coo = Mock(spec=['name']) + action_coo.name = 'COO' action_packing = Mock(spec=['name']) action_packing.name = 'Packing List' actions = { @@ -1160,7 +1171,8 @@ class PurchaseTradeTestCase(ModuleTestCase): 8: action_purchase, 9: action_shipping, 10: action_insurance, - 11: action_packing, + 11: action_coo, + 12: action_packing, } model_data = Mock() @@ -1188,6 +1200,7 @@ class PurchaseTradeTestCase(ModuleTestCase): [actions[2]], {'name': 'Draft'}, [actions[7]], {'name': 'Packing Slip'}, [actions[6]], {'name': 'Wire Order'}, + [actions[11]], {'name': 'Certificate of Origin'}, )) def test_shipment_insurance_helpers_use_fee_and_controller(self): diff --git a/modules/purchase_trade/view/template_configuration_form.xml b/modules/purchase_trade/view/template_configuration_form.xml index 945e5d2..70932f7 100644 --- a/modules/purchase_trade/view/template_configuration_form.xml +++ b/modules/purchase_trade/view/template_configuration_form.xml @@ -51,6 +51,10 @@