@pisell/pisellos 0.10.59 → 0.10.61

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 (72) hide show
  1. package/dist/modules/BookingContext/utils/buildNormalProductCacheItemFromOrderLine.d.ts +3 -1
  2. package/dist/modules/BookingContext/utils/buildNormalProductCacheItemFromOrderLine.js +25 -2
  3. package/dist/modules/Discount/index.js +1 -0
  4. package/dist/modules/Order/holderRelations.d.ts +14 -0
  5. package/dist/modules/Order/holderRelations.js +154 -0
  6. package/dist/modules/Order/index.d.ts +1 -1
  7. package/dist/modules/Order/index.js +92 -47
  8. package/dist/modules/Order/types.d.ts +19 -1
  9. package/dist/modules/Order/types.js +2 -0
  10. package/dist/modules/Order/utils/discountProductLineIdentity.d.ts +1 -1
  11. package/dist/modules/Order/utils/discountProductLineIdentity.js +142 -7
  12. package/dist/modules/Order/utils/orderCollectionIdentity.js +24 -10
  13. package/dist/modules/Order/utils.d.ts +1 -1
  14. package/dist/modules/Order/utils.js +35 -9
  15. package/dist/server/index.js +18 -12
  16. package/dist/server/modules/order/index.d.ts +4 -0
  17. package/dist/server/modules/order/index.js +255 -190
  18. package/dist/server/modules/order/types.d.ts +3 -0
  19. package/dist/shared/receipt/small-ticket.js +17 -0
  20. package/dist/shared/sales/loadSalesOrderSnapshot.js +1 -1
  21. package/dist/solution/BaseSales/index.js +97 -27
  22. package/dist/solution/BaseSales/types.d.ts +2 -1
  23. package/dist/solution/BaseSales/utils/parseSalesResponse.d.ts +3 -1
  24. package/dist/solution/BaseSales/utils/parseSalesResponse.js +7 -3
  25. package/dist/solution/BaseSales/utils/transformBaseProductToOrderProduct.js +12 -3
  26. package/dist/solution/BookingTicket/index.js +7 -4
  27. package/dist/solution/BookingTicket/utils/addProductDecision.d.ts +1 -1
  28. package/dist/solution/BookingTicket/utils/addProductDecision.js +4 -3
  29. package/dist/solution/BookingTicket/utils/scan/applyGlobalScan.js +6 -2
  30. package/dist/solution/BookingTicket/utils/scan/cloudSearch.js +1 -0
  31. package/dist/solution/BookingTicket/utils/scan/handleScan.js +2 -1
  32. package/dist/solution/UnifiedBookingSales/index.d.ts +1 -1
  33. package/dist/solution/UnifiedBookingSales/index.js +4 -4
  34. package/dist/utils/platform.d.ts +2 -0
  35. package/dist/utils/platform.js +6 -0
  36. package/lib/model/strategy/adapter/promotion/index.js +46 -1
  37. package/lib/modules/BookingContext/utils/buildNormalProductCacheItemFromOrderLine.d.ts +3 -1
  38. package/lib/modules/BookingContext/utils/buildNormalProductCacheItemFromOrderLine.js +16 -1
  39. package/lib/modules/Discount/index.js +1 -0
  40. package/lib/modules/Order/holderRelations.d.ts +14 -0
  41. package/lib/modules/Order/holderRelations.js +98 -0
  42. package/lib/modules/Order/index.d.ts +1 -1
  43. package/lib/modules/Order/index.js +44 -12
  44. package/lib/modules/Order/types.d.ts +19 -1
  45. package/lib/modules/Order/types.js +1 -0
  46. package/lib/modules/Order/utils/discountProductLineIdentity.d.ts +1 -1
  47. package/lib/modules/Order/utils/discountProductLineIdentity.js +51 -2
  48. package/lib/modules/Order/utils/orderCollectionIdentity.js +5 -0
  49. package/lib/modules/Order/utils.d.ts +1 -1
  50. package/lib/modules/Order/utils.js +27 -6
  51. package/lib/server/index.js +6 -0
  52. package/lib/server/modules/order/index.d.ts +4 -0
  53. package/lib/server/modules/order/index.js +46 -4
  54. package/lib/server/modules/order/types.d.ts +3 -0
  55. package/lib/shared/receipt/small-ticket.js +15 -0
  56. package/lib/shared/sales/loadSalesOrderSnapshot.js +1 -1
  57. package/lib/solution/BaseSales/index.js +39 -2
  58. package/lib/solution/BaseSales/types.d.ts +2 -1
  59. package/lib/solution/BaseSales/utils/parseSalesResponse.d.ts +3 -1
  60. package/lib/solution/BaseSales/utils/parseSalesResponse.js +4 -0
  61. package/lib/solution/BaseSales/utils/transformBaseProductToOrderProduct.js +7 -0
  62. package/lib/solution/BookingTicket/index.js +4 -1
  63. package/lib/solution/BookingTicket/utils/addProductDecision.d.ts +1 -1
  64. package/lib/solution/BookingTicket/utils/addProductDecision.js +4 -3
  65. package/lib/solution/BookingTicket/utils/scan/applyGlobalScan.js +7 -1
  66. package/lib/solution/BookingTicket/utils/scan/cloudSearch.js +1 -0
  67. package/lib/solution/BookingTicket/utils/scan/handleScan.js +2 -1
  68. package/lib/solution/UnifiedBookingSales/index.d.ts +1 -1
  69. package/lib/solution/UnifiedBookingSales/index.js +3 -3
  70. package/lib/utils/platform.d.ts +2 -0
  71. package/lib/utils/platform.js +7 -0
  72. package/package.json +1 -1
@@ -1,4 +1,5 @@
1
1
  var CUSTOMER_USER_PLATFORMS = new Set(['pc', 'h5']);
2
+ var SELF_SERVICE_PLATFORMS = new Set(['kiosk', 'vending']);
2
3
 
3
4
  /**
4
5
  * Normalize the runtime platform passed through solution/module otherParams.
@@ -12,4 +13,9 @@ export function normalizeRuntimePlatform(platform) {
12
13
  */
