Files

Class Index [+]

Quicksearch

TaskJuggler::TaskScenario

Attributes

isRunAway[R]

Public Class Methods

new(task, scenarioIdx, attributes) click to toggle source

Create a new TaskScenario object.

    # File lib/TaskScenario.rb, line 22
22:     def initialize(task, scenarioIdx, attributes)
23:       super
24: 
25:       # A list of all allocated leaf resources.
26:       @candidates = []
27:     end

Public Instance Methods

Xref() click to toggle source

The parser only stores the full task IDs for each of the dependencies. This function resolves them to task references and checks them. In addition to the ‘depends’ and ‘precedes’ property lists we also keep 4 additional lists. startpreds: All precedessors to the start of this task startsuccs: All successors to the start of this task endpreds: All predecessors to the end of this task endsuccs: All successors to the end of this task Each list element consists of a reference/boolean pair. The reference points to the dependent task and the boolean specifies whether the dependency originates from the end of the task or not.

     # File lib/TaskScenario.rb, line 109
109:     def Xref
110:       @property['depends', @scenarioIdx].each do |dependency|
111:         depTask = checkDependency(dependency, 'depends')
112:         a('startpreds').push([ depTask, dependency.onEnd ])
113:         depTask[dependency.onEnd ? 'endsuccs' : 'startsuccs', @scenarioIdx].
114:           push([ @property, false ])
115:       end
116: 
117:       @property['precedes', @scenarioIdx].each do |dependency|
118:         predTask = checkDependency(dependency, 'precedes')
119:         a('endsuccs').push([ predTask, dependency.onEnd ])
120:         predTask[dependency.onEnd ? 'endpreds' : 'startpreds', @scenarioIdx].
121:           push([@property, true ])
122:       end
123:     end
addBooking(booking) click to toggle source
      # File lib/TaskScenario.rb, line 1084
1084:     def addBooking(booking)
1085:       if a('booking').empty?
1086:         # For the first item use the assignment form so that the 'provided'
1087:         # attribute is set properly.
1088:         @property['booking', @scenarioIdx] = [ booking ]
1089:       else
1090:         @property['booking', @scenarioIdx] << booking
1091:       end
1092:     end
calcCriticalness() click to toggle source

Determine the criticalness of the individual task. This is a measure for the likelyhood that this task will get the resources that it needs to complete the effort. Tasks without effort are not cricital. The only exception are milestones which get an arbitrary value between 0 and 2 based on their priority.

     # File lib/TaskScenario.rb, line 686
686:     def calcCriticalness
687:       @property['criticalness', @scenarioIdx] = 0.0
688:       @property['pathcriticalness', @scenarioIdx] = nil
689: 
690:       # Users feel that milestones are somewhat important. So we use an
691:       # arbitrary value larger than 0 for them. We make it priority dependent,
692:       # so the user has some control over it. Priority 0 is 0, 500 is 1.0 and
693:       # 1000 is 2.0. These values are pretty much randomly picked and probably
694:       # require some more tuning based on real projects.
695:       if a('milestone')
696:         @property['criticalness', @scenarioIdx] = a('priority') / 500.0
697:       end
698: 
699:       # Task without efforts of allocations are not critical.
700:       return if a('effort') <= 0 || @candidates.empty?
701: 
702:       # Determine the average criticalness of all allocated resources.
703:       criticalness = 0.0
704:       @candidates.each do |resource|
705:         criticalness += resource['criticalness', @scenarioIdx]
706:       end
707:       criticalness /= @candidates.length
708: 
709:       # The task criticalness is the product of effort and average resource
710:       # criticalness.
711:       @property['criticalness', @scenarioIdx] = a('effort') * criticalness
712:     end
calcPathCriticalness(atEnd = false) click to toggle source

The path criticalness is a measure for the overall criticalness of the task taking the dependencies into account. The fact that a task is part of a chain of effort-based task raises all the task in the chain to a higher criticalness level than the individual tasks. In fact, the path criticalness of this chain is equal to the sum of the individual criticalnesses of the tasks.

     # File lib/TaskScenario.rb, line 720
720:     def calcPathCriticalness(atEnd = false)
721:       # If we have computed this already, just return the value. If we are only
722:       # at the end of the task, we do not include the criticalness of this task
723:       # as it is not really part of the path.
724:       if a('pathcriticalness')
725:         return a('pathcriticalness') - (atEnd ? 0 : a('criticalness'))
726:       end
727: 
728:       maxCriticalness = 0.0
729: 
730:       if atEnd
731:         # At the end, we only care about pathes through the successors of this
732:         # task or its parent tasks.
733:         if (criticalness = calcPathCriticalnessEndSuccs) > maxCriticalness
734:           maxCriticalness = criticalness
735:         end
736:       else
737:         # At the start of the task, we have two options.
738:         if @property.container?
739:           # For container tasks, we ignore all dependencies and check the pathes
740:           # through all the children.
741:           @property.children.each do |task|
742:             if (criticalness = task.calcPathCriticalness(@scenarioIdx, false)) >
743:               maxCriticalness
744:               maxCriticalness = criticalness
745:             end
746:           end
747:         else
748:           # For leaf tasks, we check all pathes through the start successors and
749:           # then the pathes through the end successors of this task and all its
750:           # parent tasks.
751:           a('startsuccs').each do |task, onEnd|
752:             if (criticalness = task.calcPathCriticalness(@scenarioIdx, onEnd)) >
753:               maxCriticalness
754:               maxCriticalness = criticalness
755:             end
756:           end
757: 
758:           if (criticalness = calcPathCriticalnessEndSuccs) > maxCriticalness
759:             maxCriticalness = criticalness
760:           end
761: 
762:           maxCriticalness += a('criticalness')
763:         end
764:       end
765: 
766:       @property['pathcriticalness', @scenarioIdx] = maxCriticalness
767:     end
canInheritDate?(atEnd) click to toggle source

This function determines if a task can inherit the start or end date from a parent task or the project time frame. atEnd specifies whether the check should be done for the task end (true) or task start (false).

     # File lib/TaskScenario.rb, line 872
872:     def canInheritDate?(atEnd)
873:       # Inheriting a start or end date from the enclosing task or the project
874:       # is allowed for the following scenarios:
875:       #   -  --> -   inherit start and end when no bookings but allocations
876:       #              present
877:       #   -  <-- -   dito
878:       #
879:       #   -  x-> -   inhS
880:       #   -  x-> |   inhS
881:       #   -  x-> -D  inhS
882:       #   -  x-> |D  inhS
883:       #   -  --> |   inhS
884:       #   -  --> -D  inhS
885:       #   -  --> |D  inhS
886:       #   -  <-- |   inhS
887:       #   |  --> -   inhE
888:       #   |  <-x -   inhE
889:       #   |D <-x -   inhE
890:       #   -  <-x -   inhE
891:       #   -D <-x -   inhE
892:       #   |  <-- -   inhE
893:       #   |D <-- -   inhE
894:       #   -D <-- -   inhE
895:       # Return false if we already have a date or if we have a dependency for
896:       # this end.
897:       thisEnd = atEnd ? 'end' : 'start'
898:       hasThisDeps = !a(thisEnd + 'preds').empty? || !a(thisEnd + 'succs').empty?
899:       hasThisSpec = a(thisEnd) || hasThisDeps
900:       return false if hasThisSpec
901: 
902:       # Containter task can inherit the date if they have no dependencies.
903:       return true if @property.container?
904: 
905:       thatEnd = atEnd ? 'start' : 'end'
906:       hasThatDeps = !a(thatEnd + 'preds').empty? || !a(thatEnd + 'succs').empty?
907:       hasThatSpec = a(thatEnd) || hasThatDeps
908: 
909:       # Check for tasks that have no start and end spec, no duration spec but
910:       # allocates. They can inherit the start and end date.
911:       return true if hasThatSpec && !hasDurationSpec? && !a('allocate').empty?
912: 
913:       if a('forward') ^ atEnd
914:         # the scheduling direction is pointing away from this end
915:         return true if hasDurationSpec? || !a('booking').empty?
916: 
917:         return hasThatSpec
918:       else
919:         # the scheduling direction is pointing towards this end
920:         return a(thatEnd) && !hasDurationSpec? &&
921:                a('booking').empty? #&& a('allocate').empty?
922:       end
923:     end
candidates() click to toggle source

This function must be called before prepareScheduling(). It compiles the list of leaf resources that are allocated to this task.

     # File lib/TaskScenario.rb, line 656
656:     def candidates
657:       @candidates = []
658:       a('allocate').each do |allocation|
659:         allocation.candidates.each do |candidate|
660:           candidate.allLeaves.each do |resource|
661:             @candidates << resource unless @candidates.include?(resource)
662:           end
663:         end
664:       end
665:       @candidates
666:     end
checkForLoops(path, atEnd, fromOutside) click to toggle source
     # File lib/TaskScenario.rb, line 497
