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
+
+
+
+
+
+