### Home Classes Methods

### In Files

- lib/taskjuggler/TaskScenario.rb

### Parent

ScenarioData

### Methods

- ::new
- #Xref
- #addBooking
- #assignedResources
- #calcCriticalness
- #calcPathCriticalness
- #canInheritDate?
- #candidates
- #checkForLoops
- #collectTimeOffIntervals
- #countResourceAllocations
- #earliestStart
- #finishScheduling
- #getAllocatedTime
- #getEffectiveWork
- #hasDependency?
- #hasResourceAllocated?
- #isDependencyOf
- #isFeatureOf
- #latestEnd
- #markAsScheduled
- #postScheduleCheck
- #preScheduleCheck
- #prepareScheduling
- #propagateDate
- #propagateInitialValues
- #query_activetasks
- #query_closedtasks
- #query_competitorcount
- #query_complete
- #query_cost
- #query_duration
- #query_effort
- #query_effortdone
- #query_effortleft
- #query_followers
- #query_gauge
- #query_headcount
- #query_inputs
- #query_maxend
- #query_maxstart
- #query_minend
- #query_minstart
- #query_opentasks
- #query_precursors
- #query_resources
- #query_revenue
- #query_scheduling
- #query_status
- #query_targets
- #readyForScheduling?
- #resetLoopFlags
- #schedule
- #scheduleContainer

### Files

- CHANGELOG
- COPYING
- README.rdoc

### Class/Module Index

Quicksearch

- TaskJuggler
- TaskJuggler::Account
- TaskJuggler::AccountAttribute
- TaskJuggler::AccountCredit
- TaskJuggler::AccountCreditListAttribute
- TaskJuggler::AccountListRE
- TaskJuggler::AccountScenario
- TaskJuggler::AlertLevelDefinition
- TaskJuggler::AlertLevelDefinitions
- TaskJuggler::Allocation
- TaskJuggler::AllocationAttribute
- TaskJuggler::AttributeBase
- TaskJuggler::AttributeDefinition
- TaskJuggler::AttributeOverwrite
- TaskJuggler::BatchProcessor
- TaskJuggler::Booking
- TaskJuggler::BookingListAttribute
- TaskJuggler::BooleanAttribute
- TaskJuggler::CSVFile
- TaskJuggler::CellSettingPattern
- TaskJuggler::CellSettingPatternList
- TaskJuggler::Charge
- TaskJuggler::ChargeListAttribute
- TaskJuggler::ChargeSet
- TaskJuggler::ChargeSetListAttribute
- TaskJuggler::ChartPlotter
- TaskJuggler::ChartPlotterError
- TaskJuggler::CollisionDetector
- TaskJuggler::ColumnListAttribute
- TaskJuggler::ColumnTable
- TaskJuggler::Daemon
- TaskJuggler::DaemonConnector
- TaskJuggler::DaemonConnectorMixin
- TaskJuggler::DataCache
- TaskJuggler::DataCacheEntry
- TaskJuggler::DateAttribute
- TaskJuggler::DefinitionListAttribute
- TaskJuggler::DependencyListAttribute
- TaskJuggler::DurationAttribute
- TaskJuggler::ExportRE
- TaskJuggler::FileList
- TaskJuggler::FileRecord
- TaskJuggler::FixnumAttribute
- TaskJuggler::FlagListAttribute
- TaskJuggler::FloatAttribute
- TaskJuggler::FormatListAttribute
- TaskJuggler::GanttChart
- TaskJuggler::GanttContainer
- TaskJuggler::GanttHeader
- TaskJuggler::GanttHeaderScaleItem
- TaskJuggler::GanttLine
- TaskJuggler::GanttLoadStack
- TaskJuggler::GanttMilestone
- TaskJuggler::GanttRouter
- TaskJuggler::GanttTaskBar
- TaskJuggler::HTMLDocument
- TaskJuggler::HTMLElements
- TaskJuggler::HTMLGraphics
- TaskJuggler::ICalReport
- TaskJuggler::ICalendar
- TaskJuggler::ICalendar::Component
- TaskJuggler::ICalendar::Event
- TaskJuggler::ICalendar::Journal
- TaskJuggler::ICalendar::Person
- TaskJuggler::ICalendar::Todo
- TaskJuggler::Interval
- TaskJuggler::IntervalList
- TaskJuggler::JobInfo
- TaskJuggler::Journal
- TaskJuggler::JournalEntry
- TaskJuggler::JournalEntryList
- TaskJuggler::JournalSortListAttribute
- TaskJuggler::KateSyntax
- TaskJuggler::KeywordArray
- TaskJuggler::KeywordDocumentation
- TaskJuggler::Leave
- TaskJuggler::LeaveAllowance
- TaskJuggler::LeaveAllowanceList
- TaskJuggler::LeaveAllowanceListAttribute
- TaskJuggler::LeaveList
- TaskJuggler::LeaveListAttribute
- TaskJuggler::Limits
- TaskJuggler::Limits::Limit
- TaskJuggler::LimitsAttribute
- TaskJuggler::ListAttributeBase
- TaskJuggler::Log
- TaskJuggler::LogicalAttribute
- TaskJuggler::LogicalExpression
- TaskJuggler::LogicalExpressionAttribute
- TaskJuggler::LogicalExpressionListAttribute
- TaskJuggler::LogicalFlag
- TaskJuggler::LogicalFunction
- TaskJuggler::LogicalOperation
- TaskJuggler::ManagerResponsibilities
- TaskJuggler::ManagerStatusRecord
- TaskJuggler::Message
- TaskJuggler::MessageHandler
- TaskJuggler::MessageHandlerInstance
- TaskJuggler::MspXmlRE
- TaskJuggler::Navigator
- TaskJuggler::NavigatorElement
- TaskJuggler::NikuProject
- TaskJuggler::NikuReport
- TaskJuggler::NikuResource
- TaskJuggler::NodeListAttribute
- TaskJuggler::PTNProxy
- TaskJuggler::Painter
- TaskJuggler::Painter::Circle
- TaskJuggler::Painter::Color
- TaskJuggler::Painter::Element
- TaskJuggler::Painter::Ellipse
- TaskJuggler::Painter::FontMetrics
- TaskJuggler::Painter::FontMetricsData
- TaskJuggler::Painter::Group
- TaskJuggler::Painter::Line
- TaskJuggler::Painter::Points
- TaskJuggler::Painter::PolyLine
- TaskJuggler::Painter::Primitives
- TaskJuggler::Painter::Rect
- TaskJuggler::Painter::SVGSupport
- TaskJuggler::Painter::Text
- TaskJuggler::PlaceHolderCell
- TaskJuggler::ProcessIntercom
- TaskJuggler::ProcessIntercomIface
- TaskJuggler::Project
- TaskJuggler::ProjectBroker
- TaskJuggler::ProjectBrokerIface
- TaskJuggler::ProjectFileParser
- TaskJuggler::ProjectFileScanner
- TaskJuggler::ProjectRecord
- TaskJuggler::ProjectServer
- TaskJuggler::ProjectServerIface
- TaskJuggler::PropertyAttribute
- TaskJuggler::PropertyList
- TaskJuggler::PropertySet
- TaskJuggler::PropertyTreeNode
- TaskJuggler::Query
- TaskJuggler::RTFHandlers
- TaskJuggler::RTFNavigator
- TaskJuggler::RTFQuery
- TaskJuggler::RTFReport
- TaskJuggler::RTFReportLink
- TaskJuggler::RTFWithQuerySupport
- TaskJuggler::RealFormat
- TaskJuggler::RealFormatAttribute
- TaskJuggler::ReferenceAttribute
- TaskJuggler::Report
- TaskJuggler::ReportBase
- TaskJuggler::ReportContext
- TaskJuggler::ReportScenario
- TaskJuggler::ReportServer
- TaskJuggler::ReportServerIface
- TaskJuggler::ReportServerRecord
- TaskJuggler::ReportServlet
- TaskJuggler::ReportTable
- TaskJuggler::ReportTableCell
- TaskJuggler::ReportTableColumn
- TaskJuggler::ReportTableLegend
- TaskJuggler::ReportTableLine
- TaskJuggler::Resource
- TaskJuggler::ResourceListAttribute
- TaskJuggler::ResourceListRE
- TaskJuggler::ResourceScenario
- TaskJuggler::RichText
- TaskJuggler::RichTextAttribute
- TaskJuggler::RichTextDocument
- TaskJuggler::RichTextElement
- TaskJuggler::RichTextFunctionExample
- TaskJuggler::RichTextFunctionHandler
- TaskJuggler::RichTextImage
- TaskJuggler::RichTextIntermediate
- TaskJuggler::RichTextParser
- TaskJuggler::RichTextScanner
- TaskJuggler::RichTextSnip
- TaskJuggler::RichTextSyntaxRules
- TaskJuggler::Scenario
- TaskJuggler::ScenarioData
- TaskJuggler::ScenarioListAttribute
- TaskJuggler::Scoreboard
- TaskJuggler::ScoreboardInterval
- TaskJuggler::SheetHandlerBase
- TaskJuggler::SheetReceiver
- TaskJuggler::SheetSender
- TaskJuggler::Shift
- TaskJuggler::ShiftAssignment
- TaskJuggler::ShiftAssignments
- TaskJuggler::ShiftAssignmentsAttribute
- TaskJuggler::ShiftScenario
- TaskJuggler::SimpleQueryExpander
- TaskJuggler::SortListAttribute
- TaskJuggler::StatusSheetReceiver
- TaskJuggler::StatusSheetReport
- TaskJuggler::StatusSheetSender
- TaskJuggler::StdIoWrapper
- TaskJuggler::StringAttribute
- TaskJuggler::SymbolAttribute
- TaskJuggler::SymbolListAttribute
- TaskJuggler::SyntaxReference
- TaskJuggler::TOCEntry
- TaskJuggler::TSResourceRecord
- TaskJuggler::TSTaskRecord
- TaskJuggler::TableColumnDefinition
- TaskJuggler::TableColumnSorter
- TaskJuggler::TableOfContents
- TaskJuggler::TableReport
- TaskJuggler::TableReportColumn
- TaskJuggler::TagFile
- TaskJuggler::TagFile::TagFileEntry
- TaskJuggler::Task
- TaskJuggler::TaskDepListAttribute
- TaskJuggler::TaskDependency
- TaskJuggler::TaskListAttribute
- TaskJuggler::TaskListRE
- TaskJuggler::TaskScenario
- TaskJuggler::TernarySearchTree
- TaskJuggler::TextFormatter
- TaskJuggler::TextParser
- TaskJuggler::TextParser::Macro
- TaskJuggler::TextParser::MacroTable
- TaskJuggler::TextParser::Pattern
- TaskJuggler::TextParser::Rule
- TaskJuggler::TextParser::Scanner
- TaskJuggler::TextParser::Scanner::BufferStreamHandle
- TaskJuggler::TextParser::Scanner::FileStreamHandle
- TaskJuggler::TextParser::Scanner::MacroStackEntry
- TaskJuggler::TextParser::Scanner::StreamHandle
- TaskJuggler::TextParser::SourceFileInfo
- TaskJuggler::TextParser::StackElement
- TaskJuggler::TextParser::State
- TaskJuggler::TextParser::StateTransition
- TaskJuggler::TextParser::TextParserResultArray
- TaskJuggler::TextParser::TokenDoc
- TaskJuggler::TextReport
- TaskJuggler::TimeInterval
- TaskJuggler::TimeIntervalListAttribute
- TaskJuggler::TimeSheet
- TaskJuggler::TimeSheetReceiver
- TaskJuggler::TimeSheetRecord
- TaskJuggler::TimeSheetReport
- TaskJuggler::TimeSheetSender
- TaskJuggler::TimeSheetSummary
- TaskJuggler::TimeSheets
- TaskJuggler::Tj3
- TaskJuggler::Tj3AppBase
- TaskJuggler::Tj3Client
- TaskJuggler::Tj3Daemon
- TaskJuggler::Tj3Man
- TaskJuggler::Tj3SheetAppBase
- TaskJuggler::Tj3SsReceiver
- TaskJuggler::Tj3SsSender
- TaskJuggler::Tj3TsReceiver
- TaskJuggler::Tj3TsSender
- TaskJuggler::Tj3TsSummary
- TaskJuggler::Tj3WebD
- TaskJuggler::TjException
- TaskJuggler::TjRuntimeError
- TaskJuggler::TjTime
- TaskJuggler::TjpExample
- TaskJuggler::TjpExportRE
- TaskJuggler::TjpSyntaxRules
- TaskJuggler::TraceReport
- TaskJuggler::URLParameter
- TaskJuggler::UserManual
- TaskJuggler::VimSyntax
- TaskJuggler::WebServer
- TaskJuggler::WelcomePage
- TaskJuggler::WorkingHours
- TaskJuggler::WorkingHoursAttribute
- TaskJuggler::XMLBlob
- TaskJuggler::XMLComment
- TaskJuggler::XMLDocument
- TaskJuggler::XMLElement
- TaskJuggler::XMLNamedText
- TaskJuggler::XMLText
- Diff
- Diff::Hunk
- AppConfig
- Diffable
- DiffableString
- Object
- RuntimeConfig
- String

