@pisell/pisellos 2.3.143 → 2.3.145

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 (33) hide show
  1. package/dist/modules/BookingContext/utils/productExtend.d.ts +7 -0
  2. package/dist/modules/BookingContext/utils/productExtend.js +62 -18
  3. package/dist/modules/Order/index.d.ts +2 -1
  4. package/dist/modules/Order/index.js +380 -198
  5. package/dist/modules/Order/types.d.ts +9 -0
  6. package/dist/modules/Order/utils.d.ts +5 -3
  7. package/dist/modules/Order/utils.js +32 -11
  8. package/dist/modules/ResourcePlanner/planner.d.ts +2 -0
  9. package/dist/modules/ResourcePlanner/planner.js +327 -22
  10. package/dist/modules/ResourcePlanner/types.d.ts +29 -0
  11. package/dist/solution/BookingByStep/index.d.ts +1 -1
  12. package/dist/solution/BookingTicket/utils/addProductDecision.d.ts +0 -1
  13. package/dist/solution/BookingTicket/utils/addProductDecision.js +1 -1
  14. package/dist/solution/UnifiedBookingSales/index.d.ts +3 -0
  15. package/dist/solution/UnifiedBookingSales/index.js +432 -163
  16. package/dist/solution/UnifiedBookingSales/types.d.ts +14 -2
  17. package/lib/modules/BookingContext/utils/productExtend.d.ts +7 -0
  18. package/lib/modules/BookingContext/utils/productExtend.js +45 -2
  19. package/lib/modules/Order/index.d.ts +2 -1
  20. package/lib/modules/Order/index.js +92 -10
  21. package/lib/modules/Order/types.d.ts +9 -0
  22. package/lib/modules/Order/utils.d.ts +5 -3
  23. package/lib/modules/Order/utils.js +29 -10
  24. package/lib/modules/ResourcePlanner/planner.d.ts +2 -0
  25. package/lib/modules/ResourcePlanner/planner.js +247 -7
  26. package/lib/modules/ResourcePlanner/types.d.ts +29 -0
  27. package/lib/solution/BookingByStep/index.d.ts +1 -1
  28. package/lib/solution/BookingTicket/utils/addProductDecision.d.ts +0 -1
  29. package/lib/solution/BookingTicket/utils/addProductDecision.js +1 -1
  30. package/lib/solution/UnifiedBookingSales/index.d.ts +3 -0
  31. package/lib/solution/UnifiedBookingSales/index.js +220 -27
  32. package/lib/solution/UnifiedBookingSales/types.d.ts +14 -2
  33. package/package.json +1 -1
@@ -445,6 +445,15 @@ export interface AddProductToOrderOptions {
445
445
  disableMerge?: boolean;
446
446
  /** 商品行 UID 落定后,由 OrderModule 原子绑定到该行的 Sales Notes 草稿。 */
447
447
  salesNotes?: SalesNoteDraftInput[];
448
+ /**
449
+ * 自动预约加购的同步提交门禁;抛错时不发布本次新增。
450
+ * 带门禁时先在私有草稿中准备全部商品/booking,异步等待后及原子发布前重新调用。
451
+ * 门禁须校验当前订单身份/版本和真实时间;发布前不会因本次拆行改变订单版本。
452
+ * 不传时保留原有人工加购行为。
453
+ */
454
+ availabilityCommitGuard?: () => void;
455
+ /** 发布后的订单/业务归属检查;只用于阻止旧异步收尾写入新单,不再校验截止或 revision。 */
456
+ availabilityCommitTargetGuard?: () => boolean;
448
457
  }
