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 1090
1090:     def addBooking(booking)
1091:       if a('booking').empty?
1092:         # For the first item use the assignment form so that the 'provided'
1093:         # attribute is set properly.
1094:         @property['booking', @scenarioIdx] = [ booking ]
1095:       else
1096:         @property['booking', @scenarioIdx] << booking
1097:       end
1098:     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 876
876:     def canInheritDate?(atEnd)
877:       # Inheriting a start or end date from the enclosing task or the project
878:       # is allowed for the following scenarios:
879:       #   -  --> -   inherit start and end when no bookings but allocations
880:       #              present
881:       #   -  <-- -   dito
882:       #
883:       #   -  x-> -   inhS
884:       #   -  x-> |   inhS
885:       #   -  x-> -D  inhS
886:       #   -  x-> |D  inhS
887:       #   -  --> |   inhS
888:       #   -  --> -D  inhS
889:       #   -  --> |D  inhS
890:       #   -  <-- |   inhS
891:       #   |  --> -   inhE
892:       #   |  <-x -   inhE
893:       #   |D <-x -   inhE
894:       #   -  <-x -   inhE
895:       #   -D <-x -   inhE
896:       #   |  <-- -   inhE
897:       #   |D <-- -   inhE
898:       #   -D <-- -   inhE
899:       # Return false if we already have a date or if we have a dependency for
900:       # this end.
901:       thisEnd = atEnd ? 'end' : 'start'
902:       hasThisDeps = !a(thisEnd + 'preds').empty? || !a(thisEnd + 'succs').empty?
903:       hasThisSpec = a(thisEnd) || hasThisDeps
904:       return false if hasThisSpec
905: 
906:       # Containter task can inherit the date if they have no dependencies.
907:       return true if @property.container?
908: 
909:       thatEnd = atEnd ? 'start' : 'end'
910:       hasThatDeps = !a(thatEnd + 'preds').empty? || !a(thatEnd + 'succs').empty?
911:       hasThatSpec = a(thatEnd) || hasThatDeps
912: 
913:       # Check for tasks that have no start and end spec, no duration spec but
914:       # allocates. They can inherit the start and end date.
915:       return true if hasThatSpec && !hasDurationSpec? && !a('allocate').empty?
916: 
917:       if a('forward') ^ atEnd
918:         # the scheduling direction is pointing away from this end
919:         return true if hasDurationSpec? || !a('booking').empty?
920: 
921:         return hasThatSpec
922:       else
923:         # the scheduling direction is pointing towards this end
924:         return a(thatEnd) && !hasDurationSpec? &&
925:                a('booking').empty? #&& a('allocate').empty?
926:       end
927:     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 1332
1332:     def collectTimeOffIntervals(iv, minDuration)
1333:       if a('shifts')
1334:         a('shifts').collectTimeOffIntervals(iv, minDuration)
1335:       else
1336:         []
1337:       end
1338:     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 979
 979:     def earliestStart
 980:       # This is the date that we will return.
 981:       startDate = nil
 982:       a('depends').each do |dependency|
 983:         potentialStartDate =
 984:           dependency.task[dependency.onEnd ? 'end' : 'start', @scenarioIdx]
 985:         return nil if potentialStartDate.nil?
 986: 
 987:         # Determine the end date of a 'length' gap.
 988:         dateAfterLengthGap = potentialStartDate
 989:         gapLength = dependency.gapLength
 990:         while gapLength > 0 && dateAfterLengthGap < @project['end'] do
 991:           if @project.isWorkingTime(dateAfterLengthGap)
 992:             gapLength -= 1
 993:           end
 994:           dateAfterLengthGap += @project['scheduleGranularity']
 995:         end
 996: 
 997:         # Determine the end date of a 'duration' gap.
 998:         if dateAfterLengthGap > potentialStartDate + dependency.gapDuration
 999:           potentialStartDate = dateAfterLengthGap
