This commit is contained in:
2026-05-24 18:13:34 +02:00
parent 1efaad8c38
commit 1075f49bd1
8 changed files with 385 additions and 1 deletions

View File

@@ -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,

View File

@@ -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)

View File

@@ -60,6 +60,46 @@
<field name="view" ref="ctrm_position_net_view_list"/>
<field name="act_window" ref="act_ctrm_position_net"/>
</record>
<record model="ir.ui.view" id="ctrm_pnl_realized_context_view_form">
<field name="model">ctrm.reporting.pnl.realized.context</field>
<field name="type">form</field>
<field name="name">ctrm_pnl_realized_context_form</field>
</record>
<record model="ir.ui.view" id="ctrm_pnl_realized_view_list">
<field name="model">ctrm.reporting.pnl.realized</field>
<field name="type">tree</field>
<field name="name">ctrm_pnl_realized_list</field>
</record>
<record model="ir.action.act_window" id="act_ctrm_pnl_realized">
<field name="name">Realized P&amp;L</field>
<field name="res_model">ctrm.reporting.pnl.realized</field>
<field name="context_model">ctrm.reporting.pnl.realized.context</field>
</record>
<record model="ir.action.act_window.view" id="act_ctrm_pnl_realized_view">
<field name="sequence" eval="10"/>
<field name="view" ref="ctrm_pnl_realized_view_list"/>
<field name="act_window" ref="act_ctrm_pnl_realized"/>
</record>
<record model="ir.ui.view" id="ctrm_pnl_mtm_context_view_form">
<field name="model">ctrm.reporting.pnl.mtm.context</field>
<field name="type">form</field>
<field name="name">ctrm_pnl_mtm_context_form</field>
</record>
<record model="ir.ui.view" id="ctrm_pnl_mtm_view_list">
<field name="model">ctrm.reporting.pnl.mtm</field>
<field name="type">tree</field>
<field name="name">ctrm_pnl_mtm_list</field>
</record>
<record model="ir.action.act_window" id="act_ctrm_pnl_mtm">
<field name="name">Mark-to-Market</field>
<field name="res_model">ctrm.reporting.pnl.mtm</field>
<field name="context_model">ctrm.reporting.pnl.mtm.context</field>
</record>
<record model="ir.action.act_window.view" id="act_ctrm_pnl_mtm_view">
<field name="sequence" eval="10"/>
<field name="view" ref="ctrm_pnl_mtm_view_list"/>
<field name="act_window" ref="act_ctrm_pnl_mtm"/>
</record>
<menuitem
name="Positions"
@@ -99,11 +139,13 @@
name="Realized P&amp;L"
parent="menu_ctrm_pnl"
sequence="10"
action="act_ctrm_pnl_realized"
id="menu_ctrm_pnl_realized"/>
<menuitem
name="Mark-to-Market"
parent="menu_ctrm_pnl"
sequence="20"
action="act_ctrm_pnl_mtm"
id="menu_ctrm_pnl_mtm"/>
<menuitem
name="P&amp;L Explain"

View File

@@ -0,0 +1,18 @@
<form>
<label name="date"/>
<field name="date"/>
<label name="product"/>
<field name="product"/>
<label name="counterparty"/>
<field name="counterparty"/>
<label name="currency"/>
<field name="currency"/>
<label name="purchase"/>
<field name="purchase"/>
<label name="sale"/>
<field name="sale"/>
<label name="strategy"/>
<field name="strategy"/>
<label name="state"/>
<field name="state"/>
</form>

View File

@@ -0,0 +1,21 @@
<tree>
<field name="valuation_date"/>
<field name="lot" width="120"/>
<field name="purchase" width="100"/>
<field name="purchase_line" width="120"/>
<field name="sale" width="100"/>
<field name="sale_line" width="120"/>
<field name="type"/>
<field name="reference" width="120"/>
<field name="counterparty" width="120"/>
<field name="product" width="140"/>
<field name="state"/>
<field name="quantity" symbol="unit" sum="1"/>
<field name="price"/>
<field name="mtm_price"/>
<field name="currency" width="60"/>
<field name="amount" sum="1"/>
<field name="mtm" sum="1"/>
<field name="mtm_delta" sum="1"/>
<field name="strategy"/>
</tree>

View File

@@ -0,0 +1,20 @@
<form>
<label name="date"/>
<field name="date"/>
<label name="realization"/>
<field name="realization"/>
<label name="product"/>
<field name="product"/>
<label name="counterparty"/>
<field name="counterparty"/>
<label name="currency"/>
<field name="currency"/>
<label name="purchase"/>
<field name="purchase"/>
<label name="sale"/>
<field name="sale"/>
<label name="strategy"/>
<field name="strategy"/>
<label name="state"/>
<field name="state"/>
</form>

View File

@@ -0,0 +1,20 @@
<tree>
<field name="valuation_date"/>
<field name="lot" width="120"/>
<field name="purchase" width="100"/>
<field name="purchase_line" width="120"/>
<field name="purchase_invoice" width="120"/>
<field name="sale" width="100"/>
<field name="sale_line" width="120"/>
<field name="sale_invoice" width="120"/>
<field name="type"/>
<field name="reference" width="120"/>
<field name="counterparty" width="120"/>
<field name="product" width="140"/>
<field name="state"/>
<field name="quantity" symbol="unit" sum="1"/>
<field name="currency" width="60"/>
<field name="amount" sum="1"/>
<field name="base_amount" sum="1"/>
<field name="strategy"/>
</tree>

View File

@@ -22,3 +22,15 @@ Date: 2026-05-24
- `Risk`
- `Operations & Logistics`
- `Finance & Accounting`
## P&L
- `Realized P&L` lit aussi `valuation.valuation.line`.
- Le critere de realisation est parametre dans le contexte du report.
- Defaut retenu: achat et vente factures (`Purchase and Sale Invoiced`).
- Autres vues utiles:
- vente facturee seule;
- achat facture seul;
- toutes les lignes valorisees.
- `Mark-to-Market` lit la derniere valuation et compare `mtm` a `amount`
via une colonne `MTM Delta`.