497:     def checkForLoops(path, atEnd, fromOutside)
498:       # Check if we have been here before on this path.
499:       if path.include?([ @property, atEnd ])
500:         error('loop_detected', "Loop detected at #{atEnd ? 'end' : 'start'} " +
501:                                "of task #{@property.fullId}", false)
502:         skip = true
503:         path.each do |t, e|
504:           if t == @property && e == atEnd
505:             skip = false
506:             next
507:           end
508:           next if skip
509:           info("loop_at_#{e ? 'end' : 'start'}",
510:                "Loop ctnd. at #{e ? 'end' : 'start'} of task #{t.fullId}", t)
511:         end
512:         error('loop_end', "Aborting")
513:       end
514:       # Used for debugging only
515:       if false
516:         pathText = ''
517:         path.each do |t, e|
518:           pathText += "#{t.fullId}(#{e ? 'end' : 'start'}) -> "
519:         end
520:         pathText += "#{@property.fullId}(#{atEnd ? 'end' : 'start'})"
521:         puts pathText
522:       end
523:       return if @deadEndFlags[(atEnd ? 2 : 0) + (fromOutside ? 1 : 0)]
524:       path << [ @property, atEnd ]
525: 
526:       # To find loops we have to traverse the graph in a certain order. When we
527:       # enter a task we can either come from outside or inside. The following
528:       # graph explains these definitions:
529:       #
530:       #             |      /          \      |
531:       #  outside    v    /              \    v   outside
532:       #          +------------------------------+
533:       #          |    /        Task        \    |
534:       #       -->|  o   <---          --->   o  |<--
535:       #          |/ Start                  End \|
536:       #         /+------------------------------+\
537:       #       /     ^                        ^     \
538:       #             |         inside         |
539:       #
540:       # At the top we have the parent task. At the botton the child tasks.
541:       # The horizontal arrors are start predecessors or end successors.
542:       # As the graph is doubly-linked, we need to becareful to only find real
543:       # loops. When coming from outside, we only continue to the inside and vice
544:       # versa. Horizontal moves are only made when we are in a leaf task.
545:       unless atEnd
546:         if fromOutside
547:           if @property.container?
548:             #
549:             #         |
550:             #         v
551:             #       +--------
552:             #    -->| o--+
553:             #       +----|---
554:             #            |
555:             #            V
556:             #
557:             @property.children.each do |child|
558:               child.checkForLoops(@scenarioIdx, path, false, true)
559:             end
560:           else
561:             #         |
562:             #         v
563:             #       +--------
564:             #    -->| o---->
565:             #       +--------
566:             #
567:             checkForLoops(path, true, false) # if a('forward')
568:           end
569:         else
570:           if a('startpreds').empty?
571:             #
572:             #         ^
573:             #         |
574:             #       +-|------
575:             #       | o <--
576:             #       +--------
577:             #         ^
578:             #         |
579:             #
580:             if @property.parent
581:               @property.parent.checkForLoops(@scenarioIdx, path, false, false)
582:             end
583:           else
584: 
585:             #       +--------
586:             #    <---- o <--
587:             #       +--------
588:             #          ^
589:             #          |
590:             #
591:             a('startpreds').each do |task, targetEnd|
592:               task.checkForLoops(@scenarioIdx, path, targetEnd, true)
593:             end
594:           end
595:         end
596:       else
597:         if fromOutside
598:           if @property.container?
599:             #
600:             #          |
601:             #          v
602:             #    --------+
603:             #       +--o |<--
604:             #    ---|----+
605:             #       |
606:             #       v
607:             #
608:             @property.children.each do |child|
609:               child.checkForLoops(@scenarioIdx, path, true, true)
610:             end
611:           else
612:             #          |
613:             #          v
614:             #    --------+
615:             #     <----o |<--
616:             #    --------+
617:             #
618:             checkForLoops(path, false, false) # unless a('forward')
619:           end
620:         else
621:           if a('endsuccs').empty?
622:             #
623:             #          ^
624:             #          |
625:             #    ------|-+
626:             #      --> o |
627:             #    --------+
628:             #          ^
629:             #          |
630:             #
631:             if @property.parent
632:               @property.parent.checkForLoops(@scenarioIdx, path, true, false)
633:             end
634:           else
635:             #    --------+
636:             #      --> o---->
637:             #    --------+
638:             #          ^
639:             #          |
640:             #
641:             a('endsuccs').each do |task, targetEnd|
642:               task.checkForLoops(@scenarioIdx, path, targetEnd, true)
643:             end
644:           end
645:         end
646:       end
647: 
648:       path.pop
649:       @deadEndFlags[(atEnd ? 2 : 0) + (fromOutside ? 1 : 0)] = true
650:       # puts "Finished with #{@property.fullId} #{atEnd ? 'end' : 'start'} " +
651:       #      "#{fromOutside ? 'outside' : 'inside'}"
652:     end
collectTimeOffIntervals(iv, minDuration) click to toggle source

Return a list of intervals that lay within iv and are at least minDuration long and contain no working time.

      # File lib/TaskScenario.rb, line 1326
1326:     def collectTimeOffIntervals(iv, minDuration)
1327:       if a('shifts')
1328:         a('shifts').collectTimeOffIntervals(iv, minDuration)
1329:       else
1330:         []
1331:       end
1332:     end
countResourceAllocations() click to toggle source

This function does some prep work for other functions like calcCriticalness. It compiles a list of all allocated leaf resources and stores it in @candidates. It also adds the allocated effort to the ‘alloctdeffort’ counter of each resource.

     # File lib/TaskScenario.rb, line 672
672:     def countResourceAllocations
673:       return if @candidates.empty? || a('effort') <= 0
674: 
675:       avgEffort = a('effort') / @candidates.length
676:       @candidates.each do |resource|
677:         resource['alloctdeffort', @scenarioIdx] += avgEffort
678:       end
679:     end
earliestStart() click to toggle source

Find the earliest possible start date for the task. This date must be after the end date of all the task that this task depends on. Dependencies may also require a minimum gap between the tasks.

      # File lib/TaskScenario.rb, line 973
 973:     def earliestStart
 974:       # This is the date that we will return.
 975:       startDate = nil
 976:       a('depends').each do |dependency|
 977:         potentialStartDate =
 978:           dependency.task[dependency.onEnd ? 'end' : 'start', @scenarioIdx]
 979:         return nil if potentialStartDate.nil?
 980: 
 981:         # Determine the end date of a 'length' gap.
 982:         dateAfterLengthGap = potentialStartDate
 983:         gapLength = dependency.gapLength
 984:         while gapLength > 0 && dateAfterLengthGap < @project['end'] do
 985:           if @project.isWorkingTime(dateAfterLengthGap)
 986:             gapLength -= 1
 987:           end
 988:           dateAfterLengthGap += @project['scheduleGranularity']
 989:         end
 990: 
 991:         # Determine the end date of a 'duration' gap.
 992:         if dateAfterLengthGap > potentialStartDate + dependency.gapDuration
 993:           potentialStartDate = dateAfterLengthGap
 994:         else
 995:           potentialStartDate += dependency.gapDuration
 996:         end
 997: 
 998:         startDate = potentialStartDate if startDate.nil? ||
 999:                                           startDate < potentialStartDate
1000:       end
1001: 
1002:       # If any of the parent tasks has an explicit start date, the task must
1003:       # start at or after this date.
1004:       task = @property
1005:       while (task = task.parent) do
1006:         if task['start', @scenarioIdx] &&
1007:            (startDate.nil? || task['start', @scenarioIdx] > startDate)
1008:           startDate = task['start', @scenarioIdx]
1009:           break
1010:         end
1011:       end
1012: 
1013:       # When the computed start date is after the already determined end date
1014:       # of the task, the start dependencies were too weak. This happens when
1015:       # task B depends on A and they are specified this way:
1016:       # task A: | --> D-
1017:       # task B: -D <-- |
1018:       if a('end') && startDate > a('end')
1019:         error('weak_start_dep',
1020:               "Task #{@property.fullId} has a too weak start dependencies " +
1021:               "to be scheduled properly.")
1022:       end
1023: 
1024:       startDate
1025:     end
finishScheduling() click to toggle source

When the actual scheduling process has been completed, this function must be called to do some more housekeeping. It computes some derived data based on the just scheduled values.

     # File lib/TaskScenario.rb, line 326
326:     def finishScheduling
327:       calcCompletion
328:       # This list is no longer needed, so let's save some memory. Set it to
329:       # nil so we can detect accidental use.
330:       @candidates = nil
331:     end
getAllocatedTime(startIdx, endIdx, resource = nil) click to toggle source

Compute the total time resource or all resources are allocated during interval specified by startIdx and endIdx.

      # File lib/TaskScenario.rb, line 1274
1274:     def getAllocatedTime(startIdx, endIdx, resource = nil)
1275:       return 0.0 if a('milestone')
1276: 
1277:       allocatedTime = 0.0
1278:       if @property.container?
1279:         @property.children.each do |task|
1280:           allocatedTime += task.getAllocatedTime(@scenarioIdx, startIdx, endIdx,
1281:                                                  resource)
1282:         end
1283:       else
1284:         if resource
1285:           allocatedTime += resource.getAllocatedTime(@scenarioIdx,
1286:                                                      startIdx, endIdx,
1287:                                                      @property)
1288:         else
1289:           a('assignedresources').each do |r|
1290:             allocatedTime += r.getAllocatedTime(@scenarioIdx, startIdx, endIdx,
1291:                                                 @property)
1292:           end
1293:         end
1294:       end
1295:       allocatedTime
1296:     end
getEffectiveWork(startIdx, endIdx, resource = nil) click to toggle source

Compute the effective work a resource or all resources do during the interval specified by startIdx and endIdx. The effective work is the actual work multiplied by the efficiency of the resource.

      # File lib/TaskScenario.rb, line 1301
1301:     def getEffectiveWork(startIdx, endIdx, resource = nil)
1302:       return 0.0 if a('milestone')
1303: 
1304:       workLoad = 0.0
1305:       if @property.container?
1306:         @property.children.each do |task|
1307:           workLoad += task.getEffectiveWork(@scenarioIdx, startIdx, endIdx,
1308:                                             resource)
1309:         end
1310:       else
1311:         if resource
1312:           workLoad += resource.getEffectiveWork(@scenarioIdx, startIdx, endIdx,
1313:                                                 @property)
1314:         else
1315:           a('assignedresources').each do |r|
1316:             workLoad += r.getEffectiveWork(@scenarioIdx, startIdx, endIdx,
1317:                                            @property)
1318:           end
1319:         end
1320:       end
1321:       workLoad
1322:     end
hasDependency?(depType, target, onEnd) click to toggle source

Return true of this Task has a dependency [ target, onEnd ] in the dependency category depType.

     # File lib/TaskScenario.rb, line 127
127:     def hasDependency?(depType, target, onEnd)
128:       a(depType).include?([target, onEnd])
129:     end
hasDurationSpec?() click to toggle source

Return true if the task has a effort, length or duration setting.

     # File lib/TaskScenario.rb, line 966
966:     def hasDurationSpec?
967:       a('length') > 0 || a('duration') > 0 || a('effort') > 0 || a('milestone')
968:     end
hasResourceAllocated?(interval, resource) click to toggle source