No matching classes.

# TaskJuggler::TaskScenario

### Attributes

hasDurationSpec[R]
isRunAway[R]

### Public Class Methods

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

Create a new TaskScenario object.

    # File lib/taskjuggler/TaskScenario.rb, line 22
    def initialize(task, scenarioIdx, attributes)
      super
      # Attributed are only really created when they are accessed the first
      # time. So make sure some needed attributes really exist so we don't
      # have to check for existance each time we access them.
      %( allocate assignedresources booking charge chargeset complete
          competitors criticalness depends duration
          effort end forward gauge length
          maxend maxstart minend minstart milestone pathcriticalness
          precedes priority scheduled shifts start status ).each do |attr|
        @property[attr, @scenarioIdx]
      end

      # A list of all allocated leaf resources.
      @candidates = []
      @dCache = DataCache.instance
    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/taskjuggler/TaskScenario.rb, line 146
    def Xref
      @depends.each do |dependency|
        depTask = checkDependency(dependency, 'depends')
        @startpreds.push([ depTask, dependency.onEnd ])
        depTask[dependency.onEnd ? 'endsuccs' : 'startsuccs', @scenarioIdx].
          push([ @property, false ])
      end

      @precedes.each do |dependency|
        predTask = checkDependency(dependency, 'precedes')
        @endsuccs.push([ predTask, dependency.onEnd ])
        predTask[dependency.onEnd ? 'endpreds' : 'startpreds', @scenarioIdx].
          push([@property, true ])
      end
    end

addBooking(booking) click to toggle source

    # File lib/taskjuggler/TaskScenario.rb, line 1270
    def addBooking(booking)
      # This append operation will not trigger a copy to sub-scenarios.
      # Bookings are only valid for the scenario they are defined in.
      @booking << booking
    end

assignedResources(interval = nil) click to toggle source

