@pisell/pisellos 2.3.93 → 2.3.95
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/itemRule/adapter.d.ts +4 -1
- package/dist/model/strategy/adapter/itemRule/adapter.js +135 -35
- package/dist/model/strategy/adapter/itemRule/evaluator.d.ts +8 -1
- package/dist/model/strategy/adapter/itemRule/evaluator.js +25 -1
- package/dist/model/strategy/adapter/itemRule/type.d.ts +44 -0
- package/dist/model/strategy/adapter/itemRule/type.js +19 -1
- package/dist/modules/Order/index.d.ts +18 -3
- package/dist/modules/Order/index.js +885 -485
- package/dist/modules/Order/salesNotes.d.ts +31 -0
- package/dist/modules/Order/salesNotes.js +492 -0
- package/dist/modules/Order/types.d.ts +91 -5
- package/dist/modules/Order/types.js +5 -0
- package/dist/modules/Order/utils.d.ts +12 -0
- package/dist/modules/Order/utils.js +75 -26
- package/dist/modules/ProductList/index.d.ts +2 -1
- package/dist/modules/ProductList/index.js +100 -20
- package/dist/modules/ProductList/types.d.ts +10 -0
- package/dist/modules/ResourcePlanner/planner.js +5 -3
- package/dist/modules/ResourcePlanner/types.d.ts +5 -0
- package/dist/server/index.d.ts +6 -0
- package/dist/server/index.js +43 -3
- package/dist/solution/BaseSales/index.d.ts +9 -1
- package/dist/solution/BaseSales/index.js +894 -706
- package/dist/solution/BookingByStep/index.d.ts +2 -2
- package/dist/solution/BookingTicket/utils/orderCustomer.js +3 -2
- package/dist/solution/RegisterAndLogin/config.d.ts +9 -3
- package/dist/solution/RegisterAndLogin/config.js +95 -40
- package/dist/solution/RegisterAndLogin/index.js +4 -1
- package/dist/solution/ScanOrder/index.d.ts +1 -0
- package/dist/solution/ScanOrder/index.js +31 -27
- package/dist/solution/ScanOrder/utils.d.ts +1 -0
- package/dist/solution/ScanOrder/utils.js +2 -0
- package/dist/solution/UnifiedBookingSales/index.d.ts +15 -2
- package/dist/solution/UnifiedBookingSales/index.js +767 -488
- package/dist/solution/UnifiedBookingSales/types.d.ts +14 -1
- package/dist/solution/VenueBooking/index.d.ts +1 -0
- package/dist/solution/VenueBooking/index.js +8 -4
- package/lib/model/strategy/adapter/itemRule/adapter.d.ts +4 -1
- package/lib/model/strategy/adapter/itemRule/adapter.js +73 -4
- package/lib/model/strategy/adapter/itemRule/evaluator.d.ts +8 -1
- package/lib/model/strategy/adapter/itemRule/evaluator.js +23 -1
- package/lib/model/strategy/adapter/itemRule/type.d.ts +44 -0
- package/lib/model/strategy/adapter/itemRule/type.js +19 -1
- package/lib/modules/Order/index.d.ts +18 -3
- package/lib/modules/Order/index.js +325 -28
- package/lib/modules/Order/salesNotes.d.ts +31 -0
- package/lib/modules/Order/salesNotes.js +382 -0
- package/lib/modules/Order/types.d.ts +91 -5
- package/lib/modules/Order/types.js +4 -0
- package/lib/modules/Order/utils.d.ts +12 -0
- package/lib/modules/Order/utils.js +68 -16
- package/lib/modules/ProductList/index.d.ts +2 -1
- package/lib/modules/ProductList/index.js +47 -2
- package/lib/modules/ProductList/types.d.ts +10 -0
- package/lib/modules/ResourcePlanner/planner.js +5 -3
- package/lib/modules/ResourcePlanner/types.d.ts +5 -0
- package/lib/server/index.d.ts +6 -0
- package/lib/server/index.js +37 -3
- package/lib/solution/BaseSales/index.d.ts +9 -1
- package/lib/solution/BaseSales/index.js +105 -1
- package/lib/solution/BookingByStep/index.d.ts +2 -2
- package/lib/solution/BookingTicket/utils/orderCustomer.js +2 -1
- package/lib/solution/RegisterAndLogin/config.d.ts +9 -3
- package/lib/solution/RegisterAndLogin/config.js +49 -8
- package/lib/solution/RegisterAndLogin/index.js +4 -1
- package/lib/solution/ScanOrder/index.d.ts +1 -0
- package/lib/solution/ScanOrder/index.js +5 -1
- package/lib/solution/ScanOrder/utils.d.ts +1 -0
- package/lib/solution/ScanOrder/utils.js +1 -0
- package/lib/solution/UnifiedBookingSales/index.d.ts +15 -2
- package/lib/solution/UnifiedBookingSales/index.js +140 -6
- package/lib/solution/UnifiedBookingSales/types.d.ts +14 -1
- package/lib/solution/VenueBooking/index.d.ts +1 -0
- package/lib/solution/VenueBooking/index.js +5 -1
- package/package.json +1 -1
|
@@ -15,6 +15,8 @@ exports.getChannelConfig = getChannelConfig;
|
|
|
15
15
|
* API 配置接口
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
|
+
const h5CustomerAuthApiNames = new Set(['sendEmailVerificationCode', 'sendEmailLoginCode', 'sendSmsRegisterCode', 'sendEmailRegisterLink', 'verifyEmailRegistrationLink', 'emailPasswordLogin', 'emailCodeRegister', 'phoneCodeRegister', 'resendEmailRegisterLink', 'checkEmailLinkCode', 'sendSmsLoginCode', 'phoneCodeLogin', 'guestLogin', 'checkEmailExists', 'checkEmailCode', 'checkMobileCode', 'phonePasswordLogin', 'sendPasswordResetEmail', 'sendPasswordResetSms', 'sendResetPasswordLink', 'checkResetPasswordCode', 'resetPasswordByCode', 'resetPasswordByEmail', 'resetPasswordByPhone', 'verifyCode', 'register', 'login', 'oauthLogin', 'facebookLogin', 'appleLogin']);
|
|
19
|
+
|
|
18
20
|
/**
|
|
19
21
|
* 渠道配置接口
|
|
20
22
|
*/
|
|
@@ -278,6 +280,31 @@ const onlineStoreConfig = {
|
|
|
278
280
|
})
|
|
279
281
|
}
|
|
280
282
|
};
|
|
283
|
+
function withH5AuthPrefix(config) {
|
|
284
|
+
const nextConfig = {};
|
|
285
|
+
Object.entries(config).forEach(([key, apiConfig]) => {
|
|
286
|
+
if (!apiConfig.url.startsWith('/auth/')) {
|
|
287
|
+
nextConfig[key] = apiConfig;
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
nextConfig[key] = {
|
|
291
|
+
...apiConfig,
|
|
292
|
+
url: apiConfig.url.startsWith('/h5/') ? apiConfig.url : `/h5${apiConfig.url}`,
|
|
293
|
+
options: params => {
|
|
294
|
+
const {
|
|
295
|
+
withCredentials,
|
|
296
|
+
...baseOptions
|
|
297
|
+
} = (typeof apiConfig.options === 'function' ? apiConfig.options(params) : apiConfig.options) || {};
|
|
298
|
+
return {
|
|
299
|
+
...baseOptions,
|
|
300
|
+
prefix: false
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
};
|
|
304
|
+
});
|
|
305
|
+
return nextConfig;
|
|
306
|
+
}
|
|
307
|
+
const h5OnlineStoreConfig = withH5AuthPrefix(onlineStoreConfig);
|
|
281
308
|
|
|
282
309
|
/**
|
|
283
310
|
* 默认配置(通用配置)
|
|
@@ -1066,6 +1093,7 @@ const shopWebposConfig = {
|
|
|
1066
1093
|
*/
|
|
1067
1094
|
const channelConfigMap = exports.channelConfigMap = {
|
|
1068
1095
|
'online-store': onlineStoreConfig,
|
|
1096
|
+
'online-store-h5': h5OnlineStoreConfig,
|
|
1069
1097
|
'default': defaultConfig,
|
|
1070
1098
|
'webpos': webposConfig,
|
|
1071
1099
|
'shop.webpos': shopWebposConfig
|
|
@@ -1083,9 +1111,16 @@ function getChannelConfig(channel) {
|
|
|
1083
1111
|
* 负责根据配置执行 API 调用
|
|
1084
1112
|
*/
|
|
1085
1113
|
class ApiCaller {
|
|
1086
|
-
constructor(request, config) {
|
|
1114
|
+
constructor(request, config, options = {}) {
|
|
1087
1115
|
this.request = request;
|
|
1088
1116
|
this.config = config;
|
|
1117
|
+
this.options = options;
|
|
1118
|
+
}
|
|
1119
|
+
resolveUrl(url) {
|
|
1120
|
+
if (!this.options.baseUrl || !url.startsWith('/h5/auth/')) {
|
|
1121
|
+
return url;
|
|
1122
|
+
}
|
|
1123
|
+
return `${this.options.baseUrl.replace(/\/$/, '')}${url}`;
|
|
1089
1124
|
}
|
|
1090
1125
|
|
|
1091
1126
|
/**
|
|
@@ -1111,6 +1146,12 @@ class ApiCaller {
|
|
|
1111
1146
|
...options,
|
|
1112
1147
|
...additionalOptions
|
|
1113
1148
|
};
|
|
1149
|
+
if (apiConfig.url.startsWith('/h5/auth/') && h5CustomerAuthApiNames.has(apiName)) {
|
|
1150
|
+
requestOptions.h5CustomerAuth = true;
|
|
1151
|
+
if (this.options.shopDomain) {
|
|
1152
|
+
requestOptions.h5AuthShopDomain = this.options.shopDomain;
|
|
1153
|
+
}
|
|
1154
|
+
}
|
|
1114
1155
|
|
|
1115
1156
|
// 处理动态 headers
|
|
1116
1157
|
if (requestOptions.headers && typeof requestOptions.headers === 'function') {
|
|
@@ -1123,19 +1164,19 @@ class ApiCaller {
|
|
|
1123
1164
|
switch (apiConfig.method) {
|
|
1124
1165
|
case 'GET':
|
|
1125
1166
|
if (transformedParams && Object.keys(transformedParams).length > 0) {
|
|
1126
|
-
response = await this.request.get(apiConfig.url, transformedParams, requestOptions);
|
|
1167
|
+
response = await this.request.get(this.resolveUrl(apiConfig.url), transformedParams, requestOptions);
|
|
1127
1168
|
} else {
|
|
1128
|
-
response = await this.request.get(apiConfig.url, undefined, requestOptions);
|
|
1169
|
+
response = await this.request.get(this.resolveUrl(apiConfig.url), undefined, requestOptions);
|
|
1129
1170
|
}
|
|
1130
1171
|
break;
|
|
1131
1172
|
case 'POST':
|
|
1132
|
-
response = await this.request.post(apiConfig.url, transformedParams, requestOptions);
|
|
1173
|
+
response = await this.request.post(this.resolveUrl(apiConfig.url), transformedParams, requestOptions);
|
|
1133
1174
|
break;
|
|
1134
1175
|
case 'PUT':
|
|
1135
|
-
response = await this.request.put(apiConfig.url, transformedParams, requestOptions);
|
|
1176
|
+
response = await this.request.put(this.resolveUrl(apiConfig.url), transformedParams, requestOptions);
|
|
1136
1177
|
break;
|
|
1137
1178
|
case 'DELETE':
|
|
1138
|
-
response = await this.request.delete(apiConfig.url, requestOptions);
|
|
1179
|
+
response = await this.request.delete(this.resolveUrl(apiConfig.url), requestOptions);
|
|
1139
1180
|
break;
|
|
1140
1181
|
default:
|
|
1141
1182
|
throw new Error(`不支持的 HTTP 方法: ${apiConfig.method}`);
|
|
@@ -1150,7 +1191,7 @@ class ApiCaller {
|
|
|
1150
1191
|
* 创建 API 调用器
|
|
1151
1192
|
*/
|
|
1152
1193
|
exports.ApiCaller = ApiCaller;
|
|
1153
|
-
function createApiCaller(request, channel) {
|
|
1194
|
+
function createApiCaller(request, channel, options) {
|
|
1154
1195
|
const config = getChannelConfig(channel);
|
|
1155
|
-
return new ApiCaller(request, config);
|
|
1196
|
+
return new ApiCaller(request, config, options);
|
|
1156
1197
|
}
|
|
@@ -227,7 +227,10 @@ class RegisterAndLoginImpl extends _BaseModule.BaseModule {
|
|
|
227
227
|
}
|
|
228
228
|
|
|
229
229
|
// 创建 API 调用器
|
|
230
|
-
this.apiCaller = (0, _config.createApiCaller)(this.request, this.channel
|
|
230
|
+
this.apiCaller = (0, _config.createApiCaller)(this.request, this.channel, {
|
|
231
|
+
baseUrl: this.otherParams.h5AuthBaseUrl,
|
|
232
|
+
shopDomain: this.otherParams.h5AuthShopDomain
|
|
233
|
+
});
|
|
231
234
|
|
|
232
235
|
// 尝试从本地存储恢复登录状态
|
|
233
236
|
await this.restoreLoginState();
|
|
@@ -1200,7 +1200,11 @@ class ScanOrderImpl extends _BaseSales.BaseSalesImpl {
|
|
|
1200
1200
|
return sourceMap;
|
|
1201
1201
|
}
|
|
1202
1202
|
getItemRuleRuntimeConfig() {
|
|
1203
|
-
|
|
1203
|
+
const runtimeConfig = this.itemRuleRuntimeConfig || {};
|
|
1204
|
+
return {
|
|
1205
|
+
...runtimeConfig,
|
|
1206
|
+
channel: runtimeConfig.channel ?? runtimeConfig.custom?.channel ?? this.otherParams?.channel
|
|
1207
|
+
};
|
|
1204
1208
|
}
|
|
1205
1209
|
async ensureItemRuleConfigsLoaded() {
|
|
1206
1210
|
if (this.itemRuleConfigs.length > 0) return this.itemRuleConfigs;
|
|
@@ -333,6 +333,7 @@ function buildItemRuleBusinessData(params) {
|
|
|
333
333
|
cartItems,
|
|
334
334
|
historicalItems,
|
|
335
335
|
serviceType: runtimeConfig.serviceType || 'dine-in',
|
|
336
|
+
channel: runtimeConfig.channel ?? runtimeConfig.custom?.channel,
|
|
336
337
|
submissionIndex: typeof runtimeConfig.submissionIndex === 'number' ? toNonNegativeInt(runtimeConfig.submissionIndex) : tempOrder.order_id ? 1 : 0,
|
|
337
338
|
custom: runtimeConfig.custom || {},
|
|
338
339
|
strategyConfigs: itemRuleConfigs
|
|
@@ -2,10 +2,11 @@ import type { Module, ModuleOptions, PisellCore } from '../../types';
|
|
|
2
2
|
import type { ProductData } from '../../modules';
|
|
3
3
|
import type { OpenDataConfig } from '../../modules/OpenData';
|
|
4
4
|
import type { LoadScheduleAvailableDateParams, ScheduleItem } from '../../modules/Schedule/types';
|
|
5
|
-
import type { ProductListLoadProductsParams } from '../../modules/ProductList';
|
|
5
|
+
import type { ProductListLoadProductsParams, ProductListStoreQueryParams } from '../../modules/ProductList';
|
|
6
6
|
import type { AvailabilityDateRange, AvailabilityOccupancy, AvailabilityProduct, AvailabilityResource, GetProductTimeRangesRequest } from '../../modules/ResourcePlanner';
|
|
7
|
-
import type { AvailabilityContextRef, ContextualAvailabilityQuery, ContextualAvailabilityQueryResult, ContextualCommitAvailabilitySelectionParams, ContextualProductTimeRangeGroup, UnifiedBookingSalesAddRetailProductParams, UnifiedBookingSalesApplyPlannerDiscountsParams, UnifiedBookingSalesCommitAvailabilitySelectionResult, UnifiedBookingSalesDiscountResult, UnifiedBookingSalesLoadProductsByScheduleDateParams, UnifiedBookingSalesLoadOpenDataParams, UnifiedBookingSalesPrepareAvailabilityProjectionParams, UnifiedBookingSalesPolicyResponse, UnifiedBookingSalesStageRetailProductsParams, UnifiedBookingSalesState } from './types';
|
|
7
|
+
import type { AvailabilityContextRef, ContextualAvailabilityQuery, ContextualAvailabilityQueryResult, ContextualCommitAvailabilitySelectionParams, ContextualProductTimeRangeGroup, UnifiedBookingSalesAddRetailProductParams, UnifiedBookingSalesApplyPlannerDiscountsParams, UnifiedBookingSalesCommitAvailabilitySelectionResult, UnifiedBookingSalesDiscountResult, UnifiedBookingSalesLoadProductsByScheduleDateParams, UnifiedBookingSalesLoadOpenDataParams, UnifiedBookingSalesPrepareAvailabilityProjectionParams, UnifiedBookingSalesPolicyResponse, UnifiedBookingSalesResolveProductAllergyRequirementParams, UnifiedBookingSalesStageRetailProductsParams, UnifiedBookingSalesState } from './types';
|
|
8
8
|
import { BookingTicket } from '../BookingTicket';
|
|
9
|
+
import type { AllergyRequirement } from '../../model/strategy/adapter/itemRule';
|
|
9
10
|
export * from './types';
|
|
10
11
|
export { AvailabilityError } from '../../modules/ResourcePlanner';
|
|
11
12
|
/**
|
|
@@ -76,6 +77,9 @@ export declare class UnifiedBookingSalesImpl extends BookingTicket {
|
|
|
76
77
|
private availabilityRemoteResourceQuerySequence;
|
|
77
78
|
private availabilityRemoteResourceEpoch;
|
|
78
79
|
private availabilityCommitLocks;
|
|
80
|
+
private allergyItemRuleEvaluator;
|
|
81
|
+
private allergyItemRuleConfigs;
|
|
82
|
+
private allergyItemRuleConfigsPromise;
|
|
79
83
|
initialize(core: PisellCore, options?: ModuleOptions): Promise<void>;
|
|
80
84
|
protected getSubmitOrderSalesChannel(): string;
|
|
81
85
|
private createAvailabilityFacadeError;
|
|
@@ -147,6 +151,14 @@ export declare class UnifiedBookingSalesImpl extends BookingTicket {
|
|
|
147
151
|
*/
|
|
148
152
|
loadOpenData(params: UnifiedBookingSalesLoadOpenDataParams): Promise<OpenDataConfig>;
|
|
149
153
|
getOpenData(): Promise<OpenDataConfig | null>;
|
|
154
|
+
private resetAllergyItemRuleState;
|
|
155
|
+
private fetchAllergyItemRuleConfigs;
|
|
156
|
+
private ensureAllergyItemRuleConfigsLoaded;
|
|
157
|
+
/**
|
|
158
|
+
* 评估当前渠道与商品是否命中 ALLERGY_CHECK。
|
|
159
|
+
* 只返回优先级最高 Action 中当前商品 Target 的 dataSource,不跨商品或 Action 合并。
|
|
160
|
+
*/
|
|
161
|
+
resolveProductAllergyRequirement(params: UnifiedBookingSalesResolveProductAllergyRequirementParams): Promise<AllergyRequirement | null>;
|
|
150
162
|
/**
|
|
151
163
|
* Load a resource policy without depending on the legacy appointmentBooking solution.
|
|
152
164
|
*/
|
|
@@ -159,6 +171,7 @@ export declare class UnifiedBookingSalesImpl extends BookingTicket {
|
|
|
159
171
|
callback?: (result: any) => void;
|
|
160
172
|
subscriberId?: string;
|
|
161
173
|
}): Promise<ProductData[]>;
|
|
174
|
+
queryProducts(params?: ProductListStoreQueryParams): Promise<ProductData[]>;
|
|
162
175
|
/** Date-first entry point. It delegates protocol details and cache ownership to ScheduleModule. */
|
|
163
176
|
loadScheduleAvailableDate(params: LoadScheduleAvailableDateParams): Promise<import("../../modules/Date/types").ITime[]>;
|
|
164
177
|
/**
|
|
@@ -50,6 +50,7 @@ var _remoteOccupancyCache = require("./remoteOccupancyCache");
|
|
|
50
50
|
var _BookingTicket = require("../BookingTicket");
|
|
51
51
|
var _localClock = require("../../modules/ResourcePlanner/localClock");
|
|
52
52
|
var _platform = require("../../utils/platform");
|
|
53
|
+
var _itemRule = require("../../model/strategy/adapter/itemRule");
|
|
53
54
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
54
55
|
const OPEN_DATA_SECTION_CODES = ['sale', 'reservation', 'fulfillment', 'menu', 'workflow', 'basic', 'checkout', 'availability'];
|
|
55
56
|
const OPEN_DATA_CACHE_TTL = 5 * 60 * 1000;
|
|
@@ -82,6 +83,22 @@ function getCandidateStartTime(candidate) {
|
|
|
82
83
|
return undefined;
|
|
83
84
|
}
|
|
84
85
|
}
|
|
86
|
+
function normalizePositiveIntegerList(value) {
|
|
87
|
+
if (!Array.isArray(value)) return [];
|
|
88
|
+
return Array.from(new Set(value.map(item => normalizePositiveInteger(item)).filter(item => item !== undefined)));
|
|
89
|
+
}
|
|
90
|
+
function toConfiguredBoolean(value) {
|
|
91
|
+
if (typeof value === 'boolean') return value;
|
|
92
|
+
if (typeof value === 'number') return value !== 0;
|
|
93
|
+
if (typeof value === 'string') {
|
|
94
|
+
return ['1', 'true', 'yes', 'y', 'on'].includes(value.trim().toLowerCase());
|
|
95
|
+
}
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
function normalizeItemRuleStrategyConfigs(value) {
|
|
99
|
+
if (!Array.isArray(value)) return [];
|
|
100
|
+
return value.filter(config => Boolean(config && typeof config === 'object' && config.metadata?.type === 'item_rule'));
|
|
101
|
+
}
|
|
85
102
|
function isRecord(value) {
|
|
86
103
|
return !!value && typeof value === 'object' && !Array.isArray(value);
|
|
87
104
|
}
|
|
@@ -190,8 +207,9 @@ function filterCustomerBoundDiscounts(discountList) {
|
|
|
190
207
|
}
|
|
191
208
|
const DEFAULT_AVAILABILITY_SLOT_STEP_MINUTES = 30;
|
|
192
209
|
const DEFAULT_AVAILABILITY_BUSINESS_HOURS = {
|
|
193
|
-
|
|
194
|
-
|
|
210
|
+
// Same start/end is interpreted as an overnight range, i.e. 24 hours.
|
|
211
|
+
startTime: '00:00',
|
|
212
|
+
endTime: '00:00'
|
|
195
213
|
};
|
|
196
214
|
const MAX_AVAILABILITY_RANGE_DAYS = 90;
|
|
197
215
|
const MAX_AVAILABILITY_CONTEXT_REGISTRY = 128;
|
|
@@ -1160,11 +1178,15 @@ class UnifiedBookingSalesImpl extends _BookingTicket.BookingTicket {
|
|
|
1160
1178
|
availabilityRemoteResourceQuerySequence = 0;
|
|
1161
1179
|
availabilityRemoteResourceEpoch = 0;
|
|
1162
1180
|
availabilityCommitLocks = new Set();
|
|
1181
|
+
allergyItemRuleEvaluator = new _itemRule.ItemRuleEvaluator();
|
|
1182
|
+
allergyItemRuleConfigs = [];
|
|
1183
|
+
allergyItemRuleConfigsPromise = null;
|
|
1163
1184
|
async initialize(core, options = {}) {
|
|
1164
1185
|
this.availabilityScheduleCatalog = null;
|
|
1165
1186
|
this.openDataTarget = null;
|
|
1166
1187
|
this.availabilityRuntimeOffsetMinutes = (0, _localClock.captureRuntimeOffsetMinutes)();
|
|
1167
1188
|
this.clearAvailabilityRemoteResourceState();
|
|
1189
|
+
this.resetAllergyItemRuleState();
|
|
1168
1190
|
await super.initialize(core, options);
|
|
1169
1191
|
const openDataBusinessCode = String(this.otherParams?.businessCode ?? this.otherParams?.business_code ?? '').trim();
|
|
1170
1192
|
const openDataChannel = String(this.otherParams?.channel ?? '').trim();
|
|
@@ -1309,6 +1331,7 @@ class UnifiedBookingSalesImpl extends _BookingTicket.BookingTicket {
|
|
|
1309
1331
|
cover = false
|
|
1310
1332
|
} = {}) {
|
|
1311
1333
|
const previousScope = this.getAvailabilityScopeDescriptor(this.otherParams || {});
|
|
1334
|
+
const previousItemRuleConfig = this.otherParams?.itemRule;
|
|
1312
1335
|
const explicitlySetsOpenData = Object.prototype.hasOwnProperty.call(params, 'openData');
|
|
1313
1336
|
await super.setOtherParams(params, {
|
|
1314
1337
|
cover
|
|
@@ -1321,11 +1344,15 @@ class UnifiedBookingSalesImpl extends _BookingTicket.BookingTicket {
|
|
|
1321
1344
|
if (shouldResetAvailabilityState) {
|
|
1322
1345
|
await this.resetAvailabilityProjectionState('set_other_params_reset');
|
|
1323
1346
|
}
|
|
1347
|
+
if (shouldResetAvailabilityState || explicitlySetsOpenData || previousItemRuleConfig !== this.otherParams?.itemRule) {
|
|
1348
|
+
this.resetAllergyItemRuleState();
|
|
1349
|
+
}
|
|
1324
1350
|
}
|
|
1325
1351
|
async destroy() {
|
|
1326
1352
|
this.availabilityScheduleCatalog = null;
|
|
1327
1353
|
this.availabilityRuntimeOffsetMinutes = null;
|
|
1328
1354
|
await this.resetAvailabilityProjectionState('destroy');
|
|
1355
|
+
this.resetAllergyItemRuleState();
|
|
1329
1356
|
await super.destroy();
|
|
1330
1357
|
}
|
|
1331
1358
|
createAvailabilityContextId() {
|
|
@@ -1677,6 +1704,98 @@ class UnifiedBookingSalesImpl extends _BookingTicket.BookingTicket {
|
|
|
1677
1704
|
channel
|
|
1678
1705
|
});
|
|
1679
1706
|
}
|
|
1707
|
+
resetAllergyItemRuleState() {
|
|
1708
|
+
this.allergyItemRuleConfigs = [];
|
|
1709
|
+
this.allergyItemRuleConfigsPromise = null;
|
|
1710
|
+
this.allergyItemRuleEvaluator.setStrategyConfigs([]);
|
|
1711
|
+
}
|
|
1712
|
+
async fetchAllergyItemRuleConfigs(strategyModelIds) {
|
|
1713
|
+
if (!strategyModelIds.length) return [];
|
|
1714
|
+
try {
|
|
1715
|
+
const response = await this.request.get('/promotion', {
|
|
1716
|
+
skip: 1,
|
|
1717
|
+
num: 99,
|
|
1718
|
+
status: 'active',
|
|
1719
|
+
ids: strategyModelIds
|
|
1720
|
+
});
|
|
1721
|
+
if (response?.code !== 200 || !Array.isArray(response?.data?.list)) {
|
|
1722
|
+
throw new Error('[UnifiedBookingSales] 商品过敏策略加载失败');
|
|
1723
|
+
}
|
|
1724
|
+
const list = response.data.list;
|
|
1725
|
+
return normalizeItemRuleStrategyConfigs(list.map(item => item?.metadata));
|
|
1726
|
+
} catch (error) {
|
|
1727
|
+
// this.logMethodError('fetchAllergyItemRuleConfigs', error, {
|
|
1728
|
+
// strategyModelIds,
|
|
1729
|
+
// });
|
|
1730
|
+
throw error;
|
|
1731
|
+
}
|
|
1732
|
+
}
|
|
1733
|
+
async ensureAllergyItemRuleConfigsLoaded() {
|
|
1734
|
+
if (this.allergyItemRuleConfigs.length > 0) {
|
|
1735
|
+
return this.allergyItemRuleConfigs;
|
|
1736
|
+
}
|
|
1737
|
+
if (this.allergyItemRuleConfigsPromise) {
|
|
1738
|
+
return this.allergyItemRuleConfigsPromise;
|
|
1739
|
+
}
|
|
1740
|
+
this.allergyItemRuleConfigsPromise = (async () => {
|
|
1741
|
+
const staticConfigs = normalizeItemRuleStrategyConfigs(this.otherParams?.itemRule?.strategyConfigs);
|
|
1742
|
+
if (staticConfigs.length > 0) {
|
|
1743
|
+
this.allergyItemRuleConfigs = staticConfigs;
|
|
1744
|
+
this.allergyItemRuleEvaluator.setStrategyConfigs(staticConfigs);
|
|
1745
|
+
return staticConfigs;
|
|
1746
|
+
}
|
|
1747
|
+
const openData = await this.getOpenData();
|
|
1748
|
+
const enabled = toConfiguredBoolean(openData?.['sale.enable_item_rules']);
|
|
1749
|
+
const strategyModelIds = normalizePositiveIntegerList(openData?.['sale.enabled_item_rules']);
|
|
1750
|
+
if (!enabled || !strategyModelIds.length) {
|
|
1751
|
+
this.allergyItemRuleEvaluator.setStrategyConfigs([]);
|
|
1752
|
+
return [];
|
|
1753
|
+
}
|
|
1754
|
+
const loaded = await this.fetchAllergyItemRuleConfigs(strategyModelIds);
|
|
1755
|
+
this.allergyItemRuleConfigs = loaded;
|
|
1756
|
+
this.allergyItemRuleEvaluator.setStrategyConfigs(loaded);
|
|
1757
|
+
return loaded;
|
|
1758
|
+
})();
|
|
1759
|
+
return this.allergyItemRuleConfigsPromise;
|
|
1760
|
+
}
|
|
1761
|
+
|
|
1762
|
+
/**
|
|
1763
|
+
* 评估当前渠道与商品是否命中 ALLERGY_CHECK。
|
|
1764
|
+
* 只返回优先级最高 Action 中当前商品 Target 的 dataSource,不跨商品或 Action 合并。
|
|
1765
|
+
*/
|
|
1766
|
+
async resolveProductAllergyRequirement(params) {
|
|
1767
|
+
const productId = normalizePositiveInteger(params.productId);
|
|
1768
|
+
if (!productId) return null;
|
|
1769
|
+
const strategyConfigs = await this.ensureAllergyItemRuleConfigsLoaded();
|
|
1770
|
+
if (!strategyConfigs.length) return null;
|
|
1771
|
+
const productVariantId = normalizePositiveInteger(params.productVariantId);
|
|
1772
|
+
const runtimeConfig = this.otherParams?.itemRule || {};
|
|
1773
|
+
const runtimePax = runtimeConfig.pax || {};
|
|
1774
|
+
const cartItems = (this.getOrderProducts?.() || []).map(item => ({
|
|
1775
|
+
product_id: Number(item?.product_id ?? item?.id),
|
|
1776
|
+
product_variant_id: Number(item?.product_variant_id ?? 0) || undefined,
|
|
1777
|
+
quantity: Number(item?.num ?? item?.quantity ?? 0)
|
|
1778
|
+
})).filter(item => Number.isFinite(item.product_id) && item.product_id > 0 && Number.isFinite(item.quantity) && item.quantity > 0);
|
|
1779
|
+
return this.allergyItemRuleEvaluator.getAllergyRequirement({
|
|
1780
|
+
pax: {
|
|
1781
|
+
total: Number(runtimePax.total ?? 0) || 0,
|
|
1782
|
+
adult: Number(runtimePax.adult ?? 0) || 0,
|
|
1783
|
+
child: Number(runtimePax.child ?? 0) || 0
|
|
1784
|
+
},
|
|
1785
|
+
cartItems,
|
|
1786
|
+
serviceType: String(runtimeConfig.serviceType ?? this.otherParams?.serviceType ?? ''),
|
|
1787
|
+
channel: String(params.channel ?? this.otherParams?.channel ?? this.otherParams?.platform ?? ''),
|
|
1788
|
+
currentProduct: {
|
|
1789
|
+
product_id: productId,
|
|
1790
|
+
...(productVariantId ? {
|
|
1791
|
+
product_variant_id: productVariantId
|
|
1792
|
+
} : {})
|
|
1793
|
+
},
|
|
1794
|
+
submissionIndex: Number(runtimeConfig.submissionIndex ?? 0) || 0,
|
|
1795
|
+
strategyConfigs,
|
|
1796
|
+
custom: runtimeConfig.custom
|
|
1797
|
+
});
|
|
1798
|
+
}
|
|
1680
1799
|
|
|
1681
1800
|
/**
|
|
1682
1801
|
* Load a resource policy without depending on the legacy appointmentBooking solution.
|
|
@@ -1713,6 +1832,10 @@ class UnifiedBookingSalesImpl extends _BookingTicket.BookingTicket {
|
|
|
1713
1832
|
await this.core.effects.emit(`${this.name}:onProductsLoaded`, result);
|
|
1714
1833
|
return result;
|
|
1715
1834
|
}
|
|
1835
|
+
async queryProducts(params = {}) {
|
|
1836
|
+
if (!this.store.products) throw new Error('[UnifiedBookingSales] products 模块未初始化');
|
|
1837
|
+
return this.store.products.queryProducts(params);
|
|
1838
|
+
}
|
|
1716
1839
|
|
|
1717
1840
|
/** Date-first entry point. It delegates protocol details and cache ownership to ScheduleModule. */
|
|
1718
1841
|
async loadScheduleAvailableDate(params) {
|
|
@@ -2803,7 +2926,13 @@ class UnifiedBookingSalesImpl extends _BookingTicket.BookingTicket {
|
|
|
2803
2926
|
projectionEntry
|
|
2804
2927
|
} = await this.ensureAvailabilityContextProjection(contextId, 'get_product_time_ranges');
|
|
2805
2928
|
const contextVersion = context.activeVersion;
|
|
2806
|
-
|
|
2929
|
+
const allowOutsideOperatingHoursForRequestedStartTimes = String(this.otherParams?.platform ?? '').trim().toLowerCase() === 'pos' && Object.values(request.durationStartTimesByProductId || {}).some(startTimes => Array.isArray(startTimes) && startTimes.length > 0);
|
|
2930
|
+
return (0, _ResourcePlanner.getProductTimeRanges)(projectionEntry.projection, {
|
|
2931
|
+
...request,
|
|
2932
|
+
...(allowOutsideOperatingHoursForRequestedStartTimes ? {
|
|
2933
|
+
allowOutsideOperatingHoursForRequestedStartTimes: true
|
|
2934
|
+
} : {})
|
|
2935
|
+
}).map(group => ({
|
|
2807
2936
|
...group,
|
|
2808
2937
|
ranges: group.ranges.map(candidate => this.decorateAvailabilityCandidate(context.contextId, contextVersion, candidate))
|
|
2809
2938
|
}));
|
|
@@ -3053,6 +3182,7 @@ class UnifiedBookingSalesImpl extends _BookingTicket.BookingTicket {
|
|
|
3053
3182
|
this.assertAvailabilityCommitWriteCAS(commitWriteGuard);
|
|
3054
3183
|
const candidateDurationMinutes = getCandidateDurationMinutes(params.candidate);
|
|
3055
3184
|
const candidateStartTime = getCandidateStartTime(params.candidate);
|
|
3185
|
+
const allowPosAvailabilityOverride = String(this.otherParams?.platform ?? '').trim().toLowerCase() === 'pos';
|
|
3056
3186
|
const refreshedRanges = (0, _ResourcePlanner.getProductTimeRanges)(refreshedProjection, {
|
|
3057
3187
|
productIds: [params.candidate.productId],
|
|
3058
3188
|
dateRange: {
|
|
@@ -3066,7 +3196,10 @@ class UnifiedBookingSalesImpl extends _BookingTicket.BookingTicket {
|
|
|
3066
3196
|
...(candidateStartTime ? {
|
|
3067
3197
|
durationStartTimesByProductId: {
|
|
3068
3198
|
[String(params.candidate.productId)]: [candidateStartTime]
|
|
3069
|
-
}
|
|
3199
|
+
},
|
|
3200
|
+
...(allowPosAvailabilityOverride ? {
|
|
3201
|
+
allowOutsideOperatingHoursForRequestedStartTimes: true
|
|
3202
|
+
} : {})
|
|
3070
3203
|
} : {})
|
|
3071
3204
|
} : {})
|
|
3072
3205
|
}).flatMap(group => group.ranges);
|
|
@@ -3090,7 +3223,6 @@ class UnifiedBookingSalesImpl extends _BookingTicket.BookingTicket {
|
|
|
3090
3223
|
partySize,
|
|
3091
3224
|
bookingCount
|
|
3092
3225
|
});
|
|
3093
|
-
const allowPosAvailabilityOverride = String(this.otherParams?.platform ?? '').trim().toLowerCase() === 'pos';
|
|
3094
3226
|
if (allowPosAvailabilityOverride && !validation.ok && validation.conflicts.every(conflict => POS_OVERRIDABLE_AVAILABILITY_CONFLICT_CODES.has(conflict.code))) {
|
|
3095
3227
|
const forced = buildPosForcedAvailabilityAssignments({
|
|
3096
3228
|
product,
|
|
@@ -3217,7 +3349,9 @@ class UnifiedBookingSalesImpl extends _BookingTicket.BookingTicket {
|
|
|
3217
3349
|
} else {
|
|
3218
3350
|
const beforeProductLineUids = new Set((this.getTempOrder()?.products || []).map(line => getProductLineUid(line)).filter(Boolean));
|
|
3219
3351
|
const beforeBookingUids = new Set((this.getTempOrder()?.bookings || []).map(item => getBookingUid(item)).filter(Boolean));
|
|
3220
|
-
const products = await this.addProductToOrder(transformed, booking
|
|
3352
|
+
const products = await this.addProductToOrder(transformed, booking, params.salesNotes?.length ? {
|
|
3353
|
+
salesNotes: params.salesNotes
|
|
3354
|
+
} : undefined);
|
|
3221
3355
|
const addedLines = products.filter(line => sameAvailabilityId(line.product_id ?? line.id, refreshedCandidate.productId) && line.metadata?.availability_candidate_key === refreshedCandidate.key && !beforeProductLineUids.has(getProductLineUid(line)));
|
|
3222
3356
|
productLineUids = addedLines.map(line => getProductLineUid(line)).filter(Boolean);
|
|
3223
3357
|
const addedBookings = (this.getTempOrder()?.bookings || []).filter(item => item.metadata?.availability_candidate_key === refreshedCandidate.key && !beforeBookingUids.has(getBookingUid(item)));
|
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
import type { OpenDataConfig } from '../../modules/OpenData';
|
|
2
|
-
import type { ProductListLoadProductsParams } from '../../modules/ProductList';
|
|
2
|
+
import type { ProductListLoadProductsParams, ProductListStoreQueryParams } from '../../modules/ProductList';
|
|
3
|
+
import type { ProductData } from '../../modules/Product/types';
|
|
3
4
|
import type { LoadScheduleAvailableDateParams } from '../../modules/Schedule/types';
|
|
4
5
|
import type { AvailabilityDateRange, AvailabilityErrorCode, AvailabilityId, AvailabilityOccupancy, AvailabilityProduct, AvailabilityProjection, AvailabilityQuery, AvailabilityRequirementSelection, AvailabilityResource, AvailabilitySelectionValidationResult, DateAvailabilityQueryResult, GetProductTimeRangesRequest, ProductAvailabilityQueryResult, ProductTimeRange, ProductTimeRangeGroup, ResourceAvailabilityGroup, ResourceAvailabilityRangeGroup, TimeAvailabilityGroup, TimeAvailabilityItem } from '../../modules/ResourcePlanner';
|
|
5
6
|
import type { BookingTicketState } from '../BookingTicket/types';
|
|
7
|
+
import type { AllergyRequirement } from '../../model/strategy/adapter/itemRule';
|
|
8
|
+
import type { SalesNoteDraftInput } from '../../modules/Order/types';
|
|
9
|
+
export type { ProductListStoreQueryParams } from '../../modules/ProductList';
|
|
6
10
|
export type { AvailabilityAbsoluteRange, AvailabilityAssignment, AvailabilityCell, AvailabilityDateRange, AvailabilityDisplayRange, AvailabilityErrorCode, AvailabilityId, AvailabilityMode, AvailabilityOccupancy, AvailabilityProduct, AvailabilityProductKind, AvailabilityProjection, AvailabilityProjectionInput, AvailabilityProjectionMeta, AvailabilityQuery, AvailabilityQueryResult, AvailabilityRequirementGroup, AvailabilityRequirementGroupResult, AvailabilityRequirementSelection, AvailabilityResource, AvailabilityResourceOption, AvailabilityResourceType, AvailabilityResourceWindow, AvailabilityScheduleMode, AvailabilityScheduleRef, AvailabilityScheduleRule, AvailabilitySelectionValidationParams, AvailabilitySelectionValidationResult, AvailabilitySessionRange, AvailabilityStatus, DateAvailabilityGroup, DateAvailabilityItem, DateAvailabilityQuery, DateAvailabilityQueryResult, GetProductTimeRangesRequest, ProductAvailabilityGroup, ProductAvailabilityItem, ProductAvailabilityQuery, ProductAvailabilityQueryResult, ProductTimeRange, ProductTimeRangeGroup, QueryAvailabilityBaseRequest, ResourceAvailabilityGroup, ResourceAvailabilityQuery, ResourceAvailabilityQueryResult, ResourceAvailabilityRangeGroup, TimeAvailabilityGroup, TimeAvailabilityItem, TimeAvailabilityQuery, TimeAvailabilityQueryResult, } from '../../modules/ResourcePlanner';
|
|
7
11
|
export interface UnifiedBookingSalesState extends BookingTicketState {
|
|
8
12
|
}
|
|
@@ -24,6 +28,12 @@ export declare enum UnifiedBookingSalesHooks {
|
|
|
24
28
|
export interface UnifiedBookingSalesOtherParams extends Record<string, any> {
|
|
25
29
|
enableSessionStoragePersist?: boolean;
|
|
26
30
|
}
|
|
31
|
+
export type UnifiedBookingSalesAllergyRequirement = AllergyRequirement;
|
|
32
|
+
export interface UnifiedBookingSalesResolveProductAllergyRequirementParams {
|
|
33
|
+
productId: number | string;
|
|
34
|
+
productVariantId?: number | string;
|
|
35
|
+
channel?: string;
|
|
36
|
+
}
|
|
27
37
|
export interface UnifiedBookingSalesLoadOpenDataParams {
|
|
28
38
|
businessCode: string;
|
|
29
39
|
channel: string;
|
|
@@ -151,6 +161,8 @@ export interface ContextualCommitAvailabilitySelectionParams {
|
|
|
151
161
|
requirementSelections: AvailabilityRequirementSelection[];
|
|
152
162
|
partySize: number;
|
|
153
163
|
bookingCount?: number;
|
|
164
|
+
/** 新增商品行时与商品、预约在同一次 Order mutation 内写入的 Sales Notes。 */
|
|
165
|
+
salesNotes?: SalesNoteDraftInput[];
|
|
154
166
|
/** 编辑预约时,允许本次显式报价覆盖已落库订单行的历史价格。 */
|
|
155
167
|
allowBookingReprice?: boolean;
|
|
156
168
|
/** 编辑预约重报价后,允许订单模块重新应用已选编辑态折扣。 */
|
|
@@ -208,6 +220,7 @@ export interface UnifiedBookingSalesApi {
|
|
|
208
220
|
getOpenData: () => Promise<OpenDataConfig | null>;
|
|
209
221
|
loadScheduleAvailableDate: (params: LoadScheduleAvailableDateParams) => Promise<unknown>;
|
|
210
222
|
loadProductsByScheduleDate: (params: UnifiedBookingSalesLoadProductsByScheduleDateParams) => Promise<unknown[]>;
|
|
223
|
+
queryProducts: (params?: ProductListStoreQueryParams) => Promise<ProductData[]>;
|
|
211
224
|
stageRetailProducts: (params: UnifiedBookingSalesStageRetailProductsParams) => Promise<Record<string, any> | null>;
|
|
212
225
|
addRetailProduct: (params: UnifiedBookingSalesAddRetailProductParams) => Promise<Record<string, any>[]>;
|
|
213
226
|
prepareAvailabilityProjection: (params: UnifiedBookingSalesPrepareAvailabilityProjectionParams) => Promise<AvailabilityContextRef>;
|
|
@@ -1758,7 +1758,11 @@ class VenueBookingImpl extends _BaseSales.BaseSalesImpl {
|
|
|
1758
1758
|
return sourceMap;
|
|
1759
1759
|
}
|
|
1760
1760
|
getItemRuleRuntimeConfig() {
|
|
1761
|
-
|
|
1761
|
+
const runtimeConfig = this.itemRuleRuntimeConfig || {};
|
|
1762
|
+
return {
|
|
1763
|
+
...runtimeConfig,
|
|
1764
|
+
channel: runtimeConfig.channel ?? runtimeConfig.custom?.channel ?? this.otherParams?.channel
|
|
1765
|
+
};
|
|
1762
1766
|
}
|
|
1763
1767
|
async ensureItemRuleConfigsLoaded() {
|
|
1764
1768
|
if (this.itemRuleConfigs.length > 0) return this.itemRuleConfigs;
|