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 ); 
 | 
    } 
 | 
  *] 
 | 
} 
 |