Gather a list of Resource objects that have been assigned to the task (including sub tasks) for the given Interval interval.

    # File lib/taskjuggler/TaskScenario.rb, line 1712
    def assignedResources(interval = nil)
      interval = Interval.new(a('start'), a('end')) unless interval
      list = []

      if @property.container?
        @property.kids.each do |task|
          list += task.assignedResources(@scenarioIdx, interval)
        end
        list.uniq!
      else
        @assignedresources.each do |resource|
          if resource.allocated?(@scenarioIdx, interval, @property)
            list << resource
          end
        end
      end

      list
    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/taskjuggler/TaskScenario.rb, line 820
    def calcCriticalness
      @criticalness = 0.0
      @pathcriticalness = nil

      # Users feel that milestones are somewhat important. So we use an
      # arbitrary value larger than 0 for them. We make it priority dependent,
      # so the user has some control over it. Priority 0 is 0, 500 is 1.0 and
      # 1000 is 2.0. These values are pretty much randomly picked and probably
      # require some more tuning based on real projects.
      if @milestone
        @criticalness = @priority / 500.0
      end

      # Task without efforts of allocations are not critical.
      return if @effort <= 0 || @candidates.empty?

      # Determine the average criticalness of all allocated resources.
      criticalness = 0.0
      @candidates.each do |resource|
        criticalness += resource['criticalness', @scenarioIdx]
      end
      criticalness /= @candidates.length

      # The task criticalness is the product of effort and average resource
      # criticalness.
      @criticalness = @effort * criticalness
    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/taskjuggler/TaskScenario.rb, line 854
    def calcPathCriticalness(atEnd = false)
      # If we have computed this already, just return the value. If we are only
      # at the end of the task, we do not include the criticalness of this task
      # as it is not really part of the path.
      if @pathcriticalness
        return @pathcriticalness - (atEnd ? 0 : @criticalness)
      end

      maxCriticalness = 0.0

      if atEnd
        # At the end, we only care about pathes through the successors of this
        # task or its parent tasks.
        if (criticalness = calcPathCriticalnessEndSuccs) > maxCriticalness
          maxCriticalness = criticalness
        end
      else
        # At the start of the task, we have two options.
        if @property.container?
          # For container tasks, we ignore all dependencies and check the pathes
          # through all the children.
          @property.children.each do |task|
            if (criticalness = task.calcPathCriticalness(@scenarioIdx, false)) >
              maxCriticalness
              maxCriticalness = criticalness
            end
          end
        else
          # For leaf tasks, we check all pathes through the start successors and
          # then the pathes through the end successors of this task and all its
          # parent tasks.
          @startsuccs.each do |task, onEnd|
            if (criticalness = task.calcPathCriticalness(@scenarioIdx, onEnd)) >
              maxCriticalness
              maxCriticalness = criticalness
            end
          end

          if (criticalness = calcPathCriticalnessEndSuccs) > maxCriticalness
            maxCriticalness = criticalness
          end

          maxCriticalness += @criticalness
        end
      end

      @pathcriticalness = maxCriticalness
    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/taskjuggler/TaskScenario.rb, line 1066
    def canInheritDate?(atEnd)
      # Inheriting a start or end date from the enclosing task or the project
      # is allowed for the following scenarios:
      #   -  --> -   inhS*1  -  <-- -   inhE*1
      #   -  --> |   inhS    |  <-- -   inhE
      #   -  x-> -   inhS    -  <-x -   inhE
      #   -  x-> |   inhS    |  <-x -   inhE
      #   -  x-> -D  inhS    -D <-x -   inhE
      #   -  x-> |D  inhS    |D <-x -   inhE
      #   -  --> -D  inhS    -D <-- -   inhE
      #   -  --> |D  inhS    |D <-- -   inhE
      #   -  <-- |   inhS    |  --> -   inhE
      #
      #   *1 when no bookings but allocations are present

      thisEnd, thatEnd = atEnd ? [ 'end', 'start' ] : [ 'start', 'end' ]
      # Return false if we already have a date for this end or if we have a
      # strong dependency for this end.
      return false if instance_variable_get('@' + thisEnd) ||
                      hasStrongDeps?(atEnd)

      # Containter task can inherit the date if they have no dependencies at
      # this end.
      return true if @property.container?

      hasThatSpec = !instance_variable_get('@' + thatEnd).nil? ||
                    hasStrongDeps?(!atEnd)

      # Check for tasks that have no start and end spec, no duration spec but
      # allocates. They can inherit the start and end date.
      return true if hasThatSpec && !@hasDurationSpec && !@allocate.empty?

      if @forward ^ atEnd
        # the scheduling direction is pointing away from this end
        return true if @hasDurationSpec || !@booking.empty?

        return hasThatSpec
      else
        # the scheduling direction is pointing towards this end
        return !instance_variable_get('@' + thatEnd).nil? &&
               !@hasDurationSpec && @booking.empty? #&& @allocate.empty?
      end
    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/taskjuggler/TaskScenario.rb, line 790
    def candidates
      @candidates = []
      @allocate.each do |allocation|
        allocation.candidates.each do |candidate|
          candidate.allLeaves.each do |resource|
            @candidates << resource unless @candidates.include?(resource)
          end
        end
      end
      @candidates
    end

checkForLoops(path, atEnd, fromOutside, forward) click to toggle source

To ensure that we can properly schedule the project, we need to make sure that it does not contain any circular dependencies. This method recursively checks for such loops by remembering the path. Each entry is marks the start or end of a task. atEnd specifies whether we are currently at the start or end of the task. fromOutside specifies whether we are coming from a inside or outside that tasks. See specification below. forward specifies whether we are checking the dependencies from start to end or in the opposite direction. If we are moving forward, we only move from start to end of ASAP tasks, not ALAP tasks and vice versa. For milestones, we ignore the scheduling direction.

    # File lib/taskjuggler/TaskScenario.rb, line 623
    def checkForLoops(path, atEnd, fromOutside, forward)
      # Check if we have been here before on this path.
      if path.include?([ @property, atEnd ])
        warning('loop_detected',
                "Dependency loop detected at #{atEnd ? 'end' : 'start'} " +
                "of task #{@property.fullId}", false)
        skip = true
        path.each do |t, e|
          if t == @property && e == atEnd
            skip = false
            next
          end
          next if skip
          info("loop_at_#{e ? 'end' : 'start'}",
               "Loop ctnd. at #{e ? 'end' : 'start'} of task #{t.fullId}",
               t.sourceFileInfo)
        end
        error('loop_end', "Aborting")
      end
      # Used for debugging only
      if false
        pathText = ''
        path.each do |t, e|
          pathText += "#{t.fullId}(#{e ? 'end' : 'start'}) -> "
        end
        pathText += "#{@property.fullId}(#{atEnd ? 'end' : 'start'})"
        puts pathText
      end
      return if @deadEndFlags[(atEnd ? 2 : 0) + (fromOutside ? 1 : 0)]
      path << [ @property, atEnd ]

      # To find loops we have to traverse the graph in a certain order. When we
      # enter a task we can either come from outside or inside. The following
      # graph explains these definitions:
      #
      #             |      /          \      |
      #  outside    v    /              \    v   outside
      #          +------------------------------+
      #          |    /        Task        \    |
      #       -->|  o   <---          --->   o  |<--
      #          |/ Start                  End \|
      #         /+------------------------------+\
      #       /     ^                        ^     \
      #             |         inside         |
      #
      # At the top we have the parent task. At the botton the child tasks.
      # The horizontal arrors are start predecessors or end successors.
      # As the graph is doubly-linked, we need to becareful to only find real
      # loops. When coming from outside, we only continue to the inside and vice
      # versa. Horizontal moves are only made when we are in a leaf task.
      unless atEnd
        if fromOutside
          if @property.container?
            #
            #         |
            #         v
            #       +--------
            #    -->| o--+
            #       +----|---
            #            |
            #            V
            #
            @property.children.each do |child|
              child.checkForLoops(@scenarioIdx, path, false, true, forward)
            end
          else
            #         |
            #         v
            #       +--------
            #    -->| o---->
            #       +--------
            #
            if (forward && @forward) || @milestone
              checkForLoops(path, true, false, true)
            end
          end
        else
          if @startpreds.empty?
            #
            #         ^
            #         |
            #       +-|------
            #       | o <--
            #       +--------
            #         ^
            #         |
            #
            if @property.parent
              @property.parent.checkForLoops(@scenarioIdx, path, false, false,
                                             forward)
            end
          else

            #       +--------
            #    <---- o <--
            #       +--------
            #          ^
            #          |
            #
            @startpreds.each do |task, targetEnd|
              task.checkForLoops(@scenarioIdx, path, targetEnd, true, forward)
            end
          end
        end
      else
        if fromOutside
          if @property.container?
            #
            #          |
            #          v
            #    --------+
            #       +--o |<--
            #    ---|----+
            #       |
            #       v
            #
            @property.children.each do |child|
              child.checkForLoops(@scenarioIdx, path, true, true, forward)
            end
          else
            #          |
            #          v
            #    --------+
            #     <----o |<--
            #    --------+
            #
            if (!forward && !@forward) || @milestone
              checkForLoops(path, false, false, false)
            end
          end
        else
          if @endsuccs.empty?
            #
            #          ^
            #          |
            #    ------|-+
            #      --> o |
            #    --------+
            #          ^
            #          |
            #
            if @property.parent
              @property.parent.checkForLoops(@scenarioIdx, path, true, false,
                                             forward)
            end
          else
            #    --------+
            #      --> o---->
            #    --------+
            #          ^
            #          |
            #
            @endsuccs.each do |task, targetEnd|
              task.checkForLoops(@scenarioIdx, path, targetEnd, true, forward)
            end
          end
        end
      end

      path.pop
      @deadEndFlags[(atEnd ? 2 : 0) + (fromOutside ? 1 : 0)] = true
      # puts "Finished with #{@property.fullId} #{atEnd ? 'end' : 'start'} " +
      #      "#{fromOutside ? 'outside' : 'inside'}"
    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/taskjuggler/TaskScenario.rb, line 1604
    def collectTimeOffIntervals(iv, minDuration)
      # This function is often called recursively for the same parameters. We
      # store the results in the cache to avoid repeated computations of the
      # same results.
      @dCache.cached(self, :TaskScenarioCollectTimeOffIntervals, iv,
                     minDuration) do
        il = IntervalList.new
        il << TimeInterval.new(@project['start'], @project['end'])
        if @property.leaf?
          unless (resources = @assignedresources).empty?
            # The task has assigned resources, so we can use their common time
            # off intervals.
            resources.each do |resource|
              il &= resource.collectTimeOffIntervals(@scenarioIdx, iv,
                                                     minDuration)
            end
          else
            # The task has no assigned resources. We simply use the global time
            # off intervals.
            il &= @project.collectTimeOffIntervals(iv, minDuration)
          end
        else
          @property.kids.each do |task|
            il &= task.collectTimeOffIntervals(@scenarioIdx, iv, minDuration)
          end
        end

        il
      end
    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/taskjuggler/TaskScenario.rb, line 806
    def countResourceAllocations
      return if @candidates.empty? || @effort <= 0

      avgEffort = @effort / @candidates.length
      @candidates.each do |resource|
        resource['alloctdeffort', @scenarioIdx] += avgEffort
      end
    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/taskjuggler/TaskScenario.rb, line 1159
    def earliestStart
      # This is the date that we will return.
      startDate = nil
      @depends.each do |dependency|
        potentialStartDate =
          dependency.task[dependency.onEnd ? 'end' : 'start', @scenarioIdx]
        return nil if potentialStartDate.nil?

        # Determine the end date of a 'length' gap.
        dateAfterLengthGap = potentialStartDate
        gapLength = dependency.gapLength
        while gapLength > 0 && dateAfterLengthGap < @project['end'] do
          if @project.isWorkingTime(dateAfterLengthGap)
            gapLength -= 1
          end
          dateAfterLengthGap += @project['scheduleGranularity']
        end

        # Determine the end date of a 'duration' gap.
        if dateAfterLengthGap > potentialStartDate + dependency.gapDuration
          potentialStartDate = dateAfterLengthGap
        else
          potentialStartDate += dependency.gapDuration
        end

        startDate = potentialStartDate if startDate.nil? ||
                                          startDate < potentialStartDate
      end

      # If any of the parent tasks has an explicit start date, the task must
      # start at or after this date.
      task = @property
      while (task = task.parent) do
        if task['start', @scenarioIdx] &&
           (startDate.nil? || task['start', @scenarioIdx] > startDate)
          startDate = task['start', @scenarioIdx]
          break
        end
      end

      # When the computed start date is after the already determined end date
      # of the task, the start dependencies were too weak. This happens when
      # task B depends on A and they are specified this way:
      # task A: | --> D-
      # task B: -D <-- |
      if @end && startDate > @end
        error('weak_start_dep',
              "Task #{@property.fullId} has a too weak start dependencies " +
              "to be scheduled properly.")
      end

      startDate
    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/taskjuggler/TaskScenario.rb, line 379
    def finishScheduling
      # Recursively descend into all child tasks.
      @property.children.each do |task|
        task.finishScheduling(@scenarioIdx)
      end

      @property.parents.each do |pTask|
        # Add the assigned resources to the parent task's list.
        @assignedresources.each do |resource|
          unless pTask['assignedresources', @scenarioIdx].include?(resource)
            pTask['assignedresources', @scenarioIdx] << resource
          end
        end
      end

      # These lists are no longer needed, so let's save some memory. Set it to
      # nil so we can detect accidental use.
      @candidates = nil
      @mandatories = nil
      @allLimits = nil
    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/taskjuggler/TaskScenario.rb, line 1543
    def getAllocatedTime(startIdx, endIdx, resource = nil)
      return 0.0 if @milestone || startIdx >= endIdx ||
                    (resource && !@assignedresources.include?(resource))

      @dCache.cached(self, :TaskScenarioAllocatedTime, startIdx, endIdx, resource) do
        allocatedTime = 0.0
        if @property.container?
          @property.kids.each do |task|
            allocatedTime += task.getAllocatedTime(@scenarioIdx,
                                                   startIdx, endIdx, resource)
          end
        else
          if resource
            allocatedTime += resource.getAllocatedTime(@scenarioIdx,
                                                       startIdx, endIdx,
                                                       @property)
          else
            @assignedresources.each do |r|
              allocatedTime += r.getAllocatedTime(@scenarioIdx, startIdx, endIdx,
                                                  @property)
            end
          end
        end
        allocatedTime
      end
    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/taskjuggler/TaskScenario.rb, line 1573
    def getEffectiveWork(startIdx, endIdx, resource = nil)
      # Make sure we have the real Resource and not a proxy.
      resource = resource.ptn if resource
      return 0.0 if @milestone || startIdx >= endIdx ||
                    (resource && !@assignedresources.include?(resource))

      @dCache.cached(self, :TaskScenarioEffectiveWork, startIdx, endIdx,
                     resource) do
        workLoad = 0.0
        if @property.container?
          @property.kids.each do |task|
            workLoad += task.getEffectiveWork(@scenarioIdx, startIdx, endIdx,
                                              resource)
          end
        else
          if resource
            workLoad += resource.getEffectiveWork(@scenarioIdx, startIdx,
                                                  endIdx, @property)
          else
            @assignedresources.each do |r|
              workLoad += r.getEffectiveWork(@scenarioIdx, startIdx, endIdx,
                                             @property)
            end
          end
        end
        workLoad
      end
    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/taskjuggler/TaskScenario.rb, line 164
    def hasDependency?(depType, target, onEnd)
      a(depType).include?([target, onEnd])
    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/taskjuggler/TaskScenario.rb, line 1696
    def hasResourceAllocated?(interval, resource)
      return false unless @assignedresources.include?(resource)

      if @property.leaf?
        return resource.allocated?(@scenarioIdx, interval, @property)
      else
        @property.kids.each do |t|
          return true if t.hasResourceAllocated?(@scenarioIdx, interval,
                                                 resource)
        end
      end
      false
    end

