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