| Quintiq file version 2.0 | 
| #parent: #root | 
| Method GetInventoryRHSForBalanceConstraint ( | 
|   const ProductInStockingPointInPeriodPlanningLeaf pispip, | 
|   const RunContextForCapacityPlanning runcontext, | 
|   const LibOpt_Scope scope | 
| ) const as Real | 
| { | 
|   Description: 'Calculate and returns the rhs of the balance constraints (=starting inventory of a pispip considering all decisions outside the optimizer horizon)' | 
|   TextBody: | 
|   [* | 
|     // The RHS of this constraint is the initial inventory based on all decisions outside the scope of the optimizer, it includes: | 
|     // Inventory supply that is greater than 0. If the supply is less than 0, it will be treated as a "must be fulfilled" demands | 
|     supplyquantity := ifexpr( pispip.InventorySupplyQuantity() > 0, pispip.InventorySupplyQuantity(), 0 ); | 
|      | 
|     // Inventory of the previous period if that period exists and is not part of the optimizer horizon | 
|     invstart := 0.0; | 
|     if( pispip.ProductInStockingPoint_MP().EarliestPISPIPInScope() = pispip | 
|         and not isnull( pispip.PreviousPlanningPISPIP() ) )  | 
|     { | 
|       invstart := pispip.InventoryLevelStart(); // this ensures expired quantity is also taken out of the balance  | 
|     } | 
|      | 
|      | 
|     // The sum of the new supply that starts outside the optimizer scope | 
|     sumnewsupply := 0.0;  | 
|     // Add the new supply quantity if it has a dd on a pispip outside the optimizer scope | 
|     traverse( pispip, NewSupplyOperation, newsupply, not newsupply.Quantity() = 0.0 )  | 
|                 | 
|     { | 
|       if ( ( isnull(  newsupply.PeriodTaskOperation().UnitPeriodWhenInScope() )                               // either the period task is not in scope | 
|                 or not newsupply.GetHasAllDDInOptimizerScope( runcontext, scope) ) )                          // or if it is check whether some dependent demand is taking from a pispip not in scope | 
|       { | 
|         sumnewsupply := sumnewsupply + newsupply.Quantity(); | 
|       } | 
|     } | 
|      | 
|     traverse( pispip, NewSupplyTrip.ProductInTrip, pit, not pit.Quantity() = 0.0 )  | 
|     { | 
|       if ( not scope.Contains( pit.ProductInTripInOptimizerRun() ) ) | 
|       { | 
|         sumnewsupply := sumnewsupply + pit.Quantity(); | 
|       } | 
|     } | 
|      | 
|     // Subtract any dependent demands that have a new supply outside the optimizer scope | 
|     sumdd := 0.0; | 
|     if ( this.GetPeriodsFromPeriodTaskOperation() )  | 
|     { | 
|       traverse( pispip, DependentDemandOperation, dd, not dd.Quantity() = 0.0 )  | 
|       { | 
|         if( ( isnull( dd.PeriodTaskOperation().UnitPeriodWhenInScope() ) ) )  // check if period task is not in scope,   | 
|         { | 
|           sumdd := sumdd + dd.Quantity(); | 
|         } | 
|       } | 
|     } | 
|     else | 
|     { | 
|       traverse( pispip, DependentDemandOperation, dd, not dd.Quantity() = 0.0 ) | 
|       { | 
|         if( ( isnull( dd.PeriodTaskOperation().UnitPeriodWhenInScope() )  // either the period task is not in scope,   | 
|                   or not dd.GetHasAllNewSupplyInOptimizerScope( runcontext, scope ) ) )                          // or if it is then check whether some new supply is delivering to a pispip not in scope | 
|         { | 
|           sumdd := sumdd + dd.Quantity(); | 
|         } | 
|       } | 
|     } | 
|      | 
|     traverse( pispip, DependentDemandTrip.ProductInTrip, pit, not pit.Quantity() = 0.0 )  | 
|     { | 
|       if ( not scope.Contains( pit.ProductInTripInOptimizerRun() ) ) | 
|       { | 
|         sumdd := sumdd + pit.Quantity(); | 
|       }   | 
|     } | 
|      | 
|     // subtract any postponed sales demand with original demand not in scope. Note we both handle LeafSalesDemandInPeriod and AggregatedSalesDemandInPeriod here.  | 
|     // if the original is not in scope the postponed sd will not be allowed to be replanned | 
|     postponedsd_outscope := 0.0;  | 
|     traverse( pispip, PlanningBaseSalesDemandInPeriodIsPostponed.astype( SalesDemandInPeriod ), | 
|               sdip, | 
|               not sdip.GetInScope( scope ) | 
|               and guard( not sdip.OriginalSalesDemand().astype( SalesDemandInPeriod ).GetInScope( scope ), false ) | 
|               and not scope.Contains( sdip.OriginalSalesDemand().astype( SalesDemandInPeriod ).SDIPBeforeScopeInRun() ) ) | 
|      | 
|     { | 
|       assert(  sdip.IsPostponed() and not sdip.IsManuallyPostponed(), 'sales demand taken out of balance must be postponed object and not mamually postponed' ); | 
|       postponedsd_outscope := postponedsd_outscope + sdip.FulfilledQuantity();    | 
|     } | 
|      | 
|     // If the user runs a downstream smart plan, then the user may specify a total available supply | 
|     // This total available supply will overrule the real total supply of the pispip | 
|     // Therefore, we will have to update the RHS of the balance constraint to account for the difference | 
|     magicalusersupply := 0.0; | 
|     if( pispip.GetHasMisMatchAvailableUserSupply() ) | 
|     { | 
|       magicalusersupply := pispip.TotalAvailableSupplyUser() - pispip.SupplyQuantity(); | 
|     } | 
|      | 
|     // Calculate the total RHS | 
|     rhs := -( supplyquantity + invstart + sumnewsupply - ( sumdd + postponedsd_outscope ) + magicalusersupply ); | 
|     return rhs | 
|   *] | 
|   InterfaceProperties { Accessibility: 'Module' } | 
| } |