@pisell/pisellos 2.2.309 → 2.2.310

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 (121) hide show
  1. package/dist/model/strategy/adapter/walletPass/evaluator.d.ts +2 -0
  2. package/dist/model/strategy/adapter/walletPass/evaluator.js +4 -2
  3. package/dist/modules/BookingContext/index.js +4 -4
  4. package/dist/modules/BookingContext/utils/buildCacheItemFromOrderLine.js +58 -19
  5. package/dist/modules/BookingContext/utils/buildNormalProductCacheItemFromOrderLine.js +24 -12
  6. package/dist/modules/Customer/index.js +6 -3
  7. package/dist/modules/Customer/types.d.ts +2 -0
  8. package/dist/modules/Discount/availability.d.ts +7 -0
  9. package/dist/modules/Discount/availability.js +49 -0
  10. package/dist/modules/Discount/entitlementPass.d.ts +34 -0
  11. package/dist/modules/Discount/entitlementPass.js +147 -0
  12. package/dist/modules/Discount/index.d.ts +2 -2
  13. package/dist/modules/Discount/index.js +37 -151
  14. package/dist/modules/Discount/types.d.ts +36 -5
  15. package/dist/modules/OpenData/index.d.ts +1 -1
  16. package/dist/modules/OpenData/index.js +21 -12
  17. package/dist/modules/Order/index.d.ts +32 -4
  18. package/dist/modules/Order/index.js +733 -373
  19. package/dist/modules/Order/types.d.ts +17 -0
  20. package/dist/modules/Order/utils/orderCollectionIdentity.js +16 -12
  21. package/dist/modules/Order/utils.d.ts +14 -2
  22. package/dist/modules/Order/utils.js +275 -76
  23. package/dist/modules/Payment/index.d.ts +0 -5
  24. package/dist/modules/Payment/index.js +35 -85
  25. package/dist/modules/Payment/types.d.ts +21 -1
  26. package/dist/modules/Payment/types.js +2 -0
  27. package/dist/modules/Payment/walletpass.d.ts +1 -1
  28. package/dist/modules/Payment/walletpass.js +80 -47
  29. package/dist/modules/ProductList/index.js +18 -5
  30. package/dist/modules/Rules/index.d.ts +2 -2
  31. package/dist/modules/Rules/index.js +480 -186
  32. package/dist/modules/Rules/types.d.ts +4 -0
  33. package/dist/server/index.js +8 -5
  34. package/dist/server/modules/order/index.js +6 -0
  35. package/dist/server/modules/order/types.d.ts +12 -0
  36. package/dist/server/modules/payment/index.d.ts +25 -6
  37. package/dist/server/modules/payment/index.js +660 -133
  38. package/dist/server/utils/small-ticket.d.ts +8 -5
  39. package/dist/server/utils/small-ticket.js +1269 -199
  40. package/dist/solution/BaseSales/index.d.ts +11 -2
  41. package/dist/solution/BaseSales/index.js +1463 -1173
  42. package/dist/solution/BaseSales/types.d.ts +7 -3
  43. package/dist/solution/BaseSales/utils/cartPromotion.js +141 -126
  44. package/dist/solution/BookingByStep/index.d.ts +1 -1
  45. package/dist/solution/BookingTicket/index.d.ts +12 -3
  46. package/dist/solution/BookingTicket/index.js +663 -542
  47. package/dist/solution/BookingTicket/types.d.ts +5 -1
  48. package/dist/solution/BookingTicket/utils/scan/applyGlobalScan.d.ts +2 -0
  49. package/dist/solution/BookingTicket/utils/scan/applyGlobalScan.js +338 -147
  50. package/dist/solution/BookingTicket/utils/scan/cloudSearch.js +8 -4
  51. package/dist/solution/BookingTicket/utils/scan/formatGlobalScan.d.ts +6 -0
  52. package/dist/solution/BookingTicket/utils/scan/formatGlobalScan.js +19 -6
  53. package/dist/solution/ShopDiscount/index.d.ts +2 -0
  54. package/dist/solution/ShopDiscount/index.js +99 -42
  55. package/dist/solution/ShopDiscount/utils.d.ts +4 -2
  56. package/dist/solution/ShopDiscount/utils.js +43 -23
  57. package/dist/solution/UnifiedBookingSales/index.d.ts +2 -2
  58. package/dist/solution/UnifiedBookingSales/index.js +5 -5
  59. package/dist/utils/os-private-payload.d.ts +5 -0
  60. package/dist/utils/os-private-payload.js +17 -0
  61. package/lib/model/strategy/adapter/walletPass/evaluator.d.ts +2 -0
  62. package/lib/model/strategy/adapter/walletPass/evaluator.js +2 -0
  63. package/lib/modules/BookingContext/index.js +3 -3
  64. package/lib/modules/BookingContext/utils/buildCacheItemFromOrderLine.js +35 -6
  65. package/lib/modules/BookingContext/utils/buildNormalProductCacheItemFromOrderLine.js +13 -6
  66. package/lib/modules/Customer/index.js +5 -1
  67. package/lib/modules/Customer/types.d.ts +2 -0
  68. package/lib/modules/Discount/availability.d.ts +7 -0
  69. package/lib/modules/Discount/availability.js +43 -0
  70. package/lib/modules/Discount/entitlementPass.d.ts +34 -0
  71. package/lib/modules/Discount/entitlementPass.js +141 -0
  72. package/lib/modules/Discount/index.d.ts +2 -2
  73. package/lib/modules/Discount/index.js +23 -129
  74. package/lib/modules/Discount/types.d.ts +36 -5
  75. package/lib/modules/OpenData/index.d.ts +1 -1
  76. package/lib/modules/OpenData/index.js +11 -2
  77. package/lib/modules/Order/index.d.ts +32 -4
  78. package/lib/modules/Order/index.js +341 -71
  79. package/lib/modules/Order/types.d.ts +17 -0
  80. package/lib/modules/Order/utils/orderCollectionIdentity.js +14 -11
  81. package/lib/modules/Order/utils.d.ts +14 -2
  82. package/lib/modules/Order/utils.js +186 -3
  83. package/lib/modules/Payment/index.d.ts +0 -5
  84. package/lib/modules/Payment/index.js +3 -28
  85. package/lib/modules/Payment/types.d.ts +21 -1
  86. package/lib/modules/Payment/types.js +1 -0
  87. package/lib/modules/Payment/walletpass.d.ts +1 -1
  88. package/lib/modules/Payment/walletpass.js +29 -9
  89. package/lib/modules/ProductList/index.js +14 -1
  90. package/lib/modules/Rules/index.d.ts +2 -2
  91. package/lib/modules/Rules/index.js +357 -110
  92. package/lib/modules/Rules/types.d.ts +4 -0
  93. package/lib/server/index.js +8 -5
  94. package/lib/server/modules/order/index.js +6 -0
  95. package/lib/server/modules/order/types.d.ts +12 -0
  96. package/lib/server/modules/payment/index.d.ts +25 -6
  97. package/lib/server/modules/payment/index.js +350 -78
  98. package/lib/server/utils/small-ticket.d.ts +8 -5
  99. package/lib/server/utils/small-ticket.js +1009 -121
  100. package/lib/solution/BaseSales/index.d.ts +11 -2
  101. package/lib/solution/BaseSales/index.js +164 -30
  102. package/lib/solution/BaseSales/types.d.ts +7 -3
  103. package/lib/solution/BaseSales/utils/cartPromotion.js +12 -4
  104. package/lib/solution/BookingByStep/index.d.ts +1 -1
  105. package/lib/solution/BookingTicket/index.d.ts +12 -3
  106. package/lib/solution/BookingTicket/index.js +103 -41
  107. package/lib/solution/BookingTicket/types.d.ts +5 -1
  108. package/lib/solution/BookingTicket/utils/scan/applyGlobalScan.d.ts +2 -0
  109. package/lib/solution/BookingTicket/utils/scan/applyGlobalScan.js +174 -42
  110. package/lib/solution/BookingTicket/utils/scan/cloudSearch.js +8 -4
  111. package/lib/solution/BookingTicket/utils/scan/formatGlobalScan.d.ts +6 -0
  112. package/lib/solution/BookingTicket/utils/scan/formatGlobalScan.js +11 -3
  113. package/lib/solution/ShopDiscount/index.d.ts +2 -0
  114. package/lib/solution/ShopDiscount/index.js +55 -9
  115. package/lib/solution/ShopDiscount/utils.d.ts +4 -2
  116. package/lib/solution/ShopDiscount/utils.js +42 -25
  117. package/lib/solution/UnifiedBookingSales/index.d.ts +2 -2
  118. package/lib/solution/UnifiedBookingSales/index.js +5 -5
  119. package/lib/utils/os-private-payload.d.ts +5 -0
  120. package/lib/utils/os-private-payload.js +22 -0
  121. package/package.json +1 -1
