@outbuild-company/schedule-core 1.6.0 → 1.6.2
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/cdn/schedule-core.global.js +1 -1
- package/dist/cdn/schedule-core.global.js.map +1 -1
- package/dist/index.cjs +1721 -507
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +121 -6
- package/dist/index.d.ts +121 -6
- package/dist/index.js +1721 -507
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
package/dist/index.cjs
CHANGED
|
@@ -212,19 +212,19 @@ function dispatchSelectionReplace(action, deps) {
|
|
|
212
212
|
}
|
|
213
213
|
|
|
214
214
|
// src/dispatch/shared/apply-visible-set.ts
|
|
215
|
-
function applyVisibleSet(adapter, visibleIds) {
|
|
215
|
+
function applyVisibleSet(adapter, visibleIds, activityIds = adapter.getAllIds()) {
|
|
216
216
|
const viewState = [];
|
|
217
|
-
|
|
217
|
+
for (const activityId of activityIds) {
|
|
218
218
|
const id = String(activityId);
|
|
219
219
|
const willBeVisible = visibleIds.has(id);
|
|
220
220
|
const before = adapter.isVisible(id);
|
|
221
|
-
if (before === willBeVisible)
|
|
221
|
+
if (before === willBeVisible) continue;
|
|
222
222
|
adapter.setVisible(id, willBeVisible);
|
|
223
223
|
viewState.push({
|
|
224
224
|
activityId: id,
|
|
225
225
|
visible: { before, after: willBeVisible }
|
|
226
226
|
});
|
|
227
|
-
}
|
|
227
|
+
}
|
|
228
228
|
return viewState;
|
|
229
229
|
}
|
|
230
230
|
|
|
@@ -498,6 +498,12 @@ function overlapsRange(activity, range) {
|
|
|
498
498
|
const windowStartsInside = windowStart >= taskStart && windowStart <= taskEnd;
|
|
499
499
|
return fullyInside || startsInside || windowStartsInside;
|
|
500
500
|
}
|
|
501
|
+
function matchesFilter(activity, filter, context) {
|
|
502
|
+
const passesCriteria = matchesCriteria(activity, filter, context);
|
|
503
|
+
const range = filter.dateRange;
|
|
504
|
+
const passesRange = range === void 0 || overlapsRange(activity, range);
|
|
505
|
+
return passesCriteria && passesRange;
|
|
506
|
+
}
|
|
501
507
|
function addAncestors(matchedId, parentOf, visibleIds) {
|
|
502
508
|
let ancestorId = parentOf(matchedId);
|
|
503
509
|
while (ancestorId !== null && !visibleIds.has(String(ancestorId))) {
|
|
@@ -507,13 +513,10 @@ function addAncestors(matchedId, parentOf, visibleIds) {
|
|
|
507
513
|
}
|
|
508
514
|
function evaluateVisibleIds(input) {
|
|
509
515
|
const { activities, parentOf, filter, context } = input;
|
|
510
|
-
const range = filter.dateRange;
|
|
511
516
|
const visibleIds = /* @__PURE__ */ new Set();
|
|
512
517
|
const matchedIds = [];
|
|
513
518
|
for (const activity of activities) {
|
|
514
|
-
|
|
515
|
-
const passesRange = range === void 0 || overlapsRange(activity, range);
|
|
516
|
-
if (passesCriteria && passesRange) {
|
|
519
|
+
if (matchesFilter(activity, filter, context)) {
|
|
517
520
|
visibleIds.add(String(activity.id));
|
|
518
521
|
matchedIds.push(activity.id);
|
|
519
522
|
}
|
|
@@ -523,6 +526,114 @@ function evaluateVisibleIds(input) {
|
|
|
523
526
|
}
|
|
524
527
|
return visibleIds;
|
|
525
528
|
}
|
|
529
|
+
function seedFilterProjection(input) {
|
|
530
|
+
const { activities, parentOf, filter, context } = input;
|
|
531
|
+
const matchedIds = /* @__PURE__ */ new Set();
|
|
532
|
+
const pinnedVisibleIds = /* @__PURE__ */ new Set();
|
|
533
|
+
const visibleReferenceCounts = /* @__PURE__ */ new Map();
|
|
534
|
+
const parentIds = /* @__PURE__ */ new Map();
|
|
535
|
+
const remainingChildren = /* @__PURE__ */ new Map();
|
|
536
|
+
for (const activity of activities) {
|
|
537
|
+
const activityId = String(activity.id);
|
|
538
|
+
const parentId = parentOf(activityId);
|
|
539
|
+
parentIds.set(activityId, parentId);
|
|
540
|
+
remainingChildren.set(activityId, remainingChildren.get(activityId) ?? 0);
|
|
541
|
+
if (parentId !== null) {
|
|
542
|
+
remainingChildren.set(
|
|
543
|
+
parentId,
|
|
544
|
+
(remainingChildren.get(parentId) ?? 0) + 1
|
|
545
|
+
);
|
|
546
|
+
}
|
|
547
|
+
const isMatch = matchesFilter(activity, filter, context);
|
|
548
|
+
const isPinned = input.pinnedVisibleIds?.has(activityId) === true;
|
|
549
|
+
visibleReferenceCounts.set(
|
|
550
|
+
activityId,
|
|
551
|
+
(isMatch ? 1 : 0) + (isPinned ? 1 : 0)
|
|
552
|
+
);
|
|
553
|
+
if (isMatch) matchedIds.add(activityId);
|
|
554
|
+
if (isPinned) pinnedVisibleIds.add(activityId);
|
|
555
|
+
}
|
|
556
|
+
return {
|
|
557
|
+
matchedIds,
|
|
558
|
+
pinnedVisibleIds,
|
|
559
|
+
visibleReferenceCounts,
|
|
560
|
+
parentIds,
|
|
561
|
+
remainingChildren
|
|
562
|
+
};
|
|
563
|
+
}
|
|
564
|
+
function propagateVisibleReferenceCounts(seed2) {
|
|
565
|
+
const { visibleReferenceCounts, parentIds, remainingChildren } = seed2;
|
|
566
|
+
const pendingIds = [];
|
|
567
|
+
for (const activityId of parentIds.keys()) {
|
|
568
|
+
if (remainingChildren.get(activityId) === 0) pendingIds.push(activityId);
|
|
569
|
+
}
|
|
570
|
+
while (pendingIds.length > 0) {
|
|
571
|
+
const activityId = pendingIds.pop();
|
|
572
|
+
if (activityId === void 0) break;
|
|
573
|
+
const parentId = parentIds.get(activityId);
|
|
574
|
+
if (parentId === null || parentId === void 0) continue;
|
|
575
|
+
const referenceCount = visibleReferenceCounts.get(activityId) ?? 0;
|
|
576
|
+
visibleReferenceCounts.set(
|
|
577
|
+
parentId,
|
|
578
|
+
(visibleReferenceCounts.get(parentId) ?? 0) + referenceCount
|
|
579
|
+
);
|
|
580
|
+
const remaining = (remainingChildren.get(parentId) ?? 1) - 1;
|
|
581
|
+
remainingChildren.set(parentId, remaining);
|
|
582
|
+
if (remaining === 0) pendingIds.push(parentId);
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
function collectVisibleIds(visibleReferenceCounts) {
|
|
586
|
+
const visibleIds = /* @__PURE__ */ new Set();
|
|
587
|
+
for (const [activityId, referenceCount] of visibleReferenceCounts) {
|
|
588
|
+
if (referenceCount > 0) visibleIds.add(activityId);
|
|
589
|
+
}
|
|
590
|
+
return visibleIds;
|
|
591
|
+
}
|
|
592
|
+
function buildFilterProjection(input) {
|
|
593
|
+
const seed2 = seedFilterProjection(input);
|
|
594
|
+
propagateVisibleReferenceCounts(seed2);
|
|
595
|
+
const visibleIds = collectVisibleIds(seed2.visibleReferenceCounts);
|
|
596
|
+
return {
|
|
597
|
+
filter: input.filter,
|
|
598
|
+
context: input.context,
|
|
599
|
+
matchedIds: seed2.matchedIds,
|
|
600
|
+
pinnedVisibleIds: seed2.pinnedVisibleIds,
|
|
601
|
+
visibleIds,
|
|
602
|
+
visibleReferenceCounts: seed2.visibleReferenceCounts
|
|
603
|
+
};
|
|
604
|
+
}
|
|
605
|
+
function updateFilterProjection(projection, activities, parentOf) {
|
|
606
|
+
const changedVisibilityIds = /* @__PURE__ */ new Set();
|
|
607
|
+
for (const activity of activities) {
|
|
608
|
+
const activityId = String(activity.id);
|
|
609
|
+
changedVisibilityIds.add(activityId);
|
|
610
|
+
const wasMatch = projection.matchedIds.has(activityId);
|
|
611
|
+
const isMatch = matchesFilter(
|
|
612
|
+
activity,
|
|
613
|
+
projection.filter,
|
|
614
|
+
projection.context
|
|
615
|
+
);
|
|
616
|
+
if (wasMatch === isMatch) continue;
|
|
617
|
+
if (isMatch) projection.matchedIds.add(activityId);
|
|
618
|
+
else projection.matchedIds.delete(activityId);
|
|
619
|
+
const referenceDelta = isMatch ? 1 : -1;
|
|
620
|
+
let currentId = activityId;
|
|
621
|
+
while (currentId !== null) {
|
|
622
|
+
const beforeCount = projection.visibleReferenceCounts.get(currentId) ?? 0;
|
|
623
|
+
const afterCount = beforeCount + referenceDelta;
|
|
624
|
+
projection.visibleReferenceCounts.set(currentId, afterCount);
|
|
625
|
+
const wasVisible = beforeCount > 0;
|
|
626
|
+
const isVisible = afterCount > 0;
|
|
627
|
+
if (wasVisible !== isVisible) {
|
|
628
|
+
changedVisibilityIds.add(currentId);
|
|
629
|
+
if (isVisible) projection.visibleIds.add(currentId);
|
|
630
|
+
else projection.visibleIds.delete(currentId);
|
|
631
|
+
}
|
|
632
|
+
currentId = parentOf(currentId);
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
return changedVisibilityIds;
|
|
636
|
+
}
|
|
526
637
|
|
|
527
638
|
// src/internal/filter/context.ts
|
|
528
639
|
var COLLATION_LOCALE = "en";
|
|
@@ -583,17 +694,26 @@ function isValidDateValue(value) {
|
|
|
583
694
|
function isEmptyFilter(filter) {
|
|
584
695
|
return filter.criteria.length === 0 && filter.dateRange === void 0;
|
|
585
696
|
}
|
|
586
|
-
function
|
|
697
|
+
function resolveFilter(adapter, filter, hoursPerDay) {
|
|
587
698
|
const activities = adapter.getAllActivities();
|
|
588
699
|
if (isEmptyFilter(filter)) {
|
|
589
|
-
return
|
|
700
|
+
return {
|
|
701
|
+
visibleIds: evaluateVisibleIds({
|
|
702
|
+
activities,
|
|
703
|
+
parentOf: (activityId) => adapter.getParentId(activityId),
|
|
704
|
+
filter,
|
|
705
|
+
context: buildFilterContext(hoursPerDay)
|
|
706
|
+
}),
|
|
707
|
+
projection: null
|
|
708
|
+
};
|
|
590
709
|
}
|
|
591
|
-
|
|
710
|
+
const projection = buildFilterProjection({
|
|
592
711
|
activities,
|
|
593
712
|
parentOf: (activityId) => adapter.getParentId(activityId),
|
|
594
713
|
filter,
|
|
595
714
|
context: buildFilterContext(hoursPerDay)
|
|
596
715
|
});
|
|
716
|
+
return { visibleIds: projection.visibleIds, projection };
|
|
597
717
|
}
|
|
598
718
|
function dispatchFilterSet(action, deps) {
|
|
599
719
|
const { adapter, hoursPerDay } = deps;
|
|
@@ -607,7 +727,12 @@ function dispatchFilterSet(action, deps) {
|
|
|
607
727
|
dateRange: action.dateRange
|
|
608
728
|
};
|
|
609
729
|
adapter.setActiveFilter(isEmptyFilter(filter) ? null : filter);
|
|
610
|
-
const visibleIds =
|
|
730
|
+
const { visibleIds, projection } = resolveFilter(
|
|
731
|
+
adapter,
|
|
732
|
+
filter,
|
|
733
|
+
hoursPerDay
|
|
734
|
+
);
|
|
735
|
+
adapter.setFilterProjection(projection);
|
|
611
736
|
const viewState = applyVisibleSet(adapter, visibleIds);
|
|
612
737
|
const changes = {
|
|
613
738
|
source: action,
|
|
@@ -678,11 +803,7 @@ function visualIndexInParent(taskId, adapter) {
|
|
|
678
803
|
return siblings.findIndex((id) => String(id) === String(taskId));
|
|
679
804
|
}
|
|
680
805
|
function collectChildrenSnapshots(parentId, adapter) {
|
|
681
|
-
const
|
|
682
|
-
if (key === "0") {
|
|
683
|
-
return adapter.getAllActivities().filter((a) => a.parentId === null);
|
|
684
|
-
}
|
|
685
|
-
const childIds = adapter.getChildren(parentId);
|
|
806
|
+
const childIds = String(parentId) === ROOT_PARENT_ID ? adapter.getRootIds?.() ?? adapter.getChildren(ROOT_PARENT_ID) : adapter.getChildren(parentId);
|
|
686
807
|
const out = [];
|
|
687
808
|
for (const id of childIds) {
|
|
688
809
|
const snap = adapter.getActivity(id);
|
|
@@ -726,6 +847,14 @@ function collectBranchOrder(adapter) {
|
|
|
726
847
|
visit(ROOT_PARENT_ID);
|
|
727
848
|
return branches;
|
|
728
849
|
}
|
|
850
|
+
function collectBranchOrderForParents(adapter, parentIds) {
|
|
851
|
+
const branches = [];
|
|
852
|
+
for (const parentId of parentIds) {
|
|
853
|
+
const childIds = getChildrenInVisualOrder(parentId, adapter);
|
|
854
|
+
if (childIds.length > 1) branches.push({ parentId, childIds });
|
|
855
|
+
}
|
|
856
|
+
return branches;
|
|
857
|
+
}
|
|
729
858
|
|
|
730
859
|
// src/dispatch/order.ts
|
|
731
860
|
function validateRule(rule) {
|
|
@@ -2277,6 +2406,34 @@ function wouldIntroduceCycle(existingLinks, proposedLink) {
|
|
|
2277
2406
|
}
|
|
2278
2407
|
return hasPath(adjacency, tgt, src);
|
|
2279
2408
|
}
|
|
2409
|
+
function hasDirectedCycle(links) {
|
|
2410
|
+
const adjacency = /* @__PURE__ */ new Map();
|
|
2411
|
+
const indegrees = /* @__PURE__ */ new Map();
|
|
2412
|
+
for (const link of links) {
|
|
2413
|
+
const source = String(link.source);
|
|
2414
|
+
const target = String(link.target);
|
|
2415
|
+
const neighbors = adjacency.get(source) ?? [];
|
|
2416
|
+
neighbors.push(target);
|
|
2417
|
+
adjacency.set(source, neighbors);
|
|
2418
|
+
indegrees.set(source, indegrees.get(source) ?? 0);
|
|
2419
|
+
indegrees.set(target, (indegrees.get(target) ?? 0) + 1);
|
|
2420
|
+
}
|
|
2421
|
+
const ready = new Queue();
|
|
2422
|
+
for (const [activityId, indegree] of indegrees) {
|
|
2423
|
+
if (indegree === 0) ready.enqueue(activityId);
|
|
2424
|
+
}
|
|
2425
|
+
let visited = 0;
|
|
2426
|
+
while (!ready.isEmpty()) {
|
|
2427
|
+
const current = ready.dequeue();
|
|
2428
|
+
visited += 1;
|
|
2429
|
+
for (const neighbor of adjacency.get(current) ?? []) {
|
|
2430
|
+
const indegree = indegrees.get(neighbor) - 1;
|
|
2431
|
+
indegrees.set(neighbor, indegree);
|
|
2432
|
+
if (indegree === 0) ready.enqueue(neighbor);
|
|
2433
|
+
}
|
|
2434
|
+
}
|
|
2435
|
+
return visited !== indegrees.size;
|
|
2436
|
+
}
|
|
2280
2437
|
function hasPath(adjacency, from, to) {
|
|
2281
2438
|
if (from === to) return true;
|
|
2282
2439
|
const visited = /* @__PURE__ */ new Set([from]);
|
|
@@ -2306,6 +2463,70 @@ function applyLinkOperation(op, deps) {
|
|
|
2306
2463
|
return applyDelete(op, deps);
|
|
2307
2464
|
}
|
|
2308
2465
|
}
|
|
2466
|
+
function applyLinkCreates(links, deps) {
|
|
2467
|
+
if (links.length === 0) return { applied: true };
|
|
2468
|
+
const existingLinks = deps.port.getAllLinks();
|
|
2469
|
+
const duplicateKeys = /* @__PURE__ */ new Map();
|
|
2470
|
+
const edges = [];
|
|
2471
|
+
for (const link of existingLinks) {
|
|
2472
|
+
const key = linkKey(link);
|
|
2473
|
+
if (!duplicateKeys.has(key)) duplicateKeys.set(key, link.id);
|
|
2474
|
+
edges.push({ source: link.source, target: link.target });
|
|
2475
|
+
}
|
|
2476
|
+
const existingGraphHasCycle = hasDirectedCycle(edges);
|
|
2477
|
+
let rejected = null;
|
|
2478
|
+
for (const { operation, linkId } of links) {
|
|
2479
|
+
if (String(operation.source) === String(operation.target)) {
|
|
2480
|
+
rejected = { applied: false, rejected: "self-link" };
|
|
2481
|
+
break;
|
|
2482
|
+
}
|
|
2483
|
+
if (!deps.port.getActivity(operation.source) || !deps.port.getActivity(operation.target)) {
|
|
2484
|
+
rejected = { applied: false, rejected: "activity-missing" };
|
|
2485
|
+
break;
|
|
2486
|
+
}
|
|
2487
|
+
const key = linkKey(operation);
|
|
2488
|
+
const duplicateId = duplicateKeys.get(key);
|
|
2489
|
+
if (duplicateId !== void 0) {
|
|
2490
|
+
rejected = {
|
|
2491
|
+
applied: false,
|
|
2492
|
+
rejected: "duplicate",
|
|
2493
|
+
linkId: duplicateId
|
|
2494
|
+
};
|
|
2495
|
+
break;
|
|
2496
|
+
}
|
|
2497
|
+
duplicateKeys.set(key, linkId);
|
|
2498
|
+
if (existingGraphHasCycle && // ponytail: preserve legacy sequential semantics; optimize with dynamic
|
|
2499
|
+
// SCC reachability only if corrupt cyclic schedules become measurable.
|
|
2500
|
+
wouldIntroduceCycle(edges, {
|
|
2501
|
+
source: operation.source,
|
|
2502
|
+
target: operation.target
|
|
2503
|
+
})) {
|
|
2504
|
+
return { applied: false, rejected: "cycle" };
|
|
2505
|
+
}
|
|
2506
|
+
edges.push({ source: operation.source, target: operation.target });
|
|
2507
|
+
}
|
|
2508
|
+
if (!existingGraphHasCycle && hasDirectedCycle(edges)) {
|
|
2509
|
+
return { applied: false, rejected: "cycle" };
|
|
2510
|
+
}
|
|
2511
|
+
if (rejected) return rejected;
|
|
2512
|
+
for (const { operation, linkId } of links) {
|
|
2513
|
+
const preserved = deps.preserved?.get(linkId);
|
|
2514
|
+
deps.port.addLink({
|
|
2515
|
+
id: linkId,
|
|
2516
|
+
source: operation.source,
|
|
2517
|
+
target: operation.target,
|
|
2518
|
+
type: operation.type,
|
|
2519
|
+
lag: operation.lag,
|
|
2520
|
+
...preserved?.ganttId === void 0 ? {} : { ganttId: preserved.ganttId },
|
|
2521
|
+
...preserved?.sectorId === void 0 ? {} : { sectorId: preserved.sectorId },
|
|
2522
|
+
...preserved?.proplannerId === void 0 ? {} : { proplannerId: preserved.proplannerId }
|
|
2523
|
+
});
|
|
2524
|
+
}
|
|
2525
|
+
return { applied: true };
|
|
2526
|
+
}
|
|
2527
|
+
function linkKey(link) {
|
|
2528
|
+
return JSON.stringify([String(link.source), String(link.target), link.type]);
|
|
2529
|
+
}
|
|
2309
2530
|
function applyCreate(op, deps) {
|
|
2310
2531
|
const { port } = deps;
|
|
2311
2532
|
if (String(op.source) === String(op.target)) {
|
|
@@ -2429,14 +2650,14 @@ function diffLinks(input) {
|
|
|
2429
2650
|
unresolved.push(spec);
|
|
2430
2651
|
continue;
|
|
2431
2652
|
}
|
|
2432
|
-
const key =
|
|
2653
|
+
const key = linkKey2(source, spec.type);
|
|
2433
2654
|
if (desired.has(key)) continue;
|
|
2434
2655
|
desired.set(key, { source, type: spec.type, lag: spec.lag });
|
|
2435
2656
|
}
|
|
2436
2657
|
const currentByKey = /* @__PURE__ */ new Map();
|
|
2437
2658
|
for (const link of currentLinks) {
|
|
2438
2659
|
if (String(link.target) !== String(targetActivityId)) continue;
|
|
2439
|
-
currentByKey.set(
|
|
2660
|
+
currentByKey.set(linkKey2(link.source, link.type), link);
|
|
2440
2661
|
}
|
|
2441
2662
|
const operations = [];
|
|
2442
2663
|
for (const [key, link] of currentByKey) {
|
|
@@ -2463,7 +2684,7 @@ function diffLinks(input) {
|
|
|
2463
2684
|
}
|
|
2464
2685
|
return { operations, unresolved };
|
|
2465
2686
|
}
|
|
2466
|
-
function
|
|
2687
|
+
function linkKey2(source, type) {
|
|
2467
2688
|
return `${String(source)}::${type}`;
|
|
2468
2689
|
}
|
|
2469
2690
|
function diffOutgoingLinks(input) {
|
|
@@ -2476,14 +2697,14 @@ function diffOutgoingLinks(input) {
|
|
|
2476
2697
|
unresolved.push(spec);
|
|
2477
2698
|
continue;
|
|
2478
2699
|
}
|
|
2479
|
-
const key =
|
|
2700
|
+
const key = linkKey2(target, spec.type);
|
|
2480
2701
|
if (desired.has(key)) continue;
|
|
2481
2702
|
desired.set(key, { target, type: spec.type, lag: spec.lag });
|
|
2482
2703
|
}
|
|
2483
2704
|
const currentByKey = /* @__PURE__ */ new Map();
|
|
2484
2705
|
for (const link of currentLinks) {
|
|
2485
2706
|
if (String(link.source) !== String(sourceActivityId)) continue;
|
|
2486
|
-
currentByKey.set(
|
|
2707
|
+
currentByKey.set(linkKey2(link.target, link.type), link);
|
|
2487
2708
|
}
|
|
2488
2709
|
const operations = [];
|
|
2489
2710
|
for (const [key, link] of currentByKey) {
|
|
@@ -5550,7 +5771,7 @@ var CalculateBackwardParentsLinks = class {
|
|
|
5550
5771
|
return false;
|
|
5551
5772
|
}
|
|
5552
5773
|
return allLinksCalculations.reduce(
|
|
5553
|
-
(
|
|
5774
|
+
(min2, activity) => activity.lf < min2.lf ? activity : min2
|
|
5554
5775
|
);
|
|
5555
5776
|
}
|
|
5556
5777
|
calculateSfRestrictionDates() {
|
|
@@ -7723,13 +7944,13 @@ var LinksConstraintCalculator2 = class extends CalculationsGenericMethods_defaul
|
|
|
7723
7944
|
getMinDateFromLinks(allLinksCalculations) {
|
|
7724
7945
|
try {
|
|
7725
7946
|
if (this.constraint === CONSTRAINT_TYPES.ALAP || this.constraint === CONSTRAINT_TYPES.ASAP) {
|
|
7726
|
-
const
|
|
7727
|
-
(
|
|
7947
|
+
const min2 = allLinksCalculations.reduce(
|
|
7948
|
+
(min3, activity) => activity.lf < min3.lf ? activity : min3
|
|
7728
7949
|
);
|
|
7729
|
-
return
|
|
7950
|
+
return min2;
|
|
7730
7951
|
}
|
|
7731
7952
|
return allLinksCalculations.reduce(
|
|
7732
|
-
(
|
|
7953
|
+
(min2, activity) => activity.ls < min2.ls ? activity : min2
|
|
7733
7954
|
);
|
|
7734
7955
|
} catch (error) {
|
|
7735
7956
|
throw new Error(
|
|
@@ -7739,7 +7960,7 @@ var LinksConstraintCalculator2 = class extends CalculationsGenericMethods_defaul
|
|
|
7739
7960
|
}
|
|
7740
7961
|
getMinLfFromLinks(allLinksCalculations) {
|
|
7741
7962
|
return allLinksCalculations.reduce(
|
|
7742
|
-
(
|
|
7963
|
+
(min2, activity) => activity.lf < min2.lf ? activity : min2
|
|
7743
7964
|
);
|
|
7744
7965
|
}
|
|
7745
7966
|
getMinDateBasedOnConstraint(minCalculatedDateFromLinks, constraintType = this.constraint) {
|
|
@@ -8197,7 +8418,7 @@ var BackwardPath = class extends generic_calculations_default {
|
|
|
8197
8418
|
return false;
|
|
8198
8419
|
}
|
|
8199
8420
|
return allLinksCalculations.reduce(
|
|
8200
|
-
(
|
|
8421
|
+
(min2, activity) => activity.lf < min2.lf ? activity : min2
|
|
8201
8422
|
);
|
|
8202
8423
|
}
|
|
8203
8424
|
};
|
|
@@ -8796,7 +9017,7 @@ var CriticalPath = class {
|
|
|
8796
9017
|
activitiesCount: this.forwardPathCalculations?.size || 0
|
|
8797
9018
|
}
|
|
8798
9019
|
});
|
|
8799
|
-
|
|
9020
|
+
throw error;
|
|
8800
9021
|
}
|
|
8801
9022
|
}
|
|
8802
9023
|
async calculateFloatsAsync(forward, backward, isCurrent) {
|
|
@@ -8817,7 +9038,7 @@ var CriticalPath = class {
|
|
|
8817
9038
|
activitiesCount: this.forwardPathCalculations?.size || 0
|
|
8818
9039
|
}
|
|
8819
9040
|
});
|
|
8820
|
-
|
|
9041
|
+
throw error;
|
|
8821
9042
|
}
|
|
8822
9043
|
}
|
|
8823
9044
|
};
|
|
@@ -8842,16 +9063,41 @@ function constraintDateForCriticalPath(activity, state) {
|
|
|
8842
9063
|
return typedDayIsWorking ? getEffectiveConstraintDate(activity, state) : raw;
|
|
8843
9064
|
}
|
|
8844
9065
|
function toLegacyCalendar(api) {
|
|
9066
|
+
const endDateByKey = /* @__PURE__ */ new Map();
|
|
9067
|
+
const durationByKey = /* @__PURE__ */ new Map();
|
|
9068
|
+
const closestWorkTimeByKey = /* @__PURE__ */ new Map();
|
|
8845
9069
|
return {
|
|
8846
|
-
calculateEndDate: ({ start_date, duration }) =>
|
|
8847
|
-
|
|
9070
|
+
calculateEndDate: ({ start_date, duration }) => {
|
|
9071
|
+
const key = `${start_date.getTime()}|${duration}`;
|
|
9072
|
+
const cached = endDateByKey.get(key);
|
|
9073
|
+
if (cached !== void 0) return new Date(cached);
|
|
9074
|
+
const value = api.calculateEndDate(start_date, duration);
|
|
9075
|
+
endDateByKey.set(key, value.getTime());
|
|
9076
|
+
return value;
|
|
9077
|
+
},
|
|
9078
|
+
calculateDuration: ({ start_date, end_date }) => {
|
|
9079
|
+
const key = `${start_date.getTime()}|${end_date.getTime()}`;
|
|
9080
|
+
const cached = durationByKey.get(key);
|
|
9081
|
+
if (cached !== void 0) return cached;
|
|
9082
|
+
const value = api.calculateDuration(start_date, end_date);
|
|
9083
|
+
durationByKey.set(key, value);
|
|
9084
|
+
return value;
|
|
9085
|
+
},
|
|
8848
9086
|
getWorkHours: (date2) => api.getWorkHours(date2),
|
|
8849
9087
|
$gantt: {
|
|
8850
|
-
getClosestWorkTime: ({ date: date2, dir, unit }) =>
|
|
8851
|
-
|
|
8852
|
-
|
|
8853
|
-
|
|
8854
|
-
|
|
9088
|
+
getClosestWorkTime: ({ date: date2, dir, unit }) => {
|
|
9089
|
+
const resolvedUnit = unit ?? "hour";
|
|
9090
|
+
const key = `${date2.getTime()}|${dir}|${resolvedUnit}`;
|
|
9091
|
+
const cached = closestWorkTimeByKey.get(key);
|
|
9092
|
+
if (cached !== void 0) return new Date(cached);
|
|
9093
|
+
const value = api.getClosestWorkTime({
|
|
9094
|
+
date: date2,
|
|
9095
|
+
direction: dir,
|
|
9096
|
+
unit: resolvedUnit
|
|
9097
|
+
});
|
|
9098
|
+
closestWorkTimeByKey.set(key, value.getTime());
|
|
9099
|
+
return value;
|
|
9100
|
+
}
|
|
8855
9101
|
}
|
|
8856
9102
|
};
|
|
8857
9103
|
}
|
|
@@ -8931,19 +9177,19 @@ function orderByTree(leveled) {
|
|
|
8931
9177
|
return ordered;
|
|
8932
9178
|
}
|
|
8933
9179
|
function projectBounds(state) {
|
|
8934
|
-
let
|
|
8935
|
-
let
|
|
9180
|
+
let min2 = null;
|
|
9181
|
+
let max2 = null;
|
|
8936
9182
|
state.forEachActivity((activity) => {
|
|
8937
9183
|
if (activity.startDate) {
|
|
8938
9184
|
const start = activity.startDate.getTime();
|
|
8939
|
-
|
|
9185
|
+
min2 = min2 == null ? start : Math.min(min2, start);
|
|
8940
9186
|
}
|
|
8941
9187
|
if (activity.endDate) {
|
|
8942
9188
|
const end = activity.endDate.getTime();
|
|
8943
|
-
|
|
9189
|
+
max2 = max2 == null ? end : Math.max(max2, end);
|
|
8944
9190
|
}
|
|
8945
9191
|
});
|
|
8946
|
-
return { start_date: new Date(
|
|
9192
|
+
return { start_date: new Date(min2 ?? 0), end_date: new Date(max2 ?? 0) };
|
|
8947
9193
|
}
|
|
8948
9194
|
async function createGanttAdapter(state, hoursPerDay) {
|
|
8949
9195
|
const calendarCache = /* @__PURE__ */ new Map();
|
|
@@ -9010,13 +9256,27 @@ function fieldsForActivity(activityId, progress, maps) {
|
|
|
9010
9256
|
freeSlackHours: maps.freeFloat.get(activityId)?.activityFreeFloat ?? null
|
|
9011
9257
|
};
|
|
9012
9258
|
}
|
|
9259
|
+
var datesEqual = (left, right) => left === right || left !== null && right !== null && left.getTime() === right.getTime();
|
|
9260
|
+
function setCriticalPathFieldsIfChanged(state, activityId, fields) {
|
|
9261
|
+
const activity = state.getActivity(activityId);
|
|
9262
|
+
const current = activity?.criticalPath;
|
|
9263
|
+
const next = fields.criticalPath;
|
|
9264
|
+
if (activity && current && datesEqual(current.earlyStart, next.earlyStart) && datesEqual(current.earlyFinish, next.earlyFinish) && datesEqual(current.lateStart, next.lateStart) && datesEqual(current.lateFinish, next.lateFinish) && Object.is(current.totalSlackHours, next.totalSlackHours) && Object.is(activity.isCritical, fields.isCritical) && Object.is(activity.freeSlackHours, fields.freeSlackHours)) {
|
|
9265
|
+
return;
|
|
9266
|
+
}
|
|
9267
|
+
state.setActivityFields(activityId, fields);
|
|
9268
|
+
}
|
|
9013
9269
|
async function computeCriticalPathFields(state, hoursPerDay, isCurrent = () => true) {
|
|
9014
9270
|
if (!isCurrent()) return null;
|
|
9015
9271
|
const gantt = await createGanttAdapter(state, hoursPerDay);
|
|
9016
9272
|
if (!isCurrent()) return null;
|
|
9017
9273
|
const criticalPath = new legacy_default(gantt);
|
|
9018
9274
|
const calculated = await criticalPath.calculate(gantt, isCurrent);
|
|
9019
|
-
if (!calculated
|
|
9275
|
+
if (!calculated) {
|
|
9276
|
+
if (!isCurrent()) return null;
|
|
9277
|
+
throw new Error("critical path calculation aborted");
|
|
9278
|
+
}
|
|
9279
|
+
if (!isCurrent()) return null;
|
|
9020
9280
|
const maps = {
|
|
9021
9281
|
forward: criticalPath.forwardPathCalculations,
|
|
9022
9282
|
backward: criticalPath.backwardPathCalculations,
|
|
@@ -9051,7 +9311,7 @@ async function applyCriticalPath(state, hoursPerDay, isCurrent = () => true) {
|
|
|
9051
9311
|
if (!fieldsByActivity || !isCurrent()) return false;
|
|
9052
9312
|
for (const [activityId, fields] of fieldsByActivity) {
|
|
9053
9313
|
if (!isCurrent()) return false;
|
|
9054
|
-
state
|
|
9314
|
+
setCriticalPathFieldsIfChanged(state, activityId, fields);
|
|
9055
9315
|
}
|
|
9056
9316
|
return true;
|
|
9057
9317
|
}
|
|
@@ -9528,6 +9788,40 @@ function buildLinkChangesForBatch(applied, beforeLinks, adapter) {
|
|
|
9528
9788
|
if (!finalLinkId) continue;
|
|
9529
9789
|
const after = adapter.getLink(finalLinkId);
|
|
9530
9790
|
if (!after) continue;
|
|
9791
|
+
const before = beforeLinks.get(finalLinkId);
|
|
9792
|
+
if (before) {
|
|
9793
|
+
const fields2 = {};
|
|
9794
|
+
if (String(before.source) !== String(after.source)) {
|
|
9795
|
+
fields2.source = {
|
|
9796
|
+
before: String(before.source),
|
|
9797
|
+
after: String(after.source)
|
|
9798
|
+
};
|
|
9799
|
+
}
|
|
9800
|
+
if (String(before.target) !== String(after.target)) {
|
|
9801
|
+
fields2.target = {
|
|
9802
|
+
before: String(before.target),
|
|
9803
|
+
after: String(after.target)
|
|
9804
|
+
};
|
|
9805
|
+
}
|
|
9806
|
+
if (before.type !== after.type) {
|
|
9807
|
+
fields2.type = { before: before.type, after: after.type };
|
|
9808
|
+
}
|
|
9809
|
+
if (Number(before.lag) !== Number(after.lag)) {
|
|
9810
|
+
fields2.lag = {
|
|
9811
|
+
before: Number(before.lag),
|
|
9812
|
+
after: Number(after.lag)
|
|
9813
|
+
};
|
|
9814
|
+
}
|
|
9815
|
+
if (Object.keys(fields2).length > 0) {
|
|
9816
|
+
out.push({
|
|
9817
|
+
id: String(finalLinkId),
|
|
9818
|
+
kind: "updated",
|
|
9819
|
+
fields: fields2,
|
|
9820
|
+
after: snapshotToLink(after)
|
|
9821
|
+
});
|
|
9822
|
+
}
|
|
9823
|
+
continue;
|
|
9824
|
+
}
|
|
9531
9825
|
out.push({
|
|
9532
9826
|
id: String(finalLinkId),
|
|
9533
9827
|
kind: "created",
|
|
@@ -9542,6 +9836,7 @@ function buildLinkChangesForBatch(applied, beforeLinks, adapter) {
|
|
|
9542
9836
|
continue;
|
|
9543
9837
|
}
|
|
9544
9838
|
if (op.kind === "delete") {
|
|
9839
|
+
if (adapter.getLink(op.linkId)) continue;
|
|
9545
9840
|
const beforeLink2 = beforeLinks.get(op.linkId);
|
|
9546
9841
|
out.push({
|
|
9547
9842
|
id: String(op.linkId),
|
|
@@ -10378,8 +10673,8 @@ function computeInsertionCorrelativeId(adapter, parentId, afterSiblingId, before
|
|
|
10378
10673
|
}
|
|
10379
10674
|
}
|
|
10380
10675
|
if (siblings.length === 0) return isRoot ? 0 : 0;
|
|
10381
|
-
const
|
|
10382
|
-
return
|
|
10676
|
+
const max2 = Math.max(...siblings.map(readCorrelativeId));
|
|
10677
|
+
return max2 + 1;
|
|
10383
10678
|
}
|
|
10384
10679
|
var NOT_SET_SORT_VALUE2 = Number.MAX_SAFE_INTEGER;
|
|
10385
10680
|
var TEMP_CID_SENTINEL = Number.MAX_SAFE_INTEGER - 1;
|
|
@@ -10389,12 +10684,7 @@ function readCorrelativeId(activity) {
|
|
|
10389
10684
|
return Number.isFinite(n) ? n : NOT_SET_SORT_VALUE2;
|
|
10390
10685
|
}
|
|
10391
10686
|
function collectSiblings(adapter, parentKey) {
|
|
10392
|
-
|
|
10393
|
-
adapter.forEachActivity((a) => {
|
|
10394
|
-
const ap = normalizeParentKey(a.parentId);
|
|
10395
|
-
if (ap === parentKey) siblings.push(a);
|
|
10396
|
-
});
|
|
10397
|
-
return siblings;
|
|
10687
|
+
return collectChildrenSnapshots(parentKey, adapter);
|
|
10398
10688
|
}
|
|
10399
10689
|
function findFirstSelectedSibling(taskId, selected, adapter) {
|
|
10400
10690
|
let firstSelected = taskId;
|
|
@@ -10444,7 +10734,7 @@ function computeFractionalCidAtIndex(adapter, parentKey, targetIndex, excludeId)
|
|
|
10444
10734
|
// src/dispatch/handlers/activity-create.ts
|
|
10445
10735
|
async function createActivityCore(action, deps, opts = {}) {
|
|
10446
10736
|
const { adapter, scheduler, sector, calendars, activityIdGen, uidGen } = deps;
|
|
10447
|
-
scheduler.invalidateAllCaches();
|
|
10737
|
+
if (!opts.skipCacheInvalidation) scheduler.invalidateAllCaches();
|
|
10448
10738
|
const target = validateCreateTarget(action, adapter);
|
|
10449
10739
|
if (!target.ok) return target;
|
|
10450
10740
|
const { parent, anchorSiblingId, isRoot } = target;
|
|
@@ -10473,7 +10763,7 @@ async function createActivityCore(action, deps, opts = {}) {
|
|
|
10473
10763
|
newActivity.progress
|
|
10474
10764
|
);
|
|
10475
10765
|
assignCustomId(newActivity, action, deps, anchorSiblingId, parent, opts);
|
|
10476
|
-
const insertionCorrelativeId = computeInsertionCorrelativeId(
|
|
10766
|
+
const insertionCorrelativeId = opts.insertionCorrelativeId ?? computeInsertionCorrelativeId(
|
|
10477
10767
|
adapter,
|
|
10478
10768
|
isRoot ? ROOT_PARENT_ID : action.parentId,
|
|
10479
10769
|
action.afterSiblingId,
|
|
@@ -10483,12 +10773,18 @@ async function createActivityCore(action, deps, opts = {}) {
|
|
|
10483
10773
|
const initialTouched = /* @__PURE__ */ new Set([newId]);
|
|
10484
10774
|
if (parent) initialTouched.add(parent.id);
|
|
10485
10775
|
const parentWasLeaf = parent !== null && adapter.getChildren(parent.id).length === 0;
|
|
10776
|
+
const parentCustomId = parentWasLeaf ? parent?.customId : null;
|
|
10486
10777
|
const beforeSnap = opts.skipBeforeSnap ? /* @__PURE__ */ new Map() : await snapshotActivities(adapter, initialTouched);
|
|
10487
10778
|
adapter.addActivity(newActivity);
|
|
10488
10779
|
applyParentMutations(action, newId, parent, adapter, parentWasLeaf);
|
|
10489
|
-
|
|
10490
|
-
|
|
10491
|
-
|
|
10780
|
+
if (parentCustomId) {
|
|
10781
|
+
deps.customIdTracker.releaseCustomId(parentCustomId);
|
|
10782
|
+
}
|
|
10783
|
+
if (!opts.skipRollupCascades) {
|
|
10784
|
+
recomputeHhCascadeForActivity(newId, adapter);
|
|
10785
|
+
recomputeUsedCostCascadeForActivity(newId, adapter);
|
|
10786
|
+
recomputeCostCascadeForActivity(newId, adapter);
|
|
10787
|
+
}
|
|
10492
10788
|
recomputeAndFoldCorrelatives(
|
|
10493
10789
|
adapter,
|
|
10494
10790
|
newId,
|
|
@@ -10660,330 +10956,96 @@ function findDefaultCalendarId(calendars) {
|
|
|
10660
10956
|
return "global";
|
|
10661
10957
|
}
|
|
10662
10958
|
|
|
10663
|
-
// src/
|
|
10664
|
-
function
|
|
10665
|
-
|
|
10666
|
-
|
|
10667
|
-
|
|
10668
|
-
|
|
10959
|
+
// src/propagations/upward/recompute-rollup-cascades.ts
|
|
10960
|
+
function recomputeRollupCascadesForParent(parentId, adapter) {
|
|
10961
|
+
recomputeHhCascadeForParent(parentId, adapter);
|
|
10962
|
+
recomputeUsedCostCascadeForParent(parentId, adapter);
|
|
10963
|
+
recomputeCostCascadeForParent(parentId, adapter);
|
|
10964
|
+
}
|
|
10965
|
+
|
|
10966
|
+
// src/propagations/shared/traversal-helpers.ts
|
|
10967
|
+
function collectDescendantIds(rootIds, adapter) {
|
|
10968
|
+
const out = /* @__PURE__ */ new Set();
|
|
10969
|
+
const stack = [];
|
|
10970
|
+
for (const id of rootIds) {
|
|
10971
|
+
const key = String(id);
|
|
10972
|
+
if (!out.has(key)) {
|
|
10973
|
+
out.add(key);
|
|
10974
|
+
stack.push(key);
|
|
10669
10975
|
}
|
|
10670
|
-
const parentId2 = sibling.parentId == null ? ROOT_PARENT_ID : String(sibling.parentId);
|
|
10671
|
-
return { ok: true, parentId: parentId2, firstAfter: destination.afterSiblingId };
|
|
10672
10976
|
}
|
|
10673
|
-
|
|
10674
|
-
|
|
10675
|
-
|
|
10676
|
-
|
|
10677
|
-
|
|
10678
|
-
|
|
10977
|
+
while (stack.length > 0) {
|
|
10978
|
+
const current = stack.pop();
|
|
10979
|
+
const children = adapter.getChildren(current);
|
|
10980
|
+
for (const childId of children) {
|
|
10981
|
+
const key = String(childId);
|
|
10982
|
+
if (!out.has(key)) {
|
|
10983
|
+
out.add(key);
|
|
10984
|
+
stack.push(key);
|
|
10985
|
+
}
|
|
10986
|
+
}
|
|
10987
|
+
}
|
|
10988
|
+
return out;
|
|
10679
10989
|
}
|
|
10680
|
-
function
|
|
10681
|
-
const
|
|
10990
|
+
function collectIncidentLinkIds(activityIds, adapter) {
|
|
10991
|
+
const out = /* @__PURE__ */ new Set();
|
|
10992
|
+
for (const activityId of activityIds) {
|
|
10993
|
+
for (const linkId of adapter.getOutgoingLinkIds(activityId)) {
|
|
10994
|
+
out.add(String(linkId));
|
|
10995
|
+
}
|
|
10996
|
+
for (const linkId of adapter.getIncomingLinkIds(activityId)) {
|
|
10997
|
+
out.add(String(linkId));
|
|
10998
|
+
}
|
|
10999
|
+
}
|
|
11000
|
+
return [...out];
|
|
11001
|
+
}
|
|
11002
|
+
|
|
11003
|
+
// src/internal/hierarchy/parent-demotion.ts
|
|
11004
|
+
function restoredPromotionFields(restore) {
|
|
11005
|
+
if (!restore) return null;
|
|
10682
11006
|
return {
|
|
10683
|
-
|
|
10684
|
-
|
|
10685
|
-
|
|
10686
|
-
|
|
10687
|
-
|
|
10688
|
-
|
|
11007
|
+
type: restore.type,
|
|
11008
|
+
startDate: new Date(restore.startDate),
|
|
11009
|
+
endDate: new Date(restore.endDate),
|
|
11010
|
+
durationHours: restore.durationHours,
|
|
11011
|
+
expectedProgressBaseline: restore.expectedProgressBaseline,
|
|
11012
|
+
constraintType: restore.constraintType,
|
|
11013
|
+
constraintDate: restore.constraintDate ? new Date(restore.constraintDate) : null,
|
|
11014
|
+
progress: restore.progress
|
|
10689
11015
|
};
|
|
10690
11016
|
}
|
|
10691
|
-
|
|
10692
|
-
const
|
|
10693
|
-
|
|
10694
|
-
|
|
10695
|
-
|
|
10696
|
-
|
|
10697
|
-
|
|
10698
|
-
const
|
|
10699
|
-
|
|
10700
|
-
|
|
10701
|
-
|
|
10702
|
-
|
|
10703
|
-
|
|
10704
|
-
|
|
10705
|
-
|
|
10706
|
-
|
|
10707
|
-
|
|
10708
|
-
|
|
10709
|
-
|
|
10710
|
-
|
|
10711
|
-
|
|
10712
|
-
|
|
10713
|
-
|
|
10714
|
-
|
|
10715
|
-
|
|
10716
|
-
const
|
|
10717
|
-
|
|
10718
|
-
|
|
10719
|
-
|
|
10720
|
-
|
|
10721
|
-
|
|
10722
|
-
|
|
10723
|
-
eventSource: action.eventSource
|
|
10724
|
-
},
|
|
10725
|
-
deps,
|
|
10726
|
-
{
|
|
10727
|
-
skipCorrelativeRecompute: true,
|
|
10728
|
-
skipBeforeSnap: true,
|
|
10729
|
-
customIdReferenceId: action.referenceActivityId
|
|
10730
|
-
}
|
|
10731
|
-
);
|
|
10732
|
-
if (!core.ok) return core;
|
|
10733
|
-
if (input.baselineSnapshot !== void 0) {
|
|
10734
|
-
const snapshot = input.baselineSnapshot;
|
|
10735
|
-
adapter.setActivityField(
|
|
10736
|
-
core.newId,
|
|
10737
|
-
"baselineSnapshot",
|
|
10738
|
-
snapshot ? {
|
|
10739
|
-
...snapshot,
|
|
10740
|
-
startDate: snapshot.startDate ? new Date(snapshot.startDate) : null,
|
|
10741
|
-
endDate: snapshot.endDate ? new Date(snapshot.endDate) : null
|
|
10742
|
-
} : null
|
|
10743
|
-
);
|
|
10744
|
-
}
|
|
10745
|
-
originalToNew.set(input.originalId, core.newId);
|
|
10746
|
-
createdIds.push(core.newId);
|
|
10747
|
-
if (mappedParent === void 0) prevRootId = core.newId;
|
|
10748
|
-
}
|
|
10749
|
-
const correlativeShifts = recomputeCorrelativeIds(adapter);
|
|
10750
|
-
const createdIdSet = new Set(createdIds.map(String));
|
|
10751
|
-
foldCorrelativeShifts(
|
|
10752
|
-
adapter,
|
|
10753
|
-
correlativeShifts,
|
|
10754
|
-
createdIdSet,
|
|
10755
|
-
beforeSnap,
|
|
10756
|
-
touched
|
|
10757
|
-
);
|
|
10758
|
-
for (const createdId of createdIds) touched.add(String(createdId));
|
|
10759
|
-
const appliedLinks = [];
|
|
10760
|
-
for (const link of action.links) {
|
|
10761
|
-
const newSource = originalToNew.get(link.source);
|
|
10762
|
-
const newTarget = originalToNew.get(link.target);
|
|
10763
|
-
if (newSource === void 0 || newTarget === void 0) continue;
|
|
10764
|
-
const op = {
|
|
10765
|
-
kind: "create",
|
|
10766
|
-
source: newSource,
|
|
10767
|
-
target: newTarget,
|
|
10768
|
-
type: link.type,
|
|
10769
|
-
lag: lagDaysToHours(link.lag, sector.hoursPerDay)
|
|
10770
|
-
};
|
|
10771
|
-
const res = applyLinkOperation(op, {
|
|
10772
|
-
port: adapter,
|
|
10773
|
-
newLinkId: link.linkId ? () => link.linkId : deps.linkIdGen.next
|
|
10774
|
-
});
|
|
10775
|
-
appliedLinks.push({
|
|
10776
|
-
op,
|
|
10777
|
-
finalLinkId: res.applied ? res.linkId : null,
|
|
10778
|
-
rejected: res.applied ? null : res.rejected ?? "link_op_rejected"
|
|
10779
|
-
});
|
|
10780
|
-
}
|
|
10781
|
-
const { scheduledIds } = await runPostMutation(
|
|
10782
|
-
{
|
|
10783
|
-
adapter,
|
|
10784
|
-
scheduler,
|
|
10785
|
-
sector: deps.sector,
|
|
10786
|
-
defaultBaseCalendarId: deps.defaultBaseCalendarId
|
|
10787
|
-
},
|
|
10788
|
-
{
|
|
10789
|
-
action,
|
|
10790
|
-
autoscheduleFrom: createdIds[0] ?? null,
|
|
10791
|
-
recomputeParentsFrom: createdIds,
|
|
10792
|
-
now: deps.now,
|
|
10793
|
-
options
|
|
10794
|
-
}
|
|
10795
|
-
);
|
|
10796
|
-
const linkChanges = buildLinkChangesForBatch(
|
|
10797
|
-
appliedLinks,
|
|
10798
|
-
/* @__PURE__ */ new Map(),
|
|
10799
|
-
adapter
|
|
10800
|
-
);
|
|
10801
|
-
return {
|
|
10802
|
-
ok: true,
|
|
10803
|
-
changes: await assembleChangeSet(adapter, {
|
|
10804
|
-
source: action,
|
|
10805
|
-
beforeSnap,
|
|
10806
|
-
touchedIds: touched,
|
|
10807
|
-
scheduledIds,
|
|
10808
|
-
links: linkChanges,
|
|
10809
|
-
hoursPerDay: sector.hoursPerDay
|
|
10810
|
-
})
|
|
10811
|
-
};
|
|
10812
|
-
}
|
|
10813
|
-
|
|
10814
|
-
// src/dispatch/handlers/sir-sync.ts
|
|
10815
|
-
function dispatchSirSync(action, deps) {
|
|
10816
|
-
const { adapter } = deps;
|
|
10817
|
-
const id = action.activityId;
|
|
10818
|
-
const snapshot = adapter.getActivity(id);
|
|
10819
|
-
if (!snapshot) {
|
|
10820
|
-
return { ok: false, reason: REJECTION_REASON.ACTIVITY_NOT_FOUND };
|
|
10821
|
-
}
|
|
10822
|
-
const before = snapshot.pendingRequestIds ?? [];
|
|
10823
|
-
const after = action.pendingRequests.map((r) => r.id);
|
|
10824
|
-
adapter.setActivityField(action.activityId, "pendingRequestIds", after);
|
|
10825
|
-
const change = {
|
|
10826
|
-
id,
|
|
10827
|
-
kind: "updated",
|
|
10828
|
-
fields: { pendingRequestIds: { before, after } },
|
|
10829
|
-
after: { id, pendingRequestIds: after }
|
|
10830
|
-
};
|
|
10831
|
-
return {
|
|
10832
|
-
ok: true,
|
|
10833
|
-
changes: {
|
|
10834
|
-
source: action,
|
|
10835
|
-
activities: [change],
|
|
10836
|
-
links: [],
|
|
10837
|
-
calendars: [],
|
|
10838
|
-
trackingEvents: []
|
|
10839
|
-
}
|
|
10840
|
-
};
|
|
10841
|
-
}
|
|
10842
|
-
|
|
10843
|
-
// src/dispatch/handlers/activity-lookahead-sync.ts
|
|
10844
|
-
function dispatchActivityLookaheadSync(action, deps) {
|
|
10845
|
-
const ids = [...new Set(action.activityIds.map(String))];
|
|
10846
|
-
const snapshots = ids.map((id) => deps.adapter.getActivity(id));
|
|
10847
|
-
if (snapshots.some((snapshot) => !snapshot)) {
|
|
10848
|
-
return { ok: false, reason: REJECTION_REASON.ACTIVITY_NOT_FOUND };
|
|
10849
|
-
}
|
|
10850
|
-
const activities = [];
|
|
10851
|
-
for (const [index, id] of ids.entries()) {
|
|
10852
|
-
const snapshot = snapshots[index];
|
|
10853
|
-
if (!snapshot) continue;
|
|
10854
|
-
const fields = {};
|
|
10855
|
-
if (snapshot.isLookahead !== action.isLookahead) {
|
|
10856
|
-
fields.isLookahead = {
|
|
10857
|
-
before: snapshot.isLookahead,
|
|
10858
|
-
after: action.isLookahead
|
|
10859
|
-
};
|
|
10860
|
-
deps.adapter.setActivityField(id, "isLookahead", action.isLookahead);
|
|
10861
|
-
}
|
|
10862
|
-
if (snapshot.hasLookaheadTasks !== action.hasLookaheadTasks) {
|
|
10863
|
-
fields.hasLookaheadTasks = {
|
|
10864
|
-
before: snapshot.hasLookaheadTasks,
|
|
10865
|
-
after: action.hasLookaheadTasks
|
|
10866
|
-
};
|
|
10867
|
-
deps.adapter.setActivityField(
|
|
10868
|
-
id,
|
|
10869
|
-
"hasLookaheadTasks",
|
|
10870
|
-
action.hasLookaheadTasks
|
|
10871
|
-
);
|
|
10872
|
-
}
|
|
10873
|
-
if (Object.keys(fields).length === 0) continue;
|
|
10874
|
-
activities.push({
|
|
10875
|
-
id,
|
|
10876
|
-
kind: "updated",
|
|
10877
|
-
fields,
|
|
10878
|
-
after: {
|
|
10879
|
-
...snapshot,
|
|
10880
|
-
isLookahead: action.isLookahead,
|
|
10881
|
-
hasLookaheadTasks: action.hasLookaheadTasks
|
|
10882
|
-
}
|
|
10883
|
-
});
|
|
10884
|
-
}
|
|
10885
|
-
return {
|
|
10886
|
-
ok: true,
|
|
10887
|
-
changes: {
|
|
10888
|
-
source: action,
|
|
10889
|
-
activities,
|
|
10890
|
-
links: [],
|
|
10891
|
-
calendars: [],
|
|
10892
|
-
trackingEvents: []
|
|
10893
|
-
}
|
|
10894
|
-
};
|
|
10895
|
-
}
|
|
10896
|
-
|
|
10897
|
-
// src/propagations/upward/recompute-rollup-cascades.ts
|
|
10898
|
-
function recomputeRollupCascadesForParent(parentId, adapter) {
|
|
10899
|
-
recomputeHhCascadeForParent(parentId, adapter);
|
|
10900
|
-
recomputeUsedCostCascadeForParent(parentId, adapter);
|
|
10901
|
-
recomputeCostCascadeForParent(parentId, adapter);
|
|
10902
|
-
}
|
|
10903
|
-
|
|
10904
|
-
// src/propagations/shared/traversal-helpers.ts
|
|
10905
|
-
function collectDescendantIds(rootIds, adapter) {
|
|
10906
|
-
const out = /* @__PURE__ */ new Set();
|
|
10907
|
-
const stack = [];
|
|
10908
|
-
for (const id of rootIds) {
|
|
10909
|
-
const key = String(id);
|
|
10910
|
-
if (!out.has(key)) {
|
|
10911
|
-
out.add(key);
|
|
10912
|
-
stack.push(key);
|
|
10913
|
-
}
|
|
10914
|
-
}
|
|
10915
|
-
while (stack.length > 0) {
|
|
10916
|
-
const current = stack.pop();
|
|
10917
|
-
const children = adapter.getChildren(current);
|
|
10918
|
-
for (const childId of children) {
|
|
10919
|
-
const key = String(childId);
|
|
10920
|
-
if (!out.has(key)) {
|
|
10921
|
-
out.add(key);
|
|
10922
|
-
stack.push(key);
|
|
10923
|
-
}
|
|
10924
|
-
}
|
|
10925
|
-
}
|
|
10926
|
-
return out;
|
|
10927
|
-
}
|
|
10928
|
-
function collectIncidentLinkIds(activityIds, adapter) {
|
|
10929
|
-
const out = /* @__PURE__ */ new Set();
|
|
10930
|
-
for (const activityId of activityIds) {
|
|
10931
|
-
for (const linkId of adapter.getOutgoingLinkIds(activityId)) {
|
|
10932
|
-
out.add(String(linkId));
|
|
10933
|
-
}
|
|
10934
|
-
for (const linkId of adapter.getIncomingLinkIds(activityId)) {
|
|
10935
|
-
out.add(String(linkId));
|
|
10936
|
-
}
|
|
10937
|
-
}
|
|
10938
|
-
return [...out];
|
|
10939
|
-
}
|
|
10940
|
-
|
|
10941
|
-
// src/internal/hierarchy/parent-demotion.ts
|
|
10942
|
-
function restoredPromotionFields(restore) {
|
|
10943
|
-
if (!restore) return null;
|
|
10944
|
-
return {
|
|
10945
|
-
type: restore.type,
|
|
10946
|
-
startDate: new Date(restore.startDate),
|
|
10947
|
-
endDate: new Date(restore.endDate),
|
|
10948
|
-
durationHours: restore.durationHours,
|
|
10949
|
-
expectedProgressBaseline: restore.expectedProgressBaseline,
|
|
10950
|
-
constraintType: restore.constraintType,
|
|
10951
|
-
constraintDate: restore.constraintDate ? new Date(restore.constraintDate) : null,
|
|
10952
|
-
progress: restore.progress
|
|
10953
|
-
};
|
|
10954
|
-
}
|
|
10955
|
-
function canonicalEndDateField(parent, durationHours, computeEndDate) {
|
|
10956
|
-
const startDate = parent.startDate;
|
|
10957
|
-
if (!computeEndDate || !(startDate instanceof Date)) return {};
|
|
10958
|
-
return { endDate: computeEndDate(startDate, durationHours) };
|
|
10959
|
-
}
|
|
10960
|
-
function buildParentDemotionMutations(input) {
|
|
10961
|
-
if (input.remainingChildIds.length > 0) return null;
|
|
10962
|
-
const parent = input.parent;
|
|
10963
|
-
const canonicalDuration = input.defaultDurationHours;
|
|
10964
|
-
const hasCanonicalDefaults = Number.isFinite(canonicalDuration);
|
|
10965
|
-
const fields = restoredPromotionFields(input.promotionSnapshot) ?? (hasCanonicalDefaults ? {
|
|
10966
|
-
type: DEMOTION_TARGET_TYPE,
|
|
10967
|
-
durationHours: canonicalDuration,
|
|
10968
|
-
progress: 0,
|
|
10969
|
-
autoScheduling: true,
|
|
10970
|
-
constraintType: "asap",
|
|
10971
|
-
constraintDate: null,
|
|
10972
|
-
hasNewActivities: false,
|
|
10973
|
-
...canonicalEndDateField(
|
|
10974
|
-
parent,
|
|
10975
|
-
canonicalDuration,
|
|
10976
|
-
input.computeEndDate
|
|
10977
|
-
)
|
|
10978
|
-
} : { type: DEMOTION_TARGET_TYPE });
|
|
10979
|
-
if (input.idsRemoved && input.idsRemoved.size > 0) {
|
|
10980
|
-
const existing = Array.isArray(parent.newActivityIds) ? parent.newActivityIds : [];
|
|
10981
|
-
if (existing.length > 0) {
|
|
10982
|
-
const filtered = existing.filter(
|
|
10983
|
-
(id) => !input.idsRemoved.has(id) && !input.idsRemoved.has(String(id))
|
|
10984
|
-
);
|
|
10985
|
-
if (filtered.length !== existing.length) {
|
|
10986
|
-
fields.newActivityIds = filtered;
|
|
11017
|
+
function canonicalEndDateField(parent, durationHours, computeEndDate) {
|
|
11018
|
+
const startDate = parent.startDate;
|
|
11019
|
+
if (!computeEndDate || !(startDate instanceof Date)) return {};
|
|
11020
|
+
return { endDate: computeEndDate(startDate, durationHours) };
|
|
11021
|
+
}
|
|
11022
|
+
function buildParentDemotionMutations(input) {
|
|
11023
|
+
if (input.remainingChildIds.length > 0) return null;
|
|
11024
|
+
const parent = input.parent;
|
|
11025
|
+
const canonicalDuration = input.defaultDurationHours;
|
|
11026
|
+
const hasCanonicalDefaults = Number.isFinite(canonicalDuration);
|
|
11027
|
+
const fields = restoredPromotionFields(input.promotionSnapshot) ?? (hasCanonicalDefaults ? {
|
|
11028
|
+
type: DEMOTION_TARGET_TYPE,
|
|
11029
|
+
durationHours: canonicalDuration,
|
|
11030
|
+
progress: 0,
|
|
11031
|
+
autoScheduling: true,
|
|
11032
|
+
constraintType: "asap",
|
|
11033
|
+
constraintDate: null,
|
|
11034
|
+
hasNewActivities: false,
|
|
11035
|
+
...canonicalEndDateField(
|
|
11036
|
+
parent,
|
|
11037
|
+
canonicalDuration,
|
|
11038
|
+
input.computeEndDate
|
|
11039
|
+
)
|
|
11040
|
+
} : { type: DEMOTION_TARGET_TYPE });
|
|
11041
|
+
if (input.idsRemoved && input.idsRemoved.size > 0) {
|
|
11042
|
+
const existing = Array.isArray(parent.newActivityIds) ? parent.newActivityIds : [];
|
|
11043
|
+
if (existing.length > 0) {
|
|
11044
|
+
const filtered = existing.filter(
|
|
11045
|
+
(id) => !input.idsRemoved.has(id) && !input.idsRemoved.has(String(id))
|
|
11046
|
+
);
|
|
11047
|
+
if (filtered.length !== existing.length) {
|
|
11048
|
+
fields.newActivityIds = filtered;
|
|
10987
11049
|
}
|
|
10988
11050
|
}
|
|
10989
11051
|
}
|
|
@@ -11192,7 +11254,647 @@ function buildLinkDeletions(beforeLinks) {
|
|
|
11192
11254
|
after: null
|
|
11193
11255
|
});
|
|
11194
11256
|
}
|
|
11195
|
-
return out;
|
|
11257
|
+
return out;
|
|
11258
|
+
}
|
|
11259
|
+
|
|
11260
|
+
// src/dispatch/handlers/activity-batch.ts
|
|
11261
|
+
async function dispatchActivityBatch(action, options, deps) {
|
|
11262
|
+
const validationError = validateBatch(action, deps);
|
|
11263
|
+
if (validationError) return { ok: false, reason: validationError };
|
|
11264
|
+
const plan = buildBatchPlan(action, deps);
|
|
11265
|
+
if (!plan.ok) return plan;
|
|
11266
|
+
const existingActivityIds = action.mode === "replace" ? new Set(deps.adapter.getAllIds().map((id) => String(id))) : /* @__PURE__ */ new Set();
|
|
11267
|
+
const beforeSnap = action.mode === "replace" ? await snapshotActivities(deps.adapter, existingActivityIds) : /* @__PURE__ */ new Map();
|
|
11268
|
+
const beforeLinks = action.mode === "replace" ? snapshotAllLinks(deps) : /* @__PURE__ */ new Map();
|
|
11269
|
+
const viewState = action.mode === "replace" ? captureDeletedViewState(deps.adapter, existingActivityIds) : [];
|
|
11270
|
+
deps.scheduler.invalidateAllCaches();
|
|
11271
|
+
const appliedLinks = [];
|
|
11272
|
+
if (action.mode === "replace") {
|
|
11273
|
+
for (const linkId of beforeLinks.keys()) {
|
|
11274
|
+
const deleted = applyLinkOperation(
|
|
11275
|
+
{ kind: "delete", linkId },
|
|
11276
|
+
{ port: deps.adapter }
|
|
11277
|
+
);
|
|
11278
|
+
if (!deleted.applied) {
|
|
11279
|
+
return {
|
|
11280
|
+
ok: false,
|
|
11281
|
+
reason: deleted.rejected ?? REJECTION_REASON.INVALID
|
|
11282
|
+
};
|
|
11283
|
+
}
|
|
11284
|
+
appliedLinks.push({
|
|
11285
|
+
op: { kind: "delete", linkId },
|
|
11286
|
+
finalLinkId: linkId,
|
|
11287
|
+
rejected: null
|
|
11288
|
+
});
|
|
11289
|
+
}
|
|
11290
|
+
for (const activityId of existingActivityIds) {
|
|
11291
|
+
const customId = deps.adapter.getActivity(activityId)?.customId;
|
|
11292
|
+
if (typeof customId === "string" && customId.length > 0) {
|
|
11293
|
+
deps.customIdTracker.releaseCustomId(customId);
|
|
11294
|
+
}
|
|
11295
|
+
deps.adapter.removeActivity(activityId);
|
|
11296
|
+
}
|
|
11297
|
+
}
|
|
11298
|
+
const created = await createPlannedActivities(action, plan.plan.items, deps);
|
|
11299
|
+
if (!created.ok) return created;
|
|
11300
|
+
const correlativeShifts = isSimpleRootAppend(action) ? [] : recomputeCorrelativeIds(deps.adapter);
|
|
11301
|
+
const createdIdSet = new Set(
|
|
11302
|
+
plan.plan.items.map((item) => String(item.activityId))
|
|
11303
|
+
);
|
|
11304
|
+
const existingCorrelativeShifts = correlativeShifts.filter(
|
|
11305
|
+
(shift) => !createdIdSet.has(shift.activityId)
|
|
11306
|
+
);
|
|
11307
|
+
const touchedIds = action.mode === "replace" ? new Set(existingActivityIds) : /* @__PURE__ */ new Set();
|
|
11308
|
+
foldCorrelativeShifts(
|
|
11309
|
+
deps.adapter,
|
|
11310
|
+
correlativeShifts,
|
|
11311
|
+
createdIdSet,
|
|
11312
|
+
beforeSnap,
|
|
11313
|
+
touchedIds
|
|
11314
|
+
);
|
|
11315
|
+
for (const item of plan.plan.items) {
|
|
11316
|
+
touchedIds.add(String(item.activityId));
|
|
11317
|
+
}
|
|
11318
|
+
const linkCreates = plan.plan.links.map((link) => ({
|
|
11319
|
+
operation: {
|
|
11320
|
+
kind: "create",
|
|
11321
|
+
source: link.source,
|
|
11322
|
+
target: link.target,
|
|
11323
|
+
type: link.type,
|
|
11324
|
+
lag: options.inputUnit === "hours" ? link.lag : lagDaysToHours(link.lag, deps.sector.hoursPerDay)
|
|
11325
|
+
},
|
|
11326
|
+
linkId: link.linkId
|
|
11327
|
+
}));
|
|
11328
|
+
const createdLinks = applyLinkCreates(linkCreates, {
|
|
11329
|
+
port: deps.adapter,
|
|
11330
|
+
preserved: beforeLinks
|
|
11331
|
+
});
|
|
11332
|
+
if (!createdLinks.applied) {
|
|
11333
|
+
return {
|
|
11334
|
+
ok: false,
|
|
11335
|
+
reason: createdLinks.rejected ?? REJECTION_REASON.INVALID
|
|
11336
|
+
};
|
|
11337
|
+
}
|
|
11338
|
+
for (const { operation, linkId } of linkCreates) {
|
|
11339
|
+
appliedLinks.push({
|
|
11340
|
+
op: operation,
|
|
11341
|
+
finalLinkId: linkId,
|
|
11342
|
+
rejected: null
|
|
11343
|
+
});
|
|
11344
|
+
}
|
|
11345
|
+
const createdIds = plan.plan.items.map((item) => item.activityId);
|
|
11346
|
+
const { scheduledIds } = await runPostMutation(
|
|
11347
|
+
{
|
|
11348
|
+
adapter: deps.adapter,
|
|
11349
|
+
scheduler: deps.scheduler,
|
|
11350
|
+
sector: deps.sector,
|
|
11351
|
+
defaultBaseCalendarId: deps.defaultBaseCalendarId
|
|
11352
|
+
},
|
|
11353
|
+
{
|
|
11354
|
+
action,
|
|
11355
|
+
autoscheduleFrom: action.items.length > 0 || action.links.length > 0 ? "roots" : null,
|
|
11356
|
+
recomputeParentsFrom: createdIds,
|
|
11357
|
+
now: deps.now,
|
|
11358
|
+
options
|
|
11359
|
+
}
|
|
11360
|
+
);
|
|
11361
|
+
const changes = await assembleChangeSet(deps.adapter, {
|
|
11362
|
+
source: action,
|
|
11363
|
+
beforeSnap,
|
|
11364
|
+
touchedIds,
|
|
11365
|
+
scheduledIds,
|
|
11366
|
+
links: buildLinkChangesForBatch(appliedLinks, beforeLinks, deps.adapter),
|
|
11367
|
+
hoursPerDay: deps.sector.hoursPerDay,
|
|
11368
|
+
correlativeShifts: existingCorrelativeShifts,
|
|
11369
|
+
...action.mode === "replace" ? { sirDetection: "after-diff" } : {}
|
|
11370
|
+
});
|
|
11371
|
+
return {
|
|
11372
|
+
ok: true,
|
|
11373
|
+
changes: viewState.length > 0 ? { ...changes, viewState } : changes,
|
|
11374
|
+
...action.mode === "replace" ? {
|
|
11375
|
+
__beforeSnap: beforeSnap,
|
|
11376
|
+
__beforeLinks: stringKeyedLinks(beforeLinks)
|
|
11377
|
+
} : {}
|
|
11378
|
+
};
|
|
11379
|
+
}
|
|
11380
|
+
function validateBatch(action, deps) {
|
|
11381
|
+
if (action.mode !== "append" && action.mode !== "replace") {
|
|
11382
|
+
return REJECTION_REASON.INVALID;
|
|
11383
|
+
}
|
|
11384
|
+
const batchIds = /* @__PURE__ */ new Set();
|
|
11385
|
+
const explicitActivityIds = /* @__PURE__ */ new Set();
|
|
11386
|
+
for (const item of action.items) {
|
|
11387
|
+
if (item.batchId.length === 0 || batchIds.has(item.batchId)) {
|
|
11388
|
+
return REJECTION_REASON.INVALID;
|
|
11389
|
+
}
|
|
11390
|
+
batchIds.add(item.batchId);
|
|
11391
|
+
if (item.index !== void 0 && (!Number.isInteger(item.index) || item.index < 0)) {
|
|
11392
|
+
return REJECTION_REASON.INVALID;
|
|
11393
|
+
}
|
|
11394
|
+
if (item.activityId !== void 0) {
|
|
11395
|
+
const activityId = String(item.activityId);
|
|
11396
|
+
if (activityId.length === 0 || activityId === "0" || explicitActivityIds.has(activityId) || action.mode === "append" && deps.adapter.getActivity(activityId) !== null) {
|
|
11397
|
+
return REJECTION_REASON.INVALID;
|
|
11398
|
+
}
|
|
11399
|
+
explicitActivityIds.add(activityId);
|
|
11400
|
+
}
|
|
11401
|
+
}
|
|
11402
|
+
for (const item of action.items) {
|
|
11403
|
+
if (!validReference(item.parent, true, action.mode, batchIds, deps)) {
|
|
11404
|
+
return REJECTION_REASON.PARENT_NOT_FOUND;
|
|
11405
|
+
}
|
|
11406
|
+
}
|
|
11407
|
+
if (hasParentCycle(action.items)) return REJECTION_REASON.INVALID;
|
|
11408
|
+
const explicitLinkIds = /* @__PURE__ */ new Set();
|
|
11409
|
+
for (const link of action.links) {
|
|
11410
|
+
if (!Object.values(LINK_TYPE).includes(link.type) || !Number.isFinite(link.lag)) {
|
|
11411
|
+
return !Number.isFinite(link.lag) ? REJECTION_REASON.INVALID_LINK_LAG : REJECTION_REASON.INVALID_LINK_TYPE;
|
|
11412
|
+
}
|
|
11413
|
+
if (!validReference(link.source, false, action.mode, batchIds, deps) || !validReference(link.target, false, action.mode, batchIds, deps)) {
|
|
11414
|
+
return REJECTION_REASON.ACTIVITY_NOT_FOUND;
|
|
11415
|
+
}
|
|
11416
|
+
if (link.linkId !== void 0) {
|
|
11417
|
+
const linkId = String(link.linkId);
|
|
11418
|
+
if (linkId.length === 0 || explicitLinkIds.has(linkId) || action.mode === "append" && deps.adapter.getLink(linkId) !== null) {
|
|
11419
|
+
return REJECTION_REASON.INVALID;
|
|
11420
|
+
}
|
|
11421
|
+
explicitLinkIds.add(linkId);
|
|
11422
|
+
}
|
|
11423
|
+
}
|
|
11424
|
+
return null;
|
|
11425
|
+
}
|
|
11426
|
+
function validReference(reference, allowRoot, mode, batchIds, deps) {
|
|
11427
|
+
if (typeof reference === "string") {
|
|
11428
|
+
if (reference === "0") return allowRoot;
|
|
11429
|
+
return mode === "append" && deps.adapter.getActivity(String(reference)) !== null;
|
|
11430
|
+
}
|
|
11431
|
+
return reference !== null && reference.batchId.length > 0 && batchIds.has(reference.batchId);
|
|
11432
|
+
}
|
|
11433
|
+
function hasParentCycle(items) {
|
|
11434
|
+
const parentByBatchId = /* @__PURE__ */ new Map();
|
|
11435
|
+
for (const item of items) {
|
|
11436
|
+
if (typeof item.parent !== "string") {
|
|
11437
|
+
parentByBatchId.set(item.batchId, item.parent.batchId);
|
|
11438
|
+
}
|
|
11439
|
+
}
|
|
11440
|
+
const verified = /* @__PURE__ */ new Set();
|
|
11441
|
+
for (const item of items) {
|
|
11442
|
+
const path = /* @__PURE__ */ new Set();
|
|
11443
|
+
let current = item.batchId;
|
|
11444
|
+
while (current !== void 0 && !verified.has(current)) {
|
|
11445
|
+
if (path.has(current)) return true;
|
|
11446
|
+
path.add(current);
|
|
11447
|
+
current = parentByBatchId.get(current);
|
|
11448
|
+
}
|
|
11449
|
+
for (const batchId of path) verified.add(batchId);
|
|
11450
|
+
}
|
|
11451
|
+
return false;
|
|
11452
|
+
}
|
|
11453
|
+
function buildBatchPlan(action, deps) {
|
|
11454
|
+
for (const item of action.items) {
|
|
11455
|
+
if (item.activityId !== void 0) {
|
|
11456
|
+
deps.activityIdGen.bump(item.activityId);
|
|
11457
|
+
}
|
|
11458
|
+
}
|
|
11459
|
+
const activityIdsByBatchId = /* @__PURE__ */ new Map();
|
|
11460
|
+
for (const item of action.items) {
|
|
11461
|
+
activityIdsByBatchId.set(
|
|
11462
|
+
item.batchId,
|
|
11463
|
+
item.activityId ?? deps.activityIdGen.next()
|
|
11464
|
+
);
|
|
11465
|
+
}
|
|
11466
|
+
for (const link of action.links) {
|
|
11467
|
+
if (link.linkId !== void 0) deps.linkIdGen.bump(link.linkId);
|
|
11468
|
+
}
|
|
11469
|
+
const linkIds = action.links.map(
|
|
11470
|
+
(link) => link.linkId ?? deps.linkIdGen.next()
|
|
11471
|
+
);
|
|
11472
|
+
const insertionCorrelativeIds = planInsertionCorrelatives(
|
|
11473
|
+
action,
|
|
11474
|
+
activityIdsByBatchId,
|
|
11475
|
+
deps
|
|
11476
|
+
);
|
|
11477
|
+
const items = [];
|
|
11478
|
+
for (const item of action.items) {
|
|
11479
|
+
const activityId = activityIdsByBatchId.get(item.batchId);
|
|
11480
|
+
const insertionCorrelativeId = insertionCorrelativeIds.get(item.batchId);
|
|
11481
|
+
if (activityId === void 0 || insertionCorrelativeId === void 0) {
|
|
11482
|
+
return { ok: false, reason: REJECTION_REASON.INVALID };
|
|
11483
|
+
}
|
|
11484
|
+
const parentBatchId = typeof item.parent === "string" ? null : item.parent.batchId;
|
|
11485
|
+
const parentId = typeof item.parent === "string" ? item.parent : activityIdsByBatchId.get(item.parent.batchId);
|
|
11486
|
+
if (parentId === void 0) {
|
|
11487
|
+
return { ok: false, reason: REJECTION_REASON.PARENT_NOT_FOUND };
|
|
11488
|
+
}
|
|
11489
|
+
items.push({
|
|
11490
|
+
input: item,
|
|
11491
|
+
activityId,
|
|
11492
|
+
parentId,
|
|
11493
|
+
parentBatchId,
|
|
11494
|
+
insertionCorrelativeId
|
|
11495
|
+
});
|
|
11496
|
+
}
|
|
11497
|
+
const links = [];
|
|
11498
|
+
for (const [index, link] of action.links.entries()) {
|
|
11499
|
+
const source = resolveActivityReference(link.source, activityIdsByBatchId);
|
|
11500
|
+
const target = resolveActivityReference(link.target, activityIdsByBatchId);
|
|
11501
|
+
const linkId = linkIds[index];
|
|
11502
|
+
if (source === null || target === null || linkId === void 0) {
|
|
11503
|
+
return { ok: false, reason: REJECTION_REASON.ACTIVITY_NOT_FOUND };
|
|
11504
|
+
}
|
|
11505
|
+
links.push({
|
|
11506
|
+
source,
|
|
11507
|
+
target,
|
|
11508
|
+
type: link.type,
|
|
11509
|
+
lag: link.lag,
|
|
11510
|
+
linkId
|
|
11511
|
+
});
|
|
11512
|
+
}
|
|
11513
|
+
return { ok: true, plan: { items, links } };
|
|
11514
|
+
}
|
|
11515
|
+
function planInsertionCorrelatives(action, activityIdsByBatchId, deps) {
|
|
11516
|
+
if (isSimpleRootAppend(action)) {
|
|
11517
|
+
const firstCorrelativeId = deps.adapter.activityCount();
|
|
11518
|
+
return new Map(
|
|
11519
|
+
action.items.map((item, index) => [
|
|
11520
|
+
item.batchId,
|
|
11521
|
+
firstCorrelativeId + index
|
|
11522
|
+
])
|
|
11523
|
+
);
|
|
11524
|
+
}
|
|
11525
|
+
const newIds = new Set(
|
|
11526
|
+
Array.from(activityIdsByBatchId.values(), (id) => String(id))
|
|
11527
|
+
);
|
|
11528
|
+
const plansByParent = /* @__PURE__ */ new Map();
|
|
11529
|
+
for (const item of action.items) {
|
|
11530
|
+
const activityId = activityIdsByBatchId.get(item.batchId);
|
|
11531
|
+
if (activityId === void 0) continue;
|
|
11532
|
+
const parentId = typeof item.parent === "string" ? item.parent : activityIdsByBatchId.get(item.parent.batchId);
|
|
11533
|
+
if (parentId === void 0) continue;
|
|
11534
|
+
const parentKey = String(parentId);
|
|
11535
|
+
let plan = plansByParent.get(parentKey);
|
|
11536
|
+
if (!plan) {
|
|
11537
|
+
const existing = action.mode === "replace" || typeof item.parent !== "string" && activityIdsByBatchId.has(item.parent.batchId) ? [] : collectSiblings(deps.adapter, parentKey).sort((left, right) => {
|
|
11538
|
+
const diff = readCorrelativeId(left) - readCorrelativeId(right);
|
|
11539
|
+
return diff || String(left.id).localeCompare(String(right.id));
|
|
11540
|
+
}).map((activity) => String(activity.id));
|
|
11541
|
+
plan = { existing, newIdsByTarget: /* @__PURE__ */ new Map() };
|
|
11542
|
+
plansByParent.set(parentKey, plan);
|
|
11543
|
+
}
|
|
11544
|
+
const target = item.index === void 0 ? plan.existing.length : Math.min(item.index, plan.existing.length);
|
|
11545
|
+
const idsAtTarget = plan.newIdsByTarget.get(target) ?? [];
|
|
11546
|
+
idsAtTarget.push(String(activityId));
|
|
11547
|
+
plan.newIdsByTarget.set(target, idsAtTarget);
|
|
11548
|
+
}
|
|
11549
|
+
const result = /* @__PURE__ */ new Map();
|
|
11550
|
+
const batchIdByActivityId = new Map(
|
|
11551
|
+
Array.from(activityIdsByBatchId, ([batchId, activityId]) => [
|
|
11552
|
+
String(activityId),
|
|
11553
|
+
batchId
|
|
11554
|
+
])
|
|
11555
|
+
);
|
|
11556
|
+
for (const plan of plansByParent.values()) {
|
|
11557
|
+
const order = [];
|
|
11558
|
+
for (let target = 0; target <= plan.existing.length; target += 1) {
|
|
11559
|
+
for (const newId of plan.newIdsByTarget.get(target) ?? []) {
|
|
11560
|
+
order.push(newId);
|
|
11561
|
+
}
|
|
11562
|
+
const existingId = plan.existing[target];
|
|
11563
|
+
if (existingId !== void 0) order.push(existingId);
|
|
11564
|
+
}
|
|
11565
|
+
let index = 0;
|
|
11566
|
+
while (index < order.length) {
|
|
11567
|
+
if (!newIds.has(order[index])) {
|
|
11568
|
+
index += 1;
|
|
11569
|
+
continue;
|
|
11570
|
+
}
|
|
11571
|
+
const start = index;
|
|
11572
|
+
while (index < order.length && newIds.has(order[index])) index += 1;
|
|
11573
|
+
const count = index - start;
|
|
11574
|
+
const left = start > 0 ? activityCorrelative(order[start - 1], deps) : null;
|
|
11575
|
+
const right = index < order.length ? activityCorrelative(order[index], deps) : null;
|
|
11576
|
+
for (let offset = 0; offset < count; offset += 1) {
|
|
11577
|
+
const id = order[start + offset];
|
|
11578
|
+
const batchId = batchIdByActivityId.get(id);
|
|
11579
|
+
if (batchId === void 0) continue;
|
|
11580
|
+
result.set(
|
|
11581
|
+
batchId,
|
|
11582
|
+
interpolateCorrelative(left, right, offset + 1, count)
|
|
11583
|
+
);
|
|
11584
|
+
}
|
|
11585
|
+
}
|
|
11586
|
+
}
|
|
11587
|
+
return result;
|
|
11588
|
+
}
|
|
11589
|
+
function isSimpleRootAppend(action) {
|
|
11590
|
+
return action.mode === "append" && action.items.every(
|
|
11591
|
+
(item) => item.parent === "0" && item.index === void 0
|
|
11592
|
+
);
|
|
11593
|
+
}
|
|
11594
|
+
function activityCorrelative(activityId, deps) {
|
|
11595
|
+
const activity = deps.adapter.getActivity(activityId);
|
|
11596
|
+
return activity ? readCorrelativeId(activity) : 0;
|
|
11597
|
+
}
|
|
11598
|
+
function interpolateCorrelative(left, right, position, count) {
|
|
11599
|
+
if (left !== null && right !== null && right > left) {
|
|
11600
|
+
return left + (right - left) * position / (count + 1);
|
|
11601
|
+
}
|
|
11602
|
+
if (left !== null) return left + position;
|
|
11603
|
+
if (right !== null) return right - (count - position + 1);
|
|
11604
|
+
return position - 1;
|
|
11605
|
+
}
|
|
11606
|
+
async function createPlannedActivities(action, plannedItems, deps) {
|
|
11607
|
+
const childrenByParent = /* @__PURE__ */ new Map();
|
|
11608
|
+
const ready = [];
|
|
11609
|
+
for (const item of plannedItems) {
|
|
11610
|
+
if (item.parentBatchId === null) {
|
|
11611
|
+
ready.push(item);
|
|
11612
|
+
continue;
|
|
11613
|
+
}
|
|
11614
|
+
const children = childrenByParent.get(item.parentBatchId) ?? [];
|
|
11615
|
+
children.push(item);
|
|
11616
|
+
childrenByParent.set(item.parentBatchId, children);
|
|
11617
|
+
}
|
|
11618
|
+
for (let index = 0; index < ready.length; index += 1) {
|
|
11619
|
+
const item = ready[index];
|
|
11620
|
+
const core = await createActivityCore(
|
|
11621
|
+
{
|
|
11622
|
+
parentId: item.parentId,
|
|
11623
|
+
activityId: item.activityId,
|
|
11624
|
+
...item.input.overrides === void 0 ? {} : { overrides: item.input.overrides },
|
|
11625
|
+
...action.eventSource === void 0 ? {} : { eventSource: action.eventSource }
|
|
11626
|
+
},
|
|
11627
|
+
deps,
|
|
11628
|
+
{
|
|
11629
|
+
skipCorrelativeRecompute: true,
|
|
11630
|
+
skipBeforeSnap: true,
|
|
11631
|
+
skipCacheInvalidation: true,
|
|
11632
|
+
skipRollupCascades: true,
|
|
11633
|
+
insertionCorrelativeId: item.insertionCorrelativeId
|
|
11634
|
+
}
|
|
11635
|
+
);
|
|
11636
|
+
if (!core.ok) return core;
|
|
11637
|
+
for (const child of childrenByParent.get(item.input.batchId) ?? []) {
|
|
11638
|
+
ready.push(child);
|
|
11639
|
+
}
|
|
11640
|
+
}
|
|
11641
|
+
if (ready.length !== plannedItems.length) {
|
|
11642
|
+
return { ok: false, reason: REJECTION_REASON.INVALID };
|
|
11643
|
+
}
|
|
11644
|
+
const recomputedParents = /* @__PURE__ */ new Set();
|
|
11645
|
+
for (let index = ready.length - 1; index >= 0; index -= 1) {
|
|
11646
|
+
const parentId = ready[index].parentId;
|
|
11647
|
+
if (parentId === "0" || recomputedParents.has(String(parentId))) continue;
|
|
11648
|
+
recomputedParents.add(String(parentId));
|
|
11649
|
+
recomputeRollupCascadesForParent(parentId, deps.adapter);
|
|
11650
|
+
}
|
|
11651
|
+
return { ok: true };
|
|
11652
|
+
}
|
|
11653
|
+
function resolveActivityReference(reference, activityIdsByBatchId) {
|
|
11654
|
+
if (typeof reference === "string") return reference;
|
|
11655
|
+
return activityIdsByBatchId.get(reference.batchId) ?? null;
|
|
11656
|
+
}
|
|
11657
|
+
function snapshotAllLinks(deps) {
|
|
11658
|
+
return new Map(
|
|
11659
|
+
deps.adapter.getAllLinks().map((link) => [String(link.id), { ...link }])
|
|
11660
|
+
);
|
|
11661
|
+
}
|
|
11662
|
+
function stringKeyedLinks(links) {
|
|
11663
|
+
return new Map(Array.from(links, ([linkId, link]) => [String(linkId), link]));
|
|
11664
|
+
}
|
|
11665
|
+
|
|
11666
|
+
// src/dispatch/handlers/activity-paste.ts
|
|
11667
|
+
function resolvePasteRootDestination(destination, adapter) {
|
|
11668
|
+
if ("afterSiblingId" in destination) {
|
|
11669
|
+
const sibling = adapter.getActivity(destination.afterSiblingId);
|
|
11670
|
+
if (!sibling) {
|
|
11671
|
+
return { ok: false, reason: REJECTION_REASON.ANCHOR_SIBLING_NOT_FOUND };
|
|
11672
|
+
}
|
|
11673
|
+
const parentId2 = sibling.parentId == null ? ROOT_PARENT_ID : String(sibling.parentId);
|
|
11674
|
+
return { ok: true, parentId: parentId2, firstAfter: destination.afterSiblingId };
|
|
11675
|
+
}
|
|
11676
|
+
const parentId = destination.parentId;
|
|
11677
|
+
const siblings = collectSiblings(adapter, parentId).sort(
|
|
11678
|
+
(a, b) => readCorrelativeId(a) - readCorrelativeId(b)
|
|
11679
|
+
);
|
|
11680
|
+
const before = siblings[destination.index]?.id;
|
|
11681
|
+
return { ok: true, parentId, firstBefore: before };
|
|
11682
|
+
}
|
|
11683
|
+
function applyPasteDomainSemantics(overrides) {
|
|
11684
|
+
const { customId: _customId, ...content } = overrides;
|
|
11685
|
+
return {
|
|
11686
|
+
...content,
|
|
11687
|
+
progress: 0,
|
|
11688
|
+
ponderator: 0,
|
|
11689
|
+
usedCost: 0,
|
|
11690
|
+
workHours: 0,
|
|
11691
|
+
isLookahead: false
|
|
11692
|
+
};
|
|
11693
|
+
}
|
|
11694
|
+
async function dispatchActivityPaste(action, options, deps) {
|
|
11695
|
+
const { adapter, scheduler, sector } = deps;
|
|
11696
|
+
const rootDest = resolvePasteRootDestination(action.destination, adapter);
|
|
11697
|
+
if (!rootDest.ok) return rootDest;
|
|
11698
|
+
const beforeSnap = await snapshotActivities(adapter, /* @__PURE__ */ new Set());
|
|
11699
|
+
const touched = /* @__PURE__ */ new Set();
|
|
11700
|
+
const originalToNew = /* @__PURE__ */ new Map();
|
|
11701
|
+
const createdIds = [];
|
|
11702
|
+
let prevRootId;
|
|
11703
|
+
for (const input of action.activities) {
|
|
11704
|
+
const mappedParent = originalToNew.get(input.originalParentId);
|
|
11705
|
+
let parentId;
|
|
11706
|
+
let afterSiblingId;
|
|
11707
|
+
let beforeSiblingId;
|
|
11708
|
+
if (mappedParent !== void 0) {
|
|
11709
|
+
parentId = mappedParent;
|
|
11710
|
+
} else {
|
|
11711
|
+
parentId = rootDest.parentId;
|
|
11712
|
+
if (prevRootId === void 0) {
|
|
11713
|
+
afterSiblingId = rootDest.firstAfter;
|
|
11714
|
+
beforeSiblingId = rootDest.firstBefore;
|
|
11715
|
+
} else {
|
|
11716
|
+
afterSiblingId = prevRootId;
|
|
11717
|
+
}
|
|
11718
|
+
}
|
|
11719
|
+
const core = await createActivityCore(
|
|
11720
|
+
{
|
|
11721
|
+
parentId,
|
|
11722
|
+
afterSiblingId,
|
|
11723
|
+
beforeSiblingId,
|
|
11724
|
+
...input.activityId ? { activityId: input.activityId } : {},
|
|
11725
|
+
overrides: applyPasteDomainSemantics(input.overrides),
|
|
11726
|
+
eventSource: action.eventSource
|
|
11727
|
+
},
|
|
11728
|
+
deps,
|
|
11729
|
+
{
|
|
11730
|
+
skipCorrelativeRecompute: true,
|
|
11731
|
+
skipBeforeSnap: true,
|
|
11732
|
+
customIdReferenceId: action.referenceActivityId
|
|
11733
|
+
}
|
|
11734
|
+
);
|
|
11735
|
+
if (!core.ok) return core;
|
|
11736
|
+
if (input.baselineSnapshot !== void 0) {
|
|
11737
|
+
const snapshot = input.baselineSnapshot;
|
|
11738
|
+
adapter.setActivityField(
|
|
11739
|
+
core.newId,
|
|
11740
|
+
"baselineSnapshot",
|
|
11741
|
+
snapshot ? {
|
|
11742
|
+
...snapshot,
|
|
11743
|
+
startDate: snapshot.startDate ? new Date(snapshot.startDate) : null,
|
|
11744
|
+
endDate: snapshot.endDate ? new Date(snapshot.endDate) : null
|
|
11745
|
+
} : null
|
|
11746
|
+
);
|
|
11747
|
+
}
|
|
11748
|
+
originalToNew.set(input.originalId, core.newId);
|
|
11749
|
+
createdIds.push(core.newId);
|
|
11750
|
+
if (mappedParent === void 0) prevRootId = core.newId;
|
|
11751
|
+
}
|
|
11752
|
+
const correlativeShifts = recomputeCorrelativeIds(adapter);
|
|
11753
|
+
const createdIdSet = new Set(createdIds.map(String));
|
|
11754
|
+
foldCorrelativeShifts(
|
|
11755
|
+
adapter,
|
|
11756
|
+
correlativeShifts,
|
|
11757
|
+
createdIdSet,
|
|
11758
|
+
beforeSnap,
|
|
11759
|
+
touched
|
|
11760
|
+
);
|
|
11761
|
+
for (const createdId of createdIds) touched.add(String(createdId));
|
|
11762
|
+
const appliedLinks = [];
|
|
11763
|
+
for (const link of action.links) {
|
|
11764
|
+
const newSource = originalToNew.get(link.source);
|
|
11765
|
+
const newTarget = originalToNew.get(link.target);
|
|
11766
|
+
if (newSource === void 0 || newTarget === void 0) continue;
|
|
11767
|
+
const op = {
|
|
11768
|
+
kind: "create",
|
|
11769
|
+
source: newSource,
|
|
11770
|
+
target: newTarget,
|
|
11771
|
+
type: link.type,
|
|
11772
|
+
lag: lagDaysToHours(link.lag, sector.hoursPerDay)
|
|
11773
|
+
};
|
|
11774
|
+
const res = applyLinkOperation(op, {
|
|
11775
|
+
port: adapter,
|
|
11776
|
+
newLinkId: link.linkId ? () => link.linkId : deps.linkIdGen.next
|
|
11777
|
+
});
|
|
11778
|
+
appliedLinks.push({
|
|
11779
|
+
op,
|
|
11780
|
+
finalLinkId: res.applied ? res.linkId : null,
|
|
11781
|
+
rejected: res.applied ? null : res.rejected ?? "link_op_rejected"
|
|
11782
|
+
});
|
|
11783
|
+
}
|
|
11784
|
+
const { scheduledIds } = await runPostMutation(
|
|
11785
|
+
{
|
|
11786
|
+
adapter,
|
|
11787
|
+
scheduler,
|
|
11788
|
+
sector: deps.sector,
|
|
11789
|
+
defaultBaseCalendarId: deps.defaultBaseCalendarId
|
|
11790
|
+
},
|
|
11791
|
+
{
|
|
11792
|
+
action,
|
|
11793
|
+
autoscheduleFrom: createdIds[0] ?? null,
|
|
11794
|
+
recomputeParentsFrom: createdIds,
|
|
11795
|
+
now: deps.now,
|
|
11796
|
+
options
|
|
11797
|
+
}
|
|
11798
|
+
);
|
|
11799
|
+
const linkChanges = buildLinkChangesForBatch(
|
|
11800
|
+
appliedLinks,
|
|
11801
|
+
/* @__PURE__ */ new Map(),
|
|
11802
|
+
adapter
|
|
11803
|
+
);
|
|
11804
|
+
return {
|
|
11805
|
+
ok: true,
|
|
11806
|
+
changes: await assembleChangeSet(adapter, {
|
|
11807
|
+
source: action,
|
|
11808
|
+
beforeSnap,
|
|
11809
|
+
touchedIds: touched,
|
|
11810
|
+
scheduledIds,
|
|
11811
|
+
links: linkChanges,
|
|
11812
|
+
hoursPerDay: sector.hoursPerDay
|
|
11813
|
+
})
|
|
11814
|
+
};
|
|
11815
|
+
}
|
|
11816
|
+
|
|
11817
|
+
// src/dispatch/handlers/sir-sync.ts
|
|
11818
|
+
function dispatchSirSync(action, deps) {
|
|
11819
|
+
const { adapter } = deps;
|
|
11820
|
+
const id = action.activityId;
|
|
11821
|
+
const snapshot = adapter.getActivity(id);
|
|
11822
|
+
if (!snapshot) {
|
|
11823
|
+
return { ok: false, reason: REJECTION_REASON.ACTIVITY_NOT_FOUND };
|
|
11824
|
+
}
|
|
11825
|
+
const before = snapshot.pendingRequestIds ?? [];
|
|
11826
|
+
const after = action.pendingRequests.map((r) => r.id);
|
|
11827
|
+
adapter.setActivityField(action.activityId, "pendingRequestIds", after);
|
|
11828
|
+
const change = {
|
|
11829
|
+
id,
|
|
11830
|
+
kind: "updated",
|
|
11831
|
+
fields: { pendingRequestIds: { before, after } },
|
|
11832
|
+
after: { id, pendingRequestIds: after }
|
|
11833
|
+
};
|
|
11834
|
+
return {
|
|
11835
|
+
ok: true,
|
|
11836
|
+
changes: {
|
|
11837
|
+
source: action,
|
|
11838
|
+
activities: [change],
|
|
11839
|
+
links: [],
|
|
11840
|
+
calendars: [],
|
|
11841
|
+
trackingEvents: []
|
|
11842
|
+
}
|
|
11843
|
+
};
|
|
11844
|
+
}
|
|
11845
|
+
|
|
11846
|
+
// src/dispatch/handlers/activity-lookahead-sync.ts
|
|
11847
|
+
function dispatchActivityLookaheadSync(action, deps) {
|
|
11848
|
+
const ids = [...new Set(action.activityIds.map(String))];
|
|
11849
|
+
const snapshots = ids.map((id) => deps.adapter.getActivity(id));
|
|
11850
|
+
if (snapshots.some((snapshot) => !snapshot)) {
|
|
11851
|
+
return { ok: false, reason: REJECTION_REASON.ACTIVITY_NOT_FOUND };
|
|
11852
|
+
}
|
|
11853
|
+
const activities = [];
|
|
11854
|
+
for (const [index, id] of ids.entries()) {
|
|
11855
|
+
const snapshot = snapshots[index];
|
|
11856
|
+
if (!snapshot) continue;
|
|
11857
|
+
const fields = {};
|
|
11858
|
+
if (snapshot.isLookahead !== action.isLookahead) {
|
|
11859
|
+
fields.isLookahead = {
|
|
11860
|
+
before: snapshot.isLookahead,
|
|
11861
|
+
after: action.isLookahead
|
|
11862
|
+
};
|
|
11863
|
+
deps.adapter.setActivityField(id, "isLookahead", action.isLookahead);
|
|
11864
|
+
}
|
|
11865
|
+
if (snapshot.hasLookaheadTasks !== action.hasLookaheadTasks) {
|
|
11866
|
+
fields.hasLookaheadTasks = {
|
|
11867
|
+
before: snapshot.hasLookaheadTasks,
|
|
11868
|
+
after: action.hasLookaheadTasks
|
|
11869
|
+
};
|
|
11870
|
+
deps.adapter.setActivityField(
|
|
11871
|
+
id,
|
|
11872
|
+
"hasLookaheadTasks",
|
|
11873
|
+
action.hasLookaheadTasks
|
|
11874
|
+
);
|
|
11875
|
+
}
|
|
11876
|
+
if (Object.keys(fields).length === 0) continue;
|
|
11877
|
+
activities.push({
|
|
11878
|
+
id,
|
|
11879
|
+
kind: "updated",
|
|
11880
|
+
fields,
|
|
11881
|
+
after: {
|
|
11882
|
+
...snapshot,
|
|
11883
|
+
isLookahead: action.isLookahead,
|
|
11884
|
+
hasLookaheadTasks: action.hasLookaheadTasks
|
|
11885
|
+
}
|
|
11886
|
+
});
|
|
11887
|
+
}
|
|
11888
|
+
return {
|
|
11889
|
+
ok: true,
|
|
11890
|
+
changes: {
|
|
11891
|
+
source: action,
|
|
11892
|
+
activities,
|
|
11893
|
+
links: [],
|
|
11894
|
+
calendars: [],
|
|
11895
|
+
trackingEvents: []
|
|
11896
|
+
}
|
|
11897
|
+
};
|
|
11196
11898
|
}
|
|
11197
11899
|
|
|
11198
11900
|
// src/internal/hierarchy/invalid-links.ts
|
|
@@ -12790,7 +13492,10 @@ async function dispatchStatusCriteriaSet(action, deps) {
|
|
|
12790
13492
|
}
|
|
12791
13493
|
|
|
12792
13494
|
// src/dispatch/dispatch.ts
|
|
12793
|
-
async function
|
|
13495
|
+
async function performDispatch(action, options, deps) {
|
|
13496
|
+
if (action.kind === "activity-batch") {
|
|
13497
|
+
return dispatchActivityBatch(action, options, deps);
|
|
13498
|
+
}
|
|
12794
13499
|
if (action.kind === "persistence-acknowledge") {
|
|
12795
13500
|
return dispatchPersistenceAcknowledge(action, deps);
|
|
12796
13501
|
}
|
|
@@ -16024,6 +16729,7 @@ var ScheduleState = class {
|
|
|
16024
16729
|
};
|
|
16025
16730
|
_writeCapture = new WriteCapture();
|
|
16026
16731
|
_viewState = new ViewStateStore();
|
|
16732
|
+
_filterProjection = null;
|
|
16027
16733
|
_viewStateBefore = null;
|
|
16028
16734
|
_lastStartDateBefore = null;
|
|
16029
16735
|
_promotionSnapshotBefore = null;
|
|
@@ -16138,6 +16844,13 @@ var ScheduleState = class {
|
|
|
16138
16844
|
setActiveFilter(nextFilter) {
|
|
16139
16845
|
this._captureViewStateOnce();
|
|
16140
16846
|
this._viewState.setActiveFilter(nextFilter);
|
|
16847
|
+
this._filterProjection = null;
|
|
16848
|
+
}
|
|
16849
|
+
getFilterProjection() {
|
|
16850
|
+
return this._filterProjection;
|
|
16851
|
+
}
|
|
16852
|
+
setFilterProjection(projection) {
|
|
16853
|
+
this._filterProjection = projection;
|
|
16141
16854
|
}
|
|
16142
16855
|
getActiveOrder() {
|
|
16143
16856
|
return this._viewState.getActiveOrder();
|
|
@@ -16179,6 +16892,7 @@ var ScheduleState = class {
|
|
|
16179
16892
|
}
|
|
16180
16893
|
hydrateViewState(seed2) {
|
|
16181
16894
|
this._viewState.hydrate(seed2);
|
|
16895
|
+
this._filterProjection = null;
|
|
16182
16896
|
this.invalidateDerivedOrder();
|
|
16183
16897
|
}
|
|
16184
16898
|
_captureViewStateOnce() {
|
|
@@ -16379,6 +17093,7 @@ var ScheduleState = class {
|
|
|
16379
17093
|
this._restoreActivityFields(before);
|
|
16380
17094
|
if (this._viewStateBefore !== null) {
|
|
16381
17095
|
this._viewState.restore(this._viewStateBefore);
|
|
17096
|
+
this._filterProjection = null;
|
|
16382
17097
|
this.invalidateDerivedOrder();
|
|
16383
17098
|
}
|
|
16384
17099
|
if (this._promotionSnapshotBefore !== null) {
|
|
@@ -16588,8 +17303,10 @@ function buildHistoryChangeSet(changes) {
|
|
|
16588
17303
|
...entry,
|
|
16589
17304
|
after: null
|
|
16590
17305
|
}));
|
|
17306
|
+
const source = changes.source.kind === "activity-batch" ? { ...changes.source, items: [], links: [] } : changes.source;
|
|
16591
17307
|
return cloneDomainValue({
|
|
16592
17308
|
...changes,
|
|
17309
|
+
source,
|
|
16593
17310
|
activities: activitiesWithoutAfter,
|
|
16594
17311
|
links: linksWithoutAfter
|
|
16595
17312
|
});
|
|
@@ -16606,7 +17323,7 @@ function captureCreatedSnapshots(state, changeSet) {
|
|
|
16606
17323
|
if (live) afterSnap.set(String(ch.id), structuredCloneActivity(live));
|
|
16607
17324
|
}
|
|
16608
17325
|
for (const ch of changeSet.links) {
|
|
16609
|
-
if (ch.kind
|
|
17326
|
+
if (ch.kind === "deleted") continue;
|
|
16610
17327
|
const live = state.getLink(String(ch.id));
|
|
16611
17328
|
if (live) afterLinks.set(String(ch.id), { ...live });
|
|
16612
17329
|
}
|
|
@@ -16829,6 +17546,17 @@ function applyChangeSetSide(state, entry, side) {
|
|
|
16829
17546
|
for (const ch of cs.links) {
|
|
16830
17547
|
if (ch.kind === "updated") {
|
|
16831
17548
|
if (!ch.fields) continue;
|
|
17549
|
+
if (Object.hasOwn(ch.fields, "source") || Object.hasOwn(ch.fields, "target")) {
|
|
17550
|
+
const snapshot = side === "before" ? entry.beforeLinks?.get(String(ch.id)) : entry.afterLinks?.get(String(ch.id));
|
|
17551
|
+
if (!snapshot) {
|
|
17552
|
+
throw new Error(
|
|
17553
|
+
`${side === "before" ? "undo" : "redo"}: missing link snapshot for ${ch.id}`
|
|
17554
|
+
);
|
|
17555
|
+
}
|
|
17556
|
+
state.removeLink(String(ch.id));
|
|
17557
|
+
state.addLink({ ...snapshot });
|
|
17558
|
+
continue;
|
|
17559
|
+
}
|
|
16832
17560
|
for (const [field, fv] of Object.entries(ch.fields)) {
|
|
16833
17561
|
setLinkFieldDynamic(
|
|
16834
17562
|
state,
|
|
@@ -17099,9 +17827,26 @@ function idArrayEquals(current, saved) {
|
|
|
17099
17827
|
return currentIds.every((value, index) => value === savedIds[index]);
|
|
17100
17828
|
}
|
|
17101
17829
|
|
|
17830
|
+
// src/boundary/save/link-changes.ts
|
|
17831
|
+
function linkChangedSince(baseline, current) {
|
|
17832
|
+
return baseline.source !== current.source || baseline.target !== current.target || baseline.lag !== current.lag || baseline.type !== current.type;
|
|
17833
|
+
}
|
|
17834
|
+
function checkNoUpdatedLinks(baseline, current) {
|
|
17835
|
+
const baselineById = new Map(
|
|
17836
|
+
baseline.map((link) => [String(link.id), link])
|
|
17837
|
+
);
|
|
17838
|
+
return current.filter((link) => {
|
|
17839
|
+
const base = baselineById.get(String(link.id));
|
|
17840
|
+
if (!base) return false;
|
|
17841
|
+
return linkChangedSince(base, link);
|
|
17842
|
+
});
|
|
17843
|
+
}
|
|
17844
|
+
|
|
17102
17845
|
// src/boundary/save/save-tracker.ts
|
|
17103
17846
|
var SaveTracker = class {
|
|
17104
17847
|
initialized = false;
|
|
17848
|
+
dirtyActivityIds = /* @__PURE__ */ new Set();
|
|
17849
|
+
dirtyLinkIds = /* @__PURE__ */ new Set();
|
|
17105
17850
|
linksAtLastSave = /* @__PURE__ */ new Map();
|
|
17106
17851
|
activitiesAtLastSave = /* @__PURE__ */ new Map();
|
|
17107
17852
|
init(activities, links) {
|
|
@@ -17111,10 +17856,16 @@ var SaveTracker = class {
|
|
|
17111
17856
|
}
|
|
17112
17857
|
snapshotActivities(activities) {
|
|
17113
17858
|
this.activitiesAtLastSave.clear();
|
|
17859
|
+
this.dirtyActivityIds.clear();
|
|
17114
17860
|
for (const activity of activities) {
|
|
17115
|
-
|
|
17861
|
+
const activityId = String(activity.id);
|
|
17862
|
+
if (activity.proplannerId == null) {
|
|
17863
|
+
this.dirtyActivityIds.add(activityId);
|
|
17864
|
+
continue;
|
|
17865
|
+
}
|
|
17866
|
+
const persistable = projectPersistableActivity(activity);
|
|
17116
17867
|
this.activitiesAtLastSave.set(String(activity.id), {
|
|
17117
|
-
persistable
|
|
17868
|
+
persistable,
|
|
17118
17869
|
deletedSnapshot: {
|
|
17119
17870
|
id: activity.id,
|
|
17120
17871
|
proplannerId: activity.proplannerId,
|
|
@@ -17122,12 +17873,20 @@ var SaveTracker = class {
|
|
|
17122
17873
|
hadBaseline: activity.baselineSnapshot !== null || activity.baselinePoints.length > 0
|
|
17123
17874
|
}
|
|
17124
17875
|
});
|
|
17876
|
+
if (activityChangedSince(activity, persistable)) {
|
|
17877
|
+
this.dirtyActivityIds.add(activityId);
|
|
17878
|
+
}
|
|
17125
17879
|
}
|
|
17126
17880
|
}
|
|
17127
17881
|
snapshotLinks(links) {
|
|
17128
17882
|
this.linksAtLastSave.clear();
|
|
17883
|
+
this.dirtyLinkIds.clear();
|
|
17129
17884
|
for (const link of links) {
|
|
17130
|
-
|
|
17885
|
+
const linkId = String(link.id);
|
|
17886
|
+
if (link.proplannerId == null) {
|
|
17887
|
+
this.dirtyLinkIds.add(linkId);
|
|
17888
|
+
continue;
|
|
17889
|
+
}
|
|
17131
17890
|
this.linksAtLastSave.set(String(link.id), {
|
|
17132
17891
|
id: link.id,
|
|
17133
17892
|
proplannerId: link.proplannerId,
|
|
@@ -17138,21 +17897,52 @@ var SaveTracker = class {
|
|
|
17138
17897
|
});
|
|
17139
17898
|
}
|
|
17140
17899
|
}
|
|
17141
|
-
|
|
17142
|
-
if (!this.initialized) return
|
|
17143
|
-
|
|
17144
|
-
|
|
17145
|
-
|
|
17146
|
-
|
|
17147
|
-
|
|
17148
|
-
|
|
17149
|
-
|
|
17150
|
-
|
|
17151
|
-
|
|
17152
|
-
|
|
17153
|
-
|
|
17154
|
-
|
|
17155
|
-
|
|
17900
|
+
recordChanges(activities, links, getActivity, getLink) {
|
|
17901
|
+
if (!this.initialized) return;
|
|
17902
|
+
for (const change of activities) {
|
|
17903
|
+
const activityId = String(change.id);
|
|
17904
|
+
const current = getActivity(activityId);
|
|
17905
|
+
const saved = this.activitiesAtLastSave.get(activityId);
|
|
17906
|
+
if (current === null ? saved !== void 0 : current.proplannerId == null || saved === void 0 || activityChangedSince(current, saved.persistable)) {
|
|
17907
|
+
this.dirtyActivityIds.add(activityId);
|
|
17908
|
+
} else {
|
|
17909
|
+
this.dirtyActivityIds.delete(activityId);
|
|
17910
|
+
}
|
|
17911
|
+
}
|
|
17912
|
+
for (const change of links) {
|
|
17913
|
+
const linkId = String(change.id);
|
|
17914
|
+
const current = getLink(linkId);
|
|
17915
|
+
const saved = this.linksAtLastSave.get(linkId);
|
|
17916
|
+
if (current === null ? saved !== void 0 : current.proplannerId == null || saved === void 0 || linkChangedSince(saved, current)) {
|
|
17917
|
+
this.dirtyLinkIds.add(linkId);
|
|
17918
|
+
} else {
|
|
17919
|
+
this.dirtyLinkIds.delete(linkId);
|
|
17920
|
+
}
|
|
17921
|
+
}
|
|
17922
|
+
}
|
|
17923
|
+
acknowledgePersistedLookahead(activityIds, getActivity) {
|
|
17924
|
+
if (!this.initialized) return;
|
|
17925
|
+
for (const activityId of activityIds) {
|
|
17926
|
+
const current = getActivity(activityId);
|
|
17927
|
+
const saved = this.activitiesAtLastSave.get(activityId);
|
|
17928
|
+
if (current === null || saved === void 0) continue;
|
|
17929
|
+
const persistable = {
|
|
17930
|
+
...saved.persistable,
|
|
17931
|
+
isLookahead: current.isLookahead
|
|
17932
|
+
};
|
|
17933
|
+
this.activitiesAtLastSave.set(activityId, {
|
|
17934
|
+
...saved,
|
|
17935
|
+
persistable
|
|
17936
|
+
});
|
|
17937
|
+
if (current.proplannerId == null || activityChangedSince(current, persistable)) {
|
|
17938
|
+
this.dirtyActivityIds.add(activityId);
|
|
17939
|
+
} else {
|
|
17940
|
+
this.dirtyActivityIds.delete(activityId);
|
|
17941
|
+
}
|
|
17942
|
+
}
|
|
17943
|
+
}
|
|
17944
|
+
hasUnsavedChanges() {
|
|
17945
|
+
return this.initialized && (this.dirtyActivityIds.size > 0 || this.dirtyLinkIds.size > 0);
|
|
17156
17946
|
}
|
|
17157
17947
|
deletedActivities(activities) {
|
|
17158
17948
|
if (!this.initialized) return [];
|
|
@@ -17190,10 +17980,13 @@ var SaveTracker = class {
|
|
|
17190
17980
|
if (link.proplannerId == null) return false;
|
|
17191
17981
|
const saved = this.linksAtLastSave.get(String(link.id));
|
|
17192
17982
|
if (!saved) return true;
|
|
17193
|
-
return
|
|
17983
|
+
return linkChangedSince(saved, link);
|
|
17194
17984
|
});
|
|
17195
17985
|
}
|
|
17196
17986
|
clear() {
|
|
17987
|
+
this.initialized = false;
|
|
17988
|
+
this.dirtyActivityIds.clear();
|
|
17989
|
+
this.dirtyLinkIds.clear();
|
|
17197
17990
|
this.linksAtLastSave.clear();
|
|
17198
17991
|
this.activitiesAtLastSave.clear();
|
|
17199
17992
|
}
|
|
@@ -17795,6 +18588,12 @@ function createActivityIdGenerator(options = {}) {
|
|
|
17795
18588
|
const n = typeof id === "number" ? id : Number(id);
|
|
17796
18589
|
if (!Number.isFinite(n)) return;
|
|
17797
18590
|
raise(n);
|
|
18591
|
+
},
|
|
18592
|
+
snapshot() {
|
|
18593
|
+
return counter;
|
|
18594
|
+
},
|
|
18595
|
+
restore(snapshot) {
|
|
18596
|
+
counter = snapshot;
|
|
17798
18597
|
}
|
|
17799
18598
|
};
|
|
17800
18599
|
}
|
|
@@ -17820,6 +18619,12 @@ function createLinkIdGenerator(options = {}) {
|
|
|
17820
18619
|
const n = typeof id === "number" ? id : Number(id);
|
|
17821
18620
|
if (!Number.isFinite(n)) return;
|
|
17822
18621
|
raise(n);
|
|
18622
|
+
},
|
|
18623
|
+
snapshot() {
|
|
18624
|
+
return counter;
|
|
18625
|
+
},
|
|
18626
|
+
restore(snapshot) {
|
|
18627
|
+
counter = snapshot;
|
|
17823
18628
|
}
|
|
17824
18629
|
};
|
|
17825
18630
|
}
|
|
@@ -17849,6 +18654,12 @@ function createUniqueCorrelativeIdGenerator(options = {}) {
|
|
|
17849
18654
|
if (uid2 == null) return;
|
|
17850
18655
|
const n = typeof uid2 === "number" ? uid2 : Number(uid2);
|
|
17851
18656
|
raise(n);
|
|
18657
|
+
},
|
|
18658
|
+
snapshot() {
|
|
18659
|
+
return counter;
|
|
18660
|
+
},
|
|
18661
|
+
restore(snapshot) {
|
|
18662
|
+
counter = snapshot;
|
|
17852
18663
|
}
|
|
17853
18664
|
};
|
|
17854
18665
|
}
|
|
@@ -17925,13 +18736,13 @@ function minSuffixFromArray(suffixes) {
|
|
|
17925
18736
|
if (!suffixes || suffixes.length === 0) {
|
|
17926
18737
|
throw new Error("minSuffixFromArray requires non-empty array");
|
|
17927
18738
|
}
|
|
17928
|
-
return suffixes.reduce((
|
|
18739
|
+
return suffixes.reduce((min2, current) => minSuffix(min2, current));
|
|
17929
18740
|
}
|
|
17930
18741
|
function maxSuffixFromArray(suffixes) {
|
|
17931
18742
|
if (!suffixes || suffixes.length === 0) {
|
|
17932
18743
|
throw new Error("maxSuffixFromArray requires non-empty array");
|
|
17933
18744
|
}
|
|
17934
|
-
return suffixes.reduce((
|
|
18745
|
+
return suffixes.reduce((max2, current) => maxSuffix(max2, current));
|
|
17935
18746
|
}
|
|
17936
18747
|
|
|
17937
18748
|
// src/generators/custom-id/generation-strategies.ts
|
|
@@ -18625,13 +19436,204 @@ function initializeCore(input) {
|
|
|
18625
19436
|
};
|
|
18626
19437
|
}
|
|
18627
19438
|
|
|
19439
|
+
// src/propagations/upward/date-gesture-preview.ts
|
|
19440
|
+
var emptyFormula = () => ({
|
|
19441
|
+
constantStart: null,
|
|
19442
|
+
variableStartOffset: null,
|
|
19443
|
+
constantEnd: null,
|
|
19444
|
+
variableEndOffset: null
|
|
19445
|
+
});
|
|
19446
|
+
var min = (left, right) => left === null ? right : Math.min(left, right);
|
|
19447
|
+
var max = (left, right) => left === null ? right : Math.max(left, right);
|
|
19448
|
+
function createDateGesturePreview(adapter, gesture, assertCurrent) {
|
|
19449
|
+
const participantIds = [
|
|
19450
|
+
...new Set(
|
|
19451
|
+
gesture.kind === "move" ? gesture.activityIds.map(String) : [String(gesture.activityId)]
|
|
19452
|
+
)
|
|
19453
|
+
];
|
|
19454
|
+
if (participantIds.length === 0) {
|
|
19455
|
+
throw new Error("[ScheduleCore] date gesture has no participants");
|
|
19456
|
+
}
|
|
19457
|
+
const formulas = buildParticipantFormulas(adapter, gesture, participantIds);
|
|
19458
|
+
addAncestorFormulas(adapter, participantIds, formulas);
|
|
19459
|
+
return {
|
|
19460
|
+
getDates(activityId, proposedEdgeTimeMs) {
|
|
19461
|
+
assertCurrent();
|
|
19462
|
+
if (!Number.isFinite(proposedEdgeTimeMs)) {
|
|
19463
|
+
throw new Error("[ScheduleCore] invalid date gesture edge");
|
|
19464
|
+
}
|
|
19465
|
+
const formula = formulas.get(String(activityId));
|
|
19466
|
+
if (!formula) return null;
|
|
19467
|
+
const startTimeMs = evaluateStart(formula, proposedEdgeTimeMs);
|
|
19468
|
+
const endTimeMs = evaluateEnd(formula, proposedEdgeTimeMs);
|
|
19469
|
+
if (startTimeMs === null || endTimeMs === null) {
|
|
19470
|
+
throw new Error(
|
|
19471
|
+
`[ScheduleCore] incomplete date gesture bounds for ${String(activityId)}`
|
|
19472
|
+
);
|
|
19473
|
+
}
|
|
19474
|
+
return { startTimeMs, endTimeMs };
|
|
19475
|
+
}
|
|
19476
|
+
};
|
|
19477
|
+
}
|
|
19478
|
+
function buildParticipantFormulas(adapter, gesture, participantIds) {
|
|
19479
|
+
const formulas = /* @__PURE__ */ new Map();
|
|
19480
|
+
const anchorId = gesture.kind === "move" ? String(gesture.anchorActivityId) : null;
|
|
19481
|
+
const anchor = anchorId === null ? null : requireLeafActivity(adapter, anchorId);
|
|
19482
|
+
if (anchorId !== null && !participantIds.includes(anchorId)) {
|
|
19483
|
+
throw new Error("[ScheduleCore] move anchor is not a participant");
|
|
19484
|
+
}
|
|
19485
|
+
for (const activityId of participantIds) {
|
|
19486
|
+
const activity = requireLeafActivity(adapter, activityId);
|
|
19487
|
+
if (gesture.kind === "move") {
|
|
19488
|
+
const anchorStart = anchor.startDate.getTime();
|
|
19489
|
+
formulas.set(activityId, {
|
|
19490
|
+
constantStart: null,
|
|
19491
|
+
variableStartOffset: activity.startDate.getTime() - anchorStart,
|
|
19492
|
+
constantEnd: null,
|
|
19493
|
+
variableEndOffset: activity.endDate.getTime() - anchorStart
|
|
19494
|
+
});
|
|
19495
|
+
} else if (gesture.kind === "resize-start") {
|
|
19496
|
+
formulas.set(activityId, {
|
|
19497
|
+
constantStart: null,
|
|
19498
|
+
variableStartOffset: 0,
|
|
19499
|
+
constantEnd: activity.endDate.getTime(),
|
|
19500
|
+
variableEndOffset: null
|
|
19501
|
+
});
|
|
19502
|
+
} else {
|
|
19503
|
+
formulas.set(activityId, {
|
|
19504
|
+
constantStart: activity.startDate.getTime(),
|
|
19505
|
+
variableStartOffset: null,
|
|
19506
|
+
constantEnd: null,
|
|
19507
|
+
variableEndOffset: 0
|
|
19508
|
+
});
|
|
19509
|
+
}
|
|
19510
|
+
}
|
|
19511
|
+
return formulas;
|
|
19512
|
+
}
|
|
19513
|
+
function addAncestorFormulas(adapter, participantIds, formulas) {
|
|
19514
|
+
const ancestors = findAncestorsOfDirty(adapter, new Set(participantIds));
|
|
19515
|
+
for (const parentId of ancestors) {
|
|
19516
|
+
const formula = emptyFormula();
|
|
19517
|
+
for (const childId of adapter.getChildren(parentId)) {
|
|
19518
|
+
const childFormula = formulas.get(childId);
|
|
19519
|
+
if (childFormula) {
|
|
19520
|
+
mergeFormula(formula, childFormula);
|
|
19521
|
+
continue;
|
|
19522
|
+
}
|
|
19523
|
+
const child = adapter.getActivity(childId);
|
|
19524
|
+
if (!child) {
|
|
19525
|
+
throw new Error(
|
|
19526
|
+
`[ScheduleCore] missing child ${childId} while previewing parent ${parentId}`
|
|
19527
|
+
);
|
|
19528
|
+
}
|
|
19529
|
+
formula.constantStart = min(
|
|
19530
|
+
formula.constantStart,
|
|
19531
|
+
child.startDate.getTime()
|
|
19532
|
+
);
|
|
19533
|
+
formula.constantEnd = max(formula.constantEnd, child.endDate.getTime());
|
|
19534
|
+
}
|
|
19535
|
+
formulas.set(parentId, formula);
|
|
19536
|
+
}
|
|
19537
|
+
}
|
|
19538
|
+
function requireLeafActivity(adapter, activityId) {
|
|
19539
|
+
const activity = adapter.getActivity(activityId);
|
|
19540
|
+
if (!activity) {
|
|
19541
|
+
throw new Error(
|
|
19542
|
+
`[ScheduleCore] missing date gesture activity ${activityId}`
|
|
19543
|
+
);
|
|
19544
|
+
}
|
|
19545
|
+
if (adapter.hasChildren(activityId)) {
|
|
19546
|
+
throw new Error(
|
|
19547
|
+
`[ScheduleCore] summary activity ${activityId} cannot be dragged directly`
|
|
19548
|
+
);
|
|
19549
|
+
}
|
|
19550
|
+
return activity;
|
|
19551
|
+
}
|
|
19552
|
+
function mergeFormula(target, source) {
|
|
19553
|
+
if (source.constantStart !== null) {
|
|
19554
|
+
target.constantStart = min(target.constantStart, source.constantStart);
|
|
19555
|
+
}
|
|
19556
|
+
if (source.variableStartOffset !== null) {
|
|
19557
|
+
target.variableStartOffset = min(
|
|
19558
|
+
target.variableStartOffset,
|
|
19559
|
+
source.variableStartOffset
|
|
19560
|
+
);
|
|
19561
|
+
}
|
|
19562
|
+
if (source.constantEnd !== null) {
|
|
19563
|
+
target.constantEnd = max(target.constantEnd, source.constantEnd);
|
|
19564
|
+
}
|
|
19565
|
+
if (source.variableEndOffset !== null) {
|
|
19566
|
+
target.variableEndOffset = max(
|
|
19567
|
+
target.variableEndOffset,
|
|
19568
|
+
source.variableEndOffset
|
|
19569
|
+
);
|
|
19570
|
+
}
|
|
19571
|
+
}
|
|
19572
|
+
function evaluateStart(formula, proposedEdgeTimeMs) {
|
|
19573
|
+
if (formula.variableStartOffset === null) return formula.constantStart;
|
|
19574
|
+
const variable = proposedEdgeTimeMs + formula.variableStartOffset;
|
|
19575
|
+
return formula.constantStart === null ? variable : Math.min(formula.constantStart, variable);
|
|
19576
|
+
}
|
|
19577
|
+
function evaluateEnd(formula, proposedEdgeTimeMs) {
|
|
19578
|
+
if (formula.variableEndOffset === null) return formula.constantEnd;
|
|
19579
|
+
const variable = proposedEdgeTimeMs + formula.variableEndOffset;
|
|
19580
|
+
return formula.constantEnd === null ? variable : Math.max(formula.constantEnd, variable);
|
|
19581
|
+
}
|
|
19582
|
+
|
|
18628
19583
|
// src/init/schedule-core.ts
|
|
19584
|
+
function changesRequireFilterProjectionRebuild(changes) {
|
|
19585
|
+
for (const change of changes.activities) {
|
|
19586
|
+
if (change.kind === "deleted") return true;
|
|
19587
|
+
const hasMovedParent = change.kind === "updated" && change.fields !== void 0 && "parentId" in change.fields;
|
|
19588
|
+
if (hasMovedParent) return true;
|
|
19589
|
+
}
|
|
19590
|
+
return false;
|
|
19591
|
+
}
|
|
19592
|
+
function collectChangedActivities(changes, adapter) {
|
|
19593
|
+
const activities = [];
|
|
19594
|
+
for (const change of changes.activities) {
|
|
19595
|
+
const activity = adapter.getActivity(change.id);
|
|
19596
|
+
if (activity === null) return null;
|
|
19597
|
+
activities.push(activity);
|
|
19598
|
+
}
|
|
19599
|
+
return activities;
|
|
19600
|
+
}
|
|
19601
|
+
function isCurrentFilterProjection(projection, filter, context) {
|
|
19602
|
+
if (projection === null || projection.filter !== filter) return false;
|
|
19603
|
+
const hasCurrentHours = projection.context.hoursPerDay === context.hoursPerDay;
|
|
19604
|
+
const hasCurrentLocale = projection.context.locale === context.locale;
|
|
19605
|
+
return hasCurrentHours && hasCurrentLocale;
|
|
19606
|
+
}
|
|
18629
19607
|
var ScheduleCore = class {
|
|
18630
19608
|
_status = SCHEDULE_CORE_STATUS.READY;
|
|
18631
19609
|
coreRuntime;
|
|
18632
19610
|
_undo = new UndoRecorder();
|
|
18633
19611
|
_opQueue = Promise.resolve();
|
|
18634
19612
|
_scheduleRevision = 0;
|
|
19613
|
+
/**
|
|
19614
|
+
* Revision counting mutations that have been ADMITTED, whether or not they
|
|
19615
|
+
* have changed anything yet. `_scheduleRevision` counts the ones that
|
|
19616
|
+
* actually did.
|
|
19617
|
+
*
|
|
19618
|
+
* INVARIANT: `_pendingScheduleRevision >= _scheduleRevision`, and equality
|
|
19619
|
+
* means no mutation is in flight.
|
|
19620
|
+
*
|
|
19621
|
+
* The two exist separately because the useful instant and the knowable
|
|
19622
|
+
* instant are not the same one. A critical-path calculation becomes garbage
|
|
19623
|
+
* the moment the next mutation is admitted, but whether that mutation is
|
|
19624
|
+
* substantive is only knowable once its ChangeSet exists, which is hundreds
|
|
19625
|
+
* of milliseconds later at project scale. Measured live on a 12268-activity
|
|
19626
|
+
* project: the cancellation flag flipped 929 ms into a 930 ms calculation,
|
|
19627
|
+
* 812 into 812 and 785 into 785, because the job's apply block queues behind
|
|
19628
|
+
* the very dispatch whose completion cancels it. Cancellation and job end
|
|
19629
|
+
* were the same event, so every discarded job ran the whole calculation.
|
|
19630
|
+
*
|
|
19631
|
+
* Splitting the counter buys the early signal without moving the meaning of
|
|
19632
|
+
* `_scheduleRevision`, whose three readers (`isCriticalPathSettled`,
|
|
19633
|
+
* `createDateGesturePreview` and the job's own `isCurrent`) are all written
|
|
19634
|
+
* against "the state actually changed".
|
|
19635
|
+
*/
|
|
19636
|
+
_pendingScheduleRevision = 0;
|
|
18635
19637
|
_criticalPathRevision = -1;
|
|
18636
19638
|
_activeCriticalPath = null;
|
|
18637
19639
|
constructor(input) {
|
|
@@ -18684,6 +19686,16 @@ var ScheduleCore = class {
|
|
|
18684
19686
|
this.assertReady();
|
|
18685
19687
|
return readActivity(this.coreRuntime.state, id);
|
|
18686
19688
|
}
|
|
19689
|
+
createDateGesturePreview(gesture) {
|
|
19690
|
+
this.assertReady();
|
|
19691
|
+
const revision = this._scheduleRevision;
|
|
19692
|
+
return createDateGesturePreview(this.coreRuntime.state, gesture, () => {
|
|
19693
|
+
this.assertReady();
|
|
19694
|
+
if (this._scheduleRevision !== revision) {
|
|
19695
|
+
throw new Error("[ScheduleCore] date gesture preview is stale");
|
|
19696
|
+
}
|
|
19697
|
+
});
|
|
19698
|
+
}
|
|
18687
19699
|
getAllActivitiesView() {
|
|
18688
19700
|
this.assertReady();
|
|
18689
19701
|
return readAllActivities(this.coreRuntime.state);
|
|
@@ -18781,10 +19793,7 @@ var ScheduleCore = class {
|
|
|
18781
19793
|
}
|
|
18782
19794
|
hasUnsavedChanges() {
|
|
18783
19795
|
this.assertReady();
|
|
18784
|
-
return this._saveTracker.hasUnsavedChanges(
|
|
18785
|
-
this.coreRuntime.state.getAllActivities(),
|
|
18786
|
-
this.getAllLinksView()
|
|
18787
|
-
);
|
|
19796
|
+
return this._saveTracker.hasUnsavedChanges();
|
|
18788
19797
|
}
|
|
18789
19798
|
getDeletedActivitiesSinceLastSave() {
|
|
18790
19799
|
this.assertReady();
|
|
@@ -18802,10 +19811,90 @@ var ScheduleCore = class {
|
|
|
18802
19811
|
);
|
|
18803
19812
|
return workPromise;
|
|
18804
19813
|
}
|
|
19814
|
+
/**
|
|
19815
|
+
* Marks a mutation as admitted and invalidates the calculation in flight.
|
|
19816
|
+
*
|
|
19817
|
+
* Called BEFORE `_enqueue`, which is the point of the whole thing: the wait
|
|
19818
|
+
* in the operation queue is part of the window a running calculation wastes,
|
|
19819
|
+
* and it is the longest part of it when several mutations are already
|
|
19820
|
+
* queued.
|
|
19821
|
+
*
|
|
19822
|
+
* `dispatchChangesSchedulingState` is a pure function of the action, so this
|
|
19823
|
+
* decision needs no state and cannot be wrong about the action's nature. What
|
|
19824
|
+
* it cannot know yet is whether the mutation will produce anything, which is
|
|
19825
|
+
* what `_settleScheduleMutation` reconciles afterwards.
|
|
19826
|
+
*/
|
|
19827
|
+
_beginScheduleMutation(action) {
|
|
19828
|
+
if (!dispatchChangesSchedulingState(action)) return false;
|
|
19829
|
+
return this._admitScheduleMutation();
|
|
19830
|
+
}
|
|
19831
|
+
/** Undo and redo have no action to classify: reaching them IS the mutation. */
|
|
19832
|
+
_admitScheduleMutation() {
|
|
19833
|
+
this._pendingScheduleRevision++;
|
|
19834
|
+
if (this._activeCriticalPath) {
|
|
19835
|
+
this._activeCriticalPath.cancellation.cancelled = true;
|
|
19836
|
+
}
|
|
19837
|
+
return true;
|
|
19838
|
+
}
|
|
19839
|
+
/**
|
|
19840
|
+
* Closes a mutation admitted by `_beginScheduleMutation`, on EVERY exit path.
|
|
19841
|
+
*
|
|
19842
|
+
* `changedState` false means the mutation was admitted and produced nothing
|
|
19843
|
+
* (rejected, non-substantive, or rolled back). The pending revision walks
|
|
19844
|
+
* back so the invariant holds and `isCriticalPathSettled` keeps telling the
|
|
19845
|
+
* truth, and the calculation this mutation killed for nothing is re-armed.
|
|
19846
|
+
*
|
|
19847
|
+
* The re-arm goes through the queue and that is not incidental. Started
|
|
19848
|
+
* outside it, the calculation would run immediately while the next queued
|
|
19849
|
+
* mutation is still waiting, and that mutation would kill it again on
|
|
19850
|
+
* admission: a cancel-and-restart treadmill burning a fresh project-wide deep
|
|
19851
|
+
* copy per lap. Inside the queue the restart cannot happen until the queue
|
|
19852
|
+
* drains, so at most one calculation is armed per settled mutation. The burst
|
|
19853
|
+
* scenario will NOT catch a regression here, because there every dispatch
|
|
19854
|
+
* succeeds and this path is never taken.
|
|
19855
|
+
*
|
|
19856
|
+
* It re-arms through `recomputeCriticalPath` rather than enqueueing the job
|
|
19857
|
+
* directly, because the queue slot must NOT await the job: the job's own
|
|
19858
|
+
* apply block needs a later slot on this same queue, so awaiting it from
|
|
19859
|
+
* inside a slot deadlocks the core. `recomputeCriticalPath` already has the
|
|
19860
|
+
* shape that returns the job's promise out of the slot instead of awaiting
|
|
19861
|
+
* it, and it keeps `_criticalPathReady` pointing at the live calculation.
|
|
19862
|
+
*/
|
|
19863
|
+
_settleScheduleMutation(admitted, changedState) {
|
|
19864
|
+
if (!admitted) return;
|
|
19865
|
+
if (changedState) return;
|
|
19866
|
+
this._pendingScheduleRevision = Math.max(
|
|
19867
|
+
this._pendingScheduleRevision - 1,
|
|
19868
|
+
this._scheduleRevision
|
|
19869
|
+
);
|
|
19870
|
+
if (this._pendingScheduleRevision !== this._scheduleRevision) return;
|
|
19871
|
+
if (this._criticalPathRevision === this._scheduleRevision) return;
|
|
19872
|
+
void this.recomputeCriticalPath().catch((error) => {
|
|
19873
|
+
this.coreRuntime.reporter.error(
|
|
19874
|
+
"[ScheduleCore] Critical Path re-arm after a barren mutation failed",
|
|
19875
|
+
error
|
|
19876
|
+
);
|
|
19877
|
+
});
|
|
19878
|
+
}
|
|
18805
19879
|
async dispatch(action, options = {}) {
|
|
18806
|
-
|
|
19880
|
+
const admitted = this._beginScheduleMutation(action);
|
|
19881
|
+
return this._enqueue(() => this._dispatchInner(action, options, admitted));
|
|
19882
|
+
}
|
|
19883
|
+
async applyActivityBatch(action, options = {}) {
|
|
19884
|
+
const admitted = this._beginScheduleMutation(action);
|
|
19885
|
+
return this._enqueue(() => this._dispatchInner(action, options, admitted));
|
|
19886
|
+
}
|
|
19887
|
+
async _dispatchInner(action, options = {}, admitted = false) {
|
|
19888
|
+
let changedState = false;
|
|
19889
|
+
try {
|
|
19890
|
+
const dispatched = await this._runDispatch(action, options);
|
|
19891
|
+
changedState = dispatched.changedState;
|
|
19892
|
+
return dispatched.result;
|
|
19893
|
+
} finally {
|
|
19894
|
+
this._settleScheduleMutation(admitted, changedState);
|
|
19895
|
+
}
|
|
18807
19896
|
}
|
|
18808
|
-
async
|
|
19897
|
+
async _runDispatch(action, options) {
|
|
18809
19898
|
this.assertReady();
|
|
18810
19899
|
const pipelineContext = buildPipelineContext(this.coreRuntime.state, {
|
|
18811
19900
|
hoursPerDay: this.coreRuntime.sector.hoursPerDay,
|
|
@@ -18813,11 +19902,16 @@ var ScheduleCore = class {
|
|
|
18813
19902
|
inputUnit: options.inputUnit ?? "days"
|
|
18814
19903
|
});
|
|
18815
19904
|
const historyPolicy = getDispatchHistoryPolicy(action);
|
|
19905
|
+
const generatorSnapshot = [
|
|
19906
|
+
this.coreRuntime.activityIdGenerator.snapshot(),
|
|
19907
|
+
this.coreRuntime.linkIdGenerator.snapshot(),
|
|
19908
|
+
this.coreRuntime.uniqueCorrelativeIdGenerator.snapshot()
|
|
19909
|
+
];
|
|
18816
19910
|
this.coreRuntime.state.beginWriteCapture();
|
|
18817
19911
|
this.coreRuntime.customIdTracker.beginCustomIdTransaction();
|
|
18818
19912
|
let result;
|
|
18819
19913
|
try {
|
|
18820
|
-
result = await
|
|
19914
|
+
result = await performDispatch(action, options, {
|
|
18821
19915
|
adapter: this.coreRuntime.state,
|
|
18822
19916
|
ctx: pipelineContext,
|
|
18823
19917
|
scheduler: this.coreRuntime.scheduler,
|
|
@@ -18833,9 +19927,9 @@ var ScheduleCore = class {
|
|
|
18833
19927
|
)?.id ?? null,
|
|
18834
19928
|
now: this.coreRuntime.clock ? endOfLocalDay(this.coreRuntime.clock()) : null
|
|
18835
19929
|
});
|
|
18836
|
-
if (!result.ok) this._rollback();
|
|
19930
|
+
if (!result.ok) this._rollback(generatorSnapshot);
|
|
18837
19931
|
} catch (error) {
|
|
18838
|
-
this._rollback();
|
|
19932
|
+
this._rollback(generatorSnapshot);
|
|
18839
19933
|
throw error;
|
|
18840
19934
|
} finally {
|
|
18841
19935
|
this.coreRuntime.state.endWriteCapture();
|
|
@@ -18858,19 +19952,36 @@ var ScheduleCore = class {
|
|
|
18858
19952
|
Date.now()
|
|
18859
19953
|
);
|
|
18860
19954
|
}
|
|
18861
|
-
if (result.ok
|
|
18862
|
-
|
|
18863
|
-
this.
|
|
18864
|
-
|
|
18865
|
-
|
|
18866
|
-
|
|
19955
|
+
if (result.ok) {
|
|
19956
|
+
if (historyPolicy === "clear-on-success") {
|
|
19957
|
+
this._saveTracker.snapshotActivities(
|
|
19958
|
+
this.coreRuntime.state.getAllActivities()
|
|
19959
|
+
);
|
|
19960
|
+
this._saveTracker.snapshotLinks(this.getAllLinksView());
|
|
19961
|
+
this._undo.clear();
|
|
19962
|
+
} else if (action.kind === "activity-lookahead-sync" && action.acknowledgePersistedLookahead === true) {
|
|
19963
|
+
this._saveTracker.acknowledgePersistedLookahead(
|
|
19964
|
+
action.activityIds,
|
|
19965
|
+
(activityId) => this.coreRuntime.state.getActivity(activityId)
|
|
19966
|
+
);
|
|
19967
|
+
} else {
|
|
19968
|
+
this._saveTracker.recordChanges(
|
|
19969
|
+
result.changes.activities,
|
|
19970
|
+
result.changes.links,
|
|
19971
|
+
(activityId) => this.coreRuntime.state.getActivity(activityId),
|
|
19972
|
+
(linkId) => this.coreRuntime.state.getLink(linkId)
|
|
19973
|
+
);
|
|
19974
|
+
}
|
|
18867
19975
|
}
|
|
18868
19976
|
result = this._withReappliedViewState(action, result);
|
|
18869
|
-
if (!result.ok) return result;
|
|
19977
|
+
if (!result.ok) return { result, changedState: false };
|
|
18870
19978
|
if (dispatchChangesSchedulingState(action) && changeSetIsSubstantive(result.changes)) {
|
|
18871
|
-
this._recordScheduleMutation(
|
|
19979
|
+
this._recordScheduleMutation(
|
|
19980
|
+
options.skipAutoSchedule !== true && options.skipCriticalPath !== true && resolveRunCriticalPath(action)
|
|
19981
|
+
);
|
|
19982
|
+
return { result: toPublicDispatchResult(result), changedState: true };
|
|
18872
19983
|
}
|
|
18873
|
-
return toPublicDispatchResult(result);
|
|
19984
|
+
return { result: toPublicDispatchResult(result), changedState: false };
|
|
18874
19985
|
}
|
|
18875
19986
|
recomputeCriticalPath() {
|
|
18876
19987
|
const operation = this._enqueue(async () => ({
|
|
@@ -18880,7 +19991,11 @@ var ScheduleCore = class {
|
|
|
18880
19991
|
return operation;
|
|
18881
19992
|
}
|
|
18882
19993
|
isCriticalPathSettled() {
|
|
18883
|
-
return this._criticalPathRevision === this._scheduleRevision &&
|
|
19994
|
+
return this._criticalPathRevision === this._scheduleRevision && // An admitted mutation has not landed yet, so whatever is applied now is
|
|
19995
|
+
// about to be stale. Without this term `whenCriticalPathSettled` would
|
|
19996
|
+
// spin: it would keep arming calculations that `isCurrent` kills on their
|
|
19997
|
+
// first check, each one paying the project-wide deep copy first.
|
|
19998
|
+
this._pendingScheduleRevision === this._scheduleRevision && this._activeCriticalPath === null;
|
|
18884
19999
|
}
|
|
18885
20000
|
async whenCriticalPathSettled() {
|
|
18886
20001
|
while (!this.isCriticalPathSettled()) {
|
|
@@ -18888,7 +20003,11 @@ var ScheduleCore = class {
|
|
|
18888
20003
|
}
|
|
18889
20004
|
}
|
|
18890
20005
|
_withReappliedViewState(action, result) {
|
|
18891
|
-
if (!result.ok
|
|
20006
|
+
if (!result.ok) return result;
|
|
20007
|
+
if (action.kind === "visibility-set") {
|
|
20008
|
+
this.coreRuntime.state.setFilterProjection(null);
|
|
20009
|
+
}
|
|
20010
|
+
if (!reappliesViewState(action)) return result;
|
|
18892
20011
|
const merged = this._reapplyViewState(result.changes);
|
|
18893
20012
|
return merged === null ? result : { ...result, changes: merged };
|
|
18894
20013
|
}
|
|
@@ -18909,11 +20028,44 @@ var ScheduleCore = class {
|
|
|
18909
20028
|
*/
|
|
18910
20029
|
_reapplyViewState(changes) {
|
|
18911
20030
|
const withFilter = this._reapplyActiveFilter(changes);
|
|
18912
|
-
const
|
|
18913
|
-
const
|
|
18914
|
-
|
|
20031
|
+
const sequenced = this._emitTouchedBranchOrder(withFilter ?? changes);
|
|
20032
|
+
const withOrder = this._reapplyActiveOrder(
|
|
20033
|
+
sequenced.changes ?? withFilter ?? changes,
|
|
20034
|
+
sequenced.touchedParents
|
|
20035
|
+
);
|
|
20036
|
+
const reapplied = withOrder ?? sequenced.changes ?? withFilter;
|
|
20037
|
+
const finalChanges = reapplied ?? changes;
|
|
20038
|
+
const createdIds = finalChanges.activities.filter((change) => change.kind === "created").map((change) => String(change.id));
|
|
20039
|
+
if (createdIds.length === 0) return reapplied;
|
|
20040
|
+
const createdAndAncestors = new Set(createdIds);
|
|
20041
|
+
const filterProjection = this.coreRuntime.state.getFilterProjection();
|
|
20042
|
+
for (const createdId of createdIds) {
|
|
20043
|
+
if (filterProjection !== null) {
|
|
20044
|
+
filterProjection.pinnedVisibleIds.add(createdId);
|
|
20045
|
+
}
|
|
20046
|
+
let activityId = createdId;
|
|
20047
|
+
while (activityId !== null && activityId !== ROOT_PARENT_ID) {
|
|
20048
|
+
createdAndAncestors.add(activityId);
|
|
20049
|
+
if (filterProjection !== null) {
|
|
20050
|
+
filterProjection.visibleIds.add(activityId);
|
|
20051
|
+
filterProjection.visibleReferenceCounts.set(
|
|
20052
|
+
activityId,
|
|
20053
|
+
(filterProjection.visibleReferenceCounts.get(activityId) ?? 0) + 1
|
|
20054
|
+
);
|
|
20055
|
+
}
|
|
20056
|
+
activityId = this.coreRuntime.state.getParentId(activityId);
|
|
20057
|
+
}
|
|
20058
|
+
}
|
|
20059
|
+
const createdVisibility = applyVisibleSet(
|
|
20060
|
+
this.coreRuntime.state,
|
|
20061
|
+
createdAndAncestors,
|
|
20062
|
+
createdAndAncestors
|
|
18915
20063
|
);
|
|
18916
|
-
|
|
20064
|
+
if (createdVisibility.length === 0) return reapplied;
|
|
20065
|
+
return {
|
|
20066
|
+
...finalChanges,
|
|
20067
|
+
viewState: [...finalChanges.viewState ?? [], ...createdVisibility]
|
|
20068
|
+
};
|
|
18917
20069
|
}
|
|
18918
20070
|
/**
|
|
18919
20071
|
* Emits `order` for the branches this mutation resequenced, when no user order
|
|
@@ -18935,18 +20087,19 @@ var ScheduleCore = class {
|
|
|
18935
20087
|
*/
|
|
18936
20088
|
_emitTouchedBranchOrder(changes) {
|
|
18937
20089
|
const adapter = this.coreRuntime.state;
|
|
18938
|
-
|
|
18939
|
-
const touched = collectResequencedParents(
|
|
20090
|
+
const touchedParents = collectResequencedParents(
|
|
18940
20091
|
changes,
|
|
18941
20092
|
(activityId) => adapter.getParentId(activityId)
|
|
18942
20093
|
);
|
|
18943
|
-
if (
|
|
18944
|
-
|
|
18945
|
-
|
|
18946
|
-
|
|
18947
|
-
|
|
18948
|
-
|
|
18949
|
-
|
|
20094
|
+
if (adapter.getActiveOrder() !== null) {
|
|
20095
|
+
return { changes: null, touchedParents };
|
|
20096
|
+
}
|
|
20097
|
+
if (touchedParents.size === 0) {
|
|
20098
|
+
return { changes: null, touchedParents };
|
|
20099
|
+
}
|
|
20100
|
+
const order = collectBranchOrderForParents(adapter, touchedParents);
|
|
20101
|
+
if (order.length === 0) return { changes: null, touchedParents };
|
|
20102
|
+
return { changes: { ...changes, order }, touchedParents };
|
|
18950
20103
|
}
|
|
18951
20104
|
/**
|
|
18952
20105
|
* Re-sequences the grid after any mutation that could have changed a value the
|
|
@@ -18957,23 +20110,63 @@ var ScheduleCore = class {
|
|
|
18957
20110
|
* Production only re-sorts after a bar drag; diverging from that is a
|
|
18958
20111
|
* deliberate product decision, not an oversight.
|
|
18959
20112
|
*/
|
|
18960
|
-
_reapplyActiveOrder(changes) {
|
|
20113
|
+
_reapplyActiveOrder(changes, resequencedParents) {
|
|
18961
20114
|
const adapter = this.coreRuntime.state;
|
|
18962
20115
|
if (adapter.getActiveOrder() === null) return null;
|
|
18963
20116
|
adapter.invalidateDerivedOrder();
|
|
18964
|
-
|
|
20117
|
+
const hasIndirectOrderChanges = changes.links.length > 0 || changes.calendars.length > 0;
|
|
20118
|
+
if (hasIndirectOrderChanges) {
|
|
20119
|
+
return { ...changes, order: collectBranchOrder(adapter) };
|
|
20120
|
+
}
|
|
20121
|
+
const affectedParents = new Set(resequencedParents);
|
|
20122
|
+
for (const change of changes.activities) {
|
|
20123
|
+
if (change.kind === "deleted") {
|
|
20124
|
+
return { ...changes, order: collectBranchOrder(adapter) };
|
|
20125
|
+
}
|
|
20126
|
+
const parentId = adapter.getParentId(change.id);
|
|
20127
|
+
affectedParents.add(
|
|
20128
|
+
parentId === null ? ROOT_PARENT_ID : String(parentId)
|
|
20129
|
+
);
|
|
20130
|
+
}
|
|
20131
|
+
const order = collectBranchOrderForParents(adapter, affectedParents);
|
|
20132
|
+
if (order.length === 0) return null;
|
|
20133
|
+
return { ...changes, order };
|
|
18965
20134
|
}
|
|
18966
20135
|
_reapplyActiveFilter(changes) {
|
|
18967
|
-
const filter = this.coreRuntime.state.getActiveFilter();
|
|
18968
|
-
if (filter === null) return null;
|
|
18969
20136
|
const adapter = this.coreRuntime.state;
|
|
18970
|
-
const
|
|
18971
|
-
|
|
18972
|
-
|
|
20137
|
+
const filter = adapter.getActiveFilter();
|
|
20138
|
+
if (filter === null) return null;
|
|
20139
|
+
const context = buildFilterContext(this.coreRuntime.sector.hoursPerDay);
|
|
20140
|
+
const projection = adapter.getFilterProjection();
|
|
20141
|
+
const projectionIsCurrent = isCurrentFilterProjection(
|
|
20142
|
+
projection,
|
|
18973
20143
|
filter,
|
|
18974
|
-
context
|
|
18975
|
-
|
|
18976
|
-
const
|
|
20144
|
+
context
|
|
20145
|
+
);
|
|
20146
|
+
const changedActivities = changesRequireFilterProjectionRebuild(changes) ? null : collectChangedActivities(changes, adapter);
|
|
20147
|
+
let affectedIds;
|
|
20148
|
+
let visibleIds;
|
|
20149
|
+
const needsProjectionRebuild = changedActivities === null || !projectionIsCurrent;
|
|
20150
|
+
if (needsProjectionRebuild) {
|
|
20151
|
+
const rebuiltProjection = buildFilterProjection({
|
|
20152
|
+
activities: adapter.getAllActivities(),
|
|
20153
|
+
parentOf: (activityId) => adapter.getParentId(activityId),
|
|
20154
|
+
filter,
|
|
20155
|
+
context,
|
|
20156
|
+
...projectionIsCurrent ? { pinnedVisibleIds: projection.pinnedVisibleIds } : {}
|
|
20157
|
+
});
|
|
20158
|
+
adapter.setFilterProjection(rebuiltProjection);
|
|
20159
|
+
visibleIds = rebuiltProjection.visibleIds;
|
|
20160
|
+
affectedIds = adapter.getAllIds();
|
|
20161
|
+
} else {
|
|
20162
|
+
affectedIds = updateFilterProjection(
|
|
20163
|
+
projection,
|
|
20164
|
+
changedActivities,
|
|
20165
|
+
(activityId) => adapter.getParentId(activityId)
|
|
20166
|
+
);
|
|
20167
|
+
visibleIds = projection.visibleIds;
|
|
20168
|
+
}
|
|
20169
|
+
const viewState = applyVisibleSet(adapter, visibleIds, affectedIds);
|
|
18977
20170
|
if (viewState.length === 0) return null;
|
|
18978
20171
|
return {
|
|
18979
20172
|
...changes,
|
|
@@ -18982,6 +20175,10 @@ var ScheduleCore = class {
|
|
|
18982
20175
|
}
|
|
18983
20176
|
_recordScheduleMutation(criticalPathIsFresh) {
|
|
18984
20177
|
this._scheduleRevision++;
|
|
20178
|
+
this._pendingScheduleRevision = Math.max(
|
|
20179
|
+
this._pendingScheduleRevision,
|
|
20180
|
+
this._scheduleRevision
|
|
20181
|
+
);
|
|
18985
20182
|
if (this._activeCriticalPath) {
|
|
18986
20183
|
this._activeCriticalPath.cancellation.cancelled = true;
|
|
18987
20184
|
}
|
|
@@ -19012,23 +20209,30 @@ var ScheduleCore = class {
|
|
|
19012
20209
|
this.coreRuntime.baseCalendars
|
|
19013
20210
|
)
|
|
19014
20211
|
);
|
|
19015
|
-
const isCurrent = () => !cancellation.cancelled && this._status !== SCHEDULE_CORE_STATUS.DESTROYED && this._scheduleRevision === revision
|
|
20212
|
+
const isCurrent = () => !cancellation.cancelled && this._status !== SCHEDULE_CORE_STATUS.DESTROYED && this._scheduleRevision === revision && // A mutation admitted but not yet settled is enough to make this result
|
|
20213
|
+
// garbage. Without this term the calculation only learns it is obsolete
|
|
20214
|
+
// when the mutation finishes, which measured live is the same instant the
|
|
20215
|
+
// job itself ends.
|
|
20216
|
+
this._pendingScheduleRevision === this._scheduleRevision;
|
|
19016
20217
|
const promise = (async () => {
|
|
19017
20218
|
const calculatedFields = await computeCriticalPathFields(
|
|
19018
20219
|
isolatedState,
|
|
19019
20220
|
this.coreRuntime.sector.hoursPerDay,
|
|
19020
20221
|
isCurrent
|
|
19021
20222
|
);
|
|
19022
|
-
if (!isCurrent()) return null;
|
|
19023
|
-
const fieldsByActivity = calculatedFields ?? /* @__PURE__ */ new Map();
|
|
20223
|
+
if (!isCurrent() || calculatedFields === null) return null;
|
|
19024
20224
|
return this._enqueue(async () => {
|
|
19025
20225
|
if (!isCurrent()) return null;
|
|
19026
20226
|
this.coreRuntime.state.beginWriteCapture();
|
|
19027
20227
|
this.coreRuntime.customIdTracker.beginCustomIdTransaction();
|
|
19028
20228
|
let changes;
|
|
19029
20229
|
try {
|
|
19030
|
-
for (const [activityId, fields] of
|
|
19031
|
-
|
|
20230
|
+
for (const [activityId, fields] of calculatedFields) {
|
|
20231
|
+
setCriticalPathFieldsIfChanged(
|
|
20232
|
+
this.coreRuntime.state,
|
|
20233
|
+
activityId,
|
|
20234
|
+
fields
|
|
20235
|
+
);
|
|
19032
20236
|
}
|
|
19033
20237
|
changes = await assembleChangeSet(this.coreRuntime.state, {
|
|
19034
20238
|
source: { kind: "init" },
|
|
@@ -19090,47 +20294,17 @@ var ScheduleCore = class {
|
|
|
19090
20294
|
}
|
|
19091
20295
|
return { changes };
|
|
19092
20296
|
}
|
|
19093
|
-
_recomputeAfterHistoryRestore() {
|
|
19094
|
-
const state = this.coreRuntime.state;
|
|
19095
|
-
recomputeAllProgressRollup(state);
|
|
19096
|
-
recomputeCanonicalRealWork(state);
|
|
19097
|
-
emitRealCost(state);
|
|
19098
|
-
if (this.coreRuntime.clock) {
|
|
19099
|
-
const now = endOfLocalDay(this.coreRuntime.clock());
|
|
19100
|
-
applyExpectedProgressLive(state, now);
|
|
19101
|
-
let hasActiveBaseline2 = false;
|
|
19102
|
-
state.forEachActivity((activity) => {
|
|
19103
|
-
hasActiveBaseline2 ||= getActiveBaseline(activity) !== null;
|
|
19104
|
-
});
|
|
19105
|
-
if (hasActiveBaseline2) {
|
|
19106
|
-
runExpectedProgressBase(
|
|
19107
|
-
state,
|
|
19108
|
-
now,
|
|
19109
|
-
this.coreRuntime.baseCalendars.find(
|
|
19110
|
-
(calendar) => calendar.baseDefault
|
|
19111
|
-
)?.id ?? null
|
|
19112
|
-
);
|
|
19113
|
-
}
|
|
19114
|
-
applyStatusPass(state, this.coreRuntime.sector.statusCriteria);
|
|
19115
|
-
}
|
|
19116
|
-
}
|
|
19117
20297
|
undo(options = {}) {
|
|
20298
|
+
const admitted = this._admitScheduleMutation();
|
|
20299
|
+
let changedState = false;
|
|
19118
20300
|
const operation = this._enqueue(async () => {
|
|
19119
|
-
|
|
19120
|
-
|
|
19121
|
-
|
|
19122
|
-
|
|
19123
|
-
|
|
19124
|
-
|
|
19125
|
-
|
|
19126
|
-
this._undo.pushRedo(entry);
|
|
19127
|
-
this._undo.resetCoalesce();
|
|
19128
|
-
const changes = buildInverseChangeSet(
|
|
19129
|
-
this.coreRuntime.state,
|
|
19130
|
-
entry,
|
|
19131
|
-
"before"
|
|
19132
|
-
);
|
|
19133
|
-
return this._reapplyViewState(changes) ?? changes;
|
|
20301
|
+
try {
|
|
20302
|
+
return await this._runUndo(options, (changed) => {
|
|
20303
|
+
changedState = changed;
|
|
20304
|
+
});
|
|
20305
|
+
} finally {
|
|
20306
|
+
this._settleScheduleMutation(admitted, changedState);
|
|
20307
|
+
}
|
|
19134
20308
|
});
|
|
19135
20309
|
void operation.then(
|
|
19136
20310
|
(changes) => {
|
|
@@ -19147,23 +20321,45 @@ var ScheduleCore = class {
|
|
|
19147
20321
|
);
|
|
19148
20322
|
return operation;
|
|
19149
20323
|
}
|
|
19150
|
-
|
|
19151
|
-
|
|
20324
|
+
async _runUndo(options, markChanged) {
|
|
20325
|
+
{
|
|
19152
20326
|
this.assertReady();
|
|
19153
|
-
|
|
20327
|
+
if (options.expectedUndoDepth !== void 0 && this._undo.undoDepth() !== options.expectedUndoDepth) {
|
|
20328
|
+
return null;
|
|
20329
|
+
}
|
|
20330
|
+
const entry = this._undo.takeUndo();
|
|
19154
20331
|
if (!entry) return null;
|
|
19155
|
-
|
|
19156
|
-
this._recomputeAfterHistoryRestore();
|
|
20332
|
+
applyUndo(this.coreRuntime.state, entry);
|
|
19157
20333
|
this._recordScheduleMutation(false);
|
|
20334
|
+
markChanged(true);
|
|
19158
20335
|
if (needsResync(entry)) this._resyncCustomIdTrackerFromModel();
|
|
19159
|
-
this._undo.
|
|
20336
|
+
this._undo.pushRedo(entry);
|
|
19160
20337
|
this._undo.resetCoalesce();
|
|
19161
20338
|
const changes = buildInverseChangeSet(
|
|
19162
20339
|
this.coreRuntime.state,
|
|
19163
20340
|
entry,
|
|
19164
|
-
"
|
|
20341
|
+
"before"
|
|
20342
|
+
);
|
|
20343
|
+
this._saveTracker.recordChanges(
|
|
20344
|
+
changes.activities,
|
|
20345
|
+
changes.links,
|
|
20346
|
+
(activityId) => this.coreRuntime.state.getActivity(activityId),
|
|
20347
|
+
(linkId) => this.coreRuntime.state.getLink(linkId)
|
|
19165
20348
|
);
|
|
19166
20349
|
return this._reapplyViewState(changes) ?? changes;
|
|
20350
|
+
}
|
|
20351
|
+
}
|
|
20352
|
+
redo(options = {}) {
|
|
20353
|
+
const admitted = this._admitScheduleMutation();
|
|
20354
|
+
let changedState = false;
|
|
20355
|
+
const operation = this._enqueue(async () => {
|
|
20356
|
+
try {
|
|
20357
|
+
return await this._runRedo((changed) => {
|
|
20358
|
+
changedState = changed;
|
|
20359
|
+
});
|
|
20360
|
+
} finally {
|
|
20361
|
+
this._settleScheduleMutation(admitted, changedState);
|
|
20362
|
+
}
|
|
19167
20363
|
});
|
|
19168
20364
|
void operation.then(
|
|
19169
20365
|
(changes) => {
|
|
@@ -19180,6 +20376,29 @@ var ScheduleCore = class {
|
|
|
19180
20376
|
);
|
|
19181
20377
|
return operation;
|
|
19182
20378
|
}
|
|
20379
|
+
async _runRedo(markChanged) {
|
|
20380
|
+
this.assertReady();
|
|
20381
|
+
const entry = this._undo.takeRedo();
|
|
20382
|
+
if (!entry) return null;
|
|
20383
|
+
applyRedo(this.coreRuntime.state, entry);
|
|
20384
|
+
this._recordScheduleMutation(false);
|
|
20385
|
+
markChanged(true);
|
|
20386
|
+
if (needsResync(entry)) this._resyncCustomIdTrackerFromModel();
|
|
20387
|
+
this._undo.pushUndo(entry);
|
|
20388
|
+
this._undo.resetCoalesce();
|
|
20389
|
+
const changes = buildInverseChangeSet(
|
|
20390
|
+
this.coreRuntime.state,
|
|
20391
|
+
entry,
|
|
20392
|
+
"after"
|
|
20393
|
+
);
|
|
20394
|
+
this._saveTracker.recordChanges(
|
|
20395
|
+
changes.activities,
|
|
20396
|
+
changes.links,
|
|
20397
|
+
(activityId) => this.coreRuntime.state.getActivity(activityId),
|
|
20398
|
+
(linkId) => this.coreRuntime.state.getLink(linkId)
|
|
20399
|
+
);
|
|
20400
|
+
return this._reapplyViewState(changes) ?? changes;
|
|
20401
|
+
}
|
|
19183
20402
|
canUndo() {
|
|
19184
20403
|
return this._undo.canUndo();
|
|
19185
20404
|
}
|
|
@@ -19220,10 +20439,17 @@ var ScheduleCore = class {
|
|
|
19220
20439
|
);
|
|
19221
20440
|
}
|
|
19222
20441
|
}
|
|
19223
|
-
_rollback() {
|
|
20442
|
+
_rollback(generatorSnapshot) {
|
|
19224
20443
|
try {
|
|
19225
20444
|
this.coreRuntime.state.restoreFromCapture();
|
|
19226
20445
|
this.coreRuntime.customIdTracker.rollbackCustomIdTransaction();
|
|
20446
|
+
if (generatorSnapshot) {
|
|
20447
|
+
this.coreRuntime.activityIdGenerator.restore(generatorSnapshot[0]);
|
|
20448
|
+
this.coreRuntime.linkIdGenerator.restore(generatorSnapshot[1]);
|
|
20449
|
+
this.coreRuntime.uniqueCorrelativeIdGenerator.restore(
|
|
20450
|
+
generatorSnapshot[2]
|
|
20451
|
+
);
|
|
20452
|
+
}
|
|
19227
20453
|
} catch (restoreError) {
|
|
19228
20454
|
this._status = SCHEDULE_CORE_STATUS.POISONED;
|
|
19229
20455
|
this.coreRuntime.reporter.error(
|
|
@@ -19243,18 +20469,6 @@ function toPublicDispatchResult(result) {
|
|
|
19243
20469
|
return publicResult;
|
|
19244
20470
|
}
|
|
19245
20471
|
|
|
19246
|
-
// src/boundary/save/link-changes.ts
|
|
19247
|
-
function checkNoUpdatedLinks(baseline, current) {
|
|
19248
|
-
const baselineById = new Map(
|
|
19249
|
-
baseline.map((link) => [String(link.id), link])
|
|
19250
|
-
);
|
|
19251
|
-
return current.filter((link) => {
|
|
19252
|
-
const base = baselineById.get(String(link.id));
|
|
19253
|
-
if (!base) return false;
|
|
19254
|
-
return base.lag !== link.lag || base.type !== link.type;
|
|
19255
|
-
});
|
|
19256
|
-
}
|
|
19257
|
-
|
|
19258
20472
|
// src/boundary/save/unsaved.ts
|
|
19259
20473
|
var getUnsavedActivities = (activities = []) => (activities ?? []).filter((activity) => !activity.proplannerId);
|
|
19260
20474
|
|