isDependencyOf(task, depth, list = []) 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/taskjuggler/TaskScenario.rb, line 1638
    def isDependencyOf(task, depth, list = [])
      return true if task == @property

      # If this task is already in the list of traversed task, we can ignore
      # it.
      return false if list.include?(@property)
      list << @property

      @startsuccs.each do |t, onEnd|
        unless onEnd
          # must be a start->start dependency
          return true if t.isDependencyOf(@scenarioIdx, task, depth, list)
        end
      end

      # For task to depend on this task, the start of task must be after the
      # end of this task.
      if task['start', @scenarioIdx] && @end
        return false if task['start', @scenarioIdx] < @end
      end

      # Check if any of the parent tasks is a dependency of _task_.
      t = @property.parent
      while t
        # If the parent is a dependency, than all childs are as well.
        return true if t.isDependencyOf(@scenarioIdx, task, depth, list)
        t = t.parent
      end

      return false if depth == 1

      @endsuccs.each do |ta, onEnd|
        unless onEnd
          # must be an end->start dependency
          return true if ta.isDependencyOf(@scenarioIdx, task, depth - 1, list)
        end
      end

      false
    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/taskjuggler/TaskScenario.rb, line 1681
    def isFeatureOf(task)
      sources = @property.all
      destinations = task.all

      sources.each do |s|
        destinations.each do |d|
          return true if s.isDependencyOf(@scenarioIdx, d, 0)
        end
      end

      false
    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/taskjuggler/TaskScenario.rb, line 1216
    def latestEnd
      # This is the date that we will return.
      endDate = nil
      @precedes.each do |dependency|
        potentialEndDate =
          dependency.task[dependency.onEnd ? 'end' : 'start', @scenarioIdx]
        return nil if potentialEndDate.nil?

        # Determine the end date of a 'length' gap.
        dateBeforeLengthGap = potentialEndDate
        gapLength = dependency.gapLength
        while gapLength > 0 && dateBeforeLengthGap > @project['start'] do
          if @project.isWorkingTime(dateBeforeLengthGap -
                                    @project['scheduleGranularity'])
            gapLength -= 1
          end
          dateBeforeLengthGap -= @project['scheduleGranularity']
        end

        # Determine the end date of a 'duration' gap.
        if dateBeforeLengthGap < potentialEndDate - dependency.gapDuration
          potentialEndDate = dateBeforeLengthGap
        else
          potentialEndDate -= dependency.gapDuration
        end

        endDate = potentialEndDate if endDate.nil? || endDate > potentialEndDate
      end

      # If any of the parent tasks has an explicit end date, the task must end
      # at or before this date.
      task = @property
      while (task = task.parent) do
        if task['end', @scenarioIdx] &&
           (endDate.nil? || task['end', @scenarioIdx] < endDate)
          endDate = task['end', @scenarioIdx]
          break
        end
      end

      # When the computed end date is before the already determined start date
      # of the task, the end dependencies were too weak. This happens when
      # task A precedes B and they are specified this way:
      # task A: | --> D-
      # task B: -D <-- |
      if @start && (endDate.nil? || endDate > @start)
        error('weak_end_dep',
              "Task #{@property.fullId} has a too weak end dependencies " +
              "to be scheduled properly.")
      end

      endDate
    end