@@ -199,12 +199,27 @@ export interface PointCardSnapshotItem {
199
199
  id: number | string;
200
200
  tag: 'point_card';
201
201
  product_id?: number | string;
202
+ code?: string;
202
203
  product_name: string;
203
204
  par_value: string;
204
205
  used_par_value: string;
205
206
  }
207
+ export interface PointCardSnapshotMergeContext {
208
+ customerId?: number | string | null;
209
+ orderIdentity?: OrderIdentitySnapshot | null;
210
+ }
211
+ export interface VirtualCurrencySnapshotItem {
212
+ id: number | string;
213
+ tag: 'virtual_currency';
214
+ product_id?: number | string;
215
+ product_name: string;
216
+ par_value: string;
217
+ used_par_value: string;
218
+ name: Record<string, string>;
219
+ }
206
220
  export interface OrderMetaList {
207
221
  point_cards_snapshot?: PointCardSnapshotItem[];
222
+ virtual_currency_snapshot?: VirtualCurrencySnapshotItem[];
208
223
  }
209
224
  export interface OrderProduct extends OrderProductIdentity {
210
225
  order_detail_id: number | null;
@@ -964,6 +979,8 @@ export interface OrderModuleAPI {
964
979
  }) => Promise<Discount[]>;
965
980
  getDiscountList: () => Discount[];
966
981
  getAvailableWalletIds: () => number[];
982
+ getCustomerWalletPrepareConfigEpoch: () => number;
983
+ mergePointCardSnapshotCodes: (walletPassList: unknown, context?: PointCardSnapshotMergeContext) => PointCardSnapshotItem[];
967
984
  applyDiscount: () => void;
