@pisell/pisellos 0.0.509 → 0.0.510

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.
@@ -1,10 +1,10 @@
1
- function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
2
- function _createForOfIteratorHelper(o, allowArrayLike) { var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"]; if (!it) { if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { if (it) o = it; var i = 0; var F = function F() {}; return { s: F, n: function n() { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }, e: function e(_e) { throw _e; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var normalCompletion = true, didErr = false, err; return { s: function s() { it = it.call(o); }, n: function n() { var step = it.next(); normalCompletion = step.done; return step; }, e: function e(_e2) { didErr = true; err = _e2; }, f: function f() { try { if (!normalCompletion && it.return != null) it.return(); } finally { if (didErr) throw err; } } }; }
3
1
  function _toConsumableArray(arr) { return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread(); }
4
2
  function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
5
- function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
6
3
  function _iterableToArray(iter) { if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) return Array.from(iter); }
7
4
  function _arrayWithoutHoles(arr) { if (Array.isArray(arr)) return _arrayLikeToArray(arr); }
5
+ function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
6
+ function _createForOfIteratorHelper(o, allowArrayLike) { var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"]; if (!it) { if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { if (it) o = it; var i = 0; var F = function F() {}; return { s: F, n: function n() { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }, e: function e(_e) { throw _e; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var normalCompletion = true, didErr = false, err; return { s: function s() { it = it.call(o); }, n: function n() { var step = it.next(); normalCompletion = step.done; return step; }, e: function e(_e2) { didErr = true; err = _e2; }, f: function f() { try { if (!normalCompletion && it.return != null) it.return(); } finally { if (didErr) throw err; } } }; }
7
+ function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
8
8
  function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; return arr2; }
9
9
  function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
10
10
  function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
@@ -387,6 +387,26 @@ export var OrderModule = /*#__PURE__*/function (_BaseModule) {
387
387
  this.window.localStorage.removeItem(key);
388
388
  return;
389
389
  }
390
+ if (Array.isArray(parsedData.products)) {
391
+ var _iterator = _createForOfIteratorHelper(parsedData.products),
392
+ _step;
393
+ try {
394
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
395
+ var p = _step.value;
396
+ if (!p || _typeof(p) !== 'object') continue;
397
+ if (!Array.isArray(p.product_option_item)) {
398
+ p.product_option_item = [];
399
+ }
400
+ if (!Array.isArray(p.product_bundle)) {
401
+ p.product_bundle = [];
402
+ }
403
+ }
404
+ } catch (err) {
405
+ _iterator.e(err);
406
+ } finally {
407
+ _iterator.f();
408
+ }
409
+ }
390
410
  this.store.tempOrder = parsedData;
