@pisell/pisellos 2.3.1 → 2.3.3

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 (54) hide show
  1. package/dist/model/strategy/adapter/promotion/index.js +9 -0
  2. package/dist/modules/Order/index.d.ts +9 -0
  3. package/dist/modules/Order/index.js +448 -371
  4. package/dist/modules/Order/types.d.ts +2 -0
  5. package/dist/modules/Quotation/index.d.ts +16 -2
  6. package/dist/modules/Quotation/index.js +71 -38
  7. package/dist/modules/Quotation/types.d.ts +14 -0
  8. package/dist/modules/SalesSummary/index.d.ts +1 -0
  9. package/dist/modules/SalesSummary/index.js +7 -4
  10. package/dist/modules/Schedule/index.d.ts +4 -0
  11. package/dist/modules/Schedule/index.js +31 -1
  12. package/dist/modules/Schedule/types.d.ts +2 -0
  13. package/dist/server/index.js +13 -13
  14. package/dist/server/modules/order/index.d.ts +25 -7
  15. package/dist/server/modules/order/index.js +370 -309
  16. package/dist/server/modules/products/index.d.ts +4 -0
  17. package/dist/server/modules/products/index.js +76 -45
  18. package/dist/server/modules/quotation/index.d.ts +39 -0
  19. package/dist/server/modules/quotation/index.js +278 -66
  20. package/dist/server/modules/schedule/index.d.ts +2 -0
  21. package/dist/server/modules/schedule/index.js +10 -2
  22. package/dist/solution/BaseSales/index.d.ts +10 -1
  23. package/dist/solution/BaseSales/index.js +825 -528
  24. package/dist/solution/BaseSales/types.d.ts +7 -0
  25. package/dist/solution/BaseSales/utils/quotationPrice.d.ts +12 -1
  26. package/dist/solution/BaseSales/utils/quotationPrice.js +16 -3
  27. package/dist/solution/BookingByStep/index.d.ts +1 -1
  28. package/lib/modules/Order/index.d.ts +9 -0
  29. package/lib/modules/Order/index.js +48 -9
  30. package/lib/modules/Order/types.d.ts +2 -0
  31. package/lib/modules/Quotation/index.d.ts +16 -2
  32. package/lib/modules/Quotation/index.js +49 -13
  33. package/lib/modules/Quotation/types.d.ts +14 -0
  34. package/lib/modules/SalesSummary/index.d.ts +1 -0
  35. package/lib/modules/SalesSummary/index.js +4 -3
  36. package/lib/modules/Schedule/index.d.ts +4 -0
  37. package/lib/modules/Schedule/index.js +21 -0
  38. package/lib/modules/Schedule/types.d.ts +2 -0
  39. package/lib/server/index.js +1 -1
  40. package/lib/server/modules/order/index.d.ts +25 -7
  41. package/lib/server/modules/order/index.js +88 -54
  42. package/lib/server/modules/products/index.d.ts +4 -0
  43. package/lib/server/modules/products/index.js +43 -19
  44. package/lib/server/modules/quotation/index.d.ts +39 -0
  45. package/lib/server/modules/quotation/index.js +179 -1
  46. package/lib/server/modules/schedule/index.d.ts +2 -0
  47. package/lib/server/modules/schedule/index.js +8 -1
  48. package/lib/solution/BaseSales/index.d.ts +10 -1
  49. package/lib/solution/BaseSales/index.js +218 -19
  50. package/lib/solution/BaseSales/types.d.ts +7 -0
  51. package/lib/solution/BaseSales/utils/quotationPrice.d.ts +12 -1
  52. package/lib/solution/BaseSales/utils/quotationPrice.js +15 -3
  53. package/lib/solution/BookingByStep/index.d.ts +1 -1
  54. package/package.json +1 -1
@@ -48,6 +48,7 @@ var ORDER_BUSINESS_WRITE_SOURCES = /* @__PURE__ */ new Set([
48
48
  "overwriteExistingOrder",
49
49
  "markOrderSyncedByExternalSaleNumber"
50
50
  ]);