968
985
  /**
969
986
  * 注入促销评估器。
@@ -17,6 +17,7 @@ exports.normalizeOrderCollections = normalizeOrderCollections;
17
17
  exports.repairDuplicateProductLineIdentities = repairDuplicateProductLineIdentities;
18
18
  exports.setOrderCollectionUid = setOrderCollectionUid;
19
19
  var _lodashEs = require("lodash-es");
20
+ var _entitlementPass = require("../../Discount/entitlementPass");
20
21
  const IDENTITY_CONFIG = {
21
22
  product: {
22
23
  persistentIdField: 'order_detail_id',
@@ -81,20 +82,22 @@ function setOrderCollectionUid(kind, item, uid) {
81
82
  delete record[uidField];
82
83
  return record;
83
84
  }
84
- function getGoodPassResourceId(product) {
85
- const goodPass = (product.discount_list || []).find(item => {
86
- const type = item?.type || item?.tag || item?.discount?.type || item?.discount?.tag;
87
- return type === 'good_pass';
88
- });
89
- if (!goodPass) return null;
90
- const resourceId = goodPass.discount?.resource_id ?? goodPass.resource_id ?? goodPass.discount_id ?? goodPass.id;
91
- return isBlankOrderCollectionIdentity(resourceId) ? null : String(resourceId);
85
+ function getFullCoveragePassIdentity(product) {
86
+ const pass = (product.discount_list || []).find(item => (0, _entitlementPass.isFullCoveragePass)(item));
87
+ if (!pass) return null;
88
+ const resourceId = pass.discount?.resource_id ?? pass.resource_id ?? pass.discount_id ?? pass.id;
89
+ if (isBlankOrderCollectionIdentity(resourceId)) return null;
90
+ return {
91
+ type: String(pass.type || pass.tag),
92
+ resourceId: String(resourceId)
93
+ };
92
94
  }
93
95
  function buildSplitProductUid(originalUid, product, duplicateOrdinal) {
94
96
  const encodedOriginalUid = encodeURIComponent(originalUid);
95
- const resourceId = getGoodPassResourceId(product);
96
- if (resourceId) {
97
- return `os-split:product:${encodedOriginalUid}:good-pass:${encodeURIComponent(resourceId)}:${duplicateOrdinal}`;
97
+ const passIdentity = getFullCoveragePassIdentity(product);
98
+ if (passIdentity) {
99
+ const passType = passIdentity.type === 'good_pass' ? 'good-pass' : 'entitlement-pass';
100
+ return `os-split:product:${encodedOriginalUid}:${passType}:${encodeURIComponent(passIdentity.resourceId)}:${duplicateOrdinal}`;
98
101
  }
99
102
  return `os-split:product:${encodedOriginalUid}:duplicate:${duplicateOrdinal}`;
100
103
  }
@@ -1,6 +1,6 @@
1
1
  import Decimal from 'decimal.js';
2
2
  import type { CartItem } from "../Cart";
3
- import type { OrderProductDiscountItem, OrderMetaList, OrderProduct, OrderSubmitPayload, OrderSubmitProduct, OrderSummary, OrderTempOrder, PointCardSnapshotItem } from './types';
3
+ import type { OrderProductDiscountItem, OrderMetaList, OrderProduct, OrderSubmitPayload, OrderSubmitProduct, OrderSummary, OrderTempOrder, PointCardSnapshotItem, VirtualCurrencySnapshotItem } from './types';
4
4
  import type { RulesParamsHooks } from '../Rules/types';
5
5
  import type { MapOrderPaymentsOptions, OrderPaymentData, OrderPaymentSource } from './types';
6
6
  import { createUuidV4 } from './utils/orderCollectionIdentity';
@@ -18,6 +18,18 @@ export interface OrderProductsDepositSummary {
18
18
  import type { WindowPlugin } from '../../plugins';
19
19
  export declare function normalizePointCardSnapshotItem(value: unknown): PointCardSnapshotItem | null;
20
20
  export declare function buildPointCardSnapshotFromWalletPassList(value: unknown): PointCardSnapshotItem[];
21
+ export declare function collectPointCardSnapshotCodeCandidates(value: unknown): Array<{
22
+ id: string;
23
+ code: string;
24
+ }>;
25
+ /**
26
+ * Payment 的 WalletPass 列表包含实例 code,而 prepare/config 通常只提供
27
+ * 卡名称和余额。按实例 ID 仅补齐 Snapshot 缺失的 code,避免后到的运行态
28
+ * 数据覆盖下单时已经冻结的卡名称和余额。
29
+ */
30
+ export declare function mergePointCardSnapshotCodes(snapshotsValue: unknown, walletPassesValue: unknown): PointCardSnapshotItem[];
31
+ export declare function normalizeVirtualCurrencySnapshotItem(value: unknown): VirtualCurrencySnapshotItem | null;
32
+ export declare function buildVirtualCurrencySnapshotFromList(value: unknown): VirtualCurrencySnapshotItem[];
21
33
  export declare function normalizeOrderMetaList(value: unknown): OrderMetaList | undefined;
22
34
  /**
23
35
  * 把"含 option 的主商品单价"与 bundle 合成"单行 composite 单价"。
@@ -159,7 +171,7 @@ export declare function resolveManualOverrideLineOriginalPrice(params: {
159
171
  * // => -4
160
172
  */
161
173
  export declare function resolveEffectivePerUnitDiscount(product: Record<string, any>): number;
