@pisell/pisellos 2.2.298 → 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.
- package/dist/modules/ResourcePlanner/planner.d.ts +2 -2
- package/dist/modules/ResourcePlanner/planner.js +160 -44
- package/dist/modules/ResourcePlanner/types.d.ts +12 -0
- package/dist/solution/BookingByStep/index.d.ts +1 -1
- package/dist/solution/UnifiedBookingSales/index.js +88 -19
- package/lib/modules/ResourcePlanner/planner.d.ts +2 -2
- package/lib/modules/ResourcePlanner/planner.js +129 -30
- package/lib/modules/ResourcePlanner/types.d.ts +12 -0
- package/lib/solution/BookingByStep/index.d.ts +1 -1
- package/lib/solution/UnifiedBookingSales/index.js +80 -20
- package/package.json +1 -1
|
@@ -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
|
-
|
|
677
|
-
|
|
678
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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$
|
|
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$
|
|
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,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
|
|
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
|
|
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,8 @@ 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);
|
|
1030
|
+
const operatingEndAt = dateRange.startDate === dateRange.endDate && operatingRanges.length > 0 ? formatDateTime(Math.max(...operatingRanges.map(range => range.end)), projection.meta.timezone, fixedOffsetMinutes) : undefined;
|
|
953
1031
|
const resourceRanges = requiredGroups.length ? requiredGroups.map(group => buildGroupWorkingRanges({
|
|
954
1032
|
projection,
|
|
955
1033
|
product,
|
|
@@ -957,7 +1035,8 @@ function getProductTimeRanges(projection, request = {}, runtimeOptions = {}) {
|
|
|
957
1035
|
dateRange,
|
|
958
1036
|
resourceIds: request.resourceIds,
|
|
959
1037
|
partySize: normalizedPartySize,
|
|
960
|
-
resourceWindowIndex
|
|
1038
|
+
resourceWindowIndex,
|
|
1039
|
+
includeExcludedWindows: allowExcludedScheduleDateOverride
|
|
961
1040
|
})).reduce((current, ranges) => current === null ? ranges : intersectRangeLists(current, ranges), null) || [] : null;
|
|
962
1041
|
const baseRanges = resourceRanges === null ? operatingRanges : intersectRangeLists(resourceRanges, operatingRanges);
|
|
963
1042
|
const durationMinutes = resolveRequestedDurationMinutes(product, request);
|
|
@@ -1016,6 +1095,9 @@ function getProductTimeRanges(projection, request = {}, runtimeOptions = {}) {
|
|
|
1016
1095
|
title: product.title,
|
|
1017
1096
|
kind: product.kind,
|
|
1018
1097
|
slotStepMinutes,
|
|
1098
|
+
...(operatingEndAt ? {
|
|
1099
|
+
operatingEndAt
|
|
1100
|
+
} : {}),
|
|
1019
1101
|
ranges: dedupeProductRanges(ranges)
|
|
1020
1102
|
};
|
|
1021
1103
|
});
|
|
@@ -1173,7 +1255,8 @@ function buildEvaluationContext(params) {
|
|
|
1173
1255
|
evaluatedAt,
|
|
1174
1256
|
evaluatedAtMs: evaluatedAt,
|
|
1175
1257
|
resourceIdSet: params.resourceIds?.length ? new Set(params.resourceIds.map(String)) : null,
|
|
1176
|
-
selectionsByGroupKey
|
|
1258
|
+
selectionsByGroupKey,
|
|
1259
|
+
allowExcludedScheduleDateOverride: params.allowExcludedScheduleDateOverride === true
|
|
1177
1260
|
};
|
|
1178
1261
|
}
|
|
1179
1262
|
function summarizeStatuses(statuses) {
|
|
@@ -1323,7 +1406,8 @@ function evaluateCandidateSummary(projection, candidate, request) {
|
|
|
1323
1406
|
const evaluationContext = request.evaluationContext || buildEvaluationContext({
|
|
1324
1407
|
projection,
|
|
1325
1408
|
evaluatedAt: request.evaluatedAt,
|
|
1326
|
-
resourceIds: request.resourceIds
|
|
1409
|
+
resourceIds: request.resourceIds,
|
|
1410
|
+
allowExcludedScheduleDateOverride: request.allowExcludedScheduleDateOverride
|
|
1327
1411
|
});
|
|
1328
1412
|
const requestedPartySize = normalizePartySize(request.partySize);
|
|
1329
1413
|
const requiredGroups = (product.requirementGroups || []).filter(group => group.required !== false).map(requirementGroup => evaluateRequirementGroupSummary({
|
|
@@ -1337,7 +1421,9 @@ function evaluateCandidateSummary(projection, candidate, request) {
|
|
|
1337
1421
|
groupResourceIndex: request.groupResourceIndex
|
|
1338
1422
|
}));
|
|
1339
1423
|
const gate = evaluateCandidateTimeGate({
|
|
1424
|
+
projection,
|
|
1340
1425
|
product,
|
|
1426
|
+
candidate,
|
|
1341
1427
|
candidateRange,
|
|
1342
1428
|
evaluationContext,
|
|
1343
1429
|
timezoneName: projection.meta.timezone
|
|
@@ -1379,7 +1465,8 @@ function evaluateCandidate(projection, candidate, request) {
|
|
|
1379
1465
|
projection,
|
|
1380
1466
|
evaluatedAt: request.evaluatedAt,
|
|
1381
1467
|
resourceIds: request.resourceIds,
|
|
1382
|
-
requirementSelections: request.requirementSelections
|
|
1468
|
+
requirementSelections: request.requirementSelections,
|
|
1469
|
+
allowExcludedScheduleDateOverride: request.allowExcludedScheduleDateOverride
|
|
1383
1470
|
});
|
|
1384
1471
|
const requestedPartySize = normalizePartySize(request.partySize);
|
|
1385
1472
|
const partySize = requestedPartySize ?? 1;
|
|
@@ -1392,6 +1479,12 @@ function evaluateCandidate(projection, candidate, request) {
|
|
|
1392
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)));
|
|
1393
1480
|
const options = matchedResources.map(resource => {
|
|
1394
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
|
+
});
|
|
1395
1488
|
if (!matchedCell) {
|
|
1396
1489
|
const isPast = candidateRange.endMs <= evaluationContext.evaluatedAtMs;
|
|
1397
1490
|
return {
|
|
@@ -1402,7 +1495,7 @@ function evaluateCandidate(projection, candidate, request) {
|
|
|
1402
1495
|
resourceType: resource.resourceType,
|
|
1403
1496
|
status: isPast ? 'past' : 'unavailable',
|
|
1404
1497
|
maxPartySize: 0,
|
|
1405
|
-
reasonCodes: isPast ? ['past'] : ['outside_resource_window']
|
|
1498
|
+
reasonCodes: isPast ? ['past'] : [...(isExcludedScheduleDate ? ['schedule_excluded_date'] : []), 'outside_resource_window']
|
|
1406
1499
|
};
|
|
1407
1500
|
}
|
|
1408
1501
|
const remainingCapacity = matchedCell.remainingCapacity;
|
|
@@ -1410,6 +1503,7 @@ function evaluateCandidate(projection, candidate, request) {
|
|
|
1410
1503
|
const isPast = candidateRange.endMs <= evaluationContext.evaluatedAtMs;
|
|
1411
1504
|
const maxPartySize = isPast ? 0 : Math.max(0, remainingCapacity || 0);
|
|
1412
1505
|
const requiredCapacity = resolveRequiredCapacity(requirementGroup, product, requestedPartySize);
|
|
1506
|
+
const reasonCodes = Array.from(new Set([...matchedCell.reasonCodes, ...(isExcludedScheduleDate ? ['schedule_excluded_date'] : [])]));
|
|
1413
1507
|
const status = (() => {
|
|
1414
1508
|
if (isPast) return 'past';
|
|
1415
1509
|
if (matchedCell.status === 'unavailable') return 'unavailable';
|
|
@@ -1427,7 +1521,7 @@ function evaluateCandidate(projection, candidate, request) {
|
|
|
1427
1521
|
remainingCapacity,
|
|
1428
1522
|
maxCapacity,
|
|
1429
1523
|
maxPartySize,
|
|
1430
|
-
reasonCodes: isPast ? Array.from(new Set([...
|
|
1524
|
+
reasonCodes: isPast ? Array.from(new Set([...reasonCodes, 'past'])) : reasonCodes,
|
|
1431
1525
|
matchedCell
|
|
1432
1526
|
};
|
|
1433
1527
|
});
|
|
@@ -1467,7 +1561,9 @@ function evaluateCandidate(projection, candidate, request) {
|
|
|
1467
1561
|
const requiredGroups = requirementGroups.filter(group => group.required);
|
|
1468
1562
|
const requiredStatuses = requiredGroups.map(group => group.status);
|
|
1469
1563
|
const gate = evaluateCandidateTimeGate({
|
|
1564
|
+
projection,
|
|
1470
1565
|
product,
|
|
1566
|
+
candidate,
|
|
1471
1567
|
candidateRange,
|
|
1472
1568
|
evaluationContext,
|
|
1473
1569
|
timezoneName: projection.meta.timezone
|
|
@@ -1543,7 +1639,7 @@ function assertRangesWithinProjection(projection, ranges) {
|
|
|
1543
1639
|
}
|
|
1544
1640
|
});
|
|
1545
1641
|
}
|
|
1546
|
-
function queryAvailabilityProjection(projection, request) {
|
|
1642
|
+
function queryAvailabilityProjection(projection, request, runtimeOptions = {}) {
|
|
1547
1643
|
const normalizedPartySize = normalizePartySize(request.partySize);
|
|
1548
1644
|
const evaluatedAt = formatDateTime((0, _localClock.localDateToScalar)(new Date()), projection.meta.timezone, (0, _localClock.extractFixedOffsetMinutes)(projection.meta.now) ?? (0, _localClock.captureRuntimeOffsetMinutes)());
|
|
1549
1645
|
if (request.target === 'time') {
|
|
@@ -1566,7 +1662,8 @@ function queryAvailabilityProjection(projection, request) {
|
|
|
1566
1662
|
const evaluationContext = buildEvaluationContext({
|
|
1567
1663
|
projection,
|
|
1568
1664
|
evaluatedAt,
|
|
1569
|
-
resourceIds: request.resourceIds
|
|
1665
|
+
resourceIds: request.resourceIds,
|
|
1666
|
+
allowExcludedScheduleDateOverride: runtimeOptions.allowExcludedScheduleDateOverride
|
|
1570
1667
|
});
|
|
1571
1668
|
if (request.target === 'time') {
|
|
1572
1669
|
const candidates = dedupeProductRanges(request.candidates);
|
|
@@ -1640,7 +1737,8 @@ function queryAvailabilityProjection(projection, request) {
|
|
|
1640
1737
|
resourceIds: request.resourceIds,
|
|
1641
1738
|
partySize: normalizedPartySize
|
|
1642
1739
|
}, {
|
|
1643
|
-
evaluatedAt
|
|
1740
|
+
evaluatedAt,
|
|
1741
|
+
allowExcludedScheduleDateOverride: runtimeOptions.allowExcludedScheduleDateOverride
|
|
1644
1742
|
});
|
|
1645
1743
|
const candidateGroupMap = new Map(candidateGroups.map(group => [String(group.productId), group]));
|
|
1646
1744
|
if (request.target === 'date') {
|
|
@@ -1790,7 +1888,7 @@ function summarizeProductStatus(statuses) {
|
|
|
1790
1888
|
if (statuses.every(status => status === 'past')) return 'past';
|
|
1791
1889
|
return 'unavailable';
|
|
1792
1890
|
}
|
|
1793
|
-
function validateAvailabilitySelection(projection, params) {
|
|
1891
|
+
function validateAvailabilitySelection(projection, params, runtimeOptions = {}) {
|
|
1794
1892
|
const normalizedPartySize = normalizePartySize(params.partySize);
|
|
1795
1893
|
const normalizedBookingCount = normalizeBookingCount(params.bookingCount);
|
|
1796
1894
|
assertRangesWithinProjection(projection, [params.candidate]);
|
|
@@ -1811,14 +1909,15 @@ function validateAvailabilitySelection(projection, params) {
|
|
|
1811
1909
|
evaluationContext: buildEvaluationContext({
|
|
1812
1910
|
projection,
|
|
1813
1911
|
evaluatedAt,
|
|
1814
|
-
requirementSelections: params.requirementSelections
|
|
1912
|
+
requirementSelections: params.requirementSelections,
|
|
1913
|
+
allowExcludedScheduleDateOverride: runtimeOptions.allowExcludedScheduleDateOverride
|
|
1815
1914
|
})
|
|
1816
1915
|
});
|
|
1817
1916
|
const conflicts = [];
|
|
1818
1917
|
if (evaluated.gate) {
|
|
1819
1918
|
conflicts.push({
|
|
1820
1919
|
code: evaluated.gate.conflictCode,
|
|
1821
|
-
message: evaluated.gate.
|
|
1920
|
+
message: evaluated.gate.conflictCode === 'past' ? '候选时间已过期' : evaluated.gate.conflictCode === 'schedule_excluded_date' ? '候选日期已被日程排除' : '候选时间不满足预订时间门禁',
|
|
1822
1921
|
reasonCodes: evaluated.gate.reasonCodes,
|
|
1823
1922
|
productId: params.candidate.productId,
|
|
1824
1923
|
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 {
|
|
@@ -213,6 +218,8 @@ export interface ProductTimeRangeGroup {
|
|
|
213
218
|
title?: string;
|
|
214
219
|
kind: AvailabilityProductKind;
|
|
215
220
|
slotStepMinutes?: number;
|
|
221
|
+
/** 单日查询对应营业窗口的最终结束时刻,不受资源排班是否为空影响。 */
|
|
222
|
+
operatingEndAt?: string;
|
|
216
223
|
ranges: ProductTimeRange[];
|
|
217
224
|
}
|
|
218
225
|
export interface AvailabilityAssignment extends AvailabilityAbsoluteRange {
|
|
@@ -250,6 +257,11 @@ export interface GetProductTimeRangesRuntimeOptions {
|
|
|
250
257
|
* disabled.
|
|
251
258
|
*/
|
|
252
259
|
allowSessionCustomRanges?: boolean;
|
|
260
|
+
/**
|
|
261
|
+
* Internal POS capability: materialize candidates whose schedule date was
|
|
262
|
+
* explicitly excluded, while availability evaluation keeps them unavailable.
|
|
263
|
+
*/
|
|
264
|
+
allowExcludedScheduleDateOverride?: boolean;
|
|
253
265
|
}
|
|
254
266
|
export interface QueryAvailabilityBaseRequest {
|
|
255
267
|
productIds?: AvailabilityId[];
|