@pisell/pisellos 2.3.22 → 2.3.24
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/dataVariant/adapter.d.ts +50 -0
- package/dist/model/strategy/adapter/dataVariant/adapter.js +167 -0
- package/dist/model/strategy/adapter/dataVariant/evaluator.d.ts +89 -0
- package/dist/model/strategy/adapter/dataVariant/evaluator.js +780 -0
- package/dist/model/strategy/adapter/dataVariant/examples.d.ts +39 -0
- package/dist/model/strategy/adapter/dataVariant/examples.js +277 -0
- package/dist/model/strategy/adapter/dataVariant/index.d.ts +4 -0
- package/dist/model/strategy/adapter/dataVariant/index.js +4 -0
- package/dist/model/strategy/adapter/dataVariant/type.d.ts +148 -0
- package/dist/model/strategy/adapter/dataVariant/type.js +54 -0
- package/dist/model/strategy/adapter/index.d.ts +4 -0
- package/dist/model/strategy/adapter/index.js +5 -1
- package/dist/modules/Discount/index.d.ts +5 -2
- package/dist/modules/Discount/index.js +30 -7
- package/dist/modules/Discount/types.d.ts +4 -0
- package/dist/modules/Order/index.d.ts +2 -0
- package/dist/modules/Order/index.js +42 -15
- package/dist/modules/Order/types.d.ts +2 -0
- package/dist/modules/Order/utils.d.ts +2 -2
- package/dist/modules/Order/utils.js +35 -47
- package/dist/modules/Product/types.d.ts +22 -0
- package/dist/modules/ProductList/index.d.ts +1 -1
- package/dist/modules/ProductList/index.js +4 -2
- package/dist/modules/ProductList/types.d.ts +2 -0
- package/dist/server/index.d.ts +50 -0
- package/dist/server/index.js +995 -749
- package/dist/server/modules/products/index.d.ts +26 -7
- package/dist/server/modules/products/index.js +167 -76
- package/dist/server/modules/products/types.d.ts +14 -0
- package/dist/solution/BaseSales/index.d.ts +4 -0
- package/dist/solution/BaseSales/index.js +30 -14
- package/dist/solution/BookingByStep/index.d.ts +1 -1
- package/dist/solution/BookingTicket/types.d.ts +2 -0
- package/dist/solution/ShopDiscount/index.js +15 -14
- package/lib/model/strategy/adapter/dataVariant/adapter.d.ts +50 -0
- package/lib/model/strategy/adapter/dataVariant/adapter.js +149 -0
- package/lib/model/strategy/adapter/dataVariant/evaluator.d.ts +89 -0
- package/lib/model/strategy/adapter/dataVariant/evaluator.js +583 -0
- package/lib/model/strategy/adapter/dataVariant/examples.d.ts +39 -0
- package/lib/model/strategy/adapter/dataVariant/examples.js +293 -0
- package/lib/model/strategy/adapter/dataVariant/index.d.ts +4 -0
- package/lib/model/strategy/adapter/dataVariant/index.js +38 -0
- package/lib/model/strategy/adapter/dataVariant/type.d.ts +148 -0
- package/lib/model/strategy/adapter/dataVariant/type.js +31 -0
- package/lib/model/strategy/adapter/index.d.ts +4 -0
- package/lib/model/strategy/adapter/index.js +13 -2
- package/lib/model/strategy/adapter/promotion/index.js +0 -49
- package/lib/modules/Discount/index.d.ts +5 -2
- package/lib/modules/Discount/index.js +23 -3
- package/lib/modules/Discount/types.d.ts +4 -0
- package/lib/modules/Order/index.d.ts +2 -0
- package/lib/modules/Order/index.js +27 -4
- package/lib/modules/Order/types.d.ts +2 -0
- package/lib/modules/Order/utils.d.ts +2 -2
- package/lib/modules/Order/utils.js +0 -3
- package/lib/modules/Product/types.d.ts +22 -0
- package/lib/modules/ProductList/index.d.ts +1 -1
- package/lib/modules/ProductList/index.js +4 -2
- package/lib/modules/ProductList/types.d.ts +2 -0
- package/lib/server/index.d.ts +50 -0
- package/lib/server/index.js +206 -29
- package/lib/server/modules/products/index.d.ts +26 -7
- package/lib/server/modules/products/index.js +114 -35
- package/lib/server/modules/products/types.d.ts +14 -0
- package/lib/solution/BaseSales/index.d.ts +4 -0
- package/lib/solution/BaseSales/index.js +17 -4
- package/lib/solution/BookingByStep/index.d.ts +1 -1
- package/lib/solution/BookingTicket/types.d.ts +2 -0
- package/lib/solution/ShopDiscount/index.js +2 -1
- package/package.json +1 -1
|
@@ -68,6 +68,10 @@ export interface DiscountWithReason {
|
|
|
68
68
|
discountList: Discount[];
|
|
69
69
|
unavailableReasonKey: DiscountFilterRejectKey | null;
|
|
70
70
|
}
|
|
71
|
+
export interface DiscountPrepareConfigResult {
|
|
72
|
+
discountList: Discount[];
|
|
73
|
+
availableWalletIds: number[];
|
|
74
|
+
}
|
|
71
75
|
interface ExtensionData {
|
|
72
76
|
id: number;
|
|
73
77
|
shop_id: number;
|
|
@@ -75,6 +75,7 @@ export declare class OrderModule extends BaseModule implements Module, OrderModu
|
|
|
75
75
|
apply?: boolean;
|
|
76
76
|
}): Promise<Discount[]>;
|
|
77
77
|
getDiscountList(): Discount[];
|
|
78
|
+
getAvailableWalletIds(): number[];
|
|
78
79
|
private ensureEditDiscountConfigForProductChange;
|
|
79
80
|
scanCode(code: string, customerId?: number): Promise<{
|
|
80
81
|
isAvailable: boolean;
|
|
@@ -276,6 +277,7 @@ export declare class OrderModule extends BaseModule implements Module, OrderModu
|
|
|
276
277
|
updateTempOrderContactsInfo(contactsInfo: Record<string, any> | null): Record<string, any> | null;
|
|
277
278
|
private buildTempOrderCustomer;
|
|
278
279
|
private isCustomerBoundDiscount;
|
|
280
|
+
private shouldKeepCustomerBoundDiscountAfterCustomerChange;
|
|
279
281
|
private productHasCustomerBoundDiscount;
|
|
280
282
|
private shouldSkipDiscountCalculation;
|
|
281
283
|
private clearCustomerBoundDiscounts;
|
|
@@ -308,6 +308,9 @@ var OrderModule = class extends import_BaseModule.BaseModule {
|
|
|
308
308
|
if (!this.store.summary) {
|
|
309
309
|
this.store.summary = (0, import_utils.createEmptySummary)();
|
|
310
310
|
}
|
|
311
|
+
if (!Array.isArray(this.store.availableWalletIds)) {
|
|
312
|
+
this.store.availableWalletIds = [];
|
|
313
|
+
}
|
|
311
314
|
this.request = this.core.getPlugin("request");
|
|
312
315
|
const appPlugin = this.core.getPlugin("app");
|
|
313
316
|
if (!appPlugin) {
|
|
@@ -435,19 +438,30 @@ var OrderModule = class extends import_BaseModule.BaseModule {
|
|
|
435
438
|
async loadDiscountConfig(params) {
|
|
436
439
|
var _a, _b, _c, _d, _e, _f, _g;
|
|
437
440
|
const orderId = params.orderId || ((_a = this.store.tempOrder) == null ? void 0 : _a.order_id) || void 0;
|
|
438
|
-
const
|
|
441
|
+
const prepareConfigResult = await ((_b = this.store.discount) == null ? void 0 : _b.loadPrepareConfig({
|
|
439
442
|
customer_id: params.customerId,
|
|
440
443
|
action: params.action || (orderId ? "edit" : "create"),
|
|
441
444
|
with_good_pass: 1,
|
|
442
445
|
with_discount_card: 1,
|
|
443
446
|
with_wallet_pass_holder: 1,
|
|
447
|
+
with_available_wallet_flag: 1,
|
|
448
|
+
with_available_wallet_pass_flag: 1,
|
|
444
449
|
request_timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
|
|
445
450
|
...orderId ? { order_id: orderId } : {}
|
|
446
451
|
}));
|
|
452
|
+
const discountList = prepareConfigResult == null ? void 0 : prepareConfigResult.discountList;
|
|
453
|
+
this.store.availableWalletIds = (prepareConfigResult == null ? void 0 : prepareConfigResult.availableWalletIds) || [];
|
|
447
454
|
if (discountList) {
|
|
448
|
-
const
|
|
455
|
+
const currentDiscountList = ((_c = this.store.discount) == null ? void 0 : _c.getDiscountList()) || [];
|
|
456
|
+
const currentEditDiscounts = currentDiscountList.filter((discount) => discount == null ? void 0 : discount.isEditMode);
|
|
457
|
+
const currentScanDiscounts = currentDiscountList.filter((discount) => discount == null ? void 0 : discount.isScan);
|
|
458
|
+
const currentScanDiscountIds = new Set(
|
|
459
|
+
currentScanDiscounts.map((discount) => discount == null ? void 0 : discount.id)
|
|
460
|
+
);
|
|
449
461
|
const runtimeDiscountIds = /* @__PURE__ */ new Set();
|
|
450
462
|
const runtimeDiscounts = discountList.filter((discount) => {
|
|
463
|
+
if (currentScanDiscountIds.has(discount == null ? void 0 : discount.id))
|
|
464
|
+
return false;
|
|
451
465
|
if (runtimeDiscountIds.has(discount == null ? void 0 : discount.id))
|
|
452
466
|
return false;
|
|
453
467
|
runtimeDiscountIds.add(discount == null ? void 0 : discount.id);
|
|
@@ -455,6 +469,7 @@ var OrderModule = class extends import_BaseModule.BaseModule {
|
|
|
455
469
|
});
|
|
456
470
|
const mergedDiscountList = [
|
|
457
471
|
...currentEditDiscounts,
|
|
472
|
+
...currentScanDiscounts,
|
|
458
473
|
...runtimeDiscounts
|
|
459
474
|
];
|
|
460
475
|
await ((_d = this.store.discount) == null ? void 0 : _d.setOriginalDiscountList(mergedDiscountList));
|
|
@@ -469,6 +484,9 @@ var OrderModule = class extends import_BaseModule.BaseModule {
|
|
|
469
484
|
var _a;
|
|
470
485
|
return ((_a = this.store.discount) == null ? void 0 : _a.getDiscountList()) || [];
|
|
471
486
|
}
|
|
487
|
+
getAvailableWalletIds() {
|
|
488
|
+
return Array.isArray(this.store.availableWalletIds) ? [...this.store.availableWalletIds] : [];
|
|
489
|
+
}
|
|
472
490
|
async ensureEditDiscountConfigForProductChange(tempOrder) {
|
|
473
491
|
var _a, _b, _c, _d, _e, _f;
|
|
474
492
|
const orderId = tempOrder.order_id;
|
|
@@ -2214,6 +2232,9 @@ var OrderModule = class extends import_BaseModule.BaseModule {
|
|
|
2214
2232
|
const type = (discount == null ? void 0 : discount.type) ?? (discount == null ? void 0 : discount.tag);
|
|
2215
2233
|
return type === "good_pass" || type === "discount_card" || type === "product_discount_card";
|
|
2216
2234
|
}
|
|
2235
|
+
shouldKeepCustomerBoundDiscountAfterCustomerChange(discount) {
|
|
2236
|
+
return Boolean((discount == null ? void 0 : discount.isScan) || (discount == null ? void 0 : discount.isEditMode));
|
|
2237
|
+
}
|
|
2217
2238
|
productHasCustomerBoundDiscount(product) {
|
|
2218
2239
|
if (((product == null ? void 0 : product.discount_list) || []).some((discount) => this.isCustomerBoundDiscount(discount))) {
|
|
2219
2240
|
return true;
|
|
@@ -2269,12 +2290,12 @@ var OrderModule = class extends import_BaseModule.BaseModule {
|
|
|
2269
2290
|
const currentDiscounts = ((_a = discountModule.getDiscountList) == null ? void 0 : _a.call(discountModule)) || [];
|
|
2270
2291
|
const currentOriginalDiscounts = ((_b = discountModule.getOriginalDiscountList) == null ? void 0 : _b.call(discountModule)) || [];
|
|
2271
2292
|
const nextDiscounts = currentDiscounts.filter((discount) => {
|
|
2272
|
-
if (discount
|
|
2293
|
+
if (this.shouldKeepCustomerBoundDiscountAfterCustomerChange(discount))
|
|
2273
2294
|
return true;
|
|
2274
2295
|
return !this.isCustomerBoundDiscount(discount);
|
|
2275
2296
|
});
|
|
2276
2297
|
const nextOriginalDiscounts = currentOriginalDiscounts.filter((discount) => {
|
|
2277
|
-
if (discount
|
|
2298
|
+
if (this.shouldKeepCustomerBoundDiscountAfterCustomerChange(discount))
|
|
2278
2299
|
return true;
|
|
2279
2300
|
return !this.isCustomerBoundDiscount(discount);
|
|
2280
2301
|
});
|
|
@@ -2345,6 +2366,7 @@ var OrderModule = class extends import_BaseModule.BaseModule {
|
|
|
2345
2366
|
}
|
|
2346
2367
|
const nextCustomerId = tempOrder.customer_id ?? null;
|
|
2347
2368
|
if (String(previousCustomerId ?? "") !== String(nextCustomerId ?? "")) {
|
|
2369
|
+
this.store.availableWalletIds = [];
|
|
2348
2370
|
(0, import_utils.clearTempOrderHolderAssignments)(tempOrder);
|
|
2349
2371
|
this.clearCustomerBoundDiscounts(tempOrder);
|
|
2350
2372
|
this.applyDiscount();
|
|
@@ -2384,6 +2406,7 @@ var OrderModule = class extends import_BaseModule.BaseModule {
|
|
|
2384
2406
|
tempOrder.customer = null;
|
|
2385
2407
|
if (tempOrder._extend)
|
|
2386
2408
|
delete tempOrder._extend.customerSnapshot;
|
|
2409
|
+
this.store.availableWalletIds = [];
|
|
2387
2410
|
this.clearCustomerBoundDiscounts(tempOrder);
|
|
2388
2411
|
this.persistTempOrder();
|
|
2389
2412
|
this.saveDraftInBackground();
|
|
@@ -309,6 +309,7 @@ export interface OrderState {
|
|
|
309
309
|
lastOrderInfo?: Record<string, any>;
|
|
310
310
|
syncState?: OrderSyncState;
|
|
311
311
|
discountList: any[];
|
|
312
|
+
availableWalletIds?: number[];
|
|
312
313
|
discount: DiscountModule | null;
|
|
313
314
|
rules: RulesModule | null;
|
|
314
315
|
}
|
|
@@ -775,6 +776,7 @@ export interface OrderModuleAPI {
|
|
|
775
776
|
apply?: boolean;
|
|
776
777
|
}) => Promise<Discount[]>;
|
|
777
778
|
getDiscountList: () => Discount[];
|
|
779
|
+
getAvailableWalletIds: () => number[];
|
|
778
780
|
applyDiscount: () => void;
|
|
779
781
|
/**
|
|
780
782
|
* 注入促销评估器。
|
|
@@ -67,8 +67,8 @@ export declare function sumOptionUnitPrice(options?: Array<{
|
|
|
67
67
|
/**
|
|
68
68
|
* 仅基于 tempOrder.products 重算整单定金。
|
|
69
69
|
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
70
|
+
* 商品折扣在进入定金计算前已体现在商品行价格字段中,
|
|
71
|
+
* 因此这里只按商品定金规则汇总,不再扣减 discount_list;tempOrder.bookings 不参与这里的计算。
|
|
72
72
|
*/
|
|
73
73
|
export declare function calculateOrderProductsDeposit(products?: OrderProduct[], options?: {
|
|
74
74
|
productsByUid?: Record<string, Record<string, any>>;
|
|
@@ -286,9 +286,6 @@ function calculateOrderProductsDeposit(products = [], options) {
|
|
|
286
286
|
if (!(deposit == null ? void 0 : deposit.total) || Number(deposit.total) <= 0)
|
|
287
287
|
continue;
|
|
288
288
|
total = total.plus(deposit.total);
|
|
289
|
-
for (const discount of product.discount_list || []) {
|
|
290
|
-
total = total.minus((discount == null ? void 0 : discount.amount) || 0);
|
|
291
|
-
}
|
|
292
289
|
for (const protocol of deposit.protocols || []) {
|
|
293
290
|
if (!protocolMap.has(protocol.id))
|
|
294
291
|
protocolMap.set(protocol.id, protocol);
|
|
@@ -9,6 +9,26 @@ export interface ProductCollection {
|
|
|
9
9
|
/** 商品集合封面 */
|
|
10
10
|
cover: string;
|
|
11
11
|
}
|
|
12
|
+
/**
|
|
13
|
+
* 商品 Data Variant 记录,结构与后端 data_variant 表对齐。
|
|
14
|
+
*/
|
|
15
|
+
export interface ProductDataVariant {
|
|
16
|
+
/** 变体记录 id */
|
|
17
|
+
id: number;
|
|
18
|
+
/** 店铺 id */
|
|
19
|
+
shop_id: number;
|
|
20
|
+
/** 所属模块,商品场景固定为 product */
|
|
21
|
+
module: string;
|
|
22
|
+
/** 模块数据 id,商品场景对应 product.id */
|
|
23
|
+
module_data_id: number;
|
|
24
|
+
/** 关联 data_variant_rule.id */
|
|
25
|
+
data_variant_rule_id: number;
|
|
26
|
+
/** 命中策略后覆盖到商品上的字段 */
|
|
27
|
+
data: Record<string, any>;
|
|
28
|
+
created_at: string | null;
|
|
29
|
+
updated_at: string | null;
|
|
30
|
+
deleted_at: string | null;
|
|
31
|
+
}
|
|
12
32
|
/**
|
|
13
33
|
* 商品基本信息接口
|
|
14
34
|
*/
|
|
@@ -214,6 +234,8 @@ export interface ProductData {
|
|
|
214
234
|
bundle_group_count: number;
|
|
215
235
|
/** 选项组数量 */
|
|
216
236
|
option_group_count: number;
|
|
237
|
+
/** 智能定价变体列表,/product/query with dataVariants 返回 */
|
|
238
|
+
data_variants?: ProductDataVariant[];
|
|
217
239
|
/** 服务时长 */
|
|
218
240
|
service_times?: any;
|
|
219
241
|
}
|
|
@@ -26,7 +26,7 @@ export declare class ProductList extends BaseModule implements Module {
|
|
|
26
26
|
* const products = await productList.getAddTimeProducts({ schedule_date: '2026-06-16' });
|
|
27
27
|
*/
|
|
28
28
|
getAddTimeProducts(params?: ProductListLoadProductsParams): Promise<any>;
|
|
29
|
-
loadProducts({ category_ids, product_ids, collection, menu_list_ids, customer_id: paramsCustomerId, with_count, schedule_datetime, schedule_date, cacheId, with_schedule, extension_type, status, }?: ProductListLoadProductsParams, options?: {
|
|
29
|
+
loadProducts({ category_ids, product_ids, collection, menu_list_ids, customer_id: paramsCustomerId, with_count, schedule_datetime, schedule_date, cacheId, with_schedule, extension_type, status, strategy_context, }?: ProductListLoadProductsParams, options?: {
|
|
30
30
|
callback?: (result: any) => void;
|
|
31
31
|
subscriberId?: string;
|
|
32
32
|
}): Promise<any>;
|
|
@@ -95,7 +95,8 @@ var ProductList = class extends import_BaseModule.BaseModule {
|
|
|
95
95
|
cacheId,
|
|
96
96
|
with_schedule,
|
|
97
97
|
extension_type,
|
|
98
|
-
status = "published"
|
|
98
|
+
status = "published",
|
|
99
|
+
strategy_context
|
|
99
100
|
} = {}, options) {
|
|
100
101
|
var _a, _b;
|
|
101
102
|
let userPlugin = this.core.getPlugin("user");
|
|
@@ -135,7 +136,8 @@ var ProductList = class extends import_BaseModule.BaseModule {
|
|
|
135
136
|
is_eject: 1,
|
|
136
137
|
with_schedule,
|
|
137
138
|
schedule_datetime,
|
|
138
|
-
extension_type
|
|
139
|
+
extension_type,
|
|
140
|
+
strategy_context
|
|
139
141
|
},
|
|
140
142
|
{ osServer: true, callback: options == null ? void 0 : options.callback, subscriberId: options == null ? void 0 : options.subscriberId, customToast: () => {
|
|
141
143
|
} }
|
|
@@ -12,6 +12,8 @@ export interface ProductListLoadProductsParams {
|
|
|
12
12
|
with_schedule?: number;
|
|
13
13
|
extension_type?: string[];
|
|
14
14
|
status?: string;
|
|
15
|
+
/** 智能定价条件上下文,仅影响策略命中,不参与商品集合筛选 */
|
|
16
|
+
strategy_context?: Record<string, any>;
|
|
15
17
|
}
|
|
16
18
|
export interface ProductListData {
|
|
17
19
|
list: ProductData[];
|
package/lib/server/index.d.ts
CHANGED
|
@@ -47,6 +47,8 @@ declare class Server {
|
|
|
47
47
|
private prefixRouterGet;
|
|
48
48
|
router: Router;
|
|
49
49
|
private productQuerySubscribers;
|
|
50
|
+
/** subscriberId → 智能定价变价时间点定时器句柄(与 subscriber 分离存储,便于 clear) */
|
|
51
|
+
private productQueryScheduleTimers;
|
|
50
52
|
private orderQuerySubscribers;
|
|
51
53
|
private bookingQuerySubscribers;
|
|
52
54
|
private bookingRemoteQuerySubscribers;
|
|
@@ -210,6 +212,48 @@ declare class Server {
|
|
|
210
212
|
* 根据 subscriberId 移除商品查询订阅者
|
|
211
213
|
*/
|
|
212
214
|
removeProductQuerySubscriber(subscriberId?: string): void;
|
|
215
|
+
/**
|
|
216
|
+
* 构建商品 query 响应 data 段,附带智能定价 schedule_time_points 元数据。
|
|
217
|
+
*/
|
|
218
|
+
private buildProductQueryResultPayload;
|
|
219
|
+
/**
|
|
220
|
+
* 是否启用商品 query 订阅的 schedule 时间点定时重算。
|
|
221
|
+
* strategy_context.enable_schedule_reload === false 时关闭。
|
|
222
|
+
*/
|
|
223
|
+
private shouldScheduleProductQueryReload;
|
|
224
|
+
/**
|
|
225
|
+
* 清除指定 subscriber 的全部 schedule 定时器,防止重复 query 或 unsubscribe 后泄漏。
|
|
226
|
+
*/
|
|
227
|
+
private clearSubscriberScheduleTimers;
|
|
228
|
+
/**
|
|
229
|
+
* 从 productQueryScheduleTimers 移除已触发的 timer 引用。
|
|
230
|
+
*/
|
|
231
|
+
private removeScheduleTimerRef;
|
|
232
|
+
/**
|
|
233
|
+
* 计算 schedule 变价时间点到当前时刻的延迟(ms)。已过期返回 -1。
|
|
234
|
+
*/
|
|
235
|
+
private computeScheduleTimePointDelayMs;
|
|
236
|
+
/**
|
|
237
|
+
* 定时重算前刷新 subscriber schedule 上下文:使用执行瞬间时间,禁止沿用首次 query 的 schedule_datetime。
|
|
238
|
+
*/
|
|
239
|
+
private refreshSubscriberScheduleAtExecution;
|
|
240
|
+
/**
|
|
241
|
+
* 为 subscriber 注册智能定价 scheduleTimePoints 定时重算。
|
|
242
|
+
* 必须先 clearSubscriberScheduleTimers,再调用本方法。
|
|
243
|
+
*/
|
|
244
|
+
private scheduleSubscriberTimePointReloads;
|
|
245
|
+
/**
|
|
246
|
+
* schedule 变价时间点定时器触发:刷新 schedule_datetime 后重算并 callback。
|
|
247
|
+
*/
|
|
248
|
+
private onScheduleTimePointTimerFire;
|
|
249
|
+
/**
|
|
250
|
+
* 对单个商品 query 订阅者重算并推送(定时路径;不新建 schedule timer)。
|
|
251
|
+
*/
|
|
252
|
+
private recomputeAndNotifySubscriber;
|
|
253
|
+
/**
|
|
254
|
+
* query 完成后为 subscriber 同步 schedule 定时器(先 clear 再 schedule)。
|
|
255
|
+
*/
|
|
256
|
+
private syncProductQueryScheduleTimers;
|
|
213
257
|
/**
|
|
214
258
|
* 处理商品查询请求
|
|
215
259
|
* 存储订阅者信息,便于数据变更时推送最新结果
|
|
@@ -570,6 +614,12 @@ declare class Server {
|
|
|
570
614
|
* @param options.changedIds 变更的商品 IDs,用于增量更新价格缓存
|
|
571
615
|
*/
|
|
572
616
|
private computeProductQueryResult;
|
|
617
|
+
/**
|
|
618
|
+
* 构建商品格式化上下文。
|
|
619
|
+
*
|
|
620
|
+
* 智能定价条件使用全量 menuList/scheduleList;Product Query 顶层参数只保留筛选商品集合所需字段。
|
|
621
|
+
*/
|
|
622
|
+
private buildProductFormatterContext;
|
|
573
623
|
/**
|
|
574
624
|
* 数据变更后,遍历所有订阅者重新计算查询结果并通过 callback 推送
|
|
575
625
|
* 由 ProductsModule 的 onProductsSyncCompleted 事件触发
|
package/lib/server/index.js
CHANGED
|
@@ -76,6 +76,8 @@ var Server = class {
|
|
|
76
76
|
};
|
|
77
77
|
// ---- 商品查询订阅者 ----
|
|
78
78
|
this.productQuerySubscribers = /* @__PURE__ */ new Map();
|
|
79
|
+
/** subscriberId → 智能定价变价时间点定时器句柄(与 subscriber 分离存储,便于 clear) */
|
|
80
|
+
this.productQueryScheduleTimers = /* @__PURE__ */ new Map();
|
|
79
81
|
// ---- 订单 / 预约列表查询订阅者 ----
|
|
80
82
|
this.orderQuerySubscribers = /* @__PURE__ */ new Map();
|
|
81
83
|
this.bookingQuerySubscribers = /* @__PURE__ */ new Map();
|
|
@@ -208,7 +210,7 @@ var Server = class {
|
|
|
208
210
|
*/
|
|
209
211
|
this.handleProductQuery = async ({ url, method, data, config }) => {
|
|
210
212
|
console.log("[Server] handleProductQuery:", url, method, data, config);
|
|
211
|
-
const { menu_list_ids, schedule_datetime, schedule_date, customer_id, ids, extension_type } = data;
|
|
213
|
+
const { menu_list_ids, schedule_datetime, schedule_date, customer_id, ids, extension_type, strategy_context } = data;
|
|
212
214
|
const { callback, subscriberId } = config || {};
|
|
213
215
|
this.logInfo("handleProductQuery: 开始处理商品查询请求", {
|
|
214
216
|
menu_list_ids,
|
|
@@ -216,19 +218,32 @@ var Server = class {
|
|
|
216
218
|
schedule_datetime,
|
|
217
219
|
schedule_date,
|
|
218
220
|
customer_id,
|
|
219
|
-
extension_type
|
|
221
|
+
extension_type,
|
|
222
|
+
strategyContextKeys: strategy_context ? Object.keys(strategy_context) : []
|
|
220
223
|
});
|
|
221
224
|
if (subscriberId && typeof callback === "function") {
|
|
222
225
|
this.productQuerySubscribers.set(subscriberId, {
|
|
223
226
|
callback,
|
|
224
|
-
context: { menu_list_ids, ids, schedule_date, schedule_datetime, customer_id, extension_type }
|
|
227
|
+
context: { menu_list_ids, ids, schedule_date, schedule_datetime, customer_id, extension_type, strategy_context }
|
|
225
228
|
});
|
|
226
229
|
this.logInfo("handleProductQuery: 已注册订阅者", {
|
|
227
230
|
subscriberId,
|
|
228
231
|
totalSubscribers: this.productQuerySubscribers.size
|
|
229
232
|
});
|
|
230
233
|
}
|
|
231
|
-
|
|
234
|
+
const result = await this.computeProductQueryResult({
|
|
235
|
+
menu_list_ids,
|
|
236
|
+
ids,
|
|
237
|
+
schedule_date,
|
|
238
|
+
schedule_datetime,
|
|
239
|
+
customer_id,
|
|
240
|
+
extension_type,
|
|
241
|
+
strategy_context
|
|
242
|
+
});
|
|
243
|
+
if (subscriberId && typeof callback === "function") {
|
|
244
|
+
this.syncProductQueryScheduleTimers(subscriberId, strategy_context);
|
|
245
|
+
}
|
|
246
|
+
return result;
|
|
232
247
|
};
|
|
233
248
|
/**
|
|
234
249
|
* 按商品 id 查询单条(GET /shop/product/query/:productId)
|
|
@@ -1911,6 +1926,7 @@ var Server = class {
|
|
|
1911
1926
|
*/
|
|
1912
1927
|
removeProductQuerySubscriber(subscriberId) {
|
|
1913
1928
|
if (subscriberId) {
|
|
1929
|
+
this.clearSubscriberScheduleTimers(subscriberId);
|
|
1914
1930
|
this.productQuerySubscribers.delete(subscriberId);
|
|
1915
1931
|
this.logInfo("removeProductQuerySubscriber: 已移除订阅者", {
|
|
1916
1932
|
subscriberId,
|
|
@@ -1918,6 +1934,156 @@ var Server = class {
|
|
|
1918
1934
|
});
|
|
1919
1935
|
}
|
|
1920
1936
|
}
|
|
1937
|
+
/**
|
|
1938
|
+
* 构建商品 query 响应 data 段,附带智能定价 schedule_time_points 元数据。
|
|
1939
|
+
*/
|
|
1940
|
+
buildProductQueryResultPayload(list, count) {
|
|
1941
|
+
var _a, _b;
|
|
1942
|
+
return {
|
|
1943
|
+
list,
|
|
1944
|
+
count: count ?? list.length,
|
|
1945
|
+
schedule_time_points: ((_b = (_a = this.products) == null ? void 0 : _a.getLastScheduleTimePoints) == null ? void 0 : _b.call(_a)) ?? []
|
|
1946
|
+
};
|
|
1947
|
+
}
|
|
1948
|
+
/**
|
|
1949
|
+
* 是否启用商品 query 订阅的 schedule 时间点定时重算。
|
|
1950
|
+
* strategy_context.enable_schedule_reload === false 时关闭。
|
|
1951
|
+
*/
|
|
1952
|
+
shouldScheduleProductQueryReload(strategy_context) {
|
|
1953
|
+
return (strategy_context == null ? void 0 : strategy_context.enable_schedule_reload) !== false;
|
|
1954
|
+
}
|
|
1955
|
+
/**
|
|
1956
|
+
* 清除指定 subscriber 的全部 schedule 定时器,防止重复 query 或 unsubscribe 后泄漏。
|
|
1957
|
+
*/
|
|
1958
|
+
clearSubscriberScheduleTimers(subscriberId) {
|
|
1959
|
+
const timers = this.productQueryScheduleTimers.get(subscriberId);
|
|
1960
|
+
if (timers == null ? void 0 : timers.length) {
|
|
1961
|
+
timers.forEach((timerId) => clearTimeout(timerId));
|
|
1962
|
+
}
|
|
1963
|
+
this.productQueryScheduleTimers.delete(subscriberId);
|
|
1964
|
+
const subscriber = this.productQuerySubscribers.get(subscriberId);
|
|
1965
|
+
if (subscriber) {
|
|
1966
|
+
subscriber.scheduleTimerIds = [];
|
|
1967
|
+
}
|
|
1968
|
+
}
|
|
1969
|
+
/**
|
|
1970
|
+
* 从 productQueryScheduleTimers 移除已触发的 timer 引用。
|
|
1971
|
+
*/
|
|
1972
|
+
removeScheduleTimerRef(subscriberId, timerId) {
|
|
1973
|
+
const timers = this.productQueryScheduleTimers.get(subscriberId);
|
|
1974
|
+
if (!(timers == null ? void 0 : timers.length))
|
|
1975
|
+
return;
|
|
1976
|
+
const next = timers.filter((id) => id !== timerId);
|
|
1977
|
+
if (next.length === 0) {
|
|
1978
|
+
this.productQueryScheduleTimers.delete(subscriberId);
|
|
1979
|
+
} else {
|
|
1980
|
+
this.productQueryScheduleTimers.set(subscriberId, next);
|
|
1981
|
+
}
|
|
1982
|
+
const subscriber = this.productQuerySubscribers.get(subscriberId);
|
|
1983
|
+
if (subscriber) {
|
|
1984
|
+
subscriber.scheduleTimerIds = next;
|
|
1985
|
+
}
|
|
1986
|
+
}
|
|
1987
|
+
/**
|
|
1988
|
+
* 计算 schedule 变价时间点到当前时刻的延迟(ms)。已过期返回 -1。
|
|
1989
|
+
*/
|
|
1990
|
+
computeScheduleTimePointDelayMs(scheduleDate, timePoint) {
|
|
1991
|
+
const parts = timePoint.split(":");
|
|
1992
|
+
if (parts.length < 2)
|
|
1993
|
+
return -1;
|
|
1994
|
+
const hours = parseInt(parts[0], 10);
|
|
1995
|
+
const minutes = parseInt(parts[1], 10);
|
|
1996
|
+
if (!Number.isFinite(hours) || !Number.isFinite(minutes))
|
|
1997
|
+
return -1;
|
|
1998
|
+
const target = (0, import_dayjs.default)(scheduleDate).hour(hours).minute(minutes).second(4).millisecond(0);
|
|
1999
|
+
const delayMs = target.diff((0, import_dayjs.default)());
|
|
2000
|
+
return delayMs >= 0 ? delayMs : -1;
|
|
2001
|
+
}
|
|
2002
|
+
/**
|
|
2003
|
+
* 定时重算前刷新 subscriber schedule 上下文:使用执行瞬间时间,禁止沿用首次 query 的 schedule_datetime。
|
|
2004
|
+
*/
|
|
2005
|
+
refreshSubscriberScheduleAtExecution(subscriber) {
|
|
2006
|
+
const executedAt = (0, import_dayjs.default)();
|
|
2007
|
+
const bookingDate = subscriber.context.schedule_date || executedAt.format("YYYY-MM-DD");
|
|
2008
|
+
subscriber.context.schedule_date = bookingDate;
|
|
2009
|
+
subscriber.context.schedule_datetime = `${bookingDate} ${executedAt.format("HH:mm:ss")}`;
|
|
2010
|
+
}
|
|
2011
|
+
/**
|
|
2012
|
+
* 为 subscriber 注册智能定价 scheduleTimePoints 定时重算。
|
|
2013
|
+
* 必须先 clearSubscriberScheduleTimers,再调用本方法。
|
|
2014
|
+
*/
|
|
2015
|
+
scheduleSubscriberTimePointReloads(subscriberId, timePoints) {
|
|
2016
|
+
const subscriber = this.productQuerySubscribers.get(subscriberId);
|
|
2017
|
+
if (!subscriber || !timePoints.length)
|
|
2018
|
+
return;
|
|
2019
|
+
const scheduleDate = subscriber.context.schedule_date || (0, import_dayjs.default)().format("YYYY-MM-DD");
|
|
2020
|
+
const timerIds = [];
|
|
2021
|
+
for (const timePoint of timePoints) {
|
|
2022
|
+
const delayMs = this.computeScheduleTimePointDelayMs(scheduleDate, timePoint);
|
|
2023
|
+
if (delayMs < 0)
|
|
2024
|
+
continue;
|
|
2025
|
+
const timerId = setTimeout(() => {
|
|
2026
|
+
void this.onScheduleTimePointTimerFire(subscriberId, timerId);
|
|
2027
|
+
}, delayMs);
|
|
2028
|
+
timerIds.push(timerId);
|
|
2029
|
+
}
|
|
2030
|
+
if (timerIds.length > 0) {
|
|
2031
|
+
this.productQueryScheduleTimers.set(subscriberId, timerIds);
|
|
2032
|
+
subscriber.scheduleTimerIds = timerIds;
|
|
2033
|
+
this.logInfo("scheduleSubscriberTimePointReloads: 已注册定时器", {
|
|
2034
|
+
subscriberId,
|
|
2035
|
+
timerCount: timerIds.length,
|
|
2036
|
+
timePoints
|
|
2037
|
+
});
|
|
2038
|
+
}
|
|
2039
|
+
}
|
|
2040
|
+
/**
|
|
2041
|
+
* schedule 变价时间点定时器触发:刷新 schedule_datetime 后重算并 callback。
|
|
2042
|
+
*/
|
|
2043
|
+
async onScheduleTimePointTimerFire(subscriberId, timerId) {
|
|
2044
|
+
this.removeScheduleTimerRef(subscriberId, timerId);
|
|
2045
|
+
if (!this.productQuerySubscribers.has(subscriberId))
|
|
2046
|
+
return;
|
|
2047
|
+
const subscriber = this.productQuerySubscribers.get(subscriberId);
|
|
2048
|
+
this.refreshSubscriberScheduleAtExecution(subscriber);
|
|
2049
|
+
await this.recomputeAndNotifySubscriber(subscriberId);
|
|
2050
|
+
}
|
|
2051
|
+
/**
|
|
2052
|
+
* 对单个商品 query 订阅者重算并推送(定时路径;不新建 schedule timer)。
|
|
2053
|
+
*/
|
|
2054
|
+
async recomputeAndNotifySubscriber(subscriberId) {
|
|
2055
|
+
const subscriber = this.productQuerySubscribers.get(subscriberId);
|
|
2056
|
+
if (!subscriber)
|
|
2057
|
+
return;
|
|
2058
|
+
try {
|
|
2059
|
+
const result = await this.computeProductQueryResult(subscriber.context);
|
|
2060
|
+
subscriber.callback(result);
|
|
2061
|
+
this.logInfo("recomputeAndNotifySubscriber: 已推送", {
|
|
2062
|
+
subscriberId,
|
|
2063
|
+
schedule_datetime: subscriber.context.schedule_datetime
|
|
2064
|
+
});
|
|
2065
|
+
} catch (error) {
|
|
2066
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
2067
|
+
this.logError("recomputeAndNotifySubscriber: 推送失败", {
|
|
2068
|
+
subscriberId,
|
|
2069
|
+
error: errorMessage
|
|
2070
|
+
});
|
|
2071
|
+
}
|
|
2072
|
+
}
|
|
2073
|
+
/**
|
|
2074
|
+
* query 完成后为 subscriber 同步 schedule 定时器(先 clear 再 schedule)。
|
|
2075
|
+
*/
|
|
2076
|
+
syncProductQueryScheduleTimers(subscriberId, strategy_context) {
|
|
2077
|
+
var _a, _b;
|
|
2078
|
+
this.clearSubscriberScheduleTimers(subscriberId);
|
|
2079
|
+
if (!this.shouldScheduleProductQueryReload(strategy_context)) {
|
|
2080
|
+
return;
|
|
2081
|
+
}
|
|
2082
|
+
const timePoints = ((_b = (_a = this.products) == null ? void 0 : _a.getLastScheduleTimePoints) == null ? void 0 : _b.call(_a)) ?? [];
|
|
2083
|
+
if (timePoints.length > 0) {
|
|
2084
|
+
this.scheduleSubscriberTimePointReloads(subscriberId, timePoints);
|
|
2085
|
+
}
|
|
2086
|
+
}
|
|
1921
2087
|
/**
|
|
1922
2088
|
* 执行 sales 搜索并登记可见订单号。
|
|
1923
2089
|
* 搜索参数完全透传后端,OS 不做过滤、筛选、排序。
|
|
@@ -4111,8 +4277,14 @@ var Server = class {
|
|
|
4111
4277
|
*/
|
|
4112
4278
|
async computeProductQueryResult(context, options) {
|
|
4113
4279
|
const tTotal = performance.now();
|
|
4114
|
-
const { menu_list_ids, ids, schedule_date, schedule_datetime, customer_id, extension_type, product_id } = context;
|
|
4280
|
+
const { menu_list_ids, ids, schedule_date, schedule_datetime, customer_id, extension_type, product_id, strategy_context } = context;
|
|
4115
4281
|
const queryIds = Array.isArray(ids) ? ids.map((id) => Number(id)).filter((id) => Number.isFinite(id)) : [];
|
|
4282
|
+
const formatterContext = this.buildProductFormatterContext({
|
|
4283
|
+
schedule_date,
|
|
4284
|
+
schedule_datetime,
|
|
4285
|
+
customer_id,
|
|
4286
|
+
strategy_context
|
|
4287
|
+
});
|
|
4116
4288
|
this.logInfo("computeProductQueryResult 开始", {
|
|
4117
4289
|
menuListIdsCount: (menu_list_ids == null ? void 0 : menu_list_ids.length) ?? 0,
|
|
4118
4290
|
ids: queryIds,
|
|
@@ -4121,20 +4293,17 @@ var Server = class {
|
|
|
4121
4293
|
customer_id,
|
|
4122
4294
|
extension_type,
|
|
4123
4295
|
product_id,
|
|
4296
|
+
strategyContextKeys: strategy_context ? Object.keys(strategy_context) : [],
|
|
4124
4297
|
changedIds: options == null ? void 0 : options.changedIds
|
|
4125
4298
|
});
|
|
4126
4299
|
if (!this.products) {
|
|
4127
4300
|
this.logError("computeProductQueryResult: Products 模块未注册");
|
|
4128
|
-
return { message: "Products 模块未注册", data:
|
|
4301
|
+
return { message: "Products 模块未注册", data: this.buildProductQueryResultPayload([], 0) };
|
|
4129
4302
|
}
|
|
4130
4303
|
if (queryIds.length > 0) {
|
|
4131
4304
|
const uniqueIds = Array.from(new Set(queryIds));
|
|
4132
4305
|
const tPrice2 = performance.now();
|
|
4133
|
-
const productsWithPrice = await this.products.getProductsWithPrice(schedule_date, {
|
|
4134
|
-
scheduleModule: this.getSchedule(),
|
|
4135
|
-
schedule_datetime,
|
|
4136
|
-
customer_id
|
|
4137
|
-
}, {
|
|
4306
|
+
const productsWithPrice = await this.products.getProductsWithPrice(schedule_date, formatterContext, {
|
|
4138
4307
|
changedIds: options == null ? void 0 : options.changedIds,
|
|
4139
4308
|
productIds: uniqueIds
|
|
4140
4309
|
});
|
|
@@ -4155,7 +4324,7 @@ var Server = class {
|
|
|
4155
4324
|
});
|
|
4156
4325
|
return {
|
|
4157
4326
|
code: 200,
|
|
4158
|
-
data:
|
|
4327
|
+
data: this.buildProductQueryResultPayload(extensionFilteredProducts),
|
|
4159
4328
|
message: "",
|
|
4160
4329
|
status: true
|
|
4161
4330
|
};
|
|
@@ -4163,9 +4332,7 @@ var Server = class {
|
|
|
4163
4332
|
if (product_id != null && Number.isFinite(Number(product_id))) {
|
|
4164
4333
|
const pid = Number(product_id);
|
|
4165
4334
|
const tPrice2 = performance.now();
|
|
4166
|
-
const allProductsWithPrice = await this.products.getProductsWithPrice(schedule_date, {
|
|
4167
|
-
scheduleModule: this.getSchedule()
|
|
4168
|
-
}, {
|
|
4335
|
+
const allProductsWithPrice = await this.products.getProductsWithPrice(schedule_date, formatterContext, {
|
|
4169
4336
|
changedIds: options == null ? void 0 : options.changedIds
|
|
4170
4337
|
});
|
|
4171
4338
|
(0, import_product.perfMark)("computeQuery.getProductsWithPrice(single)", performance.now() - tPrice2, {
|
|
@@ -4195,11 +4362,7 @@ var Server = class {
|
|
|
4195
4362
|
}
|
|
4196
4363
|
if (this.shouldQueryProductsByExtensionTypes(extension_type)) {
|
|
4197
4364
|
const tPrice2 = performance.now();
|
|
4198
|
-
let filteredProducts2 = await this.products.getProductsWithPrice(schedule_date, {
|
|
4199
|
-
scheduleModule: this.getSchedule(),
|
|
4200
|
-
schedule_datetime,
|
|
4201
|
-
customer_id
|
|
4202
|
-
}, {
|
|
4365
|
+
let filteredProducts2 = await this.products.getProductsWithPrice(schedule_date, formatterContext, {
|
|
4203
4366
|
changedIds: options == null ? void 0 : options.changedIds
|
|
4204
4367
|
});
|
|
4205
4368
|
(0, import_product.perfMark)("computeQuery.getProductsWithPrice(extensionType)", performance.now() - tPrice2, {
|
|
@@ -4239,18 +4402,18 @@ var Server = class {
|
|
|
4239
4402
|
});
|
|
4240
4403
|
return {
|
|
4241
4404
|
code: 200,
|
|
4242
|
-
data:
|
|
4405
|
+
data: this.buildProductQueryResultPayload(filteredProducts2),
|
|
4243
4406
|
message: "",
|
|
4244
4407
|
status: true
|
|
4245
4408
|
};
|
|
4246
4409
|
}
|
|
4247
4410
|
if (!this.menu) {
|
|
4248
4411
|
this.logError("computeProductQueryResult: Menu 模块未注册");
|
|
4249
|
-
return { message: "Menu 模块未注册", data:
|
|
4412
|
+
return { message: "Menu 模块未注册", data: this.buildProductQueryResultPayload([], 0) };
|
|
4250
4413
|
}
|
|
4251
4414
|
if (!this.schedule) {
|
|
4252
4415
|
this.logError("computeProductQueryResult: Schedule 模块未注册");
|
|
4253
|
-
return { message: "Schedule 模块未注册", data:
|
|
4416
|
+
return { message: "Schedule 模块未注册", data: this.buildProductQueryResultPayload([], 0) };
|
|
4254
4417
|
}
|
|
4255
4418
|
let activeMenuList = [];
|
|
4256
4419
|
if (menu_list_ids && Array.isArray(menu_list_ids) && menu_list_ids.length > 0) {
|
|
@@ -4274,11 +4437,7 @@ var Server = class {
|
|
|
4274
4437
|
isAllProducts: !!productScope.isAllProducts
|
|
4275
4438
|
});
|
|
4276
4439
|
const tPrice = performance.now();
|
|
4277
|
-
let filteredProducts = scopedProductIds.length > 0 ? await this.products.getProductsWithPrice(schedule_date, {
|
|
4278
|
-
scheduleModule: this.getSchedule(),
|
|
4279
|
-
schedule_datetime,
|
|
4280
|
-
customer_id
|
|
4281
|
-
}, {
|
|
4440
|
+
let filteredProducts = scopedProductIds.length > 0 ? await this.products.getProductsWithPrice(schedule_date, formatterContext, {
|
|
4282
4441
|
changedIds: options == null ? void 0 : options.changedIds,
|
|
4283
4442
|
productIds: productScope.isAllProducts ? void 0 : scopedProductIds
|
|
4284
4443
|
}) : [];
|
|
@@ -4321,11 +4480,29 @@ var Server = class {
|
|
|
4321
4480
|
});
|
|
4322
4481
|
return {
|
|
4323
4482
|
code: 200,
|
|
4324
|
-
data:
|
|
4483
|
+
data: this.buildProductQueryResultPayload(filteredProducts),
|
|
4325
4484
|
message: "",
|
|
4326
4485
|
status: true
|
|
4327
4486
|
};
|
|
4328
4487
|
}
|
|
4488
|
+
/**
|
|
4489
|
+
* 构建商品格式化上下文。
|
|
4490
|
+
*
|
|
4491
|
+
* 智能定价条件使用全量 menuList/scheduleList;Product Query 顶层参数只保留筛选商品集合所需字段。
|
|
4492
|
+
*/
|
|
4493
|
+
buildProductFormatterContext(context) {
|
|
4494
|
+
var _a, _b, _c, _d;
|
|
4495
|
+
const scheduleModule = this.getSchedule();
|
|
4496
|
+
return {
|
|
4497
|
+
scheduleModule,
|
|
4498
|
+
schedule_datetime: context.schedule_datetime,
|
|
4499
|
+
customer_id: context.customer_id,
|
|
4500
|
+
strategy_context: context.strategy_context,
|
|
4501
|
+
menuList: ((_b = (_a = this.menu) == null ? void 0 : _a.getMenuList) == null ? void 0 : _b.call(_a)) || [],
|
|
4502
|
+
scheduleList: ((_c = scheduleModule == null ? void 0 : scheduleModule.getScheduleList) == null ? void 0 : _c.call(scheduleModule)) || [],
|
|
4503
|
+
dataVariantEvaluator: (_d = this.core.context) == null ? void 0 : _d.dataVariantEvaluator
|
|
4504
|
+
};
|
|
4505
|
+
}
|
|
4329
4506
|
/**
|
|
4330
4507
|
* 数据变更后,遍历所有订阅者重新计算查询结果并通过 callback 推送
|
|
4331
4508
|
* 由 ProductsModule 的 onProductsSyncCompleted 事件触发
|