From f0ed5b9bd30a25a52887b081689f7739b2071d0d Mon Sep 17 00:00:00 2001 From: laurentbarontini Date: Sun, 24 May 2026 18:19:57 +0200 Subject: [PATCH] GR --- modules/purchase_trade/__init__.py | 4 + modules/purchase_trade/ctrm_reporting.py | 195 ++++++++++++++++++ modules/purchase_trade/ctrm_reporting.xml | 42 ++++ .../view/ctrm_pnl_dimension_context_form.xml | 22 ++ .../view/ctrm_pnl_dimension_list.xml | 14 ++ .../view/ctrm_pnl_explain_context_form.xml | 18 ++ .../view/ctrm_pnl_explain_list.xml | 13 ++ notes/ctrm_reporting.md | 9 + 8 files changed, 317 insertions(+) create mode 100644 modules/purchase_trade/view/ctrm_pnl_dimension_context_form.xml create mode 100644 modules/purchase_trade/view/ctrm_pnl_dimension_list.xml create mode 100644 modules/purchase_trade/view/ctrm_pnl_explain_context_form.xml create mode 100644 modules/purchase_trade/view/ctrm_pnl_explain_list.xml diff --git a/modules/purchase_trade/__init__.py b/modules/purchase_trade/__init__.py index 0e9dcd8..0bfb6d0 100755 --- a/modules/purchase_trade/__init__.py +++ b/modules/purchase_trade/__init__.py @@ -68,6 +68,10 @@ def register(): ctrm_reporting.CTRMRealizedPnlContext, ctrm_reporting.CTRMMtmPnl, ctrm_reporting.CTRMMtmPnlContext, + ctrm_reporting.CTRMPnlExplain, + ctrm_reporting.CTRMPnlExplainContext, + ctrm_reporting.CTRMPnlDimension, + ctrm_reporting.CTRMPnlDimensionContext, configuration.Configuration, pricing.ImportPricesStart, pricing.ImportPricesResult, diff --git a/modules/purchase_trade/ctrm_reporting.py b/modules/purchase_trade/ctrm_reporting.py index a5a8d26..4203345 100644 --- a/modules/purchase_trade/ctrm_reporting.py +++ b/modules/purchase_trade/ctrm_reporting.py @@ -555,3 +555,198 @@ class CTRMMtmPnl(ModelSQL, ModelView): 'mtm_delta'), val.strategy.as_('strategy'), where=where) + + +class CTRMPnlExplainContext(CTRMPnlContextMixin, ModelView): + "CTRM P&L Explain Context" + __name__ = 'ctrm.reporting.pnl.explain.context' + + +class CTRMPnlExplain(ModelSQL, ModelView): + "CTRM P&L Explain" + __name__ = 'ctrm.reporting.pnl.explain' + + valuation_date = fields.Date("Valuation Date") + product = fields.Many2One('product.product', "Product") + counterparty = fields.Many2One('party.party', "Counterparty") + currency = fields.Many2One('currency.currency', "Currency") + unit = fields.Many2One('product.uom', "Unit") + state = fields.Char("State") + strategy = fields.Many2One('mtm.strategy', "Strategy") + physical_pnl = fields.Numeric("Physical P&L", digits=(16, 2)) + fee_pnl = fields.Numeric("Fee P&L", digits=(16, 2)) + derivative_pnl = fields.Numeric("Derivative P&L", digits=(16, 2)) + mtm_delta = fields.Numeric("MTM Delta", digits=(16, 2)) + total_pnl = fields.Numeric("Total P&L", digits=(16, 2)) + + @classmethod + def table_query(cls): + ValuationLine = Pool().get('valuation.valuation.line') + val = ValuationLine.__table__() + + context = Transaction().context + where = val.type.in_(ALL_VALUATION_TYPES) + if context.get('date'): + where &= val.date == context['date'] + if context.get('product'): + where &= val.product == context['product'] + if context.get('counterparty'): + where &= val.counterparty == context['counterparty'] + if context.get('currency'): + where &= val.currency == context['currency'] + if context.get('purchase'): + where &= val.purchase == context['purchase'] + if context.get('sale'): + where &= val.sale == context['sale'] + if context.get('strategy'): + where &= val.strategy == context['strategy'] + if context.get('state'): + where &= val.state == context['state'] + + is_fee = val.type.in_([ + 'line fee', + 'pur. fee', + 'sale fee', + 'shipment fee', + ]) + is_derivative = val.type.in_(DERIVATIVE_VALUATION_TYPES) + physical_pnl = Case( + (is_fee | is_derivative, 0), else_=Coalesce(val.amount, 0)) + fee_pnl = Case((is_fee, Coalesce(val.amount, 0)), else_=0) + derivative_pnl = Case( + (is_derivative, Coalesce(val.amount, 0)), else_=0) + mtm_delta = Coalesce(val.mtm, 0) - Coalesce(val.amount, 0) + + group_by = [ + val.date, + val.product, + val.counterparty, + val.currency, + val.unit, + val.state, + val.strategy, + ] + + return val.select( + Literal(0).as_('create_uid'), + CurrentTimestamp().as_('create_date'), + Literal(None).as_('write_uid'), + Literal(None).as_('write_date'), + Min(val.id).as_('id'), + val.date.as_('valuation_date'), + val.product.as_('product'), + val.counterparty.as_('counterparty'), + val.currency.as_('currency'), + val.unit.as_('unit'), + val.state.as_('state'), + val.strategy.as_('strategy'), + Sum(physical_pnl).as_('physical_pnl'), + Sum(fee_pnl).as_('fee_pnl'), + Sum(derivative_pnl).as_('derivative_pnl'), + Sum(mtm_delta).as_('mtm_delta'), + Sum(Coalesce(val.amount, 0)).as_('total_pnl'), + where=where, + group_by=group_by) + + +class CTRMPnlDimensionContext(CTRMPnlContextMixin, ModelView): + "CTRM P&L by Dimension Context" + __name__ = 'ctrm.reporting.pnl.dimension.context' + + dimension = fields.Many2One('analytic.dimension', "Dimension") + value = fields.Many2One('analytic.dimension.value', "Value") + + +class CTRMPnlDimension(ModelSQL, ModelView): + "CTRM P&L by Dimension" + __name__ = 'ctrm.reporting.pnl.dimension' + + valuation_date = fields.Date("Valuation Date") + dimension = fields.Many2One('analytic.dimension', "Dimension") + value = fields.Many2One('analytic.dimension.value', "Value") + product = fields.Many2One('product.product', "Product") + counterparty = fields.Many2One('party.party', "Counterparty") + currency = fields.Many2One('currency.currency', "Currency") + unit = fields.Many2One('product.uom', "Unit") + state = fields.Char("State") + strategy = fields.Many2One('mtm.strategy', "Strategy") + quantity = fields.Numeric("Quantity", digits=(16, 5)) + amount = fields.Numeric("Amount", digits=(16, 2)) + mtm = fields.Numeric("MTM", digits=(16, 2)) + mtm_delta = fields.Numeric("MTM Delta", digits=(16, 2)) + + @classmethod + def table_query(cls): + ValuationLine = Pool().get('valuation.valuation.line') + Assignment = Pool().get('analytic.dimension.assignment') + val = ValuationLine.__table__() + purchase_assignment = Assignment.__table__() + sale_assignment = Assignment.__table__() + + context = Transaction().context + dimension_expr = Coalesce( + sale_assignment.dimension, purchase_assignment.dimension) + value_expr = Coalesce(sale_assignment.value, purchase_assignment.value) + + where = val.type.in_(ALL_VALUATION_TYPES) + if context.get('date'): + where &= val.date == context['date'] + if context.get('product'): + where &= val.product == context['product'] + if context.get('counterparty'): + where &= val.counterparty == context['counterparty'] + if context.get('currency'): + where &= val.currency == context['currency'] + if context.get('purchase'): + where &= val.purchase == context['purchase'] + if context.get('sale'): + where &= val.sale == context['sale'] + if context.get('strategy'): + where &= val.strategy == context['strategy'] + if context.get('state'): + where &= val.state == context['state'] + if context.get('dimension'): + where &= dimension_expr == context['dimension'] + if context.get('value'): + where &= value_expr == context['value'] + + group_by = [ + val.date, + dimension_expr, + value_expr, + val.product, + val.counterparty, + val.currency, + val.unit, + val.state, + val.strategy, + ] + + return ( + val + .join(purchase_assignment, 'LEFT', + condition=purchase_assignment.purchase == val.purchase) + .join(sale_assignment, 'LEFT', + condition=sale_assignment.sale == val.sale) + .select( + Literal(0).as_('create_uid'), + CurrentTimestamp().as_('create_date'), + Literal(None).as_('write_uid'), + Literal(None).as_('write_date'), + Min(val.id).as_('id'), + val.date.as_('valuation_date'), + dimension_expr.as_('dimension'), + value_expr.as_('value'), + val.product.as_('product'), + val.counterparty.as_('counterparty'), + val.currency.as_('currency'), + val.unit.as_('unit'), + val.state.as_('state'), + val.strategy.as_('strategy'), + Sum(Coalesce(val.quantity, 0)).as_('quantity'), + Sum(Coalesce(val.amount, 0)).as_('amount'), + Sum(Coalesce(val.mtm, 0)).as_('mtm'), + Sum(Coalesce(val.mtm, 0) - Coalesce(val.amount, 0)).as_( + 'mtm_delta'), + where=where, + group_by=group_by)) diff --git a/modules/purchase_trade/ctrm_reporting.xml b/modules/purchase_trade/ctrm_reporting.xml index a6906f3..870a898 100644 --- a/modules/purchase_trade/ctrm_reporting.xml +++ b/modules/purchase_trade/ctrm_reporting.xml @@ -100,6 +100,46 @@ + + ctrm.reporting.pnl.explain.context + form + ctrm_pnl_explain_context_form + + + ctrm.reporting.pnl.explain + tree + ctrm_pnl_explain_list + + + P&L Explain + ctrm.reporting.pnl.explain + ctrm.reporting.pnl.explain.context + + + + + + + + ctrm.reporting.pnl.dimension.context + form + ctrm_pnl_dimension_context_form + + + ctrm.reporting.pnl.dimension + tree + ctrm_pnl_dimension_list + + + P&L by Dimension + ctrm.reporting.pnl.dimension + ctrm.reporting.pnl.dimension.context + + + + + + +