391
411
  } catch (_unused2) {
392
412
  var _this$window;
@@ -676,32 +696,23 @@ export var OrderModule = /*#__PURE__*/function (_BaseModule) {
676
696
  key: "removeProductFromOrder",
677
697
  value: function () {
678
698
  var _removeProductFromOrder = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee9(identity) {
679
- var tempOrder, beforeProducts, removedByStrictIdentity;
699
+ var tempOrder;
680
700
  return _regeneratorRuntime().wrap(function _callee9$(_context9) {
681
701
  while (1) switch (_context9.prev = _context9.next) {
682
702
  case 0:
683
703
  tempOrder = this.ensureTempOrder();
684
- beforeProducts = tempOrder.products;
685
704
  tempOrder.products = tempOrder.products.filter(function (item) {
686
705
  return !isIdentityMatch(item, identity);
687
706
  });
688
- removedByStrictIdentity = beforeProducts.length - tempOrder.products.length;
689
- if (removedByStrictIdentity === 0 && identity.identity_key) {
690
- tempOrder.products = tempOrder.products.filter(function (item) {
691
- var isSameProduct = String(item.product_id) === String(identity.product_id) && String(item.product_variant_id) === String(identity.product_variant_id);
692
- var hasNoIdentityKey = !item.identity_key;
693
- return !(isSameProduct && hasNoIdentityKey);
694
- });
695
- }
696
707
  this.applyDiscount();
697
- _context9.next = 8;
708
+ _context9.next = 5;
698
709
  return this.recalculateSummary({
699
710
  createIfMissing: true
700
711
  });
701
- case 8:
712
+ case 5:
702
713
  this.persistTempOrder();
703
714
  return _context9.abrupt("return", tempOrder.products);
704
- case 10:
715
+ case 7:
705
716
  case "end":
706
717
  return _context9.stop();
707
718
  }
@@ -1135,46 +1146,46 @@ export var OrderModule = /*#__PURE__*/function (_BaseModule) {
1135
1146
  */
1136
1147
  function populateSavedAmounts(productList, discountList) {
1137
1148
  var savedMap = new Map();
1138
- var _iterator = _createForOfIteratorHelper(productList),
1139
- _step;
1149
+ var _iterator2 = _createForOfIteratorHelper(productList),
1150
+ _step2;
1140
1151
  try {
1141
- for (_iterator.s(); !(_step = _iterator.n()).done;) {
1142
- var product = _step.value;
1152
+ for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
1153
+ var product = _step2.value;
1143
1154
  var qty = product.num || 1;
1144
- var _iterator3 = _createForOfIteratorHelper(product.discount_list || []),
1145
- _step3;
1155
+ var _iterator4 = _createForOfIteratorHelper(product.discount_list || []),
1156
+ _step4;
1146
1157
  try {
1147
- for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
1158
+ for (_iterator4.s(); !(_step4 = _iterator4.n()).done;) {
1148
1159
  var _pd$discount, _pd$metadata;
1149
- var pd = _step3.value;
1160
+ var pd = _step4.value;
1150
1161
  var discountKey = ((_pd$discount = pd.discount) === null || _pd$discount === void 0 ? void 0 : _pd$discount.resource_id) || pd.id;
1151
1162
  if (discountKey == null) continue;
1152
1163
  var amount = new Decimal(pd.amount || 0).times(qty).plus(((_pd$metadata = pd.metadata) === null || _pd$metadata === void 0 ? void 0 : _pd$metadata.product_discount_difference) || 0);
1153
1164
  savedMap.set(discountKey, (savedMap.get(discountKey) || new Decimal(0)).plus(amount));
1154
1165
  }
1155
1166
  } catch (err) {
1156
- _iterator3.e(err);
1167
+ _iterator4.e(err);
1157
1168
  } finally {
1158
- _iterator3.f();
1169
+ _iterator4.f();
1159
1170
  }
1160
1171
  }
1161
1172
  } catch (err) {
1162
- _iterator.e(err);
1173
+ _iterator2.e(err);
1163
1174
  } finally {
1164
- _iterator.f();
1175
+ _iterator2.f();
1165
1176
  }
1166
- var _iterator2 = _createForOfIteratorHelper(discountList),
1167
- _step2;
1177
+ var _iterator3 = _createForOfIteratorHelper(discountList),
1178
+ _step3;
1168
1179
  try {
1169
- for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
1170
- var d = _step2.value;
1180
+ for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
1181
+ var d = _step3.value;
1171
1182
  var key = d.id;
1172
1183
  d.savedAmount = d.isSelected && key != null && savedMap.has(key) ? savedMap.get(key).toNumber() : 0;
1173
1184
  }
1174
1185
  } catch (err) {
1175
- _iterator2.e(err);
1186
+ _iterator3.e(err);
1176
1187
  } finally {
1177
- _iterator2.f();
1188
+ _iterator3.f();
1178
1189
  }
1179
1190
  }
1180
1191
  }]);
@@ -18,7 +18,7 @@ function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol"
18
18
  function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
19
19
  import dayjs from "dayjs";
20
20
  import Decimal from 'decimal.js';
21
- import { createEmptySummary } from "../../solution/ScanOrder/utils";
21
+ import { buildProductLineFingerprint, createEmptySummary } from "../../solution/ScanOrder/utils";
22
22
  /**
23
23
  * OrderModule 默认 Rules 钩子工厂。
24
24
  *
@@ -47,7 +47,9 @@ export function createDefaultOrderRulesHooks() {
47
47
  var _product$_origin, _product$_origin2, _product$_origin3, _product$_origin4;
48
48
  return {
49
49
  id: product.product_id,
50
- _id: product.identity_key ? "".concat(product.product_id, "_").concat(product.product_variant_id, "_").concat(product.identity_key) : "".concat(product.product_id, "_").concat(product.product_variant_id),
50
+ // Rules 引擎用 _id 作为 processedProductsMap 键;必须与购物车行级 identity 一致,
51
+ // 否则同 SKU 不同小料会在 calcDiscount 重组时互相覆盖。
52
+ _id: product.identity_key ? "".concat(product.product_id, "_").concat(product.product_variant_id, "_").concat(product.identity_key) : "".concat(product.product_id, "_").concat(product.product_variant_id, "_").concat(buildProductLineFingerprint(product.product_option_item, product.product_bundle)),
51
53
  price: product.selling_price,
52
54
  total: new Decimal(product.selling_price || 0).times(product.num || 1).toNumber(),
53
55
  origin_total: new Decimal(product.original_price || product.selling_price || 0).times(product.num || 1).toNumber(),
@@ -31,6 +31,9 @@ export interface ScanOrderOrderProductIdentity {
31
31
  product_id: number | null;
32
32
  product_variant_id: number;
33
33
  identity_key?: string;
34
+ /** 参与合并/匹配;与 remove 通配语义见 isSkuOnlyDeleteIdentity */
35
+ product_option_item?: any[];
36
+ product_bundle?: any[];
34
37
  }
35
38
  export interface ScanOrderOrderProduct extends ScanOrderOrderProductIdentity {
36
39
  order_detail_id: number | null;
@@ -81,10 +81,27 @@ export declare function buildItemRuleBusinessData(params: {
81
81
  itemRuleConfigs: StrategyConfig[];
82
82
  }): ItemRuleBusinessData;
83
83
  export declare function attachItemRuleLimitsToTopLevelProducts<T>(productList: T, limits: QuantityLimitResult[]): T;
84
+ /**
85
+ * 将选项行 / 套餐行归一成稳定 JSON,用于「同 SKU 是否合并」判断。
86
+ * 数组元素先按主键排序再序列化,避免顺序差异导致误判为不同行。
87
+ */
88
+ export declare function buildProductLineFingerprint(productOptionItem?: unknown, productBundle?: unknown): string;
89
+ /**
90
+ * removeProductFromOrder 仅传 SKU 时的通配 identity(对象上未声明选项键)。
91
+ * 与显式 `product_option_item: []` 区分:后者只匹配「无选项」行。
92
+ */
93
+ export declare function isSkuOnlyDeleteIdentity(x: ScanOrderOrderProductIdentity): boolean;
94
+ /**
95
+ * 与常见 UI 约定一致:仅当存在**一条** `product_option_item` 时,
96
+ * `${product_id}_${option_group_id}_${product_option_item_id}_${num}` 可作为删除用 `identity_key`。
97
+ * 多小料行请使用加购时的 `identity_key` / `metadata.unique_identification_number` 或按 `product_option_item` 指纹删除。
98
+ */
99
+ export declare function buildSyntheticSingleOptionIdentityKey(productId: number | null, productOptionItem: unknown): string | undefined;
84
100
  /**
85
101
  * 判断两个商品 identity 是否匹配。
86
- * 当双方都没有 identity_key 时回退到 product_id + product_variant_id 比较(向后兼容);
87
- * 一旦有一方携带 identity_key,则必须严格相等。
102
+ * - 任一方带 `identity_key`:两侧都有则比字符串相等;仅一侧有则比「另一侧有效 key 集合」是否含该 key(见 collectLineIdentityKeyCandidates)。
103
+ * - 否则若任一方为「仅 SKU」删除通配(未声明 product_option_item / product_bundle 键),只比 SKU。
104
+ * - 否则比较选项 + 套餐指纹(同 SKU 且指纹相同才合并数量)。
88
105
  */
89
106
  export declare function isIdentityMatch(a: ScanOrderOrderProductIdentity, b: ScanOrderOrderProductIdentity): boolean;
90
107
  /**
@@ -366,15 +366,116 @@ export function attachItemRuleLimitsToTopLevelProducts(productList, limits) {
366
366
  return productList;
367
367
  }
368
368
 
369
+ /**
370
+ * 将选项行 / 套餐行归一成稳定 JSON,用于「同 SKU 是否合并」判断。
371
+ * 数组元素先按主键排序再序列化,避免顺序差异导致误判为不同行。
372
+ */
373
+ export function buildProductLineFingerprint(productOptionItem, productBundle) {
374
+ var optArr = Array.isArray(productOptionItem) ? productOptionItem : [];
375
+ var normalizedOpts = optArr.map(function (item) {
376
+ var _item$price;
377
+ return {
378
+ product_option_item_id: Number(item === null || item === void 0 ? void 0 : item.product_option_item_id) || 0,
379
+ option_group_id: Number(item === null || item === void 0 ? void 0 : item.option_group_id) || 0,
380
+ num: typeof (item === null || item === void 0 ? void 0 : item.num) === 'number' && !Number.isNaN(item.num) ? Math.max(1, Math.floor(item.num)) : 1,
381
+ price: String((_item$price = item === null || item === void 0 ? void 0 : item.price) !== null && _item$price !== void 0 ? _item$price : '')
382
+ };
383
+ }).sort(function (p, q) {
384
+ if (p.product_option_item_id !== q.product_option_item_id) {
385
+ return p.product_option_item_id - q.product_option_item_id;
386
+ }
387
+ if (p.option_group_id !== q.option_group_id) return p.option_group_id - q.option_group_id;
388
+ if (p.num !== q.num) return p.num - q.num;
389
+ return p.price.localeCompare(q.price);
390
+ });
391
+ var bundleArr = Array.isArray(productBundle) ? productBundle : [];
392
+ var normalizedBundles = bundleArr.map(function (item) {
393
+ var _ref, _item$bundle_id;
394
+ return {
395
+ bundle_id: Number((_ref = (_item$bundle_id = item === null || item === void 0 ? void 0 : item.bundle_id) !== null && _item$bundle_id !== void 0 ? _item$bundle_id : item === null || item === void 0 ? void 0 : item.id) !== null && _ref !== void 0 ? _ref : 0) || 0,
396
+ product_id: Number(item === null || item === void 0 ? void 0 : item.product_id) || 0,
397
+ num: typeof (item === null || item === void 0 ? void 0 : item.num) === 'number' && !Number.isNaN(item.num) ? Math.max(1, Math.floor(item.num)) : 1
398
+ };
399
+ }).sort(function (p, q) {
400
+ if (p.bundle_id !== q.bundle_id) return p.bundle_id - q.bundle_id;
401
+ if (p.product_id !== q.product_id) return p.product_id - q.product_id;
402
+ return p.num - q.num;
403
+ });
404
+ return JSON.stringify([normalizedOpts, normalizedBundles]);
405
+ }
406
+
407
+ /**
408
+ * removeProductFromOrder 仅传 SKU 时的通配 identity(对象上未声明选项键)。
409
+ * 与显式 `product_option_item: []` 区分:后者只匹配「无选项」行。
410
+ */
411
+ export function isSkuOnlyDeleteIdentity(x) {
412
+ if (x.identity_key) return false;
413
+ return !('product_option_item' in x) && !('product_bundle' in x);
414
+ }
415
+
416
+ /**
417
+ * 与常见 UI 约定一致:仅当存在**一条** `product_option_item` 时,
418
+ * `${product_id}_${option_group_id}_${product_option_item_id}_${num}` 可作为删除用 `identity_key`。
419
+ * 多小料行请使用加购时的 `identity_key` / `metadata.unique_identification_number` 或按 `product_option_item` 指纹删除。
420
+ */
421
+ export function buildSyntheticSingleOptionIdentityKey(productId, productOptionItem) {
422
+ if (productId == null || !Number.isFinite(Number(productId))) return undefined;
423
+ var opts = Array.isArray(productOptionItem) ? productOptionItem : [];
424
+ if (opts.length !== 1) return undefined;
425
+ var o = opts[0];
426
+ var gid = Number(o === null || o === void 0 ? void 0 : o.option_group_id) || 0;
427
+ var oid = Number(o === null || o === void 0 ? void 0 : o.product_option_item_id) || 0;
428
+ var rawNum = o === null || o === void 0 ? void 0 : o.num;
429
+ var n = typeof rawNum === 'number' && !Number.isNaN(rawNum) ? Math.max(1, Math.floor(rawNum)) : 1;
430
+ return "".concat(productId, "_").concat(gid, "_").concat(oid, "_").concat(n);
431
+ }
432
+ function collectLineIdentityKeyCandidates(x) {
433
+ var _row$metadata;
434
+ var out = new Set();
435
+ var row = x;
436
+ if (typeof row.identity_key === 'string' && row.identity_key.length > 0) {
437
+ out.add(row.identity_key);
438
+ }
439
+ var uid = (_row$metadata = row.metadata) === null || _row$metadata === void 0 ? void 0 : _row$metadata.unique_identification_number;
440
+ if (typeof uid === 'string' && uid.length > 0) out.add(uid);
441
+ var synthetic = buildSyntheticSingleOptionIdentityKey(row.product_id, row.product_option_item);
442
+ if (synthetic) out.add(synthetic);
443
+ var opts = 'product_option_item' in row && Array.isArray(row.product_option_item) ? row.product_option_item : [];
444
+ var bundles = 'product_bundle' in row && Array.isArray(row.product_bundle) ? row.product_bundle : [];
445
+ // 无小料且无套餐时,与常见 UI 约定对齐:`${product_id}_${product_variant_id}_0`
446
+ if (opts.length === 0 && bundles.length === 0 && row.product_id != null && Number.isFinite(Number(row.product_id))) {
447
+ var vid = Number(row.product_variant_id) || 0;
448
+ out.add("".concat(row.product_id, "_").concat(vid, "_0"));
449
+ }
450
+ return out;
451
+ }
452
+ function fingerprintForIdentityWithOptionKeys(x) {
453
+ var row = x;
454
+ var opts = 'product_option_item' in row ? Array.isArray(row.product_option_item) ? row.product_option_item : [] : [];
455
+ var buds = 'product_bundle' in row ? Array.isArray(row.product_bundle) ? row.product_bundle : [] : [];
456
+ return buildProductLineFingerprint(opts, buds);
457
+ }
458
+
369
459
  /**
370
460
  * 判断两个商品 identity 是否匹配。
371
- * 当双方都没有 identity_key 时回退到 product_id + product_variant_id 比较(向后兼容);
372
- * 一旦有一方携带 identity_key,则必须严格相等。
461
+ * - 任一方带 `identity_key`:两侧都有则比字符串相等;仅一侧有则比「另一侧有效 key 集合」是否含该 key(见 collectLineIdentityKeyCandidates)。
462
+ * - 否则若任一方为「仅 SKU」删除通配(未声明 product_option_item / product_bundle 键),只比 SKU。
463
+ * - 否则比较选项 + 套餐指纹(同 SKU 且指纹相同才合并数量)。
373
464
  */
374
465
  export function isIdentityMatch(a, b) {
375
466
  if (a.product_id !== b.product_id || a.product_variant_id !== b.product_variant_id) return false;
376
- if (!a.identity_key && !b.identity_key) return true;
377
- return a.identity_key === b.identity_key;
467
+ if (a.identity_key || b.identity_key) {
468
+ if (a.identity_key && b.identity_key) return a.identity_key === b.identity_key;
469
+ if (a.identity_key && !b.identity_key) {
470
+ return collectLineIdentityKeyCandidates(b).has(a.identity_key);
471
+ }
472
+ if (!a.identity_key && b.identity_key) {
473
+ return collectLineIdentityKeyCandidates(a).has(b.identity_key);
474
+ }
475
+ return false;
476
+ }
477
+ if (isSkuOnlyDeleteIdentity(a) || isSkuOnlyDeleteIdentity(b)) return true;
478
+ return fingerprintForIdentityWithOptionKeys(a) === fingerprintForIdentityWithOptionKeys(b);
378
479
  }
379
480
 
380
481
  /**
@@ -414,14 +515,14 @@ export function normalizeOrderProduct(product) {
414
515
  metadata.main_product_selling_price = resolvedSellingPrice;
415
516
  }
416
517
  if (metadata.source_product_price === undefined) {
417
- var _ref, _product$_origin$pric, _product$_origin, _product$_origin2;
418
- metadata.source_product_price = (_ref = (_product$_origin$pric = (_product$_origin = product._origin) === null || _product$_origin === void 0 ? void 0 : _product$_origin.price) !== null && _product$_origin$pric !== void 0 ? _product$_origin$pric : (_product$_origin2 = product._origin) === null || _product$_origin2 === void 0 ? void 0 : _product$_origin2.base_price) !== null && _ref !== void 0 ? _ref : resolvedSellingPrice;
518
+ var _ref2, _product$_origin$pric, _product$_origin, _product$_origin2;
519
+ metadata.source_product_price = (_ref2 = (_product$_origin$pric = (_product$_origin = product._origin) === null || _product$_origin === void 0 ? void 0 : _product$_origin.price) !== null && _product$_origin$pric !== void 0 ? _product$_origin$pric : (_product$_origin2 = product._origin) === null || _product$_origin2 === void 0 ? void 0 : _product$_origin2.base_price) !== null && _ref2 !== void 0 ? _ref2 : resolvedSellingPrice;
419
520
  }
420
521
  var normalizedBundle = (product.product_bundle || []).map(function (item) {
421
- var _ref2, _item$bundle_selling_, _ref3, _ref4, _item$custom_price;
522
+ var _ref3, _item$bundle_selling_, _ref4, _ref5, _item$custom_price;
422
523
  return _objectSpread(_objectSpread({}, item), {}, {
423
- bundle_selling_price: (_ref2 = (_item$bundle_selling_ = item.bundle_selling_price) !== null && _item$bundle_selling_ !== void 0 ? _item$bundle_selling_ : item.price) !== null && _ref2 !== void 0 ? _ref2 : '0.00',
424
- custom_price: (_ref3 = (_ref4 = (_item$custom_price = item.custom_price) !== null && _item$custom_price !== void 0 ? _item$custom_price : item.bundle_selling_price) !== null && _ref4 !== void 0 ? _ref4 : item.price) !== null && _ref3 !== void 0 ? _ref3 : '0.00'
524
+ bundle_selling_price: (_ref3 = (_item$bundle_selling_ = item.bundle_selling_price) !== null && _item$bundle_selling_ !== void 0 ? _item$bundle_selling_ : item.price) !== null && _ref3 !== void 0 ? _ref3 : '0.00',
525
+ custom_price: (_ref4 = (_ref5 = (_item$custom_price = item.custom_price) !== null && _item$custom_price !== void 0 ? _item$custom_price : item.bundle_selling_price) !== null && _ref5 !== void 0 ? _ref5 : item.price) !== null && _ref4 !== void 0 ? _ref4 : '0.00'
425
526
  });
426
527
  });
427
528
  return {
@@ -288,6 +288,18 @@ var OrderModule = class extends import_BaseModule.BaseModule {
288
288
  this.window.localStorage.removeItem(key);
289
289
  return;
290
290
  }
291
+ if (Array.isArray(parsedData.products)) {
292
+ for (const p of parsedData.products) {
293
+ if (!p || typeof p !== "object")
294
+ continue;
295
+ if (!Array.isArray(p.product_option_item)) {
296
+ p.product_option_item = [];
297
+ }
298
+ if (!Array.isArray(p.product_bundle)) {
299
+ p.product_bundle = [];
300
+ }
301
+ }
302
+ }
291
303
  this.store.tempOrder = parsedData;
292
304
  } catch {
293
305
  (_b = (_a = this.window) == null ? void 0 : _a.localStorage) == null ? void 0 : _b.removeItem(key);
@@ -444,18 +456,9 @@ var OrderModule = class extends import_BaseModule.BaseModule {
444
456
  }
445
457
  async removeProductFromOrder(identity) {
446
458
  const tempOrder = this.ensureTempOrder();
447
- const beforeProducts = tempOrder.products;
448
459
  tempOrder.products = tempOrder.products.filter(
449
460
  (item) => !(0, import_utils3.isIdentityMatch)(item, identity)
450
461
  );
451
- const removedByStrictIdentity = beforeProducts.length - tempOrder.products.length;
452
- if (removedByStrictIdentity === 0 && identity.identity_key) {
453
- tempOrder.products = tempOrder.products.filter((item) => {
454
- const isSameProduct = String(item.product_id) === String(identity.product_id) && String(item.product_variant_id) === String(identity.product_variant_id);
455
- const hasNoIdentityKey = !item.identity_key;
456
- return !(isSameProduct && hasNoIdentityKey);
457
- });
458
- }
459
462
  this.applyDiscount();
460
463
  await this.recalculateSummary({ createIfMissing: true });
461
464
  this.persistTempOrder();
@@ -60,7 +60,12 @@ function createDefaultOrderRulesHooks() {
60
60
  var _a, _b, _c, _d;
61
61
  return {
62
62
  id: product.product_id,
63
- _id: product.identity_key ? `${product.product_id}_${product.product_variant_id}_${product.identity_key}` : `${product.product_id}_${product.product_variant_id}`,
63
+ // Rules 引擎用 _id 作为 processedProductsMap 键;必须与购物车行级 identity 一致,
64
+ // 否则同 SKU 不同小料会在 calcDiscount 重组时互相覆盖。
65
+ _id: product.identity_key ? `${product.product_id}_${product.product_variant_id}_${product.identity_key}` : `${product.product_id}_${product.product_variant_id}_${(0, import_utils.buildProductLineFingerprint)(
66
+ product.product_option_item,
67
+ product.product_bundle
68
+ )}`,
64
69
  price: product.selling_price,
65
70
  total: new import_decimal.default(product.selling_price || 0).times(product.num || 1).toNumber(),
66
71
  origin_total: new import_decimal.default(product.original_price || product.selling_price || 0).times(product.num || 1).toNumber(),
@@ -31,6 +31,9 @@ export interface ScanOrderOrderProductIdentity {
31
31
  product_id: number | null;
32
32
  product_variant_id: number;
33
33
  identity_key?: string;
34
+ /** 参与合并/匹配;与 remove 通配语义见 isSkuOnlyDeleteIdentity */
35
+ product_option_item?: any[];
36
+ product_bundle?: any[];
34
37
  }
35
38
  export interface ScanOrderOrderProduct extends ScanOrderOrderProductIdentity {
36
39
  order_detail_id: number | null;
@@ -81,10 +81,27 @@ export declare function buildItemRuleBusinessData(params: {
81
81
  itemRuleConfigs: StrategyConfig[];
82
82
  }): ItemRuleBusinessData;
83
83
  export declare function attachItemRuleLimitsToTopLevelProducts<T>(productList: T, limits: QuantityLimitResult[]): T;
84
+ /**
85
+ * 将选项行 / 套餐行归一成稳定 JSON,用于「同 SKU 是否合并」判断。
86
+ * 数组元素先按主键排序再序列化,避免顺序差异导致误判为不同行。
87
+ */
88
+ export declare function buildProductLineFingerprint(productOptionItem?: unknown, productBundle?: unknown): string;
89
+ /**
90
+ * removeProductFromOrder 仅传 SKU 时的通配 identity(对象上未声明选项键)。
91
+ * 与显式 `product_option_item: []` 区分:后者只匹配「无选项」行。
92
+ */
93
+ export declare function isSkuOnlyDeleteIdentity(x: ScanOrderOrderProductIdentity): boolean;
94
+ /**
95
+ * 与常见 UI 约定一致:仅当存在**一条** `product_option_item` 时,
96
+ * `${product_id}_${option_group_id}_${product_option_item_id}_${num}` 可作为删除用 `identity_key`。
97
+ * 多小料行请使用加购时的 `identity_key` / `metadata.unique_identification_number` 或按 `product_option_item` 指纹删除。
98
+ */
99
+ export declare function buildSyntheticSingleOptionIdentityKey(productId: number | null, productOptionItem: unknown): string | undefined;
84
100
  /**
85
101
  * 判断两个商品 identity 是否匹配。
86
- * 当双方都没有 identity_key 时回退到 product_id + product_variant_id 比较(向后兼容);
87
- * 一旦有一方携带 identity_key,则必须严格相等。
102
+ * - 任一方带 `identity_key`:两侧都有则比字符串相等;仅一侧有则比「另一侧有效 key 集合」是否含该 key(见 collectLineIdentityKeyCandidates)。
103
+ * - 否则若任一方为「仅 SKU」删除通配(未声明 product_option_item / product_bundle 键),只比 SKU。
104
+ * - 否则比较选项 + 套餐指纹(同 SKU 且指纹相同才合并数量)。
88
105
  */
89
106
  export declare function isIdentityMatch(a: ScanOrderOrderProductIdentity, b: ScanOrderOrderProductIdentity): boolean;
90
107
  /**
@@ -33,7 +33,9 @@ __export(utils_exports, {
33
33
  attachItemRuleLimitsToTopLevelProducts: () => attachItemRuleLimitsToTopLevelProducts,
34
34
  buildItemRuleBusinessData: () => buildItemRuleBusinessData,
35
35
  buildProductKey: () => buildProductKey,
36
+ buildProductLineFingerprint: () => buildProductLineFingerprint,
36
37
  buildQuantityLimitIndex: () => buildQuantityLimitIndex,
38
+ buildSyntheticSingleOptionIdentityKey: () => buildSyntheticSingleOptionIdentityKey,
37
39
  collectLinkProductIdsFromReservationRules: () => collectLinkProductIdsFromReservationRules,
38
40
  computeResourceIsFull: () => computeResourceIsFull,
39
41
  createEmptySummary: () => createEmptySummary,
@@ -44,6 +46,7 @@ __export(utils_exports, {
44
46
  getTopLevelVariantId: () => getTopLevelVariantId,
45
47
  hasCustomCapacityProduct: () => hasCustomCapacityProduct,
46
48
  isIdentityMatch: () => isIdentityMatch,
49
+ isSkuOnlyDeleteIdentity: () => isSkuOnlyDeleteIdentity,
47
50
  normalizeEnabledItemRuleIds: () => normalizeEnabledItemRuleIds,
48
51
  normalizeItemRuleStrategies: () => normalizeItemRuleStrategies,
49
52
  normalizeOrderProduct: () => normalizeOrderProduct,
@@ -339,12 +342,102 @@ function attachItemRuleLimitsToTopLevelProducts(productList, limits) {
339
342
  }
340
343
  return productList;
341
344
  }
345
+ function buildProductLineFingerprint(productOptionItem, productBundle) {
346
+ const optArr = Array.isArray(productOptionItem) ? productOptionItem : [];
347
+ const normalizedOpts = optArr.map((item) => ({
348
+ product_option_item_id: Number(item == null ? void 0 : item.product_option_item_id) || 0,
349
+ option_group_id: Number(item == null ? void 0 : item.option_group_id) || 0,
350
+ num: typeof (item == null ? void 0 : item.num) === "number" && !Number.isNaN(item.num) ? Math.max(1, Math.floor(item.num)) : 1,
351
+ price: String((item == null ? void 0 : item.price) ?? "")
352
+ })).sort((p, q) => {
353
+ if (p.product_option_item_id !== q.product_option_item_id) {
354
+ return p.product_option_item_id - q.product_option_item_id;
355
+ }
356
+ if (p.option_group_id !== q.option_group_id)
357
+ return p.option_group_id - q.option_group_id;
358
+ if (p.num !== q.num)
359
+ return p.num - q.num;
360
+ return p.price.localeCompare(q.price);
361
+ });
362
+ const bundleArr = Array.isArray(productBundle) ? productBundle : [];
363
+ const normalizedBundles = bundleArr.map((item) => ({
364
+ bundle_id: Number((item == null ? void 0 : item.bundle_id) ?? (item == null ? void 0 : item.id) ?? 0) || 0,
365
+ product_id: Number(item == null ? void 0 : item.product_id) || 0,
366
+ num: typeof (item == null ? void 0 : item.num) === "number" && !Number.isNaN(item.num) ? Math.max(1, Math.floor(item.num)) : 1
367
+ })).sort((p, q) => {
368
+ if (p.bundle_id !== q.bundle_id)
369
+ return p.bundle_id - q.bundle_id;
370
+ if (p.product_id !== q.product_id)
371
+ return p.product_id - q.product_id;
372
+ return p.num - q.num;
373
+ });
374
+ return JSON.stringify([normalizedOpts, normalizedBundles]);
375
+ }
376
+ function isSkuOnlyDeleteIdentity(x) {
377
+ if (x.identity_key)
378
+ return false;
379
+ return !("product_option_item" in x) && !("product_bundle" in x);
380
+ }
381
+ function buildSyntheticSingleOptionIdentityKey(productId, productOptionItem) {
382
+ if (productId == null || !Number.isFinite(Number(productId)))
383
+ return void 0;
384
+ const opts = Array.isArray(productOptionItem) ? productOptionItem : [];
385
+ if (opts.length !== 1)
386
+ return void 0;
387
+ const o = opts[0];
388
+ const gid = Number(o == null ? void 0 : o.option_group_id) || 0;
389
+ const oid = Number(o == null ? void 0 : o.product_option_item_id) || 0;
390
+ const rawNum = o == null ? void 0 : o.num;
391
+ const n = typeof rawNum === "number" && !Number.isNaN(rawNum) ? Math.max(1, Math.floor(rawNum)) : 1;
392
+ return `${productId}_${gid}_${oid}_${n}`;
393
+ }
394
+ function collectLineIdentityKeyCandidates(x) {
395
+ var _a;
396
+ const out = /* @__PURE__ */ new Set();
397
+ const row = x;
398
+ if (typeof row.identity_key === "string" && row.identity_key.length > 0) {
399
+ out.add(row.identity_key);
400
+ }
401
+ const uid = (_a = row.metadata) == null ? void 0 : _a.unique_identification_number;
402
+ if (typeof uid === "string" && uid.length > 0)
403
+ out.add(uid);
404
+ const synthetic = buildSyntheticSingleOptionIdentityKey(
405
+ row.product_id,
406
+ row.product_option_item
407
+ );
408
+ if (synthetic)
409
+ out.add(synthetic);
410
+ const opts = "product_option_item" in row && Array.isArray(row.product_option_item) ? row.product_option_item : [];
411
+ const bundles = "product_bundle" in row && Array.isArray(row.product_bundle) ? row.product_bundle : [];
412
+ if (opts.length === 0 && bundles.length === 0 && row.product_id != null && Number.isFinite(Number(row.product_id))) {
413
+ const vid = Number(row.product_variant_id) || 0;
414
+ out.add(`${row.product_id}_${vid}_0`);
415
+ }
416
+ return out;
417
+ }
418
+ function fingerprintForIdentityWithOptionKeys(x) {
419
+ const row = x;
420
+ const opts = "product_option_item" in row ? Array.isArray(row.product_option_item) ? row.product_option_item : [] : [];
421
+ const buds = "product_bundle" in row ? Array.isArray(row.product_bundle) ? row.product_bundle : [] : [];
422
+ return buildProductLineFingerprint(opts, buds);
423
+ }
342
424
  function isIdentityMatch(a, b) {
343
425
  if (a.product_id !== b.product_id || a.product_variant_id !== b.product_variant_id)
344
426
  return false;
345
- if (!a.identity_key && !b.identity_key)
427
+ if (a.identity_key || b.identity_key) {
428
+ if (a.identity_key && b.identity_key)
429
+ return a.identity_key === b.identity_key;
430
+ if (a.identity_key && !b.identity_key) {
431
+ return collectLineIdentityKeyCandidates(b).has(a.identity_key);
432
+ }
433
+ if (!a.identity_key && b.identity_key) {
434
+ return collectLineIdentityKeyCandidates(a).has(b.identity_key);
435
+ }
436
+ return false;
437
+ }
438
+ if (isSkuOnlyDeleteIdentity(a) || isSkuOnlyDeleteIdentity(b))
346
439
  return true;
347
- return a.identity_key === b.identity_key;
440
+ return fingerprintForIdentityWithOptionKeys(a) === fingerprintForIdentityWithOptionKeys(b);
348
441
  }
349
442
  function getProductIdentityIndex(products, identity) {
350
443
  return products.findIndex((item) => isIdentityMatch(item, identity));
@@ -505,7 +598,9 @@ function pickFirstCustomCapacityDimensionId(products) {
505
598
  attachItemRuleLimitsToTopLevelProducts,
506
599
  buildItemRuleBusinessData,
507
600
  buildProductKey,
601
+ buildProductLineFingerprint,
508
602
  buildQuantityLimitIndex,
603
+ buildSyntheticSingleOptionIdentityKey,
509
604
  collectLinkProductIdsFromReservationRules,
510
605
  computeResourceIsFull,
511
606
  createEmptySummary,
@@ -516,6 +611,7 @@ function pickFirstCustomCapacityDimensionId(products) {
516
611
  getTopLevelVariantId,
517
612
  hasCustomCapacityProduct,
518
613
  isIdentityMatch,
614
+ isSkuOnlyDeleteIdentity,
519
615
  normalizeEnabledItemRuleIds,
520
616
  normalizeItemRuleStrategies,
521
617
  normalizeOrderProduct,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "private": false,
3
3
  "name": "@pisell/pisellos",
4
- "version": "0.0.509",
4
+ "version": "0.0.510",
5
5
  "description": "一个可扩展的前端模块化SDK框架,支持插件系统",
6
6
  "main": "dist/index.js",
7
7
  "types": "dist/index.d.ts",