1000:         else
1001:           potentialStartDate += dependency.gapDuration
1002:         end
1003: 
1004:         startDate = potentialStartDate if startDate.nil? ||
1005:                                           startDate < potentialStartDate
1006:       end
1007: 
1008:       # If any of the parent tasks has an explicit start date, the task must
1009:       # start at or after this date.
1010:       task = @property
1011:       while (task = task.parent) do
1012:         if task['start', @scenarioIdx] &&
1013:            (startDate.nil? || task['start', @scenarioIdx] > startDate)
1014:           startDate = task['start', @scenarioIdx]
1015:           break
1016:         end
1017:       end
1018: 
1019:       # When the computed start date is after the already determined end date
1020:       # of the task, the start dependencies were too weak. This happens when
1021:       # task B depends on A and they are specified this way:
1022:       # task A: | --> D-
1023:       # task B: -D <-- |
1024:       if a('end') && startDate > a('end')
1025:         error('weak_start_dep',
1026:               "Task #{@property.fullId} has a too weak start dependencies " +
1027:               "to be scheduled properly.")
1028:       end
1029: 
1030:       startDate
1031:     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 1280
1280:     def getAllocatedTime(startIdx, endIdx, resource = nil)
1281:       return 0.0 if a('milestone')
1282: 
1283:       allocatedTime = 0.0
1284:       if @property.container?
1285:         @property.children.each do |task|
1286:           allocatedTime += task.getAllocatedTime(@scenarioIdx, startIdx, endIdx,
1287:                                                  resource)
1288:         end
1289:       else
1290:         if resource
1291:           allocatedTime += resource.getAllocatedTime(@scenarioIdx,
1292:                                                      startIdx, endIdx,
1293:                                                      @property)
1294:         else
1295:           a('assignedresources').each do |r|
1296:             allocatedTime += r.getAllocatedTime(@scenarioIdx, startIdx, endIdx,
1297:                                                 @property)
1298:           end
1299:         end
1300:       end
1301:       allocatedTime
1302:     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 1307
1307:     def getEffectiveWork(startIdx, endIdx, resource = nil)
1308:       return 0.0 if a('milestone')
1309: 
1310:       workLoad = 0.0
1311:       if @property.container?
1312:         @property.children.each do |task|
1313:           workLoad += task.getEffectiveWork(@scenarioIdx, startIdx, endIdx,
1314:                                             resource)
1315:         end
1316:       else
1317:         if resource
1318:           workLoad += resource.getEffectiveWork(@scenarioIdx, startIdx, endIdx,
1319:                                                 @property)
1320:         else
1321:           a('assignedresources').each do |r|
1322:             workLoad += r.getEffectiveWork(@scenarioIdx, startIdx, endIdx,
1323:                                            @property)
1324:           end
1325:         end
1326:       end
1327:       workLoad
1328:     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 972
972:     def hasDurationSpec?
973:       a('length') > 0 || a('duration') > 0 || a('effort') > 0 || a('milestone')
974:     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 1391
1391:     def hasResourceAllocated?(interval, resource)
1392:       if @property.leaf?
1393:         return resource.allocated?(@scenarioIdx, interval, @property)
1394:       else
1395:         @property.children.each do |t|
1396:           return true if t.hasResourceAllocated?(@scenarioIdx, interval,
1397:                                                  resource)
1398:         end
1399:       end
1400:       false
1401:     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. TODO: Change this to a non-recursive implementation.

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

If task or any of its sub-tasks depend on this task or any of its sub-tasks, we call this task a feature of task.

      # File lib/TaskScenario.rb, line 1376
1376:     def isFeatureOf(task)
1377:       sources = @property.all
1378:       destinations = task.all
1379: 
1380:       sources.each do |s|
1381:         destinations.each do |d|
1382:           return true if s.isDependencyOf(@scenarioIdx, d, 0)
1383:         end
1384:       end
1385: 
1386:       false
1387:     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 1036
1036:     def latestEnd
1037:       # This is the date that we will return.
1038:       endDate = nil
1039:       a('precedes').each do |dependency|
1040:         potentialEndDate =
1041:           dependency.task[dependency.onEnd ? 'end' : 'start', @scenarioIdx]
1042:         return nil if potentialEndDate.nil?
1043: 
1044:         # Determine the end date of a 'length' gap.
1045:         dateBeforeLengthGap = potentialEndDate
1046:         gapLength = dependency.gapLength
1047:         while gapLength > 0 && dateBeforeLengthGap > @project['start'] do
1048:           if @project.isWorkingTime(dateBeforeLengthGap -
1049:                                     @project['scheduleGranularity'])
1050:             gapLength -= 1
1051:           end
1052:           dateBeforeLengthGap -= @project['scheduleGranularity']
1053:         end
1054: 
1055:         # Determine the end date of a 'duration' gap.
1056:         if dateBeforeLengthGap < potentialEndDate - dependency.gapDuration
1057:           potentialEndDate = dateBeforeLengthGap
1058:         else
1059:           potentialEndDate -= dependency.gapDuration
1060:         end
1061: 
1062:         endDate = potentialEndDate if endDate.nil? || endDate > potentialEndDate
1063:       end
1064: 
1065:       # If any of the parent tasks has an explicit end date, the task must end
1066:       # at or before this date.
1067:       task = @property
1068:       while (task = task.parent) do
1069:         if task['end', @scenarioIdx] &&
1070:            (endDate.nil? || task['end', @scenarioIdx] < endDate)
1071:           endDate = task['end', @scenarioIdx]
1072:           break
1073:         end
1074:       end
1075: 
1076:       # When the computed end date is before the already determined start date
1077:       # of the task, the end dependencies were too weak. This happens when
1078:       # task A precedes B and they are specified this way:
1079:       # task A: | --> D-
1080:       # task B: -D <-- |
1081:       if a('start') && endDate > a('start')
1082:         error('weak_end_dep',
1083:               "Task #{@property.fullId} has a too weak end dependencies " +
1084:               "to be scheduled properly.")
1085:       end
1086: 
1087:       endDate
1088:     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 && !@property.provided('scheduled', @scenarioIdx)
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:       elsif !a('scheduled') && a('start') && a('end') &&
846:             !(a('length') == 0 && a('duration') == 0 && a('effort') == 0 &&
847:               !a('allocate').empty?)
848:         @property['scheduled', @scenarioIdx] = true
849:       end
850: 
851:       # Propagate date to all dependent tasks.
852:       a(thisEnd + 'preds').each do |task, onEnd|
853:         propagateDateToDep(task, onEnd)
854:       end
855:       a(thisEnd + 'succs').each do |task, onEnd|
856:         propagateDateToDep(task, onEnd)
857:       end
858: 
859:       # Propagate date to sub tasks which have only an implicit
860:       # dependency on the parent task and no other criteria for this end of
861:       # the task.
862:       @property.children.each do |task|
863:         if task.canInheritDate?(@scenarioIdx, atEnd)
864:           task.propagateDate(@scenarioIdx, date, atEnd)
865:         end
866:       end
867: 
868:       # The date propagation might have completed the date set of the enclosing
869:       # containter task. If so, we can schedule it as well.
870:       @property.parent.scheduleContainer(@scenarioIdx) if !@property.parent.nil?
871:     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 1100
1100:     def query_complete(query)
1101:       if @property.leaf?
1102:         query.sortable = query.numerical = complete = a('complete').to_i
1103:         query.string = "#{complete}%"
1104:       else
1105:         query.string = ''
1106:       end
1107:     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 1112
1112:     def query_cost(query)
1113:       if query.costAccount
1114:         query.sortable = query.numerical = cost =
1115:           turnover(query.startIdx, query.endIdx, query.costAccount,
1116:                    query.scopeProperty)
1117:         query.string = query.currencyFormat.format(cost)
1118:       else
1119:         query.string = 'No cost account'
1120:       end
1121:     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 1125
1125:     def query_duration(query)
1126:       query.sortable = query.numerical = duration =
1127:         (a('end') - a('start')) / (60 * 60 * 24)
1128:       query.string = query.scaleDuration(duration)
1129:     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 1161
1161:     def query_effort(query)
1162:       query.sortable = query.numerical = work =
1163:         getEffectiveWork(query.startIdx, query.endIdx, query.scopeProperty)
1164:       query.string = query.scaleLoad(work)
1165:     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 1134
1134:     def query_effortdone(query)
1135:       # For this query, we always override the query period.
1136:       query.sortable = query.numerical = effort =
1137:         getEffectiveWork(@project.dateToIdx(@project['start']),
1138:                          @project.dateToIdx(@project['now']),
1139:                          query.scopeProperty)
1140:       query.string = query.scaleLoad(effort)
1141:     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 1147
1147:     def query_effortleft(query)
1148:       # For this query, we always override the query period.
1149:       query.start = @project['now']
1150:       query.end = @project['end']
1151:       query.sortable = query.numerical = effort =
1152:         getEffectiveWork(@project.dateToIdx(@project['now']),
1153:                          @project.dateToIdx(@project['end']),
1154:                          query.scopeProperty)
1155:       query.string = query.scaleLoad(effort)
1156:     end
query_followers(query) click to toggle source
      # File lib/TaskScenario.rb, line 1167