162
- export declare function createDefaultOrderRulesHooks(): RulesParamsHooks;
174
+ export declare function createDefaultOrderRulesHooks(getBookings?: () => OrderTempOrder['bookings']): RulesParamsHooks;
163
175
  /**
164
176
  * 通过 session 类商品的开始时间结束时间生成商品的时长
165
177
  * @param {CartItem} cartItem
@@ -11,8 +11,10 @@ Object.defineProperty(exports, "buildManualProductDiscountItem", {
11
11
  });
12
12
  exports.buildPointCardSnapshotFromWalletPassList = buildPointCardSnapshotFromWalletPassList;
13
13
  exports.buildSubmitPayload = buildSubmitPayload;
14
+ exports.buildVirtualCurrencySnapshotFromList = buildVirtualCurrencySnapshotFromList;
14
15
  exports.calculateOrderProductsDeposit = calculateOrderProductsDeposit;
15
16
  exports.clearTempOrderHolderAssignments = clearTempOrderHolderAssignments;
17
+ exports.collectPointCardSnapshotCodeCandidates = collectPointCardSnapshotCodeCandidates;
16
18
  exports.composeLinePrice = composeLinePrice;
17
19
  exports.createDefaultOrderRulesHooks = createDefaultOrderRulesHooks;
18
20
  exports.createDefaultTempOrder = createDefaultTempOrder;
@@ -65,6 +67,7 @@ Object.defineProperty(exports, "mergeManualProductDiscountIntoList", {
65
67
  }
66
68
  });
67
69
  exports.mergeOrderProductDisplayFields = mergeOrderProductDisplayFields;
70
+ exports.mergePointCardSnapshotCodes = mergePointCardSnapshotCodes;
68
71
  exports.mergeRelationForms = void 0;
69
72
  exports.normalizeBookingIsAll = normalizeBookingIsAll;
70
73
  exports.normalizeBookingSubType = normalizeBookingSubType;
@@ -74,6 +77,7 @@ exports.normalizePointCardSnapshotItem = normalizePointCardSnapshotItem;
74
77
  exports.normalizeProductSkuOptions = normalizeProductSkuOptions;
75
78
  exports.normalizeSubmitBooking = normalizeSubmitBooking;
76
79
  exports.normalizeSubmitCollectPaxValue = normalizeSubmitCollectPaxValue;
80
+ exports.normalizeVirtualCurrencySnapshotItem = normalizeVirtualCurrencySnapshotItem;
77
81
  exports.productRequiresFormSubject = productRequiresFormSubject;
78
82
  exports.resolveEffectivePerUnitDiscount = resolveEffectivePerUnitDiscount;
79
83
  exports.resolveIsFormSubject = resolveIsFormSubject;
@@ -125,6 +129,7 @@ Object.defineProperty(exports, "syncManualProductDiscountProductNum", {
125
129
  }
126
130
  });
127
131
  var _dayjs = _interopRequireDefault(require("dayjs"));
132
+ var _customParseFormat = _interopRequireDefault(require("dayjs/plugin/customParseFormat"));
128
133
  var _decimal = _interopRequireDefault(require("decimal.js"));
129
134
  var _weighedProduct = require("./utils/weighedProduct");
130
135
  var _manualProductDiscount = require("./utils/manualProductDiscount");
@@ -133,7 +138,9 @@ var _utils2 = require("../../solution/ScanOrder/utils");
133
138
  var _orderCollectionIdentity = require("./utils/orderCollectionIdentity");
134
139
  var _salesNotes = require("./salesNotes");
135
140
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
141
+ _dayjs.default.extend(_customParseFormat.default);
136
142
  const POINT_CARD_SNAPSHOT_TAG = 'point_card';
143
+ const VIRTUAL_CURRENCY_SNAPSHOT_TAG = 'virtual_currency';
137
144
  function isPlainObject(value) {
138
145
  return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
139
146
  }
@@ -161,6 +168,10 @@ function normalizePointCardSnapshotItem(value) {
161
168
  if (productId !== undefined) {
162
169
  snapshot.product_id = productId;
163
170
  }
171
+ const code = normalizeSnapshotScalar(value.code);
172
+ if (code !== undefined) {
173
+ snapshot.code = String(code).trim();
174
+ }
164
175
  return snapshot;
165
176
  }
166
177
  function buildPointCardSnapshotFromWalletPassList(value) {
@@ -171,6 +182,141 @@ function buildPointCardSnapshotFromWalletPassList(value) {
171
182
  return result;
172
183
  }, []);
173
184
  }
185
+ function collectPointCardSnapshotCodeCandidates(value) {
186
+ if (!Array.isArray(value)) return [];
187
+ const codeById = new Map();
188
+ value.forEach(item => {
189
+ if (!isPlainObject(item)) return;
190
+ const id = normalizeSnapshotScalar(item.id ?? item.voucher_id);
191
+ const code = normalizeSnapshotScalar(item.code);
192
+ if (id === undefined || code === undefined) return;
193
+ const identity = String(id).trim();
194
+ const normalizedCode = String(code).trim();
195
+ if (!identity || !normalizedCode || codeById.has(identity)) return;
196
+ codeById.set(identity, normalizedCode);
197
+ });
198
+ return [...codeById].map(([id, code]) => ({
199
+ id,
200
+ code
201
+ }));
202
+ }
203
+
204
+ /**
205
+ * Payment 的 WalletPass 列表包含实例 code,而 prepare/config 通常只提供
206
+ * 卡名称和余额。按实例 ID 仅补齐 Snapshot 缺失的 code,避免后到的运行态
207
+ * 数据覆盖下单时已经冻结的卡名称和余额。
208
+ */
209
+ function mergePointCardSnapshotCodes(snapshotsValue, walletPassesValue) {
210
+ const snapshots = buildPointCardSnapshotFromWalletPassList(snapshotsValue);
211
+ if (!snapshots.length || !Array.isArray(walletPassesValue)) return snapshots;
212
+ const codeById = new Map(collectPointCardSnapshotCodeCandidates(walletPassesValue).map(({
213
+ id,
214
+ code
215
+ }) => [id, code]));
216
+ return snapshots.map(snapshot => {
217
+ if (snapshot.code) return snapshot;
218
+ const code = codeById.get(String(snapshot.id).trim());
219
+ return code ? {
220
+ ...snapshot,
221
+ code
222
+ } : snapshot;
223
+ });
224
+ }
225
+ function normalizeSnapshotNameMap(value) {
226
+ if (typeof value === 'string' || typeof value === 'number') {
227
+ const normalizedText = String(value).trim();
228
+ return normalizedText ? {
229
+ original: normalizedText
230
+ } : {};
231
+ }
232
+ if (!isPlainObject(value)) return {};
233
+ return Object.entries(value).reduce((result, [locale, text]) => {
234
+ if (!locale.trim()) return result;
235
+ if (typeof text !== 'string' && typeof text !== 'number') return result;
236
+ const normalizedText = String(text).trim();
237
+ if (normalizedText) result[locale] = normalizedText;
238
+ return result;
239
+ }, {});
240
+ }
241
+ function buildVirtualCurrencySnapshotName(value) {
242
+ if (!isPlainObject(value)) return {};
243
+ const amountFormat = String(value.amount_format ?? '').trim();
244
+ let configuredName;
245
+ if (amountFormat === '{{amount}} {{unit}}') {
246
+ configuredName = value.unit_label;
247
+ } else if (amountFormat === '{{amount}} {{sym}}') {
248
+ configuredName = value.symbol;
249
+ } else {
250
+ return {};
251
+ }
252
+ const configuredNameMap = normalizeSnapshotNameMap(configuredName);
253
+ const shortNameMap = normalizeSnapshotNameMap(value.short_name);
254
+
255
+ // The configured unit/symbol is resolved before short_name. If it already
256
+ // carries a global fallback, keep it authoritative so (for example)
257
+ // short_name.en cannot mask unit_label.original. When it only has explicit
258
+ // locale entries, merge short_name so other receipt locales can still fall
259
+ // back after the configured value has no match.
260
+ const hasConfiguredGlobalFallback = ['original', 'auto', 'default', 'en'].some(locale => Boolean(configuredNameMap[locale]));
261
+ if (hasConfiguredGlobalFallback) return configuredNameMap;
262
+ return {
263
+ ...shortNameMap,
264
+ ...configuredNameMap
265
+ };
266
+ }
267
+ function normalizeVirtualCurrencySnapshotItem(value) {
268
+ if (!isPlainObject(value)) return null;
269
+ const id = normalizeSnapshotScalar(value.id);
270
+ if (id === undefined) return null;
271
+ if (String(value.tag || '').trim().toLowerCase() !== VIRTUAL_CURRENCY_SNAPSHOT_TAG) {
272
+ return null;
273
+ }
274
+ const snapshot = {
275
+ id,
276
+ tag: 'virtual_currency',
277
+ product_name: String(value.product_name ?? ''),
278
+ par_value: String(value.par_value ?? ''),
279
+ used_par_value: String(value.used_par_value ?? ''),
280
+ name: normalizeSnapshotNameMap(value.name)
281
+ };
282
+ const productId = normalizeSnapshotScalar(value.product_id);
283
+ if (productId !== undefined) {
284
+ snapshot.product_id = productId;
285
+ }
286
+ return snapshot;
287
+ }
288
+ function normalizeVirtualCurrencySnapshotList(value) {
289
+ if (!Array.isArray(value)) return [];
290
+ return value.reduce((result, item) => {
291
+ const normalized = normalizeVirtualCurrencySnapshotItem(item);
292
+ if (normalized) result.push(normalized);
293
+ return result;
294
+ }, []);
295
+ }
296
+ function buildVirtualCurrencySnapshotFromList(value) {
297
+ if (!Array.isArray(value)) return [];
298
+ return value.reduce((result, item) => {
299
+ if (!isPlainObject(item)) return result;
300
+ const id = normalizeSnapshotScalar(item.id);
301
+ if (id === undefined) return result;
302
+ const sourceTag = String(item.tag ?? '').trim().toLowerCase();
303
+ if (sourceTag !== VIRTUAL_CURRENCY_SNAPSHOT_TAG) return result;
304
+ const snapshot = {
305
+ id,
306
+ tag: 'virtual_currency',
307
+ product_name: String(item.product_name ?? ''),
308
+ par_value: String(item.par_value ?? ''),
309
+ used_par_value: String(item.used_par_value ?? ''),
310
+ name: buildVirtualCurrencySnapshotName(item.virtual_currency_display)
311
+ };
312
+ const productId = normalizeSnapshotScalar(item.product_id);
313
+ if (productId !== undefined) {
314
+ snapshot.product_id = productId;
315
+ }
316
+ result.push(snapshot);
317
+ return result;
318
+ }, []);
319
+ }
174
320
  function normalizeOrderMetaList(value) {
175
321
  if (value === undefined) return undefined;
176
322
  if (!isPlainObject(value)) return undefined;
@@ -178,6 +324,9 @@ function normalizeOrderMetaList(value) {
178
324
  if (Object.prototype.hasOwnProperty.call(value, 'point_cards_snapshot')) {
179
325
  normalized.point_cards_snapshot = buildPointCardSnapshotFromWalletPassList(value.point_cards_snapshot);
180
326
  }
327
+ if (Object.prototype.hasOwnProperty.call(value, 'virtual_currency_snapshot')) {
328
+ normalized.virtual_currency_snapshot = normalizeVirtualCurrencySnapshotList(value.virtual_currency_snapshot);
329
+ }
181
330
  if (Object.keys(normalized).length > 0) return normalized;
182
331
  return {};
183
332
  }
@@ -566,6 +715,24 @@ function resolveSubmitBundleMapId(bundle) {
566
715
  }
567
716
  return undefined;
568
717
  }
