gantt-lib 0.110.1 → 0.112.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/core/scheduling/index.d.mts +1 -1
- package/dist/core/scheduling/index.d.ts +1 -1
- package/dist/core/scheduling/index.js +214 -209
- package/dist/core/scheduling/index.js.map +1 -1
- package/dist/core/scheduling/index.mjs +213 -209
- package/dist/core/scheduling/index.mjs.map +1 -1
- package/dist/{index-Ci6WiKhW.d.mts → index-D1s_8MxS.d.mts} +18 -5
- package/dist/{index-Ci6WiKhW.d.ts → index-D1s_8MxS.d.ts} +18 -5
- package/dist/index.css.map +1 -1
- package/dist/index.d.mts +56 -5
- package/dist/index.d.ts +56 -5
- package/dist/index.js +1836 -649
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1938 -752
- package/dist/index.mjs.map +1 -1
- package/dist/styles.css +225 -0
- package/package.json +1 -1
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { D as DAY_MS, e as DependencyError, L as LinkType,
|
|
1
|
+
export { a7 as CascadeContext, D as DAY_MS, e as DependencyError, L as LinkType, a8 as ScheduleCommandOptions, a9 as ScheduleCommandResult, aa as ScheduleDependency, ab as ScheduleTask, ac as ScheduleTaskUpdate, T as Task, ad as TaskDependency, V as ValidationResult, ae as addBusinessDays, n as alignToWorkingDay, o as areTasksHierarchicallyRelated, p as buildAdjacencyList, q as buildTaskRangeFromEnd, r as buildTaskRangeFromStart, s as calculateSuccessorDate, t as cascadeByLinks, u as clampTaskRangeForIncomingFS, v as computeLagFromDates, w as computeParentDates, x as computeParentProgress, af as createCascadeContext, y as detectCycles, z as findParentId, A as getAllDependencyEdges, C as getAllDescendants, E as getBusinessDayOffset, ag as getBusinessDaysCount, F as getChildren, H as getDependencyLag, I as getSuccessorChain, J as getTaskDuration, K as getTransitiveCascadeChain, N as isAncestorTask, O as isTaskParent, P as moveTaskRange, Q as moveTaskWithCascade, S as normalizeDependencyLag, U as normalizePredecessorDates, X as normalizeTaskDependencyLags, Y as normalizeUTCDate, Z as parseDateOnly, _ as recalculateIncomingLags, $ as recalculateProjectSchedule, a0 as recalculateTaskFromDependencies, a1 as reflowTasksOnModeSwitch, a2 as removeDependenciesBetweenTasks, a3 as resizeTaskWithCascade, a4 as shiftBusinessDayOffset, ah as subtractBusinessDays, a5 as universalCascade, a6 as validateDependencies } from '../../index-D1s_8MxS.mjs';
|
|
2
2
|
import 'react';
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { D as DAY_MS, e as DependencyError, L as LinkType,
|
|
1
|
+
export { a7 as CascadeContext, D as DAY_MS, e as DependencyError, L as LinkType, a8 as ScheduleCommandOptions, a9 as ScheduleCommandResult, aa as ScheduleDependency, ab as ScheduleTask, ac as ScheduleTaskUpdate, T as Task, ad as TaskDependency, V as ValidationResult, ae as addBusinessDays, n as alignToWorkingDay, o as areTasksHierarchicallyRelated, p as buildAdjacencyList, q as buildTaskRangeFromEnd, r as buildTaskRangeFromStart, s as calculateSuccessorDate, t as cascadeByLinks, u as clampTaskRangeForIncomingFS, v as computeLagFromDates, w as computeParentDates, x as computeParentProgress, af as createCascadeContext, y as detectCycles, z as findParentId, A as getAllDependencyEdges, C as getAllDescendants, E as getBusinessDayOffset, ag as getBusinessDaysCount, F as getChildren, H as getDependencyLag, I as getSuccessorChain, J as getTaskDuration, K as getTransitiveCascadeChain, N as isAncestorTask, O as isTaskParent, P as moveTaskRange, Q as moveTaskWithCascade, S as normalizeDependencyLag, U as normalizePredecessorDates, X as normalizeTaskDependencyLags, Y as normalizeUTCDate, Z as parseDateOnly, _ as recalculateIncomingLags, $ as recalculateProjectSchedule, a0 as recalculateTaskFromDependencies, a1 as reflowTasksOnModeSwitch, a2 as removeDependenciesBetweenTasks, a3 as resizeTaskWithCascade, a4 as shiftBusinessDayOffset, ah as subtractBusinessDays, a5 as universalCascade, a6 as validateDependencies } from '../../index-D1s_8MxS.js';
|
|
2
2
|
import 'react';
|
|
@@ -33,6 +33,7 @@ __export(scheduling_exports, {
|
|
|
33
33
|
computeLagFromDates: () => computeLagFromDates,
|
|
34
34
|
computeParentDates: () => computeParentDates,
|
|
35
35
|
computeParentProgress: () => computeParentProgress,
|
|
36
|
+
createCascadeContext: () => createCascadeContext,
|
|
36
37
|
detectCycles: () => detectCycles,
|
|
37
38
|
findParentId: () => findParentId,
|
|
38
39
|
getAllDependencyEdges: () => getAllDependencyEdges,
|
|
@@ -276,126 +277,6 @@ function calculateSuccessorDate(predecessorStart, predecessorEnd, linkType, lag
|
|
|
276
277
|
return shiftBusinessDayOffset(anchorDate, offset, weekendPredicate);
|
|
277
278
|
}
|
|
278
279
|
|
|
279
|
-
// src/core/scheduling/hierarchy.ts
|
|
280
|
-
function getChildren(parentId, tasks) {
|
|
281
|
-
return tasks.filter((t) => t.parentId === parentId);
|
|
282
|
-
}
|
|
283
|
-
function isTaskParent(taskId, tasks) {
|
|
284
|
-
return tasks.some((t) => t.parentId === taskId);
|
|
285
|
-
}
|
|
286
|
-
function computeParentDates(parentId, tasks) {
|
|
287
|
-
const children = getChildren(parentId, tasks);
|
|
288
|
-
if (children.length === 0) {
|
|
289
|
-
const parent = tasks.find((t) => t.id === parentId);
|
|
290
|
-
const start = parent ? new Date(parent.startDate) : /* @__PURE__ */ new Date();
|
|
291
|
-
const end = parent ? new Date(parent.endDate) : /* @__PURE__ */ new Date();
|
|
292
|
-
return { startDate: start, endDate: end };
|
|
293
|
-
}
|
|
294
|
-
const startDates = children.map((c) => new Date(c.startDate));
|
|
295
|
-
const endDates = children.map((c) => new Date(c.endDate));
|
|
296
|
-
const minTime = Math.min(...startDates.map((d) => d.getTime()));
|
|
297
|
-
const maxTime = Math.max(...endDates.map((d) => d.getTime()));
|
|
298
|
-
return {
|
|
299
|
-
startDate: new Date(minTime),
|
|
300
|
-
endDate: new Date(maxTime)
|
|
301
|
-
};
|
|
302
|
-
}
|
|
303
|
-
function computeParentProgress(parentId, tasks) {
|
|
304
|
-
const children = getChildren(parentId, tasks);
|
|
305
|
-
if (children.length === 0) {
|
|
306
|
-
return 0;
|
|
307
|
-
}
|
|
308
|
-
const DAY_MS2 = 24 * 60 * 60 * 1e3;
|
|
309
|
-
let totalWeight = 0;
|
|
310
|
-
let weightedSum = 0;
|
|
311
|
-
for (const child of children) {
|
|
312
|
-
const start = new Date(child.startDate).getTime();
|
|
313
|
-
const end = new Date(child.endDate).getTime();
|
|
314
|
-
const duration = (end - start + DAY_MS2) / DAY_MS2;
|
|
315
|
-
const progress = child.progress ?? 0;
|
|
316
|
-
totalWeight += duration;
|
|
317
|
-
weightedSum += duration * progress;
|
|
318
|
-
}
|
|
319
|
-
if (totalWeight === 0) {
|
|
320
|
-
return 0;
|
|
321
|
-
}
|
|
322
|
-
return Math.round(weightedSum / totalWeight * 10) / 10;
|
|
323
|
-
}
|
|
324
|
-
function getAllDescendants(parentId, tasks) {
|
|
325
|
-
const descendants = [];
|
|
326
|
-
const visited = /* @__PURE__ */ new Set();
|
|
327
|
-
function collectChildren(taskId) {
|
|
328
|
-
if (visited.has(taskId)) return;
|
|
329
|
-
visited.add(taskId);
|
|
330
|
-
const children = getChildren(taskId, tasks);
|
|
331
|
-
for (const child of children) {
|
|
332
|
-
descendants.push(child);
|
|
333
|
-
collectChildren(child.id);
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
collectChildren(parentId);
|
|
337
|
-
return descendants;
|
|
338
|
-
}
|
|
339
|
-
function getAllDependencyEdges(tasks) {
|
|
340
|
-
const edges = [];
|
|
341
|
-
for (const task of tasks) {
|
|
342
|
-
if (task.dependencies) {
|
|
343
|
-
for (const dep of task.dependencies) {
|
|
344
|
-
edges.push({
|
|
345
|
-
predecessorId: dep.taskId,
|
|
346
|
-
successorId: task.id,
|
|
347
|
-
type: dep.type,
|
|
348
|
-
lag: dep.lag ?? 0
|
|
349
|
-
});
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
return edges;
|
|
354
|
-
}
|
|
355
|
-
function removeDependenciesBetweenTasks(taskId1, taskId2, tasks) {
|
|
356
|
-
return tasks.map((task) => {
|
|
357
|
-
if (task.id === taskId1 || task.id === taskId2) {
|
|
358
|
-
if (!task.dependencies) return task;
|
|
359
|
-
const otherTaskId = task.id === taskId1 ? taskId2 : taskId1;
|
|
360
|
-
const filteredDependencies = task.dependencies.filter((dep) => dep.taskId !== otherTaskId);
|
|
361
|
-
if (filteredDependencies.length === task.dependencies.length) {
|
|
362
|
-
return task;
|
|
363
|
-
}
|
|
364
|
-
return {
|
|
365
|
-
...task,
|
|
366
|
-
dependencies: filteredDependencies.length > 0 ? filteredDependencies : void 0
|
|
367
|
-
};
|
|
368
|
-
}
|
|
369
|
-
return task;
|
|
370
|
-
});
|
|
371
|
-
}
|
|
372
|
-
function findParentId(taskId, tasks) {
|
|
373
|
-
const task = tasks.find((t) => t.id === taskId);
|
|
374
|
-
return task?.parentId;
|
|
375
|
-
}
|
|
376
|
-
function isAncestorTask(ancestorId, taskId, tasks) {
|
|
377
|
-
const taskById = new Map(tasks.map((task) => [task.id, task]));
|
|
378
|
-
const visited = /* @__PURE__ */ new Set();
|
|
379
|
-
let current = taskById.get(taskId);
|
|
380
|
-
while (current?.parentId) {
|
|
381
|
-
if (current.parentId === ancestorId) {
|
|
382
|
-
return true;
|
|
383
|
-
}
|
|
384
|
-
if (visited.has(current.parentId)) {
|
|
385
|
-
return false;
|
|
386
|
-
}
|
|
387
|
-
visited.add(current.parentId);
|
|
388
|
-
current = taskById.get(current.parentId);
|
|
389
|
-
}
|
|
390
|
-
return false;
|
|
391
|
-
}
|
|
392
|
-
function areTasksHierarchicallyRelated(taskId1, taskId2, tasks) {
|
|
393
|
-
if (taskId1 === taskId2) {
|
|
394
|
-
return true;
|
|
395
|
-
}
|
|
396
|
-
return isAncestorTask(taskId1, taskId2, tasks) || isAncestorTask(taskId2, taskId1, tasks);
|
|
397
|
-
}
|
|
398
|
-
|
|
399
280
|
// src/core/scheduling/commands.ts
|
|
400
281
|
function buildTaskRangeFromStart(startDate, duration, businessDays = false, weekendPredicate, snapDirection = 1) {
|
|
401
282
|
const normalizedStart = businessDays && weekendPredicate ? alignToWorkingDay(startDate, snapDirection, weekendPredicate) : normalizeUTCDate(startDate);
|
|
@@ -434,7 +315,7 @@ function moveTaskRange(originalStart, originalEnd, proposedStart, businessDays =
|
|
|
434
315
|
snapDirection
|
|
435
316
|
);
|
|
436
317
|
}
|
|
437
|
-
function clampTaskRangeForIncomingFS(task, proposedStart, proposedEnd, allTasks, businessDays = false, weekendPredicate) {
|
|
318
|
+
function clampTaskRangeForIncomingFS(task, proposedStart, proposedEnd, allTasks, businessDays = false, weekendPredicate, taskById) {
|
|
438
319
|
if (!task.dependencies?.length) {
|
|
439
320
|
return { start: proposedStart, end: proposedEnd };
|
|
440
321
|
}
|
|
@@ -443,7 +324,7 @@ function clampTaskRangeForIncomingFS(task, proposedStart, proposedEnd, allTasks,
|
|
|
443
324
|
if (dep.type !== "FS") {
|
|
444
325
|
continue;
|
|
445
326
|
}
|
|
446
|
-
const predecessor = allTasks.find((candidate) => candidate.id === dep.taskId);
|
|
327
|
+
const predecessor = taskById?.get(dep.taskId) ?? allTasks.find((candidate) => candidate.id === dep.taskId);
|
|
447
328
|
if (!predecessor) {
|
|
448
329
|
continue;
|
|
449
330
|
}
|
|
@@ -476,10 +357,10 @@ function clampTaskRangeForIncomingFS(task, proposedStart, proposedEnd, allTasks,
|
|
|
476
357
|
weekendPredicate
|
|
477
358
|
);
|
|
478
359
|
}
|
|
479
|
-
function recalculateIncomingLags(task, newStartDate, newEndDate, allTasks, businessDays = false, weekendPredicate) {
|
|
360
|
+
function recalculateIncomingLags(task, newStartDate, newEndDate, allTasks, businessDays = false, weekendPredicate, taskById) {
|
|
480
361
|
if (!task.dependencies) return [];
|
|
481
362
|
return task.dependencies.map((dep) => {
|
|
482
|
-
const predecessor = allTasks.find((candidate) => candidate.id === dep.taskId);
|
|
363
|
+
const predecessor = taskById?.get(dep.taskId) ?? allTasks.find((candidate) => candidate.id === dep.taskId);
|
|
483
364
|
if (!predecessor) {
|
|
484
365
|
return { ...dep, lag: getDependencyLag(dep) };
|
|
485
366
|
}
|
|
@@ -507,35 +388,45 @@ function parseCascadeDateInput(date) {
|
|
|
507
388
|
}
|
|
508
389
|
return normalizeUTCDate(/* @__PURE__ */ new Date(`${date.split("T")[0]}T00:00:00.000Z`));
|
|
509
390
|
}
|
|
510
|
-
function
|
|
511
|
-
const
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
}
|
|
391
|
+
function createCascadeContext(allTasks) {
|
|
392
|
+
const childrenByParentId = /* @__PURE__ */ new Map();
|
|
393
|
+
const dependentsByPredecessorId = /* @__PURE__ */ new Map();
|
|
394
|
+
const taskById = /* @__PURE__ */ new Map();
|
|
515
395
|
for (const task of allTasks) {
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
successorMap.set(dep.taskId, list);
|
|
522
|
-
}
|
|
396
|
+
taskById.set(task.id, task);
|
|
397
|
+
if (task.parentId) {
|
|
398
|
+
const children = childrenByParentId.get(task.parentId) ?? [];
|
|
399
|
+
children.push(task);
|
|
400
|
+
childrenByParentId.set(task.parentId, children);
|
|
523
401
|
}
|
|
402
|
+
if (!task.dependencies) continue;
|
|
403
|
+
task.dependencies.forEach((dependency, dependencyIndex) => {
|
|
404
|
+
const dependents = dependentsByPredecessorId.get(dependency.taskId) ?? [];
|
|
405
|
+
dependents.push({ task, dependencyIndex });
|
|
406
|
+
dependentsByPredecessorId.set(dependency.taskId, dependents);
|
|
407
|
+
});
|
|
524
408
|
}
|
|
525
|
-
|
|
409
|
+
return { childrenByParentId, dependentsByPredecessorId, taskById };
|
|
410
|
+
}
|
|
411
|
+
function getSuccessorChain(draggedTaskId, allTasks, linkTypes = ["FS"]) {
|
|
412
|
+
const { dependentsByPredecessorId, taskById } = createCascadeContext(allTasks);
|
|
526
413
|
const visited = /* @__PURE__ */ new Set();
|
|
527
414
|
const queue = [draggedTaskId];
|
|
528
415
|
const chain = [];
|
|
529
416
|
visited.add(draggedTaskId);
|
|
530
417
|
while (queue.length > 0) {
|
|
531
418
|
const current = queue.shift();
|
|
532
|
-
const successors =
|
|
533
|
-
for (const
|
|
419
|
+
const successors = dependentsByPredecessorId.get(current) ?? [];
|
|
420
|
+
for (const { task } of successors) {
|
|
421
|
+
if (!linkTypes.includes(task.dependencies?.find((dependency) => dependency.taskId === current)?.type ?? "FS")) {
|
|
422
|
+
continue;
|
|
423
|
+
}
|
|
424
|
+
const sid = task.id;
|
|
534
425
|
if (!visited.has(sid)) {
|
|
535
426
|
visited.add(sid);
|
|
536
|
-
const
|
|
537
|
-
if (
|
|
538
|
-
chain.push(
|
|
427
|
+
const successorTask = taskById.get(sid);
|
|
428
|
+
if (successorTask) {
|
|
429
|
+
chain.push(successorTask);
|
|
539
430
|
queue.push(sid);
|
|
540
431
|
}
|
|
541
432
|
}
|
|
@@ -544,7 +435,7 @@ function getSuccessorChain(draggedTaskId, allTasks, linkTypes = ["FS"]) {
|
|
|
544
435
|
return chain;
|
|
545
436
|
}
|
|
546
437
|
function cascadeByLinks(movedTaskId, newStart, newEnd, allTasks, skipChildCascade = false) {
|
|
547
|
-
const taskById =
|
|
438
|
+
const { childrenByParentId, dependentsByPredecessorId, taskById } = createCascadeContext(allTasks);
|
|
548
439
|
const updatedDates = /* @__PURE__ */ new Map();
|
|
549
440
|
updatedDates.set(movedTaskId, { start: newStart, end: newEnd });
|
|
550
441
|
const result = [];
|
|
@@ -554,7 +445,7 @@ function cascadeByLinks(movedTaskId, newStart, newEnd, allTasks, skipChildCascad
|
|
|
554
445
|
const currentId = queue.shift();
|
|
555
446
|
const { start: predStart, end: predEnd } = updatedDates.get(currentId);
|
|
556
447
|
if (!skipChildCascade) {
|
|
557
|
-
const children =
|
|
448
|
+
const children = childrenByParentId.get(currentId) ?? [];
|
|
558
449
|
for (const child of children) {
|
|
559
450
|
if (visited.has(child.id) || child.locked) continue;
|
|
560
451
|
const origStart = new Date(child.startDate);
|
|
@@ -577,64 +468,52 @@ function cascadeByLinks(movedTaskId, newStart, newEnd, allTasks, skipChildCascad
|
|
|
577
468
|
queue.push(child.id);
|
|
578
469
|
}
|
|
579
470
|
}
|
|
580
|
-
|
|
471
|
+
const dependents = dependentsByPredecessorId.get(currentId) ?? [];
|
|
472
|
+
for (const { task, dependencyIndex } of dependents) {
|
|
581
473
|
if (visited.has(task.id) || !task.dependencies || task.locked) continue;
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
}
|
|
610
|
-
visited.add(task.id);
|
|
611
|
-
updatedDates.set(task.id, { start: newSuccStart, end: newSuccEnd });
|
|
612
|
-
result.push(normalizeTaskDependencyLags({
|
|
613
|
-
...task,
|
|
614
|
-
startDate: newSuccStart.toISOString().split("T")[0],
|
|
615
|
-
endDate: newSuccEnd.toISOString().split("T")[0]
|
|
616
|
-
}));
|
|
617
|
-
queue.push(task.id);
|
|
618
|
-
break;
|
|
474
|
+
const dep = task.dependencies[dependencyIndex];
|
|
475
|
+
if (!dep) continue;
|
|
476
|
+
const orig = taskById.get(task.id);
|
|
477
|
+
const origStart = new Date(orig.startDate);
|
|
478
|
+
const origEnd = new Date(orig.endDate);
|
|
479
|
+
const duration = getTaskDuration(origStart, origEnd);
|
|
480
|
+
const currentTask = taskById.get(currentId);
|
|
481
|
+
const { predStart: normalizedPredStart, predEnd: normalizedPredEnd } = normalizePredecessorDates(
|
|
482
|
+
{
|
|
483
|
+
startDate: predStart,
|
|
484
|
+
endDate: predEnd,
|
|
485
|
+
type: currentTask.type
|
|
486
|
+
},
|
|
487
|
+
parseCascadeDateInput
|
|
488
|
+
);
|
|
489
|
+
const constraintDate = calculateSuccessorDate(
|
|
490
|
+
normalizedPredStart,
|
|
491
|
+
normalizedPredEnd,
|
|
492
|
+
dep.type,
|
|
493
|
+
getDependencyLag(dep)
|
|
494
|
+
);
|
|
495
|
+
let newSuccStart;
|
|
496
|
+
let newSuccEnd;
|
|
497
|
+
if (dep.type === "FS" || dep.type === "SS") {
|
|
498
|
+
({ start: newSuccStart, end: newSuccEnd } = buildTaskRangeFromStart(constraintDate, duration));
|
|
499
|
+
} else {
|
|
500
|
+
({ start: newSuccStart, end: newSuccEnd } = buildTaskRangeFromEnd(constraintDate, duration));
|
|
619
501
|
}
|
|
502
|
+
visited.add(task.id);
|
|
503
|
+
updatedDates.set(task.id, { start: newSuccStart, end: newSuccEnd });
|
|
504
|
+
result.push(normalizeTaskDependencyLags({
|
|
505
|
+
...task,
|
|
506
|
+
startDate: newSuccStart.toISOString().split("T")[0],
|
|
507
|
+
endDate: newSuccEnd.toISOString().split("T")[0]
|
|
508
|
+
}));
|
|
509
|
+
queue.push(task.id);
|
|
620
510
|
}
|
|
621
511
|
}
|
|
622
512
|
return result;
|
|
623
513
|
}
|
|
624
514
|
function getTransitiveCascadeChain(changedTaskId, allTasks, firstLevelLinkTypes) {
|
|
625
|
-
const
|
|
626
|
-
|
|
627
|
-
allTypesSuccessorMap.set(task.id, []);
|
|
628
|
-
}
|
|
629
|
-
for (const task of allTasks) {
|
|
630
|
-
if (!task.dependencies) continue;
|
|
631
|
-
for (const dep of task.dependencies) {
|
|
632
|
-
const list = allTypesSuccessorMap.get(dep.taskId) ?? [];
|
|
633
|
-
list.push(task);
|
|
634
|
-
allTypesSuccessorMap.set(dep.taskId, list);
|
|
635
|
-
}
|
|
636
|
-
}
|
|
637
|
-
const directChildren = getChildren(changedTaskId, allTasks);
|
|
515
|
+
const { childrenByParentId, dependentsByPredecessorId } = createCascadeContext(allTasks);
|
|
516
|
+
const directChildren = childrenByParentId.get(changedTaskId) ?? [];
|
|
638
517
|
const directSuccessors = getSuccessorChain(changedTaskId, allTasks, firstLevelLinkTypes);
|
|
639
518
|
const initialChain = [...directChildren, ...directSuccessors].filter(
|
|
640
519
|
(task, index, arr) => arr.findIndex((candidate) => candidate.id === task.id) === index
|
|
@@ -644,7 +523,7 @@ function getTransitiveCascadeChain(changedTaskId, allTasks, firstLevelLinkTypes)
|
|
|
644
523
|
const queue = [...initialChain];
|
|
645
524
|
while (queue.length > 0) {
|
|
646
525
|
const current = queue.shift();
|
|
647
|
-
const children =
|
|
526
|
+
const children = childrenByParentId.get(current.id) ?? [];
|
|
648
527
|
for (const child of children) {
|
|
649
528
|
if (!visited.has(child.id)) {
|
|
650
529
|
visited.add(child.id);
|
|
@@ -652,8 +531,8 @@ function getTransitiveCascadeChain(changedTaskId, allTasks, firstLevelLinkTypes)
|
|
|
652
531
|
queue.push(child);
|
|
653
532
|
}
|
|
654
533
|
}
|
|
655
|
-
const successors =
|
|
656
|
-
for (const successor of successors) {
|
|
534
|
+
const successors = dependentsByPredecessorId.get(current.id) ?? [];
|
|
535
|
+
for (const { task: successor } of successors) {
|
|
657
536
|
if (!visited.has(successor.id)) {
|
|
658
537
|
visited.add(successor.id);
|
|
659
538
|
chain.push(successor);
|
|
@@ -663,16 +542,20 @@ function getTransitiveCascadeChain(changedTaskId, allTasks, firstLevelLinkTypes)
|
|
|
663
542
|
}
|
|
664
543
|
return chain;
|
|
665
544
|
}
|
|
666
|
-
function universalCascade(movedTask, newStart, newEnd, allTasks, businessDays = false, weekendPredicate) {
|
|
667
|
-
const
|
|
668
|
-
const
|
|
669
|
-
updatedDates.set(movedTask.id, { start: newStart, end: newEnd });
|
|
670
|
-
const resultMap = /* @__PURE__ */ new Map();
|
|
671
|
-
resultMap.set(movedTask.id, normalizeTaskDependencyLags({
|
|
545
|
+
function universalCascade(movedTask, newStart, newEnd, allTasks, businessDays = false, weekendPredicate, context = createCascadeContext(allTasks)) {
|
|
546
|
+
const { childrenByParentId, dependentsByPredecessorId, taskById } = context;
|
|
547
|
+
const normalizedMovedTask = normalizeTaskDependencyLags({
|
|
672
548
|
...movedTask,
|
|
673
549
|
startDate: newStart.toISOString().split("T")[0],
|
|
674
550
|
endDate: newEnd.toISOString().split("T")[0]
|
|
675
|
-
})
|
|
551
|
+
});
|
|
552
|
+
if (!movedTask.parentId && !childrenByParentId.has(movedTask.id) && !dependentsByPredecessorId.has(movedTask.id)) {
|
|
553
|
+
return [normalizedMovedTask];
|
|
554
|
+
}
|
|
555
|
+
const updatedDates = /* @__PURE__ */ new Map();
|
|
556
|
+
updatedDates.set(movedTask.id, { start: newStart, end: newEnd });
|
|
557
|
+
const resultMap = /* @__PURE__ */ new Map();
|
|
558
|
+
resultMap.set(movedTask.id, normalizedMovedTask);
|
|
676
559
|
const queue = [[movedTask.id, "direct"]];
|
|
677
560
|
const childShifted = /* @__PURE__ */ new Set();
|
|
678
561
|
let iterations = 0;
|
|
@@ -683,7 +566,7 @@ function universalCascade(movedTask, newStart, newEnd, allTasks, businessDays =
|
|
|
683
566
|
const { start: currStart, end: currEnd } = updatedDates.get(currentId);
|
|
684
567
|
const currentOriginal = taskById.get(currentId);
|
|
685
568
|
if (arrivalMode !== "parent-recalc") {
|
|
686
|
-
const children =
|
|
569
|
+
const children = childrenByParentId.get(currentId) ?? [];
|
|
687
570
|
for (const child of children) {
|
|
688
571
|
if (childShifted.has(child.id) || child.locked) continue;
|
|
689
572
|
const parentOrigStart = new Date(currentOriginal.startDate);
|
|
@@ -729,7 +612,7 @@ function universalCascade(movedTask, newStart, newEnd, allTasks, businessDays =
|
|
|
729
612
|
if (parentId) {
|
|
730
613
|
const parent = taskById.get(parentId);
|
|
731
614
|
if (parent && !parent.locked) {
|
|
732
|
-
const siblings =
|
|
615
|
+
const siblings = childrenByParentId.get(parentId) ?? [];
|
|
733
616
|
const siblingPositions = siblings.map((sib) => {
|
|
734
617
|
if (updatedDates.has(sib.id)) return updatedDates.get(sib.id);
|
|
735
618
|
return { start: new Date(sib.startDate), end: new Date(sib.endDate) };
|
|
@@ -748,9 +631,10 @@ function universalCascade(movedTask, newStart, newEnd, allTasks, businessDays =
|
|
|
748
631
|
}
|
|
749
632
|
}
|
|
750
633
|
}
|
|
751
|
-
|
|
634
|
+
const dependents = dependentsByPredecessorId.get(currentId) ?? [];
|
|
635
|
+
for (const { task, dependencyIndex } of dependents) {
|
|
752
636
|
if (task.locked || !task.dependencies) continue;
|
|
753
|
-
const dep = task.dependencies
|
|
637
|
+
const dep = task.dependencies[dependencyIndex];
|
|
754
638
|
if (!dep) continue;
|
|
755
639
|
const origStart = new Date(task.startDate);
|
|
756
640
|
const origEnd = new Date(task.endDate);
|
|
@@ -804,6 +688,126 @@ function universalCascade(movedTask, newStart, newEnd, allTasks, businessDays =
|
|
|
804
688
|
return Array.from(resultMap.values());
|
|
805
689
|
}
|
|
806
690
|
|
|
691
|
+
// src/core/scheduling/hierarchy.ts
|
|
692
|
+
function getChildren(parentId, tasks) {
|
|
693
|
+
return tasks.filter((t) => t.parentId === parentId);
|
|
694
|
+
}
|
|
695
|
+
function isTaskParent(taskId, tasks) {
|
|
696
|
+
return tasks.some((t) => t.parentId === taskId);
|
|
697
|
+
}
|
|
698
|
+
function computeParentDates(parentId, tasks) {
|
|
699
|
+
const children = getChildren(parentId, tasks);
|
|
700
|
+
if (children.length === 0) {
|
|
701
|
+
const parent = tasks.find((t) => t.id === parentId);
|
|
702
|
+
const start = parent ? new Date(parent.startDate) : /* @__PURE__ */ new Date();
|
|
703
|
+
const end = parent ? new Date(parent.endDate) : /* @__PURE__ */ new Date();
|
|
704
|
+
return { startDate: start, endDate: end };
|
|
705
|
+
}
|
|
706
|
+
const startDates = children.map((c) => new Date(c.startDate));
|
|
707
|
+
const endDates = children.map((c) => new Date(c.endDate));
|
|
708
|
+
const minTime = Math.min(...startDates.map((d) => d.getTime()));
|
|
709
|
+
const maxTime = Math.max(...endDates.map((d) => d.getTime()));
|
|
710
|
+
return {
|
|
711
|
+
startDate: new Date(minTime),
|
|
712
|
+
endDate: new Date(maxTime)
|
|
713
|
+
};
|
|
714
|
+
}
|
|
715
|
+
function computeParentProgress(parentId, tasks) {
|
|
716
|
+
const children = getChildren(parentId, tasks);
|
|
717
|
+
if (children.length === 0) {
|
|
718
|
+
return 0;
|
|
719
|
+
}
|
|
720
|
+
const DAY_MS2 = 24 * 60 * 60 * 1e3;
|
|
721
|
+
let totalWeight = 0;
|
|
722
|
+
let weightedSum = 0;
|
|
723
|
+
for (const child of children) {
|
|
724
|
+
const start = new Date(child.startDate).getTime();
|
|
725
|
+
const end = new Date(child.endDate).getTime();
|
|
726
|
+
const duration = (end - start + DAY_MS2) / DAY_MS2;
|
|
727
|
+
const progress = child.progress ?? 0;
|
|
728
|
+
totalWeight += duration;
|
|
729
|
+
weightedSum += duration * progress;
|
|
730
|
+
}
|
|
731
|
+
if (totalWeight === 0) {
|
|
732
|
+
return 0;
|
|
733
|
+
}
|
|
734
|
+
return Math.round(weightedSum / totalWeight * 10) / 10;
|
|
735
|
+
}
|
|
736
|
+
function getAllDescendants(parentId, tasks) {
|
|
737
|
+
const descendants = [];
|
|
738
|
+
const visited = /* @__PURE__ */ new Set();
|
|
739
|
+
function collectChildren(taskId) {
|
|
740
|
+
if (visited.has(taskId)) return;
|
|
741
|
+
visited.add(taskId);
|
|
742
|
+
const children = getChildren(taskId, tasks);
|
|
743
|
+
for (const child of children) {
|
|
744
|
+
descendants.push(child);
|
|
745
|
+
collectChildren(child.id);
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
collectChildren(parentId);
|
|
749
|
+
return descendants;
|
|
750
|
+
}
|
|
751
|
+
function getAllDependencyEdges(tasks) {
|
|
752
|
+
const edges = [];
|
|
753
|
+
for (const task of tasks) {
|
|
754
|
+
if (task.dependencies) {
|
|
755
|
+
for (const dep of task.dependencies) {
|
|
756
|
+
edges.push({
|
|
757
|
+
predecessorId: dep.taskId,
|
|
758
|
+
successorId: task.id,
|
|
759
|
+
type: dep.type,
|
|
760
|
+
lag: dep.lag ?? 0
|
|
761
|
+
});
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
return edges;
|
|
766
|
+
}
|
|
767
|
+
function removeDependenciesBetweenTasks(taskId1, taskId2, tasks) {
|
|
768
|
+
return tasks.map((task) => {
|
|
769
|
+
if (task.id === taskId1 || task.id === taskId2) {
|
|
770
|
+
if (!task.dependencies) return task;
|
|
771
|
+
const otherTaskId = task.id === taskId1 ? taskId2 : taskId1;
|
|
772
|
+
const filteredDependencies = task.dependencies.filter((dep) => dep.taskId !== otherTaskId);
|
|
773
|
+
if (filteredDependencies.length === task.dependencies.length) {
|
|
774
|
+
return task;
|
|
775
|
+
}
|
|
776
|
+
return {
|
|
777
|
+
...task,
|
|
778
|
+
dependencies: filteredDependencies.length > 0 ? filteredDependencies : void 0
|
|
779
|
+
};
|
|
780
|
+
}
|
|
781
|
+
return task;
|
|
782
|
+
});
|
|
783
|
+
}
|
|
784
|
+
function findParentId(taskId, tasks) {
|
|
785
|
+
const task = tasks.find((t) => t.id === taskId);
|
|
786
|
+
return task?.parentId;
|
|
787
|
+
}
|
|
788
|
+
function isAncestorTask(ancestorId, taskId, tasks) {
|
|
789
|
+
const taskById = new Map(tasks.map((task) => [task.id, task]));
|
|
790
|
+
const visited = /* @__PURE__ */ new Set();
|
|
791
|
+
let current = taskById.get(taskId);
|
|
792
|
+
while (current?.parentId) {
|
|
793
|
+
if (current.parentId === ancestorId) {
|
|
794
|
+
return true;
|
|
795
|
+
}
|
|
796
|
+
if (visited.has(current.parentId)) {
|
|
797
|
+
return false;
|
|
798
|
+
}
|
|
799
|
+
visited.add(current.parentId);
|
|
800
|
+
current = taskById.get(current.parentId);
|
|
801
|
+
}
|
|
802
|
+
return false;
|
|
803
|
+
}
|
|
804
|
+
function areTasksHierarchicallyRelated(taskId1, taskId2, tasks) {
|
|
805
|
+
if (taskId1 === taskId2) {
|
|
806
|
+
return true;
|
|
807
|
+
}
|
|
808
|
+
return isAncestorTask(taskId1, taskId2, tasks) || isAncestorTask(taskId2, taskId1, tasks);
|
|
809
|
+
}
|
|
810
|
+
|
|
807
811
|
// src/core/scheduling/execute.ts
|
|
808
812
|
function toIsoDate(date) {
|
|
809
813
|
return date.toISOString().split("T")[0];
|
|
@@ -1259,6 +1263,7 @@ function validateDependencies(tasks) {
|
|
|
1259
1263
|
computeLagFromDates,
|
|
1260
1264
|
computeParentDates,
|
|
1261
1265
|
computeParentProgress,
|
|
1266
|
+
createCascadeContext,
|
|
1262
1267
|
detectCycles,
|
|
1263
1268
|
findParentId,
|
|
1264
1269
|
getAllDependencyEdges,
|