@pisell/pisellos 2.2.299 → 2.2.300

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.
@@ -673,14 +673,9 @@ function normalizeResource(raw) {
673
673
  var id = (_ref5 = (_raw$resourceId = raw.resourceId) !== null && _raw$resourceId !== void 0 ? _raw$resourceId : raw.resource_id) !== null && _ref5 !== void 0 ? _ref5 : raw.id;
674
674
  if (id === undefined || id === null || id === '') return null;
675
675
  var rawWindows = raw.windows || raw.times || [];
676
- return {
677
- id: id,
678
- formId: (_ref6 = (_raw$formId = raw.formId) !== null && _raw$formId !== void 0 ? _raw$formId : raw.form_id) !== null && _ref6 !== void 0 ? _ref6 : raw.resource_form_id,
679
- name: raw.resourceName || raw.main_field || raw.name || raw.title,
680
- code: raw.code,
681
- resourceType: normalizeResourceType(raw.type || raw.resourceType),
682
- capacity: Math.max(1, Number(raw.capacity || raw.max_capacity || 1) || 1),
683
- windows: (Array.isArray(rawWindows) ? rawWindows : []).flatMap(function (window) {
676
+ var rawExcludedWindows = raw.excludedWindows || raw.excluded_windows || [];
677
+ var normalizeWindows = function normalizeWindows(windows) {
678
+ return (Array.isArray(windows) ? windows : []).flatMap(function (window) {
684
679
  var range = normalizeAvailabilityLocalDateTimeRange({
685
680
  startAt: window.startAt || window.start_at || window.start,
686
681
  endAt: window.endAt || window.end_at || window.end,
@@ -694,7 +689,17 @@ function normalizeResource(raw) {
694
689
  source: window.source,
695
690
  raw: window
696
691
  }];
697
- }),
692
+ });
693
+ };
694
+ return {
695
+ id: id,
696
+ formId: (_ref6 = (_raw$formId = raw.formId) !== null && _raw$formId !== void 0 ? _raw$formId : raw.form_id) !== null && _ref6 !== void 0 ? _ref6 : raw.resource_form_id,
697
+ name: raw.resourceName || raw.main_field || raw.name || raw.title,
698
+ code: raw.code,
699
+ resourceType: normalizeResourceType(raw.type || raw.resourceType),
700
+ capacity: Math.max(1, Number(raw.capacity || raw.max_capacity || 1) || 1),
701
+ windows: normalizeWindows(rawWindows),
702
+ excludedWindows: normalizeWindows(rawExcludedWindows),
698
703
  raw: raw
699
704
  };
700
705
  }
@@ -718,6 +723,16 @@ function dateRangeDays(dateRange) {
718
723
  }
719
724
  return dates;
720
725
  }
726
+ function getExcludedScheduleDateOverrideTimePoints(date, schedule) {
727
+ var _schedule$repeat_rule;
728
+ if (!((_schedule$repeat_rule = schedule.repeat_rule) !== null && _schedule$repeat_rule !== void 0 && (_schedule$repeat_rule = _schedule$repeat_rule.excluded_date) !== null && _schedule$repeat_rule !== void 0 && _schedule$repeat_rule.length)) return [];
729
+ if (getScheduleStartEndTimePoints(date, [schedule]).length > 0) return [];
730
+ return getScheduleStartEndTimePoints(date, [_objectSpread(_objectSpread({}, schedule), {}, {
731
+ repeat_rule: _objectSpread(_objectSpread({}, schedule.repeat_rule), {}, {
732
+ excluded_date: []
733
+ })
734
+ })]);
735
+ }
721
736
  function normalizeResourceOccupancy(rawEvent, resourceId) {
722
737
  var _ref9, _ref10, _rawEvent$start_at, _ref11, _ref12, _rawEvent$end_at, _ref13, _ref14, _rawEvent$pax, _rawEvent$schedule_ev, _ref15, _rawEvent$schedule_ev2, _ref16, _rawEvent$booking_uid, _rawEvent$metadata, _ref17, _rawEvent$product_uid, _rawEvent$metadata2;
723
738
  var source = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 'remote';
@@ -824,7 +839,30 @@ export function buildAvailabilityResourcesFromV2(params) {
824
839
  });
825
840
  });
826
841
  });
842
+ var excludedWindows = dates.flatMap(function (date) {
843
+ return schedules.flatMap(function (schedule) {
844
+ return getExcludedScheduleDateOverrideTimePoints(date, schedule).flatMap(function (slot) {
845
+ var range = normalizeAvailabilityLocalDateTimeRange({
846
+ startAt: slot.start_at,
847
+ endAt: slot.end_at,
848
+ fixedOffsetMinutes: fixedOffsetMinutes,
849
+ rollOverEnd: true
850
+ });
851
+ if (!range) return [];
852
+ return [{
853
+ startAt: range.startAt,
854
+ endAt: range.endAt,
855
+ source: "resource_schedule_excluded:".concat(schedule.id),
856
+ raw: {
857
+ scheduleId: schedule.id,
858
+ excludedDate: date
859
+ }
860
+ }];
861
+ });
862
+ });
863
+ });
827
864
  resource.windows = materializedWindows.length > 0 ? materializedWindows : explicitWindows;
865
+ resource.excludedWindows = [].concat(_toConsumableArray(resource.excludedWindows || []), _toConsumableArray(excludedWindows));
828
866
  if (resource.windows.length === 0 && missingScheduleIds.length > 0) {
829
867
  resource.windows = buildResourceWindowFallbackTimes({
830
868
  raw: raw,
@@ -1092,7 +1130,7 @@ function buildBookingFromAvailabilitySelection(params) {
1092
1130
  } : {})
1093
1131
  });
1094
1132
  }
