@pisell/pisellos 2.2.190 → 2.2.192

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 (67) hide show
  1. package/dist/model/strategy/adapter/promotion/index.js +0 -9
  2. package/dist/modules/BookingContext/utils/buildCacheItemFromOrderLine.js +2 -1
  3. package/dist/modules/BookingContext/utils/buildNormalProductCacheItemFromOrderLine.js +2 -7
  4. package/dist/modules/Cart/utils/cartProduct.js +13 -2
  5. package/dist/modules/Order/index.d.ts +1 -3
  6. package/dist/modules/Order/index.js +69 -95
  7. package/dist/modules/Order/types.d.ts +9 -6
  8. package/dist/modules/Order/utils.d.ts +9 -2
  9. package/dist/modules/Order/utils.js +36 -13
  10. package/dist/modules/SalesSummary/types.d.ts +3 -1
  11. package/dist/modules/SalesSummary/utils.js +7 -3
  12. package/dist/server/modules/order/index.d.ts +60 -7
  13. package/dist/server/modules/order/index.js +670 -178
  14. package/dist/server/modules/order/types.d.ts +0 -2
  15. package/dist/server/modules/products/index.d.ts +33 -6
  16. package/dist/server/modules/products/index.js +528 -231
  17. package/dist/solution/BaseSales/index.js +13 -7
  18. package/dist/solution/BaseSales/types.d.ts +8 -5
  19. package/dist/solution/BaseSales/types.js +2 -3
  20. package/dist/solution/BaseSales/utils/cartPromotion.js +7 -7
  21. package/dist/solution/BaseSales/utils/transformBaseProductToOrderProduct.d.ts +1 -0
  22. package/dist/solution/BaseSales/utils/transformBaseProductToOrderProduct.js +16 -13
  23. package/dist/solution/BaseSales/utils.d.ts +1 -1
  24. package/dist/solution/BaseSales/utils.js +22 -19
  25. package/dist/solution/BookingByStep/index.d.ts +1 -1
  26. package/dist/solution/BookingTicket/index.d.ts +1 -1
  27. package/dist/solution/ScanOrder/index.js +7 -5
  28. package/dist/solution/ScanOrder/types.d.ts +8 -5
  29. package/dist/solution/ScanOrder/types.js +2 -3
  30. package/dist/solution/ScanOrder/utils.d.ts +1 -1
  31. package/dist/solution/ScanOrder/utils.js +26 -23
  32. package/dist/solution/VenueBooking/index.js +5 -3
  33. package/dist/solution/VenueBooking/sales-section-4-annotated.json +0 -2
  34. package/lib/model/strategy/adapter/promotion/index.js +0 -49
  35. package/lib/modules/BookingContext/utils/buildCacheItemFromOrderLine.js +2 -1
  36. package/lib/modules/BookingContext/utils/buildNormalProductCacheItemFromOrderLine.js +2 -1
  37. package/lib/modules/Cart/utils/cartProduct.js +9 -2
  38. package/lib/modules/Order/index.d.ts +1 -3
  39. package/lib/modules/Order/index.js +45 -58
  40. package/lib/modules/Order/types.d.ts +9 -6
  41. package/lib/modules/Order/utils.d.ts +9 -2
  42. package/lib/modules/Order/utils.js +41 -10
  43. package/lib/modules/SalesSummary/types.d.ts +3 -1
  44. package/lib/modules/SalesSummary/utils.js +5 -3
  45. package/lib/server/modules/order/index.d.ts +60 -7
  46. package/lib/server/modules/order/index.js +350 -40
  47. package/lib/server/modules/order/types.d.ts +0 -2
  48. package/lib/server/modules/products/index.d.ts +33 -6
  49. package/lib/server/modules/products/index.js +159 -30
  50. package/lib/solution/BaseSales/index.js +14 -5
  51. package/lib/solution/BaseSales/types.d.ts +8 -5
  52. package/lib/solution/BaseSales/utils/cartPromotion.js +6 -6
  53. package/lib/solution/BaseSales/utils/transformBaseProductToOrderProduct.d.ts +1 -0
  54. package/lib/solution/BaseSales/utils/transformBaseProductToOrderProduct.js +21 -14
  55. package/lib/solution/BaseSales/utils.d.ts +1 -1
  56. package/lib/solution/BaseSales/utils.js +11 -7
  57. package/lib/solution/BookingByStep/index.d.ts +1 -1
  58. package/lib/solution/BookingTicket/index.d.ts +1 -1
  59. package/lib/solution/ScanOrder/index.js +4 -4
  60. package/lib/solution/ScanOrder/types.d.ts +8 -5
  61. package/lib/solution/ScanOrder/utils.d.ts +1 -1
  62. package/lib/solution/ScanOrder/utils.js +12 -8
  63. package/lib/solution/VenueBooking/index.js +2 -2
  64. package/lib/solution/VenueBooking/sales-section-4-annotated.json +0 -2
  65. package/package.json +1 -1
  66. package/dist/solution/Checkout/appointmentDemo.json +0 -1
  67. package/lib/solution/Checkout/appointmentDemo.json +0 -1
@@ -28,6 +28,7 @@ var import_types = require("./types");
28
28
  var import_product = require("../../utils/product");
29
29
  var INDEXDB_STORE_NAME = "products";
30
30
  var PRODUCT_SYNC_DEBOUNCE_MS = 1e4;
