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 + + + + + + +