@outbuild-company/schedule-core 1.35.0 → 1.35.2

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,14 +13015,14 @@ 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
  }
@@ -16117,6 +16112,315 @@ async function dispatchHistoryRevert(action, options, deps) {
16117
16112
  };
16118
16113
  }
16119
16114
 
16115
+ // src/p6/status.ts
16116
+ var INVALID_STATUS = "Invalid P6 activity status";
16117
+ var P6ActivityStatusError = class extends Error {
16118
+ constructor(code, reason) {
16119
+ super(`${INVALID_STATUS}: ${reason}`);
16120
+ this.code = code;
16121
+ this.name = "P6ActivityStatusError";
16122
+ }
16123
+ code;
16124
+ };
16125
+ var COMPLETED_PHYSICAL_PERCENT2 = 100;
16126
+ var NOT_STARTED_PHYSICAL_PERCENT2 = 0;
16127
+ function validateP6ActivityStatus(activity) {
16128
+ validateRemainingDuration(activity.remainingDurationHours);
16129
+ validateDates(activity);
16130
+ validateExecutionStatus(activity);
16131
+ validateDateOrder(activity);
16132
+ }
16133
+ function validateP6ActivityDraftStatus(activity) {
16134
+ validateDraftRemainingDuration(activity.remainingDurationHours);
16135
+ validateDates(activity);
16136
+ validateDraftExecutionStatus(activity);
16137
+ validateDateOrder(activity);
16138
+ }
16139
+ function normalizeP6ActualDatePatch(current, patch) {
16140
+ if (patch.actualStart === void 0 && patch.actualFinish === void 0) {
16141
+ return patch;
16142
+ }
16143
+ const actualStart = patch.actualStart !== void 0 ? patch.actualStart : current.actualStart;
16144
+ const actualFinish = patch.actualFinish !== void 0 ? patch.actualFinish : current.actualFinish;
16145
+ const executionStatus = actualFinish !== null ? "completed" : actualStart !== null ? "in_progress" : "not_started";
16146
+ const unStarting = actualStart === null && actualFinish === null && current.actualStart !== null;
16147
+ const normalized2 = {
16148
+ ...patch,
16149
+ executionStatus,
16150
+ ...actualFinish !== null ? {
16151
+ remainingDurationHours: 0,
16152
+ physicalPercentComplete: COMPLETED_PHYSICAL_PERCENT2
16153
+ } : {},
16154
+ ...unStarting ? {
16155
+ remainingDurationHours: current.originalDurationHours,
16156
+ physicalPercentComplete: NOT_STARTED_PHYSICAL_PERCENT2
16157
+ } : {}
16158
+ };
16159
+ validateP6ActivityDraftStatus({ ...current, ...normalized2 });
16160
+ return normalized2;
16161
+ }
16162
+ function validateRemainingDuration(remainingDurationHours) {
16163
+ if (!Number.isFinite(remainingDurationHours) || remainingDurationHours < 0) {
16164
+ invalid2("Remaining Duration must be finite and non-negative");
16165
+ }
16166
+ }
16167
+ function validateDraftRemainingDuration(remainingDurationHours) {
16168
+ if (remainingDurationHours === null) return;
16169
+ validateRemainingDuration(remainingDurationHours);
16170
+ }
16171
+ function validateDates(activity) {
16172
+ const dates = [
16173
+ activity.actualStart,
16174
+ activity.actualFinish,
16175
+ activity.suspendDate,
16176
+ activity.resumeDate
16177
+ ];
16178
+ if (dates.some((date2) => date2 !== null && Number.isNaN(date2.getTime()))) {
16179
+ invalid2("actual, suspend and resume dates must be valid");
16180
+ }
16181
+ }
16182
+ function validateExecutionStatus(activity) {
16183
+ const hasActualStart = activity.actualStart !== null;
16184
+ const hasActualFinish = activity.actualFinish !== null;
16185
+ if (activity.executionStatus === "not_started") {
16186
+ if (hasActualStart || hasActualFinish) {
16187
+ invalid2("not_started cannot have actual dates");
16188
+ }
16189
+ return;
16190
+ }
16191
+ if (activity.executionStatus === "in_progress") {
16192
+ if (!hasActualStart || hasActualFinish) {
16193
+ invalid2("in_progress requires Actual Start and no Actual Finish");
16194
+ }
16195
+ return;
16196
+ }
16197
+ if (!hasActualStart || !hasActualFinish || activity.remainingDurationHours !== 0) {
16198
+ invalid2(
16199
+ "completed requires Actual Start, Actual Finish and zero Remaining Duration"
16200
+ );
16201
+ }
16202
+ }
16203
+ function validateDraftExecutionStatus(activity) {
16204
+ const hasActualStart = activity.actualStart !== null;
16205
+ const hasActualFinish = activity.actualFinish !== null;
16206
+ if (hasActualFinish && !hasActualStart) {
16207
+ invalid2("Actual Finish requires Actual Start");
16208
+ }
16209
+ if (activity.executionStatus === "not_started" && hasActualStart) {
16210
+ invalid2("not_started cannot have actual dates");
16211
+ }
16212
+ if (activity.executionStatus === "in_progress" && hasActualFinish) {
16213
+ invalid2("in_progress cannot have Actual Finish");
16214
+ }
16215
+ if (activity.executionStatus === "completed" && activity.remainingDurationHours !== null && activity.remainingDurationHours !== 0) {
16216
+ invalid2("completed requires zero Remaining Duration");
16217
+ }
16218
+ }
16219
+ function validateDateOrder(activity) {
16220
+ const { actualStart, actualFinish, suspendDate, resumeDate } = activity;
16221
+ if (actualStart !== null && actualFinish !== null && actualStart.getTime() > actualFinish.getTime()) {
16222
+ throw new P6ActivityStatusError(
16223
+ "ACTUAL_FINISH_BEFORE_START",
16224
+ "Actual Start must not be after Actual Finish"
16225
+ );
16226
+ }
16227
+ if (suspendDate !== null) {
16228
+ if (actualStart === null) invalid2("Suspend requires Actual Start");
16229
+ if (actualStart.getTime() > suspendDate.getTime()) {
16230
+ invalid2("Suspend must not be before Actual Start");
16231
+ }
16232
+ if (actualFinish !== null && suspendDate.getTime() > actualFinish.getTime()) {
16233
+ invalid2("Suspend must not be after Actual Finish");
16234
+ }
16235
+ }
16236
+ if (resumeDate === null) return;
16237
+ if (suspendDate === null) invalid2("Resume requires Suspend");
16238
+ if (suspendDate.getTime() > resumeDate.getTime()) {
16239
+ invalid2("Suspend must not be after Resume");
16240
+ }
16241
+ if (actualFinish !== null && resumeDate.getTime() > actualFinish.getTime()) {
16242
+ invalid2("Resume must not be after Actual Finish");
16243
+ }
16244
+ }
16245
+ function invalid2(reason) {
16246
+ throw new Error(`${INVALID_STATUS}: ${reason}`);
16247
+ }
16248
+
16249
+ // src/p6/lookahead-progress-transition.ts
16250
+ var LookaheadActualTransitionError = class extends Error {
16251
+ constructor(code) {
16252
+ super(code);
16253
+ this.code = code;
16254
+ this.name = "LookaheadActualTransitionError";
16255
+ }
16256
+ code;
16257
+ };
16258
+ var ACTUAL_DAY_PATTERN = /^(\d{4})-(\d{2})-(\d{2})$/;
16259
+ function resolveLookaheadActualDateSelection(input) {
16260
+ if (!validDate(input.timeSource)) invalid3("INVALID_ACTUAL_TIME_SOURCE");
16261
+ const parts = actualDayParts(input.selectedDay);
16262
+ const [year, month, day] = parts;
16263
+ const result = new Date(input.timeSource);
16264
+ result.setUTCFullYear(year, month - 1, day);
16265
+ if (!sameUtcDay2(result, parts)) invalid3("INVALID_ACTUAL_DAY");
16266
+ return result;
16267
+ }
16268
+ function actualDayParts(value) {
16269
+ const match = ACTUAL_DAY_PATTERN.exec(value);
16270
+ if (!match) invalid3("INVALID_ACTUAL_DAY");
16271
+ return [Number(match[1]), Number(match[2]), Number(match[3])];
16272
+ }
16273
+ function planLookaheadProgressActuals(input) {
16274
+ validateInput(input);
16275
+ if (input.dataDate === null) {
16276
+ return { start: null, finish: null, uncomplete: null };
16277
+ }
16278
+ const start = input.activity.executionStatus === P6_EXECUTION_STATUS.NOT_STARTED && input.nextProgress > NOT_STARTED_PROGRESS ? { required: true, suggestedAt: new Date(input.dataDate) } : null;
16279
+ const finish = input.activity.executionStatus !== P6_EXECUTION_STATUS.COMPLETED && input.nextProgress === COMPLETED_PROGRESS ? {
16280
+ optional: true,
16281
+ suggestedAt: input.displayedFinish ? new Date(input.displayedFinish) : null
16282
+ } : null;
16283
+ const uncomplete = input.activity.executionStatus === P6_EXECUTION_STATUS.COMPLETED && input.activity.actualFinish !== null && input.nextProgress < COMPLETED_PROGRESS ? {
16284
+ required: true,
16285
+ removes: new Date(input.activity.actualFinish)
16286
+ } : null;
16287
+ return { start, finish, uncomplete };
16288
+ }
16289
+ function prepareLookaheadProgressActuals(input) {
16290
+ const plan = planLookaheadProgressActuals(input);
16291
+ if (input.dataDate === null && hasIntent(input.intent)) {
16292
+ invalid3("DATA_DATE_INACTIVE");
16293
+ }
16294
+ if (plan.start && input.intent?.actualStart === void 0) {
16295
+ invalid3("ACTUAL_START_REQUIRED");
16296
+ }
16297
+ if (input.intent?.actualFinish instanceof Date && input.nextProgress !== COMPLETED_PROGRESS) {
16298
+ invalid3("ACTUAL_FINISH_REQUIRES_100");
16299
+ }
16300
+ if (plan.uncomplete && input.intent?.actualFinish !== null) {
16301
+ invalid3("ACTUAL_FINISH_REMOVAL_REQUIRED");
16302
+ }
16303
+ if (!plan.uncomplete && input.intent?.actualFinish === null) {
16304
+ invalid3("ACTUAL_FINISH_REMOVAL_NOT_APPLICABLE");
16305
+ }
16306
+ const actuals = normalizeP6ActualDatePatch(
16307
+ input.activity,
16308
+ input.intent ?? {}
16309
+ );
16310
+ if (input.dataDate === null) return actuals;
16311
+ return withMirroredPercent(input.activity, actuals, input.nextProgress);
16312
+ }
16313
+ function prepareLookaheadProgressActualsForPersistence(input) {
16314
+ const { resolveCalendar, ...progressInput } = input;
16315
+ const preparedActuals = prepareLookaheadProgressActuals(progressInput);
16316
+ if (input.dataDate === null) {
16317
+ return preparedActuals;
16318
+ }
16319
+ const actualStart = input.intent?.actualStart;
16320
+ const needsActualStartNormalization = actualStart !== void 0 && isUtcMidnight(actualStart);
16321
+ if (!needsActualStartNormalization) return preparedActuals;
16322
+ const intent = normalizeActualStart(
16323
+ input.intent,
16324
+ input.activity.actualStart,
16325
+ resolveCalendar
16326
+ );
16327
+ if (intent === void 0) {
16328
+ return preparedActuals;
16329
+ }
16330
+ return prepareLookaheadProgressActuals({ ...progressInput, intent });
16331
+ }
16332
+ function normalizeActualStart(intent, existingActualStart, resolveCalendar) {
16333
+ if (intent?.actualStart === void 0) return intent;
16334
+ if (!isUtcMidnight(intent.actualStart)) return intent;
16335
+ if (existingActualStart !== null) {
16336
+ return {
16337
+ ...intent,
16338
+ actualStart: resolveLookaheadActualDateSelection({
16339
+ selectedDay: intent.actualStart.toISOString().slice(0, 10),
16340
+ timeSource: existingActualStart
16341
+ })
16342
+ };
16343
+ }
16344
+ const calendar = resolveCalendar();
16345
+ if (calendar === null) {
16346
+ invalid3("ACTIVITY_CALENDAR_REQUIRED");
16347
+ }
16348
+ return {
16349
+ ...intent,
16350
+ actualStart: defaultActualStartTime(intent.actualStart, calendar)
16351
+ };
16352
+ }
16353
+ function withMirroredPercent(activity, actuals, nextProgress) {
16354
+ const executionStatus = actuals.executionStatus ?? activity.executionStatus;
16355
+ if (executionStatus !== P6_EXECUTION_STATUS.IN_PROGRESS) return actuals;
16356
+ switch (activity.percentCompleteType) {
16357
+ case P6_PERCENT_COMPLETE_TYPE.PHYSICAL:
16358
+ return { ...actuals, physicalPercentComplete: nextProgress };
16359
+ case P6_PERCENT_COMPLETE_TYPE.DURATION:
16360
+ return {
16361
+ ...actuals,
16362
+ remainingDurationHours: activity.originalDurationHours * (1 - nextProgress / COMPLETED_PROGRESS)
16363
+ };
16364
+ default:
16365
+ return actuals;
16366
+ }
16367
+ }
16368
+ function validateInput(input) {
16369
+ if (!Number.isFinite(input.nextProgress) || input.nextProgress < NOT_STARTED_PROGRESS || input.nextProgress > COMPLETED_PROGRESS) {
16370
+ invalid3("INVALID_PROGRESS");
16371
+ }
16372
+ if (input.dataDate !== null && !validDate(input.dataDate)) {
16373
+ invalid3("INVALID_DATA_DATE");
16374
+ }
16375
+ if (input.displayedFinish != null && !validDate(input.displayedFinish)) {
16376
+ invalid3("INVALID_DISPLAYED_FINISH");
16377
+ }
16378
+ }
16379
+ function validDate(value) {
16380
+ return value instanceof Date && Number.isFinite(value.getTime());
16381
+ }
16382
+ function sameUtcDay2(value, [year, month, day]) {
16383
+ return value.getUTCFullYear() === year && value.getUTCMonth() === month - 1 && value.getUTCDate() === day;
16384
+ }
16385
+ function hasIntent(intent) {
16386
+ return intent?.actualStart !== void 0 || intent?.actualFinish !== void 0;
16387
+ }
16388
+ function invalid3(code) {
16389
+ throw new LookaheadActualTransitionError(code);
16390
+ }
16391
+
16392
+ // src/p6/lookahead-reconciliation.ts
16393
+ function preparePersistedLookaheadDataDate(action, activity, deps) {
16394
+ const settings = deps.dataDateSettings;
16395
+ const capability = deps.dataDateCapability;
16396
+ if (action.persistedProgress === void 0 || activity?.p6 === void 0 || capability === null || !hasActiveDataDate(settings, deps.dataDateEnabled) || settings?.dataDate == null)
16397
+ return null;
16398
+ const patch = prepareLookaheadProgressActuals({
16399
+ dataDate: settings.dataDate,
16400
+ activity: activity.p6,
16401
+ nextProgress: action.persistedProgress,
16402
+ ...action.persistedActualTransition === void 0 ? {} : { intent: action.persistedActualTransition }
16403
+ });
16404
+ const prepared = capability.prepareActivityUpdate(
16405
+ activity.p6,
16406
+ {
16407
+ originalDurationHours: activity.p6.originalDurationHours,
16408
+ ...patch.actualStart === void 0 ? {} : { actualStart: patch.actualStart },
16409
+ ...patch.actualFinish === void 0 ? {} : { actualFinish: patch.actualFinish },
16410
+ ...patch.remainingDurationHours == null ? {} : { remainingDurationHours: patch.remainingDurationHours },
16411
+ ...patch.physicalPercentComplete == null ? {} : { physicalPercentComplete: patch.physicalPercentComplete }
16412
+ },
16413
+ activity.constraintType
16414
+ );
16415
+ const calendar = deps.adapter.calendarReader.getCalendar(activity.calendarId);
16416
+ const projection = calendar === null ? { p6: prepared.next, startDate: void 0, endDate: void 0 } : capability.projectLocalActivity(activity.p6, prepared.next, {
16417
+ calendar,
16418
+ dataDate: settings.dataDate,
16419
+ totalFloatMethod: settings.totalFloatMethod
16420
+ });
16421
+ return { activityId: activity.id, ...projection };
16422
+ }
16423
+
16120
16424
  // src/dispatch/handlers/persistence-acknowledge.ts
