@outbuild-company/schedule-core 1.24.0 → 1.26.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.d.cts CHANGED
@@ -724,6 +724,12 @@ interface ScheduleAutoScheduleAction {
724
724
  *
725
725
  * Link `lag` inputs are working days and are converted to stored working hours.
726
726
  * Consumer analytics context is carried by optional `eventSource` fields.
727
+ *
728
+ * `CELL_COPY`'s `copiedText` and `CELL_PASTE_COPIED`'s `clipboardText` carry the
729
+ * host's OS-clipboard text alongside the structured value. When `clipboardText`
730
+ * no longer matches the `copiedText` stashed at copy time (or nothing was ever
731
+ * copied through the core), the paste falls back to that raw text through the
732
+ * target column's own `parseInput` instead of the stashed structured value.
727
733
  */
728
734
  type DispatchAction = {
729
735
  kind: typeof DISPATCH_ACTION_KIND.ACTIVITY_COPY;
@@ -752,6 +758,7 @@ type DispatchAction = {
752
758
  kind: typeof DISPATCH_ACTION_KIND.CELL_COPY;
753
759
  activityId: ActivityId;
754
760
  column: EditableColumnName;
761
+ copiedText?: string;
755
762
  eventSource?: string;
756
763
  } | {
757
764
  kind: typeof DISPATCH_ACTION_KIND.CELL_CLIPBOARD_CLEAR;
@@ -760,6 +767,7 @@ type DispatchAction = {
760
767
  kind: typeof DISPATCH_ACTION_KIND.CELL_PASTE_COPIED;
761
768
  activityIds: ReadonlyArray<ActivityId>;
762
769
  column: EditableColumnName;
770
+ clipboardText?: string;
763
771
  eventSource?: string;
764
772
  } | {
765
773
  kind: typeof DISPATCH_ACTION_KIND.PERSISTENCE_ACKNOWLEDGE;
@@ -2154,25 +2162,35 @@ interface ScheduleCellsApi {
2154
2162
  * Copies one activity's cell value to core-owned clipboard state.
2155
2163
  *
2156
2164
  * Predecessor and successor columns copy the underlying dependency links;
2157
- * every other column copies its canonical field value.
2165
+ * every other column copies its canonical field value alongside `copiedText`,
2166
+ * the host's OS-clipboard text at copy time.
2158
2167
  * @param activityId - Source activity.
2159
2168
  * @param column - Source column.
2169
+ * @param copiedText - Host OS-clipboard text to stash alongside the value.
2160
2170
  * @param context - Optional consumer event source.
2161
2171
  * @returns The complete dispatch result.
2162
2172
  */
2163
- copy(activityId: ActivityId, column: EditableColumnName, context?: MutationContext): Promise<DispatchResult>;
2173
+ copy(activityId: ActivityId, column: EditableColumnName, copiedText?: string, context?: MutationContext): Promise<DispatchResult>;
2164
2174
  /**
2165
2175
  * Pastes the copied cell value into one or more activities' column.
2166
2176
  *
2167
2177
  * Predecessor and successor columns paste cross-compatibly with each
2168
- * other; every other column is rejected unless its own pipeline accepts
2169
- * the copied value.
2178
+ * other and ignore `clipboardText`. For every other column: `tags`,
2179
+ * `responsables`, `subcontract_id`, `constraint_type` and `calendar_id`
2180
+ * only accept a paste whose source was the exact same column, rejected
2181
+ * with `CELL_PASTE_COLUMN_MISMATCH` otherwise; every other column pair is
2182
+ * left to the target column's own pipeline. When `clipboardText` no
2183
+ * longer matches the text stashed at copy time (or nothing was ever
2184
+ * copied through the core), the stashed value is dropped and
2185
+ * `clipboardText` itself is pasted through the target column's own
2186
+ * `parseInput` instead.
2170
2187
  * @param activityIds - Destination activities.
2171
2188
  * @param column - Destination column.
2189
+ * @param clipboardText - Host OS-clipboard text at paste time.
2172
2190
  * @param context - Optional consumer event source.
2173
2191
  * @returns The complete dispatch result.
2174
2192
  */
2175
- paste(activityIds: ReadonlyArray<ActivityId>, column: EditableColumnName, context?: MutationContext): Promise<DispatchResult>;
2193
+ paste(activityIds: ReadonlyArray<ActivityId>, column: EditableColumnName, clipboardText?: string, context?: MutationContext): Promise<DispatchResult>;
2176
2194
  /** @returns The complete result of clearing core-owned cell clipboard state. */
2177
2195
  clearClipboard(context?: MutationContext): Promise<DispatchResult>;
2178
2196
  }
@@ -2669,6 +2687,7 @@ type LinkRejection = (typeof LINK_REJECTION_REASON)[keyof typeof LINK_REJECTION_
2669
2687
  declare const LOAD_DIAGNOSTIC_CODE: {
2670
2688
  readonly INVALID_BASELINE_POINT: "invalid_baseline_point";
2671
2689
  readonly UNKNOWN_CONSTRAINT: "unknown_constraint";
2690
+ readonly PROGRESS_OUT_OF_RANGE: "progress_out_of_range";
2672
2691
  readonly ORPHAN_ACTIVITY: "orphan_activity";
2673
2692
  readonly INVALID_LINK: "invalid_link";
2674
2693
  readonly DUPLICATE_LINK: "duplicate_link";
@@ -3668,6 +3687,7 @@ declare const REJECTION_REASON: {
3668
3687
  readonly NO_ACTIVITY_SELECTION: "no_activity_selection";
3669
3688
  readonly CLIPBOARD_EMPTY: "clipboard_empty";
3670
3689
  readonly CLIPBOARD_SNAPSHOT_INVALID: "clipboard_snapshot_invalid";
3690
+ readonly CELL_PASTE_COLUMN_MISMATCH: "cell_paste_column_mismatch";
3671
3691
  readonly ACTIVITY_PASTE_INVARIANT_FAILED: "activity_paste_invariant_failed";
3672
3692
  readonly SOLE_SCHEDULE_ROOT: "sole_schedule_root";
3673
3693
  readonly ANCHOR_SIBLING_CONFLICT: "anchor_sibling_conflict";
package/dist/index.d.ts CHANGED
@@ -724,6 +724,12 @@ interface ScheduleAutoScheduleAction {
724
724
  *
725
725
  * Link `lag` inputs are working days and are converted to stored working hours.
726
726
  * Consumer analytics context is carried by optional `eventSource` fields.
727
+ *
728
+ * `CELL_COPY`'s `copiedText` and `CELL_PASTE_COPIED`'s `clipboardText` carry the
729
+ * host's OS-clipboard text alongside the structured value. When `clipboardText`
730
+ * no longer matches the `copiedText` stashed at copy time (or nothing was ever
731
+ * copied through the core), the paste falls back to that raw text through the
732
+ * target column's own `parseInput` instead of the stashed structured value.
727
733
  */
728
734
  type DispatchAction = {
729
735
  kind: typeof DISPATCH_ACTION_KIND.ACTIVITY_COPY;
@@ -752,6 +758,7 @@ type DispatchAction = {
752
758
  kind: typeof DISPATCH_ACTION_KIND.CELL_COPY;
753
759
  activityId: ActivityId;
754
760
  column: EditableColumnName;
761
+ copiedText?: string;
755
762
  eventSource?: string;
756
763
  } | {
757
764
  kind: typeof DISPATCH_ACTION_KIND.CELL_CLIPBOARD_CLEAR;
@@ -760,6 +767,7 @@ type DispatchAction = {
760
767
  kind: typeof DISPATCH_ACTION_KIND.CELL_PASTE_COPIED;
761
768
  activityIds: ReadonlyArray<ActivityId>;
762
769
  column: EditableColumnName;
770
+ clipboardText?: string;
763
771
  eventSource?: string;
764
772
  } | {
765
773
  kind: typeof DISPATCH_ACTION_KIND.PERSISTENCE_ACKNOWLEDGE;
@@ -2154,25 +2162,35 @@ interface ScheduleCellsApi {
2154
2162
  * Copies one activity's cell value to core-owned clipboard state.
2155
2163
  *
2156
2164
  * Predecessor and successor columns copy the underlying dependency links;
2157
- * every other column copies its canonical field value.
2165
+ * every other column copies its canonical field value alongside `copiedText`,
2166
+ * the host's OS-clipboard text at copy time.
2158
2167
  * @param activityId - Source activity.
2159
2168
  * @param column - Source column.
2169
+ * @param copiedText - Host OS-clipboard text to stash alongside the value.
2160
2170
  * @param context - Optional consumer event source.
2161
2171
  * @returns The complete dispatch result.
2162
2172
  */
2163
- copy(activityId: ActivityId, column: EditableColumnName, context?: MutationContext): Promise<DispatchResult>;
2173
+ copy(activityId: ActivityId, column: EditableColumnName, copiedText?: string, context?: MutationContext): Promise<DispatchResult>;
2164
2174
  /**
2165
2175
  * Pastes the copied cell value into one or more activities' column.
2166
2176
  *
2167
2177
  * Predecessor and successor columns paste cross-compatibly with each
2168
- * other; every other column is rejected unless its own pipeline accepts
2169
- * the copied value.
2178
+ * other and ignore `clipboardText`. For every other column: `tags`,
2179
+ * `responsables`, `subcontract_id`, `constraint_type` and `calendar_id`
2180
+ * only accept a paste whose source was the exact same column, rejected
2181
+ * with `CELL_PASTE_COLUMN_MISMATCH` otherwise; every other column pair is
2182
+ * left to the target column's own pipeline. When `clipboardText` no
2183
+ * longer matches the text stashed at copy time (or nothing was ever
2184
+ * copied through the core), the stashed value is dropped and
2185
+ * `clipboardText` itself is pasted through the target column's own
2186
+ * `parseInput` instead.
2170
2187
  * @param activityIds - Destination activities.
2171
2188
  * @param column - Destination column.
2189
+ * @param clipboardText - Host OS-clipboard text at paste time.
2172
2190
  * @param context - Optional consumer event source.
2173
2191
  * @returns The complete dispatch result.
2174
2192
  */
2175
- paste(activityIds: ReadonlyArray<ActivityId>, column: EditableColumnName, context?: MutationContext): Promise<DispatchResult>;
2193
+ paste(activityIds: ReadonlyArray<ActivityId>, column: EditableColumnName, clipboardText?: string, context?: MutationContext): Promise<DispatchResult>;
2176
2194
  /** @returns The complete result of clearing core-owned cell clipboard state. */
2177
2195
  clearClipboard(context?: MutationContext): Promise<DispatchResult>;
2178
2196
  }
@@ -2669,6 +2687,7 @@ type LinkRejection = (typeof LINK_REJECTION_REASON)[keyof typeof LINK_REJECTION_
2669
2687
  declare const LOAD_DIAGNOSTIC_CODE: {
2670
2688
  readonly INVALID_BASELINE_POINT: "invalid_baseline_point";
2671
2689
  readonly UNKNOWN_CONSTRAINT: "unknown_constraint";
2690
+ readonly PROGRESS_OUT_OF_RANGE: "progress_out_of_range";
2672
2691
  readonly ORPHAN_ACTIVITY: "orphan_activity";
2673
2692
  readonly INVALID_LINK: "invalid_link";
2674
2693
  readonly DUPLICATE_LINK: "duplicate_link";
@@ -3668,6 +3687,7 @@ declare const REJECTION_REASON: {
3668
3687
  readonly NO_ACTIVITY_SELECTION: "no_activity_selection";
3669
3688
  readonly CLIPBOARD_EMPTY: "clipboard_empty";
3670
3689
  readonly CLIPBOARD_SNAPSHOT_INVALID: "clipboard_snapshot_invalid";
3690
+ readonly CELL_PASTE_COLUMN_MISMATCH: "cell_paste_column_mismatch";
3671
3691
  readonly ACTIVITY_PASTE_INVARIANT_FAILED: "activity_paste_invariant_failed";
3672
3692
  readonly SOLE_SCHEDULE_ROOT: "sole_schedule_root";
3673
3693
  readonly ANCHOR_SIBLING_CONFLICT: "anchor_sibling_conflict";
package/dist/index.js CHANGED
@@ -628,17 +628,19 @@ function createActivitiesApi(engine) {
628
628
  }
629
629
  function createCellsApi(engine) {
630
630
  return {
631
- copy: (activityId, column, context = {}) => engine.dispatch({
631
+ copy: (activityId, column, copiedText, context = {}) => engine.dispatch({
632
632
  ...context,
633
633
  kind: DISPATCH_ACTION_KIND.CELL_COPY,
634
634
  activityId,
635
- column
635
+ column,
636
+ ...copiedText === void 0 ? {} : { copiedText }
636
637
  }),
637
- paste: (activityIds, column, context = {}) => engine.dispatch({
638
+ paste: (activityIds, column, clipboardText, context = {}) => engine.dispatch({
638
639
  ...context,
639
640
  kind: DISPATCH_ACTION_KIND.CELL_PASTE_COPIED,
640
641
  activityIds,
641
- column
642
+ column,
643
+ ...clipboardText === void 0 ? {} : { clipboardText }
642
644
  }),
643
645
  clearClipboard: (context = {}) => engine.dispatch({
644
646
  ...context,
@@ -955,6 +957,7 @@ var REJECTION_REASON = {
955
957
  NO_ACTIVITY_SELECTION: "no_activity_selection",
956
958
  CLIPBOARD_EMPTY: "clipboard_empty",
957
959
  CLIPBOARD_SNAPSHOT_INVALID: "clipboard_snapshot_invalid",
960
+ CELL_PASTE_COLUMN_MISMATCH: "cell_paste_column_mismatch",
958
961
  ACTIVITY_PASTE_INVARIANT_FAILED: "activity_paste_invariant_failed",
959
962
  SOLE_SCHEDULE_ROOT: "sole_schedule_root",
960
963
  ANCHOR_SIBLING_CONFLICT: "anchor_sibling_conflict",
@@ -6285,7 +6288,9 @@ var P6_PERCENT_COMPLETE_TYPE = {
6285
6288
  var P6_ACTIVITY_TYPE = {
6286
6289
  TASK: "task",
6287
6290
  LEVEL_OF_EFFORT: "level_of_effort",
6288
- START_MILESTONE: "start_milestone"};
6291
+ START_MILESTONE: "start_milestone",
6292
+ RESOURCE_DEPENDENT: "resource_dependent"
6293
+ };
6289
6294
  var P6_EXECUTION_STATUS = {
6290
6295
  NOT_STARTED: "not_started",
6291
6296
  IN_PROGRESS: "in_progress",
@@ -18014,6 +18019,7 @@ var LOAD_DIAGNOSTIC_SAMPLE_LIMIT = 10;
18014
18019
  var LOAD_DIAGNOSTIC_CODE = {
18015
18020
  INVALID_BASELINE_POINT: "invalid_baseline_point",
18016
18021
  UNKNOWN_CONSTRAINT: "unknown_constraint",
18022
+ PROGRESS_OUT_OF_RANGE: "progress_out_of_range",
18017
18023
  ORPHAN_ACTIVITY: "orphan_activity",
18018
18024
  INVALID_LINK: "invalid_link",
18019
18025
  DUPLICATE_LINK: "duplicate_link"
@@ -18021,6 +18027,7 @@ var LOAD_DIAGNOSTIC_CODE = {
18021
18027
  var LOAD_DIAGNOSTIC_CODE_ORDER = [
18022
18028
  LOAD_DIAGNOSTIC_CODE.INVALID_BASELINE_POINT,
18023
18029
  LOAD_DIAGNOSTIC_CODE.UNKNOWN_CONSTRAINT,
18030
+ LOAD_DIAGNOSTIC_CODE.PROGRESS_OUT_OF_RANGE,
18024
18031
  LOAD_DIAGNOSTIC_CODE.ORPHAN_ACTIVITY,
18025
18032
  LOAD_DIAGNOSTIC_CODE.INVALID_LINK,
18026
18033
  LOAD_DIAGNOSTIC_CODE.DUPLICATE_LINK
@@ -19006,7 +19013,12 @@ function dispatchCellCopy(action, deps) {
19006
19013
  return { ok: false, reason: REJECTION_REASON.INVALID_VALUE };
19007
19014
  }
19008
19015
  const raw = action.column === COLUMN.DURATION ? canonicalValue / deps.hoursPerDay : cloneDomainValue(canonicalValue);
19009
- deps.clipboard.value = { kind: CELL_CLIPBOARD_KIND.FIELD, raw };
19016
+ deps.clipboard.value = {
19017
+ kind: CELL_CLIPBOARD_KIND.FIELD,
19018
+ raw,
19019
+ sourceColumn: action.column,
19020
+ text: action.copiedText ?? null
19021
+ };
19010
19022
  return emptyChangeSet(action);
19011
19023
  }
19012
19024
  function dispatchCellClipboardClear(action, clipboard) {
@@ -19015,6 +19027,39 @@ function dispatchCellClipboardClear(action, clipboard) {
19015
19027
  }
19016
19028
 
19017
19029
  // src/cell-copy-paste/paste.ts
19030
+ var RESTRICTED_CELL_COLUMNS = /* @__PURE__ */ new Set([
19031
+ COLUMN.TAGS,
19032
+ COLUMN.RESPONSABLES,
19033
+ COLUMN.SUBCONTRACT_ID,
19034
+ COLUMN.CONSTRAINT_TYPE,
19035
+ COLUMN.CALENDAR_ID
19036
+ ]);
19037
+ function isIncompatibleCellPaste(sourceColumn, targetColumn) {
19038
+ if (sourceColumn === targetColumn) return false;
19039
+ return RESTRICTED_CELL_COLUMNS.has(sourceColumn) || RESTRICTED_CELL_COLUMNS.has(targetColumn);
19040
+ }
19041
+ function resolveExternalPasteText(stashedField, clipboardText) {
19042
+ if (clipboardText === void 0) return null;
19043
+ const rawIsTrustworthy = stashedField !== null && (stashedField.text === null || stashedField.text === clipboardText);
19044
+ return rawIsTrustworthy ? null : clipboardText;
19045
+ }
19046
+ function resolveFieldPasteValue(clipboard, column, clipboardText) {
19047
+ const stashedField = clipboard !== null && clipboard.kind === CELL_CLIPBOARD_KIND.FIELD ? clipboard : null;
19048
+ const externalText = resolveExternalPasteText(stashedField, clipboardText);
19049
+ if (externalText !== null) {
19050
+ return { ok: true, newValue: externalText };
19051
+ }
19052
+ if (clipboard !== null && clipboard.kind !== CELL_CLIPBOARD_KIND.FIELD) {
19053
+ return { ok: false, reason: REJECTION_REASON.INVALID_VALUE };
19054
+ }
19055
+ if (stashedField === null) {
19056
+ return { ok: false, reason: REJECTION_REASON.CLIPBOARD_EMPTY };
19057
+ }
19058
+ if (isIncompatibleCellPaste(stashedField.sourceColumn, column)) {
19059
+ return { ok: false, reason: REJECTION_REASON.CELL_PASTE_COLUMN_MISMATCH };
19060
+ }
19061
+ return { ok: true, newValue: stashedField.raw };
19062
+ }
19018
19063
  function toDiffPlaceholder(activityId) {
19019
19064
  return String(activityId);
19020
19065
  }
@@ -19047,11 +19092,45 @@ function linkOperationsForTarget(targetActivityId, isPredecessors, clipboard, de
19047
19092
  });
19048
19093
  return diff.operations;
19049
19094
  }
19050
- async function dispatchCellPasteCopied(action, options, deps) {
19051
- const clipboard = deps.cellClipboard.value;
19095
+ async function dispatchCellPasteLinkColumn(action, clipboard, options, deps) {
19052
19096
  if (clipboard === null) {
19053
19097
  return { ok: false, reason: REJECTION_REASON.CLIPBOARD_EMPTY };
19054
19098
  }
19099
+ if (clipboard.kind !== CELL_CLIPBOARD_KIND.LINKS) {
19100
+ return { ok: false, reason: REJECTION_REASON.INVALID_VALUE };
19101
+ }
19102
+ const hasSelfReference = action.activityIds.some(
19103
+ (activityId) => clipboard.specs.some(
19104
+ (spec) => String(spec.otherActivityId) === String(activityId)
19105
+ )
19106
+ );
19107
+ if (hasSelfReference) {
19108
+ return { ok: false, reason: LINK_REJECTION_REASON.SELF_LINK };
19109
+ }
19110
+ const isPredecessors = action.column === COLUMN.CUSTOM_PREDECESSORS;
19111
+ const operations = action.activityIds.flatMap(
19112
+ (activityId) => linkOperationsForTarget(activityId, isPredecessors, clipboard, deps)
19113
+ );
19114
+ const createEntries = operations.filter(
19115
+ (operation) => operation.kind === LINK_OPERATION_KIND.CREATE
19116
+ ).map((operation) => ({
19117
+ operation,
19118
+ linkId: deps.linkIdGen.next()
19119
+ }));
19120
+ const creation = validateLinkCreateBatch(createEntries, {
19121
+ port: deps.adapter,
19122
+ rejectParentLinkMutations: options.rejectParentLinkMutations ?? false
19123
+ });
19124
+ if (!creation.applied) {
19125
+ return {
19126
+ ok: false,
19127
+ reason: creation.rejected ?? REJECTION_REASON.INVALID
19128
+ };
19129
+ }
19130
+ return dispatchLinkBatch(operations, action, options, deps);
19131
+ }
19132
+ async function dispatchCellPasteCopied(action, options, deps) {
19133
+ const clipboard = deps.cellClipboard.value;
19055
19134
  if (action.activityIds.length === 0) {
19056
19135
  return { ok: false, reason: REJECTION_REASON.ACTIVITY_IDS_EMPTY };
19057
19136
  }
@@ -19062,53 +19141,25 @@ async function dispatchCellPasteCopied(action, options, deps) {
19062
19141
  return { ok: false, reason: REJECTION_REASON.ACTIVITY_NOT_FOUND };
19063
19142
  }
19064
19143
  if (action.column === COLUMN.CUSTOM_PREDECESSORS || action.column === COLUMN.CUSTOM_SUCESSORS) {
19065
- if (clipboard.kind !== CELL_CLIPBOARD_KIND.LINKS) {
19066
- return { ok: false, reason: REJECTION_REASON.INVALID_VALUE };
19067
- }
19068
- const hasSelfReference = action.activityIds.some(
19069
- (activityId) => clipboard.specs.some(
19070
- (spec) => String(spec.otherActivityId) === String(activityId)
19071
- )
19072
- );
19073
- if (hasSelfReference) {
19074
- return { ok: false, reason: LINK_REJECTION_REASON.SELF_LINK };
19075
- }
19076
- const isPredecessors = action.column === COLUMN.CUSTOM_PREDECESSORS;
19077
- const operations = action.activityIds.flatMap(
19078
- (activityId) => linkOperationsForTarget(activityId, isPredecessors, clipboard, deps)
19079
- );
19080
- const createEntries = operations.filter(
19081
- (operation) => operation.kind === LINK_OPERATION_KIND.CREATE
19082
- ).map((operation) => ({
19083
- operation,
19084
- linkId: deps.linkIdGen.next()
19085
- }));
19086
- const creation = validateLinkCreateBatch(createEntries, {
19087
- port: deps.adapter,
19088
- rejectParentLinkMutations: options.rejectParentLinkMutations ?? false
19089
- });
19090
- if (!creation.applied) {
19091
- return {
19092
- ok: false,
19093
- reason: creation.rejected ?? REJECTION_REASON.INVALID
19094
- };
19095
- }
19096
- return dispatchLinkBatch(operations, action, options, deps);
19097
- }
19098
- if (clipboard.kind !== CELL_CLIPBOARD_KIND.FIELD) {
19099
- return { ok: false, reason: REJECTION_REASON.INVALID_VALUE };
19144
+ return dispatchCellPasteLinkColumn(action, clipboard, options, deps);
19100
19145
  }
19101
19146
  if (!findPipelineForColumn(action.column)) {
19102
19147
  return { ok: false, reason: REJECTION_REASON.NO_PIPELINE_FOR_COLUMN };
19103
19148
  }
19104
19149
  const column = action.column;
19150
+ const resolution = resolveFieldPasteValue(
19151
+ clipboard,
19152
+ column,
19153
+ action.clipboardText
19154
+ );
19155
+ if (!resolution.ok) return resolution;
19105
19156
  const result = await dispatchBulkEdit(
19106
19157
  {
19107
19158
  kind: DISPATCH_ACTION_KIND.BULK_EDIT,
19108
19159
  edits: action.activityIds.map((activityId) => ({
19109
19160
  activityId,
19110
19161
  column,
19111
- newValue: clipboard.raw
19162
+ newValue: resolution.newValue
19112
19163
  })),
19113
19164
  ...action.eventSource === void 0 ? {} : { eventSource: action.eventSource }
19114
19165
  },
@@ -24826,12 +24877,21 @@ function normalizePersistedActivityId(rawId) {
24826
24877
  }
24827
24878
  return rawId;
24828
24879
  }
24829
- function parseProgress(raw) {
24880
+ function parseProgress(raw, reporter, recordDiagnostic, activityId) {
24830
24881
  const progress = Number(raw);
24831
- if (!Number.isFinite(progress) || progress < NOT_STARTED_PROGRESS || progress > MAX_TOLERATED_PROGRESS) {
24882
+ if (!Number.isFinite(progress)) {
24832
24883
  throw new TypeError(`invalid activity progress ${String(raw)}`);
24833
24884
  }
24834
- return progress;
24885
+ const isBelowNotStarted = progress < NOT_STARTED_PROGRESS;
24886
+ const isAboveCompleted = progress > MAX_TOLERATED_PROGRESS;
24887
+ const isWithinRange = !isBelowNotStarted && !isAboveCompleted;
24888
+ if (isWithinRange) return progress;
24889
+ const clamped = isBelowNotStarted ? NOT_STARTED_PROGRESS : COMPLETED_PROGRESS;
24890
+ recordDiagnostic(LOAD_DIAGNOSTIC_CODE.PROGRESS_OUT_OF_RANGE, activityId);
24891
+ reporter.warn(
24892
+ `[parseFromBackend] activity "${activityId}" progress ${String(raw)} is out of range \u2014 clamping to ${clamped}`
24893
+ );
24894
+ return clamped;
24835
24895
  }
24836
24896
  function parseCriticality(raw, reporter) {
24837
24897
  if (typeof raw.is_critical === "boolean") return raw.is_critical;
@@ -24864,7 +24924,12 @@ function parseActivity(raw, context) {
24864
24924
  context.defaultCalendarId
24865
24925
  );
24866
24926
  const inheritedDefaultCalendar = raw.calendarId == null || !context.knownCalendarIds.has(String(raw.calendarId));
24867
- const progress = parseProgress(raw.progress);
24927
+ const progress = parseProgress(
24928
+ raw.progress,
24929
+ context.reporter,
24930
+ context.recordDiagnostic,
24931
+ String(raw.unique_id)
24932
+ );
24868
24933
  const { constraintType, constraintDate } = resolveConstraint(
24869
24934
  raw,
24870
24935
  context.reporter,
@@ -30093,6 +30158,39 @@ function blockedActivities(code, message, activityIds, missingInputs = []) {
30093
30158
  };
30094
30159
  }
30095
30160
 
30161
+ // src/p6/rolled-expected-finish.ts
30162
+ var PINNABLE_ACTIVITY_TYPES = /* @__PURE__ */ new Set([
30163
+ P6_ACTIVITY_TYPE.TASK,
30164
+ P6_ACTIVITY_TYPE.RESOURCE_DEPENDENT
30165
+ ]);
30166
+ var NO_ROLLED_EXPECTED_FINISHES = /* @__PURE__ */ new Map();
30167
+ function planRolledExpectedFinishes(input) {
30168
+ const adoptsShownFinishes = input.settings.useExpectedFinishDates && rollsDataDateForward(input.previousSettings, input.settings);
30169
+ if (!adoptsShownFinishes) return NO_ROLLED_EXPECTED_FINISHES;
30170
+ const pinned = /* @__PURE__ */ new Map();
30171
+ for (const [activityId, state] of input.activities) {
30172
+ const shownFinish = state.remainingEarlyFinish;
30173
+ if (shownFinish === null) continue;
30174
+ if (!needsExpectedFinishPin(state)) continue;
30175
+ pinned.set(activityId, new Date(shownFinish));
30176
+ }
30177
+ return pinned;
30178
+ }
30179
+ function rollsDataDateForward(previous, next) {
30180
+ const previousDataDate = previous.dataDate;
30181
+ const nextDataDate = next.dataDate;
30182
+ if (previousDataDate === null || nextDataDate === null) return false;
30183
+ return nextDataDate.getTime() > previousDataDate.getTime();
30184
+ }
30185
+ function needsExpectedFinishPin(state) {
30186
+ return state.executionStatus === P6_EXECUTION_STATUS.IN_PROGRESS && state.expectedFinish === null && hasPinnableActivityType(state);
30187
+ }
30188
+ function hasPinnableActivityType(state) {
30189
+ const { activityType } = state;
30190
+ if (activityType === void 0) return true;
30191
+ return PINNABLE_ACTIVITY_TYPES.has(activityType);
30192
+ }
30193
+
30096
30194
  // src/p6/commands.ts
30097
30195
  function dataDateScheduleReadiness(input) {
30098
30196
  const preflight = getP6SchedulePreflight({
@@ -30206,8 +30304,17 @@ function prepareDataDateSchedule(input) {
30206
30304
  preflight.settings,
30207
30305
  beforeProjectState
30208
30306
  );
30307
+ const rolledExpectedFinishes = planRolledExpectedFinishes({
30308
+ previousSettings: preflight.previousSettings,
30309
+ settings: preflight.settings,
30310
+ activities: preflight.activities
30311
+ });
30209
30312
  const calculated = calculateP6Schedule({
30210
- adapter: calculationState(input.state, preflight.activities),
30313
+ adapter: calculationState(
30314
+ input.state,
30315
+ preflight.activities,
30316
+ rolledExpectedFinishes
30317
+ ),
30211
30318
  settings: preflight.settings,
30212
30319
  project: {
30213
30320
  projectId: String(input.sector.projectId),
@@ -30253,11 +30360,19 @@ function requireProjectState(projectState) {
30253
30360
  "P6 capability is not enabled for this Schedule"
30254
30361
  );
30255
30362
  }
30256
- function calculationState(current, schedulableActivities) {
30363
+ function calculationState(current, schedulableActivities, rolledExpectedFinishes) {
30257
30364
  const state = current.fork();
30258
30365
  state.forEachActivity((activity) => {
30259
- if (schedulableActivities.has(activity.id)) return;
30260
- state.setActivityField(activity.id, "p6", void 0);
30366
+ if (!schedulableActivities.has(activity.id)) {
30367
+ state.setActivityField(activity.id, "p6", void 0);
30368
+ return;
30369
+ }
30370
+ const rolledExpectedFinish = rolledExpectedFinishes.get(activity.id);
30371
+ if (rolledExpectedFinish === void 0 || activity.p6 === void 0) return;
30372
+ state.setActivityField(activity.id, "p6", {
30373
+ ...activity.p6,
30374
+ expectedFinish: rolledExpectedFinish
30375
+ });
30261
30376
  });
30262
30377
  return state;
30263
30378
  }