1167:     def query_followers(query)
1168:       str = ''
1169: 
1170:       # First gather the task that depend on the start of this task.
1171:       a('startsuccs').each do |task, onEnd|
1172:         str += "* <nowiki>#{task.name}</nowiki> (#{task.fullId}) "
1173:         if onEnd
1174:           taskEnd = task['end', query.scenarioIdx].to_s(query.timeFormat)
1175:           str += "[->] #{taskEnd}"
1176:         else
1177:           taskStart = task['start', query.scenarioIdx].to_s(query.timeFormat)
1178:           str += "[->[ #{taskStart}"
1179:         end
1180:         str += "\n"
1181:       end
1182:       # Than add the tasks that depend on the end of this task.
1183:       a('endsuccs').each do |task, onEnd|
1184:         str += "* <nowiki>#{task.name}</nowiki> (#{task.fullId}) "
1185:         if onEnd
1186:           taskEnd = task['end', query.scenarioIdx].to_s(query.timeFormat)
1187:           str += "]->] #{taskEnd}"
1188:         else
1189:           taskStart = task['start', query.scenarioIdx].to_s(query.timeFormat)
1190:           str += "]->[ #{taskStart}"
1191:         end
1192:         str += "\n"
1193:       end
1194: 
1195:       rText = RichText.new(str)
1196:       query.rti = rText.generateIntermediateFormat
1197:     end
query_precursors(query) click to toggle source
      # File lib/TaskScenario.rb, line 1199
1199:     def query_precursors(query)
1200:       str = ''
1201: 
1202:       # First gather the task that depend on the start of this task.
1203:       a('startpreds').each do |task, onEnd|
1204:         str += "* <nowiki>#{task.name}</nowiki> (#{task.fullId}) "
1205:         if onEnd
1206:           taskEnd = task['end', query.scenarioIdx].to_s(query.timeFormat)
1207:           str += "]->] #{taskEnd}"
1208:         else
1209:           taskStart = task['start', query.scenarioIdx].to_s(query.timeFormat)
1210:           str += "[->[ #{taskStart}"
1211:         end
1212:         str += "\n"
1213:       end
1214:       # Than add the tasks that depend on the end of this task.
1215:       a('endpreds').each do |task, onEnd|
1216:         str += "* <nowiki>#{task.name}</nowiki> (#{task.fullId}) "
1217:         if onEnd
1218:           taskEnd = task['end', query.scenarioIdx].to_s(query.timeFormat)
1219:           str += "[->] #{taskEnd}"
1220:         else
1221:           taskStart = task['start', query.scenarioIdx].to_s(query.timeFormat)
1222:           str += "]->[ #{taskStart}"
1223:         end
1224:         str += "\n"
1225:       end
1226: 
1227:       rText = RichText.new(str)
1228:       query.rti = rText.generateIntermediateFormat
1229:     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 1233
1233:     def query_resources(query)
1234:       list = ''
1235:       a('assignedresources').each do |resource|
1236:         if getAllocatedTime(query.startIdx, query.endIdx, resource) > 0.0
1237:           list += ', ' unless list.empty?
1238:           list += "#{resource.name} (#{resource.fullId})"
1239:         end
1240:       end
1241:       query.sortable = query.string = list
1242:       rText = RichText.new(list)
1243:       query.rti = rText.generateIntermediateFormat
1244:     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 1249
1249:     def query_revenue(query)
1250:       if query.revenueAccount
1251:         query.sortable = query.numerical = revenue =
1252:           turnover(query.startIdx, query.endIdx, query.revenueAccount,
1253:                    query.scopeProperty)
1254:         query.string = query.currencyFormat.format(revenue)
1255:       else
1256:         query.string = 'No revenue account'
1257:       end
1258:     end
query_targets(query) click to toggle source
      # File lib/TaskScenario.rb, line 1260
