@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.
- package/dist/modules/ResourcePlanner/planner.d.ts +2 -2
- package/dist/modules/ResourcePlanner/planner.js +151 -41
- package/dist/modules/ResourcePlanner/types.d.ts +10 -0
- package/dist/solution/UnifiedBookingSales/index.js +88 -19
- package/lib/model/strategy/adapter/promotion/index.js +46 -1
- package/lib/modules/ResourcePlanner/planner.d.ts +2 -2
- package/lib/modules/ResourcePlanner/planner.js +125 -30
- package/lib/modules/ResourcePlanner/types.d.ts +10 -0
- 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 +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
|
|
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,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([...
|
|
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.
|
|
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
|