1095
- var POS_OVERRIDABLE_AVAILABILITY_CONFLICT_CODES = new Set(['past', 'booking_cutoff', 'resource_full', 'resource_unavailable', 'resource_capacity_exceeded',
1133
+ var POS_OVERRIDABLE_AVAILABILITY_CONFLICT_CODES = new Set(['past', 'booking_cutoff', 'schedule_excluded_date', 'resource_full', 'resource_unavailable', 'resource_capacity_exceeded',
1096
1134
  // A required group whose selected resource is outside its window is surfaced
1097
1135
  // as missing_resource at group level. Structural validation below still
1098
1136
  // rejects a genuinely missing selection.
@@ -3053,12 +3091,35 @@ export var UnifiedBookingSalesImpl = /*#__PURE__*/function (_BookingTicket) {
3053
3091
  }] : [];
3054
3092
  });
3055
3093
  });
3056
- return {
3094
+ var excludedWindows = dateRangeDays(params.dateRange).flatMap(function (date) {
3095
+ return schedules.flatMap(function (schedule) {
3096
+ return getExcludedScheduleDateOverrideTimePoints(date, schedule).flatMap(function (slot) {
3097
+ var range = normalizeAvailabilityLocalDateTimeRange({
3098
+ startAt: slot.start_at,
3099
+ endAt: slot.end_at,
3100
+ fixedOffsetMinutes: _this3.getAvailabilityRuntimeOffsetMinutes(),
3101
+ rollOverEnd: true
3102
+ });
3103
+ return range ? [{
3104
+ startAt: range.startAt,
3105
+ endAt: range.endAt,
3106
+ source: "operating_schedule_excluded:".concat(schedule.id),
3107
+ raw: {
3108
+ scheduleId: schedule.id,
3109
+ excludedDate: date
3110
+ }
3111
+ }] : [];
3112
+ });
3113
+ });
3114
+ });
3115
+ return _objectSpread({
3057
3116
  source: descriptor.source,
3058
3117
  scheduleIds: _toConsumableArray(descriptor.scheduleIds),
3059
3118
  missingScheduleIds: missingScheduleIds,
3060
3119
  windows: windows
3061
- };
3120
+ }, excludedWindows.length > 0 ? {
3121
+ excludedWindows: excludedWindows
3122
+ } : {});
3062
3123
  }
