yanweiyuan3
2023-08-09 588bc7829387dfc761cc25f06f77d4c81818bd10
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
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 );
    }
  *]
}