Returns true of the resource is assigned to this task or any of its children.

      # File lib/TaskScenario.rb, line 1369
1369:     def hasResourceAllocated?(interval, resource)
1370:       if @property.leaf?
1371:         return resource.allocated?(@scenarioIdx, interval, @property)
1372:       else
1373:         @property.children.each do |t|
1374:           return true if t.hasResourceAllocated?(@scenarioIdx, interval,
1375:                                                  resource)
1376:         end
1377:       end
1378:       false
1379:     end
isDependencyOf(task, depth) click to toggle source

Check if the Task task depends on this task. depth specifies how many dependent task are traversed at max. A value of 0 means no limit.

      # File lib/TaskScenario.rb, line 1336
1336:     def isDependencyOf(task, depth)
1337:       return true if task == @property
1338: 
1339:       # Check if any of the parent tasks is a dependency of _task_.
1340:       t = @property.parent
1341:       while t
1342:         # If the parent is a dependency, than all childs are as well.
1343:         return true if t.isDependencyOf(@scenarioIdx, task, depth)
1344:         t = t.parent
1345:       end
1346: 
1347: 
1348:       a('startsuccs').each do |dep|
1349:         unless dep[1]
1350:           # must be a start->start dependency
1351:           return true if dep[0].isDependencyOf(@scenarioIdx, task, depth)
1352:         end
1353:       end
1354: 
1355:       return false if depth == 1
1356: 
1357:       a('endsuccs').each do |dep|
1358:         unless dep[1]
1359:           # must be an end->start dependency
1360:           return true if dep[0].isDependencyOf(@scenarioIdx, task, depth - 1)
1361:         end
1362:       end
1363: 
1364:       false
1365:     end
latestEnd() click to toggle source

Find the latest possible end date for the task. This date must be before the start date of all the task that this task precedes. Dependencies may also require a minimum gap between the tasks.

      # File lib/TaskScenario.rb, line 1030
1030:     def latestEnd
1031:       # This is the date that we will return.
1032:       endDate = nil
1033:       a('precedes').each do |dependency|
1034:         potentialEndDate =
1035:           dependency.task[dependency.onEnd ? 'end' : 'start', @scenarioIdx]
1036:         return nil if potentialEndDate.nil?
1037: 
1038:         # Determine the end date of a 'length' gap.
1039:         dateBeforeLengthGap = potentialEndDate
1040:         gapLength = dependency.gapLength
1041:         while gapLength > 0 && dateBeforeLengthGap > @project['start'] do
1042:           if @project.isWorkingTime(dateBeforeLengthGap -
1043:                                     @project['scheduleGranularity'])
1044:             gapLength -= 1
1045:           end
1046:           dateBeforeLengthGap -= @project['scheduleGranularity']
1047:         end
1048: 
1049:         # Determine the end date of a 'duration' gap.
1050:         if dateBeforeLengthGap < potentialEndDate - dependency.gapDuration
1051:           potentialEndDate = dateBeforeLengthGap
1052:         else
1053:           potentialEndDate -= dependency.gapDuration
1054:         end
1055: 
1056:         endDate = potentialEndDate if endDate.nil? || endDate > potentialEndDate
1057:       end
1058: 
1059:       # If any of the parent tasks has an explicit end date, the task must end
1060:       # at or before this date.
1061:       task = @property
1062:       while (task = task.parent) do
1063:         if task['end', @scenarioIdx] &&
1064:            (endDate.nil? || task['end', @scenarioIdx] < endDate)
1065:           endDate = task['end', @scenarioIdx]
1066:           break
1067:         end
1068:       end
1069: 
1070:       # When the computed end date is before the already determined start date
1071:       # of the task, the end dependencies were too weak. This happens when
1072:       # task A precedes B and they are specified this way:
1073:       # task A: | --> D-
1074:       # task B: -D <-- |
1075:       if a('start') && endDate > a('start')
1076:         error('weak_end_dep',
1077:               "Task #{@property.fullId} has a too weak end dependencies " +
1078:               "to be scheduled properly.")
1079:       end
1080: 
1081:       endDate
1082:     end
postScheduleCheck() click to toggle source

This function is not essential but does perform a large number of consistency checks. It should be called after the scheduling run has been finished.

     # File lib/TaskScenario.rb, line 336
336:     def postScheduleCheck
337:       @errors = 0
338:       @property.children.each do |task|
339:         @errors += 1 unless task.postScheduleCheck(@scenarioIdx)
340:       end
341: 
342:       # There is no point to check the parent if the child(s) have errors.
343:       return false if @errors > 0
344: 
345:       # Same for runaway tasks. They have already been reported.
346:       if @isRunAway
347:         error('sched_runaway', "Some tasks did not fit into the project time " +
348:               "frame.")
349:       end
350: 
351:       # Make sure the task is marked complete
352:       unless a('scheduled')
353:         error('not_scheduled',
354:               "Task #{@property.fullId} has not been marked as scheduled.")
355:       end
356: 
357:       # If the task has a follower or predecessor that is a runaway this task
358:       # is also incomplete.
359:       (a('startsuccs') + a('endsuccs')).each do |task, onEnd|
360:         return false if task.isRunAway(@scenarioIdx)
361:       end
362:       (a('startpreds') + a('endpreds')).each do |task, onEnd|
363:         return false if task.isRunAway(@scenarioIdx)
364:       end
365: 
366:       # Check if the start time is ok
367:       if a('start').nil?
368:         error('task_start_undef',
369:               "Task #{@property.fullId} has undefined start time")
370:       end
371:       if a('start') < @project['start'] || a('start') > @project['end']
372:         error('task_start_range',
373:               "The start time (#{a('start')}) of task #{@property.fullId} " +
374:               "is outside the project interval (#{@project['start']} - " +
375:               "#{@project['end']})")
376:       end
377:       if !a('minstart').nil? && a('start') < a('minstart')
378:         warning('minstart',
379:                "The start time (#{a('start')}) of task #{@property.fullId} " +
380:                "is too early. Must be after #{a('minstart')}.")
381:       end
382:       if !a('maxstart').nil? && a('start') > a('maxstart')
383:         warning('maxstart',
384:                "The start time (#{a('start')}) of task #{@property.fullId} " +
385:                "is too late. Must be before #{a('maxstart')}.")
386:       end
387:       # Check if the end time is ok
388:       error('task_end_undef',
389:             "Task #{@property.fullId} has undefined end time") if a('end').nil?
390:       if a('end') < @project['start'] || a('end') > @project['end']
391:         error('task_end_range',
392:               "The end time (#{a('end')}) of task #{@property.fullId} " +
393:               "is outside the project interval (#{@project['start']} - " +
394:               "#{@project['end']})")
395:       end
396:       if !a('minend').nil? && a('end') < a('minend')
397:         warning('minend',
398:                 "The end time (#{a('end')}) of task #{@property.fullId} " +
399:                 "is too early. Must be after #{a('minend')}.")
400:       end
401:       if !a('maxend').nil? && a('end') > a('maxend')
402:         warning('maxend',
403:                 "The end time (#{a('end')}) of task #{@property.fullId} " +
404:                 "is too late. Must be before #{a('maxend')}.")
405:       end
406:       # Make sure the start is before the end
407:       if a('start') > a('end')
408:         error('start_after_end',
409:               "The start time (#{a('start')}) of task #{@property.fullId} " +
410:               "is after the end time (#{a('end')}).")
411:       end
412: 
413: 
414:       # Check that tasks fits into parent task.
415:       unless (parent = @property.parent).nil? ||
416:               parent['start', @scenarioIdx].nil? ||
417:               parent['end', @scenarioIdx].nil?
418:         if a('start') < parent['start', @scenarioIdx]
419:           error('task_start_in_parent',
420:                 "The start date (#{a('start')}) of task #{@property.fullId} " +
421:                 "is before the start date of the enclosing task " +
422:                 "#{parent['start', @scenarioIdx]}. ")
423:         end
424:         if a('end') > parent['end', @scenarioIdx]
425:           error('task_end_in_parent',
426:                 "The end date (#{a('end')}) of task #{@property.fullId} " +
427:                 "is after the end date of the enclosing task " +
428:                 "#{parent['end', @scenarioIdx]}. ")
429:         end
430:       end
431: 
432:       # Check that all preceding tasks start/end before this task.
433:       @property['depends', @scenarioIdx].each do |dependency|
434:         task = dependency.task
435:         limit = task[dependency.onEnd ? 'end' : 'start', @scenarioIdx]
436:         next if limit.nil?
437:         if limit > a('start')
438:           error('task_pred_before',
439:                 "Task #{@property.fullId} must start after " +
440:                 "#{dependency.onEnd ? 'end' : 'start'} of task " +
441:                 "#{task.fullId}.")
442:         end
443:       end
444: 
445:       # Check that all following tasks end before this task
446:       @property['precedes', @scenarioIdx].each do |dependency|
447:         task = dependency.task
448:         limit = task[dependency.onEnd ? 'end' : 'start', @scenarioIdx]
449:         next if limit.nil?
450:         if limit < a('end')
451:           error('task_succ_after',
452:                 "Task #{@property.fullId} must end before " +
453:                 "#{dependency.onEnd ? 'end' : 'start'} of task #{task.fullId}.")
454:         end
455:       end
456: 
457:       if a('milestone') && a('start') != a('end')
458:         error('milestone_times_equal',
459:               "Milestone #{@property.fullId} must have identical start and " +
460:               "end date.")
461:       end
462: 
463:       if a('fail') || a('warn')
464:         queryAttrs = { 'project' => @project,
465:                        'scenarioIdx' => @scenarioIdx,
466:                        'property' => @property,
467:                        'scopeProperty' => nil,
468:                        'start' => @project['start'],
469:                        'end' => @project['end'],
470:                        'loadUnit' => :days,
471:                        'numberFormat' => @project['numberFormat'],
472:                        'timeFormat' => @project['timeFormat'],
473:                        'currencyFormat' => @project['currencyFormat'] }
474:         query = Query.new(queryAttrs)
475:         if a('fail') && a('fail').eval(query)
476:           error('task_fail_check',
477:                 "User defined check failed for task #{@property.fullId} \n" +
478:                 "Condition: #{a('fail').to_s}\n" +
479:                 "Result:    #{a('fail').to_s(query)}")
480:         end
481:         if a('warn') && a('warn').eval(query)
482:           warning('task_warn_check',
483:                   "User defined warning triggered for task " +
484:                   "#{@property.fullId} \n" +
485:                   "Condition: #{a('warn').to_s}\n" +
486:                   "Result:    #{a('warn').to_s(query)}")
487:         end
488:       end
489: 
490:       @errors == 0
491:     end
preScheduleCheck() click to toggle source

