@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.cjs CHANGED
@@ -630,17 +630,19 @@ function createActivitiesApi(engine) {
630
630
  }
631
631
  function createCellsApi(engine) {
632
632
  return {
633
- copy: (activityId, column, context = {}) => engine.dispatch({
633
+ copy: (activityId, column, copiedText, context = {}) => engine.dispatch({
634
634
  ...context,
635
635
  kind: DISPATCH_ACTION_KIND.CELL_COPY,
636
636
  activityId,
637
- column
637
+ column,
638
+ ...copiedText === void 0 ? {} : { copiedText }
638
639
  }),
639
- paste: (activityIds, column, context = {}) => engine.dispatch({
640
+ paste: (activityIds, column, clipboardText, context = {}) => engine.dispatch({
640
641
  ...context,
641
642
  kind: DISPATCH_ACTION_KIND.CELL_PASTE_COPIED,
642
643
  activityIds,
643
- column
644
+ column,
645
+ ...clipboardText === void 0 ? {} : { clipboardText }
644
646
  }),
645
647
  clearClipboard: (context = {}) => engine.dispatch({
646
648
  ...context,
@@ -957,6 +959,7 @@ var REJECTION_REASON = {
957
959
  NO_ACTIVITY_SELECTION: "no_activity_selection",
958
960
  CLIPBOARD_EMPTY: "clipboard_empty",
959
961
  CLIPBOARD_SNAPSHOT_INVALID: "clipboard_snapshot_invalid",
962
+ CELL_PASTE_COLUMN_MISMATCH: "cell_paste_column_mismatch",
960
963
  ACTIVITY_PASTE_INVARIANT_FAILED: "activity_paste_invariant_failed",
961
964
  SOLE_SCHEDULE_ROOT: "sole_schedule_root",
962
965
  ANCHOR_SIBLING_CONFLICT: "anchor_sibling_conflict",
@@ -6287,7 +6290,9 @@ var P6_PERCENT_COMPLETE_TYPE = {
6287
6290
  var P6_ACTIVITY_TYPE = {
6288
6291
  TASK: "task",
6289
6292
  LEVEL_OF_EFFORT: "level_of_effort",
6290
- START_MILESTONE: "start_milestone"};
6293
+ START_MILESTONE: "start_milestone",
6294
+ RESOURCE_DEPENDENT: "resource_dependent"
6295
+ };
6291
6296
  var P6_EXECUTION_STATUS = {
6292
6297
  NOT_STARTED: "not_started",
6293
6298
  IN_PROGRESS: "in_progress",
@@ -18016,6 +18021,7 @@ var LOAD_DIAGNOSTIC_SAMPLE_LIMIT = 10;
18016
18021
  var LOAD_DIAGNOSTIC_CODE = {
18017
18022
  INVALID_BASELINE_POINT: "invalid_baseline_point",
18018
18023
  UNKNOWN_CONSTRAINT: "unknown_constraint",
18024
+ PROGRESS_OUT_OF_RANGE: "progress_out_of_range",
18019
18025
  ORPHAN_ACTIVITY: "orphan_activity",
18020
18026
  INVALID_LINK: "invalid_link",
18021
18027
  DUPLICATE_LINK: "duplicate_link"
@@ -18023,6 +18029,7 @@ var LOAD_DIAGNOSTIC_CODE = {
18023
18029
  var LOAD_DIAGNOSTIC_CODE_ORDER = [
18024
18030
  LOAD_DIAGNOSTIC_CODE.INVALID_BASELINE_POINT,
18025
18031
  LOAD_DIAGNOSTIC_CODE.UNKNOWN_CONSTRAINT,
18032
+ LOAD_DIAGNOSTIC_CODE.PROGRESS_OUT_OF_RANGE,
18026
18033
  LOAD_DIAGNOSTIC_CODE.ORPHAN_ACTIVITY,
18027
18034
  LOAD_DIAGNOSTIC_CODE.INVALID_LINK,
18028
18035
  LOAD_DIAGNOSTIC_CODE.DUPLICATE_LINK
@@ -19008,7 +19015,12 @@ function dispatchCellCopy(action, deps) {
19008
19015
  return { ok: false, reason: REJECTION_REASON.INVALID_VALUE };
19009
19016
  }
19010
19017
  const raw = action.column === COLUMN.DURATION ? canonicalValue / deps.hoursPerDay : cloneDomainValue(canonicalValue);
19011
- deps.clipboard.value = { kind: CELL_CLIPBOARD_KIND.FIELD, raw };
19018
+ deps.clipboard.value = {
19019
+ kind: CELL_CLIPBOARD_KIND.FIELD,
19020
+ raw,
19021
+ sourceColumn: action.column,
19022
+ text: action.copiedText ?? null
19023
+ };
19012
19024
  return emptyChangeSet(action);
19013
19025
  }
19014
19026
  function dispatchCellClipboardClear(action, clipboard) {
@@ -19017,6 +19029,39 @@ function dispatchCellClipboardClear(action, clipboard) {
19017
19029
  }
19018
19030
 
19019
19031
  // src/cell-copy-paste/paste.ts
19032
+ var RESTRICTED_CELL_COLUMNS = /* @__PURE__ */ new Set([
19033
+ COLUMN.TAGS,
19034
+ COLUMN.RESPONSABLES,
19035
+ COLUMN.SUBCONTRACT_ID,
19036
+ COLUMN.CONSTRAINT_TYPE,
19037
+ COLUMN.CALENDAR_ID
19038
+ ]);
19039
+ function isIncompatibleCellPaste(sourceColumn, targetColumn) {
19040
+ if (sourceColumn === targetColumn) return false;
19041
+ return RESTRICTED_CELL_COLUMNS.has(sourceColumn) || RESTRICTED_CELL_COLUMNS.has(targetColumn);
19042
+ }
19043
+ function resolveExternalPasteText(stashedField, clipboardText) {
19044
+ if (clipboardText === void 0) return null;
19045
+ const rawIsTrustworthy = stashedField !== null && (stashedField.text === null || stashedField.text === clipboardText);
19046
+ return rawIsTrustworthy ? null : clipboardText;
19047
+ }
19048
+ function resolveFieldPasteValue(clipboard, column, clipboardText) {
19049
+ const stashedField = clipboard !== null && clipboard.kind === CELL_CLIPBOARD_KIND.FIELD ? clipboard : null;
19050
+ const externalText = resolveExternalPasteText(stashedField, clipboardText);
19051
+ if (externalText !== null) {
19052
+ return { ok: true, newValue: externalText };
19053
+ }
19054
+ if (clipboard !== null && clipboard.kind !== CELL_CLIPBOARD_KIND.FIELD) {
19055
+ return { ok: false, reason: REJECTION_REASON.INVALID_VALUE };
19056
+ }
19057
+ if (stashedField === null) {
19058
+ return { ok: false, reason: REJECTION_REASON.CLIPBOARD_EMPTY };
19059
+ }
19060
+ if (isIncompatibleCellPaste(stashedField.sourceColumn, column)) {
19061
+ return { ok: false, reason: REJECTION_REASON.CELL_PASTE_COLUMN_MISMATCH };
19062
+ }
19063
+ return { ok: true, newValue: stashedField.raw };
19064
+ }
19020
19065
  function toDiffPlaceholder(activityId) {
19021
19066
  return String(activityId);
19022
19067
  }
@@ -19049,11 +19094,45 @@ function linkOperationsForTarget(targetActivityId, isPredecessors, clipboard, de
19049
19094
  });
19050
19095
  return diff.operations;
19051
19096
  }
19052
- async function dispatchCellPasteCopied(action, options, deps) {
19053
- const clipboard = deps.cellClipboard.value;
19097
+ async function dispatchCellPasteLinkColumn(action, clipboard, options, deps) {
19054
19098
  if (clipboard === null) {
19055
19099
  return { ok: false, reason: REJECTION_REASON.CLIPBOARD_EMPTY };
19056
19100
  }
19101
+ if (clipboard.kind !== CELL_CLIPBOARD_KIND.LINKS) {
19102
+ return { ok: false, reason: REJECTION_REASON.INVALID_VALUE };
19103
+ }
19104
+ const hasSelfReference = action.activityIds.some(
19105
+ (activityId) => clipboard.specs.some(
19106
+ (spec) => String(spec.otherActivityId) === String(activityId)
19107
+ )
19108
+ );
19109
+ if (hasSelfReference) {
19110
+ return { ok: false, reason: LINK_REJECTION_REASON.SELF_LINK };
19111
+ }
19112
+ const isPredecessors = action.column === COLUMN.CUSTOM_PREDECESSORS;
19113
+ const operations = action.activityIds.flatMap(
19114
+ (activityId) => linkOperationsForTarget(activityId, isPredecessors, clipboard, deps)
19115
+ );
19116
+ const createEntries = operations.filter(
19117
+ (operation) => operation.kind === LINK_OPERATION_KIND.CREATE
19118
+ ).map((operation) => ({
19119
+ operation,
19120
+ linkId: deps.linkIdGen.next()
19121
+ }));
19122
+ const creation = validateLinkCreateBatch(createEntries, {
19123
+ port: deps.adapter,
19124
+ rejectParentLinkMutations: options.rejectParentLinkMutations ?? false
19125
+ });
19126
+ if (!creation.applied) {
19127
+ return {
19128
+ ok: false,
19129
+ reason: creation.rejected ?? REJECTION_REASON.INVALID
19130
+ };
19131
+ }
19132
+ return dispatchLinkBatch(operations, action, options, deps);
19133
+ }
19134
+ async function dispatchCellPasteCopied(action, options, deps) {
19135
+ const clipboard = deps.cellClipboard.value;
19057
19136
  if (action.activityIds.length === 0) {
19058
19137
  return { ok: false, reason: REJECTION_REASON.ACTIVITY_IDS_EMPTY };
19059
19138
  }
@@ -19064,53 +19143,25 @@ async function dispatchCellPasteCopied(action, options, deps) {
19064
19143
  return { ok: false, reason: REJECTION_REASON.ACTIVITY_NOT_FOUND };
19065
19144
  }
19066
19145
  if (action.column === COLUMN.CUSTOM_PREDECESSORS || action.column === COLUMN.CUSTOM_SUCESSORS) {
19067
- if (clipboard.kind !== CELL_CLIPBOARD_KIND.LINKS) {
19068
- return { ok: false, reason: REJECTION_REASON.INVALID_VALUE };
19069
- }
19070
- const hasSelfReference = action.activityIds.some(
19071
- (activityId) => clipboard.specs.some(
19072
- (spec) => String(spec.otherActivityId) === String(activityId)
19073
- )
19074
- );
19075
- if (hasSelfReference) {
19076
- return { ok: false, reason: LINK_REJECTION_REASON.SELF_LINK };
19077
- }
19078
- const isPredecessors = action.column === COLUMN.CUSTOM_PREDECESSORS;
19079
- const operations = action.activityIds.flatMap(
19080
- (activityId) => linkOperationsForTarget(activityId, isPredecessors, clipboard, deps)
19081
- );
19082
- const createEntries = operations.filter(
19083
- (operation) => operation.kind === LINK_OPERATION_KIND.CREATE
19084
- ).map((operation) => ({
19085
- operation,
19086
- linkId: deps.linkIdGen.next()
19087
- }));
19088
- const creation = validateLinkCreateBatch(createEntries, {
19089
- port: deps.adapter,
19090
- rejectParentLinkMutations: options.rejectParentLinkMutations ?? false
19091
- });
19092
- if (!creation.applied) {
19093
- return {
19094
- ok: false,
19095
- reason: creation.rejected ?? REJECTION_REASON.INVALID
19096
- };
19097
- }
19098
- return dispatchLinkBatch(operations, action, options, deps);
19099
- }
19100
- if (clipboard.kind !== CELL_CLIPBOARD_KIND.FIELD) {
19101
- return { ok: false, reason: REJECTION_REASON.INVALID_VALUE };
19146
+ return dispatchCellPasteLinkColumn(action, clipboard, options, deps);
19102
19147
  }
19103
19148
  if (!findPipelineForColumn(action.column)) {
19104
19149
  return { ok: false, reason: REJECTION_REASON.NO_PIPELINE_FOR_COLUMN };
19105
19150
  }
19106
19151
  const column = action.column;
19152
+ const resolution = resolveFieldPasteValue(
19153
+ clipboard,
19154
+ column,
19155
+ action.clipboardText
19156
+ );
19157
+ if (!resolution.ok) return resolution;
19107
19158
  const result = await dispatchBulkEdit(
19108
19159
  {
19109
19160
  kind: DISPATCH_ACTION_KIND.BULK_EDIT,
19110
19161
  edits: action.activityIds.map((activityId) => ({
19111
19162
  activityId,
19112
19163
  column,
19113
- newValue: clipboard.raw
19164
+ newValue: resolution.newValue
19114
19165
  })),
19115
19166
  ...action.eventSource === void 0 ? {} : { eventSource: action.eventSource }
19116
19167
  },
@@ -24828,12 +24879,21 @@ function normalizePersistedActivityId(rawId) {
24828
24879
  }
24829
24880
  return rawId;
24830
24881
  }
24831
- function parseProgress(raw) {
24882
+ function parseProgress(raw, reporter, recordDiagnostic, activityId) {
24832
24883
  const progress = Number(raw);
24833
- if (!Number.isFinite(progress) || progress < NOT_STARTED_PROGRESS || progress > MAX_TOLERATED_PROGRESS) {
24884
+ if (!Number.isFinite(progress)) {
24834
24885
  throw new TypeError(`invalid activity progress ${String(raw)}`);
24835
24886
  }
24836
- return progress;
24887
+ const isBelowNotStarted = progress < NOT_STARTED_PROGRESS;
24888
+ const isAboveCompleted = progress > MAX_TOLERATED_PROGRESS;
24889
+ const isWithinRange = !isBelowNotStarted && !isAboveCompleted;
24890
+ if (isWithinRange) return progress;
24891
+ const clamped = isBelowNotStarted ? NOT_STARTED_PROGRESS : COMPLETED_PROGRESS;
24892
+ recordDiagnostic(LOAD_DIAGNOSTIC_CODE.PROGRESS_OUT_OF_RANGE, activityId);
24893
+ reporter.warn(
24894
+ `[parseFromBackend] activity "${activityId}" progress ${String(raw)} is out of range \u2014 clamping to ${clamped}`
24895
+ );
24896
+ return clamped;
24837
24897
  }
24838
24898
  function parseCriticality(raw, reporter) {
24839
24899
  if (typeof raw.is_critical === "boolean") return raw.is_critical;
@@ -24866,7 +24926,12 @@ function parseActivity(raw, context) {
24866
24926
  context.defaultCalendarId
24867
24927
  );
24868
24928
  const inheritedDefaultCalendar = raw.calendarId == null || !context.knownCalendarIds.has(String(raw.calendarId));
24869
- const progress = parseProgress(raw.progress);
24929
+ const progress = parseProgress(
24930
+ raw.progress,
24931
+ context.reporter,
24932
+ context.recordDiagnostic,
24933
+ String(raw.unique_id)
24934
+ );
24870
24935
  const { constraintType, constraintDate } = resolveConstraint(
24871
24936
  raw,
24872
24937
  context.reporter,
@@ -30095,6 +30160,39 @@ function blockedActivities(code, message, activityIds, missingInputs = []) {
30095
30160
  };
30096
30161
  }
30097
30162
 
30163
+ // src/p6/rolled-expected-finish.ts
30164
+ var PINNABLE_ACTIVITY_TYPES = /* @__PURE__ */ new Set([
30165
+ P6_ACTIVITY_TYPE.TASK,
30166
+ P6_ACTIVITY_TYPE.RESOURCE_DEPENDENT
30167
+ ]);
30168
+ var NO_ROLLED_EXPECTED_FINISHES = /* @__PURE__ */ new Map();
30169
+ function planRolledExpectedFinishes(input) {
30170
+ const adoptsShownFinishes = input.settings.useExpectedFinishDates && rollsDataDateForward(input.previousSettings, input.settings);
30171
+ if (!adoptsShownFinishes) return NO_ROLLED_EXPECTED_FINISHES;
30172
+ const pinned = /* @__PURE__ */ new Map();
30173
+ for (const [activityId, state] of input.activities) {
30174
+ const shownFinish = state.remainingEarlyFinish;
30175
+ if (shownFinish === null) continue;
30176
+ if (!needsExpectedFinishPin(state)) continue;
30177
+ pinned.set(activityId, new Date(shownFinish));
30178
+ }
30179
+ return pinned;
30180
+ }
30181
+ function rollsDataDateForward(previous, next) {
30182
+ const previousDataDate = previous.dataDate;
30183
+ const nextDataDate = next.dataDate;
30184
+ if (previousDataDate === null || nextDataDate === null) return false;
30185
+ return nextDataDate.getTime() > previousDataDate.getTime();
30186
+ }
30187
+ function needsExpectedFinishPin(state) {
30188
+ return state.executionStatus === P6_EXECUTION_STATUS.IN_PROGRESS && state.expectedFinish === null && hasPinnableActivityType(state);
30189
+ }
30190
+ function hasPinnableActivityType(state) {
30191
+ const { activityType } = state;
30192
+ if (activityType === void 0) return true;
30193
+ return PINNABLE_ACTIVITY_TYPES.has(activityType);
30194
+ }
30195
+
30098
30196
  // src/p6/commands.ts
30099
30197
  function dataDateScheduleReadiness(input) {
30100
30198
  const preflight = getP6SchedulePreflight({
@@ -30208,8 +30306,17 @@ function prepareDataDateSchedule(input) {
30208
30306
  preflight.settings,
30209
30307
  beforeProjectState
30210
30308
  );
30309
+ const rolledExpectedFinishes = planRolledExpectedFinishes({
30310
+ previousSettings: preflight.previousSettings,
30311
+ settings: preflight.settings,
30312
+ activities: preflight.activities
30313
+ });
30211
30314
  const calculated = calculateP6Schedule({
30212
- adapter: calculationState(input.state, preflight.activities),
30315
+ adapter: calculationState(
30316
+ input.state,
30317
+ preflight.activities,
30318
+ rolledExpectedFinishes
30319
+ ),
30213
30320
  settings: preflight.settings,
30214
30321
  project: {
30215
30322
  projectId: String(input.sector.projectId),
@@ -30255,11 +30362,19 @@ function requireProjectState(projectState) {
30255
30362
  "P6 capability is not enabled for this Schedule"
30256
30363
  );
30257
30364
  }
30258
- function calculationState(current, schedulableActivities) {
30365
+ function calculationState(current, schedulableActivities, rolledExpectedFinishes) {
30259
30366
  const state = current.fork();
30260
30367
  state.forEachActivity((activity) => {
30261
- if (schedulableActivities.has(activity.id)) return;
30262
- state.setActivityField(activity.id, "p6", void 0);
30368
+ if (!schedulableActivities.has(activity.id)) {
30369
+ state.setActivityField(activity.id, "p6", void 0);
30370
+ return;
30371
+ }
30372
+ const rolledExpectedFinish = rolledExpectedFinishes.get(activity.id);
30373
+ if (rolledExpectedFinish === void 0 || activity.p6 === void 0) return;
30374
+ state.setActivityField(activity.id, "p6", {
30375
+ ...activity.p6,
30376
+ expectedFinish: rolledExpectedFinish
30377
+ });
30263
30378
  });
30264
30379
  return state;
30265
30380
  }