markAsScheduled() click to toggle source

    # File lib/taskjuggler/TaskScenario.rb, line 40
    def markAsScheduled
      return if @scheduled
      @scheduled = true
      if @milestone
        typename = 'Milestone'
      elsif @property.leaf?
        typename = 'Task'
      else
        typename = 'Container'
      end

      Log.msg { "#{typename} #{@property.fullId} has been scheduled." }
    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/taskjuggler/TaskScenario.rb, line 404
    def postScheduleCheck
      @errors = 0
      @property.children.each do |task|
        @errors += 1 unless task.postScheduleCheck(@scenarioIdx)
      end

      # There is no point to check the parent if the child(s) have errors.
      return false if @errors > 0

      # Same for runaway tasks. They have already been reported.
      if @isRunAway
        error('sched_runaway', "Some tasks did not fit into the project time " +
              "frame.")
      end

      # Make sure the task is marked complete
      unless @scheduled
        error('not_scheduled',
              "Task #{@property.fullId} has not been marked as scheduled.")
      end

      # If the task has a follower or predecessor that is a runaway this task
      # is also incomplete.
      (@startsuccs + @endsuccs).each do |task, onEnd|
        return false if task.isRunAway(@scenarioIdx)
      end
      (@startpreds + @endpreds).each do |task, onEnd|
        return false if task.isRunAway(@scenarioIdx)
      end

      # Check if the start time is ok
      if @start.nil?
        error('task_start_undef',
              "Task #{@property.fullId} has undefined start time")
      end
      if @start < @project['start'] || @start > @project['end']
        error('task_start_range',
              "The start time (#{@start}) of task #{@property.fullId} " +
              "is outside the project interval (#{@project['start']} - " +
              "#{@project['end']})")
      end
      if !@minstart.nil? && @start < @minstart
        warning('minstart',
               "The start time (#{@start}) of task #{@property.fullId} " +
               "is too early. Must be after #{@minstart}.")
      end
      if !@maxstart.nil? && @start > @maxstart
        warning('maxstart',
               "The start time (#{@start}) of task #{@property.fullId} " +
               "is too late. Must be before #{@maxstart}.")
      end
      # Check if the end time is ok
      error('task_end_undef',
            "Task #{@property.fullId} has undefined end time") if @end.nil?
      if @end < @project['start'] || @end > @project['end']
        error('task_end_range',
              "The end time (#{@end}) of task #{@property.fullId} " +
              "is outside the project interval (#{@project['start']} - " +
              "#{@project['end']})")
      end
      if !@minend.nil? && @end < @minend
        warning('minend',
                "The end time (#{@end}) of task #{@property.fullId} " +
                "is too early. Must be after #{@minend}.")
      end
      if !@maxend.nil? && @end > @maxend
        warning('maxend',
                "The end time (#{@end}) of task #{@property.fullId} " +
                "is too late. Must be before #{@maxend}.")
      end
      # Make sure the start is before the end
      if @start > @end
        error('start_after_end',
              "The start time (#{@start}) of task #{@property.fullId} " +
              "is after the end time (#{@end}).")
      end

      # Check that tasks fits into parent task.
      unless (parent = @property.parent).nil? ||
              parent['start', @scenarioIdx].nil? ||
              parent['end', @scenarioIdx].nil?
        if @start < parent['start', @scenarioIdx]
          error('task_start_in_parent',
                "The start date (#{@start}) of task #{@property.fullId} " +
                "is before the start date (#{parent['start', @scenarioIdx]}) " +
                "of the enclosing task.")
        end
        if @end > parent['end', @scenarioIdx]
          error('task_end_in_parent',
                "The end date (#{@end}) of task #{@property.fullId} " +
                "is after the end date (#{parent['end', @scenarioIdx]}) " +
                "of the enclosing task.")
        end
      end

      # Check that all preceding tasks start/end before this task.
      @depends.each do |dependency|
        task = dependency.task
        limit = task[dependency.onEnd ? 'end' : 'start', @scenarioIdx]
        next if limit.nil?
        if @start < limit
          error('task_pred_before',
                "Task #{@property.fullId} (#{@start}) must start after " +
                "#{dependency.onEnd ? 'end' : 'start'} (#{limit}) of task " +
                "#{task.fullId}.")
        end
        if dependency.gapDuration > 0
          if limit + dependency.gapDuration > @start
            error('task_pred_before_gd',
                  "Task #{@property.fullId} must start " +
                  "#{dependency.gapDuration / (60 * 60 * 24)} days after " +
                  "#{dependency.onEnd ? 'end' : 'start'} of task " +
                  "#{task.fullId}. TaskJuggler cannot enforce this condition " +
                  "because the task is scheduled ALAP (finish-to-start) or " +
                  "has a fixed #{dependency.onEnd ? 'end' : 'start'} date.")
          end
        end
        if dependency.gapLength > 0
          if calcLength(limit, @start) < dependency.gapLength
            error('task_pred_before_gl',
                  "Task #{@property.fullId} must start " +
                  "#{@project.slotsToDays(dependency.gapLength)} " +
                  "working days after " +
                  "#{dependency.onEnd ? 'end' : 'start'} of task " +
                  "#{task.fullId}. TaskJuggler cannot enforce this condition " +
                  "because the task is scheduled ALAP (finish-to-start) or " +
                  "has a fixed #{dependency.onEnd ? 'end' : 'start'} date.")
          end
        end
      end

      # Check that all following tasks end before this task
      @precedes.each do |dependency|
        task = dependency.task
        limit = task[dependency.onEnd ? 'end' : 'start', @scenarioIdx]
        next if limit.nil?
        if limit < @end
          error('task_succ_after',
                "Task #{@property.fullId} (#{@end}) must end before " +
                "#{dependency.onEnd ? 'end' : 'start'} (#{limit}) of task " +
                "#{task.fullId}.")
        end
        if dependency.gapDuration > 0
          if limit - dependency.gapDuration < @end
            error('task_succ_after_gd',
                  "Task #{@property.fullId} must end " +
                  "#{dependency.gapDuration / (60 * 60 * 24)} days before " +
                  "#{dependency.onEnd ? 'end' : 'start'} of task " +
                  "#{task.fullId}. TaskJuggler cannot enforce this condition " +
                  "because the task is scheduled ASAP (start-to-finish) or " +
                  "has a fixed #{dependency.onEnd ? 'end' : 'start'} date.")
          end
        end
        if dependency.gapLength > 0
          if calcLength(@end, limit) < dependency.gapLength
            error('task_succ_after_gl',
                  "Task #{@property.fullId} must end " +
                  "#{@project.slotsToDays(dependency.gapLength)} " +
                  "working days before " +
                  "#{dependency.onEnd ? 'end' : 'start'} of task " +
                  "#{task.fullId}. TaskJuggler cannot enforce this condition " +
                  "because the task is scheduled ASAP (start-to-finish) or " +
                  "has a fixed #{dependency.onEnd ? 'end' : 'start'} date.")
          end
        end
      end

      if @milestone && @start != @end
        error('milestone_times_equal',
              "Milestone #{@property.fullId} must have identical start and " +
              "end date.")
      end

      if @property.leaf? && @effort == 0 && !@milestone && !@allocate.empty? &&
         @assignedresources.empty?
        # The user used an 'allocate' for the task, but did not specify any
        # 'effort'. Actual allocations will only happen when resources are
        # available by chance. If there are no assigned resources, we generate
        # a warning as this is probably not what the user intended.
        warning('allocate_no_assigned',
                "Task #{@property.id} has resource allocation requested, but " +
                "did not get any resources assigned. Either use 'effort' " +
                "to ensure allocations or use a higher 'priority'.")
      end

      thieves = []
      @competitors.each do |t|
        thieves << t if t['priority', @scenarioIdx] < @priority
      end
      unless thieves.empty?
        warning('priority_inversion',
                "Due to a mix of ALAP and ASAP scheduled tasks or a " +
                "dependency on a lower priority tasks the following " +
                "task#{thieves.length > 1 ? 's' : ''} stole resources from " +
                "#{@property.fullId} despite having a lower priority:")
        thieves.each do |t|
          info('priority_inversion_info', "Task #{t.fullId}", t.sourceFileInfo)
        end
      end

      @errors == 0
    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/taskjuggler/TaskScenario.rb, line 190
    def preScheduleCheck
      # Accounts can have sub accounts added after being used in a chargetset.
      # So we need to re-test here.
      @chargeset.each do |chargeset|
        chargeset.each do |account, share|
          unless account.leaf?
            error('account_no_leaf',
                "Chargesets may not include group account #{account.fullId}.")
          end
        end
      end

      # Leaf tasks can be turned into containers after bookings have been added.
      # We need to check for this.
      unless @property.leaf? || @booking.empty?
        error('container_booking',
              "Container task #{@property.fullId} may not have bookings.")
      end

      # Milestones may not have bookings.
      if @milestone && !@booking.empty?
        error('milestone_booking',
              "Milestone #{@property.fullId} may not have bookings.")
      end

      # All 'scheduled' tasks must have a fixed start and end date.
      if @scheduled && (@start.nil? || @end.nil?)
        error('not_scheduled',
              "Task #{@property.fullId} is marked as scheduled but does not " +
              'have a fixed start and end date.')
      end

      # If an effort has been specified resources must be allocated as well.
      if @effort > 0 && @allocate.empty?
        error('effort_no_allocations',
              "Task #{@property.fullId} has an effort but no resource " +
              "allocations.")
      end

      durationSpecs = 0
      durationSpecs += 1 if @effort > 0
      durationSpecs += 1 if @length > 0
      durationSpecs += 1 if @duration > 0
      durationSpecs += 1 if @milestone

      # The rest of this function performs a number of plausibility tests with
      # regards to task start and end critiria. To explain the various cases,
      # the following symbols are used:
      #
      # |: fixed start or end date
      # -: no fixed start or end date
      # M: Milestone
      # D: start or end dependency
      # x->: ASAP task with duration criteria
      # <-x: ALAP task with duration criteria
      # -->: ASAP task without duration criteria
      # <--: ALAP task without duration criteria

      if @property.container?
        if durationSpecs > 0
          error('container_duration',
                "Container task #{@property.fullId} may not have a duration " +
                "or be marked as milestones.")
        end
      elsif @milestone
        if durationSpecs > 1
          error('milestone_duration',
                "Milestone task #{@property.fullId} may not have a duration.")
        end
        # Milestones can have the following cases:
        #
        #   |  M -   ok     |D M -   ok     - M -   err1   -D M -   ok
        #   |  M |   err2   |D M |   err2   - M |   ok     -D M |   ok
        #   |  M -D  ok     |D M -D  ok     - M -D  ok     -D M -D  ok
        #   |  M |D  err2   |D M |D  err2   - M |D  ok     -D M |D  ok

        # err1: no start and end
        # already handled by 'start_undetermed' or 'end_undetermed'

        # err2: differnt start and end dates
        if @start && @end && @start != @end
          error('milestone_start_end',
                "Start (#{@start}) and end (#{@end}) dates of " +
                "milestone task #{@property.fullId} must be identical.")
        end
      else
        #   Error table for non-container, non-milestone tasks:
        #   AMP: Automatic milestone promotion for underspecified tasks when
        #        no bookings or allocations are present.
        #   AMPi: Automatic milestone promotion when no bookings or
        #   allocations are present. When no bookings but allocations are
        #   present the task inherits start and end date.
        #   Ref. implicitXref()|
        #   inhS: Inherit start date from parent task or project
        #   inhE: Inherit end date from parent task or project
        #
        #   | x-> -   ok     |D x-> -   ok     - x-> -   inhS   -D x-> -   ok
        #   | x-> |   err1   |D x-> |   err1   - x-> |   inhS   -D x-> |   err1
        #   | x-> -D  ok     |D x-> -D  ok     - x-> -D  inhS   -D x-> -D  ok
        #   | x-> |D  err1   |D x-> |D  err1   - x-> |D  inhS   -D x-> |D  err1
        #   | --> -   AMP    |D --> -   AMP    - --> -   AMPi   -D --> -   AMP
        #   | --> |   ok     |D --> |   ok     - --> |   inhS   -D --> |   ok
        #   | --> -D  ok     |D --> -D  ok     - --> -D  inhS   -D --> -D  ok
        #   | --> |D  ok     |D --> |D  ok     - --> |D  inhS   -D --> |D  ok
        #   | <-x -   inhE   |D <-x -   inhE   - <-x -   inhE   -D <-x -   inhE
        #   | <-x |   err1   |D <-x |   err1   - <-x |   ok     -D <-x |   ok
        #   | <-x -D  err1   |D <-x -D  err1   - <-x -D  ok     -D <-x -D  ok
        #   | <-x |D  err1   |D <-x |D  err1   - <-x |D  ok     -D <-x |D  ok
        #   | <-- -   inhE   |D <-- -   inhE   - <-- -   AMP    -D <-- -   inhE
        #   | <-- |   ok     |D <-- |   ok     - <-- |   AMP    -D <-- |   ok
        #   | <-- -D  ok     |D <-- -D  ok     - <-- -D  AMP    -D <-- -D  ok
        #   | <-- |D  ok     |D <-- |D  ok     - <-- |D  AMP    -D <-- |D  ok

        # These cases are normally autopromoted to milestones or inherit their
        # start or end dates. But this only works for tasks that have no
        # allocations or bookings.
        #   -  --> -
        #   |  --> -
        #   |D --> -
        #   -D --> -
        #   -  <-- -
        #   -  <-- |
        #   -  <-- -D
        #   -  <-- |D
        if durationSpecs == 0 &&
           ((@forward && @end.nil? && !hasDependencies(true)) ||
            (!@forward && @start.nil? && !hasDependencies(false)))
          error('task_underspecified',
                "Task #{@property.fullId} has too few specifations to be " +
                "scheduled.")
        end

        #   err1: Overspecified (12 cases)
        #   |  x-> |
        #   |  <-x |
        #   |  x-> |D
        #   |  <-x |D
        #   |D x-> |
        #   |D <-x |
        #   |D <-x |D
        #   |D x-> |D
        #   -D x-> |
        #   -D x-> |D
        #   |D <-x -D
        #   |  <-x -D
        if durationSpecs > 1
          error('multiple_durations',
                "Tasks may only have either a duration, length or effort or " +
                "be a milestone.")
        end
        startSpeced = @property.provided('start', @scenarioIdx)
        endSpeced = @property.provided('end', @scenarioIdx)
        if ((startSpeced && endSpeced) ||
            (hasDependencies(false) && @forward && endSpeced) ||
            (hasDependencies(true) && !@forward && startSpeced)) &&
           durationSpecs > 0 && !@property.provided('scheduled', @scenarioIdx)
          error('task_overspecified',
                "Task #{@property.fullId} has a start, an end and a " +
                'duration specification.')
        end
      end

      if !@booking.empty? && !@forward && !@scheduled
        error('alap_booking',
              'A task scheduled in ALAP mode may only have bookings if it ' +
              'has been marked as fully scheduled. Keep in mind that ' +
              'certain attributes like \end\ or \precedes\ automatically ' +
              'switch the task to ALAP mode.')
      end

      @startsuccs.each do |task, onEnd|
        unless task['forward', @scenarioIdx]
          task.data[@scenarioIdx].error(
            'onstart_wrong_direction',
            'Tasks with on-start dependencies must be ASAP scheduled')
        end
      end
      @endpreds.each do |task, onEnd|
        if task['forward', @scenarioIdx]
          task.data[@scenarioIdx].error(
            'onend_wrong_direction',
            'Tasks with on-end dependencies must be ALAP scheduled')
        end
      end
    end

