@pisell/pisellos 2.3.86 → 2.3.88

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.
@@ -52,6 +52,30 @@ function normalizePositiveInteger(value) {
52
52
  if (!Number.isInteger(normalized) || normalized <= 0) return undefined;
53
53
  return normalized;
54
54
  }
55
+ function getCandidateDurationMinutes(candidate) {
56
+ var startAt = candidate.bookingStartAt || candidate.startAt;
57
+ var endAt = candidate.bookingEndAt || candidate.endAt;
58
+ try {
59
+ var start = localDateTimeToScalar(parseLocalDateTime(startAt));
60
+ var end = localDateTimeToScalar(parseLocalDateTime(endAt));
61
+ var minutes = Math.round((end - start) / 60000);
62
+ return Number.isInteger(minutes) && minutes > 0 ? minutes : undefined;
63
+ } catch (_error) {
64
+ return undefined;
65
+ }
66
+ }
67
+ function getCandidateStartTime(candidate) {
68
+ if (/^([01]\d|2[0-3]):[0-5]\d$/.test(candidate.startTime || '')) {
69
+ return candidate.startTime;
70
+ }
71
+ var startAt = candidate.bookingStartAt || candidate.startAt;
72
+ try {
73
+ var start = parseLocalDateTime(startAt);
74
+ return "".concat(String(start.hour).padStart(2, '0'), ":").concat(String(start.minute).padStart(2, '0'));
75
+ } catch (_error) {
76
+ return undefined;
77
+ }
78
+ }
55
79
  function isRecord(value) {
56
80
  return !!value && _typeof(value) === 'object' && !Array.isArray(value);
57
81
  }
