Class TaskJuggler::Project
In: lib/taskjuggler/Project.rb
Parent: Object

This class implements objects that hold all project properties. Project generally consist of resources, tasks and a number of other optional properties. Tasks, Resources, Accounts and Shifts are all build on the same underlying storage class PropertyTreeNode. Properties of the same kind are kept in PropertySet objects. There is only one PropertySet for each type of property. Additionally, each property may belong to various PropertyList objects. In contrast to PropertySet objects, PropertyList object have well defined sorting order and no information about the attributes of each type of property. The PropertySet holds the blueprints for the data construction inside the PropertyTreeNode objects. It contains the list of known Attributes.

Methods

Attributes

accounts  [R] 
inputFiles  [R] 
messageHandler  [R] 
outputDir  [RW] 
reportContexts  [RW] 
reports  [R] 
resources  [R] 
scenarios  [R] 
shifts  [R] 
tasks  [R] 
timeSheets  [R] 
warnTsDeltas  [RW] 

Public Class methods

TaskJuggler keeps all times in UTC. All time values must be multiples of the used scheduling granularity. If the local time zone is not hour-aligned to UTC, the maximum allowed schedule granularity is reduced.

[Source]

# File lib/taskjuggler/Project.rb, line 995
    def Project.maxScheduleGranularity
      refTime = Time.gm(2000, 1, 1, 0, 0, 0)
      case (min = refTime.getlocal.min)
      when 0
        # We are hour-aligned to UTC; scheduleGranularity is 1 hour
        60 * 60
      when 30
        # We are half-hour off from UTC; scheduleGranularity is 30 minutes
        30 * 60
      when 15, 45
        # We are 15 or 45 minutes off from UTC; scheduleGranularity is 15
        # minutes
        15 * 60
      else
        raise "Unknown Time zone alignment #{min}"
      end
    end

Create a project with the specified id, name and version. messageHandler is a MessageHandler reference that is used to handle all error and warning messages that might occur during processing. The constructor will set default values for all project attributes.

[Source]