31
+ var PRODUCT_SILENT_REFRESH_MIN_INTERVAL_MS = 3e4;
31
32
  var ProductsModule = class extends import_BaseModule.BaseModule {
32
33
  constructor(name, version) {
33
34
  super(name, version);
@@ -46,6 +47,12 @@ var ProductsModule = class extends import_BaseModule.BaseModule {
46
47
  this.isPriceFormatterRegistered = false;
47
48
  this.quotationBridgeLoadedKey = "";
48
49
  this.pendingSyncMessages = [];
50
+ /** 最近一次 SSE 全量拉取完成时间 */
51
+ this.lastServerFullFetchAtMs = 0;
52
+ /** 最近一次 preload 完成时间(含 IndexDB 命中) */
53
+ this.lastPreloadCompletedAtMs = 0;
54
+ /** IndexDB 写入串行队列,避免快照与 patch 并发交错 */
55
+ this.productIndexDBSaveQueue = Promise.resolve();
49
56
  }
50
57
  async initialize(core, options) {
51
58
  var _a;
@@ -714,7 +721,8 @@ var ProductsModule = class extends import_BaseModule.BaseModule {
714
721
  productCount: list.length,
715
722
  duration: `${Math.round(performance.now() - t0)}ms`
716
723
  });
717
- await this.saveProductsToIndexDB(list);
724
+ await this.replaceProductsSnapshotInIndexDB(list, "loadProductsByServer");
725
+ this.lastServerFullFetchAtMs = Date.now();
718
726
  await this.core.effects.emit(import_types.ProductsHooks.onProductsLoaded, list);
719
727
  (0, import_product.perfMark)("loadProductsByServer", performance.now() - t0, { count: list.length });
720
728
  return list;
@@ -813,7 +821,8 @@ var ProductsModule = class extends import_BaseModule.BaseModule {
813
821
  async loadProductsByServerHttp(params) {
814
822
  const productList = await this.fetchProductsByHttp(params);
815
823
  if (productList.length > 0) {
816
- await this.saveProductsToIndexDB(productList);
824
+ await this.replaceProductsSnapshotInIndexDB(productList, "loadProductsByServerHttp");
825
+ this.lastServerFullFetchAtMs = Date.now();
817
826
  await this.core.effects.emit(import_types.ProductsHooks.onProductsLoaded, productList);
818
827
  }
819
828
  return productList;
@@ -952,33 +961,86 @@ var ProductsModule = class extends import_BaseModule.BaseModule {
952
961
  }
953
962
  }
954
963
  /**
955
- * 保存商品数据到 IndexDB(平铺存储)
956
- * @private
964
+ * 串行执行商品 IndexDB 写入,避免快照替换与增量 patch 并发交错。
965
+ *
966
+ * @example
967
+ * await this.runInProductIndexDBSaveQueue(async () => {
968
+ * await this.dbManager.bulkUpdate(INDEXDB_STORE_NAME, products)
969
+ * })
957
970
  */
958
- async saveProductsToIndexDB(products) {
971
+ async runInProductIndexDBSaveQueue(task) {
972
+ const queuedTask = this.productIndexDBSaveQueue.then(task, task);
973
+ this.productIndexDBSaveQueue = queuedTask.then(() => void 0, () => void 0);
974
+ return queuedTask;
975
+ }
976
+ /**
977
+ * 全量快照写入 IndexDB:clear + bulkUpdate,仅用于 SSE/HTTP 全量拉取。
978
+ *
979
+ * @example
980
+ * await this.replaceProductsSnapshotInIndexDB(list, 'loadProductsByServer')
981
+ */
982
+ async replaceProductsSnapshotInIndexDB(products, source) {
959
983
  if (!this.dbManager) {
960
- this.logWarning("saveProductsToIndexDB: dbManager 不可用");
984
+ this.logWarning("replaceProductsSnapshotInIndexDB: dbManager 不可用");
961
985
  return;
962
986
  }
963
- this.logInfo("开始保存商品数据到 IndexDB", { productCount: products.length });
964
987
  try {
965
- const t0 = performance.now();
966
- await this.dbManager.clear(INDEXDB_STORE_NAME);
967
- await this.dbManager.bulkUpdate(INDEXDB_STORE_NAME, products);
968
- (0, import_product.perfMark)("saveProductsToIndexDB", performance.now() - t0, { count: products.length });
969
- console.log(
970
- `[Products] 已将 ${products.length} 个商品平铺保存到 IndexDB`
971
- );
972
- this.logInfo("商品数据已保存到 IndexDB", { productCount: products.length });
988
+ await this.runInProductIndexDBSaveQueue(async () => {
989
+ this.logInfo("replaceProductsSnapshotInIndexDB 开始", { source, productCount: products.length });
990
+ const t0 = performance.now();
991
+ await this.dbManager.clear(INDEXDB_STORE_NAME);
992
+ if (products.length > 0) {
993
+ await this.dbManager.bulkUpdate(INDEXDB_STORE_NAME, products);
994
+ }
995
+ (0, import_product.perfMark)("replaceProductsSnapshotInIndexDB", performance.now() - t0, { count: products.length });
996
+ this.logInfo("replaceProductsSnapshotInIndexDB 完成", { source, productCount: products.length });
997
+ });
973
998
  } catch (error) {
974
999
  const errorMessage = error instanceof Error ? error.message : String(error);
975
- console.error("[Products] 保存数据到 IndexDB 失败:", error);
976
- this.logError("保存数据到 IndexDB 失败", {
1000
+ this.logError("全量保存商品到 IndexDB 失败", {
1001
+ source,
977
1002
  productCount: products.length,
978
1003
  error: errorMessage
979
1004
  });
980
1005
  }
981
1006
  }
1007
+ /**
1008
+ * 增量 upsert 商品到 IndexDB,不清空表;用于 pubsub 批次合并后的变更商品。
1009
+ *
1010
+ * @example
1011
+ * await this.patchProductsInIndexDB(changedProducts, 'pubsub.processProductSyncMessages', mergeActions)
1012
+ */
1013
+ async patchProductsInIndexDB(products, source, mergeActions = {}) {
1014
+ if (!this.dbManager || products.length === 0) {
1015
+ return;
1016
+ }
1017
+ const validProducts = products.filter((p) => (p == null ? void 0 : p.id) !== void 0 && (p == null ? void 0 : p.id) !== null);
1018
+ if (validProducts.length === 0) {
1019
+ return;
1020
+ }
1021
+ try {
1022
+ await this.runInProductIndexDBSaveQueue(async () => {
1023
+ this.logInfo("patchProductsInIndexDB 开始", { source, count: validProducts.length });
1024
+ const t0 = performance.now();
1025
+ if (typeof this.dbManager.bulkUpdate === "function") {
1026
+ await this.dbManager.bulkUpdate(INDEXDB_STORE_NAME, validProducts);
1027
+ } else if (typeof this.dbManager.update === "function") {
1028
+ for (const product of validProducts) {
1029
+ await this.dbManager.update(INDEXDB_STORE_NAME, product);
1030
+ }
1031
+ }
1032
+ (0, import_product.perfMark)("patchProductsInIndexDB", performance.now() - t0, { count: validProducts.length });
1033
+ this.logInfo("patchProductsInIndexDB 完成", { source, count: validProducts.length });
1034
+ });
1035
+ } catch (error) {
1036
+ const errorMessage = error instanceof Error ? error.message : String(error);
1037
+ this.logError("增量保存商品到 IndexDB 失败", {
1038
+ source,
1039
+ error: errorMessage,
1040
+ productCount: validProducts.length
1041
+ });
1042
+ }
1043
+ }
982
1044
  /**
983
1045
  * 同步更新商品 Map 缓存
984
1046
  * 将 list 中的商品同步到 map,以 id 为 key
@@ -1023,6 +1085,7 @@ var ProductsModule = class extends import_BaseModule.BaseModule {
1023
1085
  duration: `${Math.round(performance.now() - tTotal)}ms`,
1024
1086
  source: "IndexDB"
1025
1087
  });
1088
+ this.lastPreloadCompletedAtMs = Date.now();
1026
1089
  return;
1027
1090
  }
1028
1091
  console.log("[Products] IndexDB 中没有缓存数据,从服务器加载...");
@@ -1050,6 +1113,7 @@ var ProductsModule = class extends import_BaseModule.BaseModule {
1050
1113
  duration: `${Math.round(performance.now() - tTotal)}ms`,
1051
1114
  source: "Server"
1052
1115
  });
1116
+ this.lastPreloadCompletedAtMs = Date.now();
1053
1117
  } else {
1054
1118
  (0, import_product.perfMark)("preload(empty)", performance.now() - tTotal, { source: "empty" });
1055
1119
  this.logWarning("预加载完成但未获取到数据", {
@@ -1205,19 +1269,50 @@ var ProductsModule = class extends import_BaseModule.BaseModule {
1205
1269
  if (uniqueDeleteIds.length > 0) {
1206
1270
  await this.removeProductsByIds(uniqueDeleteIds);
1207
1271
  }
1272
+ const patchProductMap = /* @__PURE__ */ new Map();
1273
+ const patchMergeActions = {};
1274
+ let storeMutated = uniqueDeleteIds.length > 0;
1208
1275
  if (bodyUpdates.size > 0) {
1209
- await this.applyBodyUpdatesToStore(bodyUpdates);
1276
+ const bodyResult = await this.applyBodyUpdatesToStore(bodyUpdates, {
1277
+ skipIndexDB: true,
1278
+ skipEmit: true
1279
+ });
1280
+ for (const product of bodyResult.changedProducts) {
1281
+ patchProductMap.set(product.id, product);
1282
+ patchMergeActions[product.id] = bodyResult.mergeActions[product.id];
1283
+ }
1284
+ storeMutated = true;
1210
1285
  }
1286
+ let freshProducts = [];
1211
1287
  if (uniqueSSEIds.length > 0) {
1212
- const freshProducts = await this.fetchProductsBySSE(uniqueSSEIds);
1288
+ freshProducts = await this.fetchProductsBySSE(uniqueSSEIds);
1213
1289
  if (freshProducts.length > 0) {
1214
- await this.mergeProductsToStore(freshProducts);
1290
+ const mergeResult = await this.mergeProductsToStore(freshProducts, {
1291
+ skipIndexDB: true,
1292
+ skipEmit: true
1293
+ });
1294
+ for (const product of mergeResult.changedProducts) {
1295
+ patchProductMap.set(product.id, product);
1296
+ patchMergeActions[product.id] = mergeResult.mergeActions[product.id];
1297
+ }
1298
+ storeMutated = true;
1215
1299
  }
1216
1300
  this.logInfo("processProductSyncMessages: SSE 增量更新完成123", {
1217
1301
  requestedCount: uniqueSSEIds.length,
1218
1302
  receivedCount: freshProducts.length
1219
1303
  });
1220
1304
  }
1305
+ const patchProducts = [...patchProductMap.values()];
1306
+ if (patchProducts.length > 0) {
1307
+ await this.patchProductsInIndexDB(
1308
+ patchProducts,
1309
+ "pubsub.processProductSyncMessages",
1310
+ patchMergeActions
1311
+ );
1312
+ }
1313
+ if (storeMutated) {
1314
+ this.core.effects.emit(import_types.ProductsHooks.onProductsChanged, this.store.list);
1315
+ }
1221
1316
  const allChangedIds = [.../* @__PURE__ */ new Set([
1222
1317
  ...Array.from(bodyUpdates.keys()),
1223
1318
  ...uniqueSSEIds,
@@ -1281,19 +1376,26 @@ var ProductsModule = class extends import_BaseModule.BaseModule {
1281
1376
  /**
1282
1377
  * 将 body 完整数据直接覆盖到本地 store(不调用报价单接口)
1283
1378
  * 已存在的 → 直接替换;不存在的 → 追加
1284
- * 同时更新 Map 缓存、IndexDB,触发 onProductsChanged
1285
- * 价格缓存由 processProductSyncMessages 末尾统一清除
1379
+ *
1380
+ * @example
1381
+ * await this.applyBodyUpdatesToStore(bodyUpdates, { skipIndexDB: true })
1286
1382
  */
1287
- async applyBodyUpdatesToStore(bodyUpdates) {
1383
+ async applyBodyUpdatesToStore(bodyUpdates, options) {
1384
+ const changedProductIds = [...bodyUpdates.keys()];
1288
1385
  this.logInfo("applyBodyUpdatesToStore: 开始", { count: bodyUpdates.size });
1289
1386
  let updatedCount = 0;
1290
1387
  let newCount = 0;
1291
1388
  const appliedIds = /* @__PURE__ */ new Set();
1389
+ const mergeActions = {};
1390
+ const changedProducts = [];
1292
1391
  this.store.list = this.store.list.map((p) => {
1293
1392
  if (bodyUpdates.has(p.id)) {
1294
1393
  updatedCount++;
1295
1394
  appliedIds.add(p.id);
1296
- return bodyUpdates.get(p.id);
1395
+ mergeActions[p.id] = "update";
1396
+ const next = bodyUpdates.get(p.id);
1397
+ changedProducts.push(next);
1398
+ return next;
1297
1399
  }
1298
1400
  return p;
1299
1401
  });
@@ -1301,31 +1403,44 @@ var ProductsModule = class extends import_BaseModule.BaseModule {
1301
1403
  if (!appliedIds.has(id)) {
1302
1404
  this.store.list.push(body);
1303
1405
  newCount++;
1406
+ mergeActions[id] = "insert";
1407
+ changedProducts.push(body);
1304
1408
  }
1305
1409
  }
1306
1410
  this.syncProductsMap();
1307
- await this.saveProductsToIndexDB(this.store.list);
1308
- this.core.effects.emit(import_types.ProductsHooks.onProductsChanged, this.store.list);
1411
+ if (!(options == null ? void 0 : options.skipIndexDB)) {
1412
+ await this.patchProductsInIndexDB(changedProducts, "pubsub.bodyUpdate", mergeActions);
1413
+ }
1414
+ if (!(options == null ? void 0 : options.skipEmit)) {
1415
+ this.core.effects.emit(import_types.ProductsHooks.onProductsChanged, this.store.list);
1416
+ }
1309
1417
  this.logInfo("applyBodyUpdatesToStore: 完成", {
1310
1418
  updatedCount,
1311
1419
  newCount,
1312
1420
  totalCount: this.store.list.length
1313
1421
  });
1422
+ return { changedProducts, mergeActions, updatedCount, newCount };
1314
1423
  }
1315
1424
  /**
1316
1425
  * 将增量拉取的商品合并到 store
1317
1426
  * 已存在的 → 替换;新的 → 追加
1318
- * 同时更新 store.map、IndexDB,触发 onProductsChanged
1427
+ *
1428
+ * @example
1429
+ * await this.mergeProductsToStore(freshProducts, { skipIndexDB: true })
1319
1430
  */
1320
- async mergeProductsToStore(freshProducts) {
1431
+ async mergeProductsToStore(freshProducts, options) {
1321
1432
  const freshMap = /* @__PURE__ */ new Map();
1322
1433
  for (const p of freshProducts) {
1323
1434
  freshMap.set(p.id, p);
1324
1435
  }
1436
+ const mergeActions = {};
1437
+ const changedProducts = [];
1325
1438
  const updatedList = this.store.list.map((p) => {
1326
1439
  if (freshMap.has(p.id)) {
1327
1440
  const fresh = freshMap.get(p.id);
1328
1441
  freshMap.delete(p.id);
1442
+ mergeActions[p.id] = "update";
1443
+ changedProducts.push(fresh);
1329
1444
  return fresh;
1330
1445
  }
1331
1446
  return p;
@@ -1333,17 +1448,25 @@ var ProductsModule = class extends import_BaseModule.BaseModule {
1333
1448
  const newCount = freshMap.size;
1334
1449
  for (const p of freshMap.values()) {
1335
1450
  updatedList.push(p);
1451
+ mergeActions[p.id] = "insert";
1452
+ changedProducts.push(p);
1336
1453
  }
1337
1454
  const updatedCount = freshProducts.length - newCount;
1455
+ const changedProductIds = freshProducts.map((p) => p.id);
1338
1456
  this.store.list = updatedList;
1339
1457
  this.syncProductsMap();
1340
- await this.saveProductsToIndexDB(this.store.list);
1458
+ if (!(options == null ? void 0 : options.skipIndexDB)) {
1459
+ await this.patchProductsInIndexDB(changedProducts, "pubsub.mergeProductsToStore", mergeActions);
1460
+ }
1461
+ if (!(options == null ? void 0 : options.skipEmit)) {
1462
+ this.core.effects.emit(import_types.ProductsHooks.onProductsChanged, this.store.list);
1463
+ }
1341
1464
  this.logInfo("mergeProductsToStore: 合并完成", {
1342
1465
  updatedCount,
1343
1466
  newCount,
1344
1467
  totalCount: this.store.list.length
1345
1468
  });
1346
- this.core.effects.emit(import_types.ProductsHooks.onProductsChanged, this.store.list);
1469
+ return { changedProducts, mergeActions, updatedCount, newCount };
1347
1470
  }
1348
1471
  /**
1349
1472
  * 静默全量刷新:后台重新拉取全量 SSE 数据并更新本地
@@ -1352,6 +1475,12 @@ var ProductsModule = class extends import_BaseModule.BaseModule {
1352
1475
  */
1353
1476
  async silentRefresh() {
1354
1477
  const t0 = performance.now();
1478
+ const now = Date.now();
1479
+ const elapsedSincePreload = now - this.lastPreloadCompletedAtMs;
1480
+ const elapsedSinceFullFetch = now - this.lastServerFullFetchAtMs;
1481
+ if (this.lastPreloadCompletedAtMs > 0 && elapsedSincePreload < PRODUCT_SILENT_REFRESH_MIN_INTERVAL_MS || this.lastServerFullFetchAtMs > 0 && elapsedSinceFullFetch < PRODUCT_SILENT_REFRESH_MIN_INTERVAL_MS) {
1482
+ return this.store.list;
1483
+ }
1355
1484
  this.logInfo("silentRefresh 开始");
1356
1485
  try {
1357
1486
  const products = await this.loadProductsByServer();
@@ -266,7 +266,16 @@ var BaseSalesImpl = class extends import_BaseModule.BaseModule {
266
266
  product_variant_id: product.product_variant_id ?? authoritativeProduct.product_variant_id ?? 0,
267
267
  num: product.num ?? product.quantity ?? authoritativeProduct.num,
268
268
  quantity: product.quantity ?? product.num ?? authoritativeProduct.quantity,
269
- product_option_item: product.product_option_item ?? authoritativeProduct.product_option_item,
269
+ product_sku: (() => {
270
+ const authoritativeSku = (0, import_utils.ensureProductSku)(authoritativeProduct);
271
+ const productSku = (0, import_utils.ensureProductSku)(product);
272
+ const hasProductSku = product.product_sku && typeof product.product_sku === "object";
273
+ return {
274
+ ...authoritativeSku,
275
+ ...productSku,
276
+ option: hasProductSku ? productSku.option : authoritativeSku.option
277
+ };
278
+ })(),
270
279
  product_bundle: productBundle,
271
280
  metadata: product.metadata ?? authoritativeProduct.metadata
272
281
  };
@@ -823,7 +832,7 @@ var BaseSalesImpl = class extends import_BaseModule.BaseModule {
823
832
  (sum, pd) => sum + Number(pd.amount || 0),
824
833
  0
825
834
  );
826
- const optionSum = (0, import_utils.sumOptionUnitPrice)(product.product_option_item);
835
+ const optionSum = (0, import_utils.sumOptionUnitPrice)((0, import_utils.getProductSkuOptions)(product));
827
836
  const sourcePrice = ((_t = product.metadata) == null ? void 0 : _t.source_product_price) ?? (((_u = product.metadata) == null ? void 0 : _u.main_product_original_price) != null ? new import_decimal.default(Number(product.metadata.main_product_original_price) || 0).minus(optionSum).toFixed(2) : product.original_price ?? "0");
828
837
  const newSourceSellingPrice = new import_decimal.default(Number(sourcePrice) || 0).minus(totalPerUnitDiscount).toDecimalPlaces(2).toString();
829
838
  const newMainSellingPrice = new import_decimal.default(Number(newSourceSellingPrice) || 0).plus(optionSum).toDecimalPlaces(2).toFixed(2);
@@ -1002,7 +1011,7 @@ var BaseSalesImpl = class extends import_BaseModule.BaseModule {
1002
1011
  (sum, pd) => sum + (pd.amount || 0),
1003
1012
  0
1004
1013
  );
1005
- const optionSum = (0, import_utils.sumOptionUnitPrice)(product.product_option_item);
1014
+ const optionSum = (0, import_utils.sumOptionUnitPrice)((0, import_utils.getProductSkuOptions)(product));
1006
1015
  const sourcePrice = ((_h = product.metadata) == null ? void 0 : _h.source_product_price) ?? (((_i = product.metadata) == null ? void 0 : _i.main_product_original_price) != null ? new import_decimal.default(Number(product.metadata.main_product_original_price) || 0).minus(optionSum).toFixed(2) : product.original_price ?? "0");
1007
1016
  const newSourceSellingPrice = new import_decimal.default(Number(sourcePrice) || 0).minus(totalPerUnitDiscount).toDecimalPlaces(2).toString();
1008
1017
  const newMainSellingPrice = new import_decimal.default(Number(newSourceSellingPrice) || 0).plus(optionSum).toDecimalPlaces(2).toFixed(2);
@@ -1455,8 +1464,8 @@ var BaseSalesImpl = class extends import_BaseModule.BaseModule {
1455
1464
  if (identity.unique_identification_number !== void 0) {
1456
1465
  params.unique_identification_number = identity.unique_identification_number;
1457
1466
  }
1458
- if (identity.product_option_item !== void 0) {
1459
- params.product_option_item = identity.product_option_item;
1467
+ if (identity.product_sku !== void 0) {
1468
+ params.product_sku = identity.product_sku;
1460
1469
  }
1461
1470
  if (identity.product_bundle !== void 0)
1462
1471
  params.product_bundle = identity.product_bundle;
@@ -41,16 +41,20 @@ export interface BaseSalesOrderProductIdentity {
41
41
  identity_key?: string;
42
42
  unique_identification_number?: string;
43
43
  /** 参与合并/匹配;与 remove 通配语义见 isSkuOnlyDeleteIdentity */
44
- product_option_item?: any[];
44
+ product_sku?: BaseSalesProductSku;
45
45
  product_bundle?: any[];
46
46
  }
47
+ export interface BaseSalesProductSku {
48
+ option: any[];
49
+ [key: string]: any;
50
+ }
47
51
  export interface BaseSalesUpdateOrderProductQuantityParams extends BaseSalesOrderProductIdentity {
48
52
  num: number;
49
53
  }
50
54
  export interface BaseSalesOrderProduct extends BaseSalesOrderProductIdentity {
51
55
  order_detail_id: number | null;
52
56
  num: number;
53
- product_option_item: any[];
57
+ product_sku: BaseSalesProductSku;
54
58
  /**
55
59
  * 券后 **行 composite 单价** = `metadata.main_product_selling_price`
56
60
  * + Σ((bundle.bundle_selling_price ?? bundle.price) × (bundle.num ?? 1))
@@ -96,9 +100,8 @@ export interface BaseSalesOrderProduct extends BaseSalesOrderProductIdentity {
96
100
  * 出站 payload 版本的商品行。
97
101
  *
98
102
  * 与 tempOrder 内部 `BaseSalesOrderProduct` 的字段差异:
99
- * - `product_option_item[*]`:内部为 `{ option_group_item_id, option_group_id, num, price, ... }`,
100
- * 出站保留 `{ option_group_item_id, option_group_id, num }`
101
- * (后端 checkout 协议;丢弃 `price` 等运行时辅助字段)。
103
+ * - `product_sku.option[*]`:内部直接维护统一 Sales 协议字段;
104
+ * 提交时仅规范 `add_price`,保留传入/恢复得到的展示字段。
102
105
  * - `product_bundle[*].option[*]`:同上重命名规则。
103
106
  *
104
107
  * 运行时(UI 显示、加购合并、指纹、持久化)与提交边界统一消费字段名
@@ -48,7 +48,7 @@ var import_utils = require("../../../modules/Order/utils");
48
48
  var import_utils2 = require("../../ScanOrder/utils");
49
49
  function getOrderProductBasePrice(product) {
50
50
  const metadata = (product == null ? void 0 : product.metadata) || {};
51
- const optionSum = (0, import_utils.sumOptionUnitPrice)(product == null ? void 0 : product.product_option_item).toNumber();
51
+ const optionSum = (0, import_utils.sumOptionUnitPrice)((0, import_utils.getProductSkuOptions)(product)).toNumber();
52
52
  if (metadata.source_product_price !== void 0) {
53
53
  const v2 = Number(metadata.source_product_price);
54
54
  return Number.isFinite(v2) ? v2 : 0;
@@ -63,7 +63,7 @@ function getOrderProductBasePrice(product) {
63
63
  function getOrderProductOriginalPriceForPromotion(product) {
64
64
  var _a;
65
65
  const metadata = (product == null ? void 0 : product.metadata) || {};
66
- const optionSum = (0, import_utils.sumOptionUnitPrice)(product == null ? void 0 : product.product_option_item).toNumber();
66
+ const optionSum = (0, import_utils.sumOptionUnitPrice)((0, import_utils.getProductSkuOptions)(product)).toNumber();
67
67
  if (metadata.main_product_original_price !== void 0 && metadata.main_product_original_price !== null && metadata.main_product_original_price !== "") {
68
68
  const v = Number(metadata.main_product_original_price) - optionSum;
69
69
  if (Number.isFinite(v) && v >= 0)
@@ -101,7 +101,7 @@ function updatePromotionDiscountList(item, promotionDiscount) {
101
101
  var X_ITEMS_FOR_Y_PRICE = "X_ITEMS_FOR_Y_PRICE";
102
102
  function applyPromoUnitPriceToLine(item, input) {
103
103
  const metadata = item.metadata || (item.metadata = {});
104
- const optionSum = (0, import_utils.sumOptionUnitPrice)(item.product_option_item).toNumber();
104
+ const optionSum = (0, import_utils.sumOptionUnitPrice)((0, import_utils.getProductSkuOptions)(item)).toNumber();
105
105
  const promoUnitPrice = new import_decimal.default(Number(input.promoUnitPrice) || 0).toDecimalPlaces(2).toFixed(2);
106
106
  const mainSelling = new import_decimal.default(Number(promoUnitPrice) || 0).plus(optionSum).toDecimalPlaces(2).toFixed(2);
107
107
  const catalogSource = new import_decimal.default(input.originalBasePrice).toDecimalPlaces(2).toFixed(2);
@@ -169,7 +169,7 @@ function mergeIdenticalPromotionCartLines(products) {
169
169
  continue;
170
170
  }
171
171
  const fingerprint = (0, import_utils2.buildProductLineFingerprint)(
172
- line.product_option_item,
172
+ (0, import_utils.getProductSkuOptions)(line),
173
173
  line.product_bundle
174
174
  );
175
175
  const unitPrice = String(
@@ -340,7 +340,7 @@ function getProductBundleForEvaluator(product) {
340
340
  function buildConsolidateKeyForPromotion(item) {
341
341
  var _a;
342
342
  const fingerprint = (0, import_utils2.buildProductLineFingerprint)(
343
- item.product_option_item,
343
+ (0, import_utils.getProductSkuOptions)(item),
344
344
  item.product_bundle
345
345
  );
346
346
  const bookingUid = (item == null ? void 0 : item.booking_uid) ?? ((_a = item == null ? void 0 : item.metadata) == null ? void 0 : _a.booking_uid);
@@ -795,7 +795,7 @@ function processCartPromotion(list, evaluator, options) {
795
795
  strategyTitle: (_b = priced.strategyMetadata) == null ? void 0 : _b.name
796
796
  });
797
797
  } else {
798
- const optionSum = (0, import_utils.sumOptionUnitPrice)(newItem.product_option_item).toNumber();
798
+ const optionSum = (0, import_utils.sumOptionUnitPrice)((0, import_utils.getProductSkuOptions)(newItem)).toNumber();
799
799
  const sourceRestore = new import_decimal.default(originalBasePrice).toDecimalPlaces(2).toFixed(2);
800
800
  const mainRestore = new import_decimal.default(Number(sourceRestore) || 0).plus(optionSum).toDecimalPlaces(2).toFixed(2);
801
801
  metadata.source_product_price = sourceRestore;
@@ -23,6 +23,7 @@ export interface BaseProductDetailPayload {
23
23
  option?: BaseProductDetailOptionItem[];
24
24
  options?: BaseProductDetailOptionItem[];
25
25
  product_option_item?: BaseProductDetailOptionItem[];
26
+ product_sku?: Record<string, any>;
26
27
  bundle?: BaseProductDetailBundleItem[];
27
28
  product_bundle?: BaseProductDetailBundleItem[];
28
29
  unique?: string;
@@ -54,13 +54,15 @@ function normalizeOptionItems(optionItems) {
54
54
  if (!Array.isArray(optionItems))
55
55
  return [];
56
56
  return optionItems.map((optionItem) => {
57
- const optionGroupItemId = toNumber(optionItem == null ? void 0 : optionItem.id);
57
+ const optionGroupItemId = toNumber((optionItem == null ? void 0 : optionItem.option_group_item_id) ?? (optionItem == null ? void 0 : optionItem.id));
58
58
  const price = (optionItem == null ? void 0 : optionItem.price) ?? (optionItem == null ? void 0 : optionItem.add_price) ?? 0;
59
59
  return {
60
+ ...optionItem,
60
61
  option_group_item_id: optionGroupItemId,
61
62
  option_group_id: toNumber(optionItem == null ? void 0 : optionItem.option_group_id),
62
63
  num: toSafePositiveInt((optionItem == null ? void 0 : optionItem.num) ?? (optionItem == null ? void 0 : optionItem.quantity), 1),
63
- price: toNumber(price, 0)
64
+ add_price: toNumber(price, 0),
65
+ price: void 0
64
66
  };
65
67
  });
66
68
  }
@@ -89,7 +91,7 @@ function findVariantById(origin, variantId) {
89
91
  return variants.find((variant) => Number(variant == null ? void 0 : variant.id) === variantId) || null;
90
92
  }
91
93
  function transformBaseProductToOrderProduct(params) {
92
- var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k;
94
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l;
93
95
  const { payload, fallbackProductId = null } = params;
94
96
  if (!payload || typeof payload !== "object")
95
97
  return null;
@@ -118,8 +120,10 @@ function transformBaseProductToOrderProduct(params) {
118
120
  sourceProductPrice
119
121
  );
120
122
  const hasPriceOverride = payload.price_override !== void 0 && payload.price_override !== null && payload.price_override !== "";
121
- const productOptionItem = normalizeOptionItems(
122
- payload.option ?? payload.options ?? payload.product_option_item
123
+ const productOptionItem = (0, import_utils.normalizeProductSkuOptions)(
124
+ normalizeOptionItems(
125
+ ((_d = payload.product_sku) == null ? void 0 : _d.option) ?? payload.option ?? payload.options ?? payload.product_option_item
126
+ )
123
127
  );
124
128
  const productBundle = normalizeBundleItems(
125
129
  payload.bundle ?? payload.product_bundle,
@@ -129,19 +133,19 @@ function transformBaseProductToOrderProduct(params) {
129
133
  const resolvedQuantity = toSafePositiveInt(payload.quantity ?? payload.num, 1);
130
134
  const originTotal = (0, import_manualProductDiscount.resolveManualDiscountOriginTotal)({
131
135
  originTotal: payload.origin_total,
132
- extendOriginTotal: (_d = payload._extend) == null ? void 0 : _d.origin_total,
133
- originExtendOriginTotal: (_e = origin == null ? void 0 : origin._extend) == null ? void 0 : _e.origin_total,
134
- sourceExtendOriginTotal: (_f = sourceProduct == null ? void 0 : sourceProduct._extend) == null ? void 0 : _f.origin_total,
136
+ extendOriginTotal: (_e = payload._extend) == null ? void 0 : _e.origin_total,
137
+ originExtendOriginTotal: (_f = origin == null ? void 0 : origin._extend) == null ? void 0 : _f.origin_total,
138
+ sourceExtendOriginTotal: (_g = sourceProduct == null ? void 0 : sourceProduct._extend) == null ? void 0 : _g.origin_total,
135
139
  catalogUnitPrice: (origin == null ? void 0 : origin.base_price) ?? (origin == null ? void 0 : origin.price) ?? (sourceProduct == null ? void 0 : sourceProduct.base_price) ?? (sourceProduct == null ? void 0 : sourceProduct.price),
136
140
  quantity: resolvedQuantity
137
141
  });
138
142
  const sellingTotal = Number(
139
- payload.line_total ?? payload.total ?? ((_g = payload._extend) == null ? void 0 : _g.total) ?? lineCompositeTotal
143
+ payload.line_total ?? payload.total ?? ((_h = payload._extend) == null ? void 0 : _h.total) ?? lineCompositeTotal
140
144
  );
141
145
  const discountReason = (0, import_manualProductDiscount.resolveManualDiscountReasonFromSources)({
142
146
  discountReason: payload.discount_reason,
143
- extendDiscountReason: (_h = payload._extend) == null ? void 0 : _h.discount_reason,
144
- originExtendDiscountReason: (_i = origin == null ? void 0 : origin._extend) == null ? void 0 : _i.discount_reason
147
+ extendDiscountReason: (_i = payload._extend) == null ? void 0 : _i.discount_reason,
148
+ originExtendDiscountReason: (_j = origin == null ? void 0 : origin._extend) == null ? void 0 : _j.discount_reason
145
149
  });
146
150
  const lineOriginalPrice = hasPriceOverride && Number.isFinite(originTotal) ? toPriceString(originTotal) : lineCompositeTotal;
147
151
  const manualDiscountList = (0, import_manualProductDiscount.shouldBuildManualProductDiscount)({
@@ -155,8 +159,8 @@ function transformBaseProductToOrderProduct(params) {
155
159
  sellingTotal: Number.isFinite(sellingTotal) ? sellingTotal : Number(lineCompositeTotal),
156
160
  quantity: resolvedQuantity,
157
161
  message: discountReason,
158
- discountway: (_j = payload._extend) == null ? void 0 : _j.discountway,
159
- discount_per: (_k = payload._extend) == null ? void 0 : _k.discount_per
162
+ discountway: (_k = payload._extend) == null ? void 0 : _k.discountway,
163
+ discount_per: (_l = payload._extend) == null ? void 0 : _l.discount_per
160
164
  })
161
165
  ) : payload.discount_list || [];
162
166
  const optionSum = (0, import_utils.sumOptionUnitPrice)(productOptionItem).toNumber();
@@ -195,7 +199,10 @@ function transformBaseProductToOrderProduct(params) {
195
199
  product_variant_id: productVariantId,
196
200
  unique_identification_number: uniqueIdentificationNumber ? String(uniqueIdentificationNumber) : void 0,
197
201
  num: resolvedQuantity,
198
- product_option_item: productOptionItem,
202
+ product_sku: {
203
+ ...payload.product_sku && typeof payload.product_sku === "object" ? payload.product_sku : {},
204
+ option: productOptionItem
205
+ },
199
206
  product_bundle: productBundle,
200
207
  discount_list: manualDiscountList,
201
208
  // 折后行价由 normalizeOrderProduct 按 main + bundle 合成;手动改价时 bundle 已为分摊后单价
@@ -12,7 +12,7 @@ export declare function buildProductLineFingerprint(productOptionItem?: unknown,
12
12
  export declare function getSafeProductNum(num?: number): number;
13
13
  /**
14
14
  * removeProductFromOrder 仅传 SKU 时的通配 identity(对象上未声明选项键)。
15
- * 与显式 `product_option_item: []` 区分:后者只匹配「无选项」行。
15
+ * 与显式 `product_sku: { option: [] }` 区分:后者只匹配「无选项」行。
16
16
  */
17
17
  export declare function isSkuOnlyDeleteIdentity(x: BaseSalesOrderProductIdentity): boolean;
18
18
  /**
@@ -65,7 +65,7 @@ function buildProductLineFingerprint(productOptionItem, productBundle) {
65
65
  option_group_item_id: Number(item == null ? void 0 : item.option_group_item_id) || 0,
66
66
  option_group_id: Number(item == null ? void 0 : item.option_group_id) || 0,
67
67
  num: typeof (item == null ? void 0 : item.num) === "number" && !Number.isNaN(item.num) ? Math.max(1, Math.floor(item.num)) : 1,
68
- price: String((item == null ? void 0 : item.price) ?? "")
68
+ price: String((item == null ? void 0 : item.add_price) ?? (item == null ? void 0 : item.price) ?? "")
69
69
  })).sort((p, q) => {
70
70
  if (p.option_group_item_id !== q.option_group_item_id) {
71
71
  return p.option_group_item_id - q.option_group_item_id;
@@ -100,10 +100,10 @@ function getSafeProductNum(num) {
100
100
  function isSkuOnlyDeleteIdentity(x) {
101
101
  if (x.identity_key)
102
102
  return false;
103
- return !("product_option_item" in x) && !("product_bundle" in x);
103
+ return !("product_sku" in x) && !("product_bundle" in x);
104
104
  }
105
105
  function normalizeOrderProduct(product) {
106
- var _a, _b, _c, _d, _e, _f;
106
+ var _a, _b, _c, _d, _e, _f, _g;
107
107
  const metadata = { ...product.metadata || {} };
108
108
  const resolvedIdentityKey = ((_a = product.metadata) == null ? void 0 : _a.unique_identification_number) ? String(product.metadata.unique_identification_number) : product.unique_identification_number && String(product.unique_identification_number).length > 0 ? String(product.unique_identification_number) : product.identity_key && String(product.identity_key).length > 0 ? String(product.identity_key) : (0, import_utils.createUuidV4)();
109
109
  metadata.unique_identification_number = resolvedIdentityKey;
@@ -142,12 +142,16 @@ function normalizeOrderProduct(product) {
142
142
  price_type: item.price_type ?? item.custom_price_type ?? "",
143
143
  price_type_ext: item.price_type_ext ?? item.custom_price_type_ext ?? ""
144
144
  }));
145
- const normalizedOptions = product.product_option_item || [];
145
+ const normalizedOptions = (0, import_utils.normalizeProductSkuOptions)((_e = product.product_sku) == null ? void 0 : _e.option);
146
146
  const optionSum = (0, import_utils.sumOptionUnitPrice)(normalizedOptions);
147
+ const productSku = {
148
+ ...(0, import_utils.ensureProductSku)(product),
149
+ option: normalizedOptions
150
+ };
147
151
  const isV2 = metadata.price_schema_version === 2 || metadata.price_schema_version === "2";
148
152
  const variantId = Number(product.product_variant_id || 0);
149
- const variantList = variantId ? (_e = metadata == null ? void 0 : metadata.origin) == null ? void 0 : _e.variant : null;
150
- const variantPrice = Array.isArray(variantList) ? (_f = variantList.find((v) => Number(v == null ? void 0 : v.id) === variantId)) == null ? void 0 : _f.price : void 0;
153
+ const variantList = variantId ? (_f = metadata == null ? void 0 : metadata.origin) == null ? void 0 : _f.variant : null;
154
+ const variantPrice = Array.isArray(variantList) ? (_g = variantList.find((v) => Number(v == null ? void 0 : v.id) === variantId)) == null ? void 0 : _g.price : void 0;
151
155
  const resolvedSource = (() => {
152
156
  var _a2, _b2;
153
157
  if (metadata.source_product_price !== void 0) {
@@ -219,7 +223,7 @@ function normalizeOrderProduct(product) {
219
223
  product_id: product.product_id,
220
224
  num: getSafeProductNum(product.num),
221
225
  product_variant_id: product.product_variant_id,
222
- product_option_item: normalizedOptions,
226
+ product_sku: productSku,
223
227
  selling_price: composedSellingPrice,
224
228
  original_price: finalOriginalPrice,
225
229
  tax_fee: product.tax_fee || "0.00",
@@ -311,7 +311,7 @@ export declare class BookingByStepImpl extends BaseModule implements Module {
311
311
  date: string;
312
312
  status: string;
313
313
  week: string;
314
- weekNum: 0 | 5 | 1 | 4 | 2 | 3 | 6;
314
+ weekNum: 0 | 2 | 1 | 5 | 3 | 4 | 6;
315
315
  }[]>;
316
316
  submitTimeSlot(timeSlots: TimeSliceItem): void;
317
317
  private getScheduleDataByIds;
@@ -279,7 +279,7 @@ export declare class BookingTicketImpl extends BaseSalesImpl implements Module {
279
279
  * 获取当前的客户搜索条件
280
280
  * @returns 当前搜索条件
281
281
  */
282
- getCurrentCustomerSearchParams(): Omit<import("../../modules").ShopGetCustomerListParams, "skip" | "num">;
282
+ getCurrentCustomerSearchParams(): Omit<import("../../modules").ShopGetCustomerListParams, "num" | "skip">;
283
283
  /**
284
284
  * 获取客户列表状态(包含滚动加载相关状态)
285
285
  * @returns 客户状态