@outbuild-company/schedule-core 1.6.3 → 1.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cdn/schedule-core.global.js +1 -1
- package/dist/cdn/schedule-core.global.js.map +1 -1
- package/dist/index.cjs +1374 -542
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +134 -34
- package/dist/index.d.ts +134 -34
- package/dist/index.js +1374 -542
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -134,13 +134,19 @@ var REJECTION_REASON = {
|
|
|
134
134
|
ACTIVITY_NOT_FOUND: "activity_not_found",
|
|
135
135
|
PARENT_NOT_FOUND: "parent_not_found",
|
|
136
136
|
ACTIVITY_IDS_EMPTY: "activity_ids_empty",
|
|
137
|
+
NO_ACTIVITY_SELECTION: "no_activity_selection",
|
|
138
|
+
CLIPBOARD_EMPTY: "clipboard_empty",
|
|
139
|
+
ACTIVITY_PASTE_INVARIANT_FAILED: "activity_paste_invariant_failed",
|
|
137
140
|
ANCHOR_SIBLING_CONFLICT: "anchor_sibling_conflict",
|
|
138
141
|
ANCHOR_SIBLING_NOT_FOUND: "anchor_sibling_not_found",
|
|
139
142
|
ANCHOR_SIBLING_WRONG_PARENT: "anchor_sibling_wrong_parent",
|
|
140
143
|
PARENT_FROZEN_BY_SIR: "parent_frozen_by_sir",
|
|
144
|
+
SIR_REQUEST_NOT_PENDING: "sir_request_not_pending",
|
|
145
|
+
PREVIEW_UNSUPPORTED_ACTION: "preview_unsupported_action",
|
|
141
146
|
CANNOT_MOVE_INTO_OWN_DESCENDANT: "cannot_move_into_own_descendant",
|
|
142
147
|
INDENT_NO_ELIGIBLE_SIBLING: "indent_no_eligible_sibling",
|
|
143
148
|
OUTDENT_ROOT_LEVEL_NOT_EDITABLE: "outdent_root_level_not_editable",
|
|
149
|
+
HIERARCHY_CYCLE: "hierarchy_cycle",
|
|
144
150
|
INVALID_PROGRESS_VALUE: "invalid_progress_value_must_be_0_or_100"
|
|
145
151
|
};
|
|
146
152
|
|
|
@@ -269,8 +275,17 @@ function toDays(hours, hoursPerDay) {
|
|
|
269
275
|
return hours / hoursPerDay;
|
|
270
276
|
}
|
|
271
277
|
function calendarDaySpan(startDate, endDate) {
|
|
272
|
-
const
|
|
273
|
-
|
|
278
|
+
const startDay = Date.UTC(
|
|
279
|
+
startDate.getUTCFullYear(),
|
|
280
|
+
startDate.getUTCMonth(),
|
|
281
|
+
startDate.getUTCDate()
|
|
282
|
+
);
|
|
283
|
+
const endDay = Date.UTC(
|
|
284
|
+
endDate.getUTCFullYear(),
|
|
285
|
+
endDate.getUTCMonth(),
|
|
286
|
+
endDate.getUTCDate()
|
|
287
|
+
);
|
|
288
|
+
return Math.floor((endDay - startDay) / MILLISECONDS_PER_DAY) + 1;
|
|
274
289
|
}
|
|
275
290
|
var FIELD_REGISTRY = {
|
|
276
291
|
name: { valueKind: "string", extract: (activity) => activity.name },
|
|
@@ -285,7 +300,10 @@ var FIELD_REGISTRY = {
|
|
|
285
300
|
},
|
|
286
301
|
uniqueCorrelativeId: {
|
|
287
302
|
valueKind: "number",
|
|
288
|
-
extract: (activity) =>
|
|
303
|
+
extract: (activity) => {
|
|
304
|
+
const value = Number(activity.uniqueCorrelativeId);
|
|
305
|
+
return Number.isFinite(value) ? value : null;
|
|
306
|
+
}
|
|
289
307
|
},
|
|
290
308
|
progress: { valueKind: "number", extract: (activity) => activity.progress },
|
|
291
309
|
durationDays: {
|
|
@@ -294,7 +312,7 @@ var FIELD_REGISTRY = {
|
|
|
294
312
|
},
|
|
295
313
|
calendarDuration: {
|
|
296
314
|
valueKind: "number",
|
|
297
|
-
extract: (activity) => calendarDaySpan(activity.startDate, activity.endDate)
|
|
315
|
+
extract: (activity) => activity.type === "milestone" ? 0 : calendarDaySpan(activity.startDate, activity.endDate)
|
|
298
316
|
},
|
|
299
317
|
cost: { valueKind: "number", extract: (activity) => activity.cost },
|
|
300
318
|
usedCost: { valueKind: "number", extract: (activity) => activity.usedCost },
|
|
@@ -660,34 +678,102 @@ var OPERATORS_BY_KIND = {
|
|
|
660
678
|
boolean: /* @__PURE__ */ new Set(["someOf", "notSomeOf"]),
|
|
661
679
|
reference: /* @__PURE__ */ new Set(["someOf", "notSomeOf"])
|
|
662
680
|
};
|
|
663
|
-
function
|
|
681
|
+
function normalizeDateRange(value) {
|
|
682
|
+
if (value === void 0) return { ok: true, dateRange: void 0 };
|
|
683
|
+
if (typeof value !== "object" || value === null) {
|
|
684
|
+
return { ok: false, reason: "invalid_value" };
|
|
685
|
+
}
|
|
686
|
+
const { start, end } = value;
|
|
687
|
+
if (!(start instanceof Date) || !(end instanceof Date)) {
|
|
688
|
+
return { ok: false, reason: "invalid_value" };
|
|
689
|
+
}
|
|
690
|
+
const startTime = start.getTime();
|
|
691
|
+
const endTime = end.getTime();
|
|
692
|
+
if (!Number.isFinite(startTime) || !Number.isFinite(endTime) || startTime > endTime) {
|
|
693
|
+
return { ok: false, reason: "invalid_value" };
|
|
694
|
+
}
|
|
695
|
+
return {
|
|
696
|
+
ok: true,
|
|
697
|
+
dateRange: { start: new Date(startTime), end: new Date(endTime) }
|
|
698
|
+
};
|
|
699
|
+
}
|
|
700
|
+
function normalizeCriterion(criterion) {
|
|
664
701
|
const descriptor = getFieldDescriptor(criterion.field);
|
|
665
|
-
if (descriptor === null) return "unknown_field";
|
|
702
|
+
if (descriptor === null) return { ok: false, reason: "unknown_field" };
|
|
666
703
|
if (!OPERATORS_BY_KIND[descriptor.valueKind].has(criterion.operator)) {
|
|
667
|
-
return "unknown_operator";
|
|
704
|
+
return { ok: false, reason: "unknown_operator" };
|
|
705
|
+
}
|
|
706
|
+
if (descriptor.valueKind === "number") {
|
|
707
|
+
const value = normalizeNumberValue(criterion.value);
|
|
708
|
+
return value === null ? { ok: false, reason: "invalid_value" } : {
|
|
709
|
+
ok: true,
|
|
710
|
+
criterion: { ...criterion, value }
|
|
711
|
+
};
|
|
712
|
+
}
|
|
713
|
+
if (descriptor.valueKind === "date") {
|
|
714
|
+
const value = normalizeDateValue(criterion.value);
|
|
715
|
+
return value === null ? { ok: false, reason: "invalid_value" } : {
|
|
716
|
+
ok: true,
|
|
717
|
+
criterion: { ...criterion, value }
|
|
718
|
+
};
|
|
719
|
+
}
|
|
720
|
+
const rejection = validateValueForKind(criterion, descriptor.valueKind);
|
|
721
|
+
if (rejection !== null) return { ok: false, reason: rejection };
|
|
722
|
+
if (descriptor.valueKind === "boolean") {
|
|
723
|
+
return {
|
|
724
|
+
ok: true,
|
|
725
|
+
criterion: {
|
|
726
|
+
...criterion,
|
|
727
|
+
value: [...new Set(criterion.value)]
|
|
728
|
+
}
|
|
729
|
+
};
|
|
730
|
+
}
|
|
731
|
+
if (descriptor.valueKind === "id-array" || descriptor.valueKind === "enum" || descriptor.valueKind === "reference") {
|
|
732
|
+
return {
|
|
733
|
+
ok: true,
|
|
734
|
+
criterion: {
|
|
735
|
+
...criterion,
|
|
736
|
+
value: [
|
|
737
|
+
...new Set(
|
|
738
|
+
criterion.value.map(String)
|
|
739
|
+
)
|
|
740
|
+
]
|
|
741
|
+
}
|
|
742
|
+
};
|
|
668
743
|
}
|
|
669
|
-
return
|
|
744
|
+
return { ok: true, criterion };
|
|
670
745
|
}
|
|
671
746
|
function validateValueForKind(criterion, valueKind) {
|
|
672
747
|
const { value } = criterion;
|
|
673
|
-
if (valueKind === "
|
|
674
|
-
return Array.isArray(value) ? null : "invalid_value";
|
|
675
|
-
}
|
|
676
|
-
if (Array.isArray(value)) return "invalid_value";
|
|
677
|
-
if (valueKind === "number") {
|
|
678
|
-
return Number.isFinite(Number(value)) ? null : "invalid_value";
|
|
748
|
+
if (valueKind === "boolean") {
|
|
749
|
+
return Array.isArray(value) && value.every((item) => typeof item === "boolean") ? null : "invalid_value";
|
|
679
750
|
}
|
|
680
|
-
if (valueKind === "
|
|
681
|
-
return
|
|
751
|
+
if (valueKind === "id-array" || valueKind === "enum" || valueKind === "reference") {
|
|
752
|
+
return Array.isArray(value) && value.every(
|
|
753
|
+
(item) => typeof item === "string" || typeof item === "number"
|
|
754
|
+
) ? null : "invalid_value";
|
|
682
755
|
}
|
|
756
|
+
if (Array.isArray(value)) return "invalid_value";
|
|
683
757
|
return null;
|
|
684
758
|
}
|
|
685
|
-
function
|
|
686
|
-
if (value
|
|
687
|
-
if (typeof value
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
759
|
+
function normalizeNumberValue(value) {
|
|
760
|
+
if (typeof value === "string" && value.trim() === "") return null;
|
|
761
|
+
if (typeof value !== "string" && typeof value !== "number") return null;
|
|
762
|
+
const parsed2 = typeof value === "number" ? value : Number(value);
|
|
763
|
+
return Number.isFinite(parsed2) ? parsed2 : null;
|
|
764
|
+
}
|
|
765
|
+
function normalizeDateValue(value) {
|
|
766
|
+
let date2;
|
|
767
|
+
if (value instanceof Date) date2 = new Date(value.getTime());
|
|
768
|
+
else if (typeof value === "string") date2 = new Date(value);
|
|
769
|
+
else if (typeof value === "number") date2 = new Date(value);
|
|
770
|
+
else return null;
|
|
771
|
+
return Number.isFinite(date2.getTime()) ? date2 : null;
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
// src/shared/clone-domain-value.ts
|
|
775
|
+
function cloneDomainValue(value) {
|
|
776
|
+
return structuredClone(value);
|
|
691
777
|
}
|
|
692
778
|
|
|
693
779
|
// src/dispatch/filter.ts
|
|
@@ -717,15 +803,21 @@ function resolveFilter(adapter, filter, hoursPerDay) {
|
|
|
717
803
|
}
|
|
718
804
|
function dispatchFilterSet(action, deps) {
|
|
719
805
|
const { adapter, hoursPerDay } = deps;
|
|
806
|
+
const criteria = [];
|
|
720
807
|
for (const criterion of action.criteria) {
|
|
721
|
-
const
|
|
722
|
-
if (
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
808
|
+
const result = normalizeCriterion(criterion);
|
|
809
|
+
if (!result.ok) return { ok: false, reason: result.reason };
|
|
810
|
+
criteria.push(result.criterion);
|
|
811
|
+
}
|
|
812
|
+
const rangeResult = normalizeDateRange(action.dateRange);
|
|
813
|
+
if (!rangeResult.ok) return { ok: false, reason: rangeResult.reason };
|
|
814
|
+
const filter = cloneDomainValue(
|
|
815
|
+
rangeResult.dateRange === void 0 ? { criteria, logic: action.logic } : {
|
|
816
|
+
criteria,
|
|
817
|
+
logic: action.logic,
|
|
818
|
+
dateRange: rangeResult.dateRange
|
|
819
|
+
}
|
|
820
|
+
);
|
|
729
821
|
adapter.setActiveFilter(isEmptyFilter(filter) ? null : filter);
|
|
730
822
|
const { visibleIds, projection } = resolveFilter(
|
|
731
823
|
adapter,
|
|
@@ -870,7 +962,7 @@ function dispatchSortSet(action, deps) {
|
|
|
870
962
|
const rejection = validateRule(rule);
|
|
871
963
|
if (rejection !== null) return { ok: false, reason: rejection };
|
|
872
964
|
}
|
|
873
|
-
const order = action.rules.length === 0 ? null : { rules: action.rules };
|
|
965
|
+
const order = action.rules.length === 0 ? null : cloneDomainValue({ rules: action.rules });
|
|
874
966
|
adapter.setActiveOrder(order);
|
|
875
967
|
const changes = {
|
|
876
968
|
source: action,
|
|
@@ -1438,6 +1530,11 @@ function isDescendantOf(childId, ancestorId, adapter) {
|
|
|
1438
1530
|
}
|
|
1439
1531
|
return false;
|
|
1440
1532
|
}
|
|
1533
|
+
function isSummaryHierarchyLink(source, target, adapter) {
|
|
1534
|
+
const sourceIsSummary = isSummary2(source, adapter);
|
|
1535
|
+
const targetIsSummary = isSummary2(target, adapter);
|
|
1536
|
+
return targetIsSummary && isDescendantOf(source, target, adapter) || sourceIsSummary && isDescendantOf(target, source, adapter);
|
|
1537
|
+
}
|
|
1441
1538
|
function getLeafDescendants(parentId, adapter) {
|
|
1442
1539
|
const leaves = [];
|
|
1443
1540
|
const stack = [parentId];
|
|
@@ -2375,6 +2472,18 @@ var noopReporter = {
|
|
|
2375
2472
|
}
|
|
2376
2473
|
};
|
|
2377
2474
|
|
|
2475
|
+
// src/internal/state/calendar-duration.ts
|
|
2476
|
+
var CALENDAR_DAY_MS = 864e5;
|
|
2477
|
+
function computeCalendarDuration(activity) {
|
|
2478
|
+
if (activity.type === "milestone") return 0;
|
|
2479
|
+
const start = activity.startDate?.getTime();
|
|
2480
|
+
const end = activity.endDate?.getTime();
|
|
2481
|
+
if (start == null || end == null || Number.isNaN(start) || Number.isNaN(end)) {
|
|
2482
|
+
return null;
|
|
2483
|
+
}
|
|
2484
|
+
return Math.floor((end - start) / CALENDAR_DAY_MS) + 1;
|
|
2485
|
+
}
|
|
2486
|
+
|
|
2378
2487
|
// src/autoscheduler/integration/apply-results.ts
|
|
2379
2488
|
function applyResults(adapter, result) {
|
|
2380
2489
|
if (result.cancelled || result.updatedIds.length === 0) return;
|
|
@@ -2386,6 +2495,7 @@ function applyResults(adapter, result) {
|
|
|
2386
2495
|
if (!liveActivity) continue;
|
|
2387
2496
|
liveActivity.startDate = plan.startDate;
|
|
2388
2497
|
liveActivity.endDate = plan.endDate;
|
|
2498
|
+
liveActivity.calendarDuration = computeCalendarDuration(liveActivity);
|
|
2389
2499
|
adapter.updateActivity(id);
|
|
2390
2500
|
}
|
|
2391
2501
|
});
|
|
@@ -2452,13 +2562,253 @@ function hasPath(adjacency, from, to) {
|
|
|
2452
2562
|
return false;
|
|
2453
2563
|
}
|
|
2454
2564
|
|
|
2565
|
+
// src/internal/dependency-graph/strongly-connected-components.ts
|
|
2566
|
+
function circularComponents(edges) {
|
|
2567
|
+
const walk = buildWalkState(edges);
|
|
2568
|
+
for (const node of walk.adjacency.keys()) {
|
|
2569
|
+
if (!walk.records.has(node)) collectFromRoot(node, walk);
|
|
2570
|
+
}
|
|
2571
|
+
return walk.components;
|
|
2572
|
+
}
|
|
2573
|
+
function buildWalkState(edges) {
|
|
2574
|
+
const adjacency = /* @__PURE__ */ new Map();
|
|
2575
|
+
const selfLooped = /* @__PURE__ */ new Set();
|
|
2576
|
+
for (const edge of edges) {
|
|
2577
|
+
const source = String(edge.source);
|
|
2578
|
+
const target = String(edge.target);
|
|
2579
|
+
const sourceNeighbors = neighborsOf(adjacency, source);
|
|
2580
|
+
neighborsOf(adjacency, target);
|
|
2581
|
+
if (source === target) {
|
|
2582
|
+
selfLooped.add(source);
|
|
2583
|
+
continue;
|
|
2584
|
+
}
|
|
2585
|
+
sourceNeighbors.push(target);
|
|
2586
|
+
}
|
|
2587
|
+
return {
|
|
2588
|
+
adjacency,
|
|
2589
|
+
selfLooped,
|
|
2590
|
+
records: /* @__PURE__ */ new Map(),
|
|
2591
|
+
componentStack: [],
|
|
2592
|
+
nextIndex: 0,
|
|
2593
|
+
components: []
|
|
2594
|
+
};
|
|
2595
|
+
}
|
|
2596
|
+
function neighborsOf(adjacency, node) {
|
|
2597
|
+
const existing = adjacency.get(node);
|
|
2598
|
+
if (existing) return existing;
|
|
2599
|
+
const created = [];
|
|
2600
|
+
adjacency.set(node, created);
|
|
2601
|
+
return created;
|
|
2602
|
+
}
|
|
2603
|
+
function collectFromRoot(root, walk) {
|
|
2604
|
+
const frames = [];
|
|
2605
|
+
frames.push({ node: root, record: enterNode(root, walk), neighborCursor: 0 });
|
|
2606
|
+
while (frames.length > 0) {
|
|
2607
|
+
const frame = frames[frames.length - 1];
|
|
2608
|
+
if (frame === void 0) break;
|
|
2609
|
+
const neighbors = walk.adjacency.get(frame.node) ?? [];
|
|
2610
|
+
if (frame.neighborCursor < neighbors.length) {
|
|
2611
|
+
const neighbor = neighbors[frame.neighborCursor];
|
|
2612
|
+
frame.neighborCursor += 1;
|
|
2613
|
+
if (neighbor === void 0) continue;
|
|
2614
|
+
visitNeighbor(frame, neighbor, frames, walk);
|
|
2615
|
+
continue;
|
|
2616
|
+
}
|
|
2617
|
+
frames.pop();
|
|
2618
|
+
const parentFrame = frames[frames.length - 1];
|
|
2619
|
+
if (parentFrame && frame.record.lowLink < parentFrame.record.lowLink) {
|
|
2620
|
+
parentFrame.record.lowLink = frame.record.lowLink;
|
|
2621
|
+
}
|
|
2622
|
+
const isComponentRoot = frame.record.lowLink === frame.record.visitIndex;
|
|
2623
|
+
if (isComponentRoot) emitComponent(frame.node, walk);
|
|
2624
|
+
}
|
|
2625
|
+
}
|
|
2626
|
+
function visitNeighbor(frame, neighbor, frames, walk) {
|
|
2627
|
+
const neighborRecord = walk.records.get(neighbor);
|
|
2628
|
+
if (!neighborRecord) {
|
|
2629
|
+
frames.push({
|
|
2630
|
+
node: neighbor,
|
|
2631
|
+
record: enterNode(neighbor, walk),
|
|
2632
|
+
neighborCursor: 0
|
|
2633
|
+
});
|
|
2634
|
+
return;
|
|
2635
|
+
}
|
|
2636
|
+
const lowersCurrent = neighborRecord.onStack && neighborRecord.visitIndex < frame.record.lowLink;
|
|
2637
|
+
if (lowersCurrent) frame.record.lowLink = neighborRecord.visitIndex;
|
|
2638
|
+
}
|
|
2639
|
+
function enterNode(node, walk) {
|
|
2640
|
+
const record = {
|
|
2641
|
+
visitIndex: walk.nextIndex,
|
|
2642
|
+
lowLink: walk.nextIndex,
|
|
2643
|
+
onStack: true
|
|
2644
|
+
};
|
|
2645
|
+
walk.nextIndex += 1;
|
|
2646
|
+
walk.records.set(node, record);
|
|
2647
|
+
walk.componentStack.push({ node, record });
|
|
2648
|
+
return record;
|
|
2649
|
+
}
|
|
2650
|
+
function emitComponent(rootNode, walk) {
|
|
2651
|
+
const component = [];
|
|
2652
|
+
for (; ; ) {
|
|
2653
|
+
const entry = walk.componentStack.pop();
|
|
2654
|
+
if (entry === void 0) break;
|
|
2655
|
+
entry.record.onStack = false;
|
|
2656
|
+
component.push(entry.node);
|
|
2657
|
+
if (entry.node === rootNode) break;
|
|
2658
|
+
}
|
|
2659
|
+
const firstMember = component[0];
|
|
2660
|
+
const isSingleSelfLoop = component.length === 1 && firstMember !== void 0 && walk.selfLooped.has(firstMember);
|
|
2661
|
+
const isCircular = component.length > 1 || isSingleSelfLoop;
|
|
2662
|
+
if (isCircular) walk.components.push(component);
|
|
2663
|
+
}
|
|
2664
|
+
|
|
2665
|
+
// src/internal/dependency-graph/effective-graph.ts
|
|
2666
|
+
function projectEffectiveEdges(port, links, proposal) {
|
|
2667
|
+
const projectionPort = proposal ? new ReparentedPort(port, proposal) : port;
|
|
2668
|
+
return expandParentLinks([...links], projectionPort);
|
|
2669
|
+
}
|
|
2670
|
+
var ReparentedPort = class {
|
|
2671
|
+
constructor(base, proposal) {
|
|
2672
|
+
this.base = base;
|
|
2673
|
+
for (const [childId, proposedParent] of proposal.reparented) {
|
|
2674
|
+
const childKey = String(childId);
|
|
2675
|
+
const newParent = proposedParent === null || isRootParent(proposedParent) ? null : proposedParent;
|
|
2676
|
+
const oldParent = base.getActivity(childId)?.parentId ?? null;
|
|
2677
|
+
const oldKey = oldParent === null || isRootParent(oldParent) ? null : String(oldParent);
|
|
2678
|
+
const newKey = newParent === null ? null : String(newParent);
|
|
2679
|
+
if (oldKey === newKey) continue;
|
|
2680
|
+
this.reparented.set(childKey, newParent);
|
|
2681
|
+
if (oldKey !== null) {
|
|
2682
|
+
const removed = this.movedOutByParent.get(oldKey) ?? /* @__PURE__ */ new Set();
|
|
2683
|
+
removed.add(childKey);
|
|
2684
|
+
this.movedOutByParent.set(oldKey, removed);
|
|
2685
|
+
}
|
|
2686
|
+
if (newKey !== null) {
|
|
2687
|
+
const gained = this.movedInByParent.get(newKey) ?? [];
|
|
2688
|
+
gained.push(childId);
|
|
2689
|
+
this.movedInByParent.set(newKey, gained);
|
|
2690
|
+
}
|
|
2691
|
+
}
|
|
2692
|
+
}
|
|
2693
|
+
base;
|
|
2694
|
+
reparented = /* @__PURE__ */ new Map();
|
|
2695
|
+
movedOutByParent = /* @__PURE__ */ new Map();
|
|
2696
|
+
movedInByParent = /* @__PURE__ */ new Map();
|
|
2697
|
+
withOverride(activity) {
|
|
2698
|
+
if (!activity) return null;
|
|
2699
|
+
const override = this.reparented.get(String(activity.id));
|
|
2700
|
+
if (override === void 0) return activity;
|
|
2701
|
+
return { ...activity, parentId: override };
|
|
2702
|
+
}
|
|
2703
|
+
getActivity(activityId) {
|
|
2704
|
+
return this.withOverride(this.base.getActivity(activityId));
|
|
2705
|
+
}
|
|
2706
|
+
getLiveActivity(activityId) {
|
|
2707
|
+
return this.withOverride(this.base.getLiveActivity(activityId));
|
|
2708
|
+
}
|
|
2709
|
+
getAllActivities() {
|
|
2710
|
+
return this.base.getAllActivities().map((activity) => this.withOverride(activity) ?? activity);
|
|
2711
|
+
}
|
|
2712
|
+
getChildren(parentId) {
|
|
2713
|
+
const parentKey = String(parentId);
|
|
2714
|
+
const removed = this.movedOutByParent.get(parentKey);
|
|
2715
|
+
const baseChildren = this.base.getChildren(parentId);
|
|
2716
|
+
const kept = removed ? baseChildren.filter((childId) => !removed.has(String(childId))) : [...baseChildren];
|
|
2717
|
+
const gained = this.movedInByParent.get(parentKey);
|
|
2718
|
+
if (gained) kept.push(...gained);
|
|
2719
|
+
return kept;
|
|
2720
|
+
}
|
|
2721
|
+
getParent(activityId) {
|
|
2722
|
+
const override = this.reparented.get(String(activityId));
|
|
2723
|
+
if (override === void 0) return this.base.getParent(activityId);
|
|
2724
|
+
if (override === null) return null;
|
|
2725
|
+
return this.getActivity(override);
|
|
2726
|
+
}
|
|
2727
|
+
isChildOf(childId, parentId) {
|
|
2728
|
+
const targetKey = String(parentId);
|
|
2729
|
+
const visited = /* @__PURE__ */ new Set();
|
|
2730
|
+
let current = this.getActivity(childId);
|
|
2731
|
+
while (current) {
|
|
2732
|
+
const parent = current.parentId;
|
|
2733
|
+
if (parent === null || isRootParent(parent)) return false;
|
|
2734
|
+
const parentKey = String(parent);
|
|
2735
|
+
if (parentKey === targetKey) return true;
|
|
2736
|
+
if (visited.has(parentKey)) return false;
|
|
2737
|
+
visited.add(parentKey);
|
|
2738
|
+
current = this.getActivity(parent);
|
|
2739
|
+
}
|
|
2740
|
+
return false;
|
|
2741
|
+
}
|
|
2742
|
+
getAllLinks() {
|
|
2743
|
+
return this.base.getAllLinks();
|
|
2744
|
+
}
|
|
2745
|
+
getAllIds() {
|
|
2746
|
+
return this.base.getAllIds();
|
|
2747
|
+
}
|
|
2748
|
+
getLink(linkId) {
|
|
2749
|
+
return this.base.getLink(linkId);
|
|
2750
|
+
}
|
|
2751
|
+
activityExists(activityId) {
|
|
2752
|
+
return this.base.activityExists(activityId);
|
|
2753
|
+
}
|
|
2754
|
+
getOutgoingLinkIds(activityId) {
|
|
2755
|
+
return this.base.getOutgoingLinkIds(activityId);
|
|
2756
|
+
}
|
|
2757
|
+
getIncomingLinkIds(activityId) {
|
|
2758
|
+
return this.base.getIncomingLinkIds(activityId);
|
|
2759
|
+
}
|
|
2760
|
+
getClosestWorkTime(params) {
|
|
2761
|
+
return this.base.getClosestWorkTime(params);
|
|
2762
|
+
}
|
|
2763
|
+
calculateEndDate(params) {
|
|
2764
|
+
return this.base.calculateEndDate(params);
|
|
2765
|
+
}
|
|
2766
|
+
calculateDuration(params) {
|
|
2767
|
+
return this.base.calculateDuration(params);
|
|
2768
|
+
}
|
|
2769
|
+
batchUpdate(runBatch) {
|
|
2770
|
+
this.base.batchUpdate(runBatch);
|
|
2771
|
+
}
|
|
2772
|
+
updateActivity(activityId) {
|
|
2773
|
+
this.base.updateActivity(activityId);
|
|
2774
|
+
}
|
|
2775
|
+
getFlags() {
|
|
2776
|
+
return this.base.getFlags();
|
|
2777
|
+
}
|
|
2778
|
+
getProjectEnd() {
|
|
2779
|
+
return this.base.getProjectEnd();
|
|
2780
|
+
}
|
|
2781
|
+
};
|
|
2782
|
+
|
|
2783
|
+
// src/internal/dependency-graph/effective-graph-validation.ts
|
|
2784
|
+
function validateTopologyCommit(input) {
|
|
2785
|
+
const { port, currentLinks, proposedLinks, proposal } = input;
|
|
2786
|
+
const proposedEdges = projectEffectiveEdges(port, proposedLinks, proposal);
|
|
2787
|
+
if (!hasDirectedCycle(proposedEdges)) return { ok: true };
|
|
2788
|
+
const currentEdges = projectEffectiveEdges(port, currentLinks);
|
|
2789
|
+
const corruptBefore = corruptActivityIds(currentEdges);
|
|
2790
|
+
const corruptAfter = corruptActivityIds(proposedEdges);
|
|
2791
|
+
const newlyCyclicActivityIds = [...corruptAfter].filter(
|
|
2792
|
+
(activityId) => !corruptBefore.has(activityId)
|
|
2793
|
+
);
|
|
2794
|
+
if (newlyCyclicActivityIds.length === 0) return { ok: true };
|
|
2795
|
+
return { ok: false, newlyCyclicActivityIds };
|
|
2796
|
+
}
|
|
2797
|
+
function corruptActivityIds(edges) {
|
|
2798
|
+
const corrupt = /* @__PURE__ */ new Set();
|
|
2799
|
+
for (const component of circularComponents(edges)) {
|
|
2800
|
+
for (const activityId of component) corrupt.add(activityId);
|
|
2801
|
+
}
|
|
2802
|
+
return corrupt;
|
|
2803
|
+
}
|
|
2804
|
+
|
|
2455
2805
|
// src/autoscheduler/links/apply-link-operation.ts
|
|
2456
2806
|
function applyLinkOperation(op, deps) {
|
|
2457
2807
|
switch (op.kind) {
|
|
2458
2808
|
case "create":
|
|
2459
2809
|
return applyCreate(op, deps);
|
|
2460
2810
|
case "update":
|
|
2461
|
-
return
|
|
2811
|
+
return applyLinkUpdate(op, deps.port);
|
|
2462
2812
|
case "delete":
|
|
2463
2813
|
return applyDelete(op, deps);
|
|
2464
2814
|
}
|
|
@@ -2484,6 +2834,10 @@ function applyLinkCreates(links, deps) {
|
|
|
2484
2834
|
rejected = { applied: false, rejected: "activity-missing" };
|
|
2485
2835
|
break;
|
|
2486
2836
|
}
|
|
2837
|
+
if (isSummaryHierarchyLink(operation.source, operation.target, deps.port)) {
|
|
2838
|
+
rejected = { applied: false, rejected: "hierarchy-link" };
|
|
2839
|
+
break;
|
|
2840
|
+
}
|
|
2487
2841
|
const key = linkKey(operation);
|
|
2488
2842
|
const duplicateId = duplicateKeys.get(key);
|
|
2489
2843
|
if (duplicateId !== void 0) {
|
|
@@ -2509,6 +2863,24 @@ function applyLinkCreates(links, deps) {
|
|
|
2509
2863
|
return { applied: false, rejected: "cycle" };
|
|
2510
2864
|
}
|
|
2511
2865
|
if (rejected) return rejected;
|
|
2866
|
+
const proposedLinks = [
|
|
2867
|
+
...existingLinks,
|
|
2868
|
+
...links.map(({ operation, linkId }) => ({
|
|
2869
|
+
id: linkId,
|
|
2870
|
+
source: operation.source,
|
|
2871
|
+
target: operation.target,
|
|
2872
|
+
type: operation.type,
|
|
2873
|
+
lag: operation.lag
|
|
2874
|
+
}))
|
|
2875
|
+
];
|
|
2876
|
+
const effectiveVerdict = validateTopologyCommit({
|
|
2877
|
+
port: deps.port,
|
|
2878
|
+
currentLinks: existingLinks,
|
|
2879
|
+
proposedLinks
|
|
2880
|
+
});
|
|
2881
|
+
if (!effectiveVerdict.ok) {
|
|
2882
|
+
return { applied: false, rejected: "cycle" };
|
|
2883
|
+
}
|
|
2512
2884
|
for (const { operation, linkId } of links) {
|
|
2513
2885
|
const preserved = deps.preserved?.get(linkId);
|
|
2514
2886
|
deps.port.addLink({
|
|
@@ -2537,6 +2909,9 @@ function applyCreate(op, deps) {
|
|
|
2537
2909
|
if (!src || !tgt) {
|
|
2538
2910
|
return { applied: false, rejected: "activity-missing" };
|
|
2539
2911
|
}
|
|
2912
|
+
if (isSummaryHierarchyLink(op.source, op.target, port)) {
|
|
2913
|
+
return { applied: false, rejected: "hierarchy-link" };
|
|
2914
|
+
}
|
|
2540
2915
|
const existingLinks = port.getAllLinks();
|
|
2541
2916
|
for (const l of existingLinks) {
|
|
2542
2917
|
if (String(l.source) === String(op.source) && String(l.target) === String(op.target) && l.type === op.type) {
|
|
@@ -2558,11 +2933,18 @@ function applyCreate(op, deps) {
|
|
|
2558
2933
|
type: op.type,
|
|
2559
2934
|
lag: op.lag
|
|
2560
2935
|
};
|
|
2936
|
+
const effectiveVerdict = validateTopologyCommit({
|
|
2937
|
+
port,
|
|
2938
|
+
currentLinks: existingLinks,
|
|
2939
|
+
proposedLinks: [...existingLinks, link]
|
|
2940
|
+
});
|
|
2941
|
+
if (!effectiveVerdict.ok) {
|
|
2942
|
+
return { applied: false, rejected: "cycle" };
|
|
2943
|
+
}
|
|
2561
2944
|
port.addLink(link);
|
|
2562
2945
|
return { applied: true, linkId: id };
|
|
2563
2946
|
}
|
|
2564
|
-
function
|
|
2565
|
-
const { port } = deps;
|
|
2947
|
+
function applyLinkUpdate(op, port) {
|
|
2566
2948
|
const link = port.getLink(op.linkId);
|
|
2567
2949
|
if (!link) return { applied: false, rejected: "link-missing" };
|
|
2568
2950
|
if (op.lag !== void 0) port.setLinkField(op.linkId, "lag", op.lag);
|
|
@@ -2733,6 +3115,10 @@ function diffOutgoingLinks(input) {
|
|
|
2733
3115
|
}
|
|
2734
3116
|
|
|
2735
3117
|
// src/autoscheduler/links/lag-units.ts
|
|
3118
|
+
function roundLagDays(lagDays) {
|
|
3119
|
+
const rounded = Math.sign(lagDays) * Math.round(Math.abs(lagDays));
|
|
3120
|
+
return Object.is(rounded, -0) ? 0 : rounded;
|
|
3121
|
+
}
|
|
2736
3122
|
function lagDaysToHours(lagDays, hoursPerDay) {
|
|
2737
3123
|
return Math.sign(lagDays) * Math.round(Math.abs(lagDays) * hoursPerDay);
|
|
2738
3124
|
}
|
|
@@ -2995,10 +3381,7 @@ function adjustLinkLagOnTaskMove(targetId, adapter, preEditTarget) {
|
|
|
2995
3381
|
calendarApi
|
|
2996
3382
|
);
|
|
2997
3383
|
if (newLag === link.lag) continue;
|
|
2998
|
-
|
|
2999
|
-
{ kind: "update", linkId: link.id, lag: newLag },
|
|
3000
|
-
{ port: adapter }
|
|
3001
|
-
);
|
|
3384
|
+
applyLinkUpdate({ linkId: link.id, lag: newLag }, adapter);
|
|
3002
3385
|
}
|
|
3003
3386
|
}
|
|
3004
3387
|
|
|
@@ -3298,6 +3681,19 @@ function durationToHours(durationDays, hoursPerDay) {
|
|
|
3298
3681
|
return Math.round(Math.ceil(durationDays * hoursPerDay * 60) / 60);
|
|
3299
3682
|
}
|
|
3300
3683
|
|
|
3684
|
+
// src/columns/shared/error-codes.ts
|
|
3685
|
+
var PARSE_ERROR = {
|
|
3686
|
+
NOT_A_NUMBER: "not_a_number",
|
|
3687
|
+
// ISSUE-057 (CP6): la unidad de una duración se declara, no se adivina.
|
|
3688
|
+
MISSING_INPUT_UNIT: "missing_input_unit",
|
|
3689
|
+
// D-OWNER-5: una duración en días es entera; una fracción se rechaza sin
|
|
3690
|
+
// redondear (el redondeo silencioso era la puerta del bug de las 320 horas).
|
|
3691
|
+
FRACTIONAL_DAYS: "fractional_days"
|
|
3692
|
+
};
|
|
3693
|
+
var VALIDATION_REASON = {
|
|
3694
|
+
UNCHANGED: "unchanged",
|
|
3695
|
+
NEGATIVE_DURATION: "negative_duration"};
|
|
3696
|
+
|
|
3301
3697
|
// src/columns/end-date/setter.ts
|
|
3302
3698
|
var setEndDate = (value) => ({
|
|
3303
3699
|
endDate: value
|
|
@@ -3305,7 +3701,7 @@ var setEndDate = (value) => ({
|
|
|
3305
3701
|
|
|
3306
3702
|
// src/columns/shared/date-normalize.ts
|
|
3307
3703
|
function truncateToUtcDay(date2, tz) {
|
|
3308
|
-
|
|
3704
|
+
{
|
|
3309
3705
|
const parts = new Intl.DateTimeFormat("en-CA", {
|
|
3310
3706
|
timeZone: tz,
|
|
3311
3707
|
year: "numeric",
|
|
@@ -3315,9 +3711,6 @@ function truncateToUtcDay(date2, tz) {
|
|
|
3315
3711
|
const get = (kind) => Number(parts.find((p) => p.type === kind)?.value);
|
|
3316
3712
|
return new Date(Date.UTC(get("year"), get("month") - 1, get("day")));
|
|
3317
3713
|
}
|
|
3318
|
-
return new Date(
|
|
3319
|
-
Date.UTC(date2.getFullYear(), date2.getMonth(), date2.getDate())
|
|
3320
|
-
);
|
|
3321
3714
|
}
|
|
3322
3715
|
function endOfLocalDay(date2, tz) {
|
|
3323
3716
|
{
|
|
@@ -3327,11 +3720,11 @@ function endOfLocalDay(date2, tz) {
|
|
|
3327
3720
|
}
|
|
3328
3721
|
}
|
|
3329
3722
|
function nextWorkingDay(calendar, domainDay) {
|
|
3330
|
-
let cursor = truncateToUtcDay(domainDay);
|
|
3723
|
+
let cursor = truncateToUtcDay(domainDay, "UTC");
|
|
3331
3724
|
while (!calendar.isWorkTime(cursor, CALENDAR_UNIT.DAY)) {
|
|
3332
3725
|
cursor = new Date(cursor);
|
|
3333
3726
|
cursor.setUTCDate(cursor.getUTCDate() + 1);
|
|
3334
|
-
cursor = truncateToUtcDay(cursor);
|
|
3727
|
+
cursor = truncateToUtcDay(cursor, "UTC");
|
|
3335
3728
|
}
|
|
3336
3729
|
return cursor;
|
|
3337
3730
|
}
|
|
@@ -3369,11 +3762,12 @@ var DATELESS_CONSTRAINTS = /* @__PURE__ */ new Set([
|
|
|
3369
3762
|
]);
|
|
3370
3763
|
var IMPLIED_CONSTRAINT_ON_DATE_EDIT = "snet";
|
|
3371
3764
|
function normalizeConstraintDate(typedDay, calendar, constraintType) {
|
|
3765
|
+
const constraintDay = truncateToUtcDay(typedDay, "UTC");
|
|
3372
3766
|
if (!START_BASED_CONSTRAINTS2.has(constraintType) && !END_BASED_CONSTRAINTS.has(constraintType)) {
|
|
3373
|
-
return
|
|
3767
|
+
return constraintDay;
|
|
3374
3768
|
}
|
|
3375
|
-
const reference = START_BASED_CONSTRAINTS2.has(constraintType) ? shiftStart(calendar,
|
|
3376
|
-
const result = new Date(
|
|
3769
|
+
const reference = START_BASED_CONSTRAINTS2.has(constraintType) ? shiftStart(calendar, constraintDay) : shiftEnd(calendar, constraintDay);
|
|
3770
|
+
const result = new Date(constraintDay);
|
|
3377
3771
|
result.setUTCHours(
|
|
3378
3772
|
reference.getUTCHours(),
|
|
3379
3773
|
reference.getUTCMinutes(),
|
|
@@ -3401,10 +3795,6 @@ var POST_PROCESSOR = {
|
|
|
3401
3795
|
RECORD_LAST_START_DATE: "recordLastStartDate"
|
|
3402
3796
|
};
|
|
3403
3797
|
|
|
3404
|
-
// src/columns/shared/error-codes.ts
|
|
3405
|
-
var VALIDATION_REASON = {
|
|
3406
|
-
NEGATIVE_DURATION: "negative_duration"};
|
|
3407
|
-
|
|
3408
3798
|
// src/columns/shared/constraint-validation.ts
|
|
3409
3799
|
function checkConstraintViolation2(constraintType, constraintDate, projectedDate) {
|
|
3410
3800
|
return checkConstraintViolation(constraintType, constraintDate, projectedDate);
|
|
@@ -3429,10 +3819,19 @@ var durationPipeline = {
|
|
|
3429
3819
|
},
|
|
3430
3820
|
parseInput(rawValue, _activity, ctx) {
|
|
3431
3821
|
const numericValue = parseFloat(String(rawValue));
|
|
3432
|
-
if (Number.
|
|
3433
|
-
|
|
3434
|
-
|
|
3435
|
-
)
|
|
3822
|
+
if (!Number.isFinite(numericValue)) {
|
|
3823
|
+
return parseError(PARSE_ERROR.NOT_A_NUMBER);
|
|
3824
|
+
}
|
|
3825
|
+
if (ctx.inputUnit === void 0) {
|
|
3826
|
+
return parseError(PARSE_ERROR.MISSING_INPUT_UNIT);
|
|
3827
|
+
}
|
|
3828
|
+
if (ctx.inputUnit === "hours") {
|
|
3829
|
+
return parsed(numericValue);
|
|
3830
|
+
}
|
|
3831
|
+
if (!Number.isInteger(numericValue)) {
|
|
3832
|
+
return parseError(PARSE_ERROR.FRACTIONAL_DAYS);
|
|
3833
|
+
}
|
|
3834
|
+
return parsed(durationToHours(numericValue, ctx.hoursPerDay));
|
|
3436
3835
|
},
|
|
3437
3836
|
validate(_activity, oldValue, newValue) {
|
|
3438
3837
|
if (isNegativeDuration(newValue)) {
|
|
@@ -3881,17 +4280,7 @@ var startDatePipeline = {
|
|
|
3881
4280
|
transform(activity, newValue, ctx) {
|
|
3882
4281
|
const calendar = ctx.calendars.getCalendar(activity.calendarId);
|
|
3883
4282
|
if (!calendar) {
|
|
3884
|
-
|
|
3885
|
-
{
|
|
3886
|
-
startDate: newValue,
|
|
3887
|
-
...setConstraintTypeImplied(),
|
|
3888
|
-
...setConstraintDate(newValue)
|
|
3889
|
-
},
|
|
3890
|
-
{
|
|
3891
|
-
autoSchedule: true,
|
|
3892
|
-
postProcessors: ["adjustLinkLagOnTaskMove"]
|
|
3893
|
-
}
|
|
3894
|
-
);
|
|
4283
|
+
throw new Error("startDatePipeline: no calendar resolved (integrity)");
|
|
3895
4284
|
}
|
|
3896
4285
|
const finalStart = shiftStart(calendar, newValue);
|
|
3897
4286
|
const milestoneActivity = activity.type === "milestone";
|
|
@@ -3921,7 +4310,7 @@ function checkParentStartConstraint(activity, newDate, ctx) {
|
|
|
3921
4310
|
activity.id
|
|
3922
4311
|
);
|
|
3923
4312
|
if (!parentStartDate) return null;
|
|
3924
|
-
const parentDay = truncateToUtcDay(parentStartDate);
|
|
4313
|
+
const parentDay = truncateToUtcDay(parentStartDate, "UTC");
|
|
3925
4314
|
if (newDate.getTime() < parentDay.getTime()) {
|
|
3926
4315
|
return invalid("before_parent_start");
|
|
3927
4316
|
}
|
|
@@ -3969,7 +4358,6 @@ var endDatePipeline = {
|
|
|
3969
4358
|
const calendar = ctx.calendars.getCalendar(activity.calendarId);
|
|
3970
4359
|
if (!calendar) return parsed(day);
|
|
3971
4360
|
if (!calendar.isWorkTime(day, CALENDAR_UNIT.DAY)) {
|
|
3972
|
-
if (ctx.dateFormat.split(" ").length > 1) return parsed(day);
|
|
3973
4361
|
return parsed(
|
|
3974
4362
|
calendar.getClosestWorkTime({
|
|
3975
4363
|
direction: "past",
|
|
@@ -4152,7 +4540,12 @@ var constraintDatePipeline = {
|
|
|
4152
4540
|
transform(activity, newValue, ctx) {
|
|
4153
4541
|
const finalType = isUnconstrained(activity) ? IMPLIED_CONSTRAINT_ON_DATE_EDIT : effectiveConstraintType(activity);
|
|
4154
4542
|
const calendar = ctx.calendars.getCalendar(activity.calendarId);
|
|
4155
|
-
|
|
4543
|
+
if (!calendar) {
|
|
4544
|
+
throw new Error(
|
|
4545
|
+
"constraintDatePipeline: no calendar resolved (integrity)"
|
|
4546
|
+
);
|
|
4547
|
+
}
|
|
4548
|
+
const storedDate = normalizeConstraintDate(newValue, calendar, finalType);
|
|
4156
4549
|
const patch = buildConstraintDateExtraFields(activity, storedDate);
|
|
4157
4550
|
return fieldChanges(patch, {
|
|
4158
4551
|
autoSchedule: true
|
|
@@ -4452,6 +4845,29 @@ var COLUMN_WRITABLE_FIELDS = new Set(
|
|
|
4452
4845
|
COLUMN_WRITABLE_FIELD_LIST
|
|
4453
4846
|
);
|
|
4454
4847
|
|
|
4848
|
+
// src/constants/activity-properties.ts
|
|
4849
|
+
var ACTIVITY_PROPERTY = {
|
|
4850
|
+
PARENT: "parentId",
|
|
4851
|
+
TYPE: "type",
|
|
4852
|
+
START_DATE: "startDate",
|
|
4853
|
+
END_DATE: "endDate",
|
|
4854
|
+
CONSTRAINT_TYPE: "constraintType",
|
|
4855
|
+
CONSTRAINT_DATE: "constraintDate",
|
|
4856
|
+
PROGRESS: "progress",
|
|
4857
|
+
COST: "cost",
|
|
4858
|
+
USED_COST: "usedCost",
|
|
4859
|
+
REAL_COST: "realCost",
|
|
4860
|
+
WORK_HOURS: "workHours",
|
|
4861
|
+
REAL_WORK_HOURS: "realWorkHours",
|
|
4862
|
+
CORRELATIVE_ID: "correlativeId",
|
|
4863
|
+
CUSTOM_ID: "customId",
|
|
4864
|
+
NEW_ACTIVITIES_ARRAY: "newActivityIds",
|
|
4865
|
+
HAS_NEW_ACTIVITIES: "hasNewActivities",
|
|
4866
|
+
CALENDAR_DURATION: "calendarDuration"};
|
|
4867
|
+
var LINK_PROPERTY = {
|
|
4868
|
+
TYPE: "type",
|
|
4869
|
+
LAG: "lag"};
|
|
4870
|
+
|
|
4455
4871
|
// src/internal/state/dynamic-writes.ts
|
|
4456
4872
|
function setActivityFieldDynamic(state, activityId, field, value) {
|
|
4457
4873
|
state.setActivityFieldDynamic(activityId, field, value);
|
|
@@ -4503,11 +4919,6 @@ function cloneCriticalPath(value) {
|
|
|
4503
4919
|
};
|
|
4504
4920
|
}
|
|
4505
4921
|
|
|
4506
|
-
// src/shared/clone-domain-value.ts
|
|
4507
|
-
function cloneDomainValue(value) {
|
|
4508
|
-
return structuredClone(value);
|
|
4509
|
-
}
|
|
4510
|
-
|
|
4511
4922
|
// src/dispatch/shared/snapshots.ts
|
|
4512
4923
|
var YIELD_EVERY_N_ENTRIES = 200;
|
|
4513
4924
|
function collectTouchedIds(primary, changes) {
|
|
@@ -4619,6 +5030,7 @@ function diffActivity(before, after) {
|
|
|
4619
5030
|
const beforeRec = before ?? {};
|
|
4620
5031
|
const afterRec = after;
|
|
4621
5032
|
const keys = /* @__PURE__ */ new Set([...Object.keys(beforeRec), ...Object.keys(afterRec)]);
|
|
5033
|
+
keys.delete(ACTIVITY_PROPERTY.CALENDAR_DURATION);
|
|
4622
5034
|
for (const k of keys) {
|
|
4623
5035
|
if (Object.hasOwn(beforeRec, k) !== Object.hasOwn(afterRec, k) || !fieldValueEqual(beforeRec[k], afterRec[k])) {
|
|
4624
5036
|
fields[k] = {
|
|
@@ -9293,28 +9705,6 @@ async function applyCriticalPath(state, hoursPerDay, isCurrent = () => true) {
|
|
|
9293
9705
|
return true;
|
|
9294
9706
|
}
|
|
9295
9707
|
|
|
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
|
-
|
|
9318
9708
|
// src/propagations/upward/real-cost.ts
|
|
9319
9709
|
function emitRealCost(state) {
|
|
9320
9710
|
const rootIds = [];
|
|
@@ -9367,6 +9757,8 @@ var NON_SCHEDULING_INLINE_COLUMNS = /* @__PURE__ */ new Set([
|
|
|
9367
9757
|
COLUMN.TAGS
|
|
9368
9758
|
]);
|
|
9369
9759
|
var NON_SCHEDULING_KINDS = /* @__PURE__ */ new Set([
|
|
9760
|
+
"activity-clipboard-clear",
|
|
9761
|
+
"activity-copy",
|
|
9370
9762
|
"selection-toggle",
|
|
9371
9763
|
"selection-replace",
|
|
9372
9764
|
"visibility-set",
|
|
@@ -9380,6 +9772,8 @@ var NON_SCHEDULING_KINDS = /* @__PURE__ */ new Set([
|
|
|
9380
9772
|
"status-criteria-set"
|
|
9381
9773
|
]);
|
|
9382
9774
|
var PURE_VIEW_STATE_KINDS = /* @__PURE__ */ new Set([
|
|
9775
|
+
"activity-clipboard-clear",
|
|
9776
|
+
"activity-copy",
|
|
9383
9777
|
"selection-toggle",
|
|
9384
9778
|
"selection-replace",
|
|
9385
9779
|
"visibility-set",
|
|
@@ -9705,7 +10099,8 @@ async function assembleChangeSet(adapter, args) {
|
|
|
9705
10099
|
const insertedIds = collectInsertedIds(captured);
|
|
9706
10100
|
mergeDirtyBeforeImages(adapter, captured, insertedIds, before, allTouched);
|
|
9707
10101
|
}
|
|
9708
|
-
const
|
|
10102
|
+
const sirDetection = args.preview === true ? void 0 : args.sirDetection;
|
|
10103
|
+
const beforeDiffEffects = sirDetection === "before-diff" ? detectSirAutoReject(before, adapter, allTouched) : [];
|
|
9709
10104
|
const correlativeBefore = buildCorrelativeBeforeMap(
|
|
9710
10105
|
args.correlativeShifts,
|
|
9711
10106
|
allTouched
|
|
@@ -9716,7 +10111,7 @@ async function assembleChangeSet(adapter, args) {
|
|
|
9716
10111
|
allTouched,
|
|
9717
10112
|
correlativeBefore
|
|
9718
10113
|
);
|
|
9719
|
-
const effects =
|
|
10114
|
+
const effects = sirDetection === "after-diff" ? detectSirAutoReject(before, adapter, allTouched) : beforeDiffEffects;
|
|
9720
10115
|
const warnings = args.warnings ?? [];
|
|
9721
10116
|
return {
|
|
9722
10117
|
source: args.source,
|
|
@@ -10045,6 +10440,7 @@ async function dispatchInlineEdit(action, options, deps) {
|
|
|
10045
10440
|
touchedIds,
|
|
10046
10441
|
scheduledIds,
|
|
10047
10442
|
sirDetection: "before-diff",
|
|
10443
|
+
preview: options.preview,
|
|
10048
10444
|
links: linkChanges,
|
|
10049
10445
|
trackingEvents,
|
|
10050
10446
|
...constraintWarning ? { warnings: [constraintWarning] } : {},
|
|
@@ -10251,7 +10647,7 @@ async function dispatchInlineEditLinks(action, options, deps) {
|
|
|
10251
10647
|
const { specs: specsInDays } = parsePredecessorString(action.newValue);
|
|
10252
10648
|
const specs = specsInDays.map((spec) => ({
|
|
10253
10649
|
...spec,
|
|
10254
|
-
lag: lagDaysToHours(spec.lag, sector.hoursPerDay)
|
|
10650
|
+
lag: lagDaysToHours(roundLagDays(spec.lag), sector.hoursPerDay)
|
|
10255
10651
|
}));
|
|
10256
10652
|
const byCorrelative = /* @__PURE__ */ new Map();
|
|
10257
10653
|
adapter.forEachActivity((a) => {
|
|
@@ -10588,6 +10984,11 @@ function buildNewActivity(input, adapter) {
|
|
|
10588
10984
|
...base,
|
|
10589
10985
|
startDate,
|
|
10590
10986
|
endDate,
|
|
10987
|
+
calendarDuration: computeCalendarDuration({
|
|
10988
|
+
type: base.type,
|
|
10989
|
+
startDate,
|
|
10990
|
+
endDate
|
|
10991
|
+
}),
|
|
10591
10992
|
newActivityIds: [...base.newActivityIds],
|
|
10592
10993
|
pendingRequestIds: [...base.pendingRequestIds],
|
|
10593
10994
|
responsableIds: [...base.responsableIds],
|
|
@@ -11213,6 +11614,7 @@ async function dispatchActivityDelete(action, options, deps) {
|
|
|
11213
11614
|
touchedIds,
|
|
11214
11615
|
scheduledIds,
|
|
11215
11616
|
sirDetection: "after-diff",
|
|
11617
|
+
preview: options.preview,
|
|
11216
11618
|
links: buildLinkDeletions(beforeLinks),
|
|
11217
11619
|
trackingEvents: [trackingEvent],
|
|
11218
11620
|
hoursPerDay: sector.hoursPerDay,
|
|
@@ -11373,6 +11775,7 @@ async function dispatchActivityBatch(action, options, deps) {
|
|
|
11373
11775
|
links: buildLinkChangesForBatch(appliedLinks, beforeLinks, deps.adapter),
|
|
11374
11776
|
hoursPerDay: deps.sector.hoursPerDay,
|
|
11375
11777
|
correlativeShifts: existingCorrelativeShifts,
|
|
11778
|
+
preview: options.preview,
|
|
11376
11779
|
...action.mode === "replace" ? { sirDetection: "after-diff" } : {}
|
|
11377
11780
|
});
|
|
11378
11781
|
return {
|
|
@@ -11670,184 +12073,331 @@ function stringKeyedLinks(links) {
|
|
|
11670
12073
|
return new Map(Array.from(links, ([linkId, link]) => [String(linkId), link]));
|
|
11671
12074
|
}
|
|
11672
12075
|
|
|
11673
|
-
// src/dispatch/handlers/
|
|
11674
|
-
function
|
|
11675
|
-
|
|
11676
|
-
|
|
11677
|
-
|
|
11678
|
-
|
|
11679
|
-
}
|
|
11680
|
-
const parentId2 = sibling.parentId == null ? ROOT_PARENT_ID : String(sibling.parentId);
|
|
11681
|
-
return { ok: true, parentId: parentId2, firstAfter: destination.afterSiblingId };
|
|
12076
|
+
// src/dispatch/handlers/sir-sync.ts
|
|
12077
|
+
function dispatchSirSync(action, deps) {
|
|
12078
|
+
const { adapter } = deps;
|
|
12079
|
+
const id = action.activityId;
|
|
12080
|
+
const snapshot = adapter.getActivity(id);
|
|
12081
|
+
if (!snapshot) {
|
|
12082
|
+
return { ok: false, reason: REJECTION_REASON.ACTIVITY_NOT_FOUND };
|
|
11682
12083
|
}
|
|
11683
|
-
const
|
|
11684
|
-
const
|
|
11685
|
-
|
|
11686
|
-
);
|
|
11687
|
-
|
|
11688
|
-
|
|
11689
|
-
}
|
|
11690
|
-
|
|
11691
|
-
|
|
12084
|
+
const before = snapshot.pendingRequestIds ?? [];
|
|
12085
|
+
const after = action.pendingRequests.map((r) => r.id);
|
|
12086
|
+
adapter.setActivityField(action.activityId, "pendingRequestIds", after);
|
|
12087
|
+
const updated = adapter.getActivity(id);
|
|
12088
|
+
if (!updated) {
|
|
12089
|
+
return { ok: false, reason: REJECTION_REASON.ACTIVITY_NOT_FOUND };
|
|
12090
|
+
}
|
|
12091
|
+
const change = {
|
|
12092
|
+
id,
|
|
12093
|
+
kind: "updated",
|
|
12094
|
+
fields: { pendingRequestIds: { before, after } },
|
|
12095
|
+
// ISSUE-025: `after` must be the COMPLETE post-change entity; a partial
|
|
12096
|
+
// object crashes any consumer that projects the full snapshot.
|
|
12097
|
+
after: structuredCloneActivity(updated)
|
|
12098
|
+
};
|
|
11692
12099
|
return {
|
|
11693
|
-
|
|
11694
|
-
|
|
11695
|
-
|
|
11696
|
-
|
|
11697
|
-
|
|
11698
|
-
|
|
12100
|
+
ok: true,
|
|
12101
|
+
changes: {
|
|
12102
|
+
source: action,
|
|
12103
|
+
activities: [change],
|
|
12104
|
+
links: [],
|
|
12105
|
+
calendars: [],
|
|
12106
|
+
trackingEvents: []
|
|
12107
|
+
}
|
|
11699
12108
|
};
|
|
11700
12109
|
}
|
|
11701
|
-
|
|
12110
|
+
|
|
12111
|
+
// src/dispatch/handlers/bulk-edit.ts
|
|
12112
|
+
var CUSTOM_ID_COLUMN = COLUMN.CUSTOM_ID;
|
|
12113
|
+
async function dispatchBulkEdit(action, options, deps) {
|
|
11702
12114
|
const { adapter, scheduler, sector } = deps;
|
|
11703
|
-
const
|
|
11704
|
-
|
|
11705
|
-
|
|
11706
|
-
|
|
11707
|
-
|
|
11708
|
-
|
|
11709
|
-
|
|
11710
|
-
|
|
11711
|
-
|
|
11712
|
-
|
|
11713
|
-
|
|
11714
|
-
|
|
11715
|
-
|
|
11716
|
-
|
|
11717
|
-
|
|
11718
|
-
|
|
11719
|
-
|
|
11720
|
-
|
|
11721
|
-
|
|
11722
|
-
|
|
11723
|
-
|
|
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
|
-
}
|
|
12115
|
+
const editsIncludeDuration = action.edits.some(
|
|
12116
|
+
(edit) => edit.column === COLUMN.DURATION
|
|
12117
|
+
);
|
|
12118
|
+
if (editsIncludeDuration) {
|
|
12119
|
+
scheduler.invalidateExpandedLinksCache();
|
|
12120
|
+
}
|
|
12121
|
+
const verdicts = [];
|
|
12122
|
+
const beforeSnap = /* @__PURE__ */ new Map();
|
|
12123
|
+
const constraintPriorsByActivity = /* @__PURE__ */ new Map();
|
|
12124
|
+
const explicitDateEditActivities = /* @__PURE__ */ new Set();
|
|
12125
|
+
const touchedIds = /* @__PURE__ */ new Set();
|
|
12126
|
+
const trackingEvents = [];
|
|
12127
|
+
const linkChanges = [];
|
|
12128
|
+
const warnings = [];
|
|
12129
|
+
let needsAutoSchedule = false;
|
|
12130
|
+
for (const edit of action.edits) {
|
|
12131
|
+
const outcome = applyOneEdit(edit, deps);
|
|
12132
|
+
verdicts.push(outcome.verdict);
|
|
12133
|
+
if (!outcome.verdict.ok || !outcome.changes) continue;
|
|
12134
|
+
const constraintWarning = toConstraintWarning(
|
|
12135
|
+
edit.activityId,
|
|
12136
|
+
outcome.changes.constraintWarning
|
|
11741
12137
|
);
|
|
11742
|
-
if (
|
|
11743
|
-
|
|
11744
|
-
|
|
11745
|
-
|
|
11746
|
-
|
|
11747
|
-
|
|
11748
|
-
|
|
11749
|
-
|
|
11750
|
-
|
|
11751
|
-
|
|
11752
|
-
|
|
11753
|
-
)
|
|
12138
|
+
if (constraintWarning) warnings.push(constraintWarning);
|
|
12139
|
+
const editTouched = collectTouchedIds(edit.activityId, outcome.changes);
|
|
12140
|
+
mergeBeforeSnapshots(beforeSnap, editTouched, deps);
|
|
12141
|
+
for (const touchedId of editTouched) touchedIds.add(touchedId);
|
|
12142
|
+
recordConstraintPrior(constraintPriorsByActivity, edit, beforeSnap);
|
|
12143
|
+
if (isExplicitConstraintDateEdit(edit.column)) {
|
|
12144
|
+
explicitDateEditActivities.add(String(edit.activityId));
|
|
12145
|
+
}
|
|
12146
|
+
const beforeLinkLags = snapshotIncomingLinkLags(adapter, edit.activityId);
|
|
12147
|
+
const preEditSnapshot = snapshotSingleActivity(
|
|
12148
|
+
adapter,
|
|
12149
|
+
String(edit.activityId)
|
|
12150
|
+
);
|
|
12151
|
+
const customIdBeforeApply = readLiveCustomId(edit, deps);
|
|
12152
|
+
const changes = outcome.changes;
|
|
12153
|
+
applyFieldChanges(adapter, edit.activityId, changes);
|
|
12154
|
+
runPostProcessorsOnAdapter(
|
|
12155
|
+
edit.activityId,
|
|
12156
|
+
outcome.changes.postProcessors ?? [],
|
|
12157
|
+
adapter,
|
|
12158
|
+
preEditSnapshot
|
|
12159
|
+
);
|
|
12160
|
+
syncCustomIdTracker(edit, customIdBeforeApply, deps);
|
|
12161
|
+
linkChanges.push(
|
|
12162
|
+
...diffIncomingLinkLagChanges(beforeLinkLags, adapter, edit.activityId)
|
|
12163
|
+
);
|
|
12164
|
+
needsAutoSchedule ||= Boolean(outcome.changes.needsAutoSchedule);
|
|
12165
|
+
for (const trackingEvent of outcome.changes.trackingEvents ?? []) {
|
|
12166
|
+
trackingEvents.push({
|
|
12167
|
+
name: trackingEvent.name,
|
|
12168
|
+
properties: trackingEvent.properties ?? {}
|
|
12169
|
+
});
|
|
11754
12170
|
}
|
|
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
12171
|
}
|
|
11791
|
-
const
|
|
12172
|
+
const postMutationArgs = {
|
|
12173
|
+
action,
|
|
12174
|
+
autoscheduleFrom: needsAutoSchedule ? "roots" : null,
|
|
12175
|
+
recomputeParentsFrom: touchedIds,
|
|
12176
|
+
now: deps.now,
|
|
12177
|
+
options
|
|
12178
|
+
};
|
|
12179
|
+
const batchHasConstraintEdits = constraintPriorsByActivity.size > 0;
|
|
12180
|
+
const { scheduledIds } = await (batchHasConstraintEdits ? settleConstraintEdits(
|
|
11792
12181
|
{
|
|
11793
12182
|
adapter,
|
|
11794
12183
|
scheduler,
|
|
11795
|
-
sector
|
|
12184
|
+
sector,
|
|
11796
12185
|
defaultBaseCalendarId: deps.defaultBaseCalendarId
|
|
11797
12186
|
},
|
|
11798
12187
|
{
|
|
11799
|
-
|
|
11800
|
-
|
|
11801
|
-
|
|
11802
|
-
|
|
11803
|
-
|
|
12188
|
+
postMutationArgs,
|
|
12189
|
+
revertTargets: [...constraintPriorsByActivity].filter(
|
|
12190
|
+
([activityId]) => !explicitDateEditActivities.has(activityId)
|
|
12191
|
+
).map(([activityId, priorConstraintDate]) => ({
|
|
12192
|
+
activityId,
|
|
12193
|
+
priorConstraintDate
|
|
12194
|
+
})),
|
|
12195
|
+
revert: (activityId, priorConstraintDate) => revertNoOpConstraintEdit(
|
|
12196
|
+
activityId,
|
|
12197
|
+
priorConstraintDate,
|
|
12198
|
+
adapter,
|
|
12199
|
+
deps.ctx.calendars
|
|
12200
|
+
)
|
|
11804
12201
|
}
|
|
11805
|
-
)
|
|
11806
|
-
|
|
11807
|
-
|
|
11808
|
-
|
|
11809
|
-
|
|
11810
|
-
|
|
12202
|
+
) : runPostMutation(
|
|
12203
|
+
{
|
|
12204
|
+
adapter,
|
|
12205
|
+
scheduler,
|
|
12206
|
+
sector,
|
|
12207
|
+
defaultBaseCalendarId: deps.defaultBaseCalendarId
|
|
12208
|
+
},
|
|
12209
|
+
postMutationArgs
|
|
12210
|
+
));
|
|
11811
12211
|
return {
|
|
11812
12212
|
ok: true,
|
|
12213
|
+
verdicts,
|
|
11813
12214
|
changes: await assembleChangeSet(adapter, {
|
|
11814
12215
|
source: action,
|
|
11815
12216
|
beforeSnap,
|
|
11816
|
-
touchedIds
|
|
12217
|
+
touchedIds,
|
|
11817
12218
|
scheduledIds,
|
|
12219
|
+
sirDetection: "before-diff",
|
|
12220
|
+
preview: options.preview,
|
|
11818
12221
|
links: linkChanges,
|
|
12222
|
+
trackingEvents,
|
|
12223
|
+
warnings,
|
|
11819
12224
|
hoursPerDay: sector.hoursPerDay
|
|
11820
12225
|
})
|
|
11821
12226
|
};
|
|
11822
12227
|
}
|
|
12228
|
+
function applyOneEdit(edit, deps) {
|
|
12229
|
+
const { ctx } = deps;
|
|
12230
|
+
const activityId = edit.activityId;
|
|
12231
|
+
const pipeline = findPipelineForColumn(edit.column);
|
|
12232
|
+
if (!pipeline) {
|
|
12233
|
+
return {
|
|
12234
|
+
verdict: {
|
|
12235
|
+
activityId,
|
|
12236
|
+
ok: false,
|
|
12237
|
+
reason: REJECTION_REASON.NO_PIPELINE_FOR_COLUMN
|
|
12238
|
+
},
|
|
12239
|
+
changes: null
|
|
12240
|
+
};
|
|
12241
|
+
}
|
|
12242
|
+
const activitySnap = ctx.activityReader.getActivity(edit.activityId);
|
|
12243
|
+
if (!activitySnap) {
|
|
12244
|
+
return {
|
|
12245
|
+
verdict: {
|
|
12246
|
+
activityId,
|
|
12247
|
+
ok: false,
|
|
12248
|
+
reason: REJECTION_REASON.ACTIVITY_NOT_FOUND
|
|
12249
|
+
},
|
|
12250
|
+
changes: null
|
|
12251
|
+
};
|
|
12252
|
+
}
|
|
12253
|
+
const editGate = pipeline.canEdit(activitySnap, ctx.hierarchy);
|
|
12254
|
+
if (!editGate.allowed) {
|
|
12255
|
+
return {
|
|
12256
|
+
verdict: {
|
|
12257
|
+
activityId,
|
|
12258
|
+
ok: false,
|
|
12259
|
+
reason: editGate.reason ?? REJECTION_REASON.CANNOT_EDIT,
|
|
12260
|
+
...editGate.alertKey ? { alertKey: editGate.alertKey } : {}
|
|
12261
|
+
},
|
|
12262
|
+
changes: null
|
|
12263
|
+
};
|
|
12264
|
+
}
|
|
12265
|
+
const parsed2 = pipeline.parseInput(edit.newValue, activitySnap, ctx);
|
|
12266
|
+
if (!parsed2.ok) {
|
|
12267
|
+
return {
|
|
12268
|
+
verdict: {
|
|
12269
|
+
activityId,
|
|
12270
|
+
ok: false,
|
|
12271
|
+
reason: parsed2.reason ?? REJECTION_REASON.PARSE_ERROR
|
|
12272
|
+
},
|
|
12273
|
+
changes: null
|
|
12274
|
+
};
|
|
12275
|
+
}
|
|
12276
|
+
const oldValue = Reflect.get(activitySnap, pipeline.targetField);
|
|
12277
|
+
const validation = pipeline.validate(
|
|
12278
|
+
activitySnap,
|
|
12279
|
+
oldValue,
|
|
12280
|
+
parsed2.value,
|
|
12281
|
+
ctx
|
|
12282
|
+
);
|
|
12283
|
+
if (!validation.valid) {
|
|
12284
|
+
return {
|
|
12285
|
+
verdict: {
|
|
12286
|
+
activityId,
|
|
12287
|
+
ok: false,
|
|
12288
|
+
reason: validation.reason ?? REJECTION_REASON.INVALID,
|
|
12289
|
+
...validation.notification?.messageKey ? { alertKey: validation.notification.messageKey } : {}
|
|
12290
|
+
},
|
|
12291
|
+
changes: null
|
|
12292
|
+
};
|
|
12293
|
+
}
|
|
12294
|
+
if (edit.column === CUSTOM_ID_COLUMN && parsed2.value !== null) {
|
|
12295
|
+
const liveActivity = deps.adapter.getActivity(edit.activityId);
|
|
12296
|
+
const oldCustomIdRaw = liveActivity ? liveActivity.customId : null;
|
|
12297
|
+
const oldCustomId = typeof oldCustomIdRaw === "string" ? oldCustomIdRaw : null;
|
|
12298
|
+
if (deps.customIdTracker.isCustomIdInUse(String(parsed2.value), oldCustomId)) {
|
|
12299
|
+
return {
|
|
12300
|
+
verdict: {
|
|
12301
|
+
activityId,
|
|
12302
|
+
ok: false,
|
|
12303
|
+
reason: REJECTION_REASON.CUSTOM_ID_DUPLICATE
|
|
12304
|
+
},
|
|
12305
|
+
changes: null
|
|
12306
|
+
};
|
|
12307
|
+
}
|
|
12308
|
+
}
|
|
12309
|
+
const changes = pipeline.transform(
|
|
12310
|
+
activitySnap,
|
|
12311
|
+
parsed2.value,
|
|
12312
|
+
ctx,
|
|
12313
|
+
parsed2.raw
|
|
12314
|
+
);
|
|
12315
|
+
return { verdict: { activityId, ok: true }, changes };
|
|
12316
|
+
}
|
|
12317
|
+
function readLiveCustomId(edit, deps) {
|
|
12318
|
+
if (edit.column !== CUSTOM_ID_COLUMN) return null;
|
|
12319
|
+
const liveActivity = deps.adapter.getActivity(edit.activityId);
|
|
12320
|
+
const rawValue = liveActivity ? liveActivity.customId : null;
|
|
12321
|
+
return typeof rawValue === "string" ? rawValue : null;
|
|
12322
|
+
}
|
|
12323
|
+
function syncCustomIdTracker(edit, oldCustomId, deps) {
|
|
12324
|
+
if (edit.column !== CUSTOM_ID_COLUMN) return;
|
|
12325
|
+
const liveActivity = deps.adapter.getActivity(edit.activityId);
|
|
12326
|
+
const rawValue = liveActivity ? liveActivity.customId : null;
|
|
12327
|
+
const newCustomId = typeof rawValue === "string" ? rawValue : null;
|
|
12328
|
+
if (oldCustomId !== newCustomId) {
|
|
12329
|
+
deps.customIdTracker.trackCustomIdChange(oldCustomId, newCustomId);
|
|
12330
|
+
}
|
|
12331
|
+
}
|
|
12332
|
+
function recordConstraintPrior(priors, edit, beforeSnap) {
|
|
12333
|
+
if (!isConstraintEditColumn(edit.column)) return;
|
|
12334
|
+
const activityKey = String(edit.activityId);
|
|
12335
|
+
if (priors.has(activityKey)) return;
|
|
12336
|
+
const preBatchSnapshot = beforeSnap.get(activityKey);
|
|
12337
|
+
if (!preBatchSnapshot) return;
|
|
12338
|
+
priors.set(activityKey, preBatchSnapshot.constraintDate ?? null);
|
|
12339
|
+
}
|
|
12340
|
+
function mergeBeforeSnapshots(beforeSnap, ids, deps) {
|
|
12341
|
+
const missing = /* @__PURE__ */ new Set();
|
|
12342
|
+
for (const candidateId of ids) {
|
|
12343
|
+
if (!beforeSnap.has(candidateId)) missing.add(candidateId);
|
|
12344
|
+
}
|
|
12345
|
+
if (missing.size === 0) return;
|
|
12346
|
+
for (const missingId of missing) {
|
|
12347
|
+
const liveActivity = deps.adapter.getActivity(missingId);
|
|
12348
|
+
if (liveActivity) {
|
|
12349
|
+
beforeSnap.set(missingId, structuredCloneActivity(liveActivity));
|
|
12350
|
+
}
|
|
12351
|
+
}
|
|
12352
|
+
}
|
|
11823
12353
|
|
|
11824
|
-
// src/dispatch/handlers/sir-
|
|
11825
|
-
function
|
|
12354
|
+
// src/dispatch/handlers/sir-approve.ts
|
|
12355
|
+
async function dispatchSirApprove(action, options, deps) {
|
|
11826
12356
|
const { adapter } = deps;
|
|
11827
|
-
const
|
|
11828
|
-
const snapshot = adapter.getActivity(id);
|
|
12357
|
+
const snapshot = adapter.getActivity(action.activityId);
|
|
11829
12358
|
if (!snapshot) {
|
|
11830
12359
|
return { ok: false, reason: REJECTION_REASON.ACTIVITY_NOT_FOUND };
|
|
11831
12360
|
}
|
|
11832
|
-
const
|
|
11833
|
-
|
|
11834
|
-
|
|
11835
|
-
|
|
11836
|
-
|
|
11837
|
-
|
|
11838
|
-
|
|
11839
|
-
|
|
11840
|
-
|
|
11841
|
-
|
|
11842
|
-
|
|
11843
|
-
|
|
11844
|
-
|
|
11845
|
-
|
|
11846
|
-
|
|
11847
|
-
|
|
11848
|
-
|
|
11849
|
-
|
|
11850
|
-
|
|
12361
|
+
const pending = snapshot.pendingRequestIds ?? [];
|
|
12362
|
+
if (!pending.includes(action.approvedRequestId)) {
|
|
12363
|
+
return { ok: false, reason: REJECTION_REASON.SIR_REQUEST_NOT_PENDING };
|
|
12364
|
+
}
|
|
12365
|
+
const remaining = pending.filter((id) => id !== action.approvedRequestId);
|
|
12366
|
+
adapter.setActivityField(action.activityId, "pendingRequestIds", remaining);
|
|
12367
|
+
const result = await dispatchBulkEdit(
|
|
12368
|
+
{
|
|
12369
|
+
kind: "bulk-edit",
|
|
12370
|
+
edits: [
|
|
12371
|
+
{
|
|
12372
|
+
activityId: action.activityId,
|
|
12373
|
+
column: COLUMN.START_DATE,
|
|
12374
|
+
newValue: action.startDate
|
|
12375
|
+
},
|
|
12376
|
+
{
|
|
12377
|
+
activityId: action.activityId,
|
|
12378
|
+
column: COLUMN.END_DATE,
|
|
12379
|
+
newValue: action.endDate
|
|
12380
|
+
},
|
|
12381
|
+
// asap ÚLTIMO: gana al SNET implícito (paridad legacy).
|
|
12382
|
+
{
|
|
12383
|
+
activityId: action.activityId,
|
|
12384
|
+
column: COLUMN.CONSTRAINT_TYPE,
|
|
12385
|
+
newValue: "asap"
|
|
12386
|
+
}
|
|
12387
|
+
],
|
|
12388
|
+
eventSource: action.eventSource
|
|
12389
|
+
},
|
|
12390
|
+
options,
|
|
12391
|
+
deps
|
|
12392
|
+
);
|
|
12393
|
+
if (!result.ok) return result;
|
|
12394
|
+
const hardFailure = "verdicts" in result ? result.verdicts.find(
|
|
12395
|
+
(verdict) => !verdict.ok && verdict.reason !== VALIDATION_REASON.UNCHANGED
|
|
12396
|
+
) : void 0;
|
|
12397
|
+
if (hardFailure) {
|
|
12398
|
+
return { ok: false, reason: hardFailure.reason };
|
|
12399
|
+
}
|
|
12400
|
+
return { ...result, changes: { ...result.changes, source: action } };
|
|
11851
12401
|
}
|
|
11852
12402
|
|
|
11853
12403
|
// src/dispatch/handlers/activity-lookahead-sync.ts
|
|
@@ -11933,7 +12483,38 @@ function isAncestor(descendantId, candidateAncestorId, adapter) {
|
|
|
11933
12483
|
if (parentKey === candidateAncestorId) return true;
|
|
11934
12484
|
cur = parentKey;
|
|
11935
12485
|
}
|
|
11936
|
-
return false;
|
|
12486
|
+
return false;
|
|
12487
|
+
}
|
|
12488
|
+
|
|
12489
|
+
// src/dispatch/shared/hierarchy-cycle-guard.ts
|
|
12490
|
+
function reparentClosesEffectiveCycle(adapter, reparented) {
|
|
12491
|
+
const links = adapter.getAllLinks();
|
|
12492
|
+
const verdict = validateTopologyCommit({
|
|
12493
|
+
port: adapter,
|
|
12494
|
+
currentLinks: links,
|
|
12495
|
+
proposedLinks: links,
|
|
12496
|
+
proposal: { reparented }
|
|
12497
|
+
});
|
|
12498
|
+
return !verdict.ok;
|
|
12499
|
+
}
|
|
12500
|
+
function collectCyclicReparents(planned, adapter) {
|
|
12501
|
+
const cyclic = /* @__PURE__ */ new Set();
|
|
12502
|
+
if (planned.length === 0) return cyclic;
|
|
12503
|
+
const combined = new Map(
|
|
12504
|
+
planned.map((reparent) => [reparent.activityId, reparent.newParentId])
|
|
12505
|
+
);
|
|
12506
|
+
if (!reparentClosesEffectiveCycle(adapter, combined)) return cyclic;
|
|
12507
|
+
const accepted = /* @__PURE__ */ new Map();
|
|
12508
|
+
for (const reparent of planned) {
|
|
12509
|
+
const trial = new Map(accepted);
|
|
12510
|
+
trial.set(reparent.activityId, reparent.newParentId);
|
|
12511
|
+
if (reparentClosesEffectiveCycle(adapter, trial)) {
|
|
12512
|
+
cyclic.add(reparent.activityId);
|
|
12513
|
+
continue;
|
|
12514
|
+
}
|
|
12515
|
+
accepted.set(reparent.activityId, reparent.newParentId);
|
|
12516
|
+
}
|
|
12517
|
+
return cyclic;
|
|
11937
12518
|
}
|
|
11938
12519
|
|
|
11939
12520
|
// src/dispatch/handlers/activity-move.ts
|
|
@@ -11977,6 +12558,17 @@ async function dispatchActivityMove(action, options, deps) {
|
|
|
11977
12558
|
const oldParentKey = normalizeParentKey(oldParent);
|
|
11978
12559
|
const newParentKey = String(action.parentId);
|
|
11979
12560
|
const parentChanged = oldParentKey !== newParentKey;
|
|
12561
|
+
if (parentChanged) {
|
|
12562
|
+
const reparented = /* @__PURE__ */ new Map([
|
|
12563
|
+
[
|
|
12564
|
+
action.activityId,
|
|
12565
|
+
newParentIsRoot ? null : action.parentId
|
|
12566
|
+
]
|
|
12567
|
+
]);
|
|
12568
|
+
if (reparentClosesEffectiveCycle(adapter, reparented)) {
|
|
12569
|
+
return { ok: false, reason: REJECTION_REASON.HIERARCHY_CYCLE };
|
|
12570
|
+
}
|
|
12571
|
+
}
|
|
11980
12572
|
const touched = /* @__PURE__ */ new Set([action.activityId]);
|
|
11981
12573
|
if (parentChanged) {
|
|
11982
12574
|
if (oldParentKey !== ROOT_PARENT_ID) touched.add(oldParentKey);
|
|
@@ -12128,27 +12720,21 @@ async function dispatchActivityMove(action, options, deps) {
|
|
|
12128
12720
|
touchedIds: touched,
|
|
12129
12721
|
scheduledIds,
|
|
12130
12722
|
sirDetection: "before-diff",
|
|
12723
|
+
preview: options.preview,
|
|
12131
12724
|
trackingEvents: [trackingEvent],
|
|
12132
12725
|
hoursPerDay: sector.hoursPerDay
|
|
12133
12726
|
})
|
|
12134
12727
|
};
|
|
12135
12728
|
}
|
|
12136
12729
|
|
|
12137
|
-
// src/dispatch/
|
|
12138
|
-
|
|
12139
|
-
const
|
|
12140
|
-
scheduler.invalidateAllCaches();
|
|
12141
|
-
if (action.activityIds.length === 0) {
|
|
12142
|
-
return { ok: false, reason: REJECTION_REASON.ACTIVITY_IDS_EMPTY };
|
|
12143
|
-
}
|
|
12144
|
-
const selectedSet = new Set(action.activityIds);
|
|
12145
|
-
const touched = /* @__PURE__ */ new Set();
|
|
12730
|
+
// src/dispatch/shared/indent-planning.ts
|
|
12731
|
+
function planIndentMoves(activityIds, adapter) {
|
|
12732
|
+
const selectedSet = new Set(activityIds);
|
|
12146
12733
|
const failed = [];
|
|
12147
|
-
const succeededIds = [];
|
|
12148
12734
|
const moves = [];
|
|
12149
12735
|
const plannedIds = /* @__PURE__ */ new Set();
|
|
12150
12736
|
const blockingSet = new Set(selectedSet);
|
|
12151
|
-
const sortedIds = [...
|
|
12737
|
+
const sortedIds = [...activityIds].sort((a, b) => {
|
|
12152
12738
|
const aSnap = adapter.getActivity(a);
|
|
12153
12739
|
const bSnap = adapter.getActivity(b);
|
|
12154
12740
|
const ac = aSnap ? readCorrelativeId(aSnap) : NOT_SET_SORT_VALUE2;
|
|
@@ -12183,12 +12769,43 @@ async function dispatchActivityIndent(action, options, deps) {
|
|
|
12183
12769
|
const oldParentId = isRootParent(oldParentRaw) ? ROOT_PARENT_ID : String(oldParentRaw);
|
|
12184
12770
|
moves.push({ activityId: id, newParentId: targetParentId, oldParentId });
|
|
12185
12771
|
plannedIds.add(id);
|
|
12186
|
-
touched.add(id);
|
|
12187
|
-
touched.add(targetParentId);
|
|
12188
|
-
if (oldParentId !== ROOT_PARENT_ID) touched.add(oldParentId);
|
|
12189
12772
|
}
|
|
12773
|
+
const cyclicMoveIds = collectCyclicReparents(moves, adapter);
|
|
12774
|
+
for (const cyclicId of cyclicMoveIds) {
|
|
12775
|
+
failed.push({
|
|
12776
|
+
activityId: cyclicId,
|
|
12777
|
+
reason: REJECTION_REASON.HIERARCHY_CYCLE
|
|
12778
|
+
});
|
|
12779
|
+
}
|
|
12780
|
+
const allowedMoves = moves.filter(
|
|
12781
|
+
(move) => !cyclicMoveIds.has(move.activityId)
|
|
12782
|
+
);
|
|
12783
|
+
return { moves, cyclicMoveIds, allowedMoves, failed };
|
|
12784
|
+
}
|
|
12785
|
+
|
|
12786
|
+
// src/dispatch/handlers/activity-indent.ts
|
|
12787
|
+
async function dispatchActivityIndent(action, options, deps) {
|
|
12788
|
+
const { adapter, scheduler, sector } = deps;
|
|
12789
|
+
scheduler.invalidateAllCaches();
|
|
12790
|
+
if (action.activityIds.length === 0) {
|
|
12791
|
+
return { ok: false, reason: REJECTION_REASON.ACTIVITY_IDS_EMPTY };
|
|
12792
|
+
}
|
|
12793
|
+
const touched = /* @__PURE__ */ new Set();
|
|
12794
|
+
const succeededIds = [];
|
|
12795
|
+
const plan = planIndentMoves(action.activityIds, adapter);
|
|
12796
|
+
const failed = [
|
|
12797
|
+
...plan.failed
|
|
12798
|
+
];
|
|
12799
|
+
for (const move of plan.moves) {
|
|
12800
|
+
touched.add(String(move.activityId));
|
|
12801
|
+
touched.add(String(move.newParentId));
|
|
12802
|
+
if (move.oldParentId !== ROOT_PARENT_ID) {
|
|
12803
|
+
touched.add(String(move.oldParentId));
|
|
12804
|
+
}
|
|
12805
|
+
}
|
|
12806
|
+
const allowedMoves = plan.allowedMoves;
|
|
12190
12807
|
const beforeSnap = await snapshotActivities(adapter, touched);
|
|
12191
|
-
for (const { activityId, newParentId } of
|
|
12808
|
+
for (const { activityId, newParentId } of allowedMoves) {
|
|
12192
12809
|
adapter.setActivityField(activityId, ACTIVITY_PROPERTY.PARENT, newParentId);
|
|
12193
12810
|
adapter.setActivityField(
|
|
12194
12811
|
activityId,
|
|
@@ -12198,7 +12815,7 @@ async function dispatchActivityIndent(action, options, deps) {
|
|
|
12198
12815
|
succeededIds.push(activityId);
|
|
12199
12816
|
}
|
|
12200
12817
|
const promotedParents = /* @__PURE__ */ new Set();
|
|
12201
|
-
for (const { activityId, newParentId } of
|
|
12818
|
+
for (const { activityId, newParentId } of allowedMoves) {
|
|
12202
12819
|
if (promotedParents.has(newParentId)) continue;
|
|
12203
12820
|
promotedParents.add(newParentId);
|
|
12204
12821
|
const newParent = adapter.getActivity(newParentId);
|
|
@@ -12250,7 +12867,7 @@ async function dispatchActivityIndent(action, options, deps) {
|
|
|
12250
12867
|
releaseClearedCustomId(oldNewParentCustomId, deps.customIdTracker);
|
|
12251
12868
|
}
|
|
12252
12869
|
const indentAffectedParents = /* @__PURE__ */ new Set();
|
|
12253
|
-
for (const m of
|
|
12870
|
+
for (const m of allowedMoves) {
|
|
12254
12871
|
if (m.oldParentId !== ROOT_PARENT_ID)
|
|
12255
12872
|
indentAffectedParents.add(m.oldParentId);
|
|
12256
12873
|
indentAffectedParents.add(m.newParentId);
|
|
@@ -12411,8 +13028,24 @@ async function dispatchActivityOutdent(action, options, deps) {
|
|
|
12411
13028
|
if (grandparentKey !== ROOT_PARENT_ID) touched.add(String(grandparentKey));
|
|
12412
13029
|
oldParents.add(oldParentKey);
|
|
12413
13030
|
}
|
|
13031
|
+
const cyclicPlanIds = collectCyclicReparents(
|
|
13032
|
+
planned.map((plan) => ({
|
|
13033
|
+
activityId: plan.activityId,
|
|
13034
|
+
newParentId: plan.grandparentKey
|
|
13035
|
+
})),
|
|
13036
|
+
adapter
|
|
13037
|
+
);
|
|
13038
|
+
for (const cyclicId of cyclicPlanIds) {
|
|
13039
|
+
failed.push({
|
|
13040
|
+
activityId: cyclicId,
|
|
13041
|
+
reason: REJECTION_REASON.HIERARCHY_CYCLE
|
|
13042
|
+
});
|
|
13043
|
+
}
|
|
13044
|
+
const allowedPlans = planned.filter(
|
|
13045
|
+
(plan) => !cyclicPlanIds.has(plan.activityId)
|
|
13046
|
+
);
|
|
12414
13047
|
const beforeSnap = await snapshotActivities(adapter, touched);
|
|
12415
|
-
for (const plan of
|
|
13048
|
+
for (const plan of allowedPlans) {
|
|
12416
13049
|
adapter.setActivityField(
|
|
12417
13050
|
plan.activityId,
|
|
12418
13051
|
ACTIVITY_PROPERTY.PARENT,
|
|
@@ -12446,7 +13079,7 @@ async function dispatchActivityOutdent(action, options, deps) {
|
|
|
12446
13079
|
task: parentActivity
|
|
12447
13080
|
}),
|
|
12448
13081
|
idsRemoved: new Set(
|
|
12449
|
-
|
|
13082
|
+
allowedPlans.filter((plan) => plan.oldParentKey === oldParentKey).map((plan) => plan.activityId)
|
|
12450
13083
|
)
|
|
12451
13084
|
});
|
|
12452
13085
|
if (!demotion) {
|
|
@@ -12472,7 +13105,7 @@ async function dispatchActivityOutdent(action, options, deps) {
|
|
|
12472
13105
|
for (const oldParentKey of oldParents) {
|
|
12473
13106
|
outdentAffectedParents.add(String(oldParentKey));
|
|
12474
13107
|
}
|
|
12475
|
-
for (const plan of
|
|
13108
|
+
for (const plan of allowedPlans) {
|
|
12476
13109
|
if (plan.grandparentKey !== ROOT_PARENT_ID) {
|
|
12477
13110
|
outdentAffectedParents.add(String(plan.grandparentKey));
|
|
12478
13111
|
}
|
|
@@ -12560,6 +13193,17 @@ async function dispatchActivitySetProgress(action, options, deps) {
|
|
|
12560
13193
|
"[ScheduleCore] progressPipeline not registered \u2014 cannot dispatch activity-set-progress"
|
|
12561
13194
|
);
|
|
12562
13195
|
}
|
|
13196
|
+
const editGate = pipeline.canEdit(activitySnap, ctx.hierarchy);
|
|
13197
|
+
if (!editGate.allowed) {
|
|
13198
|
+
const declaredButtonOverride = action.origin === "action-button" && (editGate.reason === "has_lookahead_tasks" || editGate.reason === "summary");
|
|
13199
|
+
if (!declaredButtonOverride) {
|
|
13200
|
+
return {
|
|
13201
|
+
ok: false,
|
|
13202
|
+
reason: editGate.reason,
|
|
13203
|
+
...editGate.alertKey ? { alertKey: editGate.alertKey } : {}
|
|
13204
|
+
};
|
|
13205
|
+
}
|
|
13206
|
+
}
|
|
12563
13207
|
const changes = pipeline.transform(
|
|
12564
13208
|
activitySnap,
|
|
12565
13209
|
action.newValue,
|
|
@@ -12618,11 +13262,11 @@ async function dispatchDatesBatch(action, options, deps) {
|
|
|
12618
13262
|
const warnings = [];
|
|
12619
13263
|
let needsAutoSchedule = false;
|
|
12620
13264
|
for (const edit of action.edits) {
|
|
12621
|
-
const outcome =
|
|
13265
|
+
const outcome = applyOneEdit2(edit, deps);
|
|
12622
13266
|
verdicts.push(outcome.verdict);
|
|
12623
13267
|
if (!outcome.verdict.ok || !outcome.changes) continue;
|
|
12624
13268
|
const editTouched = collectTouchedIds(edit.activityId, outcome.changes);
|
|
12625
|
-
|
|
13269
|
+
mergeBeforeSnapshots2(beforeSnap, editTouched, deps);
|
|
12626
13270
|
for (const id of editTouched) touchedIds.add(id);
|
|
12627
13271
|
const beforeLinkLags = snapshotIncomingLinkLags(adapter, edit.activityId);
|
|
12628
13272
|
const preEditSnapshot = snapshotSingleActivity(
|
|
@@ -12676,6 +13320,7 @@ async function dispatchDatesBatch(action, options, deps) {
|
|
|
12676
13320
|
touchedIds,
|
|
12677
13321
|
scheduledIds,
|
|
12678
13322
|
sirDetection: "before-diff",
|
|
13323
|
+
preview: options.preview,
|
|
12679
13324
|
links: linkChanges,
|
|
12680
13325
|
trackingEvents,
|
|
12681
13326
|
warnings,
|
|
@@ -12683,222 +13328,18 @@ async function dispatchDatesBatch(action, options, deps) {
|
|
|
12683
13328
|
})
|
|
12684
13329
|
};
|
|
12685
13330
|
}
|
|
12686
|
-
function
|
|
13331
|
+
function applyOneEdit2(edit, deps) {
|
|
12687
13332
|
const { ctx } = deps;
|
|
12688
13333
|
const activityId = edit.activityId;
|
|
12689
13334
|
const pipeline = findPipelineForColumn(edit.column);
|
|
12690
13335
|
if (!pipeline) {
|
|
12691
13336
|
throw new Error(
|
|
12692
13337
|
`[ScheduleCore] no pipeline registered for column "${edit.column}"`
|
|
12693
|
-
);
|
|
12694
|
-
}
|
|
12695
|
-
const activitySnap = ctx.activityReader.getActivity(edit.activityId);
|
|
12696
|
-
if (!activitySnap) {
|
|
12697
|
-
throw new Error(`[ScheduleCore] activity "${edit.activityId}" not found`);
|
|
12698
|
-
}
|
|
12699
|
-
const editGate = pipeline.canEdit(activitySnap, ctx.hierarchy);
|
|
12700
|
-
if (!editGate.allowed) {
|
|
12701
|
-
return {
|
|
12702
|
-
verdict: {
|
|
12703
|
-
activityId,
|
|
12704
|
-
ok: false,
|
|
12705
|
-
reason: editGate.reason ?? REJECTION_REASON.CANNOT_EDIT,
|
|
12706
|
-
...editGate.alertKey ? { alertKey: editGate.alertKey } : {}
|
|
12707
|
-
},
|
|
12708
|
-
changes: null
|
|
12709
|
-
};
|
|
12710
|
-
}
|
|
12711
|
-
const parsed2 = pipeline.parseInput(edit.newValue, activitySnap, ctx);
|
|
12712
|
-
if (!parsed2.ok) {
|
|
12713
|
-
return {
|
|
12714
|
-
verdict: {
|
|
12715
|
-
activityId,
|
|
12716
|
-
ok: false,
|
|
12717
|
-
reason: parsed2.reason ?? REJECTION_REASON.PARSE_ERROR
|
|
12718
|
-
},
|
|
12719
|
-
changes: null
|
|
12720
|
-
};
|
|
12721
|
-
}
|
|
12722
|
-
const oldValue = Reflect.get(activitySnap, pipeline.targetField);
|
|
12723
|
-
const validation = pipeline.validate(
|
|
12724
|
-
activitySnap,
|
|
12725
|
-
oldValue,
|
|
12726
|
-
parsed2.value,
|
|
12727
|
-
ctx
|
|
12728
|
-
);
|
|
12729
|
-
if (!validation.valid) {
|
|
12730
|
-
return {
|
|
12731
|
-
verdict: {
|
|
12732
|
-
activityId,
|
|
12733
|
-
ok: false,
|
|
12734
|
-
reason: validation.reason ?? REJECTION_REASON.INVALID,
|
|
12735
|
-
...validation.notification?.messageKey ? { alertKey: validation.notification.messageKey } : {}
|
|
12736
|
-
},
|
|
12737
|
-
changes: null
|
|
12738
|
-
};
|
|
12739
|
-
}
|
|
12740
|
-
const changes = pipeline.transform(
|
|
12741
|
-
activitySnap,
|
|
12742
|
-
parsed2.value,
|
|
12743
|
-
ctx,
|
|
12744
|
-
parsed2.raw
|
|
12745
|
-
);
|
|
12746
|
-
return { verdict: { activityId, ok: true }, changes };
|
|
12747
|
-
}
|
|
12748
|
-
function mergeBeforeSnapshots(beforeSnap, ids, deps) {
|
|
12749
|
-
const missing = /* @__PURE__ */ new Set();
|
|
12750
|
-
for (const id of ids) {
|
|
12751
|
-
if (!beforeSnap.has(id)) missing.add(id);
|
|
12752
|
-
}
|
|
12753
|
-
if (missing.size === 0) return;
|
|
12754
|
-
for (const missingId of missing) {
|
|
12755
|
-
const liveActivity = deps.adapter.getActivity(missingId);
|
|
12756
|
-
if (liveActivity) {
|
|
12757
|
-
beforeSnap.set(missingId, structuredCloneActivity(liveActivity));
|
|
12758
|
-
}
|
|
12759
|
-
}
|
|
12760
|
-
}
|
|
12761
|
-
|
|
12762
|
-
// src/dispatch/handlers/bulk-edit.ts
|
|
12763
|
-
var CUSTOM_ID_COLUMN = COLUMN.CUSTOM_ID;
|
|
12764
|
-
async function dispatchBulkEdit(action, options, deps) {
|
|
12765
|
-
const { adapter, scheduler, sector } = deps;
|
|
12766
|
-
const editsIncludeDuration = action.edits.some(
|
|
12767
|
-
(edit) => edit.column === COLUMN.DURATION
|
|
12768
|
-
);
|
|
12769
|
-
if (editsIncludeDuration) {
|
|
12770
|
-
scheduler.invalidateExpandedLinksCache();
|
|
12771
|
-
}
|
|
12772
|
-
const verdicts = [];
|
|
12773
|
-
const beforeSnap = /* @__PURE__ */ new Map();
|
|
12774
|
-
const constraintPriorsByActivity = /* @__PURE__ */ new Map();
|
|
12775
|
-
const explicitDateEditActivities = /* @__PURE__ */ new Set();
|
|
12776
|
-
const touchedIds = /* @__PURE__ */ new Set();
|
|
12777
|
-
const trackingEvents = [];
|
|
12778
|
-
const linkChanges = [];
|
|
12779
|
-
const warnings = [];
|
|
12780
|
-
let needsAutoSchedule = false;
|
|
12781
|
-
for (const edit of action.edits) {
|
|
12782
|
-
const outcome = applyOneEdit2(edit, deps);
|
|
12783
|
-
verdicts.push(outcome.verdict);
|
|
12784
|
-
if (!outcome.verdict.ok || !outcome.changes) continue;
|
|
12785
|
-
const constraintWarning = toConstraintWarning(
|
|
12786
|
-
edit.activityId,
|
|
12787
|
-
outcome.changes.constraintWarning
|
|
12788
|
-
);
|
|
12789
|
-
if (constraintWarning) warnings.push(constraintWarning);
|
|
12790
|
-
const editTouched = collectTouchedIds(edit.activityId, outcome.changes);
|
|
12791
|
-
mergeBeforeSnapshots2(beforeSnap, editTouched, deps);
|
|
12792
|
-
for (const touchedId of editTouched) touchedIds.add(touchedId);
|
|
12793
|
-
recordConstraintPrior(constraintPriorsByActivity, edit, beforeSnap);
|
|
12794
|
-
if (isExplicitConstraintDateEdit(edit.column)) {
|
|
12795
|
-
explicitDateEditActivities.add(String(edit.activityId));
|
|
12796
|
-
}
|
|
12797
|
-
const beforeLinkLags = snapshotIncomingLinkLags(adapter, edit.activityId);
|
|
12798
|
-
const preEditSnapshot = snapshotSingleActivity(
|
|
12799
|
-
adapter,
|
|
12800
|
-
String(edit.activityId)
|
|
12801
|
-
);
|
|
12802
|
-
const customIdBeforeApply = readLiveCustomId(edit, deps);
|
|
12803
|
-
const changes = outcome.changes;
|
|
12804
|
-
applyFieldChanges(adapter, edit.activityId, changes);
|
|
12805
|
-
runPostProcessorsOnAdapter(
|
|
12806
|
-
edit.activityId,
|
|
12807
|
-
outcome.changes.postProcessors ?? [],
|
|
12808
|
-
adapter,
|
|
12809
|
-
preEditSnapshot
|
|
12810
|
-
);
|
|
12811
|
-
syncCustomIdTracker(edit, customIdBeforeApply, deps);
|
|
12812
|
-
linkChanges.push(
|
|
12813
|
-
...diffIncomingLinkLagChanges(beforeLinkLags, adapter, edit.activityId)
|
|
12814
|
-
);
|
|
12815
|
-
needsAutoSchedule ||= Boolean(outcome.changes.needsAutoSchedule);
|
|
12816
|
-
for (const trackingEvent of outcome.changes.trackingEvents ?? []) {
|
|
12817
|
-
trackingEvents.push({
|
|
12818
|
-
name: trackingEvent.name,
|
|
12819
|
-
properties: trackingEvent.properties ?? {}
|
|
12820
|
-
});
|
|
12821
|
-
}
|
|
12822
|
-
}
|
|
12823
|
-
const postMutationArgs = {
|
|
12824
|
-
action,
|
|
12825
|
-
autoscheduleFrom: needsAutoSchedule ? "roots" : null,
|
|
12826
|
-
recomputeParentsFrom: touchedIds,
|
|
12827
|
-
now: deps.now,
|
|
12828
|
-
options
|
|
12829
|
-
};
|
|
12830
|
-
const batchHasConstraintEdits = constraintPriorsByActivity.size > 0;
|
|
12831
|
-
const { scheduledIds } = await (batchHasConstraintEdits ? settleConstraintEdits(
|
|
12832
|
-
{
|
|
12833
|
-
adapter,
|
|
12834
|
-
scheduler,
|
|
12835
|
-
sector,
|
|
12836
|
-
defaultBaseCalendarId: deps.defaultBaseCalendarId
|
|
12837
|
-
},
|
|
12838
|
-
{
|
|
12839
|
-
postMutationArgs,
|
|
12840
|
-
revertTargets: [...constraintPriorsByActivity].filter(
|
|
12841
|
-
([activityId]) => !explicitDateEditActivities.has(activityId)
|
|
12842
|
-
).map(([activityId, priorConstraintDate]) => ({
|
|
12843
|
-
activityId,
|
|
12844
|
-
priorConstraintDate
|
|
12845
|
-
})),
|
|
12846
|
-
revert: (activityId, priorConstraintDate) => revertNoOpConstraintEdit(
|
|
12847
|
-
activityId,
|
|
12848
|
-
priorConstraintDate,
|
|
12849
|
-
adapter,
|
|
12850
|
-
deps.ctx.calendars
|
|
12851
|
-
)
|
|
12852
|
-
}
|
|
12853
|
-
) : runPostMutation(
|
|
12854
|
-
{
|
|
12855
|
-
adapter,
|
|
12856
|
-
scheduler,
|
|
12857
|
-
sector,
|
|
12858
|
-
defaultBaseCalendarId: deps.defaultBaseCalendarId
|
|
12859
|
-
},
|
|
12860
|
-
postMutationArgs
|
|
12861
|
-
));
|
|
12862
|
-
return {
|
|
12863
|
-
ok: true,
|
|
12864
|
-
verdicts,
|
|
12865
|
-
changes: await assembleChangeSet(adapter, {
|
|
12866
|
-
source: action,
|
|
12867
|
-
beforeSnap,
|
|
12868
|
-
touchedIds,
|
|
12869
|
-
scheduledIds,
|
|
12870
|
-
sirDetection: "before-diff",
|
|
12871
|
-
links: linkChanges,
|
|
12872
|
-
trackingEvents,
|
|
12873
|
-
warnings,
|
|
12874
|
-
hoursPerDay: sector.hoursPerDay
|
|
12875
|
-
})
|
|
12876
|
-
};
|
|
12877
|
-
}
|
|
12878
|
-
function applyOneEdit2(edit, deps) {
|
|
12879
|
-
const { ctx } = deps;
|
|
12880
|
-
const activityId = edit.activityId;
|
|
12881
|
-
const pipeline = findPipelineForColumn(edit.column);
|
|
12882
|
-
if (!pipeline) {
|
|
12883
|
-
return {
|
|
12884
|
-
verdict: {
|
|
12885
|
-
activityId,
|
|
12886
|
-
ok: false,
|
|
12887
|
-
reason: REJECTION_REASON.NO_PIPELINE_FOR_COLUMN
|
|
12888
|
-
},
|
|
12889
|
-
changes: null
|
|
12890
|
-
};
|
|
13338
|
+
);
|
|
12891
13339
|
}
|
|
12892
13340
|
const activitySnap = ctx.activityReader.getActivity(edit.activityId);
|
|
12893
13341
|
if (!activitySnap) {
|
|
12894
|
-
|
|
12895
|
-
verdict: {
|
|
12896
|
-
activityId,
|
|
12897
|
-
ok: false,
|
|
12898
|
-
reason: REJECTION_REASON.ACTIVITY_NOT_FOUND
|
|
12899
|
-
},
|
|
12900
|
-
changes: null
|
|
12901
|
-
};
|
|
13342
|
+
throw new Error(`[ScheduleCore] activity "${edit.activityId}" not found`);
|
|
12902
13343
|
}
|
|
12903
13344
|
const editGate = pipeline.canEdit(activitySnap, ctx.hierarchy);
|
|
12904
13345
|
if (!editGate.allowed) {
|
|
@@ -12941,21 +13382,6 @@ function applyOneEdit2(edit, deps) {
|
|
|
12941
13382
|
changes: null
|
|
12942
13383
|
};
|
|
12943
13384
|
}
|
|
12944
|
-
if (edit.column === CUSTOM_ID_COLUMN && parsed2.value !== null) {
|
|
12945
|
-
const liveActivity = deps.adapter.getActivity(edit.activityId);
|
|
12946
|
-
const oldCustomIdRaw = liveActivity ? liveActivity.customId : null;
|
|
12947
|
-
const oldCustomId = typeof oldCustomIdRaw === "string" ? oldCustomIdRaw : null;
|
|
12948
|
-
if (deps.customIdTracker.isCustomIdInUse(String(parsed2.value), oldCustomId)) {
|
|
12949
|
-
return {
|
|
12950
|
-
verdict: {
|
|
12951
|
-
activityId,
|
|
12952
|
-
ok: false,
|
|
12953
|
-
reason: REJECTION_REASON.CUSTOM_ID_DUPLICATE
|
|
12954
|
-
},
|
|
12955
|
-
changes: null
|
|
12956
|
-
};
|
|
12957
|
-
}
|
|
12958
|
-
}
|
|
12959
13385
|
const changes = pipeline.transform(
|
|
12960
13386
|
activitySnap,
|
|
12961
13387
|
parsed2.value,
|
|
@@ -12964,33 +13390,10 @@ function applyOneEdit2(edit, deps) {
|
|
|
12964
13390
|
);
|
|
12965
13391
|
return { verdict: { activityId, ok: true }, changes };
|
|
12966
13392
|
}
|
|
12967
|
-
function readLiveCustomId(edit, deps) {
|
|
12968
|
-
if (edit.column !== CUSTOM_ID_COLUMN) return null;
|
|
12969
|
-
const liveActivity = deps.adapter.getActivity(edit.activityId);
|
|
12970
|
-
const rawValue = liveActivity ? liveActivity.customId : null;
|
|
12971
|
-
return typeof rawValue === "string" ? rawValue : null;
|
|
12972
|
-
}
|
|
12973
|
-
function syncCustomIdTracker(edit, oldCustomId, deps) {
|
|
12974
|
-
if (edit.column !== CUSTOM_ID_COLUMN) return;
|
|
12975
|
-
const liveActivity = deps.adapter.getActivity(edit.activityId);
|
|
12976
|
-
const rawValue = liveActivity ? liveActivity.customId : null;
|
|
12977
|
-
const newCustomId = typeof rawValue === "string" ? rawValue : null;
|
|
12978
|
-
if (oldCustomId !== newCustomId) {
|
|
12979
|
-
deps.customIdTracker.trackCustomIdChange(oldCustomId, newCustomId);
|
|
12980
|
-
}
|
|
12981
|
-
}
|
|
12982
|
-
function recordConstraintPrior(priors, edit, beforeSnap) {
|
|
12983
|
-
if (!isConstraintEditColumn(edit.column)) return;
|
|
12984
|
-
const activityKey = String(edit.activityId);
|
|
12985
|
-
if (priors.has(activityKey)) return;
|
|
12986
|
-
const preBatchSnapshot = beforeSnap.get(activityKey);
|
|
12987
|
-
if (!preBatchSnapshot) return;
|
|
12988
|
-
priors.set(activityKey, preBatchSnapshot.constraintDate ?? null);
|
|
12989
|
-
}
|
|
12990
13393
|
function mergeBeforeSnapshots2(beforeSnap, ids, deps) {
|
|
12991
13394
|
const missing = /* @__PURE__ */ new Set();
|
|
12992
|
-
for (const
|
|
12993
|
-
if (!beforeSnap.has(
|
|
13395
|
+
for (const id of ids) {
|
|
13396
|
+
if (!beforeSnap.has(id)) missing.add(id);
|
|
12994
13397
|
}
|
|
12995
13398
|
if (missing.size === 0) return;
|
|
12996
13399
|
for (const missingId of missing) {
|
|
@@ -13053,9 +13456,33 @@ function assertValidAssignments(assignments, entityName, readCurrent, ownerByBac
|
|
|
13053
13456
|
);
|
|
13054
13457
|
}
|
|
13055
13458
|
}
|
|
13056
|
-
function
|
|
13459
|
+
function assertValidReconciledProgress(entries, deps) {
|
|
13460
|
+
const seen = /* @__PURE__ */ new Set();
|
|
13461
|
+
for (const entry of entries) {
|
|
13462
|
+
const id = String(entry.activityId);
|
|
13463
|
+
if (id.length === 0 || seen.has(id)) {
|
|
13464
|
+
throw new Error(
|
|
13465
|
+
`persistence-acknowledge: duplicate or empty reconciled activity id "${id}"`
|
|
13466
|
+
);
|
|
13467
|
+
}
|
|
13468
|
+
if (!Number.isFinite(entry.progress) || entry.progress < 0 || entry.progress > 100) {
|
|
13469
|
+
throw new Error(
|
|
13470
|
+
`persistence-acknowledge: invalid reconciled progress ${String(entry.progress)} for "${id}"`
|
|
13471
|
+
);
|
|
13472
|
+
}
|
|
13473
|
+
if (!deps.adapter.getActivity(id)) {
|
|
13474
|
+
throw new Error(
|
|
13475
|
+
`persistence-acknowledge: unknown reconciled activity id "${id}"`
|
|
13476
|
+
);
|
|
13477
|
+
}
|
|
13478
|
+
seen.add(id);
|
|
13479
|
+
}
|
|
13480
|
+
}
|
|
13481
|
+
async function dispatchPersistenceAcknowledge(action, deps) {
|
|
13057
13482
|
const activities = action.activities ?? [];
|
|
13058
13483
|
const links = action.links ?? [];
|
|
13484
|
+
const reconciledProgress = action.reconciledProgress ?? [];
|
|
13485
|
+
assertValidReconciledProgress(reconciledProgress, deps);
|
|
13059
13486
|
const activityOwnerByBackendId = /* @__PURE__ */ new Map();
|
|
13060
13487
|
for (const activity of deps.adapter.getAllActivities()) {
|
|
13061
13488
|
if (activity.proplannerId != null) {
|
|
@@ -13091,7 +13518,49 @@ function dispatchPersistenceAcknowledge(action, deps) {
|
|
|
13091
13518
|
);
|
|
13092
13519
|
const activityChanges = [];
|
|
13093
13520
|
const linkChanges = [];
|
|
13521
|
+
const progressToApply = reconciledProgress.filter((entry) => {
|
|
13522
|
+
const current = deps.adapter.getActivity(String(entry.activityId));
|
|
13523
|
+
return current !== null && current.progress !== entry.progress;
|
|
13524
|
+
});
|
|
13525
|
+
const touchedForProgress = new Set(
|
|
13526
|
+
progressToApply.map((entry) => String(entry.activityId))
|
|
13527
|
+
);
|
|
13528
|
+
const parentsToRollup = /* @__PURE__ */ new Set();
|
|
13529
|
+
for (const entry of progressToApply) {
|
|
13530
|
+
const activity = deps.adapter.getActivity(String(entry.activityId));
|
|
13531
|
+
if (activity && !isRootParent(activity.parentId)) {
|
|
13532
|
+
parentsToRollup.add(String(activity.parentId));
|
|
13533
|
+
touchedForProgress.add(String(activity.parentId));
|
|
13534
|
+
}
|
|
13535
|
+
}
|
|
13536
|
+
const beforeProgressSnap = await snapshotActivities(
|
|
13537
|
+
deps.adapter,
|
|
13538
|
+
touchedForProgress
|
|
13539
|
+
);
|
|
13094
13540
|
deps.adapter.batchUpdate(() => {
|
|
13541
|
+
for (const entry of progressToApply) {
|
|
13542
|
+
deps.adapter.setActivityField(
|
|
13543
|
+
String(entry.activityId),
|
|
13544
|
+
"progress",
|
|
13545
|
+
entry.progress
|
|
13546
|
+
);
|
|
13547
|
+
}
|
|
13548
|
+
for (const parentId of parentsToRollup) {
|
|
13549
|
+
recomputeRollupCascadesForParent(parentId, deps.adapter);
|
|
13550
|
+
}
|
|
13551
|
+
for (const id of touchedForProgress) {
|
|
13552
|
+
const before = beforeProgressSnap.get(id);
|
|
13553
|
+
const after = deps.adapter.getActivity(id);
|
|
13554
|
+
if (!before || !after || before.progress === after.progress) continue;
|
|
13555
|
+
activityChanges.push({
|
|
13556
|
+
id,
|
|
13557
|
+
kind: "updated",
|
|
13558
|
+
fields: {
|
|
13559
|
+
progress: { before: before.progress, after: after.progress }
|
|
13560
|
+
},
|
|
13561
|
+
after: { ...after }
|
|
13562
|
+
});
|
|
13563
|
+
}
|
|
13095
13564
|
for (const assignment of activities) {
|
|
13096
13565
|
const before = deps.adapter.getActivity(assignment.id);
|
|
13097
13566
|
if (!before || before.proplannerId === assignment.proplannerId) continue;
|
|
@@ -13498,8 +13967,218 @@ async function dispatchStatusCriteriaSet(action, deps) {
|
|
|
13498
13967
|
};
|
|
13499
13968
|
}
|
|
13500
13969
|
|
|
13970
|
+
// src/activity-copy-paste/policy.ts
|
|
13971
|
+
var ACTIVITY_COPY_FIELDS = [
|
|
13972
|
+
"name",
|
|
13973
|
+
"description",
|
|
13974
|
+
"type",
|
|
13975
|
+
"startDate",
|
|
13976
|
+
"durationHours",
|
|
13977
|
+
"constraintType",
|
|
13978
|
+
"constraintDate",
|
|
13979
|
+
"calendarId",
|
|
13980
|
+
"cost",
|
|
13981
|
+
"hasCustomPonderator",
|
|
13982
|
+
"subcontractId",
|
|
13983
|
+
"responsableIds",
|
|
13984
|
+
"tagIds"
|
|
13985
|
+
];
|
|
13986
|
+
|
|
13987
|
+
// src/activity-copy-paste/copy.ts
|
|
13988
|
+
function copyOverrides(activity) {
|
|
13989
|
+
const overrides = {};
|
|
13990
|
+
for (const field of ACTIVITY_COPY_FIELDS) {
|
|
13991
|
+
Object.assign(overrides, { [field]: cloneDomainValue(activity[field]) });
|
|
13992
|
+
}
|
|
13993
|
+
return overrides;
|
|
13994
|
+
}
|
|
13995
|
+
function selectedInTreeOrder(adapter, selected) {
|
|
13996
|
+
const ordered = [];
|
|
13997
|
+
const visit = (id) => {
|
|
13998
|
+
if (selected.has(String(id))) ordered.push(id);
|
|
13999
|
+
for (const childId of adapter.getChildren(id)) visit(childId);
|
|
14000
|
+
};
|
|
14001
|
+
for (const rootId of adapter.getRootIds()) visit(rootId);
|
|
14002
|
+
return ordered;
|
|
14003
|
+
}
|
|
14004
|
+
function dispatchActivityCopy(action, deps) {
|
|
14005
|
+
const selected = new Set(deps.adapter.checkedIds().map(String));
|
|
14006
|
+
if (selected.size === 0) {
|
|
14007
|
+
return { ok: false, reason: REJECTION_REASON.NO_ACTIVITY_SELECTION };
|
|
14008
|
+
}
|
|
14009
|
+
const orderedIds = selectedInTreeOrder(deps.adapter, selected);
|
|
14010
|
+
deps.clipboard.value = {
|
|
14011
|
+
activities: orderedIds.map((id) => {
|
|
14012
|
+
const activity = deps.adapter.getActivity(id);
|
|
14013
|
+
if (activity === null)
|
|
14014
|
+
throw new Error(`Selected activity ${id} is missing`);
|
|
14015
|
+
return {
|
|
14016
|
+
originalId: id,
|
|
14017
|
+
originalParentId: activity.parentId ?? "0",
|
|
14018
|
+
overrides: copyOverrides(activity)
|
|
14019
|
+
};
|
|
14020
|
+
}),
|
|
14021
|
+
links: deps.adapter.getAllLinks().filter(
|
|
14022
|
+
(link) => selected.has(String(link.source)) && selected.has(String(link.target))
|
|
14023
|
+
).map((link) => cloneDomainValue(link))
|
|
14024
|
+
};
|
|
14025
|
+
const selection = dispatchSelectionReplace(
|
|
14026
|
+
{ kind: "selection-replace", activityIds: [] },
|
|
14027
|
+
{ adapter: deps.adapter }
|
|
14028
|
+
);
|
|
14029
|
+
if (!selection.ok) return selection;
|
|
14030
|
+
return { ...selection, changes: { ...selection.changes, source: action } };
|
|
14031
|
+
}
|
|
14032
|
+
function dispatchActivityClipboardClear(action, clipboard) {
|
|
14033
|
+
clipboard.value = null;
|
|
14034
|
+
return {
|
|
14035
|
+
ok: true,
|
|
14036
|
+
changes: {
|
|
14037
|
+
source: action,
|
|
14038
|
+
activities: [],
|
|
14039
|
+
links: [],
|
|
14040
|
+
calendars: [],
|
|
14041
|
+
trackingEvents: []
|
|
14042
|
+
}
|
|
14043
|
+
};
|
|
14044
|
+
}
|
|
14045
|
+
|
|
14046
|
+
// src/activity-copy-paste/paste.ts
|
|
14047
|
+
function resolveDestination(afterSiblingId, adapter) {
|
|
14048
|
+
const sibling = adapter.getActivity(afterSiblingId);
|
|
14049
|
+
if (!sibling) {
|
|
14050
|
+
return { ok: false, reason: REJECTION_REASON.ANCHOR_SIBLING_NOT_FOUND };
|
|
14051
|
+
}
|
|
14052
|
+
return {
|
|
14053
|
+
ok: true,
|
|
14054
|
+
parentId: sibling.parentId == null ? ROOT_PARENT_ID : String(sibling.parentId)
|
|
14055
|
+
};
|
|
14056
|
+
}
|
|
14057
|
+
var invariantFailure = () => ({
|
|
14058
|
+
ok: false,
|
|
14059
|
+
reason: REJECTION_REASON.ACTIVITY_PASTE_INVARIANT_FAILED
|
|
14060
|
+
});
|
|
14061
|
+
async function dispatchActivityPasteCopied(action, options, deps) {
|
|
14062
|
+
const clipboard = deps.activityClipboard.value;
|
|
14063
|
+
if (clipboard === null) {
|
|
14064
|
+
return { ok: false, reason: REJECTION_REASON.CLIPBOARD_EMPTY };
|
|
14065
|
+
}
|
|
14066
|
+
const destination = resolveDestination(
|
|
14067
|
+
action.destination.afterSiblingId,
|
|
14068
|
+
deps.adapter
|
|
14069
|
+
);
|
|
14070
|
+
if (!destination.ok) return destination;
|
|
14071
|
+
const beforeSnapshot = await snapshotActivities(
|
|
14072
|
+
deps.adapter,
|
|
14073
|
+
/* @__PURE__ */ new Set()
|
|
14074
|
+
);
|
|
14075
|
+
const touched = /* @__PURE__ */ new Set();
|
|
14076
|
+
const originalToNew = /* @__PURE__ */ new Map();
|
|
14077
|
+
const createdIds = [];
|
|
14078
|
+
let previousRootId;
|
|
14079
|
+
for (const copied of clipboard.activities) {
|
|
14080
|
+
const mappedParent = originalToNew.get(copied.originalParentId);
|
|
14081
|
+
const created = await createActivityCore(
|
|
14082
|
+
{
|
|
14083
|
+
parentId: mappedParent ?? destination.parentId,
|
|
14084
|
+
...mappedParent === void 0 ? {
|
|
14085
|
+
afterSiblingId: previousRootId ?? action.destination.afterSiblingId
|
|
14086
|
+
} : {},
|
|
14087
|
+
overrides: cloneDomainValue(copied.overrides),
|
|
14088
|
+
eventSource: action.eventSource
|
|
14089
|
+
},
|
|
14090
|
+
deps,
|
|
14091
|
+
{
|
|
14092
|
+
skipCorrelativeRecompute: true,
|
|
14093
|
+
skipBeforeSnap: true,
|
|
14094
|
+
customIdReferenceId: action.destination.afterSiblingId
|
|
14095
|
+
}
|
|
14096
|
+
);
|
|
14097
|
+
if (!created.ok) return created;
|
|
14098
|
+
originalToNew.set(copied.originalId, created.newId);
|
|
14099
|
+
createdIds.push(created.newId);
|
|
14100
|
+
if (mappedParent === void 0) previousRootId = created.newId;
|
|
14101
|
+
}
|
|
14102
|
+
const correlativeShifts = recomputeCorrelativeIds(deps.adapter);
|
|
14103
|
+
const createdIdSet = new Set(createdIds.map(String));
|
|
14104
|
+
foldCorrelativeShifts(
|
|
14105
|
+
deps.adapter,
|
|
14106
|
+
correlativeShifts,
|
|
14107
|
+
createdIdSet,
|
|
14108
|
+
beforeSnapshot,
|
|
14109
|
+
touched
|
|
14110
|
+
);
|
|
14111
|
+
for (const createdId of createdIds) touched.add(String(createdId));
|
|
14112
|
+
const appliedLinks = [];
|
|
14113
|
+
for (const copiedLink of clipboard.links) {
|
|
14114
|
+
const source = originalToNew.get(copiedLink.source);
|
|
14115
|
+
const target = originalToNew.get(copiedLink.target);
|
|
14116
|
+
if (source === void 0 || target === void 0) return invariantFailure();
|
|
14117
|
+
const operation = {
|
|
14118
|
+
kind: "create",
|
|
14119
|
+
source,
|
|
14120
|
+
target,
|
|
14121
|
+
type: copiedLink.type,
|
|
14122
|
+
lag: copiedLink.lag
|
|
14123
|
+
};
|
|
14124
|
+
const result = applyLinkOperation(operation, {
|
|
14125
|
+
port: deps.adapter,
|
|
14126
|
+
newLinkId: deps.linkIdGen.next
|
|
14127
|
+
});
|
|
14128
|
+
if (!result.applied) return invariantFailure();
|
|
14129
|
+
appliedLinks.push({
|
|
14130
|
+
op: operation,
|
|
14131
|
+
finalLinkId: result.linkId,
|
|
14132
|
+
rejected: null
|
|
14133
|
+
});
|
|
14134
|
+
}
|
|
14135
|
+
const { scheduledIds } = await runPostMutation(
|
|
14136
|
+
{
|
|
14137
|
+
adapter: deps.adapter,
|
|
14138
|
+
scheduler: deps.scheduler,
|
|
14139
|
+
sector: deps.sector,
|
|
14140
|
+
defaultBaseCalendarId: deps.defaultBaseCalendarId
|
|
14141
|
+
},
|
|
14142
|
+
{
|
|
14143
|
+
action,
|
|
14144
|
+
autoscheduleFrom: createdIds[0] ?? null,
|
|
14145
|
+
recomputeParentsFrom: createdIds,
|
|
14146
|
+
now: deps.now,
|
|
14147
|
+
options
|
|
14148
|
+
}
|
|
14149
|
+
);
|
|
14150
|
+
const linkChanges = buildLinkChangesForBatch(
|
|
14151
|
+
appliedLinks,
|
|
14152
|
+
/* @__PURE__ */ new Map(),
|
|
14153
|
+
deps.adapter
|
|
14154
|
+
);
|
|
14155
|
+
return {
|
|
14156
|
+
ok: true,
|
|
14157
|
+
changes: await assembleChangeSet(deps.adapter, {
|
|
14158
|
+
source: action,
|
|
14159
|
+
beforeSnap: beforeSnapshot,
|
|
14160
|
+
touchedIds: touched,
|
|
14161
|
+
scheduledIds,
|
|
14162
|
+
links: linkChanges,
|
|
14163
|
+
hoursPerDay: deps.sector.hoursPerDay
|
|
14164
|
+
})
|
|
14165
|
+
};
|
|
14166
|
+
}
|
|
14167
|
+
|
|
13501
14168
|
// src/dispatch/dispatch.ts
|
|
13502
14169
|
async function performDispatch(action, options, deps) {
|
|
14170
|
+
if (action.kind === "activity-copy") {
|
|
14171
|
+
return dispatchActivityCopy(action, {
|
|
14172
|
+
adapter: deps.adapter,
|
|
14173
|
+
clipboard: deps.activityClipboard
|
|
14174
|
+
});
|
|
14175
|
+
}
|
|
14176
|
+
if (action.kind === "activity-clipboard-clear") {
|
|
14177
|
+
return dispatchActivityClipboardClear(action, deps.activityClipboard);
|
|
14178
|
+
}
|
|
14179
|
+
if (action.kind === "activity-paste-copied") {
|
|
14180
|
+
return dispatchActivityPasteCopied(action, options, deps);
|
|
14181
|
+
}
|
|
13503
14182
|
if (action.kind === "activity-batch") {
|
|
13504
14183
|
return dispatchActivityBatch(action, options, deps);
|
|
13505
14184
|
}
|
|
@@ -13524,9 +14203,6 @@ async function performDispatch(action, options, deps) {
|
|
|
13524
14203
|
if (action.kind === "activity-create") {
|
|
13525
14204
|
return dispatchActivityCreate(action, options, deps);
|
|
13526
14205
|
}
|
|
13527
|
-
if (action.kind === "activity-paste") {
|
|
13528
|
-
return dispatchActivityPaste(action, options, deps);
|
|
13529
|
-
}
|
|
13530
14206
|
if (action.kind === "activity-delete") {
|
|
13531
14207
|
return dispatchActivityDelete(action, options, deps);
|
|
13532
14208
|
}
|
|
@@ -13569,6 +14245,9 @@ async function performDispatch(action, options, deps) {
|
|
|
13569
14245
|
if (action.kind === "sir-sync") {
|
|
13570
14246
|
return dispatchSirSync(action, deps);
|
|
13571
14247
|
}
|
|
14248
|
+
if (action.kind === "sir-approve") {
|
|
14249
|
+
return dispatchSirApprove(action, options, deps);
|
|
14250
|
+
}
|
|
13572
14251
|
if (action.kind === "activity-lookahead-sync") {
|
|
13573
14252
|
return dispatchActivityLookaheadSync(action, deps);
|
|
13574
14253
|
}
|
|
@@ -16981,6 +17660,7 @@ var ScheduleState = class {
|
|
|
16981
17660
|
if (!activity) return;
|
|
16982
17661
|
this._writeCapture.note(String(activityId), activity);
|
|
16983
17662
|
activity[field] = value;
|
|
17663
|
+
refreshCalendarDurationIfDateBearing(activity, field);
|
|
16984
17664
|
if (field === ACTIVITY_PROPERTY.PARENT) this._hierarchy.markDirty();
|
|
16985
17665
|
if (fieldAffectsVisualOrder(field)) this._cache.visualOrderIds = null;
|
|
16986
17666
|
}
|
|
@@ -16994,6 +17674,7 @@ var ScheduleState = class {
|
|
|
16994
17674
|
}
|
|
16995
17675
|
this._writeCapture.note(String(activityId), activity);
|
|
16996
17676
|
Reflect.set(activity, field, coerceActivityFieldValue(field, value));
|
|
17677
|
+
refreshCalendarDurationIfDateBearing(activity, field);
|
|
16997
17678
|
if (field === ACTIVITY_PROPERTY.PARENT) this._hierarchy.markDirty();
|
|
16998
17679
|
if (fieldAffectsVisualOrder(field)) this._cache.visualOrderIds = null;
|
|
16999
17680
|
}
|
|
@@ -17004,6 +17685,7 @@ var ScheduleState = class {
|
|
|
17004
17685
|
let reparented = false;
|
|
17005
17686
|
for (const field of Object.keys(fields)) {
|
|
17006
17687
|
Reflect.set(activity, field, Reflect.get(fields, field));
|
|
17688
|
+
refreshCalendarDurationIfDateBearing(activity, field);
|
|
17007
17689
|
if (field === ACTIVITY_PROPERTY.PARENT) reparented = true;
|
|
17008
17690
|
if (fieldAffectsVisualOrder(field)) this._cache.visualOrderIds = null;
|
|
17009
17691
|
}
|
|
@@ -17172,6 +17854,12 @@ function isMutableLinkField(field) {
|
|
|
17172
17854
|
function fieldAffectsVisualOrder(field) {
|
|
17173
17855
|
return field === ACTIVITY_PROPERTY.PARENT || field === ACTIVITY_PROPERTY.CORRELATIVE_ID;
|
|
17174
17856
|
}
|
|
17857
|
+
function refreshCalendarDurationIfDateBearing(activity, field) {
|
|
17858
|
+
if (field !== ACTIVITY_PROPERTY.START_DATE && field !== ACTIVITY_PROPERTY.END_DATE && field !== ACTIVITY_PROPERTY.TYPE) {
|
|
17859
|
+
return;
|
|
17860
|
+
}
|
|
17861
|
+
activity.calendarDuration = computeCalendarDuration(activity);
|
|
17862
|
+
}
|
|
17175
17863
|
function detachLinkRef(refs, linkId) {
|
|
17176
17864
|
return refs.filter((ref) => String(ref) !== String(linkId));
|
|
17177
17865
|
}
|
|
@@ -17219,15 +17907,7 @@ function readChildren(state, parentId) {
|
|
|
17219
17907
|
return out;
|
|
17220
17908
|
}
|
|
17221
17909
|
function readChildrenIds(state, parentId) {
|
|
17222
|
-
|
|
17223
|
-
if (key === ROOT_PARENT_ID) {
|
|
17224
|
-
const roots = [];
|
|
17225
|
-
state.forEachActivity((activity, id) => {
|
|
17226
|
-
if (isRootParent(activity.parentId)) roots.push(id);
|
|
17227
|
-
});
|
|
17228
|
-
return roots;
|
|
17229
|
-
}
|
|
17230
|
-
return getChildrenInVisualOrder(key, state);
|
|
17910
|
+
return getChildrenInVisualOrder(parentId, state);
|
|
17231
17911
|
}
|
|
17232
17912
|
function readSelectedActivityIds(state) {
|
|
17233
17913
|
return state.checkedIds().map(String);
|
|
@@ -17291,7 +17971,11 @@ function buildPipelineContext(adapter, options) {
|
|
|
17291
17971
|
dateFormat: options.dateFormat ?? "YYYY-MM-DD",
|
|
17292
17972
|
customHours: options.customHours ?? {},
|
|
17293
17973
|
hoursPerDay: options.hoursPerDay,
|
|
17294
|
-
|
|
17974
|
+
// ISSUE-057 (CP6): sin default. Un comando de duración sin inputUnit se
|
|
17975
|
+
// rechaza en el pipeline; el resto de columnas no lo leen. La clave se
|
|
17976
|
+
// OMITE cuando no se declaró (exactOptionalPropertyTypes), no se pone a
|
|
17977
|
+
// undefined.
|
|
17978
|
+
...options.inputUnit !== void 0 ? { inputUnit: options.inputUnit } : {}
|
|
17295
17979
|
};
|
|
17296
17980
|
}
|
|
17297
17981
|
|
|
@@ -17371,9 +18055,10 @@ function mergeCoalesced(top, next) {
|
|
|
17371
18055
|
};
|
|
17372
18056
|
return buildUndoEntry(changeSet);
|
|
17373
18057
|
}
|
|
17374
|
-
function getDispatchHistoryPolicy(action) {
|
|
18058
|
+
function getDispatchHistoryPolicy(action, options) {
|
|
18059
|
+
if (options?.preview === true) return "skip";
|
|
17375
18060
|
if (action.kind === "persistence-acknowledge") return "clear-on-success";
|
|
17376
|
-
if (action.kind === "sir-sync" || action.kind === "activity-lookahead-sync" || action.kind === "baseline-apply" || action.kind === "ponderator-criterion-set" || action.kind === "status-criteria-set" || action.kind === "selection-toggle" || action.kind === "selection-replace" || action.kind === "visibility-set" || action.kind === "filter-set" || action.kind === "sort-set") {
|
|
18061
|
+
if (action.kind === "sir-sync" || action.kind === "activity-lookahead-sync" || action.kind === "baseline-apply" || action.kind === "ponderator-criterion-set" || action.kind === "status-criteria-set" || action.kind === "activity-clipboard-clear" || action.kind === "activity-copy" || action.kind === "selection-toggle" || action.kind === "selection-replace" || action.kind === "visibility-set" || action.kind === "filter-set" || action.kind === "sort-set") {
|
|
17377
18062
|
return "skip";
|
|
17378
18063
|
}
|
|
17379
18064
|
return "record";
|
|
@@ -18157,6 +18842,11 @@ function parseActivity(raw, context) {
|
|
|
18157
18842
|
Number(raw.sumOfDurationRecursively ?? 0),
|
|
18158
18843
|
context.hoursPerDay
|
|
18159
18844
|
),
|
|
18845
|
+
calendarDuration: computeCalendarDuration({
|
|
18846
|
+
type: raw.type,
|
|
18847
|
+
startDate,
|
|
18848
|
+
endDate
|
|
18849
|
+
}),
|
|
18160
18850
|
isCritical: Boolean(raw.is_critical),
|
|
18161
18851
|
freeSlackHours: null,
|
|
18162
18852
|
expectedProgress: null,
|
|
@@ -18521,7 +19211,8 @@ function parseSector(sector) {
|
|
|
18521
19211
|
...customIdIncrement !== void 0 ? { customIdIncrement } : {},
|
|
18522
19212
|
...isPrimaveraEndDate ? { updateDurationForPrimaveraEndDate: true } : {},
|
|
18523
19213
|
activityCreter: parseActivityCreter(sector.activity_creter),
|
|
18524
|
-
statusCriteria: DEFAULT_STATUS_CRITERIA
|
|
19214
|
+
statusCriteria: DEFAULT_STATUS_CRITERIA,
|
|
19215
|
+
ganttId: null
|
|
18525
19216
|
};
|
|
18526
19217
|
}
|
|
18527
19218
|
|
|
@@ -19313,6 +20004,7 @@ function recomputeSkippedLeafEnds(state) {
|
|
|
19313
20004
|
durationHours: activity.durationHours,
|
|
19314
20005
|
task: activity
|
|
19315
20006
|
});
|
|
20007
|
+
activity.calendarDuration = computeCalendarDuration(activity);
|
|
19316
20008
|
});
|
|
19317
20009
|
}
|
|
19318
20010
|
function recomputeDurationsFromDates(state) {
|
|
@@ -19384,6 +20076,7 @@ function initializeCore(input) {
|
|
|
19384
20076
|
);
|
|
19385
20077
|
const sector = parsed2.sector;
|
|
19386
20078
|
sector.statusCriteria = resolveStatusCriteria(input.statusCriteria);
|
|
20079
|
+
sector.ganttId = input.ganttId ?? null;
|
|
19387
20080
|
const calendars = parsed2.calendars;
|
|
19388
20081
|
const baseCalendars = parsed2.baseCalendars ?? [];
|
|
19389
20082
|
const snapshot = buildStateSnapshot(
|
|
@@ -19590,6 +20283,7 @@ function evaluateEnd(formula, proposedEdgeTimeMs) {
|
|
|
19590
20283
|
}
|
|
19591
20284
|
|
|
19592
20285
|
// src/init/schedule-core.ts
|
|
20286
|
+
var PREVIEW_DISPATCH_KINDS = /* @__PURE__ */ new Set(["inline-edit", "bulk-edit", "dates-batch"]);
|
|
19593
20287
|
function changesRequireFilterProjectionRebuild(changes) {
|
|
19594
20288
|
for (const change of changes.activities) {
|
|
19595
20289
|
if (change.kind === "deleted") return true;
|
|
@@ -19614,6 +20308,7 @@ function isCurrentFilterProjection(projection, filter, context) {
|
|
|
19614
20308
|
return hasCurrentHours && hasCurrentLocale;
|
|
19615
20309
|
}
|
|
19616
20310
|
var ScheduleCore = class {
|
|
20311
|
+
_activityClipboard = { value: null };
|
|
19617
20312
|
_status = SCHEDULE_CORE_STATUS.READY;
|
|
19618
20313
|
coreRuntime;
|
|
19619
20314
|
_undo = new UndoRecorder();
|
|
@@ -19709,6 +20404,23 @@ var ScheduleCore = class {
|
|
|
19709
20404
|
this.assertReady();
|
|
19710
20405
|
return readAllActivities(this.coreRuntime.state);
|
|
19711
20406
|
}
|
|
20407
|
+
/**
|
|
20408
|
+
* Read del plan de indent (ISSUE-080): los padres destino DISTINTOS bajo los
|
|
20409
|
+
* que quedarían las actividades si se indentaran ahora. Es exactamente la
|
|
20410
|
+
* planificación que ejecuta el handler (`planIndentMoves`, autoridad única),
|
|
20411
|
+
* expuesta para que la frontera pueda correr el protocolo de conversión a
|
|
20412
|
+
* madre (chequeo backend + modal) ANTES de despachar. Solo lecturas.
|
|
20413
|
+
*/
|
|
20414
|
+
getIndentTargetParentIds(activityIds) {
|
|
20415
|
+
this.assertReady();
|
|
20416
|
+
const plan = planIndentMoves(
|
|
20417
|
+
activityIds.map((id) => String(id)),
|
|
20418
|
+
this.coreRuntime.state
|
|
20419
|
+
);
|
|
20420
|
+
return [
|
|
20421
|
+
...new Set(plan.allowedMoves.map((move) => String(move.newParentId)))
|
|
20422
|
+
];
|
|
20423
|
+
}
|
|
19712
20424
|
getChildrenView(parentId) {
|
|
19713
20425
|
this.assertReady();
|
|
19714
20426
|
return readChildren(this.coreRuntime.state, parentId);
|
|
@@ -19721,6 +20433,26 @@ var ScheduleCore = class {
|
|
|
19721
20433
|
this.assertReady();
|
|
19722
20434
|
return readAllLinks(this.coreRuntime.state);
|
|
19723
20435
|
}
|
|
20436
|
+
auditEffectiveGraph() {
|
|
20437
|
+
this.assertReady();
|
|
20438
|
+
const state = this.coreRuntime.state;
|
|
20439
|
+
const logicalLinks = state.getAllLinks();
|
|
20440
|
+
const effectiveEdges = projectEffectiveEdges(state, logicalLinks);
|
|
20441
|
+
const components = circularComponents(effectiveEdges);
|
|
20442
|
+
let largestComponentSize = 0;
|
|
20443
|
+
for (const component of components) {
|
|
20444
|
+
if (component.length > largestComponentSize) {
|
|
20445
|
+
largestComponentSize = component.length;
|
|
20446
|
+
}
|
|
20447
|
+
}
|
|
20448
|
+
return {
|
|
20449
|
+
activityCount: state.getAllIds().length,
|
|
20450
|
+
logicalLinkCount: logicalLinks.length,
|
|
20451
|
+
effectiveEdgeCount: effectiveEdges.length,
|
|
20452
|
+
circularComponents: components,
|
|
20453
|
+
largestComponentSize
|
|
20454
|
+
};
|
|
20455
|
+
}
|
|
19724
20456
|
getProperty(activityId, property) {
|
|
19725
20457
|
this.assertReady();
|
|
19726
20458
|
const snapshot = this.coreRuntime.state.getActivity(activityId);
|
|
@@ -19749,11 +20481,11 @@ var ScheduleCore = class {
|
|
|
19749
20481
|
}
|
|
19750
20482
|
getActiveFilter() {
|
|
19751
20483
|
this.assertReady();
|
|
19752
|
-
return this.coreRuntime.state.getActiveFilter();
|
|
20484
|
+
return cloneDomainValue(this.coreRuntime.state.getActiveFilter());
|
|
19753
20485
|
}
|
|
19754
20486
|
getActiveOrder() {
|
|
19755
20487
|
this.assertReady();
|
|
19756
|
-
return this.coreRuntime.state.getActiveOrder();
|
|
20488
|
+
return cloneDomainValue(this.coreRuntime.state.getActiveOrder());
|
|
19757
20489
|
}
|
|
19758
20490
|
getVisualOrderIds() {
|
|
19759
20491
|
this.assertReady();
|
|
@@ -19804,6 +20536,74 @@ var ScheduleCore = class {
|
|
|
19804
20536
|
this.assertReady();
|
|
19805
20537
|
return this._saveTracker.hasUnsavedChanges();
|
|
19806
20538
|
}
|
|
20539
|
+
/**
|
|
20540
|
+
* Read-only view of the internal schedule revision. It moves once per
|
|
20541
|
+
* substantive CANONICAL mutation (dispatch, undo, redo). It does NOT move
|
|
20542
|
+
* for a preview dispatch nor for `applyPreviewInverse`: comparing the value
|
|
20543
|
+
* captured in `PreviewDispatchInfo.revision` against the current one
|
|
20544
|
+
* answers "did the schedule change while the preview was open".
|
|
20545
|
+
*/
|
|
20546
|
+
getScheduleRevision() {
|
|
20547
|
+
return this._scheduleRevision;
|
|
20548
|
+
}
|
|
20549
|
+
/**
|
|
20550
|
+
* Exit channel for a preview dispatch (`DispatchOptions.preview`): applies
|
|
20551
|
+
* the inverse ChangeSet the preview returned, restoring the canonical
|
|
20552
|
+
* state exactly. Like the preview itself it leaves no session trace: no
|
|
20553
|
+
* save-tracker dirty, no undo entry, no effects, and the schedule revision
|
|
20554
|
+
* does not move (see `getScheduleRevision`).
|
|
20555
|
+
*
|
|
20556
|
+
* Returns the ChangeSet to project to the view layer: the same changes as
|
|
20557
|
+
* `inverse`, but with `after` images re-read from the restored state (the
|
|
20558
|
+
* ones inside `inverse` were captured while the preview edit was applied).
|
|
20559
|
+
*/
|
|
20560
|
+
applyPreviewInverse(inverse) {
|
|
20561
|
+
return this._enqueue(async () => {
|
|
20562
|
+
this.assertReady();
|
|
20563
|
+
const state = this.coreRuntime.state;
|
|
20564
|
+
const afterSnap = /* @__PURE__ */ new Map();
|
|
20565
|
+
for (const change of inverse.activities) {
|
|
20566
|
+
if (change.kind !== "deleted" && change.after) {
|
|
20567
|
+
afterSnap.set(
|
|
20568
|
+
String(change.id),
|
|
20569
|
+
structuredCloneActivity(change.after)
|
|
20570
|
+
);
|
|
20571
|
+
}
|
|
20572
|
+
}
|
|
20573
|
+
const afterLinks = /* @__PURE__ */ new Map();
|
|
20574
|
+
for (const change of inverse.links) {
|
|
20575
|
+
if (change.kind !== "deleted" && change.after) {
|
|
20576
|
+
afterLinks.set(String(change.id), { ...change.after });
|
|
20577
|
+
}
|
|
20578
|
+
}
|
|
20579
|
+
const entry = buildUndoEntry(
|
|
20580
|
+
inverse,
|
|
20581
|
+
void 0,
|
|
20582
|
+
void 0,
|
|
20583
|
+
afterSnap,
|
|
20584
|
+
afterLinks
|
|
20585
|
+
);
|
|
20586
|
+
applyRedo(state, entry);
|
|
20587
|
+
if (needsResync(entry)) this._resyncCustomIdTrackerFromModel();
|
|
20588
|
+
const restored = {
|
|
20589
|
+
...inverse,
|
|
20590
|
+
activities: inverse.activities.map((change) => {
|
|
20591
|
+
if (change.kind === "deleted") return { ...change, after: null };
|
|
20592
|
+
const live = state.getActivity(String(change.id));
|
|
20593
|
+
return {
|
|
20594
|
+
...change,
|
|
20595
|
+
after: live ? structuredCloneActivity(live) : null
|
|
20596
|
+
};
|
|
20597
|
+
}),
|
|
20598
|
+
links: inverse.links.map((change) => {
|
|
20599
|
+
if (change.kind === "deleted") return { ...change, after: null };
|
|
20600
|
+
const live = state.getLink(String(change.id));
|
|
20601
|
+
return { ...change, after: live ? snapshotToLink(live) : null };
|
|
20602
|
+
})
|
|
20603
|
+
};
|
|
20604
|
+
return this._reapplyViewState(restored) ?? restored;
|
|
20605
|
+
});
|
|
20606
|
+
}
|
|
19807
20607
|
getDeletedActivitiesSinceLastSave() {
|
|
19808
20608
|
this.assertReady();
|
|
19809
20609
|
return this._saveTracker.deletedActivities(this.coreRuntime.state.getAllActivities()).map(cloneDomainValue);
|
|
@@ -19886,11 +20686,11 @@ var ScheduleCore = class {
|
|
|
19886
20686
|
});
|
|
19887
20687
|
}
|
|
19888
20688
|
async dispatch(action, options = {}) {
|
|
19889
|
-
const admitted = this._beginScheduleMutation(action);
|
|
20689
|
+
const admitted = options.preview === true ? false : this._beginScheduleMutation(action);
|
|
19890
20690
|
return this._enqueue(() => this._dispatchInner(action, options, admitted));
|
|
19891
20691
|
}
|
|
19892
20692
|
async applyActivityBatch(action, options = {}) {
|
|
19893
|
-
const admitted = this._beginScheduleMutation(action);
|
|
20693
|
+
const admitted = options.preview === true ? false : this._beginScheduleMutation(action);
|
|
19894
20694
|
return this._enqueue(() => this._dispatchInner(action, options, admitted));
|
|
19895
20695
|
}
|
|
19896
20696
|
async _dispatchInner(action, options = {}, admitted = false) {
|
|
@@ -19905,12 +20705,23 @@ var ScheduleCore = class {
|
|
|
19905
20705
|
}
|
|
19906
20706
|
async _runDispatch(action, options) {
|
|
19907
20707
|
this.assertReady();
|
|
20708
|
+
if (options.preview === true && !PREVIEW_DISPATCH_KINDS.has(action.kind)) {
|
|
20709
|
+
return {
|
|
20710
|
+
result: {
|
|
20711
|
+
ok: false,
|
|
20712
|
+
reason: REJECTION_REASON.PREVIEW_UNSUPPORTED_ACTION
|
|
20713
|
+
},
|
|
20714
|
+
changedState: false
|
|
20715
|
+
};
|
|
20716
|
+
}
|
|
19908
20717
|
const pipelineContext = buildPipelineContext(this.coreRuntime.state, {
|
|
19909
20718
|
hoursPerDay: this.coreRuntime.sector.hoursPerDay,
|
|
19910
20719
|
dateFormat: this.coreRuntime.sector.dateFormat,
|
|
19911
|
-
|
|
20720
|
+
// ISSUE-057 (CP6): sin default silencioso 'days'; la clave se omite si
|
|
20721
|
+
// no se declaró (exactOptionalPropertyTypes).
|
|
20722
|
+
...options.inputUnit !== void 0 ? { inputUnit: options.inputUnit } : {}
|
|
19912
20723
|
});
|
|
19913
|
-
const historyPolicy = getDispatchHistoryPolicy(action);
|
|
20724
|
+
const historyPolicy = getDispatchHistoryPolicy(action, options);
|
|
19914
20725
|
const generatorSnapshot = [
|
|
19915
20726
|
this.coreRuntime.activityIdGenerator.snapshot(),
|
|
19916
20727
|
this.coreRuntime.linkIdGenerator.snapshot(),
|
|
@@ -19931,6 +20742,7 @@ var ScheduleCore = class {
|
|
|
19931
20742
|
uidGen: this.coreRuntime.uniqueCorrelativeIdGenerator,
|
|
19932
20743
|
customIdTracker: this.coreRuntime.customIdTracker,
|
|
19933
20744
|
reporter: this.coreRuntime.reporter,
|
|
20745
|
+
activityClipboard: this._activityClipboard,
|
|
19934
20746
|
defaultBaseCalendarId: this.coreRuntime.baseCalendars.find(
|
|
19935
20747
|
(calendar) => calendar.baseDefault
|
|
19936
20748
|
)?.id ?? null,
|
|
@@ -19973,7 +20785,7 @@ var ScheduleCore = class {
|
|
|
19973
20785
|
action.activityIds,
|
|
19974
20786
|
(activityId) => this.coreRuntime.state.getActivity(activityId)
|
|
19975
20787
|
);
|
|
19976
|
-
} else {
|
|
20788
|
+
} else if (options.preview !== true) {
|
|
19977
20789
|
this._saveTracker.recordChanges(
|
|
19978
20790
|
result.changes.activities,
|
|
19979
20791
|
result.changes.links,
|
|
@@ -19984,6 +20796,20 @@ var ScheduleCore = class {
|
|
|
19984
20796
|
}
|
|
19985
20797
|
result = this._withReappliedViewState(action, result);
|
|
19986
20798
|
if (!result.ok) return { result, changedState: false };
|
|
20799
|
+
if (options.preview === true) {
|
|
20800
|
+
const inverse = buildInverseChangeSet(
|
|
20801
|
+
this.coreRuntime.state,
|
|
20802
|
+
buildUndoEntry(buildHistoryChangeSet(result.changes)),
|
|
20803
|
+
"before"
|
|
20804
|
+
);
|
|
20805
|
+
return {
|
|
20806
|
+
result: {
|
|
20807
|
+
...toPublicDispatchResult(result),
|
|
20808
|
+
preview: { inverse, revision: this._scheduleRevision }
|
|
20809
|
+
},
|
|
20810
|
+
changedState: false
|
|
20811
|
+
};
|
|
20812
|
+
}
|
|
19987
20813
|
if (dispatchChangesSchedulingState(action) && changeSetIsSubstantive(result.changes)) {
|
|
19988
20814
|
this._recordScheduleMutation(
|
|
19989
20815
|
options.skipAutoSchedule !== true && options.skipCriticalPath !== true && resolveRunCriticalPath(action)
|
|
@@ -20498,6 +21324,9 @@ var DEFAULT_HOURS_PER_DAY = 8;
|
|
|
20498
21324
|
|
|
20499
21325
|
// src/dispatch/action-kinds.ts
|
|
20500
21326
|
var DISPATCH_ACTION_KIND = {
|
|
21327
|
+
ACTIVITY_CLIPBOARD_CLEAR: "activity-clipboard-clear",
|
|
21328
|
+
ACTIVITY_COPY: "activity-copy",
|
|
21329
|
+
ACTIVITY_PASTE_COPIED: "activity-paste-copied",
|
|
20501
21330
|
PERSISTENCE_ACKNOWLEDGE: "persistence-acknowledge",
|
|
20502
21331
|
BASELINE_APPLY: "baseline-apply",
|
|
20503
21332
|
PONDERATOR_CRITERION_SET: "ponderator-criterion-set",
|
|
@@ -20515,16 +21344,19 @@ var DISPATCH_ACTION_KIND = {
|
|
|
20515
21344
|
ACTIVITY_INDENT: "activity-indent",
|
|
20516
21345
|
ACTIVITY_OUTDENT: "activity-outdent",
|
|
20517
21346
|
ACTIVITY_SET_PROGRESS: "activity-set-progress",
|
|
20518
|
-
ACTIVITY_PASTE: "activity-paste",
|
|
20519
21347
|
SELECTION_TOGGLE: "selection-toggle",
|
|
20520
21348
|
SELECTION_REPLACE: "selection-replace",
|
|
20521
21349
|
VISIBILITY_SET: "visibility-set",
|
|
20522
21350
|
FILTER_SET: "filter-set",
|
|
20523
21351
|
SORT_SET: "sort-set",
|
|
20524
21352
|
SIR_SYNC: "sir-sync",
|
|
21353
|
+
SIR_APPROVE: "sir-approve",
|
|
20525
21354
|
ACTIVITY_LOOKAHEAD_SYNC: "activity-lookahead-sync"
|
|
20526
21355
|
};
|
|
20527
21356
|
var KIND_CATALOG_COVERS_UNION = {
|
|
21357
|
+
[DISPATCH_ACTION_KIND.ACTIVITY_CLIPBOARD_CLEAR]: true,
|
|
21358
|
+
[DISPATCH_ACTION_KIND.ACTIVITY_COPY]: true,
|
|
21359
|
+
[DISPATCH_ACTION_KIND.ACTIVITY_PASTE_COPIED]: true,
|
|
20528
21360
|
[DISPATCH_ACTION_KIND.PERSISTENCE_ACKNOWLEDGE]: true,
|
|
20529
21361
|
[DISPATCH_ACTION_KIND.BASELINE_APPLY]: true,
|
|
20530
21362
|
[DISPATCH_ACTION_KIND.PONDERATOR_CRITERION_SET]: true,
|
|
@@ -20542,13 +21374,13 @@ var KIND_CATALOG_COVERS_UNION = {
|
|
|
20542
21374
|
[DISPATCH_ACTION_KIND.ACTIVITY_INDENT]: true,
|
|
20543
21375
|
[DISPATCH_ACTION_KIND.ACTIVITY_OUTDENT]: true,
|
|
20544
21376
|
[DISPATCH_ACTION_KIND.ACTIVITY_SET_PROGRESS]: true,
|
|
20545
|
-
[DISPATCH_ACTION_KIND.ACTIVITY_PASTE]: true,
|
|
20546
21377
|
[DISPATCH_ACTION_KIND.SELECTION_TOGGLE]: true,
|
|
20547
21378
|
[DISPATCH_ACTION_KIND.SELECTION_REPLACE]: true,
|
|
20548
21379
|
[DISPATCH_ACTION_KIND.VISIBILITY_SET]: true,
|
|
20549
21380
|
[DISPATCH_ACTION_KIND.FILTER_SET]: true,
|
|
20550
21381
|
[DISPATCH_ACTION_KIND.SORT_SET]: true,
|
|
20551
21382
|
[DISPATCH_ACTION_KIND.SIR_SYNC]: true,
|
|
21383
|
+
[DISPATCH_ACTION_KIND.SIR_APPROVE]: true,
|
|
20552
21384
|
[DISPATCH_ACTION_KIND.ACTIVITY_LOOKAHEAD_SYNC]: true
|
|
20553
21385
|
};
|
|
20554
21386
|
var DISPATCH_ACTION_KINDS = Object.keys(KIND_CATALOG_COVERS_UNION);
|