prepareScheduling() click to toggle source

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

    # File lib/taskjuggler/TaskScenario.rb, line 56
    def prepareScheduling
      @property['startpreds', @scenarioIdx] = []
      @property['startsuccs', @scenarioIdx] = []
      @property['endpreds', @scenarioIdx] = []
      @property['endsuccs', @scenarioIdx] = []

      @isRunAway = false

      # And as global scoreboard index
      @currentSlotIdx = nil
      # The 'done' variables count scheduled values in number of time slots.
      @doneDuration = 0
      @doneLength = 0
      # Due to the 'efficiency' factor the effort slots must be a float.
      @doneEffort = 0.0

      @projectionMode = @project.scenario(@scenarioIdx).get('projection')
      @nowIdx = @project.dateToIdx(@project['now'])

      @startIsDetermed = nil
      @endIsDetermed = nil

      # To avoid multiple calls to propagateDate() we use these flags to know
      # when we've done it already.
      @startPropagated = false
      @endPropagated = false

      @durationType =
        if @effort > 0
          @hasDurationSpec = true
          :effortTask
        elsif @length > 0
          @hasDurationSpec = true
          :lengthTask
        elsif @duration > 0
          @hasDurationSpec = true
          :durationTask
        else
          # If the task is set as milestone it has a duration spec.
          @hasDurationSpec = @milestone
          :startEndTask
        end

      markAsMilestone

      # For start-end-tasks without allocation, we don't have to do
      # anything but to set the 'scheduled' flag.
      if @durationType == :startEndTask && @start && @end && @allocate.empty?
        markAsScheduled
      end

      # Collect the limits of this task and all parent tasks into a single
      # Array.
      @allLimits = []
      task = @property
      # Reset the counters of all limits of this task.
      task['limits', @scenarioIdx].reset if task['limits', @scenarioIdx]
      until task.nil?
        if task['limits', @scenarioIdx]
          @allLimits << task['limits', @scenarioIdx]
        end
        task = task.parent
      end

      # Collect the mandatory allocations.
      @mandatories = []
      @allocate.each do |allocation|
        @mandatories << allocation if allocation.mandatory
        allocation.lockedResource = nil
      end

      bookBookings

      if @durationType == :startEndTask
        @startIdx = @project.dateToIdx(@start) if @start
        @endIdx = @project.dateToIdx(@end) if @end
      end
    end

