2015-03-31 11:32:45 +02:00
|
|
|
class Pert
|
2015-03-13 18:32:19 +01:00
|
|
|
constructor: (@list, @verbose) ->
|
2015-04-08 12:42:39 +02:00
|
|
|
@verbose = true
|
2015-03-13 21:03:11 +01:00
|
|
|
@days = []
|
2015-04-07 14:09:05 +02:00
|
|
|
@criticalPaths = []
|
2015-03-13 18:32:19 +01:00
|
|
|
|
2015-04-08 12:42:39 +02:00
|
|
|
log: (x...) ->
|
|
|
|
if chalk?
|
|
|
|
console.log chalk.bold "[ Pert ]", x...
|
|
|
|
else console.log "[ Pert ]", x...
|
|
|
|
err: (x...) ->
|
|
|
|
if chalk?
|
|
|
|
console.log chalk.bold chalk.red("[ !Pert! ]"), x...
|
|
|
|
else console.log "[ !Pert! ]", x...
|
2015-03-13 17:19:48 +01:00
|
|
|
|
|
|
|
# Returns the highest number in an array of numbers
|
|
|
|
maxa: (l) -> return Math.max.apply null, l
|
|
|
|
|
|
|
|
# Find the activity with given id
|
2015-03-13 18:32:19 +01:00
|
|
|
toActivity: (id) =>
|
|
|
|
if !@list? then @err "list is", @list
|
2015-03-13 17:19:48 +01:00
|
|
|
item = {}
|
2015-03-13 18:32:19 +01:00
|
|
|
@list.forEach (x) -> if x.id is id then item = x
|
2015-03-13 17:19:48 +01:00
|
|
|
return item
|
|
|
|
|
2015-03-13 18:32:19 +01:00
|
|
|
# Compute the item's end day
|
|
|
|
calculateEndDay: (item) =>
|
2015-03-13 17:19:48 +01:00
|
|
|
if !item.startDay?
|
2015-03-13 18:32:19 +01:00
|
|
|
@log "calculating start day of",item.id
|
|
|
|
item.startDay = @calculateStartDay item
|
|
|
|
@log "start day of",item.id,"is",item.startDay
|
2015-03-13 17:19:48 +01:00
|
|
|
item.endDay = item.startDay + item.duration
|
2015-03-13 18:32:19 +01:00
|
|
|
@log "end day of",item.id,"is",item.endDay
|
2015-03-13 21:03:11 +01:00
|
|
|
@insertDay item.endDay
|
2015-03-13 17:19:48 +01:00
|
|
|
return item.endDay
|
|
|
|
|
|
|
|
# Find out which day the activity starts
|
2015-03-13 18:32:19 +01:00
|
|
|
calculateStartDay: (item) =>
|
2015-03-13 21:03:11 +01:00
|
|
|
if !item.depends? or item.depends.length is 0
|
|
|
|
@insertDay 0
|
|
|
|
return item.startDay = 0
|
2015-03-13 18:32:19 +01:00
|
|
|
item.startDay = @maxa item.depends.map(@toActivity).map @calculateEndDay
|
|
|
|
@log "start day of",item.id,"is",item.startDay
|
2015-03-13 17:19:48 +01:00
|
|
|
# write max delay time to each depend
|
2015-03-13 18:32:19 +01:00
|
|
|
item.depends.forEach (x) =>
|
|
|
|
@log "checking permittedDelay to dependency", x, "of", item
|
|
|
|
i = @toActivity x
|
2015-03-16 11:44:08 +01:00
|
|
|
if !i.dependant? then i.dependant = [item.id]
|
|
|
|
else i.dependant.push item.id
|
2015-03-13 17:19:48 +01:00
|
|
|
if !i.permittedDelay?
|
2015-03-13 18:32:19 +01:00
|
|
|
i.permittedDelay = item.startDay - @calculateEndDay i
|
|
|
|
@log "written permittedDelay to dependency", x, "of", item, "as", i.permittedDelay
|
|
|
|
else @log "aborting permittedDelay: already calculated"
|
|
|
|
@log "permitted delay of",x,"is",i.permittedDelay
|
2015-03-13 21:03:11 +01:00
|
|
|
@insertDay item.startDay
|
2015-03-13 17:19:48 +01:00
|
|
|
return item.startDay
|
|
|
|
|
2015-04-07 13:36:07 +02:00
|
|
|
calculateDelays: (item) =>
|
|
|
|
if !item.dependant? or item.dependant.length is 0 then return no
|
2015-04-08 12:42:39 +02:00
|
|
|
lowestFDelay = 0; fDelay = no
|
2015-04-07 13:36:07 +02:00
|
|
|
for j,i of item.dependant
|
|
|
|
x = @toActivity i
|
2015-04-08 12:42:39 +02:00
|
|
|
if x.permittedDelay > 0 and (x.permittedDelay < lowestFDelay or fDelay is no)
|
|
|
|
lowestFDelay = x.permittedDelay
|
|
|
|
fDelay = yes
|
2015-04-07 13:36:07 +02:00
|
|
|
olDelay = item.chainedDelay
|
2015-04-08 12:42:39 +02:00
|
|
|
item.chainedDelay = lowestFDelay or 0
|
|
|
|
@log "chained delay of", item.id, "is", item.chainedDelay
|
2015-04-07 13:36:07 +02:00
|
|
|
return item.chainedDelay isnt olDelay
|
|
|
|
|
2015-04-07 14:09:05 +02:00
|
|
|
calculateCriticalPaths: (path) ->
|
|
|
|
@log "calculating path from",path
|
|
|
|
lastID = path[path.length - 1]
|
|
|
|
last = @toActivity lastID
|
|
|
|
if last.dependant? and last.dependant.length > 0
|
|
|
|
last.dependant.forEach (x) =>
|
|
|
|
ii = @toActivity x
|
2015-04-08 12:42:39 +02:00
|
|
|
delay = ii.permittedDelay or 0
|
|
|
|
if delay is 0
|
|
|
|
@calculateCriticalPaths path.concat x
|
|
|
|
else
|
|
|
|
@log "dead end at", lastID, "-->", x, "because delay is", delay
|
2015-04-07 14:09:05 +02:00
|
|
|
else
|
|
|
|
path.forEach (x) => @toActivity(x).critical = yes
|
2015-04-08 12:42:39 +02:00
|
|
|
@log "calculated path", path
|
2015-04-07 14:09:05 +02:00
|
|
|
@criticalPaths.push path
|
|
|
|
|
2015-03-13 17:19:48 +01:00
|
|
|
# Find out which activity has the highest id
|
2015-03-13 18:32:19 +01:00
|
|
|
highestID: => return @maxa(@list.map (x) -> x.id)
|
2015-03-13 17:19:48 +01:00
|
|
|
|
2015-03-13 21:03:11 +01:00
|
|
|
# Insert a day to the list of days if it's not there already
|
|
|
|
insertDay: (day) =>
|
|
|
|
for d in @days
|
|
|
|
if day is d then return
|
|
|
|
@days.push day
|
|
|
|
|
2015-03-14 09:42:05 +01:00
|
|
|
setData: (data) ->
|
|
|
|
@list = data
|
|
|
|
return @
|
|
|
|
|
2015-03-16 11:44:08 +01:00
|
|
|
calculate: (options,cb) ->
|
2015-03-14 09:42:05 +01:00
|
|
|
h = @highestID()
|
2015-04-07 13:36:07 +02:00
|
|
|
for x,i in @list
|
2015-03-14 09:42:05 +01:00
|
|
|
@log '('+x.id+'/'+h+')'
|
|
|
|
@calculateEndDay x
|
2015-04-07 13:36:07 +02:00
|
|
|
finished = no; i = 0
|
|
|
|
while !finished
|
|
|
|
i++; finished = yes
|
|
|
|
for x,i in @list
|
|
|
|
if @calculateDelays x
|
|
|
|
finished = no
|
|
|
|
@log "Done calculating delays. Took", i, "iterations"
|
2015-04-07 14:09:05 +02:00
|
|
|
for x,i in @list
|
|
|
|
if !x.depends? or x.depends.length is 0
|
|
|
|
@calculateCriticalPaths [x.id]
|
|
|
|
results = activities: @list, days: @days, criticalPaths: @criticalPaths
|
2015-03-16 11:44:08 +01:00
|
|
|
if options?.json
|
|
|
|
if cb? then cb(JSON.stringify results)
|
|
|
|
JSON.stringify results
|
|
|
|
else
|
|
|
|
if cb? then cb(results)
|
|
|
|
results
|