@pisell/pisellos 2.2.291 → 2.2.293

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 (174) hide show
  1. package/dist/core/index.d.ts +7 -0
  2. package/dist/core/index.js +108 -65
  3. package/dist/model/strategy/adapter/itemRule/adapter.d.ts +4 -1
  4. package/dist/model/strategy/adapter/itemRule/adapter.js +135 -35
  5. package/dist/model/strategy/adapter/itemRule/evaluator.d.ts +8 -1
  6. package/dist/model/strategy/adapter/itemRule/evaluator.js +25 -1
  7. package/dist/model/strategy/adapter/itemRule/type.d.ts +44 -0
  8. package/dist/model/strategy/adapter/itemRule/type.js +19 -1
  9. package/dist/model/strategy/adapter/promotion/index.js +0 -9
  10. package/dist/modules/BookingContext/index.d.ts +3 -0
  11. package/dist/modules/BookingContext/index.js +42 -13
  12. package/dist/modules/BookingContext/types.d.ts +4 -4
  13. package/dist/modules/BookingContext/types.js +3 -3
  14. package/dist/modules/OpenData/types.d.ts +1 -0
  15. package/dist/modules/Order/index.d.ts +18 -3
  16. package/dist/modules/Order/index.js +885 -485
  17. package/dist/modules/Order/salesNotes.d.ts +31 -0
  18. package/dist/modules/Order/salesNotes.js +492 -0
  19. package/dist/modules/Order/types.d.ts +93 -5
  20. package/dist/modules/Order/types.js +5 -0
  21. package/dist/modules/Order/utils.d.ts +12 -0
  22. package/dist/modules/Order/utils.js +96 -31
  23. package/dist/modules/Product/types.d.ts +2 -0
  24. package/dist/modules/ProductList/clientDataVariants.d.ts +50 -0
  25. package/dist/modules/ProductList/clientDataVariants.js +216 -0
  26. package/dist/modules/ProductList/index.d.ts +18 -3
  27. package/dist/modules/ProductList/index.js +220 -38
  28. package/dist/modules/ProductList/types.d.ts +10 -0
  29. package/dist/modules/Quotation/index.d.ts +22 -0
  30. package/dist/modules/Quotation/index.js +293 -28
  31. package/dist/modules/ResourcePlanner/index.d.ts +0 -22
  32. package/dist/modules/ResourcePlanner/index.js +1 -180
  33. package/dist/modules/ResourcePlanner/localClock.d.ts +33 -0
  34. package/dist/modules/ResourcePlanner/localClock.js +183 -0
  35. package/dist/modules/ResourcePlanner/planner.d.ts +5 -14
  36. package/dist/modules/ResourcePlanner/planner.js +2111 -958
  37. package/dist/modules/ResourcePlanner/types.d.ts +359 -187
  38. package/dist/modules/ResourcePlanner/types.js +33 -1
  39. package/dist/plugins/request.d.ts +1 -0
  40. package/dist/server/index.d.ts +17 -2
  41. package/dist/server/index.js +1134 -932
  42. package/dist/server/modules/floor-plan/index.d.ts +4 -0
  43. package/dist/server/modules/floor-plan/index.js +240 -98
  44. package/dist/server/modules/menu/index.js +48 -23
  45. package/dist/server/modules/order/index.d.ts +17 -7
  46. package/dist/server/modules/order/index.js +419 -207
  47. package/dist/server/modules/products/index.d.ts +10 -4
  48. package/dist/server/modules/products/index.js +177 -84
  49. package/dist/server/modules/resource/index.js +21 -13
  50. package/dist/server/utils/product.d.ts +1 -1
  51. package/dist/server/utils/product.js +186 -22
  52. package/dist/server/utils/small-ticket.d.ts +9 -1
  53. package/dist/server/utils/small-ticket.js +131 -84
  54. package/dist/solution/BaseSales/index.d.ts +26 -3
  55. package/dist/solution/BaseSales/index.js +1384 -905
  56. package/dist/solution/BaseSales/types.d.ts +2 -0
  57. package/dist/solution/BaseSales/utils/transformBaseProductToOrderProduct.d.ts +3 -0
  58. package/dist/solution/BaseSales/utils/transformBaseProductToOrderProduct.js +10 -3
  59. package/dist/solution/BaseSales/utils.js +6 -3
  60. package/dist/solution/BookingByStep/index.d.ts +4 -4
  61. package/dist/solution/BookingTicket/index.d.ts +1 -1
  62. package/dist/solution/BookingTicket/index.js +4 -2
  63. package/dist/solution/BookingTicket/utils/addProductDecision.d.ts +2 -1
  64. package/dist/solution/BookingTicket/utils/addProductDecision.js +6 -3
  65. package/dist/solution/BookingTicket/utils/orderCustomer.js +3 -2
  66. package/dist/solution/RegisterAndLogin/config.d.ts +9 -3
  67. package/dist/solution/RegisterAndLogin/config.js +95 -40
  68. package/dist/solution/RegisterAndLogin/index.js +4 -1
  69. package/dist/solution/ScanOrder/index.d.ts +1 -0
  70. package/dist/solution/ScanOrder/index.js +31 -27
  71. package/dist/solution/ScanOrder/types.d.ts +2 -0
  72. package/dist/solution/ScanOrder/utils.d.ts +1 -0
  73. package/dist/solution/ScanOrder/utils.js +8 -3
  74. package/dist/solution/UnifiedBookingSales/index.d.ts +128 -74
  75. package/dist/solution/UnifiedBookingSales/index.js +3877 -963
  76. package/dist/solution/UnifiedBookingSales/remoteOccupancyCache.d.ts +49 -0
  77. package/dist/solution/UnifiedBookingSales/remoteOccupancyCache.js +430 -0
  78. package/dist/solution/UnifiedBookingSales/types.d.ts +161 -77
  79. package/dist/solution/UnifiedBookingSales/types.js +4 -9
  80. package/dist/solution/VenueBooking/index.d.ts +3 -0
  81. package/dist/solution/VenueBooking/index.js +549 -497
  82. package/dist/solution/VenueBooking/utils/dateSummary.d.ts +1 -0
  83. package/dist/solution/VenueBooking/utils/dateSummary.js +3 -1
  84. package/dist/solution/VenueBooking/utils/slotMerge.d.ts +2 -0
  85. package/dist/solution/VenueBooking/utils/slotMerge.js +3 -1
  86. package/dist/utils/platform.d.ts +8 -0
  87. package/dist/utils/platform.js +15 -0
  88. package/lib/core/index.d.ts +7 -0
  89. package/lib/core/index.js +20 -2
  90. package/lib/model/strategy/adapter/itemRule/adapter.d.ts +4 -1
  91. package/lib/model/strategy/adapter/itemRule/adapter.js +73 -4
  92. package/lib/model/strategy/adapter/itemRule/evaluator.d.ts +8 -1
  93. package/lib/model/strategy/adapter/itemRule/evaluator.js +23 -1
  94. package/lib/model/strategy/adapter/itemRule/type.d.ts +44 -0
  95. package/lib/model/strategy/adapter/itemRule/type.js +19 -1
  96. package/lib/modules/BookingContext/index.d.ts +3 -0
  97. package/lib/modules/BookingContext/index.js +35 -8
  98. package/lib/modules/BookingContext/types.d.ts +4 -4
  99. package/lib/modules/BookingContext/types.js +3 -3
  100. package/lib/modules/OpenData/types.d.ts +1 -0
  101. package/lib/modules/Order/index.d.ts +18 -3
  102. package/lib/modules/Order/index.js +325 -28
  103. package/lib/modules/Order/salesNotes.d.ts +31 -0
  104. package/lib/modules/Order/salesNotes.js +382 -0
  105. package/lib/modules/Order/types.d.ts +93 -5
  106. package/lib/modules/Order/types.js +4 -0
  107. package/lib/modules/Order/utils.d.ts +12 -0
  108. package/lib/modules/Order/utils.js +85 -16
  109. package/lib/modules/Product/types.d.ts +2 -0
  110. package/lib/modules/ProductList/clientDataVariants.d.ts +50 -0
  111. package/lib/modules/ProductList/clientDataVariants.js +204 -0
  112. package/lib/modules/ProductList/index.d.ts +18 -3
  113. package/lib/modules/ProductList/index.js +137 -8
  114. package/lib/modules/ProductList/types.d.ts +10 -0
  115. package/lib/modules/Quotation/index.d.ts +22 -0
  116. package/lib/modules/Quotation/index.js +158 -11
  117. package/lib/modules/ResourcePlanner/index.d.ts +0 -22
  118. package/lib/modules/ResourcePlanner/index.js +9 -129
  119. package/lib/modules/ResourcePlanner/localClock.d.ts +33 -0
  120. package/lib/modules/ResourcePlanner/localClock.js +205 -0
  121. package/lib/modules/ResourcePlanner/planner.d.ts +5 -14
  122. package/lib/modules/ResourcePlanner/planner.js +1807 -783
  123. package/lib/modules/ResourcePlanner/types.d.ts +359 -187
  124. package/lib/modules/ResourcePlanner/types.js +13 -1
  125. package/lib/plugins/request.d.ts +1 -0
  126. package/lib/server/index.d.ts +17 -2
  127. package/lib/server/index.js +149 -21
  128. package/lib/server/modules/floor-plan/index.d.ts +4 -0
  129. package/lib/server/modules/floor-plan/index.js +60 -7
  130. package/lib/server/modules/menu/index.js +26 -4
  131. package/lib/server/modules/order/index.d.ts +17 -7
  132. package/lib/server/modules/order/index.js +194 -57
  133. package/lib/server/modules/products/index.d.ts +10 -4
  134. package/lib/server/modules/products/index.js +93 -23
  135. package/lib/server/modules/resource/index.js +12 -2
  136. package/lib/server/utils/product.d.ts +1 -1
  137. package/lib/server/utils/product.js +163 -3
  138. package/lib/server/utils/small-ticket.d.ts +9 -1
  139. package/lib/server/utils/small-ticket.js +123 -79
  140. package/lib/solution/BaseSales/index.d.ts +26 -3
  141. package/lib/solution/BaseSales/index.js +396 -70
  142. package/lib/solution/BaseSales/types.d.ts +2 -0
  143. package/lib/solution/BaseSales/utils/transformBaseProductToOrderProduct.d.ts +3 -0
  144. package/lib/solution/BaseSales/utils/transformBaseProductToOrderProduct.js +9 -2
  145. package/lib/solution/BaseSales/utils.js +3 -0
  146. package/lib/solution/BookingByStep/index.d.ts +4 -4
  147. package/lib/solution/BookingTicket/index.d.ts +1 -1
  148. package/lib/solution/BookingTicket/index.js +2 -1
  149. package/lib/solution/BookingTicket/utils/addProductDecision.d.ts +2 -1
  150. package/lib/solution/BookingTicket/utils/addProductDecision.js +5 -2
  151. package/lib/solution/BookingTicket/utils/orderCustomer.js +2 -1
  152. package/lib/solution/RegisterAndLogin/config.d.ts +9 -3
  153. package/lib/solution/RegisterAndLogin/config.js +49 -8
  154. package/lib/solution/RegisterAndLogin/index.js +4 -1
  155. package/lib/solution/ScanOrder/index.d.ts +1 -0
  156. package/lib/solution/ScanOrder/index.js +5 -1
  157. package/lib/solution/ScanOrder/types.d.ts +2 -0
  158. package/lib/solution/ScanOrder/utils.d.ts +1 -0
  159. package/lib/solution/ScanOrder/utils.js +4 -0
  160. package/lib/solution/UnifiedBookingSales/index.d.ts +128 -74
  161. package/lib/solution/UnifiedBookingSales/index.js +2592 -424
  162. package/lib/solution/UnifiedBookingSales/remoteOccupancyCache.d.ts +49 -0
  163. package/lib/solution/UnifiedBookingSales/remoteOccupancyCache.js +316 -0
  164. package/lib/solution/UnifiedBookingSales/types.d.ts +161 -77
  165. package/lib/solution/UnifiedBookingSales/types.js +4 -6
  166. package/lib/solution/VenueBooking/index.d.ts +3 -0
  167. package/lib/solution/VenueBooking/index.js +33 -9
  168. package/lib/solution/VenueBooking/utils/dateSummary.d.ts +1 -0
  169. package/lib/solution/VenueBooking/utils/dateSummary.js +3 -1
  170. package/lib/solution/VenueBooking/utils/slotMerge.d.ts +2 -0
  171. package/lib/solution/VenueBooking/utils/slotMerge.js +3 -1
  172. package/lib/utils/platform.d.ts +8 -0
  173. package/lib/utils/platform.js +22 -0
  174. package/package.json +1 -1