1260:     def query_targets(query)
1261:       targetList = PropertyList.new(@project.tasks, false)
1262:       targets(targetList)
1263:       targetList.delete(@property)
1264:       targetList.setSorting([['start', true, @scenarioIdx],
1265:                              ['seqno', true, 1 ]])
1266:       targetList.sort!
1267: 
1268:       res = ''
1269:       targetList.each do |task|
1270:         date = task['start', @scenarioIdx].to_s(@property.project['timeFormat'])
1271:         res += "# #{task.name} (#{task.fullId}) #{date}\n"
1272:       end
1273:       rText = RichText.new(res)
1274:       query.rti = rText.generateIntermediateFormat
1275:     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 931
931:     def scheduleContainer
932:       return if a('scheduled') || !@property.container?
933: 
934:       nStart = nil
935:       nEnd = nil
936: 
937:       @property.children.each do |task|
938:         # Abort if a child has not yet been scheduled.
939:         return unless task['scheduled', @scenarioIdx]
940: 
941:         if nStart.nil? || task['start', @scenarioIdx] < nStart
942:           nStart = task['start', @scenarioIdx]
943:         end
944:         if nEnd.nil? || task['end', @scenarioIdx] > nEnd
945:           nEnd = task['end', @scenarioIdx]
946:         end
947:       end
948: 
949:       startSet = endSet = false
950:       # Propagate the dates to other dependent tasks.
951:       if a('start').nil? || a('start') > nStart
952:         @property['start', @scenarioIdx] = nStart
953:         startSet = true
954:       end
955:       if a('end').nil? || a('end') < nEnd
956:         @property['end', @scenarioIdx] = nEnd
957:         endSet = true
958:       end
959:       unless a('start') && a('end')
960:         raise "Start (#{a('start')}) and end (#{a('end')}) must be set"
961:       end
962:       @property['scheduled', @scenarioIdx] = true
963:       Log << "Container task #{@property.fullId}: #{a('start')} -> #{a('end')}"
964: 
965:       # If we have modified the start or end date, we need to communicate this
966:       # new date to surrounding tasks.
967:       propagateDate(nStart, false) if startSet
968:       propagateDate(nEnd, true) if endSet
969:     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 1647
1647:     def bookBookings
1648:       scheduled = a('scheduled')
1649:       a('booking').each do |booking|
1650:         unless booking.resource.leaf?
1651:           error('booking_resource_not_leaf',
1652:                 "Booked resources may not be group resources", true,
1653:                 booking.sourceFileInfo)
1654:         end
1655:         unless a('forward') || scheduled
1656:           error('booking_forward_only',
1657:                 "Only forward scheduled tasks may have booking statements.")
1658:         end
1659:         slotDuration = @project['scheduleGranularity']
1660:         booking.intervals.each do |interval|
1661:           startIdx = @project.dateToIdx(interval.start)
1662:           date = interval.start
1663:           endIdx = @project.dateToIdx(interval.end)
1664:           tEnd = nil
1665:           startIdx.upto(endIdx - 1) do |idx|
1666:             tEnd = date + slotDuration
1667:             if booking.resource.bookBooking(@scenarioIdx, idx, booking)
1668:               # Booking was successful for this time slot.
1669:               @doneEffort += booking.resource['efficiency', @scenarioIdx]
1670: 
1671:               # Set start and lastSlot if appropriate. The task start will be
1672:               # set to the begining of the first booked slot. The lastSlot
1673:               # will be set to the last booked slot
1674:               @lastSlot = date if @lastSlot.nil? || date > @lastSlot
1675:               @tentativeEnd = tEnd if @tentativeEnd.nil? ||
1676:                 @tentativeEnd < tEnd
1677:               if !scheduled && (a('start').nil? || date < a('start'))
1678:                 @property['start', @scenarioIdx] = date
1679:               end
1680: 
1681:               unless a('assignedresources').include?(booking.resource)
1682:                 @property['assignedresources', @scenarioIdx] << booking.resource
1683:               end
1684:             end
1685:             if a('length') > 0 && @project.isWorkingTime(date, tEnd)
1686:               # For tasks with a 'length' we track the covered work time and
1687:               # set the task to 'scheduled' when we have enough length.
1688:               @doneLength += 1
1689:               if !scheduled && @doneLength >= a('length')
1690:                 @property['end', @scenarioIdx] = tEnd
1691:                 @property['scheduled', @scenarioIdx] = true
1692:               end
1693:             end
1694:             date = tEnd
1695:           end
1696:           if a('duration') > 0 && @tentativeEnd
1697:             @doneDuration = ((@tentativeEnd - a('start')) /
1698:                              @project['scheduleGranularity']).to_i
1699:             if !scheduled && @doneDuration >= a('duration')
1700:               @property['end', @scenarioIdx] = @tentativeEnd
1701:               @property['scheduled', @scenarioIdx] = true
1702:             end
1703:           end
1704:         end
1705:       end
1706:     end
bookResource(resource, sbIdx, date) click to toggle source
      # File lib/TaskScenario.rb, line 1593
