Risk Management - Physical Open Position

This commit is contained in:
AzureAD\SylvainDUVERNAY
2026-07-28 16:56:45 +02:00
parent 979bbec013
commit 3994154cad
6 changed files with 740 additions and 17 deletions

View File

@@ -318,6 +318,283 @@ class CTRMNetPosition(ModelSQL, ModelView):
group_by=group_by)
class CTRMOpenPositionContext(ModelView):
"CTRM Open Position Context"
__name__ = 'ctrm.reporting.risk.open_position.context'
valuation_date = fields.Date("Valuation Date")
commodity = fields.Many2One('product.category', "Commodity")
product = fields.Many2One('product.product', "Product")
pricing_status = fields.Selection([
(None, ''),
('fixed', 'Fixed'),
('unfixed', 'Unfixed'),
('partly', 'Partly fix'),
], "Pricing Status")
shipment_period = fields.Many2One('product.month', "Shipment Period")
strategy = fields.Many2One('mtm.strategy', "Strategy")
counterparty = fields.Many2One('party.party', "Counterparty")
direction = fields.Selection([
(None, ''),
('long', 'LONG'),
('short', 'SHORT'),
('flat', 'Flat'),
], "Direction")
@classmethod
def default_valuation_date(cls):
return Pool().get('ir.date').today()
class CTRMOpenPosition(ModelSQL, ModelView):
"CTRM Open Position"
__name__ = 'ctrm.reporting.risk.open_position'
product = fields.Many2One(
'product.product', "Product", readonly=True)
commodity = fields.Many2One(
'product.category', "Commodity", readonly=True)
pricing = fields.Selection([
('fixed', 'Fixed'),
('unfixed', 'Unfixed'),
('partly', 'Partly fix'),
], "Pricing", readonly=True)
shipment_period = fields.Many2One(
'product.month', "Shipment Pd", readonly=True)
strategy = fields.Many2One(
'mtm.strategy', "Strategy", readonly=True)
bought = fields.Numeric("Bought", digits=(16, 3), readonly=True)
sold = fields.Numeric("Sold", digits=(16, 3), readonly=True)
net = fields.Numeric("Net", digits=(16, 3), readonly=True)
direction = fields.Selection([
('long', 'LONG'),
('short', 'SHORT'),
('flat', 'Flat'),
], "Direction", readonly=True)
unit = fields.Many2One('product.uom', "Unit", readonly=True)
open_contracts = fields.Integer("Open Contracts", readonly=True)
unfixed_qty = fields.Numeric(
"Unfixed Qty", digits=(16, 3), readonly=True)
@classmethod
def _physical_quantity_query(cls, lot, line_field):
return lot.select(
getattr(lot, line_field).as_('line'),
Sum(Coalesce(lot.lot_quantity, 0)).as_('quantity'),
where=((lot.lot_type == 'physic')
& (getattr(lot, line_field) != Null)),
group_by=[getattr(lot, line_field)])
@classmethod
def table_query(cls):
pool = Pool()
PurchaseLine = pool.get('purchase.line')
Purchase = pool.get('purchase.purchase')
SaleLine = pool.get('sale.line')
Sale = pool.get('sale.sale')
Lot = pool.get('lot.lot')
Product = pool.get('product.product')
Template = pool.get('product.template')
TemplateCategory = pool.get('product.template-product.category')
PurchaseStrategy = pool.get('purchase.strategy')
SaleStrategy = pool.get('sale.strategy')
purchase_line = PurchaseLine.__table__()
purchase = Purchase.__table__()
sale_line = SaleLine.__table__()
sale = Sale.__table__()
lot = Lot.__table__()
product = Product.__table__()
template = Template.__table__()
template_category = TemplateCategory.__table__()
purchase_strategy = PurchaseStrategy.__table__()
sale_strategy = SaleStrategy.__table__()
context = Transaction().context
purchase_physical = cls._physical_quantity_query(lot, 'line')
purchase_contract_qty = Coalesce(
purchase_line.quantity_theorical, purchase_line.quantity, 0)
purchase_physical_qty = Coalesce(Max(purchase_physical.quantity), 0)
purchase_residual = purchase_contract_qty - purchase_physical_qty
purchase_open_qty = Case(
(purchase_line.finished == True, 0),
(purchase_residual > 0, purchase_residual),
else_=0)
purchase_fixed = purchase_line.price_type.in_(['cash', 'priced'])
purchase_where = (
(purchase_line.product != Null)
& purchase.state.in_(['confirmed', 'processing'])
& (template.type != 'service'))
if context.get('product'):
purchase_where &= purchase_line.product == context['product']
if context.get('commodity'):
purchase_where &= (
template_category.category == context['commodity'])
if context.get('shipment_period'):
purchase_where &= (
purchase_line.del_period == context['shipment_period'])
if context.get('strategy'):
purchase_where &= (
purchase_strategy.strategy == context['strategy'])
if context.get('counterparty'):
purchase_where &= purchase.party == context['counterparty']
purchase_query = (
purchase_line
.join(purchase, condition=purchase_line.purchase == purchase.id)
.join(purchase_physical, 'LEFT',
condition=purchase_physical.line == purchase_line.id)
.join(product, condition=purchase_line.product == product.id)
.join(template, condition=product.template == template.id)
.join(template_category, 'LEFT',
condition=template_category.template == template.id)
.join(purchase_strategy, 'LEFT',
condition=purchase_strategy.line == purchase_line.id)
.select(
Literal('purchase').as_('side'),
purchase_line.id.as_('line_id'),
purchase_line.product.as_('product'),
Max(template_category.category).as_('commodity'),
purchase_line.del_period.as_('shipment_period'),
Max(purchase_strategy.strategy).as_('strategy'),
purchase.party.as_('counterparty'),
purchase_line.unit.as_('unit'),
purchase_open_qty.as_('bought'),
Literal(0).as_('sold'),
Case((purchase_fixed, purchase_open_qty), else_=0).as_(
'fixed_qty'),
Case((purchase_fixed, 0), else_=purchase_open_qty).as_(
'unfixed_qty'),
where=purchase_where,
group_by=[
purchase_line.id,
purchase_line.product,
purchase_line.del_period,
purchase.party,
purchase_line.unit,
purchase_line.finished,
purchase_line.quantity_theorical,
purchase_line.quantity,
purchase_line.price_type,
],
having=purchase_open_qty > 0))
sale_physical = cls._physical_quantity_query(lot, 'sale_line')
sale_contract_qty = Coalesce(
sale_line.quantity_theorical, sale_line.quantity, 0)
sale_physical_qty = Coalesce(Max(sale_physical.quantity), 0)
sale_residual = sale_contract_qty - sale_physical_qty
sale_open_qty = Case(
(sale_line.finished == True, 0),
(sale_residual > 0, sale_residual),
else_=0)
sale_fixed = sale_line.price_type.in_(['cash', 'priced'])
sale_where = (
(sale_line.product != Null)
& sale.state.in_(['confirmed', 'processing'])
& (template.type != 'service'))
if context.get('product'):
sale_where &= sale_line.product == context['product']
if context.get('commodity'):
sale_where &= template_category.category == context['commodity']
if context.get('shipment_period'):
sale_where &= sale_line.del_period == context['shipment_period']
if context.get('strategy'):
sale_where &= sale_strategy.strategy == context['strategy']
if context.get('counterparty'):
sale_where &= sale.party == context['counterparty']
sale_query = (
sale_line
.join(sale, condition=sale_line.sale == sale.id)
.join(sale_physical, 'LEFT',
condition=sale_physical.line == sale_line.id)
.join(product, condition=sale_line.product == product.id)
.join(template, condition=product.template == template.id)
.join(template_category, 'LEFT',
condition=template_category.template == template.id)
.join(sale_strategy, 'LEFT',
condition=sale_strategy.sale_line == sale_line.id)
.select(
Literal('sale').as_('side'),
sale_line.id.as_('line_id'),
sale_line.product.as_('product'),
Max(template_category.category).as_('commodity'),
sale_line.del_period.as_('shipment_period'),
Max(sale_strategy.strategy).as_('strategy'),
sale.party.as_('counterparty'),
sale_line.unit.as_('unit'),
Literal(0).as_('bought'),
sale_open_qty.as_('sold'),
Case((sale_fixed, sale_open_qty), else_=0).as_(
'fixed_qty'),
Case((sale_fixed, 0), else_=sale_open_qty).as_(
'unfixed_qty'),
where=sale_where,
group_by=[
sale_line.id,
sale_line.product,
sale_line.del_period,
sale.party,
sale_line.unit,
sale_line.finished,
sale_line.quantity_theorical,
sale_line.quantity,
sale_line.price_type,
],
having=sale_open_qty > 0))
lines = Union(purchase_query, sale_query, all_=True)
bought = Sum(Coalesce(lines.bought, 0))
sold = Sum(Coalesce(lines.sold, 0))
net = bought - sold
fixed_qty = Sum(Coalesce(lines.fixed_qty, 0))
unfixed_qty = Sum(Coalesce(lines.unfixed_qty, 0))
pricing = Case(
((fixed_qty > 0) & (unfixed_qty > 0), 'partly'),
(unfixed_qty > 0, 'unfixed'),
else_='fixed')
direction = Case(
(net > 0, 'long'),
(net < 0, 'short'),
else_='flat')
having = (bought + sold) > 0
if context.get('pricing_status'):
having &= pricing == context['pricing_status']
if context.get('direction') == 'long':
having &= net > 0
elif context.get('direction') == 'short':
having &= net < 0
elif context.get('direction') == 'flat':
having &= net == 0
report_id = RowNumber(window=Window([],
order_by=[lines.product, lines.unit]))
return lines.select(
Literal(0).as_('create_uid'),
CurrentTimestamp().as_('create_date'),
Literal(None).as_('write_uid'),
Literal(None).as_('write_date'),
report_id.as_('id'),
lines.product.as_('product'),
Max(lines.commodity).as_('commodity'),
pricing.as_('pricing'),
Max(lines.shipment_period).as_('shipment_period'),
Max(lines.strategy).as_('strategy'),
bought.as_('bought'),
sold.as_('sold'),
net.as_('net'),
direction.as_('direction'),
lines.unit.as_('unit'),
Count(lines.line_id).as_('open_contracts'),
unfixed_qty.as_('unfixed_qty'),
group_by=[lines.product, lines.unit],
having=having)
class CTRMPnlContextMixin:
date = fields.Date("Valuation Date")
product = fields.Many2One('product.product', "Product")