This commit is contained in:
2026-05-24 19:13:58 +02:00
parent 20f51de50f
commit 2a5eec0e62
7 changed files with 503 additions and 1 deletions

View File

@@ -78,6 +78,10 @@ def register():
ctrm_reporting.CTRMShippingLogisticsContext,
ctrm_reporting.CTRMInventory,
ctrm_reporting.CTRMInventoryContext,
ctrm_reporting.CTRMContractPerformance,
ctrm_reporting.CTRMContractPerformanceContext,
ctrm_reporting.CTRMScheduling,
ctrm_reporting.CTRMSchedulingContext,
configuration.Configuration,
pricing.ImportPricesStart,
pricing.ImportPricesResult,

View File

@@ -1,4 +1,4 @@
from sql import Literal, Null, Window
from sql import Literal, Null, Union, Window
from sql.aggregate import Count, Max, Min, Sum
from sql.conditionals import Case, Coalesce
from sql.functions import CurrentTimestamp, RowNumber
@@ -1160,3 +1160,388 @@ class CTRMInventory(ModelSQL, ModelView):
Coalesce(lot.lot_qt, 0).as_('quantity'),
lot.lot_unit_line.as_('unit'),
where=where))
class CTRMContractPerformanceContext(ModelView):
"CTRM Contract Performance Context"
__name__ = 'ctrm.reporting.operations.contract.context'
side = fields.Selection([
(None, ''),
('purchase', 'Purchase'),
('sale', 'Sale'),
], "Side")
product = fields.Many2One('product.product', "Product")
counterparty = fields.Many2One('party.party', "Counterparty")
delivery_period = fields.Many2One('product.month', "Delivery Period")
status = fields.Selection([
(None, ''),
('under', 'Under delivery'),
('in_tolerance', 'In tolerance'),
('over', 'Over delivery'),
], "Status")
open_only = fields.Boolean("Open Only")
class CTRMContractPerformance(ModelSQL, ModelView):
"CTRM Contract Performance"
__name__ = 'ctrm.reporting.operations.contract'
side = fields.Selection([
('purchase', 'Purchase'),
('sale', 'Sale'),
], "Side")
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")
counterparty = fields.Many2One('party.party', "Counterparty")
product = fields.Many2One('product.product', "Product")
delivery_period = fields.Many2One('product.month', "Delivery Period")
from_date = fields.Date("From")
to_date = fields.Date("To")
contract_quantity = fields.Numeric("Contract Quantity", digits=(16, 5))
actual_quantity = fields.Numeric("Actual Quantity", digits=(16, 5))
variance_quantity = fields.Numeric("Variance", digits=(16, 5))
tolerance_min = fields.Numeric("Tolerance Min %", digits=(16, 5))
tolerance_max = fields.Numeric("Tolerance Max %", digits=(16, 5))
status = fields.Selection([
('under', 'Under delivery'),
('in_tolerance', 'In tolerance'),
('over', 'Over delivery'),
], "Status")
finished = fields.Boolean("Finished")
unit = fields.Many2One('product.uom', "Unit")
lot_count = fields.Integer("Lots")
@classmethod
def table_query(cls):
pool = Pool()
Lot = pool.get('lot.lot')
PurchaseLine = pool.get('purchase.line')
Purchase = pool.get('purchase.purchase')
SaleLine = pool.get('sale.line')
Sale = pool.get('sale.sale')
lot = Lot.__table__()
purchase_line = PurchaseLine.__table__()
purchase = Purchase.__table__()
sale_line = SaleLine.__table__()
sale = Sale.__table__()
purchase_contract_quantity = Coalesce(
purchase_line.quantity_theorical, purchase_line.quantity, 0)
purchase_tolerance_min = Coalesce(
purchase_line.tol_min, purchase.tol_min, 0)
purchase_tolerance_max = Coalesce(
purchase_line.tol_max, purchase.tol_max, 0)
purchase_actual_quantity = Sum(Coalesce(lot.lot_qt, 0))
purchase_min_quantity = purchase_contract_quantity * (
1 - (purchase_tolerance_min / 100))
purchase_max_quantity = purchase_contract_quantity * (
1 + (purchase_tolerance_max / 100))
purchase_status = Case(
(purchase_actual_quantity < purchase_min_quantity, 'under'),
(purchase_actual_quantity > purchase_max_quantity, 'over'),
else_='in_tolerance')
sale_contract_quantity = Coalesce(
sale_line.quantity_theorical, sale_line.quantity, 0)
sale_tolerance_min = Coalesce(sale_line.tol_min, sale.tol_min, 0)
sale_tolerance_max = Coalesce(sale_line.tol_max, sale.tol_max, 0)
sale_actual_quantity = Sum(Coalesce(lot.lot_qt, 0))
sale_min_quantity = sale_contract_quantity * (
1 - (sale_tolerance_min / 100))
sale_max_quantity = sale_contract_quantity * (
1 + (sale_tolerance_max / 100))
sale_status = Case(
(sale_actual_quantity < sale_min_quantity, 'under'),
(sale_actual_quantity > sale_max_quantity, 'over'),
else_='in_tolerance')
context = Transaction().context
purchase_where = (lot.lot_type == 'physic') & (lot.line != Null)
if context.get('product'):
purchase_where &= purchase_line.product == context['product']
if context.get('counterparty'):
purchase_where &= purchase.party == context['counterparty']
if context.get('delivery_period'):
purchase_where &= purchase_line.del_period == (
context['delivery_period'])
if context.get('open_only'):
purchase_where &= purchase_line.finished == False
purchase_query = (
lot
.join(purchase_line, condition=lot.line == purchase_line.id)
.join(purchase, condition=purchase_line.purchase == purchase.id)
.select(
Literal('purchase').as_('side'),
purchase.id.as_('purchase'),
lot.line.as_('purchase_line'),
Literal(None).as_('sale'),
Literal(None).as_('sale_line'),
purchase.party.as_('counterparty'),
purchase_line.product.as_('product'),
purchase_line.del_period.as_('delivery_period'),
purchase_line.from_del.as_('from_date'),
purchase_line.to_del.as_('to_date'),
purchase_contract_quantity.as_('contract_quantity'),
purchase_actual_quantity.as_('actual_quantity'),
(purchase_actual_quantity - purchase_contract_quantity).as_(
'variance_quantity'),
purchase_tolerance_min.as_('tolerance_min'),
purchase_tolerance_max.as_('tolerance_max'),
purchase_status.as_('status'),
purchase_line.finished.as_('finished'),
purchase_line.unit.as_('unit'),
Count(lot.id).as_('lot_count'),
where=purchase_where,
group_by=[
purchase.id,
lot.line,
purchase.party,
purchase_line.product,
purchase_line.del_period,
purchase_line.from_del,
purchase_line.to_del,
purchase_contract_quantity,
purchase_tolerance_min,
purchase_tolerance_max,
purchase_line.finished,
purchase_line.unit,
]))
sale_where = (lot.lot_type == 'physic') & (lot.sale_line != Null)
if context.get('product'):
sale_where &= sale_line.product == context['product']
if context.get('counterparty'):
sale_where &= sale.party == context['counterparty']
if context.get('delivery_period'):
sale_where &= sale_line.del_period == context['delivery_period']
if context.get('open_only'):
sale_where &= sale_line.finished == False
sale_query = (
lot
.join(sale_line, condition=lot.sale_line == sale_line.id)
.join(sale, condition=sale_line.sale == sale.id)
.select(
Literal('sale').as_('side'),
Literal(None).as_('purchase'),
Literal(None).as_('purchase_line'),
sale.id.as_('sale'),
lot.sale_line.as_('sale_line'),
sale.party.as_('counterparty'),
sale_line.product.as_('product'),
sale_line.del_period.as_('delivery_period'),
sale_line.from_del.as_('from_date'),
sale_line.to_del.as_('to_date'),
sale_contract_quantity.as_('contract_quantity'),
sale_actual_quantity.as_('actual_quantity'),
(sale_actual_quantity - sale_contract_quantity).as_(
'variance_quantity'),
sale_tolerance_min.as_('tolerance_min'),
sale_tolerance_max.as_('tolerance_max'),
sale_status.as_('status'),
sale_line.finished.as_('finished'),
sale_line.unit.as_('unit'),
Count(lot.id).as_('lot_count'),
where=sale_where,
group_by=[
sale.id,
lot.sale_line,
sale.party,
sale_line.product,
sale_line.del_period,
sale_line.from_del,
sale_line.to_del,
sale_contract_quantity,
sale_tolerance_min,
sale_tolerance_max,
sale_line.finished,
sale_line.unit,
]))
side = context.get('side')
if side == 'purchase':
grouped = Union(purchase_query, purchase_query, all_=False)
elif side == 'sale':
grouped = Union(sale_query, sale_query, all_=False)
else:
grouped = Union(purchase_query, sale_query, all_=True)
report_id = RowNumber(window=Window([],
order_by=[grouped.side, grouped.purchase_line,
grouped.sale_line]))
outer_where = grouped.side != Null
if context.get('status'):
outer_where &= grouped.status == context['status']
return grouped.select(
Literal(0).as_('create_uid'),
CurrentTimestamp().as_('create_date'),
Literal(None).as_('write_uid'),
Literal(None).as_('write_date'),
report_id.as_('id'),
grouped.side.as_('side'),
grouped.purchase.as_('purchase'),
grouped.purchase_line.as_('purchase_line'),
grouped.sale.as_('sale'),
grouped.sale_line.as_('sale_line'),
grouped.counterparty.as_('counterparty'),
grouped.product.as_('product'),
grouped.delivery_period.as_('delivery_period'),
grouped.from_date.as_('from_date'),
grouped.to_date.as_('to_date'),
grouped.contract_quantity.as_('contract_quantity'),
grouped.actual_quantity.as_('actual_quantity'),
grouped.variance_quantity.as_('variance_quantity'),
grouped.tolerance_min.as_('tolerance_min'),
grouped.tolerance_max.as_('tolerance_max'),
grouped.status.as_('status'),
grouped.finished.as_('finished'),
grouped.unit.as_('unit'),
grouped.lot_count.as_('lot_count'),
where=outer_where)
class CTRMSchedulingContext(ModelView):
"CTRM Scheduling Context"
__name__ = 'ctrm.reporting.operations.scheduling.context'
from_date = fields.Date("From Date")
to_date = fields.Date("To Date")
product = fields.Many2One('product.product', "Product")
counterparty = fields.Many2One('party.party', "Counterparty")
warehouse = fields.Many2One('stock.location', "Warehouse")
shipment_state = fields.Char("Shipment State")
lot_status = fields.Selection([
(None, ''),
('forecast', 'Forecast'),
('loading', 'Loading'),
('transit', 'Transit'),
('destination', 'Destination'),
('stock', 'Stock'),
('delivered', 'Delivered'),
], "Lot Status")
class CTRMScheduling(ModelSQL, ModelView):
"CTRM Scheduling"
__name__ = 'ctrm.reporting.operations.scheduling'
lot = fields.Many2One('lot.lot', "Lot")
product = fields.Many2One('product.product', "Product")
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")
counterparty = fields.Many2One('party.party', "Counterparty")
shipment_in = fields.Many2One('stock.shipment.in', "Shipment")
warehouse = fields.Many2One('stock.location', "Warehouse")
from_location = fields.Many2One('stock.location', "From")
to_location = fields.Many2One('stock.location', "To")
delivery_period = fields.Many2One('product.month', "Delivery Period")
contract_from = fields.Date("Contract From")
contract_to = fields.Date("Contract To")
scheduled_date = fields.Date("Scheduled Date")
eta = fields.Date("ETA")
etd = fields.Date("ETD")
actual_date = fields.Date("Actual Date")
lot_status = fields.Selection([
(None, ''),
('forecast', 'Forecast'),
('loading', 'Loading'),
('transit', 'Transit'),
('destination', 'Destination'),
('stock', 'Stock'),
('delivered', 'Delivered'),
], "Lot Status")
shipment_state = fields.Char("Shipment State")
quantity = fields.Numeric("Quantity", digits=(16, 5))
unit = fields.Many2One('product.uom', "Unit")
@classmethod
def table_query(cls):
pool = Pool()
Lot = pool.get('lot.lot')
PurchaseLine = pool.get('purchase.line')
Purchase = pool.get('purchase.purchase')
SaleLine = pool.get('sale.line')
Sale = pool.get('sale.sale')
ShipmentIn = pool.get('stock.shipment.in')
lot = Lot.__table__()
purchase_line = PurchaseLine.__table__()
purchase = Purchase.__table__()
sale_line = SaleLine.__table__()
sale = Sale.__table__()
shipment = ShipmentIn.__table__()
counterparty = Case(
(lot.line != Null, purchase.party), else_=sale.party)
delivery_period = Coalesce(
purchase_line.del_period, sale_line.del_period)
contract_from = Coalesce(purchase_line.from_del, sale_line.from_del)
contract_to = Coalesce(purchase_line.to_del, sale_line.to_del)
scheduled_date = Coalesce(
shipment.etad, shipment.eta, shipment.planned_date,
contract_from)
context = Transaction().context
where = lot.lot_type == 'physic'
if context.get('from_date'):
where &= scheduled_date >= context['from_date']
if context.get('to_date'):
where &= scheduled_date <= context['to_date']
if context.get('product'):
where &= lot.lot_product == context['product']
if context.get('counterparty'):
where &= counterparty == context['counterparty']
if context.get('warehouse'):
where &= shipment.warehouse == context['warehouse']
if context.get('shipment_state'):
where &= shipment.state == context['shipment_state']
if context.get('lot_status'):
where &= lot.lot_status == context['lot_status']
return (
lot
.join(purchase_line, 'LEFT',
condition=lot.line == purchase_line.id)
.join(purchase, 'LEFT',
condition=purchase_line.purchase == purchase.id)
.join(sale_line, 'LEFT',
condition=lot.sale_line == sale_line.id)
.join(sale, 'LEFT',
condition=sale_line.sale == sale.id)
.join(shipment, 'LEFT',
condition=lot.lot_shipment_in == shipment.id)
.select(
Literal(0).as_('create_uid'),
CurrentTimestamp().as_('create_date'),
Literal(None).as_('write_uid'),
Literal(None).as_('write_date'),
lot.id.as_('id'),
lot.id.as_('lot'),
lot.lot_product.as_('product'),
purchase.id.as_('purchase'),
lot.line.as_('purchase_line'),
sale.id.as_('sale'),
lot.sale_line.as_('sale_line'),
counterparty.as_('counterparty'),
lot.lot_shipment_in.as_('shipment_in'),
shipment.warehouse.as_('warehouse'),
shipment.from_location.as_('from_location'),
shipment.to_location.as_('to_location'),
delivery_period.as_('delivery_period'),
contract_from.as_('contract_from'),
contract_to.as_('contract_to'),
scheduled_date.as_('scheduled_date'),
shipment.etad.as_('eta'),
shipment.etd.as_('etd'),
shipment.effective_date.as_('actual_date'),
lot.lot_status.as_('lot_status'),
shipment.state.as_('shipment_state'),
Coalesce(lot.lot_qt, 0).as_('quantity'),
lot.lot_unit_line.as_('unit'),
where=where))

View File

@@ -200,6 +200,46 @@
<field name="view" ref="ctrm_inventory_view_list"/>
<field name="act_window" ref="act_ctrm_inventory"/>
</record>
<record model="ir.ui.view" id="ctrm_contract_performance_context_view_form">
<field name="model">ctrm.reporting.operations.contract.context</field>
<field name="type">form</field>
<field name="name">ctrm_contract_performance_context_form</field>
</record>
<record model="ir.ui.view" id="ctrm_contract_performance_view_list">
<field name="model">ctrm.reporting.operations.contract</field>
<field name="type">tree</field>
<field name="name">ctrm_contract_performance_list</field>
</record>
<record model="ir.action.act_window" id="act_ctrm_contract_performance">
<field name="name">Contract Performance</field>
<field name="res_model">ctrm.reporting.operations.contract</field>
<field name="context_model">ctrm.reporting.operations.contract.context</field>
</record>
<record model="ir.action.act_window.view" id="act_ctrm_contract_performance_view">
<field name="sequence" eval="10"/>
<field name="view" ref="ctrm_contract_performance_view_list"/>
<field name="act_window" ref="act_ctrm_contract_performance"/>
</record>
<record model="ir.ui.view" id="ctrm_scheduling_context_view_form">
<field name="model">ctrm.reporting.operations.scheduling.context</field>
<field name="type">form</field>
<field name="name">ctrm_scheduling_context_form</field>
</record>
<record model="ir.ui.view" id="ctrm_scheduling_view_list">
<field name="model">ctrm.reporting.operations.scheduling</field>
<field name="type">tree</field>
<field name="name">ctrm_scheduling_list</field>
</record>
<record model="ir.action.act_window" id="act_ctrm_scheduling">
<field name="name">Scheduling</field>
<field name="res_model">ctrm.reporting.operations.scheduling</field>
<field name="context_model">ctrm.reporting.operations.scheduling.context</field>
</record>
<record model="ir.action.act_window.view" id="act_ctrm_scheduling_view">
<field name="sequence" eval="10"/>
<field name="view" ref="ctrm_scheduling_view_list"/>
<field name="act_window" ref="act_ctrm_scheduling"/>
</record>
<menuitem
name="Positions"
@@ -314,11 +354,13 @@
name="Contract Performance"
parent="menu_ctrm_operations"
sequence="30"
action="act_ctrm_contract_performance"
id="menu_ctrm_operations_contract"/>
<menuitem
name="Scheduling"
parent="menu_ctrm_operations"
sequence="40"
action="act_ctrm_scheduling"
id="menu_ctrm_operations_scheduling"/>
<menuitem

View File

@@ -0,0 +1,14 @@
<form>
<label name="side"/>
<field name="side"/>
<label name="product"/>
<field name="product"/>
<label name="counterparty"/>
<field name="counterparty"/>
<label name="delivery_period"/>
<field name="delivery_period"/>
<label name="status"/>
<field name="status"/>
<label name="open_only"/>
<field name="open_only"/>
</form>

View File

@@ -0,0 +1,19 @@
<tree>
<field name="side"/>
<field name="purchase"/>
<field name="sale"/>
<field name="counterparty" width="160"/>
<field name="product" width="160"/>
<field name="delivery_period"/>
<field name="from_date"/>
<field name="to_date"/>
<field name="contract_quantity" sum="1"/>
<field name="actual_quantity" sum="1"/>
<field name="variance_quantity" sum="1"/>
<field name="unit"/>
<field name="tolerance_min"/>
<field name="tolerance_max"/>
<field name="status"/>
<field name="finished"/>
<field name="lot_count" sum="1"/>
</tree>

View File

@@ -0,0 +1,16 @@
<form>
<label name="from_date"/>
<field name="from_date"/>
<label name="to_date"/>
<field name="to_date"/>
<label name="product"/>
<field name="product"/>
<label name="counterparty"/>
<field name="counterparty"/>
<label name="warehouse"/>
<field name="warehouse"/>
<label name="shipment_state"/>
<field name="shipment_state"/>
<label name="lot_status"/>
<field name="lot_status"/>
</form>

View File

@@ -0,0 +1,22 @@
<tree>
<field name="scheduled_date"/>
<field name="lot" width="140"/>
<field name="product" width="160"/>
<field name="counterparty" width="160"/>
<field name="shipment_in"/>
<field name="warehouse"/>
<field name="from_location"/>
<field name="to_location"/>
<field name="delivery_period"/>
<field name="contract_from"/>
<field name="contract_to"/>
<field name="eta"/>
<field name="etd"/>
<field name="actual_date"/>
<field name="lot_status"/>
<field name="shipment_state"/>
<field name="quantity" sum="1"/>
<field name="unit"/>
<field name="purchase"/>
<field name="sale"/>
</tree>