13
14
  export function isCustomerUserPlatform(platform) {
14
15
  return CUSTOMER_USER_PLATFORMS.has(normalizeRuntimePlatform(platform));
16
+ }
17
+
18
+ /** Kiosk and vending retain distinct channels but share self-service checkout behavior. */
19
+ export function isSelfServicePlatform(platform) {
20
+ return SELF_SERVICE_PLATFORMS.has(normalizeRuntimePlatform(platform));
15
21
  }
@@ -1 +1,46 @@
1
- "use strict";
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ Object.defineProperty(exports, "BUY_X_GET_Y_FREE_STRATEGY", {
7
+ enumerable: true,
8
+ get: function () {
9
+ return _examples.BUY_X_GET_Y_FREE_STRATEGY;
10
+ }
11
+ });
12
+ Object.defineProperty(exports, "ITEM_REWARD_STRATEGY", {
13
+ enumerable: true,
14
+ get: function () {
15
+ return _examples.ITEM_REWARD_STRATEGY;
16
+ }
17
+ });
18
+ Object.defineProperty(exports, "PromotionAdapter", {
19
+ enumerable: true,
20
+ get: function () {
21
+ return _adapter.PromotionAdapter;
22
+ }
23
+ });
24
+ Object.defineProperty(exports, "PromotionEvaluator", {
25
+ enumerable: true,
26
+ get: function () {
27
+ return _evaluator.PromotionEvaluator;
28
+ }
29
+ });
30
+ Object.defineProperty(exports, "X_ITEMS_FOR_Y_PRICE_STRATEGY", {
31
+ enumerable: true,
32
+ get: function () {
33
+ return _examples.X_ITEMS_FOR_Y_PRICE_STRATEGY;
34
+ }
35
+ });
36
+ Object.defineProperty(exports, "default", {
37
+ enumerable: true,
38
+ get: function () {
39
+ return _adapter.default;
40
+ }
41
+ });
42
+ var _evaluator = require("./evaluator");
43
+ var _adapter = _interopRequireWildcard(require("./adapter"));
44
+ var _examples = require("./examples");
45
+ function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
46
+ function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
@@ -1,8 +1,10 @@
1
- import type { OrderProduct } from '../../Order/types';
1
+ import type { OrderHolderRelation, OrderProduct } from '../../Order/types';
2
2
  export interface BuildNormalProductCacheItemFromOrderLineInput {
3
3
  /** tempOrder.products 中的一行(普通商品,无关联 booking) */
4
4
  product: OrderProduct;
5
5
  /** 商品 catalog / 详情,用于 variant / option / bundle 模板字段 */
6
6
  sourceProduct?: Record<string, any> | null;
7
+ /** 整单关系集合,按商品行 UID 回填。 */
8
+ holder_relations?: OrderHolderRelation[];
7
9
  }
8
10
  export declare function buildNormalProductCacheItemFromOrderLine(input: BuildNormalProductCacheItemFromOrderLineInput): Record<string, any>;
@@ -4,13 +4,15 @@ Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
6
  exports.buildNormalProductCacheItemFromOrderLine = buildNormalProductCacheItemFromOrderLine;
7
+ var _lodashEs = require("lodash-es");
8
+ var _holderRelations = require("../../Order/holderRelations");
7
9
  var _utils = require("../../Order/utils");
8
10
  var _entitlementPass = require("../../Discount/entitlementPass");
9
11
  var _orderLineDisplay = require("./orderLineDisplay");
10
12
  /**
11
13
  * 从已加车的普通商品 order line 反查 SkuDetailModal edit 所需的 cacheItem。
12
14
  *
13
- * 与 `buildCacheItemFromOrderLine`(预约行 + booking)并列;不含 resource / duration / holder 等预约字段。
15
+ * 与 `buildCacheItemFromOrderLine`(预约行 + booking)并列;Holder 从订单关系回填,不生成预约字段。
14
16
  */
