@pisell/pisellos 2.3.55 → 2.3.57

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 (28) hide show
  1. package/dist/core/index.js +0 -1
  2. package/dist/modules/BookingContext/utils/productExtend.js +5 -9
  3. package/dist/modules/BookingContext/utils/timeSlices.js +24 -3
  4. package/dist/modules/Order/index.d.ts +8 -2
  5. package/dist/modules/Order/index.js +636 -645
  6. package/dist/modules/Order/utils/discountProductLineIdentity.d.ts +45 -0
  7. package/dist/modules/Order/utils/discountProductLineIdentity.js +227 -0
  8. package/dist/modules/Rules/index.js +46 -5
  9. package/dist/solution/BaseSales/index.js +53 -34
  10. package/dist/solution/BaseSales/utils/cartPromotion.js +19 -1
  11. package/dist/solution/BaseSales/utils/transformBaseProductToOrderProduct.js +1 -1
  12. package/dist/solution/BookingByStep/index.d.ts +1 -1
  13. package/dist/solution/BookingTicket/index.js +1 -4
  14. package/lib/core/index.js +0 -1
  15. package/lib/model/strategy/adapter/promotion/index.js +0 -51
  16. package/lib/modules/BookingContext/utils/productExtend.js +4 -3
  17. package/lib/modules/BookingContext/utils/timeSlices.js +18 -3
  18. package/lib/modules/Order/index.d.ts +8 -2
  19. package/lib/modules/Order/index.js +161 -118
  20. package/lib/modules/Order/utils/discountProductLineIdentity.d.ts +45 -0
  21. package/lib/modules/Order/utils/discountProductLineIdentity.js +170 -0
  22. package/lib/modules/Rules/index.js +47 -3
  23. package/lib/solution/BaseSales/index.js +26 -6
  24. package/lib/solution/BaseSales/utils/cartPromotion.js +18 -0
  25. package/lib/solution/BaseSales/utils/transformBaseProductToOrderProduct.js +1 -1
  26. package/lib/solution/BookingByStep/index.d.ts +1 -1
  27. package/lib/solution/BookingTicket/index.js +1 -4
  28. package/package.json +1 -1