1593:     def bookResource(resource, sbIdx, date)
1594:       booked = false
1595:       resource.allLeaves.each do |r|
1596:         # Prevent overbooking when multiple resources are allocated and
1597:         # available. If the task has allocation limits we need to make sure
1598:         # that none of them is already exceeded.
1599:         break if a('effort') > 0 && @doneEffort >= a('effort') ||
1600:                  !limitsOk?(date, resource)
1601: 
1602:         if r.book(@scenarioIdx, sbIdx, @property)
1603: 
1604:           if a('assignedresources').empty?
1605:             if a('forward')
1606:               @property['start', @scenarioIdx] = @project.idxToDate(sbIdx)
1607:             else
1608:               @property['end', @scenarioIdx] = @project.idxToDate(sbIdx + 1)
1609:             end
1610:           end
1611: 
1612:           @tentativeStart = @project.idxToDate(sbIdx)
1613:           @tentativeEnd = @project.idxToDate(sbIdx + 1)
1614: 
1615:           @doneEffort += r['efficiency', @scenarioIdx]
1616:           # Limits do not take efficiency into account. Limits are usage limits,
1617:           # not effort limits.
1618:           @limits.each do |limit|
1619:             limit.inc(date, resource)
1620:           end
1621: 
1622:           unless a('assignedresources').include?(r)
1623:             @property['assignedresources', @scenarioIdx] << r
1624:           end
1625:           booked = true
1626:         end
1627:       end
1628: 
1629:       booked
1630:     end
bookResources(date, slotDuration) click to toggle source
      # File lib/TaskScenario.rb, line 1511