718
+ function ensureSubmitDiscountIdentity(discount) {
719
+ if (!discount || typeof discount !== 'object' || Array.isArray(discount)) {
720
+ return discount;
721
+ }
722
+ const discountRecord = discount;
723
+ const metadata = discountRecord.metadata && typeof discountRecord.metadata === 'object' && !Array.isArray(discountRecord.metadata) ? discountRecord.metadata : {};
724
+ const uniqueIdentificationNumber = metadata.unique_identification_number;
725
+ if (uniqueIdentificationNumber !== undefined && uniqueIdentificationNumber !== null && String(uniqueIdentificationNumber).trim() !== '') {
726
+ return discount;
727
+ }
728
+ return {
729
+ ...discountRecord,
730
+ metadata: {
731
+ ...metadata,
732
+ unique_identification_number: (0, _orderCollectionIdentity.createUuidV4)()
733
+ }
734
+ };
735
+ }
569
736
  function collectSubmitProductDiscountList(product) {
570
737
  const submitDiscountList = Array.isArray(product?.discount_list) ? [...product.discount_list] : [];
571
738
  for (const bundle of product?.product_bundle || []) {
@@ -587,7 +754,7 @@ function collectSubmitProductDiscountList(product) {
587
754
  });
588
755
  }
589
756
  }
590
- return normalizeOrderProductDiscountList(submitDiscountList);
757
+ return normalizeOrderProductDiscountList(submitDiscountList.map(ensureSubmitDiscountIdentity));
591
758
  }
592
759
 