Before the actual scheduling work can be started, we need to do a few consistency checks on the task.

     # File lib/TaskScenario.rb, line 153
153:     def preScheduleCheck
154:       # Accounts can have sub accounts added after being used in a chargetset.
155:       # So we need to re-test here.
156:       a('chargeset').each do |chargeset|
157:         chargeset.each do |account, share|
158:           unless account.leaf?
159:             error('account_no_leaf',
160:                 "Chargesets may not include group account #{account.fullId}.")
161:           end
162:         end
163:       end
164: 
165:       # Leaf tasks can be turned into containers after bookings have been added.
166:       # We need to check for this.
167:       unless @property.leaf? || a('booking').empty?
168:         error('container_booking',
169:               "Container task #{@property.fullId} may not have bookings.")
170:       end
171: 
172:       # Milestones may not have bookings.
173:       if a('milestone') && !a('booking').empty?
174:         error('milestone_booking',
175:               "Milestone #{@property.fullId} may not have bookings.")
176:       end
177: 
178:       # All 'scheduled' tasks must have a fixed start and end date.
179:       if a('scheduled') && (a('start').nil? || a('end').nil?)
180:         error('not_scheduled',
181:               "Task #{@property.fullId} is marked as scheduled but does not " +
182:               'have a fixed start and end date.')
183:       end
184: 
185:       # If an effort has been specified resources must be allocated as well.
186:       if a('effort') > 0 && a('allocate').empty?
187:         error('effort_no_allocations',
188:               "Task #{@property.fullId} has an effort but no allocations.")
189:       end
190: 
191:       durationSpecs = 0
192:       durationSpecs += 1 if a('effort') > 0
193:       durationSpecs += 1 if a('length') > 0
194:       durationSpecs += 1 if a('duration') > 0
195:       durationSpecs += 1 if a('milestone')
196: 
197:       # The rest of this function performs a number of plausibility tests with
198:       # regards to task start and end critiria. To explain the various cases,
199:       # the following symbols are used:
200:       #
201:       # |: fixed start or end date
202:       # -: no fixed start or end date
203:       # M: Milestone
204:       # D: start or end dependency
205:       # x->: ASAP task with duration criteria
206:       # <-x: ALAP task with duration criteria
207:       # -->: ASAP task without duration criteria
208:       # <--: ALAP task without duration criteria
209: 
210:       if @property.container?
211:         if durationSpecs > 0
212:           error('container_duration',
213:                 "Container task #{@property.fullId} may not have a duration " +
214:                 "or be marked as milestones.")
215:         end
216:       elsif a('milestone')
217:         if durationSpecs > 1
218:           error('milestone_duration',
219:                 "Milestone task #{@property.fullId} may not have a duration.")
220:         end
221:         # Milestones can have the following cases:
222:         #
223:         #   |  M -   ok     |D M -   ok     - M -   err1   -D M -   ok
224:         #   |  M |   err2   |D M |   err2   - M |   ok     -D M |   ok
225:         #   |  M -D  ok     |D M -D  ok     - M -D  ok     -D M -D  ok
226:         #   |  M |D  err2   |D M |D  err2   - M |D  ok     -D M |D  ok
227: 
228:         # err1: no start and end
229:         # already handled by 'start_undetermed' or 'end_undetermed'
230: 
231:         # err2: differnt start and end dates
232:         if a('start') && a('end') && a('start') != a('end')
233:           error('milestone_start_end',
234:                 "Start (#{a('start')}) and end (#{a('end')}) dates of " +
235:                 "milestone task #{@property.fullId} must be identical.")
236:         end
237:       else
238:         #   Error table for non-container, non-milestone tasks:
239:         #   AMP: Automatic milestone promotion for underspecified tasks when
240:         #        no bookings or allocations are present.
241:         #   AMPi: Automatic milestone promotion when no bookings or
242:         #   allocations are present. When no bookings but allocations are
243:         #   present the task inherits start and end date.
244:         #   Ref. implicitXref()|
245:         #   inhS: Inherit start date from parent task or project
246:         #   inhE: Inherit end date from parent task or project
247:         #
248:         #   | x-> -   ok     |D x-> -   ok     - x-> -   inhS   -D x-> -   ok
249:         #   | x-> |   err1   |D x-> |   err1   - x-> |   inhS   -D x-> |   err1
250:         #   | x-> -D  ok     |D x-> -D  ok     - x-> -D  inhS   -D x-> -D  ok
251:         #   | x-> |D  err1   |D x-> |D  err1   - x-> |D  inhS   -D x-> |D  err1
252:         #   | --> -   AMP    |D --> -   AMP    - --> -   AMPi   -D --> -   AMP
253:         #   | --> |   ok     |D --> |   ok     - --> |   inhS   -D --> |   ok
254:         #   | --> -D  ok     |D --> -D  ok     - --> -D  inhS   -D --> -D  ok
255:         #   | --> |D  ok     |D --> |D  ok     - --> |D  inhS   -D --> |D  ok
256:         #   | <-x -   inhE   |D <-x -   inhE   - <-x -   inhE   -D <-x -   inhE
257:         #   | <-x |   err1   |D <-x |   err1   - <-x |   ok     -D <-x |   ok
258:         #   | <-x -D  err1   |D <-x -D  err1   - <-x -D  ok     -D <-x -D  ok
259:         #   | <-x |D  err1   |D <-x |D  err1   - <-x |D  ok     -D <-x |D  ok
260:         #   | <-- -   inhE   |D <-- -   inhE   - <-- -   AMP    -D <-- -   inhE
261:         #   | <-- |   ok     |D <-- |   ok     - <-- |   AMP    -D <-- |   ok
262:         #   | <-- -D  ok     |D <-- -D  ok     - <-- -D  AMP    -D <-- -D  ok
263:         #   | <-- |D  ok     |D <-- |D  ok     - <-- |D  AMP    -D <-- |D  ok
264: 
265:         # These cases are normally autopromoted to milestones or inherit their
266:         # start or end dates. But this only works for tasks that have no
267:         # allocations or bookings.
268:         #   -  --> -
269:         #   |  --> -
270:         #   |D --> -
271:         #   -D --> -
272:         #   -  <-- -
273:         #   -  <-- |
274:         #   -  <-- -D
275:         #   -  <-- |D
276:         if durationSpecs == 0 &&
277:            ((a('forward') && a('end').nil? && !hasDependencies(true)) ||
278:             (!a('forward') && a('start').nil? && !hasDependencies(false)))
279:           error('task_underspecified',
280:                 "Task #{@property.fullId} has too few specifations to be " +
281:                 "scheduled.")
282:         end
283: 
284:         #   err1: Overspecified (12 cases)
285:         #   |  x-> |
286:         #   |  <-x |
287:         #   |  x-> |D
288:         #   |  <-x |D
289:         #   |D x-> |
290:         #   |D <-x |
291:         #   |D <-x |D
292:         #   |D x-> |D
293:         #   -D x-> |
294:         #   -D x-> |D
295:         #   |D <-x -D
296:         #   |  <-x -D
297:         if durationSpecs > 1
298:           error('multiple_durations',
299:                 "Tasks may only have either a duration, length or effort or " +
300:                 "be a milestone.")
301:         end
302:         startSpeced = @property.provided('start', @scenarioIdx)
303:         endSpeced = @property.provided('end', @scenarioIdx)
304:         if ((startSpeced && endSpeced) ||
305:             (hasDependencies(false) && a('forward') && endSpeced) ||
306:             (hasDependencies(true) && !a('forward') && startSpeced)) &&
307:            durationSpecs > 0 && !a('scheduled')
308:           error('task_overspecified',
309:                 "Task #{@property.fullId} has a start, an end and a " +
310:                 'duration specification.')
311:         end
312:       end
313: 
314:       if !a('booking').empty? && !a('forward') && !a('scheduled')
315:         error('alap_booking',
316:               'A task scheduled in ALAP mode may only have bookings if it ' +
317:               'has been marked as fully scheduled. Keep in mind that ' +
318:               'certain attributes like \end\ or \precedes\ automatically ' +
319:               'switch the task to ALAP mode.')
320:       end
321:     end
prepareScheduling() click to toggle source

Call this function to reset all scheduling related data prior to scheduling.

    # File lib/TaskScenario.rb, line 31
