@pisell/pisellos 0.10.20 → 0.10.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. package/dist/model/strategy/adapter/promotion/index.js +0 -9
  2. package/dist/modules/BookingContext/index.d.ts +3 -0
  3. package/dist/modules/BookingContext/index.js +42 -13
  4. package/dist/modules/BookingContext/types.d.ts +4 -4
  5. package/dist/modules/BookingContext/types.js +3 -3
  6. package/dist/modules/BookingContext/utils/buildCacheItemFromOrderLine.js +10 -7
  7. package/dist/modules/OpenData/index.d.ts +10 -1
  8. package/dist/modules/OpenData/index.js +268 -14
  9. package/dist/modules/OpenData/types.d.ts +14 -1
  10. package/dist/modules/OpenData/utils.d.ts +1 -0
  11. package/dist/modules/OpenData/utils.js +8 -5
  12. package/dist/modules/Order/index.d.ts +6 -1
  13. package/dist/modules/Order/index.js +148 -84
  14. package/dist/modules/ProductList/clientDataVariants.d.ts +50 -0
  15. package/dist/modules/ProductList/clientDataVariants.js +216 -0
  16. package/dist/modules/ProductList/index.d.ts +14 -13
  17. package/dist/modules/ProductList/index.js +105 -77
  18. package/dist/modules/Quotation/index.d.ts +16 -0
  19. package/dist/modules/Quotation/index.js +240 -27
  20. package/dist/modules/ResourcePlanner/planner.d.ts +2 -2
  21. package/dist/modules/ResourcePlanner/planner.js +88 -21
  22. package/dist/modules/ResourcePlanner/types.d.ts +12 -0
  23. package/dist/modules/Rules/index.d.ts +5 -0
  24. package/dist/modules/Rules/index.js +27 -12
  25. package/dist/plugins/request.d.ts +2 -1
  26. package/dist/plugins/request.js +4 -2
  27. package/dist/server/index.js +33 -16
  28. package/dist/solution/BaseSales/index.d.ts +16 -1
  29. package/dist/solution/BaseSales/index.js +690 -349
  30. package/dist/solution/BookingByStep/index.d.ts +1 -1
  31. package/dist/solution/BookingTicket/index.d.ts +14 -2
  32. package/dist/solution/BookingTicket/index.js +25 -18
  33. package/dist/solution/Sales/index.js +29 -22
  34. package/dist/solution/UnifiedBookingSales/index.d.ts +1 -1
  35. package/dist/solution/UnifiedBookingSales/index.js +315 -99
  36. package/dist/solution/UnifiedBookingSales/types.d.ts +4 -0
  37. package/dist/solution/VenueBooking/index.d.ts +2 -0
  38. package/dist/solution/VenueBooking/index.js +542 -494
  39. package/dist/solution/VenueBooking/utils/dateSummary.d.ts +1 -0
  40. package/dist/solution/VenueBooking/utils/dateSummary.js +3 -1
  41. package/dist/solution/VenueBooking/utils/slotMerge.d.ts +2 -0
  42. package/dist/solution/VenueBooking/utils/slotMerge.js +3 -1
  43. package/dist/utils/platform.d.ts +8 -0
  44. package/dist/utils/platform.js +15 -0
  45. package/lib/modules/BookingContext/index.d.ts +3 -0
  46. package/lib/modules/BookingContext/index.js +35 -8
  47. package/lib/modules/BookingContext/types.d.ts +4 -4
  48. package/lib/modules/BookingContext/types.js +3 -3
  49. package/lib/modules/BookingContext/utils/buildCacheItemFromOrderLine.js +4 -0
  50. package/lib/modules/OpenData/index.d.ts +10 -1
  51. package/lib/modules/OpenData/index.js +162 -2
  52. package/lib/modules/OpenData/types.d.ts +14 -1
  53. package/lib/modules/OpenData/utils.d.ts +1 -0
  54. package/lib/modules/OpenData/utils.js +9 -5
  55. package/lib/modules/Order/index.d.ts +6 -1
  56. package/lib/modules/Order/index.js +65 -8
  57. package/lib/modules/ProductList/clientDataVariants.d.ts +50 -0
  58. package/lib/modules/ProductList/clientDataVariants.js +204 -0
  59. package/lib/modules/ProductList/index.d.ts +14 -13
  60. package/lib/modules/ProductList/index.js +77 -58
  61. package/lib/modules/Quotation/index.d.ts +16 -0
  62. package/lib/modules/Quotation/index.js +112 -9
  63. package/lib/modules/ResourcePlanner/planner.d.ts +2 -2
  64. package/lib/modules/ResourcePlanner/planner.js +80 -20
  65. package/lib/modules/ResourcePlanner/types.d.ts +12 -0
  66. package/lib/modules/Rules/index.d.ts +5 -0
  67. package/lib/modules/Rules/index.js +20 -11
  68. package/lib/plugins/request.d.ts +2 -1
  69. package/lib/plugins/request.js +3 -2
  70. package/lib/server/index.js +16 -1
  71. package/lib/solution/BaseSales/index.d.ts +16 -1
  72. package/lib/solution/BaseSales/index.js +314 -66
  73. package/lib/solution/BookingByStep/index.d.ts +1 -1
  74. package/lib/solution/BookingTicket/index.d.ts +14 -2
  75. package/lib/solution/BookingTicket/index.js +3 -0
  76. package/lib/solution/Sales/index.js +3 -0
  77. package/lib/solution/UnifiedBookingSales/index.d.ts +1 -1
  78. package/lib/solution/UnifiedBookingSales/index.js +228 -20
  79. package/lib/solution/UnifiedBookingSales/types.d.ts +4 -0
  80. package/lib/solution/VenueBooking/index.d.ts +2 -0
  81. package/lib/solution/VenueBooking/index.js +28 -8
  82. package/lib/solution/VenueBooking/utils/dateSummary.d.ts +1 -0
  83. package/lib/solution/VenueBooking/utils/dateSummary.js +3 -1
  84. package/lib/solution/VenueBooking/utils/slotMerge.d.ts +2 -0
  85. package/lib/solution/VenueBooking/utils/slotMerge.js +3 -1
  86. package/lib/utils/platform.d.ts +8 -0
  87. package/lib/utils/platform.js +22 -0
  88. package/package.json +1 -1