3063
3124
  }, {
3064
3125
  key: "loadAvailabilityProducts",
@@ -4232,7 +4293,8 @@ export var UnifiedBookingSalesImpl = /*#__PURE__*/function (_BookingTicket) {
4232
4293
  allowOutsideOperatingHoursForRequestedStartTimes: true
4233
4294
  } : {}), {
4234
4295
  evaluatedAt: this.getAvailabilityRuntimeDateTime(),
4235
- allowSessionCustomRanges: isPosAvailabilityRequest
4296
+ allowSessionCustomRanges: isPosAvailabilityRequest,
4297
+ allowExcludedScheduleDateOverride: isPosAvailabilityRequest
4236
4298
  }).map(function (group) {
4237
4299
  return _objectSpread(_objectSpread({}, group), {}, {
4238
4300
  ranges: group.ranges.map(function (candidate) {
@@ -4255,7 +4317,8 @@ export var UnifiedBookingSalesImpl = /*#__PURE__*/function (_BookingTicket) {
4255
4317
  key: "queryBookingAvailability",
4256
4318
  value: (function () {
4257
4319
  var _queryBookingAvailability = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee36(contextId, request) {
4258
- var _yield$this$ensureAva2, context, projectionEntry, refreshed, result, contextVersion, contextualResult;
4320
+ var _this$otherParams$pla2, _this$otherParams19;
4321
+ var _yield$this$ensureAva2, context, projectionEntry, refreshed, isPosAvailabilityRequest, result, contextVersion, contextualResult;
4259
4322
  return _regeneratorRuntime().wrap(function _callee36$(_context36) {
4260
4323
  while (1) switch (_context36.prev = _context36.next) {
4261
4324
  case 0:
@@ -4267,7 +4330,10 @@ export var UnifiedBookingSalesImpl = /*#__PURE__*/function (_BookingTicket) {
4267
4330
  projectionEntry = _yield$this$ensureAva2.projectionEntry;
4268
4331
  refreshed = _yield$this$ensureAva2.refreshed;
4269
4332
  this.assertContextualQueryFreshness(context, request);
4270
- result = queryAvailabilityProjection(projectionEntry.projection, request);
4333
+ isPosAvailabilityRequest = String((_this$otherParams$pla2 = (_this$otherParams19 = this.otherParams) === null || _this$otherParams19 === void 0 ? void 0 : _this$otherParams19.platform) !== null && _this$otherParams$pla2 !== void 0 ? _this$otherParams$pla2 : '').trim().toLowerCase() === 'pos';
4334
+ result = queryAvailabilityProjection(projectionEntry.projection, request, {
4335
+ allowExcludedScheduleDateOverride: isPosAvailabilityRequest
4336
+ });
4271
4337
  contextVersion = context.activeVersion;
4272
4338
  contextualResult = result.target === 'time' ? _objectSpread(_objectSpread({}, result), {}, {
4273
4339
  data: result.data.map(function (group) {
@@ -4299,7 +4365,7 @@ export var UnifiedBookingSalesImpl = /*#__PURE__*/function (_BookingTicket) {
4299
4365
  contextVersion: contextVersion,
4300
4366
  refreshed: refreshed
4301
4367
  }));
4302
- case 11:
4368
+ case 12:
4303
4369
  case "end":
4304
4370
  return _context36.stop();
4305
4371
  }
@@ -4376,7 +4442,7 @@ export var UnifiedBookingSalesImpl = /*#__PURE__*/function (_BookingTicket) {
4376
4442
  key: "commitAvailabilitySelection",
4377
4443
  value: (function () {
4378
4444
  var _commitAvailabilitySelection = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee37(contextId, params) {
4379
- var normalizedContextId, _params$bookingCount, _ref46, _params$orderProduct$, _params$orderProduct, _params$orderProduct2, _params$orderProduct3, _params$orderProduct$2, _params$orderProduct4, _params$orderProduct5, _editingLine$product_, _baseProjection$meta$, _refreshedProjection$, _baseProjection$meta$2, _this$otherParams$pla2, _this$otherParams19, _ref47, _params$orderProduct$3, _params$orderProduct6, _existingBooking$meta, _ref48, _ref49, _params$orderProduct$4, _params$orderProduct7, _params$orderProduct8, _existingBooking$meta2, _existingBooking$hold, bookingCount, orderProductQuantity, partySize, _yield$this$ensureAva3, context, projectionEntry, refreshed, boundEditingUids, orderProductId, editingLine, _editingLine$product_2, baseProjection, commitWriteGuard, prepareParams, fallbackResources, commitSnapshot, refreshedProjection, _baseProjection$meta$3, latestContext, candidateDurationMinutes, candidateStartTime, allowPosAvailabilityOverride, refreshedRanges, refreshedCandidateBase, product, validation, forced, capacityConflict, revalidationEntry, revalidatedContext, refreshedCandidate, existingBooking, existingBookingUid, booking, transformed, productLineUid, bookingUid, productLineUids, bookingUids, _transformed$product_, _transformed$product_2, _transformed$product_3, _ref50, _transformed$product_4, _this$getTempOrder4, _this$getTempOrder5, _this$getTempOrder6, beforeProductLineUids, beforeBookingUids, products, addedLines, addedBookings, nextContext, result;
4445
+ var normalizedContextId, _params$bookingCount, _ref46, _params$orderProduct$, _params$orderProduct, _params$orderProduct2, _params$orderProduct3, _params$orderProduct$2, _params$orderProduct4, _params$orderProduct5, _editingLine$product_, _baseProjection$meta$, _refreshedProjection$, _baseProjection$meta$2, _this$otherParams$pla3, _this$otherParams20, _ref47, _params$orderProduct$3, _params$orderProduct6, _existingBooking$meta, _ref48, _ref49, _params$orderProduct$4, _params$orderProduct7, _params$orderProduct8, _existingBooking$meta2, _existingBooking$hold, bookingCount, orderProductQuantity, partySize, _yield$this$ensureAva3, context, projectionEntry, refreshed, boundEditingUids, orderProductId, editingLine, _editingLine$product_2, baseProjection, commitWriteGuard, prepareParams, fallbackResources, commitSnapshot, refreshedProjection, _baseProjection$meta$3, latestContext, candidateDurationMinutes, candidateStartTime, allowPosAvailabilityOverride, refreshedRanges, refreshedCandidateBase, product, validation, forced, capacityConflict, revalidationEntry, revalidatedContext, refreshedCandidate, existingBooking, existingBookingUid, booking, transformed, productLineUid, bookingUid, productLineUids, bookingUids, _transformed$product_, _transformed$product_2, _transformed$product_3, _ref50, _transformed$product_4, _this$getTempOrder4, _this$getTempOrder5, _this$getTempOrder6, beforeProductLineUids, beforeBookingUids, products, addedLines, addedBookings, nextContext, result;
4380
4446
  return _regeneratorRuntime().wrap(function _callee37$(_context37) {
4381
4447
  while (1) switch (_context37.prev = _context37.next) {
4382
4448
  case 0:
@@ -4606,7 +4672,7 @@ export var UnifiedBookingSalesImpl = /*#__PURE__*/function (_BookingTicket) {
4606
4672
  this.assertAvailabilityCommitWriteCAS(commitWriteGuard);
4607
4673
  candidateDurationMinutes = getCandidateDurationMinutes(params.candidate);
4608
4674
  candidateStartTime = getCandidateStartTime(params.candidate);
4609
- allowPosAvailabilityOverride = String((_this$otherParams$pla2 = (_this$otherParams19 = this.otherParams) === null || _this$otherParams19 === void 0 ? void 0 : _this$otherParams19.platform) !== null && _this$otherParams$pla2 !== void 0 ? _this$otherParams$pla2 : '').trim().toLowerCase() === 'pos';
4675
+ allowPosAvailabilityOverride = String((_this$otherParams$pla3 = (_this$otherParams20 = this.otherParams) === null || _this$otherParams20 === void 0 ? void 0 : _this$otherParams20.platform) !== null && _this$otherParams$pla3 !== void 0 ? _this$otherParams$pla3 : '').trim().toLowerCase() === 'pos';
4610
4676
  refreshedRanges = getProjectionProductTimeRanges(refreshedProjection, _objectSpread({
4611
4677
  productIds: [params.candidate.productId],
4612
4678
  dateRange: {
@@ -4620,7 +4686,8 @@ export var UnifiedBookingSalesImpl = /*#__PURE__*/function (_BookingTicket) {
4620
4686
  }, allowPosAvailabilityOverride ? {
4621
4687
  allowOutsideOperatingHoursForRequestedStartTimes: true
4622
4688
  } : {}) : {}) : {}), {
4623
- allowSessionCustomRanges: allowPosAvailabilityOverride
4689
+ allowSessionCustomRanges: allowPosAvailabilityOverride,
4690
+ allowExcludedScheduleDateOverride: allowPosAvailabilityOverride
4624
4691
  }).flatMap(function (group) {
4625
4692
  return group.ranges;
4626
4693
  });
@@ -4653,6 +4720,8 @@ export var UnifiedBookingSalesImpl = /*#__PURE__*/function (_BookingTicket) {
4653
4720
  requirementSelections: params.requirementSelections,
4654
4721
  partySize: partySize,
4655
4722
  bookingCount: bookingCount
4723
+ }, {
4724
+ allowExcludedScheduleDateOverride: allowPosAvailabilityOverride
4656
4725
  });
4657
4726
  if (allowPosAvailabilityOverride && !validation.ok && validation.conflicts.every(function (conflict) {
4658
4727
  return POS_OVERRIDABLE_AVAILABILITY_CONFLICT_CODES.has(conflict.code);
@@ -1 +1,46 @@
1
- "use strict";
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ Object.defineProperty(exports, "BUY_X_GET_Y_FREE_STRATEGY", {
7
+ enumerable: true,
8
+ get: function () {
9
+ return _examples.BUY_X_GET_Y_FREE_STRATEGY;
10
+ }
11
+ });
12
+ Object.defineProperty(exports, "ITEM_REWARD_STRATEGY", {
13
+ enumerable: true,
14
+ get: function () {
15
+ return _examples.ITEM_REWARD_STRATEGY;
16
+ }
17
+ });
18
+ Object.defineProperty(exports, "PromotionAdapter", {
19
+ enumerable: true,
20
+ get: function () {
21
+ return _adapter.PromotionAdapter;
22
+ }
23
+ });
24
+ Object.defineProperty(exports, "PromotionEvaluator", {
25
+ enumerable: true,
26
+ get: function () {
27
+ return _evaluator.PromotionEvaluator;
28
+ }
29
+ });
30
+ Object.defineProperty(exports, "X_ITEMS_FOR_Y_PRICE_STRATEGY", {
31
+ enumerable: true,
32
+ get: function () {
33
+ return _examples.X_ITEMS_FOR_Y_PRICE_STRATEGY;
34
+ }
35
+ });
36
+ Object.defineProperty(exports, "default", {
37
+ enumerable: true,
38
+ get: function () {
39
+ return _adapter.default;
40
+ }
41
+ });
42
+ var _evaluator = require("./evaluator");
43
+ var _adapter = _interopRequireWildcard(require("./adapter"));
44
+ var _examples = require("./examples");
45
+ function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
46
+ function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
@@ -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;
@@ -166,9 +166,65 @@ function addLeadWindow(base, unit, unitType, _timezoneName) {
166
166
  if (unitType === 'hours') return (0, _localClock.addLocalHours)(base, unit);
167
167
  return (0, _localClock.addLocalMinutes)(base, unit);
168
168
  }
169
+ function normalizedWindowContainsRange(windows, candidateRange, timezoneName) {
170
+ return windows.some(window => {
171
+ const normalized = normalizeAbsoluteRange(window, timezoneName);
172
+ return Boolean(normalized && contains(normalized, candidateRange));
173
+ });
174
+ }
175
+ function normalizedWindowsContainDate(windows, date, timezoneName) {
176
+ return windows.some(window => {
177
+ const normalized = normalizeAbsoluteRange(window, timezoneName);
178
+ return Boolean(normalized && formatDate(normalized.start, timezoneName) === date);
179
+ });
180
+ }
181
+ function isRangeFromExcludedWindows(params) {
182
+ const candidateDate = formatDate(params.candidateRange.start, params.timezoneName);
183
+ const hasNormalWindowOnDate = normalizedWindowsContainDate(params.normalWindows, candidateDate, params.timezoneName);
184
+ const hasExcludedWindowOnDate = normalizedWindowsContainDate(params.excludedWindows, candidateDate, params.timezoneName);
185
+ if (!hasNormalWindowOnDate && hasExcludedWindowOnDate) return true;
186
+ return !normalizedWindowContainsRange(params.normalWindows, params.candidateRange, params.timezoneName) && normalizedWindowContainsRange(params.excludedWindows, params.candidateRange, params.timezoneName);
187
+ }
188
+ function isCandidateFromExcludedScheduleDate(params) {
189
+ const {
190
+ projection,
191
+ product,
192
+ candidate,
193
+ candidateRange
194
+ } = params;
195
+ const operatingHours = projection.meta.defaults.operatingHours;
196
+ if (isRangeFromExcludedWindows({
197
+ normalWindows: operatingHours.windows,
198
+ excludedWindows: operatingHours.excludedWindows || [],
199
+ candidateRange,
200
+ timezoneName: projection.meta.timezone
201
+ })) {
202
+ return true;
203
+ }
204
+ if (product.kind !== 'session' && product.kind !== 'venue_slot') {
205
+ return false;
206
+ }
207
+ if (candidate.source !== 'session_schedule') {
208
+ const hasNormalRule = (product.sessionRules || []).some(rule => matchesScheduleRuleDate(rule, candidate.date, projection.meta.timezone));
209
+ const hasExcludedOverrideRule = (product.sessionRules || []).some(rule => !matchesScheduleRuleDate(rule, candidate.date, projection.meta.timezone) && matchesScheduleRuleDate(rule, candidate.date, projection.meta.timezone, true));
210
+ return !hasNormalRule && hasExcludedOverrideRule;
211
+ }
212
+ const candidateDateRange = {
213
+ startDate: candidate.date,
214
+ endDate: candidate.date
215
+ };
216
+ const candidateIdentity = getProductTimeRangeIdentity(candidate);
217
+ const normalRanges = getSessionRangesForProduct(projection, product, candidateDateRange);
218
+ if (normalRanges.some(range => getProductTimeRangeIdentity(range) === candidateIdentity)) {
219
+ return false;
220
+ }
221
+ return getSessionRangesForProduct(projection, product, candidateDateRange, true).some(range => getProductTimeRangeIdentity(range) === candidateIdentity);
222
+ }
169
223
  function evaluateCandidateTimeGate(params) {
170
224
  const {
225
+ projection,
171
226
  product,
227
+ candidate,
172
228
  candidateRange,
173
229
  evaluationContext,
174
230
  timezoneName
@@ -181,6 +237,18 @@ function evaluateCandidateTimeGate(params) {
181
237
  conflictCode: 'past'
182
238
  };
183
239
  }
240
+ if (evaluationContext.allowExcludedScheduleDateOverride && isCandidateFromExcludedScheduleDate({
241
+ projection,
242
+ product,
243
+ candidate,
244
+ candidateRange
245
+ })) {
246
+ return {
247
+ status: 'unavailable',
248
+ reasonCodes: ['schedule_excluded_date'],
249
+ conflictCode: 'schedule_excluded_date'
250
+ };
251
+ }
184
252
  const policy = product.cutOffPolicy;
185
253
  if (!policy) return null;
186
254
  if (resolveCutOffPolicyIssue(policy)) {
@@ -324,8 +392,8 @@ function normalizeCoverage(input) {
324
392
  }
325
393
  };
326
394
  }
327
- function normalizeResourceWindow(resource, coverage, timezoneName) {
328
- return mergeRanges((resource.windows || []).map(window => normalizeAbsoluteRange(window, timezoneName)).filter(window => Boolean(window)).map(window => clipRange(window, coverage)).filter(window => Boolean(window)));
395
+ function normalizeResourceWindow(resource, coverage, timezoneName, includeExcludedWindows = false) {
396
+ return mergeRanges([...(resource.windows || []), ...(includeExcludedWindows ? resource.excludedWindows || [] : [])].map(window => normalizeAbsoluteRange(window, timezoneName)).filter(window => Boolean(window)).map(window => clipRange(window, coverage)).filter(window => Boolean(window)));
329
397
  }
330
398
  function normalizeOccupancy(occupancy, coverage, timezoneName, fixedOffsetMinutes) {
331
399
  const normalized = normalizeAbsoluteRange(occupancy, timezoneName);
@@ -389,6 +457,9 @@ function normalizeResources(resources) {
389
457
  capacity: Math.max(1, Number(resource.capacity || 1) || 1),
390
458
  windows: (resource.windows || []).map(window => ({
391
459
  ...window
460
+ })),
461
+ excludedWindows: (resource.excludedWindows || []).map(window => ({
462
+ ...window
392
463
  }))
393
464
  }));
394
465
  }
@@ -408,15 +479,20 @@ function normalizeOperatingHours(params) {
408
479
  startAt: formatDateTime(range.start, params.timezoneName, params.fixedOffsetMinutes),
409
480
  endAt: formatDateTime(range.end, params.timezoneName, params.fixedOffsetMinutes)
410
481
  }));
411
- const windows = mergeRanges(sourceWindows.map(window => normalizeAbsoluteRange(window, params.timezoneName)).filter(window => Boolean(window)).map(window => clipRange(window, params.coverage)).filter(window => Boolean(window))).map(range => ({
482
+ const normalizeWindows = source => mergeRanges(source.map(window => normalizeAbsoluteRange(window, params.timezoneName)).filter(window => Boolean(window)).map(window => clipRange(window, params.coverage)).filter(window => Boolean(window))).map(range => ({
412
483
  startAt: formatDateTime(range.start, params.timezoneName, params.fixedOffsetMinutes),
413
484
  endAt: formatDateTime(range.end, params.timezoneName, params.fixedOffsetMinutes)
414
485
  }));
486
+ const windows = normalizeWindows(sourceWindows);
487
+ const excludedWindows = normalizeWindows(params.input?.excludedWindows || []);
415
488
  return {
416
489
  source: params.input?.source || params.fallbackSource,
417
490
  scheduleIds: uniqById(params.input?.scheduleIds || []),
418
491
  missingScheduleIds: uniqById(params.input?.missingScheduleIds || []),
419
- windows
492
+ windows,
493
+ ...(excludedWindows.length > 0 ? {
494
+ excludedWindows
495
+ } : {})
420
496
  };
421
497
  }
422
498
  function matchesRequirementGroup(requirementGroup, resource, product, partySize = 1) {
@@ -698,19 +774,19 @@ function resolveProjectionDateRange(projection, requestDateRange) {
698
774
  endDate
699
775
  };
700
776
  }
701
- function buildOperatingRanges(projection, dateRange) {
777
+ function buildOperatingRanges(projection, dateRange, includeExcludedWindows = false) {
702
778
  const coverage = {
703
779
  start: toDateStart(dateRange.startDate, projection.meta.timezone),
704
780
  end: toDateEnd(nextDate(dateRange.endDate), projection.meta.timezone)
705
781
  };
706
- return mergeRanges(projection.meta.defaults.operatingHours.windows.map(window => normalizeAbsoluteRange(window, projection.meta.timezone)).filter(window => Boolean(window)).map(window => clipRange(window, coverage)).filter(window => Boolean(window)));
782
+ return mergeRanges([...projection.meta.defaults.operatingHours.windows, ...(includeExcludedWindows ? projection.meta.defaults.operatingHours.excludedWindows || [] : [])].map(window => normalizeAbsoluteRange(window, projection.meta.timezone)).filter(window => Boolean(window)).map(window => clipRange(window, coverage)).filter(window => Boolean(window)));
707
783
  }
708
- function buildResourceWindowIndex(projection) {
784
+ function buildResourceWindowIndex(projection, includeExcludedWindows = false) {
709
785
  const coverage = {
710
786
  start: toDateTime(projection.meta.coverage.startAt, projection.meta.timezone),
711
787
  end: toDateTime(projection.meta.coverage.endAt, projection.meta.timezone)
712
788
  };
713
- return new Map(projection.resources.map(resource => [String(resource.id), normalizeResourceWindow(resource, coverage, projection.meta.timezone)]));
789
+ return new Map(projection.resources.map(resource => [String(resource.id), normalizeResourceWindow(resource, coverage, projection.meta.timezone, includeExcludedWindows)]));
714
790
  }
715
791
  function buildGroupWorkingRanges(params) {
716
792
  const {
@@ -727,7 +803,7 @@ function buildGroupWorkingRanges(params) {
727
803
  };
728
804
  const filteredResourceIdSet = resourceIds?.length ? new Set(resourceIds.map(String)) : null;
729
805
  const resourceWindows = projection.resources.filter(resource => matchesRequirementGroup(requirementGroup, resource, params.product, partySize)).filter(resource => !filteredResourceIdSet || filteredResourceIdSet.has(String(resource.id))).map(resource => {
730
- const windows = params.resourceWindowIndex?.get(String(resource.id)) || normalizeResourceWindow(resource, coverage, projection.meta.timezone);
806
+ const windows = params.resourceWindowIndex?.get(String(resource.id)) || normalizeResourceWindow(resource, coverage, projection.meta.timezone, params.includeExcludedWindows === true);
731
807
  return windows.map(window => clipRange(window, coverage)).filter(window => Boolean(window));
732
808
  });
733
809
  const requiredConcurrentResources = Math.max(requirementGroup.required === false ? 0 : 1, Number(requirementGroup.min ?? (requirementGroup.required === false ? 0 : 1)) || 0);
@@ -758,12 +834,12 @@ function buildGroupWorkingRanges(params) {
758
834
  }
759
835
  return mergeRanges(satisfiedRanges);
760
836
  }
761
- function materializeScheduleRules(rules, dateRange, timezoneName, fixedOffsetMinutes) {
837
+ function materializeScheduleRules(rules, dateRange, timezoneName, fixedOffsetMinutes, allowExcludedScheduleDates = false) {
762
838
  const dates = enumerateDates(dateRange.startDate, dateRange.endDate);
763
839
  const seen = new Map();
764
840
  rules.forEach(rule => {
765
841
  dates.forEach(date => {
766
- if (!matchesScheduleRuleDate(rule, date, timezoneName)) return;
842
+ if (!matchesScheduleRuleDate(rule, date, timezoneName, allowExcludedScheduleDates)) return;
767
843
  const start = toDateTimeInDate(date, rule.startTime, timezoneName);
768
844
  let end = toDateTimeInDate(date, rule.endTime, timezoneName);
769
845
  if (end <= start) end = toDateTimeInDate(nextDate(date, timezoneName), rule.endTime, timezoneName);
@@ -795,9 +871,9 @@ function materializeScheduleRules(rules, dateRange, timezoneName, fixedOffsetMin
795
871
  });
796
872
  return Array.from(seen.values());
797
873
  }
798
- function matchesScheduleRuleDate(rule, date, _timezoneName) {
874
+ function matchesScheduleRuleDate(rule, date, _timezoneName, allowExcludedScheduleDate = false) {
799
875
  if (rule.includeDates?.includes(date)) return true;
800
- if (rule.excludeDates?.includes(date)) return false;
876
+ if (!allowExcludedScheduleDate && rule.excludeDates?.includes(date)) return false;
801
877
  if (rule.startDate && date < rule.startDate) return false;
802
878
  if (rule.endDate && date > rule.endDate) return false;
803
879
  if (rule.mode === 'designation') {
@@ -841,7 +917,7 @@ function buildGroupResourceIndex(params) {
841
917
  });
842
918
  return index;
843
919
  }
844
- function getSessionRangesForProduct(projection, product, dateRange) {
920
+ function getSessionRangesForProduct(projection, product, dateRange, allowExcludedScheduleDates = false) {
845
921
  const fixedOffsetMinutes = (0, _localClock.extractFixedOffsetMinutes)(projection.meta.now) ?? (0, _localClock.captureRuntimeOffsetMinutes)();
846
922
  const ranges = (product.sessionRanges || []).map(range => {
847
923
  const normalized = normalizeAbsoluteRange(range, projection.meta.timezone);
@@ -866,7 +942,7 @@ function getSessionRangesForProduct(projection, product, dateRange) {
866
942
  primaryScheduleRef: range.scheduleRefs?.[0]
867
943
  };
868
944
  }).filter(Boolean);
869
- const ruleRanges = materializeScheduleRules(product.sessionRules || [], dateRange, projection.meta.timezone, fixedOffsetMinutes);
945
+ const ruleRanges = materializeScheduleRules(product.sessionRules || [], dateRange, projection.meta.timezone, fixedOffsetMinutes, allowExcludedScheduleDates);
870
946
  return dedupeProductRanges([...ranges, ...ruleRanges.map(range => ({
871
947
  ...range,
872
948
  key: `${String(product.id)}:${range.startAt}:${range.endAt}`,
@@ -916,7 +992,8 @@ function getProductTimeRanges(projection, request = {}, runtimeOptions = {}) {
916
992
  const dateRange = resolveProjectionDateRange(projection, request.dateRange);
917
993
  const slotStepMinutes = projection.meta.defaults.slotStepMinutes;
918
994
  const fixedOffsetMinutes = (0, _localClock.extractFixedOffsetMinutes)(projection.meta.now) ?? (0, _localClock.captureRuntimeOffsetMinutes)();
919
- const resourceWindowIndex = buildResourceWindowIndex(projection);
995
+ const allowExcludedScheduleDateOverride = runtimeOptions.allowExcludedScheduleDateOverride === true;
996
+ const resourceWindowIndex = buildResourceWindowIndex(projection, allowExcludedScheduleDateOverride);
920
997
  const filteredProductIdSet = request.productIds?.length ? new Set(request.productIds.map(String)) : null;
921
998
  return projection.products.filter(product => !filteredProductIdSet || filteredProductIdSet.has(String(product.id))).map(product => {
922
999
  if (product.kind === 'normal') {
@@ -929,7 +1006,7 @@ function getProductTimeRanges(projection, request = {}, runtimeOptions = {}) {
929
1006
  };
930
1007
  }
931
1008
  if (product.kind === 'session' || product.kind === 'venue_slot') {
932
- const fixedRanges = getSessionRangesForProduct(projection, product, dateRange);
1009
+ const fixedRanges = getSessionRangesForProduct(projection, product, dateRange, allowExcludedScheduleDateOverride);
933
1010
  const customRanges = [];
934
1011
  const customDurationMinutes = resolveRequestedSessionDurationMinutes(product, request, runtimeOptions.allowSessionCustomRanges === true);
935
1012
  const requestedStartTimes = resolveRequestedDurationStartTimes(product, request, runtimeOptions.allowSessionCustomRanges === true);
@@ -949,7 +1026,7 @@ function getProductTimeRanges(projection, request = {}, runtimeOptions = {}) {
949
1026
  };
950
1027
  }
951
1028
  const requiredGroups = (product.requirementGroups || []).filter(group => group.required !== false);
952
- const operatingRanges = buildOperatingRanges(projection, dateRange);
1029
+ const operatingRanges = buildOperatingRanges(projection, dateRange, allowExcludedScheduleDateOverride);
953
1030
  const operatingEndAt = dateRange.startDate === dateRange.endDate && operatingRanges.length > 0 ? formatDateTime(Math.max(...operatingRanges.map(range => range.end)), projection.meta.timezone, fixedOffsetMinutes) : undefined;
954
1031
  const resourceRanges = requiredGroups.length ? requiredGroups.map(group => buildGroupWorkingRanges({
955
1032
  projection,
@@ -958,7 +1035,8 @@ function getProductTimeRanges(projection, request = {}, runtimeOptions = {}) {
958
1035
  dateRange,
959
1036
  resourceIds: request.resourceIds,
960
1037
  partySize: normalizedPartySize,
961
- resourceWindowIndex
1038
+ resourceWindowIndex,
1039
+ includeExcludedWindows: allowExcludedScheduleDateOverride
962
1040
  })).reduce((current, ranges) => current === null ? ranges : intersectRangeLists(current, ranges), null) || [] : null;
963
1041
  const baseRanges = resourceRanges === null ? operatingRanges : intersectRangeLists(resourceRanges, operatingRanges);
964
1042
  const durationMinutes = resolveRequestedDurationMinutes(product, request);
@@ -1177,7 +1255,8 @@ function buildEvaluationContext(params) {
1177
1255
  evaluatedAt,
1178
1256
  evaluatedAtMs: evaluatedAt,
1179
1257
  resourceIdSet: params.resourceIds?.length ? new Set(params.resourceIds.map(String)) : null,
1180
- selectionsByGroupKey
1258
+ selectionsByGroupKey,
1259
+ allowExcludedScheduleDateOverride: params.allowExcludedScheduleDateOverride === true
1181
1260
  };
1182
1261
  }
1183
1262
  function summarizeStatuses(statuses) {
@@ -1327,7 +1406,8 @@ function evaluateCandidateSummary(projection, candidate, request) {
1327
1406
  const evaluationContext = request.evaluationContext || buildEvaluationContext({
1328
1407
  projection,
1329
1408
  evaluatedAt: request.evaluatedAt,
1330
- resourceIds: request.resourceIds
1409
+ resourceIds: request.resourceIds,
1410
+ allowExcludedScheduleDateOverride: request.allowExcludedScheduleDateOverride
1331
1411
  });
1332
1412
  const requestedPartySize = normalizePartySize(request.partySize);
1333
1413
  const requiredGroups = (product.requirementGroups || []).filter(group => group.required !== false).map(requirementGroup => evaluateRequirementGroupSummary({
@@ -1341,7 +1421,9 @@ function evaluateCandidateSummary(projection, candidate, request) {
1341
1421
  groupResourceIndex: request.groupResourceIndex
1342
1422
  }));
1343
1423
  const gate = evaluateCandidateTimeGate({
1424
+ projection,
1344
1425
  product,
1426
+ candidate,
1345
1427
  candidateRange,
1346
1428
  evaluationContext,
1347
1429
  timezoneName: projection.meta.timezone
@@ -1383,7 +1465,8 @@ function evaluateCandidate(projection, candidate, request) {
1383
1465
  projection,
1384
1466
  evaluatedAt: request.evaluatedAt,
1385
1467
  resourceIds: request.resourceIds,
1386
- requirementSelections: request.requirementSelections
1468
+ requirementSelections: request.requirementSelections,
1469
+ allowExcludedScheduleDateOverride: request.allowExcludedScheduleDateOverride
1387
1470
  });
1388
1471
  const requestedPartySize = normalizePartySize(request.partySize);
1389
1472
  const partySize = requestedPartySize ?? 1;
@@ -1396,6 +1479,12 @@ function evaluateCandidate(projection, candidate, request) {
1396
1479
  const matchedResources = (request.groupResourceIndex?.get([String(product.id), requirementGroup.id].join(':')) || projection.resources.filter(resource => matchesRequirementGroup(requirementGroup, resource, product, partySize)).filter(resource => !evaluationContext.resourceIdSet || evaluationContext.resourceIdSet.has(String(resource.id)))).filter(resource => !selection || selection.resourceIds.some(resourceId => sameId(resourceId, resource.id)));
1397
1480
  const options = matchedResources.map(resource => {
1398
1481
  const matchedCell = aggregateMatchedCells(product.id, requirementGroup.id, resource.id, candidateRange, request.cellIndex?.get([String(product.id), requirementGroup.id, String(resource.id)].join(':')) || []);
1482
+ const isExcludedScheduleDate = evaluationContext.allowExcludedScheduleDateOverride && isRangeFromExcludedWindows({
1483
+ normalWindows: resource.windows || [],
1484
+ excludedWindows: resource.excludedWindows || [],
1485
+ candidateRange,
1486
+ timezoneName: projection.meta.timezone
1487
+ });
1399
1488
  if (!matchedCell) {
1400
1489
  const isPast = candidateRange.endMs <= evaluationContext.evaluatedAtMs;
1401
1490
  return {
@@ -1406,7 +1495,7 @@ function evaluateCandidate(projection, candidate, request) {
1406
1495
  resourceType: resource.resourceType,
1407
1496
  status: isPast ? 'past' : 'unavailable',
1408
1497
  maxPartySize: 0,
1409
- reasonCodes: isPast ? ['past'] : ['outside_resource_window']
1498
+ reasonCodes: isPast ? ['past'] : [...(isExcludedScheduleDate ? ['schedule_excluded_date'] : []), 'outside_resource_window']
1410
1499
  };
1411
1500
  }
1412
1501
  const remainingCapacity = matchedCell.remainingCapacity;
@@ -1414,6 +1503,7 @@ function evaluateCandidate(projection, candidate, request) {
1414
1503
  const isPast = candidateRange.endMs <= evaluationContext.evaluatedAtMs;
1415
1504
  const maxPartySize = isPast ? 0 : Math.max(0, remainingCapacity || 0);
1416
1505
  const requiredCapacity = resolveRequiredCapacity(requirementGroup, product, requestedPartySize);
1506
+ const reasonCodes = Array.from(new Set([...matchedCell.reasonCodes, ...(isExcludedScheduleDate ? ['schedule_excluded_date'] : [])]));
1417
1507
  const status = (() => {
1418
1508
  if (isPast) return 'past';
1419
1509
  if (matchedCell.status === 'unavailable') return 'unavailable';
@@ -1431,7 +1521,7 @@ function evaluateCandidate(projection, candidate, request) {
1431
1521
  remainingCapacity,
1432
1522
  maxCapacity,
1433
1523
  maxPartySize,
1434
- reasonCodes: isPast ? Array.from(new Set([...matchedCell.reasonCodes, 'past'])) : matchedCell.reasonCodes,
1524
+ reasonCodes: isPast ? Array.from(new Set([...reasonCodes, 'past'])) : reasonCodes,
1435
1525
  matchedCell
1436
1526
  };
1437
1527
  });
@@ -1471,7 +1561,9 @@ function evaluateCandidate(projection, candidate, request) {
1471
1561
  const requiredGroups = requirementGroups.filter(group => group.required);
1472
1562
  const requiredStatuses = requiredGroups.map(group => group.status);
1473
1563
  const gate = evaluateCandidateTimeGate({
1564
+ projection,
1474
1565
  product,
1566
+ candidate,
1475
1567
  candidateRange,
1476
1568
  evaluationContext,
1477
1569
  timezoneName: projection.meta.timezone
@@ -1547,7 +1639,7 @@ function assertRangesWithinProjection(projection, ranges) {
1547
1639
  }
1548
1640
  });
1549
1641
  }
1550
- function queryAvailabilityProjection(projection, request) {
1642
+ function queryAvailabilityProjection(projection, request, runtimeOptions = {}) {
1551
1643
  const normalizedPartySize = normalizePartySize(request.partySize);
1552
1644
  const evaluatedAt = formatDateTime((0, _localClock.localDateToScalar)(new Date()), projection.meta.timezone, (0, _localClock.extractFixedOffsetMinutes)(projection.meta.now) ?? (0, _localClock.captureRuntimeOffsetMinutes)());
1553
1645
  if (request.target === 'time') {
@@ -1570,7 +1662,8 @@ function queryAvailabilityProjection(projection, request) {
1570
1662
  const evaluationContext = buildEvaluationContext({
1571
1663
  projection,
1572
1664
  evaluatedAt,
1573
- resourceIds: request.resourceIds
1665
+ resourceIds: request.resourceIds,
1666
+ allowExcludedScheduleDateOverride: runtimeOptions.allowExcludedScheduleDateOverride
1574
1667
  });
1575
1668
  if (request.target === 'time') {
1576
1669
  const candidates = dedupeProductRanges(request.candidates);
@@ -1644,7 +1737,8 @@ function queryAvailabilityProjection(projection, request) {
1644
1737
  resourceIds: request.resourceIds,
1645
1738
  partySize: normalizedPartySize
1646
1739
  }, {
1647
- evaluatedAt
1740
+ evaluatedAt,
1741
+ allowExcludedScheduleDateOverride: runtimeOptions.allowExcludedScheduleDateOverride
1648
1742
  });
1649
1743
  const candidateGroupMap = new Map(candidateGroups.map(group => [String(group.productId), group]));
1650
1744
  if (request.target === 'date') {
@@ -1794,7 +1888,7 @@ function summarizeProductStatus(statuses) {
1794
1888
  if (statuses.every(status => status === 'past')) return 'past';
1795
1889
  return 'unavailable';
1796
1890
  }
1797
- function validateAvailabilitySelection(projection, params) {
1891
+ function validateAvailabilitySelection(projection, params, runtimeOptions = {}) {
1798
1892
  const normalizedPartySize = normalizePartySize(params.partySize);
1799
1893
  const normalizedBookingCount = normalizeBookingCount(params.bookingCount);
1800
1894
  assertRangesWithinProjection(projection, [params.candidate]);
@@ -1815,14 +1909,15 @@ function validateAvailabilitySelection(projection, params) {
1815
1909
  evaluationContext: buildEvaluationContext({
1816
1910
  projection,
1817
1911
  evaluatedAt,
1818
- requirementSelections: params.requirementSelections
1912
+ requirementSelections: params.requirementSelections,
1913
+ allowExcludedScheduleDateOverride: runtimeOptions.allowExcludedScheduleDateOverride
1819
1914
  })
1820
1915
  });
1821
1916
  const conflicts = [];
1822
1917
  if (evaluated.gate) {
1823
1918
  conflicts.push({
1824
1919
  code: evaluated.gate.conflictCode,
1825
- message: evaluated.gate.status === 'past' ? '候选时间已过期' : '候选时间不满足预订时间门禁',
1920
+ message: evaluated.gate.conflictCode === 'past' ? '候选时间已过期' : evaluated.gate.conflictCode === 'schedule_excluded_date' ? '候选日期已被日程排除' : '候选时间不满足预订时间门禁',
1826
1921
  reasonCodes: evaluated.gate.reasonCodes,
1827
1922
  productId: params.candidate.productId,
1828
1923
  candidateKey: params.candidate.key