# File lib/taskjuggler/Project.rb, line 65
    def initialize(id, name, version, messageHandler)
      @messageHandler = messageHandler
      AttributeBase.setMode(0)
      @attributes = {
        # This nested Array defines the supported alert levels. The lowest
        # level comes first at index 0 and the level rises from there on.
        # Currently, these levels are hardcoded. Each level entry has 3
        # members: the tjp syntax token, the user visible name and the
        # associated color as RGB byte array.
        'alertLevels' => AlertLevelDefinitions.new,
        'copyright' => nil,
        'costAccount' => nil,
        'currency' => "EUR",
        'currencyFormat' => RealFormat.new([ '-', '', '', ',', 2 ]),
        'dailyworkinghours' => 8.0,
        'end' => nil,
        'flags' => [],
        'journal' => Journal.new,
        'limits' => nil,
        'leaves' => LeaveList.new,
        'loadUnit' => :days,
        'name' => name,
        'navigators' => {},
        'now' => TjTime.new.align(3600),
        'numberFormat' => RealFormat.new([ '-', '', '', '.', 1]),
        'priority' => 500,
        'projectid' => id || "prj",
        'projectids' => [ id ],
        'rate' => 0.0,
        'revenueAccount' => nil,
        'scheduleGranularity' => Project.maxScheduleGranularity,
        'shortTimeFormat' => "%H:%M",
        'start' => nil,
        'timeFormat' => "%Y-%m-%d",
        'timezone' => TjTime.timeZone,
        'trackingScenarioIdx' => nil,
        'version' => version || "1.0",
        'weekStartsMonday' => true,
        'workinghours' => nil,
        'yearlyworkingdays' => 260.714
      }

      # Before we can add any properties to this project, we need to define the
      # attributes that each of the property types will be using. In TaskJuggler
      # lingo, properties of a project are resources, tasks, accounts, shifts
      # and scenarios. Each of these properties can have lots of further
      # information attached to it. These bits of information are called
      # attributes. An attribute is defined by the AttributeDefinition class.
      # The PropertySet objects need to be fed with a list of such attribute
      # definitions to register the attributes with the properties.
      @scenarios = PropertySet.new(self, true)
      attrs = [
        # ID           Name          Type
        #     Inh.   Inh.Prj  Scen.  Default
        [ 'active',   'Enabled',    BooleanAttribute,
              true,  false,   false, true ],
        [ 'id',       'ID',         StringAttribute,
              false, false,   false, nil ],
        [ 'name',     'Name',       StringAttribute,
              false, false,   false, nil ],
        [ 'ownbookings', 'Own Bookings', BooleanAttribute,
              false, false,   false, true ],
        [ 'projection', 'Projection Mode', BooleanAttribute,
              true,  false,   false, false ],
        [ 'seqno',    'No',          FixnumAttribute,
              false, false,   false, nil ],
      ]
      attrs.each { |a| @scenarios.addAttributeType(AttributeDefinition.new(*a)) }

      @shifts = PropertySet.new(self, true)
      attrs = [
        # ID           Name            Type
        #     Inh.   Inh.Prj  Scen.  Default
        [ 'bsi',      'BSI',           StringAttribute,
              false, false,   false, "" ],
        [ 'id',       'ID',            StringAttribute,
              false, false,   false, nil ],
        [ 'index',     'Index',        FixnumAttribute,
              false, false,   false, -1 ],
        [ 'leaves',    'Leaves',       LeaveListAttribute,
              true,  true,    true,  LeaveList.new ],
        [ 'name',     'Name',          StringAttribute,
              false, false,   false, nil ],
        [ 'replace',   'Replace',      BooleanAttribute,
              true,  false,   true,  false ],
        [ 'seqno',    'No',            FixnumAttribute,
              false, false,   false, nil ],
        [ 'timezone',  'Time Zone',    StringAttribute,
              true,  true,    true,  TjTime.timeZone ],
        [ 'tree',      'Tree Index',   StringAttribute,
              false, false,   false, "" ],
        [ 'workinghours', 'Working Hours', WorkingHoursAttribute,
              true,  true,    true,  nil ]
      ]
      attrs.each { |a| @shifts.addAttributeType(AttributeDefinition.new(*a)) }

      @accounts = PropertySet.new(self, true)
      attrs = [
        # ID           Name            Type
        #     Inh.   Inh.Prj  Scen.  Default
        [ 'aggregate', 'Aggregate',    SymbolAttribute,
              true,  false,   false, :tasks ],
        [ 'bsi',       'BSI',          StringAttribute,
              false, false,   false, "" ],
        [ 'credits',   'Credits',      AccountCreditListAttribute,
              false, false,   true,  [] ],
        [ 'id',       'ID',         StringAttribute,
              false, false,   false, nil ],
        [ 'index',     'Index',        FixnumAttribute,
              false, false,   false, -1 ],
        [ 'flags',     'Flags',        FlagListAttribute,
              true,  false,   true,  [] ],
        [ 'name',     'Name',       StringAttribute,
              false, false,   false, nil ],
        [ 'seqno',    'No',          FixnumAttribute,
              false, false,   false, nil ],
        [ 'tree',      'Tree Index',   StringAttribute,
              false, false,   false, "" ]
      ]
      attrs.each { |a| @accounts.addAttributeType(AttributeDefinition.new(*a)) }

      @resources = PropertySet.new(self, true)
      attrs = [
        # ID           Name            Type
        #     Inh.   Inh.Prj  Scen.  Default
        [ 'alloctdeffort', 'Alloctd. Effort', FloatAttribute,
              false, false,   true,  0.0 ],
        [ 'bsi',       'BSI',          StringAttribute,
              false, false,   false, "" ],
        [ 'chargeset', 'Charge Sets',  ChargeSetListAttribute,
              true,  false,   true,  [] ],
        [ 'criticalness', 'Criticalness', FloatAttribute,
              false, false,   true,  0.0 ],
        [ 'duties',    'Duties',       TaskListAttribute,
              false, false,   true,  [] ],
        [ 'directreports', 'Direct Reports', ResourceListAttribute,
              false, false,   true,  [] ],
        [ 'efficiency','Efficiency',   FloatAttribute,
              true,  false,   true,  1.0 ],
        [ 'effort', 'Total Effort',    FixnumAttribute,
              false, false,   true,  0 ],
        [ 'email',     'Email',        StringAttribute,
              false, false,   false, nil ],
        [ 'fail',      'Failure Conditions', LogicalExpressionListAttribute,
              false, false,   false,  [] ],
        [ 'flags',     'Flags',        FlagListAttribute,
              true,  false,   true,  [] ],
        [ 'index',     'Index',        FixnumAttribute,
              false, false,   false, -1 ],
        [ 'leaveallowances', 'Leave Allowances', LeaveAllowanceListAttribute,
              true,  false,   true,  LeaveAllowanceList.new ],
        [ 'leaves',    'Leaves',       LeaveListAttribute,
              true,  true,    true,  LeaveList.new ],
        [ 'limits',    'Limits',       LimitsAttribute,
              true,  true,    true,  nil ],
        [ 'managers', 'Managers',      ResourceListAttribute,
              true,  false,   true,  [] ],
        [ 'rate',      'Rate',         FloatAttribute,
              true,  true,    true,  0.0 ],
        [ 'reports', 'Reports', ResourceListAttribute,
              false, false,   true,  [] ],
        [ 'seqno',    'No',          FixnumAttribute,
              false, false,   false, nil ],
        [ 'shifts',    'Shifts',       ShiftAssignmentsAttribute,
              true, false,    true,  nil ],
        [ 'tree',      'Tree Index',   StringAttribute,
              false, false,   false, "" ],
        [ 'warn',      'Warning Condition', LogicalExpressionListAttribute,
              false, false,   false, [] ],
        [ 'workinghours', 'Working Hours', WorkingHoursAttribute,
              true,  true,    true,  nil ]
      ]
      attrs.each { |a| @resources.addAttributeType(AttributeDefinition.new(*a)) }

      @tasks = PropertySet.new(self, false)
      attrs = [
        # ID           Name            Type
        #     Inh.   Inh.Prj  Scen.  Default
        [ 'allocate', 'Allocations',   AllocationAttribute,
              true,  false,   true,  [] ],
        [ 'assignedresources', 'Assigned Resources', ResourceListAttribute,
              false, false,   true,  [] ],
        [ 'booking',   'Bookings',     BookingListAttribute,
              false, false,   true,  [] ],
        [ 'bsi',       'BSI',          StringAttribute,
              false, false,   false, "" ],
        [ 'charge',    'Charges',      ChargeListAttribute,
              false, false,   true,  [] ],
        [ 'chargeset', 'Charge Sets',  ChargeSetListAttribute,
              true,  false,   true,  [] ],
        [ 'complete',  'Completion',   FloatAttribute,
              false, false,   true,  nil ],
        [ 'criticalness', 'Criticalness', FloatAttribute,
              false, false,   true,  0.0 ],
        [ 'depends',   'Preceding tasks', DependencyListAttribute,
              true,  false,   true,  [] ],
        [ 'duration',  'Duration',     DurationAttribute,
              false, false,   true,  0 ],
        [ 'effort',    'Effort',       DurationAttribute,
              false, false,   true,  0 ],
        [ 'end',       'End',          DateAttribute,
              false, false,   true,  nil ],
        [ 'endpreds',  'End Preds.',   TaskDepListAttribute,
              false, false,   true,  [] ],
        [ 'endsuccs',  'End Succs.',   TaskDepListAttribute,
              false, false,   true,  [] ],
        [ 'fail',      'Failure Conditions', LogicalExpressionListAttribute,
              false, false,   false, [] ],
        [ 'flags',     'Flags',        FlagListAttribute,
              true,  false,   true,  [] ],
        [ 'forward',   'Scheduling',   BooleanAttribute,
              true,  false,   true,  true ],
        [ 'gauge',     'Schedule gauge', StringAttribute,
              false, false,   true,  nil ],
        [ 'id',       'ID',         StringAttribute,
              false, false,   false, nil ],
        [ 'index',     'Index',        FixnumAttribute,
              false, false,   false, -1 ],
        [ 'length',    'Length',       DurationAttribute,
              false, false,   true,  0 ],
        [ 'limits',    'Limits',       LimitsAttribute,
              false, false,   true,  nil ],
        [ 'maxend',    'Max. End',     DateAttribute,
              true,  false,   true,  nil ],
        [ 'maxstart',  'Max. Start',   DateAttribute,
              false, false,   true,  nil ],
        [ 'milestone', 'Milestone',    BooleanAttribute,
              false, false,   true,  false ],
        [ 'minend',    'Min. End',     DateAttribute,
              false, false,   true,  nil ],
        [ 'minstart',  'Min. Start',   DateAttribute,
              true,  false,   true,  nil ],
        [ 'name',     'Name',       StringAttribute,
              false, false,   false, nil ],
        [ 'note',      'Note',         RichTextAttribute,
              false, false,   false, nil ],
        [ 'pathcriticalness', 'Path Criticalness', FloatAttribute,
              false, false,   true, 0.0 ],
        [ 'precedes',  'Following tasks', DependencyListAttribute,
              true,  false,   true,  [] ],
        [ 'priority',  'Priority',     FixnumAttribute,
              true,  true,    true,  500 ],
        [ 'projectid', 'Project ID',   SymbolAttribute,
              true,  true,    true,  nil ],
        [ 'responsible', 'Responsible', ResourceListAttribute,
              true,  false,   true,  [] ],
        [ 'scheduled', 'Scheduled',    BooleanAttribute,
              true,  false,   true,  false ],
        [ 'seqno',    'No',          FixnumAttribute,
              false, false,   false, nil ],
        [ 'shifts',     'Shifts',      ShiftAssignmentsAttribute,
              true,  false,   true, nil ],
        [ 'start',     'Start',        DateAttribute,
              false, false,   true,  nil ],
        [ 'startpreds', 'Start Preds.', TaskDepListAttribute,
              false, false,   true,  [] ],
        [ 'startsuccs', 'Start Succs.', TaskDepListAttribute,
              false, false,   true,  [] ],
        [ 'status',    'Task Status',  StringAttribute,
              false, false,   true,  "" ],
        [ 'tree',      'Tree Index',   StringAttribute,
              false, false,   false, "" ],
        [ 'warn',      'Warning Condition', LogicalExpressionListAttribute,
              false, false,   false, [] ]
      ]
      attrs.each { |a| @tasks.addAttributeType(AttributeDefinition.new(*a)) }

      @reports = PropertySet.new(self, false)
      attrs = [
        # ID           Name            Type
        #     Inh.   Inh.Prj  Scen.  Default
        [ 'accountRoot',  'Account Root',    PropertyAttribute,
              true,  false,   false, nil ],
        [ 'bsi',       'BSI',          StringAttribute,
              false, false,   false, "" ],
        [ 'caption',   'Caption',      RichTextAttribute,
              true,  false,   false, nil ],
        [ 'center',    'Center',       RichTextAttribute,
              true,  false,   false, nil ],
        [ 'columns',   'Columns',      ColumnListAttribute,
              true,  false,   false, [] ],
        [ 'costAccount', 'Cost Account', AccountAttribute,
              true,  true,    false, nil ],
        [ 'currencyFormat', 'Currency Format', RealFormatAttribute,
              true,  true,    false, nil ],
        [ 'definitions', 'Definitions', DefinitionListAttribute,
              true,  false,   false, KeywordArray.new([ '*' ]) ],
        [ 'end',       'End',          DateAttribute,
              true,  true,    false, nil ],
        [ 'epilog',    'Epilog',       RichTextAttribute,
              true,  false,   false, nil ],
        [ 'flags',     'Flags',        FlagListAttribute,
              true,  false,   true,  [] ],
        [ 'footer',    'Footer',       RichTextAttribute,
              true,  false,   false, nil ],
        [ 'formats',   'Formats',      FormatListAttribute,
              true,  false,   false, [] ],
        [ 'ganttBars', 'Gantt Bars',   BooleanAttribute,
              true,  false,   false, true ],
        [ 'header',    'Header',       RichTextAttribute,
              true,  false,   false, nil ],
        [ 'headline',  'Headline',     RichTextAttribute,
              true,  false,   false, nil ],
        [ 'hideAccount', 'Hide Account', LogicalExpressionAttribute,
              true,  false,   false, nil ],
        [ 'hideJournalEntry', 'Hide JournalEntry', LogicalExpressionAttribute,
              true,  false,   false, nil ],
        [ 'hideResource', 'Hide Resource', LogicalExpressionAttribute,
              true,  false,   false, nil ],
        [ 'hideTask',  'Hide Task',    LogicalExpressionAttribute,
              true,  false,   false, nil ],
        [ 'id',       'ID',         StringAttribute,
              false, false,   false, nil ],
        [ 'index',     'Index',        FixnumAttribute,
              false, false,   false, -1 ],
        [ 'interactive', 'Interactive', BooleanAttribute,
              false, false,   false, false ],
        [ 'journalAttributes', 'Journal Attributes', SymbolListAttribute,
              true,  false,   false, KeywordArray.new([ '*' ]) ],
        [ 'journalMode', 'Journal Mode', SymbolAttribute,
              true,  false,   false, :journal ],
        [ 'left',      'Left',         RichTextAttribute,
              true,  false,   false, nil ],
        [ 'loadUnit',  'Load Unit',    StringAttribute,
              true,  true,    false, nil ],
        [ 'name',     'Name',       StringAttribute,
              false, false,   false, nil ],
        [ 'now',       'Now',          DateAttribute,
              true,  true,    false, nil ],
        [ 'numberFormat', 'Number Format', RealFormatAttribute,
              true,  true,    false, nil ],
        [ 'openNodes', 'Open Nodes',   NodeListAttribute,
              false, false,   false, nil ],
        [ 'prolog',    'Prolog',       RichTextAttribute,
              true, false,   false, nil],
        [ 'resourceAttributes', 'Resource Attributes', FormatListAttribute,
              true,  false,   false, KeywordArray.new([ '*' ]) ],
        [ 'resourceRoot',  'resource Root', PropertyAttribute,
              true,  false,   false, nil ],
        [ 'revenueAccount', 'Revenue Account', AccountAttribute,
              true,  true,    false, nil ],
        [ 'right',     'Right',        RichTextAttribute,
              true,  false,   false, nil ],
        [ 'rollupAccount', 'Rollup Account', LogicalExpressionAttribute,
              true,  false,   false, nil ],
        [ 'rollupResource', 'Rollup Resource', LogicalExpressionAttribute,
              true,  false,   false, nil ],
        [ 'rollupTask', 'Rollup Task', LogicalExpressionAttribute,
              true, false,    false, nil ],
        [ 'scenarios',  'Scenarios',   ScenarioListAttribute,
              true, false,    false, [ 0 ] ],
        [ 'selfcontained', 'Selfcontained', BooleanAttribute,
              true, false,    false, false ],
        [ 'seqno',    'No',          FixnumAttribute,
              false, false,   false, nil ],
        [ 'shortTimeFormat', 'Short Time Format', StringAttribute,
              true,  true,    false, nil ],
        [ 'sortAccounts', 'Sort Accounts', SortListAttribute,
              true,  false,   false, [[ 'seqno', true, -1 ]] ],
        [ 'sortJournalEntries', 'Sort Journal Entries', JournalSortListAttribute,
              true,  false,   false, [[ :alert, 1 ], [ :date, 1 ], [ :seqno, 1 ]] ],
        [ 'sortResources', 'Sort Resources', SortListAttribute,
              true,  false,   false, [[ 'seqno', true, -1 ]] ],
        [ 'sortTasks', 'Sort Tasks',   SortListAttribute,
              true,  false,   false, [[ 'seqno', true, -1 ]] ],
        [ 'start',     'Start',        DateAttribute,
              true,  true,    false, nil ],
        [ 'taskAttributes', 'Task Attributes', FormatListAttribute,
              true,  false,   false, KeywordArray.new([ '*' ]) ],
        [ 'taskRoot',  'Task Root',    PropertyAttribute,
              true,  false,   false, nil ],
        [ 'timeFormat', 'Time Format', StringAttribute,
              true,  true,    false, nil ],
        [ 'timeOffId',  'Time Off ID', StringAttribute,
              false,  false,  false, nil ],
        [ 'timeOffName',  'Time Off Name', StringAttribute,
              false,  false,  false, nil ],
        [ 'timezone', 'Time Zone',     StringAttribute,
              true,  true,    false, TjTime.timeZone ],
        [ 'title',    'Title',         StringAttribute,
              true,  false,   false, nil ],
        [ 'tree',      'Tree Index',   StringAttribute,
              false, false,   false, "" ],
        [ 'weekStartsMonday', 'Week Starts Monday', BooleanAttribute,
              true,  true,    false, false ]
      ]
      attrs.each { |a| @reports.addAttributeType(AttributeDefinition.new(*a)) }

      Scenario.new(self, 'plan', 'Plan Scenario', nil)

      # A list of files that contained the project data.
      @inputFiles = FileList.new

      @timeSheets = TimeSheets.new

      # A scoreboard that reflects the global working hours and leaves.
      @scoreboard = nil
      # A scoreboard that reflects the global working hours but no leaves.
      @scoreboardNoLeaves = nil

      # The ReportContext provides additional settings to the report that can
      # complement or replace the report attributes. Reports can include other
      # reports. During report generation, only one context is active, but the
      # context of enclosing reports needs to be preserved. Therefor we use a
      # stack to implement this.
      @reportContexts = []
      @outputDir = ''
      @warnTsDeltas = false
    end

Public Instance methods

Query the value of a Project attribute. name is the ID of the attribute.

[Source]

# File lib/taskjuggler/Project.rb, line 482
    def [](name)
      if !@attributes.has_key?(name)
        raise "Unknown project attribute #{name}"
      end
      @attributes[name]
    end

Set the Project attribute with ID name to value.

[Source]

# File lib/taskjuggler/Project.rb, line 490
    def []=(name, value)
      if !@attributes.has_key?(name)
        raise "Unknown project attribute #{name}"
      end
      @attributes[name] = value

      # If the start, end or schedule granularity have been changed, we have
      # to reset the working hours.
      if %w(start end scheduleGranularity timezone timingresolution).
        include?(name)
        if @attributes['start'] && @attributes['end']
          @attributes['workinghours'] =
            WorkingHours.new(@attributes['scheduleGranularity'],
                             @attributes['start'], @attributes['end'],
                             @attributes['timezone'])
        end
      end
      value
    end

Return the Account with the ID id or return nil if it does not exist.

[Source]

# File lib/taskjuggler/Project.rb, line 578
    def account(id)
      @accounts[id]
    end

Return true if for the date specified by the global scoreboard index sbIdx there is any resource that is available.

[Source]

# File lib/taskjuggler/Project.rb, line 987
    def anyResourceAvailable?(sbIdx)
      @resourceAvailability[sbIdx]
    end

Return the name of the attribute id. Since we don‘t know whether we are looking for a task, resource, etc. attribute, we prefer tasks over resources here.

[Source]

# File lib/taskjuggler/Project.rb, line 1016
    def attributeName(id)
      # We have to see if the attribute id is a task or resource attribute and
      # return it's name.
      (name = @tasks.attributeName(id)).nil? &&
      (name = @resources.attributeName(id)).nil?
      name
    end

Make sure that we have a least one report defined that has an output format.

[Source]

# File lib/taskjuggler/Project.rb, line 676
    def checkReports
      if @reports.empty?
        warning('no_report_defined',
                "This project has no reports defined. " +
                "No output data will be generated.")
      end

      unless @accounts.empty?
        @reports.each do |report|
          if (report.typeSpec != :accountreport) &&
             (report.get('costAccount').nil? ||
              report.get('revenueAccount').nil?)
            warning('report_without_balance',
                    "The report #{report.fullId} has no 'balance' defined. " +
                    "No cost or revenue computation will be possible.",
                    report.sourceFileInfo)
          end
        end
      end

      @reports.each do |report|
        return unless report.get('formats').empty?
      end

      warning('all_formats_empty',
              "None of the reports has a 'formats' attribute. " +
              "No output data will be generated.")
    end

[Source]

# File lib/taskjuggler/Project.rb, line 808
    def checkTimeSheets
      @timeSheets.check
    end

[Source]

# File lib/taskjuggler/Project.rb, line 954
    def collectTimeOffIntervals(iv, minDuration)
      @scoreboard.collectIntervals(iv, minDuration) do |val|
        val.is_a?(Fixnum) && (val & 0x3E) != 0
      end
    end

Convert working seconds to working days. The result depends on the setting of the global ‘dailyworkinghours’ attribute.

[Source]

# File lib/taskjuggler/Project.rb, line 913
    def convertToDailyLoad(seconds)
      seconds / (@attributes['dailyworkinghours'] * 3600.0)
    end

Return the average number of working hours per day. This defaults to 8 but can be set to other values by the user.

[Source]

# File lib/taskjuggler/Project.rb, line 517
    def dailyWorkingHours
      @attributes['dailyworkinghours'].to_f
    end

Convert a date (TjTime) to the equivalent Scoreboard index. If forceIntoProject is true, the date will be pushed into the project time frame.

[Source]

# File lib/taskjuggler/Project.rb, line 939
    def dateToIdx(date, forceIntoProject = true)
      if (date < @attributes['start'] || date > @attributes['end'])
        # Date is out of range.
        if forceIntoProject
          return 0 if date < @attributes['start']
          return scoreboardSize - 1 if date > @attributes['end']
        else
          raise "Date #{date} is out of project time range " +
                "(#{@attributes['start']} - #{@attributes['end']})"
        end
      end
      # Calculate the corresponding index.
      ((date - @attributes['start']) / @attributes['scheduleGranularity']).to_i
    end

Overload the deep_clone function so that references to the project don‘t lead to deep copying of the whole project.

[Source]

# File lib/taskjuggler/Project.rb, line 477
    def deep_clone
      self
    end

Add the CSV output format to all reports of type ‘tracereport’ if enable is true. Otherwise remove all CSV output formats.

[Source]

# File lib/taskjuggler/Project.rb, line 658
    def enableTraceReports(enable)
      @reports.each do |report|
         next unless report.typeSpec == :tracereport

         if enable
           # Enable the CSV format for the tracereport
           unless report.get('formats').include?(:csv)
             report.get('formats') << :csv
           end
         else
           # Disabe CSV format for the tracereport
           report.get('formats').delete(:csv)
         end
      end
    end

Generate an error message via the message handler.

[Source]

# File lib/taskjuggler/Project.rb, line 1042
    def error(id, text, sourceFileInfo = nil)
      @messageHandler.error(id, text, sourceFileInfo)
    end

[Source]

# File lib/taskjuggler/Project.rb, line 755
    def generateReport(reportId, regExpMode, formats = nil,
                       dynamicAttributes = nil)
      reportList = regExpMode ? reportList = matchingReports(reportId) :
                                [ reportId ]
      reportList.each do |id|
        unless (report = @reports[id])
          error('unknown_report_id',
                "Request to generate unknown report #{id}")
        end
        if formats.nil? && report.get('formats').empty?
          error('formats_empty',
                "The report #{report.fullId} has no 'formats' attribute. " +
                "No output data will be generated.", report.sourceFileInfo)
        end

        Log.startProgressMeter("Report #{report.name}")
        @reportContexts.push(context = ReportContext.new(self, report))

        # If we have dynamic attributes we need to backup the old attributes
        # first, then parse the dynamicAttributes String replacing the
        # original values.
        if dynamicAttributes
          unless dynamicAttributes.empty?
            context.attributeBackup = report.backupAttributes
            parser = ProjectFileParser.new(@messageHandler)
            parser.parseReportAttributes(report, dynamicAttributes)
          end
          report.set('interactive', true)
        end

        report.generate(formats)

        if dynamicAttributes && !dynamicAttributes.empty?
          report.restoreAttributes(context.attributeBackup)
        end
        @reportContexts.pop
        Log.stopProgressMeter
      end
    end

Call this function to generate the reports based on the scheduling result. This function may only be called after Project#schedule has been called.

[Source]

# File lib/taskjuggler/Project.rb, line 707
    def generateReports(maxCpuCores)
      @reports.index
      if maxCpuCores == 1
        @reports.each do |report|
          # Skip reports that don't have any format specified or trace reports
          # when generateTraces is false.
          next if report.get('formats').empty?

          Log.startProgressMeter("Report #{report.name}")
          @reportContexts.push(ReportContext.new(self, report))
          report.generate
          @reportContexts.pop
          Log.stopProgressMeter
        end
      else
        # Kickoff the generation of all reports by pushing the jobs into the
        # BatchProcessor queue.
        bp = BatchProcessor.new(maxCpuCores)
        @reports.each do |report|
          # Skip reports that don't have any format specified or trace reports
          # when generateTraces is false.
          next if report.get('formats').empty? ||
                  (report.typeSpec == :trace && !generateTraces)

          bp.queue(report) {
            @reportContexts.push(ReportContext.new(self, report))
            res = report.generate
            @reportContexts.pop
            res
          }
        end
        # Now wait for all the jobs to finish.
        bp.wait do |report|
          Log.startProgressMeter("Report #{report.tag.name}")
          $stdout.print(report.stdout)
          $stderr.print(report.stderr)
          if report.retVal.signaled?
            error('rg_signal', "Signal raised")
          end
          unless report.retVal.success?
            error('rg_abort', "Process aborted")
          end
          Log.stopProgressMeter
        end
      end
      DataCache.instance.flush
    end

Return the number of global working slots during the given time interval specified by startIdx and endIdx. This method takes global leaves into account.

[Source]

# File lib/taskjuggler/Project.rb, line 975
    def getWorkSlots(startIdx, endIdx)
      slots = 0
      startIdx.upto(endIdx) do |idx|
        slots += 1 unless @scoreboard[idx]
      end

      slots
    end

Return true if the interval overlaps with a globally defined working time or false if not. Global work time means, no global leaves defined and the slot lies within a defined global working time period.

[Source]

# File lib/taskjuggler/Project.rb, line 890
    def hasWorkingTime(*args)
      # Normalize argument(s) to TimeInterval
      if args.length == 1
        if args[0].is_a?(TimeInterval)
          startIdx = dateToIdx(args[0].start)
          endIdx = dateToIdx(args[0].end)
        else
          raise ArgumentError, "Unsupported argument type #{args[0].class}"
        end
      else
        startIdx = dateToIdx(args[0])
        endIdx = dateToIdx(args[1])
      end

      startIdx.upto(endIdx) do |idx|
        return true if @scoreboard[idx]
      end

      false
    end

Convert a Scoreboard index to the equivalent date. idx is the index and it must be within the range of the Scoreboard objects. If not, an exception is raised.

[Source]

# File lib/taskjuggler/Project.rb, line 929
    def idxToDate(idx)
      if $DEBUG && (idx < 0 || idx > scoreboardSize)
        raise "Scoreboard index out of range"
      end
      @attributes['start'] + idx * @attributes['scheduleGranularity']
    end

Return true if the slot or interval is withing globally defined working time or false if not. If the argument is a TimeInterval, all slots of the interval must be working time to return true as result. Global work time means, no global leaves defined and the slot lies within a defined global working time period.

[Source]

# File lib/taskjuggler/Project.rb, line 858
    def isWorkingTime(*args)
      # Normalize argument(s) to TimeInterval
      if args.length == 1
        if args[0].is_a?(Fixnum) || args[0].is_a?(Bignum)
          return @scoreboard[args[0]].nil?
        elsif args[0].is_a?(TjTime)
          return @scoreboard[dateToIdx(args[0])].nil?
        elsif args[0].is_a?(TimeInterval)
          startIdx = dateToIdx(args[0].start)
          endIdx = dateToIdx(args[0].end)
        else
          raise ArgumentError, "Unsupported argument type #{args[0].class}"
        end
      else
        startIdx = dateToIdx(args[0])
        endIdx = dateToIdx(args[1])
      end

      startIdx.upto(endIdx) do |idx|
        return false if @scoreboard[idx]
      end

      true
    end

[Source]

# File lib/taskjuggler/Project.rb, line 1024
    def journal(query)
      @attributes['journal'].to_rti(query)
    end

[Source]

# File lib/taskjuggler/Project.rb, line 795
    def listReports(reportId, regExpMode)
      reportList = regExpMode ? reportList = matchingReports(reportId) :
                                @reports[reportId] ? [ reportId ] : []
      puts "No match for #{reportId}" if reportList.empty?
      reportList.each do |id|
        report = @reports[id]
        formats = report.get('formats')
        next if formats.empty?

        puts sprintf("%s\t%s\t%s", id, formats.join(', '), report.name)
      end
    end

Return the average number of working days per month.

[Source]

# File lib/taskjuggler/Project.rb, line 527
    def monthlyWorkingDays
      @attributes['yearlyworkingdays'] / 12.0
    end

Return the Report with the ID id or return nil if it does not exist.

[Source]

# File lib/taskjuggler/Project.rb, line 593
    def report(id)
      @reports[id]
    end

Return the Report with the name name or return nil if it does not exist.

[Source]

# File lib/taskjuggler/Project.rb, line 599
    def reportByName(name)
      @reports.each do |report|
        return report if report.name == name
      end
      nil
    end

Return the Resource with the ID id or return nil if it does not exist.

[Source]

# File lib/taskjuggler/Project.rb, line 588
    def resource(id)
      @resources[id]
    end

Return the Scenario with the given id or index.

[Source]

# File lib/taskjuggler/Project.rb, line 546
    def scenario(arg)
      if arg.is_a?(Fixnum)
        @scenarios.each do |sc|
          return sc if sc.sequenceNo - 1 == arg
        end
      else
        return @scenarios[arg]
      end
      nil
    end

Return the number of defined scenarios for the project.

[Source]

# File lib/taskjuggler/Project.rb, line 511
    def scenarioCount
      @scenarios.items
    end

Return the index of the given Scenario specified by scenario or id.

[Source]

# File lib/taskjuggler/Project.rb, line 562
    def scenarioIdx(sc)
      if sc.is_a?(Scenario)
        return sc.sequenceNo - 1
      elsif @scenarios[sc].nil?
        return nil
      else
        return @scenarios[sc].sequenceNo - 1
      end
    end

This function must be called after the Project data structures have been filled with data. It schedules all scenario and stores the result in the data structures again.

[Source]

# File lib/taskjuggler/Project.rb, line 609
    def schedule
      initScoreboards

      [ @accounts, @shifts, @resources, @tasks ].each do |p|
        # Set all index counters to their proper values.
        p.index
      end

      if @tasks.empty?
        error('no_tasks', "No tasks defined")
      end

      @scenarios.each do |sc|
        # Skip disabled scenarios
        next unless sc.get('active')

        scIdx = scenarioIdx(sc)

        # All user provided values are set now. The next step is to
        # propagate inherited values. These values must be marked as
        # inherited by setting the mode to 1. As we always call
        # PropertyTreeNode#inherit this is just a safeguard.
        AttributeBase.setMode(1)
        prepareScenario(scIdx)

        # Now change to mode 2 so all values that are modified are marked
        # as computed.
        AttributeBase.setMode(2)
        # Schedule the scenario.
        scheduleScenario(scIdx)

        # Complete the data sets, and check the result.
        finishScenario(scIdx)
      end

      resources.each do |resource|
        resource.checkFailsAndWarnings
      end

      tasks.each do |task|
        task.checkFailsAndWarnings
      end

      @timeSheets.warnOnDelta if @warnTsDeltas
      true
    end

Many internal data structures use Scoreboard objects to keep track of scheduling data. These have one entry for every schedulable time slot in the project time frame. This functions returns the number of entries in the scoreboards.

[Source]

# File lib/taskjuggler/Project.rb, line 921
    def scoreboardSize
      ((@attributes['end'] - @attributes['start']) /
       @attributes['scheduleGranularity']).to_i
    end

Return the Shift with the ID id or return nil if it does not exist.

[Source]

# File lib/taskjuggler/Project.rb, line 573
    def shift(id)
      @shifts[id]
    end

Convert timeSlots to working days.

[Source]

# File lib/taskjuggler/Project.rb, line 537
    def slotsToDays(slots)
      slots * @attributes['scheduleGranularity'] / (60 * 60 * dailyWorkingHours)
    end

Return the Task with the ID id or return nil if it does not exist.

[Source]

# File lib/taskjuggler/Project.rb, line 583
    def task(id)
      @tasks[id]
    end

Print the attribute values. It‘s used for debugging only.

[Source]

# File lib/taskjuggler/Project.rb, line 1029
    def to_s
      #raise "STOP!"
      str = ''
      @attributes.each do |attribute, value|
        if value
          str += "#{attribute}: " +
                 "#{value.is_a?(PropertyTreeNode) ? value.fullId : value}"
        end
      end
      str
    end

Generate a warning message via the message handle.

[Source]

# File lib/taskjuggler/Project.rb, line 1047
    def warning(id, text, sourceFileInfo = nil)
      @messageHandler.warning(id, text, sourceFileInfo)
    end

[Source]

# File lib/taskjuggler/Project.rb, line 521
    def weeklyWorkingDays
      @attributes['workinghours'].weeklyWorkingHours /
        @attributes['dailyworkinghours']
    end

Return the number of working days (ignoring global leaves) during the given interval.

[Source]

# File lib/taskjuggler/Project.rb, line 962
    def workingDays(interval)
      startIdx = dateToIdx(interval.start)
      endIdx = dateToIdx(interval.end)
      slots = 0
      startIdx.upto(endIdx) do |idx|
        slots += 1 unless @scoreboardNoLeaves[idx]
      end
      slotsToDays(slots)
    end

Return the average number of working days per year.

[Source]

# File lib/taskjuggler/Project.rb, line 532
    def yearlyWorkingDays
      @attributes['yearlyworkingdays'].to_f
    end

Protected Instance methods

[Source]

# File lib/taskjuggler/Project.rb, line 1131
    def finishScenario(scIdx)
      Log.enter('finishScenario',
                "Finishing scenario #{scenario(scIdx).get('name')}")
      Log.startProgressMeter("Checking scenario #{scenario(scIdx).get('name')}")
      @tasks.each do |task|
        # Recursively traverse the top-level tasks to finish all tasks.
        task.finishScheduling(scIdx) unless task.parent
      end

      @resources.each do |resource|
        # Recursively traverse the top-level resources to finish them all.
        resource.finishScheduling(scIdx) unless resource.parent
      end

      i = 0
      total = @tasks.items
      @tasks.each do |task|
        task.postScheduleCheck(scIdx) if task.parent.nil?
        i += 1
        Log.progress(i.to_f / total)
      end

      Log.stopProgressMeter
      Log.exit('finishScenario',
               "Finishing scenario #{scenario(scIdx).get('name')} completed")
    end

[Source]

