class TaskJuggler::Project

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.

Attributes

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

Public Class Methods

maxScheduleGranularity() click to toggle source

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.

# File lib/taskjuggler/Project.rb, line 1016
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
new(id, name, version) click to toggle source

Create a project with the specified id, name and version. The constructor will set default values for all project attributes.

# File lib/taskjuggler/Project.rb, line 65
def initialize(id, name, version)
  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,
    'auxdir' => '',
    'copyright' => nil,
    'costaccount' => nil,
    'currency' => "EUR",
    'currencyFormat' => RealFormat.new([ '-', '', '', ',', 2 ]),
    'dailyworkinghours' => 8.0,
    'end' => nil,
    'markdate' => 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',          IntegerAttribute,
          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',        IntegerAttribute,
          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',            IntegerAttribute,
          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',        IntegerAttribute,
          false, false,   false, -1 ],
    [ 'flags',     'Flags',        FlagListAttribute,
          true,  false,   true,  [] ],
    [ 'name',     'Name',       StringAttribute,
          false, false,   false, nil ],
    [ 'seqno',    'No',          IntegerAttribute,
          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',    IntegerAttribute,
          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',        IntegerAttribute,
          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',          IntegerAttribute,
          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 ],
    [ 'competitors', 'Competitors', TaskListAttribute,
          false, false,   true,  [] ],
    [ '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 ],
    [ 'effortdone', 'Completed Effort', IntegerAttribute,
          false, false,   true,  nil ],
    [ 'effortleft', 'Remaining Effort', IntegerAttribute,
          false, false,   true,  nil ],
    [ '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',        IntegerAttribute,
          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',     IntegerAttribute,
          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 ],
    [ 'projectionmode', 'Projection Mode', BooleanAttribute,
          true,  false,   true,  false ],
    [ 'seqno',    'No',          IntegerAttribute,
          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 ],
    [ 'auxdir', 'Auxiliary files directory', StringAttribute,
          true,  true,    false, '' ],
    [ '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 ],
    [ 'markdate',  'Markdate',     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 ],
    [ 'height',    'Height',       IntegerAttribute,
          false,  false,   false, 480 ],
    [ 'id',       'ID',         StringAttribute,
          false, false,   false, nil ],
    [ 'index',     'Index',        IntegerAttribute,
          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 ],
    [ 'rawHtmlHead', 'Raw HTML Header', StringAttribute,
          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',          IntegerAttribute,
          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 ],
    [ 'width',     'Width',        IntegerAttribute,
          true,  false,   false, 640 ],
    [ 'novevents',  'No vevents in icalreports', BooleanAttribute,
          true,  false,   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

[](name) click to toggle source

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

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

Set the Project attribute with ID name to value.

# File lib/taskjuggler/Project.rb, line 511
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
account(id) click to toggle source

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

# File lib/taskjuggler/Project.rb, line 599
def account(id)
  @accounts[id]
end
anyResourceAvailable?(sbIdx) click to toggle source

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

# File lib/taskjuggler/Project.rb, line 1008
def anyResourceAvailable?(sbIdx)
  @resourceAvailability[sbIdx]
end
attributeName(id) click to toggle source

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.

# File lib/taskjuggler/Project.rb, line 1037
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
checkReports() click to toggle source

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

# File lib/taskjuggler/Project.rb, line 697
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
checkTimeSheets() click to toggle source
# File lib/taskjuggler/Project.rb, line 829
def checkTimeSheets
  @timeSheets.check
end
collectTimeOffIntervals(iv, minDuration) click to toggle source
# File lib/taskjuggler/Project.rb, line 975
def collectTimeOffIntervals(iv, minDuration)
  @scoreboard.collectIntervals(iv, minDuration) do |val|
    val.is_a?(Integer) && (val & 0x3E) != 0
  end
end
convertToDailyLoad(seconds) click to toggle source

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

# File lib/taskjuggler/Project.rb, line 934
def convertToDailyLoad(seconds)
  seconds / (@attributes['dailyworkinghours'] * 3600.0)
end
dailyWorkingHours() click to toggle source

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

# File lib/taskjuggler/Project.rb, line 538
def dailyWorkingHours
  @attributes['dailyworkinghours'].to_f
end
dateToIdx(date, forceIntoProject = true) click to toggle source

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

# File lib/taskjuggler/Project.rb, line 960
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
deep_clone() click to toggle source

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

# File lib/taskjuggler/Project.rb, line 498
def deep_clone
  self
end
enableTraceReports(enable) click to toggle source

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

# File lib/taskjuggler/Project.rb, line 679
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
generateReport(reportId, regExpMode, formats = nil, dynamicAttributes = nil) click to toggle source
# File lib/taskjuggler/Project.rb, line 776
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
        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
generateReports(maxCpuCores) click to toggle source

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

# File lib/taskjuggler/Project.rb, line 728
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
getWorkSlots(startIdx, endIdx) click to toggle source

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

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

  slots
end
hasWorkingTime(startTime, endTime) → true or false click to toggle source
hasWorkingTime(interval) → true or false

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.

# File lib/taskjuggler/Project.rb, line 911
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
idxToDate(idx) click to toggle source

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.

# File lib/taskjuggler/Project.rb, line 950
def idxToDate(idx)
  if $DEBUG && (idx < 0 || idx > scoreboardSize)
    raise "Scoreboard index out of range"
  end
  @attributes['start'] + idx * @attributes['scheduleGranularity']
end
isWorkingTime(slotIdx) → true or false click to toggle source
isWorkingTime(slot) → true or false
isWorkingTime(startTime, endTime) → true or false
isWorkingTime(interval) → true or false

Return true if the slot or interval is within 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.

# File lib/taskjuggler/Project.rb, line 879
def isWorkingTime(*args)
  # Normalize argument(s) to TimeInterval
  if args.length == 1
    if args[0].is_a?(Integer)
      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
journal(query) click to toggle source
# File lib/taskjuggler/Project.rb, line 1045
def journal(query)
  @attributes['journal'].to_rti(query)
end
listReports(reportId, regExpMode) click to toggle source
# File lib/taskjuggler/Project.rb, line 816
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
monthlyWorkingDays() click to toggle source

Return the average number of working days per month.

# File lib/taskjuggler/Project.rb, line 548
def monthlyWorkingDays
  @attributes['yearlyworkingdays'] / 12.0
end
report(id) click to toggle source

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

# File lib/taskjuggler/Project.rb, line 614
def report(id)
  @reports[id]
end
reportByName(name) click to toggle source

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

# File lib/taskjuggler/Project.rb, line 620
def reportByName(name)
  @reports.each do |report|
    return report if report.name == name
  end
  nil
end
resource(id) click to toggle source

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

# File lib/taskjuggler/Project.rb, line 609
def resource(id)
  @resources[id]
end
scenario(index) → Scenario click to toggle source
scenario(id) → Scenario

Return the Scenario with the given id or index.

# File lib/taskjuggler/Project.rb, line 567
def scenario(arg)
  if arg.is_a?(Integer)
    @scenarios.each do |sc|
      return sc if sc.sequenceNo - 1 == arg
    end
  else
    return @scenarios[arg]
  end
  nil
end
scenarioCount() click to toggle source

Return the number of defined scenarios for the project.

# File lib/taskjuggler/Project.rb, line 532
def scenarioCount
  @scenarios.items
end
scenarioIdx(scenario) click to toggle source
scenarioIdx(id)

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

# File lib/taskjuggler/Project.rb, line 583
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
schedule() click to toggle source

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.

# File lib/taskjuggler/Project.rb, line 630
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
scoreboardSize() click to toggle source

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.

# File lib/taskjuggler/Project.rb, line 942
def scoreboardSize
  ((@attributes['end'] - @attributes['start']) /
   @attributes['scheduleGranularity']).to_i
end
shift(id) click to toggle source

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

# File lib/taskjuggler/Project.rb, line 594
def shift(id)
  @shifts[id]
end
slotsToDays(slots) click to toggle source

Convert timeSlots to working days.

# File lib/taskjuggler/Project.rb, line 558
def slotsToDays(slots)
  slots * @attributes['scheduleGranularity'] / (60 * 60 * dailyWorkingHours)
end
task(id) click to toggle source

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

# File lib/taskjuggler/Project.rb, line 604
def task(id)
  @tasks[id]
end
to_s() click to toggle source

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

# File lib/taskjuggler/Project.rb, line 1050
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
weeklyWorkingDays() click to toggle source
# File lib/taskjuggler/Project.rb, line 542
def weeklyWorkingDays
  @attributes['workinghours'].weeklyWorkingHours /
    @attributes['dailyworkinghours']
end
workingDays(interval) click to toggle source

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

# File lib/taskjuggler/Project.rb, line 983
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
yearlyWorkingDays() click to toggle source

Return the average number of working days per year.

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

Protected Instance Methods

finishScenario(scIdx) click to toggle source
# File lib/taskjuggler/Project.rb, line 1141
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
prepareScenario(scIdx) click to toggle source
# File lib/taskjuggler/Project.rb, line 1064
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
scheduleScenario(scIdx) click to toggle source

Schedule all tasks for the given Scenario with index scIdx.

# File lib/taskjuggler/Project.rb, line 1169
def scheduleScenario(scIdx)
  tasks = PropertyList.new(@tasks)
  # Only care about leaf tasks that are not milestones and aren't
  # scheduled already (marked with the 'scheduled' attribute).
  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