15
17
  /**
16
18
  * 解析 v2 主商品 source 价(不含 option、不含 bundle),供 SkuDetailModal / transform 使用。
@@ -84,6 +86,15 @@ function buildNormalProductCacheItemFromOrderLine(input) {
84
86
  const original = Number(product.original_price ?? selling);
85
87
  const uid = product.metadata?.unique_identification_number;
86
88
  const originExtend = product.metadata?.origin?._extend;
89
+ const holderRelations = (0, _lodashEs.cloneDeep)((0, _holderRelations.getProductHolderRelations)(input, product));
90
+ const formRelations = holderRelations.filter(relation => relation.holder_type === 'form');
91
+ const holderIds = formRelations.map(relation => relation.holder_id);
92
+ const holderNames = formRelations.map(relation => relation.metadata?.name).filter(name => typeof name === 'string' && name.length > 0);
93
+ const holder = formRelations.length > 0 ? {
94
+ ...(formRelations[0].metadata || {}),
95
+ form_record: holderIds,
96
+ name: holderNames.join(',')
97
+ } : null;
87
98
  const shouldPreserveEntitlementPriceOverride = hasEntitlementPassDiscount(product) && (originExtend?.priceOverride !== undefined || product.metadata?.price_override != null);
88
99
  const hasPriceOverride = originExtend?.priceOverride !== undefined || shouldPreserveEntitlementPriceOverride;
89
100
  const option = (0, _utils.getProductSkuOptions)(product);
@@ -115,6 +126,9 @@ function buildNormalProductCacheItemFromOrderLine(input) {
115
126
  skuValue: JSON.parse(JSON.stringify(skuValue)),
116
127
  _skuValue: JSON.parse(JSON.stringify(skuValue)),
117
128
  isNormalProduct: true,
129
+ holder_relations: holderRelations,
130
+ holder,
131
+ holder_id: holderIds,
118
132
  metadata: {
119
133
  product_add_schedule_time: product.metadata?.product_add_schedule_time
120
134
  },
@@ -134,6 +148,7 @@ function buildNormalProductCacheItemFromOrderLine(input) {
134
148
  }
135
149
  return {
136
150
  ...(sourceProduct || {}),
151
+ holder_config: sourceProduct?.holder_config !== undefined ? sourceProduct.holder_config : product.metadata?.holder_config,
137
152
  id: resolvedId,
138
153
  product_id: resolvedId,
139
154
  _id: uid,
@@ -142,6 +142,7 @@ class DiscountModule extends _BaseModule.BaseModule {
142
142
  } = options || {};
143
143
  const result = await this.request.get(`/machinecode/batch-search`, {
144
144
  code,
145
+ resolve_ticket_order: 1,
145
146
  translate_flag: 1,
146
147
  tags: ['entitlement_pass', 'good_pass', 'product_discount_card'],
147
148
  // available: 1,
@@ -0,0 +1,14 @@
1
+ import type { OrderHolderRelation, OrderTempOrder } from './types';
2
+ /** 在原订单上补齐关系身份,让缓存、提交和重试共用同一个 UUID。 */
3
+ export declare function ensureHolderRelationUuids(order: {
4
+ holder_relations?: OrderHolderRelation[];
5
+ }): void;
6
+ export declare function hasProductBooking(product: Record<string, any>): boolean;
7
+ export declare function getProductHolderRelations(order: {
8
+ holder_relations?: OrderHolderRelation[];
9
+ }, product: Record<string, any>): OrderHolderRelation[];
10
+ export declare function replaceProductHolderRelations(order: OrderTempOrder, product: Record<string, any>, patch: OrderHolderRelation[] | undefined): void;
11
+ /** 只删除明确被用户删除的行,不从稀疏远端快照推断删除。 */
12
+ export declare function removeProductHolderRelations(order: OrderTempOrder, products: Array<Record<string, any>>): void;
13
+ /** 仅兼容尚无新字段的旧测试草稿;显式 [] 永远不恢复旧 product 数据。 */
14
+ export declare function restoreLegacyHolderRelations(order: Record<string, any>): OrderHolderRelation[] | undefined;
@@ -0,0 +1,98 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.ensureHolderRelationUuids = ensureHolderRelationUuids;
7
+ exports.getProductHolderRelations = getProductHolderRelations;
8
+ exports.hasProductBooking = hasProductBooking;
9
+ exports.removeProductHolderRelations = removeProductHolderRelations;
10
+ exports.replaceProductHolderRelations = replaceProductHolderRelations;
11
+ exports.restoreLegacyHolderRelations = restoreLegacyHolderRelations;
12
+ var _orderCollectionIdentity = require("./utils/orderCollectionIdentity");
13
+ function getHolderRelationUuid(relation) {
14
+ return typeof relation?.uuid === 'string' && relation.uuid.trim() ? relation.uuid : undefined;
15
+ }
16
+
17
+ /** 在原订单上补齐关系身份,让缓存、提交和重试共用同一个 UUID。 */
18
+ function ensureHolderRelationUuids(order) {
19
+ if (!Array.isArray(order.holder_relations)) return;
20
+ for (const relation of order.holder_relations) {
21
+ if (relation && typeof relation === 'object' && !getHolderRelationUuid(relation)) {
22
+ relation.uuid = (0, _orderCollectionIdentity.createUuidV4)();
23
+ }
24
+ }
25
+ }
26
+ function hasProductBooking(product) {
27
+ const bookingIds = [product.booking_id, product.metadata?.booking_id, product.schedule_event_id];
28
+ return Boolean(product.booking_uid || product.metadata?.booking_uid) || bookingIds.some(id => id !== undefined && id !== null && String(id) !== '' && String(id) !== '0');
29
+ }
30
+ function getProductHolderRelations(order, product) {
31
+ const uid = product.metadata?.unique_identification_number ?? product.unique_identification_number;
32
+ if (uid === undefined || uid === null || uid === '') return [];
33
+ return (order.holder_relations || []).filter(relation => relation.relation_model === 'order_detail' && String(relation.relation_uuid) === String(uid));
34
+ }
35
+ function replaceProductHolderRelations(order, product, patch) {
36
+ if (patch === undefined || hasProductBooking(product)) return;
37
+ const uid = product.metadata?.unique_identification_number ?? product.unique_identification_number;
38
+ if (uid === undefined || uid === null || uid === '') return;
39
+ const existing = getProductHolderRelations(order, product);
40
+ const next = new Map();
41
+ for (const relation of patch) {
42
+ const key = `${relation.holder_type}:${relation.holder_id}`;
43
+ const previous = existing.find(item => item.holder_type === relation.holder_type && String(item.holder_id) === String(relation.holder_id));
44
+ next.set(key, {
45
+ ...previous,
46
+ ...relation,
47
+ uuid: getHolderRelationUuid(previous) || getHolderRelationUuid(relation) || (0, _orderCollectionIdentity.createUuidV4)(),
48
+ relation_model: 'order_detail',
49
+ relation_uuid: String(uid),
50
+ ...(previous?.metadata || relation.metadata ? {
51
+ metadata: {
52
+ ...previous?.metadata,
53
+ ...relation.metadata
54
+ }
55
+ } : {})
56
+ });
57
+ }
58
+ order.holder_relations = [...(order.holder_relations || []).filter(relation => relation.relation_model !== 'order_detail' || String(relation.relation_uuid) !== String(uid)), ...next.values()];
59
+ }
60
+
61
+ /** 只删除明确被用户删除的行,不从稀疏远端快照推断删除。 */
62
+ function removeProductHolderRelations(order, products) {
63
+ if (!Array.isArray(order.holder_relations)) return;
64
+ const removedUids = new Set(products.map(product => String(product.metadata?.unique_identification_number ?? product.unique_identification_number ?? '')));
65
+ order.holder_relations = order.holder_relations.filter(relation => relation.relation_model !== 'order_detail' || !removedUids.has(String(relation.relation_uuid)));
66
+ }
67
+
68
+ /** 仅兼容尚无新字段的旧测试草稿;显式 [] 永远不恢复旧 product 数据。 */
69
+ function restoreLegacyHolderRelations(order) {
70
+ if (Object.prototype.hasOwnProperty.call(order, 'holder_relations')) {
71
+ return Array.isArray(order.holder_relations) ? order.holder_relations : undefined;
72
+ }
73
+ const relations = [];
74
+ const bookingProductUids = new Set((order.bookings || []).map(booking => String(booking.product_uid ?? booking.metadata?.product_uid ?? '')));
75
+ for (const product of order.products || []) {
76
+ if (hasProductBooking(product)) continue;
77
+ const uid = product.metadata?.unique_identification_number ?? product.unique_identification_number;
78
+ if (uid === undefined || uid === null || uid === '' || bookingProductUids.has(String(uid))) continue;
79
+ const holder = product.metadata?.holder || {};
80
+ const rawIds = product.metadata?.holder_id ?? holder.form_record;
81
+ const ids = (Array.isArray(rawIds) ? rawIds : [rawIds]).filter(id => id !== undefined && id !== null && id !== '' && Number.isInteger(Number(id)) && Number(id) >= 0);
82
+ for (const id of ids) {
83
+ const metadata = {
84
+ ...holder,
85
+ form_record: [Number(id)]
86
+ };
87
+ if (ids.length !== 1) delete metadata.name;
88
+ relations.push({
89
+ relation_model: 'order_detail',
90
+ relation_uuid: String(uid),
91
+ holder_type: 'form',
92
+ holder_id: Number(id),
93
+ metadata
94
+ });
95
+ }
96
+ }
97
+ return relations.length ? relations : undefined;
98
+ }
@@ -687,6 +687,6 @@ export declare class OrderModule extends BaseModule implements Module, OrderModu
687
687
  getOrderInfo(order_id: number): Promise<any>;
