diff --git a/modules/purchase_trade/purchase.py b/modules/purchase_trade/purchase.py index f9d05da..2ed0876 100755 --- a/modules/purchase_trade/purchase.py +++ b/modules/purchase_trade/purchase.py @@ -2297,6 +2297,78 @@ class Line(metaclass=PoolMeta): 'get_premium', setter='set_premium') fee_ = fields.Many2One('fee.fee',"Fee") pricing_rule = fields.Text("Pricing description") + parent_trader = fields.Function( + fields.Many2One('party.party', "Trader"), 'get_parent_field') + parent_operator = fields.Function( + fields.Many2One('party.party', "Operator"), 'get_parent_field') + parent_broker = fields.Function( + fields.Many2One('party.party', "Broker"), 'get_parent_field') + parent_contract_template = fields.Function( + fields.Many2One('contract.template', "Contract Template"), + 'get_parent_field') + parent_our_reference = fields.Function( + fields.Char("Our Reference"), 'get_parent_field') + parent_lc_date = fields.Function( + fields.Date("LC date"), 'get_parent_field') + parent_product_origin = fields.Function( + fields.Char("Origin"), 'get_parent_field') + price_component_count = fields.Function( + fields.Integer("Components"), 'get_relation_count') + pricing_count = fields.Function( + fields.Integer("Pricing"), 'get_relation_count') + price_summary_count = fields.Function( + fields.Integer("Summary"), 'get_relation_count') + premium_decomposition_count = fields.Function( + fields.Integer("Premium lines"), 'get_relation_count') + mtm_count = fields.Function( + fields.Integer("MTM"), 'get_relation_count') + fee_count = fields.Function( + fields.Integer("Fees"), 'get_relation_count') + lot_count = fields.Function( + fields.Integer("Lots"), 'get_relation_count') + derivative_count = fields.Function( + fields.Integer("Derivatives"), 'get_relation_count') + estimated_date_count = fields.Function( + fields.Integer("Estimated dates"), 'get_relation_count') + charter_condition_count = fields.Function( + fields.Integer("Charter conditions"), 'get_relation_count') + + _parent_field_map = { + 'parent_trader': 'trader', + 'parent_operator': 'operator', + 'parent_broker': 'broker', + 'parent_contract_template': 'contract_template', + 'parent_our_reference': 'our_reference', + 'parent_lc_date': 'lc_date', + 'parent_product_origin': 'product_origin', + } + _relation_count_map = { + 'price_component_count': 'price_components', + 'pricing_count': 'price_pricing', + 'price_summary_count': 'price_summary', + 'premium_decomposition_count': 'premium_decomposition', + 'mtm_count': 'mtm', + 'fee_count': 'fees', + 'lot_count': 'lots', + 'derivative_count': 'derivatives', + 'estimated_date_count': 'estimated_date', + 'charter_condition_count': 'charter_conditions', + } + + def get_parent_field(self, name=None): + field_name = self._parent_field_map.get(name) + if not field_name or not self.purchase: + return None + value = getattr(self.purchase, field_name, None) + if hasattr(value, 'id'): + return value.id + return value + + def get_relation_count(self, name=None): + field_name = self._relation_count_map.get(name) + if not field_name: + return 0 + return len(getattr(self, field_name, None) or []) @classmethod def default_unit(cls): diff --git a/modules/purchase_trade/purchase.xml b/modules/purchase_trade/purchase.xml index 9b8b6eb..d4e0076 100755 --- a/modules/purchase_trade/purchase.xml +++ b/modules/purchase_trade/purchase.xml @@ -37,11 +37,31 @@ this repository contains the full copyright notices and license terms. --> purchase_line_form - - purchase.line - - purchase_line_tree_sequence - + + purchase.line + + purchase_line_tree_sequence + + + purchase.line + tree + purchase_line_reporting_tree + + + Purchase Lines + purchase.line + + + + + + + + + + + + 🧠 Go to BI @@ -406,5 +426,11 @@ this repository contains the full copyright notices and license terms. --> sequence="16" action="act_contract_template_form" id="menu_purchase_contract_template_form"/> + diff --git a/modules/purchase_trade/sale.py b/modules/purchase_trade/sale.py index d0c7c60..835d520 100755 --- a/modules/purchase_trade/sale.py +++ b/modules/purchase_trade/sale.py @@ -2144,6 +2144,87 @@ class SaleLine(metaclass=PoolMeta): finished = fields.Boolean("Mark as finished") pricing_rule = fields.Text("Pricing description") price_composition = fields.One2Many('price.composition','sale_line',"Price composition") + parent_trader = fields.Function( + fields.Many2One('party.party', "Trader"), 'get_parent_field') + parent_operator = fields.Function( + fields.Many2One('party.party', "Operator"), 'get_parent_field') + parent_broker = fields.Function( + fields.Many2One('party.party', "Broker"), 'get_parent_field') + parent_contract_template = fields.Function( + fields.Many2One('contract.template', "Contract Template"), + 'get_parent_field') + parent_invoice_party = fields.Function( + fields.Many2One('party.party', "Invoice Party"), 'get_parent_field') + parent_shipment_party = fields.Function( + fields.Many2One('party.party', "Shipment Party"), 'get_parent_field') + parent_our_reference = fields.Function( + fields.Char("Our Reference"), 'get_parent_field') + parent_lc_date = fields.Function( + fields.Date("LC date"), 'get_parent_field') + parent_product_origin = fields.Function( + fields.Char("Origin"), 'get_parent_field') + price_component_count = fields.Function( + fields.Integer("Components"), 'get_relation_count') + pricing_count = fields.Function( + fields.Integer("Pricing"), 'get_relation_count') + price_summary_count = fields.Function( + fields.Integer("Summary"), 'get_relation_count') + price_composition_count = fields.Function( + fields.Integer("Price composition"), 'get_relation_count') + premium_decomposition_count = fields.Function( + fields.Integer("Premium lines"), 'get_relation_count') + mtm_count = fields.Function( + fields.Integer("MTM"), 'get_relation_count') + fee_count = fields.Function( + fields.Integer("Fees"), 'get_relation_count') + lot_count = fields.Function( + fields.Integer("Lots"), 'get_relation_count') + derivative_count = fields.Function( + fields.Integer("Derivatives"), 'get_relation_count') + estimated_date_count = fields.Function( + fields.Integer("Estimated dates"), 'get_relation_count') + charter_condition_count = fields.Function( + fields.Integer("Charter conditions"), 'get_relation_count') + + _parent_field_map = { + 'parent_trader': 'trader', + 'parent_operator': 'operator', + 'parent_broker': 'broker', + 'parent_contract_template': 'contract_template', + 'parent_invoice_party': 'invoice_party', + 'parent_shipment_party': 'shipment_party', + 'parent_our_reference': 'our_reference', + 'parent_lc_date': 'lc_date', + 'parent_product_origin': 'product_origin', + } + _relation_count_map = { + 'price_component_count': 'price_components', + 'pricing_count': 'price_pricing', + 'price_summary_count': 'price_summary', + 'price_composition_count': 'price_composition', + 'premium_decomposition_count': 'premium_decomposition', + 'mtm_count': 'mtm', + 'fee_count': 'fees', + 'lot_count': 'lots', + 'derivative_count': 'derivatives', + 'estimated_date_count': 'estimated_date', + 'charter_condition_count': 'charter_conditions', + } + + def get_parent_field(self, name=None): + field_name = self._parent_field_map.get(name) + if not field_name or not self.sale: + return None + value = getattr(self.sale, field_name, None) + if hasattr(value, 'id'): + return value.id + return value + + def get_relation_count(self, name=None): + field_name = self._relation_count_map.get(name) + if not field_name: + return 0 + return len(getattr(self, field_name, None) or []) @classmethod def default_finished(cls): diff --git a/modules/purchase_trade/sale.xml b/modules/purchase_trade/sale.xml index 029190a..4072871 100755 --- a/modules/purchase_trade/sale.xml +++ b/modules/purchase_trade/sale.xml @@ -13,11 +13,31 @@ this repository contains the full copyright notices and license terms. --> sale_line_form - - sale.line - - sale_line_tree_sequence - + + sale.line + + sale_line_tree_sequence + + + sale.line + tree + sale_line_reporting_tree + + + Sale Lines + sale.line + + + + + + + + + + + + lot.lot @@ -63,10 +83,16 @@ this repository contains the full copyright notices and license terms. --> - - sale.sale - form - sale_btb_form - - + + sale.sale + form + sale_btb_form + + + diff --git a/modules/purchase_trade/tests/test_module.py b/modules/purchase_trade/tests/test_module.py index f10926e..1cd67b0 100644 --- a/modules/purchase_trade/tests/test_module.py +++ b/modules/purchase_trade/tests/test_module.py @@ -4161,6 +4161,66 @@ class PurchaseTradeTestCase(ModuleTestCase): 'estimated_date': datetime.date(2026, 5, 1), }])]) + def test_purchase_line_reporting_parent_fields_and_counts(self): + 'purchase line reporting fields mirror parent values and relation counts' + PurchaseLine = Pool().get('purchase.line') + line = PurchaseLine() + line.purchase = Mock( + trader=Mock(id=10), + operator=Mock(id=11), + broker=Mock(id=12), + contract_template=Mock(id=13), + our_reference='OUR-PUR', + lc_date=datetime.date(2026, 5, 2), + product_origin='Origin', + ) + line.price_components = [Mock(), Mock()] + line.price_pricing = [Mock()] + line.mtm = [Mock(), Mock(), Mock()] + line.lots = [] + + self.assertEqual(line.get_parent_field('parent_trader'), 10) + self.assertEqual(line.get_parent_field('parent_operator'), 11) + self.assertEqual( + line.get_parent_field('parent_contract_template'), 13) + self.assertEqual(line.get_parent_field('parent_our_reference'), 'OUR-PUR') + self.assertEqual( + line.get_relation_count('price_component_count'), 2) + self.assertEqual(line.get_relation_count('pricing_count'), 1) + self.assertEqual(line.get_relation_count('mtm_count'), 3) + self.assertEqual(line.get_relation_count('lot_count'), 0) + + def test_sale_line_reporting_parent_fields_and_counts(self): + 'sale line reporting fields mirror parent values and relation counts' + SaleLine = Pool().get('sale.line') + line = SaleLine() + line.sale = Mock( + trader=Mock(id=20), + operator=Mock(id=21), + invoice_party=Mock(id=22), + shipment_party=Mock(id=23), + contract_template=Mock(id=24), + our_reference='OUR-SALE', + lc_date=datetime.date(2026, 5, 3), + product_origin='Origin', + ) + line.price_components = [Mock()] + line.price_composition = [Mock(), Mock()] + line.price_pricing = [] + line.fees = [Mock(), Mock(), Mock()] + + self.assertEqual(line.get_parent_field('parent_trader'), 20) + self.assertEqual(line.get_parent_field('parent_operator'), 21) + self.assertEqual(line.get_parent_field('parent_invoice_party'), 22) + self.assertEqual(line.get_parent_field('parent_shipment_party'), 23) + self.assertEqual(line.get_parent_field('parent_our_reference'), 'OUR-SALE') + self.assertEqual( + line.get_relation_count('price_component_count'), 1) + self.assertEqual( + line.get_relation_count('price_composition_count'), 2) + self.assertEqual(line.get_relation_count('pricing_count'), 0) + self.assertEqual(line.get_relation_count('fee_count'), 3) + def test_purchase_and_sale_line_keep_explicit_estimated_bl_date(self): 'new lines keep explicit estimated BL date' PurchaseLine = Pool().get('purchase.line') diff --git a/modules/purchase_trade/view/purchase_line_reporting_tree.xml b/modules/purchase_trade/view/purchase_line_reporting_tree.xml new file mode 100644 index 0000000..bd6e215 --- /dev/null +++ b/modules/purchase_trade/view/purchase_line_reporting_tree.xml @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/modules/purchase_trade/view/sale_line_reporting_tree.xml b/modules/purchase_trade/view/sale_line_reporting_tree.xml new file mode 100644 index 0000000..44691bf --- /dev/null +++ b/modules/purchase_trade/view/sale_line_reporting_tree.xml @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +