From 1075f49bd11a1e944306d448694fbacde4ee6c7b Mon Sep 17 00:00:00 2001 From: laurentbarontini Date: Sun, 24 May 2026 18:13:34 +0200 Subject: [PATCH] GR --- modules/purchase_trade/__init__.py | 4 + modules/purchase_trade/ctrm_reporting.py | 249 +++++++++++++++++- modules/purchase_trade/ctrm_reporting.xml | 42 +++ .../view/ctrm_pnl_mtm_context_form.xml | 18 ++ .../purchase_trade/view/ctrm_pnl_mtm_list.xml | 21 ++ .../view/ctrm_pnl_realized_context_form.xml | 20 ++ .../view/ctrm_pnl_realized_list.xml | 20 ++ notes/ctrm_reporting.md | 12 + 8 files changed, 385 insertions(+), 1 deletion(-) create mode 100644 modules/purchase_trade/view/ctrm_pnl_mtm_context_form.xml create mode 100644 modules/purchase_trade/view/ctrm_pnl_mtm_list.xml create mode 100644 modules/purchase_trade/view/ctrm_pnl_realized_context_form.xml create mode 100644 modules/purchase_trade/view/ctrm_pnl_realized_list.xml diff --git a/modules/purchase_trade/__init__.py b/modules/purchase_trade/__init__.py index ec54115..0e9dcd8 100755 --- a/modules/purchase_trade/__init__.py +++ b/modules/purchase_trade/__init__.py @@ -64,6 +64,10 @@ def register(): ctrm_reporting.CTRMFinancialPositionContext, ctrm_reporting.CTRMNetPosition, ctrm_reporting.CTRMNetPositionContext, + ctrm_reporting.CTRMRealizedPnl, + ctrm_reporting.CTRMRealizedPnlContext, + ctrm_reporting.CTRMMtmPnl, + ctrm_reporting.CTRMMtmPnlContext, configuration.Configuration, pricing.ImportPricesStart, pricing.ImportPricesResult, diff --git a/modules/purchase_trade/ctrm_reporting.py b/modules/purchase_trade/ctrm_reporting.py index 58a344f..a5a8d26 100644 --- a/modules/purchase_trade/ctrm_reporting.py +++ b/modules/purchase_trade/ctrm_reporting.py @@ -1,4 +1,4 @@ -from sql import Literal +from sql import Literal, Null from sql.aggregate import Max, Min, Sum from sql.conditionals import Case, Coalesce from sql.functions import CurrentTimestamp @@ -21,6 +21,7 @@ PHYSICAL_VALUATION_TYPES = [ 'market', ] DERIVATIVE_VALUATION_TYPES = ['derivative'] +ALL_VALUATION_TYPES = PHYSICAL_VALUATION_TYPES + DERIVATIVE_VALUATION_TYPES class CTRMValuationContextMixin: @@ -308,3 +309,249 @@ class CTRMNetPosition(ModelSQL, ModelView): Sum(Coalesce(val.mtm, 0)).as_('mtm'), where=where, group_by=group_by) + + +class CTRMPnlContextMixin: + date = fields.Date("Valuation Date") + product = fields.Many2One('product.product', "Product") + counterparty = fields.Many2One('party.party', "Counterparty") + currency = fields.Many2One('currency.currency', "Currency") + purchase = fields.Many2One('purchase.purchase', "Purchase") + sale = fields.Many2One('sale.sale', "Sale") + strategy = fields.Many2One('mtm.strategy', "Strategy") + state = fields.Char("State") + + @classmethod + def default_date(cls): + Date = Pool().get('ir.date') + return Date.today() + + +class CTRMRealizedPnlContext(CTRMPnlContextMixin, ModelView): + "CTRM Realized P&L Context" + __name__ = 'ctrm.reporting.pnl.realized.context' + + realization = fields.Selection([ + ('purchase_and_sale_invoiced', 'Purchase and Sale Invoiced'), + ('sale_invoiced', 'Sale Invoiced'), + ('purchase_invoiced', 'Purchase Invoiced'), + ('all_valued', 'All Valued Lines'), + ], "Realization") + + @classmethod + def default_realization(cls): + return 'purchase_and_sale_invoiced' + + +class CTRMRealizedPnl(ModelSQL, ModelView): + "CTRM Realized P&L" + __name__ = 'ctrm.reporting.pnl.realized' + + valuation_date = fields.Date("Valuation Date") + lot = fields.Many2One('lot.lot', "Lot") + purchase = fields.Many2One('purchase.purchase', "Purchase") + purchase_line = fields.Many2One('purchase.line', "Purchase Line") + purchase_invoice = fields.Many2One('account.invoice', "Purchase Invoice") + sale = fields.Many2One('sale.sale', "Sale") + sale_line = fields.Many2One('sale.line', "Sale Line") + sale_invoice = fields.Many2One('account.invoice', "Sale Invoice") + type = fields.Selection([ + ('priced', 'Price'), + ('pur. priced', 'Pur. price'), + ('pur. efp', 'Pur. efp'), + ('sale priced', 'Sale price'), + ('sale efp', 'Sale efp'), + ('line fee', 'Line fee'), + ('pur. fee', 'Pur. fee'), + ('sale fee', 'Sale fee'), + ('shipment fee', 'Shipment fee'), + ('market', 'Market'), + ('derivative', 'Derivative'), + ], "Type") + reference = fields.Char("Reference") + counterparty = fields.Many2One('party.party', "Counterparty") + product = fields.Many2One('product.product', "Product") + state = fields.Char("State") + quantity = fields.Numeric("Quantity", digits=(16, 5)) + unit = fields.Many2One('product.uom', "Unit") + currency = fields.Many2One('currency.currency', "Currency") + amount = fields.Numeric("Amount", digits=(16, 2)) + base_amount = fields.Numeric("Base Amount", digits=(16, 2)) + strategy = fields.Many2One('mtm.strategy', "Strategy") + + @classmethod + def table_query(cls): + ValuationLine = Pool().get('valuation.valuation.line') + Lot = Pool().get('lot.lot') + InvoiceLine = Pool().get('account.invoice.line') + Invoice = Pool().get('account.invoice') + + val = ValuationLine.__table__() + lot = Lot.__table__() + purchase_invoice_line = InvoiceLine.__table__() + purchase_invoice = Invoice.__table__() + sale_invoice_line = InvoiceLine.__table__() + sale_invoice = Invoice.__table__() + + context = Transaction().context + realization = context.get( + 'realization', 'purchase_and_sale_invoiced') + 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 realization == 'purchase_and_sale_invoiced': + where &= lot.invoice_line != Null + where &= lot.sale_invoice_line != Null + elif realization == 'sale_invoiced': + where &= lot.sale_invoice_line != Null + elif realization == 'purchase_invoiced': + where &= lot.invoice_line != Null + + return ( + val + .join(lot, 'LEFT', condition=lot.id == val.lot) + .join(purchase_invoice_line, 'LEFT', + condition=purchase_invoice_line.id == lot.invoice_line) + .join(purchase_invoice, 'LEFT', + condition=purchase_invoice.id == purchase_invoice_line.invoice) + .join(sale_invoice_line, 'LEFT', + condition=sale_invoice_line.id == lot.sale_invoice_line) + .join(sale_invoice, 'LEFT', + condition=sale_invoice.id == sale_invoice_line.invoice) + .select( + Literal(0).as_('create_uid'), + CurrentTimestamp().as_('create_date'), + Literal(None).as_('write_uid'), + Literal(None).as_('write_date'), + val.id.as_('id'), + val.date.as_('valuation_date'), + val.lot.as_('lot'), + val.purchase.as_('purchase'), + val.line.as_('purchase_line'), + purchase_invoice.id.as_('purchase_invoice'), + val.sale.as_('sale'), + val.sale_line.as_('sale_line'), + sale_invoice.id.as_('sale_invoice'), + val.type.as_('type'), + val.reference.as_('reference'), + val.counterparty.as_('counterparty'), + val.product.as_('product'), + val.state.as_('state'), + val.quantity.as_('quantity'), + val.unit.as_('unit'), + val.currency.as_('currency'), + val.amount.as_('amount'), + val.base_amount.as_('base_amount'), + val.strategy.as_('strategy'), + where=where)) + + +class CTRMMtmPnlContext(CTRMPnlContextMixin, ModelView): + "CTRM Mark-to-Market Context" + __name__ = 'ctrm.reporting.pnl.mtm.context' + + +class CTRMMtmPnl(ModelSQL, ModelView): + "CTRM Mark-to-Market" + __name__ = 'ctrm.reporting.pnl.mtm' + + valuation_date = fields.Date("Valuation Date") + lot = fields.Many2One('lot.lot', "Lot") + purchase = fields.Many2One('purchase.purchase', "Purchase") + purchase_line = fields.Many2One('purchase.line', "Purchase Line") + sale = fields.Many2One('sale.sale', "Sale") + sale_line = fields.Many2One('sale.line', "Sale Line") + type = fields.Selection([ + ('priced', 'Price'), + ('pur. priced', 'Pur. price'), + ('pur. efp', 'Pur. efp'), + ('sale priced', 'Sale price'), + ('sale efp', 'Sale efp'), + ('line fee', 'Line fee'), + ('pur. fee', 'Pur. fee'), + ('sale fee', 'Sale fee'), + ('shipment fee', 'Shipment fee'), + ('market', 'Market'), + ('derivative', 'Derivative'), + ], "Type") + reference = fields.Char("Reference") + counterparty = fields.Many2One('party.party', "Counterparty") + product = fields.Many2One('product.product', "Product") + state = fields.Char("State") + quantity = fields.Numeric("Quantity", digits=(16, 5)) + unit = fields.Many2One('product.uom', "Unit") + price = fields.Numeric("Price", digits=(16, 4)) + mtm_price = fields.Numeric("MTM Price", digits=(16, 4)) + currency = fields.Many2One('currency.currency', "Currency") + amount = fields.Numeric("Amount", digits=(16, 2)) + mtm = fields.Numeric("MTM", digits=(16, 2)) + mtm_delta = fields.Numeric("MTM Delta", digits=(16, 2)) + strategy = fields.Many2One('mtm.strategy', "Strategy") + + @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'] + + return val.select( + Literal(0).as_('create_uid'), + CurrentTimestamp().as_('create_date'), + Literal(None).as_('write_uid'), + Literal(None).as_('write_date'), + val.id.as_('id'), + val.date.as_('valuation_date'), + val.lot.as_('lot'), + val.purchase.as_('purchase'), + val.line.as_('purchase_line'), + val.sale.as_('sale'), + val.sale_line.as_('sale_line'), + val.type.as_('type'), + val.reference.as_('reference'), + val.counterparty.as_('counterparty'), + val.product.as_('product'), + val.state.as_('state'), + val.quantity.as_('quantity'), + val.unit.as_('unit'), + val.price.as_('price'), + val.mtm_price.as_('mtm_price'), + val.currency.as_('currency'), + val.amount.as_('amount'), + val.mtm.as_('mtm'), + (Coalesce(val.mtm, 0) - Coalesce(val.amount, 0)).as_( + 'mtm_delta'), + val.strategy.as_('strategy'), + where=where) diff --git a/modules/purchase_trade/ctrm_reporting.xml b/modules/purchase_trade/ctrm_reporting.xml index 865491b..a6906f3 100644 --- a/modules/purchase_trade/ctrm_reporting.xml +++ b/modules/purchase_trade/ctrm_reporting.xml @@ -60,6 +60,46 @@ + + ctrm.reporting.pnl.realized.context + form + ctrm_pnl_realized_context_form + + + ctrm.reporting.pnl.realized + tree + ctrm_pnl_realized_list + + + Realized P&L + ctrm.reporting.pnl.realized + ctrm.reporting.pnl.realized.context + + + + + + + + ctrm.reporting.pnl.mtm.context + form + ctrm_pnl_mtm_context_form + + + ctrm.reporting.pnl.mtm + tree + ctrm_pnl_mtm_list + + + Mark-to-Market + ctrm.reporting.pnl.mtm + ctrm.reporting.pnl.mtm.context + + + + + + +