diff --git a/modules/purchase_trade/__init__.py b/modules/purchase_trade/__init__.py
index aa496be..73c378d 100755
--- a/modules/purchase_trade/__init__.py
+++ b/modules/purchase_trade/__init__.py
@@ -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,
diff --git a/modules/purchase_trade/ctrm_reporting.py b/modules/purchase_trade/ctrm_reporting.py
index 4404472..045505c 100644
--- a/modules/purchase_trade/ctrm_reporting.py
+++ b/modules/purchase_trade/ctrm_reporting.py
@@ -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))
diff --git a/modules/purchase_trade/ctrm_reporting.xml b/modules/purchase_trade/ctrm_reporting.xml
index e4dc7c7..1d3482f 100644
--- a/modules/purchase_trade/ctrm_reporting.xml
+++ b/modules/purchase_trade/ctrm_reporting.xml
@@ -200,6 +200,46 @@
+
+ ctrm.reporting.operations.contract.context
+ form
+ ctrm_contract_performance_context_form
+
+
+ ctrm.reporting.operations.contract
+ tree
+ ctrm_contract_performance_list
+
+
+ Contract Performance
+ ctrm.reporting.operations.contract
+ ctrm.reporting.operations.contract.context
+
+
+
+
+
+
+
+ ctrm.reporting.operations.scheduling.context
+ form
+ ctrm_scheduling_context_form
+
+
+ ctrm.reporting.operations.scheduling
+ tree
+ ctrm_scheduling_list
+
+
+ Scheduling
+ ctrm.reporting.operations.scheduling
+ ctrm.reporting.operations.scheduling.context
+
+
+
+
+
+