@outbuild-company/schedule-core 1.27.0 → 1.29.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -511,6 +511,8 @@ function createActivitiesApi(engine) {
511
511
  childIds: (parentId) => engine.getChildrenIds(parentId),
512
512
  hasChildren: (parentId) => engine.hasChild(parentId),
513
513
  indentTargetParentIds: (ids) => engine.getIndentTargetParentIds(ids),
514
+ leafParentIdsPromotedBy: (actions) => engine.getLeafParentIdsPromotedBy(actions),
515
+ lookaheadConfirmationFor: (actions, options) => engine.getLookaheadConfirmation(actions, options),
514
516
  isCustomIdInUse: (customId, currentCustomId) => engine.isCustomIdInUse(customId, currentCustomId ?? null),
515
517
  clipboard: () => engine.getActivityClipboardView(),
516
518
  create: (input, options) => engine.dispatch(
@@ -581,11 +583,14 @@ function createActivitiesApi(engine) {
581
583
  newValue: progress
582
584
  }),
583
585
  copy: (context = {}) => engine.dispatch({ ...context, kind: DISPATCH_ACTION_KIND.ACTIVITY_COPY }),
584
- paste: (afterSiblingId, context = {}) => engine.dispatch({
585
- ...context,
586
- kind: DISPATCH_ACTION_KIND.ACTIVITY_PASTE_COPIED,
587
- destination: { afterSiblingId }
588
- }),
586
+ paste: (afterSiblingId, context = {}, options) => {
587
+ const action = {
588
+ ...context,
589
+ kind: DISPATCH_ACTION_KIND.ACTIVITY_PASTE_COPIED,
590
+ destination: { afterSiblingId }
591
+ };
592
+ return options === void 0 ? engine.dispatch(action) : engine.dispatch(action, options);
593
+ },
589
594
  duplicate: (activityIds, context = {}) => engine.dispatch({
590
595
  ...context,
591
596
  kind: DISPATCH_ACTION_KIND.ACTIVITY_DUPLICATE,
@@ -14843,6 +14848,184 @@ function resolveStartDate(parent, fallback, override) {
14843
14848
  return fallback ? new Date(fallback) : /* @__PURE__ */ new Date();
14844
14849
  }
14845
14850
 
14851
+ // src/activity-copy-paste/paste.ts
14852
+ function resolvePasteDestination(afterSiblingId, adapter) {
14853
+ const sibling = adapter.getActivity(afterSiblingId);
14854
+ if (!sibling) {
14855
+ return { ok: false, reason: REJECTION_REASON.ANCHOR_SIBLING_NOT_FOUND };
14856
+ }
14857
+ if (sibling.parentId == null) {
14858
+ return adapter.getRootIds().length === 1 ? {
14859
+ ok: true,
14860
+ parentId: sibling.id,
14861
+ beforeSiblingId: adapter.getChildren(sibling.id)[0]
14862
+ } : { ok: true, parentId: ROOT_PARENT_ID, afterSiblingId };
14863
+ }
14864
+ if (adapter.hasChildren(sibling.id)) {
14865
+ return {
14866
+ ok: true,
14867
+ parentId: sibling.id,
14868
+ beforeSiblingId: adapter.getChildren(sibling.id)[0]
14869
+ };
14870
+ }
14871
+ return {
14872
+ ok: true,
14873
+ parentId: String(sibling.parentId),
14874
+ afterSiblingId
14875
+ };
14876
+ }
14877
+ var invariantFailure = () => ({
14878
+ ok: false,
14879
+ reason: REJECTION_REASON.ACTIVITY_PASTE_INVARIANT_FAILED
14880
+ });
14881
+ async function dispatchActivityPasteCopied(action, options, deps) {
14882
+ const clipboard = deps.activityClipboard.value;
14883
+ if (clipboard === null) {
14884
+ return { ok: false, reason: REJECTION_REASON.CLIPBOARD_EMPTY };
14885
+ }
14886
+ const destination = resolvePasteDestination(
14887
+ action.destination.afterSiblingId,
14888
+ deps.adapter
14889
+ );
14890
+ if (!destination.ok) return destination;
14891
+ const beforeSnapshot = await snapshotActivities(
14892
+ deps.adapter,
14893
+ /* @__PURE__ */ new Set()
14894
+ );
14895
+ const touched = /* @__PURE__ */ new Set();
14896
+ const originalToNew = /* @__PURE__ */ new Map();
14897
+ const createdIds = [];
14898
+ let previousRootId;
14899
+ for (const copied of clipboard.activities) {
14900
+ const mappedParent = originalToNew.get(copied.originalParentId);
14901
+ const afterSiblingId = mappedParent === void 0 ? previousRootId ?? destination.afterSiblingId : void 0;
14902
+ const beforeSiblingId = mappedParent === void 0 && afterSiblingId === void 0 ? destination.beforeSiblingId : void 0;
14903
+ const created = await createActivityCore(
14904
+ {
14905
+ kind: DISPATCH_ACTION_KIND.ACTIVITY_CREATE,
14906
+ parentId: mappedParent ?? destination.parentId,
14907
+ ...afterSiblingId === void 0 ? {} : { afterSiblingId },
14908
+ ...beforeSiblingId === void 0 ? {} : { beforeSiblingId },
14909
+ overrides: cloneDomainValue(copied.overrides),
14910
+ eventSource: action.eventSource
14911
+ },
14912
+ deps,
14913
+ {
14914
+ skipCorrelativeRecompute: true,
14915
+ skipBeforeSnap: true,
14916
+ customIdReferenceId: action.destination.afterSiblingId,
14917
+ confirmedLookaheadParentIds: options.confirmedLookaheadParentIds,
14918
+ lookaheadTasksByActivityId: options.lookaheadTasksByActivityId
14919
+ }
14920
+ );
14921
+ if (!created.ok) return created;
14922
+ originalToNew.set(copied.originalId, created.newId);
14923
+ createdIds.push(created.newId);
14924
+ if (mappedParent === void 0) previousRootId = created.newId;
14925
+ }
14926
+ const correlativeShifts = recomputeCorrelativeIds(deps.adapter);
14927
+ const createdIdSet = new Set(createdIds.map(String));
14928
+ foldCorrelativeShifts(
14929
+ deps.adapter,
14930
+ correlativeShifts,
14931
+ createdIdSet,
14932
+ beforeSnapshot,
14933
+ touched
14934
+ );
14935
+ for (const createdId of createdIds) touched.add(String(createdId));
14936
+ const appliedLinks = [];
14937
+ for (const copiedLink of clipboard.links) {
14938
+ const source = originalToNew.get(copiedLink.source);
14939
+ const target = originalToNew.get(copiedLink.target);
14940
+ if (source === void 0 || target === void 0) return invariantFailure();
14941
+ const operation = {
14942
+ kind: LINK_OPERATION_KIND.CREATE,
14943
+ source,
14944
+ target,
14945
+ type: copiedLink.type,
14946
+ lag: copiedLink.lag
14947
+ };
14948
+ const result = applyLinkOperation(operation, {
14949
+ port: deps.adapter,
14950
+ newLinkId: deps.linkIdGen.next
14951
+ });
14952
+ if (!result.applied) return invariantFailure();
14953
+ appliedLinks.push({
14954
+ op: operation,
14955
+ finalLinkId: result.linkId,
14956
+ rejected: null
14957
+ });
14958
+ }
14959
+ const { scheduledIds } = await runPostMutation(
14960
+ {
14961
+ adapter: deps.adapter,
14962
+ scheduler: deps.scheduler,
14963
+ sector: deps.sector,
14964
+ defaultBaseCalendarId: deps.defaultBaseCalendarId,
14965
+ runAutomaticP6Schedule: deps.runAutomaticP6Schedule
14966
+ },
14967
+ {
14968
+ action,
14969
+ autoscheduleFrom: createdIds[0] ?? null,
14970
+ recomputeParentsFrom: createdIds,
14971
+ now: deps.now,
14972
+ options
14973
+ }
14974
+ );
14975
+ const linkChanges = buildLinkChangesForBatch(
14976
+ appliedLinks,
14977
+ /* @__PURE__ */ new Map(),
14978
+ deps.adapter
14979
+ );
14980
+ return {
14981
+ ok: true,
14982
+ changes: await assembleChangeSet(deps.adapter, {
14983
+ source: action,
14984
+ beforeSnap: beforeSnapshot,
14985
+ touchedIds: touched,
14986
+ scheduledIds,
14987
+ links: linkChanges,
14988
+ hoursPerDay: deps.sector.hoursPerDay
14989
+ })
14990
+ };
14991
+ }
14992
+
14993
+ // src/dispatch/confirmations.ts
14994
+ var LOOKAHEAD_CONFIRMATION_KIND = {
14995
+ HAS_LOOKAHEAD_TASKS: "has_lookahead_tasks"
14996
+ };
14997
+
14998
+ // src/dispatch/shared/hierarchy-cycle-guard.ts
14999
+ function reparentClosesEffectiveCycle(adapter, reparented) {
15000
+ const links = adapter.getAllLinks();
15001
+ const verdict = validateTopologyCommit({
15002
+ port: adapter,
15003
+ currentLinks: links,
15004
+ proposedLinks: links,
15005
+ proposal: { reparented }
15006
+ });
15007
+ return !verdict.ok;
15008
+ }
15009
+ function collectCyclicReparents(planned, adapter) {
15010
+ const cyclic = /* @__PURE__ */ new Set();
15011
+ if (planned.length === 0) return cyclic;
15012
+ const combined = new Map(
15013
+ planned.map((reparent) => [reparent.activityId, reparent.newParentId])
15014
+ );
15015
+ if (!reparentClosesEffectiveCycle(adapter, combined)) return cyclic;
15016
+ const accepted2 = /* @__PURE__ */ new Map();
15017
+ for (const reparent of planned) {
15018
+ const trial = new Map(accepted2);
15019
+ trial.set(reparent.activityId, reparent.newParentId);
15020
+ if (reparentClosesEffectiveCycle(adapter, trial)) {
15021
+ cyclic.add(reparent.activityId);
15022
+ continue;
15023
+ }
15024
+ accepted2.set(reparent.activityId, reparent.newParentId);
15025
+ }
15026
+ return cyclic;
15027
+ }
15028
+
14846
15029
  // src/dispatch/shared/positional.ts
14847
15030
  function computeInsertionCorrelativeId(adapter, parentId, afterSiblingId, beforeSiblingId) {
14848
15031
  const parentKey = parentId;
@@ -14927,6 +15110,161 @@ function computeFractionalCidAtIndex(adapter, parentKey, targetIndex, excludeId)
14927
15110
  return (before + after) / 2;
14928
15111
  }
14929
15112
 
15113
+ // src/dispatch/shared/indent-planning.ts
15114
+ function planIndentMoves(activityIds, adapter) {
15115
+ const selectedSet = new Set(activityIds);
15116
+ const failed = [];
15117
+ const moves = [];
15118
+ const plannedIds = /* @__PURE__ */ new Set();
15119
+ const blockingSet = new Set(selectedSet);
15120
+ const sortedIds = [...activityIds].sort((a, b) => {
15121
+ const aSnap = adapter.getActivity(a);
15122
+ const bSnap = adapter.getActivity(b);
15123
+ const ac = aSnap ? readCorrelativeId(aSnap) : NOT_SET_SORT_VALUE2;
15124
+ const bc = bSnap ? readCorrelativeId(bSnap) : NOT_SET_SORT_VALUE2;
15125
+ return ac - bc;
15126
+ });
15127
+ for (const id of sortedIds) {
15128
+ const activity = adapter.getActivity(id);
15129
+ if (!activity) {
15130
+ failed.push({
15131
+ activityId: id,
15132
+ reason: REJECTION_REASON.ACTIVITY_NOT_FOUND
15133
+ });
15134
+ blockingSet.delete(id);
15135
+ continue;
15136
+ }
15137
+ if (hasAncestorInSet(id, plannedIds, adapter)) continue;
15138
+ const targetParentId = findPreviousNonSelectedSibling(
15139
+ id,
15140
+ blockingSet,
15141
+ adapter
15142
+ );
15143
+ if (targetParentId == null) {
15144
+ failed.push({
15145
+ activityId: id,
15146
+ reason: REJECTION_REASON.INDENT_NO_ELIGIBLE_SIBLING
15147
+ });
15148
+ blockingSet.delete(id);
15149
+ continue;
15150
+ }
15151
+ const oldParentRaw = adapter.getActivity(id)?.parentId;
15152
+ const oldParentId = isRootParent(oldParentRaw) ? ROOT_PARENT_ID : String(oldParentRaw);
15153
+ moves.push({ activityId: id, newParentId: targetParentId, oldParentId });
15154
+ plannedIds.add(id);
15155
+ }
15156
+ const cyclicMoveIds = collectCyclicReparents(moves, adapter);
15157
+ for (const cyclicId of cyclicMoveIds) {
15158
+ failed.push({
15159
+ activityId: cyclicId,
15160
+ reason: REJECTION_REASON.HIERARCHY_CYCLE
15161
+ });
15162
+ }
15163
+ const allowedMoves = moves.filter(
15164
+ (move) => !cyclicMoveIds.has(move.activityId)
15165
+ );
15166
+ return { moves, cyclicMoveIds, allowedMoves, failed };
15167
+ }
15168
+
15169
+ // src/dispatch/shared/lookahead-parent-confirmation.ts
15170
+ function lookaheadParentsConfirmation(leafParents, options) {
15171
+ const confirmed = new Set(options.confirmedLookaheadParentIds);
15172
+ const facts = options.lookaheadTasksByActivityId;
15173
+ const blocking = [
15174
+ ...new Set(
15175
+ leafParents.filter(
15176
+ (parent) => !confirmed.has(String(parent.id)) && (facts?.[String(parent.id)] ?? parent.hasLookaheadTasks)
15177
+ ).map((parent) => String(parent.id))
15178
+ )
15179
+ ];
15180
+ const [first] = blocking;
15181
+ if (first === void 0) return null;
15182
+ return {
15183
+ ok: false,
15184
+ reason: REJECTION_REASON.CONFIRMATION_REQUIRED,
15185
+ confirmation: {
15186
+ kind: LOOKAHEAD_CONFIRMATION_KIND.HAS_LOOKAHEAD_TASKS,
15187
+ activityId: first,
15188
+ ...blocking.length > 1 ? { activityIds: blocking } : {}
15189
+ }
15190
+ };
15191
+ }
15192
+ function leafParentsPromotedBy(parentIds, adapter) {
15193
+ const promoted = /* @__PURE__ */ new Map();
15194
+ for (const parentId of parentIds) {
15195
+ const parent = adapter.getActivity(parentId);
15196
+ if (!parent) continue;
15197
+ if (adapter.getChildren(parentId).length === 0)
15198
+ promoted.set(String(parent.id), parent);
15199
+ }
15200
+ return [...promoted.values()];
15201
+ }
15202
+ function leafParentIdsPromotedByActions(actions, adapter, hasActivityClipboard) {
15203
+ return leafParentsPromotedByActions(
15204
+ actions,
15205
+ adapter,
15206
+ hasActivityClipboard
15207
+ ).map((parent) => String(parent.id));
15208
+ }
15209
+ function lookaheadConfirmationForActions(actions, options, adapter, hasActivityClipboard) {
15210
+ const rejection = lookaheadParentsConfirmation(
15211
+ leafParentsPromotedByActions(actions, adapter, hasActivityClipboard),
15212
+ options
15213
+ );
15214
+ return rejection === null ? null : rejection.confirmation;
15215
+ }
15216
+ function leafParentsPromotedByActions(actions, adapter, hasActivityClipboard) {
15217
+ const parentIds = [];
15218
+ for (const action of actions) {
15219
+ parentIds.push(
15220
+ ...targetParentIds(action, adapter, hasActivityClipboard).map(String)
15221
+ );
15222
+ }
15223
+ return leafParentsPromotedBy(parentIds, adapter);
15224
+ }
15225
+ function targetParentIds(action, adapter, hasActivityClipboard) {
15226
+ switch (action.kind) {
15227
+ case DISPATCH_ACTION_KIND.ACTIVITY_CREATE:
15228
+ return [action.parentId];
15229
+ case DISPATCH_ACTION_KIND.ACTIVITY_MOVE: {
15230
+ const activity = adapter.getActivity(action.activityId);
15231
+ if (!activity) return [];
15232
+ if (normalizeParentKey(activity.parentId) === String(action.parentId)) {
15233
+ return [];
15234
+ }
15235
+ return movesIntoOwnBranch(action, adapter) ? [] : [action.parentId];
15236
+ }
15237
+ case DISPATCH_ACTION_KIND.ACTIVITY_INDENT:
15238
+ return planIndentMoves(action.activityIds, adapter).allowedMoves.map(
15239
+ (move) => move.newParentId
15240
+ );
15241
+ case DISPATCH_ACTION_KIND.ACTIVITY_PASTE_COPIED: {
15242
+ if (!hasActivityClipboard) return [];
15243
+ const destination = resolvePasteDestination(
15244
+ action.destination.afterSiblingId,
15245
+ adapter
15246
+ );
15247
+ return destination.ok ? [destination.parentId] : [];
15248
+ }
15249
+ case ACTIVITY_BATCH_ACTION_KIND:
15250
+ return action.mode === ACTIVITY_BATCH_MODE.APPEND ? action.items.flatMap(
15251
+ (item) => typeof item.parent === "string" ? [item.parent] : []
15252
+ ) : [];
15253
+ default:
15254
+ return [];
15255
+ }
15256
+ }
15257
+ function movesIntoOwnBranch(action, adapter) {
15258
+ if (isRootParent(action.parentId)) return false;
15259
+ if (action.parentId === action.activityId || adapter.isChildOf(action.parentId, action.activityId)) {
15260
+ return true;
15261
+ }
15262
+ return reparentClosesEffectiveCycle(
15263
+ adapter,
15264
+ /* @__PURE__ */ new Map([[action.activityId, action.parentId]])
15265
+ );
15266
+ }
15267
+
14930
15268
  // src/dispatch/creation-overrides.ts
14931
15269
  var PUBLIC_CREATION_OVERRIDE_FIELDS = /* @__PURE__ */ new Set([
14932
15270
  ...[...CREATION_OVERRIDE_FIELDS].filter(
@@ -15393,7 +15731,7 @@ function removeIncidentLinks(activityId, deps) {
15393
15731
  async function createActivityCore(action, deps, opts = {}) {
15394
15732
  const { adapter, scheduler, sector, calendars, activityIdGen, uidGen } = deps;
15395
15733
  if (!opts.skipCacheInvalidation) scheduler.invalidateAllCaches();
15396
- const target = validateCreateTarget(action, adapter);
15734
+ const target = validateCreateTarget(action, adapter, opts);
15397
15735
  if (!target.ok) return target;
15398
15736
  const { parent, anchorSiblingId, isRoot } = target;
15399
15737
  const newId = resolveNewActivityId(
@@ -15477,7 +15815,7 @@ async function createActivityCore(action, deps, opts = {}) {
15477
15815
  removedParentLinks
15478
15816
  };
15479
15817
  }
15480
- function validateCreateTarget(action, adapter) {
15818
+ function validateCreateTarget(action, adapter, opts) {
15481
15819
  const isRoot = isRootParent(action.parentId);
15482
15820
  const parent = isRoot ? null : adapter.getActivity(action.parentId);
15483
15821
  if (!isRoot && !parent) {
@@ -15499,6 +15837,13 @@ function validateCreateTarget(action, adapter) {
15499
15837
  anchorSiblingId
15500
15838
  );
15501
15839
  if (anchorError) return anchorError;
15840
+ if (parent !== null && !opts.parentsAlreadyChecked) {
15841
+ const rejection = lookaheadParentsConfirmation(
15842
+ leafParentsPromotedBy([action.parentId], adapter),
15843
+ opts
15844
+ );
15845
+ if (rejection) return rejection;
15846
+ }
15502
15847
  return { ok: true, parent, anchorSiblingId, isRoot };
15503
15848
  }
15504
15849
  function validateAnchorSibling(action, adapter, isRoot, anchorSiblingId) {
@@ -15597,7 +15942,11 @@ async function dispatchActivityCreate(action, options, deps) {
15597
15942
  if (!normalized2.ok) return normalized2;
15598
15943
  const core = await createActivityCore(
15599
15944
  { ...action, overrides: normalized2.value },
15600
- deps
15945
+ deps,
15946
+ {
15947
+ confirmedLookaheadParentIds: options.confirmedLookaheadParentIds,
15948
+ lookaheadTasksByActivityId: options.lookaheadTasksByActivityId
15949
+ }
15601
15950
  );
15602
15951
  if (!core.ok) return core;
15603
15952
  const { newId } = core;
@@ -15654,6 +16003,13 @@ async function dispatchActivityBatch(action, options, deps) {
15654
16003
  if (validationError) return { ok: false, reason: validationError };
15655
16004
  const plan = buildBatchPlan(action, deps);
15656
16005
  if (!plan.ok) return plan;
16006
+ if (action.mode === ACTIVITY_BATCH_MODE.APPEND) {
16007
+ const rejection = lookaheadParentsConfirmation(
16008
+ leafParentsPromotedBy(existingParentIds(plan.plan.items), deps.adapter),
16009
+ options
16010
+ );
16011
+ if (rejection) return rejection;
16012
+ }
15657
16013
  const existingActivityIds = action.mode === ACTIVITY_BATCH_MODE.REPLACE ? new Set(deps.adapter.getAllIds().map((id) => String(id))) : /* @__PURE__ */ new Set();
15658
16014
  const beforeSnap = action.mode === ACTIVITY_BATCH_MODE.REPLACE ? await snapshotActivities(deps.adapter, existingActivityIds) : /* @__PURE__ */ new Map();
15659
16015
  const beforeLinks = action.mode === ACTIVITY_BATCH_MODE.REPLACE ? snapshotAllLinks(deps) : /* @__PURE__ */ new Map();
@@ -15778,6 +16134,11 @@ async function dispatchActivityBatch(action, options, deps) {
15778
16134
  } : {}
15779
16135
  };
15780
16136
  }
16137
+ function existingParentIds(items) {
16138
+ return items.filter(
16139
+ (item) => item.parentBatchId === null && item.parentId !== ROOT_PARENT_ID
16140
+ ).map((item) => String(item.parentId));
16141
+ }
15781
16142
  function validateBatch(action, deps) {
15782
16143
  if (action.mode !== ACTIVITY_BATCH_MODE.APPEND && action.mode !== ACTIVITY_BATCH_MODE.REPLACE) {
15783
16144
  return REJECTION_REASON.INVALID;
@@ -16075,7 +16436,8 @@ async function createPlannedActivities(action, plannedItems, deps) {
16075
16436
  skipBeforeSnap: true,
16076
16437
  skipCacheInvalidation: true,
16077
16438
  skipRollupCascades: true,
16078
- insertionCorrelativeId: item.insertionCorrelativeId
16439
+ insertionCorrelativeId: item.insertionCorrelativeId,
16440
+ parentsAlreadyChecked: true
16079
16441
  }
16080
16442
  );
16081
16443
  if (!core.ok) return core;
@@ -16838,37 +17200,6 @@ function isAncestor(descendantId, candidateAncestorId, adapter) {
16838
17200
  return false;
16839
17201
  }
16840
17202
 
16841
- // src/dispatch/shared/hierarchy-cycle-guard.ts
16842
- function reparentClosesEffectiveCycle(adapter, reparented) {
16843
- const links = adapter.getAllLinks();
16844
- const verdict = validateTopologyCommit({
16845
- port: adapter,
16846
- currentLinks: links,
16847
- proposedLinks: links,
16848
- proposal: { reparented }
16849
- });
16850
- return !verdict.ok;
16851
- }
16852
- function collectCyclicReparents(planned, adapter) {
16853
- const cyclic = /* @__PURE__ */ new Set();
16854
- if (planned.length === 0) return cyclic;
16855
- const combined = new Map(
16856
- planned.map((reparent) => [reparent.activityId, reparent.newParentId])
16857
- );
16858
- if (!reparentClosesEffectiveCycle(adapter, combined)) return cyclic;
16859
- const accepted2 = /* @__PURE__ */ new Map();
16860
- for (const reparent of planned) {
16861
- const trial = new Map(accepted2);
16862
- trial.set(reparent.activityId, reparent.newParentId);
16863
- if (reparentClosesEffectiveCycle(adapter, trial)) {
16864
- cyclic.add(reparent.activityId);
16865
- continue;
16866
- }
16867
- accepted2.set(reparent.activityId, reparent.newParentId);
16868
- }
16869
- return cyclic;
16870
- }
16871
-
16872
17203
  // src/dispatch/handlers/activity-move.ts
16873
17204
  async function dispatchActivityMove(action, options, deps) {
16874
17205
  const { adapter, scheduler, sector } = deps;
@@ -16921,6 +17252,13 @@ async function dispatchActivityMove(action, options, deps) {
16921
17252
  return { ok: false, reason: REJECTION_REASON.HIERARCHY_CYCLE };
16922
17253
  }
16923
17254
  }
17255
+ if (parentChanged && !newParentIsRoot) {
17256
+ const rejection = lookaheadParentsConfirmation(
17257
+ leafParentsPromotedBy([action.parentId], adapter),
17258
+ options
17259
+ );
17260
+ if (rejection) return rejection;
17261
+ }
16924
17262
  const touched = /* @__PURE__ */ new Set([action.activityId]);
16925
17263
  if (parentChanged) {
16926
17264
  if (oldParentKey !== ROOT_PARENT_ID) touched.add(oldParentKey);
@@ -17080,62 +17418,6 @@ async function dispatchActivityMove(action, options, deps) {
17080
17418
  };
17081
17419
  }
17082
17420
 
17083
- // src/dispatch/shared/indent-planning.ts
17084
- function planIndentMoves(activityIds, adapter) {
17085
- const selectedSet = new Set(activityIds);
17086
- const failed = [];
17087
- const moves = [];
17088
- const plannedIds = /* @__PURE__ */ new Set();
17089
- const blockingSet = new Set(selectedSet);
17090
- const sortedIds = [...activityIds].sort((a, b) => {
17091
- const aSnap = adapter.getActivity(a);
17092
- const bSnap = adapter.getActivity(b);
17093
- const ac = aSnap ? readCorrelativeId(aSnap) : NOT_SET_SORT_VALUE2;
17094
- const bc = bSnap ? readCorrelativeId(bSnap) : NOT_SET_SORT_VALUE2;
17095
- return ac - bc;
17096
- });
17097
- for (const id of sortedIds) {
17098
- const activity = adapter.getActivity(id);
17099
- if (!activity) {
17100
- failed.push({
17101
- activityId: id,
17102
- reason: REJECTION_REASON.ACTIVITY_NOT_FOUND
17103
- });
17104
- blockingSet.delete(id);
17105
- continue;
17106
- }
17107
- if (hasAncestorInSet(id, plannedIds, adapter)) continue;
17108
- const targetParentId = findPreviousNonSelectedSibling(
17109
- id,
17110
- blockingSet,
17111
- adapter
17112
- );
17113
- if (targetParentId == null) {
17114
- failed.push({
17115
- activityId: id,
17116
- reason: REJECTION_REASON.INDENT_NO_ELIGIBLE_SIBLING
17117
- });
17118
- blockingSet.delete(id);
17119
- continue;
17120
- }
17121
- const oldParentRaw = adapter.getActivity(id)?.parentId;
17122
- const oldParentId = isRootParent(oldParentRaw) ? ROOT_PARENT_ID : String(oldParentRaw);
17123
- moves.push({ activityId: id, newParentId: targetParentId, oldParentId });
17124
- plannedIds.add(id);
17125
- }
17126
- const cyclicMoveIds = collectCyclicReparents(moves, adapter);
17127
- for (const cyclicId of cyclicMoveIds) {
17128
- failed.push({
17129
- activityId: cyclicId,
17130
- reason: REJECTION_REASON.HIERARCHY_CYCLE
17131
- });
17132
- }
17133
- const allowedMoves = moves.filter(
17134
- (move) => !cyclicMoveIds.has(move.activityId)
17135
- );
17136
- return { moves, cyclicMoveIds, allowedMoves, failed };
17137
- }
17138
-
17139
17421
  // src/dispatch/handlers/activity-indent.ts
17140
17422
  async function dispatchActivityIndent(action, options, deps) {
17141
17423
  const { adapter, scheduler, sector } = deps;
@@ -17157,6 +17439,14 @@ async function dispatchActivityIndent(action, options, deps) {
17157
17439
  }
17158
17440
  }
17159
17441
  const allowedMoves = plan.allowedMoves;
17442
+ const rejection = lookaheadParentsConfirmation(
17443
+ leafParentsPromotedBy(
17444
+ allowedMoves.map((move) => move.newParentId),
17445
+ adapter
17446
+ ),
17447
+ options
17448
+ );
17449
+ if (rejection) return rejection;
17160
17450
  const beforeSnap = await snapshotActivities(adapter, touched);
17161
17451
  for (const { activityId, newParentId } of allowedMoves) {
17162
17452
  adapter.setActivityField(activityId, ACTIVITY_PROPERTY.PARENT, newParentId);
@@ -18954,146 +19244,6 @@ function dispatchActivityClipboardImport(action, deps) {
18954
19244
  return clipboardOnlyResult(action);
18955
19245
  }
18956
19246
 
18957
- // src/activity-copy-paste/paste.ts
18958
- function resolveDestination(afterSiblingId, adapter) {
18959
- const sibling = adapter.getActivity(afterSiblingId);
18960
- if (!sibling) {
18961
- return { ok: false, reason: REJECTION_REASON.ANCHOR_SIBLING_NOT_FOUND };
18962
- }
18963
- if (sibling.parentId == null) {
18964
- return adapter.getRootIds().length === 1 ? {
18965
- ok: true,
18966
- parentId: sibling.id,
18967
- beforeSiblingId: adapter.getChildren(sibling.id)[0]
18968
- } : { ok: true, parentId: ROOT_PARENT_ID, afterSiblingId };
18969
- }
18970
- if (adapter.hasChildren(sibling.id)) {
18971
- return {
18972
- ok: true,
18973
- parentId: sibling.id,
18974
- beforeSiblingId: adapter.getChildren(sibling.id)[0]
18975
- };
18976
- }
18977
- return {
18978
- ok: true,
18979
- parentId: String(sibling.parentId),
18980
- afterSiblingId
18981
- };
18982
- }
18983
- var invariantFailure = () => ({
18984
- ok: false,
18985
- reason: REJECTION_REASON.ACTIVITY_PASTE_INVARIANT_FAILED
18986
- });
18987
- async function dispatchActivityPasteCopied(action, options, deps) {
18988
- const clipboard = deps.activityClipboard.value;
18989
- if (clipboard === null) {
18990
- return { ok: false, reason: REJECTION_REASON.CLIPBOARD_EMPTY };
18991
- }
18992
- const destination = resolveDestination(
18993
- action.destination.afterSiblingId,
18994
- deps.adapter
18995
- );
18996
- if (!destination.ok) return destination;
18997
- const beforeSnapshot = await snapshotActivities(
18998
- deps.adapter,
18999
- /* @__PURE__ */ new Set()
19000
- );
19001
- const touched = /* @__PURE__ */ new Set();
19002
- const originalToNew = /* @__PURE__ */ new Map();
19003
- const createdIds = [];
19004
- let previousRootId;
19005
- for (const copied of clipboard.activities) {
19006
- const mappedParent = originalToNew.get(copied.originalParentId);
19007
- const afterSiblingId = mappedParent === void 0 ? previousRootId ?? destination.afterSiblingId : void 0;
19008
- const beforeSiblingId = mappedParent === void 0 && afterSiblingId === void 0 ? destination.beforeSiblingId : void 0;
19009
- const created = await createActivityCore(
19010
- {
19011
- kind: DISPATCH_ACTION_KIND.ACTIVITY_CREATE,
19012
- parentId: mappedParent ?? destination.parentId,
19013
- ...afterSiblingId === void 0 ? {} : { afterSiblingId },
19014
- ...beforeSiblingId === void 0 ? {} : { beforeSiblingId },
19015
- overrides: cloneDomainValue(copied.overrides),
19016
- eventSource: action.eventSource
19017
- },
19018
- deps,
19019
- {
19020
- skipCorrelativeRecompute: true,
19021
- skipBeforeSnap: true,
19022
- customIdReferenceId: action.destination.afterSiblingId
19023
- }
19024
- );
19025
- if (!created.ok) return created;
19026
- originalToNew.set(copied.originalId, created.newId);
19027
- createdIds.push(created.newId);
19028
- if (mappedParent === void 0) previousRootId = created.newId;
19029
- }
19030
- const correlativeShifts = recomputeCorrelativeIds(deps.adapter);
19031
- const createdIdSet = new Set(createdIds.map(String));
19032
- foldCorrelativeShifts(
19033
- deps.adapter,
19034
- correlativeShifts,
19035
- createdIdSet,
19036
- beforeSnapshot,
19037
- touched
19038
- );
19039
- for (const createdId of createdIds) touched.add(String(createdId));
19040
- const appliedLinks = [];
19041
- for (const copiedLink of clipboard.links) {
19042
- const source = originalToNew.get(copiedLink.source);
19043
- const target = originalToNew.get(copiedLink.target);
19044
- if (source === void 0 || target === void 0) return invariantFailure();
19045
- const operation = {
19046
- kind: LINK_OPERATION_KIND.CREATE,
19047
- source,
19048
- target,
19049
- type: copiedLink.type,
19050
- lag: copiedLink.lag
19051
- };
19052
- const result = applyLinkOperation(operation, {
19053
- port: deps.adapter,
19054
- newLinkId: deps.linkIdGen.next
19055
- });
19056
- if (!result.applied) return invariantFailure();
19057
- appliedLinks.push({
19058
- op: operation,
19059
- finalLinkId: result.linkId,
19060
- rejected: null
19061
- });
19062
- }
19063
- const { scheduledIds } = await runPostMutation(
19064
- {
19065
- adapter: deps.adapter,
19066
- scheduler: deps.scheduler,
19067
- sector: deps.sector,
19068
- defaultBaseCalendarId: deps.defaultBaseCalendarId,
19069
- runAutomaticP6Schedule: deps.runAutomaticP6Schedule
19070
- },
19071
- {
19072
- action,
19073
- autoscheduleFrom: createdIds[0] ?? null,
19074
- recomputeParentsFrom: createdIds,
19075
- now: deps.now,
19076
- options
19077
- }
19078
- );
19079
- const linkChanges = buildLinkChangesForBatch(
19080
- appliedLinks,
19081
- /* @__PURE__ */ new Map(),
19082
- deps.adapter
19083
- );
19084
- return {
19085
- ok: true,
19086
- changes: await assembleChangeSet(deps.adapter, {
19087
- source: action,
19088
- beforeSnap: beforeSnapshot,
19089
- touchedIds: touched,
19090
- scheduledIds,
19091
- links: linkChanges,
19092
- hoursPerDay: deps.sector.hoursPerDay
19093
- })
19094
- };
19095
- }
19096
-
19097
19247
  // src/activity-copy-paste/duplicate.ts
19098
19248
  var CLONE_YIELD_EVERY_N = 200;
19099
19249
  var CREATE_OPTIONS = {
@@ -32110,6 +32260,41 @@ var ScheduleCore = class _ScheduleCore {
32110
32260
  ...new Set(plan.allowedMoves.map((move) => String(move.newParentId)))
32111
32261
  ];
32112
32262
  }
32263
+ /**
32264
+ * Lists the leaf activities the actions would turn into parents.
32265
+ *
32266
+ * Each action is checked against the current state, not against the state
32267
+ * left by the actions before it.
32268
+ *
32269
+ * @param actions - Actions the caller is about to dispatch.
32270
+ */
32271
+ getLeafParentIdsPromotedBy(actions) {
32272
+ this.assertReady();
32273
+ return leafParentIdsPromotedByActions(
32274
+ actions,
32275
+ this.coreRuntime.state,
32276
+ this._activityClipboard.value !== null
32277
+ );
32278
+ }
32279
+ /**
32280
+ * Returns the lookahead confirmation the dispatch rule would raise for the
32281
+ * actions, or `null` when none is needed. The rule code is the dispatch rule
32282
+ * and nothing changes in the schedule. Like
32283
+ * {@link ScheduleCore.getLeafParentIdsPromotedBy}, it checks each action
32284
+ * against the current state.
32285
+ *
32286
+ * @param actions - Actions the caller is about to dispatch.
32287
+ * @param options - The `confirmedLookaheadParentIds` and `lookaheadTasksByActivityId` the dispatch would receive.
32288
+ */
32289
+ getLookaheadConfirmation(actions, options = {}) {
32290
+ this.assertReady();
32291
+ return lookaheadConfirmationForActions(
32292
+ actions,
32293
+ options,
32294
+ this.coreRuntime.state,
32295
+ this._activityClipboard.value !== null
32296
+ );
32297
+ }
32113
32298
  /**
32114
32299
  * Reads defensive direct-child snapshots in correlative order.
32115
32300
  *
@@ -34155,8 +34340,8 @@ function resolveExpectedFinishDateSelection(input) {
34155
34340
  });
34156
34341
  }
34157
34342
 
34158
- // src/p6/lookahead-derived.ts
34159
- function createLookaheadDerivedFields(input) {
34343
+ // src/calendar/schedule-calendars.ts
34344
+ function createScheduleCalendars(input) {
34160
34345
  const normalized2 = normalizeCalendars(
34161
34346
  [...input.calendars],
34162
34347
  void 0,
@@ -34165,11 +34350,22 @@ function createLookaheadDerivedFields(input) {
34165
34350
  const { reader } = createCalendarSetup(
34166
34351
  serializeCalendars(normalized2.calendars, normalized2.baseCalendars)
34167
34352
  );
34353
+ return {
34354
+ defaultCalendarId: normalized2.defaultCalendarId,
34355
+ resolveCalendar(calendarId) {
34356
+ const knownCalendarId = calendarId !== null && normalized2.knownCalendarIds.has(calendarId) ? calendarId : normalized2.defaultCalendarId;
34357
+ return reader.getCalendar(knownCalendarId);
34358
+ }
34359
+ };
34360
+ }
34361
+
34362
+ // src/p6/lookahead-derived.ts
34363
+ function createLookaheadDerivedFields(input) {
34364
+ const { resolveCalendar } = createScheduleCalendars(input);
34168
34365
  return {
34169
34366
  derive({ previous, next, calendarId, dataDate, totalFloatMethod }) {
34170
34367
  if (!isP6ActivityState(next)) return null;
34171
- const knownCalendarId = calendarId !== null && normalized2.knownCalendarIds.has(calendarId) ? calendarId : normalized2.defaultCalendarId;
34172
- const calendar = reader.getCalendar(knownCalendarId);
34368
+ const calendar = resolveCalendar(calendarId);
34173
34369
  if (!calendar) return null;
34174
34370
  const { p6 } = projectDataDateLocalActivity(previous, next, {
34175
34371
  calendar,
@@ -34187,6 +34383,23 @@ function createLookaheadDerivedFields(input) {
34187
34383
  };
34188
34384
  }
34189
34385
 
34386
+ // src/calendar/reader.ts
34387
+ function createCalendarReader(input) {
34388
+ const { defaultCalendarId, resolveCalendar } = createScheduleCalendars(input);
34389
+ if (defaultCalendarId === null) {
34390
+ throw new Error("Calendar reader requires at least one calendar");
34391
+ }
34392
+ return {
34393
+ getCalendar(calendarId) {
34394
+ const calendar = resolveCalendar(calendarId);
34395
+ if (!calendar) {
34396
+ throw new Error("Resolved calendar missing after normalization");
34397
+ }
34398
+ return calendar;
34399
+ }
34400
+ };
34401
+ }
34402
+
34190
34403
  // src/p6/property-contract.ts
34191
34404
  var row = (input) => {
34192
34405
  const lifecycle = input.lifecycle ?? (input.classification === "f9_output" ? "f9" : "explicit_edit");
@@ -34826,11 +35039,10 @@ var DEFAULT_HOURS_PER_DAY = 8;
34826
35039
  // src/creation/creation-kinds.ts
34827
35040
  var CREATION_KIND = {
34828
35041
  CHILD: "child",
34829
- CHILD_CONFIRMED: "child-confirmed",
34830
35042
  LINE: "line",
34831
35043
  ROOT: "root"
34832
35044
  };
34833
35045
 
34834
- export { ACTIVITY_BATCH_ACTION_KIND, ACTIVITY_BATCH_MODE, ACTIVITY_DATE_EDGE, ACTIVITY_FIELD, ACTIVITY_PONDERATOR_CRITERION, ACTIVITY_PROGRESS_ORIGIN, ACTIVITY_TYPE, CALENDAR_UNIT, CHANGE_SET_SOURCE_KIND, COLUMN, COMPLETED_PROGRESS, CONFIRMATION_KIND, CONSTRAINT_TYPE, CONSTRAINT_TYPE_TO_LABEL, CONSTRAINT_WARNING_KIND, CREATION_KIND, P6_PROPERTY_CONTRACT as DATA_DATE_PROPERTY_CONTRACT, DATE_GESTURE_KIND, DEFAULT_HOURS_PER_DAY, DISPATCH_ACTION_KIND, DISPATCH_ACTION_KINDS, DISPATCH_LINK_OPERATION_KIND, DISPATCH_TRACK_EVENT, P6CapabilityError as DataDateCapabilityError, EDIT_ALERT_KEY, EDIT_DENIAL_REASON, ENTITY_CHANGE_KIND, FILTER_FIELD, FILTER_LOGIC, FILTER_OPERATOR, FILTER_REJECTION_REASON, FILTER_VALUE_KIND, FLAT_WBS_DEPTH, GROUP_ID_PREFIX, GROUP_ID_SEPARATOR, LINK_BATCH_OPERATION_KIND, LINK_CODE, LINK_CODE_TO_TYPE, LINK_REJECTION_REASON, LINK_RELATIONSHIP, LINK_TYPE, LINK_TYPE_CODE, LookaheadActualTransitionError, MINIMUM_GROUP_DEPTH, MINIMUM_GROUP_LEVEL, NEW_ACTIVITY_DEFAULTS, NOT_STARTED_PROGRESS, ORDER_DIRECTION, ORDER_MODE, REJECTION_REASON, ROOT_PARENT_ID, SCHEDULE_CORE_STATUS, SCHEDULE_EFFECT_KIND, SIR_AUTO_REJECT_REASON, STATUS, STATUS_CRITERIA, ScheduleCore, UNASSIGNED_GROUP_LABEL, UNASSIGNED_VALUE_ID, VIEW_ATTRIBUTE_KIND, VISUAL_ROW_KIND, WORK_TIME_DIRECTION, allowedSecondaryConstraintTypes, appendGroupRowId, buildGroupRowId, checkNoUpdatedLinks, computeExpectedProgress, createLookaheadDerivedFields, createOutbuildP6ScheduleSettings as createOutbuildDataDateScheduleSettings, createSchedule, expectedProgressFromBaseline, formatUtcAsP6WallDate, getActiveBaseline, getUnsavedActivities, isRootParent, isViewAttributeCriterion, isViewAttributeOrderRule, mapP6ScheduleOptions as mapDataDateScheduleOptions, normalizeParentKey, parseFromBackend, parseP6DayAsCalendarStart, parseP6WallDateAsUtc, planLookaheadProgressActuals, prepareLookaheadProgressActuals, resolveExpectedFinishDateSelection, resolveLookaheadActualDateSelection, willRunCriticalPath, yieldToBrowser };
35046
+ export { ACTIVITY_BATCH_ACTION_KIND, ACTIVITY_BATCH_MODE, ACTIVITY_DATE_EDGE, ACTIVITY_FIELD, ACTIVITY_PONDERATOR_CRITERION, ACTIVITY_PROGRESS_ORIGIN, ACTIVITY_TYPE, CALENDAR_UNIT, CHANGE_SET_SOURCE_KIND, COLUMN, COMPLETED_PROGRESS, CONFIRMATION_KIND, CONSTRAINT_TYPE, CONSTRAINT_TYPE_TO_LABEL, CONSTRAINT_WARNING_KIND, CREATION_KIND, P6_PROPERTY_CONTRACT as DATA_DATE_PROPERTY_CONTRACT, DATE_GESTURE_KIND, DEFAULT_HOURS_PER_DAY, DISPATCH_ACTION_KIND, DISPATCH_ACTION_KINDS, DISPATCH_LINK_OPERATION_KIND, DISPATCH_TRACK_EVENT, P6CapabilityError as DataDateCapabilityError, EDIT_ALERT_KEY, EDIT_DENIAL_REASON, ENTITY_CHANGE_KIND, FILTER_FIELD, FILTER_LOGIC, FILTER_OPERATOR, FILTER_REJECTION_REASON, FILTER_VALUE_KIND, FLAT_WBS_DEPTH, GROUP_ID_PREFIX, GROUP_ID_SEPARATOR, LINK_BATCH_OPERATION_KIND, LINK_CODE, LINK_CODE_TO_TYPE, LINK_REJECTION_REASON, LINK_RELATIONSHIP, LINK_TYPE, LINK_TYPE_CODE, LOOKAHEAD_CONFIRMATION_KIND, LookaheadActualTransitionError, MINIMUM_GROUP_DEPTH, MINIMUM_GROUP_LEVEL, NEW_ACTIVITY_DEFAULTS, NOT_STARTED_PROGRESS, ORDER_DIRECTION, ORDER_MODE, REJECTION_REASON, ROOT_PARENT_ID, SCHEDULE_CORE_STATUS, SCHEDULE_EFFECT_KIND, SIR_AUTO_REJECT_REASON, STATUS, STATUS_CRITERIA, ScheduleCore, UNASSIGNED_GROUP_LABEL, UNASSIGNED_VALUE_ID, VIEW_ATTRIBUTE_KIND, VISUAL_ROW_KIND, WORK_TIME_DIRECTION, allowedSecondaryConstraintTypes, appendGroupRowId, buildGroupRowId, checkNoUpdatedLinks, computeExpectedProgress, createCalendarReader, createLookaheadDerivedFields, createOutbuildP6ScheduleSettings as createOutbuildDataDateScheduleSettings, createSchedule, expectedProgressFromBaseline, formatUtcAsP6WallDate, getActiveBaseline, getUnsavedActivities, isRootParent, isViewAttributeCriterion, isViewAttributeOrderRule, mapP6ScheduleOptions as mapDataDateScheduleOptions, normalizeParentKey, parseFromBackend, parseP6DayAsCalendarStart, parseP6WallDateAsUtc, planLookaheadProgressActuals, prepareLookaheadProgressActuals, resolveExpectedFinishDateSelection, resolveLookaheadActualDateSelection, willRunCriticalPath, yieldToBrowser };
34835
35047
  //# sourceMappingURL=index.js.map
34836
35048
  //# sourceMappingURL=index.js.map