@pisell/pisellos 2.3.74 → 2.3.75

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.
Files changed (276) hide show
  1. package/dist/core/index.js +6 -10
  2. package/dist/model/strategy/adapter/promotion/index.js +9 -0
  3. package/dist/model/strategy/adapter/walletPass/example.js +3 -26
  4. package/dist/model/strategy/strategy-example.js +5 -19
  5. package/dist/modules/Cart/index.js +0 -3
  6. package/dist/modules/Cart/utils/cartProduct.js +0 -1
  7. package/dist/modules/Date/index.js +1 -5
  8. package/dist/modules/Discount/index.js +2 -4
  9. package/dist/modules/OpenData/index.js +0 -2
  10. package/dist/modules/Order/index.js +36 -64
  11. package/dist/modules/Payment/index.js +97 -162
  12. package/dist/modules/Rules/index.js +1 -4
  13. package/dist/modules/Schedule/index.js +0 -1
  14. package/dist/modules/Schedule/utils.js +0 -1
  15. package/dist/modules/Step/index.js +0 -1
  16. package/dist/plugins/request.js +1 -4
  17. package/dist/plugins/window.js +2 -6
  18. package/dist/server/index.js +97 -129
  19. package/dist/server/modules/menu/index.js +32 -41
  20. package/dist/server/modules/order/index.js +2 -4
  21. package/dist/server/modules/products/index.js +24 -42
  22. package/dist/server/modules/quotation/index.js +29 -38
  23. package/dist/server/modules/resource/index.js +3 -4
  24. package/dist/server/modules/schedule/index.js +27 -35
  25. package/dist/server/utils/product.js +0 -1
  26. package/dist/solution/BaseSales/index.js +38 -41
  27. package/dist/solution/BookingByStep/index.js +7 -59
  28. package/dist/solution/BookingByStep/utils/capacity.js +1 -11
  29. package/dist/solution/BookingByStep/utils/resources.js +1 -3
  30. package/dist/solution/BookingByStep/utils/timeslots.js +2 -43
  31. package/dist/solution/BookingTicket/index.js +0 -2
  32. package/dist/solution/BookingTicket/utils/scan/cloudSearch.js +6 -7
  33. package/dist/solution/BookingTicket/utils/scan/handleScan.js +25 -31
  34. package/dist/solution/BookingTicket/utils/scan/index.js +6 -10
  35. package/dist/solution/BuyTickets/index.js +9 -12
  36. package/dist/solution/Checkout/index.js +177 -252
  37. package/dist/solution/Checkout/utils/index.js +4 -16
  38. package/dist/solution/RegisterAndLogin/index.js +30 -76
  39. package/dist/solution/ScanOrder/index.js +50 -60
  40. package/dist/solution/ShopDiscount/index.js +2 -7
  41. package/dist/solution/VenueBooking/index.js +45 -55
  42. package/dist/utils/task.js +15 -18
  43. package/dist/utils/watch.js +0 -1
  44. package/lib/apis/picoding.js +2 -0
  45. package/lib/core/index.js +134 -134
  46. package/lib/effects/index.js +46 -57
  47. package/lib/index.js +82 -49
  48. package/lib/model/index.js +14 -21
  49. package/lib/model/strategy/adapter/dataVariant/adapter.js +38 -50
  50. package/lib/model/strategy/adapter/dataVariant/evaluator.js +162 -236
  51. package/lib/model/strategy/adapter/dataVariant/examples.js +229 -237
  52. package/lib/model/strategy/adapter/dataVariant/index.js +39 -34
  53. package/lib/model/strategy/adapter/dataVariant/type.js +57 -28
  54. package/lib/model/strategy/adapter/index.js +100 -64
  55. package/lib/model/strategy/adapter/itemRule/adapter.js +156 -144
  56. package/lib/model/strategy/adapter/itemRule/evaluator.js +36 -48
  57. package/lib/model/strategy/adapter/itemRule/examples.js +295 -225
  58. package/lib/model/strategy/adapter/itemRule/index.js +83 -57
  59. package/lib/model/strategy/adapter/itemRule/type.js +97 -36
  60. package/lib/model/strategy/adapter/promotion/adapter.js +91 -69
  61. package/lib/model/strategy/adapter/promotion/evaluator.js +444 -310
  62. package/lib/model/strategy/adapter/promotion/examples.js +222 -234
  63. package/lib/model/strategy/adapter/promotion/index.js +1 -0
  64. package/lib/model/strategy/adapter/promotion/type.js +243 -36
  65. package/lib/model/strategy/adapter/type.js +4 -16
  66. package/lib/model/strategy/adapter/walletPass/evaluator.js +180 -150
  67. package/lib/model/strategy/adapter/walletPass/example.js +217 -190
  68. package/lib/model/strategy/adapter/walletPass/index.js +75 -51
  69. package/lib/model/strategy/adapter/walletPass/locales.js +36 -58
  70. package/lib/model/strategy/adapter/walletPass/type.js +4 -16
  71. package/lib/model/strategy/adapter/walletPass/utils.js +588 -378
  72. package/lib/model/strategy/engine.js +270 -168
  73. package/lib/model/strategy/index.js +49 -34
  74. package/lib/model/strategy/strategy-example.js +243 -239
  75. package/lib/model/strategy/type.js +100 -40
  76. package/lib/modules/Account/index.js +39 -53
  77. package/lib/modules/Account/types.js +20 -33
  78. package/lib/modules/AccountList/index.js +116 -154
  79. package/lib/modules/AccountList/types.js +30 -31
  80. package/lib/modules/AccountList/utils.js +18 -30
  81. package/lib/modules/BaseModule.js +23 -42
  82. package/lib/modules/BookingContext/index.js +158 -136
  83. package/lib/modules/BookingContext/types.js +46 -32
  84. package/lib/modules/BookingContext/utils/buildCacheItemFromOrderLine.js +138 -146
  85. package/lib/modules/BookingContext/utils/buildNormalProductCacheItemFromOrderLine.js +78 -83
  86. package/lib/modules/BookingContext/utils/cacheItemToBookingInput.js +170 -150
  87. package/lib/modules/BookingContext/utils/flexible.js +38 -55
  88. package/lib/modules/BookingContext/utils/formatResourceList.js +19 -34
  89. package/lib/modules/BookingContext/utils/index.js +124 -41
  90. package/lib/modules/BookingContext/utils/noResource.js +58 -70
  91. package/lib/modules/BookingContext/utils/orderLineDisplay.js +14 -41
  92. package/lib/modules/BookingContext/utils/productExtend.js +202 -155
  93. package/lib/modules/BookingContext/utils/resourceErrors.js +53 -53
  94. package/lib/modules/BookingContext/utils/resourceSelection.js +19 -41
  95. package/lib/modules/BookingContext/utils/resources.js +209 -167
  96. package/lib/modules/BookingContext/utils/serviceTimes.js +115 -116
  97. package/lib/modules/BookingContext/utils/timeSlices.js +76 -86
  98. package/lib/modules/Cart/index.js +170 -124
  99. package/lib/modules/Cart/types.js +46 -51
  100. package/lib/modules/Cart/utils/cartAccount.js +40 -39
  101. package/lib/modules/Cart/utils/cartDate.js +56 -61
  102. package/lib/modules/Cart/utils/cartDiscount.js +28 -33
  103. package/lib/modules/Cart/utils/cartNote.js +27 -32
  104. package/lib/modules/Cart/utils/cartProduct.js +321 -251
  105. package/lib/modules/Cart/utils/cartRelationForms.js +29 -35
  106. package/lib/modules/Cart/utils/cartResource.js +70 -78
  107. package/lib/modules/Cart/utils/changePrice.js +22 -50
  108. package/lib/modules/Cart/utils/index.js +106 -48
  109. package/lib/modules/Customer/constants.js +13 -34
  110. package/lib/modules/Customer/index.js +235 -172
  111. package/lib/modules/Customer/types.js +38 -35
  112. package/lib/modules/Date/index.js +116 -115
  113. package/lib/modules/Date/types.js +16 -28
  114. package/lib/modules/Date/utils.js +174 -123
  115. package/lib/modules/Discount/index.js +122 -127
  116. package/lib/modules/Discount/types.js +9 -31
  117. package/lib/modules/Guests/index.js +30 -56
  118. package/lib/modules/Guests/types.js +23 -33
  119. package/lib/modules/Holder/index.js +209 -189
  120. package/lib/modules/Holder/types.js +4 -16
  121. package/lib/modules/Holder/utils.js +20 -49
  122. package/lib/modules/OpenData/index.js +70 -76
  123. package/lib/modules/OpenData/types.js +4 -16
  124. package/lib/modules/OpenData/utils.js +44 -78
  125. package/lib/modules/Order/index.js +1713 -2118
  126. package/lib/modules/Order/payment-utils.js +77 -120
  127. package/lib/modules/Order/types.js +88 -35
  128. package/lib/modules/Order/utils/bundleDiscountHydration.js +41 -74
  129. package/lib/modules/Order/utils/discountProductLineIdentity.js +142 -100
  130. package/lib/modules/Order/utils/manualProductDiscount.js +124 -123
  131. package/lib/modules/Order/utils/orderCollectionIdentity.js +184 -244
  132. package/lib/modules/Order/utils.js +814 -647
  133. package/lib/modules/Payment/cash.js +20 -33
  134. package/lib/modules/Payment/cashRecommendationAlgorithm.js +145 -76
  135. package/lib/modules/Payment/eftpos.js +16 -30
  136. package/lib/modules/Payment/index.js +532 -399
  137. package/lib/modules/Payment/mx51.js +1 -0
  138. package/lib/modules/Payment/types.js +230 -108
  139. package/lib/modules/Payment/utils.js +52 -51
  140. package/lib/modules/Payment/walletpass.js +485 -660
  141. package/lib/modules/Product/index.js +51 -46
  142. package/lib/modules/Product/types.js +4 -16
  143. package/lib/modules/Product/utils.js +32 -33
  144. package/lib/modules/ProductList/index.js +113 -123
  145. package/lib/modules/ProductList/types.js +9 -31
  146. package/lib/modules/Quotation/index.js +81 -118
  147. package/lib/modules/Quotation/types.js +4 -16
  148. package/lib/modules/Resource/index.js +27 -59
  149. package/lib/modules/Resource/types.js +22 -32
  150. package/lib/modules/Resource/utils.js +30 -38
  151. package/lib/modules/ResourcePlanner/index.js +25 -23
  152. package/lib/modules/ResourcePlanner/localClock.js +54 -119
  153. package/lib/modules/ResourcePlanner/planner.js +589 -770
  154. package/lib/modules/ResourcePlanner/types.js +10 -29
  155. package/lib/modules/Rules/index.js +1218 -959
  156. package/lib/modules/Rules/types.js +17 -40
  157. package/lib/modules/SalesSummary/index.js +85 -90
  158. package/lib/modules/SalesSummary/types.js +4 -16
  159. package/lib/modules/SalesSummary/utils.js +355 -430
  160. package/lib/modules/ScanOrderLogger/index.js +59 -79
  161. package/lib/modules/ScanOrderLogger/providers/feishu.js +54 -78
  162. package/lib/modules/ScanOrderLogger/providers/grafana.js +13 -36
  163. package/lib/modules/ScanOrderLogger/types.js +4 -16
  164. package/lib/modules/Schedule/getDateIsInSchedule.js +334 -163
  165. package/lib/modules/Schedule/index.js +100 -114
  166. package/lib/modules/Schedule/type.js +4 -16
  167. package/lib/modules/Schedule/types.js +4 -16
  168. package/lib/modules/Schedule/utils.js +433 -291
  169. package/lib/modules/Step/index.js +40 -52
  170. package/lib/modules/Step/tyeps.js +4 -16
  171. package/lib/modules/Summary/index.js +66 -71
  172. package/lib/modules/Summary/types.js +4 -16
  173. package/lib/modules/Summary/utils.js +773 -418
  174. package/lib/modules/SurchargeList/index.js +46 -60
  175. package/lib/modules/SurchargeList/types.js +4 -16
  176. package/lib/modules/index.js +245 -63
  177. package/lib/plugins/app.js +4 -16
  178. package/lib/plugins/index.js +36 -25
  179. package/lib/plugins/request.js +137 -126
  180. package/lib/plugins/shopStore.js +4 -16
  181. package/lib/plugins/user.js +4 -16
  182. package/lib/plugins/window.js +171 -179
  183. package/lib/server/index.js +3050 -2665
  184. package/lib/server/modules/floor-plan/index.js +118 -134
  185. package/lib/server/modules/floor-plan/types.js +15 -30
  186. package/lib/server/modules/index.js +106 -68
  187. package/lib/server/modules/menu/index.js +142 -122
  188. package/lib/server/modules/menu/types.js +18 -31
  189. package/lib/server/modules/order/index.js +784 -1002
  190. package/lib/server/modules/order/types.js +122 -33
  191. package/lib/server/modules/order/utils/filterBookings.js +219 -194
  192. package/lib/server/modules/order/utils/filterOrders.js +118 -92
  193. package/lib/server/modules/payment/index.js +59 -83
  194. package/lib/server/modules/payment/types.js +4 -16
  195. package/lib/server/modules/products/index.js +583 -471
  196. package/lib/server/modules/products/types.js +44 -34
  197. package/lib/server/modules/quotation/index.js +137 -172
  198. package/lib/server/modules/quotation/types.js +21 -31
  199. package/lib/server/modules/resource/index.js +220 -186
  200. package/lib/server/modules/resource/types.js +42 -33
  201. package/lib/server/modules/schedule/index.js +135 -123
  202. package/lib/server/modules/schedule/types.js +14 -21
  203. package/lib/server/modules/schedule/utils.js +334 -163
  204. package/lib/server/types.js +4 -16
  205. package/lib/server/utils/index.js +25 -23
  206. package/lib/server/utils/product.js +196 -139
  207. package/lib/server/utils/schedule.js +34 -41
  208. package/lib/server/utils/small-ticket.js +479 -456
  209. package/lib/server/utils/time.js +40 -49
  210. package/lib/solution/BaseSales/index.js +1186 -1410
  211. package/lib/solution/BaseSales/types.js +42 -38
  212. package/lib/solution/BaseSales/utils/cartPromotion.js +642 -498
  213. package/lib/solution/BaseSales/utils/parseSalesResponse.js +85 -105
  214. package/lib/solution/BaseSales/utils/quotationPrice.js +56 -114
  215. package/lib/solution/BaseSales/utils/transformBaseProductToOrderProduct.js +108 -145
  216. package/lib/solution/BaseSales/utils.js +158 -143
  217. package/lib/solution/BookingByStep/index.js +1527 -1340
  218. package/lib/solution/BookingByStep/types.js +40 -99
  219. package/lib/solution/BookingByStep/utils/capacity.js +192 -160
  220. package/lib/solution/BookingByStep/utils/products.js +42 -52
  221. package/lib/solution/BookingByStep/utils/resources.js +527 -330
  222. package/lib/solution/BookingByStep/utils/stock.js +65 -44
  223. package/lib/solution/BookingByStep/utils/timeslots.js +129 -131
  224. package/lib/solution/BookingTicket/index.js +607 -522
  225. package/lib/solution/BookingTicket/types.js +99 -77
  226. package/lib/solution/BookingTicket/utils/addProductDecision.js +226 -189
  227. package/lib/solution/BookingTicket/utils/addTimeAvailability.js +58 -89
  228. package/lib/solution/BookingTicket/utils/bookingStatus.js +107 -78
  229. package/lib/solution/BookingTicket/utils/cartView.js +114 -99
  230. package/lib/solution/BookingTicket/utils/exampleData.js +185 -0
  231. package/lib/solution/BookingTicket/utils/orderCustomer.js +18 -41
  232. package/lib/solution/BookingTicket/utils/resolveBestAddTimePlan.js +219 -86
  233. package/lib/solution/BookingTicket/utils/scan/applyGlobalScan.js +168 -116
  234. package/lib/solution/BookingTicket/utils/scan/cloudSearch.js +79 -91
  235. package/lib/solution/BookingTicket/utils/scan/formatGlobalScan.js +54 -41
  236. package/lib/solution/BookingTicket/utils/scan/handleScan.js +84 -81
  237. package/lib/solution/BookingTicket/utils/scan/index.js +98 -104
  238. package/lib/solution/BookingTicket/utils/scan/scanCache.js +90 -43
  239. package/lib/solution/BookingTicket/utils/scan/scanProductValues.js +52 -64
  240. package/lib/solution/BookingTicket/utils/sessionCatalogStale.js +56 -58
  241. package/lib/solution/BookingTicket/utils/weighingProductSku.js +34 -48
  242. package/lib/solution/BuyTickets/index.js +67 -70
  243. package/lib/solution/BuyTickets/types.js +22 -38
  244. package/lib/solution/Checkout/index.js +1451 -1158
  245. package/lib/solution/Checkout/types.js +91 -61
  246. package/lib/solution/Checkout/utils/index.js +228 -156
  247. package/lib/solution/PlaceOrder/index.js +55 -0
  248. package/lib/solution/RegisterAndLogin/config.js +493 -460
  249. package/lib/solution/RegisterAndLogin/index.js +633 -630
  250. package/lib/solution/RegisterAndLogin/types.js +189 -76
  251. package/lib/solution/RegisterAndLogin/utils.js +183 -125
  252. package/lib/solution/Sales/index.js +328 -324
  253. package/lib/solution/Sales/types.js +27 -33
  254. package/lib/solution/ScanOrder/index.js +832 -864
  255. package/lib/solution/ScanOrder/types.js +33 -34
  256. package/lib/solution/ScanOrder/utils.js +409 -374
  257. package/lib/solution/ShopDiscount/index.js +226 -232
  258. package/lib/solution/ShopDiscount/types.js +15 -37
  259. package/lib/solution/ShopDiscount/utils.js +300 -172
  260. package/lib/solution/UnifiedBookingSales/index.js +1035 -1201
  261. package/lib/solution/UnifiedBookingSales/remoteOccupancyCache.js +99 -159
  262. package/lib/solution/UnifiedBookingSales/types.js +10 -32
  263. package/lib/solution/VenueBooking/index.js +811 -879
  264. package/lib/solution/VenueBooking/types.js +19 -39
  265. package/lib/solution/VenueBooking/utils/dateSummary.js +27 -56
  266. package/lib/solution/VenueBooking/utils/resource.js +31 -54
  267. package/lib/solution/VenueBooking/utils/slotMerge.js +94 -107
  268. package/lib/solution/VenueBooking/utils/timeSlot.js +132 -132
  269. package/lib/solution/VenueBooking/utils.js +130 -67
  270. package/lib/solution/index.js +124 -41
  271. package/lib/store/createStore.js +32 -29
  272. package/lib/types/index.js +4 -16
  273. package/lib/utils/payment-number.js +26 -46
  274. package/lib/utils/task.js +36 -36
  275. package/lib/utils/watch.js +81 -47
  276. package/package.json +2 -1
