@outbuild-company/schedule-core 1.6.0 → 1.6.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cdn/schedule-core.global.js +1 -1
- package/dist/cdn/schedule-core.global.js.map +1 -1
- package/dist/index.cjs +1721 -507
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +121 -6
- package/dist/index.d.ts +121 -6
- package/dist/index.js +1721 -507
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
package/dist/index.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,
|
|
@@ -10481,12 +10771,18 @@ async function createActivityCore(action, deps, opts = {}) {
|
|
|
10481
10771
|
const initialTouched = /* @__PURE__ */ new Set([newId]);
|
|
10482
10772
|
if (parent) initialTouched.add(parent.id);
|
|
10483
10773
|
const parentWasLeaf = parent !== null && adapter.getChildren(parent.id).length === 0;
|
|
10774
|
+
const parentCustomId = parentWasLeaf ? parent?.customId : null;
|
|
10484
10775
|
const beforeSnap = opts.skipBeforeSnap ? /* @__PURE__ */ new Map() : await snapshotActivities(adapter, initialTouched);
|
|
10485
10776
|
adapter.addActivity(newActivity);
|
|
10486
10777
|
applyParentMutations(action, newId, parent, adapter, parentWasLeaf);
|
|
10487
|
-
|
|
10488
|
-
|
|
10489
|
-
|
|
10778
|
+
if (parentCustomId) {
|
|
10779
|
+
deps.customIdTracker.releaseCustomId(parentCustomId);
|
|
10780
|
+
}
|
|
10781
|
+
if (!opts.skipRollupCascades) {
|
|
10782
|
+
recomputeHhCascadeForActivity(newId, adapter);
|
|
10783
|
+
recomputeUsedCostCascadeForActivity(newId, adapter);
|
|
10784
|
+
recomputeCostCascadeForActivity(newId, adapter);
|
|
10785
|
+
}
|
|
10490
10786
|
recomputeAndFoldCorrelatives(
|
|
10491
10787
|
adapter,
|
|
10492
10788
|
newId,
|
|
@@ -10658,330 +10954,96 @@ function findDefaultCalendarId(calendars) {
|
|
|
10658
10954
|
return "global";
|
|
10659
10955
|
}
|
|
10660
10956
|
|
|
10661
|
-
// src/
|
|
10662
|
-
function
|
|
10663
|
-
|
|
10664
|
-
|
|
10665
|
-
|
|
10666
|
-
|
|
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);
|
|
10667
10973
|
}
|
|
10668
|
-
const parentId2 = sibling.parentId == null ? ROOT_PARENT_ID : String(sibling.parentId);
|
|
10669
|
-
return { ok: true, parentId: parentId2, firstAfter: destination.afterSiblingId };
|
|
10670
10974
|
}
|
|
10671
|
-
|
|
10672
|
-
|
|
10673
|
-
|
|
10674
|
-
|
|
10675
|
-
|
|
10676
|
-
|
|
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;
|
|
10677
10987
|
}
|
|
10678
|
-
function
|
|
10679
|
-
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;
|
|
10680
11004
|
return {
|
|
10681
|
-
|
|
10682
|
-
|
|
10683
|
-
|
|
10684
|
-
|
|
10685
|
-
|
|
10686
|
-
|
|
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
|
|
10687
11013
|
};
|
|
10688
11014
|
}
|
|
10689
|
-
|
|
10690
|
-
const
|
|
10691
|
-
|
|
10692
|
-
|
|
10693
|
-
|
|
10694
|
-
|
|
10695
|
-
|
|
10696
|
-
const
|
|
10697
|
-
|
|
10698
|
-
|
|
10699
|
-
|
|
10700
|
-
|
|
10701
|
-
|
|
10702
|
-
|
|
10703
|
-
|
|
10704
|
-
|
|
10705
|
-
|
|
10706
|
-
|
|
10707
|
-
|
|
10708
|
-
|
|
10709
|
-
|
|
10710
|
-
|
|
10711
|
-
|
|
10712
|
-
|
|
10713
|
-
|
|
10714
|
-
const
|
|
10715
|
-
|
|
10716
|
-
|
|
10717
|
-
|
|
10718
|
-
|
|
10719
|
-
|
|
10720
|
-
|
|
10721
|
-
eventSource: action.eventSource
|
|
10722
|
-
},
|
|
10723
|
-
deps,
|
|
10724
|
-
{
|
|
10725
|
-
skipCorrelativeRecompute: true,
|
|
10726
|
-
skipBeforeSnap: true,
|
|
10727
|
-
customIdReferenceId: action.referenceActivityId
|
|
10728
|
-
}
|
|
10729
|
-
);
|
|
10730
|
-
if (!core.ok) return core;
|
|
10731
|
-
if (input.baselineSnapshot !== void 0) {
|
|
10732
|
-
const snapshot = input.baselineSnapshot;
|
|
10733
|
-
adapter.setActivityField(
|
|
10734
|
-
core.newId,
|
|
10735
|
-
"baselineSnapshot",
|
|
10736
|
-
snapshot ? {
|
|
10737
|
-
...snapshot,
|
|
10738
|
-
startDate: snapshot.startDate ? new Date(snapshot.startDate) : null,
|
|
10739
|
-
endDate: snapshot.endDate ? new Date(snapshot.endDate) : null
|
|
10740
|
-
} : null
|
|
10741
|
-
);
|
|
10742
|
-
}
|
|
10743
|
-
originalToNew.set(input.originalId, core.newId);
|
|
10744
|
-
createdIds.push(core.newId);
|
|
10745
|
-
if (mappedParent === void 0) prevRootId = core.newId;
|
|
10746
|
-
}
|
|
10747
|
-
const correlativeShifts = recomputeCorrelativeIds(adapter);
|
|
10748
|
-
const createdIdSet = new Set(createdIds.map(String));
|
|
10749
|
-
foldCorrelativeShifts(
|
|
10750
|
-
adapter,
|
|
10751
|
-
correlativeShifts,
|
|
10752
|
-
createdIdSet,
|
|
10753
|
-
beforeSnap,
|
|
10754
|
-
touched
|
|
10755
|
-
);
|
|
10756
|
-
for (const createdId of createdIds) touched.add(String(createdId));
|
|
10757
|
-
const appliedLinks = [];
|
|
10758
|
-
for (const link of action.links) {
|
|
10759
|
-
const newSource = originalToNew.get(link.source);
|
|
10760
|
-
const newTarget = originalToNew.get(link.target);
|
|
10761
|
-
if (newSource === void 0 || newTarget === void 0) continue;
|
|
10762
|
-
const op = {
|
|
10763
|
-
kind: "create",
|
|
10764
|
-
source: newSource,
|
|
10765
|
-
target: newTarget,
|
|
10766
|
-
type: link.type,
|
|
10767
|
-
lag: lagDaysToHours(link.lag, sector.hoursPerDay)
|
|
10768
|
-
};
|
|
10769
|
-
const res = applyLinkOperation(op, {
|
|
10770
|
-
port: adapter,
|
|
10771
|
-
newLinkId: link.linkId ? () => link.linkId : deps.linkIdGen.next
|
|
10772
|
-
});
|
|
10773
|
-
appliedLinks.push({
|
|
10774
|
-
op,
|
|
10775
|
-
finalLinkId: res.applied ? res.linkId : null,
|
|
10776
|
-
rejected: res.applied ? null : res.rejected ?? "link_op_rejected"
|
|
10777
|
-
});
|
|
10778
|
-
}
|
|
10779
|
-
const { scheduledIds } = await runPostMutation(
|
|
10780
|
-
{
|
|
10781
|
-
adapter,
|
|
10782
|
-
scheduler,
|
|
10783
|
-
sector: deps.sector,
|
|
10784
|
-
defaultBaseCalendarId: deps.defaultBaseCalendarId
|
|
10785
|
-
},
|
|
10786
|
-
{
|
|
10787
|
-
action,
|
|
10788
|
-
autoscheduleFrom: createdIds[0] ?? null,
|
|
10789
|
-
recomputeParentsFrom: createdIds,
|
|
10790
|
-
now: deps.now,
|
|
10791
|
-
options
|
|
10792
|
-
}
|
|
10793
|
-
);
|
|
10794
|
-
const linkChanges = buildLinkChangesForBatch(
|
|
10795
|
-
appliedLinks,
|
|
10796
|
-
/* @__PURE__ */ new Map(),
|
|
10797
|
-
adapter
|
|
10798
|
-
);
|
|
10799
|
-
return {
|
|
10800
|
-
ok: true,
|
|
10801
|
-
changes: await assembleChangeSet(adapter, {
|
|
10802
|
-
source: action,
|
|
10803
|
-
beforeSnap,
|
|
10804
|
-
touchedIds: touched,
|
|
10805
|
-
scheduledIds,
|
|
10806
|
-
links: linkChanges,
|
|
10807
|
-
hoursPerDay: sector.hoursPerDay
|
|
10808
|
-
})
|
|
10809
|
-
};
|
|
10810
|
-
}
|
|
10811
|
-
|
|
10812
|
-
// src/dispatch/handlers/sir-sync.ts
|
|
10813
|
-
function dispatchSirSync(action, deps) {
|
|
10814
|
-
const { adapter } = deps;
|
|
10815
|
-
const id = action.activityId;
|
|
10816
|
-
const snapshot = adapter.getActivity(id);
|
|
10817
|
-
if (!snapshot) {
|
|
10818
|
-
return { ok: false, reason: REJECTION_REASON.ACTIVITY_NOT_FOUND };
|
|
10819
|
-
}
|
|
10820
|
-
const before = snapshot.pendingRequestIds ?? [];
|
|
10821
|
-
const after = action.pendingRequests.map((r) => r.id);
|
|
10822
|
-
adapter.setActivityField(action.activityId, "pendingRequestIds", after);
|
|
10823
|
-
const change = {
|
|
10824
|
-
id,
|
|
10825
|
-
kind: "updated",
|
|
10826
|
-
fields: { pendingRequestIds: { before, after } },
|
|
10827
|
-
after: { id, pendingRequestIds: after }
|
|
10828
|
-
};
|
|
10829
|
-
return {
|
|
10830
|
-
ok: true,
|
|
10831
|
-
changes: {
|
|
10832
|
-
source: action,
|
|
10833
|
-
activities: [change],
|
|
10834
|
-
links: [],
|
|
10835
|
-
calendars: [],
|
|
10836
|
-
trackingEvents: []
|
|
10837
|
-
}
|
|
10838
|
-
};
|
|
10839
|
-
}
|
|
10840
|
-
|
|
10841
|
-
// src/dispatch/handlers/activity-lookahead-sync.ts
|
|
10842
|
-
function dispatchActivityLookaheadSync(action, deps) {
|
|
10843
|
-
const ids = [...new Set(action.activityIds.map(String))];
|
|
10844
|
-
const snapshots = ids.map((id) => deps.adapter.getActivity(id));
|
|
10845
|
-
if (snapshots.some((snapshot) => !snapshot)) {
|
|
10846
|
-
return { ok: false, reason: REJECTION_REASON.ACTIVITY_NOT_FOUND };
|
|
10847
|
-
}
|
|
10848
|
-
const activities = [];
|
|
10849
|
-
for (const [index, id] of ids.entries()) {
|
|
10850
|
-
const snapshot = snapshots[index];
|
|
10851
|
-
if (!snapshot) continue;
|
|
10852
|
-
const fields = {};
|
|
10853
|
-
if (snapshot.isLookahead !== action.isLookahead) {
|
|
10854
|
-
fields.isLookahead = {
|
|
10855
|
-
before: snapshot.isLookahead,
|
|
10856
|
-
after: action.isLookahead
|
|
10857
|
-
};
|
|
10858
|
-
deps.adapter.setActivityField(id, "isLookahead", action.isLookahead);
|
|
10859
|
-
}
|
|
10860
|
-
if (snapshot.hasLookaheadTasks !== action.hasLookaheadTasks) {
|
|
10861
|
-
fields.hasLookaheadTasks = {
|
|
10862
|
-
before: snapshot.hasLookaheadTasks,
|
|
10863
|
-
after: action.hasLookaheadTasks
|
|
10864
|
-
};
|
|
10865
|
-
deps.adapter.setActivityField(
|
|
10866
|
-
id,
|
|
10867
|
-
"hasLookaheadTasks",
|
|
10868
|
-
action.hasLookaheadTasks
|
|
10869
|
-
);
|
|
10870
|
-
}
|
|
10871
|
-
if (Object.keys(fields).length === 0) continue;
|
|
10872
|
-
activities.push({
|
|
10873
|
-
id,
|
|
10874
|
-
kind: "updated",
|
|
10875
|
-
fields,
|
|
10876
|
-
after: {
|
|
10877
|
-
...snapshot,
|
|
10878
|
-
isLookahead: action.isLookahead,
|
|
10879
|
-
hasLookaheadTasks: action.hasLookaheadTasks
|
|
10880
|
-
}
|
|
10881
|
-
});
|
|
10882
|
-
}
|
|
10883
|
-
return {
|
|
10884
|
-
ok: true,
|
|
10885
|
-
changes: {
|
|
10886
|
-
source: action,
|
|
10887
|
-
activities,
|
|
10888
|
-
links: [],
|
|
10889
|
-
calendars: [],
|
|
10890
|
-
trackingEvents: []
|
|
10891
|
-
}
|
|
10892
|
-
};
|
|
10893
|
-
}
|
|
10894
|
-
|
|
10895
|
-
// src/propagations/upward/recompute-rollup-cascades.ts
|
|
10896
|
-
function recomputeRollupCascadesForParent(parentId, adapter) {
|
|
10897
|
-
recomputeHhCascadeForParent(parentId, adapter);
|
|
10898
|
-
recomputeUsedCostCascadeForParent(parentId, adapter);
|
|
10899
|
-
recomputeCostCascadeForParent(parentId, adapter);
|
|
10900
|
-
}
|
|
10901
|
-
|
|
10902
|
-
// src/propagations/shared/traversal-helpers.ts
|
|
10903
|
-
function collectDescendantIds(rootIds, adapter) {
|
|
10904
|
-
const out = /* @__PURE__ */ new Set();
|
|
10905
|
-
const stack = [];
|
|
10906
|
-
for (const id of rootIds) {
|
|
10907
|
-
const key = String(id);
|
|
10908
|
-
if (!out.has(key)) {
|
|
10909
|
-
out.add(key);
|
|
10910
|
-
stack.push(key);
|
|
10911
|
-
}
|
|
10912
|
-
}
|
|
10913
|
-
while (stack.length > 0) {
|
|
10914
|
-
const current = stack.pop();
|
|
10915
|
-
const children = adapter.getChildren(current);
|
|
10916
|
-
for (const childId of children) {
|
|
10917
|
-
const key = String(childId);
|
|
10918
|
-
if (!out.has(key)) {
|
|
10919
|
-
out.add(key);
|
|
10920
|
-
stack.push(key);
|
|
10921
|
-
}
|
|
10922
|
-
}
|
|
10923
|
-
}
|
|
10924
|
-
return out;
|
|
10925
|
-
}
|
|
10926
|
-
function collectIncidentLinkIds(activityIds, adapter) {
|
|
10927
|
-
const out = /* @__PURE__ */ new Set();
|
|
10928
|
-
for (const activityId of activityIds) {
|
|
10929
|
-
for (const linkId of adapter.getOutgoingLinkIds(activityId)) {
|
|
10930
|
-
out.add(String(linkId));
|
|
10931
|
-
}
|
|
10932
|
-
for (const linkId of adapter.getIncomingLinkIds(activityId)) {
|
|
10933
|
-
out.add(String(linkId));
|
|
10934
|
-
}
|
|
10935
|
-
}
|
|
10936
|
-
return [...out];
|
|
10937
|
-
}
|
|
10938
|
-
|
|
10939
|
-
// src/internal/hierarchy/parent-demotion.ts
|
|
10940
|
-
function restoredPromotionFields(restore) {
|
|
10941
|
-
if (!restore) return null;
|
|
10942
|
-
return {
|
|
10943
|
-
type: restore.type,
|
|
10944
|
-
startDate: new Date(restore.startDate),
|
|
10945
|
-
endDate: new Date(restore.endDate),
|
|
10946
|
-
durationHours: restore.durationHours,
|
|
10947
|
-
expectedProgressBaseline: restore.expectedProgressBaseline,
|
|
10948
|
-
constraintType: restore.constraintType,
|
|
10949
|
-
constraintDate: restore.constraintDate ? new Date(restore.constraintDate) : null,
|
|
10950
|
-
progress: restore.progress
|
|
10951
|
-
};
|
|
10952
|
-
}
|
|
10953
|
-
function canonicalEndDateField(parent, durationHours, computeEndDate) {
|
|
10954
|
-
const startDate = parent.startDate;
|
|
10955
|
-
if (!computeEndDate || !(startDate instanceof Date)) return {};
|
|
10956
|
-
return { endDate: computeEndDate(startDate, durationHours) };
|
|
10957
|
-
}
|
|
10958
|
-
function buildParentDemotionMutations(input) {
|
|
10959
|
-
if (input.remainingChildIds.length > 0) return null;
|
|
10960
|
-
const parent = input.parent;
|
|
10961
|
-
const canonicalDuration = input.defaultDurationHours;
|
|
10962
|
-
const hasCanonicalDefaults = Number.isFinite(canonicalDuration);
|
|
10963
|
-
const fields = restoredPromotionFields(input.promotionSnapshot) ?? (hasCanonicalDefaults ? {
|
|
10964
|
-
type: DEMOTION_TARGET_TYPE,
|
|
10965
|
-
durationHours: canonicalDuration,
|
|
10966
|
-
progress: 0,
|
|
10967
|
-
autoScheduling: true,
|
|
10968
|
-
constraintType: "asap",
|
|
10969
|
-
constraintDate: null,
|
|
10970
|
-
hasNewActivities: false,
|
|
10971
|
-
...canonicalEndDateField(
|
|
10972
|
-
parent,
|
|
10973
|
-
canonicalDuration,
|
|
10974
|
-
input.computeEndDate
|
|
10975
|
-
)
|
|
10976
|
-
} : { type: DEMOTION_TARGET_TYPE });
|
|
10977
|
-
if (input.idsRemoved && input.idsRemoved.size > 0) {
|
|
10978
|
-
const existing = Array.isArray(parent.newActivityIds) ? parent.newActivityIds : [];
|
|
10979
|
-
if (existing.length > 0) {
|
|
10980
|
-
const filtered = existing.filter(
|
|
10981
|
-
(id) => !input.idsRemoved.has(id) && !input.idsRemoved.has(String(id))
|
|
10982
|
-
);
|
|
10983
|
-
if (filtered.length !== existing.length) {
|
|
10984
|
-
fields.newActivityIds = filtered;
|
|
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))
|
|
11044
|
+
);
|
|
11045
|
+
if (filtered.length !== existing.length) {
|
|
11046
|
+
fields.newActivityIds = filtered;
|
|
10985
11047
|
}
|
|
10986
11048
|
}
|
|
10987
11049
|
}
|
|
@@ -11190,7 +11252,647 @@ function buildLinkDeletions(beforeLinks) {
|
|
|
11190
11252
|
after: null
|
|
11191
11253
|
});
|
|
11192
11254
|
}
|
|
11193
|
-
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
|
+
};
|
|
11194
11896
|
}
|
|
11195
11897
|
|
|
11196
11898
|
// src/internal/hierarchy/invalid-links.ts
|
|
@@ -12788,7 +13490,10 @@ async function dispatchStatusCriteriaSet(action, deps) {
|
|
|
12788
13490
|
}
|
|
12789
13491
|
|
|
12790
13492
|
// src/dispatch/dispatch.ts
|
|
12791
|
-
async function
|
|
13493
|
+
async function performDispatch(action, options, deps) {
|
|
13494
|
+
if (action.kind === "activity-batch") {
|
|
13495
|
+
return dispatchActivityBatch(action, options, deps);
|
|
13496
|
+
}
|
|
12792
13497
|
if (action.kind === "persistence-acknowledge") {
|
|
12793
13498
|
return dispatchPersistenceAcknowledge(action, deps);
|
|
12794
13499
|
}
|
|
@@ -16022,6 +16727,7 @@ var ScheduleState = class {
|
|
|
16022
16727
|
};
|
|
16023
16728
|
_writeCapture = new WriteCapture();
|
|
16024
16729
|
_viewState = new ViewStateStore();
|
|
16730
|
+
_filterProjection = null;
|
|
16025
16731
|
_viewStateBefore = null;
|
|
16026
16732
|
_lastStartDateBefore = null;
|
|
16027
16733
|
_promotionSnapshotBefore = null;
|
|
@@ -16136,6 +16842,13 @@ var ScheduleState = class {
|
|
|
16136
16842
|
setActiveFilter(nextFilter) {
|
|
16137
16843
|
this._captureViewStateOnce();
|
|
16138
16844
|
this._viewState.setActiveFilter(nextFilter);
|
|
16845
|
+
this._filterProjection = null;
|
|
16846
|
+
}
|
|
16847
|
+
getFilterProjection() {
|
|
16848
|
+
return this._filterProjection;
|
|
16849
|
+
}
|
|
16850
|
+
setFilterProjection(projection) {
|
|
16851
|
+
this._filterProjection = projection;
|
|
16139
16852
|
}
|
|
16140
16853
|
getActiveOrder() {
|
|
16141
16854
|
return this._viewState.getActiveOrder();
|
|
@@ -16177,6 +16890,7 @@ var ScheduleState = class {
|
|
|
16177
16890
|
}
|
|
16178
16891
|
hydrateViewState(seed2) {
|
|
16179
16892
|
this._viewState.hydrate(seed2);
|
|
16893
|
+
this._filterProjection = null;
|
|
16180
16894
|
this.invalidateDerivedOrder();
|
|
16181
16895
|
}
|
|
16182
16896
|
_captureViewStateOnce() {
|
|
@@ -16377,6 +17091,7 @@ var ScheduleState = class {
|
|
|
16377
17091
|
this._restoreActivityFields(before);
|
|
16378
17092
|
if (this._viewStateBefore !== null) {
|
|
16379
17093
|
this._viewState.restore(this._viewStateBefore);
|
|
17094
|
+
this._filterProjection = null;
|
|
16380
17095
|
this.invalidateDerivedOrder();
|
|
16381
17096
|
}
|
|
16382
17097
|
if (this._promotionSnapshotBefore !== null) {
|
|
@@ -16586,8 +17301,10 @@ function buildHistoryChangeSet(changes) {
|
|
|
16586
17301
|
...entry,
|
|
16587
17302
|
after: null
|
|
16588
17303
|
}));
|
|
17304
|
+
const source = changes.source.kind === "activity-batch" ? { ...changes.source, items: [], links: [] } : changes.source;
|
|
16589
17305
|
return cloneDomainValue({
|
|
16590
17306
|
...changes,
|
|
17307
|
+
source,
|
|
16591
17308
|
activities: activitiesWithoutAfter,
|
|
16592
17309
|
links: linksWithoutAfter
|
|
16593
17310
|
});
|
|
@@ -16604,7 +17321,7 @@ function captureCreatedSnapshots(state, changeSet) {
|
|
|
16604
17321
|
if (live) afterSnap.set(String(ch.id), structuredCloneActivity(live));
|
|
16605
17322
|
}
|
|
16606
17323
|
for (const ch of changeSet.links) {
|
|
16607
|
-
if (ch.kind
|
|
17324
|
+
if (ch.kind === "deleted") continue;
|
|
16608
17325
|
const live = state.getLink(String(ch.id));
|
|
16609
17326
|
if (live) afterLinks.set(String(ch.id), { ...live });
|
|
16610
17327
|
}
|
|
@@ -16827,6 +17544,17 @@ function applyChangeSetSide(state, entry, side) {
|
|
|
16827
17544
|
for (const ch of cs.links) {
|
|
16828
17545
|
if (ch.kind === "updated") {
|
|
16829
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
|
+
}
|
|
16830
17558
|
for (const [field, fv] of Object.entries(ch.fields)) {
|
|
16831
17559
|
setLinkFieldDynamic(
|
|
16832
17560
|
state,
|
|
@@ -17097,9 +17825,26 @@ function idArrayEquals(current, saved) {
|
|
|
17097
17825
|
return currentIds.every((value, index) => value === savedIds[index]);
|
|
17098
17826
|
}
|
|
17099
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
|
+
|
|
17100
17843
|
// src/boundary/save/save-tracker.ts
|
|
17101
17844
|
var SaveTracker = class {
|
|
17102
17845
|
initialized = false;
|
|
17846
|
+
dirtyActivityIds = /* @__PURE__ */ new Set();
|
|
17847
|
+
dirtyLinkIds = /* @__PURE__ */ new Set();
|
|
17103
17848
|
linksAtLastSave = /* @__PURE__ */ new Map();
|
|
17104
17849
|
activitiesAtLastSave = /* @__PURE__ */ new Map();
|
|
17105
17850
|
init(activities, links) {
|
|
@@ -17109,10 +17854,16 @@ var SaveTracker = class {
|
|
|
17109
17854
|
}
|
|
17110
17855
|
snapshotActivities(activities) {
|
|
17111
17856
|
this.activitiesAtLastSave.clear();
|
|
17857
|
+
this.dirtyActivityIds.clear();
|
|
17112
17858
|
for (const activity of activities) {
|
|
17113
|
-
|
|
17859
|
+
const activityId = String(activity.id);
|
|
17860
|
+
if (activity.proplannerId == null) {
|
|
17861
|
+
this.dirtyActivityIds.add(activityId);
|
|
17862
|
+
continue;
|
|
17863
|
+
}
|
|
17864
|
+
const persistable = projectPersistableActivity(activity);
|
|
17114
17865
|
this.activitiesAtLastSave.set(String(activity.id), {
|
|
17115
|
-
persistable
|
|
17866
|
+
persistable,
|
|
17116
17867
|
deletedSnapshot: {
|
|
17117
17868
|
id: activity.id,
|
|
17118
17869
|
proplannerId: activity.proplannerId,
|
|
@@ -17120,12 +17871,20 @@ var SaveTracker = class {
|
|
|
17120
17871
|
hadBaseline: activity.baselineSnapshot !== null || activity.baselinePoints.length > 0
|
|
17121
17872
|
}
|
|
17122
17873
|
});
|
|
17874
|
+
if (activityChangedSince(activity, persistable)) {
|
|
17875
|
+
this.dirtyActivityIds.add(activityId);
|
|
17876
|
+
}
|
|
17123
17877
|
}
|
|
17124
17878
|
}
|
|
17125
17879
|
snapshotLinks(links) {
|
|
17126
17880
|
this.linksAtLastSave.clear();
|
|
17881
|
+
this.dirtyLinkIds.clear();
|
|
17127
17882
|
for (const link of links) {
|
|
17128
|
-
|
|
17883
|
+
const linkId = String(link.id);
|
|
17884
|
+
if (link.proplannerId == null) {
|
|
17885
|
+
this.dirtyLinkIds.add(linkId);
|
|
17886
|
+
continue;
|
|
17887
|
+
}
|
|
17129
17888
|
this.linksAtLastSave.set(String(link.id), {
|
|
17130
17889
|
id: link.id,
|
|
17131
17890
|
proplannerId: link.proplannerId,
|
|
@@ -17136,21 +17895,52 @@ var SaveTracker = class {
|
|
|
17136
17895
|
});
|
|
17137
17896
|
}
|
|
17138
17897
|
}
|
|
17139
|
-
|
|
17140
|
-
if (!this.initialized) return
|
|
17141
|
-
|
|
17142
|
-
|
|
17143
|
-
|
|
17144
|
-
|
|
17145
|
-
|
|
17146
|
-
|
|
17147
|
-
|
|
17148
|
-
|
|
17149
|
-
|
|
17150
|
-
|
|
17151
|
-
|
|
17152
|
-
|
|
17153
|
-
|
|
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);
|
|
17154
17944
|
}
|
|
17155
17945
|
deletedActivities(activities) {
|
|
17156
17946
|
if (!this.initialized) return [];
|
|
@@ -17188,10 +17978,13 @@ var SaveTracker = class {
|
|
|
17188
17978
|
if (link.proplannerId == null) return false;
|
|
17189
17979
|
const saved = this.linksAtLastSave.get(String(link.id));
|
|
17190
17980
|
if (!saved) return true;
|
|
17191
|
-
return
|
|
17981
|
+
return linkChangedSince(saved, link);
|
|
17192
17982
|
});
|
|
17193
17983
|
}
|
|
17194
17984
|
clear() {
|
|
17985
|
+
this.initialized = false;
|
|
17986
|
+
this.dirtyActivityIds.clear();
|
|
17987
|
+
this.dirtyLinkIds.clear();
|
|
17195
17988
|
this.linksAtLastSave.clear();
|
|
17196
17989
|
this.activitiesAtLastSave.clear();
|
|
17197
17990
|
}
|
|
@@ -17793,6 +18586,12 @@ function createActivityIdGenerator(options = {}) {
|
|
|
17793
18586
|
const n = typeof id === "number" ? id : Number(id);
|
|
17794
18587
|
if (!Number.isFinite(n)) return;
|
|
17795
18588
|
raise(n);
|
|
18589
|
+
},
|
|
18590
|
+
snapshot() {
|
|
18591
|
+
return counter;
|
|
18592
|
+
},
|
|
18593
|
+
restore(snapshot) {
|
|
18594
|
+
counter = snapshot;
|
|
17796
18595
|
}
|
|
17797
18596
|
};
|
|
17798
18597
|
}
|
|
@@ -17818,6 +18617,12 @@ function createLinkIdGenerator(options = {}) {
|
|
|
17818
18617
|
const n = typeof id === "number" ? id : Number(id);
|
|
17819
18618
|
if (!Number.isFinite(n)) return;
|
|
17820
18619
|
raise(n);
|
|
18620
|
+
},
|
|
18621
|
+
snapshot() {
|
|
18622
|
+
return counter;
|
|
18623
|
+
},
|
|
18624
|
+
restore(snapshot) {
|
|
18625
|
+
counter = snapshot;
|
|
17821
18626
|
}
|
|
17822
18627
|
};
|
|
17823
18628
|
}
|
|
@@ -17847,6 +18652,12 @@ function createUniqueCorrelativeIdGenerator(options = {}) {
|
|
|
17847
18652
|
if (uid2 == null) return;
|
|
17848
18653
|
const n = typeof uid2 === "number" ? uid2 : Number(uid2);
|
|
17849
18654
|
raise(n);
|
|
18655
|
+
},
|
|
18656
|
+
snapshot() {
|
|
18657
|
+
return counter;
|
|
18658
|
+
},
|
|
18659
|
+
restore(snapshot) {
|
|
18660
|
+
counter = snapshot;
|
|
17850
18661
|
}
|
|
17851
18662
|
};
|
|
17852
18663
|
}
|
|
@@ -17923,13 +18734,13 @@ function minSuffixFromArray(suffixes) {
|
|
|
17923
18734
|
if (!suffixes || suffixes.length === 0) {
|
|
17924
18735
|
throw new Error("minSuffixFromArray requires non-empty array");
|
|
17925
18736
|
}
|
|
17926
|
-
return suffixes.reduce((
|
|
18737
|
+
return suffixes.reduce((min2, current) => minSuffix(min2, current));
|
|
17927
18738
|
}
|
|
17928
18739
|
function maxSuffixFromArray(suffixes) {
|
|
17929
18740
|
if (!suffixes || suffixes.length === 0) {
|
|
17930
18741
|
throw new Error("maxSuffixFromArray requires non-empty array");
|
|
17931
18742
|
}
|
|
17932
|
-
return suffixes.reduce((
|
|
18743
|
+
return suffixes.reduce((max2, current) => maxSuffix(max2, current));
|
|
17933
18744
|
}
|
|
17934
18745
|
|
|
17935
18746
|
// src/generators/custom-id/generation-strategies.ts
|
|
@@ -18623,13 +19434,204 @@ function initializeCore(input) {
|
|
|
18623
19434
|
};
|
|
18624
19435
|
}
|
|
18625
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
|
+
|
|
18626
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
|
+
}
|
|
18627
19605
|
var ScheduleCore = class {
|
|
18628
19606
|
_status = SCHEDULE_CORE_STATUS.READY;
|
|
18629
19607
|
coreRuntime;
|
|
18630
19608
|
_undo = new UndoRecorder();
|
|
18631
19609
|
_opQueue = Promise.resolve();
|
|
18632
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;
|
|
18633
19635
|
_criticalPathRevision = -1;
|
|
18634
19636
|
_activeCriticalPath = null;
|
|
18635
19637
|
constructor(input) {
|
|
@@ -18682,6 +19684,16 @@ var ScheduleCore = class {
|
|
|
18682
19684
|
this.assertReady();
|
|
18683
19685
|
return readActivity(this.coreRuntime.state, id);
|
|
18684
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
|
+
}
|
|
18685
19697
|
getAllActivitiesView() {
|
|
18686
19698
|
this.assertReady();
|
|
18687
19699
|
return readAllActivities(this.coreRuntime.state);
|
|
@@ -18779,10 +19791,7 @@ var ScheduleCore = class {
|
|
|
18779
19791
|
}
|
|
18780
19792
|
hasUnsavedChanges() {
|
|
18781
19793
|
this.assertReady();
|
|
18782
|
-
return this._saveTracker.hasUnsavedChanges(
|
|
18783
|
-
this.coreRuntime.state.getAllActivities(),
|
|
18784
|
-
this.getAllLinksView()
|
|
18785
|
-
);
|
|
19794
|
+
return this._saveTracker.hasUnsavedChanges();
|
|
18786
19795
|
}
|
|
18787
19796
|
getDeletedActivitiesSinceLastSave() {
|
|
18788
19797
|
this.assertReady();
|
|
@@ -18800,10 +19809,90 @@ var ScheduleCore = class {
|
|
|
18800
19809
|
);
|
|
18801
19810
|
return workPromise;
|
|
18802
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
|
+
}
|
|
18803
19877
|
async dispatch(action, options = {}) {
|
|
18804
|
-
|
|
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
|
+
}
|
|
18805
19894
|
}
|
|
18806
|
-
async
|
|
19895
|
+
async _runDispatch(action, options) {
|
|
18807
19896
|
this.assertReady();
|
|
18808
19897
|
const pipelineContext = buildPipelineContext(this.coreRuntime.state, {
|
|
18809
19898
|
hoursPerDay: this.coreRuntime.sector.hoursPerDay,
|
|
@@ -18811,11 +19900,16 @@ var ScheduleCore = class {
|
|
|
18811
19900
|
inputUnit: options.inputUnit ?? "days"
|
|
18812
19901
|
});
|
|
18813
19902
|
const historyPolicy = getDispatchHistoryPolicy(action);
|
|
19903
|
+
const generatorSnapshot = [
|
|
19904
|
+
this.coreRuntime.activityIdGenerator.snapshot(),
|
|
19905
|
+
this.coreRuntime.linkIdGenerator.snapshot(),
|
|
19906
|
+
this.coreRuntime.uniqueCorrelativeIdGenerator.snapshot()
|
|
19907
|
+
];
|
|
18814
19908
|
this.coreRuntime.state.beginWriteCapture();
|
|
18815
19909
|
this.coreRuntime.customIdTracker.beginCustomIdTransaction();
|
|
18816
19910
|
let result;
|
|
18817
19911
|
try {
|
|
18818
|
-
result = await
|
|
19912
|
+
result = await performDispatch(action, options, {
|
|
18819
19913
|
adapter: this.coreRuntime.state,
|
|
18820
19914
|
ctx: pipelineContext,
|
|
18821
19915
|
scheduler: this.coreRuntime.scheduler,
|
|
@@ -18831,9 +19925,9 @@ var ScheduleCore = class {
|
|
|
18831
19925
|
)?.id ?? null,
|
|
18832
19926
|
now: this.coreRuntime.clock ? endOfLocalDay(this.coreRuntime.clock()) : null
|
|
18833
19927
|
});
|
|
18834
|
-
if (!result.ok) this._rollback();
|
|
19928
|
+
if (!result.ok) this._rollback(generatorSnapshot);
|
|
18835
19929
|
} catch (error) {
|
|
18836
|
-
this._rollback();
|
|
19930
|
+
this._rollback(generatorSnapshot);
|
|
18837
19931
|
throw error;
|
|
18838
19932
|
} finally {
|
|
18839
19933
|
this.coreRuntime.state.endWriteCapture();
|
|
@@ -18856,19 +19950,36 @@ var ScheduleCore = class {
|
|
|
18856
19950
|
Date.now()
|
|
18857
19951
|
);
|
|
18858
19952
|
}
|
|
18859
|
-
if (result.ok
|
|
18860
|
-
|
|
18861
|
-
this.
|
|
18862
|
-
|
|
18863
|
-
|
|
18864
|
-
|
|
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
|
+
}
|
|
18865
19973
|
}
|
|
18866
19974
|
result = this._withReappliedViewState(action, result);
|
|
18867
|
-
if (!result.ok) return result;
|
|
19975
|
+
if (!result.ok) return { result, changedState: false };
|
|
18868
19976
|
if (dispatchChangesSchedulingState(action) && changeSetIsSubstantive(result.changes)) {
|
|
18869
|
-
this._recordScheduleMutation(
|
|
19977
|
+
this._recordScheduleMutation(
|
|
19978
|
+
options.skipAutoSchedule !== true && options.skipCriticalPath !== true && resolveRunCriticalPath(action)
|
|
19979
|
+
);
|
|
19980
|
+
return { result: toPublicDispatchResult(result), changedState: true };
|
|
18870
19981
|
}
|
|
18871
|
-
return toPublicDispatchResult(result);
|
|
19982
|
+
return { result: toPublicDispatchResult(result), changedState: false };
|
|
18872
19983
|
}
|
|
18873
19984
|
recomputeCriticalPath() {
|
|
18874
19985
|
const operation = this._enqueue(async () => ({
|
|
@@ -18878,7 +19989,11 @@ var ScheduleCore = class {
|
|
|
18878
19989
|
return operation;
|
|
18879
19990
|
}
|
|
18880
19991
|
isCriticalPathSettled() {
|
|
18881
|
-
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;
|
|
18882
19997
|
}
|
|
18883
19998
|
async whenCriticalPathSettled() {
|
|
18884
19999
|
while (!this.isCriticalPathSettled()) {
|
|
@@ -18886,7 +20001,11 @@ var ScheduleCore = class {
|
|
|
18886
20001
|
}
|
|
18887
20002
|
}
|
|
18888
20003
|
_withReappliedViewState(action, result) {
|
|
18889
|
-
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;
|
|
18890
20009
|
const merged = this._reapplyViewState(result.changes);
|
|
18891
20010
|
return merged === null ? result : { ...result, changes: merged };
|
|
18892
20011
|
}
|
|
@@ -18907,11 +20026,44 @@ var ScheduleCore = class {
|
|
|
18907
20026
|
*/
|
|
18908
20027
|
_reapplyViewState(changes) {
|
|
18909
20028
|
const withFilter = this._reapplyActiveFilter(changes);
|
|
18910
|
-
const
|
|
18911
|
-
const
|
|
18912
|
-
|
|
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
|
|
18913
20061
|
);
|
|
18914
|
-
|
|
20062
|
+
if (createdVisibility.length === 0) return reapplied;
|
|
20063
|
+
return {
|
|
20064
|
+
...finalChanges,
|
|
20065
|
+
viewState: [...finalChanges.viewState ?? [], ...createdVisibility]
|
|
20066
|
+
};
|
|
18915
20067
|
}
|
|
18916
20068
|
/**
|
|
18917
20069
|
* Emits `order` for the branches this mutation resequenced, when no user order
|
|
@@ -18933,18 +20085,19 @@ var ScheduleCore = class {
|
|
|
18933
20085
|
*/
|
|
18934
20086
|
_emitTouchedBranchOrder(changes) {
|
|
18935
20087
|
const adapter = this.coreRuntime.state;
|
|
18936
|
-
|
|
18937
|
-
const touched = collectResequencedParents(
|
|
20088
|
+
const touchedParents = collectResequencedParents(
|
|
18938
20089
|
changes,
|
|
18939
20090
|
(activityId) => adapter.getParentId(activityId)
|
|
18940
20091
|
);
|
|
18941
|
-
if (
|
|
18942
|
-
|
|
18943
|
-
|
|
18944
|
-
|
|
18945
|
-
|
|
18946
|
-
|
|
18947
|
-
|
|
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 };
|
|
18948
20101
|
}
|
|
18949
20102
|
/**
|
|
18950
20103
|
* Re-sequences the grid after any mutation that could have changed a value the
|
|
@@ -18955,23 +20108,63 @@ var ScheduleCore = class {
|
|
|
18955
20108
|
* Production only re-sorts after a bar drag; diverging from that is a
|
|
18956
20109
|
* deliberate product decision, not an oversight.
|
|
18957
20110
|
*/
|
|
18958
|
-
_reapplyActiveOrder(changes) {
|
|
20111
|
+
_reapplyActiveOrder(changes, resequencedParents) {
|
|
18959
20112
|
const adapter = this.coreRuntime.state;
|
|
18960
20113
|
if (adapter.getActiveOrder() === null) return null;
|
|
18961
20114
|
adapter.invalidateDerivedOrder();
|
|
18962
|
-
|
|
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 };
|
|
18963
20132
|
}
|
|
18964
20133
|
_reapplyActiveFilter(changes) {
|
|
18965
|
-
const filter = this.coreRuntime.state.getActiveFilter();
|
|
18966
|
-
if (filter === null) return null;
|
|
18967
20134
|
const adapter = this.coreRuntime.state;
|
|
18968
|
-
const
|
|
18969
|
-
|
|
18970
|
-
|
|
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,
|
|
18971
20141
|
filter,
|
|
18972
|
-
context
|
|
18973
|
-
|
|
18974
|
-
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);
|
|
18975
20168
|
if (viewState.length === 0) return null;
|
|
18976
20169
|
return {
|
|
18977
20170
|
...changes,
|
|
@@ -18980,6 +20173,10 @@ var ScheduleCore = class {
|
|
|
18980
20173
|
}
|
|
18981
20174
|
_recordScheduleMutation(criticalPathIsFresh) {
|
|
18982
20175
|
this._scheduleRevision++;
|
|
20176
|
+
this._pendingScheduleRevision = Math.max(
|
|
20177
|
+
this._pendingScheduleRevision,
|
|
20178
|
+
this._scheduleRevision
|
|
20179
|
+
);
|
|
18983
20180
|
if (this._activeCriticalPath) {
|
|
18984
20181
|
this._activeCriticalPath.cancellation.cancelled = true;
|
|
18985
20182
|
}
|
|
@@ -19010,23 +20207,30 @@ var ScheduleCore = class {
|
|
|
19010
20207
|
this.coreRuntime.baseCalendars
|
|
19011
20208
|
)
|
|
19012
20209
|
);
|
|
19013
|
-
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;
|
|
19014
20215
|
const promise = (async () => {
|
|
19015
20216
|
const calculatedFields = await computeCriticalPathFields(
|
|
19016
20217
|
isolatedState,
|
|
19017
20218
|
this.coreRuntime.sector.hoursPerDay,
|
|
19018
20219
|
isCurrent
|
|
19019
20220
|
);
|
|
19020
|
-
if (!isCurrent()) return null;
|
|
19021
|
-
const fieldsByActivity = calculatedFields ?? /* @__PURE__ */ new Map();
|
|
20221
|
+
if (!isCurrent() || calculatedFields === null) return null;
|
|
19022
20222
|
return this._enqueue(async () => {
|
|
19023
20223
|
if (!isCurrent()) return null;
|
|
19024
20224
|
this.coreRuntime.state.beginWriteCapture();
|
|
19025
20225
|
this.coreRuntime.customIdTracker.beginCustomIdTransaction();
|
|
19026
20226
|
let changes;
|
|
19027
20227
|
try {
|
|
19028
|
-
for (const [activityId, fields] of
|
|
19029
|
-
|
|
20228
|
+
for (const [activityId, fields] of calculatedFields) {
|
|
20229
|
+
setCriticalPathFieldsIfChanged(
|
|
20230
|
+
this.coreRuntime.state,
|
|
20231
|
+
activityId,
|
|
20232
|
+
fields
|
|
20233
|
+
);
|
|
19030
20234
|
}
|
|
19031
20235
|
changes = await assembleChangeSet(this.coreRuntime.state, {
|
|
19032
20236
|
source: { kind: "init" },
|
|
@@ -19088,47 +20292,17 @@ var ScheduleCore = class {
|
|
|
19088
20292
|
}
|
|
19089
20293
|
return { changes };
|
|
19090
20294
|
}
|
|
19091
|
-
_recomputeAfterHistoryRestore() {
|
|
19092
|
-
const state = this.coreRuntime.state;
|
|
19093
|
-
recomputeAllProgressRollup(state);
|
|
19094
|
-
recomputeCanonicalRealWork(state);
|
|
19095
|
-
emitRealCost(state);
|
|
19096
|
-
if (this.coreRuntime.clock) {
|
|
19097
|
-
const now = endOfLocalDay(this.coreRuntime.clock());
|
|
19098
|
-
applyExpectedProgressLive(state, now);
|
|
19099
|
-
let hasActiveBaseline2 = false;
|
|
19100
|
-
state.forEachActivity((activity) => {
|
|
19101
|
-
hasActiveBaseline2 ||= getActiveBaseline(activity) !== null;
|
|
19102
|
-
});
|
|
19103
|
-
if (hasActiveBaseline2) {
|
|
19104
|
-
runExpectedProgressBase(
|
|
19105
|
-
state,
|
|
19106
|
-
now,
|
|
19107
|
-
this.coreRuntime.baseCalendars.find(
|
|
19108
|
-
(calendar) => calendar.baseDefault
|
|
19109
|
-
)?.id ?? null
|
|
19110
|
-
);
|
|
19111
|
-
}
|
|
19112
|
-
applyStatusPass(state, this.coreRuntime.sector.statusCriteria);
|
|
19113
|
-
}
|
|
19114
|
-
}
|
|
19115
20295
|
undo(options = {}) {
|
|
20296
|
+
const admitted = this._admitScheduleMutation();
|
|
20297
|
+
let changedState = false;
|
|
19116
20298
|
const operation = this._enqueue(async () => {
|
|
19117
|
-
|
|
19118
|
-
|
|
19119
|
-
|
|
19120
|
-
|
|
19121
|
-
|
|
19122
|
-
|
|
19123
|
-
|
|
19124
|
-
this._undo.pushRedo(entry);
|
|
19125
|
-
this._undo.resetCoalesce();
|
|
19126
|
-
const changes = buildInverseChangeSet(
|
|
19127
|
-
this.coreRuntime.state,
|
|
19128
|
-
entry,
|
|
19129
|
-
"before"
|
|
19130
|
-
);
|
|
19131
|
-
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
|
+
}
|
|
19132
20306
|
});
|
|
19133
20307
|
void operation.then(
|
|
19134
20308
|
(changes) => {
|
|
@@ -19145,23 +20319,45 @@ var ScheduleCore = class {
|
|
|
19145
20319
|
);
|
|
19146
20320
|
return operation;
|
|
19147
20321
|
}
|
|
19148
|
-
|
|
19149
|
-
|
|
20322
|
+
async _runUndo(options, markChanged) {
|
|
20323
|
+
{
|
|
19150
20324
|
this.assertReady();
|
|
19151
|
-
|
|
20325
|
+
if (options.expectedUndoDepth !== void 0 && this._undo.undoDepth() !== options.expectedUndoDepth) {
|
|
20326
|
+
return null;
|
|
20327
|
+
}
|
|
20328
|
+
const entry = this._undo.takeUndo();
|
|
19152
20329
|
if (!entry) return null;
|
|
19153
|
-
|
|
19154
|
-
this._recomputeAfterHistoryRestore();
|
|
20330
|
+
applyUndo(this.coreRuntime.state, entry);
|
|
19155
20331
|
this._recordScheduleMutation(false);
|
|
20332
|
+
markChanged(true);
|
|
19156
20333
|
if (needsResync(entry)) this._resyncCustomIdTrackerFromModel();
|
|
19157
|
-
this._undo.
|
|
20334
|
+
this._undo.pushRedo(entry);
|
|
19158
20335
|
this._undo.resetCoalesce();
|
|
19159
20336
|
const changes = buildInverseChangeSet(
|
|
19160
20337
|
this.coreRuntime.state,
|
|
19161
20338
|
entry,
|
|
19162
|
-
"
|
|
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)
|
|
19163
20346
|
);
|
|
19164
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
|
+
}
|
|
19165
20361
|
});
|
|
19166
20362
|
void operation.then(
|
|
19167
20363
|
(changes) => {
|
|
@@ -19178,6 +20374,29 @@ var ScheduleCore = class {
|
|
|
19178
20374
|
);
|
|
19179
20375
|
return operation;
|
|
19180
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
|
+
}
|
|
19181
20400
|
canUndo() {
|
|
19182
20401
|
return this._undo.canUndo();
|
|
19183
20402
|
}
|
|
@@ -19218,10 +20437,17 @@ var ScheduleCore = class {
|
|
|
19218
20437
|
);
|
|
19219
20438
|
}
|
|
19220
20439
|
}
|
|
19221
|
-
_rollback() {
|
|
20440
|
+
_rollback(generatorSnapshot) {
|
|
19222
20441
|
try {
|
|
19223
20442
|
this.coreRuntime.state.restoreFromCapture();
|
|
19224
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
|
+
}
|
|
19225
20451
|
} catch (restoreError) {
|
|
19226
20452
|
this._status = SCHEDULE_CORE_STATUS.POISONED;
|
|
19227
20453
|
this.coreRuntime.reporter.error(
|
|
@@ -19241,18 +20467,6 @@ function toPublicDispatchResult(result) {
|
|
|
19241
20467
|
return publicResult;
|
|
19242
20468
|
}
|
|
19243
20469
|
|
|
19244
|
-
// src/boundary/save/link-changes.ts
|
|
19245
|
-
function checkNoUpdatedLinks(baseline, current) {
|
|
19246
|
-
const baselineById = new Map(
|
|
19247
|
-
baseline.map((link) => [String(link.id), link])
|
|
19248
|
-
);
|
|
19249
|
-
return current.filter((link) => {
|
|
19250
|
-
const base = baselineById.get(String(link.id));
|
|
19251
|
-
if (!base) return false;
|
|
19252
|
-
return base.lag !== link.lag || base.type !== link.type;
|
|
19253
|
-
});
|
|
19254
|
-
}
|
|
19255
|
-
|
|
19256
20470
|
// src/boundary/save/unsaved.ts
|
|
19257
20471
|
var getUnsavedActivities = (activities = []) => (activities ?? []).filter((activity) => !activity.proplannerId);
|
|
19258
20472
|
|