gantt_rails 0.0.1 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,1034 @@
1
+ /*
2
+ Copyright (c) 2012-2014 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
+
12
+ The above copyright notice and this permission notice shall be
13
+ included in all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+ */
23
+
24
+ /**
25
+ * A method to instantiate valid task models from
26
+ * raw data.
27
+ */
28
+ function TaskFactory() {
29
+
30
+ /**
31
+ * Build a new Task
32
+ */
33
+ this.build = function(id, name, code, level, start, duration, collapsed) {
34
+ // Set at beginning of day
35
+ var adjusted_start = computeStart(start);
36
+ var calculated_end = computeEndByDuration(adjusted_start, duration);
37
+
38
+ return new Task(id, name, code, level, adjusted_start, calculated_end, duration, collapsed);
39
+ };
40
+
41
+ }
42
+
43
+ function Task(id, name, code, level, start, end, duration, collapsed) {
44
+ this.id = id;
45
+ this.name = name;
46
+ this.progress=0;
47
+ this.description = "";
48
+ this.code = code;
49
+ this.level = level;
50
+ this.status = "STATUS_UNDEFINED";
51
+ this.depends="";
52
+ this.canWrite=true; // by default all tasks are writeable
53
+
54
+ this.start = start;
55
+ this.duration = duration;
56
+ this.end = end;
57
+ this.startIsMilestone = false;
58
+ this.endIsMilestone = false;
59
+
60
+ this.collapsed = collapsed;
61
+
62
+ this.rowElement; //row editor html element
63
+ this.ganttElement; //gantt html element
64
+ this.master;
65
+
66
+ this.assigs = [];
67
+ }
68
+
69
+ Task.prototype.clone = function () {
70
+ var ret = {};
71
+ for (var key in this) {
72
+ if (typeof(this[key]) != "function") {
73
+ ret[key] = this[key];
74
+ }
75
+ }
76
+ return ret;
77
+ };
78
+
79
+ Task.prototype.getAssigsString = function () {
80
+ var ret = "";
81
+ for (var i=0;i<this.assigs.length;i++) {
82
+ var ass = this.assigs[i];
83
+ var res = this.master.getResource(ass.resourceId);
84
+ if (res) {
85
+ ret = ret + (ret == "" ? "" : ", ") + res.name;
86
+ }
87
+ }
88
+ return ret;
89
+ };
90
+
91
+ Task.prototype.createAssignment = function (id, resourceId, roleId, effort) {
92
+ var assig = new Assignment(id, resourceId, roleId, effort);
93
+ this.assigs.push(assig);
94
+ return assig;
95
+ };
96
+
97
+
98
+ //<%---------- SET PERIOD ---------------------- --%>
99
+ Task.prototype.setPeriod = function (start, end) {
100
+ //console.debug("setPeriod ",this.name,new Date(start),new Date(end));
101
+ //var profilerSetPer = new Profiler("gt_setPeriodJS");
102
+
103
+ if (start instanceof Date) {
104
+ start = start.getTime();
105
+ }
106
+
107
+ if (end instanceof Date) {
108
+ end = end.getTime();
109
+ }
110
+
111
+ var originalPeriod = {
112
+ start: this.start,
113
+ end: this.end,
114
+ duration: this.duration
115
+ };
116
+
117
+ //console.debug("setStart",date,date instanceof Date);
118
+ var wantedStartMillis = start;
119
+
120
+ //cannot start after end
121
+ if (start > end) {
122
+ start = end;
123
+ }
124
+
125
+ //set a legal start
126
+ start = computeStart(start);
127
+
128
+ //if depends -> start is set to max end + lag of superior
129
+ var sups = this.getSuperiors();
130
+ if (sups && sups.length > 0) {
131
+
132
+ var supEnd = 0;
133
+ for (var i=0;i<sups.length;i++) {
134
+ var link = sups[i];
135
+ supEnd = Math.max(supEnd, incrementDateByWorkingDays(link.from.end, link.lag));
136
+ }
137
+ //if changed by depends move it
138
+ if (computeStart(supEnd) != start) {
139
+ return this.moveTo(supEnd + 1, false);
140
+ }
141
+ }
142
+
143
+ var somethingChanged = false;
144
+
145
+ //move date to closest day
146
+ var date = new Date(start);
147
+
148
+ if (this.start != start || this.start != wantedStartMillis) {
149
+ this.start = start;
150
+ somethingChanged = true;
151
+ }
152
+
153
+ //set end
154
+ var wantedEndMillis = end;
155
+ end = computeEnd(end);
156
+
157
+ if (this.end != end || this.end != wantedEndMillis) {
158
+ this.end = end;
159
+ somethingChanged = true;
160
+ }
161
+
162
+
163
+ this.duration = recomputeDuration(this.start, this.end);
164
+
165
+ //profilerSetPer.stop();
166
+
167
+ //nothing changed exit
168
+ if (!somethingChanged)
169
+ return true;
170
+
171
+ //cannot write exit
172
+ if(!this.canWrite){
173
+ this.master.setErrorOnTransaction(GanttMaster.messages["CANNOT_WRITE"] + "\n" + this.name, this);
174
+ return false;
175
+ }
176
+
177
+ //external dependencies: exit with error
178
+ if (this.hasExternalDep) {
179
+ this.master.setErrorOnTransaction(GanttMaster.messages["TASK_HAS_EXTERNAL_DEPS"] + "\n" + this.name, this);
180
+ return false;
181
+ }
182
+
183
+ var todoOk = true;
184
+
185
+ //I'm restricting
186
+ var deltaPeriod = originalPeriod.duration - this.duration;
187
+ var restricting = deltaPeriod > 0;
188
+ var restrictingStart = restricting && (originalPeriod.start < this.start);
189
+ var restrictingEnd = restricting && (originalPeriod.end > this.end);
190
+
191
+ //console.debug( " originalPeriod.duration "+ originalPeriod.duration +" deltaPeriod "+deltaPeriod+" "+"restricting "+restricting);
192
+
193
+ if (restricting) {
194
+ //loops children to get boundaries
195
+ var children = this.getChildren();
196
+ var bs = Infinity;
197
+ var be = 0;
198
+ for (var i=0;i<children.length;i++) {
199
+
200
+ ch = children[i];
201
+ //console.debug("restricting: test child "+ch.name+" "+ch.end)
202
+ if (restrictingEnd) {
203
+ be = Math.max(be, ch.end);
204
+ } else {
205
+ bs = Math.min(bs, ch.start);
206
+ }
207
+ }
208
+
209
+ if (restrictingEnd) {
210
+ //console.debug("restricting end ",be, this.end);
211
+ this.end = Math.max(be, this.end);
212
+ } else {
213
+ //console.debug("restricting start");
214
+ this.start = Math.min(bs, this.start);
215
+ }
216
+
217
+ this.duration = recomputeDuration(this.start, this.end);
218
+ } else {
219
+
220
+ //check global boundaries
221
+ if (this.start < this.master.minEditableDate || this.end > this.master.maxEditableDate) {
222
+ this.master.setErrorOnTransaction(GanttMaster.messages["CHANGE_OUT_OF_SCOPE"], this);
223
+ todoOk = false;
224
+ }
225
+
226
+ //console.debug("set period: somethingChanged",this);
227
+ if (todoOk && !updateTree(this)) {
228
+ todoOk = false;
229
+ }
230
+ }
231
+
232
+ if (todoOk) {
233
+ //and now propagate to inferiors
234
+ var infs = this.getInferiors();
235
+ if (infs && infs.length > 0) {
236
+ for (var i=0;i<infs.length;i++) {
237
+ var link = infs[i];
238
+ if (!link.to.canWrite){
239
+ this.master.setErrorOnTransaction(GanttMaster.messages["CANNOT_WRITE"] + "\n" + link.to.name, link.to);
240
+ break;
241
+ }
242
+ todoOk = link.to.moveTo(end, false); //this is not the right date but moveTo checks start
243
+ if (!todoOk)
244
+ break;
245
+ }
246
+ }
247
+ }
248
+
249
+ return todoOk;
250
+ };
251
+
252
+
253
+ //<%---------- MOVE TO ---------------------- --%>
254
+ Task.prototype.moveTo = function (start, ignoreMilestones) {
255
+ //console.debug("moveTo ",this,start,ignoreMilestones);
256
+ //var profiler = new Profiler("gt_task_moveTo");
257
+
258
+ if (start instanceof Date) {
259
+ start = start.getTime();
260
+ }
261
+
262
+ var originalPeriod = {
263
+ start:this.start,
264
+ end:this.end
265
+ };
266
+
267
+ var wantedStartMillis = start;
268
+
269
+ //set a legal start
270
+ start = computeStart(start);
271
+
272
+ //if start is milestone cannot be move
273
+ if (!ignoreMilestones && this.startIsMilestone && start != this.start) {
274
+ //notify error
275
+ this.master.setErrorOnTransaction(GanttMaster.messages["START_IS_MILESTONE"], this);
276
+ return false;
277
+ } else if (this.hasExternalDep) {
278
+ //notify error
279
+ this.master.setErrorOnTransaction(GanttMaster.messages["TASK_HAS_EXTERNAL_DEPS"], this);
280
+ return false;
281
+ }
282
+
283
+ //if depends start is set to max end + lag of superior
284
+ var sups = this.getSuperiors();
285
+ if (sups && sups.length > 0) {
286
+ var supEnd = 0;
287
+ for (var i=0;i<sups.length;i++) {
288
+ var link = sups[i];
289
+ supEnd = Math.max(supEnd, incrementDateByWorkingDays(link.from.end, link.lag));
290
+ }
291
+ start = supEnd + 1;
292
+ }
293
+ //set a legal start
294
+ start = computeStart(start);
295
+
296
+ var end = computeEndByDuration(start, this.duration);
297
+
298
+ if (this.start != start || this.start != wantedStartMillis) {
299
+ //in case of end is milestone it never changes, but recompute duration
300
+ if (!ignoreMilestones && this.endIsMilestone) {
301
+ end = this.end;
302
+ this.duration = recomputeDuration(start, end);
303
+ }
304
+ this.start = start;
305
+ this.end = end;
306
+ //profiler.stop();
307
+
308
+ //check global boundaries
309
+ if (this.start < this.master.minEditableDate || this.end > this.master.maxEditableDate) {
310
+ this.master.setErrorOnTransaction(GanttMaster.messages["CHANGE_OUT_OF_SCOPE"], this);
311
+ return false;
312
+ }
313
+
314
+
315
+ var panDelta = originalPeriod.start - this.start;
316
+ //console.debug("panDelta",panDelta);
317
+ //loops children to shift them
318
+ var children = this.getChildren();
319
+ for (var i=0;i<children.length;i++) {
320
+ ch = children[i];
321
+ if (!ch.moveTo(ch.start - panDelta, false)) {
322
+ return false;
323
+ }
324
+ }
325
+
326
+
327
+ //console.debug("set period: somethingChanged",this);
328
+ if (!updateTree(this)) {
329
+ return false;
330
+ }
331
+
332
+
333
+ //and now propagate to inferiors
334
+ var infs = this.getInferiors();
335
+ if (infs && infs.length > 0) {
336
+ for (var i=0;i<infs.length;i++) {
337
+ var link = infs[i];
338
+
339
+
340
+ //this is not the right date but moveTo checks start
341
+ if (!link.to.canWrite ) {
342
+ this.master.setErrorOnTransaction(GanttMaster.messages["CANNOT_WRITE"]+ "\n"+link.to.name, link.to);
343
+ } else if (!link.to.moveTo(end, false)) {
344
+ return false;
345
+ }
346
+ }
347
+ }
348
+
349
+ }
350
+
351
+ return true;
352
+ };
353
+
354
+
355
+ function updateTree(task) {
356
+ //console.debug("updateTree ",task);
357
+ var error;
358
+
359
+ //try to enlarge parent
360
+ var p = task.getParent();
361
+
362
+ //no parent:exit
363
+ if (!p)
364
+ return true;
365
+
366
+ var newStart = p.start;
367
+ var newEnd = p.end;
368
+
369
+ if (p.start > task.start) {
370
+ if (p.startIsMilestone) {
371
+ task.master.setErrorOnTransaction(GanttMaster.messages["START_IS_MILESTONE"] + "\n" + p.name, task);
372
+ return false;
373
+ } else if (p.depends) {
374
+ task.master.setErrorOnTransaction(GanttMaster.messages["TASK_HAS_CONSTRAINTS"] + "\n" + p.name, task);
375
+ return false;
376
+ }
377
+
378
+ newStart = task.start;
379
+ }
380
+
381
+ if (p.end < task.end) {
382
+ if (p.endIsMilestone) {
383
+ task.master.setErrorOnTransaction(GanttMaster.messages["END_IS_MILESTONE"] + "\n" + p.name, task);
384
+ return false;
385
+ }
386
+
387
+ newEnd = task.end;
388
+ }
389
+
390
+ //propagate updates if needed
391
+ if (newStart != p.start || newEnd != p.end) {
392
+
393
+ //can write?
394
+ if (!p.canWrite){
395
+ task.master.setErrorOnTransaction(GanttMaster.messages["CANNOT_WRITE"] + "\n" + p.name, task);
396
+ return false;
397
+ }
398
+
399
+ //has external deps ?
400
+ if (p.hasExternalDep) {
401
+ task.master.setErrorOnTransaction(GanttMaster.messages["TASK_HAS_EXTERNAL_DEPS"] + "\n" + p.name, task);
402
+ return false;
403
+ }
404
+
405
+ return p.setPeriod(newStart, newEnd);
406
+ }
407
+
408
+
409
+ return true;
410
+ }
411
+
412
+ //<%---------- CHANGE STATUS ---------------------- --%>
413
+ Task.prototype.changeStatus = function(newStatus) {
414
+ //console.debug("changeStatus: "+this.name+" from "+this.status+" -> "+newStatus);
415
+ //compute descendant for identify a cone where status changes propagate
416
+ var cone = this.getDescendant();
417
+
418
+ function propagateStatus(task, newStatus, manuallyChanged, propagateFromParent, propagateFromChildren) {
419
+ //console.debug("propagateStatus",task.name, task.status,newStatus)
420
+ var oldStatus = task.status;
421
+
422
+ //no changes exit
423
+ if(newStatus == oldStatus){
424
+ return true;
425
+ }
426
+ //console.debug("propagateStatus: "+task.name + " from " + task.status + " to " + newStatus + " " + (manuallyChanged?" a manella":"")+(propagateFromParent?" da parent":"")+(propagateFromChildren?" da children":""));
427
+
428
+ var todoOk = true;
429
+ task.status = newStatus;
430
+
431
+ if( task.master.cannotCloseTaskIfIssueOpen && newStatus=="STATUS_DONE" && task.openIssues>0){
432
+ task.master.setErrorOnTransaction(GanttMaster.messages["CANNOT_CLOSE_TASK_IF_OPEN_ISSUE"] +" " +task.name);
433
+ return false;
434
+ }
435
+
436
+ //xxxx -> STATUS_DONE may activate dependent tasks, both suspended and undefined. Will set to done all descendants.
437
+ //STATUS_FAILED -> STATUS_DONE do nothing if not forced by hand
438
+ if (newStatus == "STATUS_DONE") {
439
+
440
+ if ((manuallyChanged || oldStatus != "STATUS_FAILED")) { //cannot change for cascade when failed
441
+
442
+ //can be closed only if superiors are already done
443
+ var sups = task.getSuperiors();
444
+ for (var i=0;i<sups.length;i++) {
445
+ if (cone.indexOf(sups[i].from) < 0) {
446
+ if (sups[i].from.status != "STATUS_DONE") {
447
+ if (manuallyChanged || propagateFromParent)
448
+ task.master.setErrorOnTransaction(GanttMaster.messages["GANTT_ERROR_DEPENDS_ON_OPEN_TASK"] + "\n" + sups[i].from.name + " -> " + task.name);
449
+ todoOk = false;
450
+ break;
451
+ }
452
+ }
453
+ }
454
+
455
+ if (todoOk) {
456
+ //todo set progress to 100% if set on config
457
+
458
+ var chds = task.getChildren();
459
+ //set children as done
460
+ for (var i=0;i<chds.length;i++)
461
+ propagateStatus(chds[i], "STATUS_DONE", false,true,false);
462
+
463
+ //set inferiors as active if outside the cone
464
+ propagateToInferiors(cone, task.getInferiors(), "STATUS_ACTIVE");
465
+ }
466
+ } else {
467
+ todoOk = false;
468
+ }
469
+
470
+
471
+ // STATUS_UNDEFINED -> STATUS_ACTIVE all children become active, if they have no dependencies.
472
+ // STATUS_SUSPENDED -> STATUS_ACTIVE sets to active all children and their descendants that have no inhibiting dependencies.
473
+ // STATUS_DONE -> STATUS_ACTIVE all those that have dependencies must be set to suspended.
474
+ // STATUS_FAILED -> STATUS_ACTIVE nothing happens: child statuses must be reset by hand.
475
+ } else if (newStatus == "STATUS_ACTIVE") {
476
+
477
+ if ((manuallyChanged || oldStatus != "STATUS_FAILED")) { //cannot change for cascade when failed
478
+
479
+ //activate parent if closed
480
+ var par=task.getParent();
481
+ if (par && par.status != "STATUS_ACTIVE") {
482
+ todoOk=propagateStatus(par,"STATUS_ACTIVE",false,false,true);
483
+ }
484
+
485
+ if(todoOk){
486
+ //can be active only if superiors are already done
487
+ var sups = task.getSuperiors();
488
+ for (var i=0;i<sups.length;i++) {
489
+ if (sups[i].from.status != "STATUS_DONE") {
490
+ if (manuallyChanged || propagateFromChildren)
491
+ task.master.setErrorOnTransaction(GanttMaster.messages["GANTT_ERROR_DEPENDS_ON_OPEN_TASK"] + "\n" + sups[i].from.name + " -> " + task.name);
492
+ todoOk = false;
493
+ break;
494
+ }
495
+ }
496
+ }
497
+
498
+ if (todoOk) {
499
+ var chds = task.getChildren();
500
+ if (oldStatus == "STATUS_UNDEFINED" || oldStatus == "STATUS_SUSPENDED") {
501
+ //set children as active
502
+ for (var i=0;i<chds.length;i++)
503
+ if (chds[i].status != "STATUS_DONE" )
504
+ propagateStatus(chds[i], "STATUS_ACTIVE", false,true,false);
505
+ }
506
+
507
+ //set inferiors as suspended
508
+ var infs = task.getInferiors();
509
+ for (var i=0;i<infs.length;i++)
510
+ propagateStatus(infs[i].to, "STATUS_SUSPENDED", false,false,false);
511
+ }
512
+ } else {
513
+ todoOk = false;
514
+ }
515
+
516
+ // xxxx -> STATUS_SUSPENDED all active children and their active descendants become suspended. when not failed or forced
517
+ // xxxx -> STATUS_UNDEFINED all active children and their active descendants become suspended. when not failed or forced
518
+ } else if (newStatus == "STATUS_SUSPENDED" || newStatus == "STATUS_UNDEFINED") {
519
+ if (manuallyChanged || oldStatus != "STATUS_FAILED") { //cannot change for cascade when failed
520
+
521
+ //suspend parent if not active
522
+ var par=task.getParent();
523
+ if (par && par.status != "STATUS_ACTIVE") {
524
+ todoOk=propagateStatus(par,newStatus,false,false,true);
525
+ }
526
+
527
+
528
+ var chds = task.getChildren();
529
+ //set children as active
530
+ for (var i=0;i<chds.length;i++){
531
+ if (chds[i].status != "STATUS_DONE")
532
+ propagateStatus(chds[i], newStatus, false,true,false);
533
+ }
534
+
535
+ //set inferiors as STATUS_SUSPENDED or STATUS_UNDEFINED
536
+ propagateToInferiors(cone, task.getInferiors(), newStatus);
537
+ } else {
538
+ todoOk = false;
539
+ }
540
+
541
+ // xxxx -> STATUS_FAILED children and dependent failed
542
+ } else if (newStatus == "STATUS_FAILED") {
543
+ var chds = task.getChildren();
544
+ //set children as failed
545
+ for (var i=0;i<chds.length;i++)
546
+ propagateStatus(chds[i], "STATUS_FAILED", false,true,false);
547
+
548
+ //set inferiors as active
549
+ //set children as done
550
+ propagateToInferiors(cone, task.getInferiors(), "STATUS_FAILED");
551
+ }
552
+ if (!todoOk){
553
+ task.status = oldStatus;
554
+ //console.debug("status rolled back: "+task.name + " to " + oldStatus);
555
+ }
556
+
557
+ return todoOk;
558
+ }
559
+
560
+ /**
561
+ * A helper method to traverse an array of 'inferior' tasks
562
+ * and signal a status change.
563
+ */
564
+ function propagateToInferiors(cone, infs, status) {
565
+ for (var i=0;i<infs.length;i++) {
566
+ if (cone.indexOf(infs[i].to) < 0) {
567
+ propagateStatus(infs[i].to, status, false, false, false);
568
+ }
569
+ }
570
+ }
571
+
572
+ var todoOk = true;
573
+ var oldStatus = this.status;
574
+
575
+ todoOk = propagateStatus(this, newStatus, true,false,false);
576
+
577
+ if (!todoOk)
578
+ this.status = oldStatus;
579
+
580
+ return todoOk;
581
+ };
582
+
583
+ Task.prototype.synchronizeStatus=function(){
584
+ var oldS=this.status;
585
+ this.status="";
586
+ return this.changeStatus(oldS);
587
+ };
588
+
589
+ Task.prototype.isLocallyBlockedByDependencies=function(){
590
+ var sups = this.getSuperiors();
591
+ var blocked=false;
592
+ for (var i=0;i<sups.length;i++) {
593
+ if (sups[i].from.status != "STATUS_DONE") {
594
+ blocked=true;
595
+ break;
596
+ }
597
+ }
598
+ return blocked;
599
+ };
600
+
601
+ //<%---------- TASK STRUCTURE ---------------------- --%>
602
+ Task.prototype.getRow = function() {
603
+ ret = -1;
604
+ if (this.master)
605
+ ret = this.master.tasks.indexOf(this);
606
+ return ret;
607
+ };
608
+
609
+
610
+ Task.prototype.getParents = function() {
611
+ var ret;
612
+ if (this.master) {
613
+ var topLevel = this.level;
614
+ var pos = this.getRow();
615
+ ret = [];
616
+ for (var i = pos; i >= 0; i--) {
617
+ var par = this.master.tasks[i];
618
+ if (topLevel > par.level) {
619
+ topLevel = par.level;
620
+ ret.push(par);
621
+ }
622
+ }
623
+ }
624
+ return ret;
625
+ };
626
+
627
+
628
+ Task.prototype.getParent = function() {
629
+ var ret;
630
+ if (this.master) {
631
+ for (var i = this.getRow(); i >= 0; i--) {
632
+ var par = this.master.tasks[i];
633
+ if (this.level > par.level) {
634
+ ret = par;
635
+ break;
636
+ }
637
+ }
638
+ }
639
+ return ret;
640
+ };
641
+
642
+
643
+ Task.prototype.isParent = function() {
644
+ var ret = false;
645
+ if (this.master) {
646
+ var pos = this.getRow();
647
+ if (pos < this.master.tasks.length - 1)
648
+ ret = this.master.tasks[pos + 1].level > this.level;
649
+ }
650
+ return ret;
651
+ };
652
+
653
+
654
+ Task.prototype.getChildren = function() {
655
+ var ret = [];
656
+ if (this.master) {
657
+ var pos = this.getRow();
658
+ for (var i = pos + 1; i < this.master.tasks.length; i++) {
659
+ var ch = this.master.tasks[i];
660
+ if (ch.level == this.level + 1)
661
+ ret.push(ch);
662
+ else if (ch.level <= this.level) // exit loop if parent or brother
663
+ break;
664
+ }
665
+ }
666
+ return ret;
667
+ };
668
+
669
+
670
+ Task.prototype.getDescendant = function() {
671
+ var ret = [];
672
+ if (this.master) {
673
+ var pos = this.getRow();
674
+ for (var i = pos + 1; i < this.master.tasks.length; i++) {
675
+ var ch = this.master.tasks[i];
676
+ if (ch.level > this.level)
677
+ ret.push(ch);
678
+ else
679
+ break;
680
+ }
681
+ }
682
+ return ret;
683
+ };
684
+
685
+
686
+ Task.prototype.getSuperiors = function() {
687
+ var ret = [];
688
+ var task = this;
689
+ if (this.master) {
690
+ ret = this.master.links.filter(function(link) {
691
+ return link.to == task;
692
+ });
693
+ }
694
+ return ret;
695
+ };
696
+
697
+ Task.prototype.getSuperiorTasks = function() {
698
+ var ret=[];
699
+ var sups = this.getSuperiors();
700
+ for (var i=0;i<sups.length;i++)
701
+ ret.push(sups[i].from);
702
+ return ret;
703
+ };
704
+
705
+
706
+ Task.prototype.getInferiors = function() {
707
+ var ret = [];
708
+ var task = this;
709
+ if (this.master) {
710
+ ret = this.master.links.filter(function(link) {
711
+ return link.from == task;
712
+ });
713
+ }
714
+ return ret;
715
+ };
716
+
717
+ Task.prototype.getInferiorTasks = function() {
718
+ var ret=[];
719
+ var infs = this.getInferiors();
720
+ for (var i=0;i<infs.length;i++)
721
+ ret.push(infs[i].to);
722
+ return ret;
723
+ };
724
+
725
+ Task.prototype.deleteTask = function() {
726
+
727
+ //delete both dom elements
728
+ if (this.rowElement) {
729
+ this.rowElement.remove();
730
+ }
731
+
732
+ //delete both dom elements
733
+ if (this.ganttElement) {
734
+ this.ganttElement.remove();
735
+ }
736
+
737
+ //remove children
738
+ var chd = this.getChildren();
739
+ for (var i=0;i<chd.length;i++) {
740
+ //add removed child in list
741
+ if(!chd[i].isNew())
742
+ this.master.deletedTaskIds.push(chd[i].id);
743
+ chd[i].deleteTask();
744
+ }
745
+
746
+ if(!this.isNew())
747
+ this.master.deletedTaskIds.push(this.id);
748
+
749
+
750
+ //remove from in-memory collection
751
+ this.master.tasks.splice(this.getRow(), 1);
752
+
753
+ //remove from links
754
+ var task = this;
755
+ this.master.links = this.master.links.filter(function(link) {
756
+ return link.from != task && link.to != task;
757
+ });
758
+ };
759
+
760
+
761
+ Task.prototype.isNew=function(){
762
+ return (this.id+"").indexOf("tmp_")==0;
763
+ };
764
+
765
+ Task.prototype.isDependent=function(t) {
766
+ //console.debug("isDependent",this.name, t.name)
767
+ var task=this;
768
+ var dep= this.master.links.filter(function(link) {
769
+ return link.from == task ;
770
+ });
771
+
772
+ // is t a direct dependency?
773
+ for (var i=0;i<dep.length;i++) {
774
+ if (dep[i].to== t)
775
+ return true;
776
+ }
777
+ // is t an indirect dependency
778
+ for (var i=0;i<dep.length;i++) {
779
+ if (dep[i].to.isDependent(t)) {
780
+ return true;
781
+ }
782
+ }
783
+ return false;
784
+ };
785
+
786
+ Task.prototype.setLatest=function(maxCost) {
787
+ this.latestStart = maxCost - this.criticalCost;
788
+ this.latestFinish = this.latestStart + this.duration;
789
+ };
790
+
791
+
792
+ //<%------------------------------------------ INDENT/OUTDENT --------------------------------%>
793
+ Task.prototype.indent = function() {
794
+ //console.debug("indent", this);
795
+ //a row above must exist
796
+ var row = this.getRow();
797
+
798
+ //no row no party
799
+ if (row <=0)
800
+ return false;
801
+
802
+ var ret = false;
803
+ var taskAbove = this.master.tasks[row - 1];
804
+ var newLev = this.level + 1;
805
+ if (newLev <= taskAbove.level + 1) {
806
+ ret = true;
807
+ //trick to get parents after indent
808
+ this.level++;
809
+ var futureParents = this.getParents();
810
+ this.level--;
811
+ var oldLevel = this.level;
812
+ for (var i = row; i < this.master.tasks.length; i++) {
813
+ var desc = this.master.tasks[i];
814
+ if (desc.level > oldLevel || desc == this) {
815
+ desc.level++;
816
+ //remove links from descendant to my parents
817
+ this.master.links = this.master.links.filter(function(link) {
818
+ var linkToParent = false;
819
+ if (link.to == desc)
820
+ linkToParent = futureParents.indexOf(link.from) >= 0;
821
+ else if (link.from == desc)
822
+ linkToParent = futureParents.indexOf(link.to) >= 0;
823
+ return !linkToParent;
824
+ });
825
+ } else
826
+ break;
827
+ }
828
+
829
+ var parent = this.getParent();
830
+ // set start date to parent' start if no deps
831
+ if(parent && !this.depends){
832
+ var new_end = computeEndByDuration(parent.start, this.duration);
833
+ this.master.changeTaskDates(this, parent.start, new_end);
834
+ }
835
+ //recompute depends string
836
+ this.master.updateDependsStrings();
837
+ //enlarge parent using a fake set period
838
+ this.setPeriod(this.start + 1, this.end + 1);
839
+
840
+ //force status check
841
+ this.synchronizeStatus();
842
+ }
843
+ return ret;
844
+ };
845
+
846
+
847
+ Task.prototype.outdent = function() {
848
+ //console.debug("outdent", this);
849
+
850
+ //a level must be >1 -> cannot escape from root
851
+ if (this.level <= 1)
852
+ return false;
853
+
854
+ var ret = false;
855
+ var oldLevel = this.level;
856
+
857
+ ret = true;
858
+ var row = this.getRow();
859
+ for (var i = row; i < this.master.tasks.length; i++) {
860
+ var desc = this.master.tasks[i];
861
+ if (desc.level > oldLevel || desc == this) {
862
+ desc.level--;
863
+ } else
864
+ break;
865
+ }
866
+
867
+ var task = this;
868
+ var chds = this.getChildren();
869
+ //remove links from me to my new children
870
+ this.master.links = this.master.links.filter(function(link) {
871
+ var linkExist = (link.to == task && chds.indexOf(link.from) >= 0 || link.from == task && chds.indexOf(link.to) >= 0);
872
+ return !linkExist;
873
+ });
874
+
875
+
876
+ //enlarge me if inherited children are larger
877
+ for (var i=0;i<chds.length;i++) {
878
+ //remove links from me to my new children
879
+ chds[i].setPeriod(chds[i].start + 1, chds[i].end + 1);
880
+ }
881
+
882
+ //recompute depends string
883
+ this.master.updateDependsStrings();
884
+
885
+ //enlarge parent using a fake set period
886
+ this.setPeriod(this.start + 1, this.end + 1);
887
+
888
+ //force status check
889
+ this.synchronizeStatus();
890
+ return ret;
891
+ };
892
+
893
+
894
+ //<%------------------------------------------ MOVE UP / MOVE DOWN --------------------------------%>
895
+ Task.prototype.moveUp = function() {
896
+ //console.debug("moveUp", this);
897
+ var ret = false;
898
+
899
+ //a row above must exist
900
+ var row = this.getRow();
901
+
902
+ //no row no party
903
+ if (row <=0)
904
+ return false;
905
+
906
+ //find new row
907
+ var newRow;
908
+ for (newRow = row - 1; newRow >= 0; newRow--) {
909
+ if (this.master.tasks[newRow].level <= this.level)
910
+ break;
911
+ }
912
+
913
+ //is a parent or a brother
914
+ if (this.master.tasks[newRow].level == this.level) {
915
+ ret = true;
916
+ //compute descendant
917
+ var descNumber = 0;
918
+ for (var i = row + 1; i < this.master.tasks.length; i++) {
919
+ var desc = this.master.tasks[i];
920
+ if (desc.level > this.level) {
921
+ descNumber++;
922
+ } else {
923
+ break;
924
+ }
925
+ }
926
+ //move in memory
927
+ var blockToMove = this.master.tasks.splice(row, descNumber + 1);
928
+ var top = this.master.tasks.splice(0, newRow);
929
+ this.master.tasks = [].concat(top, blockToMove, this.master.tasks);
930
+ //move on dom
931
+ var rows = this.master.editor.element.find("tr[taskid]");
932
+ var domBlockToMove = rows.slice(row, row + descNumber + 1);
933
+ rows.eq(newRow).before(domBlockToMove);
934
+
935
+ //recompute depends string
936
+ this.master.updateDependsStrings();
937
+ } else {
938
+ this.master.setErrorOnTransaction(GanttMaster.messages["TASK_MOVE_INCONSISTENT_LEVEL"], this);
939
+ ret = false;
940
+ }
941
+ return ret;
942
+ };
943
+
944
+
945
+ Task.prototype.moveDown = function() {
946
+ //console.debug("moveDown", this);
947
+
948
+ //a row below must exist, and cannot move root task
949
+ var row = this.getRow();
950
+ if (row >= this.master.tasks.length - 1 || row==0)
951
+ return false;
952
+
953
+ var ret = false;
954
+
955
+ //find nearest brother
956
+ var newRow;
957
+ for (newRow = row + 1; newRow < this.master.tasks.length; newRow++) {
958
+ if (this.master.tasks[newRow].level <= this.level)
959
+ break;
960
+ }
961
+
962
+ //is brother
963
+ if (this.master.tasks[newRow] && this.master.tasks[newRow].level == this.level) {
964
+ ret = true;
965
+ //find last desc
966
+ for (newRow = newRow + 1; newRow < this.master.tasks.length; newRow++) {
967
+ if (this.master.tasks[newRow].level <= this.level)
968
+ break;
969
+ }
970
+
971
+ //compute descendant
972
+ var descNumber = 0;
973
+ for (var i = row + 1; i < this.master.tasks.length; i++) {
974
+ var desc = this.master.tasks[i];
975
+ if (desc.level > this.level) {
976
+ descNumber++;
977
+ } else {
978
+ break;
979
+ }
980
+ }
981
+
982
+ //move in memory
983
+ var blockToMove = this.master.tasks.splice(row, descNumber + 1);
984
+ var top = this.master.tasks.splice(0, newRow - descNumber - 1);
985
+ this.master.tasks = [].concat(top, blockToMove, this.master.tasks);
986
+
987
+
988
+ //move on dom
989
+ var rows = this.master.editor.element.find("tr[taskid]");
990
+ var aft = rows.eq(newRow - 1);
991
+ var domBlockToMove = rows.slice(row, row + descNumber + 1);
992
+ aft.after(domBlockToMove);
993
+
994
+ //recompute depends string
995
+ this.master.updateDependsStrings();
996
+ }
997
+
998
+ return ret;
999
+ };
1000
+
1001
+
1002
+ //<%------------------------------------------------------------------------ LINKS OBJECT ---------------------------------------------------------------%>
1003
+ function Link(taskFrom, taskTo, lagInWorkingDays) {
1004
+ this.from = taskFrom;
1005
+ this.to = taskTo;
1006
+ this.lag = lagInWorkingDays;
1007
+ }
1008
+
1009
+
1010
+ //<%------------------------------------------------------------------------ ASSIGNMENT ---------------------------------------------------------------%>
1011
+ function Assignment(id, resourceId, roleId, effort) {
1012
+ this.id = id;
1013
+ this.resourceId = resourceId;
1014
+ this.roleId = roleId;
1015
+ this.effort = effort;
1016
+ }
1017
+
1018
+
1019
+ //<%------------------------------------------------------------------------ RESOURCE ---------------------------------------------------------------%>
1020
+ function Resource(id, name) {
1021
+ this.id = id;
1022
+ this.name = name;
1023
+ }
1024
+
1025
+
1026
+ //<%------------------------------------------------------------------------ ROLE ---------------------------------------------------------------%>
1027
+ function Role(id, name) {
1028
+ this.id = id;
1029
+ this.name = name;
1030
+ }
1031
+
1032
+
1033
+
1034
+