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