lib/taskjuggler/ResourceScenario.rb in taskjuggler-0.1.1 vs lib/taskjuggler/ResourceScenario.rb in taskjuggler-0.2.0
- old
+ new
@@ -44,17 +44,26 @@
# The index of the last booked time Slot.
@lastBookedSlot = nil
# Same but for each assigned resource.
@lastBookedSlots = {}
+ # Attributed are only really created when they are accessed the first
+ # time. So make sure some needed attributes really exist so we don't
+ # have to check for existance each time we access them.
+ %w( alloctdeffort criticalness directreports duties efficiency
+ effort limits managers rate reports shifts
+ vacations workinghours ).each do |attr|
+ @property[attr, @scenarioIdx]
+ end
+
@dCache = DataCache.instance
end
# This method must be called at the beginning of each scheduling run. It
# initializes variables used during the scheduling process.
def prepareScheduling
- @property['effort', @scenarioIdx] = 0
+ @effort = 0
initScoreboard if @property.leaf?
setDirectReports
end
@@ -64,45 +73,45 @@
# statistically some tasks will not get their resources. A value between
# 0 and 1 implies no guarantee, though.
def calcCriticalness
if @scoreboard.nil?
# Resources that are not allocated are not critical at all.
- @property['criticalness', @scenarioIdx] = 0.0
+ @criticalness = 0.0
else
freeSlots = 0
@scoreboard.each do |slot|
freeSlots += 1 if slot.nil?
end
- @property['criticalness', @scenarioIdx] = freeSlots == 0 ? 1.0 :
- a('alloctdeffort') / freeSlots
+ @criticalness = freeSlots == 0 ? 1.0 :
+ @alloctdeffort / freeSlots
end
end
def setDirectReports
# Only leaf resources have reporting lines.
return unless @property.leaf?
# The 'directreports' attribute is the reverse link for the 'managers'
# attribute. In contrast to the 'managers' attribute, the
# 'directreports' list has no duplicate entries.
- a('managers').each do |manager|
+ @managers.each do |manager|
unless manager['directreports', @scenarioIdx].include?(@property)
manager['directreports', @scenarioIdx] << @property
end
end
end
def setReports
- return unless a('directreports').empty?
+ return unless @directreports.empty?
- a('managers').each do |r|
+ @managers.each do |r|
r.setReports_i(@scenarioIdx, [ @property ])
end
end
def preScheduleCheck
- a('managers').each do |manager|
+ @managers.each do |manager|
unless manager.leaf?
error('manager_is_group',
"Resource #{@property.fullId} has group #{manager.fullId} " +
"assigned as manager. Managers must be leaf resources.")
end
@@ -122,11 +131,11 @@
resource.finishScheduling(@scenarioIdx)
end
if (parent = @property.parent)
# Add the assigned task to the parent duties list.
- a('duties').each do |task|
+ @duties.each do |task|
unless parent['duties', @scenarioIdx].include?(task)
parent['duties', @scenarioIdx] << task
end
end
end
@@ -135,12 +144,12 @@
# Returns true if the resource is available at the time specified by
# _sbIdx_.
def available?(sbIdx)
return false unless @scoreboard[sbIdx].nil?
- limits = a('limits')
- return false if limits && !limits.ok?(@scoreboard.idxToDate(sbIdx))
+ limits = @limits
+ return false if limits && !limits.ok?(sbIdx)
true
end
# Return true if the resource is booked for a tasks at the time specified by
@@ -154,12 +163,12 @@
# method returns true if the slot was available.
def book(sbIdx, task, force = false)
return false if !force && !available?(sbIdx)
# Make sure the task is in the list of duties.
- unless a('duties').include?(task)
- @property['duties', @scenarioIdx] << task
+ unless @duties.include?(task)
+ @duties << task
end
#puts "Booking resource #{@property.fullId} at " +
# "#{@scoreboard.idxToDate(sbIdx)}/#{sbIdx} for task #{task.fullId}\n"
@scoreboard[sbIdx] = task
@@ -168,11 +177,11 @@
t = @property
while t
t['effort', @scenarioIdx] += 1
t = t.parent
end
- a('limits').inc(@scoreboard.idxToDate(sbIdx)) if a('limits')
+ @limits.inc(sbIdx) if @limits
# Scoreboard iterations are fairly expensive but they are very frequent
# operations in later processing. To limit the interations to the
# relevant intervals, we store the interval for all bookings and for
# each individual task.
@@ -240,11 +249,11 @@
query.sortable = query.numerical = cost =
turnover(query.startIdx, query.endIdx, query.costAccount,
query.scopeProperty)
query.string = query.currencyFormat.format(cost)
else
- query.string = 'No cost account'
+ query.string = 'No \'balance\' defined!'
end
end
# The effort allocated to the Resource in the specified interval. In case a
# Task is given as scope property only the effort allocated to this Task is
@@ -283,16 +292,16 @@
query.sortable = query.numerical = revenue =
turnover(query.startIdx, query.endIdx, query.revenueAccount,
query.scopeProperty)
query.string = query.currencyFormat.format(revenue)
else
- query.string = 'No revenue account'
+ query.string = 'No \'balance\' defined!'
end
end
# The work time of the Resource that was blocked by a vacation during the
- # specified Interval. The result is in working days (effort).
+ # specified TimeInterval. The result is in working days (effort).
def query_vacationdays(query)
query.sortable = query.numerical = time =
getVacationDays(query.startIdx, query.endIdx)
query.string = query.scaleLoad(time)
end
@@ -300,11 +309,11 @@
# Returns the work of the resource (and its children) weighted by their
# efficiency.
def getEffectiveWork(startIdx, endIdx, task = nil)
# There can't be any effective work if the start is after the end or the
# todo list doesn't contain the specified task.
- return 0.0 if startIdx >= endIdx || (task && !a('duties').include?(task))
+ return 0.0 if startIdx >= endIdx || (task && !@duties.include?(task))
# The unique key we use to address the result in the cache.
key = [ self, :ResourceScenarioEffectiveWork, startIdx, endIdx,
task ].hash
work = @dCache.load(key)
@@ -322,11 +331,11 @@
else
return 0.0 if @scoreboard.nil?
work = @project.convertToDailyLoad(
getAllocatedSlots(startIdx, endIdx, task) *
- @project['scheduleGranularity']) * a('efficiency')
+ @project['scheduleGranularity']) * @efficiency
end
@dCache.store(work, key)
end
# Returns the allocated accumulated time of this resource and its children.
@@ -382,11 +391,11 @@
work += resource.getEffectiveFreeWork(@scenarioIdx, startIdx, endIdx)
end
else
work = @project.convertToDailyLoad(
getFreeSlots(startIdx, endIdx) *
- @project['scheduleGranularity']) * a('efficiency')
+ @project['scheduleGranularity']) * @efficiency
end
work
end
# Return the number of working days that are blocked by vacations.
@@ -404,11 +413,11 @@
else
initScoreboard if @scoreboard.nil?
vacationDays = @project.convertToDailyLoad(
getVacationSlots(startIdx, endIdx) *
- @project['scheduleGranularity']) * a('efficiency')
+ @project['scheduleGranularity']) * @efficiency
end
vacationDays
end
def turnover(startIdx, endIdx, account, task = nil)
@@ -416,31 +425,31 @@
if @property.container?
@property.kids.each do |child|
amount += child.turnover(@scenarioIdx, startIdx, endIdx, account, task)
end
else
- a('duties').each do |duty|
+ @duties.each do |duty|
amount += duty.turnover(@scenarioIdx, startIdx, endIdx, account,
@property)
end
end
amount
end
- # Returns the cost for using this resource during the specified Interval
- # _period_. If a Task _task_ is provided, only the work on this particular
- # task is considered.
+ # Returns the cost for using this resource during the specified
+ # TimeInterval _period_. If a Task _task_ is provided, only the work on
+ # this particular task is considered.
def cost(startIdx, endIdx, task = nil)
- getAllocatedTime(startIdx, endIdx, task) * a('rate')
+ getAllocatedTime(startIdx, endIdx, task) * @rate
end
# Returns true if the resource or any of its children is allocated during
- # the period specified with the Interval _iv_. If task is not nil
+ # the period specified with the TimeInterval _iv_. If task is not nil
# only allocations to this tasks are respected.
def allocated?(iv, task = nil)
- return false if task && !a('duties').include?(task)
+ return false if task && !@duties.include?(task)
startIdx = @project.dateToIdx(iv.start)
endIdx = @project.dateToIdx(iv.end)
startIdx, endIdx = fitIndicies(startIdx, endIdx, task)
@@ -448,52 +457,62 @@
return allocatedSub(startIdx, endIdx, task)
end
# Iterate over the scoreboard and turn its content into a set of Bookings.
- # _iv_ can be an Interval to limit the bookings within the provided
- # period.
- def getBookings(iv = nil)
- return {} if @property.container? || @scoreboard.nil? ||
- @firstBookedSlot.nil? || @lastBookedSlot.nil?
+ # _iv_ can be a TimeInterval to limit the bookings within the provided
+ # period. if _hashByTask_ is true, the result is a Hash of Arrays with
+ # bookings hashed by Task. Otherwise it's just a plain Array with
+ # Bookings.
+ def getBookings(iv = nil, hashByTask = true)
+ bookings = hashByTask ? {} : []
+ return bookings if @property.container? || @scoreboard.nil? ||
+ @firstBookedSlot.nil? || @lastBookedSlot.nil?
- bookings = {}
- lastTask = nil
- bookingStart = nil
-
# To speedup the collection we start with the first booked slot and end
# with the last booked slot.
startIdx = @firstBookedSlot
endIdx = @lastBookedSlot + 1
- # In case the index markers are still uninitialized, we have no bookings.
- return {} if startIdx.nil? || endIdx.nil?
-
- # If the user provided an Interval, we only return bookings within this
- # Interval.
+ # If the user provided a TimeInterval, we only return bookings within
+ # this TimeInterval.
if iv
ivStartIdx = @project.dateToIdx(iv.start)
ivEndIdx = @project.dateToIdx(iv.end)
startIdx = ivStartIdx if ivStartIdx > startIdx
endIdx = ivEndIdx if ivEndIdx < endIdx
end
+ lastTask = nil
+ bookingStart = nil
+
startIdx.upto(endIdx) do |idx|
task = @scoreboard[idx]
# Now we watch for task changes.
- if task != lastTask || (lastTask == nil && task.is_a?(Task)) ||
- (task.is_a?(Task) && idx == endIdx)
- unless lastTask.nil?
+ if task != lastTask ||
+ (task.is_a?(Task) && (lastTask.nil? || idx == endIdx))
+ if lastTask
+ # We've found the end of a task booking series.
# If we don't have a Booking for the task yet, we create one.
- if bookings[lastTask].nil?
- bookings[lastTask] = Booking.new(@property, lastTask, [])
+ if hashByTask
+ if bookings[lastTask].nil?
+ bookings[lastTask] = Booking.new(@property, lastTask, [])
+ end
+ # Append the new interval to the Booking.
+ bookings[lastTask].intervals <<
+ TimeInterval.new(@scoreboard.idxToDate(bookingStart),
+ @scoreboard.idxToDate(idx))
+ else
+ if bookings.empty? || bookings.last.task != lastTask
+ bookings << Booking.new(@property, lastTask, [])
+ end
+ # Append the new interval to the Booking.
+ bookings.last.intervals <<
+ TimeInterval.new(@scoreboard.idxToDate(bookingStart),
+ @scoreboard.idxToDate(idx))
end
- # Append the new interval to the Booking.
- bookings[lastTask].intervals <<
- Interval.new(@scoreboard.idxToDate(bookingStart),
- @scoreboard.idxToDate(idx))
end
# Get ready for the next task booking interval
if task.is_a?(Task)
lastTask = task
bookingStart = idx
@@ -576,23 +595,20 @@
# Create scoreboard and mark all slots as unavailable
@scoreboard = Scoreboard.new(@project['start'], @project['end'],
@project['scheduleGranularity'], 2)
# We'll need this frequently and can savely cache it here.
- @shifts = a('shifts')
- @workinghours = a('workinghours')
+ @shifts = @shifts
+ @workinghours = @workinghours
# Change all work time slots to nil (available) again.
- date = @scoreboard.idxToDate(0)
- delta = @project['scheduleGranularity']
@project.scoreboardSize.times do |i|
- @scoreboard[i] = nil if onShift?(date)
- date += delta
+ @scoreboard[i] = nil if onShift?(i)
end
# Mark all resource specific vacation slots as such
- a('vacations').each do |vacation|
+ @vacations.each do |vacation|
startIdx = @scoreboard.dateToIdx(vacation.start)
endIdx = @scoreboard.dateToIdx(vacation.end)
startIdx.upto(endIdx - 1) do |i|
# If the slot is nil, we don't set the time-off bit.
@scoreboard[i] = (@scoreboard[i].nil? ? 0 : 2) | (1 << 2)
@@ -611,11 +627,11 @@
end
unless @shifts.nil?
# Mark the vacations from all the shifts the resource is assigned to.
@project.scoreboardSize.times do |i|
- v = @shifts.getSbSlot(@scoreboard.idxToDate(i))
+ v = @shifts.getSbSlot(i)
# Check if the vacation replacement bit is set. In that case we copy
# the whole interval over to the resource scoreboard overriding any
# global vacations.
if (v & (1 << 8)) != 0
@scoreboard[i] = (v & 0x3E == 0) ? nil : (v & 0x3D)
@@ -652,25 +668,25 @@
# A manager must never show up in the list of his/her own reports.
error('manager_loop',
"Management loop detected. #{@property.fullId} has self " +
"in list of reports")
end
- @property['reports', @scenarioIdx] += reports
+ @reports += reports
# Resources can end up multiple times in the list if they have multiple
# reporting chains. We only need them once in the list.
- a('reports').uniq!
+ @reports.uniq!
- a('managers').each do |r|
- r.setReports_i(@scenarioIdx, a('reports'))
+ @managers.each do |r|
+ r.setReports_i(@scenarioIdx, @reports)
end
end
- def onShift?(date)
- if @shifts && @shifts.assigned?(date)
- return @shifts.onShift?(date)
+ def onShift?(sbIdx)
+ if @shifts && @shifts.assigned?(sbIdx)
+ return @shifts.onShift?(sbIdx)
else
- @workinghours.onShift?(date)
+ @workinghours.onShift?(sbIdx)
end
end
# Returns true if the resource or any of its children is allocated during
# the period specified with _startIdx_ and _endIdx_. If task is not nil
@@ -680,11 +696,11 @@
@property.kids.each do |resource|
return true if resource.allocatedSub(@scenarioIdx, startIdx, endIdx,
task)
end
else
- return false unless @scoreboard && a('duties').include?(task)
+ return false unless @scoreboard && @duties.include?(task)
startIdx, endIdx = fitIndicies(startIdx, endIdx, task)
return false if startIdx >= endIdx
startIdx.upto(endIdx - 1) do |idx|
@@ -701,10 +717,10 @@
@property.kids.each do |resource|
dailyRate += resource.rate(@scenarioIdx)
end
dailyRate
else
- a('rate')
+ @rate
end
end
end