449
458
  /**
450
459
  * 删除商品后的可选位置保持策略。
@@ -121,8 +121,9 @@ export declare function getBundleSellingMagnitude(bundle: Record<string, any> |
121
121
  * = `metadata.main_product_original_price` + Σ(bundle 原价 × num)。
122
122
  * - `metadata.source_product_price`:主商品/variant 基础价(已应用报价单),不含 option、
123
123
  * 不含折扣。**权威源**。
124
- * - `metadata.main_product_original_price` = source + Σ(option.price × option.num)(含 option、不含折扣)。
125
- * - `metadata.main_product_selling_price` = main_original − 主商品券 per-unit amount(含 option、含折扣)。
124
+ * - `product_sku.option[].add_price` 保留单规格折前单价;Rules 的 option.price 只用于本次计算。
125
+ * - `metadata.main_product_original_price` = source + Σ(option.add_price × option.num)(含 option、不含折扣)。
126
+ * - `metadata.main_product_selling_price` = main_original − 行内 per-unit amount(含主商品及 option 优惠)。
126
127
  * - `payment_price`:出站 payload 中为行 composite 已减整单折扣均摊价(无折扣时与 `selling_price` 同义)。
127
128
  *
128
129
  * Rules 钩子契约(hook_adapt 方案,Rules 内部保持不变):
@@ -131,8 +132,9 @@ export declare function getBundleSellingMagnitude(bundle: Record<string, any> |
131
132
  * `getProductOriginTotalPrice` 会自行叠加 option + bundle,喂 source 可避免 option 双加。
132
133
  * - `getProduct.total` = `selling_price × num`(行 composite × num),与 Rules 内部产出的
133
134
  * total(source + option + bundle)对齐。
135
+ * - `getProduct.options` 用单规格原价参与重算;主商品折后价从 main_selling 减去 option 折后合计反推。
134
136
  * - `setProduct` 反向流程:把 Rules 返回的 source-level `main_product_selling_price` / `price`
135
- * 加回 Σoptions,重新合成含 option 的 main_product_* 并派生 composite。
137
+ * 加回单规格原价合计,再减去 optionDiscountAmount;保留订单单规格原价供取消折扣及后续重算。
136
138
  * 优先级:
137
139
  * 1. `values.main_product_selling_price`(券应用分支,per-unit 券后 source-level 主价)
138
140
  * 2. `values.price`(还原分支 `restoredPrice`、good_pass 归零)
@@ -588,8 +588,9 @@ export function getBundleSellingMagnitude(bundle) {
588
588
  * = `metadata.main_product_original_price` + Σ(bundle 原价 × num)。
589
589
  * - `metadata.source_product_price`:主商品/variant 基础价(已应用报价单),不含 option、
590
590
  * 不含折扣。**权威源**。
591
- * - `metadata.main_product_original_price` = source + Σ(option.price × option.num)(含 option、不含折扣)。
592
- * - `metadata.main_product_selling_price` = main_original − 主商品券 per-unit amount(含 option、含折扣)。
591
+ * - `product_sku.option[].add_price` 保留单规格折前单价;Rules 的 option.price 只用于本次计算。
592
+ * - `metadata.main_product_original_price` = source + Σ(option.add_price × option.num)(含 option、不含折扣)。
593
+ * - `metadata.main_product_selling_price` = main_original − 行内 per-unit amount(含主商品及 option 优惠)。
593
594
  * - `payment_price`:出站 payload 中为行 composite 已减整单折扣均摊价(无折扣时与 `selling_price` 同义)。
594
595
  *
595
596
  * Rules 钩子契约(hook_adapt 方案,Rules 内部保持不变):
@@ -598,8 +599,9 @@ export function getBundleSellingMagnitude(bundle) {
598
599
  * `getProductOriginTotalPrice` 会自行叠加 option + bundle,喂 source 可避免 option 双加。
599
600
  * - `getProduct.total` = `selling_price × num`(行 composite × num),与 Rules 内部产出的
600
601
  * total(source + option + bundle)对齐。
602
+ * - `getProduct.options` 用单规格原价参与重算;主商品折后价从 main_selling 减去 option 折后合计反推。
601
603
  * - `setProduct` 反向流程:把 Rules 返回的 source-level `main_product_selling_price` / `price`
602
- * 加回 Σoptions,重新合成含 option 的 main_product_* 并派生 composite。
604
+ * 加回单规格原价合计,再减去 optionDiscountAmount;保留订单单规格原价供取消折扣及后续重算。
603
605
  * 优先级:
604
606
  * 1. `values.main_product_selling_price`(券应用分支,per-unit 券后 source-level 主价)
605
607
  * 2. `values.price`(还原分支 `restoredPrice`、good_pass 归零)
@@ -822,6 +824,15 @@ export function createDefaultOrderRulesHooks(getBookings) {
822
824
  var effectiveNum = Number(num) > 0 ? Number(num) : 1;
823
825
  return new Decimal(Number(totalLike) || 0).dividedBy(effectiveNum).toDecimalPlaces(2).toString();
824
826
  };
827
+
828
+ // optionDiscountAmount 已是每件主商品内的单规格优惠合计,不再乘商品数量。
829
+ var getOptionDiscountAmount = function getOptionDiscountAmount(discountList) {
830
+ return (discountList || []).reduce(function (total, discount) {
831
+ var _discount$metadata, _discount$metadata2;
832
+ if (discount !== null && discount !== void 0 && (_discount$metadata = discount.metadata) !== null && _discount$metadata !== void 0 && _discount$metadata.custom_product_bundle_map_id) return total;
833
+ return total.plus(Number(discount === null || discount === void 0 || (_discount$metadata2 = discount.metadata) === null || _discount$metadata2 === void 0 ? void 0 : _discount$metadata2.optionDiscountAmount) || 0);
834
+ }, new Decimal(0));
835
+ };
825
836
  var getBundleIdentity = function getBundleIdentity(bundle) {
826
837
  var _ref21, _bundle$bundle_id, _ref22, _ref23, _bundle$bundle_produc, _ref24, _ref25, _bundle$bundle_varian, _ref26, _bundle$bundle_group_;
827
838
  return [(_ref21 = (_bundle$bundle_id = bundle.bundle_id) !== null && _bundle$bundle_id !== void 0 ? _bundle$bundle_id : bundle.id) !== null && _ref21 !== void 0 ? _ref21 : '', (_ref22 = (_ref23 = (_bundle$bundle_produc = bundle.bundle_product_id) !== null && _bundle$bundle_produc !== void 0 ? _bundle$bundle_produc : bundle._bundle_product_id) !== null && _ref23 !== void 0 ? _ref23 : bundle.product_id) !== null && _ref22 !== void 0 ? _ref22 : '', (_ref24 = (_ref25 = (_bundle$bundle_varian = bundle.bundle_variant_id) !== null && _bundle$bundle_varian !== void 0 ? _bundle$bundle_varian : bundle.variant_id) !== null && _ref25 !== void 0 ? _ref25 : bundle.product_variant_id) !== null && _ref24 !== void 0 ? _ref24 : 0, (_ref26 = (_bundle$bundle_group_ = bundle.bundle_group_id) !== null && _bundle$bundle_group_ !== void 0 ? _bundle$bundle_group_ : bundle.group_id) !== null && _ref26 !== void 0 ? _ref26 : ''].join('|');
@@ -859,12 +870,14 @@ export function createDefaultOrderRulesHooks(getBookings) {
859
870
  var _product$metadata7, _product$metadata8, _product$metadata9, _metadataAny$_promoti;
860
871
  // source_product_price 是权威源;兜底链:source → mainSelling − options(反推)→ 行 selling_price
861
872
  var metadataAny = product.metadata || {};
862
- var optionSum = sumOptionUnitPrice(getProductSkuOptions(product));
873
+ var productOptions = getProductSkuOptions(product);
874
+ var optionSum = sumOptionUnitPrice(productOptions);
875
+ var optionSellingSum = optionSum.minus(getOptionDiscountAmount(product.discount_list));
863
876
  var sourcePrice;
864
877
  if (metadataAny.source_product_price !== undefined) {
865
878
  sourcePrice = String(metadataAny.source_product_price);
866
879
  } else if (metadataAny.main_product_selling_price !== undefined) {
867
- sourcePrice = new Decimal(Number(metadataAny.main_product_selling_price) || 0).minus(optionSum).toDecimalPlaces(2).toString();
880
+ sourcePrice = new Decimal(Number(metadataAny.main_product_selling_price) || 0).minus(optionSellingSum).toDecimalPlaces(2).toString();
868
881
  } else {
869
882
  var _product$selling_pric;
870
883
  sourcePrice = String((_product$selling_pric = product.selling_price) !== null && _product$selling_pric !== void 0 ? _product$selling_pric : 0);
@@ -895,6 +908,12 @@ export function createDefaultOrderRulesHooks(getBookings) {
895
908
  num: product.num || 1,
896
909
  discount_list: product.discount_list || [],
897
910
  bundle: product.product_bundle || [],
911
+ // 仅在 Rules 入参中映射 price,避免折后价污染订单的 add_price。
912
+ options: productOptions.map(function (option) {
913
+ return _objectSpread(_objectSpread({}, option), {}, {
914
+ price: option.add_price
915
+ });
916
+ }),
898
917
  booking_id: ((_product$metadata8 = product.metadata) === null || _product$metadata8 === void 0 ? void 0 : _product$metadata8.booking_id) || null,
899
918
  isClient: false,
900
919
  // 勿写死 false:否则 Rules 不会走 total≠origin_total 的手动价推断,
@@ -902,20 +921,22 @@ export function createDefaultOrderRulesHooks(getBookings) {
902
921
  isManualDiscount: resolveRulesManualDiscountFlag(metadataAny),
903
922
  holder_id: (_product$metadata9 = product.metadata) === null || _product$metadata9 === void 0 ? void 0 : _product$metadata9.holder_id,
904
923
  startDate: getProductStartDate(product),
905
- main_product_selling_price: metadataAny.main_product_selling_price !== undefined ? new Decimal(Number(metadataAny.main_product_selling_price) || 0).minus(optionSum).toDecimalPlaces(2).toString() : undefined,
924
+ main_product_selling_price: metadataAny.main_product_selling_price !== undefined ? new Decimal(Number(metadataAny.main_product_selling_price) || 0).minus(optionSellingSum).toDecimalPlaces(2).toString() : undefined,
906
925
  inPromotion: ((_metadataAny$_promoti = metadataAny._promotion) === null || _metadataAny$_promoti === void 0 ? void 0 : _metadataAny$_promoti.inPromotion) === true,
907
926
  _promotion: metadataAny._promotion
908
927
  };
909
928
  },
910
929
  setProduct: function setProduct(product, values) {
911
- var _ref32, _values$quantity, _ref33, _metadataAny$source_p, _product$product_sku, _values$discount_list, _values$quantity2;
930
+ var _ref32, _values$quantity, _ref33, _metadataAny$source_p, _values$discount_list, _product$product_sku, _values$quantity2;
912
931
  var nextNum = Number((_ref32 = (_values$quantity = values.quantity) !== null && _values$quantity !== void 0 ? _values$quantity : product.num) !== null && _ref32 !== void 0 ? _ref32 : 1) || 1;
913
932
  var metadataAny = product.metadata || {};
914
933
  var existedSource = (_ref33 = (_metadataAny$source_p = metadataAny.source_product_price) !== null && _metadataAny$source_p !== void 0 ? _metadataAny$source_p : product.selling_price) !== null && _ref33 !== void 0 ? _ref33 : '0';
915
934
  var nextOptions = getProductSkuOptions(product);
916
935
  var optionSum = sumOptionUnitPrice(nextOptions);
936
+ var nextDiscountList = (_values$discount_list = values.discount_list) !== null && _values$discount_list !== void 0 ? _values$discount_list : product.discount_list;
937
+ var optionSellingSum = optionSum.minus(getOptionDiscountAmount(nextDiscountList));
917
938
 
918
- // Rules 返回的 main_product_selling_price / price / total 均为 source-level。
939
+ // Rules 返回的 main_product_selling_price / price 为不含 option 的主商品价。
919
940
  // 优先级:main_product_selling_price(per-unit 券后 source)> price(还原/good_pass 分支)>
920
941
  // total/num(仅当 Rules 只给 total 时的兜底)> 保留当前 source。
921
942
  var nextSourceSellingPrice = values.main_product_selling_price !== undefined ? String(values.main_product_selling_price) : values.price !== undefined ? String(values.price) : values.total !== undefined ? toUnitPriceString(values.total, nextNum) : String(existedSource);
@@ -924,8 +945,8 @@ export function createDefaultOrderRulesHooks(getBookings) {
924
945
  // 缺省时保留现有 source(券前 = 当前 source)。
925
946
  var nextSourceOriginalPrice = values.original_price !== undefined ? String(values.original_price) : String(existedSource);
926
947
 
927
- // 含 option 的 main_product_*
928
- var nextMainSellingPrice = new Decimal(Number(nextSourceSellingPrice) || 0).plus(optionSum).toDecimalPlaces(2).toFixed(2);
948
+ // 成交价只加回 option 折后合计;原价仍使用订单中保存的 option 原价。
949
+ var nextMainSellingPrice = new Decimal(Number(nextSourceSellingPrice) || 0).plus(optionSellingSum).toDecimalPlaces(2).toFixed(2);
929
950
  var nextMainOriginalPrice = new Decimal(Number(nextSourceOriginalPrice) || 0).plus(optionSum).toDecimalPlaces(2).toFixed(2);
930
951
  var nextBundle = values.bundle !== undefined ? mergeRulesBundlePrices(product.product_bundle, values.bundle) : product.product_bundle;
931
952
  var composedSellingPrice = composeLinePrice({
@@ -945,7 +966,7 @@ export function createDefaultOrderRulesHooks(getBookings) {
945
966
  return _objectSpread(_objectSpread({}, product), {}, {
946
967
  selling_price: composedSellingPrice,
947
968
  original_price: finalOriginalPrice,
948
- discount_list: (_values$discount_list = values.discount_list) !== null && _values$discount_list !== void 0 ? _values$discount_list : product.discount_list,
969
+ discount_list: nextDiscountList,
949
970
  num: (_values$quantity2 = values.quantity) !== null && _values$quantity2 !== void 0 ? _values$quantity2 : product.num,
950
971
  product_bundle: nextBundle,
951
972
  metadata: _objectSpread(_objectSpread({}, metadataAny), {}, {
@@ -1,5 +1,7 @@
1
1
  import type { AvailabilityProjection, AvailabilityProjectionInput, AvailabilityQuery, AvailabilityQueryResult, AvailabilitySelectionValidationParams, AvailabilitySelectionValidationResult, GetProductTimeRangesRequest, GetProductTimeRangesRuntimeOptions, ProductTimeRangeGroup } from './types';
2
2
  export declare function createAvailabilityProjection(input: AvailabilityProjectionInput): AvailabilityProjection;
3
+ /** Resolve the label without changing the real calendar timestamp. */
4
+ export declare function getDurationBusinessDate(projection: AvailabilityProjection, startAt: string): string;
3
5
  export declare function getProductTimeRanges(projection: AvailabilityProjection, request?: GetProductTimeRangesRequest, runtimeOptions?: GetProductTimeRangesRuntimeOptions): ProductTimeRangeGroup[];
