@pisell/pisellos 0.10.15 → 0.10.17
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/core/index.js +6 -9
- package/dist/model/strategy/adapter/promotion/index.js +0 -9
- package/dist/model/strategy/adapter/walletPass/example.js +3 -26
- package/dist/model/strategy/strategy-example.js +5 -19
- package/dist/modules/Cart/index.js +0 -3
- package/dist/modules/Cart/utils/cartProduct.js +0 -1
- package/dist/modules/Date/index.js +1 -5
- package/dist/modules/Discount/index.js +2 -4
- package/dist/modules/OpenData/index.d.ts +5 -2
- package/dist/modules/OpenData/index.js +27 -14
- package/dist/modules/OpenData/types.d.ts +42 -0
- package/dist/modules/OpenData/types.js +7 -1
- package/dist/modules/OpenData/utils.d.ts +15 -1
- package/dist/modules/OpenData/utils.js +85 -0
- package/dist/modules/Order/index.d.ts +4 -2
- package/dist/modules/Order/index.js +131 -123
- package/dist/modules/Order/types.d.ts +8 -1
- package/dist/modules/Order/types.js +1 -0
- package/dist/modules/Order/utils.js +7 -1
- package/dist/modules/Payment/index.js +97 -162
- package/dist/modules/Product/types.d.ts +2 -0
- package/dist/modules/ProductList/index.js +96 -74
- package/dist/modules/Rules/index.js +12 -12
- package/dist/modules/SalesSummary/index.js +6 -4
- package/dist/modules/Schedule/index.js +0 -1
- package/dist/modules/Schedule/utils.js +0 -1
- package/dist/modules/Step/index.js +0 -1
- package/dist/plugins/request.js +1 -4
- package/dist/plugins/window.js +2 -6
- package/dist/server/index.d.ts +11 -0
- package/dist/server/index.js +977 -641
- package/dist/server/modules/menu/index.js +32 -41
- package/dist/server/modules/order/index.d.ts +8 -0
- package/dist/server/modules/order/index.js +253 -219
- package/dist/server/modules/products/index.js +24 -42
- package/dist/server/modules/quotation/index.js +29 -38
- package/dist/server/modules/resource/index.js +3 -4
- package/dist/server/modules/schedule/index.js +27 -35
- package/dist/server/utils/product.js +0 -1
- package/dist/server/utils/small-ticket.d.ts +9 -1
- package/dist/server/utils/small-ticket.js +117 -43
- package/dist/solution/BaseSales/index.d.ts +7 -0
- package/dist/solution/BaseSales/index.js +350 -215
- package/dist/solution/BaseSales/types.d.ts +2 -0
- package/dist/solution/BaseSales/utils/cartPromotion.d.ts +5 -0
- package/dist/solution/BaseSales/utils/cartPromotion.js +15 -6
- package/dist/solution/BaseSales/utils/transformBaseProductToOrderProduct.d.ts +3 -0
- package/dist/solution/BaseSales/utils/transformBaseProductToOrderProduct.js +7 -1
- package/dist/solution/BaseSales/utils.js +6 -3
- package/dist/solution/BookingByStep/index.d.ts +2 -2
- package/dist/solution/BookingByStep/index.js +7 -59
- package/dist/solution/BookingByStep/utils/capacity.js +1 -11
- package/dist/solution/BookingByStep/utils/resources.js +1 -3
- package/dist/solution/BookingByStep/utils/timeslots.js +2 -43
- package/dist/solution/BookingTicket/index.d.ts +14 -19
- package/dist/solution/BookingTicket/index.js +553 -433
- package/dist/solution/BookingTicket/utils/addProductDecision.js +3 -1
- package/dist/solution/BookingTicket/utils/scan/cloudSearch.js +6 -7
- package/dist/solution/BookingTicket/utils/scan/handleScan.js +25 -31
- package/dist/solution/BookingTicket/utils/scan/index.js +6 -10
- package/dist/solution/BuyTickets/index.js +9 -12
- package/dist/solution/Checkout/index.js +177 -252
- package/dist/solution/Checkout/utils/index.js +4 -16
- package/dist/solution/RegisterAndLogin/index.js +30 -76
- package/dist/solution/Sales/index.d.ts +20 -4
- package/dist/solution/Sales/index.js +348 -75
- package/dist/solution/Sales/types.d.ts +10 -0
- package/dist/solution/ScanOrder/index.js +50 -60
- package/dist/solution/ScanOrder/types.d.ts +2 -0
- package/dist/solution/ScanOrder/utils.js +6 -3
- package/dist/solution/ShopDiscount/index.js +2 -7
- package/dist/solution/UnifiedBookingSales/index.d.ts +5 -4
- package/dist/solution/UnifiedBookingSales/index.js +495 -422
- package/dist/solution/UnifiedBookingSales/types.d.ts +7 -1
- package/dist/solution/VenueBooking/index.js +45 -55
- package/dist/utils/task.js +15 -18
- package/dist/utils/watch.js +0 -1
- package/lib/apis/picoding.js +2 -0
- package/lib/core/index.js +131 -131
- package/lib/effects/index.js +46 -57
- package/lib/index.js +82 -49
- package/lib/model/index.js +14 -21
- package/lib/model/strategy/adapter/dataVariant/adapter.js +38 -50
- package/lib/model/strategy/adapter/dataVariant/evaluator.js +209 -315
- package/lib/model/strategy/adapter/dataVariant/examples.js +229 -237
- package/lib/model/strategy/adapter/dataVariant/index.js +39 -34
- package/lib/model/strategy/adapter/dataVariant/type.js +57 -28
- package/lib/model/strategy/adapter/index.js +100 -64
- package/lib/model/strategy/adapter/itemRule/adapter.js +156 -144
- package/lib/model/strategy/adapter/itemRule/evaluator.js +36 -48
- package/lib/model/strategy/adapter/itemRule/examples.js +295 -225
- package/lib/model/strategy/adapter/itemRule/index.js +83 -57
- package/lib/model/strategy/adapter/itemRule/type.js +97 -36
- package/lib/model/strategy/adapter/promotion/adapter.js +91 -69
- package/lib/model/strategy/adapter/promotion/evaluator.js +444 -310
- package/lib/model/strategy/adapter/promotion/examples.js +222 -234
- package/lib/model/strategy/adapter/promotion/index.js +1 -51
- package/lib/model/strategy/adapter/promotion/type.js +243 -36
- package/lib/model/strategy/adapter/type.js +4 -16
- package/lib/model/strategy/adapter/walletPass/evaluator.js +180 -150
- package/lib/model/strategy/adapter/walletPass/example.js +217 -190
- package/lib/model/strategy/adapter/walletPass/index.js +75 -51
- package/lib/model/strategy/adapter/walletPass/locales.js +36 -58
- package/lib/model/strategy/adapter/walletPass/type.js +4 -16
- package/lib/model/strategy/adapter/walletPass/utils.js +588 -378
- package/lib/model/strategy/engine.js +270 -168
- package/lib/model/strategy/index.js +49 -34
- package/lib/model/strategy/strategy-example.js +243 -239
- package/lib/model/strategy/type.js +100 -40
- package/lib/modules/Account/index.js +39 -53
- package/lib/modules/Account/types.js +20 -33
- package/lib/modules/AccountList/index.js +116 -154
- package/lib/modules/AccountList/types.js +30 -31
- package/lib/modules/AccountList/utils.js +18 -30
- package/lib/modules/BaseModule.js +23 -42
- package/lib/modules/BookingContext/index.js +158 -136
- package/lib/modules/BookingContext/types.js +46 -32
- package/lib/modules/BookingContext/utils/buildCacheItemFromOrderLine.js +138 -146
- package/lib/modules/BookingContext/utils/buildNormalProductCacheItemFromOrderLine.js +78 -83
- package/lib/modules/BookingContext/utils/cacheItemToBookingInput.js +170 -150
- package/lib/modules/BookingContext/utils/flexible.js +38 -55
- package/lib/modules/BookingContext/utils/formatResourceList.js +19 -34
- package/lib/modules/BookingContext/utils/index.js +124 -41
- package/lib/modules/BookingContext/utils/noResource.js +58 -70
- package/lib/modules/BookingContext/utils/orderLineDisplay.js +14 -41
- package/lib/modules/BookingContext/utils/productExtend.js +202 -155
- package/lib/modules/BookingContext/utils/resourceErrors.js +53 -53
- package/lib/modules/BookingContext/utils/resourceSelection.js +19 -41
- package/lib/modules/BookingContext/utils/resources.js +209 -167
- package/lib/modules/BookingContext/utils/serviceTimes.js +115 -116
- package/lib/modules/BookingContext/utils/timeSlices.js +76 -86
- package/lib/modules/Cart/index.js +170 -124
- package/lib/modules/Cart/types.js +46 -51
- package/lib/modules/Cart/utils/cartAccount.js +40 -39
- package/lib/modules/Cart/utils/cartDate.js +56 -61
- package/lib/modules/Cart/utils/cartDiscount.js +28 -33
- package/lib/modules/Cart/utils/cartNote.js +27 -32
- package/lib/modules/Cart/utils/cartProduct.js +321 -251
- package/lib/modules/Cart/utils/cartRelationForms.js +29 -35
- package/lib/modules/Cart/utils/cartResource.js +70 -78
- package/lib/modules/Cart/utils/changePrice.js +22 -50
- package/lib/modules/Cart/utils/index.js +106 -48
- package/lib/modules/Customer/constants.js +13 -34
- package/lib/modules/Customer/index.js +235 -172
- package/lib/modules/Customer/types.js +38 -35
- package/lib/modules/Date/index.js +116 -115
- package/lib/modules/Date/types.js +16 -28
- package/lib/modules/Date/utils.js +174 -123
- package/lib/modules/Discount/index.js +133 -143
- package/lib/modules/Discount/types.js +9 -31
- package/lib/modules/Guests/index.js +30 -56
- package/lib/modules/Guests/types.js +23 -33
- package/lib/modules/Holder/index.js +209 -189
- package/lib/modules/Holder/types.js +4 -16
- package/lib/modules/Holder/utils.js +20 -49
- package/lib/modules/OpenData/index.d.ts +5 -2
- package/lib/modules/OpenData/index.js +119 -78
- package/lib/modules/OpenData/types.d.ts +42 -0
- package/lib/modules/OpenData/types.js +12 -16
- package/lib/modules/OpenData/utils.d.ts +15 -1
- package/lib/modules/OpenData/utils.js +129 -78
- package/lib/modules/Order/index.d.ts +4 -2
- package/lib/modules/Order/index.js +1843 -2251
- package/lib/modules/Order/payment-utils.js +77 -120
- package/lib/modules/Order/types.d.ts +8 -1
- package/lib/modules/Order/types.js +88 -35
- package/lib/modules/Order/utils/bundleDiscountHydration.js +41 -74
- package/lib/modules/Order/utils/discountProductLineIdentity.js +142 -100
- package/lib/modules/Order/utils/manualProductDiscount.js +124 -123
- package/lib/modules/Order/utils/orderCollectionIdentity.js +184 -244
- package/lib/modules/Order/utils/virtualSettlement.js +120 -171
- package/lib/modules/Order/utils.js +834 -668
- package/lib/modules/Payment/cash.js +20 -33
- package/lib/modules/Payment/cashRecommendationAlgorithm.js +145 -76
- package/lib/modules/Payment/eftpos.js +16 -30
- package/lib/modules/Payment/index.js +532 -399
- package/lib/modules/Payment/mx51.js +1 -0
- package/lib/modules/Payment/types.js +230 -108
- package/lib/modules/Payment/utils.js +52 -51
- package/lib/modules/Payment/walletpass.js +485 -660
- package/lib/modules/Product/index.js +51 -46
- package/lib/modules/Product/types.d.ts +2 -0
- package/lib/modules/Product/types.js +4 -16
- package/lib/modules/Product/utils.js +32 -33
- package/lib/modules/ProductList/index.js +141 -161
- package/lib/modules/ProductList/types.js +9 -31
- package/lib/modules/Quotation/index.js +66 -100
- package/lib/modules/Quotation/types.js +4 -16
- package/lib/modules/Resource/index.js +27 -59
- package/lib/modules/Resource/types.js +22 -32
- package/lib/modules/Resource/utils.js +30 -38
- package/lib/modules/ResourcePlanner/index.js +25 -23
- package/lib/modules/ResourcePlanner/localClock.js +54 -119
- package/lib/modules/ResourcePlanner/planner.js +591 -772
- package/lib/modules/ResourcePlanner/types.js +10 -29
- package/lib/modules/Rules/index.js +1224 -963
- package/lib/modules/Rules/types.js +17 -40
- package/lib/modules/SalesSummary/index.js +80 -89
- package/lib/modules/SalesSummary/types.js +4 -16
- package/lib/modules/SalesSummary/utils.js +359 -442
- package/lib/modules/ScanOrderLogger/index.js +59 -79
- package/lib/modules/ScanOrderLogger/providers/feishu.js +54 -78
- package/lib/modules/ScanOrderLogger/providers/grafana.js +13 -36
- package/lib/modules/ScanOrderLogger/types.js +4 -16
- package/lib/modules/Schedule/getDateIsInSchedule.js +334 -163
- package/lib/modules/Schedule/index.js +100 -114
- package/lib/modules/Schedule/type.js +4 -16
- package/lib/modules/Schedule/types.js +4 -16
- package/lib/modules/Schedule/utils.js +433 -291
- package/lib/modules/Step/index.js +40 -52
- package/lib/modules/Step/tyeps.js +4 -16
- package/lib/modules/Summary/index.js +66 -71
- package/lib/modules/Summary/types.js +4 -16
- package/lib/modules/Summary/utils.js +773 -418
- package/lib/modules/SurchargeList/index.js +46 -60
- package/lib/modules/SurchargeList/types.js +4 -16
- package/lib/modules/index.js +245 -63
- package/lib/plugins/app.js +4 -16
- package/lib/plugins/index.js +36 -25
- package/lib/plugins/request.js +137 -126
- package/lib/plugins/shopStore.js +4 -16
- package/lib/plugins/user.js +4 -16
- package/lib/plugins/window.js +171 -179
- package/lib/server/index.d.ts +11 -0
- package/lib/server/index.js +3432 -2757
- package/lib/server/modules/floor-plan/index.js +102 -123
- package/lib/server/modules/floor-plan/types.js +15 -30
- package/lib/server/modules/index.js +106 -68
- package/lib/server/modules/menu/index.js +146 -122
- package/lib/server/modules/menu/types.js +18 -31
- package/lib/server/modules/order/index.d.ts +8 -0
- package/lib/server/modules/order/index.js +781 -956
- package/lib/server/modules/order/types.js +122 -33
- package/lib/server/modules/order/utils/filterBookings.js +219 -194
- package/lib/server/modules/order/utils/filterOrders.js +118 -92
- package/lib/server/modules/payment/index.js +59 -83
- package/lib/server/modules/payment/types.js +4 -16
- package/lib/server/modules/products/index.js +575 -459
- package/lib/server/modules/products/types.js +44 -34
- package/lib/server/modules/quotation/index.js +137 -172
- package/lib/server/modules/quotation/types.js +21 -31
- package/lib/server/modules/resource/index.js +213 -184
- package/lib/server/modules/resource/types.js +42 -33
- package/lib/server/modules/schedule/index.js +135 -123
- package/lib/server/modules/schedule/types.js +14 -21
- package/lib/server/modules/schedule/utils.js +334 -163
- package/lib/server/types.js +4 -16
- package/lib/server/utils/index.js +25 -23
- package/lib/server/utils/product.js +196 -139
- package/lib/server/utils/schedule.js +34 -41
- package/lib/server/utils/small-ticket.d.ts +9 -1
- package/lib/server/utils/small-ticket.js +565 -470
- package/lib/server/utils/time.js +40 -49
- package/lib/solution/BaseSales/index.d.ts +7 -0
- package/lib/solution/BaseSales/index.js +1385 -1503
- package/lib/solution/BaseSales/types.d.ts +2 -0
- package/lib/solution/BaseSales/types.js +42 -38
- package/lib/solution/BaseSales/utils/cartPromotion.d.ts +5 -0
- package/lib/solution/BaseSales/utils/cartPromotion.js +652 -498
- package/lib/solution/BaseSales/utils/parseSalesResponse.js +85 -105
- package/lib/solution/BaseSales/utils/quotationPrice.js +56 -114
- package/lib/solution/BaseSales/utils/transformBaseProductToOrderProduct.d.ts +3 -0
- package/lib/solution/BaseSales/utils/transformBaseProductToOrderProduct.js +138 -180
- package/lib/solution/BaseSales/utils.js +161 -143
- package/lib/solution/BookingByStep/index.d.ts +2 -2
- package/lib/solution/BookingByStep/index.js +1527 -1340
- package/lib/solution/BookingByStep/types.js +40 -99
- package/lib/solution/BookingByStep/utils/capacity.js +192 -160
- package/lib/solution/BookingByStep/utils/products.js +42 -52
- package/lib/solution/BookingByStep/utils/resources.js +527 -330
- package/lib/solution/BookingByStep/utils/stock.js +65 -44
- package/lib/solution/BookingByStep/utils/timeslots.js +129 -131
- package/lib/solution/BookingTicket/index.d.ts +14 -19
- package/lib/solution/BookingTicket/index.js +663 -526
- package/lib/solution/BookingTicket/types.js +99 -77
- package/lib/solution/BookingTicket/utils/addProductDecision.js +229 -190
- package/lib/solution/BookingTicket/utils/addTimeAvailability.js +58 -89
- package/lib/solution/BookingTicket/utils/bookingStatus.js +107 -78
- package/lib/solution/BookingTicket/utils/cartView.js +114 -99
- package/lib/solution/BookingTicket/utils/exampleData.js +185 -0
- package/lib/solution/BookingTicket/utils/orderCustomer.js +18 -41
- package/lib/solution/BookingTicket/utils/resolveBestAddTimePlan.js +219 -86
- package/lib/solution/BookingTicket/utils/scan/applyGlobalScan.js +168 -116
- package/lib/solution/BookingTicket/utils/scan/cloudSearch.js +79 -91
- package/lib/solution/BookingTicket/utils/scan/formatGlobalScan.js +54 -41
- package/lib/solution/BookingTicket/utils/scan/handleScan.js +84 -81
- package/lib/solution/BookingTicket/utils/scan/index.js +98 -104
- package/lib/solution/BookingTicket/utils/scan/scanCache.js +90 -43
- package/lib/solution/BookingTicket/utils/scan/scanProductValues.js +52 -64
- package/lib/solution/BookingTicket/utils/sessionCatalogStale.js +56 -58
- package/lib/solution/BookingTicket/utils/weighingProductSku.js +34 -48
- package/lib/solution/BuyTickets/index.js +67 -70
- package/lib/solution/BuyTickets/types.js +22 -38
- package/lib/solution/Checkout/index.js +1451 -1158
- package/lib/solution/Checkout/types.js +91 -61
- package/lib/solution/Checkout/utils/index.js +228 -156
- package/lib/solution/PlaceOrder/index.js +55 -0
- package/lib/solution/RegisterAndLogin/config.js +493 -460
- package/lib/solution/RegisterAndLogin/index.js +633 -630
- package/lib/solution/RegisterAndLogin/types.js +189 -76
- package/lib/solution/RegisterAndLogin/utils.js +183 -125
- package/lib/solution/Sales/index.d.ts +20 -4
- package/lib/solution/Sales/index.js +502 -330
- package/lib/solution/Sales/types.d.ts +10 -0
- package/lib/solution/Sales/types.js +27 -33
- package/lib/solution/ScanOrder/index.js +822 -852
- package/lib/solution/ScanOrder/types.d.ts +2 -0
- package/lib/solution/ScanOrder/types.js +33 -34
- package/lib/solution/ScanOrder/utils.js +412 -374
- package/lib/solution/ShopDiscount/index.js +226 -232
- package/lib/solution/ShopDiscount/types.js +15 -37
- package/lib/solution/ShopDiscount/utils.js +300 -172
- package/lib/solution/UnifiedBookingSales/index.d.ts +5 -4
- package/lib/solution/UnifiedBookingSales/index.js +1087 -1222
- package/lib/solution/UnifiedBookingSales/remoteOccupancyCache.js +99 -159
- package/lib/solution/UnifiedBookingSales/types.d.ts +7 -1
- package/lib/solution/UnifiedBookingSales/types.js +10 -32
- package/lib/solution/VenueBooking/index.js +811 -879
- package/lib/solution/VenueBooking/types.js +19 -39
- package/lib/solution/VenueBooking/utils/dateSummary.js +27 -56
- package/lib/solution/VenueBooking/utils/resource.js +31 -54
- package/lib/solution/VenueBooking/utils/slotMerge.js +94 -107
- package/lib/solution/VenueBooking/utils/timeSlot.js +132 -132
- package/lib/solution/VenueBooking/utils.js +130 -67
- package/lib/solution/index.js +124 -41
- package/lib/store/createStore.js +32 -29
- package/lib/types/index.js +4 -16
- package/lib/utils/payment-number.js +26 -46
- package/lib/utils/task.js +36 -36
- package/lib/utils/watch.js +81 -47
- package/package.json +2 -1
|
@@ -1,69 +1,48 @@
|
|
|
1
|
-
|
|
2
|
-
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
3
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
5
|
-
var __export = (target, all) => {
|
|
6
|
-
for (var name in all)
|
|
7
|
-
__defProp(target, name, { get: all[name], enumerable: true });
|
|
8
|
-
};
|
|
9
|
-
var __copyProps = (to, from, except, desc) => {
|
|
10
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
11
|
-
for (let key of __getOwnPropNames(from))
|
|
12
|
-
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
13
|
-
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
14
|
-
}
|
|
15
|
-
return to;
|
|
16
|
-
};
|
|
17
|
-
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
1
|
+
"use strict";
|
|
18
2
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
__export(planner_exports, {
|
|
22
|
-
createAvailabilityProjection: () => createAvailabilityProjection,
|
|
23
|
-
getProductTimeRanges: () => getProductTimeRanges,
|
|
24
|
-
queryAvailabilityProjection: () => queryAvailabilityProjection,
|
|
25
|
-
validateAvailabilitySelection: () => validateAvailabilitySelection
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
26
5
|
});
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
var
|
|
32
|
-
|
|
33
|
-
|
|
6
|
+
exports.createAvailabilityProjection = createAvailabilityProjection;
|
|
7
|
+
exports.getProductTimeRanges = getProductTimeRanges;
|
|
8
|
+
exports.queryAvailabilityProjection = queryAvailabilityProjection;
|
|
9
|
+
exports.validateAvailabilitySelection = validateAvailabilitySelection;
|
|
10
|
+
var _types = require("./types");
|
|
11
|
+
var _localClock = require("./localClock");
|
|
12
|
+
const DEFAULT_SLOT_STEP_MINUTES = 30;
|
|
13
|
+
const DEFAULT_BUSINESS_HOURS = {
|
|
14
|
+
startTime: '09:00',
|
|
15
|
+
endTime: '18:00'
|
|
34
16
|
};
|
|
35
17
|
function availabilityError(code, message, details) {
|
|
36
|
-
return new
|
|
18
|
+
return new _types.AvailabilityError(code, message, details);
|
|
37
19
|
}
|
|
38
20
|
function sameId(left, right) {
|
|
39
|
-
if (left ===
|
|
40
|
-
return false;
|
|
21
|
+
if (left === undefined || left === null || right === undefined || right === null) return false;
|
|
41
22
|
return String(left) === String(right);
|
|
42
23
|
}
|
|
43
24
|
function uniqById(values = []) {
|
|
44
|
-
const map =
|
|
45
|
-
values.forEach(
|
|
25
|
+
const map = new Map();
|
|
26
|
+
values.forEach(value => {
|
|
46
27
|
map.set(String(value), value);
|
|
47
28
|
});
|
|
48
29
|
return Array.from(map.values());
|
|
49
30
|
}
|
|
50
31
|
function normalizePartySize(partySize) {
|
|
51
|
-
if (partySize ===
|
|
52
|
-
return void 0;
|
|
32
|
+
if (partySize === undefined || partySize === null) return undefined;
|
|
53
33
|
const normalized = Number(partySize);
|
|
54
34
|
if (!Number.isInteger(normalized) || normalized <= 0) {
|
|
55
|
-
throw availabilityError(
|
|
35
|
+
throw availabilityError('INVALID_TARGET_FILTER', 'partySize 必须是正整数', {
|
|
56
36
|
partySize
|
|
57
37
|
});
|
|
58
38
|
}
|
|
59
39
|
return normalized;
|
|
60
40
|
}
|
|
61
41
|
function normalizeBookingCount(bookingCount) {
|
|
62
|
-
if (bookingCount ===
|
|
63
|
-
return 1;
|
|
42
|
+
if (bookingCount === undefined || bookingCount === null) return 1;
|
|
64
43
|
const normalized = Number(bookingCount);
|
|
65
44
|
if (!Number.isInteger(normalized) || normalized <= 0) {
|
|
66
|
-
throw availabilityError(
|
|
45
|
+
throw availabilityError('INVALID_TARGET_FILTER', 'bookingCount 必须是正整数', {
|
|
67
46
|
bookingCount
|
|
68
47
|
});
|
|
69
48
|
}
|
|
@@ -71,49 +50,51 @@ function normalizeBookingCount(bookingCount) {
|
|
|
71
50
|
}
|
|
72
51
|
function toDateTime(value, _timezoneName) {
|
|
73
52
|
try {
|
|
74
|
-
return (0,
|
|
53
|
+
return (0, _localClock.localDateTimeToScalar)((0, _localClock.parseLocalDateTime)(value));
|
|
75
54
|
} catch (_error) {
|
|
76
55
|
return Number.NaN;
|
|
77
56
|
}
|
|
78
57
|
}
|
|
79
58
|
function toDateStart(value, _timezoneName) {
|
|
80
59
|
try {
|
|
81
|
-
return (0,
|
|
60
|
+
return (0, _localClock.localDateToScalarStart)(value);
|
|
82
61
|
} catch (_error) {
|
|
83
62
|
return Number.NaN;
|
|
84
63
|
}
|
|
85
64
|
}
|
|
86
65
|
function toDateEnd(value, _timezoneName) {
|
|
87
66
|
const start = toDateStart(value);
|
|
88
|
-
return Number.isFinite(start) ? (0,
|
|
67
|
+
return Number.isFinite(start) ? (0, _localClock.addLocalCalendarDays)(start, 1) - 1 : Number.NaN;
|
|
89
68
|
}
|
|
90
69
|
function toDateTimeInDate(date, time, _timezoneName) {
|
|
91
70
|
return toDateTime(`${date}T${time.length === 5 ? `${time}:00` : time}`);
|
|
92
71
|
}
|
|
93
72
|
function addMinutesZoned(value, minutes, _timezoneName) {
|
|
94
|
-
return (0,
|
|
73
|
+
return (0, _localClock.addLocalMinutes)(value, minutes);
|
|
95
74
|
}
|
|
96
75
|
function ceilToTenMinutes(value, _timezoneName) {
|
|
97
|
-
return (0,
|
|
76
|
+
return (0, _localClock.ceilLocalScalarToTenMinutes)(value);
|
|
98
77
|
}
|
|
99
78
|
function nextDate(value, _timezoneName) {
|
|
100
|
-
return (0,
|
|
79
|
+
return (0, _localClock.nextDate)(value);
|
|
101
80
|
}
|
|
102
81
|
function formatDateTime(value, _timezoneName, fixedOffsetMinutes) {
|
|
103
|
-
return (0,
|
|
82
|
+
return (0, _localClock.formatLocalDateTime)(value, fixedOffsetMinutes ?? (0, _localClock.captureRuntimeOffsetMinutes)());
|
|
104
83
|
}
|
|
105
84
|
function formatDate(value, _timezoneName) {
|
|
106
|
-
return (0,
|
|
85
|
+
return (0, _localClock.formatLocalDate)(value);
|
|
107
86
|
}
|
|
108
87
|
function formatTime(value, _timezoneName) {
|
|
109
|
-
return (0,
|
|
88
|
+
return (0, _localClock.formatLocalTime)(value, false);
|
|
110
89
|
}
|
|
111
90
|
function normalizeAbsoluteRange(range, _timezoneName) {
|
|
112
91
|
const start = toDateTime(range.startAt);
|
|
113
92
|
const end = toDateTime(range.endAt);
|
|
114
|
-
if (!Number.isFinite(start) || !Number.isFinite(end) || end <= start)
|
|
115
|
-
|
|
116
|
-
|
|
93
|
+
if (!Number.isFinite(start) || !Number.isFinite(end) || end <= start) return null;
|
|
94
|
+
return {
|
|
95
|
+
start,
|
|
96
|
+
end
|
|
97
|
+
};
|
|
117
98
|
}
|
|
118
99
|
function getCandidateBookingRange(candidate) {
|
|
119
100
|
return {
|
|
@@ -128,13 +109,7 @@ function getCandidateRangeCacheKey(candidate) {
|
|
|
128
109
|
}
|
|
129
110
|
function getProductTimeRangeIdentity(range) {
|
|
130
111
|
const bookingRange = getCandidateBookingRange(range);
|
|
131
|
-
return [
|
|
132
|
-
String(range.productId),
|
|
133
|
-
range.startAt,
|
|
134
|
-
range.endAt,
|
|
135
|
-
bookingRange.startAt,
|
|
136
|
-
bookingRange.endAt
|
|
137
|
-
].join(":");
|
|
112
|
+
return [String(range.productId), range.startAt, range.endAt, bookingRange.startAt, bookingRange.endAt].join(':');
|
|
138
113
|
}
|
|
139
114
|
function toIndexedRange(range) {
|
|
140
115
|
return {
|
|
@@ -145,107 +120,97 @@ function toIndexedRange(range) {
|
|
|
145
120
|
}
|
|
146
121
|
function normalizeNonNegativeNumber(value) {
|
|
147
122
|
const normalized = Number(value);
|
|
148
|
-
if (!Number.isFinite(normalized) || normalized < 0)
|
|
149
|
-
return null;
|
|
123
|
+
if (!Number.isFinite(normalized) || normalized < 0) return null;
|
|
150
124
|
return normalized;
|
|
151
125
|
}
|
|
152
126
|
function resolveCutOffPolicyIssue(policy) {
|
|
153
|
-
if (policy.type ===
|
|
154
|
-
return policy.reason ||
|
|
127
|
+
if (policy.type === 'invalid') {
|
|
128
|
+
return policy.reason || 'invalid_cutoff_policy';
|
|
155
129
|
}
|
|
156
|
-
if (
|
|
157
|
-
return
|
|
130
|
+
if ('futureDay' in policy && policy.futureDay !== undefined && normalizeNonNegativeNumber(policy.futureDay) === null) {
|
|
131
|
+
return 'invalid_future_day';
|
|
158
132
|
}
|
|
159
|
-
if (policy.type ===
|
|
160
|
-
return
|
|
133
|
+
if (policy.type === 'ongoing' && policy.mode === 'after_start' && normalizeNonNegativeNumber(policy.graceMinutes) === null) {
|
|
134
|
+
return 'invalid_grace_minutes';
|
|
161
135
|
}
|
|
162
|
-
if (policy.type ===
|
|
163
|
-
if (normalizeNonNegativeNumber(policy.unit) === null)
|
|
164
|
-
|
|
165
|
-
if (!["days", "hours", "minutes"].includes(policy.unitType))
|
|
166
|
-
return "invalid_advance_unit_type";
|
|
136
|
+
if (policy.type === 'advance') {
|
|
137
|
+
if (normalizeNonNegativeNumber(policy.unit) === null) return 'invalid_advance_unit';
|
|
138
|
+
if (!['days', 'hours', 'minutes'].includes(policy.unitType)) return 'invalid_advance_unit_type';
|
|
167
139
|
}
|
|
168
140
|
return null;
|
|
169
141
|
}
|
|
170
142
|
function addLeadWindow(base, unit, unitType, _timezoneName) {
|
|
171
|
-
if (unitType ===
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
return (0, import_localClock.addLocalHours)(base, unit);
|
|
175
|
-
return (0, import_localClock.addLocalMinutes)(base, unit);
|
|
143
|
+
if (unitType === 'days') return (0, _localClock.addLocalCalendarDays)(base, unit);
|
|
144
|
+
if (unitType === 'hours') return (0, _localClock.addLocalHours)(base, unit);
|
|
145
|
+
return (0, _localClock.addLocalMinutes)(base, unit);
|
|
176
146
|
}
|
|
177
147
|
function evaluateCandidateTimeGate(params) {
|
|
178
|
-
const {
|
|
148
|
+
const {
|
|
149
|
+
product,
|
|
150
|
+
candidateRange,
|
|
151
|
+
evaluationContext,
|
|
152
|
+
timezoneName
|
|
153
|
+
} = params;
|
|
179
154
|
const now = evaluationContext.evaluatedAt;
|
|
180
155
|
if (candidateRange.endMs <= evaluationContext.evaluatedAtMs) {
|
|
181
156
|
return {
|
|
182
|
-
status:
|
|
183
|
-
reasonCodes: [
|
|
184
|
-
conflictCode:
|
|
157
|
+
status: 'past',
|
|
158
|
+
reasonCodes: ['past'],
|
|
159
|
+
conflictCode: 'past'
|
|
185
160
|
};
|
|
186
161
|
}
|
|
187
162
|
const policy = product.cutOffPolicy;
|
|
188
|
-
if (!policy)
|
|
189
|
-
return null;
|
|
163
|
+
if (!policy) return null;
|
|
190
164
|
if (resolveCutOffPolicyIssue(policy)) {
|
|
191
165
|
return {
|
|
192
|
-
status:
|
|
193
|
-
reasonCodes: [
|
|
194
|
-
conflictCode:
|
|
166
|
+
status: 'unavailable',
|
|
167
|
+
reasonCodes: ['booking_cutoff_invalid'],
|
|
168
|
+
conflictCode: 'booking_cutoff'
|
|
195
169
|
};
|
|
196
170
|
}
|
|
197
|
-
if (
|
|
171
|
+
if ('futureDay' in policy && policy.futureDay !== undefined) {
|
|
198
172
|
const futureDay = normalizeNonNegativeNumber(policy.futureDay);
|
|
199
|
-
const lastAllowedDateOrdinal = (0,
|
|
200
|
-
if ((0,
|
|
173
|
+
const lastAllowedDateOrdinal = (0, _localClock.dateOrdinalFromLocalDate)(formatDate(now)) + futureDay;
|
|
174
|
+
if ((0, _localClock.dateOrdinalFromLocalDate)(formatDate(candidateRange.start)) > lastAllowedDateOrdinal) {
|
|
201
175
|
return {
|
|
202
|
-
status:
|
|
203
|
-
reasonCodes: [
|
|
204
|
-
conflictCode:
|
|
176
|
+
status: 'unavailable',
|
|
177
|
+
reasonCodes: ['booking_cutoff_future_day'],
|
|
178
|
+
conflictCode: 'booking_cutoff'
|
|
205
179
|
};
|
|
206
180
|
}
|
|
207
181
|
}
|
|
208
|
-
if (policy.type ===
|
|
209
|
-
if (policy.mode ===
|
|
210
|
-
return null;
|
|
182
|
+
if (policy.type === 'ongoing') {
|
|
183
|
+
if (policy.mode === 'before_end') return null;
|
|
211
184
|
const graceMinutes = normalizeNonNegativeNumber(policy.graceMinutes);
|
|
212
185
|
const graceDeadline = addMinutesZoned(candidateRange.start, graceMinutes, timezoneName);
|
|
213
|
-
if (now < graceDeadline)
|
|
214
|
-
return null;
|
|
186
|
+
if (now < graceDeadline) return null;
|
|
215
187
|
return {
|
|
216
|
-
status:
|
|
217
|
-
reasonCodes: [
|
|
218
|
-
conflictCode:
|
|
188
|
+
status: 'unavailable',
|
|
189
|
+
reasonCodes: ['booking_cutoff_after_start'],
|
|
190
|
+
conflictCode: 'booking_cutoff'
|
|
219
191
|
};
|
|
220
192
|
}
|
|
221
|
-
if (policy.type ===
|
|
222
|
-
if (now < candidateRange.start)
|
|
223
|
-
return null;
|
|
193
|
+
if (policy.type === 'before_start') {
|
|
194
|
+
if (now < candidateRange.start) return null;
|
|
224
195
|
return {
|
|
225
|
-
status:
|
|
226
|
-
reasonCodes: [
|
|
227
|
-
conflictCode:
|
|
196
|
+
status: 'unavailable',
|
|
197
|
+
reasonCodes: ['booking_cutoff_before_start'],
|
|
198
|
+
conflictCode: 'booking_cutoff'
|
|
228
199
|
};
|
|
229
200
|
}
|
|
230
|
-
if (policy.type ===
|
|
231
|
-
const leadWindow = addLeadWindow(
|
|
232
|
-
|
|
233
|
-
normalizeNonNegativeNumber(policy.unit),
|
|
234
|
-
policy.unitType,
|
|
235
|
-
timezoneName
|
|
236
|
-
);
|
|
237
|
-
if (leadWindow <= candidateRange.start)
|
|
238
|
-
return null;
|
|
201
|
+
if (policy.type === 'advance') {
|
|
202
|
+
const leadWindow = addLeadWindow(now, normalizeNonNegativeNumber(policy.unit), policy.unitType, timezoneName);
|
|
203
|
+
if (leadWindow <= candidateRange.start) return null;
|
|
239
204
|
return {
|
|
240
|
-
status:
|
|
241
|
-
reasonCodes: [
|
|
242
|
-
conflictCode:
|
|
205
|
+
status: 'unavailable',
|
|
206
|
+
reasonCodes: ['booking_cutoff_advance'],
|
|
207
|
+
conflictCode: 'booking_cutoff'
|
|
243
208
|
};
|
|
244
209
|
}
|
|
245
210
|
return {
|
|
246
|
-
status:
|
|
247
|
-
reasonCodes: [
|
|
248
|
-
conflictCode:
|
|
211
|
+
status: 'unavailable',
|
|
212
|
+
reasonCodes: ['booking_cutoff_invalid'],
|
|
213
|
+
conflictCode: 'booking_cutoff'
|
|
249
214
|
};
|
|
250
215
|
}
|
|
251
216
|
function overlaps(left, right) {
|
|
@@ -257,9 +222,11 @@ function contains(outer, inner) {
|
|
|
257
222
|
function clipRange(base, clip) {
|
|
258
223
|
const start = Math.max(base.start, clip.start);
|
|
259
224
|
const end = Math.min(base.end, clip.end);
|
|
260
|
-
if (end <= start)
|
|
261
|
-
|
|
262
|
-
|
|
225
|
+
if (end <= start) return null;
|
|
226
|
+
return {
|
|
227
|
+
start,
|
|
228
|
+
end
|
|
229
|
+
};
|
|
263
230
|
}
|
|
264
231
|
function sortRanges(ranges) {
|
|
265
232
|
return [...ranges].sort((left, right) => left.start - right.start);
|
|
@@ -267,7 +234,7 @@ function sortRanges(ranges) {
|
|
|
267
234
|
function mergeRanges(ranges) {
|
|
268
235
|
const sorted = sortRanges(ranges);
|
|
269
236
|
const merged = [];
|
|
270
|
-
sorted.forEach(
|
|
237
|
+
sorted.forEach(range => {
|
|
271
238
|
const last = merged[merged.length - 1];
|
|
272
239
|
if (!last) {
|
|
273
240
|
merged.push(range);
|
|
@@ -283,11 +250,10 @@ function mergeRanges(ranges) {
|
|
|
283
250
|
}
|
|
284
251
|
function intersectRangeLists(left, right) {
|
|
285
252
|
const results = [];
|
|
286
|
-
left.forEach(
|
|
287
|
-
right.forEach(
|
|
253
|
+
left.forEach(leftRange => {
|
|
254
|
+
right.forEach(rightRange => {
|
|
288
255
|
const clipped = clipRange(leftRange, rightRange);
|
|
289
|
-
if (clipped)
|
|
290
|
-
results.push(clipped);
|
|
256
|
+
if (clipped) results.push(clipped);
|
|
291
257
|
});
|
|
292
258
|
});
|
|
293
259
|
return mergeRanges(results);
|
|
@@ -304,25 +270,28 @@ function enumerateDates(startDate, endDate) {
|
|
|
304
270
|
function normalizeCoverage(input) {
|
|
305
271
|
const startDate = input.coverage.startDate;
|
|
306
272
|
const endDate = input.coverage.endDate;
|
|
307
|
-
const isCanonicalDate =
|
|
273
|
+
const isCanonicalDate = value => {
|
|
308
274
|
try {
|
|
309
|
-
return (0,
|
|
275
|
+
return (0, _localClock.formatLocalDate)((0, _localClock.parseLocalDate)(value)) === value;
|
|
310
276
|
} catch (_error) {
|
|
311
277
|
return false;
|
|
312
278
|
}
|
|
313
279
|
};
|
|
314
280
|
if (!startDate || !endDate || !isCanonicalDate(startDate) || !isCanonicalDate(endDate)) {
|
|
315
|
-
throw availabilityError(
|
|
281
|
+
throw availabilityError('INVALID_DATE_RANGE', 'coverage.startDate / coverage.endDate 非法', {
|
|
316
282
|
coverage: input.coverage
|
|
317
283
|
});
|
|
318
284
|
}
|
|
319
285
|
if (endDate < startDate) {
|
|
320
|
-
throw availabilityError(
|
|
286
|
+
throw availabilityError('INVALID_DATE_RANGE', 'coverage.endDate 不能早于 startDate', {
|
|
321
287
|
coverage: input.coverage
|
|
322
288
|
});
|
|
323
289
|
}
|
|
324
290
|
return {
|
|
325
|
-
dateRange: {
|
|
291
|
+
dateRange: {
|
|
292
|
+
startDate,
|
|
293
|
+
endDate
|
|
294
|
+
},
|
|
326
295
|
absolute: {
|
|
327
296
|
start: toDateStart(startDate, input.timezone),
|
|
328
297
|
end: toDateEnd(nextDate(endDate, input.timezone), input.timezone)
|
|
@@ -330,17 +299,13 @@ function normalizeCoverage(input) {
|
|
|
330
299
|
};
|
|
331
300
|
}
|
|
332
301
|
function normalizeResourceWindow(resource, coverage, timezoneName) {
|
|
333
|
-
return mergeRanges(
|
|
334
|
-
(resource.windows || []).map((window) => normalizeAbsoluteRange(window, timezoneName)).filter((window) => Boolean(window)).map((window) => clipRange(window, coverage)).filter((window) => Boolean(window))
|
|
335
|
-
);
|
|
302
|
+
return mergeRanges((resource.windows || []).map(window => normalizeAbsoluteRange(window, timezoneName)).filter(window => Boolean(window)).map(window => clipRange(window, coverage)).filter(window => Boolean(window)));
|
|
336
303
|
}
|
|
337
304
|
function normalizeOccupancy(occupancy, coverage, timezoneName, fixedOffsetMinutes) {
|
|
338
305
|
const normalized = normalizeAbsoluteRange(occupancy, timezoneName);
|
|
339
|
-
if (!normalized)
|
|
340
|
-
return null;
|
|
306
|
+
if (!normalized) return null;
|
|
341
307
|
const clipped = clipRange(normalized, coverage);
|
|
342
|
-
if (!clipped)
|
|
343
|
-
return null;
|
|
308
|
+
if (!clipped) return null;
|
|
344
309
|
return {
|
|
345
310
|
...occupancy,
|
|
346
311
|
startAt: formatDateTime(clipped.start, timezoneName, fixedOffsetMinutes),
|
|
@@ -349,7 +314,7 @@ function normalizeOccupancy(occupancy, coverage, timezoneName, fixedOffsetMinute
|
|
|
349
314
|
};
|
|
350
315
|
}
|
|
351
316
|
function normalizeRequirementGroup(product, requirementGroup) {
|
|
352
|
-
const resourceType =
|
|
317
|
+
const resourceType = 'single';
|
|
353
318
|
const required = requirementGroup.required !== false;
|
|
354
319
|
const min = required ? 1 : 0;
|
|
355
320
|
return {
|
|
@@ -360,107 +325,97 @@ function normalizeRequirementGroup(product, requirementGroup) {
|
|
|
360
325
|
min,
|
|
361
326
|
max: 1,
|
|
362
327
|
resourceIds: uniqById(requirementGroup.resourceIds || []),
|
|
363
|
-
capacityRequired: Math.max(
|
|
364
|
-
1,
|
|
365
|
-
Number(requirementGroup.capacityRequired || product.capacityRequired || 1) || 1
|
|
366
|
-
)
|
|
328
|
+
capacityRequired: Math.max(1, Number(requirementGroup.capacityRequired || product.capacityRequired || 1) || 1)
|
|
367
329
|
};
|
|
368
330
|
}
|
|
369
331
|
function normalizeProducts(products) {
|
|
370
|
-
return products.map(
|
|
332
|
+
return products.map(product => ({
|
|
371
333
|
...product,
|
|
372
334
|
quantity: Math.max(1, Number(product.quantity || 1) || 1),
|
|
373
335
|
capacityRequired: Math.max(1, Number(product.capacityRequired || 1) || 1),
|
|
374
336
|
durationMinutes: Math.max(1, Number(product.durationMinutes || 30) || 30),
|
|
375
|
-
cutOffPolicy: product.cutOffPolicy ? {
|
|
376
|
-
|
|
377
|
-
|
|
337
|
+
cutOffPolicy: product.cutOffPolicy ? {
|
|
338
|
+
...product.cutOffPolicy
|
|
339
|
+
} : undefined,
|
|
340
|
+
requirementGroups: (product.requirementGroups || []).map(group => normalizeRequirementGroup(product, group)),
|
|
341
|
+
sessionRules: (product.sessionRules || []).map(rule => ({
|
|
378
342
|
...rule,
|
|
379
|
-
weekdays: rule.weekdays ? [...rule.weekdays] :
|
|
380
|
-
dates: rule.dates ? [...rule.dates] :
|
|
381
|
-
includeDates: rule.includeDates ? [...rule.includeDates] :
|
|
382
|
-
excludeDates: rule.excludeDates ? [...rule.excludeDates] :
|
|
383
|
-
scheduleRef: rule.scheduleRef ? {
|
|
343
|
+
weekdays: rule.weekdays ? [...rule.weekdays] : undefined,
|
|
344
|
+
dates: rule.dates ? [...rule.dates] : undefined,
|
|
345
|
+
includeDates: rule.includeDates ? [...rule.includeDates] : undefined,
|
|
346
|
+
excludeDates: rule.excludeDates ? [...rule.excludeDates] : undefined,
|
|
347
|
+
scheduleRef: rule.scheduleRef ? {
|
|
348
|
+
...rule.scheduleRef
|
|
349
|
+
} : undefined
|
|
384
350
|
})),
|
|
385
|
-
sessionRanges: (product.sessionRanges || []).map(
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
...
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
})
|
|
351
|
+
sessionRanges: (product.sessionRanges || []).map(range => ({
|
|
352
|
+
...range,
|
|
353
|
+
scheduleRefs: range.scheduleRefs?.map(scheduleRef => ({
|
|
354
|
+
...scheduleRef
|
|
355
|
+
}))
|
|
356
|
+
}))
|
|
392
357
|
}));
|
|
393
358
|
}
|
|
394
359
|
function normalizeResources(resources) {
|
|
395
|
-
return resources.map(
|
|
360
|
+
return resources.map(resource => ({
|
|
396
361
|
...resource,
|
|
397
|
-
resourceType:
|
|
362
|
+
resourceType: 'single',
|
|
398
363
|
capacity: Math.max(1, Number(resource.capacity || 1) || 1),
|
|
399
|
-
windows: (resource.windows || []).map(
|
|
364
|
+
windows: (resource.windows || []).map(window => ({
|
|
365
|
+
...window
|
|
366
|
+
}))
|
|
400
367
|
}));
|
|
401
368
|
}
|
|
402
369
|
function normalizeOperatingHours(params) {
|
|
403
|
-
|
|
404
|
-
const fallbackWindows = enumerateDates(params.dateRange.startDate, params.dateRange.endDate).map((date) => {
|
|
370
|
+
const fallbackWindows = enumerateDates(params.dateRange.startDate, params.dateRange.endDate).map(date => {
|
|
405
371
|
const start = toDateTimeInDate(date, params.businessHours.startTime, params.timezoneName);
|
|
406
372
|
let end = toDateTimeInDate(date, params.businessHours.endTime, params.timezoneName);
|
|
407
373
|
if (end <= start) {
|
|
408
374
|
end = toDateTimeInDate(nextDate(date), params.businessHours.endTime, params.timezoneName);
|
|
409
375
|
}
|
|
410
|
-
return {
|
|
376
|
+
return {
|
|
377
|
+
start,
|
|
378
|
+
end
|
|
379
|
+
};
|
|
411
380
|
});
|
|
412
|
-
const sourceWindows = params.input ? params.input.windows : fallbackWindows.map(
|
|
381
|
+
const sourceWindows = params.input ? params.input.windows : fallbackWindows.map(range => ({
|
|
413
382
|
startAt: formatDateTime(range.start, params.timezoneName, params.fixedOffsetMinutes),
|
|
414
383
|
endAt: formatDateTime(range.end, params.timezoneName, params.fixedOffsetMinutes)
|
|
415
384
|
}));
|
|
416
|
-
const windows = mergeRanges(
|
|
417
|
-
sourceWindows.map((window) => normalizeAbsoluteRange(window, params.timezoneName)).filter((window) => Boolean(window)).map((window) => clipRange(window, params.coverage)).filter((window) => Boolean(window))
|
|
418
|
-
).map((range) => ({
|
|
385
|
+
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 => ({
|
|
419
386
|
startAt: formatDateTime(range.start, params.timezoneName, params.fixedOffsetMinutes),
|
|
420
387
|
endAt: formatDateTime(range.end, params.timezoneName, params.fixedOffsetMinutes)
|
|
421
388
|
}));
|
|
422
389
|
return {
|
|
423
|
-
source:
|
|
424
|
-
scheduleIds: uniqById(
|
|
425
|
-
missingScheduleIds: uniqById(
|
|
390
|
+
source: params.input?.source || params.fallbackSource,
|
|
391
|
+
scheduleIds: uniqById(params.input?.scheduleIds || []),
|
|
392
|
+
missingScheduleIds: uniqById(params.input?.missingScheduleIds || []),
|
|
426
393
|
windows
|
|
427
394
|
};
|
|
428
395
|
}
|
|
429
396
|
function matchesRequirementGroup(requirementGroup, resource, product, partySize = 1) {
|
|
430
|
-
|
|
431
|
-
if (requirementGroup.formId !== void 0 && resource.formId !== void 0 && !sameId(requirementGroup.formId, resource.formId)) {
|
|
397
|
+
if (requirementGroup.formId !== undefined && resource.formId !== undefined && !sameId(requirementGroup.formId, resource.formId)) {
|
|
432
398
|
return false;
|
|
433
399
|
}
|
|
434
|
-
if (
|
|
400
|
+
if (requirementGroup.resourceIds?.length && !requirementGroup.resourceIds.some(resourceId => sameId(resourceId, resource.id))) {
|
|
435
401
|
return false;
|
|
436
402
|
}
|
|
437
403
|
return resource.capacity >= resolveRequiredCapacity(requirementGroup, product, partySize);
|
|
438
404
|
}
|
|
439
405
|
function resolveRequiredCapacity(requirementGroup, product, partySize) {
|
|
440
|
-
return Math.max(
|
|
441
|
-
1,
|
|
442
|
-
Number(
|
|
443
|
-
partySize ?? requirementGroup.capacityRequired ?? product.capacityRequired ?? 1
|
|
444
|
-
) || 1
|
|
445
|
-
);
|
|
406
|
+
return Math.max(1, Number(partySize ?? requirementGroup.capacityRequired ?? product.capacityRequired ?? 1) || 1);
|
|
446
407
|
}
|
|
447
408
|
function buildCellKey(params) {
|
|
448
|
-
return [
|
|
449
|
-
String(params.productId),
|
|
450
|
-
params.groupId,
|
|
451
|
-
String(params.resourceId),
|
|
452
|
-
params.start,
|
|
453
|
-
params.end
|
|
454
|
-
].join(":");
|
|
409
|
+
return [String(params.productId), params.groupId, String(params.resourceId), params.start, params.end].join(':');
|
|
455
410
|
}
|
|
456
411
|
function dedupeOccupancyRefKey(occupancy) {
|
|
457
|
-
if (occupancy.scheduleEventId !==
|
|
412
|
+
if (occupancy.scheduleEventId !== undefined && occupancy.scheduleEventId !== null) {
|
|
458
413
|
return `scheduleEvent:${String(occupancy.resourceId)}:${String(occupancy.scheduleEventId)}`;
|
|
459
414
|
}
|
|
460
|
-
if (occupancy.bookingUid !==
|
|
415
|
+
if (occupancy.bookingUid !== undefined && occupancy.bookingUid !== null) {
|
|
461
416
|
return `bookingUid:${String(occupancy.resourceId)}:${String(occupancy.bookingUid)}`;
|
|
462
417
|
}
|
|
463
|
-
if (occupancy.productUid !==
|
|
418
|
+
if (occupancy.productUid !== undefined && occupancy.productUid !== null) {
|
|
464
419
|
return `productUid:${String(occupancy.resourceId)}:${String(occupancy.productUid)}`;
|
|
465
420
|
}
|
|
466
421
|
return `range:${String(occupancy.resourceId)}:${occupancy.startAt}:${occupancy.endAt}:${String(occupancy.pax || 1)}`;
|
|
@@ -472,22 +427,27 @@ function buildCells(projection) {
|
|
|
472
427
|
};
|
|
473
428
|
const cells = [];
|
|
474
429
|
const timezoneName = projection.meta.timezone;
|
|
475
|
-
const fixedOffsetMinutes = (0,
|
|
430
|
+
const fixedOffsetMinutes = (0, _localClock.extractFixedOffsetMinutes)(projection.meta.now) ?? (0, _localClock.captureRuntimeOffsetMinutes)();
|
|
476
431
|
const projectionNow = toDateTime(projection.meta.now);
|
|
477
|
-
projection.products.forEach(
|
|
478
|
-
(product.requirementGroups || []).forEach(
|
|
479
|
-
projection.resources.filter(
|
|
432
|
+
projection.products.forEach(product => {
|
|
433
|
+
(product.requirementGroups || []).forEach(requirementGroup => {
|
|
434
|
+
projection.resources.filter(resource => matchesRequirementGroup(requirementGroup, resource, product)).forEach(resource => {
|
|
480
435
|
const windows = normalizeResourceWindow(resource, coverage, timezoneName);
|
|
481
|
-
const occupancies = projection.resourceOccupancies.filter(
|
|
436
|
+
const occupancies = projection.resourceOccupancies.filter(occupancy => sameId(occupancy.resourceId, resource.id)).map(occupancy => ({
|
|
482
437
|
occupancy,
|
|
483
438
|
range: normalizeAbsoluteRange(occupancy, timezoneName)
|
|
484
|
-
})).filter(
|
|
485
|
-
|
|
486
|
-
|
|
439
|
+
})).filter(item => Boolean(item.range)).map(item => ({
|
|
440
|
+
...item,
|
|
441
|
+
range: clipRange(item.range, coverage)
|
|
442
|
+
})).filter(item => Boolean(item.range));
|
|
443
|
+
const boundaries = new Set([coverage.start, coverage.end]);
|
|
444
|
+
windows.forEach(window => {
|
|
487
445
|
boundaries.add(window.start);
|
|
488
446
|
boundaries.add(window.end);
|
|
489
447
|
});
|
|
490
|
-
occupancies.forEach(({
|
|
448
|
+
occupancies.forEach(({
|
|
449
|
+
range
|
|
450
|
+
}) => {
|
|
491
451
|
boundaries.add(range.start);
|
|
492
452
|
boundaries.add(range.end);
|
|
493
453
|
});
|
|
@@ -495,39 +455,38 @@ function buildCells(projection) {
|
|
|
495
455
|
for (let index = 0; index < sortedBoundaries.length - 1; index += 1) {
|
|
496
456
|
const start = sortedBoundaries[index];
|
|
497
457
|
const end = sortedBoundaries[index + 1];
|
|
498
|
-
if (end <= start)
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
const
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
)
|
|
458
|
+
if (end <= start) continue;
|
|
459
|
+
const segment = {
|
|
460
|
+
start,
|
|
461
|
+
end
|
|
462
|
+
};
|
|
463
|
+
const isInsideWorkingWindow = windows.some(window => contains(window, segment));
|
|
464
|
+
const segmentOccupancies = isInsideWorkingWindow ? occupancies.filter(({
|
|
465
|
+
range
|
|
466
|
+
}) => overlaps(range, segment)).map(({
|
|
467
|
+
occupancy
|
|
468
|
+
}) => occupancy) : [];
|
|
469
|
+
const usedCapacity = segmentOccupancies.reduce((sum, occupancy) => sum + Math.max(1, Number(occupancy.pax || 1) || 1), 0);
|
|
507
470
|
const remainingCapacity = Math.max(0, resource.capacity - usedCapacity);
|
|
508
471
|
const requiredCapacity = resolveRequiredCapacity(requirementGroup, product);
|
|
509
|
-
const reasonCodes = Array.from(new Set(
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
return ["resource_occupied"];
|
|
517
|
-
})
|
|
518
|
-
));
|
|
519
|
-
let status = "available";
|
|
472
|
+
const reasonCodes = Array.from(new Set(segmentOccupancies.flatMap(occupancy => {
|
|
473
|
+
const source = occupancy.source || 'remote';
|
|
474
|
+
if (source === 'selection') return ['selection_occupied'];
|
|
475
|
+
if (source === 'cart') return ['cart_occupied'];
|
|
476
|
+
return ['resource_occupied'];
|
|
477
|
+
})));
|
|
478
|
+
let status = 'available';
|
|
520
479
|
if (end <= projectionNow) {
|
|
521
|
-
status =
|
|
522
|
-
reasonCodes.push(
|
|
480
|
+
status = 'past';
|
|
481
|
+
reasonCodes.push('past');
|
|
523
482
|
} else if (!isInsideWorkingWindow) {
|
|
524
|
-
status =
|
|
525
|
-
reasonCodes.push(
|
|
483
|
+
status = 'unavailable';
|
|
484
|
+
reasonCodes.push('outside_resource_window');
|
|
526
485
|
} else if (remainingCapacity < requiredCapacity) {
|
|
527
|
-
status =
|
|
528
|
-
reasonCodes.push(
|
|
486
|
+
status = 'full';
|
|
487
|
+
reasonCodes.push('capacity_full');
|
|
529
488
|
} else if (remainingCapacity < resource.capacity) {
|
|
530
|
-
status =
|
|
489
|
+
status = 'limited';
|
|
531
490
|
}
|
|
532
491
|
cells.push({
|
|
533
492
|
key: buildCellKey({
|
|
@@ -554,7 +513,7 @@ function buildCells(projection) {
|
|
|
554
513
|
remainingCapacity,
|
|
555
514
|
maxCapacity: resource.capacity,
|
|
556
515
|
reasonCodes: Array.from(new Set(reasonCodes)),
|
|
557
|
-
occupancyRefs: segmentOccupancies.map(
|
|
516
|
+
occupancyRefs: segmentOccupancies.map(occupancy => ({
|
|
558
517
|
id: occupancy.id,
|
|
559
518
|
resourceId: occupancy.resourceId,
|
|
560
519
|
source: occupancy.source,
|
|
@@ -572,18 +531,8 @@ function buildCells(projection) {
|
|
|
572
531
|
}
|
|
573
532
|
function compressCells(cells) {
|
|
574
533
|
const sorted = [...cells].sort((left, right) => {
|
|
575
|
-
const keyLeft = [
|
|
576
|
-
|
|
577
|
-
String(left.requirementGroupId),
|
|
578
|
-
String(left.resourceId),
|
|
579
|
-
left.startAt
|
|
580
|
-
].join(":");
|
|
581
|
-
const keyRight = [
|
|
582
|
-
String(right.productId),
|
|
583
|
-
String(right.requirementGroupId),
|
|
584
|
-
String(right.resourceId),
|
|
585
|
-
right.startAt
|
|
586
|
-
].join(":");
|
|
534
|
+
const keyLeft = [String(left.productId), String(left.requirementGroupId), String(left.resourceId), left.startAt].join(':');
|
|
535
|
+
const keyRight = [String(right.productId), String(right.requirementGroupId), String(right.resourceId), right.startAt].join(':');
|
|
587
536
|
return keyLeft.localeCompare(keyRight);
|
|
588
537
|
});
|
|
589
538
|
return sorted.reduce((result, cell) => {
|
|
@@ -593,7 +542,9 @@ function compressCells(cells) {
|
|
|
593
542
|
last.endTime = cell.endTime;
|
|
594
543
|
return result;
|
|
595
544
|
}
|
|
596
|
-
result.push({
|
|
545
|
+
result.push({
|
|
546
|
+
...cell
|
|
547
|
+
});
|
|
597
548
|
return result;
|
|
598
549
|
}, []);
|
|
599
550
|
}
|
|
@@ -606,68 +557,45 @@ function makeCoverageAbsolute(dateRange, timezoneName, fixedOffsetMinutes) {
|
|
|
606
557
|
}
|
|
607
558
|
function normalizeProjectionInstant(value, fallbackScalar, timezoneName, fixedOffsetMinutes) {
|
|
608
559
|
const scalar = value ? toDateTime(value) : fallbackScalar;
|
|
609
|
-
return formatDateTime(
|
|
610
|
-
Number.isFinite(scalar) ? scalar : fallbackScalar,
|
|
611
|
-
timezoneName,
|
|
612
|
-
fixedOffsetMinutes
|
|
613
|
-
);
|
|
560
|
+
return formatDateTime(Number.isFinite(scalar) ? scalar : fallbackScalar, timezoneName, fixedOffsetMinutes);
|
|
614
561
|
}
|
|
615
562
|
function cloneSnapshotMetadataValue(value) {
|
|
616
563
|
if (Array.isArray(value)) {
|
|
617
|
-
return value.map(
|
|
564
|
+
return value.map(item => cloneSnapshotMetadataValue(item));
|
|
618
565
|
}
|
|
619
|
-
if (!value || typeof value !==
|
|
620
|
-
return value;
|
|
566
|
+
if (!value || typeof value !== 'object') return value;
|
|
621
567
|
return Object.keys(value).reduce((result, key) => {
|
|
622
568
|
result[key] = cloneSnapshotMetadataValue(value[key]);
|
|
623
569
|
return result;
|
|
624
570
|
}, {});
|
|
625
571
|
}
|
|
626
572
|
function deepFreeze(value) {
|
|
627
|
-
if (!value || typeof value !==
|
|
628
|
-
return value;
|
|
573
|
+
if (!value || typeof value !== 'object' || Object.isFrozen(value)) return value;
|
|
629
574
|
Object.freeze(value);
|
|
630
|
-
Object.getOwnPropertyNames(value).forEach(
|
|
631
|
-
|
|
632
|
-
|
|
575
|
+
Object.getOwnPropertyNames(value).forEach(key => {
|
|
576
|
+
// `raw` is an explicit escape hatch to the backend/catalog record. It is not owned by the
|
|
577
|
+
// projection and must not freeze mutable ProductList / Order data shared elsewhere.
|
|
578
|
+
if (key === 'raw') return;
|
|
633
579
|
const child = value[key];
|
|
634
|
-
if (child && (typeof child ===
|
|
580
|
+
if (child && (typeof child === 'object' || typeof child === 'function')) {
|
|
635
581
|
deepFreeze(child);
|
|
636
582
|
}
|
|
637
583
|
});
|
|
638
584
|
return value;
|
|
639
585
|
}
|
|
640
586
|
function createAvailabilityProjection(input) {
|
|
641
|
-
var _a, _b;
|
|
642
587
|
const coverage = normalizeCoverage(input);
|
|
643
|
-
const fixedOffsetMinutes = (0,
|
|
644
|
-
const runtimeNow = (0,
|
|
588
|
+
const fixedOffsetMinutes = (0, _localClock.captureRuntimeOffsetMinutes)();
|
|
589
|
+
const runtimeNow = (0, _localClock.localDateToScalar)(new Date());
|
|
645
590
|
const products = normalizeProducts(input.products || []);
|
|
646
591
|
const resources = normalizeResources(input.resources || []);
|
|
647
|
-
const resourceOccupancies = uniqByOccupancy(
|
|
648
|
-
|
|
649
|
-
occupancy,
|
|
650
|
-
coverage.absolute,
|
|
651
|
-
input.timezone,
|
|
652
|
-
fixedOffsetMinutes
|
|
653
|
-
)).filter((occupancy) => Boolean(occupancy))
|
|
654
|
-
);
|
|
655
|
-
const preparedAt = normalizeProjectionInstant(
|
|
656
|
-
input.preparedAt,
|
|
657
|
-
runtimeNow,
|
|
658
|
-
input.timezone,
|
|
659
|
-
fixedOffsetMinutes
|
|
660
|
-
);
|
|
592
|
+
const resourceOccupancies = uniqByOccupancy((input.resourceOccupancies || []).map(occupancy => normalizeOccupancy(occupancy, coverage.absolute, input.timezone, fixedOffsetMinutes)).filter(occupancy => Boolean(occupancy)));
|
|
593
|
+
const preparedAt = normalizeProjectionInstant(input.preparedAt, runtimeNow, input.timezone, fixedOffsetMinutes);
|
|
661
594
|
const now = normalizeProjectionInstant(input.now, runtimeNow, input.timezone, fixedOffsetMinutes);
|
|
662
|
-
const durationCandidateAnchorAt = normalizeProjectionInstant(
|
|
663
|
-
input.durationCandidateAnchorAt || now,
|
|
664
|
-
runtimeNow,
|
|
665
|
-
input.timezone,
|
|
666
|
-
fixedOffsetMinutes
|
|
667
|
-
);
|
|
595
|
+
const durationCandidateAnchorAt = normalizeProjectionInstant(input.durationCandidateAnchorAt || now, runtimeNow, input.timezone, fixedOffsetMinutes);
|
|
668
596
|
const businessHours = {
|
|
669
|
-
startTime:
|
|
670
|
-
endTime:
|
|
597
|
+
startTime: input.businessHours?.startTime || DEFAULT_BUSINESS_HOURS.startTime,
|
|
598
|
+
endTime: input.businessHours?.endTime || DEFAULT_BUSINESS_HOURS.endTime
|
|
671
599
|
};
|
|
672
600
|
const operatingHours = normalizeOperatingHours({
|
|
673
601
|
input: input.operatingHours,
|
|
@@ -676,14 +604,14 @@ function createAvailabilityProjection(input) {
|
|
|
676
604
|
dateRange: coverage.dateRange,
|
|
677
605
|
timezoneName: input.timezone,
|
|
678
606
|
fixedOffsetMinutes,
|
|
679
|
-
fallbackSource: input.businessHours ?
|
|
607
|
+
fallbackSource: input.businessHours ? 'prepare_business_hours' : 'default_business_hours'
|
|
680
608
|
});
|
|
681
609
|
const meta = {
|
|
682
610
|
snapshotId: input.snapshotId || `availability-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
|
|
683
611
|
preparedAt,
|
|
684
612
|
timezone: input.timezone,
|
|
685
613
|
orderRevision: input.orderRevision,
|
|
686
|
-
mode: input.mode ||
|
|
614
|
+
mode: input.mode || 'appointment',
|
|
687
615
|
coverage: {
|
|
688
616
|
...coverage.dateRange,
|
|
689
617
|
...makeCoverageAbsolute(coverage.dateRange, input.timezone, fixedOffsetMinutes)
|
|
@@ -697,8 +625,8 @@ function createAvailabilityProjection(input) {
|
|
|
697
625
|
},
|
|
698
626
|
now,
|
|
699
627
|
durationCandidateAnchorAt,
|
|
700
|
-
anchors: input.anchors ? cloneSnapshotMetadataValue(input.anchors) :
|
|
701
|
-
prepareParams: input.prepareParams ? cloneSnapshotMetadataValue(input.prepareParams) :
|
|
628
|
+
anchors: input.anchors ? cloneSnapshotMetadataValue(input.anchors) : undefined,
|
|
629
|
+
prepareParams: input.prepareParams ? cloneSnapshotMetadataValue(input.prepareParams) : undefined
|
|
702
630
|
};
|
|
703
631
|
const projectionBase = {
|
|
704
632
|
meta,
|
|
@@ -712,79 +640,77 @@ function createAvailabilityProjection(input) {
|
|
|
712
640
|
});
|
|
713
641
|
}
|
|
714
642
|
function uniqByOccupancy(occupancies) {
|
|
715
|
-
const map =
|
|
716
|
-
occupancies.forEach(
|
|
643
|
+
const map = new Map();
|
|
644
|
+
occupancies.forEach(occupancy => {
|
|
717
645
|
map.set(dedupeOccupancyRefKey(occupancy), occupancy);
|
|
718
646
|
});
|
|
719
647
|
return Array.from(map.values());
|
|
720
648
|
}
|
|
721
649
|
function resolveProjectionDateRange(projection, requestDateRange) {
|
|
722
|
-
const startDate =
|
|
723
|
-
const endDate =
|
|
724
|
-
const isCanonicalDate =
|
|
650
|
+
const startDate = requestDateRange?.startDate || projection.meta.coverage.startDate;
|
|
651
|
+
const endDate = requestDateRange?.endDate || projection.meta.coverage.endDate;
|
|
652
|
+
const isCanonicalDate = value => {
|
|
725
653
|
try {
|
|
726
|
-
return (0,
|
|
654
|
+
return (0, _localClock.formatLocalDate)((0, _localClock.parseLocalDate)(value)) === value;
|
|
727
655
|
} catch (_error) {
|
|
728
656
|
return false;
|
|
729
657
|
}
|
|
730
658
|
};
|
|
731
659
|
if (!isCanonicalDate(startDate) || !isCanonicalDate(endDate) || endDate < startDate) {
|
|
732
|
-
throw availabilityError(
|
|
660
|
+
throw availabilityError('INVALID_DATE_RANGE', '查询日期范围必须是有效的 YYYY-MM-DD,且结束日期不能早于开始日期', {
|
|
733
661
|
requested: requestDateRange
|
|
734
662
|
});
|
|
735
663
|
}
|
|
736
664
|
if (startDate < projection.meta.coverage.startDate || endDate > projection.meta.coverage.endDate) {
|
|
737
|
-
throw availabilityError(
|
|
665
|
+
throw availabilityError('OUT_OF_PROJECTION_COVERAGE', '查询范围超出 projection.coverage', {
|
|
738
666
|
requested: requestDateRange,
|
|
739
667
|
coverage: projection.meta.coverage
|
|
740
668
|
});
|
|
741
669
|
}
|
|
742
|
-
return {
|
|
670
|
+
return {
|
|
671
|
+
startDate,
|
|
672
|
+
endDate
|
|
673
|
+
};
|
|
743
674
|
}
|
|
744
675
|
function buildOperatingRanges(projection, dateRange) {
|
|
745
676
|
const coverage = {
|
|
746
677
|
start: toDateStart(dateRange.startDate, projection.meta.timezone),
|
|
747
678
|
end: toDateEnd(nextDate(dateRange.endDate), projection.meta.timezone)
|
|
748
679
|
};
|
|
749
|
-
return mergeRanges(
|
|
750
|
-
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))
|
|
751
|
-
);
|
|
680
|
+
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)));
|
|
752
681
|
}
|
|
753
682
|
function buildResourceWindowIndex(projection) {
|
|
754
683
|
const coverage = {
|
|
755
684
|
start: toDateTime(projection.meta.coverage.startAt, projection.meta.timezone),
|
|
756
685
|
end: toDateTime(projection.meta.coverage.endAt, projection.meta.timezone)
|
|
757
686
|
};
|
|
758
|
-
return new Map(
|
|
759
|
-
projection.resources.map((resource) => [
|
|
760
|
-
String(resource.id),
|
|
761
|
-
normalizeResourceWindow(resource, coverage, projection.meta.timezone)
|
|
762
|
-
])
|
|
763
|
-
);
|
|
687
|
+
return new Map(projection.resources.map(resource => [String(resource.id), normalizeResourceWindow(resource, coverage, projection.meta.timezone)]));
|
|
764
688
|
}
|
|
765
689
|
function buildGroupWorkingRanges(params) {
|
|
766
|
-
const {
|
|
690
|
+
const {
|
|
691
|
+
projection,
|
|
692
|
+
requirementGroup,
|
|
693
|
+
dateRange,
|
|
694
|
+
resourceIds,
|
|
695
|
+
partySize
|
|
696
|
+
} = params;
|
|
767
697
|
const guardEndDate = nextDate(dateRange.endDate, projection.meta.timezone);
|
|
768
698
|
const coverage = {
|
|
769
699
|
start: toDateStart(dateRange.startDate, projection.meta.timezone),
|
|
770
700
|
end: toDateEnd(guardEndDate, projection.meta.timezone)
|
|
771
701
|
};
|
|
772
|
-
const filteredResourceIdSet =
|
|
773
|
-
const resourceWindows = projection.resources.filter(
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
return windows.map((window) => clipRange(window, coverage)).filter((window) => Boolean(window));
|
|
702
|
+
const filteredResourceIdSet = resourceIds?.length ? new Set(resourceIds.map(String)) : null;
|
|
703
|
+
const resourceWindows = projection.resources.filter(resource => matchesRequirementGroup(requirementGroup, resource, params.product, partySize)).filter(resource => !filteredResourceIdSet || filteredResourceIdSet.has(String(resource.id))).map(resource => {
|
|
704
|
+
const windows = params.resourceWindowIndex?.get(String(resource.id)) || normalizeResourceWindow(resource, coverage, projection.meta.timezone);
|
|
705
|
+
return windows.map(window => clipRange(window, coverage)).filter(window => Boolean(window));
|
|
777
706
|
});
|
|
778
|
-
const requiredConcurrentResources = Math.max(
|
|
779
|
-
requirementGroup.required === false ? 0 : 1,
|
|
780
|
-
Number(requirementGroup.min ?? (requirementGroup.required === false ? 0 : 1)) || 0
|
|
781
|
-
);
|
|
707
|
+
const requiredConcurrentResources = Math.max(requirementGroup.required === false ? 0 : 1, Number(requirementGroup.min ?? (requirementGroup.required === false ? 0 : 1)) || 0);
|
|
782
708
|
if (requiredConcurrentResources <= 1) {
|
|
783
|
-
return mergeRanges(resourceWindows.flatMap(
|
|
709
|
+
return mergeRanges(resourceWindows.flatMap(windows => windows));
|
|
784
710
|
}
|
|
785
|
-
const boundaries =
|
|
786
|
-
resourceWindows.forEach(
|
|
787
|
-
windows.forEach(
|
|
711
|
+
const boundaries = new Set();
|
|
712
|
+
resourceWindows.forEach(windows => {
|
|
713
|
+
windows.forEach(window => {
|
|
788
714
|
boundaries.add(window.start);
|
|
789
715
|
boundaries.add(window.end);
|
|
790
716
|
});
|
|
@@ -794,10 +720,12 @@ function buildGroupWorkingRanges(params) {
|
|
|
794
720
|
for (let index = 0; index < sortedBoundaries.length - 1; index += 1) {
|
|
795
721
|
const start = sortedBoundaries[index];
|
|
796
722
|
const end = sortedBoundaries[index + 1];
|
|
797
|
-
if (end <= start)
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
723
|
+
if (end <= start) continue;
|
|
724
|
+
const segment = {
|
|
725
|
+
start,
|
|
726
|
+
end
|
|
727
|
+
};
|
|
728
|
+
const activeResources = resourceWindows.reduce((count, windows) => windows.some(window => contains(window, segment)) ? count + 1 : count, 0);
|
|
801
729
|
if (activeResources >= requiredConcurrentResources) {
|
|
802
730
|
satisfiedRanges.push(segment);
|
|
803
731
|
}
|
|
@@ -806,21 +734,19 @@ function buildGroupWorkingRanges(params) {
|
|
|
806
734
|
}
|
|
807
735
|
function materializeScheduleRules(rules, dateRange, timezoneName, fixedOffsetMinutes) {
|
|
808
736
|
const dates = enumerateDates(dateRange.startDate, dateRange.endDate);
|
|
809
|
-
const seen =
|
|
810
|
-
rules.forEach(
|
|
811
|
-
dates.forEach(
|
|
812
|
-
if (!matchesScheduleRuleDate(rule, date, timezoneName))
|
|
813
|
-
return;
|
|
737
|
+
const seen = new Map();
|
|
738
|
+
rules.forEach(rule => {
|
|
739
|
+
dates.forEach(date => {
|
|
740
|
+
if (!matchesScheduleRuleDate(rule, date, timezoneName)) return;
|
|
814
741
|
const start = toDateTimeInDate(date, rule.startTime, timezoneName);
|
|
815
742
|
let end = toDateTimeInDate(date, rule.endTime, timezoneName);
|
|
816
|
-
if (end <= start)
|
|
817
|
-
end = toDateTimeInDate(nextDate(date, timezoneName), rule.endTime, timezoneName);
|
|
743
|
+
if (end <= start) end = toDateTimeInDate(nextDate(date, timezoneName), rule.endTime, timezoneName);
|
|
818
744
|
const key = `${date}:${start}:${end}`;
|
|
819
745
|
if (!seen.has(key)) {
|
|
820
746
|
seen.set(key, {
|
|
821
747
|
key,
|
|
822
|
-
productId:
|
|
823
|
-
source:
|
|
748
|
+
productId: '',
|
|
749
|
+
source: 'session_schedule',
|
|
824
750
|
startAt: formatDateTime(start, timezoneName, fixedOffsetMinutes),
|
|
825
751
|
endAt: formatDateTime(end, timezoneName, fixedOffsetMinutes),
|
|
826
752
|
bookingStartAt: formatDateTime(start, timezoneName, fixedOffsetMinutes),
|
|
@@ -834,85 +760,66 @@ function materializeScheduleRules(rules, dateRange, timezoneName, fixedOffsetMin
|
|
|
834
760
|
}
|
|
835
761
|
const current = seen.get(key);
|
|
836
762
|
const scheduleRef = {
|
|
837
|
-
...rule.scheduleRef || {},
|
|
763
|
+
...(rule.scheduleRef || {}),
|
|
838
764
|
ruleId: rule.id
|
|
839
765
|
};
|
|
840
766
|
current.scheduleRefs = mergeScheduleRefs(current.scheduleRefs, [scheduleRef]);
|
|
841
|
-
if (!current.primaryScheduleRef)
|
|
842
|
-
current.primaryScheduleRef = scheduleRef;
|
|
767
|
+
if (!current.primaryScheduleRef) current.primaryScheduleRef = scheduleRef;
|
|
843
768
|
});
|
|
844
769
|
});
|
|
845
770
|
return Array.from(seen.values());
|
|
846
771
|
}
|
|
847
772
|
function matchesScheduleRuleDate(rule, date, _timezoneName) {
|
|
848
|
-
|
|
849
|
-
if (
|
|
850
|
-
|
|
851
|
-
if (
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
return false;
|
|
855
|
-
if (rule.endDate && date > rule.endDate)
|
|
856
|
-
return false;
|
|
857
|
-
if (rule.mode === "designation") {
|
|
858
|
-
if ((_c = rule.dates) == null ? void 0 : _c.includes(date))
|
|
859
|
-
return true;
|
|
773
|
+
if (rule.includeDates?.includes(date)) return true;
|
|
774
|
+
if (rule.excludeDates?.includes(date)) return false;
|
|
775
|
+
if (rule.startDate && date < rule.startDate) return false;
|
|
776
|
+
if (rule.endDate && date > rule.endDate) return false;
|
|
777
|
+
if (rule.mode === 'designation') {
|
|
778
|
+
if (rule.dates?.includes(date)) return true;
|
|
860
779
|
return false;
|
|
861
780
|
}
|
|
862
|
-
if (rule.mode ===
|
|
863
|
-
if (!
|
|
864
|
-
return true;
|
|
781
|
+
if (rule.mode === 'weekly') {
|
|
782
|
+
if (!rule.weekdays?.length) return true;
|
|
865
783
|
const freq = Math.max(1, Number(rule.frequency || 1) || 1);
|
|
866
|
-
if (!rule.weekdays.includes((0,
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
const targetWeekStart = (0, import_localClock.dateOrdinalFromLocalDate)(date) - (0, import_localClock.weekdayFromLocalDate)(date);
|
|
871
|
-
const ruleWeekStart = (0, import_localClock.dateOrdinalFromLocalDate)(rule.startDate) - (0, import_localClock.weekdayFromLocalDate)(rule.startDate);
|
|
784
|
+
if (!rule.weekdays.includes((0, _localClock.weekdayFromLocalDate)(date))) return false;
|
|
785
|
+
if (!rule.startDate) return true;
|
|
786
|
+
const targetWeekStart = (0, _localClock.dateOrdinalFromLocalDate)(date) - (0, _localClock.weekdayFromLocalDate)(date);
|
|
787
|
+
const ruleWeekStart = (0, _localClock.dateOrdinalFromLocalDate)(rule.startDate) - (0, _localClock.weekdayFromLocalDate)(rule.startDate);
|
|
872
788
|
return Math.floor((targetWeekStart - ruleWeekStart) / 7) % freq === 0;
|
|
873
789
|
}
|
|
874
|
-
if (rule.mode ===
|
|
790
|
+
if (rule.mode === 'daily' && rule.startDate) {
|
|
875
791
|
const freq = Math.max(1, Number(rule.frequency || 1) || 1);
|
|
876
|
-
return ((0,
|
|
792
|
+
return ((0, _localClock.dateOrdinalFromLocalDate)(date) - (0, _localClock.dateOrdinalFromLocalDate)(rule.startDate)) % freq === 0;
|
|
877
793
|
}
|
|
878
794
|
return true;
|
|
879
795
|
}
|
|
880
796
|
function mergeScheduleRefs(base, next) {
|
|
881
|
-
const map =
|
|
882
|
-
[...base, ...next].forEach(
|
|
883
|
-
const key = [
|
|
884
|
-
ref.scheduleId ?? "",
|
|
885
|
-
ref.timeSlotId ?? "",
|
|
886
|
-
ref.ruleId ?? "",
|
|
887
|
-
ref.source ?? "",
|
|
888
|
-
ref.label ?? ""
|
|
889
|
-
].join(":");
|
|
797
|
+
const map = new Map();
|
|
798
|
+
[...base, ...next].forEach(ref => {
|
|
799
|
+
const key = [ref.scheduleId ?? '', ref.timeSlotId ?? '', ref.ruleId ?? '', ref.source ?? '', ref.label ?? ''].join(':');
|
|
890
800
|
map.set(key, ref);
|
|
891
801
|
});
|
|
892
802
|
return Array.from(map.values());
|
|
893
803
|
}
|
|
894
804
|
function buildProductIndex(projection) {
|
|
895
|
-
return new Map(projection.products.map(
|
|
805
|
+
return new Map(projection.products.map(product => [String(product.id), product]));
|
|
896
806
|
}
|
|
897
807
|
function buildGroupResourceIndex(params) {
|
|
898
|
-
|
|
899
|
-
const
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
index.set(key, params.projection.resources.filter((resource) => matchesRequirementGroup(requirementGroup, resource, product, params.partySize)).filter((resource) => !resourceIdSet || resourceIdSet.has(String(resource.id))));
|
|
808
|
+
const resourceIdSet = params.resourceIds?.length ? new Set(params.resourceIds.map(String)) : null;
|
|
809
|
+
const index = new Map();
|
|
810
|
+
params.projection.products.forEach(product => {
|
|
811
|
+
(product.requirementGroups || []).forEach(requirementGroup => {
|
|
812
|
+
const key = [String(product.id), requirementGroup.id].join(':');
|
|
813
|
+
index.set(key, params.projection.resources.filter(resource => matchesRequirementGroup(requirementGroup, resource, product, params.partySize)).filter(resource => !resourceIdSet || resourceIdSet.has(String(resource.id))));
|
|
905
814
|
});
|
|
906
815
|
});
|
|
907
816
|
return index;
|
|
908
817
|
}
|
|
909
818
|
function getSessionRangesForProduct(projection, product, dateRange) {
|
|
910
|
-
const fixedOffsetMinutes = (0,
|
|
911
|
-
const ranges = (product.sessionRanges || []).map(
|
|
912
|
-
var _a;
|
|
819
|
+
const fixedOffsetMinutes = (0, _localClock.extractFixedOffsetMinutes)(projection.meta.now) ?? (0, _localClock.captureRuntimeOffsetMinutes)();
|
|
820
|
+
const ranges = (product.sessionRanges || []).map(range => {
|
|
913
821
|
const normalized = normalizeAbsoluteRange(range, projection.meta.timezone);
|
|
914
|
-
if (!normalized)
|
|
915
|
-
return null;
|
|
822
|
+
if (!normalized) return null;
|
|
916
823
|
const startDate = formatDate(normalized.start, projection.meta.timezone);
|
|
917
824
|
if (startDate < dateRange.startDate || startDate > dateRange.endDate) {
|
|
918
825
|
return null;
|
|
@@ -920,7 +827,7 @@ function getSessionRangesForProduct(projection, product, dateRange) {
|
|
|
920
827
|
return {
|
|
921
828
|
key: `${String(product.id)}:${normalized.start}:${normalized.end}`,
|
|
922
829
|
productId: product.id,
|
|
923
|
-
source:
|
|
830
|
+
source: 'session_schedule',
|
|
924
831
|
startAt: formatDateTime(normalized.start, projection.meta.timezone, fixedOffsetMinutes),
|
|
925
832
|
endAt: formatDateTime(normalized.end, projection.meta.timezone, fixedOffsetMinutes),
|
|
926
833
|
bookingStartAt: formatDateTime(normalized.start, projection.meta.timezone, fixedOffsetMinutes),
|
|
@@ -930,16 +837,11 @@ function getSessionRangesForProduct(projection, product, dateRange) {
|
|
|
930
837
|
endTime: formatTime(normalized.end, projection.meta.timezone),
|
|
931
838
|
timezone: projection.meta.timezone,
|
|
932
839
|
scheduleRefs: range.scheduleRefs || [],
|
|
933
|
-
primaryScheduleRef:
|
|
840
|
+
primaryScheduleRef: range.scheduleRefs?.[0]
|
|
934
841
|
};
|
|
935
842
|
}).filter(Boolean);
|
|
936
|
-
const ruleRanges = materializeScheduleRules(
|
|
937
|
-
|
|
938
|
-
dateRange,
|
|
939
|
-
projection.meta.timezone,
|
|
940
|
-
fixedOffsetMinutes
|
|
941
|
-
);
|
|
942
|
-
return dedupeProductRanges([...ranges, ...ruleRanges.map((range) => ({
|
|
843
|
+
const ruleRanges = materializeScheduleRules(product.sessionRules || [], dateRange, projection.meta.timezone, fixedOffsetMinutes);
|
|
844
|
+
return dedupeProductRanges([...ranges, ...ruleRanges.map(range => ({
|
|
943
845
|
...range,
|
|
944
846
|
key: `${String(product.id)}:${range.startAt}:${range.endAt}`,
|
|
945
847
|
productId: product.id,
|
|
@@ -947,29 +849,30 @@ function getSessionRangesForProduct(projection, product, dateRange) {
|
|
|
947
849
|
}))]);
|
|
948
850
|
}
|
|
949
851
|
function dedupeProductRanges(ranges) {
|
|
950
|
-
const map =
|
|
951
|
-
ranges.forEach(
|
|
852
|
+
const map = new Map();
|
|
853
|
+
ranges.forEach(range => {
|
|
952
854
|
const key = getProductTimeRangeIdentity(range);
|
|
953
855
|
if (!map.has(key)) {
|
|
954
|
-
map.set(key, {
|
|
856
|
+
map.set(key, {
|
|
857
|
+
...range,
|
|
858
|
+
key
|
|
859
|
+
});
|
|
955
860
|
return;
|
|
956
861
|
}
|
|
957
862
|
const existing = map.get(key);
|
|
958
863
|
existing.scheduleRefs = mergeScheduleRefs(existing.scheduleRefs, range.scheduleRefs || []);
|
|
959
|
-
if (!existing.primaryScheduleRef)
|
|
960
|
-
existing.primaryScheduleRef = range.primaryScheduleRef;
|
|
864
|
+
if (!existing.primaryScheduleRef) existing.primaryScheduleRef = range.primaryScheduleRef;
|
|
961
865
|
});
|
|
962
866
|
return Array.from(map.values()).sort((left, right) => left.startAt.localeCompare(right.startAt));
|
|
963
867
|
}
|
|
964
868
|
function getProductTimeRanges(projection, request = {}) {
|
|
965
|
-
var _a;
|
|
966
869
|
const normalizedPartySize = normalizePartySize(request.partySize);
|
|
967
870
|
const dateRange = resolveProjectionDateRange(projection, request.dateRange);
|
|
968
|
-
const fixedOffsetMinutes = (0,
|
|
871
|
+
const fixedOffsetMinutes = (0, _localClock.extractFixedOffsetMinutes)(projection.meta.now) ?? (0, _localClock.captureRuntimeOffsetMinutes)();
|
|
969
872
|
const resourceWindowIndex = buildResourceWindowIndex(projection);
|
|
970
|
-
const filteredProductIdSet =
|
|
971
|
-
return projection.products.filter(
|
|
972
|
-
if (product.kind ===
|
|
873
|
+
const filteredProductIdSet = request.productIds?.length ? new Set(request.productIds.map(String)) : null;
|
|
874
|
+
return projection.products.filter(product => !filteredProductIdSet || filteredProductIdSet.has(String(product.id))).map(product => {
|
|
875
|
+
if (product.kind === 'normal') {
|
|
973
876
|
return {
|
|
974
877
|
productId: product.id,
|
|
975
878
|
title: product.title,
|
|
@@ -977,7 +880,7 @@ function getProductTimeRanges(projection, request = {}) {
|
|
|
977
880
|
ranges: []
|
|
978
881
|
};
|
|
979
882
|
}
|
|
980
|
-
if (product.kind ===
|
|
883
|
+
if (product.kind === 'session' || product.kind === 'venue_slot') {
|
|
981
884
|
return {
|
|
982
885
|
productId: product.id,
|
|
983
886
|
title: product.title,
|
|
@@ -985,9 +888,9 @@ function getProductTimeRanges(projection, request = {}) {
|
|
|
985
888
|
ranges: getSessionRangesForProduct(projection, product, dateRange)
|
|
986
889
|
};
|
|
987
890
|
}
|
|
988
|
-
const requiredGroups = (product.requirementGroups || []).filter(
|
|
891
|
+
const requiredGroups = (product.requirementGroups || []).filter(group => group.required !== false);
|
|
989
892
|
const operatingRanges = buildOperatingRanges(projection, dateRange);
|
|
990
|
-
const resourceRanges = requiredGroups.length ? requiredGroups.map(
|
|
893
|
+
const resourceRanges = requiredGroups.length ? requiredGroups.map(group => buildGroupWorkingRanges({
|
|
991
894
|
projection,
|
|
992
895
|
product,
|
|
993
896
|
requirementGroup: group,
|
|
@@ -995,32 +898,27 @@ function getProductTimeRanges(projection, request = {}) {
|
|
|
995
898
|
resourceIds: request.resourceIds,
|
|
996
899
|
partySize: normalizedPartySize,
|
|
997
900
|
resourceWindowIndex
|
|
998
|
-
})).reduce((current,
|
|
901
|
+
})).reduce((current, ranges) => current === null ? ranges : intersectRangeLists(current, ranges), null) || [] : null;
|
|
999
902
|
const baseRanges = resourceRanges === null ? operatingRanges : intersectRangeLists(resourceRanges, operatingRanges);
|
|
1000
903
|
const durationMinutes = Math.max(1, Number(product.durationMinutes || 30) || 30);
|
|
1001
904
|
const timeSliceMinutes = projection.meta.defaults.slotStepMinutes;
|
|
1002
|
-
const durationAnchor = ceilToTenMinutes(
|
|
1003
|
-
toDateTime(projection.meta.durationCandidateAnchorAt, projection.meta.timezone),
|
|
1004
|
-
projection.meta.timezone
|
|
1005
|
-
);
|
|
905
|
+
const durationAnchor = ceilToTenMinutes(toDateTime(projection.meta.durationCandidateAnchorAt, projection.meta.timezone), projection.meta.timezone);
|
|
1006
906
|
const ranges = [];
|
|
1007
|
-
baseRanges.forEach(
|
|
1008
|
-
if (range.end <= durationAnchor)
|
|
1009
|
-
return;
|
|
907
|
+
baseRanges.forEach(range => {
|
|
908
|
+
if (range.end <= durationAnchor) return;
|
|
1010
909
|
let cursor = range.start;
|
|
1011
910
|
if (cursor < durationAnchor) {
|
|
1012
911
|
cursor = durationAnchor;
|
|
1013
912
|
}
|
|
1014
913
|
while (true) {
|
|
1015
914
|
const bookingEnd = addMinutesZoned(cursor, durationMinutes, projection.meta.timezone);
|
|
1016
|
-
if (bookingEnd > range.end)
|
|
1017
|
-
break;
|
|
915
|
+
if (bookingEnd > range.end) break;
|
|
1018
916
|
const bookingDate = formatDate(cursor);
|
|
1019
917
|
if (bookingDate >= dateRange.startDate && bookingDate <= dateRange.endDate) {
|
|
1020
918
|
ranges.push({
|
|
1021
919
|
key: `${String(product.id)}:${cursor}:${bookingEnd}`,
|
|
1022
920
|
productId: product.id,
|
|
1023
|
-
source:
|
|
921
|
+
source: 'duration_window',
|
|
1024
922
|
startAt: formatDateTime(cursor, projection.meta.timezone, fixedOffsetMinutes),
|
|
1025
923
|
endAt: formatDateTime(bookingEnd, projection.meta.timezone, fixedOffsetMinutes),
|
|
1026
924
|
bookingStartAt: formatDateTime(cursor, projection.meta.timezone, fixedOffsetMinutes),
|
|
@@ -1058,61 +956,42 @@ function aggregateMatchedCells(productId, requirementGroupId, resourceId, candid
|
|
|
1058
956
|
}
|
|
1059
957
|
const overlapped = [];
|
|
1060
958
|
for (let index = Math.max(0, left - 1); index < indexedCells.length; index += 1) {
|
|
1061
|
-
const {
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
959
|
+
const {
|
|
960
|
+
cell,
|
|
961
|
+
range
|
|
962
|
+
} = indexedCells[index];
|
|
963
|
+
if (range.startMs >= candidateEndMs) break;
|
|
964
|
+
if (range.endMs <= candidateStartMs) continue;
|
|
1066
965
|
const clipped = clipRange(range, candidateRange);
|
|
1067
|
-
if (clipped)
|
|
1068
|
-
|
|
966
|
+
if (clipped) overlapped.push({
|
|
967
|
+
cell,
|
|
968
|
+
range: clipped
|
|
969
|
+
});
|
|
1069
970
|
}
|
|
1070
|
-
if (!overlapped.length)
|
|
1071
|
-
return void 0;
|
|
971
|
+
if (!overlapped.length) return undefined;
|
|
1072
972
|
let cursor = candidateRange.start;
|
|
1073
973
|
for (const item of overlapped) {
|
|
1074
|
-
if (item.range.start > cursor)
|
|
1075
|
-
return void 0;
|
|
974
|
+
if (item.range.start > cursor) return undefined;
|
|
1076
975
|
if (item.range.end > cursor) {
|
|
1077
976
|
cursor = item.range.end;
|
|
1078
977
|
}
|
|
1079
|
-
if (cursor >= candidateRange.end)
|
|
1080
|
-
break;
|
|
978
|
+
if (cursor >= candidateRange.end) break;
|
|
1081
979
|
}
|
|
1082
|
-
if (cursor < candidateRange.end)
|
|
1083
|
-
|
|
1084
|
-
const
|
|
1085
|
-
const
|
|
1086
|
-
const nonPastStatuses = statuses.filter((status) => status !== "past");
|
|
980
|
+
if (cursor < candidateRange.end) return undefined;
|
|
981
|
+
const cells = overlapped.map(item => item.cell);
|
|
982
|
+
const statuses = cells.map(cell => cell.status);
|
|
983
|
+
const nonPastStatuses = statuses.filter(status => status !== 'past');
|
|
1087
984
|
const aggregatedStatus = (() => {
|
|
1088
|
-
if (nonPastStatuses.some(
|
|
1089
|
-
|
|
1090
|
-
if (nonPastStatuses.some(
|
|
1091
|
-
|
|
1092
|
-
if (
|
|
1093
|
-
|
|
1094
|
-
if (nonPastStatuses.every((status) => status === "available"))
|
|
1095
|
-
return "available";
|
|
1096
|
-
if (statuses.every((status) => status === "past"))
|
|
1097
|
-
return "past";
|
|
1098
|
-
return "available";
|
|
985
|
+
if (nonPastStatuses.some(status => status === 'unavailable')) return 'unavailable';
|
|
986
|
+
if (nonPastStatuses.some(status => status === 'full')) return 'full';
|
|
987
|
+
if (nonPastStatuses.some(status => status === 'limited')) return 'limited';
|
|
988
|
+
if (nonPastStatuses.every(status => status === 'available')) return 'available';
|
|
989
|
+
if (statuses.every(status => status === 'past')) return 'past';
|
|
990
|
+
return 'available';
|
|
1099
991
|
})();
|
|
1100
|
-
const capacityCells = cells.filter(
|
|
1101
|
-
const reasonCodes = Array.from(new Set(cells.flatMap(
|
|
1102
|
-
const occupancyRefs = Array.from(new Map(
|
|
1103
|
-
cells.flatMap((cell) => cell.occupancyRefs || []).map((ref) => [
|
|
1104
|
-
[
|
|
1105
|
-
ref.id ?? "",
|
|
1106
|
-
ref.resourceId ?? "",
|
|
1107
|
-
ref.serverEventId ?? "",
|
|
1108
|
-
ref.bookingUid ?? "",
|
|
1109
|
-
ref.productUid ?? "",
|
|
1110
|
-
ref.scheduleEventId ?? "",
|
|
1111
|
-
ref.source ?? ""
|
|
1112
|
-
].join(":"),
|
|
1113
|
-
ref
|
|
1114
|
-
])
|
|
1115
|
-
).values());
|
|
992
|
+
const capacityCells = cells.filter(cell => cell.remainingCapacity !== undefined || cell.maxCapacity !== undefined);
|
|
993
|
+
const reasonCodes = Array.from(new Set(cells.flatMap(cell => cell.reasonCodes)));
|
|
994
|
+
const occupancyRefs = Array.from(new Map(cells.flatMap(cell => cell.occupancyRefs || []).map(ref => [[ref.id ?? '', ref.resourceId ?? '', ref.serverEventId ?? '', ref.bookingUid ?? '', ref.productUid ?? '', ref.scheduleEventId ?? '', ref.source ?? ''].join(':'), ref])).values());
|
|
1116
995
|
const firstCell = cells[0];
|
|
1117
996
|
return {
|
|
1118
997
|
...firstCell,
|
|
@@ -1123,22 +1002,14 @@ function aggregateMatchedCells(productId, requirementGroupId, resourceId, candid
|
|
|
1123
1002
|
start: candidateRange.start,
|
|
1124
1003
|
end: candidateRange.end
|
|
1125
1004
|
}),
|
|
1126
|
-
startAt: formatDateTime(
|
|
1127
|
-
|
|
1128
|
-
firstCell.timezone,
|
|
1129
|
-
(0, import_localClock.extractFixedOffsetMinutes)(firstCell.startAt) ?? (0, import_localClock.captureRuntimeOffsetMinutes)()
|
|
1130
|
-
),
|
|
1131
|
-
endAt: formatDateTime(
|
|
1132
|
-
candidateRange.end,
|
|
1133
|
-
firstCell.timezone,
|
|
1134
|
-
(0, import_localClock.extractFixedOffsetMinutes)(firstCell.startAt) ?? (0, import_localClock.captureRuntimeOffsetMinutes)()
|
|
1135
|
-
),
|
|
1005
|
+
startAt: formatDateTime(candidateRange.start, firstCell.timezone, (0, _localClock.extractFixedOffsetMinutes)(firstCell.startAt) ?? (0, _localClock.captureRuntimeOffsetMinutes)()),
|
|
1006
|
+
endAt: formatDateTime(candidateRange.end, firstCell.timezone, (0, _localClock.extractFixedOffsetMinutes)(firstCell.startAt) ?? (0, _localClock.captureRuntimeOffsetMinutes)()),
|
|
1136
1007
|
date: formatDate(candidateRange.start, firstCell.timezone),
|
|
1137
1008
|
startTime: formatTime(candidateRange.start, firstCell.timezone),
|
|
1138
1009
|
endTime: formatTime(candidateRange.end, firstCell.timezone),
|
|
1139
1010
|
status: aggregatedStatus,
|
|
1140
|
-
remainingCapacity: capacityCells.length ? Math.min(...capacityCells.map(
|
|
1141
|
-
maxCapacity: capacityCells.length ? Math.min(...capacityCells.map(
|
|
1011
|
+
remainingCapacity: capacityCells.length ? Math.min(...capacityCells.map(cell => cell.remainingCapacity ?? Number.MAX_SAFE_INTEGER)) : undefined,
|
|
1012
|
+
maxCapacity: capacityCells.length ? Math.min(...capacityCells.map(cell => cell.maxCapacity ?? Number.MAX_SAFE_INTEGER)) : undefined,
|
|
1142
1013
|
reasonCodes,
|
|
1143
1014
|
occupancyRefs
|
|
1144
1015
|
};
|
|
@@ -1159,54 +1030,44 @@ function aggregateMatchedCellSummary(candidateRange, indexedCells) {
|
|
|
1159
1030
|
const cells = [];
|
|
1160
1031
|
let cursorMs = candidateStartMs;
|
|
1161
1032
|
for (let index = Math.max(0, left - 1); index < indexedCells.length; index += 1) {
|
|
1162
|
-
const {
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1033
|
+
const {
|
|
1034
|
+
cell,
|
|
1035
|
+
range
|
|
1036
|
+
} = indexedCells[index];
|
|
1037
|
+
if (range.startMs >= candidateEndMs) break;
|
|
1038
|
+
if (range.endMs <= candidateStartMs) continue;
|
|
1167
1039
|
const clippedStartMs = Math.max(range.startMs, candidateStartMs);
|
|
1168
1040
|
const clippedEndMs = Math.min(range.endMs, candidateEndMs);
|
|
1169
|
-
if (clippedEndMs <= clippedStartMs)
|
|
1170
|
-
|
|
1171
|
-
if (clippedStartMs > cursorMs)
|
|
1172
|
-
return void 0;
|
|
1041
|
+
if (clippedEndMs <= clippedStartMs) continue;
|
|
1042
|
+
if (clippedStartMs > cursorMs) return undefined;
|
|
1173
1043
|
cells.push(cell);
|
|
1174
|
-
if (clippedEndMs > cursorMs)
|
|
1175
|
-
|
|
1176
|
-
if (cursorMs >= candidateEndMs)
|
|
1177
|
-
break;
|
|
1044
|
+
if (clippedEndMs > cursorMs) cursorMs = clippedEndMs;
|
|
1045
|
+
if (cursorMs >= candidateEndMs) break;
|
|
1178
1046
|
}
|
|
1179
|
-
if (!cells.length || cursorMs < candidateEndMs)
|
|
1180
|
-
|
|
1181
|
-
const
|
|
1182
|
-
const nonPastStatuses = statuses.filter((status2) => status2 !== "past");
|
|
1047
|
+
if (!cells.length || cursorMs < candidateEndMs) return undefined;
|
|
1048
|
+
const statuses = cells.map(cell => cell.status);
|
|
1049
|
+
const nonPastStatuses = statuses.filter(status => status !== 'past');
|
|
1183
1050
|
const status = (() => {
|
|
1184
|
-
if (nonPastStatuses.some(
|
|
1185
|
-
|
|
1186
|
-
if (nonPastStatuses.some(
|
|
1187
|
-
|
|
1188
|
-
if (
|
|
1189
|
-
|
|
1190
|
-
if (nonPastStatuses.every((cellStatus) => cellStatus === "available"))
|
|
1191
|
-
return "available";
|
|
1192
|
-
if (statuses.every((cellStatus) => cellStatus === "past"))
|
|
1193
|
-
return "past";
|
|
1194
|
-
return "available";
|
|
1051
|
+
if (nonPastStatuses.some(cellStatus => cellStatus === 'unavailable')) return 'unavailable';
|
|
1052
|
+
if (nonPastStatuses.some(cellStatus => cellStatus === 'full')) return 'full';
|
|
1053
|
+
if (nonPastStatuses.some(cellStatus => cellStatus === 'limited')) return 'limited';
|
|
1054
|
+
if (nonPastStatuses.every(cellStatus => cellStatus === 'available')) return 'available';
|
|
1055
|
+
if (statuses.every(cellStatus => cellStatus === 'past')) return 'past';
|
|
1056
|
+
return 'available';
|
|
1195
1057
|
})();
|
|
1196
|
-
const capacityCells = cells.filter(
|
|
1058
|
+
const capacityCells = cells.filter(cell => cell.remainingCapacity !== undefined || cell.maxCapacity !== undefined);
|
|
1197
1059
|
return {
|
|
1198
1060
|
status,
|
|
1199
|
-
remainingCapacity: capacityCells.length ? Math.min(...capacityCells.map(
|
|
1200
|
-
maxCapacity: capacityCells.length ? Math.min(...capacityCells.map(
|
|
1201
|
-
reasonCodes: Array.from(new Set(cells.flatMap(
|
|
1061
|
+
remainingCapacity: capacityCells.length ? Math.min(...capacityCells.map(cell => cell.remainingCapacity ?? Number.MAX_SAFE_INTEGER)) : undefined,
|
|
1062
|
+
maxCapacity: capacityCells.length ? Math.min(...capacityCells.map(cell => cell.maxCapacity ?? Number.MAX_SAFE_INTEGER)) : undefined,
|
|
1063
|
+
reasonCodes: Array.from(new Set(cells.flatMap(cell => cell.reasonCodes)))
|
|
1202
1064
|
};
|
|
1203
1065
|
}
|
|
1204
1066
|
function buildAvailabilityCellIndex(projection) {
|
|
1205
|
-
const index =
|
|
1206
|
-
projection.cells.forEach(
|
|
1207
|
-
if (cell.requirementGroupId == null || cell.resourceId == null)
|
|
1208
|
-
|
|
1209
|
-
const key = [String(cell.productId), cell.requirementGroupId, String(cell.resourceId)].join(":");
|
|
1067
|
+
const index = new Map();
|
|
1068
|
+
projection.cells.forEach(cell => {
|
|
1069
|
+
if (cell.requirementGroupId == null || cell.resourceId == null) return;
|
|
1070
|
+
const key = [String(cell.productId), cell.requirementGroupId, String(cell.resourceId)].join(':');
|
|
1210
1071
|
const entries = index.get(key) || [];
|
|
1211
1072
|
entries.push({
|
|
1212
1073
|
cell,
|
|
@@ -1223,35 +1084,27 @@ function buildAvailabilityCellIndex(projection) {
|
|
|
1223
1084
|
return index;
|
|
1224
1085
|
}
|
|
1225
1086
|
function buildEvaluationContext(params) {
|
|
1226
|
-
var _a;
|
|
1227
1087
|
const evaluatedAt = toDateTime(params.evaluatedAt || params.projection.meta.now, params.projection.meta.timezone);
|
|
1228
|
-
const selectionsByGroupKey =
|
|
1229
|
-
(params.requirementSelections || []).forEach(
|
|
1230
|
-
const key = selection.requirementGroupId || String(selection.formId ??
|
|
1088
|
+
const selectionsByGroupKey = new Map();
|
|
1089
|
+
(params.requirementSelections || []).forEach(selection => {
|
|
1090
|
+
const key = selection.requirementGroupId || String(selection.formId ?? '');
|
|
1231
1091
|
selectionsByGroupKey.set(key, selection);
|
|
1232
1092
|
});
|
|
1233
1093
|
return {
|
|
1234
1094
|
evaluatedAt,
|
|
1235
1095
|
evaluatedAtMs: evaluatedAt,
|
|
1236
|
-
resourceIdSet:
|
|
1096
|
+
resourceIdSet: params.resourceIds?.length ? new Set(params.resourceIds.map(String)) : null,
|
|
1237
1097
|
selectionsByGroupKey
|
|
1238
1098
|
};
|
|
1239
1099
|
}
|
|
1240
1100
|
function summarizeStatuses(statuses) {
|
|
1241
|
-
if (!statuses.length)
|
|
1242
|
-
|
|
1243
|
-
if (statuses.
|
|
1244
|
-
|
|
1245
|
-
if (statuses.some(
|
|
1246
|
-
|
|
1247
|
-
if (statuses.some(
|
|
1248
|
-
return "full";
|
|
1249
|
-
if (statuses.some((status) => status === "limited"))
|
|
1250
|
-
return "limited";
|
|
1251
|
-
if (statuses.every((status) => status === "available"))
|
|
1252
|
-
return "available";
|
|
1253
|
-
if (statuses.some((status) => status === "past"))
|
|
1254
|
-
return "past";
|
|
1101
|
+
if (!statuses.length) return 'unavailable';
|
|
1102
|
+
if (statuses.every(status => status === 'past')) return 'past';
|
|
1103
|
+
if (statuses.some(status => status === 'unavailable')) return 'unavailable';
|
|
1104
|
+
if (statuses.some(status => status === 'full')) return 'full';
|
|
1105
|
+
if (statuses.some(status => status === 'limited')) return 'limited';
|
|
1106
|
+
if (statuses.every(status => status === 'available')) return 'available';
|
|
1107
|
+
if (statuses.some(status => status === 'past')) return 'past';
|
|
1255
1108
|
return statuses[0];
|
|
1256
1109
|
}
|
|
1257
1110
|
function createStatusAggregate() {
|
|
@@ -1265,83 +1118,72 @@ function createStatusAggregate() {
|
|
|
1265
1118
|
}
|
|
1266
1119
|
function pushStatusAggregate(aggregate, status) {
|
|
1267
1120
|
aggregate.count += 1;
|
|
1268
|
-
if (status !==
|
|
1269
|
-
|
|
1270
|
-
if (status ===
|
|
1271
|
-
|
|
1272
|
-
if (status === "limited")
|
|
1273
|
-
aggregate.hasLimited = true;
|
|
1274
|
-
if (status === "full")
|
|
1275
|
-
aggregate.hasFull = true;
|
|
1121
|
+
if (status !== 'past') aggregate.allPast = false;
|
|
1122
|
+
if (status === 'available') aggregate.hasAvailable = true;
|
|
1123
|
+
if (status === 'limited') aggregate.hasLimited = true;
|
|
1124
|
+
if (status === 'full') aggregate.hasFull = true;
|
|
1276
1125
|
}
|
|
1277
1126
|
function summarizeStatusAggregate(aggregate) {
|
|
1278
|
-
if (aggregate.count === 0)
|
|
1279
|
-
|
|
1280
|
-
if (aggregate.
|
|
1281
|
-
|
|
1282
|
-
if (aggregate.
|
|
1283
|
-
|
|
1284
|
-
if (aggregate.hasFull)
|
|
1285
|
-
return "full";
|
|
1286
|
-
if (aggregate.allPast)
|
|
1287
|
-
return "past";
|
|
1288
|
-
return "unavailable";
|
|
1127
|
+
if (aggregate.count === 0) return 'unavailable';
|
|
1128
|
+
if (aggregate.hasAvailable) return 'available';
|
|
1129
|
+
if (aggregate.hasLimited) return 'limited';
|
|
1130
|
+
if (aggregate.hasFull) return 'full';
|
|
1131
|
+
if (aggregate.allPast) return 'past';
|
|
1132
|
+
return 'unavailable';
|
|
1289
1133
|
}
|
|
1290
1134
|
function evaluateMatchedResourceSummary(params) {
|
|
1291
1135
|
const isPast = params.candidateRange.endMs <= params.evaluationContext.evaluatedAtMs;
|
|
1292
1136
|
if (!params.matchedCell) {
|
|
1293
1137
|
return {
|
|
1294
|
-
status: isPast ?
|
|
1138
|
+
status: isPast ? 'past' : 'unavailable',
|
|
1295
1139
|
maxPartySize: 0,
|
|
1296
1140
|
remainingCapacity: 0,
|
|
1297
1141
|
maxCapacity: 0,
|
|
1298
|
-
reasonCodes: isPast ? [
|
|
1142
|
+
reasonCodes: isPast ? ['past'] : ['outside_resource_window']
|
|
1299
1143
|
};
|
|
1300
1144
|
}
|
|
1301
1145
|
const remainingCapacity = Math.max(0, params.matchedCell.remainingCapacity || 0);
|
|
1302
1146
|
const maxCapacity = Math.max(0, params.matchedCell.maxCapacity ?? params.resourceCapacity ?? 0);
|
|
1303
1147
|
const maxPartySize = isPast ? 0 : remainingCapacity;
|
|
1304
1148
|
const status = (() => {
|
|
1305
|
-
if (isPast)
|
|
1306
|
-
|
|
1307
|
-
if (params.
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
return "full";
|
|
1311
|
-
if (remainingCapacity < maxCapacity)
|
|
1312
|
-
return "limited";
|
|
1313
|
-
return "available";
|
|
1149
|
+
if (isPast) return 'past';
|
|
1150
|
+
if (params.matchedCell.status === 'unavailable') return 'unavailable';
|
|
1151
|
+
if (remainingCapacity < params.requiredCapacity) return 'full';
|
|
1152
|
+
if (remainingCapacity < maxCapacity) return 'limited';
|
|
1153
|
+
return 'available';
|
|
1314
1154
|
})();
|
|
1315
1155
|
return {
|
|
1316
1156
|
status,
|
|
1317
1157
|
remainingCapacity,
|
|
1318
1158
|
maxCapacity,
|
|
1319
1159
|
maxPartySize,
|
|
1320
|
-
reasonCodes: isPast ? Array.from(
|
|
1160
|
+
reasonCodes: isPast ? Array.from(new Set([...(params.matchedCell.reasonCodes || []), 'past'])) : params.matchedCell.reasonCodes
|
|
1321
1161
|
};
|
|
1322
1162
|
}
|
|
1323
1163
|
function evaluateRequirementGroupSummary(params) {
|
|
1324
|
-
|
|
1325
|
-
|
|
1164
|
+
const {
|
|
1165
|
+
projection,
|
|
1166
|
+
product,
|
|
1167
|
+
requirementGroup,
|
|
1168
|
+
candidateRange,
|
|
1169
|
+
requestedPartySize,
|
|
1170
|
+
evaluationContext
|
|
1171
|
+
} = params;
|
|
1326
1172
|
const required = requirementGroup.required !== false;
|
|
1327
1173
|
const minimum = Math.max(required ? 1 : 0, Number(requirementGroup.min ?? (required ? 1 : 0)) || 0);
|
|
1328
1174
|
const maximum = Math.max(minimum, Number(requirementGroup.max ?? Math.max(minimum, 1)) || Math.max(minimum, 1));
|
|
1329
|
-
const matchedResources =
|
|
1175
|
+
const matchedResources = params.groupResourceIndex?.get([String(product.id), requirementGroup.id].join(':')) || projection.resources.filter(resource => matchesRequirementGroup(requirementGroup, resource, product, requestedPartySize ?? 1)).filter(resource => !evaluationContext.resourceIdSet || evaluationContext.resourceIdSet.has(String(resource.id)));
|
|
1330
1176
|
const requiredCapacity = resolveRequiredCapacity(requirementGroup, product, requestedPartySize);
|
|
1331
1177
|
const optionStatusAggregate = createStatusAggregate();
|
|
1332
|
-
const reasonCodeSet =
|
|
1178
|
+
const reasonCodeSet = new Set();
|
|
1333
1179
|
let totalResourceCount = 0;
|
|
1334
1180
|
let availableResourceCount = 0;
|
|
1335
1181
|
let groupMaxPartySize = 0;
|
|
1336
1182
|
let groupRemainingCapacity = 0;
|
|
1337
1183
|
let groupMaxCapacity = 0;
|
|
1338
|
-
matchedResources.forEach(
|
|
1339
|
-
var _a2;
|
|
1184
|
+
matchedResources.forEach(resource => {
|
|
1340
1185
|
totalResourceCount += 1;
|
|
1341
|
-
const matchedCell = aggregateMatchedCellSummary(
|
|
1342
|
-
candidateRange,
|
|
1343
|
-
((_a2 = params.cellIndex) == null ? void 0 : _a2.get([String(product.id), requirementGroup.id, String(resource.id)].join(":"))) || []
|
|
1344
|
-
);
|
|
1186
|
+
const matchedCell = aggregateMatchedCellSummary(candidateRange, params.cellIndex?.get([String(product.id), requirementGroup.id, String(resource.id)].join(':')) || []);
|
|
1345
1187
|
const option = evaluateMatchedResourceSummary({
|
|
1346
1188
|
matchedCell,
|
|
1347
1189
|
resourceCapacity: resource.capacity,
|
|
@@ -1350,23 +1192,23 @@ function evaluateRequirementGroupSummary(params) {
|
|
|
1350
1192
|
evaluationContext
|
|
1351
1193
|
});
|
|
1352
1194
|
pushStatusAggregate(optionStatusAggregate, option.status);
|
|
1353
|
-
option.reasonCodes.forEach(
|
|
1195
|
+
option.reasonCodes.forEach(reasonCode => reasonCodeSet.add(reasonCode));
|
|
1354
1196
|
groupRemainingCapacity = Math.max(groupRemainingCapacity, option.remainingCapacity);
|
|
1355
1197
|
groupMaxCapacity = Math.max(groupMaxCapacity, option.maxCapacity);
|
|
1356
|
-
if (option.status ===
|
|
1198
|
+
if (option.status === 'available' || option.status === 'limited') {
|
|
1357
1199
|
availableResourceCount += 1;
|
|
1358
1200
|
groupMaxPartySize = Math.max(groupMaxPartySize, option.maxPartySize);
|
|
1359
1201
|
}
|
|
1360
1202
|
});
|
|
1361
|
-
let status =
|
|
1203
|
+
let status = 'available';
|
|
1362
1204
|
if (candidateRange.endMs <= evaluationContext.evaluatedAtMs) {
|
|
1363
|
-
status =
|
|
1205
|
+
status = 'past';
|
|
1364
1206
|
} else if (availableResourceCount < minimum) {
|
|
1365
|
-
status = optionStatusAggregate.hasAvailable || optionStatusAggregate.hasLimited || optionStatusAggregate.hasFull ?
|
|
1207
|
+
status = optionStatusAggregate.hasAvailable || optionStatusAggregate.hasLimited || optionStatusAggregate.hasFull ? 'full' : 'unavailable';
|
|
1366
1208
|
} else if (availableResourceCount === totalResourceCount && !optionStatusAggregate.hasLimited && !optionStatusAggregate.hasFull && !optionStatusAggregate.allPast) {
|
|
1367
|
-
status =
|
|
1209
|
+
status = 'available';
|
|
1368
1210
|
} else {
|
|
1369
|
-
status =
|
|
1211
|
+
status = 'limited';
|
|
1370
1212
|
}
|
|
1371
1213
|
return {
|
|
1372
1214
|
required,
|
|
@@ -1380,20 +1222,19 @@ function evaluateRequirementGroupSummary(params) {
|
|
|
1380
1222
|
};
|
|
1381
1223
|
}
|
|
1382
1224
|
function evaluateCandidateSummary(projection, candidate, request) {
|
|
1383
|
-
|
|
1384
|
-
const product = ((_a = request.productIndex) == null ? void 0 : _a.get(String(candidate.productId))) || projection.products.find((item) => sameId(item.id, candidate.productId));
|
|
1225
|
+
const product = request.productIndex?.get(String(candidate.productId)) || projection.products.find(item => sameId(item.id, candidate.productId));
|
|
1385
1226
|
if (!product) {
|
|
1386
|
-
throw availabilityError(
|
|
1227
|
+
throw availabilityError('INVALID_TARGET_FILTER', 'candidate.productId 不存在于 projection.products', {
|
|
1387
1228
|
candidate
|
|
1388
1229
|
});
|
|
1389
1230
|
}
|
|
1390
1231
|
const candidateRangeCacheKey = getCandidateRangeCacheKey(candidate);
|
|
1391
|
-
const candidateRange =
|
|
1232
|
+
const candidateRange = request.candidateRangeCache?.get(candidateRangeCacheKey) || (() => {
|
|
1392
1233
|
const normalized = normalizeAbsoluteRange(getCandidateBookingRange(candidate), projection.meta.timezone);
|
|
1393
1234
|
return normalized ? toIndexedRange(normalized) : null;
|
|
1394
1235
|
})();
|
|
1395
1236
|
if (!candidateRange) {
|
|
1396
|
-
throw availabilityError(
|
|
1237
|
+
throw availabilityError('INVALID_TARGET_FILTER', 'candidate 时间范围非法', {
|
|
1397
1238
|
candidate
|
|
1398
1239
|
});
|
|
1399
1240
|
}
|
|
@@ -1406,7 +1247,7 @@ function evaluateCandidateSummary(projection, candidate, request) {
|
|
|
1406
1247
|
resourceIds: request.resourceIds
|
|
1407
1248
|
});
|
|
1408
1249
|
const requestedPartySize = normalizePartySize(request.partySize);
|
|
1409
|
-
const requiredGroups = (product.requirementGroups || []).filter(
|
|
1250
|
+
const requiredGroups = (product.requirementGroups || []).filter(group => group.required !== false).map(requirementGroup => evaluateRequirementGroupSummary({
|
|
1410
1251
|
projection,
|
|
1411
1252
|
product,
|
|
1412
1253
|
requirementGroup,
|
|
@@ -1422,34 +1263,33 @@ function evaluateCandidateSummary(projection, candidate, request) {
|
|
|
1422
1263
|
evaluationContext,
|
|
1423
1264
|
timezoneName: projection.meta.timezone
|
|
1424
1265
|
});
|
|
1425
|
-
const candidateStatus = requiredGroups.length ? summarizeStatuses(requiredGroups.map(
|
|
1266
|
+
const candidateStatus = requiredGroups.length ? summarizeStatuses(requiredGroups.map(group => group.status)) : candidateRange.endMs <= evaluationContext.evaluatedAtMs ? 'past' : 'available';
|
|
1426
1267
|
const maxPartySize = requiredGroups.length ? requiredGroups.reduce((min, group) => Math.min(min, group.maxPartySize), Number.MAX_SAFE_INTEGER) : 0;
|
|
1427
1268
|
const remainingCapacity = requiredGroups.length ? requiredGroups.reduce((min, group) => Math.min(min, group.remainingCapacity), Number.MAX_SAFE_INTEGER) : 0;
|
|
1428
1269
|
const maxCapacity = requiredGroups.length ? requiredGroups.reduce((min, group) => Math.min(min, group.maxCapacity), Number.MAX_SAFE_INTEGER) : 0;
|
|
1429
|
-
const status =
|
|
1270
|
+
const status = gate?.status || candidateStatus;
|
|
1430
1271
|
return {
|
|
1431
1272
|
status,
|
|
1432
1273
|
maxPartySize: gate ? 0 : Number.isFinite(maxPartySize) ? maxPartySize : 0,
|
|
1433
|
-
remainingCapacity: status ===
|
|
1274
|
+
remainingCapacity: status === 'past' || status === 'unavailable' ? 0 : Number.isFinite(remainingCapacity) ? remainingCapacity : 0,
|
|
1434
1275
|
maxCapacity: Number.isFinite(maxCapacity) ? maxCapacity : 0,
|
|
1435
|
-
reasonCodes:
|
|
1276
|
+
reasonCodes: gate?.reasonCodes || Array.from(new Set(requiredGroups.flatMap(group => group.reasonCodes)))
|
|
1436
1277
|
};
|
|
1437
1278
|
}
|
|
1438
1279
|
function evaluateCandidate(projection, candidate, request) {
|
|
1439
|
-
|
|
1440
|
-
const product = ((_a = request.productIndex) == null ? void 0 : _a.get(String(candidate.productId))) || projection.products.find((item) => sameId(item.id, candidate.productId));
|
|
1280
|
+
const product = request.productIndex?.get(String(candidate.productId)) || projection.products.find(item => sameId(item.id, candidate.productId));
|
|
1441
1281
|
if (!product) {
|
|
1442
|
-
throw availabilityError(
|
|
1282
|
+
throw availabilityError('INVALID_TARGET_FILTER', 'candidate.productId 不存在于 projection.products', {
|
|
1443
1283
|
candidate
|
|
1444
1284
|
});
|
|
1445
1285
|
}
|
|
1446
1286
|
const candidateRangeCacheKey = getCandidateRangeCacheKey(candidate);
|
|
1447
|
-
const candidateRange =
|
|
1287
|
+
const candidateRange = request.candidateRangeCache?.get(candidateRangeCacheKey) || (() => {
|
|
1448
1288
|
const normalized = normalizeAbsoluteRange(getCandidateBookingRange(candidate), projection.meta.timezone);
|
|
1449
1289
|
return normalized ? toIndexedRange(normalized) : null;
|
|
1450
1290
|
})();
|
|
1451
1291
|
if (!candidateRange) {
|
|
1452
|
-
throw availabilityError(
|
|
1292
|
+
throw availabilityError('INVALID_TARGET_FILTER', 'candidate 时间范围非法', {
|
|
1453
1293
|
candidate
|
|
1454
1294
|
});
|
|
1455
1295
|
}
|
|
@@ -1465,81 +1305,66 @@ function evaluateCandidate(projection, candidate, request) {
|
|
|
1465
1305
|
const requestedPartySize = normalizePartySize(request.partySize);
|
|
1466
1306
|
const partySize = requestedPartySize ?? 1;
|
|
1467
1307
|
const selectionsByGroupKey = evaluationContext.selectionsByGroupKey;
|
|
1468
|
-
const requirementGroups = (product.requirementGroups || []).map(
|
|
1469
|
-
var _a2;
|
|
1308
|
+
const requirementGroups = (product.requirementGroups || []).map(requirementGroup => {
|
|
1470
1309
|
const required = requirementGroup.required !== false;
|
|
1471
1310
|
const minimum = Math.max(required ? 1 : 0, Number(requirementGroup.min ?? (required ? 1 : 0)) || 0);
|
|
1472
1311
|
const maximum = Math.max(minimum, Number(requirementGroup.max ?? Math.max(minimum, 1)) || Math.max(minimum, 1));
|
|
1473
|
-
const selection = selectionsByGroupKey.get(requirementGroup.id) || selectionsByGroupKey.get(String(requirementGroup.formId ??
|
|
1474
|
-
const matchedResources = (
|
|
1475
|
-
const options = matchedResources.map(
|
|
1476
|
-
|
|
1477
|
-
const matchedCell = aggregateMatchedCells(
|
|
1478
|
-
product.id,
|
|
1479
|
-
requirementGroup.id,
|
|
1480
|
-
resource.id,
|
|
1481
|
-
candidateRange,
|
|
1482
|
-
((_a3 = request.cellIndex) == null ? void 0 : _a3.get([String(product.id), requirementGroup.id, String(resource.id)].join(":"))) || []
|
|
1483
|
-
);
|
|
1312
|
+
const selection = selectionsByGroupKey.get(requirementGroup.id) || selectionsByGroupKey.get(String(requirementGroup.formId ?? ''));
|
|
1313
|
+
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)));
|
|
1314
|
+
const options = matchedResources.map(resource => {
|
|
1315
|
+
const matchedCell = aggregateMatchedCells(product.id, requirementGroup.id, resource.id, candidateRange, request.cellIndex?.get([String(product.id), requirementGroup.id, String(resource.id)].join(':')) || []);
|
|
1484
1316
|
if (!matchedCell) {
|
|
1485
|
-
const
|
|
1317
|
+
const isPast = candidateRange.endMs <= evaluationContext.evaluatedAtMs;
|
|
1486
1318
|
return {
|
|
1487
1319
|
resourceId: resource.id,
|
|
1488
1320
|
resourceName: resource.name,
|
|
1489
1321
|
resourceFormId: resource.formId,
|
|
1490
|
-
policyId:
|
|
1322
|
+
policyId: resource.raw?.policy_id,
|
|
1491
1323
|
resourceType: resource.resourceType,
|
|
1492
|
-
status:
|
|
1324
|
+
status: isPast ? 'past' : 'unavailable',
|
|
1493
1325
|
maxPartySize: 0,
|
|
1494
|
-
reasonCodes:
|
|
1326
|
+
reasonCodes: isPast ? ['past'] : ['outside_resource_window']
|
|
1495
1327
|
};
|
|
1496
1328
|
}
|
|
1497
1329
|
const remainingCapacity = matchedCell.remainingCapacity;
|
|
1498
1330
|
const maxCapacity = matchedCell.maxCapacity;
|
|
1499
1331
|
const isPast = candidateRange.endMs <= evaluationContext.evaluatedAtMs;
|
|
1500
|
-
const
|
|
1332
|
+
const maxPartySize = isPast ? 0 : Math.max(0, remainingCapacity || 0);
|
|
1501
1333
|
const requiredCapacity = resolveRequiredCapacity(requirementGroup, product, requestedPartySize);
|
|
1502
|
-
const
|
|
1503
|
-
if (isPast)
|
|
1504
|
-
|
|
1505
|
-
if (
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
return "full";
|
|
1509
|
-
if ((remainingCapacity || 0) < (maxCapacity || resource.capacity))
|
|
1510
|
-
return "limited";
|
|
1511
|
-
return "available";
|
|
1334
|
+
const status = (() => {
|
|
1335
|
+
if (isPast) return 'past';
|
|
1336
|
+
if (matchedCell.status === 'unavailable') return 'unavailable';
|
|
1337
|
+
if ((remainingCapacity || 0) < requiredCapacity) return 'full';
|
|
1338
|
+
if ((remainingCapacity || 0) < (maxCapacity || resource.capacity)) return 'limited';
|
|
1339
|
+
return 'available';
|
|
1512
1340
|
})();
|
|
1513
1341
|
return {
|
|
1514
1342
|
resourceId: resource.id,
|
|
1515
1343
|
resourceName: resource.name,
|
|
1516
1344
|
resourceFormId: resource.formId,
|
|
1517
|
-
policyId:
|
|
1345
|
+
policyId: resource.raw?.policy_id,
|
|
1518
1346
|
resourceType: resource.resourceType,
|
|
1519
|
-
status
|
|
1347
|
+
status,
|
|
1520
1348
|
remainingCapacity,
|
|
1521
1349
|
maxCapacity,
|
|
1522
|
-
maxPartySize
|
|
1523
|
-
reasonCodes: isPast ? Array.from(
|
|
1350
|
+
maxPartySize,
|
|
1351
|
+
reasonCodes: isPast ? Array.from(new Set([...matchedCell.reasonCodes, 'past'])) : matchedCell.reasonCodes,
|
|
1524
1352
|
matchedCell
|
|
1525
1353
|
};
|
|
1526
1354
|
});
|
|
1527
|
-
const availableOptions = options.filter(
|
|
1355
|
+
const availableOptions = options.filter(option => option.status === 'available' || option.status === 'limited');
|
|
1528
1356
|
const totalResourceCount = options.length;
|
|
1529
1357
|
const availableResourceCount = availableOptions.length;
|
|
1530
|
-
const groupMaxPartySize = availableOptions.reduce(
|
|
1531
|
-
|
|
1532
|
-
0
|
|
1533
|
-
);
|
|
1534
|
-
let status = "available";
|
|
1358
|
+
const groupMaxPartySize = availableOptions.reduce((max, option) => Math.max(max, option.maxPartySize), 0);
|
|
1359
|
+
let status = 'available';
|
|
1535
1360
|
if (candidateRange.endMs <= evaluationContext.evaluatedAtMs) {
|
|
1536
|
-
status =
|
|
1361
|
+
status = 'past';
|
|
1537
1362
|
} else if (availableResourceCount < minimum) {
|
|
1538
|
-
status = options.some(
|
|
1539
|
-
} else if (options.every(
|
|
1540
|
-
status =
|
|
1363
|
+
status = options.some(option => option.status === 'full' || option.status === 'limited' || option.status === 'available') ? 'full' : 'unavailable';
|
|
1364
|
+
} else if (options.every(option => option.status === 'available')) {
|
|
1365
|
+
status = 'available';
|
|
1541
1366
|
} else {
|
|
1542
|
-
status =
|
|
1367
|
+
status = 'limited';
|
|
1543
1368
|
}
|
|
1544
1369
|
return {
|
|
1545
1370
|
groupId: requirementGroup.id,
|
|
@@ -1554,70 +1379,66 @@ function evaluateCandidate(projection, candidate, request) {
|
|
|
1554
1379
|
availableResourceCount,
|
|
1555
1380
|
totalResourceCount,
|
|
1556
1381
|
maxPartySize: groupMaxPartySize,
|
|
1557
|
-
remainingCapacity: Math.max(...availableOptions.map(
|
|
1558
|
-
maxCapacity: Math.max(...availableOptions.map(
|
|
1559
|
-
reasonCodes: Array.from(new Set(options.flatMap(
|
|
1382
|
+
remainingCapacity: Math.max(...availableOptions.map(option => option.remainingCapacity || 0), 0),
|
|
1383
|
+
maxCapacity: Math.max(...availableOptions.map(option => option.maxCapacity || 0), 0),
|
|
1384
|
+
reasonCodes: Array.from(new Set(options.flatMap(option => option.reasonCodes))),
|
|
1560
1385
|
options
|
|
1561
1386
|
};
|
|
1562
1387
|
});
|
|
1563
|
-
const requiredGroups = requirementGroups.filter(
|
|
1564
|
-
const requiredStatuses = requiredGroups.map(
|
|
1388
|
+
const requiredGroups = requirementGroups.filter(group => group.required);
|
|
1389
|
+
const requiredStatuses = requiredGroups.map(group => group.status);
|
|
1565
1390
|
const gate = evaluateCandidateTimeGate({
|
|
1566
1391
|
product,
|
|
1567
1392
|
candidateRange,
|
|
1568
1393
|
evaluationContext,
|
|
1569
1394
|
timezoneName: projection.meta.timezone
|
|
1570
1395
|
});
|
|
1571
|
-
const candidateStatus = requiredGroups.length ? summarizeStatuses(requiredStatuses) : candidateRange.endMs <= evaluationContext.evaluatedAtMs ?
|
|
1396
|
+
const candidateStatus = requiredGroups.length ? summarizeStatuses(requiredStatuses) : candidateRange.endMs <= evaluationContext.evaluatedAtMs ? 'past' : 'available';
|
|
1572
1397
|
const maxPartySize = requiredGroups.length ? requiredGroups.reduce((min, group) => Math.min(min, group.maxPartySize), Number.MAX_SAFE_INTEGER) : 0;
|
|
1573
|
-
const selectedAssignments = requirementGroups.flatMap(
|
|
1574
|
-
const selection = selectionsByGroupKey.get(group.groupId) || selectionsByGroupKey.get(String(group.formId ??
|
|
1575
|
-
if (!selection && !(request.allowImplicitSelection && group.required))
|
|
1576
|
-
|
|
1577
|
-
const availableOptions = group.options.filter((option) => option.status === "available" || option.status === "limited");
|
|
1398
|
+
const selectedAssignments = requirementGroups.flatMap(group => {
|
|
1399
|
+
const selection = selectionsByGroupKey.get(group.groupId) || selectionsByGroupKey.get(String(group.formId ?? ''));
|
|
1400
|
+
if (!selection && !(request.allowImplicitSelection && group.required)) return [];
|
|
1401
|
+
const availableOptions = group.options.filter(option => option.status === 'available' || option.status === 'limited');
|
|
1578
1402
|
const selectedOptions = selection ? availableOptions.slice(0, group.max) : availableOptions.slice(0, Math.max(group.min, 1));
|
|
1579
|
-
const originalGroup = (product.requirementGroups || []).find(
|
|
1403
|
+
const originalGroup = (product.requirementGroups || []).find(item => item.id === group.groupId) || {
|
|
1580
1404
|
id: group.groupId,
|
|
1581
1405
|
resourceType: group.resourceType,
|
|
1582
1406
|
min: group.min,
|
|
1583
1407
|
max: group.max
|
|
1584
1408
|
};
|
|
1585
|
-
return selectedOptions.map(
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
timeSlotId: (_b2 = candidate.primaryScheduleRef) == null ? void 0 : _b2.timeSlotId
|
|
1596
|
-
};
|
|
1597
|
-
});
|
|
1409
|
+
return selectedOptions.map(selectedOption => ({
|
|
1410
|
+
productId: product.id,
|
|
1411
|
+
resourceId: selectedOption.resourceId,
|
|
1412
|
+
formId: group.formId,
|
|
1413
|
+
startAt: candidate.bookingStartAt || candidate.startAt,
|
|
1414
|
+
endAt: candidate.bookingEndAt || candidate.endAt,
|
|
1415
|
+
capacityRequired: resolveRequiredCapacity(originalGroup, product, requestedPartySize),
|
|
1416
|
+
scheduleId: candidate.primaryScheduleRef?.scheduleId,
|
|
1417
|
+
timeSlotId: candidate.primaryScheduleRef?.timeSlotId
|
|
1418
|
+
}));
|
|
1598
1419
|
});
|
|
1599
1420
|
return {
|
|
1600
1421
|
item: {
|
|
1601
1422
|
...candidate,
|
|
1602
|
-
status:
|
|
1423
|
+
status: gate?.status || candidateStatus,
|
|
1603
1424
|
maxPartySize: gate ? 0 : Number.isFinite(maxPartySize) ? maxPartySize : 0,
|
|
1604
|
-
reasonCodes:
|
|
1425
|
+
reasonCodes: gate?.reasonCodes || Array.from(new Set(requiredGroups.flatMap(group => group.reasonCodes))),
|
|
1605
1426
|
requirementGroups
|
|
1606
1427
|
},
|
|
1607
1428
|
assignments: gate ? [] : selectedAssignments,
|
|
1608
|
-
gate: gate ||
|
|
1429
|
+
gate: gate || undefined
|
|
1609
1430
|
};
|
|
1610
1431
|
}
|
|
1611
1432
|
function assertTimeCandidates(request) {
|
|
1612
|
-
if (request.target ===
|
|
1613
|
-
throw availabilityError(
|
|
1433
|
+
if (request.target === 'time' && (!Array.isArray(request.candidates) || request.candidates.length === 0)) {
|
|
1434
|
+
throw availabilityError('MISSING_TIME_CANDIDATES', 'target=time 必须传非空 candidates', {
|
|
1614
1435
|
request
|
|
1615
1436
|
});
|
|
1616
1437
|
}
|
|
1617
1438
|
}
|
|
1618
1439
|
function assertSelectedRanges(request) {
|
|
1619
|
-
if (request.target ===
|
|
1620
|
-
throw availabilityError(
|
|
1440
|
+
if (request.target === 'resource' && (!Array.isArray(request.selectedRanges) || request.selectedRanges.length === 0)) {
|
|
1441
|
+
throw availabilityError('MISSING_SELECTED_RANGE', 'target=resource 必须传非空 selectedRanges', {
|
|
1621
1442
|
request
|
|
1622
1443
|
});
|
|
1623
1444
|
}
|
|
@@ -1627,14 +1448,16 @@ function assertRangesWithinProjection(projection, ranges) {
|
|
|
1627
1448
|
start: toDateTime(projection.meta.coverage.startAt, projection.meta.timezone),
|
|
1628
1449
|
end: toDateTime(projection.meta.coverage.endAt, projection.meta.timezone)
|
|
1629
1450
|
};
|
|
1630
|
-
ranges.forEach(
|
|
1451
|
+
ranges.forEach(range => {
|
|
1631
1452
|
const displayRange = normalizeAbsoluteRange(range, projection.meta.timezone);
|
|
1632
1453
|
const bookingRange = normalizeAbsoluteRange(getCandidateBookingRange(range), projection.meta.timezone);
|
|
1633
1454
|
if (!displayRange || !bookingRange) {
|
|
1634
|
-
throw availabilityError(
|
|
1455
|
+
throw availabilityError('INVALID_TARGET_FILTER', '候选时间范围非法', {
|
|
1456
|
+
range
|
|
1457
|
+
});
|
|
1635
1458
|
}
|
|
1636
1459
|
if (!contains(coverage, displayRange) || !contains(coverage, bookingRange)) {
|
|
1637
|
-
throw availabilityError(
|
|
1460
|
+
throw availabilityError('OUT_OF_PROJECTION_COVERAGE', '候选时间范围超出 projection.coverage', {
|
|
1638
1461
|
range,
|
|
1639
1462
|
coverage: projection.meta.coverage
|
|
1640
1463
|
});
|
|
@@ -1642,22 +1465,17 @@ function assertRangesWithinProjection(projection, ranges) {
|
|
|
1642
1465
|
});
|
|
1643
1466
|
}
|
|
1644
1467
|
function queryAvailabilityProjection(projection, request) {
|
|
1645
|
-
var _a;
|
|
1646
1468
|
const normalizedPartySize = normalizePartySize(request.partySize);
|
|
1647
|
-
const evaluatedAt = formatDateTime(
|
|
1648
|
-
|
|
1649
|
-
projection.meta.timezone,
|
|
1650
|
-
(0, import_localClock.extractFixedOffsetMinutes)(projection.meta.now) ?? (0, import_localClock.captureRuntimeOffsetMinutes)()
|
|
1651
|
-
);
|
|
1652
|
-
if (request.target === "time") {
|
|
1469
|
+
const evaluatedAt = formatDateTime((0, _localClock.localDateToScalar)(new Date()), projection.meta.timezone, (0, _localClock.extractFixedOffsetMinutes)(projection.meta.now) ?? (0, _localClock.captureRuntimeOffsetMinutes)());
|
|
1470
|
+
if (request.target === 'time') {
|
|
1653
1471
|
assertTimeCandidates(request);
|
|
1654
1472
|
assertRangesWithinProjection(projection, request.candidates);
|
|
1655
1473
|
}
|
|
1656
|
-
if (request.target ===
|
|
1474
|
+
if (request.target === 'resource') {
|
|
1657
1475
|
assertSelectedRanges(request);
|
|
1658
1476
|
assertRangesWithinProjection(projection, request.selectedRanges);
|
|
1659
1477
|
}
|
|
1660
|
-
const productIdSet =
|
|
1478
|
+
const productIdSet = request.productIds?.length ? new Set(request.productIds.map(String)) : null;
|
|
1661
1479
|
const productIndex = buildProductIndex(projection);
|
|
1662
1480
|
const cellIndex = buildAvailabilityCellIndex(projection);
|
|
1663
1481
|
const groupResourceIndex = buildGroupResourceIndex({
|
|
@@ -1665,23 +1483,23 @@ function queryAvailabilityProjection(projection, request) {
|
|
|
1665
1483
|
resourceIds: request.resourceIds,
|
|
1666
1484
|
partySize: normalizedPartySize
|
|
1667
1485
|
});
|
|
1668
|
-
const candidateRangeCache =
|
|
1486
|
+
const candidateRangeCache = new Map();
|
|
1669
1487
|
const evaluationContext = buildEvaluationContext({
|
|
1670
1488
|
projection,
|
|
1671
1489
|
evaluatedAt,
|
|
1672
1490
|
resourceIds: request.resourceIds
|
|
1673
1491
|
});
|
|
1674
|
-
if (request.target ===
|
|
1492
|
+
if (request.target === 'time') {
|
|
1675
1493
|
const candidates = dedupeProductRanges(request.candidates);
|
|
1676
|
-
const candidatesByProduct =
|
|
1677
|
-
candidates.forEach(
|
|
1494
|
+
const candidatesByProduct = new Map();
|
|
1495
|
+
candidates.forEach(candidate => {
|
|
1678
1496
|
const key = String(candidate.productId);
|
|
1679
|
-
candidatesByProduct.set(key, [...candidatesByProduct.get(key) || [], candidate]);
|
|
1497
|
+
candidatesByProduct.set(key, [...(candidatesByProduct.get(key) || []), candidate]);
|
|
1680
1498
|
});
|
|
1681
|
-
const
|
|
1499
|
+
const data = projection.products.filter(product => !productIdSet || productIdSet.has(String(product.id))).map(product => ({
|
|
1682
1500
|
productId: product.id,
|
|
1683
1501
|
title: product.title,
|
|
1684
|
-
times: (candidatesByProduct.get(String(product.id)) || []).map(
|
|
1502
|
+
times: (candidatesByProduct.get(String(product.id)) || []).map(candidate => evaluateCandidate(projection, candidate, {
|
|
1685
1503
|
partySize: normalizedPartySize,
|
|
1686
1504
|
resourceIds: request.resourceIds,
|
|
1687
1505
|
evaluatedAt,
|
|
@@ -1691,25 +1509,25 @@ function queryAvailabilityProjection(projection, request) {
|
|
|
1691
1509
|
candidateRangeCache,
|
|
1692
1510
|
evaluationContext
|
|
1693
1511
|
}).item)
|
|
1694
|
-
})).filter(
|
|
1512
|
+
})).filter(group => group.times.length > 0);
|
|
1695
1513
|
return {
|
|
1696
|
-
target:
|
|
1514
|
+
target: 'time',
|
|
1697
1515
|
evaluatedAt,
|
|
1698
1516
|
snapshotId: projection.meta.snapshotId,
|
|
1699
|
-
data
|
|
1517
|
+
data
|
|
1700
1518
|
};
|
|
1701
1519
|
}
|
|
1702
|
-
if (request.target ===
|
|
1520
|
+
if (request.target === 'resource') {
|
|
1703
1521
|
const selectedRanges = dedupeProductRanges(request.selectedRanges);
|
|
1704
|
-
const rangesByProduct =
|
|
1705
|
-
selectedRanges.forEach(
|
|
1522
|
+
const rangesByProduct = new Map();
|
|
1523
|
+
selectedRanges.forEach(candidate => {
|
|
1706
1524
|
const key = String(candidate.productId);
|
|
1707
|
-
rangesByProduct.set(key, [...rangesByProduct.get(key) || [], candidate]);
|
|
1525
|
+
rangesByProduct.set(key, [...(rangesByProduct.get(key) || []), candidate]);
|
|
1708
1526
|
});
|
|
1709
|
-
const
|
|
1527
|
+
const data = projection.products.filter(product => !productIdSet || productIdSet.has(String(product.id))).map(product => ({
|
|
1710
1528
|
productId: product.id,
|
|
1711
1529
|
title: product.title,
|
|
1712
|
-
ranges: (rangesByProduct.get(String(product.id)) || []).map(
|
|
1530
|
+
ranges: (rangesByProduct.get(String(product.id)) || []).map(candidate => {
|
|
1713
1531
|
const evaluated = evaluateCandidate(projection, candidate, {
|
|
1714
1532
|
partySize: normalizedPartySize,
|
|
1715
1533
|
resourceIds: request.resourceIds,
|
|
@@ -1728,12 +1546,12 @@ function queryAvailabilityProjection(projection, request) {
|
|
|
1728
1546
|
requirementGroups: evaluated.requirementGroups
|
|
1729
1547
|
};
|
|
1730
1548
|
})
|
|
1731
|
-
})).filter(
|
|
1549
|
+
})).filter(group => group.ranges.length > 0);
|
|
1732
1550
|
return {
|
|
1733
|
-
target:
|
|
1551
|
+
target: 'resource',
|
|
1734
1552
|
evaluatedAt,
|
|
1735
1553
|
snapshotId: projection.meta.snapshotId,
|
|
1736
|
-
data
|
|
1554
|
+
data
|
|
1737
1555
|
};
|
|
1738
1556
|
}
|
|
1739
1557
|
const dateRange = resolveProjectionDateRange(projection, request.dateRange);
|
|
@@ -1743,17 +1561,17 @@ function queryAvailabilityProjection(projection, request) {
|
|
|
1743
1561
|
resourceIds: request.resourceIds,
|
|
1744
1562
|
partySize: normalizedPartySize
|
|
1745
1563
|
});
|
|
1746
|
-
const candidateGroupMap = new Map(candidateGroups.map(
|
|
1747
|
-
if (request.target ===
|
|
1748
|
-
const
|
|
1564
|
+
const candidateGroupMap = new Map(candidateGroups.map(group => [String(group.productId), group]));
|
|
1565
|
+
if (request.target === 'date') {
|
|
1566
|
+
const data = projection.products.filter(product => !productIdSet || productIdSet.has(String(product.id))).map(product => {
|
|
1749
1567
|
const group = candidateGroupMap.get(String(product.id)) || {
|
|
1750
1568
|
productId: product.id,
|
|
1751
1569
|
title: product.title,
|
|
1752
1570
|
kind: product.kind,
|
|
1753
1571
|
ranges: []
|
|
1754
1572
|
};
|
|
1755
|
-
const byDate =
|
|
1756
|
-
group.ranges.forEach(
|
|
1573
|
+
const byDate = new Map();
|
|
1574
|
+
group.ranges.forEach(candidate => {
|
|
1757
1575
|
const evaluated = evaluateCandidateSummary(projection, candidate, {
|
|
1758
1576
|
partySize: normalizedPartySize,
|
|
1759
1577
|
resourceIds: request.resourceIds,
|
|
@@ -1770,30 +1588,30 @@ function queryAvailabilityProjection(projection, request) {
|
|
|
1770
1588
|
maxPartySize: 0,
|
|
1771
1589
|
remainingCapacity: 0,
|
|
1772
1590
|
maxCapacity: 0,
|
|
1773
|
-
reasonCodeSet:
|
|
1591
|
+
reasonCodeSet: new Set(),
|
|
1774
1592
|
statusAggregate: createStatusAggregate()
|
|
1775
1593
|
};
|
|
1776
1594
|
aggregate.totalCount += 1;
|
|
1777
|
-
if (evaluated.status ===
|
|
1595
|
+
if (evaluated.status === 'available' || evaluated.status === 'limited') {
|
|
1778
1596
|
aggregate.availableCount += 1;
|
|
1779
1597
|
}
|
|
1780
1598
|
aggregate.maxPartySize = Math.max(aggregate.maxPartySize, evaluated.maxPartySize);
|
|
1781
1599
|
aggregate.remainingCapacity += evaluated.remainingCapacity;
|
|
1782
1600
|
aggregate.maxCapacity += evaluated.maxCapacity;
|
|
1783
1601
|
pushStatusAggregate(aggregate.statusAggregate, evaluated.status);
|
|
1784
|
-
evaluated.reasonCodes.forEach(
|
|
1602
|
+
evaluated.reasonCodes.forEach(reasonCode => aggregate.reasonCodeSet.add(reasonCode));
|
|
1785
1603
|
byDate.set(candidate.date, aggregate);
|
|
1786
1604
|
});
|
|
1787
|
-
const dates = enumerateDates(dateRange.startDate, dateRange.endDate).map(
|
|
1605
|
+
const dates = enumerateDates(dateRange.startDate, dateRange.endDate).map(date => {
|
|
1788
1606
|
const aggregate = byDate.get(date);
|
|
1789
1607
|
return {
|
|
1790
1608
|
date,
|
|
1791
|
-
status: aggregate ? summarizeStatusAggregate(aggregate.statusAggregate) :
|
|
1792
|
-
availableCount:
|
|
1793
|
-
totalCount:
|
|
1794
|
-
maxPartySize:
|
|
1795
|
-
remainingCapacity:
|
|
1796
|
-
maxCapacity:
|
|
1609
|
+
status: aggregate ? summarizeStatusAggregate(aggregate.statusAggregate) : 'unavailable',
|
|
1610
|
+
availableCount: aggregate?.availableCount || 0,
|
|
1611
|
+
totalCount: aggregate?.totalCount || 0,
|
|
1612
|
+
maxPartySize: aggregate?.maxPartySize || 0,
|
|
1613
|
+
remainingCapacity: aggregate?.remainingCapacity || 0,
|
|
1614
|
+
maxCapacity: aggregate?.maxCapacity || 0,
|
|
1797
1615
|
reasonCodes: aggregate ? Array.from(aggregate.reasonCodeSet) : []
|
|
1798
1616
|
};
|
|
1799
1617
|
});
|
|
@@ -1804,13 +1622,13 @@ function queryAvailabilityProjection(projection, request) {
|
|
|
1804
1622
|
};
|
|
1805
1623
|
});
|
|
1806
1624
|
return {
|
|
1807
|
-
target:
|
|
1625
|
+
target: 'date',
|
|
1808
1626
|
evaluatedAt,
|
|
1809
1627
|
snapshotId: projection.meta.snapshotId,
|
|
1810
|
-
data
|
|
1628
|
+
data
|
|
1811
1629
|
};
|
|
1812
1630
|
}
|
|
1813
|
-
const data = projection.products.filter(
|
|
1631
|
+
const data = projection.products.filter(product => !productIdSet || productIdSet.has(String(product.id))).map(product => {
|
|
1814
1632
|
const group = candidateGroupMap.get(String(product.id)) || {
|
|
1815
1633
|
productId: product.id,
|
|
1816
1634
|
title: product.title,
|
|
@@ -1818,12 +1636,12 @@ function queryAvailabilityProjection(projection, request) {
|
|
|
1818
1636
|
ranges: []
|
|
1819
1637
|
};
|
|
1820
1638
|
const productStatusAggregate = createStatusAggregate();
|
|
1821
|
-
const productReasonCodeSet =
|
|
1822
|
-
const dateStatusMap =
|
|
1639
|
+
const productReasonCodeSet = new Set();
|
|
1640
|
+
const dateStatusMap = new Map();
|
|
1823
1641
|
let availableTimeCount = 0;
|
|
1824
1642
|
let totalTimeCount = 0;
|
|
1825
1643
|
let maxPartySize = 0;
|
|
1826
|
-
group.ranges.forEach(
|
|
1644
|
+
group.ranges.forEach(candidate => {
|
|
1827
1645
|
const evaluated = evaluateCandidateSummary(projection, candidate, {
|
|
1828
1646
|
partySize: normalizedPartySize,
|
|
1829
1647
|
resourceIds: request.resourceIds,
|
|
@@ -1835,19 +1653,19 @@ function queryAvailabilityProjection(projection, request) {
|
|
|
1835
1653
|
evaluationContext
|
|
1836
1654
|
});
|
|
1837
1655
|
totalTimeCount += 1;
|
|
1838
|
-
if (evaluated.status ===
|
|
1656
|
+
if (evaluated.status === 'available' || evaluated.status === 'limited') {
|
|
1839
1657
|
availableTimeCount += 1;
|
|
1840
1658
|
}
|
|
1841
1659
|
maxPartySize = Math.max(maxPartySize, evaluated.maxPartySize);
|
|
1842
1660
|
pushStatusAggregate(productStatusAggregate, evaluated.status);
|
|
1843
|
-
evaluated.reasonCodes.forEach(
|
|
1661
|
+
evaluated.reasonCodes.forEach(reasonCode => productReasonCodeSet.add(reasonCode));
|
|
1844
1662
|
const dateAggregate = dateStatusMap.get(candidate.date) || createStatusAggregate();
|
|
1845
1663
|
pushStatusAggregate(dateAggregate, evaluated.status);
|
|
1846
1664
|
dateStatusMap.set(candidate.date, dateAggregate);
|
|
1847
1665
|
});
|
|
1848
|
-
const availableDateCount = Array.from(dateStatusMap.values()).filter(
|
|
1849
|
-
const
|
|
1850
|
-
return
|
|
1666
|
+
const availableDateCount = Array.from(dateStatusMap.values()).filter(aggregate => {
|
|
1667
|
+
const status = summarizeStatusAggregate(aggregate);
|
|
1668
|
+
return status === 'available' || status === 'limited';
|
|
1851
1669
|
}).length;
|
|
1852
1670
|
const totalDateCount = dateStatusMap.size;
|
|
1853
1671
|
const status = summarizeStatusAggregate(productStatusAggregate);
|
|
@@ -1869,22 +1687,34 @@ function queryAvailabilityProjection(projection, request) {
|
|
|
1869
1687
|
};
|
|
1870
1688
|
});
|
|
1871
1689
|
return {
|
|
1872
|
-
target:
|
|
1690
|
+
target: 'product',
|
|
1873
1691
|
evaluatedAt,
|
|
1874
1692
|
snapshotId: projection.meta.snapshotId,
|
|
1875
1693
|
data
|
|
1876
1694
|
};
|
|
1877
1695
|
}
|
|
1696
|
+
function summarizeDateStatus(statuses) {
|
|
1697
|
+
if (!statuses.length) return 'unavailable';
|
|
1698
|
+
if (statuses.some(status => status === 'available')) return 'available';
|
|
1699
|
+
if (statuses.some(status => status === 'limited')) return 'limited';
|
|
1700
|
+
if (statuses.some(status => status === 'full')) return 'full';
|
|
1701
|
+
if (statuses.every(status => status === 'past')) return 'past';
|
|
1702
|
+
return 'unavailable';
|
|
1703
|
+
}
|
|
1704
|
+
function summarizeProductStatus(statuses) {
|
|
1705
|
+
if (!statuses.length) return 'unavailable';
|
|
1706
|
+
if (statuses.some(status => status === 'available')) return 'available';
|
|
1707
|
+
if (statuses.some(status => status === 'limited')) return 'limited';
|
|
1708
|
+
if (statuses.some(status => status === 'full')) return 'full';
|
|
1709
|
+
if (statuses.every(status => status === 'past')) return 'past';
|
|
1710
|
+
return 'unavailable';
|
|
1711
|
+
}
|
|
1878
1712
|
function validateAvailabilitySelection(projection, params) {
|
|
1879
1713
|
const normalizedPartySize = normalizePartySize(params.partySize);
|
|
1880
1714
|
const normalizedBookingCount = normalizeBookingCount(params.bookingCount);
|
|
1881
1715
|
assertRangesWithinProjection(projection, [params.candidate]);
|
|
1882
1716
|
const productIndex = buildProductIndex(projection);
|
|
1883
|
-
const evaluatedAt = formatDateTime(
|
|
1884
|
-
(0, import_localClock.localDateToScalar)(/* @__PURE__ */ new Date()),
|
|
1885
|
-
projection.meta.timezone,
|
|
1886
|
-
(0, import_localClock.extractFixedOffsetMinutes)(projection.meta.now) ?? (0, import_localClock.captureRuntimeOffsetMinutes)()
|
|
1887
|
-
);
|
|
1717
|
+
const evaluatedAt = formatDateTime((0, _localClock.localDateToScalar)(new Date()), projection.meta.timezone, (0, _localClock.extractFixedOffsetMinutes)(projection.meta.now) ?? (0, _localClock.captureRuntimeOffsetMinutes)());
|
|
1888
1718
|
const evaluated = evaluateCandidate(projection, params.candidate, {
|
|
1889
1719
|
partySize: normalizedPartySize,
|
|
1890
1720
|
evaluatedAt,
|
|
@@ -1896,7 +1726,7 @@ function validateAvailabilitySelection(projection, params) {
|
|
|
1896
1726
|
projection,
|
|
1897
1727
|
partySize: normalizedPartySize
|
|
1898
1728
|
}),
|
|
1899
|
-
candidateRangeCache:
|
|
1729
|
+
candidateRangeCache: new Map(),
|
|
1900
1730
|
evaluationContext: buildEvaluationContext({
|
|
1901
1731
|
projection,
|
|
1902
1732
|
evaluatedAt,
|
|
@@ -1907,27 +1737,27 @@ function validateAvailabilitySelection(projection, params) {
|
|
|
1907
1737
|
if (evaluated.gate) {
|
|
1908
1738
|
conflicts.push({
|
|
1909
1739
|
code: evaluated.gate.conflictCode,
|
|
1910
|
-
message: evaluated.gate.status ===
|
|
1740
|
+
message: evaluated.gate.status === 'past' ? '候选时间已过期' : '候选时间不满足预订时间门禁',
|
|
1911
1741
|
reasonCodes: evaluated.gate.reasonCodes,
|
|
1912
1742
|
productId: params.candidate.productId,
|
|
1913
1743
|
candidateKey: params.candidate.key
|
|
1914
1744
|
});
|
|
1915
1745
|
return {
|
|
1916
1746
|
ok: false,
|
|
1917
|
-
status: evaluated.gate.status ===
|
|
1747
|
+
status: evaluated.gate.status === 'past' ? 'past' : 'conflict',
|
|
1918
1748
|
assignments: [],
|
|
1919
1749
|
conflicts,
|
|
1920
1750
|
availability: evaluated.item
|
|
1921
1751
|
};
|
|
1922
1752
|
}
|
|
1923
|
-
const requiredGroups = evaluated.item.requirementGroups.filter(
|
|
1924
|
-
const findSelectionForGroup =
|
|
1925
|
-
requiredGroups.forEach(
|
|
1753
|
+
const requiredGroups = evaluated.item.requirementGroups.filter(group => group.required);
|
|
1754
|
+
const findSelectionForGroup = group => params.requirementSelections.find(selection => selection.requirementGroupId && selection.requirementGroupId === group.groupId || selection.formId !== undefined && sameId(selection.formId, group.formId));
|
|
1755
|
+
requiredGroups.forEach(group => {
|
|
1926
1756
|
const selection = findSelectionForGroup(group);
|
|
1927
|
-
const selectedCount = new Set((
|
|
1757
|
+
const selectedCount = new Set((selection?.resourceIds || []).map(String)).size;
|
|
1928
1758
|
if (selectedCount < group.min) {
|
|
1929
1759
|
conflicts.push({
|
|
1930
|
-
code:
|
|
1760
|
+
code: 'missing_resource',
|
|
1931
1761
|
message: `资源组 ${group.groupId} 缺少已选资源`,
|
|
1932
1762
|
productId: params.candidate.productId,
|
|
1933
1763
|
requirementGroupId: group.groupId,
|
|
@@ -1938,7 +1768,7 @@ function validateAvailabilitySelection(projection, params) {
|
|
|
1938
1768
|
}
|
|
1939
1769
|
if (selectedCount > group.max) {
|
|
1940
1770
|
conflicts.push({
|
|
1941
|
-
code:
|
|
1771
|
+
code: 'invalid_selection_count',
|
|
1942
1772
|
message: `资源组 ${group.groupId} 已选资源数量超出上限`,
|
|
1943
1773
|
productId: params.candidate.productId,
|
|
1944
1774
|
requirementGroupId: group.groupId,
|
|
@@ -1947,10 +1777,9 @@ function validateAvailabilitySelection(projection, params) {
|
|
|
1947
1777
|
});
|
|
1948
1778
|
return;
|
|
1949
1779
|
}
|
|
1950
|
-
if (group.status ===
|
|
1951
|
-
return;
|
|
1780
|
+
if (group.status === 'available' || group.status === 'limited') return;
|
|
1952
1781
|
conflicts.push({
|
|
1953
|
-
code: group.status ===
|
|
1782
|
+
code: group.status === 'past' ? 'past' : group.status === 'full' ? 'resource_full' : 'missing_resource',
|
|
1954
1783
|
message: `资源组 ${group.groupId} 当前不可提交`,
|
|
1955
1784
|
productId: params.candidate.productId,
|
|
1956
1785
|
requirementGroupId: group.groupId,
|
|
@@ -1958,12 +1787,12 @@ function validateAvailabilitySelection(projection, params) {
|
|
|
1958
1787
|
candidateKey: params.candidate.key
|
|
1959
1788
|
});
|
|
1960
1789
|
});
|
|
1961
|
-
params.requirementSelections.forEach(
|
|
1962
|
-
const matchedGroup = evaluated.item.requirementGroups.find(
|
|
1790
|
+
params.requirementSelections.forEach(selection => {
|
|
1791
|
+
const matchedGroup = evaluated.item.requirementGroups.find(group => selection.requirementGroupId ? group.groupId === selection.requirementGroupId : sameId(group.formId, selection.formId));
|
|
1963
1792
|
if (!matchedGroup) {
|
|
1964
1793
|
conflicts.push({
|
|
1965
|
-
code:
|
|
1966
|
-
message:
|
|
1794
|
+
code: 'invalid_requirement_group',
|
|
1795
|
+
message: '选择的资源组不属于当前商品',
|
|
1967
1796
|
productId: params.candidate.productId,
|
|
1968
1797
|
requirementGroupId: selection.requirementGroupId,
|
|
1969
1798
|
formId: selection.formId,
|
|
@@ -1974,7 +1803,7 @@ function validateAvailabilitySelection(projection, params) {
|
|
|
1974
1803
|
const selectedCount = new Set(selection.resourceIds.map(String)).size;
|
|
1975
1804
|
if (!matchedGroup.required && (selectedCount < matchedGroup.min || selectedCount > matchedGroup.max)) {
|
|
1976
1805
|
conflicts.push({
|
|
1977
|
-
code:
|
|
1806
|
+
code: 'invalid_selection_count',
|
|
1978
1807
|
message: `资源组 ${matchedGroup.groupId} 已选资源数量不满足 ${matchedGroup.min}-${matchedGroup.max}`,
|
|
1979
1808
|
productId: params.candidate.productId,
|
|
1980
1809
|
requirementGroupId: matchedGroup.groupId,
|
|
@@ -1982,23 +1811,20 @@ function validateAvailabilitySelection(projection, params) {
|
|
|
1982
1811
|
candidateKey: params.candidate.key
|
|
1983
1812
|
});
|
|
1984
1813
|
}
|
|
1985
|
-
selection.resourceIds.forEach(
|
|
1986
|
-
const option = matchedGroup
|
|
1987
|
-
const matchedCell = option
|
|
1988
|
-
const remainingCapacity = matchedCell
|
|
1989
|
-
if (option && (option.status ===
|
|
1814
|
+
selection.resourceIds.forEach(resourceId => {
|
|
1815
|
+
const option = matchedGroup?.options.find(item => sameId(item.resourceId, resourceId));
|
|
1816
|
+
const matchedCell = option?.matchedCell;
|
|
1817
|
+
const remainingCapacity = matchedCell?.remainingCapacity;
|
|
1818
|
+
if (option && (option.status === 'available' || option.status === 'limited') && remainingCapacity !== undefined) {
|
|
1990
1819
|
const capacityPerBooking = normalizedPartySize ?? 1;
|
|
1991
|
-
const availableQuantity = Math.max(
|
|
1992
|
-
0,
|
|
1993
|
-
Math.floor(remainingCapacity / capacityPerBooking)
|
|
1994
|
-
);
|
|
1820
|
+
const availableQuantity = Math.max(0, Math.floor(remainingCapacity / capacityPerBooking));
|
|
1995
1821
|
if (normalizedBookingCount > availableQuantity) {
|
|
1996
1822
|
conflicts.push({
|
|
1997
|
-
code:
|
|
1823
|
+
code: 'resource_capacity_exceeded',
|
|
1998
1824
|
message: `所选资源「${option.resourceName || String(resourceId)}」当前最多可预订 ${availableQuantity} 个`,
|
|
1999
1825
|
productId: params.candidate.productId,
|
|
2000
|
-
requirementGroupId: matchedGroup
|
|
2001
|
-
formId: matchedGroup
|
|
1826
|
+
requirementGroupId: matchedGroup?.groupId,
|
|
1827
|
+
formId: matchedGroup?.formId,
|
|
2002
1828
|
resourceId,
|
|
2003
1829
|
resourceName: option.resourceName,
|
|
2004
1830
|
candidateKey: params.candidate.key,
|
|
@@ -2010,13 +1836,13 @@ function validateAvailabilitySelection(projection, params) {
|
|
|
2010
1836
|
return;
|
|
2011
1837
|
}
|
|
2012
1838
|
}
|
|
2013
|
-
if (!option || option.status !==
|
|
1839
|
+
if (!option || option.status !== 'available' && option.status !== 'limited') {
|
|
2014
1840
|
conflicts.push({
|
|
2015
|
-
code:
|
|
1841
|
+
code: 'resource_unavailable',
|
|
2016
1842
|
message: `资源 ${String(resourceId)} 当前不可提交`,
|
|
2017
1843
|
productId: params.candidate.productId,
|
|
2018
|
-
requirementGroupId: matchedGroup
|
|
2019
|
-
formId: matchedGroup
|
|
1844
|
+
requirementGroupId: matchedGroup?.groupId,
|
|
1845
|
+
formId: matchedGroup?.formId,
|
|
2020
1846
|
resourceId,
|
|
2021
1847
|
candidateKey: params.candidate.key
|
|
2022
1848
|
});
|
|
@@ -2024,17 +1850,10 @@ function validateAvailabilitySelection(projection, params) {
|
|
|
2024
1850
|
});
|
|
2025
1851
|
});
|
|
2026
1852
|
return {
|
|
2027
|
-
ok: conflicts.length === 0 && (evaluated.item.status ===
|
|
2028
|
-
status: conflicts.length === 0 ? evaluated.item.status :
|
|
1853
|
+
ok: conflicts.length === 0 && (evaluated.item.status === 'available' || evaluated.item.status === 'limited'),
|
|
1854
|
+
status: conflicts.length === 0 ? evaluated.item.status : 'conflict',
|
|
2029
1855
|
assignments: evaluated.assignments,
|
|
2030
1856
|
conflicts,
|
|
2031
1857
|
availability: evaluated.item
|
|
2032
1858
|
};
|
|
2033
|
-
}
|
|
2034
|
-
// Annotate the CommonJS export names for ESM import in node:
|
|
2035
|
-
0 && (module.exports = {
|
|
2036
|
-
createAvailabilityProjection,
|
|
2037
|
-
getProductTimeRanges,
|
|
2038
|
-
queryAvailabilityProjection,
|
|
2039
|
-
validateAvailabilitySelection
|
|
2040
|
-
});
|
|
1859
|
+
}
|