# File lib/taskjuggler/Project.rb, line 1054
    def prepareScenario(scIdx)
      Log.enter('prepareScenario',
                "Finishing scenario #{scenario(scIdx).get('name')}")
      Log.startProgressMeter("Preparing scenario " +
                             "#{scenario(scIdx).get('name')}")
      resources = PropertyList.new(@resources)
      tasks = PropertyList.new(@tasks)

      # Compile a list of leaf resources that are actually used in this
      # project.
      usedResources = []
      tasks.each do |task|
        task.candidates(scIdx).each do |resource|
          usedResources << resource unless usedResources.include?(resource)
        end
      end
      total = usedResources.length
      i = 0
      usedResources.each do |resource|
        resource.prepareScheduling(scIdx)
        resource.preScheduleCheck(scIdx)
        i += 1
        Log.progress((i.to_f / total) * 0.8)
      end

      resources.each { |resource| resource.setDirectReports(scIdx) }
      resources.each { |resource| resource.setReports(scIdx) }

      computeResourceAvailabilities(scIdx, usedResources)

      Log.progress(0.81)
      tasks.each { |task| task.prepareScheduling(scIdx) }
      Log.progress(0.82)
      tasks.each { |task| task.Xref(scIdx) }
      tasks.each { |task| task.propagateInitialValues(scIdx) }
      Log.progress(0.83)
      tasks.each { |task| task.preScheduleCheck(scIdx) }

      Log.progress(0.84)
      # Check for dependency loops in the task graph.
      tasks.each { |task| task.resetLoopFlags(scIdx) }
      tasks.each do |task|
        task.checkForLoops(scIdx, [], false, true, true) if task.parent.nil?
      end
      Log.progress(0.85)
      tasks.each { |task| task.resetLoopFlags(scIdx) }
      tasks.each do |task|
        task.checkForLoops(scIdx, [], true, true, false) if task.parent.nil?
      end
      Log.progress(0.87)

      # Compute the criticalness of the tasks and their pathes.
      tasks.each { |task| task.countResourceAllocations(scIdx) }
      Log.progress(0.88)
      resources.each { |resource| resource.calcCriticalness(scIdx) }
      Log.progress(0.9)
      tasks.each { |task| task.calcCriticalness(scIdx) }
      Log.progress(0.95)
      tasks.each { |task| task.calcPathCriticalness(scIdx) }
      Log.progress(0.99)
      @timeSheets.check
      Log.progress(1.0)

      Log.stopProgressMeter
      # This is used for debugging only
      if false
        resources.each do |resource|
          puts "#{resource}"
        end
        tasks.each do |task|
          puts "#{task}"
        end
      end
      Log.exit('prepareScenario',
               "Preparing scenario #{scenario(scIdx).get('name')} completed")
    end

Schedule all tasks for the given Scenario with index scIdx.

[Source]

# File lib/taskjuggler/Project.rb, line 1159
    def scheduleScenario(scIdx)
      tasks = PropertyList.new(@tasks)
      # Only care about leaf tasks that are not milestones are aren't
      # scheduled already (due to bookings).
      tasks.delete_if { |task| !task.leaf? ||
                               task['milestone', scIdx] ||
                               task['scheduled', scIdx] }

      Log.enter('scheduleScenario', "#{tasks.length} leaf tasks")
      # The sorting of the work item list determines which tasks will get their
      # resources first. The first sorting criterium is the user specified task
      # priority. The second criterium is the scheduler determined priority
      # stored in the pathcriticalness attribute. That way, the user can always
      # override the scheduler determined priority. To always have a defined
      # order, the third criterium is the sequence number.
      tasks.setSorting([ [ 'priority', false, scIdx ],
                         [ 'pathcriticalness', false, scIdx ],
                         [ 'seqno', true, -1 ] ])
      tasks.sort!
      totalTasks = tasks.length

      # Enter the main scheduling loop. This loop is only terminated when all
      # tasks have been scheduled or another thread has set the breakFlag to
      # true.
      Log.startProgressMeter("Scheduling scenario " +
                             "#{scenario(scIdx).get('name')}")
      failedTasks = []
      while !tasks.empty?
        # Only update the progress bar every 10 completed tasks.
        if tasks.length % 10 == 0
          percentComplete = (totalTasks - tasks.length).to_f / totalTasks
          Log.progress(percentComplete)
        end

        # Now find the task with the highest priority that can be scheduled
        # and schedule it.
        taskToRemove = nil
        tasks.each do |task|
          # Task not ready? Ignore it.
          next unless task.readyForScheduling?(scIdx)

          unless task.schedule(scIdx)
            failedTasks << task
          end
          # The task has been completed or failed. But we can remove it from
          # the todo list.
          taskToRemove = task
          # The scheduling of this task may cause other higher priority tasks
          # to be ready now. So we terminate the inner loop and start at the
          # top of the list again.
          break
        end

        # There should always be a task to be removed. If not, the scheduler
        # has found a set of tasks that deadlock each other.
        if taskToRemove
          tasks.delete(taskToRemove)
        elsif failedTasks.empty?
          warning('deadlock',
                  'Some tasks reference each other but don\'t provide ' +
                  'enough information to start the scheduling. The ' +
                  'scheduler does not know where to start scheduling ' +
                  'these tasks. You need to provide more fixed dates ' +
                  'or dependencies on already scheduled tasks.')
          failedTasks = tasks
          break
        else
          # We have some tasks that cannot be scheduled.
          break
        end
      end

      # Check for failed tasks and report the first 10 of them as
      # warnings.
      unless failedTasks.empty?
        warning('unscheduled_tasks',
                "#{failedTasks.length} tasks could not be scheduled")
        i = 0
        failedTasks.each do |t|
          warning('unscheduled_task',
                  "Task #{t.fullId}: " +
                  "#{t['start', scIdx] ? t['start', scIdx] : '<?>'} -> " +
                  "#{t['end', scIdx] ? t['end', scIdx] : '<?>'}",
                  t.sourceFileInfo)

          i += 1
          break if i >= 10
        end
        Log.stopProgressMeter
        return false
      end

      Log.stopProgressMeter
      Log.exit('scheduleScenario', "Scheduling of scenario #{scIdx} finished")
      true
    end

[Validate]