@pisell/pisellos 0.10.21 → 0.10.23
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/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/OpenData/index.d.ts +1 -0
- package/dist/modules/OpenData/index.js +28 -9
- 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/BookingTicket/index.d.ts +1 -1
- package/dist/solution/Sales/index.js +5 -3
- 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/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/OpenData/index.d.ts +1 -0
- package/lib/modules/OpenData/index.js +13 -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/BookingTicket/index.d.ts +1 -1
- package/lib/solution/Sales/index.js +2 -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
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.PRODUCT_QUERY_DATA_VARIANT_RELATIONS = void 0;
|
|
7
|
+
exports.isProductQueryHandledByOsServer = isProductQueryHandledByOsServer;
|
|
8
|
+
exports.resolveClientDataVariants = resolveClientDataVariants;
|
|
9
|
+
exports.tryResolveClientDataVariants = tryResolveClientDataVariants;
|
|
10
|
+
const PRODUCT_QUERY_DATA_VARIANT_RELATIONS = exports.PRODUCT_QUERY_DATA_VARIANT_RELATIONS = ['category', 'collection', 'resourceRelation', 'bundleGroup.bundleItem', 'optionGroup.optionItem', 'variantGroup.variantItem', 'dataVariants'];
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* 判断当前商品查询是否会被本地 OS Server 接管。
|
|
14
|
+
*
|
|
15
|
+
* H5 RequestPlugin 即使收到 osServer=true 仍会直接请求远端接口,因此必须按
|
|
16
|
+
* RequestPlugin 实际 baseUrl 对应的已注册路由判断,不能只看请求选项。
|
|
17
|
+
*/
|
|
18
|
+
function isProductQueryHandledByOsServer(core, request) {
|
|
19
|
+
const server = core?.server;
|
|
20
|
+
if (!server || typeof server.hasRoute !== 'function') return false;
|
|
21
|
+
const requestBaseUrl = String(request?.baseUrl || '/shop').replace(/\/+$/, '');
|
|
22
|
+
return server.hasRoute('post', `${requestBaseUrl}/product/query`) === true;
|
|
23
|
+
}
|
|
24
|
+
function getBundleItems(product) {
|
|
25
|
+
if (!Array.isArray(product.bundle_group)) return [];
|
|
26
|
+
return product.bundle_group.flatMap(group => Array.isArray(group?.bundle_item) ? group.bundle_item : []);
|
|
27
|
+
}
|
|
28
|
+
function hasCompleteDataVariantRelations(products) {
|
|
29
|
+
return products.every(product => Array.isArray(product?.data_variants));
|
|
30
|
+
}
|
|
31
|
+
function hasAnyDataVariants(products) {
|
|
32
|
+
return products.some(product => (product.data_variants?.length || 0) > 0 || getBundleItems(product).some(item => Array.isArray(item?.data_variants) && item.data_variants.length > 0));
|
|
33
|
+
}
|
|
34
|
+
function collectReferencedRuleIds(products) {
|
|
35
|
+
const ids = new Set();
|
|
36
|
+
const remember = variants => {
|
|
37
|
+
if (!Array.isArray(variants)) return;
|
|
38
|
+
variants.forEach(variant => {
|
|
39
|
+
const id = String(variant?.data_variant_rule_id ?? '').trim();
|
|
40
|
+
if (id) ids.add(id);
|
|
41
|
+
});
|
|
42
|
+
};
|
|
43
|
+
products.forEach(product => {
|
|
44
|
+
remember(product.data_variants);
|
|
45
|
+
getBundleItems(product).forEach(item => remember(item?.data_variants));
|
|
46
|
+
});
|
|
47
|
+
return ids;
|
|
48
|
+
}
|
|
49
|
+
function isEvaluatorReady(evaluator, products) {
|
|
50
|
+
if (typeof evaluator.isReady === 'function') {
|
|
51
|
+
if (evaluator.isReady() === false) return false;
|
|
52
|
+
}
|
|
53
|
+
if (evaluator.isReady === false || evaluator.ready === false) return false;
|
|
54
|
+
|
|
55
|
+
// The built-in evaluator is installed before its strategy request completes.
|
|
56
|
+
// A referenced Data Variant cannot be evaluated authoritatively until those
|
|
57
|
+
// configs have arrived. Evaluators without this capability remain compatible.
|
|
58
|
+
if (typeof evaluator.getStrategyConfigs === 'function') {
|
|
59
|
+
const configs = evaluator.getStrategyConfigs();
|
|
60
|
+
if (!Array.isArray(configs) || configs.length === 0) return false;
|
|
61
|
+
const availableRuleIds = new Set(configs.map(config => String(config?.metadata?.id ?? '').trim()).filter(Boolean));
|
|
62
|
+
return Array.from(collectReferencedRuleIds(products)).every(id => availableRuleIds.has(id));
|
|
63
|
+
}
|
|
64
|
+
return true;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Strict client-side Data Variant resolution for quote fast paths.
|
|
69
|
+
*
|
|
70
|
+
* Unlike resolveClientDataVariants, callers can distinguish a complete local
|
|
71
|
+
* result from a capability/readiness/error fallback and decide to query the
|
|
72
|
+
* remote product endpoint instead.
|
|
73
|
+
*/
|
|
74
|
+
function tryResolveClientDataVariants({
|
|
75
|
+
core,
|
|
76
|
+
otherParams = {},
|
|
77
|
+
products,
|
|
78
|
+
queryPayload,
|
|
79
|
+
handledByOsServer,
|
|
80
|
+
scheduleList: explicitScheduleList,
|
|
81
|
+
menuList: explicitMenuList,
|
|
82
|
+
requireCompleteDataVariantRelations = true,
|
|
83
|
+
requireEvaluatorReady = true
|
|
84
|
+
}) {
|
|
85
|
+
if (handledByOsServer) {
|
|
86
|
+
return {
|
|
87
|
+
status: 'handled-by-os-server',
|
|
88
|
+
products
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
if (products.length === 0) {
|
|
92
|
+
return {
|
|
93
|
+
status: 'empty',
|
|
94
|
+
products
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
if (requireCompleteDataVariantRelations && !hasCompleteDataVariantRelations(products)) {
|
|
98
|
+
return {
|
|
99
|
+
status: 'data-variants-unavailable',
|
|
100
|
+
products
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Explicit empty arrays mean the query hydrated the relation and there is no
|
|
105
|
+
// rule to apply. This is an authoritative local base-price result even when
|
|
106
|
+
// no evaluator has been installed.
|
|
107
|
+
if (!hasAnyDataVariants(products)) {
|
|
108
|
+
return {
|
|
109
|
+
status: 'resolved',
|
|
110
|
+
products
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
const evaluator = core?.context?.dataVariantEvaluator;
|
|
114
|
+
if (!evaluator || typeof evaluator.resolveProducts !== 'function') {
|
|
115
|
+
return {
|
|
116
|
+
status: 'evaluator-unavailable',
|
|
117
|
+
products
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
try {
|
|
121
|
+
if (requireEvaluatorReady && !isEvaluatorReady(evaluator, products)) {
|
|
122
|
+
return {
|
|
123
|
+
status: 'evaluator-not-ready',
|
|
124
|
+
products
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
} catch (error) {
|
|
128
|
+
return {
|
|
129
|
+
status: 'error',
|
|
130
|
+
products,
|
|
131
|
+
error
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
const strategyContext = queryPayload.strategy_context && typeof queryPayload.strategy_context === 'object' ? queryPayload.strategy_context : {};
|
|
135
|
+
const fatherModule = String(otherParams.fatherModule || '').trim();
|
|
136
|
+
const scheduleModule = (fatherModule ? core?.getModule(`${fatherModule}_schedule`) : null) || core?.getModule('schedule');
|
|
137
|
+
const moduleScheduleList = scheduleModule?.getScheduleList?.();
|
|
138
|
+
const contextScheduleList = core?.context?.scheduleList;
|
|
139
|
+
const contextMenuList = core?.context?.menuList;
|
|
140
|
+
const businessData = {
|
|
141
|
+
products,
|
|
142
|
+
scheduleDateTime: queryPayload.schedule_datetime || queryPayload.schedule_date,
|
|
143
|
+
scheduleList: Array.isArray(explicitScheduleList) && explicitScheduleList.length > 0 ? explicitScheduleList : Array.isArray(moduleScheduleList) && moduleScheduleList.length > 0 ? moduleScheduleList : Array.isArray(contextScheduleList) ? contextScheduleList : [],
|
|
144
|
+
menuList: Array.isArray(explicitMenuList) ? explicitMenuList : Array.isArray(otherParams.menuList) ? otherParams.menuList : Array.isArray(contextMenuList) ? contextMenuList : [],
|
|
145
|
+
customerId: queryPayload.customer_id,
|
|
146
|
+
channel: strategyContext.channel,
|
|
147
|
+
orderType: strategyContext.orderType || strategyContext.order_type,
|
|
148
|
+
businessCode: strategyContext.business_code ?? strategyContext.businessCode,
|
|
149
|
+
availableWalletIds: strategyContext.available_wallet_ids,
|
|
150
|
+
custom: strategyContext
|
|
151
|
+
};
|
|
152
|
+
try {
|
|
153
|
+
const result = evaluator.resolveProducts(businessData);
|
|
154
|
+
if (!Array.isArray(result?.products)) {
|
|
155
|
+
return {
|
|
156
|
+
status: 'error',
|
|
157
|
+
products,
|
|
158
|
+
error: new Error('Data Variant evaluator returned an invalid product list')
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
return {
|
|
162
|
+
status: 'resolved',
|
|
163
|
+
products: result.products
|
|
164
|
+
};
|
|
165
|
+
} catch (error) {
|
|
166
|
+
return {
|
|
167
|
+
status: 'error',
|
|
168
|
+
products,
|
|
169
|
+
error
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* 未启用商品 OS 路由时,在客户端补做 Data Variant 解析。
|
|
176
|
+
*
|
|
177
|
+
* 商品列表和预约报价必须共用这个边界,避免 H5 的弹窗报价绕过客户端智能定价。
|
|
178
|
+
* OS Server 已接管时直接信任服务端结果,确保同一批商品只评估一次。
|
|
179
|
+
*/
|
|
180
|
+
function resolveClientDataVariants({
|
|
181
|
+
core,
|
|
182
|
+
otherParams = {},
|
|
183
|
+
products,
|
|
184
|
+
queryPayload,
|
|
185
|
+
handledByOsServer,
|
|
186
|
+
scheduleList: explicitScheduleList,
|
|
187
|
+
menuList: explicitMenuList
|
|
188
|
+
}) {
|
|
189
|
+
const result = tryResolveClientDataVariants({
|
|
190
|
+
core,
|
|
191
|
+
otherParams,
|
|
192
|
+
products,
|
|
193
|
+
queryPayload,
|
|
194
|
+
handledByOsServer,
|
|
195
|
+
scheduleList: explicitScheduleList,
|
|
196
|
+
menuList: explicitMenuList,
|
|
197
|
+
requireCompleteDataVariantRelations: false,
|
|
198
|
+
requireEvaluatorReady: false
|
|
199
|
+
});
|
|
200
|
+
if (result.status === 'error') {
|
|
201
|
+
console.error('[ClientDataVariants] Data Variant 客户端解析失败', result.error);
|
|
202
|
+
}
|
|
203
|
+
return result.products;
|
|
204
|
+
}
|
|
@@ -9,6 +9,7 @@ export declare class ProductList extends BaseModule implements Module {
|
|
|
9
9
|
private store;
|
|
10
10
|
private request;
|
|
11
11
|
private otherParams;
|
|
12
|
+
private rawProductPriceSources;
|
|
12
13
|
constructor(name?: string, version?: string);
|
|
13
14
|
initialize(core: PisellCore, options: any): Promise<void>;
|
|
14
15
|
/**
|
|
@@ -18,19 +19,6 @@ export declare class ProductList extends BaseModule implements Module {
|
|
|
18
19
|
* productList.updateOtherParams({ channel: 'pos' });
|
|
19
20
|
*/
|
|
20
21
|
updateOtherParams(params: Record<string, any>): void;
|
|
21
|
-
/**
|
|
22
|
-
* 判断当前商品查询是否会被本地 OS Server 接管。
|
|
23
|
-
*
|
|
24
|
-
* 不能只看请求参数中的 osServer=true:H5 RequestPlugin 会透传该参数,但仍然直接请求远端接口。
|
|
25
|
-
* 这里按 RequestPlugin 实际使用的 baseUrl 检查已注册路由,避免 OS Server 与客户端重复执行
|
|
26
|
-
* Data Variant 评估。
|
|
27
|
-
*/
|
|
28
|
-
private isProductQueryHandledByOsServer;
|
|
29
|
-
/**
|
|
30
|
-
* 未启用商品 OS 路由时,在客户端补做 Data Variant 解析。
|
|
31
|
-
* OS Server 已接管时直接信任服务端结果,确保同一批商品只评估一次。
|
|
32
|
-
*/
|
|
33
|
-
private resolveClientDataVariants;
|
|
34
22
|
storeChange(path?: string, value?: any): Promise<void>;
|
|
35
23
|
/**
|
|
36
24
|
* 获取加时商品列表。
|
|
@@ -49,6 +37,19 @@ export declare class ProductList extends BaseModule implements Module {
|
|
|
49
37
|
schedule_date?: string;
|
|
50
38
|
channel?: string;
|
|
51
39
|
}): Promise<any>;
|
|
40
|
+
/**
|
|
41
|
+
* Remember raw remote product records before client-side Data Variant
|
|
42
|
+
* evaluation. Quote-only reads can then always start from a clean baseline.
|
|
43
|
+
*/
|
|
44
|
+
rememberRawProductQueryResult(products: Array<ProductData | Record<string, any>>): void;
|
|
45
|
+
private replaceRawProductQueryResult;
|
|
46
|
+
/**
|
|
47
|
+
* Return isolated raw price sources for the requested products.
|
|
48
|
+
*
|
|
49
|
+
* Missing IDs are intentionally omitted so callers can remotely fetch only
|
|
50
|
+
* the incomplete part of a batch quote.
|
|
51
|
+
*/
|
|
52
|
+
getRawProductPriceSources(ids: number[]): Map<number, ProductData>;
|
|
52
53
|
getProducts(): Promise<ProductData[]>;
|
|
53
54
|
getProduct(id: number): Promise<ProductData | undefined>;
|
|
54
55
|
getProductByIds(ids: number[]): Promise<ProductData[] | undefined>;
|
|
@@ -10,6 +10,8 @@ exports.ProductList = void 0;
|
|
|
10
10
|
var _BaseModule = require("../BaseModule");
|
|
11
11
|
var _lodashEs = require("lodash-es");
|
|
12
12
|
var _dayjs = _interopRequireDefault(require("dayjs"));
|
|
13
|
+
var _clientDataVariants = require("./clientDataVariants");
|
|
14
|
+
var _platform = require("../../utils/platform");
|
|
13
15
|
var _types = require("./types");
|
|
14
16
|
Object.keys(_types).forEach(function (key) {
|
|
15
17
|
if (key === "default" || key === "__esModule") return;
|
|
@@ -29,6 +31,7 @@ class ProductList extends _BaseModule.BaseModule {
|
|
|
29
31
|
store;
|
|
30
32
|
request;
|
|
31
33
|
otherParams = {};
|
|
34
|
+
rawProductPriceSources = new Map();
|
|
32
35
|
constructor(name, version) {
|
|
33
36
|
super(name, version);
|
|
34
37
|
}
|
|
@@ -36,6 +39,7 @@ class ProductList extends _BaseModule.BaseModule {
|
|
|
36
39
|
this.core = core;
|
|
37
40
|
this.store = options.store;
|
|
38
41
|
this.otherParams = options.otherParams || {};
|
|
42
|
+
this.rawProductPriceSources.clear();
|
|
39
43
|
if (Array.isArray(options.initialState?.list)) {
|
|
40
44
|
this.store.list = options.initialState.list.slice().sort((a, b) => Number(b.sort) - Number(a.sort));
|
|
41
45
|
this.core.effects.emit(`${this.name}:changed`, this.store.list);
|
|
@@ -55,58 +59,6 @@ class ProductList extends _BaseModule.BaseModule {
|
|
|
55
59
|
updateOtherParams(params) {
|
|
56
60
|
this.otherParams = params || {};
|
|
57
61
|
}
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* 判断当前商品查询是否会被本地 OS Server 接管。
|
|
61
|
-
*
|
|
62
|
-
* 不能只看请求参数中的 osServer=true:H5 RequestPlugin 会透传该参数,但仍然直接请求远端接口。
|
|
63
|
-
* 这里按 RequestPlugin 实际使用的 baseUrl 检查已注册路由,避免 OS Server 与客户端重复执行
|
|
64
|
-
* Data Variant 评估。
|
|
65
|
-
*/
|
|
66
|
-
isProductQueryHandledByOsServer() {
|
|
67
|
-
const server = this.core?.server;
|
|
68
|
-
if (!server || typeof server.hasRoute !== 'function') return false;
|
|
69
|
-
const requestBaseUrl = String(this.request?.baseUrl || '/shop').replace(/\/+$/, '');
|
|
70
|
-
const routePath = `${requestBaseUrl}/product/query`;
|
|
71
|
-
return server.hasRoute('post', routePath) === true;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* 未启用商品 OS 路由时,在客户端补做 Data Variant 解析。
|
|
76
|
-
* OS Server 已接管时直接信任服务端结果,确保同一批商品只评估一次。
|
|
77
|
-
*/
|
|
78
|
-
resolveClientDataVariants(products, queryPayload, handledByOsServer) {
|
|
79
|
-
if (handledByOsServer || products.length === 0) return products;
|
|
80
|
-
const evaluator = this.core?.context?.dataVariantEvaluator;
|
|
81
|
-
if (!evaluator || typeof evaluator.resolveProducts !== 'function') {
|
|
82
|
-
return products;
|
|
83
|
-
}
|
|
84
|
-
const strategyContext = queryPayload.strategy_context && typeof queryPayload.strategy_context === 'object' ? queryPayload.strategy_context : {};
|
|
85
|
-
const fatherModule = String(this.otherParams?.fatherModule || '').trim();
|
|
86
|
-
const scheduleModule = (fatherModule ? this.core.getModule(`${fatherModule}_schedule`) : null) || this.core.getModule('schedule');
|
|
87
|
-
const scheduleList = scheduleModule?.getScheduleList?.();
|
|
88
|
-
const contextScheduleList = this.core?.context?.scheduleList;
|
|
89
|
-
const contextMenuList = this.core?.context?.menuList;
|
|
90
|
-
const businessData = {
|
|
91
|
-
products,
|
|
92
|
-
scheduleDateTime: queryPayload.schedule_datetime || queryPayload.schedule_date,
|
|
93
|
-
scheduleList: Array.isArray(scheduleList) ? scheduleList : Array.isArray(contextScheduleList) ? contextScheduleList : [],
|
|
94
|
-
menuList: Array.isArray(this.otherParams?.menuList) ? this.otherParams.menuList : Array.isArray(contextMenuList) ? contextMenuList : [],
|
|
95
|
-
customerId: queryPayload.customer_id,
|
|
96
|
-
channel: strategyContext.channel,
|
|
97
|
-
orderType: strategyContext.orderType || strategyContext.order_type,
|
|
98
|
-
businessCode: strategyContext.business_code ?? strategyContext.businessCode,
|
|
99
|
-
availableWalletIds: strategyContext.available_wallet_ids,
|
|
100
|
-
custom: strategyContext
|
|
101
|
-
};
|
|
102
|
-
try {
|
|
103
|
-
const result = evaluator.resolveProducts(businessData);
|
|
104
|
-
return Array.isArray(result?.products) ? result.products : products;
|
|
105
|
-
} catch (error) {
|
|
106
|
-
console.error('[ProductList] Data Variant 客户端解析失败', error);
|
|
107
|
-
return products;
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
62
|
async storeChange(path, value) {
|
|
111
63
|
// No longer needed - products are stored as plain objects instead of Product instances
|
|
112
64
|
}
|
|
@@ -170,10 +122,12 @@ class ProductList extends _BaseModule.BaseModule {
|
|
|
170
122
|
const rawApplicationCode = this.otherParams?.channel;
|
|
171
123
|
const mappedApplicationCode = typeof rawApplicationCode === 'string' ? channelMap[rawApplicationCode] || rawApplicationCode : rawApplicationCode;
|
|
172
124
|
const queryPayload = {
|
|
173
|
-
|
|
125
|
+
...((0, _platform.isCustomerUserPlatform)(this.otherParams?.platform) ? {} : {
|
|
126
|
+
open_quotation: 1
|
|
127
|
+
}),
|
|
174
128
|
open_bundle: 0,
|
|
175
129
|
exclude_extension_type: ['product_party', 'product_event', 'product_series_event', 'product_package_ticket', 'ticket', 'event_item'],
|
|
176
|
-
with: [
|
|
130
|
+
with: [..._clientDataVariants.PRODUCT_QUERY_DATA_VARIANT_RELATIONS],
|
|
177
131
|
status,
|
|
178
132
|
num: 500,
|
|
179
133
|
skip: 1,
|
|
@@ -201,15 +155,26 @@ class ProductList extends _BaseModule.BaseModule {
|
|
|
201
155
|
extension_type,
|
|
202
156
|
strategy_context
|
|
203
157
|
};
|
|
204
|
-
const handledByOsServer =
|
|
205
|
-
const originalCallback = options?.callback;
|
|
158
|
+
const handledByOsServer = (0, _clientDataVariants.isProductQueryHandledByOsServer)(this.core, this.request);
|
|
159
|
+
const originalCallback = typeof options?.callback === 'function' ? options.callback : undefined;
|
|
206
160
|
const subscriptionCallback = originalCallback ? async result => {
|
|
207
161
|
const callbackList = result?.data?.list;
|
|
208
162
|
if (!Array.isArray(callbackList)) {
|
|
209
163
|
originalCallback(result);
|
|
210
164
|
return;
|
|
211
165
|
}
|
|
212
|
-
|
|
166
|
+
if (!handledByOsServer) {
|
|
167
|
+
// Subscription payloads may contain only changed products, so keep
|
|
168
|
+
// the existing raw catalog scope and upsert the changed records.
|
|
169
|
+
this.rememberRawProductQueryResult(callbackList);
|
|
170
|
+
}
|
|
171
|
+
const resolvedCallbackList = (0, _clientDataVariants.resolveClientDataVariants)({
|
|
172
|
+
core: this.core,
|
|
173
|
+
otherParams: this.otherParams,
|
|
174
|
+
products: callbackList,
|
|
175
|
+
queryPayload,
|
|
176
|
+
handledByOsServer
|
|
177
|
+
});
|
|
213
178
|
await this.addProduct(resolvedCallbackList);
|
|
214
179
|
originalCallback(resolvedCallbackList === callbackList ? result : {
|
|
215
180
|
...result,
|
|
@@ -225,7 +190,16 @@ class ProductList extends _BaseModule.BaseModule {
|
|
|
225
190
|
subscriberId: options?.subscriberId,
|
|
226
191
|
customToast: () => {}
|
|
227
192
|
});
|
|
228
|
-
|
|
193
|
+
if (!handledByOsServer && Array.isArray(productsData.data.list)) {
|
|
194
|
+
this.replaceRawProductQueryResult(productsData.data.list, product_ids);
|
|
195
|
+
}
|
|
196
|
+
const resolvedList = (0, _clientDataVariants.resolveClientDataVariants)({
|
|
197
|
+
core: this.core,
|
|
198
|
+
otherParams: this.otherParams,
|
|
199
|
+
products: productsData.data.list || [],
|
|
200
|
+
queryPayload,
|
|
201
|
+
handledByOsServer
|
|
202
|
+
});
|
|
229
203
|
const sortedList = resolvedList.slice().sort((a, b) => Number(b.sort) - Number(a.sort));
|
|
230
204
|
// if (sortedList.length) {
|
|
231
205
|
// sortedList.forEach((n: any) => {
|
|
@@ -253,6 +227,51 @@ class ProductList extends _BaseModule.BaseModule {
|
|
|
253
227
|
});
|
|
254
228
|
return productsData.data;
|
|
255
229
|
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Remember raw remote product records before client-side Data Variant
|
|
233
|
+
* evaluation. Quote-only reads can then always start from a clean baseline.
|
|
234
|
+
*/
|
|
235
|
+
rememberRawProductQueryResult(products) {
|
|
236
|
+
if (!Array.isArray(products)) return;
|
|
237
|
+
products.forEach(product => {
|
|
238
|
+
const source = product;
|
|
239
|
+
const id = Number(source?.id ?? source?.product_id);
|
|
240
|
+
if (!Number.isFinite(id)) return;
|
|
241
|
+
this.rawProductPriceSources.set(id, (0, _lodashEs.cloneDeep)(product));
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
replaceRawProductQueryResult(products, requestedIds) {
|
|
245
|
+
if (Array.isArray(requestedIds) && requestedIds.length > 0) {
|
|
246
|
+
requestedIds.forEach(rawId => {
|
|
247
|
+
const id = Number(rawId);
|
|
248
|
+
if (Number.isFinite(id)) this.rawProductPriceSources.delete(id);
|
|
249
|
+
});
|
|
250
|
+
} else {
|
|
251
|
+
// A non-ID query represents the latest catalog scope. Clearing before
|
|
252
|
+
// replacing prevents removed products from surviving indefinitely.
|
|
253
|
+
this.rawProductPriceSources.clear();
|
|
254
|
+
}
|
|
255
|
+
this.rememberRawProductQueryResult(products);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Return isolated raw price sources for the requested products.
|
|
260
|
+
*
|
|
261
|
+
* Missing IDs are intentionally omitted so callers can remotely fetch only
|
|
262
|
+
* the incomplete part of a batch quote.
|
|
263
|
+
*/
|
|
264
|
+
getRawProductPriceSources(ids) {
|
|
265
|
+
const products = new Map();
|
|
266
|
+
if (!Array.isArray(ids)) return products;
|
|
267
|
+
ids.forEach(rawId => {
|
|
268
|
+
const id = Number(rawId);
|
|
269
|
+
if (!Number.isFinite(id) || products.has(id)) return;
|
|
270
|
+
const product = this.rawProductPriceSources.get(id);
|
|
271
|
+
if (product) products.set(id, (0, _lodashEs.cloneDeep)(product));
|
|
272
|
+
});
|
|
273
|
+
return products;
|
|
274
|
+
}
|
|
256
275
|
async getProducts() {
|
|
257
276
|
await this.core.effects.emit(`${this.name}:onGetProducts`, this.store.list);
|
|
258
277
|
return (0, _lodashEs.cloneDeep)(this.store.list);
|
|
@@ -9,12 +9,23 @@ export declare class QuotationModule extends BaseModule implements Module {
|
|
|
9
9
|
private request;
|
|
10
10
|
private store;
|
|
11
11
|
private scheduleResolver?;
|
|
12
|
+
private quotationCache;
|
|
13
|
+
private quotationLoadPromises;
|
|
14
|
+
private quotationCacheVersions;
|
|
15
|
+
private lifecycleGeneration;
|
|
16
|
+
private activeChannelKey;
|
|
12
17
|
constructor(name?: string, version?: string);
|
|
13
18
|
initialize(core: PisellCore, options: ModuleOptions): Promise<void>;
|
|
14
19
|
loadQuotations(params?: {
|
|
15
20
|
channel?: string;
|
|
16
21
|
customer_id?: number | string;
|
|
17
22
|
}): Promise<void>;
|
|
23
|
+
ensureQuotations(params?: {
|
|
24
|
+
channel?: string;
|
|
25
|
+
customer_id?: number | string;
|
|
26
|
+
}): Promise<void>;
|
|
27
|
+
invalidateQuotationCache(channel?: string): void;
|
|
28
|
+
private fetchQuotations;
|
|
18
29
|
getQuotationList(): QuotationItem[];
|
|
19
30
|
getQuotationCustomerScopeInfo(): QuotationCustomerScopeInfo;
|
|
20
31
|
/**
|
|
@@ -57,9 +68,14 @@ export declare class QuotationModule extends BaseModule implements Module {
|
|
|
57
68
|
buildProductPriceMap(params: {
|
|
58
69
|
productIds: number[];
|
|
59
70
|
timePoints: string[];
|
|
71
|
+
channel?: string;
|
|
60
72
|
customer_id?: number | string;
|
|
61
73
|
}): Map<string, string | null>;
|
|
62
74
|
setScheduleResolver(resolver: (id: number) => ScheduleItem | undefined): void;
|
|
75
|
+
private normalizeChannel;
|
|
76
|
+
private activateChannel;
|
|
77
|
+
private getQuotationCacheVersion;
|
|
78
|
+
private bumpQuotationCacheVersion;
|
|
63
79
|
private isQuotationVisibleForCustomer;
|
|
64
80
|
private hasValidCustomerId;
|
|
65
81
|
private normalizeCustomerId;
|
|
@@ -14,6 +14,11 @@ class QuotationModule extends _BaseModule.BaseModule {
|
|
|
14
14
|
list: []
|
|
15
15
|
};
|
|
16
16
|
scheduleResolver;
|
|
17
|
+
quotationCache = new Map();
|
|
18
|
+
quotationLoadPromises = new Map();
|
|
19
|
+
quotationCacheVersions = new Map();
|
|
20
|
+
lifecycleGeneration = 0;
|
|
21
|
+
activeChannelKey = '';
|
|
17
22
|
constructor(name, version) {
|
|
18
23
|
super(name, version);
|
|
19
24
|
}
|
|
@@ -24,20 +29,99 @@ class QuotationModule extends _BaseModule.BaseModule {
|
|
|
24
29
|
this.store = {
|
|
25
30
|
list: []
|
|
26
31
|
};
|
|
32
|
+
this.quotationCache.clear();
|
|
33
|
+
this.quotationLoadPromises.clear();
|
|
34
|
+
this.quotationCacheVersions.clear();
|
|
35
|
+
this.activeChannelKey = '';
|
|
36
|
+
this.lifecycleGeneration += 1;
|
|
27
37
|
}
|
|
28
38
|
async loadQuotations(params) {
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
39
|
+
const channelKey = this.normalizeChannel(params?.channel);
|
|
40
|
+
const cacheVersion = this.bumpQuotationCacheVersion(channelKey);
|
|
41
|
+
const lifecycleGeneration = this.lifecycleGeneration;
|
|
42
|
+
this.activateChannel(channelKey);
|
|
43
|
+
const loadPromise = this.fetchQuotations({
|
|
44
|
+
channelKey,
|
|
45
|
+
cacheVersion,
|
|
46
|
+
lifecycleGeneration
|
|
47
|
+
});
|
|
48
|
+
this.quotationLoadPromises.set(channelKey, loadPromise);
|
|
49
|
+
try {
|
|
50
|
+
await loadPromise;
|
|
51
|
+
} finally {
|
|
52
|
+
if (this.quotationLoadPromises.get(channelKey) === loadPromise) {
|
|
53
|
+
this.quotationLoadPromises.delete(channelKey);
|
|
54
|
+
}
|
|
33
55
|
}
|
|
56
|
+
}
|
|
57
|
+
async ensureQuotations(params) {
|
|
58
|
+
const channelKey = this.normalizeChannel(params?.channel);
|
|
59
|
+
const lifecycleGeneration = this.lifecycleGeneration;
|
|
60
|
+
while (true) {
|
|
61
|
+
if (this.lifecycleGeneration !== lifecycleGeneration) return;
|
|
62
|
+
if (this.quotationCache.has(channelKey)) {
|
|
63
|
+
this.activateChannel(channelKey);
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
const pending = this.quotationLoadPromises.get(channelKey);
|
|
67
|
+
if (!pending) {
|
|
68
|
+
try {
|
|
69
|
+
await this.loadQuotations(params);
|
|
70
|
+
} catch (error) {
|
|
71
|
+
if (this.quotationCache.has(channelKey)) {
|
|
72
|
+
this.activateChannel(channelKey);
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
if (this.quotationLoadPromises.has(channelKey)) continue;
|
|
76
|
+
throw error;
|
|
77
|
+
}
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
const cacheVersion = this.getQuotationCacheVersion(channelKey);
|
|
81
|
+
try {
|
|
82
|
+
await pending;
|
|
83
|
+
} catch (error) {
|
|
84
|
+
const latestPending = this.quotationLoadPromises.get(channelKey);
|
|
85
|
+
if (latestPending && latestPending !== pending) continue;
|
|
86
|
+
if (this.getQuotationCacheVersion(channelKey) !== cacheVersion) continue;
|
|
87
|
+
throw error;
|
|
88
|
+
}
|
|
89
|
+
if (this.quotationCache.has(channelKey)) {
|
|
90
|
+
this.activateChannel(channelKey);
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
const latestPending = this.quotationLoadPromises.get(channelKey);
|
|
94
|
+
if (latestPending && latestPending !== pending) continue;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
invalidateQuotationCache(channel) {
|
|
98
|
+
if (channel !== undefined) {
|
|
99
|
+
const channelKey = this.normalizeChannel(channel);
|
|
100
|
+
this.bumpQuotationCacheVersion(channelKey);
|
|
101
|
+
this.quotationCache.delete(channelKey);
|
|
102
|
+
this.quotationLoadPromises.delete(channelKey);
|
|
103
|
+
if (this.activeChannelKey === channelKey) this.store.list = [];
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
const channelKeys = new Set([...this.quotationCache.keys(), ...this.quotationLoadPromises.keys(), ...this.quotationCacheVersions.keys(), this.activeChannelKey]);
|
|
107
|
+
channelKeys.forEach(channelKey => this.bumpQuotationCacheVersion(channelKey));
|
|
108
|
+
this.quotationCache.clear();
|
|
109
|
+
this.quotationLoadPromises.clear();
|
|
110
|
+
this.store.list = [];
|
|
111
|
+
}
|
|
112
|
+
async fetchQuotations(params) {
|
|
113
|
+
const query = {};
|
|
114
|
+
if (params.channelKey) query.channel = params.channelKey;
|
|
34
115
|
const res = await this.request.get('/quotation/available', query, {
|
|
35
116
|
useCache: false,
|
|
36
117
|
osServer: true
|
|
37
118
|
});
|
|
38
|
-
const list = res?.data?.list || res?.list || [];
|
|
39
|
-
|
|
40
|
-
|
|
119
|
+
const list = [...(res?.data?.list || res?.list || [])].sort((a, b) => b.sort - a.sort);
|
|
120
|
+
if (this.lifecycleGeneration !== params.lifecycleGeneration || this.getQuotationCacheVersion(params.channelKey) !== params.cacheVersion) {
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
this.quotationCache.set(params.channelKey, list);
|
|
124
|
+
if (this.activeChannelKey === params.channelKey) this.store.list = list;
|
|
41
125
|
}
|
|
42
126
|
getQuotationList() {
|
|
43
127
|
return this.store.list;
|
|
@@ -93,7 +177,8 @@ class QuotationModule extends _BaseModule.BaseModule {
|
|
|
93
177
|
datetime,
|
|
94
178
|
customer_id
|
|
95
179
|
} = params;
|
|
96
|
-
|
|
180
|
+
const quotations = params.channel === undefined ? this.store.list : this.quotationCache.get(this.normalizeChannel(params.channel)) || [];
|
|
181
|
+
for (const quotation of quotations) {
|
|
97
182
|
const visible = this.isQuotationVisibleForCustomer(quotation, customer_id);
|
|
98
183
|
if (!visible) continue;
|
|
99
184
|
const active = this.isQuotationActiveAt(quotation, datetime);
|
|
@@ -119,13 +204,15 @@ class QuotationModule extends _BaseModule.BaseModule {
|
|
|
119
204
|
*/
|
|
120
205
|
buildProductPriceMap(params) {
|
|
121
206
|
const map = new Map();
|
|
122
|
-
|
|
207
|
+
const quotations = params.channel === undefined ? this.store.list : this.quotationCache.get(this.normalizeChannel(params.channel)) || [];
|
|
208
|
+
if (!quotations.length) return map;
|
|
123
209
|
for (const productId of params.productIds) {
|
|
124
210
|
for (const timePoint of params.timePoints) {
|
|
125
211
|
const key = `${productId}:${timePoint}`;
|
|
126
212
|
const price = this.getPriceForProduct({
|
|
127
213
|
productId,
|
|
128
214
|
datetime: timePoint,
|
|
215
|
+
channel: params.channel,
|
|
129
216
|
customer_id: params.customer_id
|
|
130
217
|
});
|
|
131
218
|
if (price !== null) {
|
|
@@ -138,6 +225,22 @@ class QuotationModule extends _BaseModule.BaseModule {
|
|
|
138
225
|
setScheduleResolver(resolver) {
|
|
139
226
|
this.scheduleResolver = resolver;
|
|
140
227
|
}
|
|
228
|
+
normalizeChannel(channel) {
|
|
229
|
+
const normalized = channel?.trim().toLowerCase() || '';
|
|
230
|
+
return normalized === 'online_store' ? 'online-store' : normalized;
|
|
231
|
+
}
|
|
232
|
+
activateChannel(channelKey) {
|
|
233
|
+
this.activeChannelKey = channelKey;
|
|
234
|
+
this.store.list = this.quotationCache.get(channelKey) || [];
|
|
235
|
+
}
|
|
236
|
+
getQuotationCacheVersion(channelKey) {
|
|
237
|
+
return this.quotationCacheVersions.get(channelKey) || 0;
|
|
238
|
+
}
|
|
239
|
+
bumpQuotationCacheVersion(channelKey) {
|
|
240
|
+
const nextVersion = this.getQuotationCacheVersion(channelKey) + 1;
|
|
241
|
+
this.quotationCacheVersions.set(channelKey, nextVersion);
|
|
242
|
+
return nextVersion;
|
|
243
|
+
}
|
|
141
244
|
isQuotationVisibleForCustomer(quotation, customerId) {
|
|
142
245
|
const customers = quotation.customer || [];
|
|
143
246
|
if (customers.length === 0) return true;
|
|
@@ -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;
|