16121
16425
  var EMPTY_OWNERS = /* @__PURE__ */ new Map();
16122
16426
  function persistedOwnersOf(entities) {
@@ -16335,6 +16639,30 @@ async function dispatchActivityLookaheadSync(action, options, deps) {
16335
16639
  if (snapshots.some((snapshot) => !snapshot)) {
16336
16640
  return { ok: false, reason: REJECTION_REASON.ACTIVITY_NOT_FOUND };
16337
16641
  }
16642
+ const persistedDataDate = preparePersistedLookaheadDataDate(
16643
+ action,
16644
+ snapshots[0],
16645
+ deps
16646
+ );
16647
+ if (persistedDataDate !== null) {
16648
+ deps.adapter.setActivityField(
16649
+ persistedDataDate.activityId,
16650
+ "p6",
16651
+ persistedDataDate.p6
16652
+ );
16653
+ if (persistedDataDate.startDate !== void 0)
16654
+ deps.adapter.setActivityField(
16655
+ persistedDataDate.activityId,
16656
+ "startDate",
16657
+ persistedDataDate.startDate
16658
+ );
16659
+ if (persistedDataDate.endDate !== void 0)
16660
+ deps.adapter.setActivityField(
16661
+ persistedDataDate.activityId,
16662
+ "endDate",
16663
+ persistedDataDate.endDate
16664
+ );
16665
+ }
16338
16666
  const activities = [];
16339
16667
  for (const [index, id] of ids.entries()) {
16340
16668
  const snapshot = snapshots[index];
@@ -16375,7 +16703,7 @@ async function dispatchActivityLookaheadSync(action, options, deps) {
16375
16703
  [{ activityId: ids[0], progress: action.persistedProgress }],
16376
16704
  deps
16377
16705
  );
16378
- if (progressChanges.length > 0) {
16706
+ if (progressChanges.length > 0 || persistedDataDate !== null) {
16379
16707
  await runPostMutation(
16380
16708
  {
16381
16709
  adapter: deps.adapter,
@@ -17617,7 +17945,7 @@ async function dispatchActivitySetProgress(action, options, deps) {
17617
17945
  const statusRouting = resolveDataDateProgressButton(
17618
17946
  activitySnap,
17619
17947
  action.newValue,
17620
- editGate.allowed ? progressChanges : null,
17948
+ progressChanges,
17621
17949
  deps,
17622
17950
  options.confirmations
17623
17951
  );
@@ -17625,17 +17953,14 @@ async function dispatchActivitySetProgress(action, options, deps) {
17625
17953
  return rejectedRouting(statusRouting);
17626
17954
  }
17627
17955
  const changes = statusRouting === null ? progressChanges : statusRouting.changes;
17628
- const writesProgress = statusRouting === null || editGate.allowed;
17629
17956
  const touchedIds = collectTouchedIds(action.activityId, changes);
17630
17957
  const beforeSnap = await snapshotActivities(adapter, touchedIds);
17631
17958
  applyFieldChanges(adapter, action.activityId, changes);
17632
- if (writesProgress) {
17633
- setExplicitSummaryProgressFallbacks(
17634
- action.activityId,
17635
- action.newValue,
17636
- adapter
17637
- );
17638
- }
17959
+ setExplicitSummaryProgressFallbacks(
17960
+ action.activityId,
17961
+ action.newValue,
17962
+ adapter
17963
+ );
17639
17964
  runPostProcessorsOnAdapter(
17640
17965
  action.activityId,
17641
17966
  changes.postProcessors ?? [],
@@ -26797,140 +27122,6 @@ function changed(before, after) {
26797
27122
  return serializeCanonicalSchedule(before) !== serializeCanonicalSchedule(after);
26798
27123
  }
26799
27124
 
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
27125
  // src/p6/derived.ts
26935
27126
  var INVALID_INPUT = "Invalid P6 derived input";
26936
27127
  var PERCENT_SCALE2 = 100;
@@ -30899,48 +31090,48 @@ function optionalP6Date(rawValue) {
30899
31090
  function yesNo(rawValue) {
30900
31091
  if (rawValue === "Y") return true;
30901
31092
  if (rawValue === "N") return false;
30902
- invalid3(`expected Y/N, got "${rawValue}"`);
31093
+ invalid4(`expected Y/N, got "${rawValue}"`);
30903
31094
  }
30904
31095
  function enumValue(map, rawValue) {
30905
- if (!Object.hasOwn(map, rawValue)) invalid3(`unknown enum "${rawValue}"`);
31096
+ if (!Object.hasOwn(map, rawValue)) invalid4(`unknown enum "${rawValue}"`);
30906
31097
  return map[rawValue];
30907
31098
  }
30908
31099
  function outOfSequencePolicy(schedule) {
30909
31100
  const retained = yesNo(schedule.sched_retained_logic);
30910
31101
  const override = yesNo(schedule.sched_progress_override);
30911
31102
  if (retained && override)
30912
- invalid3("retained logic and progress override conflict");
31103
+ invalid4("retained logic and progress override conflict");
30913
31104
  if (retained) return "retained_logic";
30914
31105
  if (override) return "progress_override";
30915
31106
  return "actual_dates";
30916
31107
  }
30917
31108
  function nonNegativeNumber(rawValue, field) {
30918
31109
  const value = finiteNumber(rawValue, field);
30919
- if (value < 0) invalid3(`${field} must be a non-negative number`);
31110
+ if (value < 0) invalid4(`${field} must be a non-negative number`);
30920
31111
  return value;
30921
31112
  }
30922
31113
  function finiteNumber(rawValue, field) {
30923
31114
  const value = Number(rawValue);
30924
31115
  if (rawValue === "" || !Number.isFinite(value)) {
30925
- invalid3(`${field} must be a finite number`);
31116
+ invalid4(`${field} must be a finite number`);
30926
31117
  }
30927
31118
  return value;
30928
31119
  }
30929
31120
  function nonNegativeInteger(rawValue, field) {
30930
31121
  const value = nonNegativeNumber(rawValue, field);
30931
- if (!Number.isInteger(value)) invalid3(`${field} must be an integer`);
31122
+ if (!Number.isInteger(value)) invalid4(`${field} must be an integer`);
30932
31123
  return value;
30933
31124
  }
30934
31125
  function endingActivityId(rawValue, resolveActivityId) {
30935
31126
  if (rawValue === "") return null;
30936
31127
  if (resolveActivityId === void 0) {
30937
- invalid3("ending activity requires an ID resolver");
31128
+ invalid4("ending activity requires an ID resolver");
30938
31129
  }
30939
31130
  const resolved = resolveActivityId(rawValue);
30940
- if (resolved === null) invalid3(`unknown ending activity "${rawValue}"`);
31131
+ if (resolved === null) invalid4(`unknown ending activity "${rawValue}"`);
30941
31132
  return resolved;
30942
31133
  }
30943
- function invalid3(reason) {
31134
+ function invalid4(reason) {
30944
31135
  throw new Error(`${INVALID_OPTIONS}: ${reason}`);
30945
31136
  }
30946
31137
 
@@ -37210,109 +37401,6 @@ function serializeP6State(input) {
37210
37401
  // src/boundary/save/unsaved.ts
37211
37402
  var getUnsavedActivities = (activities = []) => (activities ?? []).filter((activity) => !activity.proplannerId);
37212
37403
 
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 withMirroredPercent(activity, actuals, nextProgress) {
37278
- const executionStatus = actuals.executionStatus ?? activity.executionStatus;
37279
- if (executionStatus !== P6_EXECUTION_STATUS.IN_PROGRESS) return actuals;
37280
- switch (activity.percentCompleteType) {
37281
- case P6_PERCENT_COMPLETE_TYPE.PHYSICAL:
37282
- return { ...actuals, physicalPercentComplete: nextProgress };
37283
- case P6_PERCENT_COMPLETE_TYPE.DURATION:
37284
- return {
37285
- ...actuals,
37286
- remainingDurationHours: activity.originalDurationHours * (1 - nextProgress / COMPLETED_PROGRESS)
37287
- };
37288
- default:
37289
- return actuals;
37290
- }
37291
- }
37292
- function validateInput(input) {
37293
- if (!Number.isFinite(input.nextProgress) || input.nextProgress < NOT_STARTED_PROGRESS || input.nextProgress > COMPLETED_PROGRESS) {
37294
- invalid4("INVALID_PROGRESS");
37295
- }
37296
- if (input.dataDate !== null && !validDate(input.dataDate)) {
37297
- invalid4("INVALID_DATA_DATE");
37298
- }
37299
- if (input.displayedFinish != null && !validDate(input.displayedFinish)) {
37300
- invalid4("INVALID_DISPLAYED_FINISH");
37301
- }
37302
- }
37303
- function validDate(value) {
37304
- return value instanceof Date && Number.isFinite(value.getTime());
37305
- }
37306
- function sameUtcDay2(value, [year, month, day]) {
37307
- return value.getUTCFullYear() === year && value.getUTCMonth() === month - 1 && value.getUTCDate() === day;
37308
- }
37309
- function hasIntent(intent) {
37310
- return intent?.actualStart !== void 0 || intent?.actualFinish !== void 0;
37311
- }
37312
- function invalid4(code) {
37313
- throw new LookaheadActualTransitionError(code);
37314
- }
37315
-
37316
37404
  // src/p6/expected-finish-selection.ts
37317
37405
  function resolveExpectedFinishDateSelection(input) {
37318
37406
  return resolveLookaheadActualDateSelection({
@@ -37344,7 +37432,20 @@ function createScheduleCalendars(input) {
37344
37432
  function createLookaheadDerivedFields(input) {
37345
37433
  const { resolveCalendar } = createScheduleCalendars(input);
37346
37434
  return {
37347
- derive({ previous, next, calendarId, dataDate, totalFloatMethod }) {
37435
+ getCalendar(calendarId) {
37436
+ const calendar = resolveCalendar(calendarId);
37437
+ if (!calendar) {
37438
+ throw new LookaheadActualTransitionError("ACTIVITY_CALENDAR_REQUIRED");
37439
+ }
37440
+ return calendar;
37441
+ },
37442
+ derive({
37443
+ previous,
37444
+ next,
37445
+ calendarId,
37446
+ dataDate,
37447
+ totalFloatMethod
37448
+ }) {
37348
37449
  if (!isP6ActivityState(next)) return null;
37349
37450
  const calendar = resolveCalendar(calendarId);
37350
37451
  if (!calendar) return null;
@@ -38126,6 +38227,7 @@ exports.parseP6DayAsCalendarStart = parseP6DayAsCalendarStart;
38126
38227
  exports.parseP6WallDateAsUtc = parseP6WallDateAsUtc;
38127
38228
  exports.planLookaheadProgressActuals = planLookaheadProgressActuals;
38128
38229
  exports.prepareLookaheadProgressActuals = prepareLookaheadProgressActuals;
38230
+ exports.prepareLookaheadProgressActualsForPersistence = prepareLookaheadProgressActualsForPersistence;
38129
38231
  exports.rescheduledOnLoad = rescheduledOnLoad;
38130
38232
  exports.resolveExpectedFinishDateSelection = resolveExpectedFinishDateSelection;
38131
38233
  exports.resolveLookaheadActualDateSelection = resolveLookaheadActualDateSelection;