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