@@ -1,51 +0,0 @@
1
- var __create = Object.create;
2
- var __defProp = Object.defineProperty;
3
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
- var __getOwnPropNames = Object.getOwnPropertyNames;
5
- var __getProtoOf = Object.getPrototypeOf;
6
- var __hasOwnProp = Object.prototype.hasOwnProperty;
7
- var __export = (target, all) => {
8
- for (var name in all)
9
- __defProp(target, name, { get: all[name], enumerable: true });
10
- };
11
- var __copyProps = (to, from, except, desc) => {
12
- if (from && typeof from === "object" || typeof from === "function") {
13
- for (let key of __getOwnPropNames(from))
14
- if (!__hasOwnProp.call(to, key) && key !== except)
15
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
16
- }
17
- return to;
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
- ));
27
- var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
28
-
29
- // src/model/strategy/adapter/promotion/index.ts
30
- var promotion_exports = {};
31
- __export(promotion_exports, {
32
- BUY_X_GET_Y_FREE_STRATEGY: () => import_examples.BUY_X_GET_Y_FREE_STRATEGY,
33
- ITEM_REWARD_STRATEGY: () => import_examples.ITEM_REWARD_STRATEGY,
34
- PromotionAdapter: () => import_adapter.PromotionAdapter,
35
- PromotionEvaluator: () => import_evaluator.PromotionEvaluator,
36
- X_ITEMS_FOR_Y_PRICE_STRATEGY: () => import_examples.X_ITEMS_FOR_Y_PRICE_STRATEGY,
37
- default: () => import_adapter2.default
38
- });
39
- module.exports = __toCommonJS(promotion_exports);
40
- var import_evaluator = require("./evaluator");
41
- var import_adapter = require("./adapter");
42
- var import_adapter2 = __toESM(require("./adapter"));
43
- var import_examples = require("./examples");
44
- // Annotate the CommonJS export names for ESM import in node:
45
- 0 && (module.exports = {
46
- BUY_X_GET_Y_FREE_STRATEGY,
47
- ITEM_REWARD_STRATEGY,
48
- PromotionAdapter,
49
- PromotionEvaluator,
50
- X_ITEMS_FOR_Y_PRICE_STRATEGY
51
- });
@@ -83,12 +83,13 @@ function getTimeObj(params) {
83
83
  const { cacheItem, useStartTime, useDuration, ctx } = params;
84
84
  const isMultiDay = (0, import_serviceTimes.isMultiDayProduct)(cacheItem);
85
85
  const flexibleOption = getFlexibleOption({ cacheItem, useStartTime, ctx });
86
- const baseSlices = (0, import_timeSlices.sliceDayIntoFiveMinutes)(
86
+ const {
87
+ timeSlices,
88
+ durationSlicesBasedOnTime
89
+ } = (0, import_timeSlices.sliceDayIntoFiveMinutes)(
87
90
  (_c = (_b = (_a = ctx == null ? void 0 : ctx.bookingConfig) == null ? void 0 : _a.config) == null ? void 0 : _b.basic) == null ? void 0 : _c.service_time,
88
91
  isMultiDay
89
92
  );
90
- const timeSlices = [...baseSlices.timeSlices];
91
- const durationSlicesBasedOnTime = [...baseSlices.durationSlicesBasedOnTime];
92
93
  if (flexibleOption) {
93
94
  durationSlicesBasedOnTime.unshift(flexibleOption);
94
95
  }
@@ -38,10 +38,21 @@ __export(timeSlices_exports, {
38
38
  module.exports = __toCommonJS(timeSlices_exports);
39
39
  var import_dayjs = __toESM(require("dayjs"));
40
40
  var import_flexible = require("./flexible");
41
+ var timeSlicesCache = /* @__PURE__ */ new Map();
42
+ function copyTimeSlices(result) {
43
+ return {
44
+ timeSlices: [...result.timeSlices],
45
+ durationSlicesBasedOnTime: [...result.durationSlicesBasedOnTime]
46
+ };
47
+ }
41
48
  function sliceDayIntoFiveMinutes(config, isDay) {
49
+ const timeSlice = config && config.value || 5;
50
+ const cacheKey = isDay ? "days" : `minutes:${timeSlice}`;
51
+ const cached = timeSlicesCache.get(cacheKey);
52
+ if (cached)
53
+ return copyTimeSlices(cached);
42
54
  const startTime = (0, import_dayjs.default)().startOf("day");
43
55
  const endTime = (0, import_dayjs.default)().endOf("day");
44
- const timeSlice = config && config.value || 5;
45
56
  const timeSlices = [];
46
57
  const durationSlicesBasedOnTime = [];
47
58
  let currentTime = startTime;
@@ -58,7 +69,9 @@ function sliceDayIntoFiveMinutes(config, isDay) {
58
69
  });
59
70
  currentDay += 1;
60
71
  }
61
- return { timeSlices, durationSlicesBasedOnTime };
72
+ const result2 = { timeSlices, durationSlicesBasedOnTime };
73
+ timeSlicesCache.set(cacheKey, result2);
74
+ return copyTimeSlices(result2);
62
75
  }
63
76
  while (currentTime.isBefore(endTime)) {
64
77
  timeSlices.push({
@@ -73,7 +86,9 @@ function sliceDayIntoFiveMinutes(config, isDay) {
73
86
  });
74
87
  currentTime = nextTime;
75
88
  }
76
- return { timeSlices, durationSlicesBasedOnTime };
89
+ const result = { timeSlices, durationSlicesBasedOnTime };
90
+ timeSlicesCache.set(cacheKey, result);
91
+ return copyTimeSlices(result);
77
92
  }
78
93
  function findNextDuration(current, array, defaultValue) {
79
94
  if (defaultValue !== void 0 && defaultValue !== null)
@@ -354,6 +354,7 @@ export declare class OrderModule extends BaseModule implements Module, OrderModu
354
354
  updateVoucherOrderPaymentsAsync(payments: OrderPaymentSource[], options?: {
355
355
  status?: 'paid' | 'payment_pending';
356
356
  }): Promise<OrderPaymentData[]>;
357
+ private normalizeShouldMergeProductResult;
357
358
  private shouldMergeProductToOrder;
358
359
  private hasOrderProductLineNote;
359
360
  /**
@@ -375,7 +376,12 @@ export declare class OrderModule extends BaseModule implements Module, OrderModu
375
376
  * 带 booking 且 num>1 时由 `addProductToOrder` 拆成多次本方法调用(每次 num=1)。
376
377
  *
377
378
  * @example
378
- * await this.appendProductLineToTempOrder(tempOrder, { product_id: 1, num: 1 }, booking);
379
+ * const pending = this.appendProductLineToTempOrder(
380
+ * tempOrder,
381
+ * { product_id: 1, num: 1 },
382
+ * booking,
383
+ * );
384
+ * if (pending) await pending;
379
385
  */
380
386
  private appendProductLineToTempOrder;
381
387
  /**
@@ -487,7 +493,7 @@ export declare class OrderModule extends BaseModule implements Module, OrderModu
487
493
  generateIdempotencyToken(): string;
488
494
  syncPaymentsToOrder<T = any>(params: SyncPaymentsToOrderParams): Promise<SyncPaymentsToOrderResult<T>>;
489
495
  createOrder(params: CommitOrderParams['query']): {
490
- type: "virtual" | "appointment_booking";
496
+ type: "appointment_booking" | "virtual";
491
497
  platform: string;
492
498
  sales_channel: string;
493
499
  order_sales_channel: string;
@@ -44,6 +44,7 @@ var import_utils3 = require("../../solution/ScanOrder/utils");
44
44
  var import_manualProductDiscount = require("./utils/manualProductDiscount");
45
45
  var import_bundleDiscountHydration = require("./utils/bundleDiscountHydration");
46
46
  var import_orderCollectionIdentity = require("./utils/orderCollectionIdentity");
47
+ var import_discountProductLineIdentity = require("./utils/discountProductLineIdentity");
47
48
  var import_Discount = require("../Discount");
48
49
  var import_Rules = require("../Rules");
49
50
  var import_types2 = require("../Rules/types");
@@ -133,7 +134,7 @@ var OrderModule = class extends import_BaseModule.BaseModule {
133
134
  this.draftPersistEnabled = true;
134
135
  }
135
136
  collectHydratedEditDiscounts(products) {
136
- var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j;
137
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l;
137
138
  const discountMap = /* @__PURE__ */ new Map();
138
139
  for (const product of (0, import_bundleDiscountHydration.expandHydratedProductsForDiscountCollection)(products)) {
139
140
  for (const item of (product == null ? void 0 : product.discount_list) || []) {
@@ -148,16 +149,18 @@ var OrderModule = class extends import_BaseModule.BaseModule {
148
149
  const key = Number.isFinite(id) ? id : String(resourceId);
149
150
  const title = ((_b = item == null ? void 0 : item.discount) == null ? void 0 : _b.title) ?? {};
150
151
  const amount = Number((item == null ? void 0 : item.amount) || 0);
152
+ const discountCardType = (_c = item == null ? void 0 : item.discount) == null ? void 0 : _c.discount_card_type;
153
+ const parValue = isDiscountCard ? discountCardType === "fixed_amount" ? ((_d = item == null ? void 0 : item.discount) == null ? void 0 : _d.fixed_amount) ?? (item == null ? void 0 : item.amount) : (_e = item == null ? void 0 : item.discount) == null ? void 0 : _e.percent : "0";
151
154
  const detail = {
152
155
  amount: String((item == null ? void 0 : item.amount) ?? "0"),
153
156
  type,
154
157
  resource_id: key,
155
158
  title,
156
- original_amount: String(((_c = item == null ? void 0 : item.discount) == null ? void 0 : _c.original_amount) ?? (item == null ? void 0 : item.amount) ?? "0"),
157
- num: Number(((_d = item == null ? void 0 : item.metadata) == null ? void 0 : _d.num) ?? (product == null ? void 0 : product.num) ?? (product == null ? void 0 : product.product_quantity) ?? 1) || 1,
159
+ original_amount: String(((_f = item == null ? void 0 : item.discount) == null ? void 0 : _f.original_amount) ?? (item == null ? void 0 : item.amount) ?? "0"),
160
+ num: Number(((_g = item == null ? void 0 : item.metadata) == null ? void 0 : _g.num) ?? (product == null ? void 0 : product.num) ?? (product == null ? void 0 : product.product_quantity) ?? 1) || 1,
158
161
  metadata: item == null ? void 0 : item.metadata,
159
162
  discount: item == null ? void 0 : item.discount,
160
- _num: Number(((_e = item == null ? void 0 : item.metadata) == null ? void 0 : _e.num) ?? (product == null ? void 0 : product.num) ?? (product == null ? void 0 : product.product_quantity) ?? 1) || 1
163
+ _num: Number(((_h = item == null ? void 0 : item.metadata) == null ? void 0 : _h.num) ?? (product == null ? void 0 : product.num) ?? (product == null ? void 0 : product.product_quantity) ?? 1) || 1
161
164
  };
162
165
  const existed = discountMap.get(key);
163
166
  if (existed) {
@@ -166,7 +169,7 @@ var OrderModule = class extends import_BaseModule.BaseModule {
166
169
  if (!isDiscountCard) {
167
170
  existed.balance = String(Number(existed.balance || 0) + amount);
168
171
  }
169
- const productIds = Array.isArray((_f = existed.limited_relation_product_data) == null ? void 0 : _f.product_ids) ? existed.limited_relation_product_data.product_ids : [];
172
+ const productIds = Array.isArray((_i = existed.limited_relation_product_data) == null ? void 0 : _i.product_ids) ? existed.limited_relation_product_data.product_ids : [];
170
173
  if ((product == null ? void 0 : product.product_id) !== void 0 && (product == null ? void 0 : product.product_id) !== null && !productIds.includes(product.product_id)) {
171
174
  existed.limited_relation_product_data = {
172
175
  ...existed.limited_relation_product_data || {},
@@ -189,9 +192,9 @@ var OrderModule = class extends import_BaseModule.BaseModule {
189
192
  encoded: "",
190
193
  code: "",
191
194
  tag: type,
192
- product_id: ((_g = item == null ? void 0 : item.discount) == null ? void 0 : _g.discount_product_id) ?? (product == null ? void 0 : product.product_id) ?? 0,
195
+ product_id: ((_j = item == null ? void 0 : item.discount) == null ? void 0 : _j.discount_product_id) ?? (product == null ? void 0 : product.product_id) ?? 0,
193
196
  relation_type: "",
194
- par_value: String(((_h = item == null ? void 0 : item.discount) == null ? void 0 : _h.original_amount) ?? (item == null ? void 0 : item.amount) ?? "0"),
197
+ par_value: String(parValue ?? "0"),
195
198
  used_par_value: String((item == null ? void 0 : item.amount) ?? "0"),
196
199
  limit_status: "enable",
197
200
  limited_relation_product_data: {
@@ -221,8 +224,8 @@ var OrderModule = class extends import_BaseModule.BaseModule {
221
224
  discount: {
222
225
  ...(item == null ? void 0 : item.discount) || {},
223
226
  resource_id: key,
224
- discount_product_id: ((_i = item == null ? void 0 : item.discount) == null ? void 0 : _i.discount_product_id) ?? (product == null ? void 0 : product.product_id) ?? 0,
225
- discount_calculation_mode: ((_j = item == null ? void 0 : item.discount) == null ? void 0 : _j.discount_calculation_mode) ?? "item_level"
227
+ discount_product_id: ((_k = item == null ? void 0 : item.discount) == null ? void 0 : _k.discount_product_id) ?? (product == null ? void 0 : product.product_id) ?? 0,
228
+ discount_calculation_mode: ((_l = item == null ? void 0 : item.discount) == null ? void 0 : _l.discount_calculation_mode) ?? "item_level"
226
229
  },
227
230
  applicableProductDetails: [detail],
228
231
  appliedProductDetails: [detail]
@@ -274,7 +277,7 @@ var OrderModule = class extends import_BaseModule.BaseModule {
274
277
  const isPersistedBundleProduct = isPersistedProduct || Boolean(
275
278
  (bundle == null ? void 0 : bundle.order_detail_id) || (bundle == null ? void 0 : bundle.orderDetailId) || (bundle == null ? void 0 : bundle.booking_id)
276
279
  );
277
- const bundleQty = new import_decimal.default(Number(qty) || 1).times(Number((bundle == null ? void 0 : bundle.num) ?? (bundle == null ? void 0 : bundle.quantity) ?? 1) || 1).toNumber();
280
+ const bundleQty = Number(qty) || 1;
278
281
  for (const pd of (bundle == null ? void 0 : bundle.discount_list) || []) {
279
282
  addProductDiscountAmount(pd, bundleQty, isPersistedBundleProduct);
280
283
  }
@@ -670,6 +673,8 @@ var OrderModule = class extends import_BaseModule.BaseModule {
670
673
  };
671
674
  })
672
675
  );
676
+ (0, import_discountProductLineIdentity.ensureUniqueProductLineUids)(tempOrder);
677
+ (0, import_discountProductLineIdentity.consolidateDiscountFreeProductLines)(tempOrder);
673
678
  }
674
679
  if (result == null ? void 0 : result.discountList) {
675
680
  OrderModule.populateSavedAmounts(
@@ -1878,6 +1883,7 @@ var OrderModule = class extends import_BaseModule.BaseModule {
1878
1883
  tempOrder.products = (tempOrder.products || []).map(
1879
1884
  (product) => this.sanitizeOrderProductForTempOrder(product)
1880
1885
  );
1886
+ (0, import_discountProductLineIdentity.ensureUniqueProductLineUids)(tempOrder);
1881
1887
  }
1882
1888
  ensureExtend(tempOrder) {
1883
1889
  if (!tempOrder._extend || typeof tempOrder._extend !== "object") {
@@ -2533,6 +2539,7 @@ var OrderModule = class extends import_BaseModule.BaseModule {
2533
2539
  delete tempOrder._extend.customerSnapshot;
2534
2540
  this.store.availableWalletIds = [];
2535
2541
  this.clearCustomerBoundDiscounts(tempOrder);
2542
+ this.applyDiscount();
2536
2543
  this.persistTempOrder();
2537
2544
  this.saveDraftInBackground();
2538
2545
  this.core.effects.emit(import_types.OrderHooks.OnOrderCustomerChange, null);
@@ -2838,21 +2845,29 @@ var OrderModule = class extends import_BaseModule.BaseModule {
2838
2845
  devicePrefix
2839
2846
  });
2840
2847
  }
2841
- async shouldMergeProductToOrder(params) {
2848
+ normalizeShouldMergeProductResult(result) {
2849
+ if (typeof result === "boolean")
2850
+ return result;
2851
+ if (result && typeof result === "object" && typeof result.shouldMerge === "boolean") {
2852
+ return result.shouldMerge;
2853
+ }
2854
+ return true;
2855
+ }
2856
+ shouldMergeProductToOrder(params) {
2842
2857
  var _a;
2843
2858
  const onBeforeMergeProductToOrder = (_a = this.orderHooks) == null ? void 0 : _a.onBeforeMergeProductToOrder;
2844
2859
  if (!onBeforeMergeProductToOrder)
2845
2860
  return true;
2846
- const result = await onBeforeMergeProductToOrder({
2861
+ const result = onBeforeMergeProductToOrder({
2847
2862
  ...params,
2848
2863
  defaultShouldMerge: true
2849
2864
  });
2850
- if (typeof result === "boolean")
2851
- return result;
2852
- if (result && typeof result === "object" && typeof result.shouldMerge === "boolean") {
2853
- return result.shouldMerge;
2865
+ if (result && typeof result === "object" && typeof result.then === "function") {
2866
+ return Promise.resolve(result).then(
2867
+ (resolved) => this.normalizeShouldMergeProductResult(resolved)
2868
+ );
2854
2869
  }
2855
- return true;
2870
+ return this.normalizeShouldMergeProductResult(result);
2856
2871
  }
2857
2872
  hasOrderProductLineNote(product) {
2858
2873
  return typeof (product == null ? void 0 : product.note) === "string" && product.note.trim().length > 0;
@@ -2895,10 +2910,15 @@ var OrderModule = class extends import_BaseModule.BaseModule {
2895
2910
  * 带 booking 且 num>1 时由 `addProductToOrder` 拆成多次本方法调用(每次 num=1)。
2896
2911
  *
2897
2912
  * @example
2898
- * await this.appendProductLineToTempOrder(tempOrder, { product_id: 1, num: 1 }, booking);
2913
+ * const pending = this.appendProductLineToTempOrder(
2914
+ * tempOrder,
2915
+ * { product_id: 1, num: 1 },
2916
+ * booking,
2917
+ * );
2918
+ * if (pending) await pending;
2899
2919
  */
2900
- async appendProductLineToTempOrder(tempOrder, product, booking, options) {
2901
- var _a, _b, _c, _d, _e, _f, _g;
2920
+ appendProductLineToTempOrder(tempOrder, product, booking, options) {
2921
+ var _a, _b, _c, _d, _e, _f;
2902
2922
  const linked = booking ? this.createLinkedProductAndBooking({ product, booking }) : null;
2903
2923
  const productToAdd = (linked == null ? void 0 : linked.product) || product;
2904
2924
  const incomingFingerprint = (0, import_utils3.buildProductLineFingerprint)(
@@ -2957,7 +2977,107 @@ var OrderModule = class extends import_BaseModule.BaseModule {
2957
2977
  (0, import_utils.getProductSkuOptions)(matchedProduct),
2958
2978
  matchedProduct.product_bundle
2959
2979
  ) : "";
2960
- const shouldMerge = matchedProduct ? await this.shouldMergeProductToOrder({
2980
+ const commitProductLine = (shouldMerge2) => {
2981
+ var _a2;
2982
+ if (matchedIndex === -1 || !matchedProduct || !shouldMerge2) {
2983
+ this.captureProductRuntime(
2984
+ tempOrder,
2985
+ productToAdd,
2986
+ normalizedIncoming
2987
+ );
2988
+ if (linked) {
2989
+ normalizedIncoming.booking_uid = linked.bookingUid;
2990
+ normalizedIncoming.metadata = {
2991
+ ...normalizedIncoming.metadata || {},
2992
+ booking_uid: linked.bookingUid
2993
+ };
2994
+ }
2995
+ const insertAtIndex = options == null ? void 0 : options.insertAtIndex;
2996
+ if (Number.isInteger(insertAtIndex) && Number(insertAtIndex) >= 0 && Number(insertAtIndex) < tempOrder.products.length) {
2997
+ tempOrder.products = [
2998
+ ...tempOrder.products.slice(0, insertAtIndex),
2999
+ normalizedIncoming,
3000
+ ...tempOrder.products.slice(insertAtIndex)
3001
+ ];
3002
+ } else {
3003
+ tempOrder.products = [...tempOrder.products, normalizedIncoming];
3004
+ }
3005
+ } else {
3006
+ const targetProduct = matchedProduct;
3007
+ const targetUid = (_a2 = targetProduct.metadata) == null ? void 0 : _a2.unique_identification_number;
3008
+ const normalizedProduct = (0, import_utils.mergeOrderProductDisplayFields)(
3009
+ targetProduct,
3010
+ (0, import_utils3.normalizeOrderProduct)({
3011
+ ...productToAdd,
3012
+ metadata: {
3013
+ ...productToAdd.metadata || {},
3014
+ unique_identification_number: targetUid
3015
+ }
3016
+ })
3017
+ );
3018
+ normalizedProduct.discount_list = (0, import_utils.normalizeOrderProductDiscountList)(
3019
+ normalizedProduct.discount_list
3020
+ );
3021
+ const nextNum = (0, import_utils3.getSafeProductNum)(targetProduct.num + normalizedProduct.num);
3022
+ const targetIsManualProductDiscount = isManualProductDiscountProduct(targetProduct);
3023
+ const nextDiscountList = targetIsManualProductDiscount ? (0, import_utils.normalizeOrderProductDiscountList)(
3024
+ (0, import_manualProductDiscount.syncManualProductDiscountProductNum)(
3025
+ targetProduct.discount_list,
3026
+ nextNum
3027
+ )
3028
+ ) : normalizedProduct.discount_list;
3029
+ tempOrder.products[matchedIndex] = {
3030
+ ...targetProduct,
3031
+ ...normalizedProduct,
3032
+ discount_list: nextDiscountList,
3033
+ metadata: {
3034
+ ...targetProduct.metadata || {},
3035
+ ...normalizedProduct.metadata || {},
3036
+ unique_identification_number: targetUid
3037
+ },
3038
+ product_sku: {
3039
+ ...targetProduct.product_sku || { option: [] },
3040
+ ...normalizedProduct.product_sku || { option: [] },
3041
+ option: (0, import_utils.getProductSkuOptions)(normalizedProduct)
3042
+ },
3043
+ ...targetIsManualProductDiscount ? {
3044
+ selling_price: targetProduct.selling_price,
3045
+ original_price: targetProduct.original_price,
3046
+ payment_price: targetProduct.payment_price,
3047
+ metadata: {
3048
+ ...normalizedProduct.metadata || {},
3049
+ ...targetProduct.metadata || {},
3050
+ unique_identification_number: targetUid
3051
+ }
3052
+ } : {},
3053
+ note: targetProduct.note,
3054
+ order_detail_id: targetProduct.order_detail_id,
3055
+ num: nextNum
3056
+ };
3057
+ this.captureProductRuntime(
3058
+ tempOrder,
3059
+ productToAdd,
3060
+ tempOrder.products[matchedIndex],
3061
+ targetUid
3062
+ );
3063
+ if (matchedIndex !== tempOrder.products.length - 1) {
3064
+ const mergedProduct = tempOrder.products[matchedIndex];
3065
+ tempOrder.products = [
3066
+ ...tempOrder.products.slice(0, matchedIndex),
3067
+ ...tempOrder.products.slice(matchedIndex + 1),
3068
+ mergedProduct
3069
+ ];
3070
+ }
3071
+ }
3072
+ if (linked) {
3073
+ tempOrder.bookings = [...tempOrder.bookings || [], linked.booking];
3074
+ }
3075
+ };
3076
+ if (!matchedProduct) {
3077
+ commitProductLine(false);
3078
+ return;
3079
+ }
3080
+ const shouldMerge = this.shouldMergeProductToOrder({
2961
3081
  incomingProduct: productToAdd,
2962
3082
  normalizedIncoming,
2963
3083
  matchedProduct,
@@ -2966,100 +3086,11 @@ var OrderModule = class extends import_BaseModule.BaseModule {
2966
3086
  existedFingerprint,
2967
3087
  tempOrder,
2968
3088
  booking
2969
- }) : false;
2970
- if (matchedIndex === -1 || !matchedProduct || !shouldMerge) {
2971
- this.captureProductRuntime(
2972
- tempOrder,
2973
- productToAdd,
2974
- normalizedIncoming
2975
- );
2976
- if (linked) {
2977
- normalizedIncoming.booking_uid = linked.bookingUid;
2978
- normalizedIncoming.metadata = {
2979
- ...normalizedIncoming.metadata || {},
2980
- booking_uid: linked.bookingUid
2981
- };
2982
- }
2983
- const insertAtIndex = options == null ? void 0 : options.insertAtIndex;
2984
- if (Number.isInteger(insertAtIndex) && Number(insertAtIndex) >= 0 && Number(insertAtIndex) < tempOrder.products.length) {
2985
- tempOrder.products = [
2986
- ...tempOrder.products.slice(0, insertAtIndex),
2987
- normalizedIncoming,
2988
- ...tempOrder.products.slice(insertAtIndex)
2989
- ];
2990
- } else {
2991
- tempOrder.products = [...tempOrder.products, normalizedIncoming];
2992
- }
2993
- } else {
2994
- const targetProduct = matchedProduct;
2995
- const targetUid = (_g = targetProduct.metadata) == null ? void 0 : _g.unique_identification_number;
2996
- const normalizedProduct = (0, import_utils.mergeOrderProductDisplayFields)(
2997
- targetProduct,
2998
- (0, import_utils3.normalizeOrderProduct)({
2999
- ...productToAdd,
3000
- metadata: {
3001
- ...productToAdd.metadata || {},
3002
- unique_identification_number: targetUid
3003
- }
3004
- })
3005
- );
3006
- normalizedProduct.discount_list = (0, import_utils.normalizeOrderProductDiscountList)(
3007
- normalizedProduct.discount_list
3008
- );
3009
- const nextNum = (0, import_utils3.getSafeProductNum)(targetProduct.num + normalizedProduct.num);
3010
- const targetIsManualProductDiscount = isManualProductDiscountProduct(targetProduct);
3011
- const nextDiscountList = targetIsManualProductDiscount ? (0, import_utils.normalizeOrderProductDiscountList)(
3012
- (0, import_manualProductDiscount.syncManualProductDiscountProductNum)(
3013
- targetProduct.discount_list,
3014
- nextNum
3015
- )
3016
- ) : normalizedProduct.discount_list;
3017
- tempOrder.products[matchedIndex] = {
3018
- ...targetProduct,
3019
- ...normalizedProduct,
3020
- discount_list: nextDiscountList,
3021
- metadata: {
3022
- ...targetProduct.metadata || {},
3023
- ...normalizedProduct.metadata || {},
3024
- unique_identification_number: targetUid
3025
- },
3026
- product_sku: {
3027
- ...targetProduct.product_sku || { option: [] },
3028
- ...normalizedProduct.product_sku || { option: [] },
3029
- option: (0, import_utils.getProductSkuOptions)(normalizedProduct)
3030
- },
3031
- ...targetIsManualProductDiscount ? {
3032
- selling_price: targetProduct.selling_price,
3033
- original_price: targetProduct.original_price,
3034
- payment_price: targetProduct.payment_price,
3035
- metadata: {
3036
- ...normalizedProduct.metadata || {},
3037
- ...targetProduct.metadata || {},
3038
- unique_identification_number: targetUid
3039
- }
3040
- } : {},
3041
- note: targetProduct.note,
3042
- order_detail_id: targetProduct.order_detail_id,
3043
- num: nextNum
3044
- };
3045
- this.captureProductRuntime(
3046
- tempOrder,
3047
- productToAdd,
3048
- tempOrder.products[matchedIndex],
3049
- targetUid
3050
- );
3051
- if (matchedIndex !== tempOrder.products.length - 1) {
3052
- const mergedProduct = tempOrder.products[matchedIndex];
3053
- tempOrder.products = [
3054
- ...tempOrder.products.slice(0, matchedIndex),
3055
- ...tempOrder.products.slice(matchedIndex + 1),
3056
- mergedProduct
3057
- ];
3058
- }
3059
- }
3060
- if (linked) {
3061
- tempOrder.bookings = [...tempOrder.bookings || [], linked.booking];
3089
+ });
3090
+ if (shouldMerge && typeof shouldMerge === "object" && typeof shouldMerge.then === "function") {
3091
+ return Promise.resolve(shouldMerge).then(commitProductLine);
3062
3092
  }
3093
+ commitProductLine(shouldMerge);
3063
3094
  }
3064
3095
  /**
3065
3096
  * 添加商品行。带 booking 时强制独立行;num>1 时拆成多条 num=1 的 product+booking(对齐 BookingByStep / ticketBooking addService)。
@@ -3087,12 +3118,14 @@ var OrderModule = class extends import_BaseModule.BaseModule {
3087
3118
  const singleLine = (0, import_lodash_es.cloneDeep)(productRecord);
3088
3119
  singleLine.num = 1;
3089
3120
  delete singleLine.product_quantity;
3090
- await this.appendProductLineToTempOrder(
3121
+ const appendResult2 = this.appendProductLineToTempOrder(
3091
3122
  tempOrder,
3092
3123
  singleLine,
3093
3124
  (0, import_lodash_es.cloneDeep)(booking),
3094
3125
  { insertAtIndex }
3095
3126
  );
3127
+ if (appendResult2)
3128
+ await appendResult2;
3096
3129
  if (insertAtIndex !== void 0)
3097
3130
  insertAtIndex += 1;
3098
3131
  }
@@ -3100,12 +3133,14 @@ var OrderModule = class extends import_BaseModule.BaseModule {
3100
3133
  return tempOrder.products;
3101
3134
  }
3102
3135
  }
3103
- await this.appendProductLineToTempOrder(
3136
+ const appendResult = this.appendProductLineToTempOrder(
3104
3137
  tempOrder,
3105
3138
  product,
3106
3139
  booking,
3107
3140
  { insertAtIndex }
3108
3141
  );
3142
+ if (appendResult)
3143
+ await appendResult;
3109
3144
  await this.finalizeProductOrderMutation(tempOrder);
3110
3145
  this.logInfo("addProductToOrder success", {
3111
3146
  product_id: product.product_id,
@@ -3140,16 +3175,24 @@ var OrderModule = class extends import_BaseModule.BaseModule {
3140
3175
  const singleLine = (0, import_lodash_es.cloneDeep)(productRecord);
3141
3176
  singleLine.num = 1;
3142
3177
  delete singleLine.product_quantity;
3143
- await this.appendProductLineToTempOrder(
3178
+ const appendResult2 = this.appendProductLineToTempOrder(
3144
3179
  tempOrder,
3145
3180
  singleLine,
3146
3181
  (0, import_lodash_es.cloneDeep)(booking)
3147
3182
  );
3183
+ if (appendResult2)
3184
+ await appendResult2;
3148
3185
  }
3149
3186
  continue;
3150
3187
  }
3151
3188
  }
3152
- await this.appendProductLineToTempOrder(tempOrder, product, booking);
3189
+ const appendResult = this.appendProductLineToTempOrder(
3190
+ tempOrder,
3191
+ product,
3192
+ booking
3193
+ );
3194
+ if (appendResult)
3195
+ await appendResult;
3153
3196
  }
3154
3197
  await this.finalizeProductOrderMutation(tempOrder, options);
3155
3198
  this.logInfo("addProductsToOrder success", {
@@ -0,0 +1,45 @@
1
+ import type { OrderTempOrder } from '../types';
2
+ /**
3
+ * 商品券拆行的身份与回并工具。
4
+ *
5
+ * 背景:
6
+ * Rules.calcDiscount 应用 good_pass 时,会把一个数量行拆成若干「用券行」和
7
+ * 「普通余量行」。Rules 返回的拆分行可能复制同一份
8
+ * metadata.unique_identification_number,进而造成:
9
+ * - 按 UID 删除时同时命中多行;
10
+ * - UI 把不同券行视为同一个商品;
11
+ * - 后续商品更新无法准确定位目标行。
12
+ *
13
+ * 处理原则:
14
+ * 1. 拆分后每一行必须拥有独立 UID;
15
+ * 2. 原始拆分来源仅记录在 tempOrder._extend.productsByUid 的运行态索引中,
16
+ * 不写入商品 metadata,不污染提交协议;
17
+ * 3. 券被移除后,只回并被本工具确认来自同一折扣拆分源的行;
18
+ * 4. 预约、赠品、促销、手动折扣、备注、称重、holder 和已落库行均为硬边界。
19
+ *
20
+ * 两个导出函数都会原地更新 tempOrder。调用顺序必须是:
21
+ * ensureUniqueProductLineUids -> consolidateDiscountFreeProductLines。
22
+ */
23
+ /** productsByUid 运行态记录中用于关联同一次折扣拆分的字段。 */
24
+ export declare const DISCOUNT_SPLIT_ORIGIN_UID_KEY = "discountSplitOriginUid";
25
+ /**
26
+ * 修复 Rules 折扣拆行复制 UID 的问题,并为拆分行记录共同来源。
27
+ *
28
+ * 第一条商品保留原 UID,后续重复行生成新 UUID;对应的 productsByUid.runtime
29
+ * 会复制原行 runtime(包括 origin),以保证删除、编辑及展示字段回读仍可工作。
30
+ *
31
+ * 没有 UID 的裸商品不会被补写 UID。这是为了兼容 Rules/mock 的历史协议形状;
32
+ * 正常 Order 写路径会在 normalize 阶段提前创建 UID。
33
+ */
34
+ export declare function ensureUniqueProductLineUids(tempOrder: OrderTempOrder): void;
35
+ /**
36
+ * 把券移除后已经恢复原价的同源折扣拆分行重新合并。
37
+ *
38
+ * 此函数刻意不执行通用的「同 SKU 合并」:没有 discountSplitOriginUid 的普通行
39
+ * 可能是业务 hook、导入订单或其它流程主动拆开的,不能在折扣重算时擅自合并。
40
+ *
41
+ * payment_price、tax_fee、surcharge_fee 属于 summary 计算层的派生值。券行与普通行
42
+ * 在回并前可能暂时不同,因此它们不参与 mergeKey;原始价格、SKU/option/bundle
43
+ * 结构以及税务/商品类型配置仍参与比较,防止不同商品语义被误合并。
44
+ */
45
+ export declare function consolidateDiscountFreeProductLines(tempOrder: OrderTempOrder): void;