31:     def prepareScheduling
32:       @property['startpreds', @scenarioIdx] = []
33:       @property['startsuccs', @scenarioIdx] =[]
34:       @property['endpreds', @scenarioIdx] = []
35:       @property['endsuccs', @scenarioIdx] = []
36: 
37:       @isRunAway = false
38: 
39:       # The following variables are only used during scheduling
40:       @lastSlot = nil
41:       # The 'done' variables count scheduled values in number of time slots.
42:       @doneDuration = 0
43:       @doneLength = 0
44:       # Due to the 'efficiency' factor the effort slots must be a float.
45:       @doneEffort = 0.0
46: 
47:       @startIsDetermed = nil
48:       @endIsDetermed = nil
49:       @tentativeStart = @tentativeEnd = nil
50: 
51:       # To avoid multiple calls to propagateDate() we use these flags to know
52:       # when we've done it already.
53:       @startPropagated = false
54:       @endPropagated = false
55: 
56:       # Inheriting start or end values is a bit tricky. This should really only
57:       # happen if the task is a leaf task and scheduled away from the specified
58:       # date. Since the default meachanism inherites all values, we have to
59:       # delete the wrong ones again.
60:       unless @property.provided('start', @scenarioIdx)
61:         @property['start', @scenarioIdx] = nil
62:       end
63:       unless @property.provided('end', @scenarioIdx)
64:         @property['end', @scenarioIdx] = nil
65:       end
66: 
67:       # Milestones may only have start or end date even when the 'scheduled'
68:       # attribute is set. For further processing, we need to add the missing
69:       # date.
70:       if a('milestone') && a('scheduled')
71:         @property['end', @scenarioIdx] = a('start') if a('start') && !a('end')
72:         @property['start', @scenarioIdx] = a('end') if !a('start') && a('end')
73:       end
74: 
75:       # Collect the limits of this task and all parent tasks into a single
76:       # Array.
77:       @limits = []
78:       task = @property
79:       # Reset the counters of all limits of this task.
80:       task['limits', @scenarioIdx].reset if task['limits', @scenarioIdx]
81:       until task.nil?
82:         if task['limits', @scenarioIdx]
83:           @limits << task['limits', @scenarioIdx]
84:         end
85:         task = task.parent
86:       end
87: 
88:       # Collect the mandatory allocations.
89:       @mandatories = []
90:       a('allocate').each do |allocation|
91:         @mandatories << allocation if allocation.mandatory
92:       end
93: 
94:       bookBookings
95:       markMilestone
96:     end
propagateDate(date, atEnd) click to toggle source

Set a new start or end date and propagate the value to all other task ends that have a direct dependency to this end of the task.

     # File lib/TaskScenario.rb, line 822
822:     def propagateDate(date, atEnd)
823:       thisEnd = atEnd ? 'end' : 'start'
824:       otherEnd = atEnd ? 'start' : 'end'
825: 
826:       # These flags are just used to avoid duplicate calls of this function
827:       # during propagateInitialValues().
828:       if atEnd
829:         @endPropagated = true
830:       else
831:         @startPropagated = true
832:       end
833: 
834:       # For leaf tasks, propagate start may set the date. Container task dates
835:       # are only set in scheduleContainer().
836:       @property[thisEnd, @scenarioIdx] = date if @property.leaf?
837: 
838:       if a('milestone')
839:         # Start and end date of a milestone are identical.
840:         @property['scheduled', @scenarioIdx] = true
841:         if a(otherEnd).nil?
842:           propagateDate(a(thisEnd), !atEnd)
843:         end
844:         Log << "Milestone #{@property.fullId}: #{a('start')} -> #{a('end')}"
845:       end
846: 
847:       # Propagate date to all dependent tasks.
848:       a(thisEnd + 'preds').each do |task, onEnd|
849:         propagateDateToDep(task, onEnd)
850:       end
851:       a(thisEnd + 'succs').each do |task, onEnd|
852:         propagateDateToDep(task, onEnd)
853:       end
854: 
855:       # Propagate date to sub tasks which have only an implicit
856:       # dependency on the parent task and no other criteria for this end of
857:       # the task.
858:       @property.children.each do |task|
859:         if task.canInheritDate?(@scenarioIdx, atEnd)
860:           task.propagateDate(@scenarioIdx, date, atEnd)
861:         end
862:       end
863: 
864:       # The date propagation might have completed the date set of the enclosing
865:       # containter task. If so, we can schedule it as well.
866:       @property.parent.scheduleContainer(@scenarioIdx) if !@property.parent.nil?
867:     end
propagateInitialValues() click to toggle source
     # File lib/TaskScenario.rb, line 131
131:     def propagateInitialValues
132:       unless @startPropagated
133:         if a('start')
134:           propagateDate(a('start'), false)
135:         elsif @property.parent.nil? &&
136:           @property.canInheritDate?(@scenarioIdx, false)
137:           propagateDate(@project['start'], false)
138:         end
139:       end
140: 
141:       unless @endPropagated
142:         if a('end')
143:           propagateDate(a('end'), true)
144:         elsif @property.parent.nil? &&
145:           @property.canInheritDate?(@scenarioIdx, true)
146:           propagateDate(@project['end'], true)
147:         end
148:       end
149:     end
query_complete(query) click to toggle source
      # File lib/TaskScenario.rb, line 1094
1094:     def query_complete(query)
1095:       if @property.leaf?
1096:         query.sortable = query.numerical = complete = a('complete').to_i
1097:         query.string = "#{complete}%"
1098:       else
1099:         query.string = ''
1100:       end
1101:     end
query_cost(query) click to toggle source

Compute the cost generated by this Task for a given Account during a given interval. If a Resource is provided as scopeProperty only the cost directly generated by the resource is taken into account.

      # File lib/TaskScenario.rb, line 1106
1106:     def query_cost(query)
1107:       if query.costAccount
1108:         query.sortable = query.numerical = cost =
1109:           turnover(query.startIdx, query.endIdx, query.costAccount,
1110:                    query.scopeProperty)
1111:         query.string = query.currencyFormat.format(cost)
1112:       else
1113:         query.string = 'No cost account'
1114:       end
1115:     end
query_duration(query) click to toggle source

The duration of the task. After scheduling, it can be determined for all tasks. Also for those who did not have a ‘duration’ attribute.

      # File lib/TaskScenario.rb, line 1119
1119:     def query_duration(query)
1120:       query.sortable = query.numerical = duration =
1121:         (a('end') - a('start')) / (60 * 60 * 24)
1122:       query.string = query.scaleDuration(duration)
1123:     end
query_effort(query) click to toggle source

The effort allocated for the task in the specified interval. In case a Resource is given as scope property only the effort allocated for this resource is taken into account.

      # File lib/TaskScenario.rb, line 1155
1155:     def query_effort(query)
1156:       query.sortable = query.numerical = work =
1157:         getEffectiveWork(query.startIdx, query.endIdx, query.scopeProperty)
1158:       query.string = query.scaleLoad(work)
1159:     end
query_effortdone(query) click to toggle source

The completed (as of ‘now’) effort allocated for the task in the specified interval. In case a Resource is given as scope property only the effort allocated for this resource is taken into account.

      # File lib/TaskScenario.rb, line 1128
1128:     def query_effortdone(query)
1129:       # For this query, we always override the query period.
1130:       query.sortable = query.numerical = effort =
1131:         getEffectiveWork(@project.dateToIdx(@project['start']),
1132:                          @project.dateToIdx(@project['now']),
1133:                          query.scopeProperty)
1134:       query.string = query.scaleLoad(effort)
1135:     end
query_effortleft(query) click to toggle source

The remaining (as of ‘now’) effort allocated for the task in the specified interval. In case a Resource is given as scope property only the effort allocated for this resource is taken into account.

      # File lib/TaskScenario.rb, line 1141
1141:     def query_effortleft(query)
1142:       # For this query, we always override the query period.
1143:       query.start = @project['now']
1144:       query.end = @project['end']
1145:       query.sortable = query.numerical = effort =
1146:         getEffectiveWork(@project.dateToIdx(@project['now']),
1147:                          @project.dateToIdx(@project['end']),
1148:                          query.scopeProperty)
1149:       query.string = query.scaleLoad(effort)
1150:     end
query_followers(query) click to toggle source
      # File lib/TaskScenario.rb, line 1161
1161:     def query_followers(query)
1162:       str = ''
1163: 
1164:       # First gather the task that depend on the start of this task.
1165:       a('startsuccs').each do |task, onEnd|
1166:         str += "* <nowiki>#{task.name}</nowiki> (#{task.fullId}) "
1167:         if onEnd
1168:           taskEnd = task['end', query.scenarioIdx].to_s(query.timeFormat)
1169:           str += "[->[ #{taskEnd}"
1170:         else
1171:           taskStart = task['start', query.scenarioIdx].to_s(query.timeFormat)
1172:           str += "[->] #{taskStart}"
1173:         end
1174:         str += "\n"
1175:       end
1176:       # Than add the tasks that depend on the end of this task.
1177:       a('endsuccs').each do |task, onEnd|
1178:         str += "* <nowiki>#{task.name}</nowiki> (#{task.fullId}) "
1179:         if onEnd
1180:           taskEnd = task['end', query.scenarioIdx].to_s(query.timeFormat)
1181:           str += "]->[ #{taskEnd}"
1182:         else
1183:           taskStart = task['start', query.scenarioIdx].to_s(query.timeFormat)
1184:           str += "]->] #{taskStart}"
1185:         end
1186:         str += "\n"
1187:       end
1188: 
1189:       rText = RichText.new(str)
1190:       query.rti = rText.generateIntermediateFormat
1191:     end
query_precursors(query) click to toggle source
      # File lib/TaskScenario.rb, line 1193
1193:     def query_precursors(query)
1194:       str = ''
1195: 
1196:       # First gather the task that depend on the start of this task.
1197:       a('startpreds').each do |task, onEnd|
1198:         str += "* <nowiki>#{task.name}</nowiki> (#{task.fullId}) "
1199:         if onEnd
1200:           taskEnd = task['end', query.scenarioIdx].to_s(query.timeFormat)
1201:           str += "]->[ #{taskEnd}"
1202:         else
1203:           taskStart = task['start', query.scenarioIdx].to_s(query.timeFormat)
1204:           str += "[->[ #{taskStart}"
1205:         end
1206:         str += "\n"
1207:       end
1208:       # Than add the tasks that depend on the end of this task.
1209:       a('endpreds').each do |task, onEnd|
1210:         str += "* <nowiki>#{task.name}</nowiki> (#{task.fullId}) "
1211:         if onEnd
1212:           taskEnd = task['end', query.scenarioIdx].to_s(query.timeFormat)
1213:           str += "[->] #{taskEnd}"
1214:         else
1215:           taskStart = task['start', query.scenarioIdx].to_s(query.timeFormat)
1216:           str += "]->] #{taskStart}"
1217:         end
1218:         str += "\n"
1219:       end
1220: 
1221:       rText = RichText.new(str)
1222:       query.rti = rText.generateIntermediateFormat
1223:     end
query_resources(query) click to toggle source

