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