@pisell/pisellos 2.3.99 → 2.3.101

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.
@@ -1,5 +1,5 @@
1
- import type { AvailabilityProjection, AvailabilityProjectionInput, AvailabilityQuery, AvailabilityQueryResult, AvailabilitySelectionValidationParams, AvailabilitySelectionValidationResult, GetProductTimeRangesRequest, ProductTimeRangeGroup } from './types';
1
+ import type { AvailabilityProjection, AvailabilityProjectionInput, AvailabilityQuery, AvailabilityQueryResult, AvailabilitySelectionValidationParams, AvailabilitySelectionValidationResult, GetProductTimeRangesRequest, GetProductTimeRangesRuntimeOptions, ProductTimeRangeGroup } from './types';
2
2
  export declare function createAvailabilityProjection(input: AvailabilityProjectionInput): AvailabilityProjection;
3
- export declare function getProductTimeRanges(projection: AvailabilityProjection, request?: GetProductTimeRangesRequest): ProductTimeRangeGroup[];
3
+ export declare function getProductTimeRanges(projection: AvailabilityProjection, request?: GetProductTimeRangesRequest, runtimeOptions?: GetProductTimeRangesRuntimeOptions): ProductTimeRangeGroup[];
4
4
  export declare function queryAvailabilityProjection(projection: AvailabilityProjection, request: AvailabilityQuery): AvailabilityQueryResult;
5
5
  export declare function validateAvailabilitySelection(projection: AvailabilityProjection, params: AvailabilitySelectionValidationParams): AvailabilitySelectionValidationResult;