51
+ var ORDER_MERGE_SELF_CHECK = false;
51
52
  var OrderModule = class extends import_BaseModule.BaseModule {
52
53
  constructor(name, version) {
53
54
  super(name, version);
@@ -56,8 +57,6 @@ var OrderModule = class extends import_BaseModule.BaseModule {
56
57
  this.pendingSyncMessages = [];
57
58
  this.isProcessingSyncBatch = false;
58
59
  this.isIdlePhase = true;
59
- // Map<resource_id, OrderId[]> 资源到订单的倒排索引
60
- this.resourceIdIndex = /* @__PURE__ */ new Map();
61
60
  this.orderSQLiteSaveQueue = Promise.resolve();
62
61
  /** 按 storageKey 记录最近一次 SQLite 写入来源与时间,用于去重 */
63
62
  this.recentSqliteWriteByStorageKey = /* @__PURE__ */ new Map();
@@ -110,10 +109,10 @@ var OrderModule = class extends import_BaseModule.BaseModule {
110
109
  });
111
110
  }
112
111
  /**
113
- * 记录信息日志
114
- * @param title 日志标题
115
- * @param metadata 日志元数据
116
- */
112
+ * 记录信息日志
113
+ * @param title 日志标题
114
+ * @param metadata 日志元数据
115
+ */
117
116
  logInfo(title, metadata) {
118
117
  try {
119
118
  if (this.logger) {
@@ -481,10 +480,6 @@ var OrderModule = class extends import_BaseModule.BaseModule {
481
480
  return this.store.list;
482
481
  }
483
482
  }
484
- getOrdersByResourceId(resourceId) {
485
- const ids = this.resourceIdIndex.get(String(resourceId)) || [];
486
- return ids.map((id) => this.store.map.get(id)).filter((order) => !!order);
487
- }
488
483
  normalizeOrderForMemoryList(order) {
489
484
  const externalSaleNumber = order == null ? void 0 : order.external_sale_number;
490
485
  if ((order == null ? void 0 : order.order_id) !== void 0 && (order == null ? void 0 : order.order_id) !== null && (order == null ? void 0 : order.order_id) !== "") {
@@ -617,22 +612,33 @@ var OrderModule = class extends import_BaseModule.BaseModule {
617
612
  const patchedOrders = [...pendingMap.values()];
618
613
  const mergeActions = {};
619
614
  const updatedList = [...this.store.list];
615
+ const identityKeyToIndex = /* @__PURE__ */ new Map();
616
+ for (let index = 0; index < updatedList.length; index += 1) {
617
+ for (const key of this.getOrderIdentityMatchKeys(updatedList[index])) {
618
+ if (!identityKeyToIndex.has(key))
619
+ identityKeyToIndex.set(key, index);
620
+ }
621
+ }
620
622
  for (const [storageKey, pendingOrder] of memoryPendingMap.entries()) {
621
- const matchIndex = this.findOrderIndexByIdentity(updatedList, pendingOrder);
622
- if (matchIndex < 0)
623
+ const matchIndex = this.lookupOrderIndexByIdentityKeys(
624
+ identityKeyToIndex,
625
+ this.getOrderIdentityMatchKeys(pendingOrder)
626
+ );
627
+ if (matchIndex >= 0) {
628
+ const mergedOrder = this.mergeOrderRecords(updatedList[matchIndex], pendingOrder, {
629
+ preferIncomingProducts: true
630
+ });
631
+ updatedList[matchIndex] = mergedOrder;
632
+ this.registerOrderIdentityKeys(identityKeyToIndex, mergedOrder, matchIndex);
633
+ mergeActions[storageKey] = "update";
623
634
  continue;
624
- updatedList[matchIndex] = this.mergeOrderRecords(updatedList[matchIndex], pendingOrder, {
625
- preferIncomingProducts: true
626
- });
627
- pendingMap.delete(storageKey);
628
- memoryPendingMap.delete(storageKey);
629
- mergeActions[storageKey] = "update";
630
- }
631
- for (const [storageKey, order] of memoryPendingMap.entries()) {
635
+ }
636
+ const insertIndex = updatedList.length;
637
+ updatedList.push(pendingOrder);
638
+ this.registerOrderIdentityKeys(identityKeyToIndex, pendingOrder, insertIndex);
632
639
  mergeActions[storageKey] = "insert";
633
- updatedList.push(order);
634
640
  }
635
- this.store.list = this.compactOrderListByIdentity(updatedList, "upsertPendingSyncOrders.store").list;
641
+ this.store.list = this.runOrderStoreSelfCheck(updatedList, "upsertPendingSyncOrders.store");
636
642
  this.syncOrdersMap();
637
643
  await this.patchOrdersInSQLite(patchedOrders, "upsertPendingSyncOrders", mergeActions);
638
644
  this.logInfo("upsertPendingSyncOrders-结束", {
@@ -674,21 +680,11 @@ var OrderModule = class extends import_BaseModule.BaseModule {
674
680
  }
675
681
  syncOrdersMap() {
676
682
  this.store.map.clear();
677
- this.resourceIdIndex.clear();
678
683
  for (const order of this.store.list) {
679
684
  const primaryIdentity = this.getOrderMapKey(order);
680
685
  if (!primaryIdentity)
681
686
  continue;
682
687
  this.store.map.set(primaryIdentity, order);
683
- const resourceIds = this.extractResourceIds(order);
684
- for (const resourceId of resourceIds) {
685
- const key = String(resourceId);
686
- const existing = this.resourceIdIndex.get(key) || [];
687
- if (existing.some((eid) => this.getIdKey(eid) === primaryIdentity))
688
- continue;
689
- existing.push(primaryIdentity);
690
- this.resourceIdIndex.set(key, existing);
691
- }
692
688
  }
693
689
  }
694
690
  getOrderMapKey(order) {
@@ -735,24 +731,6 @@ var OrderModule = class extends import_BaseModule.BaseModule {
735
731
  }
736
732
  return keys;
737
733
  }
738
- extractResourceIds(order) {
739
- const ids = /* @__PURE__ */ new Set();
740
- const visitResources = (resources) => {
741
- if (!Array.isArray(resources))
742
- return;
743
- resources.forEach((resource) => {
744
- const rid = resource == null ? void 0 : resource.relation_id;
745
- if (rid !== void 0 && rid !== null && rid !== "")
746
- ids.add(String(rid));
747
- if (Array.isArray(resource == null ? void 0 : resource.children))
748
- visitResources(resource.children);
749
- });
750
- };
751
- if (Array.isArray(order == null ? void 0 : order.bookings)) {
752
- order.bookings.forEach((booking) => visitResources((booking == null ? void 0 : booking.resources) || []));
753
- }
754
- return [...ids];
755
- }
756
734
  getOrderDateKey(order) {
757
735
  var _a, _b, _c, _d;
758
736
  const candidates = [
@@ -971,9 +949,10 @@ var OrderModule = class extends import_BaseModule.BaseModule {
971
949
  * 将增量订单合并到 store
972
950
  */
973
951
  async mergeOrdersToStore(freshOrders, source) {
952
+ const storeOrderCountBefore = this.store.list.length;
974
953
  this.logInfo("mergeOrdersToStore-开始", {
975
954
  freshOrderCount: freshOrders.length,
976
- storeOrderCountBefore: this.store.list.length,
955
+ storeOrderCountBefore,
977
956
  source
978
957
  });
979
958
  this.logDuplicateOrders(`${source}.beforeMerge`, this.store.list);
@@ -992,19 +971,31 @@ var OrderModule = class extends import_BaseModule.BaseModule {
992
971
  const patchedOrders = [];
993
972
  const mergeActions = {};
994
973
  const updatedList = [...this.store.list];
974
+ const identityKeyToIndex = /* @__PURE__ */ new Map();
975
+ for (let index = 0; index < updatedList.length; index += 1) {
976
+ const identityKeys = this.getOrderIdentityMatchKeys(updatedList[index]);
977
+ for (const key of identityKeys) {
978
+ if (!identityKeyToIndex.has(key))
979
+ identityKeyToIndex.set(key, index);
980
+ }
981
+ }
995
982
  for (const fresh of uniqueFreshOrders) {
996
- const matchIndex = this.findOrderIndexByIdentity(updatedList, fresh);
983
+ const freshIdentityKeys = this.getOrderIdentityMatchKeys(fresh);
984
+ const matchIndex = this.lookupOrderIndexByIdentityKeys(identityKeyToIndex, freshIdentityKeys);
997
985
  const storageKey = this.getOrderStorageKey(fresh);
998
986
  const mergeKey = storageKey || (fresh.order_id !== void 0 && fresh.order_id !== null ? this.getIdKey(fresh.order_id) : "");
999
987
  if (matchIndex >= 0) {
1000
988
  const mergedOrder = this.mergeOrderRecords(updatedList[matchIndex], fresh);
1001
989
  updatedList[matchIndex] = mergedOrder;
990
+ this.registerOrderIdentityKeys(identityKeyToIndex, mergedOrder, matchIndex);
1002
991
  patchedOrders.push(mergedOrder);
1003
992
  if (mergeKey)
1004
993
  mergeActions[mergeKey] = "update";
1005
994
  continue;
1006
995
  }
996
+ const insertIndex = updatedList.length;
1007
997
  updatedList.push(fresh);
998
+ this.registerOrderIdentityKeys(identityKeyToIndex, fresh, insertIndex);
1008
999
  patchedOrders.push(fresh);
1009
1000
  if (mergeKey)
1010
1001
  mergeActions[mergeKey] = "insert";
@@ -1016,7 +1007,7 @@ var OrderModule = class extends import_BaseModule.BaseModule {
1016
1007
  updateCount,
1017
1008
  storeOrderCountAfter: this.store.list.length
1018
1009
  });
1019
- this.store.list = this.compactOrderListByIdentity(updatedList, `${source}.store`).list;
1010
+ this.store.list = this.runOrderStoreSelfCheck(updatedList, `${source}.store`);
1020
1011
  this.syncOrdersMap();
1021
1012
  await this.patchOrdersInSQLite(patchedOrders, source, mergeActions);
1022
1013
  this.logInfo("mergeOrdersToStore-结束", {
@@ -1027,6 +1018,50 @@ var OrderModule = class extends import_BaseModule.BaseModule {
1027
1018
  });
1028
1019
  await this.emitOrdersChanged(patchedOrders, source);
1029
1020
  }
1021
+ /**
1022
+ * 在身份索引中查找与目标订单共享任一身份键的已存在订单下标。
1023
+ * 返回所有命中键里最小的下标,等价于原 findOrderIndexByIdentity 的「取首个匹配」语义。
1024
+ *
1025
+ * @example
1026
+ * const idx = this.lookupOrderIndexByIdentityKeys(identityKeyToIndex, freshIdentityKeys)
1027
+ */
1028
+ lookupOrderIndexByIdentityKeys(identityKeyToIndex, identityKeys) {
1029
+ let matchIndex = -1;
1030
+ for (const key of identityKeys) {
1031
+ const index = identityKeyToIndex.get(key);
1032
+ if (index === void 0)
1033
+ continue;
1034
+ if (matchIndex < 0 || index < matchIndex)
1035
+ matchIndex = index;
1036
+ }
1037
+ return matchIndex;
1038
+ }
1039
+ /**
1040
+ * 将订单的全部身份键登记到索引并指向其在列表中的下标。
1041
+ * 合并后订单新增的身份字段(如挂单同步后拿到 order_id)也会被登记。
1042
+ *
1043
+ * @example
1044
+ * this.registerOrderIdentityKeys(identityKeyToIndex, mergedOrder, matchIndex)
1045
+ */
1046
+ registerOrderIdentityKeys(identityKeyToIndex, order, index) {
1047
+ const identityKeys = this.getOrderIdentityMatchKeys(order);
1048
+ for (const key of identityKeys) {
1049
+ identityKeyToIndex.set(key, index);
1050
+ }
1051
+ }
1052
+ /**
1053
+ * 合并热路径去重兜底:默认直接返回原列表(增量索引已保证无重复)。
1054
+ * 仅当 ORDER_MERGE_SELF_CHECK 开启时,额外做一次全表压缩并在发现残留重复时告警。
1055
+ */
1056
+ runOrderStoreSelfCheck(orders, source) {
1057
+ if (!ORDER_MERGE_SELF_CHECK)
1058
+ return orders;
1059
+ const { list, removedCount } = this.compactOrderListByIdentity(orders, source);
1060
+ if (removedCount > 0) {
1061
+ this.logWarning("合并自检发现残留重复订单,已压缩", { source, removedCount });
1062
+ }
1063
+ return list;
1064
+ }
1030
1065
  normalizeOrderSyncMessage(res) {
1031
1066
  const data = (res == null ? void 0 : res.data) || res;
1032
1067
  if (!data)
@@ -1640,7 +1675,6 @@ var OrderModule = class extends import_BaseModule.BaseModule {
1640
1675
  async clear() {
1641
1676
  this.store.list = [];
1642
1677
  this.store.map = /* @__PURE__ */ new Map();
1643
- this.resourceIdIndex.clear();
1644
1678
  if (this.dbManager) {
1645
1679
  await this.runInOrderSQLiteSaveQueue(async () => {
1646
1680
  await this.dbManager.clear(INDEXDB_STORE_NAME);
@@ -24,6 +24,9 @@ export declare class ProductsModule extends BaseModule implements Module {
24
24
  private quotationBridgeLoadedKey?;
25
25
  private quotationBridgeRefreshPromise?;
26
26
  private quotationBridgeRefreshKey?;
27
+ private quotationScheduleCache;
28
+ private quotationScheduleCacheSource;
29
+ private quotationScheduleCacheLoaded;
27
30
  private productDataSource;
28
31
  private pendingSyncMessages;
29
32
  private syncTimer?;
@@ -75,6 +78,7 @@ export declare class ProductsModule extends BaseModule implements Module {
75
78
  }): Promise<void>;
76
79
  private ensureQuotationBridgeReady;
77
80
  private getQuotationBridgeScopeKey;
81
+ private getQuotedPrice;
78
82
  private buildPriceDataFromQuotation;
79
83
  private buildVariantPriceData;
80
84
  private buildBundleGroupPriceData;
@@ -46,6 +46,9 @@ var ProductsModule = class extends import_BaseModule.BaseModule {
46
46
  // 是否已注册内置价格格式化器
47
47
  this.isPriceFormatterRegistered = false;
48
48
  this.quotationBridgeLoadedKey = "";
49
+ this.quotationScheduleCache = /* @__PURE__ */ new Map();
50
+ this.quotationScheduleCacheSource = null;
51
+ this.quotationScheduleCacheLoaded = false;
49
52
  this.pendingSyncMessages = [];
50
53
  /** 最近一次 SSE 全量拉取完成时间 */
51
54
  this.lastServerFullFetchAtMs = 0;
@@ -198,25 +201,30 @@ var ProductsModule = class extends import_BaseModule.BaseModule {
198
201
  this.quotationPriceBridge = quotation;
199
202
  }
200
203
  if (params.scheduleModule) {
201
- const scheduleCache = /* @__PURE__ */ new Map();
202
- let hasLoadedScheduleList = false;
204
+ if (this.quotationScheduleCacheSource !== params.scheduleModule) {
205
+ this.quotationScheduleCache.clear();
206
+ this.quotationScheduleCacheLoaded = false;
207
+ this.quotationScheduleCacheSource = params.scheduleModule;
208
+ }
203
209
  this.quotationPriceBridge.setScheduleResolver((id) => {
204
210
  var _a2, _b, _c, _d, _e, _f;
205
211
  const scheduleId = String(id);
206
- if (scheduleCache.has(scheduleId))
207
- return scheduleCache.get(scheduleId);
208
- if (!hasLoadedScheduleList) {
212
+ if (this.quotationScheduleCache.has(scheduleId)) {
213
+ return this.quotationScheduleCache.get(scheduleId);
214
+ }
215
+ if (!this.quotationScheduleCacheLoaded) {
209
216
  const scheduleList = ((_b = (_a2 = params.scheduleModule).getScheduleList) == null ? void 0 : _b.call(_a2)) || [];
210
217
  for (const schedule of scheduleList) {
211
- scheduleCache.set(String(schedule.id), schedule);
218
+ this.quotationScheduleCache.set(String(schedule.id), schedule);
212
219
  }
213
- hasLoadedScheduleList = true;
220
+ this.quotationScheduleCacheLoaded = true;
221
+ }
222
+ if (this.quotationScheduleCache.has(scheduleId)) {
223
+ return this.quotationScheduleCache.get(scheduleId);
214
224
  }
215
- if (scheduleCache.has(scheduleId))
216
- return scheduleCache.get(scheduleId);
217
225
  const schedules = ((_d = (_c = params.scheduleModule).getScheduleListByIds) == null ? void 0 : _d.call(_c, [id])) || ((_f = (_e = params.scheduleModule).getScheduleByIds) == null ? void 0 : _f.call(_e, [id])) || [];
218
226
  if (schedules[0])
219
- scheduleCache.set(scheduleId, schedules[0]);
227
+ this.quotationScheduleCache.set(scheduleId, schedules[0]);
220
228
  return schedules[0];
221
229
  });
222
230
  }
@@ -266,12 +274,30 @@ var ProductsModule = class extends import_BaseModule.BaseModule {
266
274
  getQuotationBridgeScopeKey() {
267
275
  return this.quotationBridgeChannel || "";
268
276
  }
269
- buildPriceDataFromQuotation(product, datetime, customerId) {
277
+ getQuotedPrice(params) {
270
278
  var _a;
271
- const quotedPrice = (_a = this.quotationPriceBridge) == null ? void 0 : _a.getPriceForProduct({
279
+ const quotation = this.quotationPriceBridge;
280
+ if (!quotation)
281
+ return null;
282
+ const query = {
283
+ productId: params.productId,
284
+ variantId: params.variantId,
285
+ datetime: params.datetime,
286
+ customer_id: params.customerId
287
+ };
288
+ if (typeof quotation.getQuotationMatchForProduct === "function") {
289
+ return ((_a = quotation.getQuotationMatchForProduct(query)) == null ? void 0 : _a.price) ?? null;
290
+ }
291
+ if (typeof quotation.getPriceForProduct === "function") {
292
+ return quotation.getPriceForProduct(query);
293
+ }
294
+ return null;
295
+ }
296
+ buildPriceDataFromQuotation(product, datetime, customerId) {
297
+ const quotedPrice = this.getQuotedPrice({
272
298
  productId: product.id,
273
299
  datetime,
274
- customer_id: customerId
300
+ customerId
275
301
  });
276
302
  const productPrice = quotedPrice ?? product.price;
277
303
  const productBasePrice = quotedPrice ?? product.base_price ?? product.price ?? 0;
@@ -287,12 +313,11 @@ var ProductsModule = class extends import_BaseModule.BaseModule {
287
313
  if (!Array.isArray(product.variant))
288
314
  return [];
289
315
  return product.variant.map((variant) => {
290
- var _a;
291
- const quotedPrice = (_a = this.quotationPriceBridge) == null ? void 0 : _a.getPriceForProduct({
316
+ const quotedPrice = this.getQuotedPrice({
292
317
  productId: product.id,
293
318
  variantId: Number(variant.id),
294
319
  datetime,
295
- customer_id: customerId
320
+ customerId
296
321
  });
297
322
  const price = quotedPrice ?? variant.price ?? product.price ?? 0;
298
323
  const basePrice = quotedPrice ?? variant.base_price ?? variant.price ?? product.base_price ?? product.price ?? 0;
@@ -310,14 +335,13 @@ var ProductsModule = class extends import_BaseModule.BaseModule {
310
335
  return product.bundle_group.map((group) => ({
311
336
  id: Number(group.id),
312
337
  bundle_item: (group.bundle_item || []).map((item) => {
313
- var _a;
314
338
  const bundleProductId = Number(item.bundle_product_id);
315
339
  const bundleVariantId = Number(item.bundle_variant_id || 0);
316
- const quotedPrice = (_a = this.quotationPriceBridge) == null ? void 0 : _a.getPriceForProduct({
340
+ const quotedPrice = this.getQuotedPrice({
317
341
  productId: bundleProductId,
318
342
  variantId: bundleVariantId,
319
343
  datetime,
320
- customer_id: customerId
344
+ customerId
321
345
  });
322
346
  const price = quotedPrice ?? item.price ?? 0;
323
347
  const basePrice = quotedPrice ?? item.base_price ?? item.price ?? 0;
@@ -1,6 +1,8 @@
1
1
  import { Module, PisellCore, ModuleOptions } from '../../../types';
2
2
  import { BaseModule } from '../../../modules/BaseModule';
3
3
  import { QuotationData } from './types';
4
+ import type { ScheduleItem } from '../../../modules/Schedule/types';
5
+ import type { QuotationCustomerScopeInfo } from '../../../modules/Quotation/types';
4
6
  /**
5
7
  * 报价单模块 - 用于管理报价单数据和计算商品价格
6
8
  */
@@ -10,6 +12,7 @@ export declare class QuotationModule extends BaseModule implements Module {
10
12
  private request;
11
13
  private store;
12
14
  private dbManager;
15
+ private scheduleResolver?;
13
16
  constructor(name?: string, version?: string);
14
17
  initialize(core: PisellCore, options?: ModuleOptions): Promise<void>;
15
18
  /**
@@ -25,10 +28,46 @@ export declare class QuotationModule extends BaseModule implements Module {
25
28
  * 获取报价单列表
26
29
  */
27
30
  getQuotationList(): QuotationData[];
31
+ loadQuotations(params?: {
32
+ channel?: string;
33
+ customer_id?: number | string;
34
+ }): Promise<void>;
35
+ setScheduleResolver(resolver: (id: number) => ScheduleItem | undefined): void;
36
+ getPriceForProduct(params: {
37
+ productId: number;
38
+ variantId?: number;
39
+ datetime: string;
40
+ channel?: string;
41
+ customer_id?: number | string;
42
+ }): string | null;
43
+ getQuotationMatchForProduct(params: {
44
+ productId: number;
45
+ variantId?: number;
46
+ datetime: string;
47
+ channel?: string;
48
+ customer_id?: number | string;
49
+ }): {
50
+ price: string | null;
51
+ quotationShelfId: number;
52
+ } | null;
53
+ getQuotationShelfId(params: {
54
+ productId: number;
55
+ variantId?: number;
56
+ datetime: string;
57
+ channel?: string;
58
+ customer_id?: number | string;
59
+ }): number;
60
+ getQuotationCustomerScopeInfo(): QuotationCustomerScopeInfo;
28
61
  /**
29
62
  * 清空缓存
30
63
  */
31
64
  clear(): Promise<void>;
65
+ private normalizeCustomerId;
66
+ private hasValidCustomerId;
67
+ private isQuotationVisibleForCustomer;
68
+ private isQuotationVisibleForChannel;
69
+ private isQuotationActiveAt;
70
+ private findProductData;
32
71
  /**
33
72
  * 从 IndexDB 加载报价单数据
34
73
  * @private
@@ -1,6 +1,8 @@
1
+ var __create = Object.create;
1
2
  var __defProp = Object.defineProperty;
2
3
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
3
4
  var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __getProtoOf = Object.getPrototypeOf;
4
6
  var __hasOwnProp = Object.prototype.hasOwnProperty;
5
7
  var __export = (target, all) => {
6
8
  for (var name in all)
@@ -14,6 +16,14 @@ var __copyProps = (to, from, except, desc) => {
14
16
  }
15
17
  return to;
16
18
  };
19
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
20
+ // If the importer is in node compatibility mode or this is not an ESM
21
+ // file that has been converted to a CommonJS file using a Babel-
22
+ // compatible transform (i.e. "__esModule" has not been set), then set
23
+ // "default" to the CommonJS "module.exports" for node compatibility.
24
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
25
+ mod
26
+ ));
17
27
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
18
28
 
19
29
  // src/server/modules/quotation/index.ts
@@ -23,11 +33,16 @@ __export(quotation_exports, {
23
33
  });
24
34
  module.exports = __toCommonJS(quotation_exports);
25
35
  var import_lodash_es = require("lodash-es");
36
+ var import_dayjs = __toESM(require("dayjs"));
37
+ var import_isSameOrBefore = __toESM(require("dayjs/plugin/isSameOrBefore"));
38
+ var import_isSameOrAfter = __toESM(require("dayjs/plugin/isSameOrAfter"));
26
39
  var import_BaseModule = require("../../../modules/BaseModule");
27
40
  var import_types = require("./types");
41
+ var import_getDateIsInSchedule = require("../../../modules/Schedule/getDateIsInSchedule");
42
+ import_dayjs.default.extend(import_isSameOrBefore.default);
43
+ import_dayjs.default.extend(import_isSameOrAfter.default);
28
44
  var INDEXDB_STORE_NAME = "quotation";
29
45
  var QuotationModule = class extends import_BaseModule.BaseModule {
30
- // IndexDBManager 实例
31
46
  constructor(name, version) {
32
47
  super(name, version);
33
48
  this.defaultName = "quotation";
@@ -95,6 +110,97 @@ var QuotationModule = class extends import_BaseModule.BaseModule {
95
110
  getQuotationList() {
96
111
  return this.store.quotationList;
97
112
  }
113
+ async loadQuotations(params) {
114
+ var _a;
115
+ if (Array.isArray(this.store.quotationList) && this.store.quotationList.length > 0) {
116
+ this.store.quotationList.sort(
117
+ (a, b) => Number(a.sort ?? a.priority ?? 0) - Number(b.sort ?? b.priority ?? 0)
118
+ );
119
+ return;
120
+ }
121
+ const query = {};
122
+ if (params == null ? void 0 : params.channel)
123
+ query.channel = params.channel;
124
+ if ((params == null ? void 0 : params.channel) === "online_store")
125
+ query.channel = "online-store";
126
+ const res = await this.request.get(
127
+ "/quotation/available",
128
+ query,
129
+ { useCache: false, osServer: true }
130
+ );
131
+ const list = ((_a = res == null ? void 0 : res.data) == null ? void 0 : _a.list) || (res == null ? void 0 : res.list) || [];
132
+ list.sort((a, b) => Number(a.sort ?? a.priority ?? 0) - Number(b.sort ?? b.priority ?? 0));
133
+ this.store.quotationList = list;
134
+ await this.core.effects.emit(import_types.QuotationHooks.onQuotationLoaded, this.store.quotationList);
135
+ }
136
+ setScheduleResolver(resolver) {
137
+ this.scheduleResolver = resolver;
138
+ }
139
+ getPriceForProduct(params) {
140
+ var _a;
141
+ return ((_a = this.getQuotationMatchForProduct(params)) == null ? void 0 : _a.price) ?? null;
142
+ }
143
+ getQuotationMatchForProduct(params) {
144
+ const { productId, variantId, datetime, channel, customer_id } = params;
145
+ for (const quotation of this.store.quotationList) {
146
+ if (!this.isQuotationVisibleForChannel(quotation, channel))
147
+ continue;
148
+ if (!this.isQuotationVisibleForCustomer(quotation, customer_id))
149
+ continue;
150
+ if (!this.isQuotationActiveAt(quotation, datetime))
151
+ continue;
152
+ const match = this.findProductData(quotation, productId, variantId);
153
+ if (!match)
154
+ continue;
155
+ const value = match.value ?? match.final_price ?? match.discount_value;
156
+ if (Number(value) === 0)
157
+ continue;
158
+ if (value === void 0 || value === null || value === "")
159
+ continue;
160
+ return {
161
+ price: String(value),
162
+ quotationShelfId: Number(quotation.id) || 0
163
+ };
164
+ }
165
+ return null;
166
+ }
167
+ getQuotationShelfId(params) {
168
+ var _a;
169
+ return ((_a = this.getQuotationMatchForProduct(params)) == null ? void 0 : _a.quotationShelfId) ?? 0;
170
+ }
171
+ getQuotationCustomerScopeInfo() {
172
+ const customerById = /* @__PURE__ */ new Map();
173
+ const quotationScopes = this.store.quotationList.filter((quotation) => Array.isArray(quotation == null ? void 0 : quotation.customer) && quotation.customer.length > 0).map((quotation) => {
174
+ const customerIds = [];
175
+ const customers = [];
176
+ const localSeen = /* @__PURE__ */ new Set();
177
+ (quotation.customer || []).forEach((customer) => {
178
+ const customerId = this.normalizeCustomerId(customer == null ? void 0 : customer.id);
179
+ if (!customerId || localSeen.has(customerId))
180
+ return;
181
+ localSeen.add(customerId);
182
+ customerIds.push(customerId);
183
+ customers.push(customer);
184
+ if (!customerById.has(customerId))
185
+ customerById.set(customerId, customer);
186
+ });
187
+ return {
188
+ quotationId: Number(quotation.id),
189
+ quotationName: String(quotation.name || ""),
190
+ customerIds,
191
+ customers
192
+ };
193
+ });
194
+ const result = {
195
+ quotationCount: this.store.quotationList.length,
196
+ hasCustomerScopedQuotations: quotationScopes.length > 0,
197
+ allQuotationsCustomerScoped: this.store.quotationList.length > 0 && quotationScopes.length === this.store.quotationList.length,
198
+ customerIds: Array.from(customerById.keys()),
199
+ customers: Array.from(customerById.values()),
200
+ quotationScopes
201
+ };
202
+ return result;
203
+ }
98
204
  /**
99
205
  * 清空缓存
100
206
  */
@@ -110,6 +216,78 @@ var QuotationModule = class extends import_BaseModule.BaseModule {
110
216
  }
111
217
  console.log("[Quotation] 缓存已清空");
112
218
  }
219
+ normalizeCustomerId(customerId) {
220
+ if (customerId === void 0 || customerId === null || customerId === "")
221
+ return null;
222
+ return String(customerId);
223
+ }
224
+ hasValidCustomerId(customerId) {
225
+ if (customerId === void 0 || customerId === null || customerId === "")
226
+ return false;
227
+ return Number(customerId) > 0;
228
+ }
229
+ isQuotationVisibleForCustomer(quotation, customerId) {
230
+ const customers = quotation.customer || [];
231
+ if (!Array.isArray(customers) || customers.length === 0)
232
+ return true;
233
+ if (!this.hasValidCustomerId(customerId))
234
+ return false;
235
+ return customers.some((customer) => String(customer.id) === String(customerId));
236
+ }
237
+ isQuotationVisibleForChannel(quotation, channel) {
238
+ if (!channel)
239
+ return true;
240
+ const normalizedChannel = channel === "online_store" ? "online-store" : channel;
241
+ const channels = quotation.channel;
242
+ if (!Array.isArray(channels) || channels.length === 0)
243
+ return true;
244
+ return channels.some((item) => String(item) === normalizedChannel);
245
+ }
246
+ isQuotationActiveAt(quotation, datetime) {
247
+ const quotationSchedules = Array.isArray(quotation.schedule) ? quotation.schedule : [];
248
+ const scheduleIds = Array.isArray(quotation.schedule_ids) ? quotation.schedule_ids : [];
249
+ if (!quotationSchedules.length && !scheduleIds.length)
250
+ return false;
251
+ const scheduleItems = [];
252
+ quotationSchedules.forEach((schedule) => {
253
+ var _a;
254
+ const full = (_a = this.scheduleResolver) == null ? void 0 : _a.call(this, schedule.id);
255
+ if (full) {
256
+ scheduleItems.push(full);
257
+ return;
258
+ }
259
+ if (this.scheduleResolver)
260
+ return;
261
+ scheduleItems.push({
262
+ ...schedule,
263
+ repeat_type: schedule.repeat_type || "none",
264
+ repeat_rule: schedule.repeat_rule || null,
265
+ time_slot: schedule.time_slot || []
266
+ });
267
+ });
268
+ scheduleIds.forEach((id) => {
269
+ var _a;
270
+ const full = (_a = this.scheduleResolver) == null ? void 0 : _a.call(this, id);
271
+ if (full)
272
+ scheduleItems.push(full);
273
+ });
274
+ if (!scheduleItems.length)
275
+ return false;
276
+ return (0, import_getDateIsInSchedule.getDateIsInSchedule)(datetime, scheduleItems);
277
+ }
278
+ findProductData(quotation, productId, variantId) {
279
+ const productData = Array.isArray(quotation.product_data) ? quotation.product_data : Array.isArray(quotation.product_prices) ? quotation.product_prices : [];
280
+ if (variantId && variantId !== 0) {
281
+ const variantMatch = productData.find(
282
+ (item) => Number(item.product_id) === productId && Number(item.variant_id || 0) === variantId
283
+ );
284
+ if (variantMatch)
285
+ return variantMatch;
286
+ }
287
+ return productData.find(
288
+ (item) => Number(item.product_id) === productId && Number(item.variant_id || 0) === 0
289
+ );
290
+ }
113
291
  /**
114
292
  * 从 IndexDB 加载报价单数据
115
293
  * @private
@@ -12,6 +12,7 @@ export declare class ScheduleModuleEx extends BaseModule implements Module, Sche
12
12
  private store;
13
13
  private dbManager;
14
14
  private logger;
15
+ private hasLoadedScheduleList;
15
16
  constructor(name?: string, version?: string);
16
17
  /**
17
18
  * 记录信息日志
@@ -41,6 +42,7 @@ export declare class ScheduleModuleEx extends BaseModule implements Module, Sche
41
42
  * 获取所有日程列表
42
43
  */
43
44
  getScheduleList(): ScheduleItem[];
45
+ isScheduleListLoaded(): boolean;
44
46
  /**
45
47
  * 根据 schedule IDs 获取日程数据
46
48
  * 使用 Map 快速查询,时间复杂度 O(m),m 为 ids 数量