593
760
  /**
@@ -654,7 +821,23 @@ function resolveEffectivePerUnitDiscount(product) {
654
821
  if (!Number.isFinite(originalPrice) || !Number.isFinite(finalPrice)) return fromList;
655
822
  return fromList + (originalPrice - finalPrice);
656
823
  }
657
- function createDefaultOrderRulesHooks() {
824
+ function createDefaultOrderRulesHooks(getBookings) {
825
+ const getProductStartDate = product => {
826
+ const productUid = String(product.metadata?.unique_identification_number ?? product.unique_identification_number ?? '');
827
+ const bookingUid = String(product.booking_uid ?? product.metadata?.booking_uid ?? '');
828
+ // 每次读取当前 bookings,覆盖改期、订单回显以及直接调用 Rules 的选券入口。
829
+ const bookings = getBookings?.() || [];
830
+ const booking = bookings.find(item => productUid && String(item?.product_uid ?? item?.metadata?.product_uid ?? '') === productUid) || bookings.find(item => bookingUid && String(item?.metadata?.unique_identification_number ?? item?.booking_uid ?? '') === bookingUid);
831
+ if (booking) {
832
+ // 严格解析并保留 Invalid Date,让公共时间校验拒绝缺失/非法时间,避免回退到当前时间。
833
+ return (0, _dayjs.default)(`${booking.start_date} ${booking.start_time}`, ['YYYY-MM-DD HH:mm', 'YYYY-MM-DD HH:mm:ss', 'YYYY-MM-DD H:mm', 'YYYY-MM-DD H:mm:ss'], true);
834
+ }
835
+ if (getBookings && (bookingUid || product.booking_id || product.metadata?.booking_id)) {
836
+ return (0, _dayjs.default)('');
837
+ }
838
+ const legacyStartDate = product.metadata?.start_date || product.metadata?.startDate;
839
+ return legacyStartDate ? (0, _dayjs.default)(legacyStartDate) : undefined;
840
+ };
658
841
  const toUnitPriceString = (totalLike, num) => {
659
842
  const effectiveNum = Number(num) > 0 ? Number(num) : 1;
660
843
  return new _decimal.default(Number(totalLike) || 0).dividedBy(effectiveNum).toDecimalPlaces(2).toString();
@@ -742,7 +925,7 @@ function createDefaultOrderRulesHooks() {
742
925
  // 无券时会把 bundle 价还原成 original_price(手动改价套餐会从 20 回到 30)。
743
926
  isManualDiscount: resolveRulesManualDiscountFlag(metadataAny),
744
927
  holder_id: product.metadata?.holder_id,
745
- startDate: product.metadata?.start_date || product.metadata?.startDate,
928
+ startDate: getProductStartDate(product),
746
929
  main_product_selling_price: metadataAny.main_product_selling_price !== undefined ? new _decimal.default(Number(metadataAny.main_product_selling_price) || 0).minus(optionSum).toDecimalPlaces(2).toString() : undefined,
747
930
  inPromotion: metadataAny._promotion?.inPromotion === true,
748
931
  _promotion: metadataAny._promotion
@@ -171,11 +171,6 @@ export declare class PaymentModule extends BaseModule implements Module, Payment
171
171
  * 获取钱包支付方式
172
172
  */
173
173
  getWalletPaymentMethod(): Promise<PaymentMethod | null>;
174
- /**
175
- * 确保支付模块所需的数据库表已创建
176
- */
177
- /** @deprecated */
178
- private ensurePaymentTables;
179
174
  /**
180
175
  * 获取部分支付的订单
181
176
  */