@@ -1,69 +1,48 @@
1
- var __defProp = Object.defineProperty;
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
- // src/modules/ResourcePlanner/planner.ts
20
- var planner_exports = {};
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
- module.exports = __toCommonJS(planner_exports);
28
- var import_types = require("./types");
29
- var import_localClock = require("./localClock");
30
- var DEFAULT_SLOT_STEP_MINUTES = 30;
31
- var DEFAULT_BUSINESS_HOURS = {
32
- startTime: "09:00",
33
- endTime: "18:00"
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 import_types.AvailabilityError(code, message, details);
18
+ return new _types.AvailabilityError(code, message, details);
37
19
  }
38
20
  function sameId(left, right) {
39
- if (left === void 0 || left === null || right === void 0 || right === null)
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 = /* @__PURE__ */ new Map();
45
- values.forEach((value) => {
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 === void 0 || partySize === null)
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("INVALID_TARGET_FILTER", "partySize 必须是正整数", {
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 === void 0 || bookingCount === null)
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("INVALID_TARGET_FILTER", "bookingCount 必须是正整数", {
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, import_localClock.localDateTimeToScalar)((0, import_localClock.parseLocalDateTime)(value));
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, import_localClock.localDateToScalarStart)(value);
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, import_localClock.addLocalCalendarDays)(start, 1) - 1 : Number.NaN;
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, import_localClock.addLocalMinutes)(value, minutes);
73
+ return (0, _localClock.addLocalMinutes)(value, minutes);
95
74
  }
96
75
  function ceilToTenMinutes(value, _timezoneName) {
97
- return (0, import_localClock.ceilLocalScalarToTenMinutes)(value);
76
+ return (0, _localClock.ceilLocalScalarToTenMinutes)(value);
98
77
  }
99
78
  function nextDate(value, _timezoneName) {
100
- return (0, import_localClock.nextDate)(value);
79
+ return (0, _localClock.nextDate)(value);
101
80
  }
102
81
  function formatDateTime(value, _timezoneName, fixedOffsetMinutes) {
103
- return (0, import_localClock.formatLocalDateTime)(value, fixedOffsetMinutes ?? (0, import_localClock.captureRuntimeOffsetMinutes)());
82
+ return (0, _localClock.formatLocalDateTime)(value, fixedOffsetMinutes ?? (0, _localClock.captureRuntimeOffsetMinutes)());
104
83
  }