1511:     def bookResources(date, slotDuration)
1512:       # If there are no allocations defined, we can't do any bookings.
1513:       # In projection mode we do not allow bookings prior to the current date
1514:       # for any task (in strict mode) or tasks which have user specified
1515:       # bookings (sloppy mode).
1516:       if a('allocate').empty? ||
1517:          (@project.scenario(@scenarioIdx).get('projection') &&
1518:           date < @project['now'] &&
1519:           (@project.scenario(@scenarioIdx).get('strict') ||
1520:            a('assignedresources').empty?))
1521:         return
1522:       end
1523: 
1524:       # If the task has shifts to limit the allocations, we check that we are
1525:       # within a defined shift interval. If yes, we need to be on shift to
1526:       # continue.
1527:       if (shifts = a('shifts')) && shifts.assigned?(date)
1528:          return if !shifts.onShift?(date)
1529:       end
1530: 
1531:       # If the task has resource independent allocation limits we need to make
1532:       # sure that none of them is already exceeded.
1533:       return unless limitsOk?(date)
1534: 
1535:       sbIdx = @project.dateToIdx(date)
1536: 
1537:       # We first have to make sure that if there are mandatory resources
1538:       # that these are all available for the time slot.
1539:       takenMandatories = []
1540:       @mandatories.each do |allocation|
1541:         return unless allocation.onShift?(date)
1542: 
1543:         # For mandatory allocations with alternatives at least one of the
1544:         # alternatives must be available.
1545:         found = false
1546:         allocation.candidates(@scenarioIdx).each do |candidate|
1547:           # When a resource group is marked mandatory, all members of the
1548:           # group must be available.
1549:           allAvailable = true
1550:           candidate.allLeaves.each do |resource|
1551:             if !limitsOk?(date, resource) ||
1552:                !resource.available?(@scenarioIdx, sbIdx) ||
1553:                takenMandatories.include?(resource)
1554:               # We've found a mandatory resource that is not available for
1555:               # the slot.
1556:               allAvailable = false
1557:               break
1558:             else
1559:               takenMandatories << resource
1560:             end
1561:           end
1562:           if allAvailable
1563:             found = true
1564:             break
1565:           end
1566:         end
1567: 
1568:         # At least one mandatory resource is not available. We cannot continue.
1569:         return unless found
1570:       end
1571: 
1572:       iv = Interval.new(date, date + slotDuration)
1573:       a('allocate').each do |allocation|
1574:         next unless allocation.onShift?(date)
1575: 
1576:         # In case we have a persistent allocation we need to check if there is
1577:         # already a locked resource and use it.
1578:         if allocation.persistent && !allocation.lockedResource.nil?
1579:           bookResource(allocation.lockedResource, sbIdx, date)
1580:         else
1581:           # If not, we create a list of candidates in the proper order and
1582:           # assign the first one available.
1583:           allocation.candidates(@scenarioIdx).each do |candidate|
1584:             if bookResource(candidate, sbIdx, date)
1585:               allocation.lockedResource = candidate
1586:               break
1587:             end
1588:           end
1589:         end
1590:       end
1591:     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 1865
1865:     def calcCompletion
1866:       # If the user provided a completion degree we are not touching it.
1867:       if @property.provided('complete', @scenarioIdx)
1868:         calcStatus
1869:         return
1870:       end
1871: 
1872:       if a('start').nil? || a('end').nil?
1873:         @property['complete', @scenarioIdx] = 0.0
1874:         @property['status', @scenarioIdx] = 'unknown'
1875:         return
1876:       end
1877: 
1878:       if a('milestone')
1879:         @property['complete', @scenarioIdx] =
1880:           @property['end', @scenarioIdx] <= @project['now'] ? 100.0 : 0.0
1881:         @property['status', @scenarioIdx] =
1882:           a('end') <= @project['now'] ? 'done' : 'not reached'
1883:       else
1884:         completion = 0.0
1885:         if a('end') <= @project['now']
1886:           # The task has ended already. It's 100% complete.
1887:           completion = 100.0
1888:         elsif @project['now'] <= a('start')
1889:           # The task has not started yet. Its' 0% complete.
1890:           completion = 0.0
1891:         else
1892:           # The task is in progress. Calculate the current completion
1893:           # degree.
1894:           if @property.leaf? && a('effort') > 0
1895:             # Effort based leaf tasks. The completion degree is the percantage
1896:             # of effort that has been done already.
1897:             done = getEffectiveWork(@project.dateToIdx(a('start')),
1898:                                     @project.dateToIdx(@project['now']))
1899:             total = @project.convertToDailyLoad(
1900:               a('effort') * @project['scheduleGranularity'])
1901:             completion = done / total * 100.0
1902:           else
1903:             # Container tasks and length/duration leaf tasks. There is no way
1904:             # we can compute the completion degree of a container task with a
1905:             # mix of effort and duration task in a meaningful way.  So, we
1906:             # just go by duration.
1907:             completion = ((@project['now'] - a('start')) /
1908:                           (a('end') - a('start'))) * 100.0
1909:           end
1910:         end
1911:         @property['complete', @scenarioIdx] = completion
1912:         calcStatus
1913:       end
1914:     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 1841
1841:     def calcPathCriticalnessEndSuccs
1842:       maxCriticalness = 0.0
1843:       # Gather a list of all end-successors of this task and its parent task.
1844:       tList = []
1845:       p = @property
1846:       while (p)
1847:         p['endsuccs', @scenarioIdx].each do |task, onEnd|
1848:           tList << [ task, onEnd ] unless tList.include?([ task, onEnd ])
1849:         end
1850:         p = p.parent
1851:       end
1852: 
1853:       tList.each do |task, onEnd|
1854:         if (criticalness = task.calcPathCriticalness(@scenarioIdx, onEnd)) >
1855:           maxCriticalness
1856:           maxCriticalness = criticalness
1857:         end
1858:       end
1859: 
1860:       maxCriticalness
1861:     end
calcStatus() click to toggle source

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

      # File lib/TaskScenario.rb, line 1917
1917:     def calcStatus
1918:       @property['status', @scenarioIdx] =
1919:         if a('complete') == 0.0
1920:           'not started'
1921:         elsif a('complete') >= 100.0
1922:           'done'
1923:         else
1924:           'in progress'
1925:         end
1926:     end
checkDependency(dependency, depType) click to toggle source
      # File lib/TaskScenario.rb, line 1759
1759:     def checkDependency(dependency, depType)
1760:       if (depTask = dependency.resolve(@project)).nil?
1761:         # Remove the broken dependency. It could cause trouble later on.
1762:         @property[depType, @scenarioIdx].delete(dependency)
1763:         error('task_depend_unknown',
1764:               "Task #{@property.fullId} has unknown #{depType} " +
1765:               "#{dependency.taskId}")
1766:       end
1767: 
1768:       if depTask == @property
1769:         # Remove the broken dependency. It could cause trouble later on.
1770:         @property[depType, @scenarioIdx].delete(dependency)
1771:         error('task_depend_self', "Task #{@property.fullId} cannot " +
1772:               "depend on self")
1773:       end
1774: 
1775:       if depTask.isChildOf?(@property)
1776:         # Remove the broken dependency. It could cause trouble later on.
1777:         @property[depType, @scenarioIdx].delete(dependency)
1778:         error('task_depend_child',
1779:               "Task #{@property.fullId} cannot depend on child " +
1780:               "#{depTask.fullId}")
1781:       end
1782: 
1783:       if @property.isChildOf?(depTask)
1784:         # Remove the broken dependency. It could cause trouble later on.
1785:         @property[depType, @scenarioIdx].delete(dependency)
1786:         error('task_depend_parent',
1787:               "Task #{@property.fullId} cannot depend on parent " +
1788:               "#{depTask.fullId}")
1789:       end
1790: 
1791:       @property[depType, @scenarioIdx].each do |dep|
1792:         if dep.task == depTask && dep != dependency
1793:           # Remove the broken dependency. It could cause trouble later on.
1794:           @property[depType, @scenarioIdx].delete(dependency)
1795:           error('task_depend_multi',
1796:                 "No need to specify dependency #{depTask.fullId} multiple " +
1797:                 "times for task #{@property.fullId}.")
1798:         end
1799:       end
1800: 
1801:       depTask
1802:     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 1711
1711:     def hasDependencies(atEnd)
1712:       thisEnd = atEnd ? 'end' : 'start'
1713:       !a(thisEnd + 'succs').empty? || !a(thisEnd + 'preds').empty?
1714:     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 1718
1718:     def hasSuccessors
1719:       t = @property
1720:       while t
1721:         return true unless t['endsuccs', @scenarioIdx].empty?
1722:         t = t.parent
1723:       end
1724: 
1725:       false
1726:     end
limitsOk?(date, resource = nil) click to toggle source