A list of the resources that have been allocated to work on the task in the report time frame.

      # File lib/TaskScenario.rb, line 1227
1227:     def query_resources(query)
1228:       list = ''
1229:       a('assignedresources').each do |resource|
1230:         if getAllocatedTime(query.startIdx, query.endIdx, resource) > 0.0
1231:           list += ', ' unless list.empty?
1232:           list += "#{resource.name} (#{resource.fullId})"
1233:         end
1234:       end
1235:       query.sortable = query.string = list
1236:       rText = RichText.new(list)
1237:       query.rti = rText.generateIntermediateFormat
1238:     end
query_revenue(query) click to toggle source

Compute the revenue generated by this Task for a given Account during a given interval. If a Resource is provided as scopeProperty only the revenue directly generated by the resource is taken into account.

      # File lib/TaskScenario.rb, line 1243
1243:     def query_revenue(query)
1244:       if query.revenueAccount
1245:         query.sortable = query.numerical = revenue =
1246:           turnover(query.startIdx, query.endIdx, query.revenueAccount,
1247:                    query.scopeProperty)
1248:         query.string = query.currencyFormat.format(revenue)
1249:       else
1250:         query.string = 'No revenue account'
1251:       end
1252:     end
query_targets(query) click to toggle source
      # File lib/TaskScenario.rb, line 1254
1254:     def query_targets(query)
1255:       targetList = PropertyList.new(@project.tasks, false)
1256:       targets(targetList)
1257:       targetList.delete(@property)
1258:       targetList.setSorting([['start', true, @scenarioIdx],
1259:                              ['seqno', true, 1 ]])
1260:       targetList.sort!
1261: 
1262:       res = ''
1263:       targetList.each do |task|
1264:         date = task['start', @scenarioIdx].to_s(@property.project['timeFormat'])
1265:         res += "# #{task.name} (#{task.fullId}) #{date}\n"
1266:       end
1267:       rText = RichText.new(res)
1268:       query.rti = rText.generateIntermediateFormat
1269:     end
readyForScheduling?() click to toggle source

Check if the task is ready to be scheduled. For this it needs to have at least one specified end date and a duration criteria or the other end date.

     # File lib/TaskScenario.rb, line 772
772:     def readyForScheduling?
773:       return false if a('scheduled') || @isRunAway
774: 
775:       if a('forward')
776:         return true if a('start') && (hasDurationSpec? || a('end'))
777:       else
778:         return true if a('end') && (hasDurationSpec? || a('start'))
779:       end
780: 
781:       false
782:     end
resetLoopFlags() click to toggle source
     # File lib/TaskScenario.rb, line 493
493:     def resetLoopFlags
494:       @deadEndFlags = Array.new(4, false)
495:     end
schedule() click to toggle source

This function is the entry point for the core scheduling algorithm. It schedules the task to completion. The function returns true if a start or end date has been determined and other tasks may be ready for scheduling now.

     # File lib/TaskScenario.rb, line 788
788:     def schedule
789:       # Is the task scheduled from start to end or vice versa?
790:       forward = a('forward')
791:       # The task may not excede the project interval.
792:       limit = @project[forward ? 'end' : 'start']
793:       # Number of seconds per time slot.
794:       slotDuration = @project['scheduleGranularity']
795:       slot = nextSlot(slotDuration)
796: 
797:       # Schedule all time slots from slot in the scheduling direction until
798:       # the task is completed or a problem has been found.
799:       while !scheduleSlot(slot, slotDuration)
800:         if forward
801:           # The task is scheduled from start to end.
802:           slot += slotDuration
803:           if slot > limit
804:             markAsRunaway
805:             return false
806:           end
807:         else
808:           # The task is scheduled from end to start.
809:           slot -= slotDuration
810:           if slot < limit
811:             markAsRunaway
812:             return false
813:           end
814:         end
815:       end
816: 
817:       true
818:     end
scheduleContainer() click to toggle source

Find the smallest possible interval that encloses all child tasks. Abort the operation if any of the child tasks are not yet scheduled.

     # File lib/TaskScenario.rb, line 927
927:     def scheduleContainer
928:       return if a('scheduled') || !@property.container?
929: 
930:       nStart = nil
931:       nEnd = nil
932: 
933:       @property.children.each do |task|
934:         # Abort if a child has not yet been scheduled.
935:         return unless task['scheduled', @scenarioIdx]
936: 
937:         if nStart.nil? || task['start', @scenarioIdx] < nStart
938:           nStart = task['start', @scenarioIdx]
939:         end
940:         if nEnd.nil? || task['end', @scenarioIdx] > nEnd
941:           nEnd = task['end', @scenarioIdx]
942:         end
943:       end
944: 
945:       @property['scheduled', @scenarioIdx] = true
946: 
947:       startSet = endSet = false
948:       # Propagate the dates to other dependent tasks.
949:       if a('start').nil? || a('start') > nStart
950:         @property['start', @scenarioIdx] = nStart
951:         startSet = true
952:       end
953:       if a('end').nil? || a('end') < nEnd
954:         @property['end', @scenarioIdx] = nEnd
955:         endSet = true
956:       end
957:       Log << "Container task #{@property.fullId}: #{a('start')} -> #{a('end')}"
958: 
959:       # If we have modified the start or end date, we need to communicate this
960:       # new date to surrounding tasks.
961:       propagateDate(nStart, false) if startSet
962:       propagateDate(nEnd, true) if endSet
963:     end

Private Instance Methods

bookBookings() click to toggle source

Register the user provided bookings with the Resource scoreboards. A booking describes the assignment of a Resource to a certain Task for a specified Interval.

      # File lib/TaskScenario.rb, line 1612
1612:     def bookBookings
1613:       scheduled = a('scheduled')
1614:       a('booking').each do |booking|
1615:         unless booking.resource.leaf?
1616:           error('booking_resource_not_leaf',
1617:                 "Booked resources may not be group resources", true,
1618:                 booking.sourceFileInfo)
1619:         end
1620:         unless a('forward') || a('scheduled')
1621:           error('booking_forward_only',
1622:                 "Only forward scheduled tasks may have booking statements.")
1623:         end
1624:         slotDuration = @project['scheduleGranularity']
1625:         booking.intervals.each do |interval|
1626:           startIdx = @project.dateToIdx(interval.start)
1627:           date = interval.start
1628:           endIdx = @project.dateToIdx(interval.end)
1629:           tEnd = nil
1630:           startIdx.upto(endIdx - 1) do |idx|
1631:             tEnd = date + slotDuration
1632:             if booking.resource.bookBooking(@scenarioIdx, idx, booking)
1633:               # Booking was successful for this time slot.
1634:               @doneEffort += booking.resource['efficiency', @scenarioIdx]
1635: 
1636:               # Set start and lastSlot if appropriate. The task start will be
1637:               # set to the begining of the first booked slot. The lastSlot
1638:               # will be set to the last booked slot
1639:               @lastSlot = date if @lastSlot.nil? || date > @lastSlot
1640:               @tentativeEnd = tEnd if @tentativeEnd.nil? ||
1641:                 @tentativeEnd < tEnd
1642:               if !a('scheduled') && (a('start').nil? || date < a('start'))
1643:                 @property['start', @scenarioIdx] = date
1644:               end
1645: 
1646:               unless a('assignedresources').include?(booking.resource)
1647:                 @property['assignedresources', @scenarioIdx] << booking.resource
1648:               end
1649:             end
1650:             if a('length') > 0 && @project.isWorkingTime(date, tEnd)
1651:               # For tasks with a 'length' we track the covered work time and
1652:               # set the task to 'scheduled' when we have enough length.
1653:               @doneLength += 1
1654:               if !scheduled && @doneLength >= a('length')
1655:                 @property['end', @scenarioIdx] = tEnd
1656:                 @property['scheduled', @scenarioIdx] = true
1657:               end
1658:             end
1659:             date = tEnd
1660:           end
1661:           if a('duration') > 0 && @tentativeEnd
1662:             @doneDuration = ((@tentativeEnd - a('start')) /
1663:                              @project['scheduleGranularity']).to_i
1664:             if !scheduled && @doneDuration >= a('duration')
1665:               @property['end', @scenarioIdx] = @tentativeEnd
1666:               @property['scheduled', @scenarioIdx] = true
1667:             end
1668:           end
1669:         end
1670:       end
1671:     end
bookResource(resource, sbIdx, date) click to toggle source
      # File lib/TaskScenario.rb, line 1572
1572:     def bookResource(resource, sbIdx, date)
1573:       booked = false
1574:       resource.allLeaves.each do |r|
1575:         # Prevent overbooking when multiple resources are allocated and
1576:         # available.
1577:         break if a('effort') > 0 && @doneEffort >= a('effort')
1578: 
1579:         if r.book(@scenarioIdx, sbIdx, @property)
1580: 
1581:           if a('assignedresources').empty?
1582:             if a('forward')
1583:               @property['start', @scenarioIdx] = @project.idxToDate(sbIdx)
1584:             else
1585:               @property['end', @scenarioIdx] = @project.idxToDate(sbIdx + 1)
1586:             end
1587:           end
1588: 
1589:           @tentativeStart = @project.idxToDate(sbIdx)
1590:           @tentativeEnd = @project.idxToDate(sbIdx + 1)
1591: 
1592:           @doneEffort += r['efficiency', @scenarioIdx]
1593:           # Limits do not take efficiency into account. Limits are usage limits,
1594:           # not effort limits.
1595:           @limits.each do |limit|
1596:             limit.inc(date)
1597:           end
1598: 
1599:           unless a('assignedresources').include?(r)
1600:             @property['assignedresources', @scenarioIdx] << r
1601:           end
1602:           booked = true
1603:         end
1604:       end
1605: 
1606:       booked
1607:     end
bookResources(date, slotDuration) click to toggle source
      # File lib/TaskScenario.rb, line 1491
