@outbuild-company/schedule-core 1.35.1 → 1.35.3

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
@@ -13002,11 +13002,6 @@ function rejectedGesture(editability) {
13002
13002
  ...editability.alertKey ? { alertKey: editability.alertKey } : {}
13003
13003
  };
13004
13004
  }
13005
- var STATUS_ONLY_CHANGES = {
13006
- patch: {},
13007
- needsAutoSchedule: false,
13008
- postProcessors: []
13009
- };
13010
13005
  function resolveDataDateProgressButton(activity, progress, progressChanges, deps, confirmations) {
13011
13006
  const governsProgress = deps.dataDateCapability !== null && hasActiveDataDate(deps.dataDateSettings, deps.dataDateEnabled);
13012
13007
  if (!governsProgress) return null;
@@ -13020,19 +13015,19 @@ function resolveDataDateProgressButton(activity, progress, progressChanges, deps
13020
13015
  if (activity.p6 === void 0) return null;
13021
13016
  return resolveDataDateFieldChanges(
13022
13017
  activity,
13023
- progressChanges ?? STATUS_ONLY_CHANGES,
13018
+ progressChanges,
13024
13019
  deps,
13025
13020
  COLUMN.PROGRESS,
13026
13021
  confirmations,
13027
13022
  {
13028
13023
  progress,
13029
13024
  explicitCompletion: true,
13030
- writesProgress: progressChanges !== null
13025
+ writesProgress: activity.p6.percentCompleteType !== P6_PERCENT_COMPLETE_TYPE.UNITS
13031
13026
  }
13032
13027
  );
13033
13028
  }
13034
13029
  function resolveDataDateFieldChanges(activity, changes, deps, column, confirmations, progressRouting, editedStart = null) {
13035
- const generic = routeGenericColumnEdit(
13030
+ const routed = routeGenericColumnEdit(
13036
13031
  activity,
13037
13032
  routeConstraintTypeEdit(activity, changes, column),
13038
13033
  deps,
@@ -13041,9 +13036,8 @@ function resolveDataDateFieldChanges(activity, changes, deps, column, confirmati
13041
13036
  progressRouting,
13042
13037
  editedStart
13043
13038
  );
13044
- if (generic === null) return rejected(REJECTION_REASON.INVALID_VALUE);
13045
- if ("accepted" in generic) return generic;
13046
- const routed = limitTransitionActuals(activity, generic, deps, column);
13039
+ if (routed === null) return rejected(REJECTION_REASON.INVALID_VALUE);
13040
+ if ("accepted" in routed) return routed;
13047
13041
  if (routed.dataDatePatch === void 0) return accepted(routed);
13048
13042
  if (activity.p6 === void 0 || deps.dataDateCapability === null) {
13049
13043
  return rejected(REJECTION_REASON.INVALID_VALUE);
@@ -13103,32 +13097,6 @@ function withMirroredProgress(activity, changes, next, deps, column) {
13103
13097
  }
13104
13098
  return { ...changes, patch: { ...changes.patch, progress: percent } };
13105
13099
  }
13106
- var EXECUTION_TRANSITION_COLUMNS = /* @__PURE__ */ new Set([
13107
- COLUMN.PROGRESS,
13108
- COLUMN.EXECUTION_STATUS,
13109
- COLUMN.PHYSICAL_PERCENT_COMPLETE
13110
- ]);
13111
- function limitTransitionActuals(activity, changes, deps, column) {
13112
- const dataDate = deps.dataDateSettings?.dataDate ?? null;
13113
- if (!EXECUTION_TRANSITION_COLUMNS.has(column) || dataDate === null || changes.dataDatePatch === void 0 || !hasActiveDataDate(deps.dataDateSettings, deps.dataDateEnabled)) {
13114
- return changes;
13115
- }
13116
- const calendar = deps.ctx.calendars.getCalendar(activity.calendarId);
13117
- const limit = calendar === null ? { start: dataDate, finish: dataDate } : completionActualsLimit(dataDate, calendar);
13118
- const recordedStart = activity.p6?.actualStart ?? null;
13119
- const floor = recordedStart !== null && recordedStart <= dataDate ? recordedStart : null;
13120
- const alternative = changes.confirmationAlternative;
13121
- return {
13122
- ...changes,
13123
- dataDatePatch: capActualDates(changes.dataDatePatch, floor, limit),
13124
- ...alternative === void 0 ? {} : {
13125
- confirmationAlternative: {
13126
- ...alternative,
13127
- effects: capActualDates(alternative.effects, floor, limit)
13128
- }
13129
- }
13130
- };
13131
- }
13132
13100
  function accepted(changes) {
13133
13101
  return { accepted: true, changes };
13134
13102
  }
@@ -16117,6 +16085,315 @@ async function dispatchHistoryRevert(action, options, deps) {
16117
16085
  };
16118
16086
  }
16119
16087
 
16088
+ // src/p6/status.ts
16089
+ var INVALID_STATUS = "Invalid P6 activity status";
16090
+ var P6ActivityStatusError = class extends Error {
16091
+ constructor(code, reason) {
16092
+ super(`${INVALID_STATUS}: ${reason}`);
16093
+ this.code = code;
16094
+ this.name = "P6ActivityStatusError";
16095
+ }
16096
+ code;
16097
+ };
16098
+ var COMPLETED_PHYSICAL_PERCENT2 = 100;
16099
+ var NOT_STARTED_PHYSICAL_PERCENT2 = 0;
16100
+ function validateP6ActivityStatus(activity) {
16101
+ validateRemainingDuration(activity.remainingDurationHours);
16102
+ validateDates(activity);
16103
+ validateExecutionStatus(activity);
16104
+ validateDateOrder(activity);
16105
+ }
16106
+ function validateP6ActivityDraftStatus(activity) {
16107
+ validateDraftRemainingDuration(activity.remainingDurationHours);
16108
+ validateDates(activity);
16109
+ validateDraftExecutionStatus(activity);
16110
+ validateDateOrder(activity);
16111
+ }
16112
+ function normalizeP6ActualDatePatch(current, patch) {
16113
+ if (patch.actualStart === void 0 && patch.actualFinish === void 0) {
16114
+ return patch;
16115
+ }
16116
+ const actualStart = patch.actualStart !== void 0 ? patch.actualStart : current.actualStart;
16117
+ const actualFinish = patch.actualFinish !== void 0 ? patch.actualFinish : current.actualFinish;
16118
+ const executionStatus = actualFinish !== null ? "completed" : actualStart !== null ? "in_progress" : "not_started";
16119
+ const unStarting = actualStart === null && actualFinish === null && current.actualStart !== null;
16120
+ const normalized2 = {
16121
+ ...patch,
16122
+ executionStatus,
16123
+ ...actualFinish !== null ? {
16124
+ remainingDurationHours: 0,
16125
+ physicalPercentComplete: COMPLETED_PHYSICAL_PERCENT2
16126
+ } : {},
16127
+ ...unStarting ? {
16128
+ remainingDurationHours: current.originalDurationHours,
16129
+ physicalPercentComplete: NOT_STARTED_PHYSICAL_PERCENT2
16130
+ } : {}
16131
+ };
16132
+ validateP6ActivityDraftStatus({ ...current, ...normalized2 });
16133
+ return normalized2;
16134
+ }
16135
+ function validateRemainingDuration(remainingDurationHours) {
16136
+ if (!Number.isFinite(remainingDurationHours) || remainingDurationHours < 0) {
16137
+ invalid2("Remaining Duration must be finite and non-negative");
16138
+ }
16139
+ }
16140
+ function validateDraftRemainingDuration(remainingDurationHours) {
16141
+ if (remainingDurationHours === null) return;
16142
+ validateRemainingDuration(remainingDurationHours);
16143
+ }
16144
+ function validateDates(activity) {
16145
+ const dates = [
16146
+ activity.actualStart,
16147
+ activity.actualFinish,
16148
+ activity.suspendDate,
16149
+ activity.resumeDate
16150
+ ];
16151
+ if (dates.some((date2) => date2 !== null && Number.isNaN(date2.getTime()))) {
16152
+ invalid2("actual, suspend and resume dates must be valid");
16153
+ }
16154
+ }
16155
+ function validateExecutionStatus(activity) {
16156
+ const hasActualStart = activity.actualStart !== null;
16157
+ const hasActualFinish = activity.actualFinish !== null;
16158
+ if (activity.executionStatus === "not_started") {
16159
+ if (hasActualStart || hasActualFinish) {
16160
+ invalid2("not_started cannot have actual dates");
16161
+ }
16162
+ return;
16163
+ }
16164
+ if (activity.executionStatus === "in_progress") {
16165
+ if (!hasActualStart || hasActualFinish) {
16166
+ invalid2("in_progress requires Actual Start and no Actual Finish");
16167
+ }
16168
+ return;
16169
+ }
16170
+ if (!hasActualStart || !hasActualFinish || activity.remainingDurationHours !== 0) {
16171
+ invalid2(
16172
+ "completed requires Actual Start, Actual Finish and zero Remaining Duration"
16173
+ );
16174
+ }
16175
+ }
16176
+ function validateDraftExecutionStatus(activity) {
16177
+ const hasActualStart = activity.actualStart !== null;
16178
+ const hasActualFinish = activity.actualFinish !== null;
16179
+ if (hasActualFinish && !hasActualStart) {
16180
+ invalid2("Actual Finish requires Actual Start");
16181
+ }
16182
+ if (activity.executionStatus === "not_started" && hasActualStart) {
16183
+ invalid2("not_started cannot have actual dates");
16184
+ }
16185
+ if (activity.executionStatus === "in_progress" && hasActualFinish) {
16186
+ invalid2("in_progress cannot have Actual Finish");
16187
+ }
16188
+ if (activity.executionStatus === "completed" && activity.remainingDurationHours !== null && activity.remainingDurationHours !== 0) {
16189
+ invalid2("completed requires zero Remaining Duration");
16190
+ }
16191
+ }
16192
+ function validateDateOrder(activity) {
16193
+ const { actualStart, actualFinish, suspendDate, resumeDate } = activity;
16194
+ if (actualStart !== null && actualFinish !== null && actualStart.getTime() > actualFinish.getTime()) {
16195
+ throw new P6ActivityStatusError(
16196
+ "ACTUAL_FINISH_BEFORE_START",
16197
+ "Actual Start must not be after Actual Finish"
16198
+ );
16199
+ }
16200
+ if (suspendDate !== null) {
16201
+ if (actualStart === null) invalid2("Suspend requires Actual Start");
16202
+ if (actualStart.getTime() > suspendDate.getTime()) {
16203
+ invalid2("Suspend must not be before Actual Start");
16204
+ }
16205
+ if (actualFinish !== null && suspendDate.getTime() > actualFinish.getTime()) {
16206
+ invalid2("Suspend must not be after Actual Finish");
16207
+ }
16208
+ }
16209
+ if (resumeDate === null) return;
16210
+ if (suspendDate === null) invalid2("Resume requires Suspend");
16211
+ if (suspendDate.getTime() > resumeDate.getTime()) {
16212
+ invalid2("Suspend must not be after Resume");
16213
+ }
16214
+ if (actualFinish !== null && resumeDate.getTime() > actualFinish.getTime()) {
16215
+ invalid2("Resume must not be after Actual Finish");
16216
+ }
16217
+ }
16218
+ function invalid2(reason) {
16219
+ throw new Error(`${INVALID_STATUS}: ${reason}`);
16220
+ }
16221
+
16222
+ // src/p6/lookahead-progress-transition.ts
16223
+ var LookaheadActualTransitionError = class extends Error {
16224
+ constructor(code) {
16225
+ super(code);
16226
+ this.code = code;
16227
+ this.name = "LookaheadActualTransitionError";
16228
+ }
16229
+ code;
16230
+ };
16231
+ var ACTUAL_DAY_PATTERN = /^(\d{4})-(\d{2})-(\d{2})$/;
16232
+ function resolveLookaheadActualDateSelection(input) {
16233
+ if (!validDate(input.timeSource)) invalid3("INVALID_ACTUAL_TIME_SOURCE");
16234
+ const parts = actualDayParts(input.selectedDay);
16235
+ const [year, month, day] = parts;
16236
+ const result = new Date(input.timeSource);
16237
+ result.setUTCFullYear(year, month - 1, day);
16238
+ if (!sameUtcDay2(result, parts)) invalid3("INVALID_ACTUAL_DAY");
16239
+ return result;
16240
+ }
16241
+ function actualDayParts(value) {
16242
+ const match = ACTUAL_DAY_PATTERN.exec(value);
16243
+ if (!match) invalid3("INVALID_ACTUAL_DAY");
16244
+ return [Number(match[1]), Number(match[2]), Number(match[3])];
16245
+ }
16246
+ function planLookaheadProgressActuals(input) {
16247
+ validateInput(input);
16248
+ if (input.dataDate === null) {
16249
+ return { start: null, finish: null, uncomplete: null };
16250
+ }
16251
+ const start = input.activity.executionStatus === P6_EXECUTION_STATUS.NOT_STARTED && input.nextProgress > NOT_STARTED_PROGRESS ? { required: true, suggestedAt: new Date(input.dataDate) } : null;
16252
+ const finish = input.activity.executionStatus !== P6_EXECUTION_STATUS.COMPLETED && input.nextProgress === COMPLETED_PROGRESS ? {
16253
+ optional: true,
16254
+ suggestedAt: input.displayedFinish ? new Date(input.displayedFinish) : null
16255
+ } : null;
16256
+ const uncomplete = input.activity.executionStatus === P6_EXECUTION_STATUS.COMPLETED && input.activity.actualFinish !== null && input.nextProgress < COMPLETED_PROGRESS ? {
16257
+ required: true,
16258
+ removes: new Date(input.activity.actualFinish)
16259
+ } : null;
16260
+ return { start, finish, uncomplete };
16261
+ }
16262
+ function prepareLookaheadProgressActuals(input) {
16263
+ const plan = planLookaheadProgressActuals(input);
16264
+ if (input.dataDate === null && hasIntent(input.intent)) {
16265
+ invalid3("DATA_DATE_INACTIVE");
16266
+ }
16267
+ if (plan.start && input.intent?.actualStart === void 0) {
16268
+ invalid3("ACTUAL_START_REQUIRED");
16269
+ }
16270
+ if (input.intent?.actualFinish instanceof Date && input.nextProgress !== COMPLETED_PROGRESS) {
16271
+ invalid3("ACTUAL_FINISH_REQUIRES_100");
16272
+ }
16273
+ if (plan.uncomplete && input.intent?.actualFinish !== null) {
16274
+ invalid3("ACTUAL_FINISH_REMOVAL_REQUIRED");
16275
+ }
16276
+ if (!plan.uncomplete && input.intent?.actualFinish === null) {
16277
+ invalid3("ACTUAL_FINISH_REMOVAL_NOT_APPLICABLE");
16278
+ }
16279
+ const actuals = normalizeP6ActualDatePatch(
16280
+ input.activity,
16281
+ input.intent ?? {}
16282
+ );
16283
+ if (input.dataDate === null) return actuals;
16284
+ return withMirroredPercent(input.activity, actuals, input.nextProgress);
16285
+ }
16286
+ function prepareLookaheadProgressActualsForPersistence(input) {
16287
+ const { resolveCalendar, ...progressInput } = input;
16288
+ const preparedActuals = prepareLookaheadProgressActuals(progressInput);
16289
+ if (input.dataDate === null) {
16290
+ return preparedActuals;
16291
+ }
16292
+ const actualStart = input.intent?.actualStart;
16293
+ const needsActualStartNormalization = actualStart !== void 0 && isUtcMidnight(actualStart);
16294
+ if (!needsActualStartNormalization) return preparedActuals;
16295
+ const intent = normalizeActualStart(
16296
+ input.intent,
16297
+ input.activity.actualStart,
16298
+ resolveCalendar
16299
+ );
16300
+ if (intent === void 0) {
16301
+ return preparedActuals;
16302
+ }
16303
+ return prepareLookaheadProgressActuals({ ...progressInput, intent });
16304
+ }
16305
+ function normalizeActualStart(intent, existingActualStart, resolveCalendar) {
16306
+ if (intent?.actualStart === void 0) return intent;
16307
+ if (!isUtcMidnight(intent.actualStart)) return intent;
16308
+ if (existingActualStart !== null) {
16309
+ return {
16310
+ ...intent,
16311
+ actualStart: resolveLookaheadActualDateSelection({
16312
+ selectedDay: intent.actualStart.toISOString().slice(0, 10),
16313
+ timeSource: existingActualStart
16314
+ })
16315
+ };
16316
+ }
16317
+ const calendar = resolveCalendar();
16318
+ if (calendar === null) {
16319
+ invalid3("ACTIVITY_CALENDAR_REQUIRED");
16320
+ }
16321
+ return {
16322
+ ...intent,
16323
+ actualStart: defaultActualStartTime(intent.actualStart, calendar)
16324
+ };
16325
+ }
16326
+ function withMirroredPercent(activity, actuals, nextProgress) {
16327
+ const executionStatus = actuals.executionStatus ?? activity.executionStatus;
16328
+ if (executionStatus !== P6_EXECUTION_STATUS.IN_PROGRESS) return actuals;
16329
+ switch (activity.percentCompleteType) {
16330
+ case P6_PERCENT_COMPLETE_TYPE.PHYSICAL:
16331
+ return { ...actuals, physicalPercentComplete: nextProgress };
16332
+ case P6_PERCENT_COMPLETE_TYPE.DURATION:
16333
+ return {
16334
+ ...actuals,
16335
+ remainingDurationHours: activity.originalDurationHours * (1 - nextProgress / COMPLETED_PROGRESS)
16336
+ };
16337
+ default:
16338
+ return actuals;
16339
+ }
16340
+ }
16341
+ function validateInput(input) {
16342
+ if (!Number.isFinite(input.nextProgress) || input.nextProgress < NOT_STARTED_PROGRESS || input.nextProgress > COMPLETED_PROGRESS) {
16343
+ invalid3("INVALID_PROGRESS");
16344
+ }
16345
+ if (input.dataDate !== null && !validDate(input.dataDate)) {
16346
+ invalid3("INVALID_DATA_DATE");
16347
+ }
16348
+ if (input.displayedFinish != null && !validDate(input.displayedFinish)) {
16349
+ invalid3("INVALID_DISPLAYED_FINISH");
16350
+ }
16351
+ }
16352
+ function validDate(value) {
16353
+ return value instanceof Date && Number.isFinite(value.getTime());
16354
+ }
16355
+ function sameUtcDay2(value, [year, month, day]) {
16356
+ return value.getUTCFullYear() === year && value.getUTCMonth() === month - 1 && value.getUTCDate() === day;
16357
+ }
16358
+ function hasIntent(intent) {
16359
+ return intent?.actualStart !== void 0 || intent?.actualFinish !== void 0;
16360
+ }
16361
+ function invalid3(code) {
16362
+ throw new LookaheadActualTransitionError(code);
16363
+ }
16364
+
16365
+ // src/p6/lookahead-reconciliation.ts
16366
+ function preparePersistedLookaheadDataDate(action, activity, deps) {
16367
+ const settings = deps.dataDateSettings;
16368
+ const capability = deps.dataDateCapability;
16369
+ if (action.persistedProgress === void 0 || activity?.p6 === void 0 || capability === null || !hasActiveDataDate(settings, deps.dataDateEnabled) || settings?.dataDate == null)
16370
+ return null;
16371
+ const patch = prepareLookaheadProgressActuals({
16372
+ dataDate: settings.dataDate,
16373
+ activity: activity.p6,
16374
+ nextProgress: action.persistedProgress,
16375
+ ...action.persistedActualTransition === void 0 ? {} : { intent: action.persistedActualTransition }
16376
+ });
16377
+ const prepared = capability.prepareActivityUpdate(
16378
+ activity.p6,
16379
+ {
16380
+ originalDurationHours: activity.p6.originalDurationHours,
16381
+ ...patch.actualStart === void 0 ? {} : { actualStart: patch.actualStart },
16382
+ ...patch.actualFinish === void 0 ? {} : { actualFinish: patch.actualFinish },
16383
+ ...patch.remainingDurationHours == null ? {} : { remainingDurationHours: patch.remainingDurationHours },
16384
+ ...patch.physicalPercentComplete == null ? {} : { physicalPercentComplete: patch.physicalPercentComplete }
16385
+ },
16386
+ activity.constraintType
16387
+ );
16388
+ const calendar = deps.adapter.calendarReader.getCalendar(activity.calendarId);
16389
+ const projection = calendar === null ? { p6: prepared.next, startDate: void 0, endDate: void 0 } : capability.projectLocalActivity(activity.p6, prepared.next, {
16390
+ calendar,
16391
+ dataDate: settings.dataDate,
16392
+ totalFloatMethod: settings.totalFloatMethod
16393
+ });
16394
+ return { activityId: activity.id, ...projection };
16395
+ }
16396
+
16120
16397
  // src/dispatch/handlers/persistence-acknowledge.ts
16121
16398
  var EMPTY_OWNERS = /* @__PURE__ */ new Map();
16122
16399
  function persistedOwnersOf(entities) {
@@ -16335,6 +16612,30 @@ async function dispatchActivityLookaheadSync(action, options, deps) {
16335
16612
  if (snapshots.some((snapshot) => !snapshot)) {
16336
16613
  return { ok: false, reason: REJECTION_REASON.ACTIVITY_NOT_FOUND };
16337
16614
  }
16615
+ const persistedDataDate = preparePersistedLookaheadDataDate(
16616
+ action,
16617
+ snapshots[0],
16618
+ deps
16619
+ );
16620
+ if (persistedDataDate !== null) {
16621
+ deps.adapter.setActivityField(
16622
+ persistedDataDate.activityId,
16623
+ "p6",
16624
+ persistedDataDate.p6
16625
+ );
16626
+ if (persistedDataDate.startDate !== void 0)
16627
+ deps.adapter.setActivityField(
16628
+ persistedDataDate.activityId,
16629
+ "startDate",
16630
+ persistedDataDate.startDate
16631
+ );
16632
+ if (persistedDataDate.endDate !== void 0)
16633
+ deps.adapter.setActivityField(
16634
+ persistedDataDate.activityId,
16635
+ "endDate",
16636
+ persistedDataDate.endDate
16637
+ );
16638
+ }
16338
16639
  const activities = [];
16339
16640
  for (const [index, id] of ids.entries()) {
16340
16641
  const snapshot = snapshots[index];
@@ -16375,7 +16676,7 @@ async function dispatchActivityLookaheadSync(action, options, deps) {
16375
16676
  [{ activityId: ids[0], progress: action.persistedProgress }],
16376
16677
  deps
16377
16678
  );
16378
- if (progressChanges.length > 0) {
16679
+ if (progressChanges.length > 0 || persistedDataDate !== null) {
16379
16680
  await runPostMutation(
16380
16681
  {
16381
16682
  adapter: deps.adapter,
@@ -17617,7 +17918,7 @@ async function dispatchActivitySetProgress(action, options, deps) {
17617
17918
  const statusRouting = resolveDataDateProgressButton(
17618
17919
  activitySnap,
17619
17920
  action.newValue,
17620
- editGate.allowed ? progressChanges : null,
17921
+ progressChanges,
17621
17922
  deps,
17622
17923
  options.confirmations
17623
17924
  );
@@ -17625,17 +17926,14 @@ async function dispatchActivitySetProgress(action, options, deps) {
17625
17926
  return rejectedRouting(statusRouting);
17626
17927
  }
17627
17928
  const changes = statusRouting === null ? progressChanges : statusRouting.changes;
17628
- const writesProgress = statusRouting === null || editGate.allowed;
17629
17929
  const touchedIds = collectTouchedIds(action.activityId, changes);
17630
- const beforeSnap = await snapshotActivities(adapter, touchedIds);
17631
- applyFieldChanges(adapter, action.activityId, changes);
17632
- if (writesProgress) {
17633
- setExplicitSummaryProgressFallbacks(
17634
- action.activityId,
17635
- action.newValue,
17636
- adapter
17637
- );
17638
- }
17930
+ const beforeSnap = await snapshotActivities(adapter, touchedIds);
17931
+ applyFieldChanges(adapter, action.activityId, changes);
17932
+ setExplicitSummaryProgressFallbacks(
17933
+ action.activityId,
17934
+ action.newValue,
17935
+ adapter
17936
+ );
17639
17937
  runPostProcessorsOnAdapter(
17640
17938
  action.activityId,
17641
17939
  changes.postProcessors ?? [],
@@ -26797,140 +27095,6 @@ function changed(before, after) {
26797
27095
  return serializeCanonicalSchedule(before) !== serializeCanonicalSchedule(after);
26798
27096
  }
26799
27097
 
26800
- // src/p6/status.ts
26801
- var INVALID_STATUS = "Invalid P6 activity status";
26802
- var P6ActivityStatusError = class extends Error {
26803
- constructor(code, reason) {
26804
- super(`${INVALID_STATUS}: ${reason}`);
26805
- this.code = code;
26806
- this.name = "P6ActivityStatusError";
26807
- }
26808
- code;
26809
- };
26810
- var COMPLETED_PHYSICAL_PERCENT2 = 100;
26811
- var NOT_STARTED_PHYSICAL_PERCENT2 = 0;
26812
- function validateP6ActivityStatus(activity) {
26813
- validateRemainingDuration(activity.remainingDurationHours);
26814
- validateDates(activity);
26815
- validateExecutionStatus(activity);
26816
- validateDateOrder(activity);
26817
- }
26818
- function validateP6ActivityDraftStatus(activity) {
26819
- validateDraftRemainingDuration(activity.remainingDurationHours);
26820
- validateDates(activity);
26821
- validateDraftExecutionStatus(activity);
26822
- validateDateOrder(activity);
26823
- }
26824
- function normalizeP6ActualDatePatch(current, patch) {
26825
- if (patch.actualStart === void 0 && patch.actualFinish === void 0) {
26826
- return patch;
26827
- }
26828
- const actualStart = patch.actualStart !== void 0 ? patch.actualStart : current.actualStart;
26829
- const actualFinish = patch.actualFinish !== void 0 ? patch.actualFinish : current.actualFinish;
26830
- const executionStatus = actualFinish !== null ? "completed" : actualStart !== null ? "in_progress" : "not_started";
26831
- const unStarting = actualStart === null && actualFinish === null && current.actualStart !== null;
26832
- const normalized2 = {
26833
- ...patch,
26834
- executionStatus,
26835
- ...actualFinish !== null ? {
26836
- remainingDurationHours: 0,
26837
- physicalPercentComplete: COMPLETED_PHYSICAL_PERCENT2
26838
- } : {},
26839
- ...unStarting ? {
26840
- remainingDurationHours: current.originalDurationHours,
26841
- physicalPercentComplete: NOT_STARTED_PHYSICAL_PERCENT2
26842
- } : {}
26843
- };
26844
- validateP6ActivityDraftStatus({ ...current, ...normalized2 });
26845
- return normalized2;
26846
- }
26847
- function validateRemainingDuration(remainingDurationHours) {
26848
- if (!Number.isFinite(remainingDurationHours) || remainingDurationHours < 0) {
26849
- invalid2("Remaining Duration must be finite and non-negative");
26850
- }
26851
- }
26852
- function validateDraftRemainingDuration(remainingDurationHours) {
26853
- if (remainingDurationHours === null) return;
26854
- validateRemainingDuration(remainingDurationHours);
26855
- }
26856
- function validateDates(activity) {
26857
- const dates = [
26858
- activity.actualStart,
26859
- activity.actualFinish,
26860
- activity.suspendDate,
26861
- activity.resumeDate
26862
- ];
26863
- if (dates.some((date2) => date2 !== null && Number.isNaN(date2.getTime()))) {
26864
- invalid2("actual, suspend and resume dates must be valid");
26865
- }
26866
- }
26867
- function validateExecutionStatus(activity) {
26868
- const hasActualStart = activity.actualStart !== null;
26869
- const hasActualFinish = activity.actualFinish !== null;
26870
- if (activity.executionStatus === "not_started") {
26871
- if (hasActualStart || hasActualFinish) {
26872
- invalid2("not_started cannot have actual dates");
26873
- }
26874
- return;
26875
- }
26876
- if (activity.executionStatus === "in_progress") {
26877
- if (!hasActualStart || hasActualFinish) {
26878
- invalid2("in_progress requires Actual Start and no Actual Finish");
26879
- }
26880
- return;
26881
- }
26882
- if (!hasActualStart || !hasActualFinish || activity.remainingDurationHours !== 0) {
26883
- invalid2(
26884
- "completed requires Actual Start, Actual Finish and zero Remaining Duration"
26885
- );
26886
- }
26887
- }
26888
- function validateDraftExecutionStatus(activity) {
26889
- const hasActualStart = activity.actualStart !== null;
26890
- const hasActualFinish = activity.actualFinish !== null;
26891
- if (hasActualFinish && !hasActualStart) {
26892
- invalid2("Actual Finish requires Actual Start");
26893
- }
26894
- if (activity.executionStatus === "not_started" && hasActualStart) {
26895
- invalid2("not_started cannot have actual dates");
26896
- }
26897
- if (activity.executionStatus === "in_progress" && hasActualFinish) {
26898
- invalid2("in_progress cannot have Actual Finish");
26899
- }
26900
- if (activity.executionStatus === "completed" && activity.remainingDurationHours !== null && activity.remainingDurationHours !== 0) {
26901
- invalid2("completed requires zero Remaining Duration");
26902
- }
26903
- }
26904
- function validateDateOrder(activity) {
26905
- const { actualStart, actualFinish, suspendDate, resumeDate } = activity;
26906
- if (actualStart !== null && actualFinish !== null && actualStart.getTime() > actualFinish.getTime()) {
26907
- throw new P6ActivityStatusError(
26908
- "ACTUAL_FINISH_BEFORE_START",
26909
- "Actual Start must not be after Actual Finish"
26910
- );
26911
- }
26912
- if (suspendDate !== null) {
26913
- if (actualStart === null) invalid2("Suspend requires Actual Start");
26914
- if (actualStart.getTime() > suspendDate.getTime()) {
26915
- invalid2("Suspend must not be before Actual Start");
26916
- }
26917
- if (actualFinish !== null && suspendDate.getTime() > actualFinish.getTime()) {
26918
- invalid2("Suspend must not be after Actual Finish");
26919
- }
26920
- }
26921
- if (resumeDate === null) return;
26922
- if (suspendDate === null) invalid2("Resume requires Suspend");
26923
- if (suspendDate.getTime() > resumeDate.getTime()) {
26924
- invalid2("Suspend must not be after Resume");
26925
- }
26926
- if (actualFinish !== null && resumeDate.getTime() > actualFinish.getTime()) {
26927
- invalid2("Resume must not be after Actual Finish");
26928
- }
26929
- }
26930
- function invalid2(reason) {
26931
- throw new Error(`${INVALID_STATUS}: ${reason}`);
26932
- }
26933
-
26934
27098
  // src/p6/derived.ts
26935
27099
  var INVALID_INPUT = "Invalid P6 derived input";
26936
27100
  var PERCENT_SCALE2 = 100;
@@ -30899,48 +31063,48 @@ function optionalP6Date(rawValue) {
30899
31063
  function yesNo(rawValue) {
30900
31064
  if (rawValue === "Y") return true;
30901
31065
  if (rawValue === "N") return false;
30902
- invalid3(`expected Y/N, got "${rawValue}"`);
31066
+ invalid4(`expected Y/N, got "${rawValue}"`);
30903
31067
  }
30904
31068
  function enumValue(map, rawValue) {
30905
- if (!Object.hasOwn(map, rawValue)) invalid3(`unknown enum "${rawValue}"`);
31069
+ if (!Object.hasOwn(map, rawValue)) invalid4(`unknown enum "${rawValue}"`);
30906
31070
  return map[rawValue];
30907
31071
  }
30908
31072
  function outOfSequencePolicy(schedule) {
30909
31073
  const retained = yesNo(schedule.sched_retained_logic);
30910
31074
  const override = yesNo(schedule.sched_progress_override);
30911
31075
  if (retained && override)
30912
- invalid3("retained logic and progress override conflict");
31076
+ invalid4("retained logic and progress override conflict");
30913
31077
  if (retained) return "retained_logic";
30914
31078
  if (override) return "progress_override";
30915
31079
  return "actual_dates";
30916
31080
  }
30917
31081
  function nonNegativeNumber(rawValue, field) {
30918
31082
  const value = finiteNumber(rawValue, field);
30919
- if (value < 0) invalid3(`${field} must be a non-negative number`);
31083
+ if (value < 0) invalid4(`${field} must be a non-negative number`);
30920
31084
  return value;
30921
31085
  }
30922
31086
  function finiteNumber(rawValue, field) {
30923
31087
  const value = Number(rawValue);
30924
31088
  if (rawValue === "" || !Number.isFinite(value)) {
30925
- invalid3(`${field} must be a finite number`);
31089
+ invalid4(`${field} must be a finite number`);
30926
31090
  }
30927
31091
  return value;
30928
31092
  }
30929
31093
  function nonNegativeInteger(rawValue, field) {
30930
31094
  const value = nonNegativeNumber(rawValue, field);
30931
- if (!Number.isInteger(value)) invalid3(`${field} must be an integer`);
31095
+ if (!Number.isInteger(value)) invalid4(`${field} must be an integer`);
30932
31096
  return value;
30933
31097
  }
30934
31098
  function endingActivityId(rawValue, resolveActivityId) {
30935
31099
  if (rawValue === "") return null;
30936
31100
  if (resolveActivityId === void 0) {
30937
- invalid3("ending activity requires an ID resolver");
31101
+ invalid4("ending activity requires an ID resolver");
30938
31102
  }
30939
31103
  const resolved = resolveActivityId(rawValue);
30940
- if (resolved === null) invalid3(`unknown ending activity "${rawValue}"`);
31104
+ if (resolved === null) invalid4(`unknown ending activity "${rawValue}"`);
30941
31105
  return resolved;
30942
31106
  }
30943
- function invalid3(reason) {
31107
+ function invalid4(reason) {
30944
31108
  throw new Error(`${INVALID_OPTIONS}: ${reason}`);
30945
31109
  }
30946
31110
 
@@ -30971,13 +31135,11 @@ function prepareDataDateSettingsUpdate(current, patch) {
30971
31135
  };
30972
31136
  }
30973
31137
  function prepareDataDateActivityUpdate(current, patch, primaryConstraint) {
30974
- const percentRouted = routeActivityPercentComplete(current, patch);
31138
+ const synchronized = synchronizeNotStartedDurations(current, patch);
31139
+ const percentRouted = routeActivityPercentComplete(current, synchronized);
30975
31140
  validateActivityInputPatch(percentRouted);
30976
31141
  validateSecondaryConstraint(percentRouted, primaryConstraint);
30977
- const routed = synchronizeNotStartedDurations(
30978
- current,
30979
- gatePhysicalPercentInput(current, percentRouted)
30980
- );
31142
+ const routed = gatePhysicalPercentInput(current, percentRouted);
30981
31143
  const next = cloneDomainValue({
30982
31144
  ...current,
30983
31145
  ...routed,
@@ -30990,7 +31152,7 @@ function prepareDataDateActivityUpdate(current, patch, primaryConstraint) {
30990
31152
  };
30991
31153
  }
30992
31154
  function synchronizeNotStartedDurations(current, patch) {
30993
- if (current.executionStatus !== P6_EXECUTION_STATUS.NOT_STARTED || patch.remainingDurationHours === void 0 || patch.originalDurationHours !== void 0) {
31155
+ if (current.executionStatus !== P6_EXECUTION_STATUS.NOT_STARTED || patch.actualStart != null || patch.activityPercentComplete !== void 0 || patch.remainingDurationHours === void 0 || patch.originalDurationHours !== void 0) {
30994
31156
  return patch;
30995
31157
  }
30996
31158
  return {
@@ -31035,7 +31197,7 @@ function routeActivityPercentComplete(current, patch) {
31035
31197
  case P6_PERCENT_COMPLETE_TYPE.DURATION:
31036
31198
  return {
31037
31199
  ...rest,
31038
- remainingDurationHours: current.originalDurationHours * (1 - activityPercentComplete / FULL_PERCENT)
31200
+ remainingDurationHours: (rest.originalDurationHours ?? current.originalDurationHours) * (1 - activityPercentComplete / FULL_PERCENT)
31039
31201
  };
31040
31202
  case P6_PERCENT_COMPLETE_TYPE.UNITS:
31041
31203
  throw new Error(
@@ -37210,136 +37372,6 @@ function serializeP6State(input) {
37210
37372
  // src/boundary/save/unsaved.ts
37211
37373
  var getUnsavedActivities = (activities = []) => (activities ?? []).filter((activity) => !activity.proplannerId);
37212
37374
 
37213
- // src/p6/lookahead-progress-transition.ts
37214
- var LookaheadActualTransitionError = class extends Error {
37215
- constructor(code) {
37216
- super(code);
37217
- this.code = code;
37218
- this.name = "LookaheadActualTransitionError";
37219
- }
37220
- code;
37221
- };
37222
- var ACTUAL_DAY_PATTERN = /^(\d{4})-(\d{2})-(\d{2})$/;
37223
- function resolveLookaheadActualDateSelection(input) {
37224
- if (!validDate(input.timeSource)) invalid4("INVALID_ACTUAL_TIME_SOURCE");
37225
- const parts = actualDayParts(input.selectedDay);
37226
- const [year, month, day] = parts;
37227
- const result = new Date(input.timeSource);
37228
- result.setUTCFullYear(year, month - 1, day);
37229
- if (!sameUtcDay2(result, parts)) invalid4("INVALID_ACTUAL_DAY");
37230
- return result;
37231
- }
37232
- function actualDayParts(value) {
37233
- const match = ACTUAL_DAY_PATTERN.exec(value);
37234
- if (!match) invalid4("INVALID_ACTUAL_DAY");
37235
- return [Number(match[1]), Number(match[2]), Number(match[3])];
37236
- }
37237
- function planLookaheadProgressActuals(input) {
37238
- validateInput(input);
37239
- if (input.dataDate === null) {
37240
- return { start: null, finish: null, uncomplete: null };
37241
- }
37242
- const start = input.activity.executionStatus === P6_EXECUTION_STATUS.NOT_STARTED && input.nextProgress > NOT_STARTED_PROGRESS ? { required: true, suggestedAt: new Date(input.dataDate) } : null;
37243
- const finish = input.activity.executionStatus !== P6_EXECUTION_STATUS.COMPLETED && input.nextProgress === COMPLETED_PROGRESS ? {
37244
- optional: true,
37245
- suggestedAt: input.displayedFinish ? new Date(input.displayedFinish) : null
37246
- } : null;
37247
- const uncomplete = input.activity.executionStatus === P6_EXECUTION_STATUS.COMPLETED && input.activity.actualFinish !== null && input.nextProgress < COMPLETED_PROGRESS ? {
37248
- required: true,
37249
- removes: new Date(input.activity.actualFinish)
37250
- } : null;
37251
- return { start, finish, uncomplete };
37252
- }
37253
- function prepareLookaheadProgressActuals(input) {
37254
- const plan = planLookaheadProgressActuals(input);
37255
- if (input.dataDate === null && hasIntent(input.intent)) {
37256
- invalid4("DATA_DATE_INACTIVE");
37257
- }
37258
- if (plan.start && input.intent?.actualStart === void 0) {
37259
- invalid4("ACTUAL_START_REQUIRED");
37260
- }
37261
- if (input.intent?.actualFinish instanceof Date && input.nextProgress !== COMPLETED_PROGRESS) {
37262
- invalid4("ACTUAL_FINISH_REQUIRES_100");
37263
- }
37264
- if (plan.uncomplete && input.intent?.actualFinish !== null) {
37265
- invalid4("ACTUAL_FINISH_REMOVAL_REQUIRED");
37266
- }
37267
- if (!plan.uncomplete && input.intent?.actualFinish === null) {
37268
- invalid4("ACTUAL_FINISH_REMOVAL_NOT_APPLICABLE");
37269
- }
37270
- const actuals = normalizeP6ActualDatePatch(
37271
- input.activity,
37272
- input.intent ?? {}
37273
- );
37274
- if (input.dataDate === null) return actuals;
37275
- return withMirroredPercent(input.activity, actuals, input.nextProgress);
37276
- }
37277
- function prepareLookaheadProgressActualsForPersistence(input) {
37278
- const { resolveCalendar, ...progressInput } = input;
37279
- const preparedActuals = prepareLookaheadProgressActuals(progressInput);
37280
- if (input.dataDate === null) {
37281
- return preparedActuals;
37282
- }
37283
- const actualStart = input.intent?.actualStart;
37284
- const needsActualStartNormalization = actualStart !== void 0 && isUtcMidnight(actualStart);
37285
- if (!needsActualStartNormalization) return preparedActuals;
37286
- const intent = normalizeActualStart(input.intent, resolveCalendar);
37287
- if (intent === void 0) {
37288
- return preparedActuals;
37289
- }
37290
- return prepareLookaheadProgressActuals({ ...progressInput, intent });
37291
- }
37292
- function normalizeActualStart(intent, resolveCalendar) {
37293
- if (intent?.actualStart === void 0) return intent;
37294
- if (!isUtcMidnight(intent.actualStart)) return intent;
37295
- const calendar = resolveCalendar();
37296
- if (calendar === null) {
37297
- invalid4("ACTIVITY_CALENDAR_REQUIRED");
37298
- }
37299
- return {
37300
- ...intent,
37301
- actualStart: defaultActualStartTime(intent.actualStart, calendar)
37302
- };
37303
- }
37304
- function withMirroredPercent(activity, actuals, nextProgress) {
37305
- const executionStatus = actuals.executionStatus ?? activity.executionStatus;
37306
- if (executionStatus !== P6_EXECUTION_STATUS.IN_PROGRESS) return actuals;
37307
- switch (activity.percentCompleteType) {
37308
- case P6_PERCENT_COMPLETE_TYPE.PHYSICAL:
37309
- return { ...actuals, physicalPercentComplete: nextProgress };
37310
- case P6_PERCENT_COMPLETE_TYPE.DURATION:
37311
- return {
37312
- ...actuals,
37313
- remainingDurationHours: activity.originalDurationHours * (1 - nextProgress / COMPLETED_PROGRESS)
37314
- };
37315
- default:
37316
- return actuals;
37317
- }
37318
- }
37319
- function validateInput(input) {
37320
- if (!Number.isFinite(input.nextProgress) || input.nextProgress < NOT_STARTED_PROGRESS || input.nextProgress > COMPLETED_PROGRESS) {
37321
- invalid4("INVALID_PROGRESS");
37322
- }
37323
- if (input.dataDate !== null && !validDate(input.dataDate)) {
37324
- invalid4("INVALID_DATA_DATE");
37325
- }
37326
- if (input.displayedFinish != null && !validDate(input.displayedFinish)) {
37327
- invalid4("INVALID_DISPLAYED_FINISH");
37328
- }
37329
- }
37330
- function validDate(value) {
37331
- return value instanceof Date && Number.isFinite(value.getTime());
37332
- }
37333
- function sameUtcDay2(value, [year, month, day]) {
37334
- return value.getUTCFullYear() === year && value.getUTCMonth() === month - 1 && value.getUTCDate() === day;
37335
- }
37336
- function hasIntent(intent) {
37337
- return intent?.actualStart !== void 0 || intent?.actualFinish !== void 0;
37338
- }
37339
- function invalid4(code) {
37340
- throw new LookaheadActualTransitionError(code);
37341
- }
37342
-
37343
37375
  // src/p6/expected-finish-selection.ts
37344
37376
  function resolveExpectedFinishDateSelection(input) {
37345
37377
  return resolveLookaheadActualDateSelection({