@@ -3060,6 +3060,67 @@ class OrderModule extends _BaseModule.BaseModule {
3060
3060
  commitProductLine(shouldMerge);
3061
3061
  }
3062
3062
 
3063
+ /**
3064
+ * booking 数量拆行时,若 Holder 数量与拆行数严格一致,则按下标分配。
3065
+ * 数量不一致时保持旧数据,避免猜测 Holder 与商品份数的对应关系。
3066
+ */
3067
+ createSplitBookingLine(product, booking, index, splitCount) {
3068
+ const singleLine = (0, _lodashEs.cloneDeep)(product);
3069
+ singleLine.num = 1;
3070
+ delete singleLine.product_quantity;
3071
+ const singleBooking = (0, _lodashEs.cloneDeep)(booking);
3072
+ const bookingHolder = booking?.holder && typeof booking.holder === 'object' ? booking.holder : null;
3073
+ const metadataHolder = booking?.metadata?.holder && typeof booking.metadata.holder === 'object' ? booking.metadata.holder : null;
3074
+ const rawHolderIds = bookingHolder?.form_record ?? booking?.metadata?.holder_id ?? metadataHolder?.form_record ?? product?.metadata?.holder_id;
3075
+ const holderIds = (Array.isArray(rawHolderIds) ? rawHolderIds : [rawHolderIds]).map(holderId => holderId && typeof holderId === 'object' ? holderId.form_record_id : holderId).filter(holderId => holderId !== undefined && holderId !== null && holderId !== '');
3076
+ if (holderIds.length !== splitCount) {
3077
+ return {
3078
+ product: singleLine,
3079
+ booking: singleBooking
3080
+ };
3081
+ }
3082
+ const holderId = holderIds[index];
3083
+ const rawHolderName = bookingHolder?.name ?? metadataHolder?.name;
3084
+ const holderNames = Array.isArray(rawHolderName) ? rawHolderName.map(name => String(name || '').trim()) : typeof rawHolderName === 'string' ? rawHolderName.split(',').map(name => name.trim()) : [];
3085
+ const holderName = holderNames.length === splitCount ? holderNames[index] : undefined;
3086
+ const splitHolder = holder => ({
3087
+ ...holder,
3088
+ form_record: [holderId],
3089
+ ...(holderName ? {
3090
+ name: holderName
3091
+ } : Object.prototype.hasOwnProperty.call(holder, 'name') ? {
3092
+ name: undefined
3093
+ } : {})
3094
+ });
3095
+ singleLine.metadata = {
3096
+ ...(singleLine.metadata || {}),
3097
+ holder_id: [holderId]
3098
+ };
3099
+ if (singleLine._extend && typeof singleLine._extend === 'object') {
3100
+ singleLine._extend = {
3101
+ ...singleLine._extend,
3102
+ holder_id: [holderId]
3103
+ };
3104
+ }
3105
+ if (singleBooking.holder && typeof singleBooking.holder === 'object') {
3106
+ singleBooking.holder = splitHolder(singleBooking.holder);
3107
+ }
3108
+ singleBooking.metadata = {
3109
+ ...(singleBooking.metadata || {}),
3110
+ holder_id: [holderId],
3111
+ ...(singleBooking.metadata?.holder && typeof singleBooking.metadata.holder === 'object' ? {
3112
+ holder: splitHolder(singleBooking.metadata.holder)
3113
+ } : {})
3114
+ };
3115
+ if (Object.prototype.hasOwnProperty.call(singleBooking, 'holder_id')) {
3116
+ singleBooking.holder_id = [holderId];
3117
+ }
3118
+ return {
3119
+ product: singleLine,
3120
+ booking: singleBooking
3121
+ };
3122
+ }
3123
+
3063
3124
  /**
3064
3125
  * 添加商品行。带 booking 时强制独立行;num>1 时拆成多条 num=1 的 product+booking(对齐 BookingByStep / ticketBooking addService)。
3065
3126
  *
@@ -3080,10 +3141,8 @@ class OrderModule extends _BaseModule.BaseModule {
3080
3141
  const splitCount = (0, _utils3.getSafeProductNum)(productRecord.num ?? productRecord.product_quantity ?? 1);
3081
3142
  if (splitCount > 1) {
3082
3143
  for (let i = 0; i < splitCount; i += 1) {
3083
- const singleLine = (0, _lodashEs.cloneDeep)(productRecord);
3084
- singleLine.num = 1;
3085
- delete singleLine.product_quantity;
3086
- const appendResult = this.appendProductLineToTempOrder(tempOrder, singleLine, (0, _lodashEs.cloneDeep)(booking), {
3144
+ const splitLine = this.createSplitBookingLine(productRecord, booking, i, splitCount);
3145
+ const appendResult = this.appendProductLineToTempOrder(tempOrder, splitLine.product, splitLine.booking, {
3087
3146
  insertAtIndex,
3088
3147
  disableMerge: options?.disableMerge
3089
3148
  });
@@ -3130,10 +3189,8 @@ class OrderModule extends _BaseModule.BaseModule {
3130
3189
  const splitCount = (0, _utils3.getSafeProductNum)(productRecord.num ?? productRecord.product_quantity ?? 1);
3131
3190
  if (splitCount > 1) {
3132
3191
  for (let i = 0; i < splitCount; i += 1) {
3133
- const singleLine = (0, _lodashEs.cloneDeep)(productRecord);
3134
- singleLine.num = 1;
3135
- delete singleLine.product_quantity;
3136
- const appendResult = this.appendProductLineToTempOrder(tempOrder, singleLine, (0, _lodashEs.cloneDeep)(booking));
3192
+ const splitLine = this.createSplitBookingLine(productRecord, booking, i, splitCount);
3193
+ const appendResult = this.appendProductLineToTempOrder(tempOrder, splitLine.product, splitLine.booking);
3137
3194
  if (appendResult) await appendResult;
3138
3195
  }
3139
3196
  continue;
@@ -0,0 +1,50 @@
1
+ import type { PisellCore } from '../../types';
2
+ import type { RequestPlugin } from '../../plugins';
3
+ import type { ProductData } from '../Product/types';
4
+ export declare const PRODUCT_QUERY_DATA_VARIANT_RELATIONS: readonly ["category", "collection", "resourceRelation", "bundleGroup.bundleItem", "optionGroup.optionItem", "variantGroup.variantItem", "dataVariants"];
5
+ /**
6
+ * 判断当前商品查询是否会被本地 OS Server 接管。
7
+ *
8
+ * H5 RequestPlugin 即使收到 osServer=true 仍会直接请求远端接口,因此必须按
9
+ * RequestPlugin 实际 baseUrl 对应的已注册路由判断,不能只看请求选项。
10
+ */
11
+ export declare function isProductQueryHandledByOsServer(core: PisellCore | undefined, request: RequestPlugin | undefined): boolean;
12
+ export interface ResolveClientDataVariantsParams {
13
+ core: PisellCore | undefined;
14
+ otherParams?: Record<string, any>;
15
+ products: ProductData[];
16
+ queryPayload: Record<string, any>;
17
+ handledByOsServer: boolean;
18
+ scheduleList?: unknown;
19
+ menuList?: unknown;
20
+ /** Quote fast paths require hydrated relations; legacy list resolution does not. */
21
+ requireCompleteDataVariantRelations?: boolean;
22
+ /** Quote fast paths require every referenced rule; legacy list resolution is best-effort. */
23
+ requireEvaluatorReady?: boolean;
24
+ }
25
+ export type ResolveClientDataVariantsResult = {
26
+ status: 'handled-by-os-server' | 'empty' | 'data-variants-unavailable' | 'evaluator-unavailable' | 'evaluator-not-ready';
27
+ products: ProductData[];
28
+ } | {
29
+ status: 'resolved';
30
+ products: ProductData[];
31
+ } | {
32
+ status: 'error';
33
+ products: ProductData[];
34
+ error: unknown;
35
+ };
36
+ /**
37
+ * Strict client-side Data Variant resolution for quote fast paths.
38
+ *
39
+ * Unlike resolveClientDataVariants, callers can distinguish a complete local
40
+ * result from a capability/readiness/error fallback and decide to query the
41
+ * remote product endpoint instead.
42
+ */
43
+ export declare function tryResolveClientDataVariants({ core, otherParams, products, queryPayload, handledByOsServer, scheduleList: explicitScheduleList, menuList: explicitMenuList, requireCompleteDataVariantRelations, requireEvaluatorReady, }: ResolveClientDataVariantsParams): ResolveClientDataVariantsResult;
44
+ /**
45
+ * 未启用商品 OS 路由时,在客户端补做 Data Variant 解析。
46
+ *
47
+ * 商品列表和预约报价必须共用这个边界,避免 H5 的弹窗报价绕过客户端智能定价。
48
+ * OS Server 已接管时直接信任服务端结果,确保同一批商品只评估一次。
49
+ */
50
+ export declare function resolveClientDataVariants({ core, otherParams, products, queryPayload, handledByOsServer, scheduleList: explicitScheduleList, menuList: explicitMenuList, }: ResolveClientDataVariantsParams): ProductData[];
@@ -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
- open_quotation: 1,
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: ['category', 'collection', 'resourceRelation', 'bundleGroup.bundleItem', 'optionGroup.optionItem', 'variantGroup.variantItem', 'dataVariants'],
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 = this.isProductQueryHandledByOsServer();
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
- const resolvedCallbackList = this.resolveClientDataVariants(callbackList, queryPayload, handledByOsServer);
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
- const resolvedList = this.resolveClientDataVariants(productsData.data.list || [], queryPayload, handledByOsServer);
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;