@outbuild-company/schedule-core 1.6.1 → 1.6.3
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/README.md +26 -0
- package/dist/cdn/schedule-core.global.js +1 -1
- package/dist/cdn/schedule-core.global.js.map +1 -1
- package/dist/index.cjs +1733 -512
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +161 -19
- package/dist/index.d.ts +161 -19
- package/dist/index.js +1732 -513
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
package/dist/index.cjs
CHANGED
|
@@ -212,19 +212,19 @@ function dispatchSelectionReplace(action, deps) {
|
|
|
212
212
|
}
|
|
213
213
|
|
|
214
214
|
// src/dispatch/shared/apply-visible-set.ts
|
|
215
|
-
function applyVisibleSet(adapter, visibleIds) {
|
|
215
|
+
function applyVisibleSet(adapter, visibleIds, activityIds = adapter.getAllIds()) {
|
|
216
216
|
const viewState = [];
|
|
217
|
-
|
|
217
|
+
for (const activityId of activityIds) {
|
|
218
218
|
const id = String(activityId);
|
|
219
219
|
const willBeVisible = visibleIds.has(id);
|
|
220
220
|
const before = adapter.isVisible(id);
|
|
221
|
-
if (before === willBeVisible)
|
|
221
|
+
if (before === willBeVisible) continue;
|
|
222
222
|
adapter.setVisible(id, willBeVisible);
|
|
223
223
|
viewState.push({
|
|
224
224
|
activityId: id,
|
|
225
225
|
visible: { before, after: willBeVisible }
|
|
226
226
|
});
|
|
227
|
-
}
|
|
227
|
+
}
|
|
228
228
|
return viewState;
|
|
229
229
|
}
|
|
230
230
|
|
|
@@ -498,6 +498,12 @@ function overlapsRange(activity, range) {
|
|
|
498
498
|
const windowStartsInside = windowStart >= taskStart && windowStart <= taskEnd;
|
|
499
499
|
return fullyInside || startsInside || windowStartsInside;
|
|
500
500
|
}
|
|
501
|
+
function matchesFilter(activity, filter, context) {
|
|
502
|
+
const passesCriteria = matchesCriteria(activity, filter, context);
|
|
503
|
+
const range = filter.dateRange;
|
|
504
|
+
const passesRange = range === void 0 || overlapsRange(activity, range);
|
|
505
|
+
return passesCriteria && passesRange;
|
|
506
|
+
}
|
|
501
507
|
function addAncestors(matchedId, parentOf, visibleIds) {
|
|
502
508
|
let ancestorId = parentOf(matchedId);
|
|
503
509
|
while (ancestorId !== null && !visibleIds.has(String(ancestorId))) {
|
|
@@ -507,13 +513,10 @@ function addAncestors(matchedId, parentOf, visibleIds) {
|
|
|
507
513
|
}
|
|
508
514
|
function evaluateVisibleIds(input) {
|
|
509
515
|
const { activities, parentOf, filter, context } = input;
|
|
510
|
-
const range = filter.dateRange;
|
|
511
516
|
const visibleIds = /* @__PURE__ */ new Set();
|
|
512
517
|
const matchedIds = [];
|
|
513
518
|
for (const activity of activities) {
|
|
514
|
-
|
|
515
|
-
const passesRange = range === void 0 || overlapsRange(activity, range);
|
|
516
|
-
if (passesCriteria && passesRange) {
|
|
519
|
+
if (matchesFilter(activity, filter, context)) {
|
|
517
520
|
visibleIds.add(String(activity.id));
|
|
518
521
|
matchedIds.push(activity.id);
|
|
519
522
|
}
|
|
@@ -523,6 +526,114 @@ function evaluateVisibleIds(input) {
|
|
|
523
526
|
}
|
|
524
527
|
return visibleIds;
|
|
525
528
|
}
|
|
529
|
+
function seedFilterProjection(input) {
|
|
530
|
+
const { activities, parentOf, filter, context } = input;
|
|
531
|
+
const matchedIds = /* @__PURE__ */ new Set();
|
|
532
|
+
const pinnedVisibleIds = /* @__PURE__ */ new Set();
|
|
533
|
+
const visibleReferenceCounts = /* @__PURE__ */ new Map();
|
|
534
|
+
const parentIds = /* @__PURE__ */ new Map();
|
|
535
|
+
const remainingChildren = /* @__PURE__ */ new Map();
|
|
536
|
+
for (const activity of activities) {
|
|
537
|
+
const activityId = String(activity.id);
|
|
538
|
+
const parentId = parentOf(activityId);
|
|
539
|
+
parentIds.set(activityId, parentId);
|
|
540
|
+
remainingChildren.set(activityId, remainingChildren.get(activityId) ?? 0);
|
|
541
|
+
if (parentId !== null) {
|
|
542
|
+
remainingChildren.set(
|
|
543
|
+
parentId,
|
|
544
|
+
(remainingChildren.get(parentId) ?? 0) + 1
|
|
545
|
+
);
|
|
546
|
+
}
|
|
547
|
+
const isMatch = matchesFilter(activity, filter, context);
|
|
548
|
+
const isPinned = input.pinnedVisibleIds?.has(activityId) === true;
|
|
549
|
+
visibleReferenceCounts.set(
|
|
550
|
+
activityId,
|
|
551
|
+
(isMatch ? 1 : 0) + (isPinned ? 1 : 0)
|
|
552
|
+
);
|
|
553
|
+
if (isMatch) matchedIds.add(activityId);
|
|
554
|
+
if (isPinned) pinnedVisibleIds.add(activityId);
|
|
555
|
+
}
|
|
556
|
+
return {
|
|
557
|
+
matchedIds,
|
|
558
|
+
pinnedVisibleIds,
|
|
559
|
+
visibleReferenceCounts,
|
|
560
|
+
parentIds,
|
|
561
|
+
remainingChildren
|
|
562
|
+
};
|
|
563
|
+
}
|
|
564
|
+
function propagateVisibleReferenceCounts(seed2) {
|
|
565
|
+
const { visibleReferenceCounts, parentIds, remainingChildren } = seed2;
|
|
566
|
+
const pendingIds = [];
|
|
567
|
+
for (const activityId of parentIds.keys()) {
|
|
568
|
+
if (remainingChildren.get(activityId) === 0) pendingIds.push(activityId);
|
|
569
|
+
}
|
|
570
|
+
while (pendingIds.length > 0) {
|
|
571
|
+
const activityId = pendingIds.pop();
|
|
572
|
+
if (activityId === void 0) break;
|
|
573
|
+
const parentId = parentIds.get(activityId);
|
|
574
|
+
if (parentId === null || parentId === void 0) continue;
|
|
575
|
+
const referenceCount = visibleReferenceCounts.get(activityId) ?? 0;
|
|
576
|
+
visibleReferenceCounts.set(
|
|
577
|
+
parentId,
|
|
578
|
+
(visibleReferenceCounts.get(parentId) ?? 0) + referenceCount
|
|
579
|
+
);
|
|
580
|
+
const remaining = (remainingChildren.get(parentId) ?? 1) - 1;
|
|
581
|
+
remainingChildren.set(parentId, remaining);
|
|
582
|
+
if (remaining === 0) pendingIds.push(parentId);
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
function collectVisibleIds(visibleReferenceCounts) {
|
|
586
|
+
const visibleIds = /* @__PURE__ */ new Set();
|
|
587
|
+
for (const [activityId, referenceCount] of visibleReferenceCounts) {
|
|
588
|
+
if (referenceCount > 0) visibleIds.add(activityId);
|
|
589
|
+
}
|
|
590
|
+
return visibleIds;
|
|
591
|
+
}
|
|
592
|
+
function buildFilterProjection(input) {
|
|
593
|
+
const seed2 = seedFilterProjection(input);
|
|
594
|
+
propagateVisibleReferenceCounts(seed2);
|
|
595
|
+
const visibleIds = collectVisibleIds(seed2.visibleReferenceCounts);
|
|
596
|
+
return {
|
|
597
|
+
filter: input.filter,
|
|
598
|
+
context: input.context,
|
|
599
|
+
matchedIds: seed2.matchedIds,
|
|
600
|
+
pinnedVisibleIds: seed2.pinnedVisibleIds,
|
|
601
|
+
visibleIds,
|
|
602
|
+
visibleReferenceCounts: seed2.visibleReferenceCounts
|
|
603
|
+
};
|
|
604
|
+
}
|
|
605
|
+
function updateFilterProjection(projection, activities, parentOf) {
|
|
606
|
+
const changedVisibilityIds = /* @__PURE__ */ new Set();
|
|
607
|
+
for (const activity of activities) {
|
|
608
|
+
const activityId = String(activity.id);
|
|
609
|
+
changedVisibilityIds.add(activityId);
|
|
610
|
+
const wasMatch = projection.matchedIds.has(activityId);
|
|
611
|
+
const isMatch = matchesFilter(
|
|
612
|
+
activity,
|
|
613
|
+
projection.filter,
|
|
614
|
+
projection.context
|
|
615
|
+
);
|
|
616
|
+
if (wasMatch === isMatch) continue;
|
|
617
|
+
if (isMatch) projection.matchedIds.add(activityId);
|
|
618
|
+
else projection.matchedIds.delete(activityId);
|
|
619
|
+
const referenceDelta = isMatch ? 1 : -1;
|
|
620
|
+
let currentId = activityId;
|
|
621
|
+
while (currentId !== null) {
|
|
622
|
+
const beforeCount = projection.visibleReferenceCounts.get(currentId) ?? 0;
|
|
623
|
+
const afterCount = beforeCount + referenceDelta;
|
|
624
|
+
projection.visibleReferenceCounts.set(currentId, afterCount);
|
|
625
|
+
const wasVisible = beforeCount > 0;
|
|
626
|
+
const isVisible = afterCount > 0;
|
|
627
|
+
if (wasVisible !== isVisible) {
|
|
628
|
+
changedVisibilityIds.add(currentId);
|
|
629
|
+
if (isVisible) projection.visibleIds.add(currentId);
|
|
630
|
+
else projection.visibleIds.delete(currentId);
|
|
631
|
+
}
|
|
632
|
+
currentId = parentOf(currentId);
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
return changedVisibilityIds;
|
|
636
|
+
}
|
|
526
637
|
|
|
527
638
|
// src/internal/filter/context.ts
|
|
528
639
|
var COLLATION_LOCALE = "en";
|
|
@@ -583,17 +694,26 @@ function isValidDateValue(value) {
|
|
|
583
694
|
function isEmptyFilter(filter) {
|
|
584
695
|
return filter.criteria.length === 0 && filter.dateRange === void 0;
|
|
585
696
|
}
|
|
586
|
-
function
|
|
697
|
+
function resolveFilter(adapter, filter, hoursPerDay) {
|
|
587
698
|
const activities = adapter.getAllActivities();
|
|
588
699
|
if (isEmptyFilter(filter)) {
|
|
589
|
-
return
|
|
700
|
+
return {
|
|
701
|
+
visibleIds: evaluateVisibleIds({
|
|
702
|
+
activities,
|
|
703
|
+
parentOf: (activityId) => adapter.getParentId(activityId),
|
|
704
|
+
filter,
|
|
705
|
+
context: buildFilterContext(hoursPerDay)
|
|
706
|
+
}),
|
|
707
|
+
projection: null
|
|
708
|
+
};
|
|
590
709
|
}
|
|
591
|
-
|
|
710
|
+
const projection = buildFilterProjection({
|
|
592
711
|
activities,
|
|
593
712
|
parentOf: (activityId) => adapter.getParentId(activityId),
|
|
594
713
|
filter,
|
|
595
714
|
context: buildFilterContext(hoursPerDay)
|
|
596
715
|
});
|
|
716
|
+
return { visibleIds: projection.visibleIds, projection };
|
|
597
717
|
}
|
|
598
718
|
function dispatchFilterSet(action, deps) {
|
|
599
719
|
const { adapter, hoursPerDay } = deps;
|
|
@@ -607,7 +727,12 @@ function dispatchFilterSet(action, deps) {
|
|
|
607
727
|
dateRange: action.dateRange
|
|
608
728
|
};
|
|
609
729
|
adapter.setActiveFilter(isEmptyFilter(filter) ? null : filter);
|
|
610
|
-
const visibleIds =
|
|
730
|
+
const { visibleIds, projection } = resolveFilter(
|
|
731
|
+
adapter,
|
|
732
|
+
filter,
|
|
733
|
+
hoursPerDay
|
|
734
|
+
);
|
|
735
|
+
adapter.setFilterProjection(projection);
|
|
611
736
|
const viewState = applyVisibleSet(adapter, visibleIds);
|
|
612
737
|
const changes = {
|
|
613
738
|
source: action,
|
|
@@ -678,11 +803,7 @@ function visualIndexInParent(taskId, adapter) {
|
|
|
678
803
|
return siblings.findIndex((id) => String(id) === String(taskId));
|
|
679
804
|
}
|
|
680
805
|
function collectChildrenSnapshots(parentId, adapter) {
|
|
681
|
-
const
|
|
682
|
-
if (key === "0") {
|
|
683
|
-
return adapter.getAllActivities().filter((a) => a.parentId === null);
|
|
684
|
-
}
|
|
685
|
-
const childIds = adapter.getChildren(parentId);
|
|
806
|
+
const childIds = String(parentId) === ROOT_PARENT_ID ? adapter.getRootIds?.() ?? adapter.getChildren(ROOT_PARENT_ID) : adapter.getChildren(parentId);
|
|
686
807
|
const out = [];
|
|
687
808
|
for (const id of childIds) {
|
|
688
809
|
const snap = adapter.getActivity(id);
|
|
@@ -726,6 +847,14 @@ function collectBranchOrder(adapter) {
|
|
|
726
847
|
visit(ROOT_PARENT_ID);
|
|
727
848
|
return branches;
|
|
728
849
|
}
|
|
850
|
+
function collectBranchOrderForParents(adapter, parentIds) {
|
|
851
|
+
const branches = [];
|
|
852
|
+
for (const parentId of parentIds) {
|
|
853
|
+
const childIds = getChildrenInVisualOrder(parentId, adapter);
|
|
854
|
+
if (childIds.length > 1) branches.push({ parentId, childIds });
|
|
855
|
+
}
|
|
856
|
+
return branches;
|
|
857
|
+
}
|
|
729
858
|
|
|
730
859
|
// src/dispatch/order.ts
|
|
731
860
|
function validateRule(rule) {
|
|
@@ -2277,6 +2406,34 @@ function wouldIntroduceCycle(existingLinks, proposedLink) {
|
|
|
2277
2406
|
}
|
|
2278
2407
|
return hasPath(adjacency, tgt, src);
|
|
2279
2408
|
}
|
|
2409
|
+
function hasDirectedCycle(links) {
|
|
2410
|
+
const adjacency = /* @__PURE__ */ new Map();
|
|
2411
|
+
const indegrees = /* @__PURE__ */ new Map();
|
|
2412
|
+
for (const link of links) {
|
|
2413
|
+
const source = String(link.source);
|
|
2414
|
+
const target = String(link.target);
|
|
2415
|
+
const neighbors = adjacency.get(source) ?? [];
|
|
2416
|
+
neighbors.push(target);
|
|
2417
|
+
adjacency.set(source, neighbors);
|
|
2418
|
+
indegrees.set(source, indegrees.get(source) ?? 0);
|
|
2419
|
+
indegrees.set(target, (indegrees.get(target) ?? 0) + 1);
|
|
2420
|
+
}
|
|
2421
|
+
const ready = new Queue();
|
|
2422
|
+
for (const [activityId, indegree] of indegrees) {
|
|
2423
|
+
if (indegree === 0) ready.enqueue(activityId);
|
|
2424
|
+
}
|
|
2425
|
+
let visited = 0;
|
|
2426
|
+
while (!ready.isEmpty()) {
|
|
2427
|
+
const current = ready.dequeue();
|
|
2428
|
+
visited += 1;
|
|
2429
|
+
for (const neighbor of adjacency.get(current) ?? []) {
|
|
2430
|
+
const indegree = indegrees.get(neighbor) - 1;
|
|
2431
|
+
indegrees.set(neighbor, indegree);
|
|
2432
|
+
if (indegree === 0) ready.enqueue(neighbor);
|
|
2433
|
+
}
|
|
2434
|
+
}
|
|
2435
|
+
return visited !== indegrees.size;
|
|
2436
|
+
}
|
|
2280
2437
|
function hasPath(adjacency, from, to) {
|
|
2281
2438
|
if (from === to) return true;
|
|
2282
2439
|
const visited = /* @__PURE__ */ new Set([from]);
|
|
@@ -2306,6 +2463,70 @@ function applyLinkOperation(op, deps) {
|
|
|
2306
2463
|
return applyDelete(op, deps);
|
|
2307
2464
|
}
|
|
2308
2465
|
}
|
|
2466
|
+
function applyLinkCreates(links, deps) {
|
|
2467
|
+
if (links.length === 0) return { applied: true };
|
|
2468
|
+
const existingLinks = deps.port.getAllLinks();
|
|
2469
|
+
const duplicateKeys = /* @__PURE__ */ new Map();
|
|
2470
|
+
const edges = [];
|
|
2471
|
+
for (const link of existingLinks) {
|
|
2472
|
+
const key = linkKey(link);
|
|
2473
|
+
if (!duplicateKeys.has(key)) duplicateKeys.set(key, link.id);
|
|
2474
|
+
edges.push({ source: link.source, target: link.target });
|
|
2475
|
+
}
|
|
2476
|
+
const existingGraphHasCycle = hasDirectedCycle(edges);
|
|
2477
|
+
let rejected = null;
|
|
2478
|
+
for (const { operation, linkId } of links) {
|
|
2479
|
+
if (String(operation.source) === String(operation.target)) {
|
|
2480
|
+
rejected = { applied: false, rejected: "self-link" };
|
|
2481
|
+
break;
|
|
2482
|
+
}
|
|
2483
|
+
if (!deps.port.getActivity(operation.source) || !deps.port.getActivity(operation.target)) {
|
|
2484
|
+
rejected = { applied: false, rejected: "activity-missing" };
|
|
2485
|
+
break;
|
|
2486
|
+
}
|
|
2487
|
+
const key = linkKey(operation);
|
|
2488
|
+
const duplicateId = duplicateKeys.get(key);
|
|
2489
|
+
if (duplicateId !== void 0) {
|
|
2490
|
+
rejected = {
|
|
2491
|
+
applied: false,
|
|
2492
|
+
rejected: "duplicate",
|
|
2493
|
+
linkId: duplicateId
|
|
2494
|
+
};
|
|
2495
|
+
break;
|
|
2496
|
+
}
|
|
2497
|
+
duplicateKeys.set(key, linkId);
|
|
2498
|
+
if (existingGraphHasCycle && // ponytail: preserve legacy sequential semantics; optimize with dynamic
|
|
2499
|
+
// SCC reachability only if corrupt cyclic schedules become measurable.
|
|
2500
|
+
wouldIntroduceCycle(edges, {
|
|
2501
|
+
source: operation.source,
|
|
2502
|
+
target: operation.target
|
|
2503
|
+
})) {
|
|
2504
|
+
return { applied: false, rejected: "cycle" };
|
|
2505
|
+
}
|
|
2506
|
+
edges.push({ source: operation.source, target: operation.target });
|
|
2507
|
+
}
|
|
2508
|
+
if (!existingGraphHasCycle && hasDirectedCycle(edges)) {
|
|
2509
|
+
return { applied: false, rejected: "cycle" };
|
|
2510
|
+
}
|
|
2511
|
+
if (rejected) return rejected;
|
|
2512
|
+
for (const { operation, linkId } of links) {
|
|
2513
|
+
const preserved = deps.preserved?.get(linkId);
|
|
2514
|
+
deps.port.addLink({
|
|
2515
|
+
id: linkId,
|
|
2516
|
+
source: operation.source,
|
|
2517
|
+
target: operation.target,
|
|
2518
|
+
type: operation.type,
|
|
2519
|
+
lag: operation.lag,
|
|
2520
|
+
...preserved?.ganttId === void 0 ? {} : { ganttId: preserved.ganttId },
|
|
2521
|
+
...preserved?.sectorId === void 0 ? {} : { sectorId: preserved.sectorId },
|
|
2522
|
+
...preserved?.proplannerId === void 0 ? {} : { proplannerId: preserved.proplannerId }
|
|
2523
|
+
});
|
|
2524
|
+
}
|
|
2525
|
+
return { applied: true };
|
|
2526
|
+
}
|
|
2527
|
+
function linkKey(link) {
|
|
2528
|
+
return JSON.stringify([String(link.source), String(link.target), link.type]);
|
|
2529
|
+
}
|
|
2309
2530
|
function applyCreate(op, deps) {
|
|
2310
2531
|
const { port } = deps;
|
|
2311
2532
|
if (String(op.source) === String(op.target)) {
|
|
@@ -2429,14 +2650,14 @@ function diffLinks(input) {
|
|
|
2429
2650
|
unresolved.push(spec);
|
|
2430
2651
|
continue;
|
|
2431
2652
|
}
|
|
2432
|
-
const key =
|
|
2653
|
+
const key = linkKey2(source, spec.type);
|
|
2433
2654
|
if (desired.has(key)) continue;
|
|
2434
2655
|
desired.set(key, { source, type: spec.type, lag: spec.lag });
|
|
2435
2656
|
}
|
|
2436
2657
|
const currentByKey = /* @__PURE__ */ new Map();
|
|
2437
2658
|
for (const link of currentLinks) {
|
|
2438
2659
|
if (String(link.target) !== String(targetActivityId)) continue;
|
|
2439
|
-
currentByKey.set(
|
|
2660
|
+
currentByKey.set(linkKey2(link.source, link.type), link);
|
|
2440
2661
|
}
|
|
2441
2662
|
const operations = [];
|
|
2442
2663
|
for (const [key, link] of currentByKey) {
|
|
@@ -2463,7 +2684,7 @@ function diffLinks(input) {
|
|
|
2463
2684
|
}
|
|
2464
2685
|
return { operations, unresolved };
|
|
2465
2686
|
}
|
|
2466
|
-
function
|
|
2687
|
+
function linkKey2(source, type) {
|
|
2467
2688
|
return `${String(source)}::${type}`;
|
|
2468
2689
|
}
|
|
2469
2690
|
function diffOutgoingLinks(input) {
|
|
@@ -2476,14 +2697,14 @@ function diffOutgoingLinks(input) {
|
|
|
2476
2697
|
unresolved.push(spec);
|
|
2477
2698
|
continue;
|
|
2478
2699
|
}
|
|
2479
|
-
const key =
|
|
2700
|
+
const key = linkKey2(target, spec.type);
|
|
2480
2701
|
if (desired.has(key)) continue;
|
|
2481
2702
|
desired.set(key, { target, type: spec.type, lag: spec.lag });
|
|
2482
2703
|
}
|
|
2483
2704
|
const currentByKey = /* @__PURE__ */ new Map();
|
|
2484
2705
|
for (const link of currentLinks) {
|
|
2485
2706
|
if (String(link.source) !== String(sourceActivityId)) continue;
|
|
2486
|
-
currentByKey.set(
|
|
2707
|
+
currentByKey.set(linkKey2(link.target, link.type), link);
|
|
2487
2708
|
}
|
|
2488
2709
|
const operations = [];
|
|
2489
2710
|
for (const [key, link] of currentByKey) {
|
|
@@ -4204,29 +4425,6 @@ function toConstraintWarning(activityId, notification) {
|
|
|
4204
4425
|
};
|
|
4205
4426
|
}
|
|
4206
4427
|
|
|
4207
|
-
// src/constants/activity-properties.ts
|
|
4208
|
-
var ACTIVITY_PROPERTY = {
|
|
4209
|
-
PARENT: "parentId",
|
|
4210
|
-
TYPE: "type",
|
|
4211
|
-
START_DATE: "startDate",
|
|
4212
|
-
END_DATE: "endDate",
|
|
4213
|
-
CONSTRAINT_TYPE: "constraintType",
|
|
4214
|
-
CONSTRAINT_DATE: "constraintDate",
|
|
4215
|
-
AUTO_SCHEDULING: "autoScheduling",
|
|
4216
|
-
PROGRESS: "progress",
|
|
4217
|
-
COST: "cost",
|
|
4218
|
-
USED_COST: "usedCost",
|
|
4219
|
-
REAL_COST: "realCost",
|
|
4220
|
-
WORK_HOURS: "workHours",
|
|
4221
|
-
REAL_WORK_HOURS: "realWorkHours",
|
|
4222
|
-
CORRELATIVE_ID: "correlativeId",
|
|
4223
|
-
CUSTOM_ID: "customId",
|
|
4224
|
-
NEW_ACTIVITIES_ARRAY: "newActivityIds",
|
|
4225
|
-
HAS_NEW_ACTIVITIES: "hasNewActivities"};
|
|
4226
|
-
var LINK_PROPERTY = {
|
|
4227
|
-
TYPE: "type",
|
|
4228
|
-
LAG: "lag"};
|
|
4229
|
-
|
|
4230
4428
|
// src/columns/types.ts
|
|
4231
4429
|
var COLUMN_WRITABLE_FIELD_LIST = [
|
|
4232
4430
|
"name",
|
|
@@ -5550,7 +5748,7 @@ var CalculateBackwardParentsLinks = class {
|
|
|
5550
5748
|
return false;
|
|
5551
5749
|
}
|
|
5552
5750
|
return allLinksCalculations.reduce(
|
|
5553
|
-
(
|
|
5751
|
+
(min2, activity) => activity.lf < min2.lf ? activity : min2
|
|
5554
5752
|
);
|
|
5555
5753
|
}
|
|
5556
5754
|
calculateSfRestrictionDates() {
|
|
@@ -7723,13 +7921,13 @@ var LinksConstraintCalculator2 = class extends CalculationsGenericMethods_defaul
|
|
|
7723
7921
|
getMinDateFromLinks(allLinksCalculations) {
|
|
7724
7922
|
try {
|
|
7725
7923
|
if (this.constraint === CONSTRAINT_TYPES.ALAP || this.constraint === CONSTRAINT_TYPES.ASAP) {
|
|
7726
|
-
const
|
|
7727
|
-
(
|
|
7924
|
+
const min2 = allLinksCalculations.reduce(
|
|
7925
|
+
(min3, activity) => activity.lf < min3.lf ? activity : min3
|
|
7728
7926
|
);
|
|
7729
|
-
return
|
|
7927
|
+
return min2;
|
|
7730
7928
|
}
|
|
7731
7929
|
return allLinksCalculations.reduce(
|
|
7732
|
-
(
|
|
7930
|
+
(min2, activity) => activity.ls < min2.ls ? activity : min2
|
|
7733
7931
|
);
|
|
7734
7932
|
} catch (error) {
|
|
7735
7933
|
throw new Error(
|
|
@@ -7739,7 +7937,7 @@ var LinksConstraintCalculator2 = class extends CalculationsGenericMethods_defaul
|
|
|
7739
7937
|
}
|
|
7740
7938
|
getMinLfFromLinks(allLinksCalculations) {
|
|
7741
7939
|
return allLinksCalculations.reduce(
|
|
7742
|
-
(
|
|
7940
|
+
(min2, activity) => activity.lf < min2.lf ? activity : min2
|
|
7743
7941
|
);
|
|
7744
7942
|
}
|
|
7745
7943
|
getMinDateBasedOnConstraint(minCalculatedDateFromLinks, constraintType = this.constraint) {
|
|
@@ -8197,7 +8395,7 @@ var BackwardPath = class extends generic_calculations_default {
|
|
|
8197
8395
|
return false;
|
|
8198
8396
|
}
|
|
8199
8397
|
return allLinksCalculations.reduce(
|
|
8200
|
-
(
|
|
8398
|
+
(min2, activity) => activity.lf < min2.lf ? activity : min2
|
|
8201
8399
|
);
|
|
8202
8400
|
}
|
|
8203
8401
|
};
|
|
@@ -8796,7 +8994,7 @@ var CriticalPath = class {
|
|
|
8796
8994
|
activitiesCount: this.forwardPathCalculations?.size || 0
|
|
8797
8995
|
}
|
|
8798
8996
|
});
|
|
8799
|
-
|
|
8997
|
+
throw error;
|
|
8800
8998
|
}
|
|
8801
8999
|
}
|
|
8802
9000
|
async calculateFloatsAsync(forward, backward, isCurrent) {
|
|
@@ -8817,7 +9015,7 @@ var CriticalPath = class {
|
|
|
8817
9015
|
activitiesCount: this.forwardPathCalculations?.size || 0
|
|
8818
9016
|
}
|
|
8819
9017
|
});
|
|
8820
|
-
|
|
9018
|
+
throw error;
|
|
8821
9019
|
}
|
|
8822
9020
|
}
|
|
8823
9021
|
};
|
|
@@ -8842,16 +9040,41 @@ function constraintDateForCriticalPath(activity, state) {
|
|
|
8842
9040
|
return typedDayIsWorking ? getEffectiveConstraintDate(activity, state) : raw;
|
|
8843
9041
|
}
|
|
8844
9042
|
function toLegacyCalendar(api) {
|
|
9043
|
+
const endDateByKey = /* @__PURE__ */ new Map();
|
|
9044
|
+
const durationByKey = /* @__PURE__ */ new Map();
|
|
9045
|
+
const closestWorkTimeByKey = /* @__PURE__ */ new Map();
|
|
8845
9046
|
return {
|
|
8846
|
-
calculateEndDate: ({ start_date, duration }) =>
|
|
8847
|
-
|
|
9047
|
+
calculateEndDate: ({ start_date, duration }) => {
|
|
9048
|
+
const key = `${start_date.getTime()}|${duration}`;
|
|
9049
|
+
const cached = endDateByKey.get(key);
|
|
9050
|
+
if (cached !== void 0) return new Date(cached);
|
|
9051
|
+
const value = api.calculateEndDate(start_date, duration);
|
|
9052
|
+
endDateByKey.set(key, value.getTime());
|
|
9053
|
+
return value;
|
|
9054
|
+
},
|
|
9055
|
+
calculateDuration: ({ start_date, end_date }) => {
|
|
9056
|
+
const key = `${start_date.getTime()}|${end_date.getTime()}`;
|
|
9057
|
+
const cached = durationByKey.get(key);
|
|
9058
|
+
if (cached !== void 0) return cached;
|
|
9059
|
+
const value = api.calculateDuration(start_date, end_date);
|
|
9060
|
+
durationByKey.set(key, value);
|
|
9061
|
+
return value;
|
|
9062
|
+
},
|
|
8848
9063
|
getWorkHours: (date2) => api.getWorkHours(date2),
|
|
8849
9064
|
$gantt: {
|
|
8850
|
-
getClosestWorkTime: ({ date: date2, dir, unit }) =>
|
|
8851
|
-
|
|
8852
|
-
|
|
8853
|
-
|
|
8854
|
-
|
|
9065
|
+
getClosestWorkTime: ({ date: date2, dir, unit }) => {
|
|
9066
|
+
const resolvedUnit = unit ?? "hour";
|
|
9067
|
+
const key = `${date2.getTime()}|${dir}|${resolvedUnit}`;
|
|
9068
|
+
const cached = closestWorkTimeByKey.get(key);
|
|
9069
|
+
if (cached !== void 0) return new Date(cached);
|
|
9070
|
+
const value = api.getClosestWorkTime({
|
|
9071
|
+
date: date2,
|
|
9072
|
+
direction: dir,
|
|
9073
|
+
unit: resolvedUnit
|
|
9074
|
+
});
|
|
9075
|
+
closestWorkTimeByKey.set(key, value.getTime());
|
|
9076
|
+
return value;
|
|
9077
|
+
}
|
|
8855
9078
|
}
|
|
8856
9079
|
};
|
|
8857
9080
|
}
|
|
@@ -8931,19 +9154,19 @@ function orderByTree(leveled) {
|
|
|
8931
9154
|
return ordered;
|
|
8932
9155
|
}
|
|
8933
9156
|
function projectBounds(state) {
|
|
8934
|
-
let
|
|
8935
|
-
let
|
|
9157
|
+
let min2 = null;
|
|
9158
|
+
let max2 = null;
|
|
8936
9159
|
state.forEachActivity((activity) => {
|
|
8937
9160
|
if (activity.startDate) {
|
|
8938
9161
|
const start = activity.startDate.getTime();
|
|
8939
|
-
|
|
9162
|
+
min2 = min2 == null ? start : Math.min(min2, start);
|
|
8940
9163
|
}
|
|
8941
9164
|
if (activity.endDate) {
|
|
8942
9165
|
const end = activity.endDate.getTime();
|
|
8943
|
-
|
|
9166
|
+
max2 = max2 == null ? end : Math.max(max2, end);
|
|
8944
9167
|
}
|
|
8945
9168
|
});
|
|
8946
|
-
return { start_date: new Date(
|
|
9169
|
+
return { start_date: new Date(min2 ?? 0), end_date: new Date(max2 ?? 0) };
|
|
8947
9170
|
}
|
|
8948
9171
|
async function createGanttAdapter(state, hoursPerDay) {
|
|
8949
9172
|
const calendarCache = /* @__PURE__ */ new Map();
|
|
@@ -9010,13 +9233,27 @@ function fieldsForActivity(activityId, progress, maps) {
|
|
|
9010
9233
|
freeSlackHours: maps.freeFloat.get(activityId)?.activityFreeFloat ?? null
|
|
9011
9234
|
};
|
|
9012
9235
|
}
|
|
9236
|
+
var datesEqual = (left, right) => left === right || left !== null && right !== null && left.getTime() === right.getTime();
|
|
9237
|
+
function setCriticalPathFieldsIfChanged(state, activityId, fields) {
|
|
9238
|
+
const activity = state.getActivity(activityId);
|
|
9239
|
+
const current = activity?.criticalPath;
|
|
9240
|
+
const next = fields.criticalPath;
|
|
9241
|
+
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)) {
|
|
9242
|
+
return;
|
|
9243
|
+
}
|
|
9244
|
+
state.setActivityFields(activityId, fields);
|
|
9245
|
+
}
|
|
9013
9246
|
async function computeCriticalPathFields(state, hoursPerDay, isCurrent = () => true) {
|
|
9014
9247
|
if (!isCurrent()) return null;
|
|
9015
9248
|
const gantt = await createGanttAdapter(state, hoursPerDay);
|
|
9016
9249
|
if (!isCurrent()) return null;
|
|
9017
9250
|
const criticalPath = new legacy_default(gantt);
|
|
9018
9251
|
const calculated = await criticalPath.calculate(gantt, isCurrent);
|
|
9019
|
-
if (!calculated
|
|
9252
|
+
if (!calculated) {
|
|
9253
|
+
if (!isCurrent()) return null;
|
|
9254
|
+
throw new Error("critical path calculation aborted");
|
|
9255
|
+
}
|
|
9256
|
+
if (!isCurrent()) return null;
|
|
9020
9257
|
const maps = {
|
|
9021
9258
|
forward: criticalPath.forwardPathCalculations,
|
|
9022
9259
|
backward: criticalPath.backwardPathCalculations,
|
|
@@ -9051,11 +9288,33 @@ async function applyCriticalPath(state, hoursPerDay, isCurrent = () => true) {
|
|
|
9051
9288
|
if (!fieldsByActivity || !isCurrent()) return false;
|
|
9052
9289
|
for (const [activityId, fields] of fieldsByActivity) {
|
|
9053
9290
|
if (!isCurrent()) return false;
|
|
9054
|
-
state
|
|
9291
|
+
setCriticalPathFieldsIfChanged(state, activityId, fields);
|
|
9055
9292
|
}
|
|
9056
9293
|
return true;
|
|
9057
9294
|
}
|
|
9058
9295
|
|
|
9296
|
+
// src/constants/activity-properties.ts
|
|
9297
|
+
var ACTIVITY_PROPERTY = {
|
|
9298
|
+
PARENT: "parentId",
|
|
9299
|
+
TYPE: "type",
|
|
9300
|
+
START_DATE: "startDate",
|
|
9301
|
+
END_DATE: "endDate",
|
|
9302
|
+
CONSTRAINT_TYPE: "constraintType",
|
|
9303
|
+
CONSTRAINT_DATE: "constraintDate",
|
|
9304
|
+
PROGRESS: "progress",
|
|
9305
|
+
COST: "cost",
|
|
9306
|
+
USED_COST: "usedCost",
|
|
9307
|
+
REAL_COST: "realCost",
|
|
9308
|
+
WORK_HOURS: "workHours",
|
|
9309
|
+
REAL_WORK_HOURS: "realWorkHours",
|
|
9310
|
+
CORRELATIVE_ID: "correlativeId",
|
|
9311
|
+
CUSTOM_ID: "customId",
|
|
9312
|
+
NEW_ACTIVITIES_ARRAY: "newActivityIds",
|
|
9313
|
+
HAS_NEW_ACTIVITIES: "hasNewActivities"};
|
|
9314
|
+
var LINK_PROPERTY = {
|
|
9315
|
+
TYPE: "type",
|
|
9316
|
+
LAG: "lag"};
|
|
9317
|
+
|
|
9059
9318
|
// src/propagations/upward/real-cost.ts
|
|
9060
9319
|
function emitRealCost(state) {
|
|
9061
9320
|
const rootIds = [];
|
|
@@ -9528,6 +9787,40 @@ function buildLinkChangesForBatch(applied, beforeLinks, adapter) {
|
|
|
9528
9787
|
if (!finalLinkId) continue;
|
|
9529
9788
|
const after = adapter.getLink(finalLinkId);
|
|
9530
9789
|
if (!after) continue;
|
|
9790
|
+
const before = beforeLinks.get(finalLinkId);
|
|
9791
|
+
if (before) {
|
|
9792
|
+
const fields2 = {};
|
|
9793
|
+
if (String(before.source) !== String(after.source)) {
|
|
9794
|
+
fields2.source = {
|
|
9795
|
+
before: String(before.source),
|
|
9796
|
+
after: String(after.source)
|
|
9797
|
+
};
|
|
9798
|
+
}
|
|
9799
|
+
if (String(before.target) !== String(after.target)) {
|
|
9800
|
+
fields2.target = {
|
|
9801
|
+
before: String(before.target),
|
|
9802
|
+
after: String(after.target)
|
|
9803
|
+
};
|
|
9804
|
+
}
|
|
9805
|
+
if (before.type !== after.type) {
|
|
9806
|
+
fields2.type = { before: before.type, after: after.type };
|
|
9807
|
+
}
|
|
9808
|
+
if (Number(before.lag) !== Number(after.lag)) {
|
|
9809
|
+
fields2.lag = {
|
|
9810
|
+
before: Number(before.lag),
|
|
9811
|
+
after: Number(after.lag)
|
|
9812
|
+
};
|
|
9813
|
+
}
|
|
9814
|
+
if (Object.keys(fields2).length > 0) {
|
|
9815
|
+
out.push({
|
|
9816
|
+
id: String(finalLinkId),
|
|
9817
|
+
kind: "updated",
|
|
9818
|
+
fields: fields2,
|
|
9819
|
+
after: snapshotToLink(after)
|
|
9820
|
+
});
|
|
9821
|
+
}
|
|
9822
|
+
continue;
|
|
9823
|
+
}
|
|
9531
9824
|
out.push({
|
|
9532
9825
|
id: String(finalLinkId),
|
|
9533
9826
|
kind: "created",
|
|
@@ -9542,6 +9835,7 @@ function buildLinkChangesForBatch(applied, beforeLinks, adapter) {
|
|
|
9542
9835
|
continue;
|
|
9543
9836
|
}
|
|
9544
9837
|
if (op.kind === "delete") {
|
|
9838
|
+
if (adapter.getLink(op.linkId)) continue;
|
|
9545
9839
|
const beforeLink2 = beforeLinks.get(op.linkId);
|
|
9546
9840
|
out.push({
|
|
9547
9841
|
id: String(op.linkId),
|
|
@@ -9620,16 +9914,14 @@ async function dispatchInlineEdit(action, options, deps) {
|
|
|
9620
9914
|
const { adapter, ctx, scheduler, sector } = deps;
|
|
9621
9915
|
const pipeline = findPipelineForColumn(action.column);
|
|
9622
9916
|
if (!pipeline) {
|
|
9623
|
-
|
|
9624
|
-
|
|
9625
|
-
|
|
9917
|
+
return {
|
|
9918
|
+
ok: false,
|
|
9919
|
+
reason: REJECTION_REASON.NO_PIPELINE_FOR_COLUMN
|
|
9920
|
+
};
|
|
9626
9921
|
}
|
|
9627
|
-
if (
|
|
9922
|
+
if (action.column === COLUMN.DURATION) {
|
|
9628
9923
|
scheduler.invalidateExpandedLinksCache();
|
|
9629
9924
|
}
|
|
9630
|
-
if (action.column === ACTIVITY_PROPERTY.PARENT) {
|
|
9631
|
-
scheduler.invalidateAllCaches();
|
|
9632
|
-
}
|
|
9633
9925
|
const activitySnap = ctx.activityReader.getActivity(action.activityId);
|
|
9634
9926
|
if (!activitySnap) {
|
|
9635
9927
|
return { ok: false, reason: REJECTION_REASON.ACTIVITY_NOT_FOUND };
|
|
@@ -9760,9 +10052,6 @@ async function dispatchInlineEdit(action, options, deps) {
|
|
|
9760
10052
|
})
|
|
9761
10053
|
};
|
|
9762
10054
|
}
|
|
9763
|
-
function columnInvalidatesExpandedLinks(column) {
|
|
9764
|
-
return column === COLUMN.DURATION || column === ACTIVITY_PROPERTY.AUTO_SCHEDULING;
|
|
9765
|
-
}
|
|
9766
10055
|
|
|
9767
10056
|
// src/dispatch/handlers/link.ts
|
|
9768
10057
|
function collectBatchContext(operations, adapter) {
|
|
@@ -10272,11 +10561,24 @@ function buildNewActivity(input, adapter) {
|
|
|
10272
10561
|
startDate: rawStartDate,
|
|
10273
10562
|
endDate: rawStartDate
|
|
10274
10563
|
};
|
|
10275
|
-
|
|
10564
|
+
let startDate = overrides.startDate ? new Date(base.startDate) : adapter.getClosestWorkTime({
|
|
10276
10565
|
date: base.startDate,
|
|
10277
10566
|
dir: WORK_TIME_DIRECTION.FUTURE,
|
|
10278
10567
|
task: base
|
|
10279
10568
|
});
|
|
10569
|
+
if (!overrides.startDate && parent && isMilestoneType(parent.type)) {
|
|
10570
|
+
const inheritedDay = new Date(parent.startDate);
|
|
10571
|
+
inheritedDay.setUTCHours(0, 0, 0, 0);
|
|
10572
|
+
const snappedDay = new Date(startDate);
|
|
10573
|
+
snappedDay.setUTCHours(0, 0, 0, 0);
|
|
10574
|
+
if (snappedDay.getTime() !== inheritedDay.getTime()) {
|
|
10575
|
+
startDate = adapter.getClosestWorkTime({
|
|
10576
|
+
date: inheritedDay,
|
|
10577
|
+
dir: WORK_TIME_DIRECTION.FUTURE,
|
|
10578
|
+
task: base
|
|
10579
|
+
});
|
|
10580
|
+
}
|
|
10581
|
+
}
|
|
10280
10582
|
const endDate = adapter.calculateEndDate({
|
|
10281
10583
|
startDate,
|
|
10282
10584
|
durationHours: base.durationHours,
|
|
@@ -10378,8 +10680,8 @@ function computeInsertionCorrelativeId(adapter, parentId, afterSiblingId, before
|
|
|
10378
10680
|
}
|
|
10379
10681
|
}
|
|
10380
10682
|
if (siblings.length === 0) return isRoot ? 0 : 0;
|
|
10381
|
-
const
|
|
10382
|
-
return
|
|
10683
|
+
const max2 = Math.max(...siblings.map(readCorrelativeId));
|
|
10684
|
+
return max2 + 1;
|
|
10383
10685
|
}
|
|
10384
10686
|
var NOT_SET_SORT_VALUE2 = Number.MAX_SAFE_INTEGER;
|
|
10385
10687
|
var TEMP_CID_SENTINEL = Number.MAX_SAFE_INTEGER - 1;
|
|
@@ -10389,12 +10691,7 @@ function readCorrelativeId(activity) {
|
|
|
10389
10691
|
return Number.isFinite(n) ? n : NOT_SET_SORT_VALUE2;
|
|
10390
10692
|
}
|
|
10391
10693
|
function collectSiblings(adapter, parentKey) {
|
|
10392
|
-
|
|
10393
|
-
adapter.forEachActivity((a) => {
|
|
10394
|
-
const ap = normalizeParentKey(a.parentId);
|
|
10395
|
-
if (ap === parentKey) siblings.push(a);
|
|
10396
|
-
});
|
|
10397
|
-
return siblings;
|
|
10694
|
+
return collectChildrenSnapshots(parentKey, adapter);
|
|
10398
10695
|
}
|
|
10399
10696
|
function findFirstSelectedSibling(taskId, selected, adapter) {
|
|
10400
10697
|
let firstSelected = taskId;
|
|
@@ -10444,7 +10741,7 @@ function computeFractionalCidAtIndex(adapter, parentKey, targetIndex, excludeId)
|
|
|
10444
10741
|
// src/dispatch/handlers/activity-create.ts
|
|
10445
10742
|
async function createActivityCore(action, deps, opts = {}) {
|
|
10446
10743
|
const { adapter, scheduler, sector, calendars, activityIdGen, uidGen } = deps;
|
|
10447
|
-
scheduler.invalidateAllCaches();
|
|
10744
|
+
if (!opts.skipCacheInvalidation) scheduler.invalidateAllCaches();
|
|
10448
10745
|
const target = validateCreateTarget(action, adapter);
|
|
10449
10746
|
if (!target.ok) return target;
|
|
10450
10747
|
const { parent, anchorSiblingId, isRoot } = target;
|
|
@@ -10473,7 +10770,7 @@ async function createActivityCore(action, deps, opts = {}) {
|
|
|
10473
10770
|
newActivity.progress
|
|
10474
10771
|
);
|
|
10475
10772
|
assignCustomId(newActivity, action, deps, anchorSiblingId, parent, opts);
|
|
10476
|
-
const insertionCorrelativeId = computeInsertionCorrelativeId(
|
|
10773
|
+
const insertionCorrelativeId = opts.insertionCorrelativeId ?? computeInsertionCorrelativeId(
|
|
10477
10774
|
adapter,
|
|
10478
10775
|
isRoot ? ROOT_PARENT_ID : action.parentId,
|
|
10479
10776
|
action.afterSiblingId,
|
|
@@ -10490,9 +10787,11 @@ async function createActivityCore(action, deps, opts = {}) {
|
|
|
10490
10787
|
if (parentCustomId) {
|
|
10491
10788
|
deps.customIdTracker.releaseCustomId(parentCustomId);
|
|
10492
10789
|
}
|
|
10493
|
-
|
|
10494
|
-
|
|
10495
|
-
|
|
10790
|
+
if (!opts.skipRollupCascades) {
|
|
10791
|
+
recomputeHhCascadeForActivity(newId, adapter);
|
|
10792
|
+
recomputeUsedCostCascadeForActivity(newId, adapter);
|
|
10793
|
+
recomputeCostCascadeForActivity(newId, adapter);
|
|
10794
|
+
}
|
|
10496
10795
|
recomputeAndFoldCorrelatives(
|
|
10497
10796
|
adapter,
|
|
10498
10797
|
newId,
|
|
@@ -10664,296 +10963,62 @@ function findDefaultCalendarId(calendars) {
|
|
|
10664
10963
|
return "global";
|
|
10665
10964
|
}
|
|
10666
10965
|
|
|
10667
|
-
// src/
|
|
10668
|
-
function
|
|
10669
|
-
|
|
10670
|
-
|
|
10671
|
-
|
|
10672
|
-
|
|
10966
|
+
// src/propagations/upward/recompute-rollup-cascades.ts
|
|
10967
|
+
function recomputeRollupCascadesForParent(parentId, adapter) {
|
|
10968
|
+
recomputeHhCascadeForParent(parentId, adapter);
|
|
10969
|
+
recomputeUsedCostCascadeForParent(parentId, adapter);
|
|
10970
|
+
recomputeCostCascadeForParent(parentId, adapter);
|
|
10971
|
+
}
|
|
10972
|
+
|
|
10973
|
+
// src/propagations/shared/traversal-helpers.ts
|
|
10974
|
+
function collectDescendantIds(rootIds, adapter) {
|
|
10975
|
+
const out = /* @__PURE__ */ new Set();
|
|
10976
|
+
const stack = [];
|
|
10977
|
+
for (const id of rootIds) {
|
|
10978
|
+
const key = String(id);
|
|
10979
|
+
if (!out.has(key)) {
|
|
10980
|
+
out.add(key);
|
|
10981
|
+
stack.push(key);
|
|
10673
10982
|
}
|
|
10674
|
-
const parentId2 = sibling.parentId == null ? ROOT_PARENT_ID : String(sibling.parentId);
|
|
10675
|
-
return { ok: true, parentId: parentId2, firstAfter: destination.afterSiblingId };
|
|
10676
10983
|
}
|
|
10677
|
-
|
|
10678
|
-
|
|
10679
|
-
|
|
10680
|
-
|
|
10681
|
-
|
|
10682
|
-
|
|
10984
|
+
while (stack.length > 0) {
|
|
10985
|
+
const current = stack.pop();
|
|
10986
|
+
const children = adapter.getChildren(current);
|
|
10987
|
+
for (const childId of children) {
|
|
10988
|
+
const key = String(childId);
|
|
10989
|
+
if (!out.has(key)) {
|
|
10990
|
+
out.add(key);
|
|
10991
|
+
stack.push(key);
|
|
10992
|
+
}
|
|
10993
|
+
}
|
|
10994
|
+
}
|
|
10995
|
+
return out;
|
|
10683
10996
|
}
|
|
10684
|
-
function
|
|
10685
|
-
const
|
|
10997
|
+
function collectIncidentLinkIds(activityIds, adapter) {
|
|
10998
|
+
const out = /* @__PURE__ */ new Set();
|
|
10999
|
+
for (const activityId of activityIds) {
|
|
11000
|
+
for (const linkId of adapter.getOutgoingLinkIds(activityId)) {
|
|
11001
|
+
out.add(String(linkId));
|
|
11002
|
+
}
|
|
11003
|
+
for (const linkId of adapter.getIncomingLinkIds(activityId)) {
|
|
11004
|
+
out.add(String(linkId));
|
|
11005
|
+
}
|
|
11006
|
+
}
|
|
11007
|
+
return [...out];
|
|
11008
|
+
}
|
|
11009
|
+
|
|
11010
|
+
// src/internal/hierarchy/parent-demotion.ts
|
|
11011
|
+
function restoredPromotionFields(restore) {
|
|
11012
|
+
if (!restore) return null;
|
|
10686
11013
|
return {
|
|
10687
|
-
|
|
10688
|
-
|
|
10689
|
-
|
|
10690
|
-
|
|
10691
|
-
|
|
10692
|
-
|
|
10693
|
-
|
|
10694
|
-
|
|
10695
|
-
async function dispatchActivityPaste(action, options, deps) {
|
|
10696
|
-
const { adapter, scheduler, sector } = deps;
|
|
10697
|
-
const rootDest = resolvePasteRootDestination(action.destination, adapter);
|
|
10698
|
-
if (!rootDest.ok) return rootDest;
|
|
10699
|
-
const beforeSnap = await snapshotActivities(adapter, /* @__PURE__ */ new Set());
|
|
10700
|
-
const touched = /* @__PURE__ */ new Set();
|
|
10701
|
-
const originalToNew = /* @__PURE__ */ new Map();
|
|
10702
|
-
const createdIds = [];
|
|
10703
|
-
let prevRootId;
|
|
10704
|
-
for (const input of action.activities) {
|
|
10705
|
-
const mappedParent = originalToNew.get(input.originalParentId);
|
|
10706
|
-
let parentId;
|
|
10707
|
-
let afterSiblingId;
|
|
10708
|
-
let beforeSiblingId;
|
|
10709
|
-
if (mappedParent !== void 0) {
|
|
10710
|
-
parentId = mappedParent;
|
|
10711
|
-
} else {
|
|
10712
|
-
parentId = rootDest.parentId;
|
|
10713
|
-
if (prevRootId === void 0) {
|
|
10714
|
-
afterSiblingId = rootDest.firstAfter;
|
|
10715
|
-
beforeSiblingId = rootDest.firstBefore;
|
|
10716
|
-
} else {
|
|
10717
|
-
afterSiblingId = prevRootId;
|
|
10718
|
-
}
|
|
10719
|
-
}
|
|
10720
|
-
const core = await createActivityCore(
|
|
10721
|
-
{
|
|
10722
|
-
parentId,
|
|
10723
|
-
afterSiblingId,
|
|
10724
|
-
beforeSiblingId,
|
|
10725
|
-
...input.activityId ? { activityId: input.activityId } : {},
|
|
10726
|
-
overrides: applyPasteDomainSemantics(input.overrides),
|
|
10727
|
-
eventSource: action.eventSource
|
|
10728
|
-
},
|
|
10729
|
-
deps,
|
|
10730
|
-
{
|
|
10731
|
-
skipCorrelativeRecompute: true,
|
|
10732
|
-
skipBeforeSnap: true,
|
|
10733
|
-
customIdReferenceId: action.referenceActivityId
|
|
10734
|
-
}
|
|
10735
|
-
);
|
|
10736
|
-
if (!core.ok) return core;
|
|
10737
|
-
if (input.baselineSnapshot !== void 0) {
|
|
10738
|
-
const snapshot = input.baselineSnapshot;
|
|
10739
|
-
adapter.setActivityField(
|
|
10740
|
-
core.newId,
|
|
10741
|
-
"baselineSnapshot",
|
|
10742
|
-
snapshot ? {
|
|
10743
|
-
...snapshot,
|
|
10744
|
-
startDate: snapshot.startDate ? new Date(snapshot.startDate) : null,
|
|
10745
|
-
endDate: snapshot.endDate ? new Date(snapshot.endDate) : null
|
|
10746
|
-
} : null
|
|
10747
|
-
);
|
|
10748
|
-
}
|
|
10749
|
-
originalToNew.set(input.originalId, core.newId);
|
|
10750
|
-
createdIds.push(core.newId);
|
|
10751
|
-
if (mappedParent === void 0) prevRootId = core.newId;
|
|
10752
|
-
}
|
|
10753
|
-
const correlativeShifts = recomputeCorrelativeIds(adapter);
|
|
10754
|
-
const createdIdSet = new Set(createdIds.map(String));
|
|
10755
|
-
foldCorrelativeShifts(
|
|
10756
|
-
adapter,
|
|
10757
|
-
correlativeShifts,
|
|
10758
|
-
createdIdSet,
|
|
10759
|
-
beforeSnap,
|
|
10760
|
-
touched
|
|
10761
|
-
);
|
|
10762
|
-
for (const createdId of createdIds) touched.add(String(createdId));
|
|
10763
|
-
const appliedLinks = [];
|
|
10764
|
-
for (const link of action.links) {
|
|
10765
|
-
const newSource = originalToNew.get(link.source);
|
|
10766
|
-
const newTarget = originalToNew.get(link.target);
|
|
10767
|
-
if (newSource === void 0 || newTarget === void 0) continue;
|
|
10768
|
-
const op = {
|
|
10769
|
-
kind: "create",
|
|
10770
|
-
source: newSource,
|
|
10771
|
-
target: newTarget,
|
|
10772
|
-
type: link.type,
|
|
10773
|
-
lag: lagDaysToHours(link.lag, sector.hoursPerDay)
|
|
10774
|
-
};
|
|
10775
|
-
const res = applyLinkOperation(op, {
|
|
10776
|
-
port: adapter,
|
|
10777
|
-
newLinkId: link.linkId ? () => link.linkId : deps.linkIdGen.next
|
|
10778
|
-
});
|
|
10779
|
-
appliedLinks.push({
|
|
10780
|
-
op,
|
|
10781
|
-
finalLinkId: res.applied ? res.linkId : null,
|
|
10782
|
-
rejected: res.applied ? null : res.rejected ?? "link_op_rejected"
|
|
10783
|
-
});
|
|
10784
|
-
}
|
|
10785
|
-
const { scheduledIds } = await runPostMutation(
|
|
10786
|
-
{
|
|
10787
|
-
adapter,
|
|
10788
|
-
scheduler,
|
|
10789
|
-
sector: deps.sector,
|
|
10790
|
-
defaultBaseCalendarId: deps.defaultBaseCalendarId
|
|
10791
|
-
},
|
|
10792
|
-
{
|
|
10793
|
-
action,
|
|
10794
|
-
autoscheduleFrom: createdIds[0] ?? null,
|
|
10795
|
-
recomputeParentsFrom: createdIds,
|
|
10796
|
-
now: deps.now,
|
|
10797
|
-
options
|
|
10798
|
-
}
|
|
10799
|
-
);
|
|
10800
|
-
const linkChanges = buildLinkChangesForBatch(
|
|
10801
|
-
appliedLinks,
|
|
10802
|
-
/* @__PURE__ */ new Map(),
|
|
10803
|
-
adapter
|
|
10804
|
-
);
|
|
10805
|
-
return {
|
|
10806
|
-
ok: true,
|
|
10807
|
-
changes: await assembleChangeSet(adapter, {
|
|
10808
|
-
source: action,
|
|
10809
|
-
beforeSnap,
|
|
10810
|
-
touchedIds: touched,
|
|
10811
|
-
scheduledIds,
|
|
10812
|
-
links: linkChanges,
|
|
10813
|
-
hoursPerDay: sector.hoursPerDay
|
|
10814
|
-
})
|
|
10815
|
-
};
|
|
10816
|
-
}
|
|
10817
|
-
|
|
10818
|
-
// src/dispatch/handlers/sir-sync.ts
|
|
10819
|
-
function dispatchSirSync(action, deps) {
|
|
10820
|
-
const { adapter } = deps;
|
|
10821
|
-
const id = action.activityId;
|
|
10822
|
-
const snapshot = adapter.getActivity(id);
|
|
10823
|
-
if (!snapshot) {
|
|
10824
|
-
return { ok: false, reason: REJECTION_REASON.ACTIVITY_NOT_FOUND };
|
|
10825
|
-
}
|
|
10826
|
-
const before = snapshot.pendingRequestIds ?? [];
|
|
10827
|
-
const after = action.pendingRequests.map((r) => r.id);
|
|
10828
|
-
adapter.setActivityField(action.activityId, "pendingRequestIds", after);
|
|
10829
|
-
const change = {
|
|
10830
|
-
id,
|
|
10831
|
-
kind: "updated",
|
|
10832
|
-
fields: { pendingRequestIds: { before, after } },
|
|
10833
|
-
after: { id, pendingRequestIds: after }
|
|
10834
|
-
};
|
|
10835
|
-
return {
|
|
10836
|
-
ok: true,
|
|
10837
|
-
changes: {
|
|
10838
|
-
source: action,
|
|
10839
|
-
activities: [change],
|
|
10840
|
-
links: [],
|
|
10841
|
-
calendars: [],
|
|
10842
|
-
trackingEvents: []
|
|
10843
|
-
}
|
|
10844
|
-
};
|
|
10845
|
-
}
|
|
10846
|
-
|
|
10847
|
-
// src/dispatch/handlers/activity-lookahead-sync.ts
|
|
10848
|
-
function dispatchActivityLookaheadSync(action, deps) {
|
|
10849
|
-
const ids = [...new Set(action.activityIds.map(String))];
|
|
10850
|
-
const snapshots = ids.map((id) => deps.adapter.getActivity(id));
|
|
10851
|
-
if (snapshots.some((snapshot) => !snapshot)) {
|
|
10852
|
-
return { ok: false, reason: REJECTION_REASON.ACTIVITY_NOT_FOUND };
|
|
10853
|
-
}
|
|
10854
|
-
const activities = [];
|
|
10855
|
-
for (const [index, id] of ids.entries()) {
|
|
10856
|
-
const snapshot = snapshots[index];
|
|
10857
|
-
if (!snapshot) continue;
|
|
10858
|
-
const fields = {};
|
|
10859
|
-
if (snapshot.isLookahead !== action.isLookahead) {
|
|
10860
|
-
fields.isLookahead = {
|
|
10861
|
-
before: snapshot.isLookahead,
|
|
10862
|
-
after: action.isLookahead
|
|
10863
|
-
};
|
|
10864
|
-
deps.adapter.setActivityField(id, "isLookahead", action.isLookahead);
|
|
10865
|
-
}
|
|
10866
|
-
if (snapshot.hasLookaheadTasks !== action.hasLookaheadTasks) {
|
|
10867
|
-
fields.hasLookaheadTasks = {
|
|
10868
|
-
before: snapshot.hasLookaheadTasks,
|
|
10869
|
-
after: action.hasLookaheadTasks
|
|
10870
|
-
};
|
|
10871
|
-
deps.adapter.setActivityField(
|
|
10872
|
-
id,
|
|
10873
|
-
"hasLookaheadTasks",
|
|
10874
|
-
action.hasLookaheadTasks
|
|
10875
|
-
);
|
|
10876
|
-
}
|
|
10877
|
-
if (Object.keys(fields).length === 0) continue;
|
|
10878
|
-
activities.push({
|
|
10879
|
-
id,
|
|
10880
|
-
kind: "updated",
|
|
10881
|
-
fields,
|
|
10882
|
-
after: {
|
|
10883
|
-
...snapshot,
|
|
10884
|
-
isLookahead: action.isLookahead,
|
|
10885
|
-
hasLookaheadTasks: action.hasLookaheadTasks
|
|
10886
|
-
}
|
|
10887
|
-
});
|
|
10888
|
-
}
|
|
10889
|
-
return {
|
|
10890
|
-
ok: true,
|
|
10891
|
-
changes: {
|
|
10892
|
-
source: action,
|
|
10893
|
-
activities,
|
|
10894
|
-
links: [],
|
|
10895
|
-
calendars: [],
|
|
10896
|
-
trackingEvents: []
|
|
10897
|
-
}
|
|
10898
|
-
};
|
|
10899
|
-
}
|
|
10900
|
-
|
|
10901
|
-
// src/propagations/upward/recompute-rollup-cascades.ts
|
|
10902
|
-
function recomputeRollupCascadesForParent(parentId, adapter) {
|
|
10903
|
-
recomputeHhCascadeForParent(parentId, adapter);
|
|
10904
|
-
recomputeUsedCostCascadeForParent(parentId, adapter);
|
|
10905
|
-
recomputeCostCascadeForParent(parentId, adapter);
|
|
10906
|
-
}
|
|
10907
|
-
|
|
10908
|
-
// src/propagations/shared/traversal-helpers.ts
|
|
10909
|
-
function collectDescendantIds(rootIds, adapter) {
|
|
10910
|
-
const out = /* @__PURE__ */ new Set();
|
|
10911
|
-
const stack = [];
|
|
10912
|
-
for (const id of rootIds) {
|
|
10913
|
-
const key = String(id);
|
|
10914
|
-
if (!out.has(key)) {
|
|
10915
|
-
out.add(key);
|
|
10916
|
-
stack.push(key);
|
|
10917
|
-
}
|
|
10918
|
-
}
|
|
10919
|
-
while (stack.length > 0) {
|
|
10920
|
-
const current = stack.pop();
|
|
10921
|
-
const children = adapter.getChildren(current);
|
|
10922
|
-
for (const childId of children) {
|
|
10923
|
-
const key = String(childId);
|
|
10924
|
-
if (!out.has(key)) {
|
|
10925
|
-
out.add(key);
|
|
10926
|
-
stack.push(key);
|
|
10927
|
-
}
|
|
10928
|
-
}
|
|
10929
|
-
}
|
|
10930
|
-
return out;
|
|
10931
|
-
}
|
|
10932
|
-
function collectIncidentLinkIds(activityIds, adapter) {
|
|
10933
|
-
const out = /* @__PURE__ */ new Set();
|
|
10934
|
-
for (const activityId of activityIds) {
|
|
10935
|
-
for (const linkId of adapter.getOutgoingLinkIds(activityId)) {
|
|
10936
|
-
out.add(String(linkId));
|
|
10937
|
-
}
|
|
10938
|
-
for (const linkId of adapter.getIncomingLinkIds(activityId)) {
|
|
10939
|
-
out.add(String(linkId));
|
|
10940
|
-
}
|
|
10941
|
-
}
|
|
10942
|
-
return [...out];
|
|
10943
|
-
}
|
|
10944
|
-
|
|
10945
|
-
// src/internal/hierarchy/parent-demotion.ts
|
|
10946
|
-
function restoredPromotionFields(restore) {
|
|
10947
|
-
if (!restore) return null;
|
|
10948
|
-
return {
|
|
10949
|
-
type: restore.type,
|
|
10950
|
-
startDate: new Date(restore.startDate),
|
|
10951
|
-
endDate: new Date(restore.endDate),
|
|
10952
|
-
durationHours: restore.durationHours,
|
|
10953
|
-
expectedProgressBaseline: restore.expectedProgressBaseline,
|
|
10954
|
-
constraintType: restore.constraintType,
|
|
10955
|
-
constraintDate: restore.constraintDate ? new Date(restore.constraintDate) : null,
|
|
10956
|
-
progress: restore.progress
|
|
11014
|
+
type: restore.type,
|
|
11015
|
+
startDate: new Date(restore.startDate),
|
|
11016
|
+
endDate: new Date(restore.endDate),
|
|
11017
|
+
durationHours: restore.durationHours,
|
|
11018
|
+
expectedProgressBaseline: restore.expectedProgressBaseline,
|
|
11019
|
+
constraintType: restore.constraintType,
|
|
11020
|
+
constraintDate: restore.constraintDate ? new Date(restore.constraintDate) : null,
|
|
11021
|
+
progress: restore.progress
|
|
10957
11022
|
};
|
|
10958
11023
|
}
|
|
10959
11024
|
function canonicalEndDateField(parent, durationHours, computeEndDate) {
|
|
@@ -11196,7 +11261,647 @@ function buildLinkDeletions(beforeLinks) {
|
|
|
11196
11261
|
after: null
|
|
11197
11262
|
});
|
|
11198
11263
|
}
|
|
11199
|
-
return out;
|
|
11264
|
+
return out;
|
|
11265
|
+
}
|
|
11266
|
+
|
|
11267
|
+
// src/dispatch/handlers/activity-batch.ts
|
|
11268
|
+
async function dispatchActivityBatch(action, options, deps) {
|
|
11269
|
+
const validationError = validateBatch(action, deps);
|
|
11270
|
+
if (validationError) return { ok: false, reason: validationError };
|
|
11271
|
+
const plan = buildBatchPlan(action, deps);
|
|
11272
|
+
if (!plan.ok) return plan;
|
|
11273
|
+
const existingActivityIds = action.mode === "replace" ? new Set(deps.adapter.getAllIds().map((id) => String(id))) : /* @__PURE__ */ new Set();
|
|
11274
|
+
const beforeSnap = action.mode === "replace" ? await snapshotActivities(deps.adapter, existingActivityIds) : /* @__PURE__ */ new Map();
|
|
11275
|
+
const beforeLinks = action.mode === "replace" ? snapshotAllLinks(deps) : /* @__PURE__ */ new Map();
|
|
11276
|
+
const viewState = action.mode === "replace" ? captureDeletedViewState(deps.adapter, existingActivityIds) : [];
|
|
11277
|
+
deps.scheduler.invalidateAllCaches();
|
|
11278
|
+
const appliedLinks = [];
|
|
11279
|
+
if (action.mode === "replace") {
|
|
11280
|
+
for (const linkId of beforeLinks.keys()) {
|
|
11281
|
+
const deleted = applyLinkOperation(
|
|
11282
|
+
{ kind: "delete", linkId },
|
|
11283
|
+
{ port: deps.adapter }
|
|
11284
|
+
);
|
|
11285
|
+
if (!deleted.applied) {
|
|
11286
|
+
return {
|
|
11287
|
+
ok: false,
|
|
11288
|
+
reason: deleted.rejected ?? REJECTION_REASON.INVALID
|
|
11289
|
+
};
|
|
11290
|
+
}
|
|
11291
|
+
appliedLinks.push({
|
|
11292
|
+
op: { kind: "delete", linkId },
|
|
11293
|
+
finalLinkId: linkId,
|
|
11294
|
+
rejected: null
|
|
11295
|
+
});
|
|
11296
|
+
}
|
|
11297
|
+
for (const activityId of existingActivityIds) {
|
|
11298
|
+
const customId = deps.adapter.getActivity(activityId)?.customId;
|
|
11299
|
+
if (typeof customId === "string" && customId.length > 0) {
|
|
11300
|
+
deps.customIdTracker.releaseCustomId(customId);
|
|
11301
|
+
}
|
|
11302
|
+
deps.adapter.removeActivity(activityId);
|
|
11303
|
+
}
|
|
11304
|
+
}
|
|
11305
|
+
const created = await createPlannedActivities(action, plan.plan.items, deps);
|
|
11306
|
+
if (!created.ok) return created;
|
|
11307
|
+
const correlativeShifts = isSimpleRootAppend(action) ? [] : recomputeCorrelativeIds(deps.adapter);
|
|
11308
|
+
const createdIdSet = new Set(
|
|
11309
|
+
plan.plan.items.map((item) => String(item.activityId))
|
|
11310
|
+
);
|
|
11311
|
+
const existingCorrelativeShifts = correlativeShifts.filter(
|
|
11312
|
+
(shift) => !createdIdSet.has(shift.activityId)
|
|
11313
|
+
);
|
|
11314
|
+
const touchedIds = action.mode === "replace" ? new Set(existingActivityIds) : /* @__PURE__ */ new Set();
|
|
11315
|
+
foldCorrelativeShifts(
|
|
11316
|
+
deps.adapter,
|
|
11317
|
+
correlativeShifts,
|
|
11318
|
+
createdIdSet,
|
|
11319
|
+
beforeSnap,
|
|
11320
|
+
touchedIds
|
|
11321
|
+
);
|
|
11322
|
+
for (const item of plan.plan.items) {
|
|
11323
|
+
touchedIds.add(String(item.activityId));
|
|
11324
|
+
}
|
|
11325
|
+
const linkCreates = plan.plan.links.map((link) => ({
|
|
11326
|
+
operation: {
|
|
11327
|
+
kind: "create",
|
|
11328
|
+
source: link.source,
|
|
11329
|
+
target: link.target,
|
|
11330
|
+
type: link.type,
|
|
11331
|
+
lag: options.inputUnit === "hours" ? link.lag : lagDaysToHours(link.lag, deps.sector.hoursPerDay)
|
|
11332
|
+
},
|
|
11333
|
+
linkId: link.linkId
|
|
11334
|
+
}));
|
|
11335
|
+
const createdLinks = applyLinkCreates(linkCreates, {
|
|
11336
|
+
port: deps.adapter,
|
|
11337
|
+
preserved: beforeLinks
|
|
11338
|
+
});
|
|
11339
|
+
if (!createdLinks.applied) {
|
|
11340
|
+
return {
|
|
11341
|
+
ok: false,
|
|
11342
|
+
reason: createdLinks.rejected ?? REJECTION_REASON.INVALID
|
|
11343
|
+
};
|
|
11344
|
+
}
|
|
11345
|
+
for (const { operation, linkId } of linkCreates) {
|
|
11346
|
+
appliedLinks.push({
|
|
11347
|
+
op: operation,
|
|
11348
|
+
finalLinkId: linkId,
|
|
11349
|
+
rejected: null
|
|
11350
|
+
});
|
|
11351
|
+
}
|
|
11352
|
+
const createdIds = plan.plan.items.map((item) => item.activityId);
|
|
11353
|
+
const { scheduledIds } = await runPostMutation(
|
|
11354
|
+
{
|
|
11355
|
+
adapter: deps.adapter,
|
|
11356
|
+
scheduler: deps.scheduler,
|
|
11357
|
+
sector: deps.sector,
|
|
11358
|
+
defaultBaseCalendarId: deps.defaultBaseCalendarId
|
|
11359
|
+
},
|
|
11360
|
+
{
|
|
11361
|
+
action,
|
|
11362
|
+
autoscheduleFrom: action.items.length > 0 || action.links.length > 0 ? "roots" : null,
|
|
11363
|
+
recomputeParentsFrom: createdIds,
|
|
11364
|
+
now: deps.now,
|
|
11365
|
+
options
|
|
11366
|
+
}
|
|
11367
|
+
);
|
|
11368
|
+
const changes = await assembleChangeSet(deps.adapter, {
|
|
11369
|
+
source: action,
|
|
11370
|
+
beforeSnap,
|
|
11371
|
+
touchedIds,
|
|
11372
|
+
scheduledIds,
|
|
11373
|
+
links: buildLinkChangesForBatch(appliedLinks, beforeLinks, deps.adapter),
|
|
11374
|
+
hoursPerDay: deps.sector.hoursPerDay,
|
|
11375
|
+
correlativeShifts: existingCorrelativeShifts,
|
|
11376
|
+
...action.mode === "replace" ? { sirDetection: "after-diff" } : {}
|
|
11377
|
+
});
|
|
11378
|
+
return {
|
|
11379
|
+
ok: true,
|
|
11380
|
+
changes: viewState.length > 0 ? { ...changes, viewState } : changes,
|
|
11381
|
+
...action.mode === "replace" ? {
|
|
11382
|
+
__beforeSnap: beforeSnap,
|
|
11383
|
+
__beforeLinks: stringKeyedLinks(beforeLinks)
|
|
11384
|
+
} : {}
|
|
11385
|
+
};
|
|
11386
|
+
}
|
|
11387
|
+
function validateBatch(action, deps) {
|
|
11388
|
+
if (action.mode !== "append" && action.mode !== "replace") {
|
|
11389
|
+
return REJECTION_REASON.INVALID;
|
|
11390
|
+
}
|
|
11391
|
+
const batchIds = /* @__PURE__ */ new Set();
|
|
11392
|
+
const explicitActivityIds = /* @__PURE__ */ new Set();
|
|
11393
|
+
for (const item of action.items) {
|
|
11394
|
+
if (item.batchId.length === 0 || batchIds.has(item.batchId)) {
|
|
11395
|
+
return REJECTION_REASON.INVALID;
|
|
11396
|
+
}
|
|
11397
|
+
batchIds.add(item.batchId);
|
|
11398
|
+
if (item.index !== void 0 && (!Number.isInteger(item.index) || item.index < 0)) {
|
|
11399
|
+
return REJECTION_REASON.INVALID;
|
|
11400
|
+
}
|
|
11401
|
+
if (item.activityId !== void 0) {
|
|
11402
|
+
const activityId = String(item.activityId);
|
|
11403
|
+
if (activityId.length === 0 || activityId === "0" || explicitActivityIds.has(activityId) || action.mode === "append" && deps.adapter.getActivity(activityId) !== null) {
|
|
11404
|
+
return REJECTION_REASON.INVALID;
|
|
11405
|
+
}
|
|
11406
|
+
explicitActivityIds.add(activityId);
|
|
11407
|
+
}
|
|
11408
|
+
}
|
|
11409
|
+
for (const item of action.items) {
|
|
11410
|
+
if (!validReference(item.parent, true, action.mode, batchIds, deps)) {
|
|
11411
|
+
return REJECTION_REASON.PARENT_NOT_FOUND;
|
|
11412
|
+
}
|
|
11413
|
+
}
|
|
11414
|
+
if (hasParentCycle(action.items)) return REJECTION_REASON.INVALID;
|
|
11415
|
+
const explicitLinkIds = /* @__PURE__ */ new Set();
|
|
11416
|
+
for (const link of action.links) {
|
|
11417
|
+
if (!Object.values(LINK_TYPE).includes(link.type) || !Number.isFinite(link.lag)) {
|
|
11418
|
+
return !Number.isFinite(link.lag) ? REJECTION_REASON.INVALID_LINK_LAG : REJECTION_REASON.INVALID_LINK_TYPE;
|
|
11419
|
+
}
|
|
11420
|
+
if (!validReference(link.source, false, action.mode, batchIds, deps) || !validReference(link.target, false, action.mode, batchIds, deps)) {
|
|
11421
|
+
return REJECTION_REASON.ACTIVITY_NOT_FOUND;
|
|
11422
|
+
}
|
|
11423
|
+
if (link.linkId !== void 0) {
|
|
11424
|
+
const linkId = String(link.linkId);
|
|
11425
|
+
if (linkId.length === 0 || explicitLinkIds.has(linkId) || action.mode === "append" && deps.adapter.getLink(linkId) !== null) {
|
|
11426
|
+
return REJECTION_REASON.INVALID;
|
|
11427
|
+
}
|
|
11428
|
+
explicitLinkIds.add(linkId);
|
|
11429
|
+
}
|
|
11430
|
+
}
|
|
11431
|
+
return null;
|
|
11432
|
+
}
|
|
11433
|
+
function validReference(reference, allowRoot, mode, batchIds, deps) {
|
|
11434
|
+
if (typeof reference === "string") {
|
|
11435
|
+
if (reference === "0") return allowRoot;
|
|
11436
|
+
return mode === "append" && deps.adapter.getActivity(String(reference)) !== null;
|
|
11437
|
+
}
|
|
11438
|
+
return reference !== null && reference.batchId.length > 0 && batchIds.has(reference.batchId);
|
|
11439
|
+
}
|
|
11440
|
+
function hasParentCycle(items) {
|
|
11441
|
+
const parentByBatchId = /* @__PURE__ */ new Map();
|
|
11442
|
+
for (const item of items) {
|
|
11443
|
+
if (typeof item.parent !== "string") {
|
|
11444
|
+
parentByBatchId.set(item.batchId, item.parent.batchId);
|
|
11445
|
+
}
|
|
11446
|
+
}
|
|
11447
|
+
const verified = /* @__PURE__ */ new Set();
|
|
11448
|
+
for (const item of items) {
|
|
11449
|
+
const path = /* @__PURE__ */ new Set();
|
|
11450
|
+
let current = item.batchId;
|
|
11451
|
+
while (current !== void 0 && !verified.has(current)) {
|
|
11452
|
+
if (path.has(current)) return true;
|
|
11453
|
+
path.add(current);
|
|
11454
|
+
current = parentByBatchId.get(current);
|
|
11455
|
+
}
|
|
11456
|
+
for (const batchId of path) verified.add(batchId);
|
|
11457
|
+
}
|
|
11458
|
+
return false;
|
|
11459
|
+
}
|
|
11460
|
+
function buildBatchPlan(action, deps) {
|
|
11461
|
+
for (const item of action.items) {
|
|
11462
|
+
if (item.activityId !== void 0) {
|
|
11463
|
+
deps.activityIdGen.bump(item.activityId);
|
|
11464
|
+
}
|
|
11465
|
+
}
|
|
11466
|
+
const activityIdsByBatchId = /* @__PURE__ */ new Map();
|
|
11467
|
+
for (const item of action.items) {
|
|
11468
|
+
activityIdsByBatchId.set(
|
|
11469
|
+
item.batchId,
|
|
11470
|
+
item.activityId ?? deps.activityIdGen.next()
|
|
11471
|
+
);
|
|
11472
|
+
}
|
|
11473
|
+
for (const link of action.links) {
|
|
11474
|
+
if (link.linkId !== void 0) deps.linkIdGen.bump(link.linkId);
|
|
11475
|
+
}
|
|
11476
|
+
const linkIds = action.links.map(
|
|
11477
|
+
(link) => link.linkId ?? deps.linkIdGen.next()
|
|
11478
|
+
);
|
|
11479
|
+
const insertionCorrelativeIds = planInsertionCorrelatives(
|
|
11480
|
+
action,
|
|
11481
|
+
activityIdsByBatchId,
|
|
11482
|
+
deps
|
|
11483
|
+
);
|
|
11484
|
+
const items = [];
|
|
11485
|
+
for (const item of action.items) {
|
|
11486
|
+
const activityId = activityIdsByBatchId.get(item.batchId);
|
|
11487
|
+
const insertionCorrelativeId = insertionCorrelativeIds.get(item.batchId);
|
|
11488
|
+
if (activityId === void 0 || insertionCorrelativeId === void 0) {
|
|
11489
|
+
return { ok: false, reason: REJECTION_REASON.INVALID };
|
|
11490
|
+
}
|
|
11491
|
+
const parentBatchId = typeof item.parent === "string" ? null : item.parent.batchId;
|
|
11492
|
+
const parentId = typeof item.parent === "string" ? item.parent : activityIdsByBatchId.get(item.parent.batchId);
|
|
11493
|
+
if (parentId === void 0) {
|
|
11494
|
+
return { ok: false, reason: REJECTION_REASON.PARENT_NOT_FOUND };
|
|
11495
|
+
}
|
|
11496
|
+
items.push({
|
|
11497
|
+
input: item,
|
|
11498
|
+
activityId,
|
|
11499
|
+
parentId,
|
|
11500
|
+
parentBatchId,
|
|
11501
|
+
insertionCorrelativeId
|
|
11502
|
+
});
|
|
11503
|
+
}
|
|
11504
|
+
const links = [];
|
|
11505
|
+
for (const [index, link] of action.links.entries()) {
|
|
11506
|
+
const source = resolveActivityReference(link.source, activityIdsByBatchId);
|
|
11507
|
+
const target = resolveActivityReference(link.target, activityIdsByBatchId);
|
|
11508
|
+
const linkId = linkIds[index];
|
|
11509
|
+
if (source === null || target === null || linkId === void 0) {
|
|
11510
|
+
return { ok: false, reason: REJECTION_REASON.ACTIVITY_NOT_FOUND };
|
|
11511
|
+
}
|
|
11512
|
+
links.push({
|
|
11513
|
+
source,
|
|
11514
|
+
target,
|
|
11515
|
+
type: link.type,
|
|
11516
|
+
lag: link.lag,
|
|
11517
|
+
linkId
|
|
11518
|
+
});
|
|
11519
|
+
}
|
|
11520
|
+
return { ok: true, plan: { items, links } };
|
|
11521
|
+
}
|
|
11522
|
+
function planInsertionCorrelatives(action, activityIdsByBatchId, deps) {
|
|
11523
|
+
if (isSimpleRootAppend(action)) {
|
|
11524
|
+
const firstCorrelativeId = deps.adapter.activityCount();
|
|
11525
|
+
return new Map(
|
|
11526
|
+
action.items.map((item, index) => [
|
|
11527
|
+
item.batchId,
|
|
11528
|
+
firstCorrelativeId + index
|
|
11529
|
+
])
|
|
11530
|
+
);
|
|
11531
|
+
}
|
|
11532
|
+
const newIds = new Set(
|
|
11533
|
+
Array.from(activityIdsByBatchId.values(), (id) => String(id))
|
|
11534
|
+
);
|
|
11535
|
+
const plansByParent = /* @__PURE__ */ new Map();
|
|
11536
|
+
for (const item of action.items) {
|
|
11537
|
+
const activityId = activityIdsByBatchId.get(item.batchId);
|
|
11538
|
+
if (activityId === void 0) continue;
|
|
11539
|
+
const parentId = typeof item.parent === "string" ? item.parent : activityIdsByBatchId.get(item.parent.batchId);
|
|
11540
|
+
if (parentId === void 0) continue;
|
|
11541
|
+
const parentKey = String(parentId);
|
|
11542
|
+
let plan = plansByParent.get(parentKey);
|
|
11543
|
+
if (!plan) {
|
|
11544
|
+
const existing = action.mode === "replace" || typeof item.parent !== "string" && activityIdsByBatchId.has(item.parent.batchId) ? [] : collectSiblings(deps.adapter, parentKey).sort((left, right) => {
|
|
11545
|
+
const diff = readCorrelativeId(left) - readCorrelativeId(right);
|
|
11546
|
+
return diff || String(left.id).localeCompare(String(right.id));
|
|
11547
|
+
}).map((activity) => String(activity.id));
|
|
11548
|
+
plan = { existing, newIdsByTarget: /* @__PURE__ */ new Map() };
|
|
11549
|
+
plansByParent.set(parentKey, plan);
|
|
11550
|
+
}
|
|
11551
|
+
const target = item.index === void 0 ? plan.existing.length : Math.min(item.index, plan.existing.length);
|
|
11552
|
+
const idsAtTarget = plan.newIdsByTarget.get(target) ?? [];
|
|
11553
|
+
idsAtTarget.push(String(activityId));
|
|
11554
|
+
plan.newIdsByTarget.set(target, idsAtTarget);
|
|
11555
|
+
}
|
|
11556
|
+
const result = /* @__PURE__ */ new Map();
|
|
11557
|
+
const batchIdByActivityId = new Map(
|
|
11558
|
+
Array.from(activityIdsByBatchId, ([batchId, activityId]) => [
|
|
11559
|
+
String(activityId),
|
|
11560
|
+
batchId
|
|
11561
|
+
])
|
|
11562
|
+
);
|
|
11563
|
+
for (const plan of plansByParent.values()) {
|
|
11564
|
+
const order = [];
|
|
11565
|
+
for (let target = 0; target <= plan.existing.length; target += 1) {
|
|
11566
|
+
for (const newId of plan.newIdsByTarget.get(target) ?? []) {
|
|
11567
|
+
order.push(newId);
|
|
11568
|
+
}
|
|
11569
|
+
const existingId = plan.existing[target];
|
|
11570
|
+
if (existingId !== void 0) order.push(existingId);
|
|
11571
|
+
}
|
|
11572
|
+
let index = 0;
|
|
11573
|
+
while (index < order.length) {
|
|
11574
|
+
if (!newIds.has(order[index])) {
|
|
11575
|
+
index += 1;
|
|
11576
|
+
continue;
|
|
11577
|
+
}
|
|
11578
|
+
const start = index;
|
|
11579
|
+
while (index < order.length && newIds.has(order[index])) index += 1;
|
|
11580
|
+
const count = index - start;
|
|
11581
|
+
const left = start > 0 ? activityCorrelative(order[start - 1], deps) : null;
|
|
11582
|
+
const right = index < order.length ? activityCorrelative(order[index], deps) : null;
|
|
11583
|
+
for (let offset = 0; offset < count; offset += 1) {
|
|
11584
|
+
const id = order[start + offset];
|
|
11585
|
+
const batchId = batchIdByActivityId.get(id);
|
|
11586
|
+
if (batchId === void 0) continue;
|
|
11587
|
+
result.set(
|
|
11588
|
+
batchId,
|
|
11589
|
+
interpolateCorrelative(left, right, offset + 1, count)
|
|
11590
|
+
);
|
|
11591
|
+
}
|
|
11592
|
+
}
|
|
11593
|
+
}
|
|
11594
|
+
return result;
|
|
11595
|
+
}
|
|
11596
|
+
function isSimpleRootAppend(action) {
|
|
11597
|
+
return action.mode === "append" && action.items.every(
|
|
11598
|
+
(item) => item.parent === "0" && item.index === void 0
|
|
11599
|
+
);
|
|
11600
|
+
}
|
|
11601
|
+
function activityCorrelative(activityId, deps) {
|
|
11602
|
+
const activity = deps.adapter.getActivity(activityId);
|
|
11603
|
+
return activity ? readCorrelativeId(activity) : 0;
|
|
11604
|
+
}
|
|
11605
|
+
function interpolateCorrelative(left, right, position, count) {
|
|
11606
|
+
if (left !== null && right !== null && right > left) {
|
|
11607
|
+
return left + (right - left) * position / (count + 1);
|
|
11608
|
+
}
|
|
11609
|
+
if (left !== null) return left + position;
|
|
11610
|
+
if (right !== null) return right - (count - position + 1);
|
|
11611
|
+
return position - 1;
|
|
11612
|
+
}
|
|
11613
|
+
async function createPlannedActivities(action, plannedItems, deps) {
|
|
11614
|
+
const childrenByParent = /* @__PURE__ */ new Map();
|
|
11615
|
+
const ready = [];
|
|
11616
|
+
for (const item of plannedItems) {
|
|
11617
|
+
if (item.parentBatchId === null) {
|
|
11618
|
+
ready.push(item);
|
|
11619
|
+
continue;
|
|
11620
|
+
}
|
|
11621
|
+
const children = childrenByParent.get(item.parentBatchId) ?? [];
|
|
11622
|
+
children.push(item);
|
|
11623
|
+
childrenByParent.set(item.parentBatchId, children);
|
|
11624
|
+
}
|
|
11625
|
+
for (let index = 0; index < ready.length; index += 1) {
|
|
11626
|
+
const item = ready[index];
|
|
11627
|
+
const core = await createActivityCore(
|
|
11628
|
+
{
|
|
11629
|
+
parentId: item.parentId,
|
|
11630
|
+
activityId: item.activityId,
|
|
11631
|
+
...item.input.overrides === void 0 ? {} : { overrides: item.input.overrides },
|
|
11632
|
+
...action.eventSource === void 0 ? {} : { eventSource: action.eventSource }
|
|
11633
|
+
},
|
|
11634
|
+
deps,
|
|
11635
|
+
{
|
|
11636
|
+
skipCorrelativeRecompute: true,
|
|
11637
|
+
skipBeforeSnap: true,
|
|
11638
|
+
skipCacheInvalidation: true,
|
|
11639
|
+
skipRollupCascades: true,
|
|
11640
|
+
insertionCorrelativeId: item.insertionCorrelativeId
|
|
11641
|
+
}
|
|
11642
|
+
);
|
|
11643
|
+
if (!core.ok) return core;
|
|
11644
|
+
for (const child of childrenByParent.get(item.input.batchId) ?? []) {
|
|
11645
|
+
ready.push(child);
|
|
11646
|
+
}
|
|
11647
|
+
}
|
|
11648
|
+
if (ready.length !== plannedItems.length) {
|
|
11649
|
+
return { ok: false, reason: REJECTION_REASON.INVALID };
|
|
11650
|
+
}
|
|
11651
|
+
const recomputedParents = /* @__PURE__ */ new Set();
|
|
11652
|
+
for (let index = ready.length - 1; index >= 0; index -= 1) {
|
|
11653
|
+
const parentId = ready[index].parentId;
|
|
11654
|
+
if (parentId === "0" || recomputedParents.has(String(parentId))) continue;
|
|
11655
|
+
recomputedParents.add(String(parentId));
|
|
11656
|
+
recomputeRollupCascadesForParent(parentId, deps.adapter);
|
|
11657
|
+
}
|
|
11658
|
+
return { ok: true };
|
|
11659
|
+
}
|
|
11660
|
+
function resolveActivityReference(reference, activityIdsByBatchId) {
|
|
11661
|
+
if (typeof reference === "string") return reference;
|
|
11662
|
+
return activityIdsByBatchId.get(reference.batchId) ?? null;
|
|
11663
|
+
}
|
|
11664
|
+
function snapshotAllLinks(deps) {
|
|
11665
|
+
return new Map(
|
|
11666
|
+
deps.adapter.getAllLinks().map((link) => [String(link.id), { ...link }])
|
|
11667
|
+
);
|
|
11668
|
+
}
|
|
11669
|
+
function stringKeyedLinks(links) {
|
|
11670
|
+
return new Map(Array.from(links, ([linkId, link]) => [String(linkId), link]));
|
|
11671
|
+
}
|
|
11672
|
+
|
|
11673
|
+
// src/dispatch/handlers/activity-paste.ts
|
|
11674
|
+
function resolvePasteRootDestination(destination, adapter) {
|
|
11675
|
+
if ("afterSiblingId" in destination) {
|
|
11676
|
+
const sibling = adapter.getActivity(destination.afterSiblingId);
|
|
11677
|
+
if (!sibling) {
|
|
11678
|
+
return { ok: false, reason: REJECTION_REASON.ANCHOR_SIBLING_NOT_FOUND };
|
|
11679
|
+
}
|
|
11680
|
+
const parentId2 = sibling.parentId == null ? ROOT_PARENT_ID : String(sibling.parentId);
|
|
11681
|
+
return { ok: true, parentId: parentId2, firstAfter: destination.afterSiblingId };
|
|
11682
|
+
}
|
|
11683
|
+
const parentId = destination.parentId;
|
|
11684
|
+
const siblings = collectSiblings(adapter, parentId).sort(
|
|
11685
|
+
(a, b) => readCorrelativeId(a) - readCorrelativeId(b)
|
|
11686
|
+
);
|
|
11687
|
+
const before = siblings[destination.index]?.id;
|
|
11688
|
+
return { ok: true, parentId, firstBefore: before };
|
|
11689
|
+
}
|
|
11690
|
+
function applyPasteDomainSemantics(overrides) {
|
|
11691
|
+
const { customId: _customId, ...content } = overrides;
|
|
11692
|
+
return {
|
|
11693
|
+
...content,
|
|
11694
|
+
progress: 0,
|
|
11695
|
+
ponderator: 0,
|
|
11696
|
+
usedCost: 0,
|
|
11697
|
+
workHours: 0,
|
|
11698
|
+
isLookahead: false
|
|
11699
|
+
};
|
|
11700
|
+
}
|
|
11701
|
+
async function dispatchActivityPaste(action, options, deps) {
|
|
11702
|
+
const { adapter, scheduler, sector } = deps;
|
|
11703
|
+
const rootDest = resolvePasteRootDestination(action.destination, adapter);
|
|
11704
|
+
if (!rootDest.ok) return rootDest;
|
|
11705
|
+
const beforeSnap = await snapshotActivities(adapter, /* @__PURE__ */ new Set());
|
|
11706
|
+
const touched = /* @__PURE__ */ new Set();
|
|
11707
|
+
const originalToNew = /* @__PURE__ */ new Map();
|
|
11708
|
+
const createdIds = [];
|
|
11709
|
+
let prevRootId;
|
|
11710
|
+
for (const input of action.activities) {
|
|
11711
|
+
const mappedParent = originalToNew.get(input.originalParentId);
|
|
11712
|
+
let parentId;
|
|
11713
|
+
let afterSiblingId;
|
|
11714
|
+
let beforeSiblingId;
|
|
11715
|
+
if (mappedParent !== void 0) {
|
|
11716
|
+
parentId = mappedParent;
|
|
11717
|
+
} else {
|
|
11718
|
+
parentId = rootDest.parentId;
|
|
11719
|
+
if (prevRootId === void 0) {
|
|
11720
|
+
afterSiblingId = rootDest.firstAfter;
|
|
11721
|
+
beforeSiblingId = rootDest.firstBefore;
|
|
11722
|
+
} else {
|
|
11723
|
+
afterSiblingId = prevRootId;
|
|
11724
|
+
}
|
|
11725
|
+
}
|
|
11726
|
+
const core = await createActivityCore(
|
|
11727
|
+
{
|
|
11728
|
+
parentId,
|
|
11729
|
+
afterSiblingId,
|
|
11730
|
+
beforeSiblingId,
|
|
11731
|
+
...input.activityId ? { activityId: input.activityId } : {},
|
|
11732
|
+
overrides: applyPasteDomainSemantics(input.overrides),
|
|
11733
|
+
eventSource: action.eventSource
|
|
11734
|
+
},
|
|
11735
|
+
deps,
|
|
11736
|
+
{
|
|
11737
|
+
skipCorrelativeRecompute: true,
|
|
11738
|
+
skipBeforeSnap: true,
|
|
11739
|
+
customIdReferenceId: action.referenceActivityId
|
|
11740
|
+
}
|
|
11741
|
+
);
|
|
11742
|
+
if (!core.ok) return core;
|
|
11743
|
+
if (input.baselineSnapshot !== void 0) {
|
|
11744
|
+
const snapshot = input.baselineSnapshot;
|
|
11745
|
+
adapter.setActivityField(
|
|
11746
|
+
core.newId,
|
|
11747
|
+
"baselineSnapshot",
|
|
11748
|
+
snapshot ? {
|
|
11749
|
+
...snapshot,
|
|
11750
|
+
startDate: snapshot.startDate ? new Date(snapshot.startDate) : null,
|
|
11751
|
+
endDate: snapshot.endDate ? new Date(snapshot.endDate) : null
|
|
11752
|
+
} : null
|
|
11753
|
+
);
|
|
11754
|
+
}
|
|
11755
|
+
originalToNew.set(input.originalId, core.newId);
|
|
11756
|
+
createdIds.push(core.newId);
|
|
11757
|
+
if (mappedParent === void 0) prevRootId = core.newId;
|
|
11758
|
+
}
|
|
11759
|
+
const correlativeShifts = recomputeCorrelativeIds(adapter);
|
|
11760
|
+
const createdIdSet = new Set(createdIds.map(String));
|
|
11761
|
+
foldCorrelativeShifts(
|
|
11762
|
+
adapter,
|
|
11763
|
+
correlativeShifts,
|
|
11764
|
+
createdIdSet,
|
|
11765
|
+
beforeSnap,
|
|
11766
|
+
touched
|
|
11767
|
+
);
|
|
11768
|
+
for (const createdId of createdIds) touched.add(String(createdId));
|
|
11769
|
+
const appliedLinks = [];
|
|
11770
|
+
for (const link of action.links) {
|
|
11771
|
+
const newSource = originalToNew.get(link.source);
|
|
11772
|
+
const newTarget = originalToNew.get(link.target);
|
|
11773
|
+
if (newSource === void 0 || newTarget === void 0) continue;
|
|
11774
|
+
const op = {
|
|
11775
|
+
kind: "create",
|
|
11776
|
+
source: newSource,
|
|
11777
|
+
target: newTarget,
|
|
11778
|
+
type: link.type,
|
|
11779
|
+
lag: lagDaysToHours(link.lag, sector.hoursPerDay)
|
|
11780
|
+
};
|
|
11781
|
+
const res = applyLinkOperation(op, {
|
|
11782
|
+
port: adapter,
|
|
11783
|
+
newLinkId: link.linkId ? () => link.linkId : deps.linkIdGen.next
|
|
11784
|
+
});
|
|
11785
|
+
appliedLinks.push({
|
|
11786
|
+
op,
|
|
11787
|
+
finalLinkId: res.applied ? res.linkId : null,
|
|
11788
|
+
rejected: res.applied ? null : res.rejected ?? "link_op_rejected"
|
|
11789
|
+
});
|
|
11790
|
+
}
|
|
11791
|
+
const { scheduledIds } = await runPostMutation(
|
|
11792
|
+
{
|
|
11793
|
+
adapter,
|
|
11794
|
+
scheduler,
|
|
11795
|
+
sector: deps.sector,
|
|
11796
|
+
defaultBaseCalendarId: deps.defaultBaseCalendarId
|
|
11797
|
+
},
|
|
11798
|
+
{
|
|
11799
|
+
action,
|
|
11800
|
+
autoscheduleFrom: createdIds[0] ?? null,
|
|
11801
|
+
recomputeParentsFrom: createdIds,
|
|
11802
|
+
now: deps.now,
|
|
11803
|
+
options
|
|
11804
|
+
}
|
|
11805
|
+
);
|
|
11806
|
+
const linkChanges = buildLinkChangesForBatch(
|
|
11807
|
+
appliedLinks,
|
|
11808
|
+
/* @__PURE__ */ new Map(),
|
|
11809
|
+
adapter
|
|
11810
|
+
);
|
|
11811
|
+
return {
|
|
11812
|
+
ok: true,
|
|
11813
|
+
changes: await assembleChangeSet(adapter, {
|
|
11814
|
+
source: action,
|
|
11815
|
+
beforeSnap,
|
|
11816
|
+
touchedIds: touched,
|
|
11817
|
+
scheduledIds,
|
|
11818
|
+
links: linkChanges,
|
|
11819
|
+
hoursPerDay: sector.hoursPerDay
|
|
11820
|
+
})
|
|
11821
|
+
};
|
|
11822
|
+
}
|
|
11823
|
+
|
|
11824
|
+
// src/dispatch/handlers/sir-sync.ts
|
|
11825
|
+
function dispatchSirSync(action, deps) {
|
|
11826
|
+
const { adapter } = deps;
|
|
11827
|
+
const id = action.activityId;
|
|
11828
|
+
const snapshot = adapter.getActivity(id);
|
|
11829
|
+
if (!snapshot) {
|
|
11830
|
+
return { ok: false, reason: REJECTION_REASON.ACTIVITY_NOT_FOUND };
|
|
11831
|
+
}
|
|
11832
|
+
const before = snapshot.pendingRequestIds ?? [];
|
|
11833
|
+
const after = action.pendingRequests.map((r) => r.id);
|
|
11834
|
+
adapter.setActivityField(action.activityId, "pendingRequestIds", after);
|
|
11835
|
+
const change = {
|
|
11836
|
+
id,
|
|
11837
|
+
kind: "updated",
|
|
11838
|
+
fields: { pendingRequestIds: { before, after } },
|
|
11839
|
+
after: { id, pendingRequestIds: after }
|
|
11840
|
+
};
|
|
11841
|
+
return {
|
|
11842
|
+
ok: true,
|
|
11843
|
+
changes: {
|
|
11844
|
+
source: action,
|
|
11845
|
+
activities: [change],
|
|
11846
|
+
links: [],
|
|
11847
|
+
calendars: [],
|
|
11848
|
+
trackingEvents: []
|
|
11849
|
+
}
|
|
11850
|
+
};
|
|
11851
|
+
}
|
|
11852
|
+
|
|
11853
|
+
// src/dispatch/handlers/activity-lookahead-sync.ts
|
|
11854
|
+
function dispatchActivityLookaheadSync(action, deps) {
|
|
11855
|
+
const ids = [...new Set(action.activityIds.map(String))];
|
|
11856
|
+
const snapshots = ids.map((id) => deps.adapter.getActivity(id));
|
|
11857
|
+
if (snapshots.some((snapshot) => !snapshot)) {
|
|
11858
|
+
return { ok: false, reason: REJECTION_REASON.ACTIVITY_NOT_FOUND };
|
|
11859
|
+
}
|
|
11860
|
+
const activities = [];
|
|
11861
|
+
for (const [index, id] of ids.entries()) {
|
|
11862
|
+
const snapshot = snapshots[index];
|
|
11863
|
+
if (!snapshot) continue;
|
|
11864
|
+
const fields = {};
|
|
11865
|
+
if (snapshot.isLookahead !== action.isLookahead) {
|
|
11866
|
+
fields.isLookahead = {
|
|
11867
|
+
before: snapshot.isLookahead,
|
|
11868
|
+
after: action.isLookahead
|
|
11869
|
+
};
|
|
11870
|
+
deps.adapter.setActivityField(id, "isLookahead", action.isLookahead);
|
|
11871
|
+
}
|
|
11872
|
+
if (snapshot.hasLookaheadTasks !== action.hasLookaheadTasks) {
|
|
11873
|
+
fields.hasLookaheadTasks = {
|
|
11874
|
+
before: snapshot.hasLookaheadTasks,
|
|
11875
|
+
after: action.hasLookaheadTasks
|
|
11876
|
+
};
|
|
11877
|
+
deps.adapter.setActivityField(
|
|
11878
|
+
id,
|
|
11879
|
+
"hasLookaheadTasks",
|
|
11880
|
+
action.hasLookaheadTasks
|
|
11881
|
+
);
|
|
11882
|
+
}
|
|
11883
|
+
if (Object.keys(fields).length === 0) continue;
|
|
11884
|
+
activities.push({
|
|
11885
|
+
id,
|
|
11886
|
+
kind: "updated",
|
|
11887
|
+
fields,
|
|
11888
|
+
after: {
|
|
11889
|
+
...snapshot,
|
|
11890
|
+
isLookahead: action.isLookahead,
|
|
11891
|
+
hasLookaheadTasks: action.hasLookaheadTasks
|
|
11892
|
+
}
|
|
11893
|
+
});
|
|
11894
|
+
}
|
|
11895
|
+
return {
|
|
11896
|
+
ok: true,
|
|
11897
|
+
changes: {
|
|
11898
|
+
source: action,
|
|
11899
|
+
activities,
|
|
11900
|
+
links: [],
|
|
11901
|
+
calendars: [],
|
|
11902
|
+
trackingEvents: []
|
|
11903
|
+
}
|
|
11904
|
+
};
|
|
11200
11905
|
}
|
|
11201
11906
|
|
|
11202
11907
|
// src/internal/hierarchy/invalid-links.ts
|
|
@@ -12794,7 +13499,10 @@ async function dispatchStatusCriteriaSet(action, deps) {
|
|
|
12794
13499
|
}
|
|
12795
13500
|
|
|
12796
13501
|
// src/dispatch/dispatch.ts
|
|
12797
|
-
async function
|
|
13502
|
+
async function performDispatch(action, options, deps) {
|
|
13503
|
+
if (action.kind === "activity-batch") {
|
|
13504
|
+
return dispatchActivityBatch(action, options, deps);
|
|
13505
|
+
}
|
|
12798
13506
|
if (action.kind === "persistence-acknowledge") {
|
|
12799
13507
|
return dispatchPersistenceAcknowledge(action, deps);
|
|
12800
13508
|
}
|
|
@@ -16028,6 +16736,7 @@ var ScheduleState = class {
|
|
|
16028
16736
|
};
|
|
16029
16737
|
_writeCapture = new WriteCapture();
|
|
16030
16738
|
_viewState = new ViewStateStore();
|
|
16739
|
+
_filterProjection = null;
|
|
16031
16740
|
_viewStateBefore = null;
|
|
16032
16741
|
_lastStartDateBefore = null;
|
|
16033
16742
|
_promotionSnapshotBefore = null;
|
|
@@ -16142,6 +16851,13 @@ var ScheduleState = class {
|
|
|
16142
16851
|
setActiveFilter(nextFilter) {
|
|
16143
16852
|
this._captureViewStateOnce();
|
|
16144
16853
|
this._viewState.setActiveFilter(nextFilter);
|
|
16854
|
+
this._filterProjection = null;
|
|
16855
|
+
}
|
|
16856
|
+
getFilterProjection() {
|
|
16857
|
+
return this._filterProjection;
|
|
16858
|
+
}
|
|
16859
|
+
setFilterProjection(projection) {
|
|
16860
|
+
this._filterProjection = projection;
|
|
16145
16861
|
}
|
|
16146
16862
|
getActiveOrder() {
|
|
16147
16863
|
return this._viewState.getActiveOrder();
|
|
@@ -16183,6 +16899,7 @@ var ScheduleState = class {
|
|
|
16183
16899
|
}
|
|
16184
16900
|
hydrateViewState(seed2) {
|
|
16185
16901
|
this._viewState.hydrate(seed2);
|
|
16902
|
+
this._filterProjection = null;
|
|
16186
16903
|
this.invalidateDerivedOrder();
|
|
16187
16904
|
}
|
|
16188
16905
|
_captureViewStateOnce() {
|
|
@@ -16383,6 +17100,7 @@ var ScheduleState = class {
|
|
|
16383
17100
|
this._restoreActivityFields(before);
|
|
16384
17101
|
if (this._viewStateBefore !== null) {
|
|
16385
17102
|
this._viewState.restore(this._viewStateBefore);
|
|
17103
|
+
this._filterProjection = null;
|
|
16386
17104
|
this.invalidateDerivedOrder();
|
|
16387
17105
|
}
|
|
16388
17106
|
if (this._promotionSnapshotBefore !== null) {
|
|
@@ -16592,8 +17310,10 @@ function buildHistoryChangeSet(changes) {
|
|
|
16592
17310
|
...entry,
|
|
16593
17311
|
after: null
|
|
16594
17312
|
}));
|
|
17313
|
+
const source = changes.source.kind === "activity-batch" ? { ...changes.source, items: [], links: [] } : changes.source;
|
|
16595
17314
|
return cloneDomainValue({
|
|
16596
17315
|
...changes,
|
|
17316
|
+
source,
|
|
16597
17317
|
activities: activitiesWithoutAfter,
|
|
16598
17318
|
links: linksWithoutAfter
|
|
16599
17319
|
});
|
|
@@ -16610,7 +17330,7 @@ function captureCreatedSnapshots(state, changeSet) {
|
|
|
16610
17330
|
if (live) afterSnap.set(String(ch.id), structuredCloneActivity(live));
|
|
16611
17331
|
}
|
|
16612
17332
|
for (const ch of changeSet.links) {
|
|
16613
|
-
if (ch.kind
|
|
17333
|
+
if (ch.kind === "deleted") continue;
|
|
16614
17334
|
const live = state.getLink(String(ch.id));
|
|
16615
17335
|
if (live) afterLinks.set(String(ch.id), { ...live });
|
|
16616
17336
|
}
|
|
@@ -16833,6 +17553,17 @@ function applyChangeSetSide(state, entry, side) {
|
|
|
16833
17553
|
for (const ch of cs.links) {
|
|
16834
17554
|
if (ch.kind === "updated") {
|
|
16835
17555
|
if (!ch.fields) continue;
|
|
17556
|
+
if (Object.hasOwn(ch.fields, "source") || Object.hasOwn(ch.fields, "target")) {
|
|
17557
|
+
const snapshot = side === "before" ? entry.beforeLinks?.get(String(ch.id)) : entry.afterLinks?.get(String(ch.id));
|
|
17558
|
+
if (!snapshot) {
|
|
17559
|
+
throw new Error(
|
|
17560
|
+
`${side === "before" ? "undo" : "redo"}: missing link snapshot for ${ch.id}`
|
|
17561
|
+
);
|
|
17562
|
+
}
|
|
17563
|
+
state.removeLink(String(ch.id));
|
|
17564
|
+
state.addLink({ ...snapshot });
|
|
17565
|
+
continue;
|
|
17566
|
+
}
|
|
16836
17567
|
for (const [field, fv] of Object.entries(ch.fields)) {
|
|
16837
17568
|
setLinkFieldDynamic(
|
|
16838
17569
|
state,
|
|
@@ -17103,9 +17834,26 @@ function idArrayEquals(current, saved) {
|
|
|
17103
17834
|
return currentIds.every((value, index) => value === savedIds[index]);
|
|
17104
17835
|
}
|
|
17105
17836
|
|
|
17837
|
+
// src/boundary/save/link-changes.ts
|
|
17838
|
+
function linkChangedSince(baseline, current) {
|
|
17839
|
+
return baseline.source !== current.source || baseline.target !== current.target || baseline.lag !== current.lag || baseline.type !== current.type;
|
|
17840
|
+
}
|
|
17841
|
+
function checkNoUpdatedLinks(baseline, current) {
|
|
17842
|
+
const baselineById = new Map(
|
|
17843
|
+
baseline.map((link) => [String(link.id), link])
|
|
17844
|
+
);
|
|
17845
|
+
return current.filter((link) => {
|
|
17846
|
+
const base = baselineById.get(String(link.id));
|
|
17847
|
+
if (!base) return false;
|
|
17848
|
+
return linkChangedSince(base, link);
|
|
17849
|
+
});
|
|
17850
|
+
}
|
|
17851
|
+
|
|
17106
17852
|
// src/boundary/save/save-tracker.ts
|
|
17107
17853
|
var SaveTracker = class {
|
|
17108
17854
|
initialized = false;
|
|
17855
|
+
dirtyActivityIds = /* @__PURE__ */ new Set();
|
|
17856
|
+
dirtyLinkIds = /* @__PURE__ */ new Set();
|
|
17109
17857
|
linksAtLastSave = /* @__PURE__ */ new Map();
|
|
17110
17858
|
activitiesAtLastSave = /* @__PURE__ */ new Map();
|
|
17111
17859
|
init(activities, links) {
|
|
@@ -17115,10 +17863,16 @@ var SaveTracker = class {
|
|
|
17115
17863
|
}
|
|
17116
17864
|
snapshotActivities(activities) {
|
|
17117
17865
|
this.activitiesAtLastSave.clear();
|
|
17866
|
+
this.dirtyActivityIds.clear();
|
|
17118
17867
|
for (const activity of activities) {
|
|
17119
|
-
|
|
17868
|
+
const activityId = String(activity.id);
|
|
17869
|
+
if (activity.proplannerId == null) {
|
|
17870
|
+
this.dirtyActivityIds.add(activityId);
|
|
17871
|
+
continue;
|
|
17872
|
+
}
|
|
17873
|
+
const persistable = projectPersistableActivity(activity);
|
|
17120
17874
|
this.activitiesAtLastSave.set(String(activity.id), {
|
|
17121
|
-
persistable
|
|
17875
|
+
persistable,
|
|
17122
17876
|
deletedSnapshot: {
|
|
17123
17877
|
id: activity.id,
|
|
17124
17878
|
proplannerId: activity.proplannerId,
|
|
@@ -17126,12 +17880,20 @@ var SaveTracker = class {
|
|
|
17126
17880
|
hadBaseline: activity.baselineSnapshot !== null || activity.baselinePoints.length > 0
|
|
17127
17881
|
}
|
|
17128
17882
|
});
|
|
17883
|
+
if (activityChangedSince(activity, persistable)) {
|
|
17884
|
+
this.dirtyActivityIds.add(activityId);
|
|
17885
|
+
}
|
|
17129
17886
|
}
|
|
17130
17887
|
}
|
|
17131
17888
|
snapshotLinks(links) {
|
|
17132
17889
|
this.linksAtLastSave.clear();
|
|
17890
|
+
this.dirtyLinkIds.clear();
|
|
17133
17891
|
for (const link of links) {
|
|
17134
|
-
|
|
17892
|
+
const linkId = String(link.id);
|
|
17893
|
+
if (link.proplannerId == null) {
|
|
17894
|
+
this.dirtyLinkIds.add(linkId);
|
|
17895
|
+
continue;
|
|
17896
|
+
}
|
|
17135
17897
|
this.linksAtLastSave.set(String(link.id), {
|
|
17136
17898
|
id: link.id,
|
|
17137
17899
|
proplannerId: link.proplannerId,
|
|
@@ -17142,21 +17904,52 @@ var SaveTracker = class {
|
|
|
17142
17904
|
});
|
|
17143
17905
|
}
|
|
17144
17906
|
}
|
|
17145
|
-
|
|
17146
|
-
if (!this.initialized) return
|
|
17147
|
-
|
|
17148
|
-
|
|
17149
|
-
|
|
17150
|
-
|
|
17151
|
-
|
|
17152
|
-
|
|
17153
|
-
|
|
17154
|
-
|
|
17155
|
-
|
|
17156
|
-
|
|
17157
|
-
|
|
17158
|
-
|
|
17159
|
-
|
|
17907
|
+
recordChanges(activities, links, getActivity, getLink) {
|
|
17908
|
+
if (!this.initialized) return;
|
|
17909
|
+
for (const change of activities) {
|
|
17910
|
+
const activityId = String(change.id);
|
|
17911
|
+
const current = getActivity(activityId);
|
|
17912
|
+
const saved = this.activitiesAtLastSave.get(activityId);
|
|
17913
|
+
if (current === null ? saved !== void 0 : current.proplannerId == null || saved === void 0 || activityChangedSince(current, saved.persistable)) {
|
|
17914
|
+
this.dirtyActivityIds.add(activityId);
|
|
17915
|
+
} else {
|
|
17916
|
+
this.dirtyActivityIds.delete(activityId);
|
|
17917
|
+
}
|
|
17918
|
+
}
|
|
17919
|
+
for (const change of links) {
|
|
17920
|
+
const linkId = String(change.id);
|
|
17921
|
+
const current = getLink(linkId);
|
|
17922
|
+
const saved = this.linksAtLastSave.get(linkId);
|
|
17923
|
+
if (current === null ? saved !== void 0 : current.proplannerId == null || saved === void 0 || linkChangedSince(saved, current)) {
|
|
17924
|
+
this.dirtyLinkIds.add(linkId);
|
|
17925
|
+
} else {
|
|
17926
|
+
this.dirtyLinkIds.delete(linkId);
|
|
17927
|
+
}
|
|
17928
|
+
}
|
|
17929
|
+
}
|
|
17930
|
+
acknowledgePersistedLookahead(activityIds, getActivity) {
|
|
17931
|
+
if (!this.initialized) return;
|
|
17932
|
+
for (const activityId of activityIds) {
|
|
17933
|
+
const current = getActivity(activityId);
|
|
17934
|
+
const saved = this.activitiesAtLastSave.get(activityId);
|
|
17935
|
+
if (current === null || saved === void 0) continue;
|
|
17936
|
+
const persistable = {
|
|
17937
|
+
...saved.persistable,
|
|
17938
|
+
isLookahead: current.isLookahead
|
|
17939
|
+
};
|
|
17940
|
+
this.activitiesAtLastSave.set(activityId, {
|
|
17941
|
+
...saved,
|
|
17942
|
+
persistable
|
|
17943
|
+
});
|
|
17944
|
+
if (current.proplannerId == null || activityChangedSince(current, persistable)) {
|
|
17945
|
+
this.dirtyActivityIds.add(activityId);
|
|
17946
|
+
} else {
|
|
17947
|
+
this.dirtyActivityIds.delete(activityId);
|
|
17948
|
+
}
|
|
17949
|
+
}
|
|
17950
|
+
}
|
|
17951
|
+
hasUnsavedChanges() {
|
|
17952
|
+
return this.initialized && (this.dirtyActivityIds.size > 0 || this.dirtyLinkIds.size > 0);
|
|
17160
17953
|
}
|
|
17161
17954
|
deletedActivities(activities) {
|
|
17162
17955
|
if (!this.initialized) return [];
|
|
@@ -17194,10 +17987,13 @@ var SaveTracker = class {
|
|
|
17194
17987
|
if (link.proplannerId == null) return false;
|
|
17195
17988
|
const saved = this.linksAtLastSave.get(String(link.id));
|
|
17196
17989
|
if (!saved) return true;
|
|
17197
|
-
return
|
|
17990
|
+
return linkChangedSince(saved, link);
|
|
17198
17991
|
});
|
|
17199
17992
|
}
|
|
17200
17993
|
clear() {
|
|
17994
|
+
this.initialized = false;
|
|
17995
|
+
this.dirtyActivityIds.clear();
|
|
17996
|
+
this.dirtyLinkIds.clear();
|
|
17201
17997
|
this.linksAtLastSave.clear();
|
|
17202
17998
|
this.activitiesAtLastSave.clear();
|
|
17203
17999
|
}
|
|
@@ -17362,7 +18158,7 @@ function parseActivity(raw, context) {
|
|
|
17362
18158
|
context.hoursPerDay
|
|
17363
18159
|
),
|
|
17364
18160
|
isCritical: Boolean(raw.is_critical),
|
|
17365
|
-
freeSlackHours:
|
|
18161
|
+
freeSlackHours: null,
|
|
17366
18162
|
expectedProgress: null,
|
|
17367
18163
|
expectedProgressBaseline: null,
|
|
17368
18164
|
status: null,
|
|
@@ -17799,13 +18595,19 @@ function createActivityIdGenerator(options = {}) {
|
|
|
17799
18595
|
const n = typeof id === "number" ? id : Number(id);
|
|
17800
18596
|
if (!Number.isFinite(n)) return;
|
|
17801
18597
|
raise(n);
|
|
18598
|
+
},
|
|
18599
|
+
snapshot() {
|
|
18600
|
+
return counter;
|
|
18601
|
+
},
|
|
18602
|
+
restore(snapshot) {
|
|
18603
|
+
counter = snapshot;
|
|
17802
18604
|
}
|
|
17803
18605
|
};
|
|
17804
18606
|
}
|
|
17805
18607
|
|
|
17806
18608
|
// src/generators/link-id-generator.ts
|
|
17807
18609
|
function createLinkIdGenerator(options = {}) {
|
|
17808
|
-
let counter = options.seed ??
|
|
18610
|
+
let counter = options.seed ?? 0;
|
|
17809
18611
|
const raise = (value) => {
|
|
17810
18612
|
if (value > counter) counter = value;
|
|
17811
18613
|
};
|
|
@@ -17824,6 +18626,12 @@ function createLinkIdGenerator(options = {}) {
|
|
|
17824
18626
|
const n = typeof id === "number" ? id : Number(id);
|
|
17825
18627
|
if (!Number.isFinite(n)) return;
|
|
17826
18628
|
raise(n);
|
|
18629
|
+
},
|
|
18630
|
+
snapshot() {
|
|
18631
|
+
return counter;
|
|
18632
|
+
},
|
|
18633
|
+
restore(snapshot) {
|
|
18634
|
+
counter = snapshot;
|
|
17827
18635
|
}
|
|
17828
18636
|
};
|
|
17829
18637
|
}
|
|
@@ -17853,6 +18661,12 @@ function createUniqueCorrelativeIdGenerator(options = {}) {
|
|
|
17853
18661
|
if (uid2 == null) return;
|
|
17854
18662
|
const n = typeof uid2 === "number" ? uid2 : Number(uid2);
|
|
17855
18663
|
raise(n);
|
|
18664
|
+
},
|
|
18665
|
+
snapshot() {
|
|
18666
|
+
return counter;
|
|
18667
|
+
},
|
|
18668
|
+
restore(snapshot) {
|
|
18669
|
+
counter = snapshot;
|
|
17856
18670
|
}
|
|
17857
18671
|
};
|
|
17858
18672
|
}
|
|
@@ -17929,13 +18743,13 @@ function minSuffixFromArray(suffixes) {
|
|
|
17929
18743
|
if (!suffixes || suffixes.length === 0) {
|
|
17930
18744
|
throw new Error("minSuffixFromArray requires non-empty array");
|
|
17931
18745
|
}
|
|
17932
|
-
return suffixes.reduce((
|
|
18746
|
+
return suffixes.reduce((min2, current) => minSuffix(min2, current));
|
|
17933
18747
|
}
|
|
17934
18748
|
function maxSuffixFromArray(suffixes) {
|
|
17935
18749
|
if (!suffixes || suffixes.length === 0) {
|
|
17936
18750
|
throw new Error("maxSuffixFromArray requires non-empty array");
|
|
17937
18751
|
}
|
|
17938
|
-
return suffixes.reduce((
|
|
18752
|
+
return suffixes.reduce((max2, current) => maxSuffix(max2, current));
|
|
17939
18753
|
}
|
|
17940
18754
|
|
|
17941
18755
|
// src/generators/custom-id/generation-strategies.ts
|
|
@@ -18555,7 +19369,8 @@ var SCHEDULE_CORE_STATUS = {
|
|
|
18555
19369
|
};
|
|
18556
19370
|
function initializeCore(input) {
|
|
18557
19371
|
const clock = input.clock ?? null;
|
|
18558
|
-
const
|
|
19372
|
+
const initializedAt = clock?.() ?? null;
|
|
19373
|
+
const loadNow = initializedAt ? endOfLocalDay(initializedAt) : null;
|
|
18559
19374
|
const reporter = input.reporter ?? noopReporter;
|
|
18560
19375
|
const parsed2 = parseFromBackend(
|
|
18561
19376
|
{
|
|
@@ -18586,6 +19401,7 @@ function initializeCore(input) {
|
|
|
18586
19401
|
knownIds: parsed2.activities.map((a) => a.id)
|
|
18587
19402
|
});
|
|
18588
19403
|
const linkIdGen = createLinkIdGenerator({
|
|
19404
|
+
seed: initializedAt?.getTime() ?? 0,
|
|
18589
19405
|
knownIds: parsed2.links.map((l) => l.id)
|
|
18590
19406
|
});
|
|
18591
19407
|
const uidGen = createUniqueCorrelativeIdGenerator({
|
|
@@ -18629,13 +19445,204 @@ function initializeCore(input) {
|
|
|
18629
19445
|
};
|
|
18630
19446
|
}
|
|
18631
19447
|
|
|
19448
|
+
// src/propagations/upward/date-gesture-preview.ts
|
|
19449
|
+
var emptyFormula = () => ({
|
|
19450
|
+
constantStart: null,
|
|
19451
|
+
variableStartOffset: null,
|
|
19452
|
+
constantEnd: null,
|
|
19453
|
+
variableEndOffset: null
|
|
19454
|
+
});
|
|
19455
|
+
var min = (left, right) => left === null ? right : Math.min(left, right);
|
|
19456
|
+
var max = (left, right) => left === null ? right : Math.max(left, right);
|
|
19457
|
+
function createDateGesturePreview(adapter, gesture, assertCurrent) {
|
|
19458
|
+
const participantIds = [
|
|
19459
|
+
...new Set(
|
|
19460
|
+
gesture.kind === "move" ? gesture.activityIds.map(String) : [String(gesture.activityId)]
|
|
19461
|
+
)
|
|
19462
|
+
];
|
|
19463
|
+
if (participantIds.length === 0) {
|
|
19464
|
+
throw new Error("[ScheduleCore] date gesture has no participants");
|
|
19465
|
+
}
|
|
19466
|
+
const formulas = buildParticipantFormulas(adapter, gesture, participantIds);
|
|
19467
|
+
addAncestorFormulas(adapter, participantIds, formulas);
|
|
19468
|
+
return {
|
|
19469
|
+
getDates(activityId, proposedEdgeTimeMs) {
|
|
19470
|
+
assertCurrent();
|
|
19471
|
+
if (!Number.isFinite(proposedEdgeTimeMs)) {
|
|
19472
|
+
throw new Error("[ScheduleCore] invalid date gesture edge");
|
|
19473
|
+
}
|
|
19474
|
+
const formula = formulas.get(String(activityId));
|
|
19475
|
+
if (!formula) return null;
|
|
19476
|
+
const startTimeMs = evaluateStart(formula, proposedEdgeTimeMs);
|
|
19477
|
+
const endTimeMs = evaluateEnd(formula, proposedEdgeTimeMs);
|
|
19478
|
+
if (startTimeMs === null || endTimeMs === null) {
|
|
19479
|
+
throw new Error(
|
|
19480
|
+
`[ScheduleCore] incomplete date gesture bounds for ${String(activityId)}`
|
|
19481
|
+
);
|
|
19482
|
+
}
|
|
19483
|
+
return { startTimeMs, endTimeMs };
|
|
19484
|
+
}
|
|
19485
|
+
};
|
|
19486
|
+
}
|
|
19487
|
+
function buildParticipantFormulas(adapter, gesture, participantIds) {
|
|
19488
|
+
const formulas = /* @__PURE__ */ new Map();
|
|
19489
|
+
const anchorId = gesture.kind === "move" ? String(gesture.anchorActivityId) : null;
|
|
19490
|
+
const anchor = anchorId === null ? null : requireLeafActivity(adapter, anchorId);
|
|
19491
|
+
if (anchorId !== null && !participantIds.includes(anchorId)) {
|
|
19492
|
+
throw new Error("[ScheduleCore] move anchor is not a participant");
|
|
19493
|
+
}
|
|
19494
|
+
for (const activityId of participantIds) {
|
|
19495
|
+
const activity = requireLeafActivity(adapter, activityId);
|
|
19496
|
+
if (gesture.kind === "move") {
|
|
19497
|
+
const anchorStart = anchor.startDate.getTime();
|
|
19498
|
+
formulas.set(activityId, {
|
|
19499
|
+
constantStart: null,
|
|
19500
|
+
variableStartOffset: activity.startDate.getTime() - anchorStart,
|
|
19501
|
+
constantEnd: null,
|
|
19502
|
+
variableEndOffset: activity.endDate.getTime() - anchorStart
|
|
19503
|
+
});
|
|
19504
|
+
} else if (gesture.kind === "resize-start") {
|
|
19505
|
+
formulas.set(activityId, {
|
|
19506
|
+
constantStart: null,
|
|
19507
|
+
variableStartOffset: 0,
|
|
19508
|
+
constantEnd: activity.endDate.getTime(),
|
|
19509
|
+
variableEndOffset: null
|
|
19510
|
+
});
|
|
19511
|
+
} else {
|
|
19512
|
+
formulas.set(activityId, {
|
|
19513
|
+
constantStart: activity.startDate.getTime(),
|
|
19514
|
+
variableStartOffset: null,
|
|
19515
|
+
constantEnd: null,
|
|
19516
|
+
variableEndOffset: 0
|
|
19517
|
+
});
|
|
19518
|
+
}
|
|
19519
|
+
}
|
|
19520
|
+
return formulas;
|
|
19521
|
+
}
|
|
19522
|
+
function addAncestorFormulas(adapter, participantIds, formulas) {
|
|
19523
|
+
const ancestors = findAncestorsOfDirty(adapter, new Set(participantIds));
|
|
19524
|
+
for (const parentId of ancestors) {
|
|
19525
|
+
const formula = emptyFormula();
|
|
19526
|
+
for (const childId of adapter.getChildren(parentId)) {
|
|
19527
|
+
const childFormula = formulas.get(childId);
|
|
19528
|
+
if (childFormula) {
|
|
19529
|
+
mergeFormula(formula, childFormula);
|
|
19530
|
+
continue;
|
|
19531
|
+
}
|
|
19532
|
+
const child = adapter.getActivity(childId);
|
|
19533
|
+
if (!child) {
|
|
19534
|
+
throw new Error(
|
|
19535
|
+
`[ScheduleCore] missing child ${childId} while previewing parent ${parentId}`
|
|
19536
|
+
);
|
|
19537
|
+
}
|
|
19538
|
+
formula.constantStart = min(
|
|
19539
|
+
formula.constantStart,
|
|
19540
|
+
child.startDate.getTime()
|
|
19541
|
+
);
|
|
19542
|
+
formula.constantEnd = max(formula.constantEnd, child.endDate.getTime());
|
|
19543
|
+
}
|
|
19544
|
+
formulas.set(parentId, formula);
|
|
19545
|
+
}
|
|
19546
|
+
}
|
|
19547
|
+
function requireLeafActivity(adapter, activityId) {
|
|
19548
|
+
const activity = adapter.getActivity(activityId);
|
|
19549
|
+
if (!activity) {
|
|
19550
|
+
throw new Error(
|
|
19551
|
+
`[ScheduleCore] missing date gesture activity ${activityId}`
|
|
19552
|
+
);
|
|
19553
|
+
}
|
|
19554
|
+
if (adapter.hasChildren(activityId)) {
|
|
19555
|
+
throw new Error(
|
|
19556
|
+
`[ScheduleCore] summary activity ${activityId} cannot be dragged directly`
|
|
19557
|
+
);
|
|
19558
|
+
}
|
|
19559
|
+
return activity;
|
|
19560
|
+
}
|
|
19561
|
+
function mergeFormula(target, source) {
|
|
19562
|
+
if (source.constantStart !== null) {
|
|
19563
|
+
target.constantStart = min(target.constantStart, source.constantStart);
|
|
19564
|
+
}
|
|
19565
|
+
if (source.variableStartOffset !== null) {
|
|
19566
|
+
target.variableStartOffset = min(
|
|
19567
|
+
target.variableStartOffset,
|
|
19568
|
+
source.variableStartOffset
|
|
19569
|
+
);
|
|
19570
|
+
}
|
|
19571
|
+
if (source.constantEnd !== null) {
|
|
19572
|
+
target.constantEnd = max(target.constantEnd, source.constantEnd);
|
|
19573
|
+
}
|
|
19574
|
+
if (source.variableEndOffset !== null) {
|
|
19575
|
+
target.variableEndOffset = max(
|
|
19576
|
+
target.variableEndOffset,
|
|
19577
|
+
source.variableEndOffset
|
|
19578
|
+
);
|
|
19579
|
+
}
|
|
19580
|
+
}
|
|
19581
|
+
function evaluateStart(formula, proposedEdgeTimeMs) {
|
|
19582
|
+
if (formula.variableStartOffset === null) return formula.constantStart;
|
|
19583
|
+
const variable = proposedEdgeTimeMs + formula.variableStartOffset;
|
|
19584
|
+
return formula.constantStart === null ? variable : Math.min(formula.constantStart, variable);
|
|
19585
|
+
}
|
|
19586
|
+
function evaluateEnd(formula, proposedEdgeTimeMs) {
|
|
19587
|
+
if (formula.variableEndOffset === null) return formula.constantEnd;
|
|
19588
|
+
const variable = proposedEdgeTimeMs + formula.variableEndOffset;
|
|
19589
|
+
return formula.constantEnd === null ? variable : Math.max(formula.constantEnd, variable);
|
|
19590
|
+
}
|
|
19591
|
+
|
|
18632
19592
|
// src/init/schedule-core.ts
|
|
19593
|
+
function changesRequireFilterProjectionRebuild(changes) {
|
|
19594
|
+
for (const change of changes.activities) {
|
|
19595
|
+
if (change.kind === "deleted") return true;
|
|
19596
|
+
const hasMovedParent = change.kind === "updated" && change.fields !== void 0 && "parentId" in change.fields;
|
|
19597
|
+
if (hasMovedParent) return true;
|
|
19598
|
+
}
|
|
19599
|
+
return false;
|
|
19600
|
+
}
|
|
19601
|
+
function collectChangedActivities(changes, adapter) {
|
|
19602
|
+
const activities = [];
|
|
19603
|
+
for (const change of changes.activities) {
|
|
19604
|
+
const activity = adapter.getActivity(change.id);
|
|
19605
|
+
if (activity === null) return null;
|
|
19606
|
+
activities.push(activity);
|
|
19607
|
+
}
|
|
19608
|
+
return activities;
|
|
19609
|
+
}
|
|
19610
|
+
function isCurrentFilterProjection(projection, filter, context) {
|
|
19611
|
+
if (projection === null || projection.filter !== filter) return false;
|
|
19612
|
+
const hasCurrentHours = projection.context.hoursPerDay === context.hoursPerDay;
|
|
19613
|
+
const hasCurrentLocale = projection.context.locale === context.locale;
|
|
19614
|
+
return hasCurrentHours && hasCurrentLocale;
|
|
19615
|
+
}
|
|
18633
19616
|
var ScheduleCore = class {
|
|
18634
19617
|
_status = SCHEDULE_CORE_STATUS.READY;
|
|
18635
19618
|
coreRuntime;
|
|
18636
19619
|
_undo = new UndoRecorder();
|
|
18637
19620
|
_opQueue = Promise.resolve();
|
|
18638
19621
|
_scheduleRevision = 0;
|
|
19622
|
+
/**
|
|
19623
|
+
* Revision counting mutations that have been ADMITTED, whether or not they
|
|
19624
|
+
* have changed anything yet. `_scheduleRevision` counts the ones that
|
|
19625
|
+
* actually did.
|
|
19626
|
+
*
|
|
19627
|
+
* INVARIANT: `_pendingScheduleRevision >= _scheduleRevision`, and equality
|
|
19628
|
+
* means no mutation is in flight.
|
|
19629
|
+
*
|
|
19630
|
+
* The two exist separately because the useful instant and the knowable
|
|
19631
|
+
* instant are not the same one. A critical-path calculation becomes garbage
|
|
19632
|
+
* the moment the next mutation is admitted, but whether that mutation is
|
|
19633
|
+
* substantive is only knowable once its ChangeSet exists, which is hundreds
|
|
19634
|
+
* of milliseconds later at project scale. Measured live on a 12268-activity
|
|
19635
|
+
* project: the cancellation flag flipped 929 ms into a 930 ms calculation,
|
|
19636
|
+
* 812 into 812 and 785 into 785, because the job's apply block queues behind
|
|
19637
|
+
* the very dispatch whose completion cancels it. Cancellation and job end
|
|
19638
|
+
* were the same event, so every discarded job ran the whole calculation.
|
|
19639
|
+
*
|
|
19640
|
+
* Splitting the counter buys the early signal without moving the meaning of
|
|
19641
|
+
* `_scheduleRevision`, whose three readers (`isCriticalPathSettled`,
|
|
19642
|
+
* `createDateGesturePreview` and the job's own `isCurrent`) are all written
|
|
19643
|
+
* against "the state actually changed".
|
|
19644
|
+
*/
|
|
19645
|
+
_pendingScheduleRevision = 0;
|
|
18639
19646
|
_criticalPathRevision = -1;
|
|
18640
19647
|
_activeCriticalPath = null;
|
|
18641
19648
|
constructor(input) {
|
|
@@ -18688,6 +19695,16 @@ var ScheduleCore = class {
|
|
|
18688
19695
|
this.assertReady();
|
|
18689
19696
|
return readActivity(this.coreRuntime.state, id);
|
|
18690
19697
|
}
|
|
19698
|
+
createDateGesturePreview(gesture) {
|
|
19699
|
+
this.assertReady();
|
|
19700
|
+
const revision = this._scheduleRevision;
|
|
19701
|
+
return createDateGesturePreview(this.coreRuntime.state, gesture, () => {
|
|
19702
|
+
this.assertReady();
|
|
19703
|
+
if (this._scheduleRevision !== revision) {
|
|
19704
|
+
throw new Error("[ScheduleCore] date gesture preview is stale");
|
|
19705
|
+
}
|
|
19706
|
+
});
|
|
19707
|
+
}
|
|
18691
19708
|
getAllActivitiesView() {
|
|
18692
19709
|
this.assertReady();
|
|
18693
19710
|
return readAllActivities(this.coreRuntime.state);
|
|
@@ -18785,10 +19802,7 @@ var ScheduleCore = class {
|
|
|
18785
19802
|
}
|
|
18786
19803
|
hasUnsavedChanges() {
|
|
18787
19804
|
this.assertReady();
|
|
18788
|
-
return this._saveTracker.hasUnsavedChanges(
|
|
18789
|
-
this.coreRuntime.state.getAllActivities(),
|
|
18790
|
-
this.getAllLinksView()
|
|
18791
|
-
);
|
|
19805
|
+
return this._saveTracker.hasUnsavedChanges();
|
|
18792
19806
|
}
|
|
18793
19807
|
getDeletedActivitiesSinceLastSave() {
|
|
18794
19808
|
this.assertReady();
|
|
@@ -18806,10 +19820,90 @@ var ScheduleCore = class {
|
|
|
18806
19820
|
);
|
|
18807
19821
|
return workPromise;
|
|
18808
19822
|
}
|
|
19823
|
+
/**
|
|
19824
|
+
* Marks a mutation as admitted and invalidates the calculation in flight.
|
|
19825
|
+
*
|
|
19826
|
+
* Called BEFORE `_enqueue`, which is the point of the whole thing: the wait
|
|
19827
|
+
* in the operation queue is part of the window a running calculation wastes,
|
|
19828
|
+
* and it is the longest part of it when several mutations are already
|
|
19829
|
+
* queued.
|
|
19830
|
+
*
|
|
19831
|
+
* `dispatchChangesSchedulingState` is a pure function of the action, so this
|
|
19832
|
+
* decision needs no state and cannot be wrong about the action's nature. What
|
|
19833
|
+
* it cannot know yet is whether the mutation will produce anything, which is
|
|
19834
|
+
* what `_settleScheduleMutation` reconciles afterwards.
|
|
19835
|
+
*/
|
|
19836
|
+
_beginScheduleMutation(action) {
|
|
19837
|
+
if (!dispatchChangesSchedulingState(action)) return false;
|
|
19838
|
+
return this._admitScheduleMutation();
|
|
19839
|
+
}
|
|
19840
|
+
/** Undo and redo have no action to classify: reaching them IS the mutation. */
|
|
19841
|
+
_admitScheduleMutation() {
|
|
19842
|
+
this._pendingScheduleRevision++;
|
|
19843
|
+
if (this._activeCriticalPath) {
|
|
19844
|
+
this._activeCriticalPath.cancellation.cancelled = true;
|
|
19845
|
+
}
|
|
19846
|
+
return true;
|
|
19847
|
+
}
|
|
19848
|
+
/**
|
|
19849
|
+
* Closes a mutation admitted by `_beginScheduleMutation`, on EVERY exit path.
|
|
19850
|
+
*
|
|
19851
|
+
* `changedState` false means the mutation was admitted and produced nothing
|
|
19852
|
+
* (rejected, non-substantive, or rolled back). The pending revision walks
|
|
19853
|
+
* back so the invariant holds and `isCriticalPathSettled` keeps telling the
|
|
19854
|
+
* truth, and the calculation this mutation killed for nothing is re-armed.
|
|
19855
|
+
*
|
|
19856
|
+
* The re-arm goes through the queue and that is not incidental. Started
|
|
19857
|
+
* outside it, the calculation would run immediately while the next queued
|
|
19858
|
+
* mutation is still waiting, and that mutation would kill it again on
|
|
19859
|
+
* admission: a cancel-and-restart treadmill burning a fresh project-wide deep
|
|
19860
|
+
* copy per lap. Inside the queue the restart cannot happen until the queue
|
|
19861
|
+
* drains, so at most one calculation is armed per settled mutation. The burst
|
|
19862
|
+
* scenario will NOT catch a regression here, because there every dispatch
|
|
19863
|
+
* succeeds and this path is never taken.
|
|
19864
|
+
*
|
|
19865
|
+
* It re-arms through `recomputeCriticalPath` rather than enqueueing the job
|
|
19866
|
+
* directly, because the queue slot must NOT await the job: the job's own
|
|
19867
|
+
* apply block needs a later slot on this same queue, so awaiting it from
|
|
19868
|
+
* inside a slot deadlocks the core. `recomputeCriticalPath` already has the
|
|
19869
|
+
* shape that returns the job's promise out of the slot instead of awaiting
|
|
19870
|
+
* it, and it keeps `_criticalPathReady` pointing at the live calculation.
|
|
19871
|
+
*/
|
|
19872
|
+
_settleScheduleMutation(admitted, changedState) {
|
|
19873
|
+
if (!admitted) return;
|
|
19874
|
+
if (changedState) return;
|
|
19875
|
+
this._pendingScheduleRevision = Math.max(
|
|
19876
|
+
this._pendingScheduleRevision - 1,
|
|
19877
|
+
this._scheduleRevision
|
|
19878
|
+
);
|
|
19879
|
+
if (this._pendingScheduleRevision !== this._scheduleRevision) return;
|
|
19880
|
+
if (this._criticalPathRevision === this._scheduleRevision) return;
|
|
19881
|
+
void this.recomputeCriticalPath().catch((error) => {
|
|
19882
|
+
this.coreRuntime.reporter.error(
|
|
19883
|
+
"[ScheduleCore] Critical Path re-arm after a barren mutation failed",
|
|
19884
|
+
error
|
|
19885
|
+
);
|
|
19886
|
+
});
|
|
19887
|
+
}
|
|
18809
19888
|
async dispatch(action, options = {}) {
|
|
18810
|
-
|
|
19889
|
+
const admitted = this._beginScheduleMutation(action);
|
|
19890
|
+
return this._enqueue(() => this._dispatchInner(action, options, admitted));
|
|
19891
|
+
}
|
|
19892
|
+
async applyActivityBatch(action, options = {}) {
|
|
19893
|
+
const admitted = this._beginScheduleMutation(action);
|
|
19894
|
+
return this._enqueue(() => this._dispatchInner(action, options, admitted));
|
|
19895
|
+
}
|
|
19896
|
+
async _dispatchInner(action, options = {}, admitted = false) {
|
|
19897
|
+
let changedState = false;
|
|
19898
|
+
try {
|
|
19899
|
+
const dispatched = await this._runDispatch(action, options);
|
|
19900
|
+
changedState = dispatched.changedState;
|
|
19901
|
+
return dispatched.result;
|
|
19902
|
+
} finally {
|
|
19903
|
+
this._settleScheduleMutation(admitted, changedState);
|
|
19904
|
+
}
|
|
18811
19905
|
}
|
|
18812
|
-
async
|
|
19906
|
+
async _runDispatch(action, options) {
|
|
18813
19907
|
this.assertReady();
|
|
18814
19908
|
const pipelineContext = buildPipelineContext(this.coreRuntime.state, {
|
|
18815
19909
|
hoursPerDay: this.coreRuntime.sector.hoursPerDay,
|
|
@@ -18817,11 +19911,16 @@ var ScheduleCore = class {
|
|
|
18817
19911
|
inputUnit: options.inputUnit ?? "days"
|
|
18818
19912
|
});
|
|
18819
19913
|
const historyPolicy = getDispatchHistoryPolicy(action);
|
|
19914
|
+
const generatorSnapshot = [
|
|
19915
|
+
this.coreRuntime.activityIdGenerator.snapshot(),
|
|
19916
|
+
this.coreRuntime.linkIdGenerator.snapshot(),
|
|
19917
|
+
this.coreRuntime.uniqueCorrelativeIdGenerator.snapshot()
|
|
19918
|
+
];
|
|
18820
19919
|
this.coreRuntime.state.beginWriteCapture();
|
|
18821
19920
|
this.coreRuntime.customIdTracker.beginCustomIdTransaction();
|
|
18822
19921
|
let result;
|
|
18823
19922
|
try {
|
|
18824
|
-
result = await
|
|
19923
|
+
result = await performDispatch(action, options, {
|
|
18825
19924
|
adapter: this.coreRuntime.state,
|
|
18826
19925
|
ctx: pipelineContext,
|
|
18827
19926
|
scheduler: this.coreRuntime.scheduler,
|
|
@@ -18837,9 +19936,9 @@ var ScheduleCore = class {
|
|
|
18837
19936
|
)?.id ?? null,
|
|
18838
19937
|
now: this.coreRuntime.clock ? endOfLocalDay(this.coreRuntime.clock()) : null
|
|
18839
19938
|
});
|
|
18840
|
-
if (!result.ok) this._rollback();
|
|
19939
|
+
if (!result.ok) this._rollback(generatorSnapshot);
|
|
18841
19940
|
} catch (error) {
|
|
18842
|
-
this._rollback();
|
|
19941
|
+
this._rollback(generatorSnapshot);
|
|
18843
19942
|
throw error;
|
|
18844
19943
|
} finally {
|
|
18845
19944
|
this.coreRuntime.state.endWriteCapture();
|
|
@@ -18862,19 +19961,36 @@ var ScheduleCore = class {
|
|
|
18862
19961
|
Date.now()
|
|
18863
19962
|
);
|
|
18864
19963
|
}
|
|
18865
|
-
if (result.ok
|
|
18866
|
-
|
|
18867
|
-
this.
|
|
18868
|
-
|
|
18869
|
-
|
|
18870
|
-
|
|
19964
|
+
if (result.ok) {
|
|
19965
|
+
if (historyPolicy === "clear-on-success") {
|
|
19966
|
+
this._saveTracker.snapshotActivities(
|
|
19967
|
+
this.coreRuntime.state.getAllActivities()
|
|
19968
|
+
);
|
|
19969
|
+
this._saveTracker.snapshotLinks(this.getAllLinksView());
|
|
19970
|
+
this._undo.clear();
|
|
19971
|
+
} else if (action.kind === "activity-lookahead-sync" && action.acknowledgePersistedLookahead === true) {
|
|
19972
|
+
this._saveTracker.acknowledgePersistedLookahead(
|
|
19973
|
+
action.activityIds,
|
|
19974
|
+
(activityId) => this.coreRuntime.state.getActivity(activityId)
|
|
19975
|
+
);
|
|
19976
|
+
} else {
|
|
19977
|
+
this._saveTracker.recordChanges(
|
|
19978
|
+
result.changes.activities,
|
|
19979
|
+
result.changes.links,
|
|
19980
|
+
(activityId) => this.coreRuntime.state.getActivity(activityId),
|
|
19981
|
+
(linkId) => this.coreRuntime.state.getLink(linkId)
|
|
19982
|
+
);
|
|
19983
|
+
}
|
|
18871
19984
|
}
|
|
18872
19985
|
result = this._withReappliedViewState(action, result);
|
|
18873
|
-
if (!result.ok) return result;
|
|
19986
|
+
if (!result.ok) return { result, changedState: false };
|
|
18874
19987
|
if (dispatchChangesSchedulingState(action) && changeSetIsSubstantive(result.changes)) {
|
|
18875
|
-
this._recordScheduleMutation(
|
|
19988
|
+
this._recordScheduleMutation(
|
|
19989
|
+
options.skipAutoSchedule !== true && options.skipCriticalPath !== true && resolveRunCriticalPath(action)
|
|
19990
|
+
);
|
|
19991
|
+
return { result: toPublicDispatchResult(result), changedState: true };
|
|
18876
19992
|
}
|
|
18877
|
-
return toPublicDispatchResult(result);
|
|
19993
|
+
return { result: toPublicDispatchResult(result), changedState: false };
|
|
18878
19994
|
}
|
|
18879
19995
|
recomputeCriticalPath() {
|
|
18880
19996
|
const operation = this._enqueue(async () => ({
|
|
@@ -18884,7 +20000,11 @@ var ScheduleCore = class {
|
|
|
18884
20000
|
return operation;
|
|
18885
20001
|
}
|
|
18886
20002
|
isCriticalPathSettled() {
|
|
18887
|
-
return this._criticalPathRevision === this._scheduleRevision &&
|
|
20003
|
+
return this._criticalPathRevision === this._scheduleRevision && // An admitted mutation has not landed yet, so whatever is applied now is
|
|
20004
|
+
// about to be stale. Without this term `whenCriticalPathSettled` would
|
|
20005
|
+
// spin: it would keep arming calculations that `isCurrent` kills on their
|
|
20006
|
+
// first check, each one paying the project-wide deep copy first.
|
|
20007
|
+
this._pendingScheduleRevision === this._scheduleRevision && this._activeCriticalPath === null;
|
|
18888
20008
|
}
|
|
18889
20009
|
async whenCriticalPathSettled() {
|
|
18890
20010
|
while (!this.isCriticalPathSettled()) {
|
|
@@ -18892,7 +20012,11 @@ var ScheduleCore = class {
|
|
|
18892
20012
|
}
|
|
18893
20013
|
}
|
|
18894
20014
|
_withReappliedViewState(action, result) {
|
|
18895
|
-
if (!result.ok
|
|
20015
|
+
if (!result.ok) return result;
|
|
20016
|
+
if (action.kind === "visibility-set") {
|
|
20017
|
+
this.coreRuntime.state.setFilterProjection(null);
|
|
20018
|
+
}
|
|
20019
|
+
if (!reappliesViewState(action)) return result;
|
|
18896
20020
|
const merged = this._reapplyViewState(result.changes);
|
|
18897
20021
|
return merged === null ? result : { ...result, changes: merged };
|
|
18898
20022
|
}
|
|
@@ -18913,11 +20037,44 @@ var ScheduleCore = class {
|
|
|
18913
20037
|
*/
|
|
18914
20038
|
_reapplyViewState(changes) {
|
|
18915
20039
|
const withFilter = this._reapplyActiveFilter(changes);
|
|
18916
|
-
const
|
|
18917
|
-
const
|
|
18918
|
-
|
|
20040
|
+
const sequenced = this._emitTouchedBranchOrder(withFilter ?? changes);
|
|
20041
|
+
const withOrder = this._reapplyActiveOrder(
|
|
20042
|
+
sequenced.changes ?? withFilter ?? changes,
|
|
20043
|
+
sequenced.touchedParents
|
|
20044
|
+
);
|
|
20045
|
+
const reapplied = withOrder ?? sequenced.changes ?? withFilter;
|
|
20046
|
+
const finalChanges = reapplied ?? changes;
|
|
20047
|
+
const createdIds = finalChanges.activities.filter((change) => change.kind === "created").map((change) => String(change.id));
|
|
20048
|
+
if (createdIds.length === 0) return reapplied;
|
|
20049
|
+
const createdAndAncestors = new Set(createdIds);
|
|
20050
|
+
const filterProjection = this.coreRuntime.state.getFilterProjection();
|
|
20051
|
+
for (const createdId of createdIds) {
|
|
20052
|
+
if (filterProjection !== null) {
|
|
20053
|
+
filterProjection.pinnedVisibleIds.add(createdId);
|
|
20054
|
+
}
|
|
20055
|
+
let activityId = createdId;
|
|
20056
|
+
while (activityId !== null && activityId !== ROOT_PARENT_ID) {
|
|
20057
|
+
createdAndAncestors.add(activityId);
|
|
20058
|
+
if (filterProjection !== null) {
|
|
20059
|
+
filterProjection.visibleIds.add(activityId);
|
|
20060
|
+
filterProjection.visibleReferenceCounts.set(
|
|
20061
|
+
activityId,
|
|
20062
|
+
(filterProjection.visibleReferenceCounts.get(activityId) ?? 0) + 1
|
|
20063
|
+
);
|
|
20064
|
+
}
|
|
20065
|
+
activityId = this.coreRuntime.state.getParentId(activityId);
|
|
20066
|
+
}
|
|
20067
|
+
}
|
|
20068
|
+
const createdVisibility = applyVisibleSet(
|
|
20069
|
+
this.coreRuntime.state,
|
|
20070
|
+
createdAndAncestors,
|
|
20071
|
+
createdAndAncestors
|
|
18919
20072
|
);
|
|
18920
|
-
|
|
20073
|
+
if (createdVisibility.length === 0) return reapplied;
|
|
20074
|
+
return {
|
|
20075
|
+
...finalChanges,
|
|
20076
|
+
viewState: [...finalChanges.viewState ?? [], ...createdVisibility]
|
|
20077
|
+
};
|
|
18921
20078
|
}
|
|
18922
20079
|
/**
|
|
18923
20080
|
* Emits `order` for the branches this mutation resequenced, when no user order
|
|
@@ -18939,18 +20096,19 @@ var ScheduleCore = class {
|
|
|
18939
20096
|
*/
|
|
18940
20097
|
_emitTouchedBranchOrder(changes) {
|
|
18941
20098
|
const adapter = this.coreRuntime.state;
|
|
18942
|
-
|
|
18943
|
-
const touched = collectResequencedParents(
|
|
20099
|
+
const touchedParents = collectResequencedParents(
|
|
18944
20100
|
changes,
|
|
18945
20101
|
(activityId) => adapter.getParentId(activityId)
|
|
18946
20102
|
);
|
|
18947
|
-
if (
|
|
18948
|
-
|
|
18949
|
-
|
|
18950
|
-
|
|
18951
|
-
|
|
18952
|
-
|
|
18953
|
-
|
|
20103
|
+
if (adapter.getActiveOrder() !== null) {
|
|
20104
|
+
return { changes: null, touchedParents };
|
|
20105
|
+
}
|
|
20106
|
+
if (touchedParents.size === 0) {
|
|
20107
|
+
return { changes: null, touchedParents };
|
|
20108
|
+
}
|
|
20109
|
+
const order = collectBranchOrderForParents(adapter, touchedParents);
|
|
20110
|
+
if (order.length === 0) return { changes: null, touchedParents };
|
|
20111
|
+
return { changes: { ...changes, order }, touchedParents };
|
|
18954
20112
|
}
|
|
18955
20113
|
/**
|
|
18956
20114
|
* Re-sequences the grid after any mutation that could have changed a value the
|
|
@@ -18961,23 +20119,63 @@ var ScheduleCore = class {
|
|
|
18961
20119
|
* Production only re-sorts after a bar drag; diverging from that is a
|
|
18962
20120
|
* deliberate product decision, not an oversight.
|
|
18963
20121
|
*/
|
|
18964
|
-
_reapplyActiveOrder(changes) {
|
|
20122
|
+
_reapplyActiveOrder(changes, resequencedParents) {
|
|
18965
20123
|
const adapter = this.coreRuntime.state;
|
|
18966
20124
|
if (adapter.getActiveOrder() === null) return null;
|
|
18967
20125
|
adapter.invalidateDerivedOrder();
|
|
18968
|
-
|
|
20126
|
+
const hasIndirectOrderChanges = changes.links.length > 0 || changes.calendars.length > 0;
|
|
20127
|
+
if (hasIndirectOrderChanges) {
|
|
20128
|
+
return { ...changes, order: collectBranchOrder(adapter) };
|
|
20129
|
+
}
|
|
20130
|
+
const affectedParents = new Set(resequencedParents);
|
|
20131
|
+
for (const change of changes.activities) {
|
|
20132
|
+
if (change.kind === "deleted") {
|
|
20133
|
+
return { ...changes, order: collectBranchOrder(adapter) };
|
|
20134
|
+
}
|
|
20135
|
+
const parentId = adapter.getParentId(change.id);
|
|
20136
|
+
affectedParents.add(
|
|
20137
|
+
parentId === null ? ROOT_PARENT_ID : String(parentId)
|
|
20138
|
+
);
|
|
20139
|
+
}
|
|
20140
|
+
const order = collectBranchOrderForParents(adapter, affectedParents);
|
|
20141
|
+
if (order.length === 0) return null;
|
|
20142
|
+
return { ...changes, order };
|
|
18969
20143
|
}
|
|
18970
20144
|
_reapplyActiveFilter(changes) {
|
|
18971
|
-
const filter = this.coreRuntime.state.getActiveFilter();
|
|
18972
|
-
if (filter === null) return null;
|
|
18973
20145
|
const adapter = this.coreRuntime.state;
|
|
18974
|
-
const
|
|
18975
|
-
|
|
18976
|
-
|
|
20146
|
+
const filter = adapter.getActiveFilter();
|
|
20147
|
+
if (filter === null) return null;
|
|
20148
|
+
const context = buildFilterContext(this.coreRuntime.sector.hoursPerDay);
|
|
20149
|
+
const projection = adapter.getFilterProjection();
|
|
20150
|
+
const projectionIsCurrent = isCurrentFilterProjection(
|
|
20151
|
+
projection,
|
|
18977
20152
|
filter,
|
|
18978
|
-
context
|
|
18979
|
-
|
|
18980
|
-
const
|
|
20153
|
+
context
|
|
20154
|
+
);
|
|
20155
|
+
const changedActivities = changesRequireFilterProjectionRebuild(changes) ? null : collectChangedActivities(changes, adapter);
|
|
20156
|
+
let affectedIds;
|
|
20157
|
+
let visibleIds;
|
|
20158
|
+
const needsProjectionRebuild = changedActivities === null || !projectionIsCurrent;
|
|
20159
|
+
if (needsProjectionRebuild) {
|
|
20160
|
+
const rebuiltProjection = buildFilterProjection({
|
|
20161
|
+
activities: adapter.getAllActivities(),
|
|
20162
|
+
parentOf: (activityId) => adapter.getParentId(activityId),
|
|
20163
|
+
filter,
|
|
20164
|
+
context,
|
|
20165
|
+
...projectionIsCurrent ? { pinnedVisibleIds: projection.pinnedVisibleIds } : {}
|
|
20166
|
+
});
|
|
20167
|
+
adapter.setFilterProjection(rebuiltProjection);
|
|
20168
|
+
visibleIds = rebuiltProjection.visibleIds;
|
|
20169
|
+
affectedIds = adapter.getAllIds();
|
|
20170
|
+
} else {
|
|
20171
|
+
affectedIds = updateFilterProjection(
|
|
20172
|
+
projection,
|
|
20173
|
+
changedActivities,
|
|
20174
|
+
(activityId) => adapter.getParentId(activityId)
|
|
20175
|
+
);
|
|
20176
|
+
visibleIds = projection.visibleIds;
|
|
20177
|
+
}
|
|
20178
|
+
const viewState = applyVisibleSet(adapter, visibleIds, affectedIds);
|
|
18981
20179
|
if (viewState.length === 0) return null;
|
|
18982
20180
|
return {
|
|
18983
20181
|
...changes,
|
|
@@ -18986,6 +20184,10 @@ var ScheduleCore = class {
|
|
|
18986
20184
|
}
|
|
18987
20185
|
_recordScheduleMutation(criticalPathIsFresh) {
|
|
18988
20186
|
this._scheduleRevision++;
|
|
20187
|
+
this._pendingScheduleRevision = Math.max(
|
|
20188
|
+
this._pendingScheduleRevision,
|
|
20189
|
+
this._scheduleRevision
|
|
20190
|
+
);
|
|
18989
20191
|
if (this._activeCriticalPath) {
|
|
18990
20192
|
this._activeCriticalPath.cancellation.cancelled = true;
|
|
18991
20193
|
}
|
|
@@ -19016,23 +20218,30 @@ var ScheduleCore = class {
|
|
|
19016
20218
|
this.coreRuntime.baseCalendars
|
|
19017
20219
|
)
|
|
19018
20220
|
);
|
|
19019
|
-
const isCurrent = () => !cancellation.cancelled && this._status !== SCHEDULE_CORE_STATUS.DESTROYED && this._scheduleRevision === revision
|
|
20221
|
+
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
|
|
20222
|
+
// garbage. Without this term the calculation only learns it is obsolete
|
|
20223
|
+
// when the mutation finishes, which measured live is the same instant the
|
|
20224
|
+
// job itself ends.
|
|
20225
|
+
this._pendingScheduleRevision === this._scheduleRevision;
|
|
19020
20226
|
const promise = (async () => {
|
|
19021
20227
|
const calculatedFields = await computeCriticalPathFields(
|
|
19022
20228
|
isolatedState,
|
|
19023
20229
|
this.coreRuntime.sector.hoursPerDay,
|
|
19024
20230
|
isCurrent
|
|
19025
20231
|
);
|
|
19026
|
-
if (!isCurrent()) return null;
|
|
19027
|
-
const fieldsByActivity = calculatedFields ?? /* @__PURE__ */ new Map();
|
|
20232
|
+
if (!isCurrent() || calculatedFields === null) return null;
|
|
19028
20233
|
return this._enqueue(async () => {
|
|
19029
20234
|
if (!isCurrent()) return null;
|
|
19030
20235
|
this.coreRuntime.state.beginWriteCapture();
|
|
19031
20236
|
this.coreRuntime.customIdTracker.beginCustomIdTransaction();
|
|
19032
20237
|
let changes;
|
|
19033
20238
|
try {
|
|
19034
|
-
for (const [activityId, fields] of
|
|
19035
|
-
|
|
20239
|
+
for (const [activityId, fields] of calculatedFields) {
|
|
20240
|
+
setCriticalPathFieldsIfChanged(
|
|
20241
|
+
this.coreRuntime.state,
|
|
20242
|
+
activityId,
|
|
20243
|
+
fields
|
|
20244
|
+
);
|
|
19036
20245
|
}
|
|
19037
20246
|
changes = await assembleChangeSet(this.coreRuntime.state, {
|
|
19038
20247
|
source: { kind: "init" },
|
|
@@ -19094,47 +20303,17 @@ var ScheduleCore = class {
|
|
|
19094
20303
|
}
|
|
19095
20304
|
return { changes };
|
|
19096
20305
|
}
|
|
19097
|
-
_recomputeAfterHistoryRestore() {
|
|
19098
|
-
const state = this.coreRuntime.state;
|
|
19099
|
-
recomputeAllProgressRollup(state);
|
|
19100
|
-
recomputeCanonicalRealWork(state);
|
|
19101
|
-
emitRealCost(state);
|
|
19102
|
-
if (this.coreRuntime.clock) {
|
|
19103
|
-
const now = endOfLocalDay(this.coreRuntime.clock());
|
|
19104
|
-
applyExpectedProgressLive(state, now);
|
|
19105
|
-
let hasActiveBaseline2 = false;
|
|
19106
|
-
state.forEachActivity((activity) => {
|
|
19107
|
-
hasActiveBaseline2 ||= getActiveBaseline(activity) !== null;
|
|
19108
|
-
});
|
|
19109
|
-
if (hasActiveBaseline2) {
|
|
19110
|
-
runExpectedProgressBase(
|
|
19111
|
-
state,
|
|
19112
|
-
now,
|
|
19113
|
-
this.coreRuntime.baseCalendars.find(
|
|
19114
|
-
(calendar) => calendar.baseDefault
|
|
19115
|
-
)?.id ?? null
|
|
19116
|
-
);
|
|
19117
|
-
}
|
|
19118
|
-
applyStatusPass(state, this.coreRuntime.sector.statusCriteria);
|
|
19119
|
-
}
|
|
19120
|
-
}
|
|
19121
20306
|
undo(options = {}) {
|
|
20307
|
+
const admitted = this._admitScheduleMutation();
|
|
20308
|
+
let changedState = false;
|
|
19122
20309
|
const operation = this._enqueue(async () => {
|
|
19123
|
-
|
|
19124
|
-
|
|
19125
|
-
|
|
19126
|
-
|
|
19127
|
-
|
|
19128
|
-
|
|
19129
|
-
|
|
19130
|
-
this._undo.pushRedo(entry);
|
|
19131
|
-
this._undo.resetCoalesce();
|
|
19132
|
-
const changes = buildInverseChangeSet(
|
|
19133
|
-
this.coreRuntime.state,
|
|
19134
|
-
entry,
|
|
19135
|
-
"before"
|
|
19136
|
-
);
|
|
19137
|
-
return this._reapplyViewState(changes) ?? changes;
|
|
20310
|
+
try {
|
|
20311
|
+
return await this._runUndo(options, (changed) => {
|
|
20312
|
+
changedState = changed;
|
|
20313
|
+
});
|
|
20314
|
+
} finally {
|
|
20315
|
+
this._settleScheduleMutation(admitted, changedState);
|
|
20316
|
+
}
|
|
19138
20317
|
});
|
|
19139
20318
|
void operation.then(
|
|
19140
20319
|
(changes) => {
|
|
@@ -19151,23 +20330,45 @@ var ScheduleCore = class {
|
|
|
19151
20330
|
);
|
|
19152
20331
|
return operation;
|
|
19153
20332
|
}
|
|
19154
|
-
|
|
19155
|
-
|
|
20333
|
+
async _runUndo(options, markChanged) {
|
|
20334
|
+
{
|
|
19156
20335
|
this.assertReady();
|
|
19157
|
-
|
|
20336
|
+
if (options.expectedUndoDepth !== void 0 && this._undo.undoDepth() !== options.expectedUndoDepth) {
|
|
20337
|
+
return null;
|
|
20338
|
+
}
|
|
20339
|
+
const entry = this._undo.takeUndo();
|
|
19158
20340
|
if (!entry) return null;
|
|
19159
|
-
|
|
19160
|
-
this._recomputeAfterHistoryRestore();
|
|
20341
|
+
applyUndo(this.coreRuntime.state, entry);
|
|
19161
20342
|
this._recordScheduleMutation(false);
|
|
20343
|
+
markChanged(true);
|
|
19162
20344
|
if (needsResync(entry)) this._resyncCustomIdTrackerFromModel();
|
|
19163
|
-
this._undo.
|
|
20345
|
+
this._undo.pushRedo(entry);
|
|
19164
20346
|
this._undo.resetCoalesce();
|
|
19165
20347
|
const changes = buildInverseChangeSet(
|
|
19166
20348
|
this.coreRuntime.state,
|
|
19167
20349
|
entry,
|
|
19168
|
-
"
|
|
20350
|
+
"before"
|
|
20351
|
+
);
|
|
20352
|
+
this._saveTracker.recordChanges(
|
|
20353
|
+
changes.activities,
|
|
20354
|
+
changes.links,
|
|
20355
|
+
(activityId) => this.coreRuntime.state.getActivity(activityId),
|
|
20356
|
+
(linkId) => this.coreRuntime.state.getLink(linkId)
|
|
19169
20357
|
);
|
|
19170
20358
|
return this._reapplyViewState(changes) ?? changes;
|
|
20359
|
+
}
|
|
20360
|
+
}
|
|
20361
|
+
redo(options = {}) {
|
|
20362
|
+
const admitted = this._admitScheduleMutation();
|
|
20363
|
+
let changedState = false;
|
|
20364
|
+
const operation = this._enqueue(async () => {
|
|
20365
|
+
try {
|
|
20366
|
+
return await this._runRedo((changed) => {
|
|
20367
|
+
changedState = changed;
|
|
20368
|
+
});
|
|
20369
|
+
} finally {
|
|
20370
|
+
this._settleScheduleMutation(admitted, changedState);
|
|
20371
|
+
}
|
|
19171
20372
|
});
|
|
19172
20373
|
void operation.then(
|
|
19173
20374
|
(changes) => {
|
|
@@ -19184,6 +20385,29 @@ var ScheduleCore = class {
|
|
|
19184
20385
|
);
|
|
19185
20386
|
return operation;
|
|
19186
20387
|
}
|
|
20388
|
+
async _runRedo(markChanged) {
|
|
20389
|
+
this.assertReady();
|
|
20390
|
+
const entry = this._undo.takeRedo();
|
|
20391
|
+
if (!entry) return null;
|
|
20392
|
+
applyRedo(this.coreRuntime.state, entry);
|
|
20393
|
+
this._recordScheduleMutation(false);
|
|
20394
|
+
markChanged(true);
|
|
20395
|
+
if (needsResync(entry)) this._resyncCustomIdTrackerFromModel();
|
|
20396
|
+
this._undo.pushUndo(entry);
|
|
20397
|
+
this._undo.resetCoalesce();
|
|
20398
|
+
const changes = buildInverseChangeSet(
|
|
20399
|
+
this.coreRuntime.state,
|
|
20400
|
+
entry,
|
|
20401
|
+
"after"
|
|
20402
|
+
);
|
|
20403
|
+
this._saveTracker.recordChanges(
|
|
20404
|
+
changes.activities,
|
|
20405
|
+
changes.links,
|
|
20406
|
+
(activityId) => this.coreRuntime.state.getActivity(activityId),
|
|
20407
|
+
(linkId) => this.coreRuntime.state.getLink(linkId)
|
|
20408
|
+
);
|
|
20409
|
+
return this._reapplyViewState(changes) ?? changes;
|
|
20410
|
+
}
|
|
19187
20411
|
canUndo() {
|
|
19188
20412
|
return this._undo.canUndo();
|
|
19189
20413
|
}
|
|
@@ -19224,10 +20448,17 @@ var ScheduleCore = class {
|
|
|
19224
20448
|
);
|
|
19225
20449
|
}
|
|
19226
20450
|
}
|
|
19227
|
-
_rollback() {
|
|
20451
|
+
_rollback(generatorSnapshot) {
|
|
19228
20452
|
try {
|
|
19229
20453
|
this.coreRuntime.state.restoreFromCapture();
|
|
19230
20454
|
this.coreRuntime.customIdTracker.rollbackCustomIdTransaction();
|
|
20455
|
+
if (generatorSnapshot) {
|
|
20456
|
+
this.coreRuntime.activityIdGenerator.restore(generatorSnapshot[0]);
|
|
20457
|
+
this.coreRuntime.linkIdGenerator.restore(generatorSnapshot[1]);
|
|
20458
|
+
this.coreRuntime.uniqueCorrelativeIdGenerator.restore(
|
|
20459
|
+
generatorSnapshot[2]
|
|
20460
|
+
);
|
|
20461
|
+
}
|
|
19231
20462
|
} catch (restoreError) {
|
|
19232
20463
|
this._status = SCHEDULE_CORE_STATUS.POISONED;
|
|
19233
20464
|
this.coreRuntime.reporter.error(
|
|
@@ -19247,18 +20478,6 @@ function toPublicDispatchResult(result) {
|
|
|
19247
20478
|
return publicResult;
|
|
19248
20479
|
}
|
|
19249
20480
|
|
|
19250
|
-
// src/boundary/save/link-changes.ts
|
|
19251
|
-
function checkNoUpdatedLinks(baseline, current) {
|
|
19252
|
-
const baselineById = new Map(
|
|
19253
|
-
baseline.map((link) => [String(link.id), link])
|
|
19254
|
-
);
|
|
19255
|
-
return current.filter((link) => {
|
|
19256
|
-
const base = baselineById.get(String(link.id));
|
|
19257
|
-
if (!base) return false;
|
|
19258
|
-
return base.lag !== link.lag || base.type !== link.type;
|
|
19259
|
-
});
|
|
19260
|
-
}
|
|
19261
|
-
|
|
19262
20481
|
// src/boundary/save/unsaved.ts
|
|
19263
20482
|
var getUnsavedActivities = (activities = []) => (activities ?? []).filter((activity) => !activity.proplannerId);
|
|
19264
20483
|
|
|
@@ -19358,6 +20577,8 @@ exports.LINK_TYPE_CODE = LINK_TYPE_CODE;
|
|
|
19358
20577
|
exports.NEW_ACTIVITY_DEFAULTS = NEW_ACTIVITY_DEFAULTS;
|
|
19359
20578
|
exports.REJECTION_REASON = REJECTION_REASON;
|
|
19360
20579
|
exports.ROOT_PARENT_ID = ROOT_PARENT_ID;
|
|
20580
|
+
exports.SCHEDULE_CORE_STATUS = SCHEDULE_CORE_STATUS;
|
|
20581
|
+
exports.STATUS = STATUS;
|
|
19361
20582
|
exports.ScheduleCore = ScheduleCore;
|
|
19362
20583
|
exports.WORK_TIME_DIRECTION = WORK_TIME_DIRECTION;
|
|
19363
20584
|
exports.checkNoUpdatedLinks = checkNoUpdatedLinks;
|