@pisell/pisellos 0.10.21 → 0.10.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/model/strategy/adapter/promotion/index.js +0 -9
- package/dist/modules/BookingContext/index.d.ts +3 -0
- package/dist/modules/BookingContext/index.js +42 -13
- package/dist/modules/BookingContext/types.d.ts +4 -4
- package/dist/modules/BookingContext/types.js +3 -3
- package/dist/modules/BookingContext/utils/buildCacheItemFromOrderLine.js +10 -7
- package/dist/modules/Order/index.d.ts +5 -0
- package/dist/modules/Order/index.js +148 -84
- package/dist/modules/ProductList/clientDataVariants.d.ts +50 -0
- package/dist/modules/ProductList/clientDataVariants.js +216 -0
- package/dist/modules/ProductList/index.d.ts +14 -13
- package/dist/modules/ProductList/index.js +105 -77
- package/dist/modules/Quotation/index.d.ts +16 -0
- package/dist/modules/Quotation/index.js +240 -27
- package/dist/modules/ResourcePlanner/planner.d.ts +2 -2
- package/dist/modules/ResourcePlanner/planner.js +88 -21
- package/dist/modules/ResourcePlanner/types.d.ts +12 -0
- package/dist/modules/Rules/index.d.ts +5 -0
- package/dist/modules/Rules/index.js +27 -12
- package/dist/solution/BaseSales/index.d.ts +16 -1
- package/dist/solution/BaseSales/index.js +667 -349
- package/dist/solution/BookingByStep/index.d.ts +1 -1
- package/dist/solution/UnifiedBookingSales/index.d.ts +1 -1
- package/dist/solution/UnifiedBookingSales/index.js +290 -81
- package/dist/solution/UnifiedBookingSales/types.d.ts +4 -0
- package/dist/solution/VenueBooking/index.d.ts +2 -0
- package/dist/solution/VenueBooking/index.js +542 -494
- package/dist/solution/VenueBooking/utils/dateSummary.d.ts +1 -0
- package/dist/solution/VenueBooking/utils/dateSummary.js +3 -1
- package/dist/solution/VenueBooking/utils/slotMerge.d.ts +2 -0
- package/dist/solution/VenueBooking/utils/slotMerge.js +3 -1
- package/dist/utils/platform.d.ts +8 -0
- package/dist/utils/platform.js +15 -0
- package/lib/model/strategy/adapter/promotion/index.js +46 -1
- package/lib/modules/BookingContext/index.d.ts +3 -0
- package/lib/modules/BookingContext/index.js +35 -8
- package/lib/modules/BookingContext/types.d.ts +4 -4
- package/lib/modules/BookingContext/types.js +3 -3
- package/lib/modules/BookingContext/utils/buildCacheItemFromOrderLine.js +4 -0
- package/lib/modules/Order/index.d.ts +5 -0
- package/lib/modules/Order/index.js +65 -8
- package/lib/modules/ProductList/clientDataVariants.d.ts +50 -0
- package/lib/modules/ProductList/clientDataVariants.js +204 -0
- package/lib/modules/ProductList/index.d.ts +14 -13
- package/lib/modules/ProductList/index.js +77 -58
- package/lib/modules/Quotation/index.d.ts +16 -0
- package/lib/modules/Quotation/index.js +112 -9
- package/lib/modules/ResourcePlanner/planner.d.ts +2 -2
- package/lib/modules/ResourcePlanner/planner.js +80 -20
- package/lib/modules/ResourcePlanner/types.d.ts +12 -0
- package/lib/modules/Rules/index.d.ts +5 -0
- package/lib/modules/Rules/index.js +20 -11
- package/lib/solution/BaseSales/index.d.ts +16 -1
- package/lib/solution/BaseSales/index.js +293 -66
- package/lib/solution/BookingByStep/index.d.ts +1 -1
- package/lib/solution/UnifiedBookingSales/index.d.ts +1 -1
- package/lib/solution/UnifiedBookingSales/index.js +225 -20
- package/lib/solution/UnifiedBookingSales/types.d.ts +4 -0
- package/lib/solution/VenueBooking/index.d.ts +2 -0
- package/lib/solution/VenueBooking/index.js +28 -8
- package/lib/solution/VenueBooking/utils/dateSummary.d.ts +1 -0
- package/lib/solution/VenueBooking/utils/dateSummary.js +3 -1
- package/lib/solution/VenueBooking/utils/slotMerge.d.ts +2 -0
- package/lib/solution/VenueBooking/utils/slotMerge.js +3 -1
- package/lib/utils/platform.d.ts +8 -0
- package/lib/utils/platform.js +22 -0
- package/package.json +1 -1
|
@@ -11,8 +11,8 @@ var _types = require("./types");
|
|
|
11
11
|
var _localClock = require("./localClock");
|
|
12
12
|
const DEFAULT_SLOT_STEP_MINUTES = 30;
|
|
13
13
|
const DEFAULT_BUSINESS_HOURS = {
|
|
14
|
-
startTime: '
|
|
15
|
-
endTime: '
|
|
14
|
+
startTime: '00:00',
|
|
15
|
+
endTime: '00:00'
|
|
16
16
|
};
|
|
17
17
|
function availabilityError(code, message, details) {
|
|
18
18
|
return new _types.AvailabilityError(code, message, details);
|
|
@@ -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));
|
|
@@ -75,6 +88,9 @@ function addMinutesZoned(value, minutes, _timezoneName) {
|
|
|
75
88
|
function ceilToTenMinutes(value, _timezoneName) {
|
|
76
89
|
return (0, _localClock.ceilLocalScalarToTenMinutes)(value);
|
|
77
90
|
}
|
|
91
|
+
function floorToMinute(value) {
|
|
92
|
+
return Math.floor(value / 60000) * 60000;
|
|
93
|
+
}
|
|
78
94
|
function nextDate(value, _timezoneName) {
|
|
79
95
|
return (0, _localClock.nextDate)(value);
|
|
80
96
|
}
|
|
@@ -865,9 +881,30 @@ function dedupeProductRanges(ranges) {
|
|
|
865
881
|
});
|
|
866
882
|
return Array.from(map.values()).sort((left, right) => left.startAt.localeCompare(right.startAt));
|
|
867
883
|
}
|
|
868
|
-
function
|
|
884
|
+
function createDurationTimeRange(product, start, durationMinutes, projection, fixedOffsetMinutes) {
|
|
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) {
|
|
889
|
+
return {
|
|
890
|
+
key: `${String(product.id)}:${start}:${bookingEnd}`,
|
|
891
|
+
productId: product.id,
|
|
892
|
+
source: 'duration_window',
|
|
893
|
+
startAt: formatDateTime(start, projection.meta.timezone, fixedOffsetMinutes),
|
|
894
|
+
endAt: formatDateTime(bookingEnd, projection.meta.timezone, fixedOffsetMinutes),
|
|
895
|
+
bookingStartAt: formatDateTime(start, projection.meta.timezone, fixedOffsetMinutes),
|
|
896
|
+
bookingEndAt: formatDateTime(bookingEnd, projection.meta.timezone, fixedOffsetMinutes),
|
|
897
|
+
date: formatDate(start, projection.meta.timezone),
|
|
898
|
+
startTime: formatTime(start, projection.meta.timezone),
|
|
899
|
+
endTime: formatTime(bookingEnd, projection.meta.timezone),
|
|
900
|
+
timezone: projection.meta.timezone,
|
|
901
|
+
scheduleRefs: []
|
|
902
|
+
};
|
|
903
|
+
}
|
|
904
|
+
function getProductTimeRanges(projection, request = {}, runtimeOptions = {}) {
|
|
869
905
|
const normalizedPartySize = normalizePartySize(request.partySize);
|
|
870
906
|
const dateRange = resolveProjectionDateRange(projection, request.dateRange);
|
|
907
|
+
const slotStepMinutes = projection.meta.defaults.slotStepMinutes;
|
|
871
908
|
const fixedOffsetMinutes = (0, _localClock.extractFixedOffsetMinutes)(projection.meta.now) ?? (0, _localClock.captureRuntimeOffsetMinutes)();
|
|
872
909
|
const resourceWindowIndex = buildResourceWindowIndex(projection);
|
|
873
910
|
const filteredProductIdSet = request.productIds?.length ? new Set(request.productIds.map(String)) : null;
|
|
@@ -877,6 +914,7 @@ function getProductTimeRanges(projection, request = {}) {
|
|
|
877
914
|
productId: product.id,
|
|
878
915
|
title: product.title,
|
|
879
916
|
kind: product.kind,
|
|
917
|
+
slotStepMinutes,
|
|
880
918
|
ranges: []
|
|
881
919
|
};
|
|
882
920
|
}
|
|
@@ -885,6 +923,7 @@ function getProductTimeRanges(projection, request = {}) {
|
|
|
885
923
|
productId: product.id,
|
|
886
924
|
title: product.title,
|
|
887
925
|
kind: product.kind,
|
|
926
|
+
slotStepMinutes,
|
|
888
927
|
ranges: getSessionRangesForProduct(projection, product, dateRange)
|
|
889
928
|
};
|
|
890
929
|
}
|
|
@@ -900,11 +939,30 @@ function getProductTimeRanges(projection, request = {}) {
|
|
|
900
939
|
resourceWindowIndex
|
|
901
940
|
})).reduce((current, ranges) => current === null ? ranges : intersectRangeLists(current, ranges), null) || [] : null;
|
|
902
941
|
const baseRanges = resourceRanges === null ? operatingRanges : intersectRangeLists(resourceRanges, operatingRanges);
|
|
903
|
-
const durationMinutes =
|
|
904
|
-
const
|
|
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
|
+
})();
|
|
905
953
|
const durationAnchor = ceilToTenMinutes(toDateTime(projection.meta.durationCandidateAnchorAt, projection.meta.timezone), projection.meta.timezone);
|
|
954
|
+
const requestedStartTimes = resolveRequestedDurationStartTimes(product, request);
|
|
906
955
|
const ranges = [];
|
|
907
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
|
+
}
|
|
908
966
|
if (range.end <= durationAnchor) return;
|
|
909
967
|
let cursor = range.start;
|
|
910
968
|
if (cursor < durationAnchor) {
|
|
@@ -915,28 +973,28 @@ function getProductTimeRanges(projection, request = {}) {
|
|
|
915
973
|
if (bookingEnd > range.end) break;
|
|
916
974
|
const bookingDate = formatDate(cursor);
|
|
917
975
|
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
|
-
});
|
|
976
|
+
ranges.push(createDurationTimeRange(product, cursor, durationMinutes, projection, fixedOffsetMinutes));
|
|
932
977
|
}
|
|
933
|
-
cursor = addMinutesZoned(cursor,
|
|
978
|
+
cursor = addMinutesZoned(cursor, slotStepMinutes, projection.meta.timezone);
|
|
934
979
|
}
|
|
935
980
|
});
|
|
981
|
+
if (dateRange.startDate === dateRange.endDate && requestedStartTimes.length > 0) {
|
|
982
|
+
requestedStartTimes.forEach(startTime => {
|
|
983
|
+
const start = toDateTimeInDate(dateRange.startDate, startTime, projection.meta.timezone);
|
|
984
|
+
if (!Number.isFinite(start)) return;
|
|
985
|
+
const end = addMinutesZoned(start, durationMinutes, projection.meta.timezone);
|
|
986
|
+
const startsInOperatingRange = operatingRanges.some(range => range.start <= start && start < range.end);
|
|
987
|
+
if (!startsInOperatingRange && !request.allowOutsideOperatingHoursForRequestedStartTimes) {
|
|
988
|
+
return;
|
|
989
|
+
}
|
|
990
|
+
ranges.push(createDurationTimeRange(product, start, durationMinutes, projection, fixedOffsetMinutes));
|
|
991
|
+
});
|
|
992
|
+
}
|
|
936
993
|
return {
|
|
937
994
|
productId: product.id,
|
|
938
995
|
title: product.title,
|
|
939
996
|
kind: product.kind,
|
|
997
|
+
slotStepMinutes,
|
|
940
998
|
ranges: dedupeProductRanges(ranges)
|
|
941
999
|
};
|
|
942
1000
|
});
|
|
@@ -1560,6 +1618,8 @@ function queryAvailabilityProjection(projection, request) {
|
|
|
1560
1618
|
dateRange,
|
|
1561
1619
|
resourceIds: request.resourceIds,
|
|
1562
1620
|
partySize: normalizedPartySize
|
|
1621
|
+
}, {
|
|
1622
|
+
evaluatedAt
|
|
1563
1623
|
});
|
|
1564
1624
|
const candidateGroupMap = new Map(candidateGroups.map(group => [String(group.productId), group]));
|
|
1565
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;
|
|
@@ -211,6 +212,7 @@ export interface ProductTimeRangeGroup {
|
|
|
211
212
|
productId: AvailabilityId;
|
|
212
213
|
title?: string;
|
|
213
214
|
kind: AvailabilityProductKind;
|
|
215
|
+
slotStepMinutes?: number;
|
|
214
216
|
ranges: ProductTimeRange[];
|
|
215
217
|
}
|
|
216
218
|
export interface AvailabilityAssignment extends AvailabilityAbsoluteRange {
|
|
@@ -231,6 +233,16 @@ export interface GetProductTimeRangesRequest {
|
|
|
231
233
|
dateRange?: Partial<AvailabilityDateRange>;
|
|
232
234
|
resourceIds?: AvailabilityId[];
|
|
233
235
|
partySize?: number;
|
|
236
|
+
durationMinutesByProductId?: Record<string, number>;
|
|
237
|
+
durationStartTimesByProductId?: Record<string, string[]>;
|
|
238
|
+
/**
|
|
239
|
+
* POS 手工输入时间的兼容开关:只为明确请求的 start time 补候选,
|
|
240
|
+
* 不改变常规候选列表仍按营业时间生成的规则。
|
|
241
|
+
*/
|
|
242
|
+
allowOutsideOperatingHoursForRequestedStartTimes?: boolean;
|
|
243
|
+
}
|
|
244
|
+
export interface GetProductTimeRangesRuntimeOptions {
|
|
245
|
+
evaluatedAt?: string;
|
|
234
246
|
}
|
|
235
247
|
export interface QueryAvailabilityBaseRequest {
|
|
236
248
|
productIds?: 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
|
-
|
|
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
|
-
|
|
47
|
-
|
|
48
|
-
const
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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生效
|
|
@@ -129,8 +129,23 @@ export declare class BaseSalesImpl extends BaseModule implements Module {
|
|
|
129
129
|
*/
|
|
130
130
|
protected preferAuthoritativeProductQueryPrice(): boolean;
|
|
131
131
|
private getPriceQuerySchedule;
|
|
132
|
+
private isCustomerUserPriceQuery;
|
|
133
|
+
private getPriceQueryScheduleList;
|
|
134
|
+
private buildPriceQueryPayload;
|
|
135
|
+
private getRawProductPriceSources;
|
|
136
|
+
private rememberRawProductPriceSources;
|
|
137
|
+
private getSelectedProductVariantId;
|
|
138
|
+
/**
|
|
139
|
+
* 将目录原始商品还原为本次选择的 SKU 基线。
|
|
140
|
+
*
|
|
141
|
+
* 这里绝不能复用已评估后的目录商品,否则从命中智能价切回不命中的
|
|
142
|
+
* 日期时会残留旧价格。套餐仅验证所选关系存在,最终仍由 merge 方法把
|
|
143
|
+
* 评估后的套餐价格映射回用户的选择数据。
|
|
144
|
+
*/
|
|
145
|
+
private prepareRawProductForPriceQuery;
|
|
146
|
+
private resolveLocalProductForPriceQuery;
|
|
132
147
|
private loadProductsForPriceQuery;
|
|
133
|
-
private
|
|
148
|
+
private resolveProductsForPriceQuery;
|
|
134
149
|
private getAuthoritativeBundleItems;
|
|
135
150
|
private findAuthoritativeBundleItem;
|
|
136
151
|
private getAuthoritativeBundleUnitPrice;
|