Quintiq file version 2.0
|
#parent: #root
|
Method HandlePostPeriodRollSalesDemands
|
{
|
Description: 'Finalize the period roll, sychornize periods, aggregate/disaggregate sales demands'
|
TextBody:
|
[*
|
// ODE2 May-29-2017 (updated)
|
/* IMPORTANT NOTE
|
This aggregation/disaggregation is performed with the following assumptions:
|
1) Sales demand in periods are generated for Planning AND Base periods
|
2) "Master" quantities are set on Planning sales demand in periods and automatically disaggregated on Base sales demand in periods.
|
As a result, synchronization needs to be perfomed on planning SDiPs only.
|
Any of the above item changed, this is expected to be broken.
|
*/
|
|
traverse( this, SalesDemand, sd )
|
{
|
// Get all SDiPs (Aggregated SDiP and Leaf SDiP) for which period is deleted.
|
deadsdips := selectset( sd, AsMasterSalesDemand, sdip,
|
guard( sdip.AsSalesDemandInPeriodBase().Period_MP().IsDeleted(), true )
|
or not sdip.AsSalesDemandInPeriodBase().Period_MP().IsPlanning() );
|
|
// The available planning pispip within start and end. Depending on the sales demand, planningpispips can contain both leaf or non-leaf products
|
planningpispips := selectset( sd, ProductInStockingPoint_MP.ProductInStockingPointInPeriod, pispip,
|
not pispip.Period_MP().IsDeleted()
|
and pispip.Period_MP().IsPlanning()
|
and pispip.Period_MP().IsInPeriod( sd.StartDate(), sd.EndDate() ) );
|
|
traverse( deadsdips, Elements, deadsdip )
|
{
|
// Propagate relation between SDiP and PISPIP to avoid re-creating a SDiP which was generated at a previous iteration
|
Transaction::Transaction().Propagate( relation( BaseSalesDemandInPeriod, AsPlanningBaseSalesDemandInPeriod ) );
|
|
// Get the planning pispips that can fit in the start and end
|
fitplanningpispips := selectset( planningpispips, Elements, pp,
|
pp.Period_MP().IsInPeriod( deadsdip.StartDate(), deadsdip.EndDate() ) );
|
|
// Aggregate to new planning period
|
if( fitplanningpispips.Size() = 1 )
|
{
|
sdip := null( BaseSalesDemandInPeriod )
|
|
// When the dead sdip is an Aggregated SDiP
|
if( deadsdip.istype( AggregatedSalesDemandInPeriod ) )
|
{
|
// Create aggregated SDiP
|
aggrsdip := sd.GetSalesDemandInPeriod( fitplanningpispips.Element( 0 ) ).astype( AggregatedSalesDemandInPeriod );
|
|
// Create new disaggregated SDiP
|
traverse( deadsdip.astype( AggregatedSalesDemandInPeriod ), DisaggregatedSalesDemandInPeriod, deaddisaggrsdip )
|
{
|
// Propagate relation between SDiP and PISPIP to avoid re-creating a SDiP which was generated at a previous iteration
|
Transaction::Transaction().Propagate( relation( BaseSalesDemandInPeriod, AsPlanningBaseSalesDemandInPeriod ) );
|
|
pisp := ProductInStockingPoint_MP::FindProductInStockingPointTypeIndex( deaddisaggrsdip.ProductID(), deaddisaggrsdip.StockingPointID() );
|
|
pispip := select( pisp, ProductInStockingPointInPeriod, pp,
|
not pp.Period_MP().IsDeleted()
|
and pp.Period_MP().IsPlanning()
|
and pp.Period_MP().IsInPeriod( deaddisaggrsdip.StartDate(), deaddisaggrsdip.EndDate() ) );
|
if( not isnull( pispip ) )
|
{
|
sdip := select( aggrsdip, DisaggregatedSalesDemandInPeriod, dsdip, dsdip.AsSalesDemandInPeriodBase() = pispip );
|
if( isnull( sdip ) )
|
{
|
sdip := DisaggregatedSalesDemandInPeriod::Create( aggrsdip, pispip.astype( ProductInStockingPointInPeriodPlanningLeaf ), 0.0, false );
|
}
|
|
// Keep the has optimizer flag, either one is true, it will be true
|
hasoptimizerqty := sdip.HasOptimizerFulfillment() or deaddisaggrsdip.HasOptimizerFulfillment();
|
|
// Aggregate the quantity
|
aggregatedfulfillment := sdip.OptimizerFulfilledQuantity() + deaddisaggrsdip.OptimizerFulfilledQuantity();
|
|
sdip.UpdateOptimizerFulfillment( hasoptimizerqty, aggregatedfulfillment );
|
}
|
}
|
}
|
// When the dead sdip is a Leaf SDiP
|
else
|
{
|
// Only one, so use element 0
|
sdip := sd.GetSalesDemandInPeriod( fitplanningpispips.Element( 0 ) );
|
|
// Keep the has optimizer flag, either one is true, it will be true
|
hasoptimizerqty := sdip.HasOptimizerFulfillment() or deadsdip.HasOptimizerFulfillment();
|
|
// Aggregate the quantity
|
aggregatedfulfillment := deadsdip.OptimizerFulfilledQuantity() + sdip.OptimizerFulfilledQuantity();
|
|
sdip.UpdateOptimizerFulfillment( hasoptimizerqty, aggregatedfulfillment );
|
}
|
}
|
// Disaggregate to new planning period
|
else
|
{
|
// For greedy distribution
|
remainingqty := deadsdip.OptimizerFulfilledQuantity();
|
|
traverse( fitplanningpispips, Elements, planningpispip )
|
{
|
sdip := sd.GetSalesDemandInPeriod( planningpispip );
|
|
// Propagate the sdip quatnity for greedy distribution
|
Transaction::Transaction().Propagate( attribute( SalesDemandInPeriod, Quantity ) );
|
|
// Greedy
|
optimizerqty := minvalue( sdip.Quantity(), remainingqty );
|
remainingqty := maxvalue( remainingqty - optimizerqty, 0 );
|
sdip.UpdateOptimizerFulfillment( deadsdip.HasOptimizerFulfillment(), optimizerqty );
|
}
|
}
|
}
|
// Delete all dead sales demand in period
|
BaseSalesDemandInPeriod::Delete( deadsdips );
|
}
|
*]
|
}
|