1491:     def bookResources(date, slotDuration)
1492:       # If there are no allocations defined, we can't do any bookings.
1493:       # In projection mode we do not allow bookings prior to the current date
1494:       # for any task (in strict mode) or tasks which have user specified
1495:       # bookings (sloppy mode).
1496:       if a('allocate').empty? ||
1497:          (@project.scenario(@scenarioIdx).get('projection') &&
1498:           date < @project['now'] &&
1499:           (@project.scenario(@scenarioIdx).get('strict') ||
1500:            a('assignedresources').empty?))
1501:         return
1502:       end
1503: 
1504:       # If the task has shifts to limit the allocations, we check that we are
1505:       # within a defined shift interval. If yes, we need to be on shift to
1506:       # continue.
1507:       if (shifts = a('shifts')) && shifts.assigned?(date)
1508:          return if !shifts.onShift?(date)
1509:       end
1510: 
1511:       # If the task has allocation limits we need to make sure that none of them
1512:       # is already exceeded.
1513:       @limits.each do |limit|
1514:         return if !limit.ok?(date)
1515:       end
1516: 
1517:       sbIdx = @project.dateToIdx(date)
1518: 
1519:       # We first have to make sure that if there are mandatory resources
1520:       # that these are all available for the time slot.
1521:       takenMandatories = []
1522:       @mandatories.each do |allocation|
1523:         return unless allocation.onShift?(date)
1524: 
1525:         # For mandatory allocations with alternatives at least one of the
1526:         # alternatives must be available.
1527:         found = false
1528:         allocation.candidates(@scenarioIdx).each do |candidate|
1529:           # When a resource group is marked mandatory, all members of the
1530:           # group must be available.
1531:           allAvailable = true
1532:           candidate.allLeaves.each do |resource|
1533:             if !resource.available?(@scenarioIdx, sbIdx) ||
1534:                takenMandatories.include?(resource)
1535:               allAvailable = false
1536:               break
1537:             else
1538:               takenMandatories << resource
1539:             end
1540:           end
1541:           if allAvailable
1542:             found = true
1543:             break
1544:           end
1545:         end
1546: 
1547:         # At least one mandatory resource is not available. We cannot continue.
1548:         return unless found
1549:       end
1550: 
1551:       iv = Interval.new(date, date + slotDuration)
1552:       a('allocate').each do |allocation|
1553:         next unless allocation.onShift?(date)
1554: 
1555:         # In case we have a persistent allocation we need to check if there is
1556:         # already a locked resource and use it.
1557:         if allocation.persistent && !allocation.lockedResource.nil?
1558:           bookResource(allocation.lockedResource, sbIdx, date)
1559:         else
1560:           # If not, we create a list of candidates in the proper order and
1561:           # assign the first one available.
1562:           allocation.candidates(@scenarioIdx).each do |candidate|
1563:             if bookResource(candidate, sbIdx, date)
1564:               allocation.lockedResource = candidate
1565:               break
1566:             end
1567:           end
1568:         end
1569:       end
1570:     end
calcCompletion() click to toggle source

Calculate the current completion degree for tasks that have no user specified completion value.

      # File lib/TaskScenario.rb, line 1830
1830:     def calcCompletion
1831:       # If the user provided a completion degree we are not touching it.
1832:       if @property.provided('complete', @scenarioIdx)
1833:         calcStatus
1834:         return
1835:       end
1836: 
1837:       if a('start').nil? || a('end').nil?
1838:         @property['complete', @scenarioIdx] = 0.0
1839:         @property['status', @scenarioIdx] = 'unknown'
1840:         return
1841:       end
1842: 
1843:       if a('milestone')
1844:         @property['complete', @scenarioIdx] =
1845:           @property['end', @scenarioIdx] <= @project['now'] ? 100.0 : 0.0
1846:         @property['status', @scenarioIdx] =
1847:           a('end') <= @project['now'] ? 'done' : 'not reached'
1848:       else
1849:         completion = 0.0
1850:         if a('end') <= @project['now']
1851:           # The task has ended already. It's 100% complete.
1852:           completion = 100.0
1853:         elsif @project['now'] <= a('start')
1854:           # The task has not started yet. Its' 0% complete.
1855:           completion = 0.0
1856:         else
1857:           # The task is in progress. Calculate the current completion
1858:           # degree.
1859:           if @property.leaf? && a('effort') > 0
1860:             # Effort based leaf tasks. The completion degree is the percantage
1861:             # of effort that has been done already.
1862:             done = getEffectiveWork(@project.dateToIdx(a('start')),
1863:                                     @project.dateToIdx(@project['now']))
1864:             total = @project.convertToDailyLoad(
1865:               a('effort') * @project['scheduleGranularity'])
1866:             completion = done / total * 100.0
1867:           else
1868:             # Container tasks and length/duration leaf tasks. There is no way
1869:             # we can compute the completion degree of a container task with a
1870:             # mix of effort and duration task in a meaningful way.  So, we
1871:             # just go by duration.
1872:             completion = ((@project['now'] - a('start')) /
1873:                           (a('end') - a('start'))) * 100.0
1874:           end
1875:         end
1876:         @property['complete', @scenarioIdx] = completion
1877:         calcStatus
1878:       end
1879:     end
calcPathCriticalnessEndSuccs() click to toggle source

This is a helper function for calcPathCriticalness(). It computes the larges criticalness of the pathes through the end-successors of this task and all its parent tasks.

      # File lib/TaskScenario.rb, line 1806
1806:     def calcPathCriticalnessEndSuccs
1807:       maxCriticalness = 0.0
1808:       # Gather a list of all end-successors of this task and its parent task.
1809:       tList = []
1810:       p = @property
1811:       while (p)
1812:         p['endsuccs', @scenarioIdx].each do |task, onEnd|
1813:           tList << [ task, onEnd ] unless tList.include?([ task, onEnd ])
1814:         end
1815:         p = p.parent
1816:       end
1817: 
1818:       tList.each do |task, onEnd|
1819:         if (criticalness = task.calcPathCriticalness(@scenarioIdx, onEnd)) >
1820:           maxCriticalness
1821:           maxCriticalness = criticalness
1822:         end
1823:       end
1824: 
1825:       maxCriticalness
1826:     end
calcStatus() click to toggle source

Calculate the status of the task based on the ‘complete’ attribute.

      # File lib/TaskScenario.rb, line 1882
1882:     def calcStatus
1883:       @property['status', @scenarioIdx] =
1884:         if a('complete') == 0.0
1885:           'not started'
1886:         elsif a('complete') >= 100.0
1887:           'done'
1888:         else
1889:           'in progress'
1890:         end
1891:     end
checkDependency(dependency, depType) click to toggle source
      # File lib/TaskScenario.rb, line 1724
1724:     def checkDependency(dependency, depType)
1725:       if (depTask = dependency.resolve(@project)).nil?
1726:         # Remove the broken dependency. It could cause trouble later on.
1727:         @property[depType, @scenarioIdx].delete(dependency)
1728:         error('task_depend_unknown',
1729:               "Task #{@property.fullId} has unknown #{depType} " +
1730:               "#{dependency.taskId}")
1731:       end
1732: 
1733:       if depTask == @property
1734:         # Remove the broken dependency. It could cause trouble later on.
1735:         @property[depType, @scenarioIdx].delete(dependency)
1736:         error('task_depend_self', "Task #{@property.fullId} cannot " +
1737:               "depend on self")
1738:       end
1739: 
1740:       if depTask.isChildOf?(@property)
1741:         # Remove the broken dependency. It could cause trouble later on.
1742:         @property[depType, @scenarioIdx].delete(dependency)
1743:         error('task_depend_child',
1744:               "Task #{@property.fullId} cannot depend on child " +
1745:               "#{depTask.fullId}")
1746:       end
1747: 
1748:       if @property.isChildOf?(depTask)
1749:         # Remove the broken dependency. It could cause trouble later on.
1750:         @property[depType, @scenarioIdx].delete(dependency)
1751:         error('task_depend_parent',
1752:               "Task #{@property.fullId} cannot depend on parent " +
1753:               "#{depTask.fullId}")
1754:       end
1755: 
1756:       @property[depType, @scenarioIdx].each do |dep|
1757:         if dep.task == depTask && dep != dependency
1758:           # Remove the broken dependency. It could cause trouble later on.
1759:           @property[depType, @scenarioIdx].delete(dependency)
1760:           error('task_depend_multi',
1761:                 "No need to specify dependency #{depTask.fullId} multiple " +
1762:                 "times for task #{@property.fullId}.")
1763:         end
1764:       end
1765: 
1766:       depTask
1767:     end
hasDependencies(atEnd) click to toggle source

This function checks if the task has a dependency on another task or fixed date for a certain end. If atEnd is true, the task end will be checked. Otherwise the start.

      # File lib/TaskScenario.rb, line 1676
1676:     def hasDependencies(atEnd)
1677:       thisEnd = atEnd ? 'end' : 'start'
1678:       !a(thisEnd + 'succs').empty? || !a(thisEnd + 'preds').empty?
1679:     end
hasSuccessors() click to toggle source

Return true if this task or any of its parent tasks has at least one sucessor task.

      # File lib/TaskScenario.rb, line 1683
1683:     def hasSuccessors
1684:       t = @property
1685:       while t
1686:         return true unless t['endsuccs', @scenarioIdx].empty?
1687:         t = t.parent
1688:       end
1689: 
1690:       false
1691:     end
markAsRunaway() click to toggle source
      # File lib/TaskScenario.rb, line 1693
1693:     def markAsRunaway
1694:       warning('runaway', "Task #{@property.fullId} does not fit into " +
1695:                          "project time frame")
1696: 
1697:       @isRunAway = true
1698:     end
markMilestone() click to toggle source

This function determines if a task is really a milestones and marks them accordingly.

      # File lib/TaskScenario.rb, line 1702