@@ -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;
@@ -23,12 +25,22 @@ Object.keys(_types).forEach(function (key) {
23
25
  });
24
26
  });
25
27
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
28
+ function normalizeQuerySet(values) {
29
+ if (!Array.isArray(values) || values.length === 0) return null;
30
+ const normalized = values.map(value => String(value ?? '').trim()).filter(Boolean);
31
+ return normalized.length ? new Set(normalized) : null;
32
+ }
33
+ function matchesAny(values, querySet) {
34
+ if (!querySet) return true;
35
+ return values.some(value => querySet.has(String(value ?? '').trim()));
36
+ }
26
37
  class ProductList extends _BaseModule.BaseModule {
27
38
  defaultName = 'productList';
28
39
  defaultVersion = '1.0.0';
29
40
  store;
30
41
  request;
31
42
  otherParams = {};
43
+ rawProductPriceSources = new Map();
32
44
  constructor(name, version) {
33
45
  super(name, version);
34
46
  }
@@ -36,6 +48,7 @@ class ProductList extends _BaseModule.BaseModule {
36
48
  this.core = core;
37
49
  this.store = options.store;
38
50
  this.otherParams = options.otherParams || {};
51
+ this.rawProductPriceSources.clear();
39
52
  if (Array.isArray(options.initialState?.list)) {
40
53
  this.store.list = options.initialState.list.slice().sort((a, b) => Number(b.sort) - Number(a.sort));
41
54
  this.core.effects.emit(`${this.name}:changed`, this.store.list);
@@ -118,10 +131,12 @@ class ProductList extends _BaseModule.BaseModule {
118
131
  const rawApplicationCode = this.otherParams?.channel;
119
132
  const mappedApplicationCode = typeof rawApplicationCode === 'string' ? channelMap[rawApplicationCode] || rawApplicationCode : rawApplicationCode;
120
133
  const queryPayload = {
121
- open_quotation: 1,
134
+ ...((0, _platform.isCustomerUserPlatform)(this.otherParams?.platform) ? {} : {
135
+ open_quotation: 1
136
+ }),
122
137
  open_bundle: 0,
123
138
  exclude_extension_type: ['product_party', 'product_event', 'product_series_event', 'product_package_ticket', 'ticket', 'event_item'],
124
- with: ['category', 'collection', 'resourceRelation', 'bundleGroup.bundleItem', 'optionGroup.optionItem', 'variantGroup.variantItem', 'dataVariants'],
139
+ with: [..._clientDataVariants.PRODUCT_QUERY_DATA_VARIANT_RELATIONS],
125
140
  status,
126
141
  num: 500,
127
142
  skip: 1,
@@ -149,11 +164,34 @@ class ProductList extends _BaseModule.BaseModule {
149
164
  extension_type,
150
165
  strategy_context
151
166
  };
167
+ const handledByOsServer = (0, _clientDataVariants.isProductQueryHandledByOsServer)(this.core, this.request);
152
168
  const originalCallback = options?.callback;
153
- const subscriptionCallback = originalCallback ? async result => {
154
- const products = Array.isArray(result?.data?.list) ? result.data.list : [];
169
+ const subscriptionCallback = typeof originalCallback === 'function' ? async result => {
170
+ const callbackList = result?.data?.list;
171
+ if (!Array.isArray(callbackList)) {
172
+ originalCallback(result);
173
+ return;
174
+ }
175
+ if (!handledByOsServer) {
176
+ // Subscription callbacks may contain only changed products. Upsert
177
+ // their raw records without evicting other IDs from the scope.
178
+ this.rememberRawProductQueryResult(callbackList);
179
+ }
180
+ const products = (0, _clientDataVariants.resolveClientDataVariants)({
181
+ core: this.core,
182
+ otherParams: this.otherParams,
183
+ products: callbackList,
184
+ queryPayload,
185
+ handledByOsServer
186
+ });
155
187
  await this.addProduct(products);
156
- originalCallback(result);
188
+ originalCallback(handledByOsServer ? result : {
189
+ ...result,
190
+ data: {
191
+ ...result.data,
192
+ list: products
193
+ }
194
+ });
157
195
  } : undefined;
158
196
  const productsData = await this.request.post(`/product/query`, queryPayload, {
159
197
  osServer: true,
@@ -161,7 +199,17 @@ class ProductList extends _BaseModule.BaseModule {
161
199
  subscriberId: options?.subscriberId,
162
200
  customToast: () => {}
163
201
  });
164
- const sortedList = (productsData.data.list || []).slice().sort((a, b) => Number(b.sort) - Number(a.sort));
202
+ if (!handledByOsServer && Array.isArray(productsData.data.list)) {
203
+ this.replaceRawProductQueryResult(productsData.data.list, product_ids);
204
+ }
205
+ const resolvedList = (0, _clientDataVariants.resolveClientDataVariants)({
206
+ core: this.core,
207
+ otherParams: this.otherParams,
208
+ products: productsData.data.list || [],
209
+ queryPayload,
210
+ handledByOsServer
211
+ });
212
+ const sortedList = resolvedList.slice().sort((a, b) => Number(b.sort) - Number(a.sort));
165
213
  // if (sortedList.length) {
166
214
  // sortedList.forEach((n: any) => {
167
215
  // if (n.is_eject !== 1 && n['schedule.ids'] && n['schedule.ids'].length) {
@@ -188,6 +236,51 @@ class ProductList extends _BaseModule.BaseModule {
188
236
  });
189
237
  return productsData.data;
190
238
  }
239
+
240
+ /**
241
+ * Remember raw remote product records before client-side Data Variant
242
+ * evaluation. Quote-only reads can then always start from a clean baseline.
243
+ */
244
+ rememberRawProductQueryResult(products) {
245
+ if (!Array.isArray(products)) return;
246
+ products.forEach(product => {
247
+ const source = product;
248
+ const id = Number(source?.id ?? source?.product_id);
249
+ if (!Number.isFinite(id)) return;
250
+ this.rawProductPriceSources.set(id, (0, _lodashEs.cloneDeep)(product));
251
+ });
252
+ }
253
+ replaceRawProductQueryResult(products, requestedIds) {
254
+ if (Array.isArray(requestedIds) && requestedIds.length > 0) {
255
+ requestedIds.forEach(rawId => {
256
+ const id = Number(rawId);
257
+ if (Number.isFinite(id)) this.rawProductPriceSources.delete(id);
258
+ });
259
+ } else {
260
+ // A non-ID query represents the latest catalog scope. Clearing before
261
+ // replacing prevents removed products from surviving indefinitely.
262
+ this.rawProductPriceSources.clear();
263
+ }
264
+ this.rememberRawProductQueryResult(products);
265
+ }
266
+
267
+ /**
268
+ * Return isolated raw price sources for the requested products.
269
+ *
270
+ * Missing IDs are intentionally omitted so callers can remotely fetch only
271
+ * the incomplete part of a batch quote.
272
+ */
273
+ getRawProductPriceSources(ids) {
274
+ const products = new Map();
275
+ if (!Array.isArray(ids)) return products;
276
+ ids.forEach(rawId => {
277
+ const id = Number(rawId);
278
+ if (!Number.isFinite(id) || products.has(id)) return;
279
+ const product = this.rawProductPriceSources.get(id);
280
+ if (product) products.set(id, (0, _lodashEs.cloneDeep)(product));
281
+ });
282
+ return products;
283
+ }
191
284
  async getProducts() {
192
285
  await this.core.effects.emit(`${this.name}:onGetProducts`, this.store.list);
193
286
  return (0, _lodashEs.cloneDeep)(this.store.list);
@@ -197,10 +290,46 @@ class ProductList extends _BaseModule.BaseModule {
197
290
  const product = this.store.list.find(product => product.id === id);
198
291
  return product ? (0, _lodashEs.cloneDeep)(product) : undefined;
199
292
  }
293
+ async queryProducts(params = {}) {
294
+ const includeIds = normalizeQuerySet(params.includeIds);
295
+ const excludeIds = normalizeQuerySet(params.excludeIds);
296
+ const categoryIds = normalizeQuerySet(params.categoryIds);
297
+ const collectionIds = normalizeQuerySet(params.collectionIds);
298
+ const extensionTypes = normalizeQuerySet(params.extensionTypes);
299
+ const statuses = normalizeQuerySet(params.statuses);
300
+ const channelApplications = normalizeQuerySet(params.channelApplications);
301
+ const codeOrBarcode = typeof params.codeOrBarcode === 'string' ? params.codeOrBarcode.trim() : '';
302
+ const products = (this.store.list || []).filter(product => {
303
+ const productId = String(product.id ?? '').trim();
304
+ if (includeIds && !includeIds.has(productId)) return false;
305
+ if (excludeIds && excludeIds.has(productId)) return false;
306
+ if (categoryIds) {
307
+ const productCategoryIds = (product.categories || []).flatMap(category => [category?.id, category?.pivot?.product_category_id]);
308
+ if (!matchesAny(productCategoryIds, categoryIds)) return false;
309
+ }
310
+ if (collectionIds) {
311
+ const productCollectionIds = (product.collection || []).map(collection => collection?.id);
312
+ if (!matchesAny(productCollectionIds, collectionIds)) return false;
313
+ }
314
+ if (extensionTypes && !extensionTypes.has(String(product.extension_type ?? '').trim())) return false;
315
+ if (statuses && !statuses.has(String(product.status ?? '').trim())) return false;
316
+ if (channelApplications) {
317
+ if (!matchesAny(product.channel_application || [], channelApplications)) return false;
318
+ }
319
+ if (codeOrBarcode) {
320
+ const code = typeof product.code === 'string' ? product.code.trim() : '';
321
+ const barcode = typeof product.barcode === 'string' ? product.barcode.trim() : '';
322
+ if (code !== codeOrBarcode && barcode !== codeOrBarcode) return false;
323
+ }
324
+ return true;
325
+ });
326
+ return (0, _lodashEs.cloneDeep)(products);
327
+ }
200
328
  async getProductByIds(ids) {
201
329
  await this.core.effects.emit(`${this.name}:onGetProductByIds`, this.store.list);
202
- const products = this.store.list.filter(product => ids.includes(product.id));
203
- return products;
330
+ return this.queryProducts({
331
+ includeIds: ids
332
+ });
204
333
  }
205
334
  async addProduct(products) {
206
335
  // list 需要根据 id 去重
@@ -15,6 +15,16 @@ export interface ProductListLoadProductsParams {
15
15
  /** 智能定价条件上下文,仅影响策略命中,不参与商品集合筛选 */
16
16
  strategy_context?: Record<string, any>;
17
17
  }
18
+ export interface ProductListStoreQueryParams {
19
+ includeIds?: Array<number | string>;
20
+ excludeIds?: Array<number | string>;
21
+ categoryIds?: Array<number | string>;
22
+ collectionIds?: Array<number | string>;
23
+ extensionTypes?: string[];
24
+ statuses?: ProductData['status'][];
25
+ channelApplications?: string[];
26
+ codeOrBarcode?: string;
27
+ }
18
28
  export interface ProductListData {
19
29
  list: ProductData[];
20
30
  selectProducts: ProductData[];
@@ -9,13 +9,27 @@ export declare class QuotationModule extends BaseModule implements Module {
9
9
  private request;
10
10
  private store;
11
11
  private scheduleResolver?;
12
+ private app;
13
+ private quotationListUpdatedListener?;
14
+ private quotationCache;
15
+ private quotationLoadPromises;
16
+ private quotationCacheVersions;
17
+ private lifecycleGeneration;
18
+ private activeChannelKey;
12
19
  constructor(name?: string, version?: string);
13
20
  initialize(core: PisellCore, options: ModuleOptions): Promise<void>;
14
21
  loadQuotations(params?: {
15
22
  channel?: string;
16
23
  customer_id?: number | string;
17
24
  }): Promise<void>;
25
+ ensureQuotations(params?: {
26
+ channel?: string;
27
+ customer_id?: number | string;
28
+ }): Promise<void>;
29
+ invalidateQuotationCache(channel?: string): void;
30
+ private fetchQuotations;
18
31
  getQuotationList(): QuotationItem[];
32
+ setQuotationListUpdatedListener(listener?: () => void): void;
19
33
  getQuotationCustomerScopeInfo(): QuotationCustomerScopeInfo;
20
34
  /**
21
35
  * Look up the quotation price for a specific product (+ optional variant) at a given datetime.
@@ -57,9 +71,17 @@ export declare class QuotationModule extends BaseModule implements Module {
57
71
  buildProductPriceMap(params: {
58
72
  productIds: number[];
59
73
  timePoints: string[];
74
+ channel?: string;
60
75
  customer_id?: number | string;
61
76
  }): Map<string, string | null>;
62
77
  setScheduleResolver(resolver: (id: number) => ScheduleItem | undefined): void;
78
+ private applyQuotationResponse;
79
+ private notifyQuotationListUpdated;
80
+ private getQuotationCacheKeyData;
81
+ private normalizeChannel;
82
+ private activateChannel;
83
+ private getQuotationCacheVersion;
84
+ private bumpQuotationCacheVersion;
63
85
  private isQuotationVisibleForCustomer;
64
86
  private hasValidCustomerId;
65
87
  private normalizeCustomerId;
@@ -14,6 +14,13 @@ class QuotationModule extends _BaseModule.BaseModule {
14
14
  list: []
15
15
  };
16
16
  scheduleResolver;
17
+ app;
18
+ quotationListUpdatedListener;
19
+ quotationCache = new Map();
20
+ quotationLoadPromises = new Map();
21
+ quotationCacheVersions = new Map();
22
+ lifecycleGeneration = 0;
23
+ activeChannelKey = '';
17
24
  constructor(name, version) {
18
25
  super(name, version);
19
26
  }
@@ -21,27 +28,117 @@ class QuotationModule extends _BaseModule.BaseModule {
21
28
  this.core = core;
22
29
  this.request = core.getPlugin('request');
23
30
  if (!this.request) throw new Error('QuotationModule 需要 request 插件支持');
31
+ this.app = core.getPlugin('app')?.getApp?.();
24
32
  this.store = {
25
33
  list: []
26
34
  };
35
+ this.quotationCache.clear();
36
+ this.quotationLoadPromises.clear();
37
+ this.quotationCacheVersions.clear();
38
+ this.activeChannelKey = '';
39
+ this.lifecycleGeneration += 1;
27
40
  }
28
41
  async loadQuotations(params) {
29
- const query = {};
30
- if (params?.channel) query.channel = params.channel;
31
- if (params?.channel === 'online_store') {
32
- query.channel = 'online-store';
42
+ const channelKey = this.normalizeChannel(params?.channel);
43
+ const cacheVersion = this.bumpQuotationCacheVersion(channelKey);
44
+ const lifecycleGeneration = this.lifecycleGeneration;
45
+ this.activateChannel(channelKey);
46
+ const loadPromise = this.fetchQuotations({
47
+ channelKey,
48
+ cacheVersion,
49
+ lifecycleGeneration
50
+ });
51
+ this.quotationLoadPromises.set(channelKey, loadPromise);
52
+ try {
53
+ await loadPromise;
54
+ } finally {
55
+ if (this.quotationLoadPromises.get(channelKey) === loadPromise) {
56
+ this.quotationLoadPromises.delete(channelKey);
57
+ }
33
58
  }
59
+ }
60
+ async ensureQuotations(params) {
61
+ const channelKey = this.normalizeChannel(params?.channel);
62
+ const lifecycleGeneration = this.lifecycleGeneration;
63
+ while (true) {
64
+ if (this.lifecycleGeneration !== lifecycleGeneration) return;
65
+ if (this.quotationCache.has(channelKey)) {
66
+ this.activateChannel(channelKey);
67
+ return;
68
+ }
69
+ const pending = this.quotationLoadPromises.get(channelKey);
70
+ if (!pending) {
71
+ try {
72
+ await this.loadQuotations(params);
73
+ } catch (error) {
74
+ if (this.quotationCache.has(channelKey)) {
75
+ this.activateChannel(channelKey);
76
+ return;
77
+ }
78
+ if (this.quotationLoadPromises.has(channelKey)) continue;
79
+ throw error;
80
+ }
81
+ continue;
82
+ }
83
+ const cacheVersion = this.getQuotationCacheVersion(channelKey);
84
+ try {
85
+ await pending;
86
+ } catch (error) {
87
+ const latestPending = this.quotationLoadPromises.get(channelKey);
88
+ if (latestPending && latestPending !== pending) continue;
89
+ if (this.getQuotationCacheVersion(channelKey) !== cacheVersion) continue;
90
+ throw error;
91
+ }
92
+ if (this.quotationCache.has(channelKey)) {
93
+ this.activateChannel(channelKey);
94
+ return;
95
+ }
96
+ const latestPending = this.quotationLoadPromises.get(channelKey);
97
+ if (latestPending && latestPending !== pending) continue;
98
+ }
99
+ }
100
+ invalidateQuotationCache(channel) {
101
+ if (channel !== undefined) {
102
+ const channelKey = this.normalizeChannel(channel);
103
+ this.bumpQuotationCacheVersion(channelKey);
104
+ this.quotationCache.delete(channelKey);
105
+ this.quotationLoadPromises.delete(channelKey);
106
+ if (this.activeChannelKey === channelKey) this.store.list = [];
107
+ return;
108
+ }
109
+ const channelKeys = new Set([...this.quotationCache.keys(), ...this.quotationLoadPromises.keys(), ...this.quotationCacheVersions.keys(), this.activeChannelKey]);
110
+ channelKeys.forEach(channelKey => this.bumpQuotationCacheVersion(channelKey));
111
+ this.quotationCache.clear();
112
+ this.quotationLoadPromises.clear();
113
+ this.store.list = [];
114
+ }
115
+ async fetchQuotations(params) {
116
+ const query = {};
117
+ if (params.channelKey) query.channel = params.channelKey;
118
+ let initialResultApplied = false;
119
+ const applyRemoteUpdate = response => {
120
+ const applied = this.applyQuotationResponse(response, params);
121
+ if (applied && initialResultApplied && this.activeChannelKey === params.channelKey) {
122
+ this.notifyQuotationListUpdated();
123
+ }
124
+ };
34
125
  const res = await this.request.get('/quotation/available', query, {
35
- useCache: false,
36
- osServer: true
126
+ cache: {
127
+ type: 'indexDB',
128
+ updateCache: true,
129
+ cacheKeyData: this.getQuotationCacheKeyData(query)
130
+ },
131
+ cacheUpdateChange: applyRemoteUpdate
37
132
  });
38
- const list = res?.data?.list || res?.list || [];
39
- list.sort((a, b) => b.sort - a.sort);
40
- this.store.list = list;
133
+ this.applyQuotationResponse(res, params);
134
+ initialResultApplied = true;
41
135
  }
42
136
  getQuotationList() {
43
137
  return this.store.list;
44
138
  }
139
+ setQuotationListUpdatedListener(listener) {
140
+ this.quotationListUpdatedListener = listener;
141
+ }
45
142
  getQuotationCustomerScopeInfo() {
46
143
  const customerById = new Map();
47
144
  const quotationScopes = this.store.list.filter(quotation => Array.isArray(quotation.customer) && quotation.customer.length > 0).map(quotation => {
@@ -93,7 +190,8 @@ class QuotationModule extends _BaseModule.BaseModule {
93
190
  datetime,
94
191
  customer_id
95
192
  } = params;
96
- for (const quotation of this.store.list) {
193
+ const quotations = params.channel === undefined ? this.store.list : this.quotationCache.get(this.normalizeChannel(params.channel)) || [];
194
+ for (const quotation of quotations) {
97
195
  const visible = this.isQuotationVisibleForCustomer(quotation, customer_id);
98
196
  if (!visible) continue;
99
197
  const active = this.isQuotationActiveAt(quotation, datetime);
@@ -119,13 +217,15 @@ class QuotationModule extends _BaseModule.BaseModule {
119
217
  */
120
218
  buildProductPriceMap(params) {
121
219
  const map = new Map();
122
- if (!this.store.list.length) return map;
220
+ const quotations = params.channel === undefined ? this.store.list : this.quotationCache.get(this.normalizeChannel(params.channel)) || [];
221
+ if (!quotations.length) return map;
123
222
  for (const productId of params.productIds) {
124
223
  for (const timePoint of params.timePoints) {
125
224
  const key = `${productId}:${timePoint}`;
126
225
  const price = this.getPriceForProduct({
127
226
  productId,
128
227
  datetime: timePoint,
228
+ channel: params.channel,
129
229
  customer_id: params.customer_id
130
230
  });
131
231
  if (price !== null) {
@@ -138,6 +238,53 @@ class QuotationModule extends _BaseModule.BaseModule {
138
238
  setScheduleResolver(resolver) {
139
239
  this.scheduleResolver = resolver;
140
240
  }
241
+ applyQuotationResponse(response, params) {
242
+ const source = response?.data?.list || response?.list || [];
243
+ const list = [...source].sort((a, b) => Number(b.sort || 0) - Number(a.sort || 0));
244
+ if (this.lifecycleGeneration !== params.lifecycleGeneration || this.getQuotationCacheVersion(params.channelKey) !== params.cacheVersion) {
245
+ return false;
246
+ }
247
+ this.quotationCache.set(params.channelKey, list);
248
+ if (this.activeChannelKey === params.channelKey) this.store.list = list;
249
+ return true;
250
+ }
251
+ notifyQuotationListUpdated() {
252
+ try {
253
+ this.quotationListUpdatedListener?.();
254
+ } catch (error) {
255
+ console.warn('[QuotationModule] 报价单后台更新回调失败', error);
256
+ }
257
+ }
258
+ getQuotationCacheKeyData(query) {
259
+ let shopId = '';
260
+ try {
261
+ const storedCore = this.app?.storage?.getStorage?.('core');
262
+ const coreInfo = typeof storedCore === 'string' ? JSON.parse(storedCore || '{}') : storedCore || {};
263
+ shopId = String(coreInfo?.id || '');
264
+ } catch {
265
+ // 缓存隔离信息读取失败时仍按请求参数缓存。
266
+ }
267
+ return {
268
+ ...query,
269
+ shop_id: shopId
270
+ };
271
+ }
272
+ normalizeChannel(channel) {
273
+ const normalized = channel?.trim().toLowerCase() || '';
274
+ return normalized === 'online_store' ? 'online-store' : normalized;
275
+ }
276
+ activateChannel(channelKey) {
277
+ this.activeChannelKey = channelKey;
278
+ this.store.list = this.quotationCache.get(channelKey) || [];
279
+ }
280
+ getQuotationCacheVersion(channelKey) {
281
+ return this.quotationCacheVersions.get(channelKey) || 0;
282
+ }
283
+ bumpQuotationCacheVersion(channelKey) {
284
+ const nextVersion = this.getQuotationCacheVersion(channelKey) + 1;
285
+ this.quotationCacheVersions.set(channelKey, nextVersion);
286
+ return nextVersion;
287
+ }
141
288
  isQuotationVisibleForCustomer(quotation, customerId) {
142
289
  const customers = quotation.customer || [];
143
290
  if (customers.length === 0) return true;
@@ -1,24 +1,2 @@
1
- import { BaseModule } from '../BaseModule';
2
- import type { Module, ModuleOptions, PisellCore } from '../../types';
3
- import type { ResourcePlannerAssignmentResult, ResourcePlannerAssignableSlotResult, ResourcePlannerContextInput, ResourcePlannerProjection, ResourcePlannerQuery, ResourcePlannerSelectionPatch, ResourcePlannerSnapshot, ResourcePlannerValidationResult } from './types';
4
1
  export * from './types';
5
2
  export * from './planner';
6
- export declare enum ResourcePlannerHooks {
7
- onContextChanged = "resourcePlanner:onContextChanged",
8
- onSelectionChanged = "resourcePlanner:onSelectionChanged",
9
- onProjectionChanged = "resourcePlanner:onProjectionChanged"
10
- }
11
- export declare class ResourcePlannerModule extends BaseModule implements Module {
12
- protected defaultName: string;
13
- protected defaultVersion: string;
14
- private store;
15
- initialize(core: PisellCore, options: ModuleOptions): Promise<void>;
16
- setContext(input: ResourcePlannerContextInput): ResourcePlannerSnapshot;
17
- mergeContext(input: ResourcePlannerContextInput): ResourcePlannerSnapshot;
18
- setSelectionPatch(patch: ResourcePlannerSelectionPatch): ResourcePlannerSnapshot;
19
- queryAvailability(query?: ResourcePlannerQuery): ResourcePlannerProjection;
20
- autoAssign(query?: ResourcePlannerQuery): ResourcePlannerAssignmentResult;
21
- resolveAssignableSlots(query?: ResourcePlannerQuery): ResourcePlannerAssignableSlotResult;
22
- validateSelection(): ResourcePlannerValidationResult;
23
- getSnapshot(): ResourcePlannerSnapshot;
24
- }
@@ -3,145 +3,25 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- var _exportNames = {
7
- ResourcePlannerHooks: true,
8
- ResourcePlannerModule: true
9
- };
10
- exports.ResourcePlannerModule = exports.ResourcePlannerHooks = void 0;
11
- var _BaseModule = require("../BaseModule");
12
- var _planner = require("./planner");
13
- Object.keys(_planner).forEach(function (key) {
6
+ var _types = require("./types");
7
+ Object.keys(_types).forEach(function (key) {
14
8
  if (key === "default" || key === "__esModule") return;
15
- if (Object.prototype.hasOwnProperty.call(_exportNames, key)) return;
16
- if (key in exports && exports[key] === _planner[key]) return;
9
+ if (key in exports && exports[key] === _types[key]) return;
17
10
  Object.defineProperty(exports, key, {
18
11
  enumerable: true,
19
12
  get: function () {
20
- return _planner[key];
13
+ return _types[key];
21
14
  }
22
15
  });
23
16
  });
24
- var _types = require("./types");
25
- Object.keys(_types).forEach(function (key) {
17
+ var _planner = require("./planner");
18
+ Object.keys(_planner).forEach(function (key) {
26
19
  if (key === "default" || key === "__esModule") return;
27
- if (Object.prototype.hasOwnProperty.call(_exportNames, key)) return;
28
- if (key in exports && exports[key] === _types[key]) return;
20
+ if (key in exports && exports[key] === _planner[key]) return;
29
21
  Object.defineProperty(exports, key, {
30
22
  enumerable: true,
31
23
  get: function () {
32
- return _types[key];
24
+ return _planner[key];
33
25
  }
34
26
  });
35
- });
36
- let ResourcePlannerHooks = exports.ResourcePlannerHooks = /*#__PURE__*/function (ResourcePlannerHooks) {
37
- ResourcePlannerHooks["onContextChanged"] = "resourcePlanner:onContextChanged";
38
- ResourcePlannerHooks["onSelectionChanged"] = "resourcePlanner:onSelectionChanged";
39
- ResourcePlannerHooks["onProjectionChanged"] = "resourcePlanner:onProjectionChanged";
40
- return ResourcePlannerHooks;
41
- }({});
42
- class ResourcePlannerModule extends _BaseModule.BaseModule {
43
- defaultName = 'resourcePlanner';
44
- defaultVersion = '1.0.0';
45
- store;
46
- async initialize(core, options) {
47
- this.core = core;
48
- const initialState = options.initialState;
49
- this.store = {
50
- context: (0, _planner.normalizePlannerContext)(initialState?.context || _planner.DEFAULT_RESOURCE_PLANNER_CONTEXT),
51
- selection: initialState?.selection || {
52
- ..._planner.DEFAULT_RESOURCE_PLANNER_SELECTION
53
- },
54
- projection: initialState?.projection || null,
55
- version: initialState?.version || 0
56
- };
57
- }
58
- setContext(input) {
59
- this.store.context = (0, _planner.normalizePlannerContext)(input);
60
- this.store.version += 1;
61
- this.store.projection = null;
62
- const snapshot = this.getSnapshot();
63
- void this.core.effects.emit(`${this.name}:contextChanged`, snapshot);
64
- return snapshot;
65
- }
66
- mergeContext(input) {
67
- return this.setContext({
68
- ...this.store.context,
69
- ...input,
70
- products: input.products || this.store.context.products,
71
- resources: input.resources || this.store.context.resources,
72
- externalEvents: input.externalEvents || this.store.context.externalEvents
73
- });
74
- }
75
- setSelectionPatch(patch) {
76
- const assignments = patch.assignments ? patch.replaceAssignments === false ? [...this.store.selection.assignments, ...patch.assignments] : patch.assignments : this.store.selection.assignments;
77
- this.store.selection = {
78
- ...this.store.selection,
79
- ...patch,
80
- productIds: patch.productIds || this.store.selection.productIds,
81
- resourceIds: patch.resourceIds || this.store.selection.resourceIds,
82
- assignments
83
- };
84
- this.store.version += 1;
85
- const snapshot = this.getSnapshot();
86
- void this.core.effects.emit(`${this.name}:selectionChanged`, snapshot);
87
- return snapshot;
88
- }
89
- queryAvailability(query = {}) {
90
- const mergedQuery = {
91
- mode: this.store.selection.mode,
92
- productIds: this.store.selection.productIds,
93
- date: this.store.selection.date,
94
- startTime: this.store.selection.startTime,
95
- endTime: this.store.selection.endTime,
96
- resourceIds: this.store.selection.resourceIds,
97
- ...query
98
- };
99
- const projection = (0, _planner.queryPlannerAvailability)(this.store.context, mergedQuery);
100
- this.store.projection = projection;
101
- this.store.version += 1;
102
- void this.core.effects.emit(`${this.name}:projectionChanged`, this.getSnapshot());
103
- return projection;
104
- }
105
- autoAssign(query = {}) {
106
- const result = (0, _planner.autoAssignResources)(this.store.context, {
107
- mode: this.store.selection.mode,
108
- productIds: this.store.selection.productIds,
109
- date: this.store.selection.date,
110
- startTime: this.store.selection.startTime,
111
- endTime: this.store.selection.endTime,
112
- resourceIds: this.store.selection.resourceIds,
113
- ...query
114
- });
115
- const resourceIds = Array.from(new Map(result.assignments.map(assignment => [String(assignment.resourceId), assignment.resourceId])).values());
116
- this.setSelectionPatch({
117
- assignments: result.assignments,
118
- ...(resourceIds.length ? {
119
- resourceIds
120
- } : {})
121
- });
122
- return result;
123
- }
124
- resolveAssignableSlots(query = {}) {
125
- return (0, _planner.resolveAssignableSlots)(this.store.context, {
126
- mode: this.store.selection.mode,
127
- productIds: this.store.selection.productIds,
128
- date: this.store.selection.date,
129
- startTime: this.store.selection.startTime,
130
- endTime: this.store.selection.endTime,
131
- resourceIds: this.store.selection.resourceIds,
132
- ...query
133
- });
134
- }
135
- validateSelection() {
136
- return (0, _planner.validateAssignments)(this.store.context, this.store.selection);
137
- }
138
- getSnapshot() {
139
- return {
140
- context: this.store.context,
141
- selection: this.store.selection,
142
- projection: this.store.projection,
143
- version: this.store.version
144
- };
145
- }
146
- }
147
- exports.ResourcePlannerModule = ResourcePlannerModule;
27
+ });