688
688
  getVouchers(): any[];
689
689
  }
690
- export type { AddProductToOrderOptions, OrderMetaList, PointCardSnapshotItem, VirtualCurrencySnapshotItem, RemoveProductsFromOrderOptions, SalesNote, SalesNoteDraftInput, SalesNoteLocalizedSelection, SalesNoteLocalizedText, SalesNoteRelation, SalesNoteTextInput, SetSalesNoteInput, UpdateOrderBookingParams, UpdateOrderProductParams, UpdateOrderProductQuantityParams, } from './types';
690
+ export type { OrderHolderRelation, AddProductToOrderOptions, OrderMetaList, PointCardSnapshotItem, VirtualCurrencySnapshotItem, RemoveProductsFromOrderOptions, SalesNote, SalesNoteDraftInput, SalesNoteLocalizedSelection, SalesNoteLocalizedText, SalesNoteRelation, SalesNoteTextInput, SetSalesNoteInput, UpdateOrderBookingParams, UpdateOrderProductParams, UpdateOrderProductQuantityParams, } from './types';
691
691
  export { isFulfillmentTimingBooking } from './pickupTiming';
692
692
  export type { PickupTimingBooking, PickupTimingBookingInput, PickupTimingBookingMetadata, PickupTimingSelectionMode, PickupTimingSource, } from './pickupTiming';
@@ -15,6 +15,7 @@ var _BaseModule = require("../BaseModule");
15
15
  var _types = require("./types");
16
16
  var _utils = require("./utils");
17
17
  var _utils2 = require("../Product/utils");
18
+ var _holderRelations = require("./holderRelations");
18
19
  var _weighedProduct = require("./utils/weighedProduct");
19
20
  var _pickupTiming = require("./pickupTiming");
20
21
  var _dayjs = _interopRequireDefault(require("dayjs"));
@@ -602,7 +603,7 @@ class OrderModule extends _BaseModule.BaseModule {
602
603
  this.store.rules = rules;
603
604
  }
604
605
  createDefaultRulesHooks() {
605
- return (0, _utils.createDefaultOrderRulesHooks)(() => this.store.tempOrder?.bookings || []);
606
+ return (0, _utils.createDefaultOrderRulesHooks)(() => this.store.tempOrder?.bookings || [], () => this.store.tempOrder?.holder_relations);
606
607
  }
607
608
 
608
609
  /**
@@ -916,7 +917,7 @@ class OrderModule extends _BaseModule.BaseModule {
916
917
  discount_list: (0, _utils.normalizeOrderProductDiscountList)((0, _manualProductDiscount.syncManualProductDiscountProductNum)(mergedProduct.discount_list, mergedProduct.num))
917
918
  };
918
919
  }));
919
- (0, _discountProductLineIdentity.ensureUniqueProductLineUids)(tempOrder);
920
+ (0, _discountProductLineIdentity.ensureUniqueProductLineUids)(tempOrder, discountList);
920
921
  (0, _discountProductLineIdentity.consolidateDiscountFreeProductLines)(tempOrder);
921
922
  }
922
923
  if (result?.discountList) {
@@ -1161,7 +1162,9 @@ class OrderModule extends _BaseModule.BaseModule {
1161
1162
  * sessionStorage 的 cacheId 分桶。写入失败由 BaseModule 静默降级。
1162
1163
  */