1702:     def markMilestone
1703:       return if @property.container? || hasDurationSpec? ||
1704:         !a('booking').empty? || !a('allocate').empty?
1705: 
1706:       # The following cases qualify for an automatic milestone promotion.
1707:       #   -  --> -
1708:       #   |  --> -
1709:       #   |D --> -
1710:       #   -D --> -
1711:       #   -  <-- -
1712:       #   -  <-- |
1713:       #   -  <-- -D
1714:       #   -  <-- |D
1715:       hasStartSpec = !a('start').nil? || !a('depends').empty?
1716:       hasEndSpec = !a('end').nil? || !a('precedes').empty?
1717: 
1718:       @property['milestone', @scenarioIdx] =
1719:         (hasStartSpec && a('forward') && !hasEndSpec) ||
1720:         (!hasStartSpec && !a('forward') && hasEndSpec) ||
1721:         (!hasStartSpec && !hasEndSpec)
1722:     end
nextSlot(slotDuration) click to toggle source

Return the date of the next slot this task wants to have scheduled. This must either be the first slot ever or it must be directly adjecent to the previous slot. If this task has not yet been scheduled at all, @lastSlot is still nil. Otherwise it contains the date of the last scheduled slot.

      # File lib/TaskScenario.rb, line 1481
1481:     def nextSlot(slotDuration)
1482:       return nil if a('scheduled') || @isRunAway
1483: 
1484:       if a('forward')
1485:         @lastSlot.nil? ? a('start') : @lastSlot + slotDuration
1486:       else
1487:         @lastSlot.nil? ? a('end') - slotDuration : @lastSlot - slotDuration
1488:       end
1489:     end
propagateDateToDep(task, atEnd) click to toggle source

This function is called to propagate the start or end date of the current task to a dependend Task task. If atEnd is true, the date should be propagated to the end of the task, otherwise to the start.

      # File lib/TaskScenario.rb, line 1778
1778:     def propagateDateToDep(task, atEnd)
1779:       #puts "Propagate #{atEnd ? 'end' : 'start'} to dep. #{task.fullId}"
1780:       # Don't propagate if the task is already completely scheduled or is a
1781:       # container.
1782:       return if task['scheduled', @scenarioIdx] || task.container?
1783: 
1784:       # Don't propagate if the task already has a date for that end.
1785:       return unless task[atEnd ? 'end' : 'start', @scenarioIdx].nil?
1786: 
1787:       # Don't propagate if the task has a duration or is a milestone and the
1788:       # task end to set is in the scheduling direction.
1789:       return if task.hasDurationSpec?(@scenarioIdx) &&
1790:                 !(atEnd ^ task['forward', @scenarioIdx])
1791: 
1792:       # Check if all other dependencies for that task end have been determined
1793:       # already and use the latest or earliest possible date. Don't propagate
1794:       # if we don't have all dates yet.
1795:       return if (nDate = (atEnd ? task.latestEnd(@scenarioIdx) :
1796:                                   task.earliestStart(@scenarioIdx))).nil?
1797: 
1798:       # Looks like it is ok to propagate the date.
1799:       task.propagateDate(@scenarioIdx, nDate, atEnd)
1800:       # puts "Propagate #{atEnd ? 'end' : 'start'} to dep. #{task.fullId} done"
1801:     end
scheduleSlot(slot, slotDuration) click to toggle source
      # File lib/TaskScenario.rb, line 1382
1382:     def scheduleSlot(slot, slotDuration)
1383:       # Tasks must always be scheduled in a single contigous fashion. @lastSlot
1384:       # indicates the slot that was used for the previous call. Depending on the
1385:       # scheduling direction the next slot must be scheduled either right before
1386:       # or after this slot. If the current slot is not directly aligned, we'll
1387:       # wait for another call with a proper slot. The function returns true
1388:       # only if a slot could be scheduled.
1389:       if a('forward')
1390:         # On first call, the @lastSlot is not set yet. We set it to the slot
1391:         # before the start slot.
1392:         if @lastSlot.nil?
1393:           @lastSlot = a('start') - slotDuration
1394:           @tentativeEnd = slot + slotDuration
1395:         end
1396: 
1397:         return false unless slot == @lastSlot + slotDuration
1398:       else
1399:         # On first call, the @lastSlot is not set yet. We set it to the slot
1400:         # to the end slot.
1401:         if @lastSlot.nil?
1402:           @lastSlot = a('end')
1403:           @tentativeStart = slot
1404:         end
1405: 
1406:         return false unless slot == @lastSlot - slotDuration
1407:       end
1408:       @lastSlot = slot
1409: 
1410:       if a('length') > 0 || a('duration') > 0
1411:         # The doneDuration counts the number of scheduled slots. It is increased
1412:         # by one with every scheduled slot. The doneLength is only increased for
1413:         # global working time slots.
1414:         bookResources(slot, slotDuration)
1415:         @doneDuration += 1
1416:         if @project.isWorkingTime(slot, slot + slotDuration)
1417:           @doneLength += 1
1418:         end
1419: 
1420:         # If we have reached the specified duration or lengths, we set the end
1421:         # or start date and propagate the value to neighbouring tasks.
1422:         if (a('length') > 0 && @doneLength >= a('length')) ||
1423:            (a('duration') > 0 && @doneDuration >= a('duration'))
1424:           @property['scheduled', @scenarioIdx] = true
1425:           if a('forward')
1426:             propagateDate(slot + slotDuration, true)
1427:           else
1428:             propagateDate(slot, false)
1429:           end
1430:           return true
1431:         end
1432:       elsif a('effort') > 0
1433:         bookResources(slot, slotDuration) if @doneEffort < a('effort')
1434:         if @doneEffort >= a('effort')
1435:           # The specified effort has been reached. The has been fully scheduled
1436:           # now.
1437:           @property['scheduled', @scenarioIdx] = true
1438:           if a('forward')
1439:             propagateDate(@tentativeEnd, true)
1440:           else
1441:             propagateDate(@tentativeStart, false)
1442:           end
1443:           return true
1444:         end
1445:       elsif a('milestone')
1446:         if a('forward')
1447:           propagateDate(a('start'), true)
1448:         else
1449:           propagateDate(a('end'), false)
1450:         end
1451:         return true
1452:       elsif a('start') && a('end')
1453:         # Task with start and end date but no duration criteria
1454:         if a('allocate').empty?
1455:           # For start-end-tasks without allocation, we don't have to do
1456:           # anything but to set the 'scheduled' flag.
1457:           @property['scheduled', @scenarioIdx] = true
1458:           @property.parent.scheduleContainer(@scenarioIdx) if @property.parent
1459:           return true
1460:         end
1461: 
1462:         bookResources(slot, slotDuration)
1463: 
1464:         # Depending on the scheduling direction we can mark the task as
1465:         # scheduled once we have reached the other end.
1466:         if (a('forward') && slot + slotDuration >= a('end')) ||
1467:            (!a('forward') && slot <= a('start'))
1468:           @property['scheduled', @scenarioIdx] = true
1469:           @property.parent.scheduleContainer(@scenarioIdx) if @property.parent
1470:           return true
1471:         end
1472:       end
1473: 
1474:       false
1475:     end
setDetermination(setStart, value) click to toggle source

Set @startIsDetermed or @endIsDetermed (depending on _setStart) to value.

      # File lib/TaskScenario.rb, line 1771
1771:     def setDetermination(setStart, value)
1772:       setStart ? @startIsDetermed = value : @endIsDetermed = value
1773:     end
targets(list) click to toggle source

Recursively compile a list of Task properties which depend on the current task.

      # File lib/TaskScenario.rb, line 1895
1895:     def targets(list)
1896:       # A target must be a leaf function that has no direct or indirect
1897:       # (through parent) following tasks.
1898:       if @property.leaf? && !hasSuccessors && !list.include?(@property)
1899:         list << @property
1900:         return
1901:       end
1902: 
1903:       a('endsuccs').each do |t, onEnd|
1904:         t.targets(@scenarioIdx, list)
1905:       end
1906: 
1907:       # Check of indirect followers.
1908:       @property.parent.targets(@scenarioIdx, list) if @property.parent
1909:     end
turnover(startIdx, endIdx, account, resource = nil) click to toggle source

Compute the turnover generated by this Task for a given Account account during the interval specified by startIdx and endIdx. These can either be TjTime values or Scoreboard indexes. If a Resource resource is given, only the turnover directly generated by the resource is taken into account.

      # File lib/TaskScenario.rb, line 1916
1916:     def turnover(startIdx, endIdx, account, resource = nil)
1917:       amount = 0.0
1918:       if @property.container?
1919:         @property.children.each do |child|
1920:           amount += child.turnover(@scenarioIdx, startIdx, endIdx, account,
1921:                                    resource)
1922:         end
1923:       end
1924: 
1925:       # If there are no chargeset defined for this task, we don't need to
1926:       # compute the resource related or other cost.
1927:       unless a('chargeset').empty?
1928:         resourceCost = 0.0
1929:         otherCost = 0.0
1930: 
1931:         # Container tasks don't have resource cost.
1932:         unless @property.container?
1933:           if resource
1934:             resourceCost = resource.cost(@scenarioIdx, startIdx, endIdx,
1935:                                          @property)
1936:           else
1937:             a('assignedresources').each do |r|
1938:               resourceCost += r.cost(@scenarioIdx, startIdx, endIdx, @property)
1939:             end
1940:           end
1941:         end
1942: 
1943:         unless a('charge').empty?
1944:           # Add one-time and periodic charges to the amount.
1945:           startDate = startIdx.is_a?(TjTime) ? startIdx :
1946:             @project.idxToDate(startIdx)
1947:           endDate = endIdx.is_a?(TjTime) ? endIdx :
1948:             @project.idxToDate(endIdx)
1949:           iv = Interval.new(startDate, endDate)
1950:           a('charge').each do |charge|
1951:             otherCost += charge.turnover(iv)
1952:           end
1953:         end
1954: 
1955:         totalCost = resourceCost + otherCost
1956:         # Now weight the total cost by the share of the account
1957:         a('chargeset').each do |set|
1958:           set.each do |accnt, share|
1959:             if share > 0.0 && (accnt == account || accnt.isChildOf?(account))
1960:               amount += totalCost * share
1961:             end
1962:           end
1963:         end
1964:       end
1965: 
1966:       amount
1967:     end

Disabled; run with --debug to generate this.

[Validate]

Generated with the Darkfish Rdoc Generator 1.1.6.