Check if all of the task limits are not exceded at the given date. If a resource is provided, the limit for that particular resource is checked. If no resource is provided, only non-resource-specific limits are checked.

      # File lib/TaskScenario.rb, line 1636
1636:     def limitsOk?(date, resource = nil)
1637:       @limits.each do |limit|
1638:         return false if !limit.ok?(date, true, resource)
1639:       end
1640:       true
1641:     end
markAsRunaway() click to toggle source
      # File lib/TaskScenario.rb, line 1728
1728:     def markAsRunaway
1729:       warning('runaway', "Task #{@property.fullId} does not fit into " +
1730:                          "project time frame")
1731: 
1732:       @isRunAway = true
1733:     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 1737
1737:     def markMilestone
1738:       return if @property.container? || hasDurationSpec? ||
1739:         !a('booking').empty? || !a('allocate').empty?
1740: 
1741:       # The following cases qualify for an automatic milestone promotion.
1742:       #   -  --> -
1743:       #   |  --> -
1744:       #   |D --> -
1745:       #   -D --> -
1746:       #   -  <-- -
1747:       #   -  <-- |
1748:       #   -  <-- -D
1749:       #   -  <-- |D
1750:       hasStartSpec = !a('start').nil? || !a('depends').empty?
1751:       hasEndSpec = !a('end').nil? || !a('precedes').empty?
1752: 
1753:       @property['milestone', @scenarioIdx] =
1754:         (hasStartSpec && a('forward') && !hasEndSpec) ||
1755:         (!hasStartSpec && !a('forward') && hasEndSpec) ||
1756:         (!hasStartSpec && !hasEndSpec)
1757:     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 1501
1501:     def nextSlot(slotDuration)
1502:       return nil if a('scheduled') || @isRunAway
1503: 
1504:       if a('forward')
1505:         @lastSlot.nil? ? a('start') : @lastSlot + slotDuration
1506:       else
1507:         @lastSlot.nil? ? a('end') - slotDuration : @lastSlot - slotDuration
1508:       end
1509:     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 1813
1813:     def propagateDateToDep(task, atEnd)
1814:       #puts "Propagate #{atEnd ? 'end' : 'start'} to dep. #{task.fullId}"
1815:       # Don't propagate if the task is already completely scheduled or is a
1816:       # container.
1817:       return if task['scheduled', @scenarioIdx] || task.container?
1818: 
1819:       # Don't propagate if the task already has a date for that end.
1820:       return unless task[atEnd ? 'end' : 'start', @scenarioIdx].nil?
1821: 
1822:       # Don't propagate if the task has a duration or is a milestone and the
1823:       # task end to set is in the scheduling direction.
1824:       return if task.hasDurationSpec?(@scenarioIdx) &&
1825:                 !(atEnd ^ task['forward', @scenarioIdx])
1826: 
1827:       # Check if all other dependencies for that task end have been determined
1828:       # already and use the latest or earliest possible date. Don't propagate
1829:       # if we don't have all dates yet.
1830:       return if (nDate = (atEnd ? task.latestEnd(@scenarioIdx) :
1831:                                   task.earliestStart(@scenarioIdx))).nil?
1832: 
1833:       # Looks like it is ok to propagate the date.
1834:       task.propagateDate(@scenarioIdx, nDate, atEnd)
1835:       # puts "Propagate #{atEnd ? 'end' : 'start'} to dep. #{task.fullId} done"
1836:     end
scheduleSlot(slot, slotDuration) click to toggle source
      # File lib/TaskScenario.rb, line 1404