4
6
  export declare function queryAvailabilityProjection(projection: AvailabilityProjection, request: AvailabilityQuery, runtimeOptions?: GetProductTimeRangesRuntimeOptions): AvailabilityQueryResult;
5
7
  export declare function validateAvailabilitySelection(projection: AvailabilityProjection, params: AvailabilitySelectionValidationParams, runtimeOptions?: GetProductTimeRangesRuntimeOptions): AvailabilitySelectionValidationResult;
@@ -1,4 +1,8 @@
1
1
  function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
2
+ function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest(); }
3
+ function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
4
+ function _iterableToArrayLimit(r, l) { var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (null != t) { var e, n, i, u, a = [], f = !0, o = !1; try { if (i = (t = t.call(r)).next, 0 === l) { if (Object(t) !== t) return; f = !1; } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0); } catch (r) { o = !0, n = r; } finally { try { if (!f && null != t.return && (u = t.return(), Object(u) !== u)) return; } finally { if (o) throw n; } } return a; } }
5
+ function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
2
6
  function _toConsumableArray(arr) { return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread(); }
3
7
  function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
4
8
  function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
@@ -263,6 +267,18 @@ function evaluateCandidateTimeGate(params) {
263
267
  };
264
268
  }
265
269
  var policy = product.cutOffPolicy;
