Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.
 
 
 
 
 
 

1091 lignes
32 KiB

  1. /*
  2. Copyright (c) 2012-2017 Open Lab
  3. Written by Roberto Bicchierai and Silvia Chelazzi http://roberto.open-lab.com
  4. Permission is hereby granted, free of charge, to any person obtaining
  5. a copy of this software and associated documentation files (the
  6. "Software"), to deal in the Software without restriction, including
  7. without limitation the rights to use, copy, modify, merge, publish,
  8. distribute, sublicense, and/or sell copies of the Software, and to
  9. permit persons to whom the Software is furnished to do so, subject to
  10. the following conditions:
  11. The above copyright notice and this permission notice shall be
  12. included in all copies or substantial portions of the Software.
  13. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  14. EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  15. MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  16. NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  17. LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  18. OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  19. WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  20. */
  21. /**
  22. * A method to instantiate valid task models from
  23. * raw data.
  24. */
  25. function TaskFactory() {
  26. /**
  27. * Build a new Task
  28. */
  29. this.build = function (id, name, code, level, start, duration, collapsed) {
  30. // Set at beginning of day
  31. var adjusted_start = computeStart(start);
  32. var calculated_end = computeEndByDuration(adjusted_start, duration);
  33. return new Task(id, name, code, level, adjusted_start, calculated_end, duration, collapsed);
  34. };
  35. }
  36. function Task(id, name, code, level, start, end, duration, collapsed) {
  37. this.id = id;
  38. this.name = name;
  39. this.progress = 0;
  40. this.progressByWorklog = false;
  41. this.relevance = 0;
  42. this.type = "";
  43. this.typeId = "";
  44. this.description = "";
  45. this.code = code;
  46. this.level = level;
  47. this.status = "STATUS_UNDEFINED";
  48. this.depends = "";
  49. this.canWrite = true; // by default all tasks are writeable
  50. this.start = start;
  51. this.duration = duration;
  52. this.end = end;
  53. this.startIsMilestone = false;
  54. this.endIsMilestone = false;
  55. this.collapsed = collapsed;
  56. this.rowElement; //row editor html element
  57. this.ganttElement; //gantt html element
  58. this.master;
  59. this.assigs = [];
  60. }
  61. Task.prototype.clone = function () {
  62. var ret = {};
  63. for (var key in this) {
  64. if (typeof(this[key]) != "function") {
  65. ret[key] = this[key];
  66. }
  67. }
  68. return ret;
  69. };
  70. Task.prototype.getAssigsString = function () {
  71. var ret = "";
  72. for (var i = 0; i < this.assigs.length; i++) {
  73. var ass = this.assigs[i];
  74. var res = this.master.getResource(ass.resourceId);
  75. if (res) {
  76. ret = ret + (ret == "" ? "" : ", ") + res.name;
  77. }
  78. }
  79. return ret;
  80. };
  81. Task.prototype.createAssignment = function (id, resourceId, roleId, effort) {
  82. var assig = new Assignment(id, resourceId, roleId, effort);
  83. this.assigs.push(assig);
  84. return assig;
  85. };
  86. //<%---------- SET PERIOD ---------------------- --%>
  87. Task.prototype.setPeriod = function (start, end) {
  88. //console.debug("setPeriod ",this.code,this.name,new Date(start), new Date(end));
  89. //var profilerSetPer = new Profiler("gt_setPeriodJS");
  90. if (start instanceof Date) {
  91. start = start.getTime();
  92. }
  93. if (end instanceof Date) {
  94. end = end.getTime();
  95. }
  96. var originalPeriod = {
  97. start: this.start,
  98. end: this.end,
  99. duration: this.duration
  100. };
  101. //compute legal start/end //todo mossa qui R&S 30/3/2016 perchè altrimenti il calcolo della durata, che è stato modificato sommando giorni, sbaglia
  102. start = computeStart(start);
  103. end=computeEnd(end);
  104. var newDuration = recomputeDuration(start, end);
  105. //if are equals do nothing and return true
  106. if ( start == originalPeriod.start && end == originalPeriod.end && newDuration == originalPeriod.duration) {
  107. //console.debug("Periods are identical!")
  108. return true;
  109. }
  110. if (newDuration == this.duration) { // is shift
  111. return this.moveTo(start, false);
  112. }
  113. //console.debug("setStart",date,date instanceof Date);
  114. var wantedStartMillis = start;
  115. //cannot start after end
  116. if (start > end) {
  117. start = end;
  118. }
  119. //set a legal start
  120. //start = computeStart(start); //todo R&S 30/3/2016 messo in vetta
  121. //if there are dependencies compute the start date and eventually moveTo
  122. var startBySuperiors = this.computeStartBySuperiors(start);
  123. if (startBySuperiors != start) {
  124. return this.moveTo(startBySuperiors, false);
  125. }
  126. var somethingChanged = false;
  127. if (this.start != start || this.start != wantedStartMillis) {
  128. this.start = start;
  129. somethingChanged = true;
  130. }
  131. //set end
  132. var wantedEndMillis = end;
  133. //end = computeEnd(end);//todo R&S 30/3/2016 messo in vetta
  134. if (this.end != end || this.end != wantedEndMillis) {
  135. this.end = end;
  136. somethingChanged = true;
  137. }
  138. this.duration = recomputeDuration(this.start, this.end);
  139. //profilerSetPer.stop();
  140. //nothing changed exit
  141. if (!somethingChanged)
  142. return true;
  143. //cannot write exit
  144. if (!this.canWrite) {
  145. this.master.setErrorOnTransaction("\"" + this.name + "\"\n" + GanttMaster.messages["CANNOT_WRITE"], this);
  146. return false;
  147. }
  148. //external dependencies: exit with error
  149. if (this.hasExternalDep) {
  150. this.master.setErrorOnTransaction("\"" + this.name + "\"\n" + GanttMaster.messages["TASK_HAS_EXTERNAL_DEPS"], this);
  151. return false;
  152. }
  153. var todoOk = true;
  154. //I'm restricting
  155. var deltaPeriod = originalPeriod.duration - this.duration;
  156. var restricting = deltaPeriod > 0;
  157. var restrictingStart = restricting && (originalPeriod.start < this.start);
  158. var restrictingEnd = restricting && (originalPeriod.end > this.end);
  159. //console.debug( " originalPeriod.duration "+ originalPeriod.duration +" deltaPeriod "+deltaPeriod+" "+"restricting "+restricting);
  160. if (restricting) {
  161. //loops children to get boundaries
  162. var children = this.getChildren();
  163. var bs = Infinity;
  164. var be = 0;
  165. for (var i = 0; i < children.length; i++) {
  166. var ch = children[i];
  167. //console.debug("restricting: test child "+ch.name+" "+ch.end)
  168. if (restrictingEnd) {
  169. be = Math.max(be, ch.end);
  170. } else {
  171. bs = Math.min(bs, ch.start);
  172. }
  173. }
  174. if (restrictingEnd) {
  175. //console.debug("restricting end ",be, this.end);
  176. this.end = Math.max(be, this.end);
  177. } else {
  178. //console.debug("restricting start");
  179. this.start = Math.min(bs, this.start);
  180. }
  181. this.duration = recomputeDuration(this.start, this.end);
  182. } else {
  183. //check global boundaries
  184. if (this.start < this.master.minEditableDate || this.end > this.master.maxEditableDate) {
  185. this.master.setErrorOnTransaction("\"" + this.name + "\"\n" +GanttMaster.messages["CHANGE_OUT_OF_SCOPE"], this);
  186. todoOk = false;
  187. }
  188. //console.debug("set period: somethingChanged",this);
  189. if (todoOk && !updateTree(this)) {
  190. todoOk = false;
  191. }
  192. }
  193. if (todoOk) {
  194. todoOk = this.propagateToInferiors(end);
  195. }
  196. return todoOk;
  197. };
  198. //<%---------- MOVE TO ---------------------- --%>
  199. Task.prototype.moveTo = function (start, ignoreMilestones) {
  200. //console.debug("moveTo ",this.code,this.name,new Date(start),this.duration,ignoreMilestones);
  201. //var profiler = new Profiler("gt_task_moveTo");
  202. if (start instanceof Date) {
  203. start = start.getTime();
  204. }
  205. var originalPeriod = {
  206. start: this.start,
  207. end: this.end
  208. };
  209. var wantedStartMillis = start;
  210. //set a legal start
  211. start = computeStart(start);
  212. //if start is milestone cannot be move
  213. if (!ignoreMilestones && this.startIsMilestone && start != this.start) {
  214. //notify error
  215. this.master.setErrorOnTransaction("\"" + this.name + "\"\n" +GanttMaster.messages["START_IS_MILESTONE"], this);
  216. return false;
  217. } else if (this.hasExternalDep) {
  218. //notify error
  219. this.master.setErrorOnTransaction("\"" + this.name + "\"\n" +GanttMaster.messages["TASK_HAS_EXTERNAL_DEPS"], this);
  220. return false;
  221. }
  222. //if depends, start is set to max end + lag of superior
  223. var startBySuperiors = this.computeStartBySuperiors(start);
  224. //todo if there are dependencies the new start,end must be contained into parent dates
  225. /*var parent=this.getParent();
  226. if (start!=startBySuperiors){
  227. var proposedEnd = computeEndByDuration(startBySuperiors, this.duration);
  228. // if outside parent's scoce error
  229. if (parent && (startBySuperiors<parent.start || proposedEnd>parent.end)) {
  230. this.master.setErrorOnTransaction("\"" + this.name + "\"\n" +GanttMaster.messages["CANNOT_MOVE_TASK"], this);
  231. return false;
  232. } else {
  233. start = startBySuperiors;
  234. }
  235. }*/
  236. start = startBySuperiors;
  237. var end = computeEndByDuration(start, this.duration);
  238. if (this.start != start || this.start != wantedStartMillis) {
  239. /*//in case of end is milestone it never changes, but recompute duration
  240. if (!ignoreMilestones && this.endIsMilestone) {
  241. end = this.end;
  242. this.duration = recomputeDuration(start, end);
  243. }*/
  244. //in case of end is milestone it never changes!
  245. if (!ignoreMilestones && this.endIsMilestone && end!=this.end) {
  246. this.master.setErrorOnTransaction("\"" + this.name + "\"\n" +GanttMaster.messages["END_IS_MILESTONE"], this);
  247. return false;
  248. }
  249. this.start = start;
  250. this.end = end;
  251. //profiler.stop();
  252. //check global boundaries
  253. if (this.start < this.master.minEditableDate || this.end > this.master.maxEditableDate) {
  254. this.master.setErrorOnTransaction("\"" + this.name + "\"\n" +GanttMaster.messages["CHANGE_OUT_OF_SCOPE"], this);
  255. return false;
  256. }
  257. // 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
  258. // when moving children you MUST consider WORKING days,
  259. var panDeltaInWD = new Date(originalPeriod.start).distanceInWorkingDays(new Date(this.start));
  260. //loops children to shift them
  261. var children = this.getChildren();
  262. for (var i = 0; i < children.length; i++) {
  263. ch = children[i];
  264. var chStart=new Date(ch.start).incrementDateByWorkingDays(panDeltaInWD);
  265. if (!ch.moveTo(chStart, false)) {
  266. //console.debug("esco")
  267. return false;
  268. }
  269. }
  270. //console.debug("set period: somethingChanged",this);
  271. if (!updateTree(this)) {
  272. return false;
  273. }
  274. return this.propagateToInferiors(end);
  275. }
  276. return true;
  277. };
  278. //<%---------- PROPAGATE TO INFERIORS ---------------------- --%>
  279. Task.prototype.propagateToInferiors = function (end) {
  280. //and now propagate to inferiors
  281. var todoOk = true;
  282. var infs = this.getInferiors();
  283. if (infs && infs.length > 0) {
  284. for (var i = 0; i < infs.length; i++) {
  285. var link = infs[i];
  286. if (!link.to.canWrite) {
  287. this.master.setErrorOnTransaction(GanttMaster.messages["CANNOT_WRITE"] + "\n\"" + link.to.name + "\"", link.to);
  288. break;
  289. }
  290. todoOk = link.to.moveTo(end, false); //this is not the right date but moveTo checks start
  291. if (!todoOk)
  292. break;
  293. }
  294. }
  295. return todoOk;
  296. };
  297. //<%---------- COMPUTE START BY SUPERIORS ---------------------- --%>
  298. Task.prototype.computeStartBySuperiors = function (proposedStart) {
  299. //if depends -> start is set to max end + lag of superior
  300. var supEnd=proposedStart;
  301. var sups = this.getSuperiors();
  302. if (sups && sups.length > 0) {
  303. supEnd=0;
  304. for (var i = 0; i < sups.length; i++) {
  305. var link = sups[i];
  306. supEnd = Math.max(supEnd, incrementDateByWorkingDays(link.from.end, link.lag));
  307. }
  308. supEnd+=1;
  309. }
  310. return computeStart(supEnd);
  311. };
  312. function updateTree(task) {
  313. //console.debug("updateTree ",task.code,task.name);
  314. var error;
  315. //try to enlarge parent
  316. var p = task.getParent();
  317. //no parent:exit
  318. if (!p)
  319. return true;
  320. var newStart = p.start;
  321. var newEnd = p.end;
  322. if (p.start > task.start) {
  323. if (p.startIsMilestone) {
  324. task.master.setErrorOnTransaction("\"" + p.name + "\"\n" + GanttMaster.messages["START_IS_MILESTONE"], task);
  325. return false;
  326. } else if (p.depends) {
  327. task.master.setErrorOnTransaction("\"" + p.name + "\"\n" + GanttMaster.messages["TASK_HAS_CONSTRAINTS"], task);
  328. return false;
  329. }
  330. newStart = task.start;
  331. }
  332. if (p.end < task.end) {
  333. if (p.endIsMilestone) {
  334. task.master.setErrorOnTransaction("\"" + p.name + "\"\n" + GanttMaster.messages["END_IS_MILESTONE"], task);
  335. return false;
  336. }
  337. newEnd = task.end;
  338. }
  339. //propagate updates if needed
  340. if (newStart != p.start || newEnd != p.end) {
  341. //can write?
  342. if (!p.canWrite) {
  343. task.master.setErrorOnTransaction(GanttMaster.messages["CANNOT_WRITE"] + "\n" + p.name, task);
  344. return false;
  345. }
  346. //has external deps ?
  347. if (p.hasExternalDep) {
  348. task.master.setErrorOnTransaction(GanttMaster.messages["TASK_HAS_EXTERNAL_DEPS"] + "\n\"" + p.name + "\"", task);
  349. return false;
  350. }
  351. return p.setPeriod(newStart, newEnd);
  352. }
  353. return true;
  354. }
  355. //<%---------- CHANGE STATUS ---------------------- --%>
  356. Task.prototype.changeStatus = function (newStatus,forceStatusCheck) {
  357. //console.debug("changeStatus: "+this.name+" from "+this.status+" -> "+newStatus);
  358. var cone = this.getDescendant();
  359. function propagateStatus(task, newStatus, manuallyChanged, propagateFromParent, propagateFromChildren) {
  360. //console.debug("propagateStatus",task.name, task.status,newStatus, manuallyChanged, propagateFromParent, propagateFromChildren);
  361. var oldStatus = task.status;
  362. //no changes exit
  363. if (newStatus == oldStatus && !forceStatusCheck) {
  364. return true;
  365. }
  366. var todoOk = true;
  367. task.status = newStatus;
  368. //xxxx -> STATUS_DONE may activate dependent tasks, both suspended and undefined. Will set to done all children.
  369. //STATUS_FAILED -> STATUS_DONE do nothing if not forced by hand
  370. if (newStatus == "STATUS_DONE") {
  371. // cannot close task if open issues
  372. if (task.master.permissions.cannotCloseTaskIfIssueOpen && task.openIssues > 0) {
  373. task.master.setErrorOnTransaction(GanttMaster.messages["CANNOT_CLOSE_TASK_IF_OPEN_ISSUE"] + " \"" + task.name + "\"");
  374. return false;
  375. }
  376. if ((manuallyChanged || oldStatus != "STATUS_FAILED")) { //cannot set failed task as closed for cascade - only if changed manually
  377. //can be closed only if superiors are already done
  378. var sups = task.getSuperiors();
  379. for (var i = 0; i < sups.length; i++) {
  380. if (sups[i].from.status != "STATUS_DONE" && cone.indexOf(sups[i].from)<0) { // è un errore se un predecessore è non chiuso ed è fuori dal cono
  381. 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
  382. task.master.setErrorOnTransaction(GanttMaster.messages["GANTT_ERROR_DEPENDS_ON_OPEN_TASK"] + "\n\"" + sups[i].from.name + "\" -> \"" + task.name + "\"");
  383. todoOk = false;
  384. break;
  385. }
  386. }
  387. if (todoOk) {
  388. // set progress to 100% if needed by settings
  389. if (task.master.set100OnClose && !task.progressByWorklog ){
  390. task.progress=100;
  391. }
  392. //set children as done
  393. propagateStatusToChildren(task,newStatus,false);
  394. //set inferiors as active
  395. propagateStatusToInferiors( task.getInferiors(), "STATUS_ACTIVE");
  396. }
  397. } else { // una propagazione tenta di chiudere un task fallito
  398. todoOk = false;
  399. }
  400. // STATUS_UNDEFINED -> STATUS_ACTIVE all children become active, if they have no dependencies.
  401. // STATUS_SUSPENDED -> STATUS_ACTIVE sets to active all children and their descendants that have no inhibiting dependencies.
  402. // STATUS_DONE -> STATUS_ACTIVE all those that have dependencies must be set to suspended.
  403. // STATUS_FAILED -> STATUS_ACTIVE nothing happens: child statuses must be reset by hand.
  404. } else if (newStatus == "STATUS_ACTIVE") {
  405. if ((manuallyChanged || oldStatus != "STATUS_FAILED")) { //cannot set failed task as closed for cascade - only if changed manually
  406. //can be active only if superiors are already done, not only on this task, but also on ancestors superiors
  407. var sups = task.getSuperiors();
  408. for (var i = 0; i < sups.length; i++) {
  409. if (sups[i].from.status != "STATUS_DONE") {
  410. if (manuallyChanged || propagateFromChildren)
  411. task.master.setErrorOnTransaction(GanttMaster.messages["GANTT_ERROR_DEPENDS_ON_OPEN_TASK"] + "\n\"" + sups[i].from.name + "\" -> \"" + task.name + "\"");
  412. todoOk = false;
  413. break;
  414. }
  415. }
  416. // check if parent is already active
  417. if (todoOk) {
  418. var par = task.getParent();
  419. if (par && par.status != "STATUS_ACTIVE") {
  420. // todoOk = propagateStatus(par, "STATUS_ACTIVE", false, false, true); //todo abbiamo deciso di non far propagare lo status verso l'alto
  421. todoOk = false;
  422. }
  423. }
  424. if (todoOk) {
  425. if (oldStatus == "STATUS_UNDEFINED" || oldStatus == "STATUS_SUSPENDED") {
  426. //set children as active
  427. propagateStatusToChildren(task,newStatus,true);
  428. }
  429. //set inferiors as suspended
  430. propagateStatusToInferiors( task.getInferiors(), "STATUS_SUSPENDED");
  431. }
  432. } else {
  433. todoOk = false;
  434. }
  435. // xxxx -> STATUS_SUSPENDED all active children and their active descendants become suspended. when not failed or forced
  436. } else if (newStatus == "STATUS_SUSPENDED" ) {
  437. if (manuallyChanged || oldStatus != "STATUS_FAILED") { //cannot set failed task as closed for cascade - only if changed manually
  438. //check if parent if not active
  439. var par = task.getParent();
  440. if (par && (par.status != "STATUS_ACTIVE" && par.status != "STATUS_SUSPENDED")) {
  441. todoOk = false;
  442. }
  443. if (todoOk) {
  444. //set children as STATUS_SUSPENDED
  445. propagateStatusToChildren(task, newStatus, true);
  446. //set inferiors as STATUS_SUSPENDED
  447. propagateStatusToInferiors( task.getInferiors(), newStatus);
  448. }
  449. } else {
  450. todoOk = false;
  451. }
  452. // xxxx -> STATUS_FAILED children and dependent failed
  453. // xxxx -> STATUS_UNDEFINED children and dependant become undefined.
  454. } else if (newStatus == "STATUS_FAILED" || newStatus == "STATUS_UNDEFINED") {
  455. //set children as failed or undefined
  456. propagateStatusToChildren(task,newStatus,false);
  457. //set inferiors as failed
  458. propagateStatusToInferiors( task.getInferiors(), newStatus);
  459. }
  460. if (!todoOk) {
  461. task.status = oldStatus;
  462. //console.debug("status rolled back: "+task.name + " to " + oldStatus);
  463. }
  464. return todoOk;
  465. }
  466. /**
  467. * A helper method to traverse an array of 'inferior' tasks
  468. * and signal a status change.
  469. */
  470. function propagateStatusToInferiors( infs, status) {
  471. for (var i = 0; i < infs.length; i++) {
  472. propagateStatus(infs[i].to, status, false, false, false);
  473. }
  474. }
  475. /**
  476. * A helper method to loop children and propagate new status
  477. */
  478. function propagateStatusToChildren(task, newStatus, skipClosedTasks) {
  479. var chds = task.getChildren();
  480. for (var i = 0; i < chds.length; i++)
  481. if (!(skipClosedTasks && chds[i].status == "STATUS_DONE") )
  482. propagateStatus(chds[i], newStatus, false, true, false);
  483. }
  484. var manuallyChanged=true;
  485. var oldStatus = this.status;
  486. //first call
  487. if (propagateStatus(this, newStatus, manuallyChanged, false, false)) {
  488. return true;
  489. } else {
  490. this.status = oldStatus;
  491. return false;
  492. }
  493. };
  494. Task.prototype.synchronizeStatus = function () {
  495. //console.debug("synchronizeStatus",this.name);
  496. var oldS = this.status;
  497. 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
  498. return this.changeStatus(oldS,true);
  499. };
  500. Task.prototype.isLocallyBlockedByDependencies = function () {
  501. var sups = this.getSuperiors();
  502. var blocked = false;
  503. for (var i = 0; i < sups.length; i++) {
  504. if (sups[i].from.status != "STATUS_DONE") {
  505. blocked = true;
  506. break;
  507. }
  508. }
  509. return blocked;
  510. };
  511. //<%---------- TASK STRUCTURE ---------------------- --%>
  512. Task.prototype.getRow = function () {
  513. ret = -1;
  514. if (this.master)
  515. ret = this.master.tasks.indexOf(this);
  516. return ret;
  517. };
  518. Task.prototype.getParents = function () {
  519. var ret;
  520. if (this.master) {
  521. var topLevel = this.level;
  522. var pos = this.getRow();
  523. ret = [];
  524. for (var i = pos; i >= 0; i--) {
  525. var par = this.master.tasks[i];
  526. if (topLevel > par.level) {
  527. topLevel = par.level;
  528. ret.push(par);
  529. }
  530. }
  531. }
  532. return ret;
  533. };
  534. Task.prototype.getParent = function () {
  535. var ret;
  536. if (this.master) {
  537. for (var i = this.getRow(); i >= 0; i--) {
  538. var par = this.master.tasks[i];
  539. if (this.level > par.level) {
  540. ret = par;
  541. break;
  542. }
  543. }
  544. }
  545. return ret;
  546. };
  547. Task.prototype.isParent = function () {
  548. var ret = false;
  549. if (this.master) {
  550. var pos = this.getRow();
  551. if (pos < this.master.tasks.length - 1)
  552. ret = this.master.tasks[pos + 1].level > this.level;
  553. }
  554. return ret;
  555. };
  556. Task.prototype.getChildren = function () {
  557. var ret = [];
  558. if (this.master) {
  559. var pos = this.getRow();
  560. for (var i = pos + 1; i < this.master.tasks.length; i++) {
  561. var ch = this.master.tasks[i];
  562. if (ch.level == this.level + 1)
  563. ret.push(ch);
  564. else if (ch.level <= this.level) // exit loop if parent or brother
  565. break;
  566. }
  567. }
  568. return ret;
  569. };
  570. Task.prototype.getDescendant = function () {
  571. var ret = [];
  572. if (this.master) {
  573. var pos = this.getRow();
  574. for (var i = pos + 1; i < this.master.tasks.length; i++) {
  575. var ch = this.master.tasks[i];
  576. if (ch.level > this.level)
  577. ret.push(ch);
  578. else
  579. break;
  580. }
  581. }
  582. return ret;
  583. };
  584. Task.prototype.getSuperiors = function () {
  585. var ret = [];
  586. var task = this;
  587. if (this.master) {
  588. ret = this.master.links.filter(function (link) {
  589. return link.to == task;
  590. });
  591. }
  592. return ret;
  593. };
  594. Task.prototype.getSuperiorTasks = function () {
  595. var ret = [];
  596. var sups = this.getSuperiors();
  597. for (var i = 0; i < sups.length; i++)
  598. ret.push(sups[i].from);
  599. return ret;
  600. };
  601. Task.prototype.getInferiors = function () {
  602. var ret = [];
  603. var task = this;
  604. if (this.master) {
  605. ret = this.master.links.filter(function (link) {
  606. return link.from == task;
  607. });
  608. }
  609. return ret;
  610. };
  611. Task.prototype.getInferiorTasks = function () {
  612. var ret = [];
  613. var infs = this.getInferiors();
  614. for (var i = 0; i < infs.length; i++)
  615. ret.push(infs[i].to);
  616. return ret;
  617. };
  618. Task.prototype.deleteTask = function () {
  619. //console.debug("deleteTask",this.name,this.master.deletedTaskIds)
  620. //if is the current one remove it
  621. if (this.master.currentTask && this.master.currentTask.id==this.id)
  622. delete this.master.currentTask;
  623. //delete both dom elements if exists
  624. if (this.rowElement)
  625. this.rowElement.remove();
  626. if (this.ganttElement)
  627. this.ganttElement.remove();
  628. //remove children
  629. var chd = this.getChildren();
  630. for (var i = 0; i < chd.length; i++) {
  631. //add removed child in list
  632. chd[i].deleteTask();
  633. }
  634. if (!this.isNew())
  635. this.master.deletedTaskIds.push(this.id);
  636. //remove from in-memory collection
  637. this.master.tasks.splice(this.getRow(), 1);
  638. //remove from links
  639. var task = this;
  640. this.master.links = this.master.links.filter(function (link) {
  641. return link.from != task && link.to != task;
  642. });
  643. };
  644. Task.prototype.isNew = function () {
  645. return (this.id + "").indexOf("tmp_") == 0;
  646. };
  647. Task.prototype.isDependent = function (t) {
  648. //console.debug("isDependent",this.name, t.name)
  649. var task = this;
  650. var dep = this.master.links.filter(function (link) {
  651. return link.from == task;
  652. });
  653. // is t a direct dependency?
  654. for (var i = 0; i < dep.length; i++) {
  655. if (dep[i].to == t)
  656. return true;
  657. }
  658. // is t an indirect dependency
  659. for (var i = 0; i < dep.length; i++) {
  660. if (dep[i].to.isDependent(t)) {
  661. return true;
  662. }
  663. }
  664. return false;
  665. };
  666. Task.prototype.setLatest = function (maxCost) {
  667. this.latestStart = maxCost - this.criticalCost;
  668. this.latestFinish = this.latestStart + this.duration;
  669. };
  670. //<%------------------------------------------ INDENT/OUTDENT --------------------------------%>
  671. Task.prototype.indent = function () {
  672. //console.debug("indent", this);
  673. //a row above must exist
  674. var row = this.getRow();
  675. //no row no party
  676. if (row <= 0)
  677. return false;
  678. var ret = false;
  679. var taskAbove = this.master.tasks[row - 1];
  680. var newLev = this.level + 1;
  681. if (newLev <= taskAbove.level + 1) {
  682. ret = true;
  683. //trick to get parents after indent
  684. this.level++;
  685. var futureParents = this.getParents();
  686. this.level--;
  687. var oldLevel = this.level;
  688. for (var i = row; i < this.master.tasks.length; i++) {
  689. var desc = this.master.tasks[i];
  690. if (desc.level > oldLevel || desc == this) {
  691. desc.level++;
  692. //remove links from this and descendant to my parents
  693. this.master.links = this.master.links.filter(function (link) {
  694. var linkToParent = false;
  695. if (link.to == desc)
  696. linkToParent = futureParents.indexOf(link.from) >= 0;
  697. else if (link.from == desc)
  698. linkToParent = futureParents.indexOf(link.to) >= 0;
  699. return !linkToParent;
  700. });
  701. //remove links from this and descendants to predecessors of parents in order to avoid loop
  702. var predecessorsOfFutureParents=[];
  703. for (var j=0;j<futureParents.length;j++)
  704. predecessorsOfFutureParents=predecessorsOfFutureParents.concat(futureParents[j].getSuperiorTasks());
  705. this.master.links = this.master.links.filter(function (link) {
  706. var linkToParent = false;
  707. if (link.from == desc)
  708. linkToParent = predecessorsOfFutureParents.indexOf(link.to) >= 0;
  709. return !linkToParent;
  710. });
  711. } else
  712. break;
  713. }
  714. var parent = this.getParent();
  715. // set start date to parent' start if no deps
  716. if (parent && !this.depends) {
  717. var new_end = computeEndByDuration(parent.start, this.duration);
  718. this.master.changeTaskDates(this, parent.start, new_end);
  719. }
  720. //recompute depends string
  721. this.master.updateDependsStrings();
  722. //enlarge parent using a fake set period
  723. updateTree(this);
  724. //force status check starting from parent
  725. this.getParent().synchronizeStatus();
  726. }
  727. return ret;
  728. };
  729. Task.prototype.outdent = function () {
  730. //console.debug("outdent", this);
  731. //a level must be >1 -> cannot escape from root
  732. if (this.level <= 0)//2017-5-20秦修改,可以添加0级,这样才合理嘛
  733. return false;
  734. var ret = false;
  735. var oldLevel = this.level;
  736. ret = true;
  737. var row = this.getRow();
  738. for (var i = row; i < this.master.tasks.length; i++) {
  739. var desc = this.master.tasks[i];
  740. if (desc.level > oldLevel || desc == this) {
  741. desc.level--;
  742. } else
  743. break;
  744. }
  745. var task = this;
  746. var chds = this.getChildren();
  747. //remove links from me to my new children
  748. this.master.links = this.master.links.filter(function (link) {
  749. var linkExist = (link.to == task && chds.indexOf(link.from) >= 0 || link.from == task && chds.indexOf(link.to) >= 0);
  750. return !linkExist;
  751. });
  752. //enlarge me if inherited children are larger
  753. for (var i = 0; i < chds.length; i++) {
  754. //remove links from me to my new children
  755. chds[i].setPeriod(chds[i].start + 1, chds[i].end + 1);
  756. }
  757. //recompute depends string
  758. this.master.updateDependsStrings();
  759. //enlarge parent using a fake set period
  760. this.setPeriod(this.start + 1, this.end + 1);
  761. //force status check
  762. this.synchronizeStatus();
  763. return ret;
  764. };
  765. //<%------------------------------------------ MOVE UP / MOVE DOWN --------------------------------%>
  766. Task.prototype.moveUp = function () {
  767. //console.debug("moveUp", this);
  768. var ret = false;
  769. //a row above must exist
  770. var row = this.getRow();
  771. //no row no party
  772. if (row <= 0)
  773. return false;
  774. //find new row
  775. var newRow;
  776. for (newRow = row - 1; newRow >= 0; newRow--) {
  777. if (this.master.tasks[newRow].level <= this.level)
  778. break;
  779. }
  780. //is a parent or a brother
  781. if (this.master.tasks[newRow].level == this.level) {
  782. ret = true;
  783. //compute descendant
  784. var descNumber = 0;
  785. for (var i = row + 1; i < this.master.tasks.length; i++) {
  786. var desc = this.master.tasks[i];
  787. if (desc.level > this.level) {
  788. descNumber++;
  789. } else {
  790. break;
  791. }
  792. }
  793. //move in memory
  794. var blockToMove = this.master.tasks.splice(row, descNumber + 1);
  795. var top = this.master.tasks.splice(0, newRow);
  796. this.master.tasks = [].concat(top, blockToMove, this.master.tasks);
  797. //move on dom
  798. var rows = this.master.editor.element.find("tr[taskid]");
  799. var domBlockToMove = rows.slice(row, row + descNumber + 1);
  800. rows.eq(newRow).before(domBlockToMove);
  801. //recompute depends string
  802. this.master.updateDependsStrings();
  803. } else {
  804. this.master.setErrorOnTransaction(GanttMaster.messages["TASK_MOVE_INCONSISTENT_LEVEL"], this);
  805. ret = false;
  806. }
  807. return ret;
  808. };
  809. Task.prototype.moveDown = function () {
  810. //console.debug("moveDown", this);
  811. //a row below must exist, and cannot move root task
  812. var row = this.getRow();
  813. if (row >= this.master.tasks.length - 1 || row == 0)
  814. return false;
  815. var ret = false;
  816. //find nearest brother
  817. var newRow;
  818. for (newRow = row + 1; newRow < this.master.tasks.length; newRow++) {
  819. if (this.master.tasks[newRow].level <= this.level)
  820. break;
  821. }
  822. //is brother
  823. if (this.master.tasks[newRow] && this.master.tasks[newRow].level == this.level) {
  824. ret = true;
  825. //find last desc
  826. for (newRow = newRow + 1; newRow < this.master.tasks.length; newRow++) {
  827. if (this.master.tasks[newRow].level <= this.level)
  828. break;
  829. }
  830. //compute descendant
  831. var descNumber = 0;
  832. for (var i = row + 1; i < this.master.tasks.length; i++) {
  833. var desc = this.master.tasks[i];
  834. if (desc.level > this.level) {
  835. descNumber++;
  836. } else {
  837. break;
  838. }
  839. }
  840. //move in memory
  841. var blockToMove = this.master.tasks.splice(row, descNumber + 1);
  842. var top = this.master.tasks.splice(0, newRow - descNumber - 1);
  843. this.master.tasks = [].concat(top, blockToMove, this.master.tasks);
  844. //move on dom
  845. var rows = this.master.editor.element.find("tr[taskid]");
  846. var aft = rows.eq(newRow - 1);
  847. var domBlockToMove = rows.slice(row, row + descNumber + 1);
  848. aft.after(domBlockToMove);
  849. //recompute depends string
  850. this.master.updateDependsStrings();
  851. }
  852. return ret;
  853. };
  854. //<%------------------------------------------------------------------------ LINKS OBJECT ---------------------------------------------------------------%>
  855. function Link(taskFrom, taskTo, lagInWorkingDays) {
  856. this.from = taskFrom;
  857. this.to = taskTo;
  858. this.lag = lagInWorkingDays;
  859. }
  860. //<%------------------------------------------------------------------------ ASSIGNMENT ---------------------------------------------------------------%>
  861. function Assignment(id, resourceId, roleId, effort) {
  862. this.id = id;
  863. this.resourceId = resourceId;
  864. this.roleId = roleId;
  865. this.effort = effort;
  866. }
  867. //<%------------------------------------------------------------------------ RESOURCE ---------------------------------------------------------------%>
  868. function Resource(id, name) {
  869. this.id = id;
  870. this.name = name;
  871. }
  872. //<%------------------------------------------------------------------------ ROLE ---------------------------------------------------------------%>
  873. function Role(id, name) {
  874. this.id = id;
  875. this.name = name;
  876. }