propagateDate(date, atEnd, ignoreEffort = false) 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/taskjuggler/TaskScenario.rb, line 975
    def propagateDate(date, atEnd, ignoreEffort = false)
      logTag = "propagateDate_#{@property.id}_#{atEnd ? 'end' : 'start'}"
      Log.enter(logTag, "Propagating #{atEnd ? 'end' : 'start'} date " +
                        "to task #{@property.id}")
      thisEnd = atEnd ? 'end' : 'start'
      otherEnd = atEnd ? 'start' : 'end'
      #puts "Propagating #{thisEnd} date #{date} of #{@property.fullId} " +
      #     "#{ignoreEffort ? "ignoring effort" : "" }"

      # These flags are just used to avoid duplicate calls of this function
      # during propagateInitialValues().
      if atEnd
        @endPropagated = true
      else
        @startPropagated = true
      end

      # For leaf tasks, propagate start may set the date. Container task dates
      # are only set in scheduleContainer().
      if @property.leaf?
        instance_variable_set(('@' + thisEnd).intern, date)
        typename = 'Task'
        if @durationType == :startEndTask
          instance_variable_set(('@' + thisEnd + 'Idx').intern,
                                @project.dateToIdx(date))
          if @milestone
            typename = 'Milestone'
          end
        end
        Log.msg { "Update #{typename} #{@property.fullId}: #{period_to_s}" }
      end

      if @milestone
        # Start and end date of a milestone are identical.
        markAsScheduled
        if a(otherEnd).nil?
          propagateDate(a(thisEnd), !atEnd)
        end
      elsif !@scheduled && @start && @end &&
            !(@length == 0 && @duration == 0 && @effort == 0 &&
              !@allocate.empty?)
        markAsScheduled
      end

      # Propagate date to all dependent tasks. Don't do this for start
      # successors or end predecessors if this task is effort based. In this
      # case, the date might still change to align with the first/last
      # allocation. In these cases, bookResource() has to propagate the final
      # date.
      if atEnd
        if ignoreEffort || @effort == 0
          @endpreds.each do |task, onEnd|
            propagateDateToDep(task, onEnd)
          end
        end
        @endsuccs.each do |task, onEnd|
          propagateDateToDep(task, onEnd)
        end
      else
        if ignoreEffort || @effort == 0
          @startsuccs.each do |task, onEnd|
            propagateDateToDep(task, onEnd)
          end
        end
        @startpreds.each do |task, onEnd|
          propagateDateToDep(task, onEnd)
        end
      end

      # Propagate date to sub tasks which have only an implicit
      # dependency on the parent task and no other criteria for this end of
      # the task.
      @property.children.each do |task|
        if task.canInheritDate?(@scenarioIdx, atEnd)
          task.propagateDate(@scenarioIdx, date, atEnd)
        end
      end

      # The date propagation might have completed the date set of the enclosing
      # containter task. If so, we can schedule it as well.
      @property.parents.each do |parent|
        parent.scheduleContainer(@scenarioIdx)
      end
      Log.exit(logTag, "Finished propagation of " +
                       "#{atEnd ? 'end' : 'start'} date " +
                       "to task #{@property.id}")
    end

propagateInitialValues() click to toggle source

    # File lib/taskjuggler/TaskScenario.rb, line 168
    def propagateInitialValues
      unless @startPropagated
        if @start
          propagateDate(@start, false, true)
        elsif @property.parent.nil? &&
              @property.canInheritDate?(@scenarioIdx, false)
          propagateDate(@project['start'], false, true)
        end
      end

      unless @endPropagated
        if @end
          propagateDate(@end, true, true)
        elsif @property.parent.nil? &&
              @property.canInheritDate?(@scenarioIdx, true)
          propagateDate(@project['end'], true, true)
        end
      end
    end

query_activetasks(query) click to toggle source

    # File lib/taskjuggler/TaskScenario.rb, line 1276
    def query_activetasks(query)
      count = activeTasks(query)

      query.sortable = query.numerical = count
      # For the string output, we only use integer numbers.
      query.string = "#{count.to_i}"
    end

query_closedtasks(query) click to toggle source

    # File lib/taskjuggler/TaskScenario.rb, line 1284
    def query_closedtasks(query)
      count = closedTasks(query)

      query.sortable = query.numerical = count
      # For the string output, we only use integer numbers.
      query.string = "#{count.to_i}"
    end

query_competitorcount(query) click to toggle source

    # File lib/taskjuggler/TaskScenario.rb, line 1292
    def query_competitorcount(query)
      query.sortable = query.numerical = @competitors.length
      query.string = "#{@competitors.length}"
    end

query_complete(query) click to toggle source

    # File lib/taskjuggler/TaskScenario.rb, line 1297
    def query_complete(query)
      # If we haven't calculated the value yet, calculate it first.
      unless @complete
        calcCompletion
      end

      query.sortable = query.numerical = @complete
      # For the string output, we only use integer numbers.
      query.string = "#{@complete.to_i}%"
    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/taskjuggler/TaskScenario.rb, line 1311
    def query_cost(query)
      if query.costAccount
        query.sortable = query.numerical = cost =
          turnover(query.startIdx, query.endIdx, query.costAccount,
                   query.scopeProperty)
        query.string = query.currencyFormat.format(cost)
      else
        query.string = 'No \balance\ defined!'
      end
    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/taskjuggler/TaskScenario.rb, line 1324
    def query_duration(query)
      query.sortable = query.numerical = duration =
        (@end - @start) / (60 * 60 * 24)
      query.string = query.scaleDuration(duration)
    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/taskjuggler/TaskScenario.rb, line 1358
    def query_effort(query)
      query.sortable = query.numerical = work =
        getEffectiveWork(query.startIdx, query.endIdx, query.scopeProperty)
      query.string = query.scaleLoad(work)
    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/taskjuggler/TaskScenario.rb, line 1333
    def query_effortdone(query)
      # For this query, we always override the query period.
      query.sortable = query.numerical = effort =
        getEffectiveWork(@project.dateToIdx(@project['start'], false),
                         @project.dateToIdx(@project['now']),
                         query.scopeProperty)
      query.string = query.scaleLoad(effort)
    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/taskjuggler/TaskScenario.rb, line 1346
    def query_effortleft(query)
      # For this query, we always override the query period.
      query.sortable = query.numerical = effort =
        getEffectiveWork(@project.dateToIdx(@project['now']),
                         @project.dateToIdx(@project['end'], false),
                         query.scopeProperty)
      query.string = query.scaleLoad(effort)
    end

query_followers(query) click to toggle source

    # File lib/taskjuggler/TaskScenario.rb, line 1364
    def query_followers(query)
      list = []

      # First gather the task that depend on the start of this task.
      @startsuccs.each do |task, onEnd|
        if onEnd
          date = task['end', query.scenarioIdx].to_s(query.timeFormat)
          dep = "[->]"
        else
          date = task['start', query.scenarioIdx].to_s(query.timeFormat)
          dep = "[->["
        end
        list << generateDepencyListItem(query, task, dep, date)
      end
      # Than add the tasks that depend on the end of this task.
      @endsuccs.each do |task, onEnd|
        if onEnd
          date = task['end', query.scenarioIdx].to_s(query.timeFormat)
          dep = "]->]"
        else
          date = task['start', query.scenarioIdx].to_s(query.timeFormat)
          dep = "]->["
        end
        list << generateDepencyListItem(query, task, dep, date)
      end

      query.assignList(list)
    end

query_gauge(query) click to toggle source

    # File lib/taskjuggler/TaskScenario.rb, line 1393
    def query_gauge(query)
      # If we haven't calculated the schedule status yet, calculate it first.
      calcGauge unless @gauge

      query.string = @gauge
    end

query_headcount(query) click to toggle source