270
+ // Current duration items can be shown outside business hours. Their real
271
+ // interval must remain unavailable, including products with no resource groups.
272
+ if (evaluationContext.useDurationTimeGrid && product.kind === 'duration' && !projection.meta.defaults.operatingHours.windows.some(function (window) {
273
+ var range = normalizeAbsoluteRange(window);
274
+ return range && contains(range, candidateRange);
275
+ })) {
276
+ return {
277
+ status: 'unavailable',
278
+ reasonCodes: ['outside_operating_hours'],
279
+ conflictCode: 'booking_cutoff'
280
+ };
281
+ }
266
282
  if (!policy) return null;
267
283
  if (resolveCutOffPolicyIssue(policy)) {
268
284
  return {
@@ -299,8 +315,8 @@ function evaluateCandidateTimeGate(params) {
299
315
  // otherwise a minute refresh turns it into 0 capacity and incorrectly
300
316
  // advances a create flow to the next slot. The earlier `past` gate still
301
317
  // rejects the anchor once its booking range has ended.
302
- var isActivePosDurationAnchor = evaluationContext.useExactDurationCandidateMinute && product.kind === 'duration' && !product.isFlexibleDuration && evaluationContext.durationCandidateAnchorAtMs === candidateRange.start;
303
- var usesMinutePrecision = product.isFlexibleDuration || evaluationContext.useExactDurationCandidateMinute && product.kind === 'duration';
318
+ var isActivePosDurationAnchor = evaluationContext.useExactDurationCandidateMinute && !evaluationContext.enforceSaleCutoff && product.kind === 'duration' && !product.isFlexibleDuration && evaluationContext.durationCandidateAnchorAtMs === candidateRange.start;
319
+ var usesMinutePrecision = product.isFlexibleDuration || evaluationContext.useDurationTimeGrid || evaluationContext.useExactDurationCandidateMinute && product.kind === 'duration';
304
320
  var hasNotReachedStart = isActivePosDurationAnchor || (usesMinutePrecision ? floorToMinute(now) <= candidateRange.start : now < candidateRange.start);
305
321
  if (hasNotReachedStart) return null;
306
322
  return {
@@ -494,7 +510,7 @@ function normalizeResources(resources) {
494
510
  });
495
511
  }
496
512
  function normalizeOperatingHours(params) {
497
- var _params$input, _params$input2, _params$input3, _params$input4;
513
+ var _params$input, _params$input2, _params$input3, _params$input4, _params$input5, _params$input6;
498
514
  var fallbackWindows = enumerateDates(params.dateRange.startDate, params.dateRange.endDate).map(function (date) {
499
515
  var start = toDateTimeInDate(date, params.businessHours.startTime, params.timezoneName);
500
516
  var end = toDateTimeInDate(date, params.businessHours.endTime, params.timezoneName);
@@ -530,11 +546,43 @@ function normalizeOperatingHours(params) {
530
546
  };
531
547
  var windows = normalizeWindows(sourceWindows);
532
548
  var excludedWindows = normalizeWindows(((_params$input = params.input) === null || _params$input === void 0 ? void 0 : _params$input.excludedWindows) || []);
549
+ // Keep original dates and opening anchors separately: the availability
550
+ // windows below may be clipped at midnight or merged without segment identity.
551
+ var periodMap = new Map();
552
+ var originalPeriods = ((_params$input2 = params.input) === null || _params$input2 === void 0 ? void 0 : _params$input2.businessPeriods) || [].concat(_toConsumableArray(sourceWindows), _toConsumableArray(((_params$input3 = params.input) === null || _params$input3 === void 0 ? void 0 : _params$input3.excludedWindows) || [])).map(function (window) {
553
+ return {
554
+ businessDate: window.businessDate || window.startAt.slice(0, 10),
555
+ startAt: window.openingAnchorAt || window.startAt,
556
+ endAt: window.endAt
557
+ };
558
+ });
559
+ originalPeriods.forEach(function (period) {
560
+ var range = normalizeAbsoluteRange(period);
561
+ if (!range) return;
562
+ var previous = periodMap.get(period.businessDate);
563
+ periodMap.set(period.businessDate, {
564
+ start: previous ? Math.min(previous.start, range.start) : range.start,
565
+ end: previous ? Math.max(previous.end, range.end) : range.end
566
+ });
567
+ });
568
+ var businessPeriods = Array.from(periodMap, function (_ref) {
569
+ var _ref2 = _slicedToArray(_ref, 2),
570
+ businessDate = _ref2[0],
571
+ range = _ref2[1];
572
+ return {
573
+ businessDate: businessDate,
574
+ startAt: formatDateTime(range.start, params.timezoneName, params.fixedOffsetMinutes),
575
+ endAt: formatDateTime(range.end, params.timezoneName, params.fixedOffsetMinutes)
576
+ };
577
+ }).sort(function (left, right) {
578
+ return left.startAt.localeCompare(right.startAt);
579
+ });
533
580
  return _objectSpread({
534
- source: ((_params$input2 = params.input) === null || _params$input2 === void 0 ? void 0 : _params$input2.source) || params.fallbackSource,
535
- scheduleIds: uniqById(((_params$input3 = params.input) === null || _params$input3 === void 0 ? void 0 : _params$input3.scheduleIds) || []),
536
- missingScheduleIds: uniqById(((_params$input4 = params.input) === null || _params$input4 === void 0 ? void 0 : _params$input4.missingScheduleIds) || []),
537
- windows: windows
581
+ source: ((_params$input4 = params.input) === null || _params$input4 === void 0 ? void 0 : _params$input4.source) || params.fallbackSource,
582
+ scheduleIds: uniqById(((_params$input5 = params.input) === null || _params$input5 === void 0 ? void 0 : _params$input5.scheduleIds) || []),
583
+ missingScheduleIds: uniqById(((_params$input6 = params.input) === null || _params$input6 === void 0 ? void 0 : _params$input6.missingScheduleIds) || []),
584
+ windows: windows,
585
+ businessPeriods: businessPeriods
538
586
  }, excludedWindows.length > 0 ? {
539
587
  excludedWindows: excludedWindows
540
588
  } : {});
@@ -553,8 +601,8 @@ function matchesRequirementGroup(requirementGroup, resource, product) {
553
601
  return resource.capacity >= resolveRequiredCapacity(requirementGroup, product, partySize);
554
602
  }
555
603
  function resolveRequiredCapacity(requirementGroup, product, partySize) {
556
- var _ref, _ref2;
557
- return Math.max(1, Number((_ref = (_ref2 = partySize !== null && partySize !== void 0 ? partySize : requirementGroup.capacityRequired) !== null && _ref2 !== void 0 ? _ref2 : product.capacityRequired) !== null && _ref !== void 0 ? _ref : 1) || 1);
604
+ var _ref3, _ref4;
605
+ return Math.max(1, Number((_ref3 = (_ref4 = partySize !== null && partySize !== void 0 ? partySize : requirementGroup.capacityRequired) !== null && _ref4 !== void 0 ? _ref4 : product.capacityRequired) !== null && _ref3 !== void 0 ? _ref3 : 1) || 1);
558
606
  }
559
607
  function buildCellKey(params) {
560
608
  return [String(params.productId), params.groupId, String(params.resourceId), params.start, params.end].join(':');
@@ -608,8 +656,8 @@ function buildCells(projection) {
608
656
  boundaries.add(window.start);
609
657
  boundaries.add(window.end);
610
658
  });
611
- occupancies.forEach(function (_ref3) {
612
- var range = _ref3.range;
659
+ occupancies.forEach(function (_ref5) {
660
+ var range = _ref5.range;
613
661
  boundaries.add(range.start);
614
662
  boundaries.add(range.end);
615
663
  });
@@ -627,11 +675,11 @@ function buildCells(projection) {
627
675
  var isInsideWorkingWindow = windows.some(function (window) {
628
676
  return contains(window, segment);
629
677
  });
630
- var segmentOccupancies = isInsideWorkingWindow ? occupancies.filter(function (_ref4) {
631
- var range = _ref4.range;
678
+ var segmentOccupancies = isInsideWorkingWindow ? occupancies.filter(function (_ref6) {
679
+ var range = _ref6.range;
632
680
  return overlaps(range, segment);
633
- }).map(function (_ref5) {
634
- var occupancy = _ref5.occupancy;
681
+ }).map(function (_ref7) {
682
+ var occupancy = _ref7.occupancy;
635
683
  return occupancy;
636
684
  }) : [];
637
685
  var usedCapacity = segmentOccupancies.reduce(function (sum, occupancy) {
@@ -1108,6 +1156,211 @@ function createDurationTimeRangeWithEnd(product, start, bookingEnd, projection,
1108
1156
  scheduleRefs: []
1109
1157
  };
1110
1158
  }
1159
+
1160
+ /** Resolve the label without changing the real calendar timestamp. */
1161
+ export function getDurationBusinessDate(projection, startAt) {
1162
+ var start = toDateTime(startAt);
1163
+ var periods = projection.meta.defaults.operatingHours.businessPeriods || [];
1164
+ var period = _toConsumableArray(periods).reverse().find(function (item) {
1165
+ var range = normalizeAbsoluteRange(item);
1166
+ return range && range.start <= start && start < range.end;
1167
+ });
1168
+ return (period === null || period === void 0 ? void 0 : period.businessDate) || startAt.slice(0, 10);
1169
+ }
1170
+ function getAlignedDurationRanges(params) {
1171
+ var _request$rememberedSt, _request$editingStart;
1172
+ var projection = params.projection,
1173
+ product = params.product,
1174
+ request = params.request,
1175
+ runtimeOptions = params.runtimeOptions,
1176
+ dateRange = params.dateRange,
1177
+ operatingRanges = params.operatingRanges,
1178
+ baseRanges = params.baseRanges,
1179
+ resourceWindowIndex = params.resourceWindowIndex,
1180
+ fixedOffsetMinutes = params.fixedOffsetMinutes;
1181
+ var step = projection.meta.defaults.slotStepMinutes;
1182
+ var now = floorToMinute(toDateTime(runtimeOptions.evaluatedAt || projection.meta.durationCandidateAnchorAt));
1183
+ if (!Number.isFinite(now)) throw availabilityError('INVALID_TARGET_FILTER', 'evaluatedAt 必须是有效时间');
1184
+ var nowAt = formatDateTime(now, projection.meta.timezone, fixedOffsetMinutes);
1185
+ var currentBusinessDate = getDurationBusinessDate(projection, nowAt);
1186
+ var inRequestedDays = function inRequestedDays(date) {
1187
+ return date >= dateRange.startDate && date <= dateRange.endDate;
1188
+ };
1189
+ var periods = (projection.meta.defaults.operatingHours.businessPeriods || []).filter(function (period) {
1190
+ return inRequestedDays(period.businessDate);
1191
+ });
1192
+ // A prepared month includes tomorrow's windows too; the single-day UI must
1193
+ // receive the selected business cycle's end, not tomorrow's closing time.
1194
+ var operatingEndAt = dateRange.startDate === dateRange.endDate && periods.length ? periods.reduce(function (latest, period) {
1195
+ return period.endAt > latest ? period.endAt : latest;
1196
+ }, periods[0].endAt) : undefined;
1197
+ var ranges = [];
1198
+ var duration = resolveRequestedDurationMinutes(product, request);
1199
+ var addRange = function addRange(start, end, businessDate) {
1200
+ if (end <= start) return;
1201
+ var candidate = createDurationTimeRangeWithEnd(product, start, end, projection, fixedOffsetMinutes);
1202
+ candidate.businessDate = businessDate || getDurationBusinessDate(projection, candidate.startAt);
1203
+ if (inRequestedDays(candidate.businessDate)) ranges.push(candidate);
1204
+ };
1205
+ // A flexible end comes from resources that actually cover this exact start,
1206
+ // never from the next future window. Keep alternative real ends separate.
1207
+ var flexibleEnds = function flexibleEnds(start) {
1208
+ var _projection$meta$defa;
1209
+ var operating = operatingRanges.find(function (range) {
1210
+ return range.start <= start && start < range.end;
1211
+ });
1212
+ if (!operating) return [];
1213
+ var businessDate = getDurationBusinessDate(projection, formatDateTime(start, projection.meta.timezone, fixedOffsetMinutes));
1214
+ var period = (_projection$meta$defa = projection.meta.defaults.operatingHours.businessPeriods) === null || _projection$meta$defa === void 0 ? void 0 : _projection$meta$defa.find(function (item) {
1215
+ return item.businessDate === businessDate;
1216
+ });
1217
+ var operatingEnd = Math.min(operating.end, period ? toDateTime(period.endAt) : operating.end);
1218
+ var requiredGroups = (product.requirementGroups || []).filter(function (group) {
1219
+ return group.required !== false;
1220
+ });
1221
+ var groups = requiredGroups.map(function (group) {
1222
+ return projection.resources.filter(function (resource) {
1223
+ return matchesRequirementGroup(group, resource, product, request.partySize);
1224
+ }).filter(function (resource) {
1225
+ var _request$resourceIds;
1226
+ return !((_request$resourceIds = request.resourceIds) !== null && _request$resourceIds !== void 0 && _request$resourceIds.length) || request.resourceIds.some(function (id) {
1227
+ return sameId(id, resource.id);
1228
+ });
1229
+ }).flatMap(function (resource) {
1230
+ return (resourceWindowIndex.get(String(resource.id)) || []).filter(function (range) {
1231
+ return range.start <= start && start < range.end;
1232
+ }).map(function (range) {
1233
+ return Math.min(range.end, operatingEnd);
1234
+ });
1235
+ });
1236
+ });
1237
+ if (groups.some(function (ends) {
1238
+ return ends.length === 0;
1239
+ })) return [];
1240
+ if (!groups.length) return [operatingEnd];
1241
+ return Array.from(new Set(groups.flat())).filter(function (end) {
1242
+ return groups.every(function (ends) {
1243
+ return ends.some(function (availableEnd) {
1244
+ return availableEnd >= end;
1245
+ });
1246
+ });
1247
+ }).sort(function (left, right) {
1248
+ return left - right;
1249
+ });
1250
+ };
1251
+ if (product.isFlexibleDuration) {
1252
+ // Preserve the existing continuous-window UI, with the business-day label.
1253
+ periods.forEach(function (period) {
1254
+ var periodRange = normalizeAbsoluteRange(period);
1255
+ baseRanges.forEach(function (range) {
1256
+ var start = Math.max(range.start, periodRange.start, now);
1257
+ var end = Math.min(range.end, periodRange.end);
1258
+ if (start < end) addRange(start, end, period.businessDate);
1259
+ });
1260
+ });
1261
+ } else {
1262
+ periods.forEach(function (period) {
1263
+ var periodRange = normalizeAbsoluteRange(period);
1264
+ var anchor = 60 % step === 0 ? toDateStart(period.businessDate) : periodRange.start;
1265
+ baseRanges.forEach(function (range) {
1266
+ var lower = Math.max(range.start, periodRange.start, now);
1267
+ var upper = Math.min(range.end, periodRange.end);
1268
+ var cursor = anchor + Math.ceil((lower - anchor) / (step * 60000)) * step * 60000;
1269
+ for (; cursor + duration * 60000 <= upper; cursor += step * 60000) {
1270
+ addRange(cursor, cursor + duration * 60000, period.businessDate);
1271
+ }
1272
+ });
1273
+ });
1274
+ }
1275
+ var unavailableCurrentTime;
1276
+ if (inRequestedDays(currentBusinessDate)) {
1277
+ if (product.isFlexibleDuration) {
1278
+ var ends = flexibleEnds(now);
1279
+ ends.forEach(function (end) {
1280
+ return addRange(now, end, currentBusinessDate);
1281
+ });
1282
+ if (!ends.length) {
1283
+ unavailableCurrentTime = {
1284
+ productId: product.id,
1285
+ startAt: nowAt,
1286
+ date: formatDate(now),
1287
+ businessDate: currentBusinessDate,
1288
+ startTime: formatTime(now),
1289
+ status: 'unavailable',
1290
+ reasonCodes: [operatingRanges.some(function (range) {
1291
+ return range.start <= now && now < range.end;
1292
+ }) ? 'outside_resource_window' : 'outside_operating_hours']
1293
+ };
1294
+ }
1295
+ } else {
1296
+ // Even outside a working window the fixed product has a real end. Query
1297
+ // reports unavailable; POS manual capabilities remain a separate policy.
1298
+ addRange(now, now + duration * 60000, currentBusinessDate);
1299
+ }
1300
+ }
1301
+ var rememberedAt = (_request$rememberedSt = request.rememberedStartAtByProductId) === null || _request$rememberedSt === void 0 ? void 0 : _request$rememberedSt[String(product.id)];
1302
+ if (rememberedAt !== undefined) {
1303
+ var start = toDateTime(rememberedAt);
1304
+ if (!Number.isFinite(start)) throw availabilityError('INVALID_TARGET_FILTER', '记忆开始时间必须是完整日期时间');
1305
+ if (product.isFlexibleDuration) {
1306
+ flexibleEnds(start).forEach(function (end) {
1307
+ return addRange(start, end);
1308
+ });
1309
+ } else {
1310
+ addRange(start, start + duration * 60000);
1311
+ }
1312
+ }
1313
+ var editingAt = (_request$editingStart = request.editingStartAtByProductId) === null || _request$editingStart === void 0 ? void 0 : _request$editingStart[String(product.id)];
1314
+ if (editingAt !== undefined) {
1315
+ var _request$durationMinu3;
1316
+ var _start = toDateTime(editingAt);
1317
+ var editingDuration = Number((_request$durationMinu3 = request.durationMinutesByProductId) === null || _request$durationMinu3 === void 0 ? void 0 : _request$durationMinu3[String(product.id)]);
1318
+ if (!Number.isFinite(_start) || !Number.isInteger(editingDuration) || editingDuration <= 0) {
1319
+ throw availabilityError('INVALID_TARGET_FILTER', '编辑时间必须包含完整开始日期时间和明确的正整数时长');
1320
+ }
1321
+ // Editing restores the original interval, including its actual overnight
1322
+ // date. It neither adopts the current grid nor recomputes a flexible end.
1323
+ if (request.allowOutsideOperatingHoursForRequestedStartTimes || operatingRanges.some(function (range) {
1324
+ return range.start <= _start && _start < range.end;
1325
+ })) {
1326
+ addRange(_start, addMinutesZoned(_start, editingDuration, projection.meta.timezone));
1327
+ }
1328
+ }
1329
+
1330
+ // Explicit custom input keeps its existing start + requested duration
1331
+ // semantics. Remembered input above must never use this default-30 path.
1332
+ if (dateRange.startDate === dateRange.endDate) {
1333
+ resolveRequestedDurationStartTimes(product, request).forEach(function (startTime) {
1334
+ var sameDayStart = toDateTimeInDate(dateRange.startDate, startTime);
1335
+ var period = periods.find(function (item) {
1336
+ return item.businessDate === dateRange.startDate;
1337
+ });
1338
+ var periodRange = period ? normalizeAbsoluteRange(period) : null;
1339
+ var nextDayStart = addLocalCalendarDays(sameDayStart, 1);
1340
+ // HH:mm is entered within a selected business day, whose early hours
1341
+ // may belong to the next calendar day. Do not move other out-of-hours
1342
+ // manual input to tomorrow merely because the business closes overnight.
1343
+ var start = periodRange && !(periodRange.start <= sameDayStart && sameDayStart < periodRange.end) && periodRange.start <= nextDayStart && nextDayStart < periodRange.end ? nextDayStart : sameDayStart;
1344
+ if (request.allowOutsideOperatingHoursForRequestedStartTimes || operatingRanges.some(function (range) {
1345
+ return range.start <= start && start < range.end;
1346
+ })) {
1347
+ addRange(start, start + duration * 60000);
1348
+ }
1349
+ });
1350
+ }
1351
+ return _objectSpread(_objectSpread({
1352
+ productId: product.id,
1353
+ title: product.title,
1354
+ kind: product.kind,
1355
+ slotStepMinutes: step
1356
+ }, operatingEndAt ? {
1357
+ operatingEndAt: operatingEndAt
1358
+ } : {}), {}, {
1359
+ ranges: dedupeProductRanges(ranges)
1360
+ }, unavailableCurrentTime ? {
1361
+ unavailableCurrentTime: unavailableCurrentTime
1362
+ } : {});
1363
+ }
1111
1364
  export function getProductTimeRanges(projection) {
1112
1365
  var _extractFixedOffsetMi3, _request$productIds;
1113
1366
  var request = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
@@ -1173,6 +1426,19 @@ export function getProductTimeRanges(projection) {
1173
1426
  return current === null ? ranges : intersectRangeLists(current, ranges);
1174
1427
  }, null) || [] : null;
1175
1428
  var baseRanges = resourceRanges === null ? operatingRanges : intersectRangeLists(resourceRanges, operatingRanges);
1429
+ if (runtimeOptions.useDurationTimeGrid) {
1430
+ return getAlignedDurationRanges({
1431
+ projection: projection,
1432
+ product: product,
1433
+ request: request,
1434
+ runtimeOptions: runtimeOptions,
1435
+ dateRange: dateRange,
1436
+ operatingRanges: operatingRanges,
1437
+ baseRanges: baseRanges,
1438
+ resourceWindowIndex: resourceWindowIndex,
1439
+ fixedOffsetMinutes: fixedOffsetMinutes
1440
+ });
1441
+ }
1176
1442
  var durationMinutes = resolveRequestedDurationMinutes(product, request);
1177
1443
  var flexibleStartAt = function () {
1178
1444
  if (!product.isFlexibleDuration) return undefined;
@@ -1454,7 +1720,9 @@ function buildEvaluationContext(params) {
1454
1720
  selectionsByGroupKey: selectionsByGroupKey,
1455
1721
  useExactDurationCandidateMinute: params.useExactDurationCandidateMinute === true,
1456
1722
  durationCandidateAnchorAtMs: Number.isFinite(durationCandidateAnchorAt) ? floorToMinute(durationCandidateAnchorAt) : null,
1457
- allowExcludedScheduleDateOverride: params.allowExcludedScheduleDateOverride === true
1723
+ allowExcludedScheduleDateOverride: params.allowExcludedScheduleDateOverride === true,
1724
+ useDurationTimeGrid: params.useDurationTimeGrid === true,
1725
+ enforceSaleCutoff: params.enforceSaleCutoff === true
1458
1726
  };
1459
1727
  }
1460
1728
  function summarizeStatuses(statuses) {
@@ -1504,7 +1772,7 @@ function summarizeStatusAggregate(aggregate) {
1504
1772
  return 'unavailable';
1505
1773
  }
1506
1774
  function evaluateMatchedResourceSummary(params) {
1507
- var _ref6, _params$matchedCell$m;
1775
+ var _ref8, _params$matchedCell$m;
1508
1776
  var isPast = params.candidateRange.endMs <= params.evaluationContext.evaluatedAtMs;
1509
1777
  if (!params.matchedCell) {
1510
1778
  return {
@@ -1516,7 +1784,7 @@ function evaluateMatchedResourceSummary(params) {
1516
1784
  };
1517
1785
  }
1518
1786
  var remainingCapacity = Math.max(0, params.matchedCell.remainingCapacity || 0);
1519
- var maxCapacity = Math.max(0, (_ref6 = (_params$matchedCell$m = params.matchedCell.maxCapacity) !== null && _params$matchedCell$m !== void 0 ? _params$matchedCell$m : params.resourceCapacity) !== null && _ref6 !== void 0 ? _ref6 : 0);
1787
+ var maxCapacity = Math.max(0, (_ref8 = (_params$matchedCell$m = params.matchedCell.maxCapacity) !== null && _params$matchedCell$m !== void 0 ? _params$matchedCell$m : params.resourceCapacity) !== null && _ref8 !== void 0 ? _ref8 : 0);
1520
1788
  var maxPartySize = isPast ? 0 : remainingCapacity;
1521
1789
  var status = function () {
1522
1790
  if (isPast) return 'past';
@@ -1942,6 +2210,8 @@ export function queryAvailabilityProjection(projection, request) {
1942
2210
  projection: projection,
1943
2211
  evaluatedAt: evaluatedAt,
1944
2212
  resourceIds: request.resourceIds,
2213
+ useDurationTimeGrid: runtimeOptions.useDurationTimeGrid,
2214
+ enforceSaleCutoff: runtimeOptions.enforceSaleCutoff,
1945
2215
  useExactDurationCandidateMinute: runtimeOptions.useExactDurationCandidateMinute,
1946
2216
  allowExcludedScheduleDateOverride: runtimeOptions.allowExcludedScheduleDateOverride
1947
2217
  });
@@ -2032,6 +2302,8 @@ export function queryAvailabilityProjection(projection, request) {
2032
2302
  partySize: normalizedPartySize
2033
2303
  }, {
2034
2304
  evaluatedAt: evaluatedAt,
2305
+ useDurationTimeGrid: runtimeOptions.useDurationTimeGrid,
2306
+ enforceSaleCutoff: runtimeOptions.enforceSaleCutoff,
2035
2307
  useExactDurationCandidateMinute: runtimeOptions.useExactDurationCandidateMinute,
2036
2308
  allowExcludedScheduleDateOverride: runtimeOptions.allowExcludedScheduleDateOverride
2037
2309
  });
@@ -2060,7 +2332,8 @@ export function queryAvailabilityProjection(projection, request) {
2060
2332
  candidateRangeCache: candidateRangeCache,
2061
2333
  evaluationContext: evaluationContext
2062
2334
  });
2063
- var aggregate = byDate.get(candidate.date) || {
2335
+ var candidateDate = runtimeOptions.useDurationTimeGrid ? candidate.businessDate || candidate.date : candidate.date;
2336
+ var aggregate = byDate.get(candidateDate) || {
2064
2337
  availableCount: 0,
2065
2338
  totalCount: 0,
2066
2339
  maxPartySize: 0,
@@ -2080,8 +2353,26 @@ export function queryAvailabilityProjection(projection, request) {
2080
2353
  evaluated.reasonCodes.forEach(function (reasonCode) {
2081
2354
  return aggregate.reasonCodeSet.add(reasonCode);
2082
2355
  });
2083
- byDate.set(candidate.date, aggregate);
2356
+ byDate.set(candidateDate, aggregate);
2084
2357
  });
2358
+ if (group.unavailableCurrentTime) {
2359
+ var current = group.unavailableCurrentTime;
2360
+ var aggregate = byDate.get(current.businessDate) || {
2361
+ availableCount: 0,
2362
+ totalCount: 0,
2363
+ maxPartySize: 0,
2364
+ remainingCapacity: 0,
2365
+ maxCapacity: 0,
2366
+ reasonCodeSet: new Set(),
2367
+ statusAggregate: createStatusAggregate()
2368
+ };
2369
+ aggregate.totalCount += 1;
2370
+ pushStatusAggregate(aggregate.statusAggregate, 'unavailable');
2371
+ current.reasonCodes.forEach(function (reason) {
2372
+ return aggregate.reasonCodeSet.add(reason);
2373
+ });
2374
+ byDate.set(current.businessDate, aggregate);
2375
+ }
2085
2376
  var dates = enumerateDates(dateRange.startDate, dateRange.endDate).map(function (date) {
2086
2377
  var aggregate = byDate.get(date);
2087
2378
  return {
@@ -2143,10 +2434,22 @@ export function queryAvailabilityProjection(projection, request) {
2143
2434
  evaluated.reasonCodes.forEach(function (reasonCode) {
2144
2435
  return productReasonCodeSet.add(reasonCode);
2145
2436
  });
2146
- var dateAggregate = dateStatusMap.get(candidate.date) || createStatusAggregate();
2437
+ var candidateDate = runtimeOptions.useDurationTimeGrid ? candidate.businessDate || candidate.date : candidate.date;
2438
+ var dateAggregate = dateStatusMap.get(candidateDate) || createStatusAggregate();
2147
2439
  pushStatusAggregate(dateAggregate, evaluated.status);
2148
- dateStatusMap.set(candidate.date, dateAggregate);
2440
+ dateStatusMap.set(candidateDate, dateAggregate);
2149
2441
  });
2442
+ if (group.unavailableCurrentTime) {
2443
+ var current = group.unavailableCurrentTime;
2444
+ totalTimeCount += 1;
2445
+ pushStatusAggregate(productStatusAggregate, 'unavailable');
2446
+ current.reasonCodes.forEach(function (reason) {
2447
+ return productReasonCodeSet.add(reason);
2448
+ });
2449
+ var dateAggregate = dateStatusMap.get(current.businessDate) || createStatusAggregate();
2450
+ pushStatusAggregate(dateAggregate, 'unavailable');
2451
+ dateStatusMap.set(current.businessDate, dateAggregate);
2452
+ }
2150
2453
  var availableDateCount = Array.from(dateStatusMap.values()).filter(function (aggregate) {
2151
2454
  var status = summarizeStatusAggregate(aggregate);
2152
2455
  return status === 'available' || status === 'limited';
@@ -2233,6 +2536,8 @@ export function validateAvailabilitySelection(projection, params) {
2233
2536
  projection: projection,
2234
2537
  evaluatedAt: evaluatedAt,
2235
2538
  requirementSelections: params.requirementSelections,
2539
+ useDurationTimeGrid: runtimeOptions.useDurationTimeGrid,
2540
+ enforceSaleCutoff: runtimeOptions.enforceSaleCutoff,
2236
2541
  useExactDurationCandidateMinute: runtimeOptions.useExactDurationCandidateMinute,
2237
2542
  allowExcludedScheduleDateOverride: runtimeOptions.allowExcludedScheduleDateOverride
2238
2543
  })