@outbuild-company/schedule-core 1.6.1 → 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 +1717 -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 +1717 -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,
|
|
@@ -10490,9 +10780,11 @@ async function createActivityCore(action, deps, opts = {}) {
|
|
|
10490
10780
|
if (parentCustomId) {
|
|
10491
10781
|
deps.customIdTracker.releaseCustomId(parentCustomId);
|
|
10492
10782
|
}
|
|
10493
|
-
|
|
10494
|
-
|
|
10495
|
-
|
|
10783
|
+
if (!opts.skipRollupCascades) {
|
|
10784
|
+
recomputeHhCascadeForActivity(newId, adapter);
|
|
10785
|
+
recomputeUsedCostCascadeForActivity(newId, adapter);
|
|
10786
|
+
recomputeCostCascadeForActivity(newId, adapter);
|
|
10787
|
+
}
|
|
10496
10788
|
recomputeAndFoldCorrelatives(
|
|
10497
10789
|
adapter,
|
|
10498
10790
|
newId,
|
|
@@ -10664,331 +10956,97 @@ function findDefaultCalendarId(calendars) {
|
|
|
10664
10956
|
return "global";
|
|
10665
10957
|
}
|
|
10666
10958
|
|
|
10667
|
-
// src/
|
|
10668
|
-
function
|
|
10669
|
-
|
|
10670
|
-
|
|
10671
|
-
|
|
10672
|
-
|
|
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);
|
|
10673
10975
|
}
|
|
10674
|
-
const parentId2 = sibling.parentId == null ? ROOT_PARENT_ID : String(sibling.parentId);
|
|
10675
|
-
return { ok: true, parentId: parentId2, firstAfter: destination.afterSiblingId };
|
|
10676
10976
|
}
|
|
10677
|
-
|
|
10678
|
-
|
|
10679
|
-
|
|
10680
|
-
|
|
10681
|
-
|
|
10682
|
-
|
|
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;
|
|
10683
10989
|
}
|
|
10684
|
-
function
|
|
10685
|
-
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;
|
|
10686
11006
|
return {
|
|
10687
|
-
|
|
10688
|
-
|
|
10689
|
-
|
|
10690
|
-
|
|
10691
|
-
|
|
10692
|
-
|
|
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
|
|
10693
11015
|
};
|
|
10694
11016
|
}
|
|
10695
|
-
|
|
10696
|
-
const
|
|
10697
|
-
|
|
10698
|
-
|
|
10699
|
-
|
|
10700
|
-
|
|
10701
|
-
|
|
10702
|
-
const
|
|
10703
|
-
|
|
10704
|
-
|
|
10705
|
-
|
|
10706
|
-
|
|
10707
|
-
|
|
10708
|
-
|
|
10709
|
-
|
|
10710
|
-
|
|
10711
|
-
|
|
10712
|
-
|
|
10713
|
-
|
|
10714
|
-
|
|
10715
|
-
|
|
10716
|
-
|
|
10717
|
-
|
|
10718
|
-
|
|
10719
|
-
|
|
10720
|
-
const
|
|
10721
|
-
|
|
10722
|
-
|
|
10723
|
-
|
|
10724
|
-
beforeSiblingId,
|
|
10725
|
-
...input.activityId ? { activityId: input.activityId } : {},
|
|
10726
|
-
overrides: applyPasteDomainSemantics(input.overrides),
|
|
10727
|
-
eventSource: action.eventSource
|
|
10728
|
-
},
|
|
10729
|
-
deps,
|
|
10730
|
-
{
|
|
10731
|
-
skipCorrelativeRecompute: true,
|
|
10732
|
-
skipBeforeSnap: true,
|
|
10733
|
-
customIdReferenceId: action.referenceActivityId
|
|
10734
|
-
}
|
|
10735
|
-
);
|
|
10736
|
-
if (!core.ok) return core;
|
|
10737
|
-
if (input.baselineSnapshot !== void 0) {
|
|
10738
|
-
const snapshot = input.baselineSnapshot;
|
|
10739
|
-
adapter.setActivityField(
|
|
10740
|
-
core.newId,
|
|
10741
|
-
"baselineSnapshot",
|
|
10742
|
-
snapshot ? {
|
|
10743
|
-
...snapshot,
|
|
10744
|
-
startDate: snapshot.startDate ? new Date(snapshot.startDate) : null,
|
|
10745
|
-
endDate: snapshot.endDate ? new Date(snapshot.endDate) : null
|
|
10746
|
-
} : null
|
|
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))
|
|
10747
11046
|
);
|
|
10748
|
-
|
|
10749
|
-
|
|
10750
|
-
|
|
10751
|
-
if (mappedParent === void 0) prevRootId = core.newId;
|
|
10752
|
-
}
|
|
10753
|
-
const correlativeShifts = recomputeCorrelativeIds(adapter);
|
|
10754
|
-
const createdIdSet = new Set(createdIds.map(String));
|
|
10755
|
-
foldCorrelativeShifts(
|
|
10756
|
-
adapter,
|
|
10757
|
-
correlativeShifts,
|
|
10758
|
-
createdIdSet,
|
|
10759
|
-
beforeSnap,
|
|
10760
|
-
touched
|
|
10761
|
-
);
|
|
10762
|
-
for (const createdId of createdIds) touched.add(String(createdId));
|
|
10763
|
-
const appliedLinks = [];
|
|
10764
|
-
for (const link of action.links) {
|
|
10765
|
-
const newSource = originalToNew.get(link.source);
|
|
10766
|
-
const newTarget = originalToNew.get(link.target);
|
|
10767
|
-
if (newSource === void 0 || newTarget === void 0) continue;
|
|
10768
|
-
const op = {
|
|
10769
|
-
kind: "create",
|
|
10770
|
-
source: newSource,
|
|
10771
|
-
target: newTarget,
|
|
10772
|
-
type: link.type,
|
|
10773
|
-
lag: lagDaysToHours(link.lag, sector.hoursPerDay)
|
|
10774
|
-
};
|
|
10775
|
-
const res = applyLinkOperation(op, {
|
|
10776
|
-
port: adapter,
|
|
10777
|
-
newLinkId: link.linkId ? () => link.linkId : deps.linkIdGen.next
|
|
10778
|
-
});
|
|
10779
|
-
appliedLinks.push({
|
|
10780
|
-
op,
|
|
10781
|
-
finalLinkId: res.applied ? res.linkId : null,
|
|
10782
|
-
rejected: res.applied ? null : res.rejected ?? "link_op_rejected"
|
|
10783
|
-
});
|
|
10784
|
-
}
|
|
10785
|
-
const { scheduledIds } = await runPostMutation(
|
|
10786
|
-
{
|
|
10787
|
-
adapter,
|
|
10788
|
-
scheduler,
|
|
10789
|
-
sector: deps.sector,
|
|
10790
|
-
defaultBaseCalendarId: deps.defaultBaseCalendarId
|
|
10791
|
-
},
|
|
10792
|
-
{
|
|
10793
|
-
action,
|
|
10794
|
-
autoscheduleFrom: createdIds[0] ?? null,
|
|
10795
|
-
recomputeParentsFrom: createdIds,
|
|
10796
|
-
now: deps.now,
|
|
10797
|
-
options
|
|
10798
|
-
}
|
|
10799
|
-
);
|
|
10800
|
-
const linkChanges = buildLinkChangesForBatch(
|
|
10801
|
-
appliedLinks,
|
|
10802
|
-
/* @__PURE__ */ new Map(),
|
|
10803
|
-
adapter
|
|
10804
|
-
);
|
|
10805
|
-
return {
|
|
10806
|
-
ok: true,
|
|
10807
|
-
changes: await assembleChangeSet(adapter, {
|
|
10808
|
-
source: action,
|
|
10809
|
-
beforeSnap,
|
|
10810
|
-
touchedIds: touched,
|
|
10811
|
-
scheduledIds,
|
|
10812
|
-
links: linkChanges,
|
|
10813
|
-
hoursPerDay: sector.hoursPerDay
|
|
10814
|
-
})
|
|
10815
|
-
};
|
|
10816
|
-
}
|
|
10817
|
-
|
|
10818
|
-
// src/dispatch/handlers/sir-sync.ts
|
|
10819
|
-
function dispatchSirSync(action, deps) {
|
|
10820
|
-
const { adapter } = deps;
|
|
10821
|
-
const id = action.activityId;
|
|
10822
|
-
const snapshot = adapter.getActivity(id);
|
|
10823
|
-
if (!snapshot) {
|
|
10824
|
-
return { ok: false, reason: REJECTION_REASON.ACTIVITY_NOT_FOUND };
|
|
10825
|
-
}
|
|
10826
|
-
const before = snapshot.pendingRequestIds ?? [];
|
|
10827
|
-
const after = action.pendingRequests.map((r) => r.id);
|
|
10828
|
-
adapter.setActivityField(action.activityId, "pendingRequestIds", after);
|
|
10829
|
-
const change = {
|
|
10830
|
-
id,
|
|
10831
|
-
kind: "updated",
|
|
10832
|
-
fields: { pendingRequestIds: { before, after } },
|
|
10833
|
-
after: { id, pendingRequestIds: after }
|
|
10834
|
-
};
|
|
10835
|
-
return {
|
|
10836
|
-
ok: true,
|
|
10837
|
-
changes: {
|
|
10838
|
-
source: action,
|
|
10839
|
-
activities: [change],
|
|
10840
|
-
links: [],
|
|
10841
|
-
calendars: [],
|
|
10842
|
-
trackingEvents: []
|
|
10843
|
-
}
|
|
10844
|
-
};
|
|
10845
|
-
}
|
|
10846
|
-
|
|
10847
|
-
// src/dispatch/handlers/activity-lookahead-sync.ts
|
|
10848
|
-
function dispatchActivityLookaheadSync(action, deps) {
|
|
10849
|
-
const ids = [...new Set(action.activityIds.map(String))];
|
|
10850
|
-
const snapshots = ids.map((id) => deps.adapter.getActivity(id));
|
|
10851
|
-
if (snapshots.some((snapshot) => !snapshot)) {
|
|
10852
|
-
return { ok: false, reason: REJECTION_REASON.ACTIVITY_NOT_FOUND };
|
|
10853
|
-
}
|
|
10854
|
-
const activities = [];
|
|
10855
|
-
for (const [index, id] of ids.entries()) {
|
|
10856
|
-
const snapshot = snapshots[index];
|
|
10857
|
-
if (!snapshot) continue;
|
|
10858
|
-
const fields = {};
|
|
10859
|
-
if (snapshot.isLookahead !== action.isLookahead) {
|
|
10860
|
-
fields.isLookahead = {
|
|
10861
|
-
before: snapshot.isLookahead,
|
|
10862
|
-
after: action.isLookahead
|
|
10863
|
-
};
|
|
10864
|
-
deps.adapter.setActivityField(id, "isLookahead", action.isLookahead);
|
|
10865
|
-
}
|
|
10866
|
-
if (snapshot.hasLookaheadTasks !== action.hasLookaheadTasks) {
|
|
10867
|
-
fields.hasLookaheadTasks = {
|
|
10868
|
-
before: snapshot.hasLookaheadTasks,
|
|
10869
|
-
after: action.hasLookaheadTasks
|
|
10870
|
-
};
|
|
10871
|
-
deps.adapter.setActivityField(
|
|
10872
|
-
id,
|
|
10873
|
-
"hasLookaheadTasks",
|
|
10874
|
-
action.hasLookaheadTasks
|
|
10875
|
-
);
|
|
10876
|
-
}
|
|
10877
|
-
if (Object.keys(fields).length === 0) continue;
|
|
10878
|
-
activities.push({
|
|
10879
|
-
id,
|
|
10880
|
-
kind: "updated",
|
|
10881
|
-
fields,
|
|
10882
|
-
after: {
|
|
10883
|
-
...snapshot,
|
|
10884
|
-
isLookahead: action.isLookahead,
|
|
10885
|
-
hasLookaheadTasks: action.hasLookaheadTasks
|
|
10886
|
-
}
|
|
10887
|
-
});
|
|
10888
|
-
}
|
|
10889
|
-
return {
|
|
10890
|
-
ok: true,
|
|
10891
|
-
changes: {
|
|
10892
|
-
source: action,
|
|
10893
|
-
activities,
|
|
10894
|
-
links: [],
|
|
10895
|
-
calendars: [],
|
|
10896
|
-
trackingEvents: []
|
|
10897
|
-
}
|
|
10898
|
-
};
|
|
10899
|
-
}
|
|
10900
|
-
|
|
10901
|
-
// src/propagations/upward/recompute-rollup-cascades.ts
|
|
10902
|
-
function recomputeRollupCascadesForParent(parentId, adapter) {
|
|
10903
|
-
recomputeHhCascadeForParent(parentId, adapter);
|
|
10904
|
-
recomputeUsedCostCascadeForParent(parentId, adapter);
|
|
10905
|
-
recomputeCostCascadeForParent(parentId, adapter);
|
|
10906
|
-
}
|
|
10907
|
-
|
|
10908
|
-
// src/propagations/shared/traversal-helpers.ts
|
|
10909
|
-
function collectDescendantIds(rootIds, adapter) {
|
|
10910
|
-
const out = /* @__PURE__ */ new Set();
|
|
10911
|
-
const stack = [];
|
|
10912
|
-
for (const id of rootIds) {
|
|
10913
|
-
const key = String(id);
|
|
10914
|
-
if (!out.has(key)) {
|
|
10915
|
-
out.add(key);
|
|
10916
|
-
stack.push(key);
|
|
10917
|
-
}
|
|
10918
|
-
}
|
|
10919
|
-
while (stack.length > 0) {
|
|
10920
|
-
const current = stack.pop();
|
|
10921
|
-
const children = adapter.getChildren(current);
|
|
10922
|
-
for (const childId of children) {
|
|
10923
|
-
const key = String(childId);
|
|
10924
|
-
if (!out.has(key)) {
|
|
10925
|
-
out.add(key);
|
|
10926
|
-
stack.push(key);
|
|
10927
|
-
}
|
|
10928
|
-
}
|
|
10929
|
-
}
|
|
10930
|
-
return out;
|
|
10931
|
-
}
|
|
10932
|
-
function collectIncidentLinkIds(activityIds, adapter) {
|
|
10933
|
-
const out = /* @__PURE__ */ new Set();
|
|
10934
|
-
for (const activityId of activityIds) {
|
|
10935
|
-
for (const linkId of adapter.getOutgoingLinkIds(activityId)) {
|
|
10936
|
-
out.add(String(linkId));
|
|
10937
|
-
}
|
|
10938
|
-
for (const linkId of adapter.getIncomingLinkIds(activityId)) {
|
|
10939
|
-
out.add(String(linkId));
|
|
10940
|
-
}
|
|
10941
|
-
}
|
|
10942
|
-
return [...out];
|
|
10943
|
-
}
|
|
10944
|
-
|
|
10945
|
-
// src/internal/hierarchy/parent-demotion.ts
|
|
10946
|
-
function restoredPromotionFields(restore) {
|
|
10947
|
-
if (!restore) return null;
|
|
10948
|
-
return {
|
|
10949
|
-
type: restore.type,
|
|
10950
|
-
startDate: new Date(restore.startDate),
|
|
10951
|
-
endDate: new Date(restore.endDate),
|
|
10952
|
-
durationHours: restore.durationHours,
|
|
10953
|
-
expectedProgressBaseline: restore.expectedProgressBaseline,
|
|
10954
|
-
constraintType: restore.constraintType,
|
|
10955
|
-
constraintDate: restore.constraintDate ? new Date(restore.constraintDate) : null,
|
|
10956
|
-
progress: restore.progress
|
|
10957
|
-
};
|
|
10958
|
-
}
|
|
10959
|
-
function canonicalEndDateField(parent, durationHours, computeEndDate) {
|
|
10960
|
-
const startDate = parent.startDate;
|
|
10961
|
-
if (!computeEndDate || !(startDate instanceof Date)) return {};
|
|
10962
|
-
return { endDate: computeEndDate(startDate, durationHours) };
|
|
10963
|
-
}
|
|
10964
|
-
function buildParentDemotionMutations(input) {
|
|
10965
|
-
if (input.remainingChildIds.length > 0) return null;
|
|
10966
|
-
const parent = input.parent;
|
|
10967
|
-
const canonicalDuration = input.defaultDurationHours;
|
|
10968
|
-
const hasCanonicalDefaults = Number.isFinite(canonicalDuration);
|
|
10969
|
-
const fields = restoredPromotionFields(input.promotionSnapshot) ?? (hasCanonicalDefaults ? {
|
|
10970
|
-
type: DEMOTION_TARGET_TYPE,
|
|
10971
|
-
durationHours: canonicalDuration,
|
|
10972
|
-
progress: 0,
|
|
10973
|
-
autoScheduling: true,
|
|
10974
|
-
constraintType: "asap",
|
|
10975
|
-
constraintDate: null,
|
|
10976
|
-
hasNewActivities: false,
|
|
10977
|
-
...canonicalEndDateField(
|
|
10978
|
-
parent,
|
|
10979
|
-
canonicalDuration,
|
|
10980
|
-
input.computeEndDate
|
|
10981
|
-
)
|
|
10982
|
-
} : { type: DEMOTION_TARGET_TYPE });
|
|
10983
|
-
if (input.idsRemoved && input.idsRemoved.size > 0) {
|
|
10984
|
-
const existing = Array.isArray(parent.newActivityIds) ? parent.newActivityIds : [];
|
|
10985
|
-
if (existing.length > 0) {
|
|
10986
|
-
const filtered = existing.filter(
|
|
10987
|
-
(id) => !input.idsRemoved.has(id) && !input.idsRemoved.has(String(id))
|
|
10988
|
-
);
|
|
10989
|
-
if (filtered.length !== existing.length) {
|
|
10990
|
-
fields.newActivityIds = filtered;
|
|
10991
|
-
}
|
|
11047
|
+
if (filtered.length !== existing.length) {
|
|
11048
|
+
fields.newActivityIds = filtered;
|
|
11049
|
+
}
|
|
10992
11050
|
}
|
|
10993
11051
|
}
|
|
10994
11052
|
if (parent.hasLookaheadTasks) fields.isLookahead = true;
|
|
@@ -11196,7 +11254,647 @@ function buildLinkDeletions(beforeLinks) {
|
|
|
11196
11254
|
after: null
|
|
11197
11255
|
});
|
|
11198
11256
|
}
|
|
11199
|
-
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
|
+
};
|
|
11200
11898
|
}
|
|
11201
11899
|
|
|
11202
11900
|
// src/internal/hierarchy/invalid-links.ts
|
|
@@ -12794,7 +13492,10 @@ async function dispatchStatusCriteriaSet(action, deps) {
|
|
|
12794
13492
|
}
|
|
12795
13493
|
|
|
12796
13494
|
// src/dispatch/dispatch.ts
|
|
12797
|
-
async function
|
|
13495
|
+
async function performDispatch(action, options, deps) {
|
|
13496
|
+
if (action.kind === "activity-batch") {
|
|
13497
|
+
return dispatchActivityBatch(action, options, deps);
|
|
13498
|
+
}
|
|
12798
13499
|
if (action.kind === "persistence-acknowledge") {
|
|
12799
13500
|
return dispatchPersistenceAcknowledge(action, deps);
|
|
12800
13501
|
}
|
|
@@ -16028,6 +16729,7 @@ var ScheduleState = class {
|
|
|
16028
16729
|
};
|
|
16029
16730
|
_writeCapture = new WriteCapture();
|
|
16030
16731
|
_viewState = new ViewStateStore();
|
|
16732
|
+
_filterProjection = null;
|
|
16031
16733
|
_viewStateBefore = null;
|
|
16032
16734
|
_lastStartDateBefore = null;
|
|
16033
16735
|
_promotionSnapshotBefore = null;
|
|
@@ -16142,6 +16844,13 @@ var ScheduleState = class {
|
|
|
16142
16844
|
setActiveFilter(nextFilter) {
|
|
16143
16845
|
this._captureViewStateOnce();
|
|
16144
16846
|
this._viewState.setActiveFilter(nextFilter);
|
|
16847
|
+
this._filterProjection = null;
|
|
16848
|
+
}
|
|
16849
|
+
getFilterProjection() {
|
|
16850
|
+
return this._filterProjection;
|
|
16851
|
+
}
|
|
16852
|
+
setFilterProjection(projection) {
|
|
16853
|
+
this._filterProjection = projection;
|
|
16145
16854
|
}
|
|
16146
16855
|
getActiveOrder() {
|
|
16147
16856
|
return this._viewState.getActiveOrder();
|
|
@@ -16183,6 +16892,7 @@ var ScheduleState = class {
|
|
|
16183
16892
|
}
|
|
16184
16893
|
hydrateViewState(seed2) {
|
|
16185
16894
|
this._viewState.hydrate(seed2);
|
|
16895
|
+
this._filterProjection = null;
|
|
16186
16896
|
this.invalidateDerivedOrder();
|
|
16187
16897
|
}
|
|
16188
16898
|
_captureViewStateOnce() {
|
|
@@ -16383,6 +17093,7 @@ var ScheduleState = class {
|
|
|
16383
17093
|
this._restoreActivityFields(before);
|
|
16384
17094
|
if (this._viewStateBefore !== null) {
|
|
16385
17095
|
this._viewState.restore(this._viewStateBefore);
|
|
17096
|
+
this._filterProjection = null;
|
|
16386
17097
|
this.invalidateDerivedOrder();
|
|
16387
17098
|
}
|
|
16388
17099
|
if (this._promotionSnapshotBefore !== null) {
|
|
@@ -16592,8 +17303,10 @@ function buildHistoryChangeSet(changes) {
|
|
|
16592
17303
|
...entry,
|
|
16593
17304
|
after: null
|
|
16594
17305
|
}));
|
|
17306
|
+
const source = changes.source.kind === "activity-batch" ? { ...changes.source, items: [], links: [] } : changes.source;
|
|
16595
17307
|
return cloneDomainValue({
|
|
16596
17308
|
...changes,
|
|
17309
|
+
source,
|
|
16597
17310
|
activities: activitiesWithoutAfter,
|
|
16598
17311
|
links: linksWithoutAfter
|
|
16599
17312
|
});
|
|
@@ -16610,7 +17323,7 @@ function captureCreatedSnapshots(state, changeSet) {
|
|
|
16610
17323
|
if (live) afterSnap.set(String(ch.id), structuredCloneActivity(live));
|
|
16611
17324
|
}
|
|
16612
17325
|
for (const ch of changeSet.links) {
|
|
16613
|
-
if (ch.kind
|
|
17326
|
+
if (ch.kind === "deleted") continue;
|
|
16614
17327
|
const live = state.getLink(String(ch.id));
|
|
16615
17328
|
if (live) afterLinks.set(String(ch.id), { ...live });
|
|
16616
17329
|
}
|
|
@@ -16833,6 +17546,17 @@ function applyChangeSetSide(state, entry, side) {
|
|
|
16833
17546
|
for (const ch of cs.links) {
|
|
16834
17547
|
if (ch.kind === "updated") {
|
|
16835
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
|
+
}
|
|
16836
17560
|
for (const [field, fv] of Object.entries(ch.fields)) {
|
|
16837
17561
|
setLinkFieldDynamic(
|
|
16838
17562
|
state,
|
|
@@ -17103,9 +17827,26 @@ function idArrayEquals(current, saved) {
|
|
|
17103
17827
|
return currentIds.every((value, index) => value === savedIds[index]);
|
|
17104
17828
|
}
|
|
17105
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
|
+
|
|
17106
17845
|
// src/boundary/save/save-tracker.ts
|
|
17107
17846
|
var SaveTracker = class {
|
|
17108
17847
|
initialized = false;
|
|
17848
|
+
dirtyActivityIds = /* @__PURE__ */ new Set();
|
|
17849
|
+
dirtyLinkIds = /* @__PURE__ */ new Set();
|
|
17109
17850
|
linksAtLastSave = /* @__PURE__ */ new Map();
|
|
17110
17851
|
activitiesAtLastSave = /* @__PURE__ */ new Map();
|
|
17111
17852
|
init(activities, links) {
|
|
@@ -17115,10 +17856,16 @@ var SaveTracker = class {
|
|
|
17115
17856
|
}
|
|
17116
17857
|
snapshotActivities(activities) {
|
|
17117
17858
|
this.activitiesAtLastSave.clear();
|
|
17859
|
+
this.dirtyActivityIds.clear();
|
|
17118
17860
|
for (const activity of activities) {
|
|
17119
|
-
|
|
17861
|
+
const activityId = String(activity.id);
|
|
17862
|
+
if (activity.proplannerId == null) {
|
|
17863
|
+
this.dirtyActivityIds.add(activityId);
|
|
17864
|
+
continue;
|
|
17865
|
+
}
|
|
17866
|
+
const persistable = projectPersistableActivity(activity);
|
|
17120
17867
|
this.activitiesAtLastSave.set(String(activity.id), {
|
|
17121
|
-
persistable
|
|
17868
|
+
persistable,
|
|
17122
17869
|
deletedSnapshot: {
|
|
17123
17870
|
id: activity.id,
|
|
17124
17871
|
proplannerId: activity.proplannerId,
|
|
@@ -17126,12 +17873,20 @@ var SaveTracker = class {
|
|
|
17126
17873
|
hadBaseline: activity.baselineSnapshot !== null || activity.baselinePoints.length > 0
|
|
17127
17874
|
}
|
|
17128
17875
|
});
|
|
17876
|
+
if (activityChangedSince(activity, persistable)) {
|
|
17877
|
+
this.dirtyActivityIds.add(activityId);
|
|
17878
|
+
}
|
|
17129
17879
|
}
|
|
17130
17880
|
}
|
|
17131
17881
|
snapshotLinks(links) {
|
|
17132
17882
|
this.linksAtLastSave.clear();
|
|
17883
|
+
this.dirtyLinkIds.clear();
|
|
17133
17884
|
for (const link of links) {
|
|
17134
|
-
|
|
17885
|
+
const linkId = String(link.id);
|
|
17886
|
+
if (link.proplannerId == null) {
|
|
17887
|
+
this.dirtyLinkIds.add(linkId);
|
|
17888
|
+
continue;
|
|
17889
|
+
}
|
|
17135
17890
|
this.linksAtLastSave.set(String(link.id), {
|
|
17136
17891
|
id: link.id,
|
|
17137
17892
|
proplannerId: link.proplannerId,
|
|
@@ -17142,21 +17897,52 @@ var SaveTracker = class {
|
|
|
17142
17897
|
});
|
|
17143
17898
|
}
|
|
17144
17899
|
}
|
|
17145
|
-
|
|
17146
|
-
if (!this.initialized) return
|
|
17147
|
-
|
|
17148
|
-
|
|
17149
|
-
|
|
17150
|
-
|
|
17151
|
-
|
|
17152
|
-
|
|
17153
|
-
|
|
17154
|
-
|
|
17155
|
-
|
|
17156
|
-
|
|
17157
|
-
|
|
17158
|
-
|
|
17159
|
-
|
|
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);
|
|
17160
17946
|
}
|
|
17161
17947
|
deletedActivities(activities) {
|
|
17162
17948
|
if (!this.initialized) return [];
|
|
@@ -17194,10 +17980,13 @@ var SaveTracker = class {
|
|
|
17194
17980
|
if (link.proplannerId == null) return false;
|
|
17195
17981
|
const saved = this.linksAtLastSave.get(String(link.id));
|
|
17196
17982
|
if (!saved) return true;
|
|
17197
|
-
return
|
|
17983
|
+
return linkChangedSince(saved, link);
|
|
17198
17984
|
});
|
|
17199
17985
|
}
|
|
17200
17986
|
clear() {
|
|
17987
|
+
this.initialized = false;
|
|
17988
|
+
this.dirtyActivityIds.clear();
|
|
17989
|
+
this.dirtyLinkIds.clear();
|
|
17201
17990
|
this.linksAtLastSave.clear();
|
|
17202
17991
|
this.activitiesAtLastSave.clear();
|
|
17203
17992
|
}
|
|
@@ -17799,6 +18588,12 @@ function createActivityIdGenerator(options = {}) {
|
|
|
17799
18588
|
const n = typeof id === "number" ? id : Number(id);
|
|
17800
18589
|
if (!Number.isFinite(n)) return;
|
|
17801
18590
|
raise(n);
|
|
18591
|
+
},
|
|
18592
|
+
snapshot() {
|
|
18593
|
+
return counter;
|
|
18594
|
+
},
|
|
18595
|
+
restore(snapshot) {
|
|
18596
|
+
counter = snapshot;
|
|
17802
18597
|
}
|
|
17803
18598
|
};
|
|
17804
18599
|
}
|
|
@@ -17824,6 +18619,12 @@ function createLinkIdGenerator(options = {}) {
|
|
|
17824
18619
|
const n = typeof id === "number" ? id : Number(id);
|
|
17825
18620
|
if (!Number.isFinite(n)) return;
|
|
17826
18621
|
raise(n);
|
|
18622
|
+
},
|
|
18623
|
+
snapshot() {
|
|
18624
|
+
return counter;
|
|
18625
|
+
},
|
|
18626
|
+
restore(snapshot) {
|
|
18627
|
+
counter = snapshot;
|
|
17827
18628
|
}
|
|
17828
18629
|
};
|
|
17829
18630
|
}
|
|
@@ -17853,6 +18654,12 @@ function createUniqueCorrelativeIdGenerator(options = {}) {
|
|
|
17853
18654
|
if (uid2 == null) return;
|
|
17854
18655
|
const n = typeof uid2 === "number" ? uid2 : Number(uid2);
|
|
17855
18656
|
raise(n);
|
|
18657
|
+
},
|
|
18658
|
+
snapshot() {
|
|
18659
|
+
return counter;
|
|
18660
|
+
},
|
|
18661
|
+
restore(snapshot) {
|
|
18662
|
+
counter = snapshot;
|
|
17856
18663
|
}
|
|
17857
18664
|
};
|
|
17858
18665
|
}
|
|
@@ -17929,13 +18736,13 @@ function minSuffixFromArray(suffixes) {
|
|
|
17929
18736
|
if (!suffixes || suffixes.length === 0) {
|
|
17930
18737
|
throw new Error("minSuffixFromArray requires non-empty array");
|
|
17931
18738
|
}
|
|
17932
|
-
return suffixes.reduce((
|
|
18739
|
+
return suffixes.reduce((min2, current) => minSuffix(min2, current));
|
|
17933
18740
|
}
|
|
17934
18741
|
function maxSuffixFromArray(suffixes) {
|
|
17935
18742
|
if (!suffixes || suffixes.length === 0) {
|
|
17936
18743
|
throw new Error("maxSuffixFromArray requires non-empty array");
|
|
17937
18744
|
}
|
|
17938
|
-
return suffixes.reduce((
|
|
18745
|
+
return suffixes.reduce((max2, current) => maxSuffix(max2, current));
|
|
17939
18746
|
}
|
|
17940
18747
|
|
|
17941
18748
|
// src/generators/custom-id/generation-strategies.ts
|
|
@@ -18629,13 +19436,204 @@ function initializeCore(input) {
|
|
|
18629
19436
|
};
|
|
18630
19437
|
}
|
|
18631
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
|
+
|
|
18632
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
|
+
}
|
|
18633
19607
|
var ScheduleCore = class {
|
|
18634
19608
|
_status = SCHEDULE_CORE_STATUS.READY;
|
|
18635
19609
|
coreRuntime;
|
|
18636
19610
|
_undo = new UndoRecorder();
|
|
18637
19611
|
_opQueue = Promise.resolve();
|
|
18638
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;
|
|
18639
19637
|
_criticalPathRevision = -1;
|
|
18640
19638
|
_activeCriticalPath = null;
|
|
18641
19639
|
constructor(input) {
|
|
@@ -18688,6 +19686,16 @@ var ScheduleCore = class {
|
|
|
18688
19686
|
this.assertReady();
|
|
18689
19687
|
return readActivity(this.coreRuntime.state, id);
|
|
18690
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
|
+
}
|
|
18691
19699
|
getAllActivitiesView() {
|
|
18692
19700
|
this.assertReady();
|
|
18693
19701
|
return readAllActivities(this.coreRuntime.state);
|
|
@@ -18785,10 +19793,7 @@ var ScheduleCore = class {
|
|
|
18785
19793
|
}
|
|
18786
19794
|
hasUnsavedChanges() {
|
|
18787
19795
|
this.assertReady();
|
|
18788
|
-
return this._saveTracker.hasUnsavedChanges(
|
|
18789
|
-
this.coreRuntime.state.getAllActivities(),
|
|
18790
|
-
this.getAllLinksView()
|
|
18791
|
-
);
|
|
19796
|
+
return this._saveTracker.hasUnsavedChanges();
|
|
18792
19797
|
}
|
|
18793
19798
|
getDeletedActivitiesSinceLastSave() {
|
|
18794
19799
|
this.assertReady();
|
|
@@ -18806,10 +19811,90 @@ var ScheduleCore = class {
|
|
|
18806
19811
|
);
|
|
18807
19812
|
return workPromise;
|
|
18808
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
|
+
}
|
|
18809
19879
|
async dispatch(action, options = {}) {
|
|
18810
|
-
|
|
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
|
+
}
|
|
18811
19896
|
}
|
|
18812
|
-
async
|
|
19897
|
+
async _runDispatch(action, options) {
|
|
18813
19898
|
this.assertReady();
|
|
18814
19899
|
const pipelineContext = buildPipelineContext(this.coreRuntime.state, {
|
|
18815
19900
|
hoursPerDay: this.coreRuntime.sector.hoursPerDay,
|
|
@@ -18817,11 +19902,16 @@ var ScheduleCore = class {
|
|
|
18817
19902
|
inputUnit: options.inputUnit ?? "days"
|
|
18818
19903
|
});
|
|
18819
19904
|
const historyPolicy = getDispatchHistoryPolicy(action);
|
|
19905
|
+
const generatorSnapshot = [
|
|
19906
|
+
this.coreRuntime.activityIdGenerator.snapshot(),
|
|
19907
|
+
this.coreRuntime.linkIdGenerator.snapshot(),
|
|
19908
|
+
this.coreRuntime.uniqueCorrelativeIdGenerator.snapshot()
|
|
19909
|
+
];
|
|
18820
19910
|
this.coreRuntime.state.beginWriteCapture();
|
|
18821
19911
|
this.coreRuntime.customIdTracker.beginCustomIdTransaction();
|
|
18822
19912
|
let result;
|
|
18823
19913
|
try {
|
|
18824
|
-
result = await
|
|
19914
|
+
result = await performDispatch(action, options, {
|
|
18825
19915
|
adapter: this.coreRuntime.state,
|
|
18826
19916
|
ctx: pipelineContext,
|
|
18827
19917
|
scheduler: this.coreRuntime.scheduler,
|
|
@@ -18837,9 +19927,9 @@ var ScheduleCore = class {
|
|
|
18837
19927
|
)?.id ?? null,
|
|
18838
19928
|
now: this.coreRuntime.clock ? endOfLocalDay(this.coreRuntime.clock()) : null
|
|
18839
19929
|
});
|
|
18840
|
-
if (!result.ok) this._rollback();
|
|
19930
|
+
if (!result.ok) this._rollback(generatorSnapshot);
|
|
18841
19931
|
} catch (error) {
|
|
18842
|
-
this._rollback();
|
|
19932
|
+
this._rollback(generatorSnapshot);
|
|
18843
19933
|
throw error;
|
|
18844
19934
|
} finally {
|
|
18845
19935
|
this.coreRuntime.state.endWriteCapture();
|
|
@@ -18862,19 +19952,36 @@ var ScheduleCore = class {
|
|
|
18862
19952
|
Date.now()
|
|
18863
19953
|
);
|
|
18864
19954
|
}
|
|
18865
|
-
if (result.ok
|
|
18866
|
-
|
|
18867
|
-
this.
|
|
18868
|
-
|
|
18869
|
-
|
|
18870
|
-
|
|
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
|
+
}
|
|
18871
19975
|
}
|
|
18872
19976
|
result = this._withReappliedViewState(action, result);
|
|
18873
|
-
if (!result.ok) return result;
|
|
19977
|
+
if (!result.ok) return { result, changedState: false };
|
|
18874
19978
|
if (dispatchChangesSchedulingState(action) && changeSetIsSubstantive(result.changes)) {
|
|
18875
|
-
this._recordScheduleMutation(
|
|
19979
|
+
this._recordScheduleMutation(
|
|
19980
|
+
options.skipAutoSchedule !== true && options.skipCriticalPath !== true && resolveRunCriticalPath(action)
|
|
19981
|
+
);
|
|
19982
|
+
return { result: toPublicDispatchResult(result), changedState: true };
|
|
18876
19983
|
}
|
|
18877
|
-
return toPublicDispatchResult(result);
|
|
19984
|
+
return { result: toPublicDispatchResult(result), changedState: false };
|
|
18878
19985
|
}
|
|
18879
19986
|
recomputeCriticalPath() {
|
|
18880
19987
|
const operation = this._enqueue(async () => ({
|
|
@@ -18884,7 +19991,11 @@ var ScheduleCore = class {
|
|
|
18884
19991
|
return operation;
|
|
18885
19992
|
}
|
|
18886
19993
|
isCriticalPathSettled() {
|
|
18887
|
-
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;
|
|
18888
19999
|
}
|
|
18889
20000
|
async whenCriticalPathSettled() {
|
|
18890
20001
|
while (!this.isCriticalPathSettled()) {
|
|
@@ -18892,7 +20003,11 @@ var ScheduleCore = class {
|
|
|
18892
20003
|
}
|
|
18893
20004
|
}
|
|
18894
20005
|
_withReappliedViewState(action, result) {
|
|
18895
|
-
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;
|
|
18896
20011
|
const merged = this._reapplyViewState(result.changes);
|
|
18897
20012
|
return merged === null ? result : { ...result, changes: merged };
|
|
18898
20013
|
}
|
|
@@ -18913,11 +20028,44 @@ var ScheduleCore = class {
|
|
|
18913
20028
|
*/
|
|
18914
20029
|
_reapplyViewState(changes) {
|
|
18915
20030
|
const withFilter = this._reapplyActiveFilter(changes);
|
|
18916
|
-
const
|
|
18917
|
-
const
|
|
18918
|
-
|
|
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
|
|
18919
20063
|
);
|
|
18920
|
-
|
|
20064
|
+
if (createdVisibility.length === 0) return reapplied;
|
|
20065
|
+
return {
|
|
20066
|
+
...finalChanges,
|
|
20067
|
+
viewState: [...finalChanges.viewState ?? [], ...createdVisibility]
|
|
20068
|
+
};
|
|
18921
20069
|
}
|
|
18922
20070
|
/**
|
|
18923
20071
|
* Emits `order` for the branches this mutation resequenced, when no user order
|
|
@@ -18939,18 +20087,19 @@ var ScheduleCore = class {
|
|
|
18939
20087
|
*/
|
|
18940
20088
|
_emitTouchedBranchOrder(changes) {
|
|
18941
20089
|
const adapter = this.coreRuntime.state;
|
|
18942
|
-
|
|
18943
|
-
const touched = collectResequencedParents(
|
|
20090
|
+
const touchedParents = collectResequencedParents(
|
|
18944
20091
|
changes,
|
|
18945
20092
|
(activityId) => adapter.getParentId(activityId)
|
|
18946
20093
|
);
|
|
18947
|
-
if (
|
|
18948
|
-
|
|
18949
|
-
|
|
18950
|
-
|
|
18951
|
-
|
|
18952
|
-
|
|
18953
|
-
|
|
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 };
|
|
18954
20103
|
}
|
|
18955
20104
|
/**
|
|
18956
20105
|
* Re-sequences the grid after any mutation that could have changed a value the
|
|
@@ -18961,23 +20110,63 @@ var ScheduleCore = class {
|
|
|
18961
20110
|
* Production only re-sorts after a bar drag; diverging from that is a
|
|
18962
20111
|
* deliberate product decision, not an oversight.
|
|
18963
20112
|
*/
|
|
18964
|
-
_reapplyActiveOrder(changes) {
|
|
20113
|
+
_reapplyActiveOrder(changes, resequencedParents) {
|
|
18965
20114
|
const adapter = this.coreRuntime.state;
|
|
18966
20115
|
if (adapter.getActiveOrder() === null) return null;
|
|
18967
20116
|
adapter.invalidateDerivedOrder();
|
|
18968
|
-
|
|
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 };
|
|
18969
20134
|
}
|
|
18970
20135
|
_reapplyActiveFilter(changes) {
|
|
18971
|
-
const filter = this.coreRuntime.state.getActiveFilter();
|
|
18972
|
-
if (filter === null) return null;
|
|
18973
20136
|
const adapter = this.coreRuntime.state;
|
|
18974
|
-
const
|
|
18975
|
-
|
|
18976
|
-
|
|
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,
|
|
18977
20143
|
filter,
|
|
18978
|
-
context
|
|
18979
|
-
|
|
18980
|
-
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);
|
|
18981
20170
|
if (viewState.length === 0) return null;
|
|
18982
20171
|
return {
|
|
18983
20172
|
...changes,
|
|
@@ -18986,6 +20175,10 @@ var ScheduleCore = class {
|
|
|
18986
20175
|
}
|
|
18987
20176
|
_recordScheduleMutation(criticalPathIsFresh) {
|
|
18988
20177
|
this._scheduleRevision++;
|
|
20178
|
+
this._pendingScheduleRevision = Math.max(
|
|
20179
|
+
this._pendingScheduleRevision,
|
|
20180
|
+
this._scheduleRevision
|
|
20181
|
+
);
|
|
18989
20182
|
if (this._activeCriticalPath) {
|
|
18990
20183
|
this._activeCriticalPath.cancellation.cancelled = true;
|
|
18991
20184
|
}
|
|
@@ -19016,23 +20209,30 @@ var ScheduleCore = class {
|
|
|
19016
20209
|
this.coreRuntime.baseCalendars
|
|
19017
20210
|
)
|
|
19018
20211
|
);
|
|
19019
|
-
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;
|
|
19020
20217
|
const promise = (async () => {
|
|
19021
20218
|
const calculatedFields = await computeCriticalPathFields(
|
|
19022
20219
|
isolatedState,
|
|
19023
20220
|
this.coreRuntime.sector.hoursPerDay,
|
|
19024
20221
|
isCurrent
|
|
19025
20222
|
);
|
|
19026
|
-
if (!isCurrent()) return null;
|
|
19027
|
-
const fieldsByActivity = calculatedFields ?? /* @__PURE__ */ new Map();
|
|
20223
|
+
if (!isCurrent() || calculatedFields === null) return null;
|
|
19028
20224
|
return this._enqueue(async () => {
|
|
19029
20225
|
if (!isCurrent()) return null;
|
|
19030
20226
|
this.coreRuntime.state.beginWriteCapture();
|
|
19031
20227
|
this.coreRuntime.customIdTracker.beginCustomIdTransaction();
|
|
19032
20228
|
let changes;
|
|
19033
20229
|
try {
|
|
19034
|
-
for (const [activityId, fields] of
|
|
19035
|
-
|
|
20230
|
+
for (const [activityId, fields] of calculatedFields) {
|
|
20231
|
+
setCriticalPathFieldsIfChanged(
|
|
20232
|
+
this.coreRuntime.state,
|
|
20233
|
+
activityId,
|
|
20234
|
+
fields
|
|
20235
|
+
);
|
|
19036
20236
|
}
|
|
19037
20237
|
changes = await assembleChangeSet(this.coreRuntime.state, {
|
|
19038
20238
|
source: { kind: "init" },
|
|
@@ -19094,47 +20294,17 @@ var ScheduleCore = class {
|
|
|
19094
20294
|
}
|
|
19095
20295
|
return { changes };
|
|
19096
20296
|
}
|
|
19097
|
-
_recomputeAfterHistoryRestore() {
|
|
19098
|
-
const state = this.coreRuntime.state;
|
|
19099
|
-
recomputeAllProgressRollup(state);
|
|
19100
|
-
recomputeCanonicalRealWork(state);
|
|
19101
|
-
emitRealCost(state);
|
|
19102
|
-
if (this.coreRuntime.clock) {
|
|
19103
|
-
const now = endOfLocalDay(this.coreRuntime.clock());
|
|
19104
|
-
applyExpectedProgressLive(state, now);
|
|
19105
|
-
let hasActiveBaseline2 = false;
|
|
19106
|
-
state.forEachActivity((activity) => {
|
|
19107
|
-
hasActiveBaseline2 ||= getActiveBaseline(activity) !== null;
|
|
19108
|
-
});
|
|
19109
|
-
if (hasActiveBaseline2) {
|
|
19110
|
-
runExpectedProgressBase(
|
|
19111
|
-
state,
|
|
19112
|
-
now,
|
|
19113
|
-
this.coreRuntime.baseCalendars.find(
|
|
19114
|
-
(calendar) => calendar.baseDefault
|
|
19115
|
-
)?.id ?? null
|
|
19116
|
-
);
|
|
19117
|
-
}
|
|
19118
|
-
applyStatusPass(state, this.coreRuntime.sector.statusCriteria);
|
|
19119
|
-
}
|
|
19120
|
-
}
|
|
19121
20297
|
undo(options = {}) {
|
|
20298
|
+
const admitted = this._admitScheduleMutation();
|
|
20299
|
+
let changedState = false;
|
|
19122
20300
|
const operation = this._enqueue(async () => {
|
|
19123
|
-
|
|
19124
|
-
|
|
19125
|
-
|
|
19126
|
-
|
|
19127
|
-
|
|
19128
|
-
|
|
19129
|
-
|
|
19130
|
-
this._undo.pushRedo(entry);
|
|
19131
|
-
this._undo.resetCoalesce();
|
|
19132
|
-
const changes = buildInverseChangeSet(
|
|
19133
|
-
this.coreRuntime.state,
|
|
19134
|
-
entry,
|
|
19135
|
-
"before"
|
|
19136
|
-
);
|
|
19137
|
-
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
|
+
}
|
|
19138
20308
|
});
|
|
19139
20309
|
void operation.then(
|
|
19140
20310
|
(changes) => {
|
|
@@ -19151,23 +20321,45 @@ var ScheduleCore = class {
|
|
|
19151
20321
|
);
|
|
19152
20322
|
return operation;
|
|
19153
20323
|
}
|
|
19154
|
-
|
|
19155
|
-
|
|
20324
|
+
async _runUndo(options, markChanged) {
|
|
20325
|
+
{
|
|
19156
20326
|
this.assertReady();
|
|
19157
|
-
|
|
20327
|
+
if (options.expectedUndoDepth !== void 0 && this._undo.undoDepth() !== options.expectedUndoDepth) {
|
|
20328
|
+
return null;
|
|
20329
|
+
}
|
|
20330
|
+
const entry = this._undo.takeUndo();
|
|
19158
20331
|
if (!entry) return null;
|
|
19159
|
-
|
|
19160
|
-
this._recomputeAfterHistoryRestore();
|
|
20332
|
+
applyUndo(this.coreRuntime.state, entry);
|
|
19161
20333
|
this._recordScheduleMutation(false);
|
|
20334
|
+
markChanged(true);
|
|
19162
20335
|
if (needsResync(entry)) this._resyncCustomIdTrackerFromModel();
|
|
19163
|
-
this._undo.
|
|
20336
|
+
this._undo.pushRedo(entry);
|
|
19164
20337
|
this._undo.resetCoalesce();
|
|
19165
20338
|
const changes = buildInverseChangeSet(
|
|
19166
20339
|
this.coreRuntime.state,
|
|
19167
20340
|
entry,
|
|
19168
|
-
"
|
|
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)
|
|
19169
20348
|
);
|
|
19170
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
|
+
}
|
|
19171
20363
|
});
|
|
19172
20364
|
void operation.then(
|
|
19173
20365
|
(changes) => {
|
|
@@ -19184,6 +20376,29 @@ var ScheduleCore = class {
|
|
|
19184
20376
|
);
|
|
19185
20377
|
return operation;
|
|
19186
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
|
+
}
|
|
19187
20402
|
canUndo() {
|
|
19188
20403
|
return this._undo.canUndo();
|
|
19189
20404
|
}
|
|
@@ -19224,10 +20439,17 @@ var ScheduleCore = class {
|
|
|
19224
20439
|
);
|
|
19225
20440
|
}
|
|
19226
20441
|
}
|
|
19227
|
-
_rollback() {
|
|
20442
|
+
_rollback(generatorSnapshot) {
|
|
19228
20443
|
try {
|
|
19229
20444
|
this.coreRuntime.state.restoreFromCapture();
|
|
19230
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
|
+
}
|
|
19231
20453
|
} catch (restoreError) {
|
|
19232
20454
|
this._status = SCHEDULE_CORE_STATUS.POISONED;
|
|
19233
20455
|
this.coreRuntime.reporter.error(
|
|
@@ -19247,18 +20469,6 @@ function toPublicDispatchResult(result) {
|
|
|
19247
20469
|
return publicResult;
|
|
19248
20470
|
}
|
|
19249
20471
|
|
|
19250
|
-
// src/boundary/save/link-changes.ts
|
|
19251
|
-
function checkNoUpdatedLinks(baseline, current) {
|
|
19252
|
-
const baselineById = new Map(
|
|
19253
|
-
baseline.map((link) => [String(link.id), link])
|
|
19254
|
-
);
|
|
19255
|
-
return current.filter((link) => {
|
|
19256
|
-
const base = baselineById.get(String(link.id));
|
|
19257
|
-
if (!base) return false;
|
|
19258
|
-
return base.lag !== link.lag || base.type !== link.type;
|
|
19259
|
-
});
|
|
19260
|
-
}
|
|
19261
|
-
|
|
19262
20472
|
// src/boundary/save/unsaved.ts
|
|
19263
20473
|
var getUnsavedActivities = (activities = []) => (activities ?? []).filter((activity) => !activity.proplannerId);
|
|
19264
20474
|
|