The number of different resources assigned to the task during the query interval. Each resource is counted based on their mathematically rounded efficiency.

    # File lib/taskjuggler/TaskScenario.rb, line 1403
    def query_headcount(query)
      headcount = 0
      assignedResources(Interval.new(query.start, query.end)).each do |res|
        headcount += res['efficiency', @scenarioIdx].round
      end

      query.sortable = query.numerical = headcount
      query.string = query.numberFormat.format(headcount)
    end

query_inputs(query) click to toggle source

    # File lib/taskjuggler/TaskScenario.rb, line 1413
    def query_inputs(query)
      inputList = PropertyList.new(@project.tasks, false)
      inputs(inputList, true)
      inputList.delete(@property)
      inputList.setSorting([['start', true, @scenarioIdx],
                            ['seqno', true, -1 ]])
      inputList.sort!

      query.assignList(generateTaskList(inputList, query))
    end

query_maxend(query) click to toggle source

    # File lib/taskjuggler/TaskScenario.rb, line 1424
    def query_maxend(query)
      queryDateLimit(query, @maxend)
    end

query_maxstart(query) click to toggle source

    # File lib/taskjuggler/TaskScenario.rb, line 1428
    def query_maxstart(query)
      queryDateLimit(query, @maxstart)
    end

query_minend(query) click to toggle source

    # File lib/taskjuggler/TaskScenario.rb, line 1432
    def query_minend(query)
      queryDateLimit(query, @minend)
    end

query_minstart(query) click to toggle source

    # File lib/taskjuggler/TaskScenario.rb, line 1436
    def query_minstart(query)
      queryDateLimit(query, @minstart)
    end

query_opentasks(query) click to toggle source

    # File lib/taskjuggler/TaskScenario.rb, line 1440
    def query_opentasks(query)
      count = openTasks(query)

      query.sortable = query.numerical = count
      # For the string output, we only use integer numbers.
      query.string = "#{count.to_i}"
    end

query_precursors(query) click to toggle source

    # File lib/taskjuggler/TaskScenario.rb, line 1448
    def query_precursors(query)
      list = []

      # First gather the task that depend on the start of this task.
      @startpreds.each do |task, onEnd|
        if onEnd
          date = task['end', query.scenarioIdx].to_s(query.timeFormat)
          dep = "]->["
        else
          date = task['start', query.scenarioIdx].to_s(query.timeFormat)
          dep = "[->["
        end
        list << generateDepencyListItem(query, task, dep, date)
      end
      # Than add the tasks that depend on the end of this task.
      @endpreds.each do |task, onEnd|
        if onEnd
          date = task['end', query.scenarioIdx].to_s(query.timeFormat)
          dep = "]->]"
        else
          date = task['start', query.scenarioIdx].to_s(query.timeFormat)
          dep = "[->]"
        end
        list << generateDepencyListItem(query, task, dep, date)
      end

      query.assignList(list)
    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/taskjuggler/TaskScenario.rb, line 1479
    def query_resources(query)
      list = []
      iv = TimeInterval.new(query.start, query.end)
      assignedResources(iv).each do |resource|
        if resource.allocated?(@scenarioIdx, iv, @property)
          if query.listItem
            rti = RichText.new(query.listItem, RTFHandlers.create(@project)).
              generateIntermediateFormat
            unless rti
              error('bad_resource_ts_query',
                    "Syntax error in query statement for task attribute " +
                    "'resources'.")
            end
            q = query.dup
            q.property = resource
            rti.setQuery(q)
            list << "<nowiki>#{rti.to_s}</nowiki>"
          else
            list << "<nowiki>#{resource.name} (#{resource.fullId})</nowiki>"
          end
        end
      end
      query.assignList(list)
    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/taskjuggler/TaskScenario.rb, line 1507
    def query_revenue(query)
      if query.revenueAccount
        query.sortable = query.numerical = revenue =
          turnover(query.startIdx, query.endIdx, query.revenueAccount,
                   query.scopeProperty)
        query.string = query.currencyFormat.format(revenue)
      else
        query.string = 'No \balance\ defined!'
      end
    end

query_scheduling(query) click to toggle source

    # File lib/taskjuggler/TaskScenario.rb, line 1518
    def query_scheduling(query)
      query.string = @forward ? 'ASAP' : 'ASAP' if @property.leaf?
    end

query_status(query) click to toggle source

    # File lib/taskjuggler/TaskScenario.rb, line 1522
    def query_status(query)
      # If we haven't calculated the completion yet, calculate it first.
      calcStatus if @status.empty?

      query.string = @status
    end

query_targets(query) click to toggle source

    # File lib/taskjuggler/TaskScenario.rb, line 1529
    def query_targets(query)
      targetList = PropertyList.new(@project.tasks, false)
      targets(targetList, true)
      targetList.delete(@property)
      targetList.setSorting([['start', true, @scenarioIdx],
                             ['seqno', true, -1 ]])
      targetList.sort!

      query.assignList(generateTaskList(targetList, query))
    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/taskjuggler/TaskScenario.rb, line 906
    def readyForScheduling?
      # If the tasks has already been scheduled, we still call it 'ready' so
      # it will be removed from the todo list.
      return true if @scheduled

      return false if @isRunAway

      if @forward
        return true if @start && (@hasDurationSpec || @end)
      else
        return true if @end && (@hasDurationSpec || @start)
      end

      false
    end

resetLoopFlags() click to toggle source

    # File lib/taskjuggler/TaskScenario.rb, line 608
    def resetLoopFlags
      @deadEndFlags = Array.new(4, false)
    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/taskjuggler/TaskScenario.rb, line 926
    def schedule
      # Check if the task has already been scheduled e. g. by propagateDate().
      return true if @scheduled

      logTag = "schedule_#{@property.id}"
      Log.enter(logTag, "Scheduling task #{@property.id}")
      # Compute 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, @currentSlotIdx is still nil. Otherwise it contains the index
      # of the last scheduled slot.
      if @forward
        # On first call, the @currentSlotIdx is not set yet. We set it to the
        # start slot index or the 'now' slot if we are in projection mode and
        # the tasks has allocations.
        if @currentSlotIdx.nil?
          @currentSlotIdx = @project.dateToIdx(
            @projectionMode && (@project['now'] > @start) && !@allocate.empty? ?
            @project['now'] : @start)
        end
      else
        # On first call, the @currentSlotIdx is not set yet. We set it to the
        # slot index of the slot before the end slot.
        if @currentSlotIdx.nil?
          @currentSlotIdx = @project.dateToIdx(@end) - 1
        end
      end

      # Schedule all time slots from slot in the scheduling direction until
      # the task is completed or a problem has been found.
      # The task may not excede the project interval.
      lowerLimit = @project.dateToIdx(@project['start'])
      upperLimit = @project.dateToIdx(@project['end'])
      delta = @forward ? 1 : -1
      while scheduleSlot
        @currentSlotIdx += delta
        if @currentSlotIdx < lowerLimit || upperLimit < @currentSlotIdx
          markAsRunaway
          Log.exit(logTag, "Scheduling of task #{@property.id} failed")
          return false
        end
      end

      Log.exit(logTag, "Scheduling of task #{@property.id} completed")
      true
    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/taskjuggler/TaskScenario.rb, line 1112
    def scheduleContainer
      return if @scheduled || !@property.container?

      nStart = nil
      nEnd = nil

      @property.kids.each do |task|
        # Abort if a child has not yet been scheduled. Since we haven't done
        # the consistency check yet, we can't rely on start and end being set
        # if 'scheduled' is set.
        return if (!task['scheduled', @scenarioIdx] ||
                   task['start', @scenarioIdx].nil? ||
                   task['end', @scenarioIdx].nil?)

        if nStart.nil? || task['start', @scenarioIdx] < nStart
          nStart = task['start', @scenarioIdx]
        end
        if nEnd.nil? || task['end', @scenarioIdx] > nEnd
          nEnd = task['end', @scenarioIdx]
        end
      end

      startSet = endSet = false
      # Propagate the dates to other dependent tasks.
      if @start.nil? || @start > nStart
        @start = nStart
        startSet = true
      end
      if @end.nil? || @end < nEnd
        @end = nEnd
        endSet = true
      end
      unless @start && @end
        raise "Start (#{@start}) and end (#{@end}) must be set"
      end
      Log.msg { "Container task #{@property.fullId} completed: #{period_to_s}" }
      markAsScheduled

      # If we have modified the start or end date, we need to communicate this
      # new date to surrounding tasks.
      propagateDate(nStart, false) if startSet
      propagateDate(nEnd, true) if endSet
    end

[Validate]

Generated with the Darkfish Rdoc Generator 2.