1163
1164
  persistTempOrder() {
1164
- if (!this.sessionStoragePersistEnabled || !this.store.tempOrder) return;
1165
+ if (!this.store.tempOrder) return;
1166
+ (0, _holderRelations.ensureHolderRelationUuids)(this.store.tempOrder);
1167
+ if (!this.sessionStoragePersistEnabled) return;
1165
1168
  this.checkSaveCache({
1166
1169
  cacheId: this.cacheId,
1167
1170
  fatherModule: this.fatherModule,
@@ -1870,7 +1873,10 @@ class OrderModule extends _BaseModule.BaseModule {
1870
1873
  source_product_price: this.normalizeFingerprintMoney(metadata.source_product_price),
1871
1874
  main_product_selling_price: this.normalizeFingerprintMoney(metadata.main_product_selling_price),
1872
1875
  main_product_original_price: this.normalizeFingerprintMoney(metadata.main_product_original_price),
1873
- product_discount_difference: this.normalizeFingerprintMoney(metadata.product_discount_difference)
1876
+ product_discount_difference: this.normalizeFingerprintMoney(metadata.product_discount_difference),
1877
+ ...(!(0, _holderRelations.hasProductBooking)(product) ? {
1878
+ holder_relations: this.normalizeFingerprintValue((0, _holderRelations.getProductHolderRelations)(this.store.tempOrder || {}, product))
1879
+ } : {})
1874
1880
  },
1875
1881
  product_sku: this.normalizeFingerprintValue({
1876
1882
  code: sku.code ?? '',
@@ -1906,7 +1912,7 @@ class OrderModule extends _BaseModule.BaseModule {
1906
1912
  items.sort((left, right) => JSON.stringify(left).localeCompare(JSON.stringify(right)));
1907
1913
  return JSON.stringify(items);
1908
1914
  }
1909
- buildProductStructuralFingerprintItem(product) {
1915
+ buildProductStructuralFingerprintItem(product, order = this.store.tempOrder || {}) {
1910
1916
  const sku = product?.product_sku || {};
1911
1917
  const metadata = product?.metadata || {};
1912
1918
  const discountTypes = (product?.discount_list || []).map(discount => discount?.type ?? discount?.tag);
@@ -1921,7 +1927,10 @@ class OrderModule extends _BaseModule.BaseModule {
1921
1927
  unique_identification_number: metadata.unique_identification_number ?? product?.unique_identification_number ?? '',
1922
1928
  price_override: metadata.price_override ?? '',
1923
1929
  is_manual_discount: metadata.is_manual_discount ?? '',
1924
- product_discount_reason: metadata.product_discount_reason ?? ''
1930
+ product_discount_reason: metadata.product_discount_reason ?? '',
1931
+ ...(!(0, _holderRelations.hasProductBooking)(product) ? {
1932
+ holder_relations: this.normalizeFingerprintValue((0, _holderRelations.getProductHolderRelations)(order, product))
1933
+ } : {})
1925
1934
  },
1926
1935
  product_sku: this.normalizeFingerprintValue({
1927
1936
  code: sku.code ?? '',
@@ -1959,8 +1968,8 @@ class OrderModule extends _BaseModule.BaseModule {
1959
1968
  })))
1960
1969
  };
1961
1970
  }
1962
- buildOrderProductsStructuralFingerprint(products) {
1963
- const items = (products || []).map(product => this.buildProductStructuralFingerprintItem(product));
1971
+ buildOrderProductsStructuralFingerprint(products, order = this.store.tempOrder || {}) {
1972
+ const items = (products || []).map(product => this.buildProductStructuralFingerprintItem(product, order));
1964
1973
  items.sort((left, right) => JSON.stringify(left).localeCompare(JSON.stringify(right)));
1965
1974
  return JSON.stringify(items);
1966
1975
  }
@@ -2027,7 +2036,7 @@ class OrderModule extends _BaseModule.BaseModule {
2027
2036
  const manualDepositOverride = this.getManualDepositOverride(tempOrder);
2028
2037
  if (currentFingerprint !== snapshot.productFingerprint) {
2029
2038
  const currentStructuralFingerprint = this.buildOrderProductsStructuralFingerprint(tempOrder.products);
2030
- const snapshotStructuralFingerprint = this.buildOrderProductsStructuralFingerprint(Array.isArray(snapshot.products) ? snapshot.products : []);
2039
+ const snapshotStructuralFingerprint = this.buildOrderProductsStructuralFingerprint(Array.isArray(snapshot.products) ? snapshot.products : [], snapshot);
2031
2040
  if (currentStructuralFingerprint !== snapshotStructuralFingerprint) return null;
2032
2041
  }
2033
2042
  let summary = {
@@ -2248,9 +2257,16 @@ class OrderModule extends _BaseModule.BaseModule {
2248
2257
  product.metadata = metadata;
2249
2258
  return String(uid);
2250
2259
  }
2251
- sanitizeOrderProductForTempOrder(product) {
2260
+ sanitizeOrderProductForTempOrder(product, deferHolderCleanup = false) {
2252
2261
  if (product.metadata && typeof product.metadata === 'object') {
2253
2262
  delete product.metadata.origin;
2263
+ if (!deferHolderCleanup && !(0, _holderRelations.hasProductBooking)(product)) {
2264
+ delete product.metadata.holder;
2265
+ delete product.metadata.holder_id;
2266
+ }
2267
+ }
2268
+ if (product._extend?.holder_relations !== undefined) {
2269
+ delete product._extend.holder_relations;
2254
2270
  }
2255
2271
  return product;
2256
2272
  }
@@ -3039,6 +3055,7 @@ class OrderModule extends _BaseModule.BaseModule {
3039
3055
  const nextCustomerId = tempOrder.customer_id ?? null;
3040
3056
  if (String(previousCustomerId ?? '') !== String(nextCustomerId ?? '')) {
3041
3057
  this.clearCustomerWalletState(tempOrder);
3058
+ (0, _holderRelations.removeProductHolderRelations)(tempOrder, tempOrder.products.filter(product => !(0, _holderRelations.hasProductBooking)(product)));
3042
3059
  }
3043
3060
  this.persistTempOrder();
3044
3061
  return {
@@ -3580,10 +3597,13 @@ class OrderModule extends _BaseModule.BaseModule {
3580
3597
  const hasIncomingProductNote = this.hasOrderProductLineNote(normalizedIncoming);
3581
3598
  const hasIncomingManualProductDiscount = isManualProductDiscountProduct(normalizedIncoming);
3582
3599
  const hasIncomingBookingUid = this.hasLinkedBookingUid(productToAdd) || this.hasLinkedBookingUid(normalizedIncoming);
3600
+ const holderRelationsPatch = !hasIncomingBookingUid ? productToAdd._extend?.holder_relations : undefined;
3601
+ const hasIncomingHolder = Array.isArray(holderRelationsPatch) && holderRelationsPatch.length > 0;
3583
3602
  const isIncomingCustomItem = productToAdd.metadata?.origin?.isCustomItem === true || productToAdd._origin?.isCustomItem === true;
3584
- const shouldForceNewLine = disableMerge || !!booking || hasIncomingGoodPass || isIncomingPersistedEntitlementPass || hasIncomingBookingUid || isIncomingCustomItem || isWeighingProduct;
3603
+ const shouldForceNewLine = disableMerge || !!booking || hasIncomingGoodPass || isIncomingPersistedEntitlementPass || hasIncomingBookingUid || hasIncomingHolder || isIncomingCustomItem || isWeighingProduct;
3585
3604
  const matchedIndex = shouldForceNewLine ? -1 : tempOrder.products.findIndex(item => {
3586
3605
  if (this.hasGoodPassDiscount(item)) return false;
3606
+ if ((0, _holderRelations.getProductHolderRelations)(tempOrder, item).length > 0 || (0, _utils.hasAssignedHolderId)(item.metadata?.holder_id)) return false;
3587
3607
  const isRuntimeEntitlementPass = this.isRuntimeEntitlementAppend(item, tempOrder);
3588
3608
  if (isIncomingEntitlementAppend !== isRuntimeEntitlementPass) {
3589
3609
  return false;
@@ -3674,6 +3694,7 @@ class OrderModule extends _BaseModule.BaseModule {
3674
3694
  if (linked) {
3675
3695
  tempOrder.bookings = [...(tempOrder.bookings || []), linked.booking];
3676
3696
  }
3697
+ (0, _holderRelations.replaceProductHolderRelations)(tempOrder, committedProduct, holderRelationsPatch);
3677
3698
  return committedProduct;
3678
3699
  };
3679
3700
  if (!matchedProduct) {
@@ -3779,6 +3800,7 @@ class OrderModule extends _BaseModule.BaseModule {
3779
3800
  }));
3780
3801
  const mutationSnapshot = salesNoteDrafts.length > 0 ? {
3781
3802
  products: (0, _lodashEs.cloneDeep)(tempOrder.products || []),
3803
+ holder_relations: (0, _lodashEs.cloneDeep)(tempOrder.holder_relations),
3782
3804
  bookings: (0, _lodashEs.cloneDeep)(tempOrder.bookings || []),
3783
3805
  notes: (0, _lodashEs.cloneDeep)(tempOrder.notes || []),
3784
3806
  extend: (0, _lodashEs.cloneDeep)(tempOrder._extend || {})
@@ -3803,6 +3825,7 @@ class OrderModule extends _BaseModule.BaseModule {
3803
3825
  const rollbackProductAndNotesMutation = () => {
3804
3826
  if (!mutationSnapshot) return;
3805
3827
  tempOrder.products = mutationSnapshot.products;
3828
+ if (mutationSnapshot.holder_relations === undefined) delete tempOrder.holder_relations;else tempOrder.holder_relations = mutationSnapshot.holder_relations;
3806
3829
  tempOrder.bookings = mutationSnapshot.bookings;
3807
3830
  tempOrder.notes = mutationSnapshot.notes;
3808
3831
  tempOrder._extend = mutationSnapshot.extend;
@@ -4255,6 +4278,7 @@ class OrderModule extends _BaseModule.BaseModule {
4255
4278
  if (uid) reapplyWeighedDiscountProductUids?.push(String(uid));
4256
4279
  }
4257
4280
  tempOrder.products[productIndex] = normalizedProduct;
4281
+ (0, _holderRelations.replaceProductHolderRelations)(tempOrder, normalizedProduct, updates._extend?.holder_relations);
4258
4282
  this.syncTempOrderHolderFromBookings(tempOrder);
4259
4283
  this.captureProductRuntime(tempOrder, updates, tempOrder.products[productIndex], tempOrder.products[productIndex].metadata?.unique_identification_number);
4260
4284
  return tempOrder.products[productIndex].metadata?.unique_identification_number;
@@ -4536,6 +4560,7 @@ class OrderModule extends _BaseModule.BaseModule {
4536
4560
  tempOrder.products = nextProductIndex >= 0 ? [...productsWithoutRemaining.slice(0, nextProductIndex), remainingProduct, ...productsWithoutRemaining.slice(nextProductIndex)] : [...productsWithoutRemaining, remainingProduct];
4537
4561
  }
4538
4562
  }
4563
+ (0, _holderRelations.removeProductHolderRelations)(tempOrder, productsBeforeRemove.filter(product => !tempOrder.products.includes(product)));
4539
4564
  this.removeSalesNotesForMissingProductLines(tempOrder);
4540
4565
  if (mutation.commit) tempOrder = await mutation.commit();
4541
4566
  await this.finalizeAfterProductsMutation(tempOrder);
@@ -4559,6 +4584,7 @@ class OrderModule extends _BaseModule.BaseModule {
4559
4584
  const mutation = this.beginCartMutation();
4560
4585
  let tempOrder = mutation.draft;
4561
4586
  this.ensureSalesNotesLoadedForMutation(tempOrder);
4587
+ (0, _holderRelations.removeProductHolderRelations)(tempOrder, tempOrder.products);
4562
4588
  tempOrder.products = [];
4563
4589
  tempOrder.bookings = [];
4564
4590
  (0, _virtualSettlement.releaseTempOrderSettlementState)(tempOrder);
@@ -4589,6 +4615,7 @@ class OrderModule extends _BaseModule.BaseModule {
4589
4615
  async clearOrderForSettlementSwitch() {
4590
4616
  const tempOrder = this.ensureTempOrder();
4591
4617
  this.ensureSalesNotesLoadedForMutation(tempOrder);
4618
+ (0, _holderRelations.removeProductHolderRelations)(tempOrder, tempOrder.products);
4592
4619
  tempOrder.products = [];
4593
4620
  tempOrder.bookings = [];
4594
4621
  this.removeSalesNotesForMissingProductLines(tempOrder);
@@ -5331,6 +5358,7 @@ class OrderModule extends _BaseModule.BaseModule {
5331
5358
  originalSalesSnapshot: {
5332
5359
  summary: (0, _lodashEs.cloneDeep)(this.store.summary),
5333
5360
  products: (0, _lodashEs.cloneDeep)(nextTempOrder.products || []),
5361
+ holder_relations: (0, _lodashEs.cloneDeep)(nextTempOrder.holder_relations),
5334
5362
  bookings: (0, _lodashEs.cloneDeep)(nextTempOrder.bookings || []),
5335
5363
  payments: (0, _lodashEs.cloneDeep)(nextTempOrder.payments || []),
5336
5364
  surcharges: (0, _lodashEs.cloneDeep)(nextTempOrder.surcharges || []),
@@ -5483,6 +5511,9 @@ class OrderModule extends _BaseModule.BaseModule {
5483
5511
  nextTempOrder.metadata = raw.metadata && typeof raw.metadata === 'object' ? {
5484
5512
  ...raw.metadata
5485
5513
  } : {};
5514
+ const holderRelations = (0, _holderRelations.restoreLegacyHolderRelations)(raw);
5515
+ if (holderRelations === undefined) delete nextTempOrder.holder_relations;else nextTempOrder.holder_relations = (0, _lodashEs.cloneDeep)(holderRelations);
5516
+ (0, _holderRelations.ensureHolderRelationUuids)(nextTempOrder);
5486
5517
  nextTempOrder.holder = raw.holder && typeof raw.holder === 'object' ? {
5487
5518
  ...raw.holder
5488
5519
  } : null;
@@ -5560,6 +5591,7 @@ class OrderModule extends _BaseModule.BaseModule {
5560
5591
  }
5561
5592
  tempOrderExtend.productsByUid = productsByUid;
5562
5593
  this.hydrateLinkedBookingIdentity(nextTempOrder);
5594
+ nextTempOrder.products.forEach(product => this.sanitizeOrderProductForTempOrder(product));
5563
5595
  if (options?.ensureIdentity !== false) {
5564
5596
  this.ensureExternalSaleNumber(nextTempOrder);
5565
5597
  }
@@ -5720,7 +5752,7 @@ class OrderModule extends _BaseModule.BaseModule {
5720
5752
  };
5721
5753
  if (result.metadata) delete result.metadata.price_schema_version;
5722
5754
  const restored = (0, _bundleDiscountHydration.restoreHydratedBundleDiscounts)(result, _utils.normalizeOrderProductDiscountList);
5723
- return this.sanitizeOrderProductForTempOrder(this.normalizeVirtualSettlementProductIfEnabled(restored));
5755
+ return this.sanitizeOrderProductForTempOrder(this.normalizeVirtualSettlementProductIfEnabled(restored), true);
5724
5756
  }
5725
5757
  getLastOrderInfo() {
5726
5758
  return this.store?.lastOrderInfo;
@@ -242,6 +242,11 @@ export interface OrderProduct extends OrderProductIdentity {
242
242
  product_cover?: string;
243
243
  metadata: Record<string, any>;
244
244
  note?: string;
245
+ /** 仅作按行更新输入;正式数据保存于订单顶层。 */
246
+ _extend?: {
247
+ holder_relations?: OrderHolderRelation[];
248
+ [key: string]: any;
249
+ };
245
250
  }
246
251
  export interface OrderSummary {
247
252
  product_quantity: number;
@@ -339,6 +344,17 @@ export interface SalesNotesMutationSnapshot {
339
344
  }
340
345
  export type SalesNoteProtocolRelation = SalesNoteRelation;
341
346
  export type SalesNoteProtocol = SalesNote;
347
+ /** 后端关系及 metadata 均为开放对象,未知字段必须往返保留。 */
348
+ export interface OrderHolderRelation {
349
+ /** 关系自身的 UUID;新选择/旧草稿可缺省,入单与提交时由 OS 补齐。 */
350
+ uuid?: string;
351
+ relation_model: string;
352
+ relation_uuid: string;
353
+ holder_type: 'form' | 'customer';
354
+ holder_id: number;
355
+ metadata?: Record<string, any>;
356
+ [key: string]: any;
357
+ }
342
358
  export interface OrderTempOrder {
343
359
  order_id: number | null;
344
360
  external_sale_number: string;
@@ -378,6 +394,7 @@ export interface OrderTempOrder {
378
394
  created_at: string | undefined;
379
395
  request_unique_idempotency_token?: string;
380
396
  products: OrderProduct[];
397
+ holder_relations?: OrderHolderRelation[];
381
398
  notes: SalesNote[];
382
399
  bookings: any[];
383
400
  payments: any[];
@@ -397,7 +414,7 @@ export interface OrderTempOrder {
397
414
  [key: string]: any;
398
415
  };
399
416
  }
400
- export interface OrderSubmitProduct extends Omit<OrderProduct, 'unique_identification_number' | 'num'> {
417
+ export interface OrderSubmitProduct extends Omit<OrderProduct, 'unique_identification_number' | 'num' | '_extend'> {
401
418
  product_quantity: number;
402
419
  payment_price: string;
403
420
  product_sku: OrderProductSku;
@@ -598,6 +615,7 @@ export interface SubmitSalesOrderParams {
598
615
  schedule_date: string;
599
616
  created_at?: string | undefined;
600
617
  products: OrderSubmitProduct[];
618
+ holder_relations?: OrderHolderRelation[];
601
619
  bookings: OrderSubmitBooking[];
602
620
  payments: any[];
603
621
  vouchers: any[];
@@ -63,6 +63,7 @@ let OrderHooks = exports.OrderHooks = /*#__PURE__*/function (OrderHooks) {
63
63
  * Sales Notes 乐观写入前的可恢复快照。
64
64
  * 该对象应视为 opaque token,只用于原样传回 restoreSalesNotesMutationSnapshot。
65
65
  */
66
+ /** 后端关系及 metadata 均为开放对象,未知字段必须往返保留。 */
66
67
  /**
67
68
  * 添加商品行时的顺序控制。
68
69
  * 默认追加到 products 尾部;预约数量同步可插入到既有预约组的最新行之后,
@@ -31,7 +31,7 @@ export declare const DISCOUNT_SPLIT_ORIGIN_UID_KEY = "discountSplitOriginUid";
31
31
  * 没有 UID 的裸商品不会被补写 UID。这是为了兼容 Rules/mock 的历史协议形状;
32
32
  * 正常 Order 写路径会在 normalize 阶段提前创建 UID。
33
33
  */
34
- export declare function ensureUniqueProductLineUids(tempOrder: OrderTempOrder): void;
34
+ export declare function ensureUniqueProductLineUids(tempOrder: OrderTempOrder, discounts?: Array<Record<string, any>>): void;
35
35
  /**
36
36
  * 把券移除后已经恢复原价的同源折扣拆分行重新合并。
37
37
  *
@@ -9,6 +9,7 @@ exports.ensureUniqueProductLineUids = ensureUniqueProductLineUids;
9
9
  var _lodashEs = require("lodash-es");
10
10
  var _utils = require("../../../solution/ScanOrder/utils");
11
11
  var _orderCollectionIdentity = require("./orderCollectionIdentity");
12
+ var _holderRelations = require("../holderRelations");
12
13
  /**
13
14
  * 商品券拆行的身份与回并工具。
14
15
  *
@@ -72,16 +73,20 @@ function ensureProductsByUid(tempOrder) {
72
73
  * 没有 UID 的裸商品不会被补写 UID。这是为了兼容 Rules/mock 的历史协议形状;
73
74
  * 正常 Order 写路径会在 normalize 阶段提前创建 UID。
74
75
  */
75
- function ensureUniqueProductLineUids(tempOrder) {
76
+ function ensureUniqueProductLineUids(tempOrder, discounts = []) {
76
77
  // usedUids 只描述本次 Rules 返回的商品集合,不能直接依赖 productsByUid:
77
78
  // productsByUid 可能还保留已删除行的历史 runtime,历史 UID 不应导致当前行被改号。
78
79
  const usedUids = new Set();
79
80
  const productsByUid = ensureProductsByUid(tempOrder);
81
+ const splitGroups = new Map();
80
82
  tempOrder.products = (tempOrder.products || []).map(product => {
81
83
  const sourceUid = getProductLineUid(product);
82
84
  // 单测 mock 或兼容旧 Rules 时可能返回没有 UID 的裸商品。
83
85
  // 这里不主动补 UID,避免一个“校验重复”的工具意外改变旧协议对象形状。
84
86
  if (!sourceUid) return product;
87
+ const group = splitGroups.get(sourceUid) || [];
88
+ group.push(product);
89
+ splitGroups.set(sourceUid, group);
85
90
 
86
91
  // 实际场景:可乐 ×5 首次进入 Rules 时只有一行。
87
92
  // 使用三张券后 Rules 可能返回「可乐 ×2 + 三条可乐 ×1」,但四条都复制原 UID。
@@ -122,6 +127,50 @@ function ensureUniqueProductLineUids(tempOrder) {
122
127
  usedUids.add(splitUid);
123
128
  return product;
124
129
  });
130
+
131
+ // 仅按本次券上的明确 Holder 分配迁移。数组顺序不能代表券对应的人。
132
+ // 未能明确分配时保留原 UID,不把整组关系复制给每一个拆分行。
133
+ for (const [sourceUid, products] of splitGroups) {
134
+ if (products.length < 2) continue;
135
+ const relations = (0, _holderRelations.getProductHolderRelations)(tempOrder, {
136
+ metadata: {
137
+ unique_identification_number: sourceUid
138
+ }
139
+ });
140
+ if (!relations.length || relations.some(relation => relation.holder_type !== 'form')) continue;
141
+ const assignments = new Map();
142
+ let remaining = [...relations];
143
+ let ambiguous = false;
144
+ for (const product of products) {
145
+ const holderIds = new Set();
146
+ for (const item of product.discount_list || []) {
147
+ const discount = discounts.find(candidate => String(candidate.id) === String(item.discount?.resource_id));
148
+ if (discount?.holder?.holder_type !== 'form') continue;
149
+ const value = discount.holder.holder_id;
150
+ (Array.isArray(value) ? value : [value]).forEach(id => {
151
+ const recordId = id && typeof id === 'object' ? id.form_record_id : id;
152
+ if (recordId !== undefined && recordId !== null && recordId !== '') holderIds.add(String(recordId));
153
+ });
154
+ }
155
+ if (!holderIds.size) continue;
156
+ const assigned = remaining.filter(relation => holderIds.has(String(relation.holder_id)));
157
+ if (assigned.length !== Number(product.num)) {
158
+ ambiguous = true;
159
+ break;
160
+ }
161
+ assignments.set(product, assigned);
162
+ remaining = remaining.filter(relation => !assigned.includes(relation));
163
+ }
164
+ const unassigned = products.filter(product => !assignments.has(product));
165
+ if (ambiguous || unassigned.length > 1) continue;
166
+ if (unassigned.length === 1) {
167
+ if (remaining.length !== Number(unassigned[0].num)) continue;
168
+ assignments.set(unassigned[0], remaining);
169
+ } else if (remaining.length) continue;
170
+ for (const [product, assigned] of assignments) {
171
+ for (const relation of assigned) relation.relation_uuid = String(getProductLineUid(product));
172
+ }
173
+ }
125
174
  }
126
175
 
127
176
  /**
@@ -154,7 +203,7 @@ function consolidateDiscountFreeProductLines(tempOrder) {
154
203
  // - 手动改价/备注:用户显式表达了不同价格或不同制作要求;
155
204
  // - 称重商品:每次称重的净重和金额属于独立交易事实;
156
205
  // - 预约/holder/历史订单行:分别关联预约、持有人或后端 order_detail。
157
- const hasMergeBoundary = hasDiscount || Boolean(product.metadata?._giftInfo || product._giftInfo) || Boolean(product.metadata?._promotion) || isManualProductDiscountLine(product) || hasProductLineNote(product) || Number(product.product_sku?.open_sold_weight || 0) === 1 || product.metadata?.is_edit_for_runtime === true || bookingId !== undefined && bookingId !== null && bookingId !== 0 && bookingId !== '0' || bookingUid !== undefined && bookingUid !== null && String(bookingUid) !== '' || holderId !== undefined && holderId !== null && String(holderId) !== '' || orderDetailId !== undefined && orderDetailId !== null && orderDetailId !== '';
206
+ const hasMergeBoundary = hasDiscount || Boolean(product.metadata?._giftInfo || product._giftInfo) || Boolean(product.metadata?._promotion) || isManualProductDiscountLine(product) || hasProductLineNote(product) || Number(product.product_sku?.open_sold_weight || 0) === 1 || product.metadata?.is_edit_for_runtime === true || bookingId !== undefined && bookingId !== null && bookingId !== 0 && bookingId !== '0' || bookingUid !== undefined && bookingUid !== null && String(bookingUid) !== '' || (0, _holderRelations.getProductHolderRelations)(tempOrder, product).length > 0 || holderId !== undefined && holderId !== null && String(holderId) !== '' || orderDetailId !== undefined && orderDetailId !== null && orderDetailId !== '';
158
207
  if (hasMergeBoundary) {
159
208
  consolidatedProducts.push(product);
160
209
  continue;