@outbuild-company/schedule-core 1.23.0 → 1.25.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
@@ -2669,6 +2669,7 @@ type LinkRejection = (typeof LINK_REJECTION_REASON)[keyof typeof LINK_REJECTION_
2669
2669
  declare const LOAD_DIAGNOSTIC_CODE: {
2670
2670
  readonly INVALID_BASELINE_POINT: "invalid_baseline_point";
2671
2671
  readonly UNKNOWN_CONSTRAINT: "unknown_constraint";
2672
+ readonly PROGRESS_OUT_OF_RANGE: "progress_out_of_range";
2672
2673
  readonly ORPHAN_ACTIVITY: "orphan_activity";
2673
2674
  readonly INVALID_LINK: "invalid_link";
2674
2675
  readonly DUPLICATE_LINK: "duplicate_link";
package/dist/index.d.ts CHANGED
@@ -2669,6 +2669,7 @@ type LinkRejection = (typeof LINK_REJECTION_REASON)[keyof typeof LINK_REJECTION_
2669
2669
  declare const LOAD_DIAGNOSTIC_CODE: {
2670
2670
  readonly INVALID_BASELINE_POINT: "invalid_baseline_point";
2671
2671
  readonly UNKNOWN_CONSTRAINT: "unknown_constraint";
2672
+ readonly PROGRESS_OUT_OF_RANGE: "progress_out_of_range";
2672
2673
  readonly ORPHAN_ACTIVITY: "orphan_activity";
2673
2674
  readonly INVALID_LINK: "invalid_link";
2674
2675
  readonly DUPLICATE_LINK: "duplicate_link";
package/dist/index.js CHANGED
@@ -6285,7 +6285,9 @@ var P6_PERCENT_COMPLETE_TYPE = {
6285
6285
  var P6_ACTIVITY_TYPE = {
6286
6286
  TASK: "task",
6287
6287
  LEVEL_OF_EFFORT: "level_of_effort",
6288
- START_MILESTONE: "start_milestone"};
6288
+ START_MILESTONE: "start_milestone",
6289
+ RESOURCE_DEPENDENT: "resource_dependent"
6290
+ };
6289
6291
  var P6_EXECUTION_STATUS = {
6290
6292
  NOT_STARTED: "not_started",
6291
6293
  IN_PROGRESS: "in_progress",
@@ -11977,14 +11979,65 @@ function createOutbuildP6ActivityDraft(activity) {
11977
11979
  if (state === void 0) return void 0;
11978
11980
  return {
11979
11981
  ...state,
11980
- remainingDurationHours: draftRemainingDuration(activity)
11982
+ ...outbuildProgressExecution(activity, activity.durationHours)
11981
11983
  };
11982
11984
  }
11983
- function draftRemainingDuration(activity) {
11985
+ var COMPLETED_PHYSICAL_PERCENT = 100;
11986
+ var NOT_STARTED_PHYSICAL_PERCENT = 0;
11987
+ function outbuildProgressExecution(activity, originalDurationHours) {
11984
11988
  if (activity.progress === NOT_STARTED_PROGRESS) {
11985
- return activity.durationHours;
11989
+ return {
11990
+ executionStatus: P6_EXECUTION_STATUS.NOT_STARTED,
11991
+ actualStart: null,
11992
+ actualFinish: null,
11993
+ remainingDurationHours: originalDurationHours,
11994
+ physicalPercentComplete: NOT_STARTED_PHYSICAL_PERCENT,
11995
+ actualDurationHours: 0,
11996
+ atCompletionDurationHours: originalDurationHours,
11997
+ durationPercentComplete: 0
11998
+ };
11986
11999
  }
11987
- return isCompletedProgress(activity.progress) ? 0 : null;
12000
+ const actualStart = cloneValidDate(activity.startDate, "actualStart");
12001
+ if (isCompletedProgress(activity.progress)) {
12002
+ const hasMeasurableDuration = originalDurationHours > 0;
12003
+ return {
12004
+ executionStatus: P6_EXECUTION_STATUS.COMPLETED,
12005
+ actualStart,
12006
+ actualFinish: cloneValidDate(activity.endDate, "actualFinish"),
12007
+ remainingDurationHours: 0,
12008
+ physicalPercentComplete: COMPLETED_PHYSICAL_PERCENT,
12009
+ actualDurationHours: originalDurationHours,
12010
+ atCompletionDurationHours: originalDurationHours,
12011
+ durationPercentComplete: hasMeasurableDuration ? COMPLETED_PHYSICAL_PERCENT : NOT_STARTED_PHYSICAL_PERCENT
12012
+ };
12013
+ }
12014
+ return {
12015
+ executionStatus: P6_EXECUTION_STATUS.IN_PROGRESS,
12016
+ actualStart,
12017
+ actualFinish: null,
12018
+ remainingDurationHours: originalDurationHours,
12019
+ physicalPercentComplete: activity.progress,
12020
+ actualDurationHours: 0,
12021
+ atCompletionDurationHours: originalDurationHours,
12022
+ durationPercentComplete: 0
12023
+ };
12024
+ }
12025
+ function awaitsOutbuildExecution(draft) {
12026
+ return draft.executionStatus === P6_EXECUTION_STATUS.NOT_STARTED && draft.actualStart === null;
12027
+ }
12028
+ function reconcileOutbuildProgress(activity, draft) {
12029
+ const isProgressed = activity.progress !== NOT_STARTED_PROGRESS;
12030
+ if (!isProgressed || !awaitsOutbuildExecution(draft)) return draft;
12031
+ const inferred = outbuildProgressExecution(
12032
+ activity,
12033
+ draft.originalDurationHours
12034
+ );
12035
+ const keepsRecordedRemaining = inferred.executionStatus === P6_EXECUTION_STATUS.IN_PROGRESS && draft.remainingDurationHours !== null;
12036
+ return {
12037
+ ...draft,
12038
+ ...inferred,
12039
+ ...keepsRecordedRemaining ? { remainingDurationHours: draft.remainingDurationHours } : {}
12040
+ };
11988
12041
  }
11989
12042
 
11990
12043
  // src/p6/inactive-drafts.ts
@@ -12037,17 +12090,41 @@ function ordinaryInputChanges(before, activity) {
12037
12090
  ...before.startDate.getTime() !== activity.startDate.getTime() ? { plannedStart: new Date(activity.startDate) } : {},
12038
12091
  ...before.endDate.getTime() !== activity.endDate.getTime() ? { plannedFinish: new Date(activity.endDate) } : {},
12039
12092
  ...durationChanged ? { originalDurationHours: activity.durationHours } : {},
12040
- ...durationChanged || before.progress !== activity.progress ? remainingInputChanges(before, activity) : {}
12093
+ ...durationChanged || before.progress !== activity.progress ? executionInputChanges(before, activity, durationChanged) : {}
12041
12094
  };
12042
12095
  }
12043
- function remainingInputChanges(before, activity) {
12096
+ function executionInputChanges(before, activity, durationChanged) {
12044
12097
  const current = activity.p6;
12045
- if (current.actualStart !== null || current.remainingDurationHours !== before.p6.remainingDurationHours)
12046
- return {};
12047
- const remaining = draftRemainingDuration(activity);
12098
+ const hasExplicitRemainingCorrection = current.remainingDurationHours !== before.p6.remainingDurationHours;
12099
+ if (hasExplicitRemainingCorrection) return {};
12100
+ const progressChanged = before.progress !== activity.progress;
12101
+ const resetsToNotStarted = progressChanged && activity.progress === NOT_STARTED_PROGRESS;
12102
+ if (awaitsOutbuildExecution(current) || resetsToNotStarted) {
12103
+ const originalDurationHours = durationChanged ? activity.durationHours : current.originalDurationHours;
12104
+ return outbuildProgressExecution(activity, originalDurationHours);
12105
+ }
12106
+ const isStarted = current.actualStart !== null;
12107
+ if (!isStarted || !progressChanged) return {};
12108
+ return startedProgressChanges(current, activity);
12109
+ }
12110
+ function startedProgressChanges(current, activity) {
12111
+ const mirrorsPhysicalPercent = current.percentCompleteType === P6_PERCENT_COMPLETE_TYPE.PHYSICAL;
12112
+ if (!isCompletedProgress(activity.progress)) {
12113
+ return {
12114
+ ...current.actualFinish === null ? {} : {
12115
+ executionStatus: P6_EXECUTION_STATUS.IN_PROGRESS,
12116
+ actualFinish: null
12117
+ },
12118
+ ...mirrorsPhysicalPercent ? { physicalPercentComplete: activity.progress } : {}
12119
+ };
12120
+ }
12121
+ const finishesAfterActualStart = current.actualStart !== null && activity.endDate.getTime() >= current.actualStart.getTime();
12122
+ if (current.actualFinish !== null || !finishesAfterActualStart) return {};
12048
12123
  return {
12049
- remainingDurationHours: remaining,
12050
- ...remaining !== null ? { atCompletionDurationHours: remaining } : {}
12124
+ executionStatus: P6_EXECUTION_STATUS.COMPLETED,
12125
+ actualFinish: new Date(activity.endDate),
12126
+ remainingDurationHours: 0,
12127
+ physicalPercentComplete: COMPLETED_PROGRESS
12051
12128
  };
12052
12129
  }
12053
12130
 
@@ -17939,6 +18016,7 @@ var LOAD_DIAGNOSTIC_SAMPLE_LIMIT = 10;
17939
18016
  var LOAD_DIAGNOSTIC_CODE = {
17940
18017
  INVALID_BASELINE_POINT: "invalid_baseline_point",
17941
18018
  UNKNOWN_CONSTRAINT: "unknown_constraint",
18019
+ PROGRESS_OUT_OF_RANGE: "progress_out_of_range",
17942
18020
  ORPHAN_ACTIVITY: "orphan_activity",
17943
18021
  INVALID_LINK: "invalid_link",
17944
18022
  DUPLICATE_LINK: "duplicate_link"
@@ -17946,6 +18024,7 @@ var LOAD_DIAGNOSTIC_CODE = {
17946
18024
  var LOAD_DIAGNOSTIC_CODE_ORDER = [
17947
18025
  LOAD_DIAGNOSTIC_CODE.INVALID_BASELINE_POINT,
17948
18026
  LOAD_DIAGNOSTIC_CODE.UNKNOWN_CONSTRAINT,
18027
+ LOAD_DIAGNOSTIC_CODE.PROGRESS_OUT_OF_RANGE,
17949
18028
  LOAD_DIAGNOSTIC_CODE.ORPHAN_ACTIVITY,
17950
18029
  LOAD_DIAGNOSTIC_CODE.INVALID_LINK,
17951
18030
  LOAD_DIAGNOSTIC_CODE.DUPLICATE_LINK
@@ -24751,12 +24830,21 @@ function normalizePersistedActivityId(rawId) {
24751
24830
  }
24752
24831
  return rawId;
24753
24832
  }
24754
- function parseProgress(raw) {
24833
+ function parseProgress(raw, reporter, recordDiagnostic, activityId) {
24755
24834
  const progress = Number(raw);
24756
- if (!Number.isFinite(progress) || progress < NOT_STARTED_PROGRESS || progress > MAX_TOLERATED_PROGRESS) {
24835
+ if (!Number.isFinite(progress)) {
24757
24836
  throw new TypeError(`invalid activity progress ${String(raw)}`);
24758
24837
  }
24759
- return progress;
24838
+ const isBelowNotStarted = progress < NOT_STARTED_PROGRESS;
24839
+ const isAboveCompleted = progress > MAX_TOLERATED_PROGRESS;
24840
+ const isWithinRange = !isBelowNotStarted && !isAboveCompleted;
24841
+ if (isWithinRange) return progress;
24842
+ const clamped = isBelowNotStarted ? NOT_STARTED_PROGRESS : COMPLETED_PROGRESS;
24843
+ recordDiagnostic(LOAD_DIAGNOSTIC_CODE.PROGRESS_OUT_OF_RANGE, activityId);
24844
+ reporter.warn(
24845
+ `[parseFromBackend] activity "${activityId}" progress ${String(raw)} is out of range \u2014 clamping to ${clamped}`
24846
+ );
24847
+ return clamped;
24760
24848
  }
24761
24849
  function parseCriticality(raw, reporter) {
24762
24850
  if (typeof raw.is_critical === "boolean") return raw.is_critical;
@@ -24789,7 +24877,12 @@ function parseActivity(raw, context) {
24789
24877
  context.defaultCalendarId
24790
24878
  );
24791
24879
  const inheritedDefaultCalendar = raw.calendarId == null || !context.knownCalendarIds.has(String(raw.calendarId));
24792
- const progress = parseProgress(raw.progress);
24880
+ const progress = parseProgress(
24881
+ raw.progress,
24882
+ context.reporter,
24883
+ context.recordDiagnostic,
24884
+ String(raw.unique_id)
24885
+ );
24793
24886
  const { constraintType, constraintDate } = resolveConstraint(
24794
24887
  raw,
24795
24888
  context.reporter,
@@ -26243,8 +26336,8 @@ var P6ActivityStatusError = class extends Error {
26243
26336
  }
26244
26337
  code;
26245
26338
  };
26246
- var COMPLETED_PHYSICAL_PERCENT = 100;
26247
- var NOT_STARTED_PHYSICAL_PERCENT = 0;
26339
+ var COMPLETED_PHYSICAL_PERCENT2 = 100;
26340
+ var NOT_STARTED_PHYSICAL_PERCENT2 = 0;
26248
26341
  function validateP6ActivityStatus(activity) {
26249
26342
  validateRemainingDuration(activity.remainingDurationHours);
26250
26343
  validateDates(activity);
@@ -26270,11 +26363,11 @@ function normalizeP6ActualDatePatch(current, patch) {
26270
26363
  executionStatus,
26271
26364
  ...actualFinish !== null ? {
26272
26365
  remainingDurationHours: 0,
26273
- physicalPercentComplete: COMPLETED_PHYSICAL_PERCENT
26366
+ physicalPercentComplete: COMPLETED_PHYSICAL_PERCENT2
26274
26367
  } : {},
26275
26368
  ...unStarting ? {
26276
26369
  remainingDurationHours: current.originalDurationHours,
26277
- physicalPercentComplete: NOT_STARTED_PHYSICAL_PERCENT
26370
+ physicalPercentComplete: NOT_STARTED_PHYSICAL_PERCENT2
26278
26371
  } : {}
26279
26372
  };
26280
26373
  validateP6ActivityDraftStatus({ ...current, ...normalized2 });
@@ -30018,6 +30111,39 @@ function blockedActivities(code, message, activityIds, missingInputs = []) {
30018
30111
  };
30019
30112
  }
30020
30113
 
30114
+ // src/p6/rolled-expected-finish.ts
30115
+ var PINNABLE_ACTIVITY_TYPES = /* @__PURE__ */ new Set([
30116
+ P6_ACTIVITY_TYPE.TASK,
30117
+ P6_ACTIVITY_TYPE.RESOURCE_DEPENDENT
30118
+ ]);
30119
+ var NO_ROLLED_EXPECTED_FINISHES = /* @__PURE__ */ new Map();
30120
+ function planRolledExpectedFinishes(input) {
30121
+ const adoptsShownFinishes = input.settings.useExpectedFinishDates && rollsDataDateForward(input.previousSettings, input.settings);
30122
+ if (!adoptsShownFinishes) return NO_ROLLED_EXPECTED_FINISHES;
30123
+ const pinned = /* @__PURE__ */ new Map();
30124
+ for (const [activityId, state] of input.activities) {
30125
+ const shownFinish = state.remainingEarlyFinish;
30126
+ if (shownFinish === null) continue;
30127
+ if (!needsExpectedFinishPin(state)) continue;
30128
+ pinned.set(activityId, new Date(shownFinish));
30129
+ }
30130
+ return pinned;
30131
+ }
30132
+ function rollsDataDateForward(previous, next) {
30133
+ const previousDataDate = previous.dataDate;
30134
+ const nextDataDate = next.dataDate;
30135
+ if (previousDataDate === null || nextDataDate === null) return false;
30136
+ return nextDataDate.getTime() > previousDataDate.getTime();
30137
+ }
30138
+ function needsExpectedFinishPin(state) {
30139
+ return state.executionStatus === P6_EXECUTION_STATUS.IN_PROGRESS && state.expectedFinish === null && hasPinnableActivityType(state);
30140
+ }
30141
+ function hasPinnableActivityType(state) {
30142
+ const { activityType } = state;
30143
+ if (activityType === void 0) return true;
30144
+ return PINNABLE_ACTIVITY_TYPES.has(activityType);
30145
+ }
30146
+
30021
30147
  // src/p6/commands.ts
30022
30148
  function dataDateScheduleReadiness(input) {
30023
30149
  const preflight = getP6SchedulePreflight({
@@ -30131,8 +30257,17 @@ function prepareDataDateSchedule(input) {
30131
30257
  preflight.settings,
30132
30258
  beforeProjectState
30133
30259
  );
30260
+ const rolledExpectedFinishes = planRolledExpectedFinishes({
30261
+ previousSettings: preflight.previousSettings,
30262
+ settings: preflight.settings,
30263
+ activities: preflight.activities
30264
+ });
30134
30265
  const calculated = calculateP6Schedule({
30135
- adapter: calculationState(input.state, preflight.activities),
30266
+ adapter: calculationState(
30267
+ input.state,
30268
+ preflight.activities,
30269
+ rolledExpectedFinishes
30270
+ ),
30136
30271
  settings: preflight.settings,
30137
30272
  project: {
30138
30273
  projectId: String(input.sector.projectId),
@@ -30178,11 +30313,19 @@ function requireProjectState(projectState) {
30178
30313
  "P6 capability is not enabled for this Schedule"
30179
30314
  );
30180
30315
  }
30181
- function calculationState(current, schedulableActivities) {
30316
+ function calculationState(current, schedulableActivities, rolledExpectedFinishes) {
30182
30317
  const state = current.fork();
30183
30318
  state.forEachActivity((activity) => {
30184
- if (schedulableActivities.has(activity.id)) return;
30185
- state.setActivityField(activity.id, "p6", void 0);
30319
+ if (!schedulableActivities.has(activity.id)) {
30320
+ state.setActivityField(activity.id, "p6", void 0);
30321
+ return;
30322
+ }
30323
+ const rolledExpectedFinish = rolledExpectedFinishes.get(activity.id);
30324
+ if (rolledExpectedFinish === void 0 || activity.p6 === void 0) return;
30325
+ state.setActivityField(activity.id, "p6", {
30326
+ ...activity.p6,
30327
+ expectedFinish: rolledExpectedFinish
30328
+ });
30186
30329
  });
30187
30330
  return state;
30188
30331
  }
@@ -30307,7 +30450,7 @@ function initializeDataDate(input, activities) {
30307
30450
  function attachImportedDataDate(activities, imported, settings) {
30308
30451
  for (const activity of activities) {
30309
30452
  const importedP6 = Object.hasOwn(imported, activity.id) ? imported[activity.id] : void 0;
30310
- const p6 = importedP6 ?? createOutbuildP6ActivityDraft(activity);
30453
+ const p6 = importedP6 === void 0 ? createOutbuildP6ActivityDraft(activity) : reconcileOutbuildProgress(activity, importedP6);
30311
30454
  if (p6 === void 0) continue;
30312
30455
  activity.p6 = p6;
30313
30456
  if (!hasActiveDataDate(settings)) continue;