@outbuild-company/schedule-core 1.1.1 → 1.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -835,49 +835,63 @@ function appendToBucket(index, key, link) {
835
835
  }
836
836
  index.set(key, [link]);
837
837
  }
838
- function filterToChainEndpoints(leaves, adapter, linksByActivity) {
838
+ function filterToChainEndpoints(leaves, adapter, linksByActivity, selectSourceLinks) {
839
839
  if (leaves.length <= 1) return leaves;
840
840
  const leafSet = new Set(leaves.map(String));
841
841
  const keptLeaves = [];
842
842
  for (const leafId of leaves) {
843
- if (!shouldSkipMidChainLeaf(leafId, leafSet, adapter, linksByActivity)) {
843
+ if (!shouldSkipMidChainLeaf(
844
+ leafId,
845
+ leafSet,
846
+ adapter,
847
+ linksByActivity,
848
+ selectSourceLinks
849
+ )) {
844
850
  keptLeaves.push(leafId);
845
851
  }
846
852
  }
847
853
  return keptLeaves.length > 0 ? keptLeaves : leaves;
848
854
  }
849
- function shouldSkipMidChainLeaf(leafId, leafSet, adapter, linksByActivity) {
855
+ function shouldSkipMidChainLeaf(leafId, leafSet, adapter, linksByActivity, selectSourceLinks) {
850
856
  const leafKey = String(leafId);
851
857
  const leafTask = adapter.getActivity(leafId);
852
858
  if (!leafTask) return false;
853
859
  if (leafTask.autoScheduling === false) return false;
854
860
  const linksOnThis = linksByActivity.get(leafKey) ?? [];
855
861
  for (const siblingLink of linksOnThis) {
856
- if (isSelfLink(siblingLink)) continue;
857
- const otherId = otherEndOf(siblingLink, leafKey);
858
- if (!leafSet.has(String(otherId))) continue;
859
- const siblingTask = adapter.getActivity(otherId);
860
- if (!siblingTask) continue;
861
- if (siblingTask.autoScheduling === false) continue;
862
- if (linkConnectsMidChain(siblingLink, leafId, leafTask, otherId, siblingTask)) {
862
+ if (isMidChainConnection(
863
+ siblingLink,
864
+ leafKey,
865
+ leafTask,
866
+ leafSet,
867
+ adapter,
868
+ selectSourceLinks
869
+ ))
863
870
  return true;
864
- }
865
871
  }
866
872
  return false;
867
873
  }
874
+ function isMidChainConnection(link, leafKey, leafTask, leafSet, adapter, selectSourceLinks) {
875
+ if (isSelfLink(link)) return false;
876
+ const relevantDirection = selectSourceLinks ? String(link.source) === leafKey : String(link.target) === leafKey;
877
+ if (!relevantDirection) return false;
878
+ const otherId = otherEndOf(link, leafKey);
879
+ if (!leafSet.has(String(otherId))) return false;
880
+ const siblingTask = adapter.getActivity(otherId);
881
+ return Boolean(
882
+ siblingTask?.autoScheduling !== false && siblingTask && linkConnectsMidChain(link, leafTask, siblingTask)
883
+ );
884
+ }
868
885
  function isSelfLink(link) {
869
886
  return link.source === link.target;
870
887
  }
871
888
  function otherEndOf(link, knownEndKey) {
872
889
  return String(link.source) === knownEndKey ? link.target : link.source;
873
890
  }
874
- function linkConnectsMidChain(link, leafId, leafTask, otherId, siblingTask) {
891
+ function linkConnectsMidChain(link, leafTask, siblingTask) {
875
892
  const lag = link.lag ?? 0;
876
893
  const absLag = Math.abs(lag);
877
- if (link.target === leafId && absLag <= leafTask.durationHours) return true;
878
- if (link.target === otherId && absLag <= siblingTask.durationHours)
879
- return true;
880
- return false;
894
+ return String(link.target) === String(siblingTask.id) && absLag <= siblingTask.durationHours || String(link.target) === String(leafTask.id) && absLag <= leafTask.durationHours;
881
895
  }
882
896
  function expandParentLinks(links, adapter) {
883
897
  const expanded = [];
@@ -886,115 +900,98 @@ function expandParentLinks(links, adapter) {
886
900
  for (const link of links) {
887
901
  const sourceIsSummary = isSummary2(link.source, adapter);
888
902
  const targetIsSummary = isSummary2(link.target, adapter);
889
- if (sourceIsSummary && targetIsSummary) continue;
890
- if (targetIsSummary && isDescendantOf(link.source, link.target, adapter) || sourceIsSummary && isDescendantOf(link.target, link.source, adapter)) {
903
+ if (isInternalSummaryLink(link, sourceIsSummary, targetIsSummary, adapter))
891
904
  continue;
892
- }
893
905
  if (!sourceIsSummary && !targetIsSummary) {
894
906
  expanded.push(link);
895
907
  continue;
896
908
  }
897
- let sourceIds = sourceIsSummary ? getLeafDescendants(link.source, adapter) : [link.source];
898
- let targetIds = targetIsSummary ? getLeafDescendants(link.target, adapter) : [link.target];
899
- if (sourceIsSummary) {
900
- sourceIds = keepBoundDefiningLeaves(sourceIds, false, adapter);
901
- }
902
- if (targetIsSummary) {
903
- const chainEndpoints = filterToChainEndpoints(
904
- targetIds,
905
- adapter,
906
- linksByActivity
907
- );
908
- if (link.type === LINK_TYPE.FINISH_TO_FINISH || link.type === LINK_TYPE.START_TO_FINISH) {
909
- virtualIdCounter = pushOffsetPreservingLinks(
910
- expanded,
911
- sourceIds,
912
- chainEndpoints,
913
- link,
914
- link.target,
915
- adapter,
916
- virtualIdCounter
917
- );
918
- continue;
919
- }
920
- targetIds = keepBoundDefiningLeaves(chainEndpoints, true, adapter);
921
- }
922
- virtualIdCounter = pushExpandedLinks(
909
+ const sourceIds = expandedEndpoints(
910
+ link.source,
911
+ sourceIsSummary,
912
+ adapter,
913
+ linksByActivity,
914
+ true
915
+ );
916
+ const targetIds = expandedEndpoints(
917
+ link.target,
918
+ targetIsSummary,
919
+ adapter,
920
+ linksByActivity,
921
+ false
922
+ );
923
+ virtualIdCounter = pushOffsetPreservingLinks(
923
924
  expanded,
924
925
  sourceIds,
925
926
  targetIds,
926
927
  link,
928
+ adapter,
927
929
  virtualIdCounter
928
930
  );
929
931
  }
930
932
  return expanded;
931
933
  }
932
- function pushOffsetPreservingLinks(expanded, sourceIds, leafIds, originalLink, summaryId, adapter, virtualIdCounter) {
933
- const summary = adapter.getActivity(summaryId);
934
- const summaryStart = summary?.startDate;
935
- const summaryDuration = summary?.durationHours ?? 0;
936
- const userLag = originalLink.lag ?? 0;
934
+ function isInternalSummaryLink(link, sourceIsSummary, targetIsSummary, adapter) {
935
+ return targetIsSummary && isDescendantOf(link.source, link.target, adapter) || sourceIsSummary && isDescendantOf(link.target, link.source, adapter);
936
+ }
937
+ function expandedEndpoints(activityId, summary, adapter, linksByActivity, sourceSide) {
938
+ if (!summary) return [activityId];
939
+ return filterToChainEndpoints(
940
+ getLeafDescendants(activityId, adapter),
941
+ adapter,
942
+ linksByActivity,
943
+ sourceSide
944
+ );
945
+ }
946
+ function pushOffsetPreservingLinks(expanded, sourceIds, leafIds, originalLink, adapter, virtualIdCounter) {
937
947
  const source = adapter.getActivity(originalLink.source);
948
+ const target = adapter.getActivity(originalLink.target);
949
+ const targetStart = target?.startDate;
950
+ const userLag = originalLink.lag ?? 0;
951
+ const sourceEndpointIsStart = originalLink.type === LINK_TYPE.START_TO_START || originalLink.type === LINK_TYPE.START_TO_FINISH;
952
+ const targetEndpointIsFinish = originalLink.type === LINK_TYPE.FINISH_TO_FINISH || originalLink.type === LINK_TYPE.START_TO_FINISH;
953
+ const sourceLag = sourceEndpointIsStart ? -(source?.durationHours ?? 0) : 0;
954
+ const targetLag = targetEndpointIsFinish ? -(target?.durationHours ?? 0) : 0;
938
955
  let counter = virtualIdCounter;
939
956
  for (const srcId of sourceIds) {
940
957
  for (const tgtId of leafIds) {
941
958
  if (String(srcId) === String(tgtId)) continue;
942
- const leaf = adapter.getActivity(tgtId);
943
- const offTarget = summaryStart && leaf && source ? adapter.calculateDuration({
944
- startDate: summaryStart,
945
- endDate: leaf.startDate,
946
- task: source
947
- }) : 0;
948
- expanded.push({
949
- id: `virtual_${originalLink.id}_${counter}`,
950
- source: srcId,
951
- target: tgtId,
952
- type: originalLink.type,
953
- lag: originalLink.lag,
954
- _sourceLag: 0,
955
- _targetLag: -summaryDuration,
956
- _trueLag: userLag + offTarget
957
- });
959
+ expanded.push(
960
+ buildVirtualLink(
961
+ originalLink,
962
+ srcId,
963
+ tgtId,
964
+ counter,
965
+ sourceLag,
966
+ targetLag,
967
+ userLag,
968
+ source,
969
+ targetStart,
970
+ adapter
971
+ )
972
+ );
958
973
  counter += 1;
959
974
  }
960
975
  }
961
976
  return counter;
962
977
  }
963
- function keepBoundDefiningLeaves(leaves, startBound, adapter) {
964
- if (leaves.length <= 1) return leaves;
965
- let best = null;
966
- for (const id of leaves) {
967
- const a = adapter.getActivity(id);
968
- const d = startBound ? a?.startDate : a?.endDate;
969
- if (!d) continue;
970
- const t = d.getTime();
971
- if (best === null) best = t;
972
- else if (startBound ? t < best : t > best) best = t;
973
- }
974
- if (best === null) return leaves;
975
- const kept = leaves.filter((id) => {
976
- const a = adapter.getActivity(id);
977
- const d = startBound ? a?.startDate : a?.endDate;
978
- return d != null && d.getTime() === best;
979
- });
980
- return kept.length > 0 ? kept : leaves;
981
- }
982
- function pushExpandedLinks(expanded, sourceIds, targetIds, originalLink, virtualIdCounter) {
983
- let counter = virtualIdCounter;
984
- for (const srcId of sourceIds) {
985
- for (const tgtId of targetIds) {
986
- if (String(srcId) === String(tgtId)) continue;
987
- expanded.push({
988
- id: `virtual_${originalLink.id}_${counter}`,
989
- source: srcId,
990
- target: tgtId,
991
- type: originalLink.type,
992
- lag: originalLink.lag
993
- });
994
- counter += 1;
995
- }
996
- }
997
- return counter;
978
+ function buildVirtualLink(originalLink, sourceId, targetId, counter, sourceLag, targetLag, userLag, source, targetStart, adapter) {
979
+ const leaf = adapter.getActivity(targetId);
980
+ const offTarget = targetStart && leaf && source ? adapter.calculateDuration({
981
+ startDate: targetStart,
982
+ endDate: leaf.startDate,
983
+ task: source
984
+ }) : 0;
985
+ return {
986
+ id: `virtual_${originalLink.id}_${counter}`,
987
+ source: sourceId,
988
+ target: targetId,
989
+ type: originalLink.type,
990
+ lag: originalLink.lag,
991
+ _sourceLag: sourceLag,
992
+ _targetLag: targetLag,
993
+ _trueLag: userLag + offTarget
994
+ };
998
995
  }
999
996
 
1000
997
  // src/autoscheduler/utils/expanded-links-cache.ts
@@ -2260,12 +2257,6 @@ async function updateParentBoundsFromChildren(adapter, dirtyIds, recomputeProgre
2260
2257
  recomputeRealWork: options.recomputeRealWork ?? false
2261
2258
  });
2262
2259
  }
2263
- function recomputeParentBoundsSync(adapter, dirtyIds, recomputeProgress = true) {
2264
- const parents = findAncestorsOfDirty(adapter, dirtyIds);
2265
- for (const parentId of parents) {
2266
- recomputeParentFromChildren(parentId, adapter, recomputeProgress);
2267
- }
2268
- }
2269
2260
  function recomputeSingleParentDisplayBounds(parentId, adapter) {
2270
2261
  recomputeParentFromChildren(parentId, adapter, false);
2271
2262
  }
@@ -3766,6 +3757,7 @@ function buildActivityChanges(adapter, before, touched) {
3766
3757
  }
3767
3758
  const beforeSnap = before.get(id);
3768
3759
  const diff = diffActivity(beforeSnap, afterSnap);
3760
+ if (beforeSnap && Object.keys(diff).length === 0) continue;
3769
3761
  if (!beforeSnap) {
3770
3762
  out.push({
3771
3763
  id,
@@ -3919,6 +3911,77 @@ function computeExpectedProgressLive(rootIds, now, state) {
3919
3911
  for (const activityId of rootIds) visit(activityId);
3920
3912
  return changed;
3921
3913
  }
3914
+ function applyExpectedProgressLive(state, now) {
3915
+ const rootIds = [];
3916
+ state.forEachActivity((activity, activityId) => {
3917
+ if (activity.parentId === null) rootIds.push(activityId);
3918
+ });
3919
+ computeExpectedProgressLive(rootIds, now, state);
3920
+ }
3921
+
3922
+ // src/propagations/shared/get-active-baseline.ts
3923
+ function getActiveBaseline(activity) {
3924
+ const points = activity?.baselinePoints;
3925
+ if (!Array.isArray(points) || points.length === 0) return null;
3926
+ return points.find((point) => point.isActiveVersion) ?? null;
3927
+ }
3928
+
3929
+ // src/columns/expected-progress-base/compute.ts
3930
+ var isProject2 = (a) => a.type === "project";
3931
+ var activeBaselineOf = (a) => getActiveBaseline(a);
3932
+ var leafExpected = (a, now, adapter, defaultBaseCalendarId) => {
3933
+ const base = activeBaselineOf(a);
3934
+ if (!base) return 0;
3935
+ const cal = (base.baseCalendarId != null ? adapter.getBaseCalendar(`${base.baseCalendarId}-base`) : null) ?? (defaultBaseCalendarId ? adapter.getBaseCalendar(defaultBaseCalendarId) : null);
3936
+ if (!cal) return 0;
3937
+ if (!base.startDate || !base.endDate) return 0;
3938
+ return expectedProgressFromBaseline(base.startDate, base.endDate, now, cal);
3939
+ };
3940
+ function computeExpectedProgress(rootIds, now, adapter, defaultBaseCalendarId = null) {
3941
+ const changed = [];
3942
+ const writeIfChanged = (id, value) => {
3943
+ const a = adapter.getActivity(id);
3944
+ if (!a) return;
3945
+ if (a.expectedProgressBaseline !== value) {
3946
+ adapter.setActivityField(id, "expectedProgressBaseline", value);
3947
+ changed.push(id);
3948
+ }
3949
+ };
3950
+ const visit = (id) => {
3951
+ const a = adapter.getActivity(id);
3952
+ if (!a) return 0;
3953
+ if (!isProject2(a)) {
3954
+ const v2 = leafExpected(a, now, adapter, defaultBaseCalendarId);
3955
+ writeIfChanged(id, v2);
3956
+ return v2;
3957
+ }
3958
+ let acc = 0;
3959
+ for (const childId of adapter.getChildrenIds(id)) {
3960
+ const childBase = visit(childId);
3961
+ const child = adapter.getActivity(childId);
3962
+ const weight = typeof child?.ponderator === "number" ? child.ponderator : 0;
3963
+ acc += childBase * weight;
3964
+ }
3965
+ const v = acc / 100;
3966
+ writeIfChanged(id, v);
3967
+ return v;
3968
+ };
3969
+ for (const id of rootIds) visit(id);
3970
+ return changed;
3971
+ }
3972
+ function runExpectedProgressBase(state, now, defaultBaseCalendarId = null) {
3973
+ const adapter = {
3974
+ getChildrenIds: (parentId) => state.getChildren(String(parentId)).map((childId) => String(childId)),
3975
+ getActivity: (id) => state.getActivity(id),
3976
+ setActivityField: (id, field, value) => setActivityFieldDynamic(state, id, field, value),
3977
+ getBaseCalendar: (engineId) => state.calendarReader.getCalendar(engineId)
3978
+ };
3979
+ const rootIds = [];
3980
+ state.forEachActivity((activity, id) => {
3981
+ if (activity.parentId === null) rootIds.push(id);
3982
+ });
3983
+ return computeExpectedProgress(rootIds, now, adapter, defaultBaseCalendarId);
3984
+ }
3922
3985
 
3923
3986
  // src/columns/status/compute.ts
3924
3987
  var roundToTwoDecimals = (value) => Math.round(value * 100) / 100;
@@ -3954,594 +4017,290 @@ function applyStatusPass(state, criteria) {
3954
4017
  return changed;
3955
4018
  }
3956
4019
 
3957
- // src/internal/hierarchy/visual-order.ts
3958
- var NOT_SET_SORT_VALUE = Number.MAX_SAFE_INTEGER;
3959
- function getChildrenInVisualOrder(parentId, adapter) {
3960
- const children = collectChildrenSnapshots(parentId, adapter);
3961
- children.sort(byCorrelativeIdThenId);
3962
- return children.map((a) => String(a.id));
3963
- }
3964
- function iterateInVisualOrder(adapter) {
3965
- const result = [];
3966
- const visit = (activity) => {
3967
- result.push(activity);
3968
- const childIds = getChildrenInVisualOrder(String(activity.id), adapter);
3969
- for (const childId of childIds) {
3970
- const child = adapter.getActivity(childId);
3971
- if (child) visit(child);
3972
- }
4020
+ // src/critical-path/legacy/shims/moment.js
4021
+ function moment(input) {
4022
+ const d = input instanceof Date ? new Date(input.getTime()) : input && typeof input === "object" && typeof input.valueOf === "function" ? new Date(input.valueOf()) : new Date(input);
4023
+ const accessor = (getUTC, setUTC) => (value) => {
4024
+ if (value === void 0) return d[getUTC]();
4025
+ d[setUTC](value);
4026
+ return api;
3973
4027
  };
3974
- const rootIds = getChildrenInVisualOrder(ROOT_PARENT_ID, adapter);
3975
- for (const rootId of rootIds) {
3976
- const root = adapter.getActivity(rootId);
3977
- if (root) visit(root);
3978
- }
3979
- return result;
4028
+ const api = {
4029
+ __isMomentShim: true,
4030
+ clone: () => moment(d),
4031
+ hours: accessor("getUTCHours", "setUTCHours"),
4032
+ minutes: accessor("getUTCMinutes", "setUTCMinutes"),
4033
+ seconds: accessor("getUTCSeconds", "setUTCSeconds"),
4034
+ milliseconds: accessor("getUTCMilliseconds", "setUTCMilliseconds"),
4035
+ valueOf: () => d.getTime(),
4036
+ toDate: () => new Date(d.getTime()),
4037
+ getTime: () => d.getTime()
4038
+ };
4039
+ return api;
3980
4040
  }
3981
- function findPreviousNonSelectedSibling(taskId, selected, adapter) {
3982
- const activity = adapter.getActivity(taskId);
3983
- if (!activity) return null;
3984
- const parentKey = parentKeyOf(activity);
3985
- const siblings = getChildrenInVisualOrder(parentKey, adapter);
3986
- const idx = siblings.findIndex((id) => String(id) === String(taskId));
3987
- if (idx <= 0) return null;
3988
- for (let i = idx - 1; i >= 0; i--) {
3989
- const candidateId = siblings[i];
3990
- if (candidateId === void 0) continue;
3991
- if (!setHas(selected, candidateId)) return candidateId;
4041
+ moment.isMoment = (x) => Boolean(x) && typeof x === "object" && x.__isMomentShim === true;
4042
+ var moment_default = moment;
4043
+
4044
+ // src/critical-path/legacy/helpers/isValidDate.js
4045
+ function isDate(obj) {
4046
+ if (obj && obj.constructor === Date) {
4047
+ return Boolean(obj.getFullYear && obj.getMonth && obj.getDate);
3992
4048
  }
3993
- return null;
4049
+ return false;
3994
4050
  }
3995
- function visualIndexInParent(taskId, adapter) {
3996
- const activity = adapter.getActivity(taskId);
3997
- if (!activity) return -1;
3998
- const parentKey = parentKeyOf(activity);
3999
- const siblings = getChildrenInVisualOrder(parentKey, adapter);
4000
- return siblings.findIndex((id) => String(id) === String(taskId));
4051
+ function isValidDate2(obj) {
4052
+ return isDate(obj) && !isNaN(obj.getTime());
4001
4053
  }
4002
- function collectChildrenSnapshots(parentId, adapter) {
4003
- const key = String(parentId);
4004
- if (key === "0") {
4005
- return adapter.getAllActivities().filter((a) => a.parentId === null);
4006
- }
4007
- const childIds = adapter.getChildren(parentId);
4008
- const out = [];
4009
- for (const id of childIds) {
4010
- const snap = adapter.getActivity(id);
4011
- if (snap) out.push(snap);
4054
+
4055
+ // src/critical-path/legacy/helpers/addDurationToDate.js
4056
+ function addDurationToDate(activityCalendar, date2, duration, activity) {
4057
+ if (!isValidDate2(new Date(date2))) {
4058
+ throw new Error(
4059
+ `Invalid date argument for calculateEndDate method. Date: ${date2}, Activity: ${JSON.stringify(activity)}`
4060
+ );
4012
4061
  }
4013
- return out;
4014
- }
4015
- function byCorrelativeIdThenId(a, b) {
4016
- const ac = correlativeIdOf(a);
4017
- const bc = correlativeIdOf(b);
4018
- if (ac !== bc) return ac - bc;
4019
- return String(a.id).localeCompare(String(b.id));
4020
- }
4021
- function correlativeIdOf(activity) {
4022
- const raw = activity.correlativeId;
4023
- const n = typeof raw === "number" ? raw : Number(raw);
4024
- return Number.isFinite(n) ? n : NOT_SET_SORT_VALUE;
4025
- }
4026
- function parentKeyOf(activity) {
4027
- const parentId = activity.parentId;
4028
- if (parentId === null) return "0";
4029
- return parentId;
4030
- }
4031
- function setHas(set, id) {
4032
- if (set.has(id)) return true;
4033
- return set.has(String(id));
4062
+ const dateToCalculate = date2;
4063
+ return activityCalendar.calculateEndDate({
4064
+ start_date: new Date(dateToCalculate),
4065
+ duration,
4066
+ unit: "hour",
4067
+ task: activity
4068
+ });
4034
4069
  }
4035
4070
 
4036
- // src/internal/hierarchy/recompute-correlative-ids.ts
4037
- function recomputeCorrelativeIds(adapter) {
4038
- adapter.invalidateVisualOrderIds?.();
4039
- const roots = [];
4040
- adapter.forEachActivity((activity) => {
4041
- if (isRootParent(activity.parentId)) roots.push(activity);
4071
+ // src/critical-path/legacy/helpers/getNextWorkingHour.js
4072
+ function getNextWorkingHour(activityParams) {
4073
+ if (!isValidDate2(new Date(activityParams.dateBaseToCalculate))) {
4074
+ throw new Error("Invalid date argument for getClosestWorkTime method");
4075
+ }
4076
+ const { dateBaseToCalculate, activityCalendar, direction, activity } = activityParams;
4077
+ const newDate = activityCalendar.$gantt.getClosestWorkTime({
4078
+ date: new Date(dateBaseToCalculate),
4079
+ dir: direction,
4080
+ unit: "hour",
4081
+ task: activity
4042
4082
  });
4043
- roots.sort(byCorrelativeId);
4044
- const stack = [];
4045
- pushInReverseOrder(stack, roots);
4046
- const shifts = [];
4047
- let counter = 0;
4048
- while (stack.length > 0) {
4049
- const activity = stack.pop();
4050
- if (activity.correlativeId !== counter) {
4051
- const before = typeof activity.correlativeId === "number" ? activity.correlativeId : void 0;
4052
- shifts.push({ activityId: String(activity.id), before });
4053
- if (before !== void 0)
4054
- adapter.noteCorrelativeBefore?.(String(activity.id), before);
4055
- setCorrelativeId(activity, counter);
4056
- }
4057
- counter += 1;
4058
- const children = collectChildrenSnapshots(String(activity.id), adapter);
4059
- children.sort(byCorrelativeId);
4060
- pushInReverseOrder(stack, children);
4061
- }
4062
- return shifts;
4063
- }
4064
- function pushInReverseOrder(stack, activities) {
4065
- for (let i = activities.length - 1; i >= 0; i -= 1) {
4066
- stack.push(activities[i]);
4067
- }
4068
- }
4069
- function getCorrelativeId(activity) {
4070
- const raw = activity.correlativeId;
4071
- const n = typeof raw === "number" ? raw : Number(raw);
4072
- return Number.isFinite(n) ? n : NOT_SET_SORT_VALUE;
4073
- }
4074
- var byCorrelativeId = (a, b) => getCorrelativeId(a) - getCorrelativeId(b);
4075
- function setCorrelativeId(activity, value) {
4076
- activity.correlativeId = value;
4083
+ return newDate;
4077
4084
  }
4078
4085
 
4079
- // src/propagations/upward/recompute-real-work.ts
4080
- function recomputeCanonicalRealWork(state) {
4081
- const rootIds = [];
4082
- const changedIds = [];
4083
- state.forEachActivity((activity, activityId) => {
4084
- if (activity.parentId === null) rootIds.push(String(activityId));
4085
- });
4086
- const visit = (activityId) => {
4087
- const activity = state.getActivity(activityId);
4088
- if (!activity) return 0;
4089
- const childIds = [...state.getChildren(activityId)];
4090
- const realWork = childIds.length === 0 ? computeLeafRealWork(activity.workHours, activity.progress) : childIds.reduce(
4091
- (total, childId) => total + visit(String(childId)),
4092
- 0
4093
- );
4094
- state.setActivityField(
4095
- activityId,
4096
- ACTIVITY_PROPERTY.REAL_WORK_HOURS,
4097
- realWork
4098
- );
4099
- changedIds.push(activityId);
4100
- return realWork;
4086
+ // src/critical-path/legacy/helpers/filters.js
4087
+ var byFirstLevel = (activity) => {
4088
+ if (!activity) return false;
4089
+ return activity["$level"] === 1;
4090
+ };
4091
+ var filterByParentType = (activity) => activity.type === "project";
4092
+ var avoidSubproject = (activity) => activity["$level"] !== 0;
4093
+ var byTaskType = (activity) => activity.type === "task";
4094
+ var byTaskTypeAndMilestone = (activity) => activity.type === "task" || activity.type === "milestone";
4095
+ var filters_default = {
4096
+ byFirstLevel,
4097
+ filterByParentType,
4098
+ avoidSubproject,
4099
+ byTaskType,
4100
+ byTaskTypeAndMilestone
4101
+ };
4102
+
4103
+ // src/critical-path/legacy/constants/constraints.js
4104
+ var CONSTRAINT_TYPES = Object.freeze({
4105
+ MFO: "mfo",
4106
+ MSO: "mso",
4107
+ FNLT: "fnlt",
4108
+ FNET: "fnet",
4109
+ SNET: "snet",
4110
+ SNLT: "snlt",
4111
+ ALAP: "alap",
4112
+ ASAP: "asap"
4113
+ });
4114
+
4115
+ // src/critical-path/legacy/constants/linkTypes.js
4116
+ var LINK_TYPES = Object.freeze({
4117
+ FS: "fs",
4118
+ SS: "ss",
4119
+ FF: "ff",
4120
+ SF: "sf"
4121
+ });
4122
+
4123
+ // src/critical-path/legacy/constants/linkByCode.js
4124
+ function getLinkNameByCode(code) {
4125
+ const linkMap = {
4126
+ 0: "fs",
4127
+ 1: "ss",
4128
+ 2: "ff",
4129
+ 3: "sf"
4101
4130
  };
4102
- for (const rootId of rootIds) visit(rootId);
4103
- return changedIds;
4131
+ return linkMap[Number(code)] || "sf";
4104
4132
  }
4105
4133
 
4106
- // src/init/initial-passes.ts
4107
- async function runInitialPasses(deps) {
4108
- const { state, sector, scheduler, skipAutoSchedule, now } = deps;
4109
- recomputeCorrelativeIds(state);
4110
- if (sector.updateDurationForPrimaveraEndDate) {
4111
- recomputeDurationsFromDates(state);
4112
- }
4113
- normalizeLoadedConstraintDates(state);
4114
- recomputeCanonicalRealWork(state);
4115
- if (skipAutoSchedule) {
4116
- if (now) {
4117
- applyExpectedProgressLive(state, now);
4118
- applyStatusPass(state, sector.statusCriteria);
4134
+ // src/critical-path/legacy/base/identifiers/secondLevelActivities.js
4135
+ function identifySecondLevelActivities(params) {
4136
+ const { gantt, pendingParentsWithNoLinks, calculations, linkProperty } = params;
4137
+ try {
4138
+ const subprojectsWithNoLinks = getSubprojectsWithNoLinks({
4139
+ gantt,
4140
+ pendingParentsWithNoLinks
4141
+ });
4142
+ if (subprojectsWithNoLinks.length === 0) {
4143
+ return /* @__PURE__ */ new Set();
4119
4144
  }
4120
- return;
4121
- }
4122
- const result = await scheduler.schedule({});
4123
- applyResults(state, result);
4124
- normalizeMilestoneConstraintDatesForDisplay(state);
4125
- recomputeSkippedLeafEnds(state);
4126
- await updateParentBoundsFromChildren(state, void 0, false);
4127
- if (now) {
4128
- applyExpectedProgressLive(state, now);
4129
- applyStatusPass(state, sector.statusCriteria);
4145
+ const allChildrenFromParents = getAllChildrenFromParents({
4146
+ gantt,
4147
+ subprojectsWithNoLinks
4148
+ });
4149
+ const childrenThatCanBeCalculated = filterChildrenThatCanBeCalculated({
4150
+ gantt,
4151
+ childrenIds: allChildrenFromParents,
4152
+ linkProperty,
4153
+ getArrayOfLinkedActivities,
4154
+ calculations
4155
+ });
4156
+ return new Set(childrenThatCanBeCalculated.map((activity) => activity.id));
4157
+ } catch (e) {
4158
+ throw e;
4130
4159
  }
4131
4160
  }
4132
- function applyExpectedProgressLive(state, now) {
4133
- const rootIds = [];
4134
- state.forEachActivity((activity, activityId) => {
4135
- if (activity.parentId === null) rootIds.push(activityId);
4136
- });
4137
- computeExpectedProgressLive(rootIds, now, state);
4161
+ function getSubprojectsWithNoLinks(params) {
4162
+ const { gantt, pendingParentsWithNoLinks } = params;
4163
+ return gantt.getTaskByTime().filter(filters_default.filterByParentType).filter((activity) => pendingParentsWithNoLinks.has(activity.id)).map((activity) => activity.id);
4138
4164
  }
4139
- function recomputeSkippedLeafEnds(state) {
4140
- state.forEachActivity((activity) => {
4141
- if (activity.autoScheduling) return;
4142
- if (!activity.startDate || activity.durationHours == null) return;
4143
- if (state.getChildren(activity.id).length > 0) return;
4144
- activity.endDate = state.calculateEndDate({
4145
- startDate: activity.startDate,
4146
- durationHours: activity.durationHours,
4147
- task: activity
4165
+ function getAllChildrenFromParents(params) {
4166
+ const { gantt, subprojectsWithNoLinks } = params;
4167
+ const allChildrenSet = /* @__PURE__ */ new Set();
4168
+ for (const subprojectId of subprojectsWithNoLinks) {
4169
+ const childrenIds = gantt.getChildren(subprojectId);
4170
+ const validChildren = childrenIds.filter((childId) => {
4171
+ const childActivity = gantt.getTask(childId);
4172
+ const isTaskOrMilestone = filters_default.byTaskTypeAndMilestone(childActivity);
4173
+ const hasProgressLessThan100 = Number(childActivity.progress) < 100;
4174
+ return isTaskOrMilestone && hasProgressLessThan100;
4148
4175
  });
4149
- });
4176
+ validChildren.forEach((childId) => allChildrenSet.add(childId));
4177
+ }
4178
+ return [...allChildrenSet];
4150
4179
  }
4151
- function recomputeDurationsFromDates(state) {
4152
- state.forEachActivity((activity) => {
4153
- if (!activity.startDate || !activity.endDate) return;
4154
- activity.durationHours = state.calculateDuration({
4155
- startDate: activity.startDate,
4156
- endDate: activity.endDate,
4157
- task: activity
4180
+ function filterChildrenThatCanBeCalculated(params) {
4181
+ const {
4182
+ gantt,
4183
+ childrenIds,
4184
+ linkProperty,
4185
+ getArrayOfLinkedActivities: getArrayOfLinkedActivities3,
4186
+ calculations
4187
+ } = params;
4188
+ const calculableChildren = [];
4189
+ for (const childId of childrenIds) {
4190
+ const activity = gantt.getTask(childId);
4191
+ const links = activity[linkProperty] || [];
4192
+ const linkedActivities = getArrayOfLinkedActivities3({
4193
+ gantt,
4194
+ links,
4195
+ linkDirection: linkProperty === "$source" ? "target" : "source"
4158
4196
  });
4159
- });
4160
- }
4161
- function normalizeLoadedConstraintDates(state) {
4162
- state.forEachActivity((activity) => {
4163
- const type = activity.constraintType;
4164
- if (!type || !activity.constraintDate) return;
4165
- const constraintDate = activity.constraintDate;
4166
- if (activity.type === "milestone" && (constraintDate.getUTCHours() !== 0 || constraintDate.getUTCMinutes() !== 0)) {
4167
- return;
4168
- }
4169
- const calendar = state.calendarReader.getCalendar(
4170
- String(activity.calendarId)
4171
- );
4172
- if (!calendar) return;
4173
- activity.constraintDate = normalizeConstraintDate(
4174
- activity.constraintDate,
4175
- calendar,
4176
- type
4197
+ const isLinkedWithCalculatedOrSibling = linkedActivities.every(
4198
+ (linkedActivityId) => calculations.has(linkedActivityId)
4177
4199
  );
4178
- });
4200
+ if (isLinkedWithCalculatedOrSibling) {
4201
+ calculableChildren.push(activity);
4202
+ }
4203
+ }
4204
+ return calculableChildren;
4179
4205
  }
4180
- function normalizeMilestoneConstraintDatesForDisplay(state) {
4181
- state.forEachActivity((activity) => {
4182
- if (activity.type !== "milestone") return;
4183
- const type = activity.constraintType;
4184
- if (!type || !activity.constraintDate) return;
4185
- const calendar = state.calendarReader.getCalendar(
4186
- String(activity.calendarId)
4187
- );
4188
- if (!calendar) return;
4189
- activity.constraintDate = normalizeConstraintDate(
4190
- activity.constraintDate,
4191
- calendar,
4192
- type
4193
- );
4194
- });
4206
+ function getArrayOfLinkedActivities(params) {
4207
+ const { gantt, links, linkDirection } = params;
4208
+ const linkedActivityIds = [];
4209
+ for (const linkId of links) {
4210
+ const link = gantt.getLink(linkId);
4211
+ if (!link) {
4212
+ continue;
4213
+ }
4214
+ const linkedActivityId = Number(link[linkDirection]);
4215
+ linkedActivityIds.push(linkedActivityId);
4216
+ }
4217
+ return linkedActivityIds;
4195
4218
  }
4196
4219
 
4197
- // src/propagations/shared/get-active-baseline.ts
4198
- function getActiveBaseline(activity) {
4199
- const points = activity?.baselinePoints;
4200
- if (!Array.isArray(points) || points.length === 0) return null;
4201
- return points.find((point) => point.isActiveVersion) ?? null;
4202
- }
4203
-
4204
- // src/columns/expected-progress-base/compute.ts
4205
- var isProject2 = (a) => a.type === "project";
4206
- var activeBaselineOf = (a) => getActiveBaseline(a);
4207
- var leafExpected = (a, now, adapter, defaultBaseCalendarId) => {
4208
- const base = activeBaselineOf(a);
4209
- if (!base) return 0;
4210
- const cal = (base.baseCalendarId != null ? adapter.getBaseCalendar(`${base.baseCalendarId}-base`) : null) ?? (defaultBaseCalendarId ? adapter.getBaseCalendar(defaultBaseCalendarId) : null);
4211
- if (!cal) return 0;
4212
- if (!base.startDate || !base.endDate) return 0;
4213
- return expectedProgressFromBaseline(base.startDate, base.endDate, now, cal);
4214
- };
4215
- function computeExpectedProgress(rootIds, now, adapter, defaultBaseCalendarId = null) {
4216
- const changed = [];
4217
- const writeIfChanged = (id, value) => {
4218
- const a = adapter.getActivity(id);
4219
- if (!a) return;
4220
- if (a.expectedProgressBaseline !== value) {
4221
- adapter.setActivityField(id, "expectedProgressBaseline", value);
4222
- changed.push(id);
4223
- }
4224
- };
4225
- const visit = (id) => {
4226
- const a = adapter.getActivity(id);
4227
- if (!a) return 0;
4228
- if (!isProject2(a)) {
4229
- const v2 = leafExpected(a, now, adapter, defaultBaseCalendarId);
4230
- writeIfChanged(id, v2);
4231
- return v2;
4220
+ // src/critical-path/legacy/base/identifiers/initialActivities.js
4221
+ function identifyInitialActivities({ gantt, linkProperty, direction }) {
4222
+ try {
4223
+ const chainStartActivities = getChainStartActivities(gantt, linkProperty);
4224
+ if (direction === "forward") {
4225
+ return {
4226
+ chainStartActivities,
4227
+ activitiesStartChainButChildrenOfLinkedParents: null
4228
+ };
4232
4229
  }
4233
- let acc = 0;
4234
- for (const childId of adapter.getChildrenIds(id)) {
4235
- const childBase = visit(childId);
4236
- const child = adapter.getActivity(childId);
4237
- const weight = typeof child?.ponderator === "number" ? child.ponderator : 0;
4238
- acc += childBase * weight;
4230
+ if (direction === "backward") {
4231
+ return processBackwardActivities(
4232
+ chainStartActivities,
4233
+ gantt,
4234
+ linkProperty
4235
+ );
4239
4236
  }
4240
- const v = acc / 100;
4241
- writeIfChanged(id, v);
4242
- return v;
4243
- };
4244
- for (const id of rootIds) visit(id);
4245
- return changed;
4246
- }
4247
- function runExpectedProgressBase(state, now, defaultBaseCalendarId = null) {
4248
- const adapter = {
4249
- getChildrenIds: (parentId) => state.getChildren(String(parentId)).map((childId) => String(childId)),
4250
- getActivity: (id) => state.getActivity(id),
4251
- setActivityField: (id, field, value) => setActivityFieldDynamic(state, id, field, value),
4252
- getBaseCalendar: (engineId) => state.calendarReader.getCalendar(engineId)
4253
- };
4254
- const rootIds = [];
4255
- state.forEachActivity((activity, id) => {
4256
- if (activity.parentId === null) rootIds.push(id);
4257
- });
4258
- return computeExpectedProgress(rootIds, now, adapter, defaultBaseCalendarId);
4259
- }
4260
-
4261
- // src/critical-path/legacy/shims/moment.js
4262
- function moment(input) {
4263
- const d = input instanceof Date ? new Date(input.getTime()) : input && typeof input === "object" && typeof input.valueOf === "function" ? new Date(input.valueOf()) : new Date(input);
4264
- const accessor = (getUTC, setUTC) => (value) => {
4265
- if (value === void 0) return d[getUTC]();
4266
- d[setUTC](value);
4267
- return api;
4268
- };
4269
- const api = {
4270
- __isMomentShim: true,
4271
- clone: () => moment(d),
4272
- hours: accessor("getUTCHours", "setUTCHours"),
4273
- minutes: accessor("getUTCMinutes", "setUTCMinutes"),
4274
- seconds: accessor("getUTCSeconds", "setUTCSeconds"),
4275
- milliseconds: accessor("getUTCMilliseconds", "setUTCMilliseconds"),
4276
- valueOf: () => d.getTime(),
4277
- toDate: () => new Date(d.getTime()),
4278
- getTime: () => d.getTime()
4279
- };
4280
- return api;
4281
- }
4282
- moment.isMoment = (x) => Boolean(x) && typeof x === "object" && x.__isMomentShim === true;
4283
- var moment_default = moment;
4284
-
4285
- // src/critical-path/legacy/helpers/isValidDate.js
4286
- function isDate(obj) {
4287
- if (obj && obj.constructor === Date) {
4288
- return Boolean(obj.getFullYear && obj.getMonth && obj.getDate);
4237
+ } catch (e) {
4238
+ console.log("Critical Path", e);
4239
+ throw e;
4289
4240
  }
4290
- return false;
4291
- }
4292
- function isValidDate2(obj) {
4293
- return isDate(obj) && !isNaN(obj.getTime());
4294
4241
  }
4295
-
4296
- // src/critical-path/legacy/helpers/addDurationToDate.js
4297
- function addDurationToDate(activityCalendar, date2, duration, activity) {
4298
- if (!isValidDate2(new Date(date2))) {
4299
- throw new Error(
4300
- `Invalid date argument for calculateEndDate method. Date: ${date2}, Activity: ${JSON.stringify(activity)}`
4301
- );
4302
- }
4303
- const dateToCalculate = date2;
4304
- return activityCalendar.calculateEndDate({
4305
- start_date: new Date(dateToCalculate),
4306
- duration,
4307
- unit: "hour",
4308
- task: activity
4309
- });
4242
+ function getChainStartActivities(gantt, linkProperty) {
4243
+ return gantt.getTaskByTime().filter(filters_default.avoidSubproject).filter(filters_default.byTaskTypeAndMilestone).filter((activity) => !activity[linkProperty].length);
4310
4244
  }
4311
-
4312
- // src/critical-path/legacy/helpers/getNextWorkingHour.js
4313
- function getNextWorkingHour(activityParams) {
4314
- if (!isValidDate2(new Date(activityParams.dateBaseToCalculate))) {
4315
- throw new Error("Invalid date argument for getClosestWorkTime method");
4316
- }
4317
- const { dateBaseToCalculate, activityCalendar, direction, activity } = activityParams;
4318
- const newDate = activityCalendar.$gantt.getClosestWorkTime({
4319
- date: new Date(dateBaseToCalculate),
4320
- dir: direction,
4321
- unit: "hour",
4322
- task: activity
4245
+ function processBackwardActivities(chainStartActivities, gantt, linkProperty) {
4246
+ const withoutLinkedParents = [];
4247
+ const activitiesStartChainButChildrenOfLinkedParents = /* @__PURE__ */ new Set();
4248
+ chainStartActivities.forEach((activity) => {
4249
+ const parentId = Number(activity.parent);
4250
+ const parentActivity = gantt.getTask(parentId);
4251
+ if (!parentActivity) return;
4252
+ if (parentActivity[linkProperty].length > 0) {
4253
+ activitiesStartChainButChildrenOfLinkedParents.add(activity.id);
4254
+ } else if (parentActivity.constraint_type !== "fnlt") {
4255
+ withoutLinkedParents.push(activity);
4256
+ }
4323
4257
  });
4324
- return newDate;
4325
- }
4326
-
4327
- // src/critical-path/legacy/helpers/filters.js
4328
- var byFirstLevel = (activity) => {
4329
- if (!activity) return false;
4330
- return activity["$level"] === 1;
4331
- };
4332
- var filterByParentType = (activity) => activity.type === "project";
4333
- var avoidSubproject = (activity) => activity["$level"] !== 0;
4334
- var byTaskType = (activity) => activity.type === "task";
4335
- var byTaskTypeAndMilestone = (activity) => activity.type === "task" || activity.type === "milestone";
4336
- var filters_default = {
4337
- byFirstLevel,
4338
- filterByParentType,
4339
- avoidSubproject,
4340
- byTaskType,
4341
- byTaskTypeAndMilestone
4342
- };
4343
-
4344
- // src/critical-path/legacy/constants/constraints.js
4345
- var CONSTRAINT_TYPES = Object.freeze({
4346
- MFO: "mfo",
4347
- MSO: "mso",
4348
- FNLT: "fnlt",
4349
- FNET: "fnet",
4350
- SNET: "snet",
4351
- SNLT: "snlt",
4352
- ALAP: "alap",
4353
- ASAP: "asap"
4354
- });
4355
-
4356
- // src/critical-path/legacy/constants/linkTypes.js
4357
- var LINK_TYPES = Object.freeze({
4358
- FS: "fs",
4359
- SS: "ss",
4360
- FF: "ff",
4361
- SF: "sf"
4362
- });
4363
-
4364
- // src/critical-path/legacy/constants/linkByCode.js
4365
- function getLinkNameByCode(code) {
4366
- const linkMap = {
4367
- 0: "fs",
4368
- 1: "ss",
4369
- 2: "ff",
4370
- 3: "sf"
4258
+ return {
4259
+ chainStartActivities: withoutLinkedParents,
4260
+ activitiesStartChainButChildrenOfLinkedParents
4371
4261
  };
4372
- return linkMap[Number(code)] || "sf";
4373
4262
  }
4374
4263
 
4375
- // src/critical-path/legacy/base/identifiers/secondLevelActivities.js
4376
- function identifySecondLevelActivities(params) {
4377
- const { gantt, pendingParentsWithNoLinks, calculations, linkProperty } = params;
4264
+ // src/critical-path/legacy/base/identifiers/identifyParentsWithAndWithoutLinks.js
4265
+ function identifyParentsWithAndWithoutLinks({
4266
+ gantt,
4267
+ direction,
4268
+ linkProperty
4269
+ }) {
4378
4270
  try {
4379
- const subprojectsWithNoLinks = getSubprojectsWithNoLinks({
4380
- gantt,
4381
- pendingParentsWithNoLinks
4382
- });
4383
- if (subprojectsWithNoLinks.length === 0) {
4384
- return /* @__PURE__ */ new Set();
4271
+ const allParentActivities = getAllParentActivities(gantt);
4272
+ const parentsWithFullProgress = /* @__PURE__ */ new Set();
4273
+ const pendingParentsWithNoLinks = /* @__PURE__ */ new Set();
4274
+ const pendingParentsWithLinks = /* @__PURE__ */ new Set();
4275
+ if (direction === "backward") {
4276
+ const parentsWithFullProgressArray = getParentsWithFullProgress(allParentActivities);
4277
+ parentsWithFullProgressArray.forEach((activity) => {
4278
+ parentsWithFullProgress.add(activity.id);
4279
+ });
4385
4280
  }
4386
- const allChildrenFromParents = getAllChildrenFromParents({
4387
- gantt,
4388
- subprojectsWithNoLinks
4281
+ const parentsWithNoLinks = getParentsWithNoLinks(
4282
+ allParentActivities,
4283
+ direction,
4284
+ linkProperty
4285
+ );
4286
+ const difference = getDifference(allParentActivities, parentsWithNoLinks);
4287
+ parentsWithNoLinks.forEach((activity) => {
4288
+ pendingParentsWithNoLinks.add(activity.id);
4389
4289
  });
4390
- const childrenThatCanBeCalculated = filterChildrenThatCanBeCalculated({
4391
- gantt,
4392
- childrenIds: allChildrenFromParents,
4393
- linkProperty,
4394
- getArrayOfLinkedActivities,
4395
- calculations
4290
+ difference.forEach((activity) => {
4291
+ pendingParentsWithLinks.add(activity.id);
4396
4292
  });
4397
- return new Set(childrenThatCanBeCalculated.map((activity) => activity.id));
4293
+ return {
4294
+ parentsWithFullProgress,
4295
+ pendingParentsWithNoLinks,
4296
+ pendingParentsWithLinks
4297
+ };
4398
4298
  } catch (e) {
4399
4299
  throw e;
4400
4300
  }
4401
4301
  }
4402
- function getSubprojectsWithNoLinks(params) {
4403
- const { gantt, pendingParentsWithNoLinks } = params;
4404
- return gantt.getTaskByTime().filter(filters_default.filterByParentType).filter((activity) => pendingParentsWithNoLinks.has(activity.id)).map((activity) => activity.id);
4405
- }
4406
- function getAllChildrenFromParents(params) {
4407
- const { gantt, subprojectsWithNoLinks } = params;
4408
- const allChildrenSet = /* @__PURE__ */ new Set();
4409
- for (const subprojectId of subprojectsWithNoLinks) {
4410
- const childrenIds = gantt.getChildren(subprojectId);
4411
- const validChildren = childrenIds.filter((childId) => {
4412
- const childActivity = gantt.getTask(childId);
4413
- const isTaskOrMilestone = filters_default.byTaskTypeAndMilestone(childActivity);
4414
- const hasProgressLessThan100 = Number(childActivity.progress) < 100;
4415
- return isTaskOrMilestone && hasProgressLessThan100;
4416
- });
4417
- validChildren.forEach((childId) => allChildrenSet.add(childId));
4418
- }
4419
- return [...allChildrenSet];
4420
- }
4421
- function filterChildrenThatCanBeCalculated(params) {
4422
- const {
4423
- gantt,
4424
- childrenIds,
4425
- linkProperty,
4426
- getArrayOfLinkedActivities: getArrayOfLinkedActivities3,
4427
- calculations
4428
- } = params;
4429
- const calculableChildren = [];
4430
- for (const childId of childrenIds) {
4431
- const activity = gantt.getTask(childId);
4432
- const links = activity[linkProperty] || [];
4433
- const linkedActivities = getArrayOfLinkedActivities3({
4434
- gantt,
4435
- links,
4436
- linkDirection: linkProperty === "$source" ? "target" : "source"
4437
- });
4438
- const isLinkedWithCalculatedOrSibling = linkedActivities.every(
4439
- (linkedActivityId) => calculations.has(linkedActivityId)
4440
- );
4441
- if (isLinkedWithCalculatedOrSibling) {
4442
- calculableChildren.push(activity);
4443
- }
4444
- }
4445
- return calculableChildren;
4446
- }
4447
- function getArrayOfLinkedActivities(params) {
4448
- const { gantt, links, linkDirection } = params;
4449
- const linkedActivityIds = [];
4450
- for (const linkId of links) {
4451
- const link = gantt.getLink(linkId);
4452
- if (!link) {
4453
- continue;
4454
- }
4455
- const linkedActivityId = Number(link[linkDirection]);
4456
- linkedActivityIds.push(linkedActivityId);
4457
- }
4458
- return linkedActivityIds;
4459
- }
4460
-
4461
- // src/critical-path/legacy/base/identifiers/initialActivities.js
4462
- function identifyInitialActivities({ gantt, linkProperty, direction }) {
4463
- try {
4464
- const chainStartActivities = getChainStartActivities(gantt, linkProperty);
4465
- if (direction === "forward") {
4466
- return {
4467
- chainStartActivities,
4468
- activitiesStartChainButChildrenOfLinkedParents: null
4469
- };
4470
- }
4471
- if (direction === "backward") {
4472
- return processBackwardActivities(
4473
- chainStartActivities,
4474
- gantt,
4475
- linkProperty
4476
- );
4477
- }
4478
- } catch (e) {
4479
- console.log("Critical Path", e);
4480
- throw e;
4481
- }
4482
- }
4483
- function getChainStartActivities(gantt, linkProperty) {
4484
- return gantt.getTaskByTime().filter(filters_default.avoidSubproject).filter(filters_default.byTaskTypeAndMilestone).filter((activity) => !activity[linkProperty].length);
4485
- }
4486
- function processBackwardActivities(chainStartActivities, gantt, linkProperty) {
4487
- const withoutLinkedParents = [];
4488
- const activitiesStartChainButChildrenOfLinkedParents = /* @__PURE__ */ new Set();
4489
- chainStartActivities.forEach((activity) => {
4490
- const parentId = Number(activity.parent);
4491
- const parentActivity = gantt.getTask(parentId);
4492
- if (!parentActivity) return;
4493
- if (parentActivity[linkProperty].length > 0) {
4494
- activitiesStartChainButChildrenOfLinkedParents.add(activity.id);
4495
- } else if (parentActivity.constraint_type !== "fnlt") {
4496
- withoutLinkedParents.push(activity);
4497
- }
4498
- });
4499
- return {
4500
- chainStartActivities: withoutLinkedParents,
4501
- activitiesStartChainButChildrenOfLinkedParents
4502
- };
4503
- }
4504
-
4505
- // src/critical-path/legacy/base/identifiers/identifyParentsWithAndWithoutLinks.js
4506
- function identifyParentsWithAndWithoutLinks({
4507
- gantt,
4508
- direction,
4509
- linkProperty
4510
- }) {
4511
- try {
4512
- const allParentActivities = getAllParentActivities(gantt);
4513
- const parentsWithFullProgress = /* @__PURE__ */ new Set();
4514
- const pendingParentsWithNoLinks = /* @__PURE__ */ new Set();
4515
- const pendingParentsWithLinks = /* @__PURE__ */ new Set();
4516
- if (direction === "backward") {
4517
- const parentsWithFullProgressArray = getParentsWithFullProgress(allParentActivities);
4518
- parentsWithFullProgressArray.forEach((activity) => {
4519
- parentsWithFullProgress.add(activity.id);
4520
- });
4521
- }
4522
- const parentsWithNoLinks = getParentsWithNoLinks(
4523
- allParentActivities,
4524
- direction,
4525
- linkProperty
4526
- );
4527
- const difference = getDifference(allParentActivities, parentsWithNoLinks);
4528
- parentsWithNoLinks.forEach((activity) => {
4529
- pendingParentsWithNoLinks.add(activity.id);
4530
- });
4531
- difference.forEach((activity) => {
4532
- pendingParentsWithLinks.add(activity.id);
4533
- });
4534
- return {
4535
- parentsWithFullProgress,
4536
- pendingParentsWithNoLinks,
4537
- pendingParentsWithLinks
4538
- };
4539
- } catch (e) {
4540
- throw e;
4541
- }
4542
- }
4543
- function getAllParentActivities(gantt) {
4544
- return gantt.getTaskByTime().filter(filters_default.filterByParentType).filter(filters_default.avoidSubproject);
4302
+ function getAllParentActivities(gantt) {
4303
+ return gantt.getTaskByTime().filter(filters_default.filterByParentType).filter(filters_default.avoidSubproject);
4545
4304
  }
4546
4305
  function getParentsWithNoLinks(activities, direction, linkProperty) {
4547
4306
  return activities.filter((activity) => {
@@ -9491,13 +9250,18 @@ var NON_SCHEDULING_INLINE_COLUMNS = /* @__PURE__ */ new Set([
9491
9250
  COLUMN.SUBCONTRACT_ID,
9492
9251
  COLUMN.TAGS
9493
9252
  ]);
9494
- var PROP_ONLY_KINDS = /* @__PURE__ */ new Set([
9253
+ var NON_SCHEDULING_KINDS = /* @__PURE__ */ new Set([
9495
9254
  "selection-toggle",
9496
9255
  "selection-replace",
9497
9256
  "visibility-set",
9498
9257
  "sir-sync",
9499
- "activity-lookahead-sync"
9258
+ "activity-lookahead-sync",
9259
+ "persistence-acknowledge",
9260
+ "baseline-apply",
9261
+ "ponderator-criterion-set",
9262
+ "status-criteria-set"
9500
9263
  ]);
9264
+ var NO_STRUCTURAL_EXEMPTIONS = /* @__PURE__ */ new Set();
9501
9265
  function editsOnlyNonSchedulingColumns(action) {
9502
9266
  if (action.kind === "inline-edit") {
9503
9267
  return NON_SCHEDULING_INLINE_COLUMNS.has(action.column);
@@ -9510,11 +9274,14 @@ function editsOnlyNonSchedulingColumns(action) {
9510
9274
  return false;
9511
9275
  }
9512
9276
  function isSchedulingDispatch(action, structuralStaleKinds) {
9513
- if (PROP_ONLY_KINDS.has(action.kind)) return false;
9277
+ if (NON_SCHEDULING_KINDS.has(action.kind)) return false;
9514
9278
  if (structuralStaleKinds.has(action.kind)) return false;
9515
9279
  if (editsOnlyNonSchedulingColumns(action)) return false;
9516
9280
  return true;
9517
9281
  }
9282
+ function dispatchChangesSchedulingState(action) {
9283
+ return isSchedulingDispatch(action, NO_STRUCTURAL_EXEMPTIONS);
9284
+ }
9518
9285
  function resolveRunCriticalPath(action) {
9519
9286
  return isSchedulingDispatch(
9520
9287
  action,
@@ -10233,26 +10000,21 @@ async function dispatchLinkBatch(operations, source, options, deps, deterministi
10233
10000
  };
10234
10001
  }
10235
10002
  function pickAutoscheduleTrigger(applied, beforeLinks, adapter) {
10236
- for (const a of applied) {
10237
- if (a.rejected !== null) continue;
10238
- if (a.op.kind === "create") {
10239
- return adapter.hasChildren(a.op.target) ? "roots" : String(a.op.target);
10240
- }
10241
- if (a.op.kind === "delete") {
10242
- const snap = beforeLinks.get(a.op.linkId);
10243
- if (snap) {
10244
- return adapter.hasChildren(snap.target) ? "roots" : String(snap.target);
10245
- }
10246
- }
10247
- if (a.op.kind === "update") {
10248
- const link = adapter.getLink(a.op.linkId);
10249
- if (link) {
10250
- return adapter.hasChildren(link.target) ? "roots" : String(link.target);
10251
- }
10252
- }
10003
+ for (const operation of applied) {
10004
+ if (operation.rejected !== null) continue;
10005
+ const target = autoscheduleTarget(operation.op, beforeLinks, adapter);
10006
+ if (target !== null)
10007
+ return adapter.hasChildren(target) ? "roots" : String(target);
10253
10008
  }
10254
10009
  return null;
10255
10010
  }
10011
+ function autoscheduleTarget(operation, beforeLinks, adapter) {
10012
+ if (operation.kind === "create") return operation.target;
10013
+ if (operation.kind === "delete") {
10014
+ return beforeLinks.get(operation.linkId)?.target ?? null;
10015
+ }
10016
+ return adapter.getLink(operation.linkId)?.target ?? null;
10017
+ }
10256
10018
  function toLinkOperation(action, hoursPerDay) {
10257
10019
  if (action.kind === "link-create") {
10258
10020
  return {
@@ -10323,79 +10085,201 @@ var DISPATCH_TRACK_EVENT = {
10323
10085
  ACTIVITY_OUTDENT: "schedule_activity_outdent"
10324
10086
  };
10325
10087
 
10326
- // src/propagations/upward/recompute-hh-cascade.ts
10327
- function recomputeHhCascadeForParent(startParentId, adapter) {
10328
- if (!startParentId || startParentId === "0") return;
10329
- let currentParentId = String(startParentId);
10330
- while (currentParentId !== null) {
10331
- const childIds = adapter.getChildren(currentParentId);
10332
- let hhSum = 0;
10333
- let rwSum = 0;
10088
+ // src/internal/hierarchy/visual-order.ts
10089
+ var NOT_SET_SORT_VALUE = Number.MAX_SAFE_INTEGER;
10090
+ function getChildrenInVisualOrder(parentId, adapter) {
10091
+ const children = collectChildrenSnapshots(parentId, adapter);
10092
+ children.sort(compareActivitiesInVisualOrder);
10093
+ return children.map((a) => String(a.id));
10094
+ }
10095
+ function iterateInVisualOrder(adapter) {
10096
+ const result = [];
10097
+ const visit = (activity) => {
10098
+ result.push(activity);
10099
+ const childIds = getChildrenInVisualOrder(String(activity.id), adapter);
10334
10100
  for (const childId of childIds) {
10335
10101
  const child = adapter.getActivity(childId);
10336
- if (!child) continue;
10337
- const hh = readNumber(child, ACTIVITY_PROPERTY.WORK_HOURS);
10338
- const rw = readChildRealWork(childId, child, hh, adapter);
10339
- hhSum += hh;
10340
- rwSum += rw;
10102
+ if (child) visit(child);
10341
10103
  }
10342
- adapter.setActivityField(
10343
- currentParentId,
10344
- ACTIVITY_PROPERTY.WORK_HOURS,
10345
- hhSum
10346
- );
10347
- adapter.setActivityField(
10348
- currentParentId,
10349
- ACTIVITY_PROPERTY.REAL_WORK_HOURS,
10350
- rwSum
10351
- );
10352
- const parentSnap = adapter.getActivity(currentParentId);
10353
- if (!parentSnap) break;
10354
- const grandParent = parentSnap.parentId;
10355
- if (!grandParent || isRootParent(grandParent)) break;
10356
- currentParentId = grandParent;
10104
+ };
10105
+ const rootIds = getChildrenInVisualOrder(ROOT_PARENT_ID, adapter);
10106
+ for (const rootId of rootIds) {
10107
+ const root = adapter.getActivity(rootId);
10108
+ if (root) visit(root);
10357
10109
  }
10110
+ return result;
10358
10111
  }
10359
- function recomputeHhCascadeForActivity(activityId, adapter) {
10360
- const activity = adapter.getActivity(activityId);
10361
- if (!activity) return;
10362
- const parentId = activity.parentId;
10363
- if (!parentId || isRootParent(parentId)) return;
10364
- recomputeHhCascadeForParent(parentId, adapter);
10365
- }
10366
- function readNumber(activity, key) {
10367
- const v = activity[key];
10368
- if (typeof v === "number" && Number.isFinite(v)) return v;
10369
- return 0;
10112
+ function findPreviousNonSelectedSibling(taskId, selected, adapter) {
10113
+ const activity = adapter.getActivity(taskId);
10114
+ if (!activity) return null;
10115
+ const parentKey = parentKeyOf(activity);
10116
+ const siblings = getChildrenInVisualOrder(parentKey, adapter);
10117
+ const idx = siblings.findIndex((id) => String(id) === String(taskId));
10118
+ if (idx <= 0) return null;
10119
+ for (let i = idx - 1; i >= 0; i--) {
10120
+ const candidateId = siblings[i];
10121
+ if (candidateId === void 0) continue;
10122
+ if (!setHas(selected, candidateId)) return candidateId;
10123
+ }
10124
+ return null;
10370
10125
  }
10371
- function readChildRealWork(childId, child, workHours, adapter) {
10372
- return adapter.hasChildren(childId) ? readNumber(child, ACTIVITY_PROPERTY.REAL_WORK_HOURS) : computeLeafRealWork(
10373
- workHours,
10374
- readNumber(child, ACTIVITY_PROPERTY.PROGRESS)
10375
- );
10126
+ function visualIndexInParent(taskId, adapter) {
10127
+ const activity = adapter.getActivity(taskId);
10128
+ if (!activity) return -1;
10129
+ const parentKey = parentKeyOf(activity);
10130
+ const siblings = getChildrenInVisualOrder(parentKey, adapter);
10131
+ return siblings.findIndex((id) => String(id) === String(taskId));
10376
10132
  }
10377
-
10378
- // src/propagations/upward/recompute-used-cost-cascade.ts
10379
- function recomputeUsedCostCascadeForParent(startParentId, adapter) {
10380
- if (!startParentId || startParentId === "0") return;
10381
- let currentParentId = String(startParentId);
10382
- while (currentParentId !== null) {
10383
- const childIds = adapter.getChildren(currentParentId);
10384
- let sum = 0;
10385
- for (const childId of childIds) {
10386
- const child = adapter.getActivity(childId);
10387
- if (!child) continue;
10388
- sum += readNumber2(child, ACTIVITY_PROPERTY.USED_COST);
10389
- }
10390
- adapter.setActivityField(currentParentId, ACTIVITY_PROPERTY.USED_COST, sum);
10391
- const parentSnap = adapter.getActivity(currentParentId);
10392
- if (!parentSnap) break;
10393
- const grandParent = parentSnap.parentId;
10394
- if (!grandParent || isRootParent(grandParent)) break;
10395
- currentParentId = grandParent;
10133
+ function collectChildrenSnapshots(parentId, adapter) {
10134
+ const key = String(parentId);
10135
+ if (key === "0") {
10136
+ return adapter.getAllActivities().filter((a) => a.parentId === null);
10396
10137
  }
10138
+ const childIds = adapter.getChildren(parentId);
10139
+ const out = [];
10140
+ for (const id of childIds) {
10141
+ const snap = adapter.getActivity(id);
10142
+ if (snap) out.push(snap);
10143
+ }
10144
+ return out;
10397
10145
  }
10398
- function recomputeUsedCostCascadeForActivity(activityId, adapter) {
10146
+ function compareActivitiesInVisualOrder(a, b) {
10147
+ const ac = correlativeIdOf(a);
10148
+ const bc = correlativeIdOf(b);
10149
+ if (ac !== bc) return ac - bc;
10150
+ return String(a.id).localeCompare(String(b.id));
10151
+ }
10152
+ function correlativeIdOf(activity) {
10153
+ const raw = activity.correlativeId;
10154
+ const n = typeof raw === "number" ? raw : Number(raw);
10155
+ return Number.isFinite(n) ? n : NOT_SET_SORT_VALUE;
10156
+ }
10157
+ function parentKeyOf(activity) {
10158
+ const parentId = activity.parentId;
10159
+ if (parentId === null) return "0";
10160
+ return parentId;
10161
+ }
10162
+ function setHas(set, id) {
10163
+ if (set.has(id)) return true;
10164
+ return set.has(String(id));
10165
+ }
10166
+
10167
+ // src/internal/hierarchy/recompute-correlative-ids.ts
10168
+ function recomputeCorrelativeIds(adapter) {
10169
+ adapter.invalidateVisualOrderIds?.();
10170
+ const roots = [];
10171
+ adapter.forEachActivity((activity) => {
10172
+ if (isRootParent(activity.parentId)) roots.push(activity);
10173
+ });
10174
+ roots.sort(byCorrelativeId);
10175
+ const stack = [];
10176
+ pushInReverseOrder(stack, roots);
10177
+ const shifts = [];
10178
+ let counter = 0;
10179
+ while (stack.length > 0) {
10180
+ const activity = stack.pop();
10181
+ if (activity.correlativeId !== counter) {
10182
+ const before = typeof activity.correlativeId === "number" ? activity.correlativeId : void 0;
10183
+ shifts.push({ activityId: String(activity.id), before });
10184
+ if (before !== void 0)
10185
+ adapter.noteCorrelativeBefore?.(String(activity.id), before);
10186
+ setCorrelativeId(activity, counter);
10187
+ }
10188
+ counter += 1;
10189
+ const children = collectChildrenSnapshots(String(activity.id), adapter);
10190
+ children.sort(byCorrelativeId);
10191
+ pushInReverseOrder(stack, children);
10192
+ }
10193
+ return shifts;
10194
+ }
10195
+ function pushInReverseOrder(stack, activities) {
10196
+ for (let i = activities.length - 1; i >= 0; i -= 1) {
10197
+ stack.push(activities[i]);
10198
+ }
10199
+ }
10200
+ function getCorrelativeId(activity) {
10201
+ const raw = activity.correlativeId;
10202
+ const n = typeof raw === "number" ? raw : Number(raw);
10203
+ return Number.isFinite(n) ? n : NOT_SET_SORT_VALUE;
10204
+ }
10205
+ var byCorrelativeId = (a, b) => getCorrelativeId(a) - getCorrelativeId(b);
10206
+ function setCorrelativeId(activity, value) {
10207
+ activity.correlativeId = value;
10208
+ }
10209
+
10210
+ // src/propagations/upward/recompute-hh-cascade.ts
10211
+ function recomputeHhCascadeForParent(startParentId, adapter) {
10212
+ if (!startParentId || startParentId === "0") return;
10213
+ let currentParentId = String(startParentId);
10214
+ while (currentParentId !== null) {
10215
+ const childIds = adapter.getChildren(currentParentId);
10216
+ let hhSum = 0;
10217
+ let rwSum = 0;
10218
+ for (const childId of childIds) {
10219
+ const child = adapter.getActivity(childId);
10220
+ if (!child) continue;
10221
+ const hh = readNumber(child, ACTIVITY_PROPERTY.WORK_HOURS);
10222
+ const rw = readChildRealWork(childId, child, hh, adapter);
10223
+ hhSum += hh;
10224
+ rwSum += rw;
10225
+ }
10226
+ adapter.setActivityField(
10227
+ currentParentId,
10228
+ ACTIVITY_PROPERTY.WORK_HOURS,
10229
+ hhSum
10230
+ );
10231
+ adapter.setActivityField(
10232
+ currentParentId,
10233
+ ACTIVITY_PROPERTY.REAL_WORK_HOURS,
10234
+ rwSum
10235
+ );
10236
+ const parentSnap = adapter.getActivity(currentParentId);
10237
+ if (!parentSnap) break;
10238
+ const grandParent = parentSnap.parentId;
10239
+ if (!grandParent || isRootParent(grandParent)) break;
10240
+ currentParentId = grandParent;
10241
+ }
10242
+ }
10243
+ function recomputeHhCascadeForActivity(activityId, adapter) {
10244
+ const activity = adapter.getActivity(activityId);
10245
+ if (!activity) return;
10246
+ const parentId = activity.parentId;
10247
+ if (!parentId || isRootParent(parentId)) return;
10248
+ recomputeHhCascadeForParent(parentId, adapter);
10249
+ }
10250
+ function readNumber(activity, key) {
10251
+ const v = activity[key];
10252
+ if (typeof v === "number" && Number.isFinite(v)) return v;
10253
+ return 0;
10254
+ }
10255
+ function readChildRealWork(childId, child, workHours, adapter) {
10256
+ return adapter.hasChildren(childId) ? readNumber(child, ACTIVITY_PROPERTY.REAL_WORK_HOURS) : computeLeafRealWork(
10257
+ workHours,
10258
+ readNumber(child, ACTIVITY_PROPERTY.PROGRESS)
10259
+ );
10260
+ }
10261
+
10262
+ // src/propagations/upward/recompute-used-cost-cascade.ts
10263
+ function recomputeUsedCostCascadeForParent(startParentId, adapter) {
10264
+ if (!startParentId || startParentId === "0") return;
10265
+ let currentParentId = String(startParentId);
10266
+ while (currentParentId !== null) {
10267
+ const childIds = adapter.getChildren(currentParentId);
10268
+ let sum = 0;
10269
+ for (const childId of childIds) {
10270
+ const child = adapter.getActivity(childId);
10271
+ if (!child) continue;
10272
+ sum += readNumber2(child, ACTIVITY_PROPERTY.USED_COST);
10273
+ }
10274
+ adapter.setActivityField(currentParentId, ACTIVITY_PROPERTY.USED_COST, sum);
10275
+ const parentSnap = adapter.getActivity(currentParentId);
10276
+ if (!parentSnap) break;
10277
+ const grandParent = parentSnap.parentId;
10278
+ if (!grandParent || isRootParent(grandParent)) break;
10279
+ currentParentId = grandParent;
10280
+ }
10281
+ }
10282
+ function recomputeUsedCostCascadeForActivity(activityId, adapter) {
10399
10283
  const activity = adapter.getActivity(activityId);
10400
10284
  if (!activity) return;
10401
10285
  const parentId = activity.parentId;
@@ -10873,27 +10757,6 @@ function buildCreateChangeSet(adapter, action, coreResult, scheduledIds, hoursPe
10873
10757
  scheduledBeforeImages: existingScheduledBeforeImages,
10874
10758
  trackingEvents: [trackingEvent]});
10875
10759
  }
10876
- function createActivitySync(action, deps) {
10877
- const result = createActivityCore(action, deps);
10878
- if (!result.ok) {
10879
- return { ok: false, reason: result.reason };
10880
- }
10881
- recomputeParentBoundsSync(deps.adapter, /* @__PURE__ */ new Set([result.newId]));
10882
- const snapshot = deps.adapter.getActivity(result.newId);
10883
- if (!snapshot) {
10884
- throw new Error(
10885
- `[ScheduleCore] createActivity: new activity ${result.newId} missing after commit`
10886
- );
10887
- }
10888
- const changes = buildCreateChangeSet(
10889
- deps.adapter,
10890
- action,
10891
- result,
10892
- [],
10893
- deps.sector.hoursPerDay
10894
- );
10895
- return { ok: true, activity: snapshot, changes };
10896
- }
10897
10760
  async function dispatchActivityCreate(action, options, deps) {
10898
10761
  const { adapter, scheduler, sector } = deps;
10899
10762
  const core = createActivityCore(action, deps);
@@ -12549,17 +12412,524 @@ function mergeBeforeSnapshots2(beforeSnap, ids, deps) {
12549
12412
  for (const candidateId of ids) {
12550
12413
  if (!beforeSnap.has(candidateId)) missing.add(candidateId);
12551
12414
  }
12552
- if (missing.size === 0) return;
12553
- for (const [snappedId, snapshot] of snapshotActivities(
12415
+ if (missing.size === 0) return;
12416
+ for (const [snappedId, snapshot] of snapshotActivities(
12417
+ deps.adapter,
12418
+ missing
12419
+ )) {
12420
+ beforeSnap.set(snappedId, snapshot);
12421
+ }
12422
+ }
12423
+
12424
+ // src/dispatch/handlers/persistence-acknowledge.ts
12425
+ function assertValidAssignment(assignment, entityName, readCurrent, ownerByBackendId, seenIds, seenBackendIds) {
12426
+ const id = String(assignment.id);
12427
+ if (id.length === 0 || seenIds.has(id)) {
12428
+ throw new Error(
12429
+ `persistence-acknowledge: duplicate or empty ${entityName} id "${id}"`
12430
+ );
12431
+ }
12432
+ if (!Number.isSafeInteger(assignment.proplannerId) || assignment.proplannerId <= 0) {
12433
+ throw new Error(
12434
+ `persistence-acknowledge: invalid ${entityName} proplannerId ${String(assignment.proplannerId)}`
12435
+ );
12436
+ }
12437
+ if (seenBackendIds.has(assignment.proplannerId)) {
12438
+ throw new Error(
12439
+ `persistence-acknowledge: duplicate ${entityName} proplannerId ${String(assignment.proplannerId)}`
12440
+ );
12441
+ }
12442
+ const state = readCurrent(id);
12443
+ if (!state.exists) {
12444
+ throw new Error(
12445
+ `persistence-acknowledge: unknown ${entityName} id "${id}"`
12446
+ );
12447
+ }
12448
+ if (state.current !== null && state.current !== assignment.proplannerId) {
12449
+ throw new Error(
12450
+ `persistence-acknowledge: ${entityName} "${id}" already has proplannerId ${String(state.current)}`
12451
+ );
12452
+ }
12453
+ const existingOwner = ownerByBackendId.get(assignment.proplannerId);
12454
+ if (existingOwner !== void 0 && existingOwner !== id) {
12455
+ throw new Error(
12456
+ `persistence-acknowledge: ${entityName} proplannerId ${String(assignment.proplannerId)} already belongs to "${existingOwner}"`
12457
+ );
12458
+ }
12459
+ seenIds.add(id);
12460
+ seenBackendIds.add(assignment.proplannerId);
12461
+ }
12462
+ function assertValidAssignments(assignments, entityName, readCurrent, ownerByBackendId) {
12463
+ const seenIds = /* @__PURE__ */ new Set();
12464
+ const seenBackendIds = /* @__PURE__ */ new Set();
12465
+ for (const assignment of assignments) {
12466
+ assertValidAssignment(
12467
+ assignment,
12468
+ entityName,
12469
+ readCurrent,
12470
+ ownerByBackendId,
12471
+ seenIds,
12472
+ seenBackendIds
12473
+ );
12474
+ }
12475
+ }
12476
+ function dispatchPersistenceAcknowledge(action, deps) {
12477
+ const activities = action.activities ?? [];
12478
+ const links = action.links ?? [];
12479
+ const activityOwnerByBackendId = /* @__PURE__ */ new Map();
12480
+ for (const activity of deps.adapter.getAllActivities()) {
12481
+ if (activity.proplannerId != null) {
12482
+ activityOwnerByBackendId.set(activity.proplannerId, String(activity.id));
12483
+ }
12484
+ }
12485
+ const linkOwnerByBackendId = /* @__PURE__ */ new Map();
12486
+ for (const link of deps.adapter.getAllLinks()) {
12487
+ if (link.proplannerId != null) {
12488
+ linkOwnerByBackendId.set(link.proplannerId, String(link.id));
12489
+ }
12490
+ }
12491
+ assertValidAssignments(
12492
+ activities,
12493
+ "activity",
12494
+ (id) => {
12495
+ const activity = deps.adapter.getActivity(id);
12496
+ return {
12497
+ exists: activity !== null,
12498
+ current: activity?.proplannerId ?? null
12499
+ };
12500
+ },
12501
+ activityOwnerByBackendId
12502
+ );
12503
+ assertValidAssignments(
12504
+ links,
12505
+ "link",
12506
+ (id) => {
12507
+ const link = deps.adapter.getLink(id);
12508
+ return { exists: link !== null, current: link?.proplannerId ?? null };
12509
+ },
12510
+ linkOwnerByBackendId
12511
+ );
12512
+ const activityChanges = [];
12513
+ const linkChanges = [];
12514
+ deps.adapter.batchUpdate(() => {
12515
+ for (const assignment of activities) {
12516
+ const before = deps.adapter.getActivity(assignment.id);
12517
+ if (!before || before.proplannerId === assignment.proplannerId) continue;
12518
+ deps.adapter.setActivityField(
12519
+ assignment.id,
12520
+ "proplannerId",
12521
+ assignment.proplannerId
12522
+ );
12523
+ activityChanges.push({
12524
+ id: assignment.id,
12525
+ kind: "updated",
12526
+ fields: {
12527
+ proplannerId: {
12528
+ before: before.proplannerId,
12529
+ after: assignment.proplannerId
12530
+ }
12531
+ },
12532
+ after: { ...before, proplannerId: assignment.proplannerId }
12533
+ });
12534
+ }
12535
+ for (const assignment of links) {
12536
+ const before = deps.adapter.getLink(assignment.id);
12537
+ if (!before || before.proplannerId === assignment.proplannerId) continue;
12538
+ deps.adapter.setLinkProplannerId(assignment.id, assignment.proplannerId);
12539
+ linkChanges.push({
12540
+ id: assignment.id,
12541
+ kind: "updated",
12542
+ fields: {
12543
+ proplannerId: {
12544
+ before: before.proplannerId,
12545
+ after: assignment.proplannerId
12546
+ }
12547
+ },
12548
+ after: { ...before, proplannerId: assignment.proplannerId }
12549
+ });
12550
+ }
12551
+ });
12552
+ return {
12553
+ ok: true,
12554
+ changes: {
12555
+ source: action,
12556
+ activities: activityChanges,
12557
+ links: linkChanges,
12558
+ calendars: [],
12559
+ trackingEvents: []
12560
+ }
12561
+ };
12562
+ }
12563
+
12564
+ // src/baselines/normalize-baseline-point.ts
12565
+ function parseBaselineDate(value) {
12566
+ return typeof value === "string" && value.length > 0 ? parseBackendDate(value) : value instanceof Date ? value : null;
12567
+ }
12568
+ function parseFiniteNonNegative(value) {
12569
+ const numeric = typeof value === "number" ? value : typeof value === "string" && value.trim() !== "" ? Number(value) : Number.NaN;
12570
+ if (!Number.isFinite(numeric) || numeric < 0) return null;
12571
+ return numeric;
12572
+ }
12573
+ function normalizeBaselinePoint(point, reporter) {
12574
+ const durationDays = parseFiniteNonNegative(point.duration);
12575
+ const cost = parseFiniteNonNegative(point.cost);
12576
+ const workHours = parseFiniteNonNegative(point.hh_work);
12577
+ if (durationDays === null || cost === null || workHours === null) {
12578
+ reporter.warn(
12579
+ `[baseline] dropping point with non-finite/negative magnitude \u2014 duration=${JSON.stringify(point.duration)}, cost=${JSON.stringify(point.cost)}, hh_work=${JSON.stringify(point.hh_work)}`
12580
+ );
12581
+ return null;
12582
+ }
12583
+ return {
12584
+ startDate: parseBaselineDate(point.start_date),
12585
+ endDate: parseBaselineDate(point.end_date),
12586
+ durationDays,
12587
+ cost,
12588
+ workHours,
12589
+ versionId: typeof point.sectorbaselineversionId === "number" ? point.sectorbaselineversionId : null,
12590
+ isActiveVersion: Boolean(point.sectorbaselineversion?.active),
12591
+ isVisibleVersion: Boolean(point.sectorbaselineversion?.visible),
12592
+ baseCalendarId: point.baseCalendarId != null ? String(point.baseCalendarId) : null,
12593
+ hoursPerDay: typeof point.hoursPerDay === "number" ? point.hoursPerDay : null,
12594
+ hoursPerWeek: typeof point.hoursPerWeek === "number" ? point.hoursPerWeek : null
12595
+ };
12596
+ }
12597
+
12598
+ // src/baselines/apply-baseline-points.ts
12599
+ function applyBaselinePoints(adapter, points, reporter) {
12600
+ const idByProplanner = /* @__PURE__ */ new Map();
12601
+ adapter.forEachActivity((a, id) => {
12602
+ const pid = a.proplannerId;
12603
+ if (typeof pid === "number") idByProplanner.set(pid, id);
12604
+ });
12605
+ const grouped = /* @__PURE__ */ new Map();
12606
+ for (const p of points) {
12607
+ const activityId = p.activityId;
12608
+ if (typeof activityId !== "number") continue;
12609
+ const coreId = idByProplanner.get(activityId);
12610
+ if (coreId === void 0) continue;
12611
+ const arr = grouped.get(coreId);
12612
+ if (arr) arr.push(p);
12613
+ else grouped.set(coreId, [p]);
12614
+ }
12615
+ const changed = [];
12616
+ for (const [coreId, group] of grouped) {
12617
+ adapter.setActivityField(
12618
+ coreId,
12619
+ "baselinePoints",
12620
+ group.map((point) => normalizeBaselinePoint(point, reporter)).filter((point) => point !== null)
12621
+ );
12622
+ changed.push(coreId);
12623
+ }
12624
+ adapter.forEachActivity((a, id) => {
12625
+ if (grouped.has(id)) return;
12626
+ const existing = a.baselinePoints;
12627
+ if (Array.isArray(existing) && existing.length > 0) {
12628
+ adapter.setActivityField(id, "baselinePoints", []);
12629
+ changed.push(id);
12630
+ }
12631
+ });
12632
+ return changed;
12633
+ }
12634
+
12635
+ // src/baselines/baseline-snapshot.ts
12636
+ function buildBaselineSnapshot(activity) {
12637
+ const active = getActiveBaseline(activity);
12638
+ if (!active) return null;
12639
+ return {
12640
+ startDate: active.startDate,
12641
+ endDate: active.endDate,
12642
+ durationDays: active.durationDays,
12643
+ cost: active.cost,
12644
+ workHours: active.workHours
12645
+ };
12646
+ }
12647
+ function emitBaselineSnapshotColumns(state) {
12648
+ const changed = [];
12649
+ state.forEachActivity((activity, id) => {
12650
+ const snapshot = buildBaselineSnapshot(activity);
12651
+ if (!snapshot) {
12652
+ if (activity.baselineSnapshot !== null) {
12653
+ state.setActivityField(id, "baselineSnapshot", null);
12654
+ changed.push(id);
12655
+ }
12656
+ return;
12657
+ }
12658
+ state.setActivityField(id, "baselineSnapshot", snapshot);
12659
+ changed.push(id);
12660
+ });
12661
+ return changed;
12662
+ }
12663
+
12664
+ // src/baselines/apply-baseline-overlay.ts
12665
+ function applyBaselineOverlay(state, points, now, reporter, defaultBaseCalendarId = null) {
12666
+ const changed = /* @__PURE__ */ new Set();
12667
+ for (const id of applyBaselinePoints(
12668
+ state,
12669
+ points,
12670
+ reporter
12671
+ )) {
12672
+ changed.add(id);
12673
+ }
12674
+ if (now) {
12675
+ for (const id of runExpectedProgressBase(state, now, defaultBaseCalendarId))
12676
+ changed.add(id);
12677
+ }
12678
+ for (const id of emitBaselineSnapshotColumns(state)) changed.add(id);
12679
+ for (const id of emitRealCost(state)) changed.add(id);
12680
+ return [...changed];
12681
+ }
12682
+
12683
+ // src/propagations/downward/recompute-ponderator-for-parent.ts
12684
+ function recomputePonderatorsForParent(parentId, criterion, adapter) {
12685
+ if (!parentId || parentId === "0") return;
12686
+ const childIds = adapter.getChildren(parentId);
12687
+ if (childIds.length === 0) return;
12688
+ const pesoById = /* @__PURE__ */ new Map();
12689
+ let denominator = 0;
12690
+ for (const childId of childIds) {
12691
+ const peso = criterionValue(String(childId), criterion, adapter);
12692
+ pesoById.set(String(childId), peso);
12693
+ denominator += peso;
12694
+ }
12695
+ const relationValue = denominator ? 100 / denominator : 0;
12696
+ for (const childId of childIds) {
12697
+ const child = adapter.getActivity(childId);
12698
+ if (!child) continue;
12699
+ const points = child.baselinePoints;
12700
+ const active = getActiveBaseline(child);
12701
+ if (active) {
12702
+ const peso = pesoById.get(String(childId)) ?? 0;
12703
+ adapter.setActivityField(
12704
+ childId,
12705
+ COLUMN.PONDERATOR,
12706
+ relationValue * peso
12707
+ );
12708
+ } else if (!Array.isArray(points) || points.length === 0) {
12709
+ adapter.setActivityField(childId, COLUMN.PONDERATOR, 0);
12710
+ }
12711
+ }
12712
+ }
12713
+ function criterionValue(childId, criterion, adapter) {
12714
+ const child = adapter.getActivity(childId);
12715
+ if (!child) return 0;
12716
+ const active = getActiveBaseline(child);
12717
+ if (!active) return 0;
12718
+ if (criterion === "COST") return toNumber(active.cost);
12719
+ if (criterion === "HH") return toNumber(active.workHours);
12720
+ const type = child.type;
12721
+ if (type === "project")
12722
+ return sumRecursiveBaselineDurationDays(childId, adapter);
12723
+ if (type === "milestone") return 0;
12724
+ return toNumber(active.durationDays);
12725
+ }
12726
+ function sumRecursiveBaselineDurationDays(activityId, adapter) {
12727
+ let sum = 0;
12728
+ for (const childId of adapter.getChildren(activityId)) {
12729
+ const child = adapter.getActivity(childId);
12730
+ if (!child) continue;
12731
+ const active = getActiveBaseline(child);
12732
+ if (!active) continue;
12733
+ const type = child.type;
12734
+ if (type === "project") {
12735
+ sum += sumRecursiveBaselineDurationDays(String(childId), adapter);
12736
+ } else if (type !== "milestone") {
12737
+ sum += toNumber(active.durationDays);
12738
+ }
12739
+ }
12740
+ return sum;
12741
+ }
12742
+ function toNumber(v) {
12743
+ const n = typeof v === "string" ? parseFloat(v) : v;
12744
+ return typeof n === "number" && Number.isFinite(n) ? n : 0;
12745
+ }
12746
+
12747
+ // src/propagations/downward/recompute-all-ponderators.ts
12748
+ function recomputeAllPonderators(criterion, adapter) {
12749
+ adapter.forEachActivity((_a, id) => {
12750
+ adapter.setActivityField(id, COLUMN.PONDERATOR, 0);
12751
+ });
12752
+ adapter.forEachActivity((_a, id) => {
12753
+ if (adapter.getChildren(id).length > 0) {
12754
+ recomputePonderatorsForParent(String(id), criterion, adapter);
12755
+ }
12756
+ });
12757
+ }
12758
+
12759
+ // src/propagations/upward/progress-rollup.ts
12760
+ function recomputeAllProgressRollup(adapter) {
12761
+ for (const parentId of parentsDeepestFirst(adapter)) {
12762
+ rollupParent(parentId, adapter);
12763
+ }
12764
+ }
12765
+ function parentsDeepestFirst(adapter) {
12766
+ const parents = /* @__PURE__ */ new Set();
12767
+ adapter.forEachActivity((_activity, id) => {
12768
+ if (adapter.getChildren(id).length > 0) parents.add(id);
12769
+ });
12770
+ const depthById = /* @__PURE__ */ new Map();
12771
+ for (const id of parents) depthById.set(id, depthOf2(id, adapter));
12772
+ return [...parents].sort(
12773
+ (a, b) => (depthById.get(b) ?? 0) - (depthById.get(a) ?? 0)
12774
+ );
12775
+ }
12776
+ function depthOf2(id, adapter) {
12777
+ let depth = 0;
12778
+ let current = id;
12779
+ while (current && current !== "0") {
12780
+ if (!adapter.getActivity(current)) break;
12781
+ depth += 1;
12782
+ current = adapter.getParentId(current);
12783
+ }
12784
+ return depth;
12785
+ }
12786
+ function rollupParent(parentId, adapter) {
12787
+ const childIds = adapter.getChildren(parentId);
12788
+ if (childIds.length === 0) return;
12789
+ const children = [];
12790
+ for (const childId of childIds) {
12791
+ const child = adapter.getActivity(childId);
12792
+ if (!child) continue;
12793
+ children.push({
12794
+ progress: Number(child.progress ?? 0),
12795
+ ponderator: Number(child.ponderator ?? 0)
12796
+ });
12797
+ }
12798
+ const rollup = computeWeightedProgressRollup(children);
12799
+ if (rollup !== null) {
12800
+ adapter.setActivityField(
12801
+ parentId,
12802
+ COLUMN.PROGRESS,
12803
+ roundProgressPerLevel(rollup)
12804
+ );
12805
+ }
12806
+ }
12807
+
12808
+ // src/dispatch/shared/baseline-weighted-fields.ts
12809
+ function recomputeBaselineWeightedFields(deps, criterion) {
12810
+ recomputeAllPonderators(criterion, deps.adapter);
12811
+ recomputeAllProgressRollup(deps.adapter);
12812
+ if (deps.now) {
12813
+ runExpectedProgressBase(
12814
+ deps.adapter,
12815
+ deps.now,
12816
+ deps.defaultBaseCalendarId ?? null
12817
+ );
12818
+ applyExpectedProgressLive(deps.adapter, deps.now);
12819
+ }
12820
+ emitRealCost(deps.adapter);
12821
+ if (deps.now) applyStatusPass(deps.adapter, deps.sector.statusCriteria);
12822
+ }
12823
+
12824
+ // src/dispatch/handlers/baseline-apply.ts
12825
+ function activeBaselineSignature(deps) {
12826
+ return JSON.stringify(
12827
+ deps.adapter.getAllIds().map((id) => {
12828
+ const point = deps.adapter.getActivity(id)?.baselinePoints.find((candidate) => candidate.isActiveVersion);
12829
+ return point ? [
12830
+ String(id),
12831
+ point.versionId,
12832
+ point.startDate?.getTime() ?? null,
12833
+ point.endDate?.getTime() ?? null,
12834
+ point.durationDays,
12835
+ point.cost,
12836
+ point.workHours,
12837
+ point.baseCalendarId,
12838
+ point.hoursPerDay,
12839
+ point.hoursPerWeek
12840
+ ] : [String(id), null];
12841
+ })
12842
+ );
12843
+ }
12844
+ function dispatchBaselineApply(action, deps) {
12845
+ const beforeSnap = snapshotActivities(
12554
12846
  deps.adapter,
12555
- missing
12556
- )) {
12557
- beforeSnap.set(snappedId, snapshot);
12847
+ new Set(deps.adapter.getAllIds())
12848
+ );
12849
+ const activeBaselineBefore = activeBaselineSignature(deps);
12850
+ const changedIds = applyBaselineOverlay(
12851
+ deps.adapter,
12852
+ action.points,
12853
+ deps.now,
12854
+ deps.reporter,
12855
+ deps.defaultBaseCalendarId ?? null
12856
+ );
12857
+ if (activeBaselineSignature(deps) !== activeBaselineBefore) {
12858
+ recomputeBaselineWeightedFields(
12859
+ deps,
12860
+ deps.sector.activityCreter ?? "DURATION"
12861
+ );
12558
12862
  }
12863
+ return {
12864
+ ok: true,
12865
+ changes: assembleChangeSet(deps.adapter, {
12866
+ source: action,
12867
+ beforeSnap,
12868
+ touchedIds: changedIds,
12869
+ scheduledIds: [],
12870
+ hoursPerDay: deps.sector.hoursPerDay
12871
+ })
12872
+ };
12873
+ }
12874
+
12875
+ // src/dispatch/handlers/ponderator-criterion-set.ts
12876
+ function assertValidCriterion(value) {
12877
+ if (value === "DURATION" || value === "COST" || value === "HH") return;
12878
+ throw new Error(
12879
+ `ponderator-criterion-set: invalid criterion "${String(value)}"`
12880
+ );
12881
+ }
12882
+ function dispatchPonderatorCriterionSet(action, deps) {
12883
+ assertValidCriterion(action.criterion);
12884
+ const allIds = new Set(deps.adapter.getAllIds());
12885
+ const beforeSnap = snapshotActivities(deps.adapter, allIds);
12886
+ recomputeBaselineWeightedFields(deps, action.criterion);
12887
+ deps.sector.activityCreter = action.criterion;
12888
+ return {
12889
+ ok: true,
12890
+ changes: assembleChangeSet(deps.adapter, {
12891
+ source: action,
12892
+ beforeSnap,
12893
+ touchedIds: allIds,
12894
+ scheduledIds: [],
12895
+ hoursPerDay: deps.sector.hoursPerDay
12896
+ })
12897
+ };
12898
+ }
12899
+
12900
+ // src/dispatch/handlers/status-criteria-set.ts
12901
+ function dispatchStatusCriteriaSet(action, deps) {
12902
+ const resolved = resolveStatusCriteria(action.criteria);
12903
+ const allIds = new Set(deps.adapter.getAllIds());
12904
+ const beforeSnap = snapshotActivities(deps.adapter, allIds);
12905
+ const changedIds = applyStatusPass(deps.adapter, resolved);
12906
+ deps.sector.statusCriteria = resolved;
12907
+ return {
12908
+ ok: true,
12909
+ changes: assembleChangeSet(deps.adapter, {
12910
+ source: action,
12911
+ beforeSnap,
12912
+ touchedIds: changedIds,
12913
+ scheduledIds: [],
12914
+ hoursPerDay: deps.sector.hoursPerDay
12915
+ })
12916
+ };
12559
12917
  }
12560
12918
 
12561
12919
  // src/dispatch/dispatch.ts
12562
12920
  async function dispatch(action, options, deps) {
12921
+ if (action.kind === "persistence-acknowledge") {
12922
+ return dispatchPersistenceAcknowledge(action, deps);
12923
+ }
12924
+ if (action.kind === "baseline-apply") {
12925
+ return dispatchBaselineApply(action, deps);
12926
+ }
12927
+ if (action.kind === "ponderator-criterion-set") {
12928
+ return dispatchPonderatorCriterionSet(action, deps);
12929
+ }
12930
+ if (action.kind === "status-criteria-set") {
12931
+ return dispatchStatusCriteriaSet(action, deps);
12932
+ }
12563
12933
  if (action.kind === "link-create" || action.kind === "link-update" || action.kind === "link-delete") {
12564
12934
  return dispatchLink(action, options, deps);
12565
12935
  }
@@ -15723,6 +16093,18 @@ var HierarchyIndex = class {
15723
16093
  arr.push(activity.id);
15724
16094
  this._childrenByParent.set(key, arr);
15725
16095
  }
16096
+ const compareIds = (left, right) => {
16097
+ const leftActivity = this.activities.get(left);
16098
+ const rightActivity = this.activities.get(right);
16099
+ if (!(leftActivity && rightActivity)) {
16100
+ return String(left).localeCompare(String(right));
16101
+ }
16102
+ return compareActivitiesInVisualOrder(leftActivity, rightActivity);
16103
+ };
16104
+ this._roots.sort(compareIds);
16105
+ for (const children of this._childrenByParent.values()) {
16106
+ children.sort(compareIds);
16107
+ }
15726
16108
  }
15727
16109
  };
15728
16110
 
@@ -16162,438 +16544,145 @@ var ScheduleState = class {
16162
16544
  this._viewState.restore(this._viewStateBefore);
16163
16545
  }
16164
16546
  if (this._lastStartDateBefore !== null) {
16165
- this._lastStartDate = this._lastStartDateBefore;
16166
- }
16167
- this._cache.ids = null;
16168
- this._cache.visualOrderIds = null;
16169
- this._hierarchy.markDirty();
16170
- }
16171
- _restoreCorrelatives(correlativeBefore) {
16172
- for (const [id, before] of correlativeBefore) {
16173
- const live = this._activities.get(id);
16174
- if (live) live.correlativeId = before;
16175
- }
16176
- }
16177
- _undoLinkAdds(ops) {
16178
- for (const op of ops)
16179
- if (op.kind === "link-add") this._detachLink(op.linkId);
16180
- }
16181
- _undoInserts(ops) {
16182
- for (const op of ops)
16183
- if (op.kind === "insert") this._activities.delete(op.activityId);
16184
- }
16185
- _reInsertRemovedActivities(ops) {
16186
- for (const op of ops)
16187
- if (op.kind === "remove") this._activities.set(op.activityId, op.before);
16188
- }
16189
- _reAddRemovedLinks(ops) {
16190
- for (const op of ops)
16191
- if (op.kind === "link-remove") this._attachLink(op.before);
16192
- }
16193
- _restoreLinkFields(ops) {
16194
- for (const op of ops)
16195
- if (op.kind === "link-field" && this._links.has(op.linkId))
16196
- this._links.set(op.linkId, op.before);
16197
- }
16198
- _restoreActivityFields(before) {
16199
- for (const [id, snapshot] of before) {
16200
- const live = this._activities.get(id);
16201
- if (live) this._restoreScalarFields(live, snapshot);
16202
- }
16203
- }
16204
- _restoreScalarFields(live, before) {
16205
- for (const key of Object.keys(live)) {
16206
- if (!Object.hasOwn(before, key)) Reflect.deleteProperty(live, key);
16207
- }
16208
- for (const key of Object.keys(before)) {
16209
- Reflect.set(live, key, Reflect.get(before, key));
16210
- }
16211
- }
16212
- _attachLink(link) {
16213
- this._links.set(String(link.id), { ...link });
16214
- this._indexLink(link);
16215
- }
16216
- _detachLink(linkId) {
16217
- const link = this.getLink(linkId);
16218
- if (!link) return;
16219
- this._links.delete(String(linkId));
16220
- this._deindexLink(link);
16221
- }
16222
- };
16223
- var EMPTY_LINK_IDS = [];
16224
- function isMutableLinkField(field) {
16225
- return field === LINK_PROPERTY.LAG || field === LINK_PROPERTY.TYPE;
16226
- }
16227
- function fieldAffectsVisualOrder(field) {
16228
- return field === ACTIVITY_PROPERTY.PARENT || field === ACTIVITY_PROPERTY.CORRELATIVE_ID;
16229
- }
16230
- function detachLinkRef(refs, linkId) {
16231
- return refs.filter((ref) => String(ref) !== String(linkId));
16232
- }
16233
- function pushLinkRef(map, key, linkId) {
16234
- const refs = map.get(key);
16235
- if (refs) refs.push(linkId);
16236
- else map.set(key, [linkId]);
16237
- }
16238
- function buildFlags(snapshot) {
16239
- const rawFlags = snapshot.runtimeFlags ?? {};
16240
- return {
16241
- isPasting: Boolean(rawFlags.isPasting),
16242
- isBulkOperation: Boolean(rawFlags.isBulkOperation),
16243
- dragWithMultiple: Boolean(rawFlags.dragWithMultiple),
16244
- draggingSingleMilestone: Boolean(rawFlags.draggingSingleMilestone),
16245
- creatingActivity: false,
16246
- avoidAutoScheduleBeforeDrag: false,
16247
- maxPerformance: Boolean(rawFlags.maxPerformance),
16248
- allCheckedTaskIds: []
16249
- };
16250
- }
16251
-
16252
- // src/init/read-api.ts
16253
- function readActivity(state, id) {
16254
- return state.getActivity(id);
16255
- }
16256
- function readAllActivities(state) {
16257
- return state.getAllActivities();
16258
- }
16259
- function readAllIds(state) {
16260
- return state.getAllIds().map(String);
16261
- }
16262
- function forEachActivityId(state, visit) {
16263
- state.forEachActivity((_activity, id) => visit(id));
16264
- }
16265
- function readChildren(state, parentId) {
16266
- const childIds = parentId === ROOT_PARENT_ID ? readChildrenIds(state, ROOT_PARENT_ID) : state.getChildren(parentId);
16267
- const out = [];
16268
- for (const id of childIds) {
16269
- const snap = state.getActivity(id);
16270
- if (snap) out.push(snap);
16271
- }
16272
- out.sort((a, b) => (a.correlativeId ?? 0) - (b.correlativeId ?? 0));
16273
- return out;
16274
- }
16275
- function readChildrenIds(state, parentId) {
16276
- const key = parentId;
16277
- if (key === ROOT_PARENT_ID) {
16278
- const roots = [];
16279
- state.forEachActivity((activity, id) => {
16280
- if (isRootParent(activity.parentId)) roots.push(id);
16281
- });
16282
- return roots;
16283
- }
16284
- return [...state.getChildren(key)].map(String);
16285
- }
16286
- function readSelectedActivityIds(state) {
16287
- return state.checkedIds().map(String);
16288
- }
16289
- function readHasChild(state, parentId) {
16290
- return state.getChildren(parentId).length > 0;
16291
- }
16292
- function readLink(state, id) {
16293
- const l = state.getLink(id);
16294
- return l ? snapshotToLink(l) : null;
16295
- }
16296
- function readAllLinks(state) {
16297
- return state.getAllLinks().map((l) => snapshotToLink(l));
16298
- }
16299
-
16300
- // src/propagations/downward/recompute-ponderator-for-parent.ts
16301
- function recomputePonderatorsForParent(parentId, criterion, adapter) {
16302
- if (!parentId || parentId === "0") return;
16303
- const childIds = adapter.getChildren(parentId);
16304
- if (childIds.length === 0) return;
16305
- const pesoById = /* @__PURE__ */ new Map();
16306
- let denominator = 0;
16307
- for (const childId of childIds) {
16308
- const peso = criterionValue(String(childId), criterion, adapter);
16309
- pesoById.set(String(childId), peso);
16310
- denominator += peso;
16311
- }
16312
- const relationValue = denominator ? 100 / denominator : 0;
16313
- for (const childId of childIds) {
16314
- const child = adapter.getActivity(childId);
16315
- if (!child) continue;
16316
- const points = child.baselinePoints;
16317
- const active = getActiveBaseline(child);
16318
- if (active) {
16319
- const peso = pesoById.get(String(childId)) ?? 0;
16320
- adapter.setActivityField(
16321
- childId,
16322
- COLUMN.PONDERATOR,
16323
- relationValue * peso
16324
- );
16325
- } else if (!Array.isArray(points) || points.length === 0) {
16326
- adapter.setActivityField(childId, COLUMN.PONDERATOR, 0);
16327
- }
16328
- }
16329
- }
16330
- function criterionValue(childId, criterion, adapter) {
16331
- const child = adapter.getActivity(childId);
16332
- if (!child) return 0;
16333
- const active = getActiveBaseline(child);
16334
- if (!active) return 0;
16335
- if (criterion === "COST") return toNumber(active.cost);
16336
- if (criterion === "HH") return toNumber(active.workHours);
16337
- const type = child.type;
16338
- if (type === "project")
16339
- return sumRecursiveBaselineDurationDays(childId, adapter);
16340
- if (type === "milestone") return 0;
16341
- return toNumber(active.durationDays);
16342
- }
16343
- function sumRecursiveBaselineDurationDays(activityId, adapter) {
16344
- let sum = 0;
16345
- for (const childId of adapter.getChildren(activityId)) {
16346
- const child = adapter.getActivity(childId);
16347
- if (!child) continue;
16348
- const active = getActiveBaseline(child);
16349
- if (!active) continue;
16350
- const type = child.type;
16351
- if (type === "project") {
16352
- sum += sumRecursiveBaselineDurationDays(String(childId), adapter);
16353
- } else if (type !== "milestone") {
16354
- sum += toNumber(active.durationDays);
16355
- }
16356
- }
16357
- return sum;
16358
- }
16359
- function toNumber(v) {
16360
- const n = typeof v === "string" ? parseFloat(v) : v;
16361
- return typeof n === "number" && Number.isFinite(n) ? n : 0;
16362
- }
16363
-
16364
- // src/propagations/downward/recompute-all-ponderators.ts
16365
- function recomputeAllPonderators(criterion, adapter) {
16366
- adapter.forEachActivity((_a, id) => {
16367
- adapter.setActivityField(id, COLUMN.PONDERATOR, 0);
16368
- });
16369
- adapter.forEachActivity((_a, id) => {
16370
- if (adapter.getChildren(id).length > 0) {
16371
- recomputePonderatorsForParent(String(id), criterion, adapter);
16547
+ this._lastStartDate = this._lastStartDateBefore;
16372
16548
  }
16373
- });
16374
- }
16375
-
16376
- // src/propagations/upward/progress-rollup.ts
16377
- function recomputeAllProgressRollup(adapter) {
16378
- for (const parentId of parentsDeepestFirst(adapter)) {
16379
- rollupParent(parentId, adapter);
16549
+ this._cache.ids = null;
16550
+ this._cache.visualOrderIds = null;
16551
+ this._hierarchy.markDirty();
16380
16552
  }
16381
- }
16382
- function parentsDeepestFirst(adapter) {
16383
- const parents = /* @__PURE__ */ new Set();
16384
- adapter.forEachActivity((_activity, id) => {
16385
- if (adapter.getChildren(id).length > 0) parents.add(id);
16386
- });
16387
- const depthById = /* @__PURE__ */ new Map();
16388
- for (const id of parents) depthById.set(id, depthOf2(id, adapter));
16389
- return [...parents].sort(
16390
- (a, b) => (depthById.get(b) ?? 0) - (depthById.get(a) ?? 0)
16391
- );
16392
- }
16393
- function depthOf2(id, adapter) {
16394
- let depth = 0;
16395
- let current = id;
16396
- while (current && current !== "0") {
16397
- if (!adapter.getActivity(current)) break;
16398
- depth += 1;
16399
- current = adapter.getParentId(current);
16553
+ _restoreCorrelatives(correlativeBefore) {
16554
+ for (const [id, before] of correlativeBefore) {
16555
+ const live = this._activities.get(id);
16556
+ if (live) live.correlativeId = before;
16557
+ }
16400
16558
  }
16401
- return depth;
16402
- }
16403
- function rollupParent(parentId, adapter) {
16404
- const childIds = adapter.getChildren(parentId);
16405
- if (childIds.length === 0) return;
16406
- const children = [];
16407
- for (const childId of childIds) {
16408
- const child = adapter.getActivity(childId);
16409
- if (!child) continue;
16410
- children.push({
16411
- progress: Number(child.progress ?? 0),
16412
- ponderator: Number(child.ponderator ?? 0)
16413
- });
16559
+ _undoLinkAdds(ops) {
16560
+ for (const op of ops)
16561
+ if (op.kind === "link-add") this._detachLink(op.linkId);
16414
16562
  }
16415
- const rollup = computeWeightedProgressRollup(children);
16416
- if (rollup !== null) {
16417
- adapter.setActivityField(
16418
- parentId,
16419
- COLUMN.PROGRESS,
16420
- roundProgressPerLevel(rollup)
16421
- );
16563
+ _undoInserts(ops) {
16564
+ for (const op of ops)
16565
+ if (op.kind === "insert") this._activities.delete(op.activityId);
16422
16566
  }
16423
- }
16424
-
16425
- // src/baselines/normalize-baseline-point.ts
16426
- function parseBaselineDate(value) {
16427
- return typeof value === "string" && value.length > 0 ? parseBackendDate(value) : value instanceof Date ? value : null;
16428
- }
16429
- function parseFiniteNonNegative(value) {
16430
- const numeric = typeof value === "number" ? value : typeof value === "string" && value.trim() !== "" ? Number(value) : Number.NaN;
16431
- if (!Number.isFinite(numeric) || numeric < 0) return null;
16432
- return numeric;
16433
- }
16434
- function normalizeBaselinePoint(point, reporter) {
16435
- const durationDays = parseFiniteNonNegative(point.duration);
16436
- const cost = parseFiniteNonNegative(point.cost);
16437
- const workHours = parseFiniteNonNegative(point.hh_work);
16438
- if (durationDays === null || cost === null || workHours === null) {
16439
- reporter.warn(
16440
- `[baseline] dropping point with non-finite/negative magnitude \u2014 duration=${JSON.stringify(point.duration)}, cost=${JSON.stringify(point.cost)}, hh_work=${JSON.stringify(point.hh_work)}`
16441
- );
16442
- return null;
16567
+ _reInsertRemovedActivities(ops) {
16568
+ for (const op of ops)
16569
+ if (op.kind === "remove") this._activities.set(op.activityId, op.before);
16443
16570
  }
16444
- return {
16445
- startDate: parseBaselineDate(point.start_date),
16446
- endDate: parseBaselineDate(point.end_date),
16447
- durationDays,
16448
- cost,
16449
- workHours,
16450
- versionId: typeof point.sectorbaselineversionId === "number" ? point.sectorbaselineversionId : null,
16451
- isActiveVersion: Boolean(point.sectorbaselineversion?.active),
16452
- baseCalendarId: point.baseCalendarId != null ? String(point.baseCalendarId) : null,
16453
- hoursPerDay: typeof point.hoursPerDay === "number" ? point.hoursPerDay : null,
16454
- hoursPerWeek: typeof point.hoursPerWeek === "number" ? point.hoursPerWeek : null
16455
- };
16456
- }
16457
-
16458
- // src/baselines/apply-baseline-points.ts
16459
- function applyBaselinePoints(adapter, points, reporter) {
16460
- const idByProplanner = /* @__PURE__ */ new Map();
16461
- adapter.forEachActivity((a, id) => {
16462
- const pid = a.proplannerId;
16463
- if (typeof pid === "number") idByProplanner.set(pid, id);
16464
- });
16465
- const grouped = /* @__PURE__ */ new Map();
16466
- for (const p of points) {
16467
- const activityId = p.activityId;
16468
- if (typeof activityId !== "number") continue;
16469
- const coreId = idByProplanner.get(activityId);
16470
- if (coreId === void 0) continue;
16471
- const arr = grouped.get(coreId);
16472
- if (arr) arr.push(p);
16473
- else grouped.set(coreId, [p]);
16571
+ _reAddRemovedLinks(ops) {
16572
+ for (const op of ops)
16573
+ if (op.kind === "link-remove") this._attachLink(op.before);
16474
16574
  }
16475
- const changed = [];
16476
- for (const [coreId, group] of grouped) {
16477
- adapter.setActivityField(
16478
- coreId,
16479
- "baselinePoints",
16480
- group.map((point) => normalizeBaselinePoint(point, reporter)).filter((point) => point !== null)
16481
- );
16482
- changed.push(coreId);
16575
+ _restoreLinkFields(ops) {
16576
+ for (const op of ops)
16577
+ if (op.kind === "link-field" && this._links.has(op.linkId))
16578
+ this._links.set(op.linkId, op.before);
16483
16579
  }
16484
- adapter.forEachActivity((a, id) => {
16485
- if (grouped.has(id)) return;
16486
- const existing = a.baselinePoints;
16487
- if (Array.isArray(existing) && existing.length > 0) {
16488
- adapter.setActivityField(id, "baselinePoints", []);
16489
- changed.push(id);
16580
+ _restoreActivityFields(before) {
16581
+ for (const [id, snapshot] of before) {
16582
+ const live = this._activities.get(id);
16583
+ if (live) this._restoreScalarFields(live, snapshot);
16490
16584
  }
16491
- });
16492
- return changed;
16585
+ }
16586
+ _restoreScalarFields(live, before) {
16587
+ for (const key of Object.keys(live)) {
16588
+ if (!Object.hasOwn(before, key)) Reflect.deleteProperty(live, key);
16589
+ }
16590
+ for (const key of Object.keys(before)) {
16591
+ Reflect.set(live, key, Reflect.get(before, key));
16592
+ }
16593
+ }
16594
+ _attachLink(link) {
16595
+ this._links.set(String(link.id), { ...link });
16596
+ this._indexLink(link);
16597
+ }
16598
+ _detachLink(linkId) {
16599
+ const link = this.getLink(linkId);
16600
+ if (!link) return;
16601
+ this._links.delete(String(linkId));
16602
+ this._deindexLink(link);
16603
+ }
16604
+ };
16605
+ var EMPTY_LINK_IDS = [];
16606
+ function isMutableLinkField(field) {
16607
+ return field === LINK_PROPERTY.LAG || field === LINK_PROPERTY.TYPE;
16493
16608
  }
16494
-
16495
- // src/baselines/baseline-snapshot.ts
16496
- function buildBaselineSnapshot(activity) {
16497
- const active = getActiveBaseline(activity);
16498
- if (!active) return null;
16609
+ function fieldAffectsVisualOrder(field) {
16610
+ return field === ACTIVITY_PROPERTY.PARENT || field === ACTIVITY_PROPERTY.CORRELATIVE_ID;
16611
+ }
16612
+ function detachLinkRef(refs, linkId) {
16613
+ return refs.filter((ref) => String(ref) !== String(linkId));
16614
+ }
16615
+ function pushLinkRef(map, key, linkId) {
16616
+ const refs = map.get(key);
16617
+ if (refs) refs.push(linkId);
16618
+ else map.set(key, [linkId]);
16619
+ }
16620
+ function buildFlags(snapshot) {
16621
+ const rawFlags = snapshot.runtimeFlags ?? {};
16499
16622
  return {
16500
- startDate: active.startDate,
16501
- endDate: active.endDate,
16502
- durationDays: active.durationDays,
16503
- cost: active.cost,
16504
- workHours: active.workHours
16623
+ isPasting: Boolean(rawFlags.isPasting),
16624
+ isBulkOperation: Boolean(rawFlags.isBulkOperation),
16625
+ dragWithMultiple: Boolean(rawFlags.dragWithMultiple),
16626
+ draggingSingleMilestone: Boolean(rawFlags.draggingSingleMilestone),
16627
+ creatingActivity: false,
16628
+ avoidAutoScheduleBeforeDrag: false,
16629
+ maxPerformance: Boolean(rawFlags.maxPerformance),
16630
+ allCheckedTaskIds: []
16505
16631
  };
16506
16632
  }
16507
- function emitBaselineSnapshotColumns(state) {
16508
- const changed = [];
16509
- state.forEachActivity((activity, id) => {
16510
- const snapshot = buildBaselineSnapshot(activity);
16511
- if (!snapshot) return;
16512
- state.setActivityField(id, "baselineSnapshot", snapshot);
16513
- changed.push(id);
16514
- });
16515
- return changed;
16516
- }
16517
16633
 
16518
- // src/baselines/apply-baseline-overlay.ts
16519
- function applyBaselineOverlay(state, points, now, reporter, defaultBaseCalendarId = null) {
16520
- if (points.length === 0) return [];
16521
- const changed = /* @__PURE__ */ new Set();
16522
- for (const id of applyBaselinePoints(
16523
- state,
16524
- points,
16525
- reporter
16526
- )) {
16527
- changed.add(id);
16528
- }
16529
- if (now) {
16530
- for (const id of runExpectedProgressBase(state, now, defaultBaseCalendarId))
16531
- changed.add(id);
16532
- }
16533
- for (const id of emitBaselineSnapshotColumns(state)) changed.add(id);
16534
- for (const id of emitRealCost(state)) changed.add(id);
16535
- return [...changed];
16634
+ // src/shared/clone-domain-value.ts
16635
+ function cloneDomainValue(value) {
16636
+ return structuredClone(value);
16536
16637
  }
16537
16638
 
16538
- // src/init/recompute-api.ts
16539
- function readPonderator(a) {
16540
- const v = a.ponderator;
16541
- return typeof v === "number" && Number.isFinite(v) ? v : 0;
16639
+ // src/init/read-api.ts
16640
+ function readActivity(state, id) {
16641
+ const activity = state.getActivity(id);
16642
+ return activity ? cloneDomainValue(activity) : null;
16542
16643
  }
16543
- function readProgress(a) {
16544
- const v = a.progress;
16545
- return typeof v === "number" && Number.isFinite(v) ? v : 0;
16644
+ function readAllActivities(state) {
16645
+ return state.getAllActivities().map(cloneDomainValue);
16546
16646
  }
16547
- function recomputeAllPonderators2(state, _hoursPerDay, criterion) {
16548
- const before = /* @__PURE__ */ new Map();
16549
- state.forEachActivity((a, id) => {
16550
- before.set(id, readPonderator(a));
16551
- });
16552
- recomputeAllPonderators(criterion, state);
16553
- const changed = [];
16554
- state.forEachActivity((a, id) => {
16555
- if (readPonderator(a) !== (before.get(id) ?? 0)) {
16556
- changed.push(structuredCloneActivity(a));
16557
- }
16558
- });
16559
- return changed;
16647
+ function readAllIds(state) {
16648
+ return state.getAllIds().map(String);
16560
16649
  }
16561
- function recomputeAllProgressRollup2(state, _hoursPerDay) {
16562
- const before = /* @__PURE__ */ new Map();
16563
- state.forEachActivity((a, id) => {
16564
- before.set(id, readProgress(a));
16565
- });
16566
- recomputeAllProgressRollup(state);
16567
- const changed = [];
16568
- state.forEachActivity((a, id) => {
16569
- if (readProgress(a) !== (before.get(id) ?? 0)) {
16570
- changed.push(structuredCloneActivity(a));
16571
- }
16572
- });
16573
- return changed;
16650
+ function forEachActivityId(state, visit) {
16651
+ state.forEachActivity((_activity, id) => visit(id));
16574
16652
  }
16575
- function applyBaselines(state, _hoursPerDay, points, now, reporter, defaultBaseCalendarId = null) {
16576
- const changedIds = applyBaselineOverlay(
16577
- state,
16578
- points,
16579
- now,
16580
- reporter,
16581
- defaultBaseCalendarId
16582
- );
16583
- const changed = [];
16584
- for (const id of changedIds) {
16653
+ function readChildren(state, parentId) {
16654
+ const childIds = parentId === ROOT_PARENT_ID ? readChildrenIds(state, ROOT_PARENT_ID) : state.getChildren(parentId);
16655
+ const out = [];
16656
+ for (const id of childIds) {
16585
16657
  const snap = state.getActivity(id);
16586
- if (snap) changed.push(structuredCloneActivity(snap));
16658
+ if (snap) out.push(cloneDomainValue(snap));
16659
+ }
16660
+ out.sort((a, b) => (a.correlativeId ?? 0) - (b.correlativeId ?? 0));
16661
+ return out;
16662
+ }
16663
+ function readChildrenIds(state, parentId) {
16664
+ const key = parentId;
16665
+ if (key === ROOT_PARENT_ID) {
16666
+ const roots = [];
16667
+ state.forEachActivity((activity, id) => {
16668
+ if (isRootParent(activity.parentId)) roots.push(id);
16669
+ });
16670
+ return roots;
16587
16671
  }
16588
- return changed;
16672
+ return [...state.getChildren(key)].map(String);
16589
16673
  }
16590
- function recomputeStatus(state, _hoursPerDay, criteria) {
16591
- const changed = [];
16592
- for (const id of applyStatusPass(state, criteria)) {
16593
- const snap = state.getActivity(id);
16594
- if (snap) changed.push(structuredCloneActivity(snap));
16595
- }
16596
- return changed;
16674
+ function readSelectedActivityIds(state) {
16675
+ return state.checkedIds().map(String);
16676
+ }
16677
+ function readHasChild(state, parentId) {
16678
+ return state.getChildren(parentId).length > 0;
16679
+ }
16680
+ function readLink(state, id) {
16681
+ const l = state.getLink(id);
16682
+ return l ? snapshotToLink(l) : null;
16683
+ }
16684
+ function readAllLinks(state) {
16685
+ return state.getAllLinks().map((l) => snapshotToLink(l));
16597
16686
  }
16598
16687
 
16599
16688
  // src/internal/state/project-work-hours.ts
@@ -16703,8 +16792,12 @@ function mergeCoalesced(top, next) {
16703
16792
  };
16704
16793
  return buildUndoEntry(changeSet);
16705
16794
  }
16706
- function isUndoableAction(action) {
16707
- return action.kind !== "sir-sync" && action.kind !== "activity-lookahead-sync";
16795
+ function getDispatchHistoryPolicy(action) {
16796
+ if (action.kind === "persistence-acknowledge") return "clear-on-success";
16797
+ if (action.kind === "sir-sync" || action.kind === "activity-lookahead-sync" || action.kind === "baseline-apply" || action.kind === "ponderator-criterion-set" || action.kind === "status-criteria-set") {
16798
+ return "skip";
16799
+ }
16800
+ return "record";
16708
16801
  }
16709
16802
  function needsResync(entry) {
16710
16803
  for (const change of entry.changeSet.activities) {
@@ -16971,6 +17064,7 @@ var UndoRecorder = class {
16971
17064
  clear() {
16972
17065
  this.undoStack.length = 0;
16973
17066
  this.redoStack.length = 0;
17067
+ this.resetCoalesce();
16974
17068
  }
16975
17069
  };
16976
17070
 
@@ -18339,6 +18433,117 @@ function snapDemotedLeafTiming(state, activityId) {
18339
18433
  state.setActivityField(activityId, ACTIVITY_PROPERTY.END_DATE, end);
18340
18434
  }
18341
18435
 
18436
+ // src/propagations/upward/recompute-real-work.ts
18437
+ function recomputeCanonicalRealWork(state) {
18438
+ const rootIds = [];
18439
+ const changedIds = [];
18440
+ state.forEachActivity((activity, activityId) => {
18441
+ if (activity.parentId === null) rootIds.push(String(activityId));
18442
+ });
18443
+ const visit = (activityId) => {
18444
+ const activity = state.getActivity(activityId);
18445
+ if (!activity) return 0;
18446
+ const childIds = [...state.getChildren(activityId)];
18447
+ const realWork = childIds.length === 0 ? computeLeafRealWork(activity.workHours, activity.progress) : childIds.reduce(
18448
+ (total, childId) => total + visit(String(childId)),
18449
+ 0
18450
+ );
18451
+ state.setActivityField(
18452
+ activityId,
18453
+ ACTIVITY_PROPERTY.REAL_WORK_HOURS,
18454
+ realWork
18455
+ );
18456
+ changedIds.push(activityId);
18457
+ return realWork;
18458
+ };
18459
+ for (const rootId of rootIds) visit(rootId);
18460
+ return changedIds;
18461
+ }
18462
+
18463
+ // src/init/initial-passes.ts
18464
+ async function runInitialPasses(deps) {
18465
+ const { state, sector, scheduler, skipAutoSchedule, now } = deps;
18466
+ recomputeCorrelativeIds(state);
18467
+ if (sector.updateDurationForPrimaveraEndDate) {
18468
+ recomputeDurationsFromDates(state);
18469
+ }
18470
+ normalizeLoadedConstraintDates(state);
18471
+ recomputeCanonicalRealWork(state);
18472
+ if (skipAutoSchedule) {
18473
+ if (now) {
18474
+ applyExpectedProgressLive(state, now);
18475
+ applyStatusPass(state, sector.statusCriteria);
18476
+ }
18477
+ return;
18478
+ }
18479
+ const result = await scheduler.schedule({});
18480
+ applyResults(state, result);
18481
+ normalizeMilestoneConstraintDatesForDisplay(state);
18482
+ recomputeSkippedLeafEnds(state);
18483
+ await updateParentBoundsFromChildren(state, void 0, false);
18484
+ if (now) {
18485
+ applyExpectedProgressLive(state, now);
18486
+ applyStatusPass(state, sector.statusCriteria);
18487
+ }
18488
+ }
18489
+ function recomputeSkippedLeafEnds(state) {
18490
+ state.forEachActivity((activity) => {
18491
+ if (activity.autoScheduling) return;
18492
+ if (!activity.startDate || activity.durationHours == null) return;
18493
+ if (state.getChildren(activity.id).length > 0) return;
18494
+ activity.endDate = state.calculateEndDate({
18495
+ startDate: activity.startDate,
18496
+ durationHours: activity.durationHours,
18497
+ task: activity
18498
+ });
18499
+ });
18500
+ }
18501
+ function recomputeDurationsFromDates(state) {
18502
+ state.forEachActivity((activity) => {
18503
+ if (!activity.startDate || !activity.endDate) return;
18504
+ activity.durationHours = state.calculateDuration({
18505
+ startDate: activity.startDate,
18506
+ endDate: activity.endDate,
18507
+ task: activity
18508
+ });
18509
+ });
18510
+ }
18511
+ function normalizeLoadedConstraintDates(state) {
18512
+ state.forEachActivity((activity) => {
18513
+ const type = activity.constraintType;
18514
+ if (!type || !activity.constraintDate) return;
18515
+ const constraintDate = activity.constraintDate;
18516
+ if (activity.type === "milestone" && (constraintDate.getUTCHours() !== 0 || constraintDate.getUTCMinutes() !== 0)) {
18517
+ return;
18518
+ }
18519
+ const calendar = state.calendarReader.getCalendar(
18520
+ String(activity.calendarId)
18521
+ );
18522
+ if (!calendar) return;
18523
+ activity.constraintDate = normalizeConstraintDate(
18524
+ activity.constraintDate,
18525
+ calendar,
18526
+ type
18527
+ );
18528
+ });
18529
+ }
18530
+ function normalizeMilestoneConstraintDatesForDisplay(state) {
18531
+ state.forEachActivity((activity) => {
18532
+ if (activity.type !== "milestone") return;
18533
+ const type = activity.constraintType;
18534
+ if (!type || !activity.constraintDate) return;
18535
+ const calendar = state.calendarReader.getCalendar(
18536
+ String(activity.calendarId)
18537
+ );
18538
+ if (!calendar) return;
18539
+ activity.constraintDate = normalizeConstraintDate(
18540
+ activity.constraintDate,
18541
+ calendar,
18542
+ type
18543
+ );
18544
+ });
18545
+ }
18546
+
18342
18547
  // src/init/initialize-core.ts
18343
18548
  var SCHEDULE_CORE_STATUS = {
18344
18549
  READY: "ready",
@@ -18426,48 +18631,8 @@ function initializeCore(input) {
18426
18631
  }
18427
18632
 
18428
18633
  // src/init/schedule-core.ts
18429
- function assertValidPersistenceAssignments(assignments, entityName, readCurrent, ownerByBackendId) {
18430
- const seenIds = /* @__PURE__ */ new Set();
18431
- const seenBackendIds = /* @__PURE__ */ new Set();
18432
- for (const assignment of assignments) {
18433
- const id = String(assignment.id);
18434
- if (id.length === 0 || seenIds.has(id)) {
18435
- throw new Error(
18436
- `acknowledgePersistence: duplicate or empty ${entityName} id "${id}"`
18437
- );
18438
- }
18439
- if (!Number.isSafeInteger(assignment.proplannerId) || assignment.proplannerId <= 0 || seenBackendIds.has(assignment.proplannerId)) {
18440
- throw new Error(
18441
- `acknowledgePersistence: invalid or duplicate ${entityName} proplannerId ${String(assignment.proplannerId)}`
18442
- );
18443
- }
18444
- const state = readCurrent(id);
18445
- if (!state.exists) {
18446
- throw new Error(
18447
- `acknowledgePersistence: unknown ${entityName} id "${id}"`
18448
- );
18449
- }
18450
- if (state.current !== null && state.current !== assignment.proplannerId) {
18451
- throw new Error(
18452
- `acknowledgePersistence: ${entityName} "${id}" already has proplannerId ${String(state.current)}`
18453
- );
18454
- }
18455
- const existingOwner = ownerByBackendId.get(assignment.proplannerId);
18456
- if (existingOwner !== void 0 && existingOwner !== id) {
18457
- throw new Error(
18458
- `acknowledgePersistence: ${entityName} proplannerId ${String(assignment.proplannerId)} already belongs to "${existingOwner}"`
18459
- );
18460
- }
18461
- seenIds.add(id);
18462
- seenBackendIds.add(assignment.proplannerId);
18463
- }
18464
- }
18465
18634
  var ScheduleCore = class {
18466
18635
  _status = SCHEDULE_CORE_STATUS.READY;
18467
- /** True while an async dispatch is suspended inside the write-capture window.
18468
- * A sync facade mutation running in that gap would corrupt the in-flight
18469
- * journal, so sync mutators refuse to run while it is set. */
18470
- _dispatchInFlight = false;
18471
18636
  coreRuntime;
18472
18637
  _undo = new UndoRecorder();
18473
18638
  _opQueue = Promise.resolve();
@@ -18518,7 +18683,7 @@ var ScheduleCore = class {
18518
18683
  return this._status;
18519
18684
  }
18520
18685
  getSector() {
18521
- return this.coreRuntime.sector;
18686
+ return cloneDomainValue(this.coreRuntime.sector);
18522
18687
  }
18523
18688
  getActivityView(id) {
18524
18689
  this.assertReady();
@@ -18544,7 +18709,7 @@ var ScheduleCore = class {
18544
18709
  this.assertReady();
18545
18710
  const snapshot = this.coreRuntime.state.getActivity(activityId);
18546
18711
  if (!snapshot) return null;
18547
- return snapshot[property];
18712
+ return cloneDomainValue(snapshot[property]);
18548
18713
  }
18549
18714
  getAllIds() {
18550
18715
  this.assertReady();
@@ -18571,7 +18736,7 @@ var ScheduleCore = class {
18571
18736
  */
18572
18737
  getVisualOrderIds() {
18573
18738
  this.assertReady();
18574
- return this.coreRuntime.state.getVisualOrderIds();
18739
+ return [...this.coreRuntime.state.getVisualOrderIds()];
18575
18740
  }
18576
18741
  hasChild(parentId) {
18577
18742
  this.assertReady();
@@ -18579,15 +18744,21 @@ var ScheduleCore = class {
18579
18744
  }
18580
18745
  getCalendar(id) {
18581
18746
  this.assertReady();
18582
- return this.coreRuntime.calendars.get(id) ?? null;
18747
+ const calendar = this.coreRuntime.calendars.get(id);
18748
+ return calendar ? cloneDomainValue(calendar) : null;
18583
18749
  }
18584
18750
  getAllCalendars() {
18585
18751
  this.assertReady();
18586
- return Array.from(this.coreRuntime.calendars.values());
18752
+ return Array.from(
18753
+ this.coreRuntime.calendars.values(),
18754
+ (calendar) => cloneDomainValue(calendar)
18755
+ );
18587
18756
  }
18588
18757
  getBaseCalendars() {
18589
18758
  this.assertReady();
18590
- return this.coreRuntime.baseCalendars;
18759
+ return this.coreRuntime.baseCalendars.map(
18760
+ (calendar) => cloneDomainValue(calendar)
18761
+ );
18591
18762
  }
18592
18763
  isCustomIdInUse(customId, currentCustomId = null) {
18593
18764
  this.assertReady();
@@ -18604,184 +18775,9 @@ var ScheduleCore = class {
18604
18775
  this.assertReady();
18605
18776
  return this._saveTracker.modifiedLinks(this.getAllLinksView());
18606
18777
  }
18607
- markLinksPersisted() {
18608
- this._saveTracker.snapshotLinks(this.getAllLinksView());
18609
- }
18610
18778
  getModifiedActivities() {
18611
18779
  this.assertReady();
18612
- return this._saveTracker.modifiedActivities(
18613
- this.coreRuntime.state.getAllActivities()
18614
- );
18615
- }
18616
- markActivitiesPersisted() {
18617
- this._saveTracker.snapshotActivities(
18618
- this.coreRuntime.state.getAllActivities()
18619
- );
18620
- }
18621
- /**
18622
- * Reconciles the backend identities returned by a successful schedule save.
18623
- *
18624
- * This is an application-boundary acknowledgement, not an editable schedule
18625
- * gesture: it does not autoschedule, recompute, emit an undo entry, or invent
18626
- * domain changes. Every assignment is validated before the first mutation so
18627
- * a malformed/partial response cannot leave the core half-reconciled. Once
18628
- * applied, the complete live graph becomes the new save-tracker baseline.
18629
- */
18630
- acknowledgePersistence(acknowledgement) {
18631
- this.assertReady();
18632
- const activities = acknowledgement.activities ?? [];
18633
- const links = acknowledgement.links ?? [];
18634
- const activityOwnerByBackendId = /* @__PURE__ */ new Map();
18635
- for (const activity of this.coreRuntime.state.getAllActivities()) {
18636
- if (activity.proplannerId != null) {
18637
- activityOwnerByBackendId.set(
18638
- activity.proplannerId,
18639
- String(activity.id)
18640
- );
18641
- }
18642
- }
18643
- const linkOwnerByBackendId = /* @__PURE__ */ new Map();
18644
- for (const link of this.coreRuntime.state.getAllLinks()) {
18645
- if (link.proplannerId != null) {
18646
- linkOwnerByBackendId.set(link.proplannerId, String(link.id));
18647
- }
18648
- }
18649
- assertValidPersistenceAssignments(
18650
- activities,
18651
- "activity",
18652
- (id) => {
18653
- const activity = this.coreRuntime.state.getActivity(id);
18654
- return {
18655
- exists: activity !== null,
18656
- current: activity?.proplannerId ?? null
18657
- };
18658
- },
18659
- activityOwnerByBackendId
18660
- );
18661
- assertValidPersistenceAssignments(
18662
- links,
18663
- "link",
18664
- (id) => {
18665
- const link = this.coreRuntime.state.getLink(id);
18666
- return {
18667
- exists: link !== null,
18668
- current: link?.proplannerId ?? null
18669
- };
18670
- },
18671
- linkOwnerByBackendId
18672
- );
18673
- this.coreRuntime.state.batchUpdate(() => {
18674
- for (const assignment of activities) {
18675
- this.coreRuntime.state.setActivityField(
18676
- assignment.id,
18677
- "proplannerId",
18678
- assignment.proplannerId
18679
- );
18680
- }
18681
- for (const assignment of links) {
18682
- this.coreRuntime.state.setLinkProplannerId(
18683
- assignment.id,
18684
- assignment.proplannerId
18685
- );
18686
- }
18687
- });
18688
- this.markActivitiesPersisted();
18689
- this.markLinksPersisted();
18690
- }
18691
- applyBaselines(points) {
18692
- this.assertReady();
18693
- const now = this.coreRuntime.clock ? endOfLocalDay(this.coreRuntime.clock()) : null;
18694
- const defaultBaseCalendarId = this.coreRuntime.baseCalendars.find((calendar) => calendar.baseDefault)?.id ?? null;
18695
- return applyBaselines(
18696
- this.coreRuntime.state,
18697
- this.coreRuntime.sector.hoursPerDay,
18698
- points,
18699
- now,
18700
- this.coreRuntime.reporter,
18701
- defaultBaseCalendarId
18702
- );
18703
- }
18704
- /**
18705
- * Whole-tree ponderator recompute (zeroes, then redistributes 100 within
18706
- * each parent from its children's active baselines, weighted by `criterion`).
18707
- * Returns the activities whose `ponderator` changed, for a prop-only repaint.
18708
- * The base rollup (`applyBaselines`) depends on fresh ponderators, so callers
18709
- * run this before it on a criterion change or baseline save.
18710
- */
18711
- recomputeAllPonderators(criterion) {
18712
- this.assertReady();
18713
- return recomputeAllPonderators2(
18714
- this.coreRuntime.state,
18715
- this.coreRuntime.sector.hoursPerDay,
18716
- criterion
18717
- );
18718
- }
18719
- /**
18720
- * Recompute the weighted-progress rollup of every parent, bottom-up.
18721
- * Returns the activities whose `progress` changed, for a prop-only repaint.
18722
- */
18723
- recomputeAllProgressRollup() {
18724
- this.assertReady();
18725
- return recomputeAllProgressRollup2(
18726
- this.coreRuntime.state,
18727
- this.coreRuntime.sector.hoursPerDay
18728
- );
18729
- }
18730
- /**
18731
- * Set the project's status criterion (the Baseline↔Actual toggle) and
18732
- * recompute every activity's `status` against it. Stores the criterion so
18733
- * future dispatches keep deriving status with it. Returns the activities
18734
- * whose `status` changed, for a prop-only repaint.
18735
- */
18736
- setStatusCriteria(criteria) {
18737
- this.assertReady();
18738
- const resolved = resolveStatusCriteria(criteria);
18739
- this.coreRuntime.sector.statusCriteria = resolved;
18740
- return recomputeStatus(
18741
- this.coreRuntime.state,
18742
- this.coreRuntime.sector.hoursPerDay,
18743
- resolved
18744
- );
18745
- }
18746
- allocateActivityId() {
18747
- this.assertReady();
18748
- return this.coreRuntime.activityIdGenerator.next();
18749
- }
18750
- createActivity(input) {
18751
- this.assertReady();
18752
- if (this._dispatchInFlight) {
18753
- throw new Error(
18754
- "[ScheduleCore] createActivity called during an in-flight dispatch"
18755
- );
18756
- }
18757
- const action = { kind: "activity-create", ...input };
18758
- this.coreRuntime.state.beginWriteCapture();
18759
- this.coreRuntime.customIdTracker.beginCustomIdTransaction();
18760
- let result;
18761
- try {
18762
- result = createActivitySync(action, {
18763
- adapter: this.coreRuntime.state,
18764
- scheduler: this.coreRuntime.scheduler,
18765
- sector: this.coreRuntime.sector,
18766
- calendars: this.coreRuntime.calendars,
18767
- activityIdGen: this.coreRuntime.activityIdGenerator,
18768
- uidGen: this.coreRuntime.uniqueCorrelativeIdGenerator,
18769
- customIdTracker: this.coreRuntime.customIdTracker
18770
- });
18771
- if (!result.ok) this._rollback();
18772
- } catch (error) {
18773
- this._rollback();
18774
- throw error;
18775
- } finally {
18776
- this.coreRuntime.state.endWriteCapture();
18777
- this.coreRuntime.customIdTracker.commitCustomIdTransaction();
18778
- }
18779
- if (!result.ok) return result;
18780
- return {
18781
- ok: true,
18782
- activity: result.activity,
18783
- changes: result.changes
18784
- };
18780
+ return this._saveTracker.modifiedActivities(this.coreRuntime.state.getAllActivities()).map(cloneDomainValue);
18785
18781
  }
18786
18782
  _enqueue(work) {
18787
18783
  const workPromise = this._opQueue.then(work, work);
@@ -18801,10 +18797,9 @@ var ScheduleCore = class {
18801
18797
  dateFormat: this.coreRuntime.sector.dateFormat,
18802
18798
  inputUnit: options.inputUnit ?? "days"
18803
18799
  });
18804
- const undoable = isUndoableAction(action);
18800
+ const historyPolicy = getDispatchHistoryPolicy(action);
18805
18801
  this.coreRuntime.state.beginWriteCapture();
18806
18802
  this.coreRuntime.customIdTracker.beginCustomIdTransaction();
18807
- this._dispatchInFlight = true;
18808
18803
  let result;
18809
18804
  try {
18810
18805
  result = await dispatch(action, options, {
@@ -18817,6 +18812,7 @@ var ScheduleCore = class {
18817
18812
  linkIdGen: this.coreRuntime.linkIdGenerator,
18818
18813
  uidGen: this.coreRuntime.uniqueCorrelativeIdGenerator,
18819
18814
  customIdTracker: this.coreRuntime.customIdTracker,
18815
+ reporter: this.coreRuntime.reporter,
18820
18816
  defaultBaseCalendarId: this.coreRuntime.baseCalendars.find(
18821
18817
  (calendar) => calendar.baseDefault
18822
18818
  )?.id ?? null,
@@ -18827,11 +18823,10 @@ var ScheduleCore = class {
18827
18823
  this._rollback();
18828
18824
  throw error;
18829
18825
  } finally {
18830
- this._dispatchInFlight = false;
18831
18826
  this.coreRuntime.state.endWriteCapture();
18832
18827
  this.coreRuntime.customIdTracker.commitCustomIdTransaction();
18833
18828
  }
18834
- if (result.ok && undoable && changeSetIsSubstantive(result.changes)) {
18829
+ if (result.ok && historyPolicy === "record" && changeSetIsSubstantive(result.changes)) {
18835
18830
  const created = captureCreatedSnapshots(
18836
18831
  this.coreRuntime.state,
18837
18832
  result.changes
@@ -18848,11 +18843,18 @@ var ScheduleCore = class {
18848
18843
  Date.now()
18849
18844
  );
18850
18845
  }
18846
+ if (result.ok && historyPolicy === "clear-on-success") {
18847
+ this._saveTracker.snapshotActivities(
18848
+ this.coreRuntime.state.getAllActivities()
18849
+ );
18850
+ this._saveTracker.snapshotLinks(this.getAllLinksView());
18851
+ this._undo.clear();
18852
+ }
18851
18853
  if (!result.ok) return result;
18852
- if (changeSetIsSubstantive(result.changes)) {
18854
+ if (dispatchChangesSchedulingState(action) && changeSetIsSubstantive(result.changes)) {
18853
18855
  this._recordScheduleMutation(options.skipCriticalPath !== true);
18854
18856
  }
18855
- return { ...result, changes: result.changes };
18857
+ return cloneDomainValue(result);
18856
18858
  }
18857
18859
  /**
18858
18860
  * Starts or joins the Critical Path calculation for the current schedule
@@ -18985,12 +18987,42 @@ var ScheduleCore = class {
18985
18987
  }
18986
18988
  return { changes };
18987
18989
  }
18990
+ /**
18991
+ * Undo/Redo restores the user's historical mutation while retaining current
18992
+ * non-historical truth (for example a refreshed baseline). Re-derive every
18993
+ * value that depends on both so the restored model is immediately coherent.
18994
+ */
18995
+ _recomputeAfterHistoryRestore() {
18996
+ const state = this.coreRuntime.state;
18997
+ recomputeAllProgressRollup(state);
18998
+ recomputeCanonicalRealWork(state);
18999
+ emitRealCost(state);
19000
+ if (this.coreRuntime.clock) {
19001
+ const now = endOfLocalDay(this.coreRuntime.clock());
19002
+ applyExpectedProgressLive(state, now);
19003
+ let hasActiveBaseline = false;
19004
+ state.forEachActivity((activity) => {
19005
+ hasActiveBaseline ||= getActiveBaseline(activity) !== null;
19006
+ });
19007
+ if (hasActiveBaseline) {
19008
+ runExpectedProgressBase(
19009
+ state,
19010
+ now,
19011
+ this.coreRuntime.baseCalendars.find(
19012
+ (calendar) => calendar.baseDefault
19013
+ )?.id ?? null
19014
+ );
19015
+ }
19016
+ applyStatusPass(state, this.coreRuntime.sector.statusCriteria);
19017
+ }
19018
+ }
18988
19019
  undo() {
18989
19020
  const operation = this._enqueue(async () => {
18990
19021
  this.assertReady();
18991
19022
  const entry = this._undo.takeUndo();
18992
19023
  if (!entry) return null;
18993
19024
  applyUndo(this.coreRuntime.state, entry);
19025
+ this._recomputeAfterHistoryRestore();
18994
19026
  this._recordScheduleMutation(false);
18995
19027
  if (needsResync(entry)) this._resyncCustomIdTrackerFromModel();
18996
19028
  this._undo.pushRedo(entry);
@@ -19018,6 +19050,7 @@ var ScheduleCore = class {
19018
19050
  const entry = this._undo.takeRedo();
19019
19051
  if (!entry) return null;
19020
19052
  applyRedo(this.coreRuntime.state, entry);
19053
+ this._recomputeAfterHistoryRestore();
19021
19054
  this._recordScheduleMutation(false);
19022
19055
  if (needsResync(entry)) this._resyncCustomIdTrackerFromModel();
19023
19056
  this._undo.pushUndo(entry);
@@ -19039,15 +19072,20 @@ var ScheduleCore = class {
19039
19072
  );
19040
19073
  return operation;
19041
19074
  }
19042
- clearHistory() {
19043
- this._undo.clear();
19044
- }
19045
19075
  canUndo() {
19046
19076
  return this._undo.canUndo();
19047
19077
  }
19048
19078
  canRedo() {
19049
19079
  return this._undo.canRedo();
19050
19080
  }
19081
+ /**
19082
+ * Establishes a new persistence boundary without mutating schedule state.
19083
+ * Completed saves call this synchronously so neither prior undo entries nor
19084
+ * their redo branch can cross the persisted boundary.
19085
+ */
19086
+ clearHistory() {
19087
+ this._undo.clear();
19088
+ }
19051
19089
  undoDepth() {
19052
19090
  return this._undo.undoDepth();
19053
19091
  }
@@ -19133,6 +19171,10 @@ var DEFAULT_HOURS_PER_DAY = 8;
19133
19171
 
19134
19172
  // src/dispatch/action-kinds.ts
19135
19173
  var DISPATCH_ACTION_KIND = {
19174
+ PERSISTENCE_ACKNOWLEDGE: "persistence-acknowledge",
19175
+ BASELINE_APPLY: "baseline-apply",
19176
+ PONDERATOR_CRITERION_SET: "ponderator-criterion-set",
19177
+ STATUS_CRITERIA_SET: "status-criteria-set",
19136
19178
  INLINE_EDIT: "inline-edit",
19137
19179
  DATES_BATCH: "dates-batch",
19138
19180
  BULK_EDIT: "bulk-edit",
@@ -19154,6 +19196,10 @@ var DISPATCH_ACTION_KIND = {
19154
19196
  ACTIVITY_LOOKAHEAD_SYNC: "activity-lookahead-sync"
19155
19197
  };
19156
19198
  var KIND_CATALOG_COVERS_UNION = {
19199
+ [DISPATCH_ACTION_KIND.PERSISTENCE_ACKNOWLEDGE]: true,
19200
+ [DISPATCH_ACTION_KIND.BASELINE_APPLY]: true,
19201
+ [DISPATCH_ACTION_KIND.PONDERATOR_CRITERION_SET]: true,
19202
+ [DISPATCH_ACTION_KIND.STATUS_CRITERIA_SET]: true,
19157
19203
  [DISPATCH_ACTION_KIND.INLINE_EDIT]: true,
19158
19204
  [DISPATCH_ACTION_KIND.DATES_BATCH]: true,
19159
19205
  [DISPATCH_ACTION_KIND.BULK_EDIT]: true,
@@ -19202,7 +19248,6 @@ exports.REJECTION_REASON = REJECTION_REASON;
19202
19248
  exports.ROOT_PARENT_ID = ROOT_PARENT_ID;
19203
19249
  exports.ScheduleCore = ScheduleCore;
19204
19250
  exports.WORK_TIME_DIRECTION = WORK_TIME_DIRECTION;
19205
- exports.applyBaselinePoints = applyBaselinePoints;
19206
19251
  exports.checkNoUpdatedLinks = checkNoUpdatedLinks;
19207
19252
  exports.computeExpectedProgress = computeExpectedProgress;
19208
19253
  exports.expectedProgressFromBaseline = expectedProgressFromBaseline;
@@ -19211,8 +19256,6 @@ exports.getUnsavedActivities = getUnsavedActivities;
19211
19256
  exports.isRootParent = isRootParent;
19212
19257
  exports.normalizeParentKey = normalizeParentKey;
19213
19258
  exports.parseFromBackend = parseFromBackend;
19214
- exports.recomputeAllPonderators = recomputeAllPonderators;
19215
- exports.recomputeAllProgressRollup = recomputeAllProgressRollup;
19216
19259
  exports.willRunCriticalPath = willRunCriticalPath;
19217
19260
  exports.yieldToBrowser = yieldToBrowser;
19218
19261
  //# sourceMappingURL=index.cjs.map