105
84
  function formatDate(value, _timezoneName) {
106
- return (0, import_localClock.formatLocalDate)(value);
85
+ return (0, _localClock.formatLocalDate)(value);
107
86
  }
108
87
  function formatTime(value, _timezoneName) {
109
- return (0, import_localClock.formatLocalTime)(value, false);
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
- return null;
116
- return { start, end };
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 === "invalid") {
154
- return policy.reason || "invalid_cutoff_policy";
127
+ if (policy.type === 'invalid') {
128
+ return policy.reason || 'invalid_cutoff_policy';
155
129
  }
156
- if ("futureDay" in policy && policy.futureDay !== void 0 && normalizeNonNegativeNumber(policy.futureDay) === null) {
157
- return "invalid_future_day";
130
+ if ('futureDay' in policy && policy.futureDay !== undefined && normalizeNonNegativeNumber(policy.futureDay) === null) {
131
+ return 'invalid_future_day';
158
132
  }
159
- if (policy.type === "ongoing" && policy.mode === "after_start" && normalizeNonNegativeNumber(policy.graceMinutes) === null) {
160
- return "invalid_grace_minutes";
133
+ if (policy.type === 'ongoing' && policy.mode === 'after_start' && normalizeNonNegativeNumber(policy.graceMinutes) === null) {
134
+ return 'invalid_grace_minutes';
161
135
  }
162
- if (policy.type === "advance") {
163
- if (normalizeNonNegativeNumber(policy.unit) === null)
164
- return "invalid_advance_unit";
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 === "days")
172
- return (0, import_localClock.addLocalCalendarDays)(base, unit);
173
- if (unitType === "hours")
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 { product, candidateRange, evaluationContext, timezoneName } = params;
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: "past",
183
- reasonCodes: ["past"],
184
- conflictCode: "past"
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: "unavailable",
193
- reasonCodes: ["booking_cutoff_invalid"],
194
- conflictCode: "booking_cutoff"
166
+ status: 'unavailable',
167
+ reasonCodes: ['booking_cutoff_invalid'],
168
+ conflictCode: 'booking_cutoff'
195
169
  };
196
170
  }
197
- if ("futureDay" in policy && policy.futureDay !== void 0) {
171
+ if ('futureDay' in policy && policy.futureDay !== undefined) {
198
172
  const futureDay = normalizeNonNegativeNumber(policy.futureDay);
199
- const lastAllowedDateOrdinal = (0, import_localClock.dateOrdinalFromLocalDate)(formatDate(now)) + futureDay;
200
- if ((0, import_localClock.dateOrdinalFromLocalDate)(formatDate(candidateRange.start)) > lastAllowedDateOrdinal) {
173
+ const lastAllowedDateOrdinal = (0, _localClock.dateOrdinalFromLocalDate)(formatDate(now)) + futureDay;
174
+ if ((0, _localClock.dateOrdinalFromLocalDate)(formatDate(candidateRange.start)) > lastAllowedDateOrdinal) {
201
175
  return {
202
- status: "unavailable",
203
- reasonCodes: ["booking_cutoff_future_day"],
204
- conflictCode: "booking_cutoff"
176
+ status: 'unavailable',
177
+ reasonCodes: ['booking_cutoff_future_day'],
178
+ conflictCode: 'booking_cutoff'
205
179
  };
206
180
  }
207
181
  }
208
- if (policy.type === "ongoing") {
209
- if (policy.mode === "before_end")
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: "unavailable",
217
- reasonCodes: ["booking_cutoff_after_start"],
218
- conflictCode: "booking_cutoff"
188
+ status: 'unavailable',
189
+ reasonCodes: ['booking_cutoff_after_start'],
190
+ conflictCode: 'booking_cutoff'
219
191
  };
220
192
  }
221
- if (policy.type === "before_start") {
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: "unavailable",
226
- reasonCodes: ["booking_cutoff_before_start"],
227
- conflictCode: "booking_cutoff"
196
+ status: 'unavailable',
197
+ reasonCodes: ['booking_cutoff_before_start'],
198
+ conflictCode: 'booking_cutoff'
228
199
  };
229
200
  }
230
- if (policy.type === "advance") {
231
- const leadWindow = addLeadWindow(
232
- now,
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: "unavailable",
241
- reasonCodes: ["booking_cutoff_advance"],
242
- conflictCode: "booking_cutoff"
205
+ status: 'unavailable',
206
+ reasonCodes: ['booking_cutoff_advance'],
207
+ conflictCode: 'booking_cutoff'
243
208
  };
244
209
  }
245
210
  return {
246
- status: "unavailable",
247
- reasonCodes: ["booking_cutoff_invalid"],
248
- conflictCode: "booking_cutoff"
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
- return null;
262
- return { start, end };
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((range) => {
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((leftRange) => {
287
- right.forEach((rightRange) => {
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 = (value) => {
273
+ const isCanonicalDate = value => {
308
274
  try {
309
- return (0, import_localClock.formatLocalDate)((0, import_localClock.parseLocalDate)(value)) === value;
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("INVALID_DATE_RANGE", "coverage.startDate / coverage.endDate 非法", {
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("INVALID_DATE_RANGE", "coverage.endDate 不能早于 startDate", {
286
+ throw availabilityError('INVALID_DATE_RANGE', 'coverage.endDate 不能早于 startDate', {
321
287
  coverage: input.coverage
322
288
  });
323
289
  }
324
290
  return {
325
- dateRange: { startDate, endDate },
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 = "single";
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((product) => ({
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 ? { ...product.cutOffPolicy } : void 0,
376
- requirementGroups: (product.requirementGroups || []).map((group) => normalizeRequirementGroup(product, group)),
377
- sessionRules: (product.sessionRules || []).map((rule) => ({
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] : void 0,
380
- dates: rule.dates ? [...rule.dates] : void 0,
381
- includeDates: rule.includeDates ? [...rule.includeDates] : void 0,
382
- excludeDates: rule.excludeDates ? [...rule.excludeDates] : void 0,
383
- scheduleRef: rule.scheduleRef ? { ...rule.scheduleRef } : void 0
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((range) => {
386
- var _a;
387
- return {
388
- ...range,
389
- scheduleRefs: (_a = range.scheduleRefs) == null ? void 0 : _a.map((scheduleRef) => ({ ...scheduleRef }))
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((resource) => ({
360
+ return resources.map(resource => ({
396
361
  ...resource,
397
- resourceType: "single",
362
+ resourceType: 'single',
398
363
  capacity: Math.max(1, Number(resource.capacity || 1) || 1),
399
- windows: (resource.windows || []).map((window) => ({ ...window }))
364
+ windows: (resource.windows || []).map(window => ({
365
+ ...window
366
+ }))
400
367
  }));
401
368
  }
402
369
  function normalizeOperatingHours(params) {
403
- var _a, _b, _c;
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 { start, end };
376
+ return {
377
+ start,
378
+ end
379
+ };
411
380
  });
412
- const sourceWindows = params.input ? params.input.windows : fallbackWindows.map((range) => ({
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: ((_a = params.input) == null ? void 0 : _a.source) || params.fallbackSource,
424
- scheduleIds: uniqById(((_b = params.input) == null ? void 0 : _b.scheduleIds) || []),
425
- missingScheduleIds: uniqById(((_c = params.input) == null ? void 0 : _c.missingScheduleIds) || []),
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
- var _a;
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 (((_a = requirementGroup.resourceIds) == null ? void 0 : _a.length) && !requirementGroup.resourceIds.some((resourceId) => sameId(resourceId, resource.id))) {
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 !== void 0 && occupancy.scheduleEventId !== null) {
412
+ if (occupancy.scheduleEventId !== undefined && occupancy.scheduleEventId !== null) {
458
413
  return `scheduleEvent:${String(occupancy.resourceId)}:${String(occupancy.scheduleEventId)}`;
459
414
  }
460
- if (occupancy.bookingUid !== void 0 && occupancy.bookingUid !== null) {
415
+ if (occupancy.bookingUid !== undefined && occupancy.bookingUid !== null) {
461
416
  return `bookingUid:${String(occupancy.resourceId)}:${String(occupancy.bookingUid)}`;
462
417
  }
463
- if (occupancy.productUid !== void 0 && occupancy.productUid !== null) {
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, import_localClock.extractFixedOffsetMinutes)(projection.meta.now) ?? (0, import_localClock.captureRuntimeOffsetMinutes)();
430
+ const fixedOffsetMinutes = (0, _localClock.extractFixedOffsetMinutes)(projection.meta.now) ?? (0, _localClock.captureRuntimeOffsetMinutes)();
476
431
  const projectionNow = toDateTime(projection.meta.now);
477
- projection.products.forEach((product) => {
478
- (product.requirementGroups || []).forEach((requirementGroup) => {
479
- projection.resources.filter((resource) => matchesRequirementGroup(requirementGroup, resource, product)).forEach((resource) => {
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((occupancy) => sameId(occupancy.resourceId, resource.id)).map((occupancy) => ({
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((item) => Boolean(item.range)).map((item) => ({ ...item, range: clipRange(item.range, coverage) })).filter((item) => Boolean(item.range));
485
- const boundaries = /* @__PURE__ */ new Set([coverage.start, coverage.end]);
486
- windows.forEach((window) => {
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(({ range }) => {
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
- continue;
500
- const segment = { start, end };
501
- const isInsideWorkingWindow = windows.some((window) => contains(window, segment));
502
- const segmentOccupancies = isInsideWorkingWindow ? occupancies.filter(({ range }) => overlaps(range, segment)).map(({ occupancy }) => occupancy) : [];
503
- const usedCapacity = segmentOccupancies.reduce(
504
- (sum, occupancy) => sum + Math.max(1, Number(occupancy.pax || 1) || 1),
505
- 0
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
- segmentOccupancies.flatMap((occupancy) => {
511
- const source = occupancy.source || "remote";
512
- if (source === "selection")
513
- return ["selection_occupied"];
514
- if (source === "cart")
515
- return ["cart_occupied"];
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 = "past";
522
- reasonCodes.push("past");
480
+ status = 'past';
481
+ reasonCodes.push('past');
523
482
  } else if (!isInsideWorkingWindow) {
524
- status = "unavailable";
525
- reasonCodes.push("outside_resource_window");
483
+ status = 'unavailable';
484
+ reasonCodes.push('outside_resource_window');
526
485
  } else if (remainingCapacity < requiredCapacity) {
527
- status = "full";
528
- reasonCodes.push("capacity_full");
486
+ status = 'full';
487
+ reasonCodes.push('capacity_full');
529
488
  } else if (remainingCapacity < resource.capacity) {
530
- status = "limited";
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((occupancy) => ({
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
- String(left.productId),
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({ ...cell });
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((item) => cloneSnapshotMetadataValue(item));
564
+ return value.map(item => cloneSnapshotMetadataValue(item));
618
565
  }
619
- if (!value || typeof value !== "object")
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 !== "object" || Object.isFrozen(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((key) => {
631
- if (key === "raw")
632
- return;
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 === "object" || typeof child === "function")) {
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, import_localClock.captureRuntimeOffsetMinutes)();
644
- const runtimeNow = (0, import_localClock.localDateToScalar)(/* @__PURE__ */ new Date());
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
- (input.resourceOccupancies || []).map((occupancy) => normalizeOccupancy(
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: ((_a = input.businessHours) == null ? void 0 : _a.startTime) || DEFAULT_BUSINESS_HOURS.startTime,
670
- endTime: ((_b = input.businessHours) == null ? void 0 : _b.endTime) || DEFAULT_BUSINESS_HOURS.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 ? "prepare_business_hours" : "default_business_hours"
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 || "appointment",
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) : void 0,
701
- prepareParams: input.prepareParams ? cloneSnapshotMetadataValue(input.prepareParams) : void 0
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 = /* @__PURE__ */ new Map();
716
- occupancies.forEach((occupancy) => {
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 = (requestDateRange == null ? void 0 : requestDateRange.startDate) || projection.meta.coverage.startDate;
723
- const endDate = (requestDateRange == null ? void 0 : requestDateRange.endDate) || projection.meta.coverage.endDate;
724
- const isCanonicalDate = (value) => {
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, import_localClock.formatLocalDate)((0, import_localClock.parseLocalDate)(value)) === value;
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("INVALID_DATE_RANGE", "查询日期范围必须是有效的 YYYY-MM-DD,且结束日期不能早于开始日期", {
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("OUT_OF_PROJECTION_COVERAGE", "查询范围超出 projection.coverage", {
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 { startDate, endDate };
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 { projection, requirementGroup, dateRange, resourceIds, partySize } = params;
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 = (resourceIds == null ? void 0 : resourceIds.length) ? new Set(resourceIds.map(String)) : null;
773
- const resourceWindows = projection.resources.filter((resource) => matchesRequirementGroup(requirementGroup, resource, params.product, partySize)).filter((resource) => !filteredResourceIdSet || filteredResourceIdSet.has(String(resource.id))).map((resource) => {
774
- var _a;
775
- const windows = ((_a = params.resourceWindowIndex) == null ? void 0 : _a.get(String(resource.id))) || normalizeResourceWindow(resource, coverage, projection.meta.timezone);
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((windows) => windows));
709
+ return mergeRanges(resourceWindows.flatMap(windows => windows));
784
710
  }
785
- const boundaries = /* @__PURE__ */ new Set();
786
- resourceWindows.forEach((windows) => {
787
- windows.forEach((window) => {
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
- continue;
799
- const segment = { start, end };
800
- const activeResources = resourceWindows.reduce((count, windows) => windows.some((window) => contains(window, segment)) ? count + 1 : count, 0);
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 = /* @__PURE__ */ new Map();
810
- rules.forEach((rule) => {
811
- dates.forEach((date) => {
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: "session_schedule",
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
- var _a, _b, _c, _d;
849
- if ((_a = rule.includeDates) == null ? void 0 : _a.includes(date))
850
- return true;
851
- if ((_b = rule.excludeDates) == null ? void 0 : _b.includes(date))
852
- return false;
853
- if (rule.startDate && date < rule.startDate)
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 === "weekly") {
863
- if (!((_d = rule.weekdays) == null ? void 0 : _d.length))
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, import_localClock.weekdayFromLocalDate)(date)))
867
- return false;
868
- if (!rule.startDate)
869
- return true;
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 === "daily" && rule.startDate) {
790
+ if (rule.mode === 'daily' && rule.startDate) {
875
791
  const freq = Math.max(1, Number(rule.frequency || 1) || 1);
876
- return ((0, import_localClock.dateOrdinalFromLocalDate)(date) - (0, import_localClock.dateOrdinalFromLocalDate)(rule.startDate)) % freq === 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 = /* @__PURE__ */ new Map();
882
- [...base, ...next].forEach((ref) => {
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((product) => [String(product.id), product]));
805
+ return new Map(projection.products.map(product => [String(product.id), product]));
896
806
  }
897
807
  function buildGroupResourceIndex(params) {
898
- var _a;
899
- const resourceIdSet = ((_a = params.resourceIds) == null ? void 0 : _a.length) ? new Set(params.resourceIds.map(String)) : null;
900
- const index = /* @__PURE__ */ new Map();
901
- params.projection.products.forEach((product) => {
902
- (product.requirementGroups || []).forEach((requirementGroup) => {
903
- const key = [String(product.id), requirementGroup.id].join(":");
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, import_localClock.extractFixedOffsetMinutes)(projection.meta.now) ?? (0, import_localClock.captureRuntimeOffsetMinutes)();
911
- const ranges = (product.sessionRanges || []).map((range) => {
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: "session_schedule",
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: (_a = range.scheduleRefs) == null ? void 0 : _a[0]
840
+ primaryScheduleRef: range.scheduleRefs?.[0]
934
841
  };
935
842
  }).filter(Boolean);
936
- const ruleRanges = materializeScheduleRules(
937
- product.sessionRules || [],
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 = /* @__PURE__ */ new Map();
951
- ranges.forEach((range) => {
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, { ...range, 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, import_localClock.extractFixedOffsetMinutes)(projection.meta.now) ?? (0, import_localClock.captureRuntimeOffsetMinutes)();
871
+ const fixedOffsetMinutes = (0, _localClock.extractFixedOffsetMinutes)(projection.meta.now) ?? (0, _localClock.captureRuntimeOffsetMinutes)();
969
872
  const resourceWindowIndex = buildResourceWindowIndex(projection);
970
- const filteredProductIdSet = ((_a = request.productIds) == null ? void 0 : _a.length) ? new Set(request.productIds.map(String)) : null;
971
- return projection.products.filter((product) => !filteredProductIdSet || filteredProductIdSet.has(String(product.id))).map((product) => {
972
- if (product.kind === "normal") {
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 === "session" || product.kind === "venue_slot") {
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((group) => group.required !== false);
891
+ const requiredGroups = (product.requirementGroups || []).filter(group => group.required !== false);
989
892
  const operatingRanges = buildOperatingRanges(projection, dateRange);
990
- const resourceRanges = requiredGroups.length ? requiredGroups.map((group) => buildGroupWorkingRanges({
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, ranges2) => current === null ? ranges2 : intersectRangeLists(current, ranges2), null) || [] : null;
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((range) => {
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: "duration_window",
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 { cell, range } = indexedCells[index];
1062
- if (range.startMs >= candidateEndMs)
1063
- break;
1064
- if (range.endMs <= candidateStartMs)
1065
- continue;
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
- overlapped.push({ cell, range: clipped });
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
- return void 0;
1084
- const cells = overlapped.map((item) => item.cell);
1085
- const statuses = cells.map((cell) => cell.status);
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((status) => status === "unavailable"))
1089
- return "unavailable";
1090
- if (nonPastStatuses.some((status) => status === "full"))
1091
- return "full";
1092
- if (nonPastStatuses.some((status) => status === "limited"))
1093
- return "limited";
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((cell) => cell.remainingCapacity !== void 0 || cell.maxCapacity !== void 0);
1101
- const reasonCodes = Array.from(new Set(cells.flatMap((cell) => cell.reasonCodes)));
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
- candidateRange.start,
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((cell) => cell.remainingCapacity ?? Number.MAX_SAFE_INTEGER)) : void 0,
1141
- maxCapacity: capacityCells.length ? Math.min(...capacityCells.map((cell) => cell.maxCapacity ?? Number.MAX_SAFE_INTEGER)) : void 0,
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 { cell, range } = indexedCells[index];
1163
- if (range.startMs >= candidateEndMs)
1164
- break;
1165
- if (range.endMs <= candidateStartMs)
1166
- continue;
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
- continue;
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
- cursorMs = clippedEndMs;
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
- return void 0;
1181
- const statuses = cells.map((cell) => cell.status);
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((cellStatus) => cellStatus === "unavailable"))
1185
- return "unavailable";
1186
- if (nonPastStatuses.some((cellStatus) => cellStatus === "full"))
1187
- return "full";
1188
- if (nonPastStatuses.some((cellStatus) => cellStatus === "limited"))
1189
- return "limited";
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((cell) => cell.remainingCapacity !== void 0 || cell.maxCapacity !== void 0);
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((cell) => cell.remainingCapacity ?? Number.MAX_SAFE_INTEGER)) : void 0,
1200
- maxCapacity: capacityCells.length ? Math.min(...capacityCells.map((cell) => cell.maxCapacity ?? Number.MAX_SAFE_INTEGER)) : void 0,
1201
- reasonCodes: Array.from(new Set(cells.flatMap((cell) => cell.reasonCodes)))
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 = /* @__PURE__ */ new Map();
1206
- projection.cells.forEach((cell) => {
1207
- if (cell.requirementGroupId == null || cell.resourceId == null)
1208
- return;
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 = /* @__PURE__ */ new Map();
1229
- (params.requirementSelections || []).forEach((selection) => {
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: ((_a = params.resourceIds) == null ? void 0 : _a.length) ? new Set(params.resourceIds.map(String)) : null,
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
- return "unavailable";
1243
- if (statuses.every((status) => status === "past"))
1244
- return "past";
1245
- if (statuses.some((status) => status === "unavailable"))
1246
- return "unavailable";
1247
- if (statuses.some((status) => status === "full"))
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 !== "past")
1269
- aggregate.allPast = false;
1270
- if (status === "available")
1271
- aggregate.hasAvailable = true;
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
- return "unavailable";
1280
- if (aggregate.hasAvailable)
1281
- return "available";
1282
- if (aggregate.hasLimited)
1283
- return "limited";
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 ? "past" : "unavailable",
1138
+ status: isPast ? 'past' : 'unavailable',
1295
1139
  maxPartySize: 0,
1296
1140
  remainingCapacity: 0,
1297
1141
  maxCapacity: 0,
1298
- reasonCodes: isPast ? ["past"] : ["outside_resource_window"]
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
- return "past";
1307
- if (params.matchedCell.status === "unavailable")
1308
- return "unavailable";
1309
- if (remainingCapacity < params.requiredCapacity)
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(/* @__PURE__ */ new Set([...params.matchedCell.reasonCodes || [], "past"])) : params.matchedCell.reasonCodes
1160
+ reasonCodes: isPast ? Array.from(new Set([...(params.matchedCell.reasonCodes || []), 'past'])) : params.matchedCell.reasonCodes
1321
1161
  };
1322
1162
  }
1323
1163
  function evaluateRequirementGroupSummary(params) {
1324
- var _a;
1325
- const { projection, product, requirementGroup, candidateRange, requestedPartySize, evaluationContext } = params;
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 = ((_a = params.groupResourceIndex) == null ? void 0 : _a.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)));
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 = /* @__PURE__ */ new Set();
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((resource) => {
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((reasonCode) => reasonCodeSet.add(reasonCode));
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 === "available" || option.status === "limited") {
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 = "available";
1203
+ let status = 'available';
1362
1204
  if (candidateRange.endMs <= evaluationContext.evaluatedAtMs) {
1363
- status = "past";
1205
+ status = 'past';
1364
1206
  } else if (availableResourceCount < minimum) {
1365
- status = optionStatusAggregate.hasAvailable || optionStatusAggregate.hasLimited || optionStatusAggregate.hasFull ? "full" : "unavailable";
1207
+ status = optionStatusAggregate.hasAvailable || optionStatusAggregate.hasLimited || optionStatusAggregate.hasFull ? 'full' : 'unavailable';
1366
1208
  } else if (availableResourceCount === totalResourceCount && !optionStatusAggregate.hasLimited && !optionStatusAggregate.hasFull && !optionStatusAggregate.allPast) {
1367
- status = "available";
1209
+ status = 'available';
1368
1210
  } else {
1369
- status = "limited";
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
- var _a, _b;
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("INVALID_TARGET_FILTER", "candidate.productId 不存在于 projection.products", {
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 = ((_b = request.candidateRangeCache) == null ? void 0 : _b.get(candidateRangeCacheKey)) || (() => {
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("INVALID_TARGET_FILTER", "candidate 时间范围非法", {
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((group) => group.required !== false).map((requirementGroup) => evaluateRequirementGroupSummary({
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((group) => group.status)) : candidateRange.endMs <= evaluationContext.evaluatedAtMs ? "past" : "available";
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 = (gate == null ? void 0 : gate.status) || candidateStatus;
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 === "past" || status === "unavailable" ? 0 : Number.isFinite(remainingCapacity) ? remainingCapacity : 0,
1274
+ remainingCapacity: status === 'past' || status === 'unavailable' ? 0 : Number.isFinite(remainingCapacity) ? remainingCapacity : 0,
1434
1275
  maxCapacity: Number.isFinite(maxCapacity) ? maxCapacity : 0,
1435
- reasonCodes: (gate == null ? void 0 : gate.reasonCodes) || Array.from(new Set(requiredGroups.flatMap((group) => group.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
- var _a, _b;
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("INVALID_TARGET_FILTER", "candidate.productId 不存在于 projection.products", {
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 = ((_b = request.candidateRangeCache) == null ? void 0 : _b.get(candidateRangeCacheKey)) || (() => {
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("INVALID_TARGET_FILTER", "candidate 时间范围非法", {
1292
+ throw availabilityError('INVALID_TARGET_FILTER', 'candidate 时间范围非法', {
1453
1293
  candidate
1454
1294
  });
1455
1295
  }
@@ -1465,79 +1305,64 @@ 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((requirementGroup) => {
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 = (((_a2 = request.groupResourceIndex) == null ? void 0 : _a2.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)));
1475
- const options = matchedResources.map((resource) => {
1476
- var _a3;
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 isPast2 = candidateRange.endMs <= evaluationContext.evaluatedAtMs;
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
1322
  resourceType: resource.resourceType,
1491
- status: isPast2 ? "past" : "unavailable",
1323
+ status: isPast ? 'past' : 'unavailable',
1492
1324
  maxPartySize: 0,
1493
- reasonCodes: isPast2 ? ["past"] : ["outside_resource_window"]
1325
+ reasonCodes: isPast ? ['past'] : ['outside_resource_window']
1494
1326
  };
1495
1327
  }
1496
1328
  const remainingCapacity = matchedCell.remainingCapacity;
1497
1329
  const maxCapacity = matchedCell.maxCapacity;
1498
1330
  const isPast = candidateRange.endMs <= evaluationContext.evaluatedAtMs;
1499
- const maxPartySize2 = isPast ? 0 : Math.max(0, remainingCapacity || 0);
1331
+ const maxPartySize = isPast ? 0 : Math.max(0, remainingCapacity || 0);
1500
1332
  const requiredCapacity = resolveRequiredCapacity(requirementGroup, product, requestedPartySize);
1501
- const status2 = (() => {
1502
- if (isPast)
1503
- return "past";
1504
- if (matchedCell.status === "unavailable")
1505
- return "unavailable";
1506
- if ((remainingCapacity || 0) < requiredCapacity)
1507
- return "full";
1508
- if ((remainingCapacity || 0) < (maxCapacity || resource.capacity))
1509
- return "limited";
1510
- return "available";
1333
+ const status = (() => {
1334
+ if (isPast) return 'past';
1335
+ if (matchedCell.status === 'unavailable') return 'unavailable';
1336
+ if ((remainingCapacity || 0) < requiredCapacity) return 'full';
1337
+ if ((remainingCapacity || 0) < (maxCapacity || resource.capacity)) return 'limited';
1338
+ return 'available';
1511
1339
  })();
1512
1340
  return {
1513
1341
  resourceId: resource.id,
1514
1342
  resourceName: resource.name,
1515
1343
  resourceFormId: resource.formId,
1516
1344
  resourceType: resource.resourceType,
1517
- status: status2,
1345
+ status,
1518
1346
  remainingCapacity,
1519
1347
  maxCapacity,
1520
- maxPartySize: maxPartySize2,
1521
- reasonCodes: isPast ? Array.from(/* @__PURE__ */ new Set([...matchedCell.reasonCodes, "past"])) : matchedCell.reasonCodes,
1348
+ maxPartySize,
1349
+ reasonCodes: isPast ? Array.from(new Set([...matchedCell.reasonCodes, 'past'])) : matchedCell.reasonCodes,
1522
1350
  matchedCell
1523
1351
  };
1524
1352
  });
1525
- const availableOptions = options.filter((option) => option.status === "available" || option.status === "limited");
1353
+ const availableOptions = options.filter(option => option.status === 'available' || option.status === 'limited');
1526
1354
  const totalResourceCount = options.length;
1527
1355
  const availableResourceCount = availableOptions.length;
1528
- const groupMaxPartySize = availableOptions.reduce(
1529
- (max, option) => Math.max(max, option.maxPartySize),
1530
- 0
1531
- );
1532
- let status = "available";
1356
+ const groupMaxPartySize = availableOptions.reduce((max, option) => Math.max(max, option.maxPartySize), 0);
1357
+ let status = 'available';
1533
1358
  if (candidateRange.endMs <= evaluationContext.evaluatedAtMs) {
1534
- status = "past";
1359
+ status = 'past';
1535
1360
  } else if (availableResourceCount < minimum) {
1536
- status = options.some((option) => option.status === "full" || option.status === "limited" || option.status === "available") ? "full" : "unavailable";
1537
- } else if (options.every((option) => option.status === "available")) {
1538
- status = "available";
1361
+ status = options.some(option => option.status === 'full' || option.status === 'limited' || option.status === 'available') ? 'full' : 'unavailable';
1362
+ } else if (options.every(option => option.status === 'available')) {
1363
+ status = 'available';
1539
1364
  } else {
1540
- status = "limited";
1365
+ status = 'limited';
1541
1366
  }
1542
1367
  return {
1543
1368
  groupId: requirementGroup.id,
@@ -1552,70 +1377,66 @@ function evaluateCandidate(projection, candidate, request) {
1552
1377
  availableResourceCount,
1553
1378
  totalResourceCount,
1554
1379
  maxPartySize: groupMaxPartySize,
1555
- remainingCapacity: Math.max(...availableOptions.map((option) => option.remainingCapacity || 0), 0),
1556
- maxCapacity: Math.max(...availableOptions.map((option) => option.maxCapacity || 0), 0),
1557
- reasonCodes: Array.from(new Set(options.flatMap((option) => option.reasonCodes))),
1380
+ remainingCapacity: Math.max(...availableOptions.map(option => option.remainingCapacity || 0), 0),
1381
+ maxCapacity: Math.max(...availableOptions.map(option => option.maxCapacity || 0), 0),
1382
+ reasonCodes: Array.from(new Set(options.flatMap(option => option.reasonCodes))),
1558
1383
  options
1559
1384
  };
1560
1385
  });
1561
- const requiredGroups = requirementGroups.filter((group) => group.required);
1562
- const requiredStatuses = requiredGroups.map((group) => group.status);
1386
+ const requiredGroups = requirementGroups.filter(group => group.required);
1387
+ const requiredStatuses = requiredGroups.map(group => group.status);
1563
1388
  const gate = evaluateCandidateTimeGate({
1564
1389
  product,
1565
1390
  candidateRange,
1566
1391
  evaluationContext,
1567
1392
  timezoneName: projection.meta.timezone
1568
1393
  });
1569
- const candidateStatus = requiredGroups.length ? summarizeStatuses(requiredStatuses) : candidateRange.endMs <= evaluationContext.evaluatedAtMs ? "past" : "available";
1394
+ const candidateStatus = requiredGroups.length ? summarizeStatuses(requiredStatuses) : candidateRange.endMs <= evaluationContext.evaluatedAtMs ? 'past' : 'available';
1570
1395
  const maxPartySize = requiredGroups.length ? requiredGroups.reduce((min, group) => Math.min(min, group.maxPartySize), Number.MAX_SAFE_INTEGER) : 0;
1571
- const selectedAssignments = requirementGroups.flatMap((group) => {
1572
- const selection = selectionsByGroupKey.get(group.groupId) || selectionsByGroupKey.get(String(group.formId ?? ""));
1573
- if (!selection && !(request.allowImplicitSelection && group.required))
1574
- return [];
1575
- const availableOptions = group.options.filter((option) => option.status === "available" || option.status === "limited");
1396
+ const selectedAssignments = requirementGroups.flatMap(group => {
1397
+ const selection = selectionsByGroupKey.get(group.groupId) || selectionsByGroupKey.get(String(group.formId ?? ''));
1398
+ if (!selection && !(request.allowImplicitSelection && group.required)) return [];
1399
+ const availableOptions = group.options.filter(option => option.status === 'available' || option.status === 'limited');
1576
1400
  const selectedOptions = selection ? availableOptions.slice(0, group.max) : availableOptions.slice(0, Math.max(group.min, 1));
1577
- const originalGroup = (product.requirementGroups || []).find((item) => item.id === group.groupId) || {
1401
+ const originalGroup = (product.requirementGroups || []).find(item => item.id === group.groupId) || {
1578
1402
  id: group.groupId,
1579
1403
  resourceType: group.resourceType,
1580
1404
  min: group.min,
1581
1405
  max: group.max
1582
1406
  };
1583
- return selectedOptions.map((selectedOption) => {
1584
- var _a2, _b2;
1585
- return {
1586
- productId: product.id,
1587
- resourceId: selectedOption.resourceId,
1588
- formId: group.formId,
1589
- startAt: candidate.bookingStartAt || candidate.startAt,
1590
- endAt: candidate.bookingEndAt || candidate.endAt,
1591
- capacityRequired: resolveRequiredCapacity(originalGroup, product, requestedPartySize),
1592
- scheduleId: (_a2 = candidate.primaryScheduleRef) == null ? void 0 : _a2.scheduleId,
1593
- timeSlotId: (_b2 = candidate.primaryScheduleRef) == null ? void 0 : _b2.timeSlotId
1594
- };
1595
- });
1407
+ return selectedOptions.map(selectedOption => ({
1408
+ productId: product.id,
1409
+ resourceId: selectedOption.resourceId,
1410
+ formId: group.formId,
1411
+ startAt: candidate.bookingStartAt || candidate.startAt,
1412
+ endAt: candidate.bookingEndAt || candidate.endAt,
1413
+ capacityRequired: resolveRequiredCapacity(originalGroup, product, requestedPartySize),
1414
+ scheduleId: candidate.primaryScheduleRef?.scheduleId,
1415
+ timeSlotId: candidate.primaryScheduleRef?.timeSlotId
1416
+ }));
1596
1417
  });
1597
1418
  return {
1598
1419
  item: {
1599
1420
  ...candidate,
1600
- status: (gate == null ? void 0 : gate.status) || candidateStatus,
1421
+ status: gate?.status || candidateStatus,
1601
1422
  maxPartySize: gate ? 0 : Number.isFinite(maxPartySize) ? maxPartySize : 0,
1602
- reasonCodes: (gate == null ? void 0 : gate.reasonCodes) || Array.from(new Set(requiredGroups.flatMap((group) => group.reasonCodes))),
1423
+ reasonCodes: gate?.reasonCodes || Array.from(new Set(requiredGroups.flatMap(group => group.reasonCodes))),
1603
1424
  requirementGroups
1604
1425
  },
1605
1426
  assignments: gate ? [] : selectedAssignments,
1606
- gate: gate || void 0
1427
+ gate: gate || undefined
1607
1428
  };
1608
1429
  }
1609
1430
  function assertTimeCandidates(request) {
1610
- if (request.target === "time" && (!Array.isArray(request.candidates) || request.candidates.length === 0)) {
1611
- throw availabilityError("MISSING_TIME_CANDIDATES", "target=time 必须传非空 candidates", {
1431
+ if (request.target === 'time' && (!Array.isArray(request.candidates) || request.candidates.length === 0)) {
1432
+ throw availabilityError('MISSING_TIME_CANDIDATES', 'target=time 必须传非空 candidates', {
1612
1433
  request
1613
1434
  });
1614
1435
  }
1615
1436
  }
1616
1437
  function assertSelectedRanges(request) {
1617
- if (request.target === "resource" && (!Array.isArray(request.selectedRanges) || request.selectedRanges.length === 0)) {
1618
- throw availabilityError("MISSING_SELECTED_RANGE", "target=resource 必须传非空 selectedRanges", {
1438
+ if (request.target === 'resource' && (!Array.isArray(request.selectedRanges) || request.selectedRanges.length === 0)) {
1439
+ throw availabilityError('MISSING_SELECTED_RANGE', 'target=resource 必须传非空 selectedRanges', {
1619
1440
  request
1620
1441
  });
1621
1442
  }
@@ -1625,14 +1446,16 @@ function assertRangesWithinProjection(projection, ranges) {
1625
1446
  start: toDateTime(projection.meta.coverage.startAt, projection.meta.timezone),
1626
1447
  end: toDateTime(projection.meta.coverage.endAt, projection.meta.timezone)
1627
1448
  };
1628
- ranges.forEach((range) => {
1449
+ ranges.forEach(range => {
1629
1450
  const displayRange = normalizeAbsoluteRange(range, projection.meta.timezone);
1630
1451
  const bookingRange = normalizeAbsoluteRange(getCandidateBookingRange(range), projection.meta.timezone);
1631
1452
  if (!displayRange || !bookingRange) {
1632
- throw availabilityError("INVALID_TARGET_FILTER", "候选时间范围非法", { range });
1453
+ throw availabilityError('INVALID_TARGET_FILTER', '候选时间范围非法', {
1454
+ range
1455
+ });
1633
1456
  }
1634
1457
  if (!contains(coverage, displayRange) || !contains(coverage, bookingRange)) {
1635
- throw availabilityError("OUT_OF_PROJECTION_COVERAGE", "候选时间范围超出 projection.coverage", {
1458
+ throw availabilityError('OUT_OF_PROJECTION_COVERAGE', '候选时间范围超出 projection.coverage', {
1636
1459
  range,
1637
1460
  coverage: projection.meta.coverage
1638
1461
  });
@@ -1640,22 +1463,17 @@ function assertRangesWithinProjection(projection, ranges) {
1640
1463
  });
1641
1464
  }
1642
1465
  function queryAvailabilityProjection(projection, request) {
1643
- var _a;
1644
1466
  const normalizedPartySize = normalizePartySize(request.partySize);
1645
- const evaluatedAt = formatDateTime(
1646
- (0, import_localClock.localDateToScalar)(/* @__PURE__ */ new Date()),
1647
- projection.meta.timezone,
1648
- (0, import_localClock.extractFixedOffsetMinutes)(projection.meta.now) ?? (0, import_localClock.captureRuntimeOffsetMinutes)()
1649
- );
1650
- if (request.target === "time") {
1467
+ const evaluatedAt = formatDateTime((0, _localClock.localDateToScalar)(new Date()), projection.meta.timezone, (0, _localClock.extractFixedOffsetMinutes)(projection.meta.now) ?? (0, _localClock.captureRuntimeOffsetMinutes)());
1468
+ if (request.target === 'time') {
1651
1469
  assertTimeCandidates(request);
1652
1470
  assertRangesWithinProjection(projection, request.candidates);
1653
1471
  }
1654
- if (request.target === "resource") {
1472
+ if (request.target === 'resource') {
1655
1473
  assertSelectedRanges(request);
1656
1474
  assertRangesWithinProjection(projection, request.selectedRanges);
1657
1475
  }
1658
- const productIdSet = ((_a = request.productIds) == null ? void 0 : _a.length) ? new Set(request.productIds.map(String)) : null;
1476
+ const productIdSet = request.productIds?.length ? new Set(request.productIds.map(String)) : null;
1659
1477
  const productIndex = buildProductIndex(projection);
1660
1478
  const cellIndex = buildAvailabilityCellIndex(projection);
1661
1479
  const groupResourceIndex = buildGroupResourceIndex({
@@ -1663,23 +1481,23 @@ function queryAvailabilityProjection(projection, request) {
1663
1481
  resourceIds: request.resourceIds,
1664
1482
  partySize: normalizedPartySize
1665
1483
  });
1666
- const candidateRangeCache = /* @__PURE__ */ new Map();
1484
+ const candidateRangeCache = new Map();
1667
1485
  const evaluationContext = buildEvaluationContext({
1668
1486
  projection,
1669
1487
  evaluatedAt,
1670
1488
  resourceIds: request.resourceIds
1671
1489
  });
1672
- if (request.target === "time") {
1490
+ if (request.target === 'time') {
1673
1491
  const candidates = dedupeProductRanges(request.candidates);
1674
- const candidatesByProduct = /* @__PURE__ */ new Map();
1675
- candidates.forEach((candidate) => {
1492
+ const candidatesByProduct = new Map();
1493
+ candidates.forEach(candidate => {
1676
1494
  const key = String(candidate.productId);
1677
- candidatesByProduct.set(key, [...candidatesByProduct.get(key) || [], candidate]);
1495
+ candidatesByProduct.set(key, [...(candidatesByProduct.get(key) || []), candidate]);
1678
1496
  });
1679
- const data2 = projection.products.filter((product) => !productIdSet || productIdSet.has(String(product.id))).map((product) => ({
1497
+ const data = projection.products.filter(product => !productIdSet || productIdSet.has(String(product.id))).map(product => ({
1680
1498
  productId: product.id,
1681
1499
  title: product.title,
1682
- times: (candidatesByProduct.get(String(product.id)) || []).map((candidate) => evaluateCandidate(projection, candidate, {
1500
+ times: (candidatesByProduct.get(String(product.id)) || []).map(candidate => evaluateCandidate(projection, candidate, {
1683
1501
  partySize: normalizedPartySize,
1684
1502
  resourceIds: request.resourceIds,
1685
1503
  evaluatedAt,
@@ -1689,25 +1507,25 @@ function queryAvailabilityProjection(projection, request) {
1689
1507
  candidateRangeCache,
1690
1508
  evaluationContext
1691
1509
  }).item)
1692
- })).filter((group) => group.times.length > 0);
1510
+ })).filter(group => group.times.length > 0);
1693
1511
  return {
1694
- target: "time",
1512
+ target: 'time',
1695
1513
  evaluatedAt,
1696
1514
  snapshotId: projection.meta.snapshotId,
1697
- data: data2
1515
+ data
1698
1516
  };
1699
1517
  }
1700
- if (request.target === "resource") {
1518
+ if (request.target === 'resource') {
1701
1519
  const selectedRanges = dedupeProductRanges(request.selectedRanges);
1702
- const rangesByProduct = /* @__PURE__ */ new Map();
1703
- selectedRanges.forEach((candidate) => {
1520
+ const rangesByProduct = new Map();
1521
+ selectedRanges.forEach(candidate => {
1704
1522
  const key = String(candidate.productId);
1705
- rangesByProduct.set(key, [...rangesByProduct.get(key) || [], candidate]);
1523
+ rangesByProduct.set(key, [...(rangesByProduct.get(key) || []), candidate]);
1706
1524
  });
1707
- const data2 = projection.products.filter((product) => !productIdSet || productIdSet.has(String(product.id))).map((product) => ({
1525
+ const data = projection.products.filter(product => !productIdSet || productIdSet.has(String(product.id))).map(product => ({
1708
1526
  productId: product.id,
1709
1527
  title: product.title,
1710
- ranges: (rangesByProduct.get(String(product.id)) || []).map((candidate) => {
1528
+ ranges: (rangesByProduct.get(String(product.id)) || []).map(candidate => {
1711
1529
  const evaluated = evaluateCandidate(projection, candidate, {
1712
1530
  partySize: normalizedPartySize,
1713
1531
  resourceIds: request.resourceIds,
@@ -1726,12 +1544,12 @@ function queryAvailabilityProjection(projection, request) {
1726
1544
  requirementGroups: evaluated.requirementGroups
1727
1545
  };
1728
1546
  })
1729
- })).filter((group) => group.ranges.length > 0);
1547
+ })).filter(group => group.ranges.length > 0);
1730
1548
  return {
1731
- target: "resource",
1549
+ target: 'resource',
1732
1550
  evaluatedAt,
1733
1551
  snapshotId: projection.meta.snapshotId,
1734
- data: data2
1552
+ data
1735
1553
  };
1736
1554
  }
1737
1555
  const dateRange = resolveProjectionDateRange(projection, request.dateRange);
@@ -1741,17 +1559,17 @@ function queryAvailabilityProjection(projection, request) {
1741
1559
  resourceIds: request.resourceIds,
1742
1560
  partySize: normalizedPartySize
1743
1561
  });
1744
- const candidateGroupMap = new Map(candidateGroups.map((group) => [String(group.productId), group]));
1745
- if (request.target === "date") {
1746
- const data2 = projection.products.filter((product) => !productIdSet || productIdSet.has(String(product.id))).map((product) => {
1562
+ const candidateGroupMap = new Map(candidateGroups.map(group => [String(group.productId), group]));
1563
+ if (request.target === 'date') {
1564
+ const data = projection.products.filter(product => !productIdSet || productIdSet.has(String(product.id))).map(product => {
1747
1565
  const group = candidateGroupMap.get(String(product.id)) || {
1748
1566
  productId: product.id,
1749
1567
  title: product.title,
1750
1568
  kind: product.kind,
1751
1569
  ranges: []
1752
1570
  };
1753
- const byDate = /* @__PURE__ */ new Map();
1754
- group.ranges.forEach((candidate) => {
1571
+ const byDate = new Map();
1572
+ group.ranges.forEach(candidate => {
1755
1573
  const evaluated = evaluateCandidateSummary(projection, candidate, {
1756
1574
  partySize: normalizedPartySize,
1757
1575
  resourceIds: request.resourceIds,
@@ -1768,30 +1586,30 @@ function queryAvailabilityProjection(projection, request) {
1768
1586
  maxPartySize: 0,
1769
1587
  remainingCapacity: 0,
1770
1588
  maxCapacity: 0,
1771
- reasonCodeSet: /* @__PURE__ */ new Set(),
1589
+ reasonCodeSet: new Set(),
1772
1590
  statusAggregate: createStatusAggregate()
1773
1591
  };
1774
1592
  aggregate.totalCount += 1;
1775
- if (evaluated.status === "available" || evaluated.status === "limited") {
1593
+ if (evaluated.status === 'available' || evaluated.status === 'limited') {
1776
1594
  aggregate.availableCount += 1;
1777
1595
  }
1778
1596
  aggregate.maxPartySize = Math.max(aggregate.maxPartySize, evaluated.maxPartySize);
1779
1597
  aggregate.remainingCapacity += evaluated.remainingCapacity;
1780
1598
  aggregate.maxCapacity += evaluated.maxCapacity;
1781
1599
  pushStatusAggregate(aggregate.statusAggregate, evaluated.status);
1782
- evaluated.reasonCodes.forEach((reasonCode) => aggregate.reasonCodeSet.add(reasonCode));
1600
+ evaluated.reasonCodes.forEach(reasonCode => aggregate.reasonCodeSet.add(reasonCode));
1783
1601
  byDate.set(candidate.date, aggregate);
1784
1602
  });
1785
- const dates = enumerateDates(dateRange.startDate, dateRange.endDate).map((date) => {
1603
+ const dates = enumerateDates(dateRange.startDate, dateRange.endDate).map(date => {
1786
1604
  const aggregate = byDate.get(date);
1787
1605
  return {
1788
1606
  date,
1789
- status: aggregate ? summarizeStatusAggregate(aggregate.statusAggregate) : "unavailable",
1790
- availableCount: (aggregate == null ? void 0 : aggregate.availableCount) || 0,
1791
- totalCount: (aggregate == null ? void 0 : aggregate.totalCount) || 0,
1792
- maxPartySize: (aggregate == null ? void 0 : aggregate.maxPartySize) || 0,
1793
- remainingCapacity: (aggregate == null ? void 0 : aggregate.remainingCapacity) || 0,
1794
- maxCapacity: (aggregate == null ? void 0 : aggregate.maxCapacity) || 0,
1607
+ status: aggregate ? summarizeStatusAggregate(aggregate.statusAggregate) : 'unavailable',
1608
+ availableCount: aggregate?.availableCount || 0,
1609
+ totalCount: aggregate?.totalCount || 0,
1610
+ maxPartySize: aggregate?.maxPartySize || 0,
1611
+ remainingCapacity: aggregate?.remainingCapacity || 0,
1612
+ maxCapacity: aggregate?.maxCapacity || 0,
1795
1613
  reasonCodes: aggregate ? Array.from(aggregate.reasonCodeSet) : []
1796
1614
  };
1797
1615
  });
@@ -1802,13 +1620,13 @@ function queryAvailabilityProjection(projection, request) {
1802
1620
  };
1803
1621
  });
1804
1622
  return {
1805
- target: "date",
1623
+ target: 'date',
1806
1624
  evaluatedAt,
1807
1625
  snapshotId: projection.meta.snapshotId,
1808
- data: data2
1626
+ data
1809
1627
  };
1810
1628
  }
1811
- const data = projection.products.filter((product) => !productIdSet || productIdSet.has(String(product.id))).map((product) => {
1629
+ const data = projection.products.filter(product => !productIdSet || productIdSet.has(String(product.id))).map(product => {
1812
1630
  const group = candidateGroupMap.get(String(product.id)) || {
1813
1631
  productId: product.id,
1814
1632
  title: product.title,
@@ -1816,12 +1634,12 @@ function queryAvailabilityProjection(projection, request) {
1816
1634
  ranges: []
1817
1635
  };
1818
1636
  const productStatusAggregate = createStatusAggregate();
1819
- const productReasonCodeSet = /* @__PURE__ */ new Set();
1820
- const dateStatusMap = /* @__PURE__ */ new Map();
1637
+ const productReasonCodeSet = new Set();
1638
+ const dateStatusMap = new Map();
1821
1639
  let availableTimeCount = 0;
1822
1640
  let totalTimeCount = 0;
1823
1641
  let maxPartySize = 0;
1824
- group.ranges.forEach((candidate) => {
1642
+ group.ranges.forEach(candidate => {
1825
1643
  const evaluated = evaluateCandidateSummary(projection, candidate, {
1826
1644
  partySize: normalizedPartySize,
1827
1645
  resourceIds: request.resourceIds,
@@ -1833,19 +1651,19 @@ function queryAvailabilityProjection(projection, request) {
1833
1651
  evaluationContext
1834
1652
  });
1835
1653
  totalTimeCount += 1;
1836
- if (evaluated.status === "available" || evaluated.status === "limited") {
1654
+ if (evaluated.status === 'available' || evaluated.status === 'limited') {
1837
1655
  availableTimeCount += 1;
1838
1656
  }
1839
1657
  maxPartySize = Math.max(maxPartySize, evaluated.maxPartySize);
1840
1658
  pushStatusAggregate(productStatusAggregate, evaluated.status);
1841
- evaluated.reasonCodes.forEach((reasonCode) => productReasonCodeSet.add(reasonCode));
1659
+ evaluated.reasonCodes.forEach(reasonCode => productReasonCodeSet.add(reasonCode));
1842
1660
  const dateAggregate = dateStatusMap.get(candidate.date) || createStatusAggregate();
1843
1661
  pushStatusAggregate(dateAggregate, evaluated.status);
1844
1662
  dateStatusMap.set(candidate.date, dateAggregate);
1845
1663
  });
1846
- const availableDateCount = Array.from(dateStatusMap.values()).filter((aggregate) => {
1847
- const status2 = summarizeStatusAggregate(aggregate);
1848
- return status2 === "available" || status2 === "limited";
1664
+ const availableDateCount = Array.from(dateStatusMap.values()).filter(aggregate => {
1665
+ const status = summarizeStatusAggregate(aggregate);
1666
+ return status === 'available' || status === 'limited';
1849
1667
  }).length;
1850
1668
  const totalDateCount = dateStatusMap.size;
1851
1669
  const status = summarizeStatusAggregate(productStatusAggregate);
@@ -1867,22 +1685,34 @@ function queryAvailabilityProjection(projection, request) {
1867
1685
  };
1868
1686
  });
1869
1687
  return {
1870
- target: "product",
1688
+ target: 'product',
1871
1689
  evaluatedAt,
1872
1690
  snapshotId: projection.meta.snapshotId,
1873
1691
  data
1874
1692
  };
1875
1693
  }
1694
+ function summarizeDateStatus(statuses) {
1695
+ if (!statuses.length) return 'unavailable';
1696
+ if (statuses.some(status => status === 'available')) return 'available';
1697
+ if (statuses.some(status => status === 'limited')) return 'limited';
1698
+ if (statuses.some(status => status === 'full')) return 'full';
1699
+ if (statuses.every(status => status === 'past')) return 'past';
1700
+ return 'unavailable';
1701
+ }
1702
+ function summarizeProductStatus(statuses) {
1703
+ if (!statuses.length) return 'unavailable';
1704
+ if (statuses.some(status => status === 'available')) return 'available';
1705
+ if (statuses.some(status => status === 'limited')) return 'limited';
1706
+ if (statuses.some(status => status === 'full')) return 'full';
1707
+ if (statuses.every(status => status === 'past')) return 'past';
1708
+ return 'unavailable';
1709
+ }
1876
1710
  function validateAvailabilitySelection(projection, params) {
1877
1711
  const normalizedPartySize = normalizePartySize(params.partySize);
1878
1712
  const normalizedBookingCount = normalizeBookingCount(params.bookingCount);
1879
1713
  assertRangesWithinProjection(projection, [params.candidate]);
1880
1714
  const productIndex = buildProductIndex(projection);
1881
- const evaluatedAt = formatDateTime(
1882
- (0, import_localClock.localDateToScalar)(/* @__PURE__ */ new Date()),
1883
- projection.meta.timezone,
1884
- (0, import_localClock.extractFixedOffsetMinutes)(projection.meta.now) ?? (0, import_localClock.captureRuntimeOffsetMinutes)()
1885
- );
1715
+ const evaluatedAt = formatDateTime((0, _localClock.localDateToScalar)(new Date()), projection.meta.timezone, (0, _localClock.extractFixedOffsetMinutes)(projection.meta.now) ?? (0, _localClock.captureRuntimeOffsetMinutes)());
1886
1716
  const evaluated = evaluateCandidate(projection, params.candidate, {
1887
1717
  partySize: normalizedPartySize,
1888
1718
  evaluatedAt,
@@ -1894,7 +1724,7 @@ function validateAvailabilitySelection(projection, params) {
1894
1724
  projection,
1895
1725
  partySize: normalizedPartySize
1896
1726
  }),
1897
- candidateRangeCache: /* @__PURE__ */ new Map(),
1727
+ candidateRangeCache: new Map(),
1898
1728
  evaluationContext: buildEvaluationContext({
1899
1729
  projection,
1900
1730
  evaluatedAt,
@@ -1905,27 +1735,27 @@ function validateAvailabilitySelection(projection, params) {
1905
1735
  if (evaluated.gate) {
1906
1736
  conflicts.push({
1907
1737
  code: evaluated.gate.conflictCode,
1908
- message: evaluated.gate.status === "past" ? "候选时间已过期" : "候选时间不满足预订时间门禁",
1738
+ message: evaluated.gate.status === 'past' ? '候选时间已过期' : '候选时间不满足预订时间门禁',
1909
1739
  reasonCodes: evaluated.gate.reasonCodes,
1910
1740
  productId: params.candidate.productId,
1911
1741
  candidateKey: params.candidate.key
1912
1742
  });
1913
1743
  return {
1914
1744
  ok: false,
1915
- status: evaluated.gate.status === "past" ? "past" : "conflict",
1745
+ status: evaluated.gate.status === 'past' ? 'past' : 'conflict',
1916
1746
  assignments: [],
1917
1747
  conflicts,
1918
1748
  availability: evaluated.item
1919
1749
  };
1920
1750
  }
1921
- const requiredGroups = evaluated.item.requirementGroups.filter((group) => group.required);
1922
- const findSelectionForGroup = (group) => params.requirementSelections.find((selection) => selection.requirementGroupId && selection.requirementGroupId === group.groupId || selection.formId !== void 0 && sameId(selection.formId, group.formId));
1923
- requiredGroups.forEach((group) => {
1751
+ const requiredGroups = evaluated.item.requirementGroups.filter(group => group.required);
1752
+ const findSelectionForGroup = group => params.requirementSelections.find(selection => selection.requirementGroupId && selection.requirementGroupId === group.groupId || selection.formId !== undefined && sameId(selection.formId, group.formId));
1753
+ requiredGroups.forEach(group => {
1924
1754
  const selection = findSelectionForGroup(group);
1925
- const selectedCount = new Set(((selection == null ? void 0 : selection.resourceIds) || []).map(String)).size;
1755
+ const selectedCount = new Set((selection?.resourceIds || []).map(String)).size;
1926
1756
  if (selectedCount < group.min) {
1927
1757
  conflicts.push({
1928
- code: "missing_resource",
1758
+ code: 'missing_resource',
1929
1759
  message: `资源组 ${group.groupId} 缺少已选资源`,
1930
1760
  productId: params.candidate.productId,
1931
1761
  requirementGroupId: group.groupId,
@@ -1936,7 +1766,7 @@ function validateAvailabilitySelection(projection, params) {
1936
1766
  }
1937
1767
  if (selectedCount > group.max) {
1938
1768
  conflicts.push({
1939
- code: "invalid_selection_count",
1769
+ code: 'invalid_selection_count',
1940
1770
  message: `资源组 ${group.groupId} 已选资源数量超出上限`,
1941
1771
  productId: params.candidate.productId,
1942
1772
  requirementGroupId: group.groupId,
@@ -1945,10 +1775,9 @@ function validateAvailabilitySelection(projection, params) {
1945
1775
  });
1946
1776
  return;
1947
1777
  }
1948
- if (group.status === "available" || group.status === "limited")
1949
- return;
1778
+ if (group.status === 'available' || group.status === 'limited') return;
1950
1779
  conflicts.push({
1951
- code: group.status === "past" ? "past" : group.status === "full" ? "resource_full" : "missing_resource",
1780
+ code: group.status === 'past' ? 'past' : group.status === 'full' ? 'resource_full' : 'missing_resource',
1952
1781
  message: `资源组 ${group.groupId} 当前不可提交`,
1953
1782
  productId: params.candidate.productId,
1954
1783
  requirementGroupId: group.groupId,
@@ -1956,12 +1785,12 @@ function validateAvailabilitySelection(projection, params) {
1956
1785
  candidateKey: params.candidate.key
1957
1786
  });
1958
1787
  });
1959
- params.requirementSelections.forEach((selection) => {
1960
- const matchedGroup = evaluated.item.requirementGroups.find((group) => selection.requirementGroupId ? group.groupId === selection.requirementGroupId : sameId(group.formId, selection.formId));
1788
+ params.requirementSelections.forEach(selection => {
1789
+ const matchedGroup = evaluated.item.requirementGroups.find(group => selection.requirementGroupId ? group.groupId === selection.requirementGroupId : sameId(group.formId, selection.formId));
1961
1790
  if (!matchedGroup) {
1962
1791
  conflicts.push({
1963
- code: "invalid_requirement_group",
1964
- message: "选择的资源组不属于当前商品",
1792
+ code: 'invalid_requirement_group',
1793
+ message: '选择的资源组不属于当前商品',
1965
1794
  productId: params.candidate.productId,
1966
1795
  requirementGroupId: selection.requirementGroupId,
1967
1796
  formId: selection.formId,
@@ -1972,7 +1801,7 @@ function validateAvailabilitySelection(projection, params) {
1972
1801
  const selectedCount = new Set(selection.resourceIds.map(String)).size;
1973
1802
  if (!matchedGroup.required && (selectedCount < matchedGroup.min || selectedCount > matchedGroup.max)) {
1974
1803
  conflicts.push({
1975
- code: "invalid_selection_count",
1804
+ code: 'invalid_selection_count',
1976
1805
  message: `资源组 ${matchedGroup.groupId} 已选资源数量不满足 ${matchedGroup.min}-${matchedGroup.max}`,
1977
1806
  productId: params.candidate.productId,
1978
1807
  requirementGroupId: matchedGroup.groupId,
@@ -1980,23 +1809,20 @@ function validateAvailabilitySelection(projection, params) {
1980
1809
  candidateKey: params.candidate.key
1981
1810
  });
1982
1811
  }
1983
- selection.resourceIds.forEach((resourceId) => {
1984
- const option = matchedGroup == null ? void 0 : matchedGroup.options.find((item) => sameId(item.resourceId, resourceId));
1985
- const matchedCell = option == null ? void 0 : option.matchedCell;
1986
- const remainingCapacity = matchedCell == null ? void 0 : matchedCell.remainingCapacity;
1987
- if (option && (option.status === "available" || option.status === "limited") && remainingCapacity !== void 0) {
1812
+ selection.resourceIds.forEach(resourceId => {
1813
+ const option = matchedGroup?.options.find(item => sameId(item.resourceId, resourceId));
1814
+ const matchedCell = option?.matchedCell;
1815
+ const remainingCapacity = matchedCell?.remainingCapacity;
1816
+ if (option && (option.status === 'available' || option.status === 'limited') && remainingCapacity !== undefined) {
1988
1817
  const capacityPerBooking = normalizedPartySize ?? 1;
1989
- const availableQuantity = Math.max(
1990
- 0,
1991
- Math.floor(remainingCapacity / capacityPerBooking)
1992
- );
1818
+ const availableQuantity = Math.max(0, Math.floor(remainingCapacity / capacityPerBooking));
1993
1819
  if (normalizedBookingCount > availableQuantity) {
1994
1820
  conflicts.push({
1995
- code: "resource_capacity_exceeded",
1821
+ code: 'resource_capacity_exceeded',
1996
1822
  message: `所选资源「${option.resourceName || String(resourceId)}」当前最多可预订 ${availableQuantity} 个`,
1997
1823
  productId: params.candidate.productId,
1998
- requirementGroupId: matchedGroup == null ? void 0 : matchedGroup.groupId,
1999
- formId: matchedGroup == null ? void 0 : matchedGroup.formId,
1824
+ requirementGroupId: matchedGroup?.groupId,
1825
+ formId: matchedGroup?.formId,
2000
1826
  resourceId,
2001
1827
  resourceName: option.resourceName,
2002
1828
  candidateKey: params.candidate.key,
@@ -2008,13 +1834,13 @@ function validateAvailabilitySelection(projection, params) {
2008
1834
  return;
2009
1835
  }
2010
1836
  }
2011
- if (!option || option.status !== "available" && option.status !== "limited") {
1837
+ if (!option || option.status !== 'available' && option.status !== 'limited') {
2012
1838
  conflicts.push({
2013
- code: "resource_unavailable",
1839
+ code: 'resource_unavailable',
2014
1840
  message: `资源 ${String(resourceId)} 当前不可提交`,
2015
1841
  productId: params.candidate.productId,
2016
- requirementGroupId: matchedGroup == null ? void 0 : matchedGroup.groupId,
2017
- formId: matchedGroup == null ? void 0 : matchedGroup.formId,
1842
+ requirementGroupId: matchedGroup?.groupId,
1843
+ formId: matchedGroup?.formId,
2018
1844
  resourceId,
2019
1845
  candidateKey: params.candidate.key
2020
1846
  });
@@ -2022,17 +1848,10 @@ function validateAvailabilitySelection(projection, params) {
2022
1848
  });
2023
1849
  });
2024
1850
  return {
2025
- ok: conflicts.length === 0 && (evaluated.item.status === "available" || evaluated.item.status === "limited"),
2026
- status: conflicts.length === 0 ? evaluated.item.status : "conflict",
1851
+ ok: conflicts.length === 0 && (evaluated.item.status === 'available' || evaluated.item.status === 'limited'),
1852
+ status: conflicts.length === 0 ? evaluated.item.status : 'conflict',
2027
1853
  assignments: evaluated.assignments,
2028
1854
  conflicts,
2029
1855
  availability: evaluated.item
2030
1856
  };
2031
- }
2032
- // Annotate the CommonJS export names for ESM import in node:
2033
- 0 && (module.exports = {
2034
- createAvailabilityProjection,
2035
- getProductTimeRanges,
2036
- queryAvailabilityProjection,
2037
- validateAvailabilitySelection
2038
- });
1857
+ }