You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1260 lines
38 KiB
1260 lines
38 KiB
/* |
|
Copyright (c) 2012-2018 Open Lab |
|
Written by Roberto Bicchierai and Silvia Chelazzi http://roberto.open-lab.com |
|
Permission is hereby granted, free of charge, to any person obtaining |
|
a copy of this software and associated documentation files (the |
|
"Software"), to deal in the Software without restriction, including |
|
without limitation the rights to use, copy, modify, merge, publish, |
|
distribute, sublicense, and/or sell copies of the Software, and to |
|
permit persons to whom the Software is furnished to do so, subject to |
|
the following conditions: |
|
|
|
The above copyright notice and this permission notice shall be |
|
included in all copies or substantial portions of the Software. |
|
|
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |
|
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE |
|
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION |
|
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION |
|
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
|
*/ |
|
|
|
/** |
|
* A method to instantiate valid task models from |
|
* raw data. |
|
*/ |
|
function TaskFactory() { |
|
|
|
/** |
|
* Build a new Task |
|
*/ |
|
this.build = function (id, name, code, level, start, duration, collapsed) { |
|
// Set at beginning of day |
|
var adjusted_start = computeStart(start); |
|
var calculated_end = computeEndByDuration(adjusted_start, duration); |
|
return new Task(id, name, code, level, adjusted_start, calculated_end, duration, collapsed); |
|
}; |
|
|
|
} |
|
|
|
function Task(id, name, code, level, start, end, duration, collapsed) { |
|
this.id = id; |
|
this.name = name; |
|
this.progress = 0; |
|
this.progressByWorklog = false; |
|
this.relevance = 0; |
|
this.type = ""; |
|
this.typeId = ""; |
|
this.description = ""; |
|
this.code = code; |
|
this.level = level; |
|
this.status = "STATUS_UNDEFINED"; |
|
this.depends = ""; |
|
|
|
this.start = start; |
|
this.duration = duration; |
|
this.end = end; |
|
|
|
this.startIsMilestone = false; |
|
this.endIsMilestone = false; |
|
|
|
this.collapsed = collapsed; |
|
|
|
//permissions |
|
// by default all true, but must be inherited from parent |
|
this.canWrite = true; |
|
this.canAdd = true; |
|
this.canDelete = true; |
|
this.canAddIssue = true; |
|
|
|
this.rowElement; //row editor html element |
|
this.ganttElement; //gantt html element |
|
this.master; |
|
|
|
|
|
this.assigs = []; |
|
this.material = []; |
|
this.tools = []; |
|
} |
|
|
|
Task.prototype.clone = function () { |
|
var ret = {}; |
|
for (var key in this) { |
|
if (typeof(this[key]) != "function") |
|
if (typeof(this[key]) != "object" || Array.isArray(this[key])) |
|
ret[key] = this[key]; |
|
} |
|
return ret; |
|
}; |
|
|
|
Task.prototype.getAssigsString = function () { |
|
var ret = ""; |
|
for (var i = 0; i < this.assigs.length; i++) { |
|
var ass = this.assigs[i]; |
|
var res = this.master.getResource(ass.resourceId); |
|
if (res) { |
|
ret = ret + (ret == "" ? "" : ", ") + res.name; |
|
} |
|
} |
|
return ret; |
|
}; |
|
|
|
Task.prototype.getMaterialString = function () { |
|
var ret = ""; |
|
for (var i = 0; i < this.assigs.length; i++) { |
|
var ass = this.assigs[i]; |
|
var res = this.master.getResource(ass.resourceId); |
|
if (res) { |
|
ret = ret + (ret == "" ? "" : ", ") + res.name; |
|
} |
|
} |
|
return ret; |
|
}; |
|
|
|
Task.prototype.getToolsString = function () { |
|
var ret = ""; |
|
for (var i = 0; i < this.assigs.length; i++) { |
|
var ass = this.assigs[i]; |
|
var res = this.master.getResource(ass.resourceId); |
|
if (res) { |
|
ret = ret + (ret == "" ? "" : ", ") + res.name; |
|
} |
|
} |
|
return ret; |
|
}; |
|
|
|
Task.prototype.createAssignment = function (id, resourceId, roleId, effort) { |
|
var assig = new Assignment(id, resourceId, roleId, effort); |
|
this.assigs.push(assig); |
|
return assig; |
|
}; |
|
|
|
|
|
//<%---------- SET PERIOD ---------------------- --%> |
|
Task.prototype.setPeriod = function (start, end) { |
|
//console.debug("setPeriod ",this.code,this.name,new Date(start), new Date(end)); |
|
//var profilerSetPer = new Profiler("gt_setPeriodJS"); |
|
|
|
if (start instanceof Date) { |
|
start = start.getTime(); |
|
} |
|
|
|
if (end instanceof Date) { |
|
end = end.getTime(); |
|
} |
|
|
|
var originalPeriod = { |
|
start: this.start, |
|
end: this.end, |
|
duration: this.duration |
|
}; |
|
|
|
|
|
//compute legal start/end //todo mossa qui R&S 30/3/2016 perchè altrimenti il calcolo della durata, che è stato modificato sommando giorni, sbaglia |
|
start = computeStart(start); |
|
end=computeEnd(end); |
|
|
|
var newDuration = recomputeDuration(start, end); |
|
|
|
//if are equals do nothing and return true |
|
if ( start == originalPeriod.start && end == originalPeriod.end && newDuration == originalPeriod.duration) { |
|
return true; |
|
} |
|
|
|
if (newDuration == this.duration) { // is shift |
|
return this.moveTo(start, false,true); |
|
} |
|
|
|
var wantedStartMillis = start; |
|
|
|
var children = this.getChildren(); |
|
|
|
if(this.master.shrinkParent && children.length>0) { |
|
var chPeriod= this.getChildrenBoudaries(); |
|
start = chPeriod.start; |
|
end = chPeriod.end; |
|
} |
|
|
|
|
|
//cannot start after end |
|
if (start > end) { |
|
start = end; |
|
} |
|
|
|
//if there are dependencies compute the start date and eventually moveTo |
|
var startBySuperiors = this.computeStartBySuperiors(start); |
|
if (startBySuperiors != start) { |
|
return this.moveTo(startBySuperiors, false,true); |
|
} |
|
|
|
var somethingChanged = false; |
|
|
|
if (this.start != start || this.start != wantedStartMillis) { |
|
this.start = start; |
|
somethingChanged = true; |
|
} |
|
|
|
//set end |
|
var wantedEndMillis = end; |
|
|
|
if (this.end != end || this.end != wantedEndMillis) { |
|
this.end = end; |
|
somethingChanged = true; |
|
} |
|
|
|
this.duration = recomputeDuration(this.start, this.end); |
|
|
|
//profilerSetPer.stop(); |
|
|
|
//nothing changed exit |
|
if (!somethingChanged) |
|
return true; |
|
|
|
//cannot write exit |
|
if (!this.canWrite) { |
|
this.master.setErrorOnTransaction("\"" + this.name + "\"\n" + GanttMaster.messages["CANNOT_WRITE"], this); |
|
return false; |
|
} |
|
|
|
//external dependencies: exit with error |
|
if (this.hasExternalDep) { |
|
this.master.setErrorOnTransaction("\"" + this.name + "\"\n" + GanttMaster.messages["TASK_HAS_EXTERNAL_DEPS"], this); |
|
return false; |
|
} |
|
|
|
var todoOk = true; |
|
|
|
//I'm restricting |
|
var deltaPeriod = originalPeriod.duration - this.duration; |
|
var restricting = deltaPeriod > 0; |
|
var enlarging = deltaPeriod < 0; |
|
var restrictingStart = restricting && (originalPeriod.start < this.start); |
|
var restrictingEnd = restricting && (originalPeriod.end > this.end); |
|
|
|
if (restricting) { |
|
//loops children to get boundaries |
|
var bs = Infinity; |
|
var be = 0; |
|
for (var i = 0; i < children.length; i++) { |
|
|
|
var ch = children[i]; |
|
if (restrictingEnd) { |
|
be = Math.max(be, ch.end); |
|
} else { |
|
bs = Math.min(bs, ch.start); |
|
} |
|
} |
|
|
|
if (restrictingEnd) { |
|
this.end = Math.max(be, this.end); |
|
} else { |
|
this.start = Math.min(bs, this.start); |
|
} |
|
this.duration = recomputeDuration(this.start, this.end); |
|
if (this.master.shrinkParent ) { |
|
todoOk = updateTree(this); |
|
} |
|
|
|
} else { |
|
|
|
//check global boundaries |
|
if (this.start < this.master.minEditableDate || this.end > this.master.maxEditableDate) { |
|
this.master.setErrorOnTransaction("\"" + this.name + "\"\n" +GanttMaster.messages["CHANGE_OUT_OF_SCOPE"], this); |
|
todoOk = false; |
|
} |
|
|
|
//console.debug("set period: somethingChanged",this); |
|
if (todoOk ) { |
|
todoOk = updateTree(this); |
|
} |
|
} |
|
|
|
if (todoOk) { |
|
todoOk = this.propagateToInferiors(end); |
|
} |
|
return todoOk; |
|
}; |
|
|
|
|
|
//<%---------- MOVE TO ---------------------- --%> |
|
Task.prototype.moveTo = function (start, ignoreMilestones, propagateToInferiors) { |
|
//console.debug("moveTo ",this.name,new Date(start),this.duration,ignoreMilestones); |
|
//var profiler = new Profiler("gt_task_moveTo"); |
|
|
|
if (start instanceof Date) { |
|
start = start.getTime(); |
|
} |
|
|
|
var originalPeriod = { |
|
start: this.start, |
|
end: this.end |
|
}; |
|
|
|
var wantedStartMillis = start; |
|
|
|
//set a legal start |
|
start = computeStart(start); |
|
|
|
//if depends, start is set to max end + lag of superior |
|
start = this.computeStartBySuperiors(start); |
|
|
|
var end = computeEndByDuration(start, this.duration); |
|
|
|
|
|
//check milestones compatibility |
|
if (!this.checkMilestonesConstraints(start,end,ignoreMilestones)) |
|
return false; |
|
|
|
if (this.start != start || this.start != wantedStartMillis) { |
|
//in case of end is milestone it never changes! |
|
//if (!ignoreMilestones && this.endIsMilestone && end != this.end) { |
|
// this.master.setErrorOnTransaction("\"" + this.name + "\"\n" + GanttMaster.messages["END_IS_MILESTONE"], this); |
|
// return false; |
|
//} |
|
this.start = start; |
|
this.end = end; |
|
//profiler.stop(); |
|
|
|
//check global boundaries |
|
if (this.start < this.master.minEditableDate || this.end > this.master.maxEditableDate) { |
|
this.master.setErrorOnTransaction("\"" + this.name + "\"\n" +GanttMaster.messages["CHANGE_OUT_OF_SCOPE"], this); |
|
return false; |
|
} |
|
|
|
|
|
// bicch 22/4/2016: quando si sposta un task con child a cavallo di holidays, i figli devono essere shiftati in workingDays, non in millisecondi, altrimenti si cambiano le durate |
|
// when moving children you MUST consider WORKING days, |
|
var panDeltaInWM = getDistanceInUnits(new Date(originalPeriod.start),new Date(this.start)); |
|
|
|
//loops children to shift them |
|
var children = this.getChildren(); |
|
for (var i = 0; i < children.length; i++) { |
|
var ch = children[i]; |
|
var chStart=incrementDateByUnits(new Date(ch.start),panDeltaInWM); |
|
ch.moveTo(chStart,false,false); |
|
} |
|
|
|
if (!updateTree(this)) { |
|
return false; |
|
} |
|
|
|
if (propagateToInferiors) { |
|
this.propagateToInferiors(end); |
|
var todoOk = true; |
|
var descendants = this.getDescendant(); |
|
for (var i = 0; i < descendants.length; i++) { |
|
ch = descendants[i]; |
|
if (!ch.propagateToInferiors(ch.end)) |
|
return false; |
|
} |
|
} |
|
} |
|
|
|
return true; |
|
}; |
|
|
|
|
|
Task.prototype.checkMilestonesConstraints = function (newStart,newEnd,ignoreMilestones) { |
|
|
|
//if start is milestone cannot be move |
|
if (!ignoreMilestones && (this.startIsMilestone && newStart != this.start )) { |
|
//notify error |
|
this.master.setErrorOnTransaction("\"" + this.name + "\"\n" + GanttMaster.messages["START_IS_MILESTONE"], this); |
|
return false; |
|
} else if (!ignoreMilestones && (this.endIsMilestone && newEnd != this.end)) { |
|
//notify error |
|
this.master.setErrorOnTransaction("\"" + this.name + "\"\n" + GanttMaster.messages["END_IS_MILESTONE"], this); |
|
return false; |
|
} else if (this.hasExternalDep) { |
|
//notify error |
|
this.master.setErrorOnTransaction("\"" + this.name + "\"\n" + GanttMaster.messages["TASK_HAS_EXTERNAL_DEPS"], this); |
|
return false; |
|
} |
|
return true; |
|
}; |
|
|
|
//<%---------- PROPAGATE TO INFERIORS ---------------------- --%> |
|
Task.prototype.propagateToInferiors = function (end) { |
|
//console.debug("propagateToInferiors "+this.name) |
|
//and now propagate to inferiors |
|
var todoOk = true; |
|
var infs = this.getInferiors(); |
|
if (infs && infs.length > 0) { |
|
for (var i = 0; i < infs.length; i++) { |
|
var link = infs[i]; |
|
if (!link.to.canWrite) { |
|
this.master.setErrorOnTransaction(GanttMaster.messages["CANNOT_WRITE"] + "\n\"" + link.to.name + "\"", link.to); |
|
break; |
|
} |
|
todoOk = link.to.moveTo(end, false,true); //this is not the right date but moveTo checks start |
|
if (!todoOk) |
|
break; |
|
} |
|
} |
|
return todoOk; |
|
}; |
|
|
|
|
|
//<%---------- COMPUTE START BY SUPERIORS ---------------------- --%> |
|
Task.prototype.computeStartBySuperiors = function (proposedStart) { |
|
//if depends -> start is set to max end + lag of superior |
|
var supEnd=proposedStart; |
|
var sups = this.getSuperiors(); |
|
if (sups && sups.length > 0) { |
|
supEnd=0; |
|
for (var i = 0; i < sups.length; i++) { |
|
var link = sups[i]; |
|
supEnd = Math.max(supEnd, incrementDateByUnits(new Date(link.from.end), link.lag)); |
|
} |
|
supEnd+=1; |
|
} |
|
return computeStart(supEnd); |
|
}; |
|
|
|
|
|
function updateTree(task) { |
|
//console.debug("updateTree ",task.code,task.name, new Date(task.start), new Date(task.end)); |
|
var error; |
|
|
|
//try to enlarge parent |
|
var p = task.getParent(); |
|
|
|
//no parent:exit |
|
if (!p) |
|
return true; |
|
|
|
var newStart; |
|
var newEnd; |
|
|
|
//id shrink start and end are computed on children boundaries |
|
if (task.master.shrinkParent) { |
|
var chPeriod= p.getChildrenBoudaries(); |
|
newStart = chPeriod.start; |
|
newEnd = chPeriod.end; |
|
} else { |
|
newStart = p.start; |
|
newEnd = p.end; |
|
|
|
if (p.start > task.start) { |
|
newStart = task.start; |
|
} |
|
if (p.end < task.end) { |
|
newEnd = task.end; |
|
} |
|
} |
|
|
|
if (p.start!=newStart) { |
|
if (p.startIsMilestone) { |
|
task.master.setErrorOnTransaction("\"" + p.name + "\"\n" + GanttMaster.messages["START_IS_MILESTONE"], task); |
|
return false; |
|
} else if (p.depends) { |
|
task.master.setErrorOnTransaction("\"" + p.name + "\"\n" + GanttMaster.messages["TASK_HAS_CONSTRAINTS"], task); |
|
return false; |
|
} |
|
} |
|
if (p.end!=newEnd) { |
|
if (p.endIsMilestone) { |
|
task.master.setErrorOnTransaction("\"" + p.name + "\"\n" + GanttMaster.messages["END_IS_MILESTONE"], task); |
|
return false; |
|
} |
|
} |
|
|
|
|
|
//propagate updates if needed |
|
if (newStart != p.start || newEnd != p.end) { |
|
|
|
//can write? |
|
if (!p.canWrite) { |
|
task.master.setErrorOnTransaction(GanttMaster.messages["CANNOT_WRITE"] + "\n" + p.name, task); |
|
return false; |
|
} |
|
|
|
//has external deps ? |
|
if (p.hasExternalDep) { |
|
task.master.setErrorOnTransaction(GanttMaster.messages["TASK_HAS_EXTERNAL_DEPS"] + "\n\"" + p.name + "\"", task); |
|
return false; |
|
} |
|
|
|
return p.setPeriod(newStart, newEnd); |
|
} |
|
|
|
return true; |
|
} |
|
|
|
|
|
Task.prototype.getChildrenBoudaries = function () { |
|
var newStart = Infinity; |
|
var newEnd = -Infinity; |
|
var children = this.getChildren(); |
|
for (var i = 0; i < children.length; i++) { |
|
var ch = children[i]; |
|
newStart = Math.min(newStart, ch.start); |
|
newEnd = Math.max(newEnd, ch.end); |
|
} |
|
return({start:newStart,end:newEnd}) |
|
} |
|
|
|
//<%---------- CHANGE STATUS ---------------------- --%> |
|
Task.prototype.changeStatus = function (newStatus,forceStatusCheck) { |
|
//console.debug("changeStatus: "+this.name+" from "+this.status+" -> "+newStatus); |
|
|
|
var cone = this.getDescendant(); |
|
|
|
function propagateStatus(task, newStatus, manuallyChanged, propagateFromParent, propagateFromChildren) { |
|
//console.debug("propagateStatus",task.name, task.status,newStatus, manuallyChanged, propagateFromParent, propagateFromChildren); |
|
var oldStatus = task.status; |
|
|
|
//no changes exit |
|
if (newStatus == oldStatus && !forceStatusCheck) { |
|
return true; |
|
} |
|
|
|
var todoOk = true; |
|
task.status = newStatus; |
|
|
|
|
|
//xxxx -> STATUS_DONE may activate dependent tasks, both suspended and undefined. Will set to done all children. |
|
//STATUS_FAILED -> STATUS_DONE do nothing if not forced by hand |
|
if (newStatus == "STATUS_DONE") { |
|
|
|
// cannot close task if open issues |
|
if (task.master.permissions.cannotCloseTaskIfIssueOpen && task.openIssues > 0) { |
|
task.master.setErrorOnTransaction(GanttMaster.messages["CANNOT_CLOSE_TASK_IF_OPEN_ISSUE"] + " \"" + task.name + "\""); |
|
return false; |
|
} |
|
|
|
|
|
if ((manuallyChanged || oldStatus != "STATUS_FAILED")) { //cannot set failed task as closed for cascade - only if changed manually |
|
|
|
//can be closed only if superiors are already done |
|
var sups = task.getSuperiors(); |
|
for (var i = 0; i < sups.length; i++) { |
|
if (sups[i].from.status != "STATUS_DONE" && cone.indexOf(sups[i].from)<0) { // è un errore se un predecessore è non chiuso ed è fuori dal cono |
|
if (manuallyChanged || propagateFromParent) //genere un errore bloccante se è cambiato a mano o se il cambiamento arriva dal parent ed ho una dipendenza fuori dal cono (altrimenti avrei un attivo figlio di un chiuso |
|
task.master.setErrorOnTransaction(GanttMaster.messages["GANTT_ERROR_DEPENDS_ON_OPEN_TASK"] + "\n\"" + sups[i].from.name + "\" -> \"" + task.name + "\""); |
|
todoOk = false; |
|
break; |
|
} |
|
} |
|
|
|
if (todoOk) { |
|
// set progress to 100% if needed by settings |
|
if (task.master.set100OnClose && !task.progressByWorklog ){ |
|
task.progress=100; |
|
} |
|
|
|
//set children as done |
|
propagateStatusToChildren(task,newStatus,false); |
|
|
|
//set inferiors as active |
|
propagateStatusToInferiors( task.getInferiors(), "STATUS_ACTIVE"); |
|
} |
|
} else { // una propagazione tenta di chiudere un task fallito |
|
todoOk = false; |
|
} |
|
|
|
|
|
// STATUS_UNDEFINED -> STATUS_ACTIVE all children become active, if they have no dependencies. |
|
// STATUS_SUSPENDED -> STATUS_ACTIVE sets to active all children and their descendants that have no inhibiting dependencies. |
|
// STATUS_WAITING -> STATUS_ACTIVE sets to active all children and their descendants that have no inhibiting dependencies. |
|
// STATUS_DONE -> STATUS_ACTIVE all those that have dependencies must be set to suspended. |
|
// STATUS_FAILED -> STATUS_ACTIVE nothing happens: child statuses must be reset by hand. |
|
} else if (newStatus == "STATUS_ACTIVE") { |
|
|
|
if (manuallyChanged || (oldStatus != "STATUS_FAILED" && oldStatus != "STATUS_SUSPENDED")) { //cannot set failed or suspended task as active for cascade - only if changed manually |
|
|
|
//can be active only if superiors are already done, not only on this task, but also on ancestors superiors |
|
var sups = task.getSuperiors(); |
|
|
|
for (var i = 0; i < sups.length; i++) { |
|
if (sups[i].from.status != "STATUS_DONE") { |
|
if (manuallyChanged || propagateFromChildren) |
|
task.master.setErrorOnTransaction(GanttMaster.messages["GANTT_ERROR_DEPENDS_ON_OPEN_TASK"] + "\n\"" + sups[i].from.name + "\" -> \"" + task.name + "\""); |
|
todoOk = false; |
|
break; |
|
} |
|
} |
|
|
|
// check if parent is already active |
|
if (todoOk) { |
|
var par = task.getParent(); |
|
if (par && par.status != "STATUS_ACTIVE") { |
|
// todoOk = propagateStatus(par, "STATUS_ACTIVE", false, false, true); //todo abbiamo deciso di non far propagare lo status verso l'alto |
|
todoOk = false; |
|
} |
|
} |
|
|
|
|
|
if (todoOk) { |
|
if (oldStatus == "STATUS_UNDEFINED" || oldStatus == "STATUS_SUSPENDED" || oldStatus == "STATUS_WAITING" ) { |
|
//set children as active |
|
propagateStatusToChildren(task,newStatus,true); |
|
} |
|
|
|
//set inferiors as suspended |
|
//propagateStatusToInferiors( task.getInferiors(), "STATUS_SUSPENDED"); |
|
propagateStatusToInferiors( task.getInferiors(), "STATUS_WAITING"); |
|
} |
|
} else { |
|
todoOk = false; |
|
} |
|
|
|
// xxxx -> STATUS_WAITING all active children and their active descendants become waiting. when not failed or forced |
|
} else if (newStatus == "STATUS_WAITING" ) { |
|
if (manuallyChanged || oldStatus != "STATUS_FAILED") { //cannot set failed task as waiting for cascade - only if changed manually |
|
|
|
//check if parent if not active |
|
var par = task.getParent(); |
|
if (par && (par.status != "STATUS_ACTIVE" && par.status != "STATUS_SUSPENDED" && par.status != "STATUS_WAITING")) { |
|
todoOk = false; |
|
} |
|
|
|
|
|
if (todoOk) { |
|
//set children as STATUS_WAITING |
|
propagateStatusToChildren(task, "STATUS_WAITING", true); |
|
|
|
//set inferiors as STATUS_WAITING |
|
propagateStatusToInferiors( task.getInferiors(), "STATUS_WAITING"); |
|
} |
|
} else { |
|
todoOk = false; |
|
} |
|
|
|
// xxxx -> STATUS_SUSPENDED all active children and their active descendants become suspended. when not failed or forced |
|
} else if (newStatus == "STATUS_SUSPENDED" ) { |
|
if (manuallyChanged || oldStatus != "STATUS_FAILED") { //cannot set failed task as closed for cascade - only if changed manually |
|
|
|
//check if parent if not active |
|
var par = task.getParent(); |
|
if (par && (par.status != "STATUS_ACTIVE" && par.status != "STATUS_SUSPENDED" && par.status != "STATUS_WAITING")) { |
|
todoOk = false; |
|
} |
|
|
|
|
|
if (todoOk) { |
|
//set children as STATUS_SUSPENDED |
|
propagateStatusToChildren(task, "STATUS_SUSPENDED", true); |
|
|
|
//set inferiors as STATUS_SUSPENDED |
|
propagateStatusToInferiors( task.getInferiors(), "STATUS_SUSPENDED"); |
|
} |
|
} else { |
|
todoOk = false; |
|
} |
|
|
|
// xxxx -> STATUS_FAILED children and dependent failed |
|
// xxxx -> STATUS_UNDEFINED children and dependant become undefined. |
|
} else if (newStatus == "STATUS_FAILED" || newStatus == "STATUS_UNDEFINED") { |
|
|
|
//set children as failed or undefined |
|
propagateStatusToChildren(task,newStatus,false); |
|
|
|
//set inferiors as failed |
|
propagateStatusToInferiors( task.getInferiors(), newStatus); |
|
} |
|
if (!todoOk) { |
|
task.status = oldStatus; |
|
//console.debug("status rolled back: "+task.name + " to " + oldStatus); |
|
} |
|
|
|
return todoOk; |
|
} |
|
|
|
/** |
|
* A helper method to traverse an array of 'inferior' tasks |
|
* and signal a status change. |
|
*/ |
|
function propagateStatusToInferiors( infs, status) { |
|
for (var i = 0; i < infs.length; i++) { |
|
propagateStatus(infs[i].to, status, false, false, false); |
|
} |
|
} |
|
|
|
/** |
|
* A helper method to loop children and propagate new status |
|
*/ |
|
function propagateStatusToChildren(task, newStatus, skipClosedTasks) { |
|
var chds = task.getChildren(); |
|
for (var i = 0; i < chds.length; i++) |
|
if (!(skipClosedTasks && chds[i].status == "STATUS_DONE") ) |
|
propagateStatus(chds[i], newStatus, false, true, false); |
|
} |
|
|
|
|
|
var manuallyChanged=true; |
|
|
|
var oldStatus = this.status; |
|
//first call |
|
if (propagateStatus(this, newStatus, manuallyChanged, false, false)) { |
|
return true; |
|
} else { |
|
this.status = oldStatus; |
|
return false; |
|
} |
|
}; |
|
|
|
Task.prototype.synchronizeStatus = function () { |
|
//console.debug("synchronizeStatus",this.name); |
|
var oldS = this.status; |
|
this.status = this.getParent()?this.getParent().status:"STATUS_UNDEFINED"; // di default si invalida lo stato mettendo quello del padre, in modo che inde/outd siano consistenti |
|
return this.changeStatus(oldS,true); |
|
}; |
|
|
|
Task.prototype.isLocallyBlockedByDependencies = function () { |
|
var sups = this.getSuperiors(); |
|
var blocked = false; |
|
for (var i = 0; i < sups.length; i++) { |
|
if (sups[i].from.status != "STATUS_DONE") { |
|
blocked = true; |
|
break; |
|
} |
|
} |
|
return blocked; |
|
}; |
|
|
|
//<%---------- TASK STRUCTURE ---------------------- --%> |
|
Task.prototype.getRow = function () { |
|
ret = -1; |
|
if (this.master) |
|
ret = this.master.tasks.indexOf(this); |
|
return ret; |
|
}; |
|
|
|
|
|
Task.prototype.getParents = function () { |
|
var ret; |
|
if (this.master) { |
|
var topLevel = this.level; |
|
var pos = this.getRow(); |
|
ret = []; |
|
for (var i = pos; i >= 0; i--) { |
|
var par = this.master.tasks[i]; |
|
if (topLevel > par.level) { |
|
topLevel = par.level; |
|
ret.push(par); |
|
} |
|
} |
|
} |
|
return ret; |
|
}; |
|
|
|
|
|
Task.prototype.getParent = function () { |
|
var ret; |
|
if (this.master) { |
|
for (var i = this.getRow(); i >= 0; i--) { |
|
var par = this.master.tasks[i]; |
|
if (this.level > par.level) { |
|
ret = par; |
|
break; |
|
} |
|
} |
|
} |
|
return ret; |
|
}; |
|
|
|
|
|
Task.prototype.isParent = function () { |
|
var ret = false; |
|
if (this.master) { |
|
var pos = this.getRow(); |
|
if (pos < this.master.tasks.length - 1) |
|
ret = this.master.tasks[pos + 1].level > this.level; |
|
} |
|
return ret; |
|
}; |
|
|
|
|
|
Task.prototype.getChildren = function () { |
|
var ret = []; |
|
if (this.master) { |
|
var pos = this.getRow(); |
|
for (var i = pos + 1; i < this.master.tasks.length; i++) { |
|
var ch = this.master.tasks[i]; |
|
if (ch.level == this.level + 1) |
|
ret.push(ch); |
|
else if (ch.level <= this.level) // exit loop if parent or brother |
|
break; |
|
} |
|
} |
|
return ret; |
|
}; |
|
|
|
|
|
Task.prototype.getDescendant = function () { |
|
var ret = []; |
|
if (this.master) { |
|
var pos = this.getRow(); |
|
for (var i = pos + 1; i < this.master.tasks.length; i++) { |
|
var ch = this.master.tasks[i]; |
|
if (ch.level > this.level) |
|
ret.push(ch); |
|
else |
|
break; |
|
} |
|
} |
|
return ret; |
|
}; |
|
|
|
|
|
Task.prototype.getSuperiors = function () { |
|
var ret = []; |
|
var task = this; |
|
if (this.master) { |
|
ret = this.master.links.filter(function (link) { |
|
return link.to == task; |
|
}); |
|
} |
|
return ret; |
|
}; |
|
|
|
Task.prototype.getSuperiorTasks = function () { |
|
var ret = []; |
|
var sups = this.getSuperiors(); |
|
for (var i = 0; i < sups.length; i++) |
|
ret.push(sups[i].from); |
|
return ret; |
|
}; |
|
|
|
|
|
Task.prototype.getInferiors = function () { |
|
var ret = []; |
|
var task = this; |
|
if (this.master) { |
|
ret = this.master.links.filter(function (link) { |
|
return link.from == task; |
|
}); |
|
} |
|
return ret; |
|
}; |
|
|
|
Task.prototype.getInferiorTasks = function () { |
|
var ret = []; |
|
var infs = this.getInferiors(); |
|
for (var i = 0; i < infs.length; i++) |
|
ret.push(infs[i].to); |
|
return ret; |
|
}; |
|
|
|
Task.prototype.deleteTask = function () { |
|
//console.debug("deleteTask",this.name,this.master.deletedTaskIds) |
|
//if is the current one remove it |
|
if (this.master.currentTask && this.master.currentTask.id==this.id) |
|
delete this.master.currentTask; |
|
|
|
//delete both dom elements if exists |
|
if (this.rowElement) |
|
this.rowElement.remove(); |
|
if (this.ganttElement) |
|
this.ganttElement.remove(); |
|
|
|
//remove children |
|
var chd = this.getChildren(); |
|
for (var i = 0; i < chd.length; i++) { |
|
//add removed child in list |
|
chd[i].deleteTask(); |
|
} |
|
|
|
if (!this.isNew()) |
|
this.master.deletedTaskIds.push(this.id); |
|
|
|
|
|
//remove from in-memory collection |
|
this.master.tasks.splice(this.getRow(), 1); |
|
|
|
//remove from links |
|
var task = this; |
|
this.master.links = this.master.links.filter(function (link) { |
|
return link.from != task && link.to != task; |
|
}); |
|
}; |
|
|
|
|
|
Task.prototype.isNew = function () { |
|
return (this.id + "").indexOf("tmp_") == 0; |
|
}; |
|
|
|
Task.prototype.isDependent = function (t) { |
|
//console.debug("isDependent",this.name, t.name) |
|
var task = this; |
|
var dep = this.master.links.filter(function (link) { |
|
return link.from == task; |
|
}); |
|
|
|
// is t a direct dependency? |
|
for (var i = 0; i < dep.length; i++) { |
|
if (dep[i].to == t) |
|
return true; |
|
} |
|
// is t an indirect dependency |
|
for (var i = 0; i < dep.length; i++) { |
|
if (dep[i].to.isDependent(t)) { |
|
return true; |
|
} |
|
} |
|
return false; |
|
}; |
|
|
|
Task.prototype.setLatest = function (maxCost) { |
|
this.latestStart = maxCost - this.criticalCost; |
|
this.latestFinish = this.latestStart + this.duration; |
|
}; |
|
|
|
|
|
//<%------------------------------------------ INDENT/OUTDENT --------------------------------%> |
|
Task.prototype.indent = function () { |
|
//console.debug("indent", this); |
|
//a row above must exist |
|
var row = this.getRow(); |
|
|
|
//no row no party |
|
if (row <= 0) |
|
return false; |
|
|
|
var ret = false; |
|
var taskAbove = this.master.tasks[row - 1]; |
|
var newLev = this.level + 1; |
|
if (newLev <= taskAbove.level + 1) { |
|
ret = true; |
|
|
|
//trick to get parents after indent |
|
this.level++; |
|
var futureParents = this.getParents(); |
|
this.level--; |
|
|
|
var oldLevel = this.level; |
|
for (var i = row; i < this.master.tasks.length; i++) { |
|
var desc = this.master.tasks[i]; |
|
if (desc.level > oldLevel || desc == this) { |
|
desc.level++; |
|
//remove links from this and descendant to my parents |
|
this.master.links = this.master.links.filter(function (link) { |
|
var linkToParent = false; |
|
if (link.to == desc) |
|
linkToParent = futureParents.indexOf(link.from) >= 0; |
|
else if (link.from == desc) |
|
linkToParent = futureParents.indexOf(link.to) >= 0; |
|
return !linkToParent; |
|
}); |
|
//remove links from this and descendants to predecessors of parents in order to avoid loop |
|
var predecessorsOfFutureParents=[]; |
|
for (var j=0;j<futureParents.length;j++) |
|
predecessorsOfFutureParents=predecessorsOfFutureParents.concat(futureParents[j].getSuperiorTasks()); |
|
|
|
this.master.links = this.master.links.filter(function (link) { |
|
var linkToParent = false; |
|
if (link.from == desc) |
|
linkToParent = predecessorsOfFutureParents.indexOf(link.to) >= 0; |
|
return !linkToParent; |
|
}); |
|
|
|
|
|
} else |
|
break; |
|
} |
|
|
|
var parent = this.getParent(); |
|
// set start date to parent' start if no deps |
|
if (parent && !this.depends) { |
|
var new_end = computeEndByDuration(parent.start, this.duration); |
|
this.master.changeTaskDates(this, parent.start, new_end); |
|
} |
|
|
|
|
|
//recompute depends string |
|
this.master.updateDependsStrings(); |
|
//enlarge parent using a fake set period |
|
updateTree(this); |
|
//force status check starting from parent |
|
this.getParent().synchronizeStatus(); |
|
} |
|
return ret; |
|
}; |
|
|
|
|
|
Task.prototype.outdent = function () { |
|
//console.debug("outdent", this); |
|
|
|
//a level must be >1 -> cannot escape from root |
|
if (this.level <= 1) |
|
return false; |
|
|
|
var ret = false; |
|
var oldLevel = this.level; |
|
|
|
ret = true; |
|
var row = this.getRow(); |
|
for (var i = row; i < this.master.tasks.length; i++) { |
|
var desc = this.master.tasks[i]; |
|
if (desc.level > oldLevel || desc == this) { |
|
desc.level--; |
|
} else |
|
break; |
|
} |
|
|
|
var task = this; |
|
var chds = this.getChildren(); |
|
//remove links from me to my new children |
|
this.master.links = this.master.links.filter(function (link) { |
|
var linkExist = (link.to == task && chds.indexOf(link.from) >= 0 || link.from == task && chds.indexOf(link.to) >= 0); |
|
return !linkExist; |
|
}); |
|
|
|
|
|
//enlarge me if inherited children are larger |
|
for (var i = 0; i < chds.length; i++) { |
|
//remove links from me to my new children |
|
chds[i].setPeriod(chds[i].start + 1, chds[i].end + 1); |
|
} |
|
|
|
//recompute depends string |
|
this.master.updateDependsStrings(); |
|
|
|
//enlarge parent using a fake set period |
|
this.setPeriod(this.start + 1, this.end + 1); |
|
|
|
//force status check |
|
this.synchronizeStatus(); |
|
return ret; |
|
}; |
|
|
|
|
|
//<%------------------------------------------ MOVE UP / MOVE DOWN --------------------------------%> |
|
Task.prototype.moveUp = function () { |
|
//console.debug("moveUp", this); |
|
var ret = false; |
|
|
|
//a row above must exist |
|
var row = this.getRow(); |
|
|
|
//no row no party |
|
if (row <= 0) |
|
return false; |
|
|
|
//find new row |
|
var newRow; |
|
for (newRow = row - 1; newRow >= 0; newRow--) { |
|
if (this.master.tasks[newRow].level <= this.level) |
|
break; |
|
} |
|
|
|
//is a parent or a brother |
|
if (this.master.tasks[newRow].level == this.level) { |
|
ret = true; |
|
//compute descendant |
|
var descNumber = 0; |
|
for (var i = row + 1; i < this.master.tasks.length; i++) { |
|
var desc = this.master.tasks[i]; |
|
if (desc.level > this.level) { |
|
descNumber++; |
|
} else { |
|
break; |
|
} |
|
} |
|
//move in memory |
|
var blockToMove = this.master.tasks.splice(row, descNumber + 1); |
|
var top = this.master.tasks.splice(0, newRow); |
|
this.master.tasks = [].concat(top, blockToMove, this.master.tasks); |
|
//move on dom |
|
var rows = this.master.editor.element.find("tr[taskid]"); |
|
var domBlockToMove = rows.slice(row, row + descNumber + 1); |
|
rows.eq(newRow).before(domBlockToMove); |
|
|
|
//recompute depends string |
|
this.master.updateDependsStrings(); |
|
} else { |
|
this.master.setErrorOnTransaction(GanttMaster.messages["TASK_MOVE_INCONSISTENT_LEVEL"], this); |
|
ret = false; |
|
} |
|
return ret; |
|
}; |
|
|
|
|
|
Task.prototype.moveDown = function () { |
|
//console.debug("moveDown", this); |
|
|
|
//a row below must exist, and cannot move root task |
|
var row = this.getRow(); |
|
if (row >= this.master.tasks.length - 1 || row == 0) |
|
return false; |
|
|
|
var ret = false; |
|
|
|
//find nearest brother |
|
var newRow; |
|
for (newRow = row + 1; newRow < this.master.tasks.length; newRow++) { |
|
if (this.master.tasks[newRow].level <= this.level) |
|
break; |
|
} |
|
|
|
//is brother |
|
if (this.master.tasks[newRow] && this.master.tasks[newRow].level == this.level) { |
|
ret = true; |
|
//find last desc |
|
for (newRow = newRow + 1; newRow < this.master.tasks.length; newRow++) { |
|
if (this.master.tasks[newRow].level <= this.level) |
|
break; |
|
} |
|
|
|
//compute descendant |
|
var descNumber = 0; |
|
for (var i = row + 1; i < this.master.tasks.length; i++) { |
|
var desc = this.master.tasks[i]; |
|
if (desc.level > this.level) { |
|
descNumber++; |
|
} else { |
|
break; |
|
} |
|
} |
|
|
|
//move in memory |
|
var blockToMove = this.master.tasks.splice(row, descNumber + 1); |
|
var top = this.master.tasks.splice(0, newRow - descNumber - 1); |
|
this.master.tasks = [].concat(top, blockToMove, this.master.tasks); |
|
|
|
|
|
//move on dom |
|
var rows = this.master.editor.element.find("tr[taskid]"); |
|
var aft = rows.eq(newRow - 1); |
|
var domBlockToMove = rows.slice(row, row + descNumber + 1); |
|
aft.after(domBlockToMove); |
|
|
|
//recompute depends string |
|
this.master.updateDependsStrings(); |
|
} |
|
|
|
return ret; |
|
}; |
|
|
|
|
|
Task.prototype.canStatusBeChangedTo=function(newStatus) { |
|
//lo stato corrente è sempre ok |
|
if (newStatus==this.status) |
|
return true; |
|
|
|
var parent=this.getParent(); |
|
|
|
//---------------------------------------------------------------------- STATUS_DONE ---------------------------------------------------------------- |
|
// un task può essere STATUS_DONE se di root |
|
// se il suo padre non è fallito o undefined |
|
// se non ha previouses aperti |
|
if ("STATUS_DONE"==newStatus) { |
|
if (!parent ) |
|
return true; |
|
|
|
if ("STATUS_FAILED"==parent.status || "STATUS_UNDEFINED"==parent.status) |
|
return false; |
|
|
|
var sups=this.getSuperiorTasks(); |
|
for (var i=0;i<sups.length;i++) { |
|
if ("STATUS_DONE"!=sups[i].status) { // è un errore se un predecessore non è chiuso |
|
return false; |
|
} |
|
} |
|
return true; |
|
|
|
|
|
//---------------------------------------------------------------------- STATUS_ACTIVE ---------------------------------------------------------------- |
|
//un task può essere STATUS_ACTIVE se l'eventuale padre è active e se tutti i predecessori sono in STATUS_DONE |
|
} else if ("STATUS_ACTIVE"==newStatus) { |
|
if (!parent ) |
|
return true; |
|
|
|
if (!"STATUS_ACTIVE"==parent.status) |
|
return false; |
|
|
|
var sups=this.getSuperiorTasks(); |
|
for (var i=0;i<sups.length;i++) { |
|
if ("STATUS_DONE"!=sups[i].status) { // è un errore se un predecessore non è chiuso |
|
return false; |
|
} |
|
} |
|
return true; |
|
|
|
|
|
//---------------------------------------------------------------------- STATUS_WAITING ---------------------------------------------------------------- |
|
//un task può essere STATUS_WAITING solo se ha il padre o un predecessore STATUS_WAITING || un predecessore active STATUS_ACTIVE |
|
} else if ("STATUS_WAITING"==newStatus) { |
|
//un task può essere STATUS_WAITING solo se ha il padre |
|
if (!parent ) |
|
return false; |
|
|
|
if ("STATUS_FAILED"==parent.status || "STATUS_UNDEFINED"==parent.status) |
|
return false; |
|
|
|
|
|
if ("STATUS_WAITING"==parent.status) |
|
return true; |
|
|
|
var sups=this.getSuperiorTasks(); |
|
for (var i=0;i<sups.length;i++) { |
|
if ("STATUS_WAITING"==sups[i].status || "STATUS_ACTIVE"==sups[i].status) { |
|
return true; |
|
} |
|
} |
|
return false; |
|
|
|
|
|
//---------------------------------------------------------------------- STATUS_SUSPENDED ---------------------------------------------------------------- |
|
//un task può essere is STATUS_SUSPENDED (a mano) se di root |
|
// se il parent è STATUS_ACTIVE o STATUS_SUSPENDED e se non |
|
} else if ("STATUS_SUSPENDED"==newStatus) { |
|
if (!parent ) |
|
return true; |
|
|
|
if ("STATUS_UNDEFINED"==parent.status || "STATUS_FAILED"==parent.status) |
|
return false; |
|
|
|
return true; |
|
|
|
//---------------------------------------------------------------------- STATUS_FAILED ---------------------------------------------------------------- |
|
} else if ("STATUS_FAILED"==newStatus) { |
|
//può essere in STATUS_FAILED sempre |
|
return true; |
|
|
|
//---------------------------------------------------------------------- STATUS_UNDEFINED ---------------------------------------------------------------- |
|
} else if ("STATUS_UNDEFINED"==newStatus) { |
|
//può essere in STATUS_UNDEFINED sempre |
|
return true; |
|
} |
|
|
|
return false; |
|
}; |
|
|
|
|
|
//<%------------------------------------------------------------------------ LINKS OBJECT ---------------------------------------------------------------%> |
|
function Link(taskFrom, taskTo, lagInWorkingDays) { |
|
this.from = taskFrom; |
|
this.to = taskTo; |
|
this.lag = lagInWorkingDays; |
|
} |
|
|
|
|
|
//<%------------------------------------------------------------------------ ASSIGNMENT ---------------------------------------------------------------%> |
|
function Assignment(id, resourceId, roleId, effort) { |
|
this.id = id; |
|
this.resourceId = resourceId; |
|
this.roleId = roleId; |
|
this.effort = effort; |
|
} |
|
|
|
|
|
//<%------------------------------------------------------------------------ RESOURCE ---------------------------------------------------------------%> |
|
function Resource(id, name) { |
|
this.id = id; |
|
this.name = name; |
|
} |
|
|
|
|
|
//<%------------------------------------------------------------------------ ROLE ---------------------------------------------------------------%> |
|
function Role(id, name) { |
|
this.id = id; |
|
this.name = name; |
|
} |
|
|
|
|
|
|
|
|
|
|