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