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