@pisell/pisellos 2.2.299 → 2.2.301

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.
@@ -1,5 +1,5 @@
1
1
  import type { AvailabilityProjection, AvailabilityProjectionInput, AvailabilityQuery, AvailabilityQueryResult, AvailabilitySelectionValidationParams, AvailabilitySelectionValidationResult, GetProductTimeRangesRequest, GetProductTimeRangesRuntimeOptions, ProductTimeRangeGroup } from './types';
2
2
  export declare function createAvailabilityProjection(input: AvailabilityProjectionInput): AvailabilityProjection;
3
3
  export declare function getProductTimeRanges(projection: AvailabilityProjection, request?: GetProductTimeRangesRequest, runtimeOptions?: GetProductTimeRangesRuntimeOptions): ProductTimeRangeGroup[];
4
- export declare function queryAvailabilityProjection(projection: AvailabilityProjection, request: AvailabilityQuery): AvailabilityQueryResult;
5
- export declare function validateAvailabilitySelection(projection: AvailabilityProjection, params: AvailabilitySelectionValidationParams): AvailabilitySelectionValidationResult;
4
+ export declare function queryAvailabilityProjection(projection: AvailabilityProjection, request: AvailabilityQuery, runtimeOptions?: GetProductTimeRangesRuntimeOptions): AvailabilityQueryResult;
5
+ export declare function validateAvailabilitySelection(projection: AvailabilityProjection, params: AvailabilitySelectionValidationParams, runtimeOptions?: GetProductTimeRangesRuntimeOptions): AvailabilitySelectionValidationResult;
@@ -175,8 +175,70 @@ function addLeadWindow(base, unit, unitType, _timezoneName) {
175
175
  if (unitType === 'hours') return addLocalHours(base, unit);
176
176
  return addLocalMinutes(base, unit);
177
177
  }
178
+ function normalizedWindowContainsRange(windows, candidateRange, timezoneName) {
179
+ return windows.some(function (window) {
180
+ var normalized = normalizeAbsoluteRange(window, timezoneName);
181
+ return Boolean(normalized && contains(normalized, candidateRange));
182
+ });
183
+ }
184
+ function normalizedWindowsContainDate(windows, date, timezoneName) {
185
+ return windows.some(function (window) {
186
+ var normalized = normalizeAbsoluteRange(window, timezoneName);
187
+ return Boolean(normalized && formatDate(normalized.start, timezoneName) === date);
188
+ });
189
+ }
190
+ function isRangeFromExcludedWindows(params) {
191
+ var candidateDate = formatDate(params.candidateRange.start, params.timezoneName);
192
+ var hasNormalWindowOnDate = normalizedWindowsContainDate(params.normalWindows, candidateDate, params.timezoneName);
193
+ var hasExcludedWindowOnDate = normalizedWindowsContainDate(params.excludedWindows, candidateDate, params.timezoneName);
194
+ if (!hasNormalWindowOnDate && hasExcludedWindowOnDate) return true;
195
+ return !normalizedWindowContainsRange(params.normalWindows, params.candidateRange, params.timezoneName) && normalizedWindowContainsRange(params.excludedWindows, params.candidateRange, params.timezoneName);
196
+ }
197
+ function isCandidateFromExcludedScheduleDate(params) {
198
+ var projection = params.projection,
199
+ product = params.product,
200
+ candidate = params.candidate,
201
+ candidateRange = params.candidateRange;
202
+ var operatingHours = projection.meta.defaults.operatingHours;
203
+ if (isRangeFromExcludedWindows({
204
+ normalWindows: operatingHours.windows,
205
+ excludedWindows: operatingHours.excludedWindows || [],
206
+ candidateRange: candidateRange,
207
+ timezoneName: projection.meta.timezone
208
+ })) {
209
+ return true;
210
+ }
211
+ if (product.kind !== 'session' && product.kind !== 'venue_slot') {
212
+ return false;
213
+ }
214
+ if (candidate.source !== 'session_schedule') {
215
+ var hasNormalRule = (product.sessionRules || []).some(function (rule) {
216
+ return matchesScheduleRuleDate(rule, candidate.date, projection.meta.timezone);
217
+ });
218
+ var hasExcludedOverrideRule = (product.sessionRules || []).some(function (rule) {
219
+ return !matchesScheduleRuleDate(rule, candidate.date, projection.meta.timezone) && matchesScheduleRuleDate(rule, candidate.date, projection.meta.timezone, true);
220
+ });
221
+ return !hasNormalRule && hasExcludedOverrideRule;
222
+ }
223
+ var candidateDateRange = {
224
+ startDate: candidate.date,
225
+ endDate: candidate.date
226
+ };
227
+ var candidateIdentity = getProductTimeRangeIdentity(candidate);
228
+ var normalRanges = getSessionRangesForProduct(projection, product, candidateDateRange);
229
+ if (normalRanges.some(function (range) {
230
+ return getProductTimeRangeIdentity(range) === candidateIdentity;
231
+ })) {
232
+ return false;
233
+ }
234
+ return getSessionRangesForProduct(projection, product, candidateDateRange, true).some(function (range) {
235
+ return getProductTimeRangeIdentity(range) === candidateIdentity;
236
+ });
237
+ }
178
238
  function evaluateCandidateTimeGate(params) {
179
- var product = params.product,
239
+ var projection = params.projection,
240
+ product = params.product,
241
+ candidate = params.candidate,
180
242
  candidateRange = params.candidateRange,
181
243
  evaluationContext = params.evaluationContext,
182
244
  timezoneName = params.timezoneName;
@@ -188,6 +250,18 @@ function evaluateCandidateTimeGate(params) {
188
250
  conflictCode: 'past'
189
251
  };
190
252
  }