@@ -96,6 +96,9 @@ function addMinutesZoned(value, minutes, _timezoneName) {
96
96
  function ceilToTenMinutes(value, _timezoneName) {
97
97
  return ceilLocalScalarToTenMinutes(value);
98
98
  }
99
+ function floorToMinute(value) {
100
+ return Math.floor(value / 60000) * 60000;
101
+ }
99
102
  function nextDate(value, _timezoneName) {
100
103
  return getNextLocalDate(value);
101
104
  }
@@ -981,6 +984,9 @@ function dedupeProductRanges(ranges) {
981
984
  }
982
985
  function createDurationTimeRange(product, start, durationMinutes, projection, fixedOffsetMinutes) {
983
986
  var bookingEnd = addMinutesZoned(start, durationMinutes, projection.meta.timezone);
987
+ return createDurationTimeRangeWithEnd(product, start, bookingEnd, projection, fixedOffsetMinutes);
988
+ }
989
+ function createDurationTimeRangeWithEnd(product, start, bookingEnd, projection, fixedOffsetMinutes) {
984
990
  return {
985
991
  key: "".concat(String(product.id), ":").concat(start, ":").concat(bookingEnd),
986
992
  productId: product.id,
@@ -999,6 +1005,7 @@ function createDurationTimeRange(product, start, durationMinutes, projection, fi
999
1005
  export function getProductTimeRanges(projection) {
1000
1006
  var _extractFixedOffsetMi3, _request$productIds;
1001
1007
  var request = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
1008
+ var runtimeOptions = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
1002
1009
  var normalizedPartySize = normalizePartySize(request.partySize);
1003
1010
  var dateRange = resolveProjectionDateRange(projection, request.dateRange);
1004
1011
  var slotStepMinutes = projection.meta.defaults.slotStepMinutes;
@@ -1045,10 +1052,29 @@ export function getProductTimeRanges(projection) {
1045
1052
  }, null) || [] : null;
1046
1053
  var baseRanges = resourceRanges === null ? operatingRanges : intersectRangeLists(resourceRanges, operatingRanges);
1047
1054
  var durationMinutes = resolveRequestedDurationMinutes(product, request);
1055
+ var flexibleStartAt = function () {
1056
+ if (!product.isFlexibleDuration) return undefined;
1057
+ var evaluatedAt = toDateTime(runtimeOptions.evaluatedAt || projection.meta.durationCandidateAnchorAt, projection.meta.timezone);
1058
+ if (!Number.isFinite(evaluatedAt)) {
1059
+ throw availabilityError('INVALID_TARGET_FILTER', 'runtimeOptions.evaluatedAt 必须是有效时间', {
1060
+ evaluatedAt: runtimeOptions.evaluatedAt
1061
+ });
1062
+ }
1063
+ return floorToMinute(evaluatedAt);
1064
+ }();
1048
1065
  var durationAnchor = ceilToTenMinutes(toDateTime(projection.meta.durationCandidateAnchorAt, projection.meta.timezone), projection.meta.timezone);
1049
1066
  var requestedStartTimes = resolveRequestedDurationStartTimes(product, request);
1050
1067
  var ranges = [];
1051
1068
  baseRanges.forEach(function (range) {
1069
+ if (product.isFlexibleDuration && flexibleStartAt !== undefined) {
1070
+ var start = Math.max(range.start, flexibleStartAt);
1071
+ if (start >= range.end) return;
1072
+ var bookingDate = formatDate(start);
1073
+ if (bookingDate >= dateRange.startDate && bookingDate <= dateRange.endDate) {
1074
+ ranges.push(createDurationTimeRangeWithEnd(product, start, range.end, projection, fixedOffsetMinutes));
1075
+ }
1076
+ return;
1077
+ }
1052
1078
  if (range.end <= durationAnchor) return;
1053
1079
  var cursor = range.start;
1054
1080
  if (cursor < durationAnchor) {
@@ -1057,8 +1083,8 @@ export function getProductTimeRanges(projection) {
1057
1083
  while (true) {
1058
1084
  var bookingEnd = addMinutesZoned(cursor, durationMinutes, projection.meta.timezone);
1059
1085
  if (bookingEnd > range.end) break;
1060
- var bookingDate = formatDate(cursor);
1061
- if (bookingDate >= dateRange.startDate && bookingDate <= dateRange.endDate) {
1086
+ var _bookingDate = formatDate(cursor);
1087
+ if (_bookingDate >= dateRange.startDate && _bookingDate <= dateRange.endDate) {
1062
1088
  ranges.push(createDurationTimeRange(product, cursor, durationMinutes, projection, fixedOffsetMinutes));
1063
1089
  }
1064
1090
  cursor = addMinutesZoned(cursor, slotStepMinutes, projection.meta.timezone);
@@ -1858,6 +1884,8 @@ export function queryAvailabilityProjection(projection, request) {
1858
1884
  dateRange: dateRange,
1859
1885
  resourceIds: request.resourceIds,
1860
1886
  partySize: normalizedPartySize
1887
+ }, {
1888
+ evaluatedAt: evaluatedAt
1861
1889
  });
1862
1890
  var candidateGroupMap = new Map(candidateGroups.map(function (group) {
1863
1891
  return [String(group.productId), group];
@@ -81,6 +81,7 @@ export interface AvailabilityProduct {
81
81
  id: AvailabilityId;
82
82
  title?: string;
83
83
  kind: AvailabilityProductKind;
84
+ isFlexibleDuration?: boolean;
84
85
  durationMinutes?: number;
85
86
  quantity?: number;
86
87
  capacityRequired?: number;
@@ -240,6 +241,9 @@ export interface GetProductTimeRangesRequest {
240
241
  */
241
242
  allowOutsideOperatingHoursForRequestedStartTimes?: boolean;
242
243
  }
244
+ export interface GetProductTimeRangesRuntimeOptions {
245
+ evaluatedAt?: string;
246
+ }
243
247
  export interface QueryAvailabilityBaseRequest {
244
248
  productIds?: AvailabilityId[];
245
249
  resourceIds?: AvailabilityId[];
@@ -14,6 +14,11 @@ export declare class RulesModule extends BaseModule implements Module, RulesModu
14
14
  setRulesList(rulesList: Rules[]): Promise<void>;
15
15
  getRulesList(): Rules[];
16
16
  getWalletPassEvaluator(): import("../..").WalletPassEvaluator | undefined;
17
+ private normalizeHolderRecordIds;
18
+ /**
19
+ * 特定 form Holder 卡只授权给绑定同一 Holder Record 的商品行。
20
+ * required/isNeedHolder 仅控制是否强制选择 Holder,不参与优惠资格。
21
+ */
17
22
  private checkHolderMatch;
18
23
  isDiscountListAvailable({ oldDiscountList, newDiscountList, productList, orderTotalAmount, holders, isFormSubject, }: {
19
24
  oldDiscountList: Discount[];
@@ -1,6 +1,5 @@
1
1
  var _excluded = ["_original_add_price"],
2
2
  _excluded2 = ["_original_price", "_original_add_price"];
3
- 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); }
4
3
  function _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }
5
4
  function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }
6
5
  function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
@@ -12,6 +11,7 @@ function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o =
12
11
  function _iterableToArray(iter) { if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) return Array.from(iter); }
13
12
  function _arrayWithoutHoles(arr) { if (Array.isArray(arr)) return _arrayLikeToArray(arr); }
14
13
  function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; return arr2; }
14
+ 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); }
15
15
  function _regeneratorRuntime() { "use strict"; /*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/facebook/regenerator/blob/main/LICENSE */ _regeneratorRuntime = function _regeneratorRuntime() { return e; }; var t, e = {}, r = Object.prototype, n = r.hasOwnProperty, o = Object.defineProperty || function (t, e, r) { t[e] = r.value; }, i = "function" == typeof Symbol ? Symbol : {}, a = i.iterator || "@@iterator", c = i.asyncIterator || "@@asyncIterator", u = i.toStringTag || "@@toStringTag"; function define(t, e, r) { return Object.defineProperty(t, e, { value: r, enumerable: !0, configurable: !0, writable: !0 }), t[e]; } try { define({}, ""); } catch (t) { define = function define(t, e, r) { return t[e] = r; }; } function wrap(t, e, r, n) { var i = e && e.prototype instanceof Generator ? e : Generator, a = Object.create(i.prototype), c = new Context(n || []); return o(a, "_invoke", { value: makeInvokeMethod(t, r, c) }), a; } function tryCatch(t, e, r) { try { return { type: "normal", arg: t.call(e, r) }; } catch (t) { return { type: "throw", arg: t }; } } e.wrap = wrap; var h = "suspendedStart", l = "suspendedYield", f = "executing", s = "completed", y = {}; function Generator() {} function GeneratorFunction() {} function GeneratorFunctionPrototype() {} var p = {}; define(p, a, function () { return this; }); var d = Object.getPrototypeOf, v = d && d(d(values([]))); v && v !== r && n.call(v, a) && (p = v); var g = GeneratorFunctionPrototype.prototype = Generator.prototype = Object.create(p); function defineIteratorMethods(t) { ["next", "throw", "return"].forEach(function (e) { define(t, e, function (t) { return this._invoke(e, t); }); }); } function AsyncIterator(t, e) { function invoke(r, o, i, a) { var c = tryCatch(t[r], t, o); if ("throw" !== c.type) { var u = c.arg, h = u.value; return h && "object" == _typeof(h) && n.call(h, "__await") ? e.resolve(h.__await).then(function (t) { invoke("next", t, i, a); }, function (t) { invoke("throw", t, i, a); }) : e.resolve(h).then(function (t) { u.value = t, i(u); }, function (t) { return invoke("throw", t, i, a); }); } a(c.arg); } var r; o(this, "_invoke", { value: function value(t, n) { function callInvokeWithMethodAndArg() { return new e(function (e, r) { invoke(t, n, e, r); }); } return r = r ? r.then(callInvokeWithMethodAndArg, callInvokeWithMethodAndArg) : callInvokeWithMethodAndArg(); } }); } function makeInvokeMethod(e, r, n) { var o = h; return function (i, a) { if (o === f) throw new Error("Generator is already running"); if (o === s) { if ("throw" === i) throw a; return { value: t, done: !0 }; } for (n.method = i, n.arg = a;;) { var c = n.delegate; if (c) { var u = maybeInvokeDelegate(c, n); if (u) { if (u === y) continue; return u; } } if ("next" === n.method) n.sent = n._sent = n.arg;else if ("throw" === n.method) { if (o === h) throw o = s, n.arg; n.dispatchException(n.arg); } else "return" === n.method && n.abrupt("return", n.arg); o = f; var p = tryCatch(e, r, n); if ("normal" === p.type) { if (o = n.done ? s : l, p.arg === y) continue; return { value: p.arg, done: n.done }; } "throw" === p.type && (o = s, n.method = "throw", n.arg = p.arg); } }; } function maybeInvokeDelegate(e, r) { var n = r.method, o = e.iterator[n]; if (o === t) return r.delegate = null, "throw" === n && e.iterator.return && (r.method = "return", r.arg = t, maybeInvokeDelegate(e, r), "throw" === r.method) || "return" !== n && (r.method = "throw", r.arg = new TypeError("The iterator does not provide a '" + n + "' method")), y; var i = tryCatch(o, e.iterator, r.arg); if ("throw" === i.type) return r.method = "throw", r.arg = i.arg, r.delegate = null, y; var a = i.arg; return a ? a.done ? (r[e.resultName] = a.value, r.next = e.nextLoc, "return" !== r.method && (r.method = "next", r.arg = t), r.delegate = null, y) : a : (r.method = "throw", r.arg = new TypeError("iterator result is not an object"), r.delegate = null, y); } function pushTryEntry(t) { var e = { tryLoc: t[0] }; 1 in t && (e.catchLoc = t[1]), 2 in t && (e.finallyLoc = t[2], e.afterLoc = t[3]), this.tryEntries.push(e); } function resetTryEntry(t) { var e = t.completion || {}; e.type = "normal", delete e.arg, t.completion = e; } function Context(t) { this.tryEntries = [{ tryLoc: "root" }], t.forEach(pushTryEntry, this), this.reset(!0); } function values(e) { if (e || "" === e) { var r = e[a]; if (r) return r.call(e); if ("function" == typeof e.next) return e; if (!isNaN(e.length)) { var o = -1, i = function next() { for (; ++o < e.length;) if (n.call(e, o)) return next.value = e[o], next.done = !1, next; return next.value = t, next.done = !0, next; }; return i.next = i; } } throw new TypeError(_typeof(e) + " is not iterable"); } return GeneratorFunction.prototype = GeneratorFunctionPrototype, o(g, "constructor", { value: GeneratorFunctionPrototype, configurable: !0 }), o(GeneratorFunctionPrototype, "constructor", { value: GeneratorFunction, configurable: !0 }), GeneratorFunction.displayName = define(GeneratorFunctionPrototype, u, "GeneratorFunction"), e.isGeneratorFunction = function (t) { var e = "function" == typeof t && t.constructor; return !!e && (e === GeneratorFunction || "GeneratorFunction" === (e.displayName || e.name)); }, e.mark = function (t) { return Object.setPrototypeOf ? Object.setPrototypeOf(t, GeneratorFunctionPrototype) : (t.__proto__ = GeneratorFunctionPrototype, define(t, u, "GeneratorFunction")), t.prototype = Object.create(g), t; }, e.awrap = function (t) { return { __await: t }; }, defineIteratorMethods(AsyncIterator.prototype), define(AsyncIterator.prototype, c, function () { return this; }), e.AsyncIterator = AsyncIterator, e.async = function (t, r, n, o, i) { void 0 === i && (i = Promise); var a = new AsyncIterator(wrap(t, r, n, o), i); return e.isGeneratorFunction(r) ? a : a.next().then(function (t) { return t.done ? t.value : a.next(); }); }, defineIteratorMethods(g), define(g, u, "Generator"), define(g, a, function () { return this; }), define(g, "toString", function () { return "[object Generator]"; }), e.keys = function (t) { var e = Object(t), r = []; for (var n in e) r.push(n); return r.reverse(), function next() { for (; r.length;) { var t = r.pop(); if (t in e) return next.value = t, next.done = !1, next; } return next.done = !0, next; }; }, e.values = values, Context.prototype = { constructor: Context, reset: function reset(e) { if (this.prev = 0, this.next = 0, this.sent = this._sent = t, this.done = !1, this.delegate = null, this.method = "next", this.arg = t, this.tryEntries.forEach(resetTryEntry), !e) for (var r in this) "t" === r.charAt(0) && n.call(this, r) && !isNaN(+r.slice(1)) && (this[r] = t); }, stop: function stop() { this.done = !0; var t = this.tryEntries[0].completion; if ("throw" === t.type) throw t.arg; return this.rval; }, dispatchException: function dispatchException(e) { if (this.done) throw e; var r = this; function handle(n, o) { return a.type = "throw", a.arg = e, r.next = n, o && (r.method = "next", r.arg = t), !!o; } for (var o = this.tryEntries.length - 1; o >= 0; --o) { var i = this.tryEntries[o], a = i.completion; if ("root" === i.tryLoc) return handle("end"); if (i.tryLoc <= this.prev) { var c = n.call(i, "catchLoc"), u = n.call(i, "finallyLoc"); if (c && u) { if (this.prev < i.catchLoc) return handle(i.catchLoc, !0); if (this.prev < i.finallyLoc) return handle(i.finallyLoc); } else if (c) { if (this.prev < i.catchLoc) return handle(i.catchLoc, !0); } else { if (!u) throw new Error("try statement without catch or finally"); if (this.prev < i.finallyLoc) return handle(i.finallyLoc); } } } }, abrupt: function abrupt(t, e) { for (var r = this.tryEntries.length - 1; r >= 0; --r) { var o = this.tryEntries[r]; if (o.tryLoc <= this.prev && n.call(o, "finallyLoc") && this.prev < o.finallyLoc) { var i = o; break; } } i && ("break" === t || "continue" === t) && i.tryLoc <= e && e <= i.finallyLoc && (i = null); var a = i ? i.completion : {}; return a.type = t, a.arg = e, i ? (this.method = "next", this.next = i.finallyLoc, y) : this.complete(a); }, complete: function complete(t, e) { if ("throw" === t.type) throw t.arg; return "break" === t.type || "continue" === t.type ? this.next = t.arg : "return" === t.type ? (this.rval = this.arg = t.arg, this.method = "return", this.next = "end") : "normal" === t.type && e && (this.next = e), y; }, finish: function finish(t) { for (var e = this.tryEntries.length - 1; e >= 0; --e) { var r = this.tryEntries[e]; if (r.finallyLoc === t) return this.complete(r.completion, r.afterLoc), resetTryEntry(r), y; } }, catch: function _catch(t) { for (var e = this.tryEntries.length - 1; e >= 0; --e) { var r = this.tryEntries[e]; if (r.tryLoc === t) { var n = r.completion; if ("throw" === n.type) { var o = n.arg; resetTryEntry(r); } return o; } } throw new Error("illegal catch attempt"); }, delegateYield: function delegateYield(e, r, n) { return this.delegate = { iterator: values(e), resultName: r, nextLoc: n }, "next" === this.method && (this.arg = t), y; } }, e; }
16
16
  function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { Promise.resolve(value).then(_next, _throw); } }
17
17
  function _asyncToGenerator(fn) { return function () { var self = this, args = arguments; return new Promise(function (resolve, reject) { var gen = fn.apply(self, args); function _next(value) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value); } function _throw(err) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err); } _next(undefined); }); }; }
@@ -109,24 +109,39 @@ export var RulesModule = /*#__PURE__*/function (_BaseModule) {
109
109
  var _this$window$getWalle, _this$window;
110
110
  return (_this$window$getWalle = (_this$window = this.window).getWalletPassEvaluator) === null || _this$window$getWalle === void 0 ? void 0 : _this$window$getWalle.call(_this$window);
111
111
  }
112
+ }, {
113
+ key: "normalizeHolderRecordIds",
114
+ value: function normalizeHolderRecordIds(value) {
115
+ var records = Array.isArray(value) ? value : [value];
116
+ return Array.from(new Set(records.map(function (record) {
117
+ if (record && _typeof(record) === 'object') {
118
+ return record.form_record_id;
119
+ }
120
+ return record;
121
+ }).filter(function (record) {
122
+ return record !== undefined && record !== null && record !== '' && record !== 0 && record !== '0';
123
+ }).map(String)));
124
+ }
112
125
 
113
- // 商品不需要holder,则不需要判断,直接返回true,商品需要holder但是还没填写,那么暂时不使用带有holder的券,直到填写才去匹配
126
+ /**
127
+ * 特定 form Holder 卡只授权给绑定同一 Holder Record 的商品行。
128
+ * required/isNeedHolder 仅控制是否强制选择 Holder,不参与优惠资格。
129
+ */
114
130
  }, {
115
131
  key: "checkHolderMatch",
116
132
  value: function checkHolderMatch(discount, product, holders) {
117
133
  var _discount$holder;
118
134
  // 非表单类型 holder 视为匹配
119
135
  if (((_discount$holder = discount.holder) === null || _discount$holder === void 0 ? void 0 : _discount$holder.holder_type) !== 'form') return true;
120
- // 主预约holder, 目前(20251124)默认只考虑单个holder的情况
121
- // 旧版 holders[0].form_record_id 新版 holders[0]
122
- var orderHolderId = Array.isArray(holders) && holders.length > 0 ? holders[0].form_record_id || holders[0] : undefined;
123
- var productHolderId = Array.isArray(product.holder_id) ? product.holder_id[0] : product.holder_id;
124
- // 商品不需要选holder,则不对比holder,直接返回true,
125
- if (!product.isNeedHolder) return true;
126
- // 商品需要holder,但父预约及商品都无holder,返回false
127
- if (!orderHolderId && !productHolderId) return false;
128
- // 最终直接匹配 holder 是否相同
129
- return (productHolderId || orderHolderId) === discount.holder.holder_id;
136
+ var discountHolderIds = this.normalizeHolderRecordIds(discount.holder.holder_id);
137
+ if (!discountHolderIds.length) return false;
138
+ var productHolderIds = this.normalizeHolderRecordIds(product.holder_id);
139
+ var candidateHolderIds = productHolderIds.length ? productHolderIds : this.normalizeHolderRecordIds(holders);
140
+ if (!candidateHolderIds.length) return false;
141
+ var discountHolderIdSet = new Set(discountHolderIds);
142
+ return candidateHolderIds.some(function (holderId) {
143
+ return discountHolderIdSet.has(holderId);
144
+ });
130
145
  }
131
146
 
132
147
  // 判断discountList 是否可以对当前productList生效
@@ -326,7 +326,7 @@ export declare class BookingByStepImpl extends BaseModule implements Module {
326
326
  date: string;
327
327
  status: string;
328
328
  week: string;
329
- weekNum: 0 | 2 | 1 | 3 | 4 | 5 | 6;
329
+ weekNum: 0 | 1 | 2 | 6 | 4 | 5 | 3;
330
330
  }[]>;
331
331
  submitTimeSlot(timeSlots: TimeSliceItem): void;
332
332
  private getScheduleDataByIds;
@@ -244,7 +244,7 @@ export declare class UnifiedBookingSalesImpl extends BookingTicket {
244
244
  private releaseAvailabilityCommitLock;
245
245
  /** IO boundary: loads catalog, schedules, resources, occupancies, and registers one context. */
246
246
  prepareAvailabilityProjection(params: UnifiedBookingSalesPrepareAvailabilityProjectionParams): Promise<AvailabilityContextRef>;
247
- /** Pure configuration calculation over the current context projection. */
247
+ /** Candidate calculation over the current context projection and runtime minute. */
248
248
  getProductTimeRanges(contextId: string, request?: GetProductTimeRangesRequest): Promise<ContextualProductTimeRangeGroup[]>;
249
249
  /** Thin facade: auto-refresh on cart drift, then delegate to the pure projection query. */
250
250
  queryBookingAvailability(contextId: string, request: ContextualAvailabilityQuery): Promise<ContextualAvailabilityQueryResult>;
@@ -36,6 +36,7 @@ function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" ==
36
36
  import dayjs from 'dayjs';
37
37
  import { cloneDeep } from 'lodash-es';
38
38
  import { OpenDataModule } from "../../modules";
39
+ import { isProductFlexibleDuration } from "../../modules/BookingContext/utils";
39
40
  import { getScheduleStartEndTimePoints } from "../../modules/Schedule/getDateIsInSchedule";
40
41
  import { AvailabilityError, createAvailabilityProjection, getProductTimeRanges as getProjectionProductTimeRanges, queryAvailabilityProjection, validateAvailabilitySelection } from "../../modules/ResourcePlanner";
41
42
  import { UnifiedBookingSalesHooks } from "./types";
@@ -544,6 +545,7 @@ export function normalizeProductForAvailability(product) {
544
545
  var fixedOffsetMinutes = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : captureRuntimeOffsetMinutes();
545
546
  var raw = product;
546
547
  var kind = resolveProductKind(raw);
548
+ var isFlexibleDuration = kind === 'duration' && isProductFlexibleDuration(raw.duration);
547
549
  var sessionRules = [];
548
550
  var sessionRanges = [];
549
551
  var explicitSession = ((_raw$cartDetailValue = raw.cartDetailValue) === null || _raw$cartDetailValue === void 0 ? void 0 : _raw$cartDetailValue.session) || ((_raw$_extend = raw._extend) === null || _raw$_extend === void 0 || (_raw$_extend = _raw$_extend.other) === null || _raw$_extend === void 0 ? void 0 : _raw$_extend.session) || raw.session;
@@ -565,7 +567,8 @@ export function normalizeProductForAvailability(product) {
565
567
  id: (_raw$id = raw.id) !== null && _raw$id !== void 0 ? _raw$id : raw.product_id,
566
568
  title: pickLocalizedText(raw.title || raw.name || raw.product_title),
567
569
  kind: kind,
568
- durationMinutes: kind === 'duration' ? Math.max(1, Number(((_raw$duration = raw.duration) === null || _raw$duration === void 0 ? void 0 : _raw$duration.value) || raw.duration || raw.service_duration || 30) || 30) : undefined,
570
+ isFlexibleDuration: isFlexibleDuration || undefined,
571
+ durationMinutes: kind === 'duration' && !isFlexibleDuration ? Math.max(1, Number(((_raw$duration = raw.duration) === null || _raw$duration === void 0 ? void 0 : _raw$duration.value) || raw.duration || raw.service_duration || 30) || 30) : undefined,
569
572
  requirementGroups: normalizeRequirementGroups(raw),
570
573
  sessionRules: sessionRules,
571
574
  sessionRanges: sessionRanges,
@@ -4457,7 +4460,7 @@ export var UnifiedBookingSalesImpl = /*#__PURE__*/function (_BookingTicket) {
4457
4460
  return _prepareAvailabilityProjection.apply(this, arguments);
4458
4461
  }
4459
4462
  return prepareAvailabilityProjection;
4460
- }() /** Pure configuration calculation over the current context projection. */)
4463
+ }() /** Candidate calculation over the current context projection and runtime minute. */)
4461
4464
  }, {
4462
4465
  key: "getProductTimeRanges",
4463
4466
  value: (function () {
@@ -4488,7 +4491,9 @@ export var UnifiedBookingSalesImpl = /*#__PURE__*/function (_BookingTicket) {
4488
4491
  });
4489
4492
  return _context40.abrupt("return", getProjectionProductTimeRanges(projectionEntry.projection, _objectSpread(_objectSpread({}, request), allowOutsideOperatingHoursForRequestedStartTimes ? {
4490
4493
  allowOutsideOperatingHoursForRequestedStartTimes: true
4491
- } : {})).map(function (group) {
4494
+ } : {}), {
4495
+ evaluatedAt: this.getAvailabilityRuntimeDateTime()
4496
+ }).map(function (group) {
4492
4497
  return _objectSpread(_objectSpread({}, group), {}, {
4493
4498
  ranges: group.ranges.map(function (candidate) {
4494
4499
  return _this10.decorateAvailabilityCandidate(context.contextId, contextVersion, candidate);
@@ -1,5 +1,5 @@
1
- import type { AvailabilityProjection, AvailabilityProjectionInput, AvailabilityQuery, AvailabilityQueryResult, AvailabilitySelectionValidationParams, AvailabilitySelectionValidationResult, GetProductTimeRangesRequest, ProductTimeRangeGroup } from './types';
1
+ import type { AvailabilityProjection, AvailabilityProjectionInput, AvailabilityQuery, AvailabilityQueryResult, AvailabilitySelectionValidationParams, AvailabilitySelectionValidationResult, GetProductTimeRangesRequest, GetProductTimeRangesRuntimeOptions, ProductTimeRangeGroup } from './types';
2
2
  export declare function createAvailabilityProjection(input: AvailabilityProjectionInput): AvailabilityProjection;
3
- export declare function getProductTimeRanges(projection: AvailabilityProjection, request?: GetProductTimeRangesRequest): ProductTimeRangeGroup[];
3
+ export declare function getProductTimeRanges(projection: AvailabilityProjection, request?: GetProductTimeRangesRequest, runtimeOptions?: GetProductTimeRangesRuntimeOptions): ProductTimeRangeGroup[];
4
4
  export declare function queryAvailabilityProjection(projection: AvailabilityProjection, request: AvailabilityQuery): AvailabilityQueryResult;
5
5
  export declare function validateAvailabilitySelection(projection: AvailabilityProjection, params: AvailabilitySelectionValidationParams): AvailabilitySelectionValidationResult;
@@ -88,6 +88,9 @@ function addMinutesZoned(value, minutes, _timezoneName) {
88
88
  function ceilToTenMinutes(value, _timezoneName) {
89
89
  return (0, _localClock.ceilLocalScalarToTenMinutes)(value);
90
90
  }
91
+ function floorToMinute(value) {
92
+ return Math.floor(value / 60000) * 60000;
93
+ }
91
94
  function nextDate(value, _timezoneName) {
92
95
  return (0, _localClock.nextDate)(value);
93
96
  }
@@ -880,6 +883,9 @@ function dedupeProductRanges(ranges) {
880
883
  }
881
884
  function createDurationTimeRange(product, start, durationMinutes, projection, fixedOffsetMinutes) {
882
885
  const bookingEnd = addMinutesZoned(start, durationMinutes, projection.meta.timezone);
886
+ return createDurationTimeRangeWithEnd(product, start, bookingEnd, projection, fixedOffsetMinutes);
887
+ }
888
+ function createDurationTimeRangeWithEnd(product, start, bookingEnd, projection, fixedOffsetMinutes) {
883
889
  return {
884
890
  key: `${String(product.id)}:${start}:${bookingEnd}`,
885
891
  productId: product.id,
@@ -895,7 +901,7 @@ function createDurationTimeRange(product, start, durationMinutes, projection, fi
895
901
  scheduleRefs: []
896
902
  };
897
903
  }
898
- function getProductTimeRanges(projection, request = {}) {
904
+ function getProductTimeRanges(projection, request = {}, runtimeOptions = {}) {
899
905
  const normalizedPartySize = normalizePartySize(request.partySize);
900
906
  const dateRange = resolveProjectionDateRange(projection, request.dateRange);
901
907
  const slotStepMinutes = projection.meta.defaults.slotStepMinutes;
@@ -934,10 +940,29 @@ function getProductTimeRanges(projection, request = {}) {
934
940
  })).reduce((current, ranges) => current === null ? ranges : intersectRangeLists(current, ranges), null) || [] : null;
935
941
  const baseRanges = resourceRanges === null ? operatingRanges : intersectRangeLists(resourceRanges, operatingRanges);
936
942
  const durationMinutes = resolveRequestedDurationMinutes(product, request);
943
+ const flexibleStartAt = (() => {
944
+ if (!product.isFlexibleDuration) return undefined;
945
+ const evaluatedAt = toDateTime(runtimeOptions.evaluatedAt || projection.meta.durationCandidateAnchorAt, projection.meta.timezone);
946
+ if (!Number.isFinite(evaluatedAt)) {
947
+ throw availabilityError('INVALID_TARGET_FILTER', 'runtimeOptions.evaluatedAt 必须是有效时间', {
948
+ evaluatedAt: runtimeOptions.evaluatedAt
949
+ });
950
+ }
951
+ return floorToMinute(evaluatedAt);
952
+ })();
937
953
  const durationAnchor = ceilToTenMinutes(toDateTime(projection.meta.durationCandidateAnchorAt, projection.meta.timezone), projection.meta.timezone);
938
954
  const requestedStartTimes = resolveRequestedDurationStartTimes(product, request);
939
955
  const ranges = [];
940
956
  baseRanges.forEach(range => {
957
+ if (product.isFlexibleDuration && flexibleStartAt !== undefined) {
958
+ const start = Math.max(range.start, flexibleStartAt);
959
+ if (start >= range.end) return;
960
+ const bookingDate = formatDate(start);
961
+ if (bookingDate >= dateRange.startDate && bookingDate <= dateRange.endDate) {
962
+ ranges.push(createDurationTimeRangeWithEnd(product, start, range.end, projection, fixedOffsetMinutes));
963
+ }
964
+ return;
965
+ }
941
966
  if (range.end <= durationAnchor) return;
942
967
  let cursor = range.start;
943
968
  if (cursor < durationAnchor) {
@@ -1593,6 +1618,8 @@ function queryAvailabilityProjection(projection, request) {
1593
1618
  dateRange,
1594
1619
  resourceIds: request.resourceIds,
1595
1620
  partySize: normalizedPartySize
1621
+ }, {
1622
+ evaluatedAt
1596
1623
  });
1597
1624
  const candidateGroupMap = new Map(candidateGroups.map(group => [String(group.productId), group]));
1598
1625
  if (request.target === 'date') {
@@ -81,6 +81,7 @@ export interface AvailabilityProduct {
81
81
  id: AvailabilityId;
82
82
  title?: string;
83
83
  kind: AvailabilityProductKind;
84
+ isFlexibleDuration?: boolean;
84
85
  durationMinutes?: number;
85
86
  quantity?: number;
86
87
  capacityRequired?: number;
@@ -240,6 +241,9 @@ export interface GetProductTimeRangesRequest {
240
241
  */
241
242
  allowOutsideOperatingHoursForRequestedStartTimes?: boolean;
242
243
  }
244
+ export interface GetProductTimeRangesRuntimeOptions {
245
+ evaluatedAt?: string;
246
+ }
243
247
  export interface QueryAvailabilityBaseRequest {
244
248
  productIds?: AvailabilityId[];
245
249
  resourceIds?: AvailabilityId[];
@@ -14,6 +14,11 @@ export declare class RulesModule extends BaseModule implements Module, RulesModu
14
14
  setRulesList(rulesList: Rules[]): Promise<void>;
15
15
  getRulesList(): Rules[];
16
16
  getWalletPassEvaluator(): import("../..").WalletPassEvaluator | undefined;
17
+ private normalizeHolderRecordIds;
18
+ /**
19
+ * 特定 form Holder 卡只授权给绑定同一 Holder Record 的商品行。
20
+ * required/isNeedHolder 仅控制是否强制选择 Holder,不参与优惠资格。
21
+ */
17
22
  private checkHolderMatch;
18
23
  isDiscountListAvailable({ oldDiscountList, newDiscountList, productList, orderTotalAmount, holders, isFormSubject, }: {
19
24
  oldDiscountList: Discount[];
@@ -38,21 +38,30 @@ class RulesModule extends _BaseModule.BaseModule {
38
38
  getWalletPassEvaluator() {
39
39
  return this.window.getWalletPassEvaluator?.();
40
40
  }
41
+ normalizeHolderRecordIds(value) {
42
+ const records = Array.isArray(value) ? value : [value];
43
+ return Array.from(new Set(records.map(record => {
44
+ if (record && typeof record === 'object') {
45
+ return record.form_record_id;
46
+ }
47
+ return record;
48
+ }).filter(record => record !== undefined && record !== null && record !== '' && record !== 0 && record !== '0').map(String)));
49
+ }
41
50
 
42
- // 商品不需要holder,则不需要判断,直接返回true,商品需要holder但是还没填写,那么暂时不使用带有holder的券,直到填写才去匹配
51
+ /**
52
+ * 特定 form Holder 卡只授权给绑定同一 Holder Record 的商品行。
53
+ * required/isNeedHolder 仅控制是否强制选择 Holder,不参与优惠资格。
54
+ */
43
55
  checkHolderMatch(discount, product, holders) {
44
56
  // 非表单类型 holder 视为匹配
45
57
  if (discount.holder?.holder_type !== 'form') return true;
46
- // 主预约holder, 目前(20251124)默认只考虑单个holder的情况
47
- // 旧版 holders[0].form_record_id 新版 holders[0]
48
- const orderHolderId = Array.isArray(holders) && holders.length > 0 ? holders[0].form_record_id || holders[0] : undefined;
49
- const productHolderId = Array.isArray(product.holder_id) ? product.holder_id[0] : product.holder_id;
50
- // 商品不需要选holder,则不对比holder,直接返回true,
51
- if (!product.isNeedHolder) return true;
52
- // 商品需要holder,但父预约及商品都无holder,返回false
53
- if (!orderHolderId && !productHolderId) return false;
54
- // 最终直接匹配 holder 是否相同
55
- return (productHolderId || orderHolderId) === discount.holder.holder_id;
58
+ const discountHolderIds = this.normalizeHolderRecordIds(discount.holder.holder_id);
59
+ if (!discountHolderIds.length) return false;
60
+ const productHolderIds = this.normalizeHolderRecordIds(product.holder_id);
61
+ const candidateHolderIds = productHolderIds.length ? productHolderIds : this.normalizeHolderRecordIds(holders);
62
+ if (!candidateHolderIds.length) return false;
63
+ const discountHolderIdSet = new Set(discountHolderIds);
64
+ return candidateHolderIds.some(holderId => discountHolderIdSet.has(holderId));
56
65
  }
57
66
 
58
67
  // 判断discountList 是否可以对当前productList生效
@@ -326,7 +326,7 @@ export declare class BookingByStepImpl extends BaseModule implements Module {
326
326
  date: string;
327
327
  status: string;
328
328
  week: string;
329
- weekNum: 0 | 2 | 1 | 3 | 4 | 5 | 6;
329
+ weekNum: 0 | 1 | 2 | 6 | 4 | 5 | 3;
330
330
  }[]>;
331
331
  submitTimeSlot(timeSlots: TimeSliceItem): void;
332
332
  private getScheduleDataByIds;
@@ -244,7 +244,7 @@ export declare class UnifiedBookingSalesImpl extends BookingTicket {
244
244
  private releaseAvailabilityCommitLock;
245
245
  /** IO boundary: loads catalog, schedules, resources, occupancies, and registers one context. */
246
246
  prepareAvailabilityProjection(params: UnifiedBookingSalesPrepareAvailabilityProjectionParams): Promise<AvailabilityContextRef>;
247
- /** Pure configuration calculation over the current context projection. */
247
+ /** Candidate calculation over the current context projection and runtime minute. */
248
248
  getProductTimeRanges(contextId: string, request?: GetProductTimeRangesRequest): Promise<ContextualProductTimeRangeGroup[]>;
249
249
  /** Thin facade: auto-refresh on cart drift, then delegate to the pure projection query. */
250
250
  queryBookingAvailability(contextId: string, request: ContextualAvailabilityQuery): Promise<ContextualAvailabilityQueryResult>;
@@ -32,6 +32,7 @@ exports.normalizeProductForAvailability = normalizeProductForAvailability;
32
32
  var _dayjs = _interopRequireDefault(require("dayjs"));
33
33
  var _lodashEs = require("lodash-es");
34
34
  var _modules = require("../../modules");
35
+ var _utils = require("../../modules/BookingContext/utils");
35
36
  var _getDateIsInSchedule = require("../../modules/Schedule/getDateIsInSchedule");
36
37
  var _ResourcePlanner = require("../../modules/ResourcePlanner");
37
38
  var _types = require("./types");
@@ -503,6 +504,7 @@ function resolveProductScheduleItems(product, scheduleList) {
503
504
  function normalizeProductForAvailability(product, scheduleList = [], fixedOffsetMinutes = (0, _localClock.captureRuntimeOffsetMinutes)()) {
504
505
  const raw = product;
505
506
  const kind = resolveProductKind(raw);
507
+ const isFlexibleDuration = kind === 'duration' && (0, _utils.isProductFlexibleDuration)(raw.duration);
506
508
  const sessionRules = [];
507
509
  const sessionRanges = [];
508
510
  const explicitSession = raw.cartDetailValue?.session || raw._extend?.other?.session || raw.session;
@@ -524,7 +526,8 @@ function normalizeProductForAvailability(product, scheduleList = [], fixedOffset
524
526
  id: raw.id ?? raw.product_id,
525
527
  title: pickLocalizedText(raw.title || raw.name || raw.product_title),
526
528
  kind,
527
- durationMinutes: kind === 'duration' ? Math.max(1, Number(raw.duration?.value || raw.duration || raw.service_duration || 30) || 30) : undefined,
529
+ isFlexibleDuration: isFlexibleDuration || undefined,
530
+ durationMinutes: kind === 'duration' && !isFlexibleDuration ? Math.max(1, Number(raw.duration?.value || raw.duration || raw.service_duration || 30) || 30) : undefined,
528
531
  requirementGroups: normalizeRequirementGroups(raw),
529
532
  sessionRules,
530
533
  sessionRanges,
@@ -2919,7 +2922,7 @@ class UnifiedBookingSalesImpl extends _BookingTicket.BookingTicket {
2919
2922
  return handle;
2920
2923
  }
2921
2924
 
2922
- /** Pure configuration calculation over the current context projection. */
2925
+ /** Candidate calculation over the current context projection and runtime minute. */
2923
2926
  async getProductTimeRanges(contextId, request = {}) {
2924
2927
  const {
2925
2928
  context,
@@ -2932,6 +2935,8 @@ class UnifiedBookingSalesImpl extends _BookingTicket.BookingTicket {
2932
2935
  ...(allowOutsideOperatingHoursForRequestedStartTimes ? {
2933
2936
  allowOutsideOperatingHoursForRequestedStartTimes: true
2934
2937
  } : {})
2938
+ }, {
2939
+ evaluatedAt: this.getAvailabilityRuntimeDateTime()
2935
2940
  }).map(group => ({
2936
2941
  ...group,
2937
2942
  ranges: group.ranges.map(candidate => this.decorateAvailabilityCandidate(context.contextId, contextVersion, candidate))
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "private": false,
3
3
  "name": "@pisell/pisellos",
4
- "version": "2.3.99",
4
+ "version": "2.3.101",
5
5
  "description": "一个可扩展的前端模块化SDK框架,支持插件系统",
6
6
  "main": "dist/index.js",
7
7
  "types": "dist/index.d.ts",