1404:     def scheduleSlot(slot, slotDuration)
1405:       # Tasks must always be scheduled in a single contigous fashion. @lastSlot
1406:       # indicates the slot that was used for the previous call. Depending on the
1407:       # scheduling direction the next slot must be scheduled either right before
1408:       # or after this slot. If the current slot is not directly aligned, we'll
1409:       # wait for another call with a proper slot. The function returns true
1410:       # only if a slot could be scheduled.
1411:       if a('forward')
1412:         # On first call, the @lastSlot is not set yet. We set it to the slot
1413:         # before the start slot.
1414:         if @lastSlot.nil?
1415:           @lastSlot = a('start') - slotDuration
1416:           @tentativeEnd = slot + slotDuration
1417:         end
1418: 
1419:         return false unless slot == @lastSlot + slotDuration
1420:       else
1421:         # On first call, the @lastSlot is not set yet. We set it to the slot
1422:         # to the end slot.
1423:         if @lastSlot.nil?
1424:           @lastSlot = a('end')
1425:           @tentativeStart = slot
1426:         end
1427: 
1428:         return false unless slot == @lastSlot - slotDuration
1429:       end
1430:       @lastSlot = slot
1431: 
1432:       if a('length') > 0 || a('duration') > 0
1433:         # The doneDuration counts the number of scheduled slots. It is increased
1434:         # by one with every scheduled slot. The doneLength is only increased for
1435:         # global working time slots.
1436:         bookResources(slot, slotDuration)
1437:         @doneDuration += 1
1438:         if @project.isWorkingTime(slot, slot + slotDuration)
1439:           @doneLength += 1
1440:         end
1441: 
1442:         # If we have reached the specified duration or lengths, we set the end
1443:         # or start date and propagate the value to neighbouring tasks.
1444:         if (a('length') > 0 && @doneLength >= a('length')) ||
1445:            (a('duration') > 0 && @doneDuration >= a('duration'))
1446:           if a('forward')
1447:             propagateDate(slot + slotDuration, true)
1448:           else
1449:             propagateDate(slot, false)
1450:           end
1451:           return true
1452:         end
1453:       elsif a('effort') > 0
1454:         bookResources(slot, slotDuration) if @doneEffort < a('effort')
1455:         if @doneEffort >= a('effort')
1456:           # The specified effort has been reached. The has been fully scheduled
1457:           # now.
1458:           if a('forward')
1459:             propagateDate(@tentativeEnd, true)
1460:           else
1461:             propagateDate(@tentativeStart, false)
1462:           end
1463:           return true
1464:         end
1465:       elsif a('milestone')
1466:         if a('forward')
1467:           propagateDate(a('start'), true)
1468:         else
1469:           propagateDate(a('end'), false)
1470:         end
1471:         return true
1472:       elsif a('start') && a('end')
1473:         # Task with start and end date but no duration criteria
1474:         if a('allocate').empty?
1475:           # For start-end-tasks without allocation, we don't have to do
1476:           # anything but to set the 'scheduled' flag.
1477:           @property['scheduled', @scenarioIdx] = true
1478:           @property.parent.scheduleContainer(@scenarioIdx) if @property.parent
1479:           return true
1480:         end
1481: 
1482:         bookResources(slot, slotDuration)
1483: 
1484:         # Depending on the scheduling direction we can mark the task as
1485:         # scheduled once we have reached the other end.
1486:         if (a('forward') && slot + slotDuration >= a('end')) ||
1487:            (!a('forward') && slot <= a('start'))
1488:           @property['scheduled', @scenarioIdx] = true
1489:           @property.parent.scheduleContainer(@scenarioIdx) if @property.parent
1490:           return true
1491:         end
1492:       end
1493: 
1494:       false
1495:     end
setDetermination(setStart, value) click to toggle source

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

      # File lib/TaskScenario.rb, line 1806
1806:     def setDetermination(setStart, value)
1807:       setStart ? @startIsDetermed = value : @endIsDetermed = value
1808:     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 1930
1930:     def targets(list)
1931:       # A target must be a leaf function that has no direct or indirect
1932:       # (through parent) following tasks.
1933:       if @property.leaf? && !hasSuccessors && !list.include?(@property)
1934:         list << @property
1935:         return
1936:       end
1937: 
1938:       a('endsuccs').each do |t, onEnd|
1939:         t.targets(@scenarioIdx, list)
1940:       end
1941: 
1942:       # Check of indirect followers.
1943:       @property.parent.targets(@scenarioIdx, list) if @property.parent
1944:     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 1951
1951:     def turnover(startIdx, endIdx, account, resource = nil)
1952:       amount = 0.0
1953:       if @property.container?
1954:         @property.children.each do |child|
1955:           amount += child.turnover(@scenarioIdx, startIdx, endIdx, account,
1956:                                    resource)
1957:         end
1958:       end
1959: 
1960:       # If there are no chargeset defined for this task, we don't need to
1961:       # compute the resource related or other cost.
1962:       unless a('chargeset').empty?
1963:         resourceCost = 0.0
1964:         otherCost = 0.0
1965: 
1966:         # Container tasks don't have resource cost.
1967:         unless @property.container?
1968:           if resource
1969:             resourceCost = resource.cost(@scenarioIdx, startIdx, endIdx,
1970:                                          @property)
1971:           else
1972:             a('assignedresources').each do |r|
1973:               resourceCost += r.cost(@scenarioIdx, startIdx, endIdx, @property)
1974:             end
1975:           end
1976:         end
1977: 
1978:         unless a('charge').empty?
1979:           # Add one-time and periodic charges to the amount.
1980:           startDate = startIdx.is_a?(TjTime) ? startIdx :
1981:             @project.idxToDate(startIdx)
1982:           endDate = endIdx.is_a?(TjTime) ? endIdx :
1983:             @project.idxToDate(endIdx)
1984:           iv = Interval.new(startDate, endDate)
1985:           a('charge').each do |charge|
1986:             otherCost += charge.turnover(iv)
1987:           end
1988:         end
1989: 
1990:         totalCost = resourceCost + otherCost
1991:         # Now weight the total cost by the share of the account
1992:         a('chargeset').each do |set|
1993:           set.each do |accnt, share|
1994:             if share > 0.0 && (accnt == account || accnt.isChildOf?(account))
1995:               amount += totalCost * share
1996:             end
1997:           end
1998:         end
1999:       end
2000: 
2001:       amount
2002:     end

Disabled; run with --debug to generate this.

[Validate]

Generated with the Darkfish Rdoc Generator 1.1.6.