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