@@ -159,10 +159,6 @@ class PaymentModule extends _BaseModule.BaseModule {
159
159
  this.app = this.appPlugin.getApp();
160
160
  this.dbManager = this.app.dbManager;
161
161
  this.logger = this.app.logger;
162
-
163
- // 确保支付模块所需的对象存储已创建
164
- // await this.ensurePaymentTables();
165
-
166
162
  this.registerNetworkHandlers();
167
163
 
168
164
  // // 预连接数据库
@@ -1111,7 +1107,7 @@ class PaymentModule extends _BaseModule.BaseModule {
1111
1107
  */
1112
1108
  async getCashPaymentMethod() {
1113
1109
  try {
1114
- const payMethods = await this.dbManager.getAll('pay_method');
1110
+ const payMethods = await this.getPayMethodListAsync();
1115
1111
  return payMethods.find(method => method.code === _types.PaymentMethodType.Cash) || null;
1116
1112
  } catch (error) {
1117
1113
  console.error('[PaymentModule] 获取现金支付方式失败', error);
@@ -1124,7 +1120,7 @@ class PaymentModule extends _BaseModule.BaseModule {
1124
1120
  */
1125
1121
  async getEftposPaymentMethod() {
1126
1122
  try {
1127
- const payMethods = await this.dbManager.getAll('pay_method');
1123
+ const payMethods = await this.getPayMethodListAsync();
1128
1124
  return payMethods.find(method => method.code === _types.PaymentMethodType.Eftpos) || null;
1129
1125
  } catch (error) {
1130
1126
  console.error('[PaymentModule] 获取Eftpos支付方式失败', error);
@@ -1137,7 +1133,7 @@ class PaymentModule extends _BaseModule.BaseModule {
1137
1133
  */
1138
1134
  async getWalletPaymentMethod() {
1139
1135
  try {
1140
- const payMethods = await this.dbManager.getAll('pay_method');
1136
+ const payMethods = await this.getPayMethodListAsync();
1141
1137
  return payMethods.find(method => method.code === _types.PaymentMethodType.Wallet) || null;
1142
1138
  } catch (error) {
1143
1139
  console.error('[PaymentModule] 获取钱包支付方式失败', error);
@@ -1145,27 +1141,6 @@ class PaymentModule extends _BaseModule.BaseModule {
1145
1141
  }
1146
1142
  }
1147
1143
 
1148
- /**
1149
- * 确保支付模块所需的数据库表已创建
1150
- */
1151
- /** @deprecated */
1152
- async ensurePaymentTables() {
1153
- try {
1154
- // 尝试访问 pay_method 表,如果不存在会抛出错误
1155
- await this.dbManager.getAll('pay_method');
1156
- } catch (error) {
1157
- console.warn('[PaymentModule] pay_method 表不存在,请在数据库配置中添加以下配置:');
1158
- console.warn('{ name: "pay_method", keyPath: "id" }');
1159
- }
1160
- try {
1161
- // 尝试访问 order 表,如果不存在会抛出错误
1162
- await this.dbManager.getAll('order');
1163
- } catch (error) {
1164
- console.warn('[PaymentModule] order 表不存在,请在数据库配置中添加以下配置:');
1165
- console.warn('{ name: "order", keyPath: "uuid" }');
1166
- }
1167
- }
1168
-
1169
1144
  /**
1170
1145
  * 获取部分支付的订单
1171
1146
  */
@@ -412,6 +412,8 @@ export interface WalletAssetsBehaviorOptions {
412
412
  export interface RefreshWalletAssetsOptions {
413
413
  /** 商品/金额变化默认保留并重验选择;客户变化由实现层强制清空。 */
414
414
  preserveSelection?: boolean;
415
+ /** 客户变化时,仅保留与本次扫码匹配的搜索结果;其他搜索缓存仍会清空。 */
416
+ retainSearchResultCodes?: string[];
415
417
  }
416
418
  export interface SelectWalletAssetOptions {
417
419
  selected: boolean;
@@ -419,6 +421,8 @@ export interface SelectWalletAssetOptions {
419
421
  }
420
422
  export interface ScanWalletAssetResult {
421
423
  type: SearchIdentificationCodeResult['type'];
424
+ /** walletCode 时保留完整 Wallet Detail,供客户解析与客户选择器使用。 */
425
+ data?: WalletDetailSearchData;
422
426
  asset?: any;
423
427
  selected: boolean;
424
428
  state: WalletAssetsState;
@@ -864,6 +868,8 @@ export interface SearchIdentificationCodeParams extends WalletDeductionRecommend
864
868
  * 搜索识别码响应数据
865
869
  */
866
870
  export interface SearchIdentificationCodeResponse {
871
+ /** 业务是否成功 */
872
+ status?: boolean;
867
873
  /** 响应状态码 */
868
874
  code?: number;
869
875
  /** 响应消息 */
@@ -871,12 +877,26 @@ export interface SearchIdentificationCodeResponse {
871
877
  /** 响应数据 */
872
878
  data?: SearchIdentificationCodeItem[];
873
879
  }
880
+ /** Wallet code 精确搜索返回的 Wallet Detail。 */
881
+ export interface WalletDetailSearchData {
882
+ id?: string | number;
883
+ customer_id?: string | number;
884
+ customer?: Record<string, any> | null;
885
+ is_cross_shop_wallet?: boolean | 0 | 1;
886
+ [key: string]: any;
887
+ }
888
+ export interface WalletDetailSearchResponse {
889
+ status?: boolean;
890
+ code?: number;
891
+ message?: string;
892
+ data?: WalletDetailSearchData | null;
893
+ }
874
894
  /**
875
895
  * 搜索识别码结果类型
876
896
  */
877
897
  export type SearchIdentificationCodeResult = {
878
898
  type: 'walletCode';
879
- data: SearchIdentificationCodeItem[];
899
+ data: WalletDetailSearchData;
880
900
  } | {
881
901
  type: 'normalCode';
882
902
  data: SearchIdentificationCodeItem[];
@@ -221,6 +221,7 @@ let PaymentHooks = exports.PaymentHooks = /*#__PURE__*/function (PaymentHooks) {
221
221
  /**
222
222
  * 搜索识别码响应数据
223
223
  */
224
+ /** Wallet code 精确搜索返回的 Wallet Detail。 */
224
225
  /**
225
226
  * 搜索识别码结果类型
226
227
  */
@@ -96,7 +96,7 @@ export declare class WalletPassPaymentImpl implements WalletPassPayment {
96
96
  * 搜索识别码信息
97
97
  * 通过识别码搜索相关的钱包通行证信息
98
98
  * 基于 WalletDeductionRecommendParams 参数结构
99
- * 特殊逻辑:当识别码长度为9位且前3位为"000"时,调用 /wallet/detail/search 接口
99
+ * 特殊逻辑:WL 或指定 200 格式的 Wallet Code 调用 /wallet/detail/search 接口。
100
100
  */
101
101
  searchIdentificationCodeAsync(params: SearchIdentificationCodeParams, config?: {
102
102
  noCache?: boolean;
@@ -705,7 +705,7 @@ class WalletPassPaymentImpl {
705
705
  * 搜索识别码信息
706
706
  * 通过识别码搜索相关的钱包通行证信息
707
707
  * 基于 WalletDeductionRecommendParams 参数结构
708
- * 特殊逻辑:当识别码长度为9位且前3位为"000"时,调用 /wallet/detail/search 接口
708
+ * 特殊逻辑:WL 或指定 200 格式的 Wallet Code 调用 /wallet/detail/search 接口。
709
709
  */
710
710
  async searchIdentificationCodeAsync(params, config = {}) {
711
711
  try {
@@ -725,6 +725,7 @@ class WalletPassPaymentImpl {
725
725
  const walletDetailParams = {
726
726
  code: code,
727
727
  with_customer: 1,
728
+ with_franchisor_data: 1,
728
729
  with: ['wallet']
729
730
  };
730
731
  this.paymentModule.logInfo('[WalletPass] 搜索钱包识别码', {
@@ -733,15 +734,18 @@ class WalletPassPaymentImpl {
733
734
  with: walletDetailParams.with
734
735
  });
735
736
  const response = await this.paymentModule.request.post('/wallet/detail/search', walletDetailParams);
736
- const searchResults = response?.data || [];
737
+ const walletDetail = response?.data;
738
+ if (response?.status !== true || response?.code !== 200 || !walletDetail || typeof walletDetail !== 'object' || Array.isArray(walletDetail)) {
739
+ throw new Error(response?.message || 'Wallet detail search failed');
740
+ }
737
741
  this.paymentModule.logInfo('[WalletPass] 钱包识别码搜索完成', {
738
742
  code: code,
739
- results_count: searchResults.length,
743
+ results_count: 1,
740
744
  type: 'walletCode'
741
745
  });
742
746
  return {
743
747
  type: 'walletCode',
744
- data: searchResults
748
+ data: walletDetail
745
749
  };
746
750
  }
747
751
 
@@ -830,9 +834,16 @@ class WalletPassPaymentImpl {
830
834
  const previousCustomerId = this.walletAssetsState.customerId;
831
835
  const customerChanged = String(previousCustomerId ?? '') !== String(nextCustomerId ?? '');
832
836
  const preserveSelection = options.preserveSelection !== false && !customerChanged;
837
+ const retainedSearchCodeKeys = new Set((options.retainSearchResultCodes || []).map(normalizeWalletAssetCode).filter(Boolean));
838
+ const retainedSearchResults = customerChanged && retainedSearchCodeKeys.size > 0 ? this.searchResults.filter(item => isWalletPaymentAsset(item) && getWalletAssetCodeKeys(item).some(code => retainedSearchCodeKeys.has(code))) : [];
833
839
  if (customerChanged) {
834
840
  this.clearSearchResults();
841
+ if (retainedSearchResults.length > 0) {
842
+ this.searchResults = mergeWalletAssets(retainedSearchResults);
843
+ this.queuePendingAutoSelectForAssets(retainedSearchResults);
844
+ }
835
845
  }
846
+ const preserveSearchContext = preserveSelection || retainedSearchResults.length > 0;
836
847
  const committedKeys = new Set(this.walletAssetsState.committedIds.map(id => String(id)));
837
848
  const committedItems = this.walletAssetsState.items.filter(item => committedKeys.has(getWalletAssetKey(item)));
838
849
  const selectedIds = preserveSelection ? [...this.walletAssetsState.selectedIds] : [];
@@ -901,8 +912,8 @@ class WalletPassPaymentImpl {
901
912
  error: null
902
913
  });
903
914
  try {
904
- const searchedWalletAssetCodes = preserveSelection ? this.getSearchedWalletAssetCodes() : [];
905
- const useAggregateRecalculate = preserveSelection && (selectedIds.length > 0 || searchedWalletAssetCodes.length > 0);
915
+ const searchedWalletAssetCodes = preserveSearchContext ? this.getSearchedWalletAssetCodes() : [];
916
+ const useAggregateRecalculate = preserveSearchContext && (selectedIds.length > 0 || searchedWalletAssetCodes.length > 0);
906
917
  const aggregateResult = useAggregateRecalculate ? await this.aggregateRecalculateWalletAssetsAsync(businessData, selectedIds) : null;
907
918
  const result = aggregateResult || (await this.initializeWalletDataFromBusinessAsync(businessData));
908
919
  if (requestSequence !== this.walletAssetsRequestSequence) {
@@ -920,7 +931,7 @@ class WalletPassPaymentImpl {
920
931
  this.searchResults = this.searchResults.filter(item => !missingSelectedKeys.has(getWalletAssetKey(item)));
921
932
  }
922
933
  }
923
- const searchedWalletAssets = preserveSelection ? this.syncSearchedWalletAssets([...(result.transformList || []), ...(result.noApplicableVoucher || [])]) : [];
934
+ const searchedWalletAssets = preserveSearchContext ? this.syncSearchedWalletAssets([...(result.transformList || []), ...(result.noApplicableVoucher || [])]) : [];
924
935
  if (requestSequence !== this.walletAssetsRequestSequence) {
925
936
  return this.getWalletAssetsState();
926
937
  }
@@ -1122,14 +1133,23 @@ class WalletPassPaymentImpl {
1122
1133
  code: normalizedCode,
1123
1134
  prepare_payments: this.formatWalletPassList2PreparePayments(this.walletAssetsState.selectedItems)
1124
1135
  });
1136
+ if (result.type === 'walletCode') {
1137
+ return {
1138
+ type: 'walletCode',
1139
+ data: result.data,
1140
+ asset: result.data,
1141
+ selected: false,
1142
+ state: this.getWalletAssetsState()
1143
+ };
1144
+ }
1125
1145
  const paymentAssets = this.formatSearchedWalletPaymentAssets(result.data.filter(isWalletPaymentAsset));
1126
1146
  if (paymentAssets.length > 0) {
1127
1147
  this.searchResults = mergeWalletAssets(this.searchResults, paymentAssets);
1128
1148
  }
1129
1149
  const asset = paymentAssets.find(item => String(item?.code || '') === normalizedCode) || paymentAssets[0];
1130
- if (!asset || result.type === 'walletCode') {
1150
+ if (!asset) {
1131
1151
  return {
1132
- type: result.type,
1152
+ type: 'normalCode',
1133
1153
  asset,
1134
1154
  selected: false,
1135
1155
  state: this.getWalletAssetsState()
@@ -130,6 +130,19 @@ class ProductList extends _BaseModule.BaseModule {
130
130
  };
131
131
  const rawApplicationCode = this.otherParams?.channel;
132
132
  const mappedApplicationCode = typeof rawApplicationCode === 'string' ? channelMap[rawApplicationCode] || rawApplicationCode : rawApplicationCode;
133
+ const configuredBusinessCode = this.otherParams?.businessCode ?? this.otherParams?.business_code;
134
+ // application_code 是后端商品筛选字段;智能定价评估器则只读取
135
+ // strategy_context 中的 channel / business_code。默认补齐两者,
136
+ // 但允许调用方显式传入的上下文覆盖默认值。
137
+ const resolvedStrategyContext = {
138
+ ...(mappedApplicationCode ? {
139
+ channel: mappedApplicationCode
140
+ } : {}),
141
+ ...(configuredBusinessCode ? {
142
+ business_code: configuredBusinessCode
143
+ } : {}),
144
+ ...(strategy_context || {})
145
+ };
133
146
  const queryPayload = {
134
147
  ...((0, _platform.isCustomerUserPlatform)(this.otherParams?.platform) ? {} : {
135
148
  open_quotation: 1
@@ -162,7 +175,7 @@ class ProductList extends _BaseModule.BaseModule {
162
175
  with_schedule,
163
176
  schedule_datetime,
164
177
  extension_type,
165
- strategy_context
178
+ strategy_context: Object.keys(resolvedStrategyContext).length ? resolvedStrategyContext : undefined
166
179
  };
167
180
  const handledByOsServer = (0, _clientDataVariants.isProductQueryHandledByOsServer)(this.core, this.request);
168
181
  const originalCallback = options?.callback;