@@ -4332,7 +4356,7 @@ export var UnifiedBookingSalesImpl = /*#__PURE__*/function (_BookingTicket) {
4332
4356
  key: "commitAvailabilitySelection",
4333
4357
  value: (function () {
4334
4358
  var _commitAvailabilitySelection = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee37(contextId, params) {
4335
- var normalizedContextId, _params$bookingCount, _ref46, _params$orderProduct$, _params$orderProduct, _params$orderProduct2, _params$orderProduct3, _params$orderProduct$2, _params$orderProduct4, _params$orderProduct5, _editingLine$product_, _baseProjection$meta$, _refreshedProjection$, _baseProjection$meta$2, _this$otherParams$pla, _this$otherParams17, _ref47, _params$orderProduct$3, _params$orderProduct6, _existingBooking$meta, _ref48, _ref49, _params$orderProduct$4, _params$orderProduct7, _params$orderProduct8, _existingBooking$meta2, _existingBooking$hold, bookingCount, orderProductQuantity, partySize, _yield$this$ensureAva3, context, projectionEntry, refreshed, boundEditingUids, orderProductId, editingLine, _editingLine$product_2, baseProjection, commitWriteGuard, prepareParams, fallbackResources, commitSnapshot, refreshedProjection, _baseProjection$meta$3, latestContext, refreshedRanges, refreshedCandidateBase, product, validation, allowPosAvailabilityOverride, forced, capacityConflict, revalidationEntry, revalidatedContext, refreshedCandidate, existingBooking, existingBookingUid, booking, transformed, productLineUid, bookingUid, productLineUids, bookingUids, _transformed$product_, _transformed$product_2, _transformed$product_3, _ref50, _transformed$product_4, _this$getTempOrder4, _this$getTempOrder5, _this$getTempOrder6, beforeProductLineUids, beforeBookingUids, products, addedLines, addedBookings, nextContext, result;
4359
+ var normalizedContextId, _params$bookingCount, _ref46, _params$orderProduct$, _params$orderProduct, _params$orderProduct2, _params$orderProduct3, _params$orderProduct$2, _params$orderProduct4, _params$orderProduct5, _editingLine$product_, _baseProjection$meta$, _refreshedProjection$, _baseProjection$meta$2, _this$otherParams$pla, _this$otherParams17, _ref47, _params$orderProduct$3, _params$orderProduct6, _existingBooking$meta, _ref48, _ref49, _params$orderProduct$4, _params$orderProduct7, _params$orderProduct8, _existingBooking$meta2, _existingBooking$hold, bookingCount, orderProductQuantity, partySize, _yield$this$ensureAva3, context, projectionEntry, refreshed, boundEditingUids, orderProductId, editingLine, _editingLine$product_2, baseProjection, commitWriteGuard, prepareParams, fallbackResources, commitSnapshot, refreshedProjection, _baseProjection$meta$3, latestContext, candidateDurationMinutes, candidateStartTime, refreshedRanges, refreshedCandidateBase, product, validation, allowPosAvailabilityOverride, forced, capacityConflict, revalidationEntry, revalidatedContext, refreshedCandidate, existingBooking, existingBookingUid, booking, transformed, productLineUid, bookingUid, productLineUids, bookingUids, _transformed$product_, _transformed$product_2, _transformed$product_3, _ref50, _transformed$product_4, _this$getTempOrder4, _this$getTempOrder5, _this$getTempOrder6, beforeProductLineUids, beforeBookingUids, products, addedLines, addedBookings, nextContext, result;
4336
4360
  return _regeneratorRuntime().wrap(function _callee37$(_context37) {
4337
4361
  while (1) switch (_context37.prev = _context37.next) {
4338
4362
  case 0:
@@ -4560,20 +4584,26 @@ export var UnifiedBookingSalesImpl = /*#__PURE__*/function (_BookingTicket) {
4560
4584
  });
4561
4585
  case 67:
4562
4586
  this.assertAvailabilityCommitWriteCAS(commitWriteGuard);
4563
- refreshedRanges = getProjectionProductTimeRanges(refreshedProjection, {
4587
+ candidateDurationMinutes = getCandidateDurationMinutes(params.candidate);
4588
+ candidateStartTime = getCandidateStartTime(params.candidate);
4589
+ refreshedRanges = getProjectionProductTimeRanges(refreshedProjection, _objectSpread({
4564
4590
  productIds: [params.candidate.productId],
4565
4591
  dateRange: {
4566
4592
  startDate: params.candidate.startAt.slice(0, 10),
4567
4593
  endDate: params.candidate.startAt.slice(0, 10)
4568
4594
  }
4569
- }).flatMap(function (group) {
4595
+ }, candidateDurationMinutes ? _objectSpread({
4596
+ durationMinutesByProductId: _defineProperty({}, String(params.candidate.productId), candidateDurationMinutes)
4597
+ }, candidateStartTime ? {
4598
+ durationStartTimesByProductId: _defineProperty({}, String(params.candidate.productId), [candidateStartTime])
4599
+ } : {}) : {})).flatMap(function (group) {
4570
4600
  return group.ranges;
4571
4601
  });
4572
4602
  refreshedCandidateBase = refreshedRanges.find(function (candidate) {
4573
4603
  return candidate.startAt === params.candidate.startAt && candidate.endAt === params.candidate.endAt && candidate.bookingStartAt === params.candidate.bookingStartAt && candidate.bookingEndAt === params.candidate.bookingEndAt;
4574
4604
  });
4575
4605
  if (refreshedCandidateBase) {
4576
- _context37.next = 72;
4606
+ _context37.next = 74;
4577
4607
  break;
4578
4608
  }
4579
4609
  throw new AvailabilityError('REVALIDATION_FAILED', '缓存重校验后候选时间已不在商品配置中', {
@@ -4581,18 +4611,18 @@ export var UnifiedBookingSalesImpl = /*#__PURE__*/function (_BookingTicket) {
4581
4611
  candidate: params.candidate,
4582
4612
  refreshedSnapshotId: refreshedProjection.meta.snapshotId
4583
4613
  });
4584
- case 72:
4614
+ case 74:
4585
4615
  product = refreshedProjection.products.find(function (item) {
4586
4616
  return sameAvailabilityId(item.id, refreshedCandidateBase.productId);
4587
4617
  });
4588
4618
  if (product) {
4589
- _context37.next = 75;
4619
+ _context37.next = 77;
4590
4620
  break;
4591
4621
  }
4592
4622
  throw new AvailabilityError('REVALIDATION_FAILED', '缓存重校验后商品不在 projection 中', {
4593
4623
  productId: refreshedCandidateBase.productId
4594
4624
  });
4595
- case 75:
4625
+ case 77:
4596
4626
  validation = validateAvailabilitySelection(refreshedProjection, {
4597
4627
  candidate: refreshedCandidateBase,
4598
4628
  requirementSelections: params.requirementSelections,
@@ -4621,7 +4651,7 @@ export var UnifiedBookingSalesImpl = /*#__PURE__*/function (_BookingTicket) {
4621
4651
  });
4622
4652
  }
4623
4653
  if (validation.ok) {
4624
- _context37.next = 81;
4654
+ _context37.next = 83;
4625
4655
  break;
4626
4656
  }
4627
4657
  capacityConflict = validation.conflicts.find(function (conflict) {
@@ -4631,7 +4661,7 @@ export var UnifiedBookingSalesImpl = /*#__PURE__*/function (_BookingTicket) {
4631
4661
  validation: validation,
4632
4662
  refreshedSnapshotId: refreshedProjection.meta.snapshotId
4633
4663
  });
4634
- case 81:
4664
+ case 83:
4635
4665
  // Revalidation failure must leave the caller's candidate reusable. Only publish the
4636
4666
  // refreshed projection/version after candidate and resource validation has passed;
4637
4667
  // otherwise a second Confirm from the still-open modal would incorrectly become stale.
@@ -4644,7 +4674,7 @@ export var UnifiedBookingSalesImpl = /*#__PURE__*/function (_BookingTicket) {
4644
4674
  incrementVersion: true
4645
4675
  });
4646
4676
  revalidatedContext = this.getAvailabilityContextEntry(normalizedContextId);
4647
- _context37.next = 85;
4677
+ _context37.next = 87;
4648
4678
  return this.emitAvailabilityDiagnostic({
4649
4679
  action: 'refresh',
4650
4680
  contextId: normalizedContextId,
@@ -4655,7 +4685,7 @@ export var UnifiedBookingSalesImpl = /*#__PURE__*/function (_BookingTicket) {
4655
4685
  previousOrderRevision: baseProjection.meta.orderRevision,
4656
4686
  reason: 'commit_cached_occupancy_revalidation'
4657
4687
  });
4658
- case 85:
4688
+ case 87:
4659
4689
  commitWriteGuard = {
4660
4690
  contextId: normalizedContextId,
4661
4691
  expectedVersion: revalidatedContext.activeVersion,
@@ -4667,13 +4697,13 @@ export var UnifiedBookingSalesImpl = /*#__PURE__*/function (_BookingTicket) {
4667
4697
  existingBooking = editingLine ? this.findLinkedBooking(editingLine) : undefined;
4668
4698
  existingBookingUid = existingBooking ? getBookingUid(existingBooking) : '';
4669
4699
  if (!(params.mode === 'edit' && !existingBookingUid)) {
4670
- _context37.next = 91;
4700
+ _context37.next = 93;
4671
4701
  break;
4672
4702
  }
4673
4703
  throw new AvailabilityError('PRODUCT_LINE_NOT_FOUND', '编辑商品行缺少关联 booking', {
4674
4704
  productLineUid: params.productLineUid
4675
4705
  });
4676
- case 91:
4706
+ case 93:
4677
4707
  this.assertAvailabilityCommitWriteCAS(commitWriteGuard);
4678
4708
  booking = buildBookingFromAvailabilitySelection({
4679
4709
  assignments: validation.assignments,
@@ -4689,13 +4719,13 @@ export var UnifiedBookingSalesImpl = /*#__PURE__*/function (_BookingTicket) {
4689
4719
  quantity: bookingCount
4690
4720
  }), 'unified_booking_sales', product);
4691
4721
  if (transformed) {
4692
- _context37.next = 96;
4722
+ _context37.next = 98;
4693
4723
  break;
4694
4724
  }
4695
4725
  throw new AvailabilityError('INVALID_TARGET_FILTER', 'orderProduct 无法转换为订单行', {
4696
4726
  orderProduct: params.orderProduct
4697
4727
  });
4698
- case 96:
4728
+ case 98:
4699
4729
  transformed.num = bookingCount;
4700
4730
  transformed.metadata = _objectSpread(_objectSpread({}, transformed.metadata || {}), {}, {
4701
4731
  unified_booking_sales: true,
@@ -4708,7 +4738,7 @@ export var UnifiedBookingSalesImpl = /*#__PURE__*/function (_BookingTicket) {
4708
4738
  productLineUids = [];
4709
4739
  bookingUids = [];
4710
4740
  if (!(params.mode === 'edit' && editingLine && params.productLineUid)) {
4711
- _context37.next = 115;
4741
+ _context37.next = 117;
4712
4742
  break;
4713
4743
  }
4714
4744
  transformed.product_sku = (_transformed$product_ = transformed.product_sku) !== null && _transformed$product_ !== void 0 ? _transformed$product_ : editingLine.product_sku;
@@ -4718,7 +4748,7 @@ export var UnifiedBookingSalesImpl = /*#__PURE__*/function (_BookingTicket) {
4718
4748
  unique_identification_number: params.productLineUid,
4719
4749
  booking_uid: existingBookingUid
4720
4750
  });
4721
- _context37.next = 109;
4751
+ _context37.next = 111;
4722
4752
  return this.updateOrderProduct(_objectSpread(_objectSpread({
4723
4753
  product_id: (_transformed$product_3 = transformed.product_id) !== null && _transformed$product_3 !== void 0 ? _transformed$product_3 : editingLine.product_id,
4724
4754
  product_variant_id: (_ref50 = (_transformed$product_4 = transformed.product_variant_id) !== null && _transformed$product_4 !== void 0 ? _transformed$product_4 : editingLine.product_variant_id) !== null && _ref50 !== void 0 ? _ref50 : 0,
@@ -4730,23 +4760,23 @@ export var UnifiedBookingSalesImpl = /*#__PURE__*/function (_BookingTicket) {
4730
4760
  } : {}), params.allowBookingDiscountReapply !== undefined ? {
4731
4761
  allow_booking_discount_reapply: params.allowBookingDiscountReapply
4732
4762
  } : {}));
4733
- case 109:
4763
+ case 111:
4734
4764
  productLineUid = params.productLineUid;
4735
4765
  bookingUid = existingBookingUid;
4736
4766
  productLineUids = [productLineUid];
4737
4767
  bookingUids = [bookingUid];
4738
- _context37.next = 127;
4768
+ _context37.next = 129;
4739
4769
  break;
4740
- case 115:
4770
+ case 117:
4741
4771
  beforeProductLineUids = new Set((((_this$getTempOrder4 = this.getTempOrder()) === null || _this$getTempOrder4 === void 0 ? void 0 : _this$getTempOrder4.products) || []).map(function (line) {
4742
4772
  return getProductLineUid(line);
4743
4773
  }).filter(Boolean));
4744
4774
  beforeBookingUids = new Set((((_this$getTempOrder5 = this.getTempOrder()) === null || _this$getTempOrder5 === void 0 ? void 0 : _this$getTempOrder5.bookings) || []).map(function (item) {
4745
4775
  return getBookingUid(item);
4746
4776
  }).filter(Boolean));
4747
- _context37.next = 119;
4777
+ _context37.next = 121;
4748
4778
  return this.addProductToOrder(transformed, booking);
4749
- case 119:
4779
+ case 121:
4750
4780
  products = _context37.sent;
4751
4781
  addedLines = products.filter(function (line) {
4752
4782
  var _line$product_id, _line$metadata;
@@ -4770,7 +4800,7 @@ export var UnifiedBookingSalesImpl = /*#__PURE__*/function (_BookingTicket) {
4770
4800
  }
4771
4801
  productLineUid = productLineUids[productLineUids.length - 1] || '';
4772
4802
  bookingUid = bookingUids[bookingUids.length - 1] || '';
4773
- case 127:
4803
+ case 129:
4774
4804
  this.invalidateAvailabilityContextsAfterCommit();
4775
4805
  nextContext = this.getAvailabilityContextEntry(normalizedContextId);
4776
4806
  result = {
@@ -4784,21 +4814,21 @@ export var UnifiedBookingSalesImpl = /*#__PURE__*/function (_BookingTicket) {
4784
4814
  productLineUids: productLineUids,
4785
4815
  bookingUids: bookingUids
4786
4816
  };
4787
- _context37.next = 132;
4817
+ _context37.next = 134;
4788
4818
  return this.core.effects.emit("".concat(this.name, ":").concat(UnifiedBookingSalesHooks.onAvailabilitySelectionCommitted), result);
4789
- case 132:
4819
+ case 134:
4790
4820
  return _context37.abrupt("return", result);
4791
- case 133:
4792
- _context37.prev = 133;
4793
- _context37.next = 136;
4821
+ case 135:
4822
+ _context37.prev = 135;
4823
+ _context37.next = 138;
4794
4824
  return this.releaseAvailabilityCommitLock(normalizedContextId);
4795
- case 136:
4796
- return _context37.finish(133);
4797
- case 137:
4825
+ case 138:
4826
+ return _context37.finish(135);
4827
+ case 139:
4798
4828
  case "end":
4799
4829
  return _context37.stop();
4800
4830
  }
4801
- }, _callee37, this, [[5,, 133, 137]]);
4831
+ }, _callee37, this, [[5,, 135, 139]]);
4802
4832
  }));
4803
4833
  function commitAvailabilitySelection(_x32, _x33) {
4804
4834
  return _commitAvailabilitySelection.apply(this, arguments);
@@ -3794,6 +3794,13 @@ class OrderModule extends _BaseModule.BaseModule {
3794
3794
  }
3795
3795
  this.store.syncState = 'submitting';
3796
3796
  const paymentSyncIdempotencyToken = this.buildPaymentSyncIdempotencyToken(tempOrder, effectivePayments);
3797
+ const enhancePayload = (payload, ctx) => {
3798
+ const enhancedPayload = params.enhancePayload ? params.enhancePayload(payload, ctx) : payload;
3799
+ return {
3800
+ ...enhancedPayload,
3801
+ request_unique_idempotency_token: paymentSyncIdempotencyToken
3802
+ };
3803
+ };
3797
3804
  const submitParams = {
3798
3805
  payments: effectivePayments,
3799
3806
  paymentStatus,
@@ -3801,13 +3808,7 @@ class OrderModule extends _BaseModule.BaseModule {
3801
3808
  businessCode: params.businessCode,
3802
3809
  channel: params.channel,
3803
3810
  confirmPendingVoucherPayments: true,
3804
- enhancePayload: payload => {
3805
- const nextPayload = {
3806
- ...payload,
3807
- request_unique_idempotency_token: paymentSyncIdempotencyToken
3808
- };
3809
- return nextPayload;
3810
- }
3811
+ enhancePayload
3811
3812
  };
3812
3813
  const submitResult = params.checkoutSyncTaskEnabled === false ? await this.submitTempOrderAsync(submitParams) : await this.submitTempOrder(submitParams);
3813
3814
  return {
@@ -608,6 +608,7 @@ export interface SyncPaymentsToOrderParams {
608
608
  businessCode?: string;
609
609
  channel?: string;
610
610
  confirmPendingVoucherPayments?: boolean;
611
+ enhancePayload?: SubmitPayloadEnhancer;
611
612
  }
612
613
  export interface SyncPaymentsToOrderResult<T = any> {
613
614
  isFullyPaid: boolean;
@@ -48,6 +48,19 @@ function normalizeBookingCount(bookingCount) {
48
48
  }
49
49
  return normalized;
50
50
  }
51
+ function resolveRequestedDurationMinutes(product, request) {
52
+ const configured = Math.max(1, Number(product.durationMinutes || 30) || 30);
53
+ if (product.kind !== 'duration') return configured;
54
+ const override = request.durationMinutesByProductId?.[String(product.id)];
55
+ const normalized = Number(override);
56
+ return Number.isInteger(normalized) && normalized > 0 ? normalized : configured;
57
+ }
58
+ function resolveRequestedDurationStartTimes(product, request) {
59
+ if (product.kind !== 'duration') return [];
60
+ const values = request.durationStartTimesByProductId?.[String(product.id)];
61
+ if (!Array.isArray(values)) return [];
62
+ return Array.from(new Set(values.filter(value => typeof value === 'string' && /^([01]\d|2[0-3]):[0-5]\d$/.test(value))));
63
+ }
51
64
  function toDateTime(value, _timezoneName) {
52
65
  try {
53
66
  return (0, _localClock.localDateTimeToScalar)((0, _localClock.parseLocalDateTime)(value));
@@ -865,9 +878,27 @@ function dedupeProductRanges(ranges) {
865
878
  });
866
879
  return Array.from(map.values()).sort((left, right) => left.startAt.localeCompare(right.startAt));
867
880
  }
881
+ function createDurationTimeRange(product, start, durationMinutes, projection, fixedOffsetMinutes) {
882
+ const bookingEnd = addMinutesZoned(start, durationMinutes, projection.meta.timezone);
883
+ return {
884
+ key: `${String(product.id)}:${start}:${bookingEnd}`,
885
+ productId: product.id,
886
+ source: 'duration_window',
887
+ startAt: formatDateTime(start, projection.meta.timezone, fixedOffsetMinutes),
888
+ endAt: formatDateTime(bookingEnd, projection.meta.timezone, fixedOffsetMinutes),
889
+ bookingStartAt: formatDateTime(start, projection.meta.timezone, fixedOffsetMinutes),
890
+ bookingEndAt: formatDateTime(bookingEnd, projection.meta.timezone, fixedOffsetMinutes),
891
+ date: formatDate(start, projection.meta.timezone),
892
+ startTime: formatTime(start, projection.meta.timezone),
893
+ endTime: formatTime(bookingEnd, projection.meta.timezone),
894
+ timezone: projection.meta.timezone,
895
+ scheduleRefs: []
896
+ };
897
+ }
868
898
  function getProductTimeRanges(projection, request = {}) {
869
899
  const normalizedPartySize = normalizePartySize(request.partySize);
870
900
  const dateRange = resolveProjectionDateRange(projection, request.dateRange);
901
+ const slotStepMinutes = projection.meta.defaults.slotStepMinutes;
871
902
  const fixedOffsetMinutes = (0, _localClock.extractFixedOffsetMinutes)(projection.meta.now) ?? (0, _localClock.captureRuntimeOffsetMinutes)();
872
903
  const resourceWindowIndex = buildResourceWindowIndex(projection);
873
904
  const filteredProductIdSet = request.productIds?.length ? new Set(request.productIds.map(String)) : null;
@@ -877,6 +908,7 @@ function getProductTimeRanges(projection, request = {}) {
877
908
  productId: product.id,
878
909
  title: product.title,
879
910
  kind: product.kind,
911
+ slotStepMinutes,
880
912
  ranges: []
881
913
  };
882
914
  }
@@ -885,6 +917,7 @@ function getProductTimeRanges(projection, request = {}) {
885
917
  productId: product.id,
886
918
  title: product.title,
887
919
  kind: product.kind,
920
+ slotStepMinutes,
888
921
  ranges: getSessionRangesForProduct(projection, product, dateRange)
889
922
  };
890
923
  }
@@ -900,9 +933,9 @@ function getProductTimeRanges(projection, request = {}) {
900
933
  resourceWindowIndex
901
934
  })).reduce((current, ranges) => current === null ? ranges : intersectRangeLists(current, ranges), null) || [] : null;
902
935
  const baseRanges = resourceRanges === null ? operatingRanges : intersectRangeLists(resourceRanges, operatingRanges);
903
- const durationMinutes = Math.max(1, Number(product.durationMinutes || 30) || 30);
904
- const timeSliceMinutes = projection.meta.defaults.slotStepMinutes;
936
+ const durationMinutes = resolveRequestedDurationMinutes(product, request);
905
937
  const durationAnchor = ceilToTenMinutes(toDateTime(projection.meta.durationCandidateAnchorAt, projection.meta.timezone), projection.meta.timezone);
938
+ const requestedStartTimes = resolveRequestedDurationStartTimes(product, request);
906
939
  const ranges = [];
907
940
  baseRanges.forEach(range => {
908
941
  if (range.end <= durationAnchor) return;
@@ -915,28 +948,26 @@ function getProductTimeRanges(projection, request = {}) {
915
948
  if (bookingEnd > range.end) break;
916
949
  const bookingDate = formatDate(cursor);
917
950
  if (bookingDate >= dateRange.startDate && bookingDate <= dateRange.endDate) {
918
- ranges.push({
919
- key: `${String(product.id)}:${cursor}:${bookingEnd}`,
920
- productId: product.id,
921
- source: 'duration_window',
922
- startAt: formatDateTime(cursor, projection.meta.timezone, fixedOffsetMinutes),
923
- endAt: formatDateTime(bookingEnd, projection.meta.timezone, fixedOffsetMinutes),
924
- bookingStartAt: formatDateTime(cursor, projection.meta.timezone, fixedOffsetMinutes),
925
- bookingEndAt: formatDateTime(bookingEnd, projection.meta.timezone, fixedOffsetMinutes),
926
- date: formatDate(cursor, projection.meta.timezone),
927
- startTime: formatTime(cursor, projection.meta.timezone),
928
- endTime: formatTime(bookingEnd, projection.meta.timezone),
929
- timezone: projection.meta.timezone,
930
- scheduleRefs: []
931
- });
951
+ ranges.push(createDurationTimeRange(product, cursor, durationMinutes, projection, fixedOffsetMinutes));
932
952
  }
933
- cursor = addMinutesZoned(cursor, timeSliceMinutes, projection.meta.timezone);
953
+ cursor = addMinutesZoned(cursor, slotStepMinutes, projection.meta.timezone);
934
954
  }
935
955
  });
956
+ if (dateRange.startDate === dateRange.endDate && requestedStartTimes.length > 0) {
957
+ requestedStartTimes.forEach(startTime => {
958
+ const start = toDateTimeInDate(dateRange.startDate, startTime, projection.meta.timezone);
959
+ if (!Number.isFinite(start)) return;
960
+ const end = addMinutesZoned(start, durationMinutes, projection.meta.timezone);
961
+ const startsInOperatingRange = operatingRanges.some(range => range.start <= start && start < range.end);
962
+ if (!startsInOperatingRange) return;
963
+ ranges.push(createDurationTimeRange(product, start, durationMinutes, projection, fixedOffsetMinutes));
964
+ });
965
+ }
936
966
  return {
937
967
  productId: product.id,
938
968
  title: product.title,
939
969
  kind: product.kind,
970
+ slotStepMinutes,
940
971
  ranges: dedupeProductRanges(ranges)
941
972
  };
942
973
  });
@@ -211,6 +211,7 @@ export interface ProductTimeRangeGroup {
211
211
  productId: AvailabilityId;
212
212
  title?: string;
213
213
  kind: AvailabilityProductKind;
214
+ slotStepMinutes?: number;
214
215
  ranges: ProductTimeRange[];
215
216
  }
216
217
  export interface AvailabilityAssignment extends AvailabilityAbsoluteRange {
@@ -231,6 +232,8 @@ export interface GetProductTimeRangesRequest {
231
232
  dateRange?: Partial<AvailabilityDateRange>;
232
233
  resourceIds?: AvailabilityId[];
233
234
  partySize?: number;
235
+ durationMinutesByProductId?: Record<string, number>;
236
+ durationStartTimesByProductId?: Record<string, string[]>;
234
237
  }
235
238
  export interface QueryAvailabilityBaseRequest {
236
239
  productIds?: AvailabilityId[];
@@ -511,6 +511,10 @@ declare class Server {
511
511
  private buildCheckoutTaskIdempotencyKey;
512
512
  private isBlankCheckoutValue;
513
513
  private isLocalCheckoutOrderId;
514
+ private applyCheckoutFulfillmentBuzzer;
515
+ private buildCheckoutResourceIdentifierBuzzer;
516
+ private getCheckoutResourceIdentifier;
517
+ private stringifyCheckoutResourceIdentifier;
514
518
  private normalizeCheckoutSubmitData;
515
519
  private ensureCheckoutOrderNumbers;
516
520
  private dispatchCheckoutSyncTask;
@@ -40,6 +40,8 @@ Object.keys(_modules).forEach(function (key) {
40
40
  });
41
41
  });
42
42
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
43
+ const OS_FULFILLMENT_REF_MODE_FIELD = '_os_fulfillment_ref_mode';
44
+
43
45
  // 重新导出类型供外部使用
44
46
 
45
47
  /** 商品查询订阅者 */
@@ -624,7 +626,8 @@ class Server {
624
626
  title
625
627
  } = params;
626
628
  const normalizedData = await this.normalizeCheckoutSubmitData(params.data, title);
627
- const checkoutData = await this.ensureCheckoutOrderNumbers(normalizedData, title);
629
+ const numberedData = await this.ensureCheckoutOrderNumbers(normalizedData, title);
630
+ const checkoutData = this.applyCheckoutFulfillmentBuzzer(numberedData, title);
628
631
  const remoteCheckoutData = this.buildRemoteCheckoutData(checkoutData, title);
629
632
  if (!this.app?.request?.post) {
630
633
  throw new Error('app.request 不可用');
@@ -693,6 +696,7 @@ class Server {
693
696
  const next = (0, _lodashEs.cloneDeep)(this.omitCheckoutSyncMode(data));
694
697
  if (!next || typeof next !== 'object') return next;
695
698
  const record = next;
699
+ delete record[OS_FULFILLMENT_REF_MODE_FIELD];
696
700
  if (!Array.isArray(record.payments)) return next;
697
701
  const originalPayments = record.payments;
698
702
  const filteredPayments = originalPayments.filter(payment => !(0, _paymentUtils.isPendingOrderPayment)(payment));
@@ -3379,6 +3383,82 @@ class Server {
3379
3383
  isLocalCheckoutOrderId(orderId) {
3380
3384
  return typeof orderId === 'string' && orderId.startsWith('LOCAL_');
3381
3385
  }
3386
+ applyCheckoutFulfillmentBuzzer(data, title) {
3387
+ if (!data || typeof data !== 'object') return data;
3388
+ const next = {
3389
+ ...data
3390
+ };
3391
+ const refMode = String(next[OS_FULFILLMENT_REF_MODE_FIELD] || '').trim();
3392
+ delete next[OS_FULFILLMENT_REF_MODE_FIELD];
3393
+ if (!refMode) return next;
3394
+ if (refMode === 'manual_input') {
3395
+ this.logInfo(`${title}: fulfillment ref mode 保留手动 buzzer`, {
3396
+ fulfillment_ref_mode: refMode,
3397
+ has_buzzer: !this.isBlankCheckoutValue(next.buzzer)
3398
+ });
3399
+ return next;
3400
+ }
3401
+ if (refMode === 'resource_identifier') {
3402
+ next.buzzer = this.buildCheckoutResourceIdentifierBuzzer(next.bookings);
3403
+ } else if (refMode === 'sale_number') {
3404
+ next.buzzer = this.isBlankCheckoutValue(next.shop_order_number) ? '' : String(next.shop_order_number);
3405
+ } else if (refMode === 'short_number') {
3406
+ next.buzzer = this.isBlankCheckoutValue(next.shop_full_order_number) ? '' : String(next.shop_full_order_number);
3407
+ } else {
3408
+ this.logWarning(`${title}: 未识别的 fulfillment ref mode,跳过 buzzer 自动填充`, {
3409
+ fulfillment_ref_mode: refMode
3410
+ });
3411
+ return next;
3412
+ }
3413
+ this.logInfo(`${title}: fulfillment ref mode 已填充 buzzer`, {
3414
+ fulfillment_ref_mode: refMode,
3415
+ buzzer: next.buzzer
3416
+ });
3417
+ return next;
3418
+ }
3419
+ buildCheckoutResourceIdentifierBuzzer(bookings) {
3420
+ if (!Array.isArray(bookings)) return '';
3421
+ const values = [];
3422
+ const seen = new Set();
3423
+ const visitResource = resource => {
3424
+ const identifier = this.getCheckoutResourceIdentifier(resource);
3425
+ if (identifier && !seen.has(identifier)) {
3426
+ seen.add(identifier);
3427
+ values.push(identifier);
3428
+ }
3429
+ if (Array.isArray(resource?.children)) {
3430
+ resource.children.forEach(visitResource);
3431
+ }
3432
+ };
3433
+ bookings.forEach(booking => {
3434
+ if (Array.isArray(booking?.resources)) {
3435
+ booking.resources.forEach(visitResource);
3436
+ }
3437
+ });
3438
+ return values.join(',');
3439
+ }
3440
+ getCheckoutResourceIdentifier(resource) {
3441
+ if (!resource || typeof resource !== 'object') return '';
3442
+ const candidates = [resource.main_field, resource.name, resource.title, resource.metadata?.resource_name, resource.metadata?.name, resource.relation_id, resource.id];
3443
+ for (const candidate of candidates) {
3444
+ const value = this.stringifyCheckoutResourceIdentifier(candidate);
3445
+ if (value) return value;
3446
+ }
3447
+ return '';
3448
+ }
3449
+ stringifyCheckoutResourceIdentifier(value) {
3450
+ if (value === undefined || value === null) return '';
3451
+ if (typeof value === 'string' || typeof value === 'number') {
3452
+ return String(value).trim();
3453
+ }
3454
+ if (typeof value !== 'object' || Array.isArray(value)) return '';
3455
+ const localeCandidates = [value.original, value.auto, value.en, value['zh-CN'], value.zh_CN, value['zh-HK'], value.zh_HK];
3456
+ for (const candidate of localeCandidates) {
3457
+ const normalized = this.stringifyCheckoutResourceIdentifier(candidate);
3458
+ if (normalized) return normalized;
3459
+ }
3460
+ return '';
3461
+ }
3382
3462
  async normalizeCheckoutSubmitData(data, title) {
3383
3463
  if (!data || typeof data !== 'object') return data;
3384
3464
  const next = {
@@ -3936,7 +4016,8 @@ class Server {
3936
4016
  });
3937
4017
  }
3938
4018
  const normalizedData = await this.normalizeCheckoutSubmitData(data, title);
3939
- const checkoutData = await this.ensureCheckoutOrderNumbers(normalizedData, title);
4019
+ const numberedData = await this.ensureCheckoutOrderNumbers(normalizedData, title);
4020
+ const checkoutData = this.applyCheckoutFulfillmentBuzzer(numberedData, title);
3940
4021
  const pendingResult = await this.handlePendingSyncCheckoutOrder({
3941
4022
  backendPath,
3942
4023
  data: checkoutData,
@@ -151,6 +151,9 @@ export declare class BaseSalesImpl extends BaseModule implements Module {
151
151
  private getAuthoritativeBundleUnitPrice;
152
152
  private mergeProductForPriceQuery;
153
153
  protected getSubmitOrderSalesChannel(): string | undefined;
154
+ private getFulfillmentRefModeConfigs;
155
+ private getFulfillmentRefMode;
156
+ private buildSubmitPayloadEnhancerWithFulfillmentRefMode;
154
157
  /**
155
158
  * 工厂入口:根据子模块名实例化对应模块。
156
159
  * 默认使用 BookingByStep 公共 `createModule`。子类若需引入额外类型(如 customer),
@@ -36,6 +36,7 @@ var _transformBaseProductToOrderProduct = require("./utils/transformBaseProductT
36
36
  var _quotationPrice = require("./utils/quotationPrice");
37
37
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
38
38
  const SERVER_ORDER_CHANGED_EVENT = 'order:onOrdersChanged';
39
+ const OS_FULFILLMENT_REF_MODE_FIELD = '_os_fulfillment_ref_mode';
39
40
  const WALLET_PAYMENT_CODE_BY_TAG = {
40
41
  product_voucher: 'PRODUCTVOUCHER',
41
42
  gift_card: 'GIFTCARD',
@@ -1041,6 +1042,30 @@ class BaseSalesImpl extends _BaseModule.BaseModule {
1041
1042
  getSubmitOrderSalesChannel() {
1042
1043
  return this.otherParams?.channel;
1043
1044
  }
1045
+ getFulfillmentRefModeConfigs() {
1046
+ const candidates = [this.otherParams?.openData, this.otherParams?.dineInConfig, this.store?.openData?.getOpenData?.()];
1047
+ return candidates.filter(candidate => candidate && typeof candidate === 'object' && !Array.isArray(candidate));
1048
+ }
1049
+ getFulfillmentRefMode() {
1050
+ for (const config of this.getFulfillmentRefModeConfigs()) {
1051
+ const mode = config['fulfillment.fulfillment_ref_mode'];
1052
+ if (mode === undefined || mode === null) continue;
1053
+ const normalized = String(mode).trim();
1054
+ if (normalized) return normalized;
1055
+ }
1056
+ return undefined;
1057
+ }
1058
+ buildSubmitPayloadEnhancerWithFulfillmentRefMode(enhancePayload) {
1059
+ const fulfillmentRefMode = this.getFulfillmentRefMode();
1060
+ if (!fulfillmentRefMode) return enhancePayload;
1061
+ return (payload, ctx) => {
1062
+ const enhancedPayload = enhancePayload ? enhancePayload(payload, ctx) : payload;
1063
+ return {
1064
+ ...enhancedPayload,
1065
+ [OS_FULFILLMENT_REF_MODE_FIELD]: fulfillmentRefMode
1066
+ };
1067
+ };
1068
+ }
1044
1069
 
1045
1070
  /**
1046
1071
  * 工厂入口:根据子模块名实例化对应模块。
@@ -2018,6 +2043,7 @@ class BaseSalesImpl extends _BaseModule.BaseModule {
2018
2043
  // this.store.order.persistTempOrder();
2019
2044
  const inferredPaymentStatus = this.getSubmitPaymentStatus(params?.paymentStatus);
2020
2045
  const smallTicketDataFlag = params?.smallTicketDataFlag ?? this.getDefaultCheckoutSmallTicketDataFlag();
2046
+ const enhancePayload = this.buildSubmitPayloadEnhancerWithFulfillmentRefMode(params?.enhancePayload);
2021
2047
  const submitParams = {
2022
2048
  cacheId: this.cacheId,
2023
2049
  platform: this.otherParams?.platform,
@@ -2037,8 +2063,8 @@ class BaseSalesImpl extends _BaseModule.BaseModule {
2037
2063
  ...(params?.confirmPendingVoucherPayments !== undefined ? {
2038
2064
  confirmPendingVoucherPayments: params.confirmPendingVoucherPayments
2039
2065
  } : {}),
2040
- ...(params?.enhancePayload !== undefined ? {
2041
- enhancePayload: params.enhancePayload
2066
+ ...(enhancePayload !== undefined ? {
2067
+ enhancePayload
2042
2068
  } : {})
2043
2069
  };
2044
2070
  return this.shouldUseCheckoutSyncTask() ? this.store.order.submitTempOrder(submitParams) : this.store.order.submitTempOrderAsync(submitParams);
@@ -2064,6 +2090,7 @@ class BaseSalesImpl extends _BaseModule.BaseModule {
2064
2090
  }
2065
2091
  const inferredPaymentStatus = this.getSubmitPaymentStatus(params?.paymentStatus);
2066
2092
  const smallTicketDataFlag = params?.smallTicketDataFlag ?? this.getDefaultCheckoutSmallTicketDataFlag();
2093
+ const enhancePayload = this.buildSubmitPayloadEnhancerWithFulfillmentRefMode(params?.enhancePayload);
2067
2094
  const submitParams = {
2068
2095
  cacheId: this.cacheId,
2069
2096
  platform: this.otherParams?.platform,
@@ -2083,8 +2110,8 @@ class BaseSalesImpl extends _BaseModule.BaseModule {
2083
2110
  ...(params?.confirmPendingVoucherPayments !== undefined ? {
2084
2111
  confirmPendingVoucherPayments: params.confirmPendingVoucherPayments
2085
2112
  } : {}),
2086
- ...(params?.enhancePayload !== undefined ? {
2087
- enhancePayload: params.enhancePayload
2113
+ ...(enhancePayload !== undefined ? {
2114
+ enhancePayload
2088
2115
  } : {})
2089
2116
  };
2090
2117
  return this.store.order.submitTempOrderAsync(submitParams);
@@ -2157,6 +2184,7 @@ class BaseSalesImpl extends _BaseModule.BaseModule {
2157
2184
  if (!this.store.order) throw new Error('order 模块未初始化');
2158
2185
  const businessCode = params.businessCode ?? this.otherParams?.businessCode ?? this.otherParams?.business_code;
2159
2186
  const channel = params.channel ?? this.getSubmitOrderSalesChannel();
2187
+ const enhancePayload = this.buildSubmitPayloadEnhancerWithFulfillmentRefMode(params.enhancePayload);
2160
2188
  const syncParams = {
2161
2189
  ...params,
2162
2190
  checkoutSyncTaskEnabled: this.shouldUseCheckoutSyncTask(),
@@ -2165,6 +2193,9 @@ class BaseSalesImpl extends _BaseModule.BaseModule {
2165
2193
  } : {}),
2166
2194
  ...(channel !== undefined ? {
2167
2195
  channel
2196
+ } : {}),
2197
+ ...(enhancePayload !== undefined ? {
2198
+ enhancePayload
2168
2199
  } : {})
2169
2200
  };
2170
2201
  const syncState = this.store.order.getOrderSyncState();