253
+ if (evaluationContext.allowExcludedScheduleDateOverride && isCandidateFromExcludedScheduleDate({
254
+ projection: projection,
255
+ product: product,
256
+ candidate: candidate,
257
+ candidateRange: candidateRange
258
+ })) {
259
+ return {
260
+ status: 'unavailable',
261
+ reasonCodes: ['schedule_excluded_date'],
262
+ conflictCode: 'schedule_excluded_date'
263
+ };
264
+ }
191
265
  var policy = product.cutOffPolicy;
192
266
  if (!policy) return null;
193
267
  if (resolveCutOffPolicyIssue(policy)) {
@@ -220,10 +294,12 @@ function evaluateCandidateTimeGate(params) {
220
294
  };
221
295
  }
222
296
  if (policy.type === 'before_start') {
223
- // Flexible-duration candidates start at minute precision. Keep the current
224
- // minute selectable instead of treating the freshly generated candidate as
225
- // already started because the query clock still contains seconds.
226
- var hasNotReachedStart = product.isFlexibleDuration ? floorToMinute(now) <= candidateRange.start : now < candidateRange.start;
297
+ // Flexible-duration candidates and POS fixed-duration anchors start at
298
+ // minute precision. Keep that current minute selectable instead of treating
299
+ // the freshly generated candidate as already started because the query
300
+ // clock still contains seconds.
301
+ var usesMinutePrecision = product.isFlexibleDuration || evaluationContext.useExactDurationCandidateMinute && product.kind === 'duration';
302
+ var hasNotReachedStart = usesMinutePrecision ? floorToMinute(now) <= candidateRange.start : now < candidateRange.start;
227
303
  if (hasNotReachedStart) return null;
228
304
  return {
229
305
  status: 'unavailable',
@@ -334,7 +410,8 @@ function normalizeCoverage(input) {
334
410
  };
335
411
  }
336
412
  function normalizeResourceWindow(resource, coverage, timezoneName) {
337
- return mergeRanges((resource.windows || []).map(function (window) {
413
+ var includeExcludedWindows = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false;
414
+ return mergeRanges([].concat(_toConsumableArray(resource.windows || []), _toConsumableArray(includeExcludedWindows ? resource.excludedWindows || [] : [])).map(function (window) {
338
415
  return normalizeAbsoluteRange(window, timezoneName);
339
416
  }).filter(function (window) {
340
417
  return Boolean(window);
@@ -407,12 +484,15 @@ function normalizeResources(resources) {
407
484
  capacity: Math.max(1, Number(resource.capacity || 1) || 1),
408
485
  windows: (resource.windows || []).map(function (window) {
409
486
  return _objectSpread({}, window);
487
+ }),
488
+ excludedWindows: (resource.excludedWindows || []).map(function (window) {
489
+ return _objectSpread({}, window);
410
490
  })
411
491
  });
412
492
  });
413
493
  }
414
494
  function normalizeOperatingHours(params) {
415
- var _params$input, _params$input2, _params$input3;
495
+ var _params$input, _params$input2, _params$input3, _params$input4;
416
496
  var fallbackWindows = enumerateDates(params.dateRange.startDate, params.dateRange.endDate).map(function (date) {
417
497
  var start = toDateTimeInDate(date, params.businessHours.startTime, params.timezoneName);
418
498
  var end = toDateTimeInDate(date, params.businessHours.endTime, params.timezoneName);
@@ -430,26 +510,32 @@ function normalizeOperatingHours(params) {
430
510
  endAt: formatDateTime(range.end, params.timezoneName, params.fixedOffsetMinutes)
431
511
  };
432
512
  });
433
- var windows = mergeRanges(sourceWindows.map(function (window) {
434
- return normalizeAbsoluteRange(window, params.timezoneName);
435
- }).filter(function (window) {
436
- return Boolean(window);
437
- }).map(function (window) {
438
- return clipRange(window, params.coverage);
439
- }).filter(function (window) {
440
- return Boolean(window);
441
- })).map(function (range) {
442
- return {
443
- startAt: formatDateTime(range.start, params.timezoneName, params.fixedOffsetMinutes),
444
- endAt: formatDateTime(range.end, params.timezoneName, params.fixedOffsetMinutes)
445
- };
446
- });
447
- return {
448
- source: ((_params$input = params.input) === null || _params$input === void 0 ? void 0 : _params$input.source) || params.fallbackSource,
449
- scheduleIds: uniqById(((_params$input2 = params.input) === null || _params$input2 === void 0 ? void 0 : _params$input2.scheduleIds) || []),
450
- missingScheduleIds: uniqById(((_params$input3 = params.input) === null || _params$input3 === void 0 ? void 0 : _params$input3.missingScheduleIds) || []),
451
- windows: windows
513
+ var normalizeWindows = function normalizeWindows(source) {
514
+ return mergeRanges(source.map(function (window) {
515
+ return normalizeAbsoluteRange(window, params.timezoneName);
516
+ }).filter(function (window) {
517
+ return Boolean(window);
518
+ }).map(function (window) {
519
+ return clipRange(window, params.coverage);
520
+ }).filter(function (window) {
521
+ return Boolean(window);
522
+ })).map(function (range) {
523
+ return {
524
+ startAt: formatDateTime(range.start, params.timezoneName, params.fixedOffsetMinutes),
525
+ endAt: formatDateTime(range.end, params.timezoneName, params.fixedOffsetMinutes)
526
+ };
527
+ });
452
528
  };
529
+ var windows = normalizeWindows(sourceWindows);
530
+ var excludedWindows = normalizeWindows(((_params$input = params.input) === null || _params$input === void 0 ? void 0 : _params$input.excludedWindows) || []);
531
+ return _objectSpread({
532
+ source: ((_params$input2 = params.input) === null || _params$input2 === void 0 ? void 0 : _params$input2.source) || params.fallbackSource,
533
+ scheduleIds: uniqById(((_params$input3 = params.input) === null || _params$input3 === void 0 ? void 0 : _params$input3.scheduleIds) || []),
534
+ missingScheduleIds: uniqById(((_params$input4 = params.input) === null || _params$input4 === void 0 ? void 0 : _params$input4.missingScheduleIds) || []),
535
+ windows: windows
536
+ }, excludedWindows.length > 0 ? {
537
+ excludedWindows: excludedWindows
538
+ } : {});
453
539
  }
454
540
  function matchesRequirementGroup(requirementGroup, resource, product) {
455
541
  var _requirementGroup$res;
@@ -759,11 +845,12 @@ function resolveProjectionDateRange(projection, requestDateRange) {
759
845
  };
760
846
  }
761
847
  function buildOperatingRanges(projection, dateRange) {
848
+ var includeExcludedWindows = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
762
849
  var coverage = {
763
850
  start: toDateStart(dateRange.startDate, projection.meta.timezone),
764
851
  end: toDateEnd(nextDate(dateRange.endDate), projection.meta.timezone)
765
852
  };
766
- return mergeRanges(projection.meta.defaults.operatingHours.windows.map(function (window) {
853
+ return mergeRanges([].concat(_toConsumableArray(projection.meta.defaults.operatingHours.windows), _toConsumableArray(includeExcludedWindows ? projection.meta.defaults.operatingHours.excludedWindows || [] : [])).map(function (window) {
767
854
  return normalizeAbsoluteRange(window, projection.meta.timezone);
768
855
  }).filter(function (window) {
769
856
  return Boolean(window);
@@ -774,12 +861,13 @@ function buildOperatingRanges(projection, dateRange) {
774
861
  }));
775
862
  }
776
863
  function buildResourceWindowIndex(projection) {
864
+ var includeExcludedWindows = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
777
865
  var coverage = {
778
866
  start: toDateTime(projection.meta.coverage.startAt, projection.meta.timezone),
779
867
  end: toDateTime(projection.meta.coverage.endAt, projection.meta.timezone)
780
868
  };
781
869
  return new Map(projection.resources.map(function (resource) {
782
- return [String(resource.id), normalizeResourceWindow(resource, coverage, projection.meta.timezone)];
870
+ return [String(resource.id), normalizeResourceWindow(resource, coverage, projection.meta.timezone, includeExcludedWindows)];
783
871
  }));
784
872
  }
785
873
  function buildGroupWorkingRanges(params) {
@@ -801,7 +889,7 @@ function buildGroupWorkingRanges(params) {
801
889
  return !filteredResourceIdSet || filteredResourceIdSet.has(String(resource.id));
802
890
  }).map(function (resource) {
803
891
  var _params$resourceWindo;
804
- var windows = ((_params$resourceWindo = params.resourceWindowIndex) === null || _params$resourceWindo === void 0 ? void 0 : _params$resourceWindo.get(String(resource.id))) || normalizeResourceWindow(resource, coverage, projection.meta.timezone);
892
+ var windows = ((_params$resourceWindo = params.resourceWindowIndex) === null || _params$resourceWindo === void 0 ? void 0 : _params$resourceWindo.get(String(resource.id))) || normalizeResourceWindow(resource, coverage, projection.meta.timezone, params.includeExcludedWindows === true);
805
893
  return windows.map(function (window) {
806
894
  return clipRange(window, coverage);
807
895
  }).filter(function (window) {
@@ -848,11 +936,12 @@ function buildGroupWorkingRanges(params) {
848
936
  return mergeRanges(satisfiedRanges);
849
937
  }
850
938
  function materializeScheduleRules(rules, dateRange, timezoneName, fixedOffsetMinutes) {
939
+ var allowExcludedScheduleDates = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : false;
851
940
  var dates = enumerateDates(dateRange.startDate, dateRange.endDate);
852
941
  var seen = new Map();
853
942
  rules.forEach(function (rule) {
854
943
  dates.forEach(function (date) {
855
- if (!matchesScheduleRuleDate(rule, date, timezoneName)) return;
944
+ if (!matchesScheduleRuleDate(rule, date, timezoneName, allowExcludedScheduleDates)) return;
856
945
  var start = toDateTimeInDate(date, rule.startTime, timezoneName);
857
946
  var end = toDateTimeInDate(date, rule.endTime, timezoneName);
858
947
  if (end <= start) end = toDateTimeInDate(nextDate(date, timezoneName), rule.endTime, timezoneName);
@@ -885,8 +974,9 @@ function materializeScheduleRules(rules, dateRange, timezoneName, fixedOffsetMin
885
974
  }
886
975
  function matchesScheduleRuleDate(rule, date, _timezoneName) {
887
976
  var _rule$includeDates, _rule$excludeDates;
977
+ var allowExcludedScheduleDate = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false;
888
978
  if ((_rule$includeDates = rule.includeDates) !== null && _rule$includeDates !== void 0 && _rule$includeDates.includes(date)) return true;
889
- if ((_rule$excludeDates = rule.excludeDates) !== null && _rule$excludeDates !== void 0 && _rule$excludeDates.includes(date)) return false;
979
+ if (!allowExcludedScheduleDate && (_rule$excludeDates = rule.excludeDates) !== null && _rule$excludeDates !== void 0 && _rule$excludeDates.includes(date)) return false;
890
980
  if (rule.startDate && date < rule.startDate) return false;
891
981
  if (rule.endDate && date > rule.endDate) return false;
892
982
  if (rule.mode === 'designation') {
@@ -942,6 +1032,7 @@ function buildGroupResourceIndex(params) {
942
1032
  }
943
1033
  function getSessionRangesForProduct(projection, product, dateRange) {
944
1034
  var _extractFixedOffsetMi2;
1035
+ var allowExcludedScheduleDates = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false;
945
1036
  var fixedOffsetMinutes = (_extractFixedOffsetMi2 = extractFixedOffsetMinutes(projection.meta.now)) !== null && _extractFixedOffsetMi2 !== void 0 ? _extractFixedOffsetMi2 : captureRuntimeOffsetMinutes();
946
1037
  var ranges = (product.sessionRanges || []).map(function (range) {
947
1038
  var _range$scheduleRefs2;
@@ -967,7 +1058,7 @@ function getSessionRangesForProduct(projection, product, dateRange) {
967
1058
  primaryScheduleRef: (_range$scheduleRefs2 = range.scheduleRefs) === null || _range$scheduleRefs2 === void 0 ? void 0 : _range$scheduleRefs2[0]
968
1059
  };
969
1060
  }).filter(Boolean);
970
- var ruleRanges = materializeScheduleRules(product.sessionRules || [], dateRange, projection.meta.timezone, fixedOffsetMinutes);
1061
+ var ruleRanges = materializeScheduleRules(product.sessionRules || [], dateRange, projection.meta.timezone, fixedOffsetMinutes, allowExcludedScheduleDates);
971
1062
  return dedupeProductRanges([].concat(_toConsumableArray(ranges), _toConsumableArray(ruleRanges.map(function (range) {
972
1063
  return _objectSpread(_objectSpread({}, range), {}, {
973
1064
  key: "".concat(String(product.id), ":").concat(range.startAt, ":").concat(range.endAt),
@@ -1022,7 +1113,8 @@ export function getProductTimeRanges(projection) {
1022
1113
  var dateRange = resolveProjectionDateRange(projection, request.dateRange);
1023
1114
  var slotStepMinutes = projection.meta.defaults.slotStepMinutes;
1024
1115
  var fixedOffsetMinutes = (_extractFixedOffsetMi3 = extractFixedOffsetMinutes(projection.meta.now)) !== null && _extractFixedOffsetMi3 !== void 0 ? _extractFixedOffsetMi3 : captureRuntimeOffsetMinutes();
1025
- var resourceWindowIndex = buildResourceWindowIndex(projection);
1116
+ var allowExcludedScheduleDateOverride = runtimeOptions.allowExcludedScheduleDateOverride === true;
1117
+ var resourceWindowIndex = buildResourceWindowIndex(projection, allowExcludedScheduleDateOverride);
1026
1118
  var filteredProductIdSet = (_request$productIds = request.productIds) !== null && _request$productIds !== void 0 && _request$productIds.length ? new Set(request.productIds.map(String)) : null;
1027
1119
  return projection.products.filter(function (product) {
1028
1120
  return !filteredProductIdSet || filteredProductIdSet.has(String(product.id));
@@ -1037,7 +1129,7 @@ export function getProductTimeRanges(projection) {
1037
1129
  };
1038
1130
  }
1039
1131
  if (product.kind === 'session' || product.kind === 'venue_slot') {
1040
- var fixedRanges = getSessionRangesForProduct(projection, product, dateRange);
1132
+ var fixedRanges = getSessionRangesForProduct(projection, product, dateRange, allowExcludedScheduleDateOverride);
1041
1133
  var customRanges = [];
1042
1134
  var customDurationMinutes = resolveRequestedSessionDurationMinutes(product, request, runtimeOptions.allowSessionCustomRanges === true);
1043
1135
  var _requestedStartTimes = resolveRequestedDurationStartTimes(product, request, runtimeOptions.allowSessionCustomRanges === true);
@@ -1059,7 +1151,7 @@ export function getProductTimeRanges(projection) {
1059
1151
  var requiredGroups = (product.requirementGroups || []).filter(function (group) {
1060
1152
  return group.required !== false;
1061
1153
  });
1062
- var operatingRanges = buildOperatingRanges(projection, dateRange);
1154
+ var operatingRanges = buildOperatingRanges(projection, dateRange, allowExcludedScheduleDateOverride);
1063
1155
  var operatingEndAt = dateRange.startDate === dateRange.endDate && operatingRanges.length > 0 ? formatDateTime(Math.max.apply(Math, _toConsumableArray(operatingRanges.map(function (range) {
1064
1156
  return range.end;
1065
1157
  }))), projection.meta.timezone, fixedOffsetMinutes) : undefined;
@@ -1071,7 +1163,8 @@ export function getProductTimeRanges(projection) {
1071
1163
  dateRange: dateRange,
1072
1164
  resourceIds: request.resourceIds,
1073
1165
  partySize: normalizedPartySize,
1074
- resourceWindowIndex: resourceWindowIndex
1166
+ resourceWindowIndex: resourceWindowIndex,
1167
+ includeExcludedWindows: allowExcludedScheduleDateOverride
1075
1168
  });
1076
1169
  }).reduce(function (current, ranges) {
1077
1170
  return current === null ? ranges : intersectRangeLists(current, ranges);
@@ -1088,7 +1181,8 @@ export function getProductTimeRanges(projection) {
1088
1181
  }
1089
1182
  return floorToMinute(evaluatedAt);
1090
1183
  }();
1091
- var durationAnchor = ceilToTenMinutes(toDateTime(projection.meta.durationCandidateAnchorAt, projection.meta.timezone), projection.meta.timezone);
1184
+ var durationCandidateAnchor = toDateTime(projection.meta.durationCandidateAnchorAt, projection.meta.timezone);
1185
+ var durationAnchor = runtimeOptions.useExactDurationCandidateMinute ? floorToMinute(durationCandidateAnchor) : ceilToTenMinutes(durationCandidateAnchor, projection.meta.timezone);
1092
1186
  var requestedStartTimes = resolveRequestedDurationStartTimes(product, request);
1093
1187
  var ranges = [];
1094
1188
  baseRanges.forEach(function (range) {
@@ -1353,7 +1447,9 @@ function buildEvaluationContext(params) {
1353
1447
  evaluatedAt: evaluatedAt,
1354
1448
  evaluatedAtMs: evaluatedAt,
1355
1449
  resourceIdSet: (_params$resourceIds2 = params.resourceIds) !== null && _params$resourceIds2 !== void 0 && _params$resourceIds2.length ? new Set(params.resourceIds.map(String)) : null,
1356
- selectionsByGroupKey: selectionsByGroupKey
1450
+ selectionsByGroupKey: selectionsByGroupKey,
1451
+ useExactDurationCandidateMinute: params.useExactDurationCandidateMinute === true,
1452
+ allowExcludedScheduleDateOverride: params.allowExcludedScheduleDateOverride === true
1357
1453
  };
1358
1454
  }
1359
1455
  function summarizeStatuses(statuses) {
@@ -1525,7 +1621,8 @@ function evaluateCandidateSummary(projection, candidate, request) {
1525
1621
  var evaluationContext = request.evaluationContext || buildEvaluationContext({
1526
1622
  projection: projection,
1527
1623
  evaluatedAt: request.evaluatedAt,
1528
- resourceIds: request.resourceIds
1624
+ resourceIds: request.resourceIds,
1625
+ allowExcludedScheduleDateOverride: request.allowExcludedScheduleDateOverride
1529
1626
  });
1530
1627
  var requestedPartySize = normalizePartySize(request.partySize);
1531
1628
  var requiredGroups = (product.requirementGroups || []).filter(function (group) {
@@ -1543,7 +1640,9 @@ function evaluateCandidateSummary(projection, candidate, request) {
1543
1640
  });
1544
1641
  });
1545
1642
  var gate = evaluateCandidateTimeGate({
1643
+ projection: projection,
1546
1644
  product: product,
1645
+ candidate: candidate,
1547
1646
  candidateRange: candidateRange,
1548
1647
  evaluationContext: evaluationContext,
1549
1648
  timezoneName: projection.meta.timezone
@@ -1598,7 +1697,8 @@ function evaluateCandidate(projection, candidate, request) {
1598
1697
  projection: projection,
1599
1698
  evaluatedAt: request.evaluatedAt,
1600
1699
  resourceIds: request.resourceIds,
1601
- requirementSelections: request.requirementSelections
1700
+ requirementSelections: request.requirementSelections,
1701
+ allowExcludedScheduleDateOverride: request.allowExcludedScheduleDateOverride
1602
1702
  });
1603
1703
  var requestedPartySize = normalizePartySize(request.partySize);
1604
1704
  var partySize = requestedPartySize !== null && requestedPartySize !== void 0 ? requestedPartySize : 1;
@@ -1621,6 +1721,12 @@ function evaluateCandidate(projection, candidate, request) {
1621
1721
  var options = matchedResources.map(function (resource) {
1622
1722
  var _request$cellIndex, _resource$raw2;
1623
1723
  var matchedCell = aggregateMatchedCells(product.id, requirementGroup.id, resource.id, candidateRange, ((_request$cellIndex = request.cellIndex) === null || _request$cellIndex === void 0 ? void 0 : _request$cellIndex.get([String(product.id), requirementGroup.id, String(resource.id)].join(':'))) || []);
1724
+ var isExcludedScheduleDate = evaluationContext.allowExcludedScheduleDateOverride && isRangeFromExcludedWindows({
1725
+ normalWindows: resource.windows || [],
1726
+ excludedWindows: resource.excludedWindows || [],
1727
+ candidateRange: candidateRange,
1728
+ timezoneName: projection.meta.timezone
1729
+ });
1624
1730
  if (!matchedCell) {
1625
1731
  var _resource$raw;
1626
1732
  var _isPast = candidateRange.endMs <= evaluationContext.evaluatedAtMs;
@@ -1632,7 +1738,7 @@ function evaluateCandidate(projection, candidate, request) {
1632
1738
  resourceType: resource.resourceType,
1633
1739
  status: _isPast ? 'past' : 'unavailable',
1634
1740
  maxPartySize: 0,
1635
- reasonCodes: _isPast ? ['past'] : ['outside_resource_window']
1741
+ reasonCodes: _isPast ? ['past'] : [].concat(_toConsumableArray(isExcludedScheduleDate ? ['schedule_excluded_date'] : []), ['outside_resource_window'])
1636
1742
  };
1637
1743
  }
1638
1744
  var remainingCapacity = matchedCell.remainingCapacity;
@@ -1640,6 +1746,7 @@ function evaluateCandidate(projection, candidate, request) {
1640
1746
  var isPast = candidateRange.endMs <= evaluationContext.evaluatedAtMs;
1641
1747
  var maxPartySize = isPast ? 0 : Math.max(0, remainingCapacity || 0);
1642
1748
  var requiredCapacity = resolveRequiredCapacity(requirementGroup, product, requestedPartySize);
1749
+ var reasonCodes = Array.from(new Set([].concat(_toConsumableArray(matchedCell.reasonCodes), _toConsumableArray(isExcludedScheduleDate ? ['schedule_excluded_date'] : []))));
1643
1750
  var status = function () {
1644
1751
  if (isPast) return 'past';
1645
1752
  if (matchedCell.status === 'unavailable') return 'unavailable';
@@ -1657,7 +1764,7 @@ function evaluateCandidate(projection, candidate, request) {
1657
1764
  remainingCapacity: remainingCapacity,
1658
1765
  maxCapacity: maxCapacity,
1659
1766
  maxPartySize: maxPartySize,
1660
- reasonCodes: isPast ? Array.from(new Set([].concat(_toConsumableArray(matchedCell.reasonCodes), ['past']))) : matchedCell.reasonCodes,
1767
+ reasonCodes: isPast ? Array.from(new Set([].concat(reasonCodes, ['past']))) : reasonCodes,
1661
1768
  matchedCell: matchedCell
1662
1769
  };
1663
1770
  });
@@ -1715,7 +1822,9 @@ function evaluateCandidate(projection, candidate, request) {
1715
1822
  return group.status;
1716
1823
  });
1717
1824
  var gate = evaluateCandidateTimeGate({
1825
+ projection: projection,
1718
1826
  product: product,
1827
+ candidate: candidate,
1719
1828
  candidateRange: candidateRange,
1720
1829
  evaluationContext: evaluationContext,
1721
1830
  timezoneName: projection.meta.timezone
@@ -1804,6 +1913,7 @@ function assertRangesWithinProjection(projection, ranges) {
1804
1913
  }
1805
1914
  export function queryAvailabilityProjection(projection, request) {
1806
1915
  var _extractFixedOffsetMi6, _request$productIds2;
1916
+ var runtimeOptions = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
1807
1917
  var normalizedPartySize = normalizePartySize(request.partySize);
1808
1918
  var evaluatedAt = formatDateTime(localDateToScalar(new Date()), projection.meta.timezone, (_extractFixedOffsetMi6 = extractFixedOffsetMinutes(projection.meta.now)) !== null && _extractFixedOffsetMi6 !== void 0 ? _extractFixedOffsetMi6 : captureRuntimeOffsetMinutes());
1809
1919
  if (request.target === 'time') {
@@ -1826,7 +1936,9 @@ export function queryAvailabilityProjection(projection, request) {
1826
1936
  var evaluationContext = buildEvaluationContext({
1827
1937
  projection: projection,
1828
1938
  evaluatedAt: evaluatedAt,
1829
- resourceIds: request.resourceIds
1939
+ resourceIds: request.resourceIds,
1940
+ useExactDurationCandidateMinute: runtimeOptions.useExactDurationCandidateMinute,
1941
+ allowExcludedScheduleDateOverride: runtimeOptions.allowExcludedScheduleDateOverride
1830
1942
  });
1831
1943
  if (request.target === 'time') {
1832
1944
  var candidates = dedupeProductRanges(request.candidates);
@@ -1914,7 +2026,9 @@ export function queryAvailabilityProjection(projection, request) {
1914
2026
  resourceIds: request.resourceIds,
1915
2027
  partySize: normalizedPartySize
1916
2028
  }, {
1917
- evaluatedAt: evaluatedAt
2029
+ evaluatedAt: evaluatedAt,
2030
+ useExactDurationCandidateMinute: runtimeOptions.useExactDurationCandidateMinute,
2031
+ allowExcludedScheduleDateOverride: runtimeOptions.allowExcludedScheduleDateOverride
1918
2032
  });
1919
2033
  var candidateGroupMap = new Map(candidateGroups.map(function (group) {
1920
2034
  return [String(group.productId), group];
@@ -2092,6 +2206,7 @@ function summarizeProductStatus(statuses) {
2092
2206
  }
2093
2207
  export function validateAvailabilitySelection(projection, params) {
2094
2208
  var _extractFixedOffsetMi7;
2209
+ var runtimeOptions = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
2095
2210
  var normalizedPartySize = normalizePartySize(params.partySize);
2096
2211
  var normalizedBookingCount = normalizeBookingCount(params.bookingCount);
2097
2212
  assertRangesWithinProjection(projection, [params.candidate]);
@@ -2112,14 +2227,16 @@ export function validateAvailabilitySelection(projection, params) {
2112
2227
  evaluationContext: buildEvaluationContext({
2113
2228
  projection: projection,
2114
2229
  evaluatedAt: evaluatedAt,
2115
- requirementSelections: params.requirementSelections
2230
+ requirementSelections: params.requirementSelections,
2231
+ useExactDurationCandidateMinute: runtimeOptions.useExactDurationCandidateMinute,
2232
+ allowExcludedScheduleDateOverride: runtimeOptions.allowExcludedScheduleDateOverride
2116
2233
  })
2117
2234
  });
2118
2235
  var conflicts = [];
2119
2236
  if (evaluated.gate) {
2120
2237
  conflicts.push({
2121
2238
  code: evaluated.gate.conflictCode,
2122
- message: evaluated.gate.status === 'past' ? '候选时间已过期' : '候选时间不满足预订时间门禁',
2239
+ message: evaluated.gate.conflictCode === 'past' ? '候选时间已过期' : evaluated.gate.conflictCode === 'schedule_excluded_date' ? '候选日期已被日程排除' : '候选时间不满足预订时间门禁',
2123
2240
  reasonCodes: evaluated.gate.reasonCodes,
2124
2241
  productId: params.candidate.productId,
2125
2242
  candidateKey: params.candidate.key
@@ -101,6 +101,9 @@ export interface AvailabilityOperatingHoursSnapshot {
101
101
  scheduleIds: AvailabilityId[];
102
102
  missingScheduleIds: AvailabilityId[];
103
103
  windows: AvailabilityResourceWindow[];
104
+ /** Schedule windows omitted only because their date is excluded. POS may use
105
+ * these to surface an unavailable, manually selectable candidate. */
106
+ excludedWindows?: AvailabilityResourceWindow[];
104
107
  }
105
108
  export interface AvailabilityResource {
106
109
  id: AvailabilityId;
@@ -110,6 +113,8 @@ export interface AvailabilityResource {
110
113
  resourceType: AvailabilityResourceType;
111
114
  capacity: number;
112
115
  windows: AvailabilityResourceWindow[];
116
+ /** Resource work windows omitted only by an excluded schedule date. */
117
+ excludedWindows?: AvailabilityResourceWindow[];
113
118
  raw?: Record<string, any>;
114
119
  }
115
120
  export interface AvailabilityOccupancy extends AvailabilityAbsoluteRange {
@@ -245,6 +250,13 @@ export interface GetProductTimeRangesRequest {
245
250
  }
246
251
  export interface GetProductTimeRangesRuntimeOptions {
247
252
  evaluatedAt?: string;
253
+ /**
254
+ * Internal POS capability: fixed-duration candidates start from the stable
255
+ * context anchor's current minute instead of rounding up to ten minutes, and
256
+ * that minute remains selectable during before-start cutoff evaluation.
257
+ * Other callers keep the legacy alignment and strict second-level cutoff.
258
+ */
259
+ useExactDurationCandidateMinute?: boolean;
248
260
  /**
249
261
  * Internal POS capability: allow explicit duration/start overrides to append
250
262
  * a custom duration candidate for Session products. Fixed Session ranges are
@@ -252,6 +264,11 @@ export interface GetProductTimeRangesRuntimeOptions {
252
264
  * disabled.
253
265
  */
254
266
  allowSessionCustomRanges?: boolean;
267
+ /**
268
+ * Internal POS capability: materialize candidates whose schedule date was
269
+ * explicitly excluded, while availability evaluation keeps them unavailable.
270
+ */
271
+ allowExcludedScheduleDateOverride?: boolean;
255
272
  }
256
273
  export interface QueryAvailabilityBaseRequest {
257
274
  productIds?: AvailabilityId[];