@pisell/pisellos 0.10.7 → 0.10.9

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 (124) hide show
  1. package/dist/core/index.js +0 -1
  2. package/dist/model/strategy/adapter/dataVariant/evaluator.d.ts +18 -2
  3. package/dist/model/strategy/adapter/dataVariant/evaluator.js +287 -101
  4. package/dist/model/strategy/adapter/dataVariant/type.d.ts +25 -0
  5. package/dist/model/strategy/adapter/walletPass/evaluator.js +0 -2
  6. package/dist/model/strategy/adapter/walletPass/type.d.ts +5 -0
  7. package/dist/model/strategy/adapter/walletPass/utils.js +57 -26
  8. package/dist/modules/BookingContext/utils/buildCacheItemFromOrderLine.js +18 -12
  9. package/dist/modules/BookingContext/utils/productExtend.js +5 -9
  10. package/dist/modules/BookingContext/utils/timeSlices.js +24 -3
  11. package/dist/modules/Customer/index.d.ts +4 -0
  12. package/dist/modules/Customer/index.js +91 -59
  13. package/dist/modules/Customer/types.d.ts +2 -0
  14. package/dist/modules/Discount/index.d.ts +2 -0
  15. package/dist/modules/Discount/index.js +38 -5
  16. package/dist/modules/Discount/types.d.ts +11 -0
  17. package/dist/modules/Order/index.d.ts +32 -6
  18. package/dist/modules/Order/index.js +1165 -819
  19. package/dist/modules/Order/types.d.ts +32 -4
  20. package/dist/modules/Order/types.js +11 -0
  21. package/dist/modules/Order/utils/discountProductLineIdentity.d.ts +45 -0
  22. package/dist/modules/Order/utils/discountProductLineIdentity.js +227 -0
  23. package/dist/modules/Order/utils/virtualSettlement.d.ts +56 -0
  24. package/dist/modules/Order/utils/virtualSettlement.js +271 -0
  25. package/dist/modules/Order/utils.js +89 -48
  26. package/dist/modules/Payment/types.d.ts +2 -0
  27. package/dist/modules/Payment/utils.js +4 -1
  28. package/dist/modules/Payment/walletpass.d.ts +11 -0
  29. package/dist/modules/Payment/walletpass.js +235 -60
  30. package/dist/modules/Product/types.d.ts +23 -0
  31. package/dist/modules/ProductList/index.d.ts +15 -2
  32. package/dist/modules/ProductList/index.js +92 -13
  33. package/dist/modules/Rules/index.js +125 -52
  34. package/dist/modules/SalesSummary/index.d.ts +1 -0
  35. package/dist/modules/SalesSummary/index.js +4 -3
  36. package/dist/modules/SalesSummary/types.d.ts +1 -0
  37. package/dist/modules/SalesSummary/utils.d.ts +1 -0
  38. package/dist/modules/SalesSummary/utils.js +28 -2
  39. package/dist/server/index.d.ts +15 -0
  40. package/dist/server/index.js +894 -781
  41. package/dist/server/modules/order/index.d.ts +2 -0
  42. package/dist/server/modules/order/index.js +261 -96
  43. package/dist/server/modules/order/types.d.ts +1 -1
  44. package/dist/server/utils/small-ticket.js +2 -1
  45. package/dist/solution/BaseSales/index.d.ts +41 -7
  46. package/dist/solution/BaseSales/index.js +1405 -974
  47. package/dist/solution/BaseSales/utils/cartPromotion.js +26 -3
  48. package/dist/solution/BaseSales/utils/transformBaseProductToOrderProduct.d.ts +1 -0
  49. package/dist/solution/BaseSales/utils/transformBaseProductToOrderProduct.js +79 -13
  50. package/dist/solution/BaseSales/utils.js +31 -20
  51. package/dist/solution/BookingByStep/index.d.ts +3 -3
  52. package/dist/solution/BookingTicket/index.d.ts +5 -1
  53. package/dist/solution/BookingTicket/index.js +221 -162
  54. package/dist/solution/BookingTicket/types.d.ts +3 -0
  55. package/dist/solution/BookingTicket/utils/addProductDecision.js +2 -1
  56. package/dist/solution/BookingTicket/utils/cartView.js +44 -32
  57. package/dist/solution/Sales/index.d.ts +1 -0
  58. package/dist/solution/Sales/index.js +16 -2
  59. package/dist/solution/ScanOrder/index.js +25 -64
  60. package/dist/solution/ScanOrder/utils.js +31 -20
  61. package/dist/solution/UnifiedBookingSales/index.d.ts +5 -4
  62. package/dist/solution/UnifiedBookingSales/index.js +51 -39
  63. package/lib/core/index.js +0 -1
  64. package/lib/model/strategy/adapter/dataVariant/evaluator.d.ts +18 -2
  65. package/lib/model/strategy/adapter/dataVariant/evaluator.js +170 -7
  66. package/lib/model/strategy/adapter/dataVariant/type.d.ts +25 -0
  67. package/lib/model/strategy/adapter/walletPass/evaluator.js +0 -2
  68. package/lib/model/strategy/adapter/walletPass/type.d.ts +5 -0
  69. package/lib/model/strategy/adapter/walletPass/utils.js +62 -11
  70. package/lib/modules/BookingContext/utils/buildCacheItemFromOrderLine.js +6 -1
  71. package/lib/modules/BookingContext/utils/productExtend.js +4 -3
  72. package/lib/modules/BookingContext/utils/timeSlices.js +18 -3
  73. package/lib/modules/Customer/index.d.ts +4 -0
  74. package/lib/modules/Customer/index.js +11 -0
  75. package/lib/modules/Customer/types.d.ts +2 -0
  76. package/lib/modules/Discount/index.d.ts +2 -0
  77. package/lib/modules/Discount/index.js +37 -3
  78. package/lib/modules/Discount/types.d.ts +11 -0
  79. package/lib/modules/Order/index.d.ts +32 -6
  80. package/lib/modules/Order/index.js +518 -148
  81. package/lib/modules/Order/types.d.ts +32 -4
  82. package/lib/modules/Order/utils/discountProductLineIdentity.d.ts +45 -0
  83. package/lib/modules/Order/utils/discountProductLineIdentity.js +170 -0
  84. package/lib/modules/Order/utils/virtualSettlement.d.ts +56 -0
  85. package/lib/modules/Order/utils/virtualSettlement.js +300 -0
  86. package/lib/modules/Order/utils.js +65 -21
  87. package/lib/modules/Payment/types.d.ts +2 -0
  88. package/lib/modules/Payment/utils.js +5 -1
  89. package/lib/modules/Payment/walletpass.d.ts +11 -0
  90. package/lib/modules/Payment/walletpass.js +198 -17
  91. package/lib/modules/Product/types.d.ts +23 -0
  92. package/lib/modules/ProductList/index.d.ts +15 -2
  93. package/lib/modules/ProductList/index.js +85 -3
  94. package/lib/modules/Rules/index.js +100 -20
  95. package/lib/modules/SalesSummary/index.d.ts +1 -0
  96. package/lib/modules/SalesSummary/index.js +10 -2
  97. package/lib/modules/SalesSummary/types.d.ts +1 -0
  98. package/lib/modules/SalesSummary/utils.d.ts +1 -0
  99. package/lib/modules/SalesSummary/utils.js +30 -2
  100. package/lib/server/index.d.ts +15 -0
  101. package/lib/server/index.js +105 -14
  102. package/lib/server/modules/order/index.d.ts +2 -0
  103. package/lib/server/modules/order/index.js +75 -5
  104. package/lib/server/modules/order/types.d.ts +1 -1
  105. package/lib/server/utils/small-ticket.js +1 -1
  106. package/lib/solution/BaseSales/index.d.ts +41 -7
  107. package/lib/solution/BaseSales/index.js +325 -39
  108. package/lib/solution/BaseSales/utils/cartPromotion.js +26 -2
  109. package/lib/solution/BaseSales/utils/transformBaseProductToOrderProduct.d.ts +1 -0
  110. package/lib/solution/BaseSales/utils/transformBaseProductToOrderProduct.js +62 -12
  111. package/lib/solution/BaseSales/utils.js +29 -18
  112. package/lib/solution/BookingByStep/index.d.ts +3 -3
  113. package/lib/solution/BookingTicket/index.d.ts +5 -1
  114. package/lib/solution/BookingTicket/index.js +31 -6
  115. package/lib/solution/BookingTicket/types.d.ts +3 -0
  116. package/lib/solution/BookingTicket/utils/addProductDecision.js +2 -1
  117. package/lib/solution/BookingTicket/utils/cartView.js +43 -30
  118. package/lib/solution/Sales/index.d.ts +1 -0
  119. package/lib/solution/Sales/index.js +15 -3
  120. package/lib/solution/ScanOrder/index.js +17 -54
  121. package/lib/solution/ScanOrder/utils.js +29 -18
  122. package/lib/solution/UnifiedBookingSales/index.d.ts +5 -4
  123. package/lib/solution/UnifiedBookingSales/index.js +35 -26
  124. package/package.json +1 -1
@@ -3,7 +3,7 @@ import type { ConditionScope, DataVariantBusinessData, DataVariantMenu, DataVari
3
3
  /**
4
4
  * Data Variant 评估器。
5
5
  *
6
- * 当前阶段只提供策略模型能力和示例验证,不接入 ProductList 业务链路。
6
+ * OS Server 商品路由开启时由 ProductsModule 调用;路由关闭时由 ProductList 在远端响应后调用。
7
7
  */
8
8
  export declare class DataVariantEvaluator {
9
9
  private adapter;
@@ -17,7 +17,7 @@ export declare class DataVariantEvaluator {
17
17
  /**
18
18
  * 批量解析商品 Data Variant。
19
19
  *
20
- * 先执行全局预计算,再只针对商品自身 data_variants 引用到的规则做商品级判断。
20
+ * 优先使用商品 metadata.data_variant_ids;缺少权威命中结果时才执行本地策略判断。
21
21
  */
22
22
  resolveProducts(businessData: DataVariantBusinessData): ResolvedDataVariantResult;
23
23
  /**
@@ -34,8 +34,24 @@ export declare class DataVariantEvaluator {
34
34
  * 这里不依赖餐牌,只读取策略配置和调用方传入的 scheduleList。
35
35
  */
36
36
  buildScheduleTimePoints(strategyConfigs: StrategyConfig<DataVariantRuleMetadata>[], scheduleList: NonNullable<DataVariantBusinessData['scheduleList']>): string[];
37
+ /**
38
+ * 判断商品(含套餐子商品)是否仍需要本地执行策略条件。
39
+ * metadata.data_variant_ids 只要是数组,即使为空,也表示接口已经给出权威命中结果。
40
+ */
41
+ private requiresLocalStrategyEvaluation;
42
+ /**
43
+ * 将接口返回的高 -> 低优先级 Variant ID 转成数值优先级。
44
+ * 返回 null 表示接口没有提供该字段;空 Map 表示已计算但没有任何命中。
45
+ */
46
+ private getAuthoritativeVariantPriorities;
47
+ private normalizeDataVariantId;
37
48
  private buildGlobalEffectiveRuleMap;
38
49
  private resolveProduct;
50
+ /**
51
+ * 多货币 Data Variant 只生成候选列表,不参与商品字段覆盖。
52
+ * rule_id=0 是无条件默认价;同币种命中的条件价优先于默认价。
53
+ */
54
+ private resolveSettlementOptions;
39
55
  /**
40
56
  * 解析套餐子商品上的 Data Variant 覆盖。
41
57
  *
@@ -43,7 +43,7 @@ var DataVariantEvaluator = class {
43
43
  /**
44
44
  * 批量解析商品 Data Variant。
45
45
  *
46
- * 先执行全局预计算,再只针对商品自身 data_variants 引用到的规则做商品级判断。
46
+ * 优先使用商品 metadata.data_variant_ids;缺少权威命中结果时才执行本地策略判断。
47
47
  */
48
48
  resolveProducts(businessData) {
49
49
  const strategyConfigs = businessData.strategyConfigs ? this.normalizeStrategyConfigs(businessData.strategyConfigs) : this.strategyConfigs;
@@ -52,11 +52,17 @@ var DataVariantEvaluator = class {
52
52
  strategyConfigs,
53
53
  businessData.scheduleList || []
54
54
  );
55
- const { globalEffectiveRuleMap, rawResults } = this.buildGlobalEffectiveRuleMap(
55
+ const shouldEvaluateStrategies = businessData.products.some(
56
+ (product) => this.requiresLocalStrategyEvaluation(product)
57
+ );
58
+ const { globalEffectiveRuleMap, rawResults } = shouldEvaluateStrategies ? this.buildGlobalEffectiveRuleMap(
56
59
  strategyConfigs,
57
60
  businessData,
58
61
  menuScopeMap
59
- );
62
+ ) : {
63
+ globalEffectiveRuleMap: /* @__PURE__ */ new Map(),
64
+ rawResults: []
65
+ };
60
66
  const products = businessData.products.map((product) => {
61
67
  const resolvedProduct = this.resolveProduct(
62
68
  product,
@@ -154,6 +160,56 @@ var DataVariantEvaluator = class {
154
160
  );
155
161
  return this.extractTimePointsFromSchedules(matchedSchedules);
156
162
  }
163
+ /**
164
+ * 判断商品(含套餐子商品)是否仍需要本地执行策略条件。
165
+ * metadata.data_variant_ids 只要是数组,即使为空,也表示接口已经给出权威命中结果。
166
+ */
167
+ requiresLocalStrategyEvaluation(product) {
168
+ const dataVariants = this.getProductDataVariants(product);
169
+ const hasConditionalVariant = dataVariants.some(
170
+ (variant) => Number(variant.data_variant_rule_id) !== 0
171
+ );
172
+ if (hasConditionalVariant && this.getAuthoritativeVariantPriorities(product) === null) {
173
+ return true;
174
+ }
175
+ const bundleGroups = Array.isArray(product.bundle_group) ? product.bundle_group : [];
176
+ return bundleGroups.some((group) => {
177
+ const bundleItems = Array.isArray(group == null ? void 0 : group.bundle_item) ? group.bundle_item : [];
178
+ return bundleItems.some(
179
+ (item) => this.requiresLocalStrategyEvaluation(
180
+ this.toBundleItemDataVariantProduct(item)
181
+ )
182
+ );
183
+ });
184
+ }
185
+ /**
186
+ * 将接口返回的高 -> 低优先级 Variant ID 转成数值优先级。
187
+ * 返回 null 表示接口没有提供该字段;空 Map 表示已计算但没有任何命中。
188
+ */
189
+ getAuthoritativeVariantPriorities(product) {
190
+ var _a;
191
+ const rawIds = (_a = product == null ? void 0 : product.metadata) == null ? void 0 : _a.data_variant_ids;
192
+ if (!Array.isArray(rawIds))
193
+ return null;
194
+ const orderedIds = [];
195
+ const seen = /* @__PURE__ */ new Set();
196
+ for (const rawId of rawIds) {
197
+ const id = this.normalizeDataVariantId(rawId);
198
+ if (id === null || seen.has(id))
199
+ continue;
200
+ seen.add(id);
201
+ orderedIds.push(id);
202
+ }
203
+ return new Map(
204
+ orderedIds.map((id, index) => [id, orderedIds.length - index])
205
+ );
206
+ }
207
+ normalizeDataVariantId(value) {
208
+ if (value === void 0 || value === null)
209
+ return null;
210
+ const normalized = String(value).trim();
211
+ return normalized || null;
212
+ }
157
213
  buildGlobalEffectiveRuleMap(strategyConfigs, businessData, menuScopeMap) {
158
214
  const globalEffectiveRuleMap = /* @__PURE__ */ new Map();
159
215
  const rawResults = [];
@@ -182,7 +238,26 @@ var DataVariantEvaluator = class {
182
238
  if (!dataVariants.length)
183
239
  return product;
184
240
  const matchedVariants = [];
241
+ const authoritativeVariantPriorities = this.getAuthoritativeVariantPriorities(product);
242
+ const settlementVariants = dataVariants.filter(
243
+ (variant) => variant.business_type === "multi_currency_settlement" && variant.status === "enable"
244
+ );
185
245
  for (const variant of dataVariants) {
246
+ if (variant.business_type === "multi_currency_settlement")
247
+ continue;
248
+ if (authoritativeVariantPriorities !== null) {
249
+ const variantId = this.normalizeDataVariantId(variant.id);
250
+ const priority = variantId === null ? void 0 : authoritativeVariantPriorities.get(variantId);
251
+ if (priority === void 0)
252
+ continue;
253
+ matchedVariants.push({
254
+ ruleId: String(variant.data_variant_rule_id),
255
+ priority,
256
+ data: variant.data || {},
257
+ variant
258
+ });
259
+ continue;
260
+ }
186
261
  const rule = globalEffectiveRuleMap.get(
187
262
  String(variant.data_variant_rule_id)
188
263
  );
@@ -198,9 +273,91 @@ var DataVariantEvaluator = class {
198
273
  variant
199
274
  });
200
275
  }
201
- if (!matchedVariants.length)
202
- return product;
203
- return this.mergeMatchedVariants(product, matchedVariants);
276
+ const resolvedProduct = matchedVariants.length ? this.mergeMatchedVariants(product, matchedVariants) : product;
277
+ const availableSettlementOptions = this.resolveSettlementOptions(
278
+ settlementVariants,
279
+ product,
280
+ businessData,
281
+ globalEffectiveRuleMap,
282
+ menuScopeMap,
283
+ authoritativeVariantPriorities
284
+ );
285
+ if (!settlementVariants.length)
286
+ return resolvedProduct;
287
+ return {
288
+ ...resolvedProduct,
289
+ available_settlement_options: availableSettlementOptions
290
+ };
291
+ }
292
+ /**
293
+ * 多货币 Data Variant 只生成候选列表,不参与商品字段覆盖。
294
+ * rule_id=0 是无条件默认价;同币种命中的条件价优先于默认价。
295
+ */
296
+ resolveSettlementOptions(variants, product, businessData, globalEffectiveRuleMap, menuScopeMap, authoritativeVariantPriorities) {
297
+ if (!variants.length)
298
+ return [];
299
+ const candidates = variants.flatMap((variant) => {
300
+ var _a, _b;
301
+ const settlement = (_a = variant.data) == null ? void 0 : _a.multi_currency_settlement;
302
+ if (!settlement || typeof settlement !== "object")
303
+ return [];
304
+ const ruleId = String(variant.data_variant_rule_id);
305
+ const isDefault = Number(variant.data_variant_rule_id) === 0;
306
+ const rule = isDefault ? void 0 : globalEffectiveRuleMap.get(ruleId);
307
+ const variantId = this.normalizeDataVariantId(variant.id);
308
+ const authoritativePriority = variantId === null ? void 0 : authoritativeVariantPriorities == null ? void 0 : authoritativeVariantPriorities.get(variantId);
309
+ let available;
310
+ let priority;
311
+ if (isDefault) {
312
+ available = true;
313
+ priority = Number.NEGATIVE_INFINITY;
314
+ } else if (authoritativeVariantPriorities !== null) {
315
+ available = authoritativePriority !== void 0;
316
+ priority = authoritativePriority ?? (rule == null ? void 0 : rule.priority) ?? Number.NEGATIVE_INFINITY;
317
+ } else {
318
+ available = Boolean(
319
+ rule && this.evaluateProductRules(
320
+ rule,
321
+ product,
322
+ businessData,
323
+ menuScopeMap
324
+ )
325
+ );
326
+ priority = (rule == null ? void 0 : rule.priority) ?? Number.NEGATIVE_INFINITY;
327
+ }
328
+ const settlementType = String(settlement.settlement_type || "");
329
+ const currencyProductId = this.toNumber(settlement.currency_product_id);
330
+ const settlementKey = settlementType === "virtual_currency" ? `virtual_currency:${currencyProductId || ""}` : settlementType || `variant:${variant.id ?? ruleId}`;
331
+ return [{
332
+ key: settlementKey,
333
+ priority,
334
+ isDefault,
335
+ option: {
336
+ ...settlement,
337
+ settlement_type: settlementType,
338
+ ...currencyProductId ? { currency_product_id: currencyProductId } : {},
339
+ price: String(((_b = variant.data) == null ? void 0 : _b.price) ?? "0"),
340
+ data_variant_id: variant.id,
341
+ data_variant_rule_id: variant.data_variant_rule_id,
342
+ available,
343
+ disabled_reason: available ? "" : String(
344
+ settlement.disabled_reason || "Not available for this purchase."
345
+ )
346
+ }
347
+ }];
348
+ });
349
+ const grouped = /* @__PURE__ */ new Map();
350
+ for (const candidate of candidates) {
351
+ const group = grouped.get(candidate.key) || [];
352
+ group.push(candidate);
353
+ grouped.set(candidate.key, group);
354
+ }
355
+ return Array.from(grouped.values()).map((group) => {
356
+ const matchedConditional = group.filter((candidate) => !candidate.isDefault && candidate.option.available).sort((a, b) => b.priority - a.priority)[0];
357
+ const defaultCandidate = group.find((candidate) => candidate.isDefault);
358
+ const unavailableCandidate = [...group].filter((candidate) => !candidate.option.available).sort((a, b) => b.priority - a.priority)[0];
359
+ return (matchedConditional || defaultCandidate || unavailableCandidate).option;
360
+ });
204
361
  }
205
362
  /**
206
363
  * 解析套餐子商品上的 Data Variant 覆盖。
@@ -262,7 +419,8 @@ var DataVariantEvaluator = class {
262
419
  product_variant_id: variantId,
263
420
  variant_id: variantId,
264
421
  collection: item.collection,
265
- data_variants: item.data_variants
422
+ data_variants: item.data_variants,
423
+ metadata: item.metadata
266
424
  };
267
425
  }
268
426
  /**
@@ -501,6 +659,11 @@ var DataVariantEvaluator = class {
501
659
  }
502
660
  result._dataVariant = {
503
661
  appliedRuleIds,
662
+ appliedVariantIds: Array.from(
663
+ new Set(
664
+ [...sortedVariants].reverse().map((matched) => this.normalizeDataVariantId(matched.variant.id)).filter((id) => id !== null)
665
+ )
666
+ ),
504
667
  appliedFields: Array.from(appliedFields),
505
668
  original
506
669
  };
@@ -35,6 +35,10 @@ export interface DataVariantItem {
35
35
  module_data_id?: number;
36
36
  /** 关联策略模型 metadata.id / data_variant_rule.id */
37
37
  data_variant_rule_id: number | string;
38
+ /** 业务类型;多货币结算候选不会走普通字段覆盖 */
39
+ business_type?: 'multi_currency_settlement' | string;
40
+ /** 数据状态;多货币结算仅处理 enable 记录 */
41
+ status?: 'enable' | 'disable' | string;
38
42
  /** 命中策略后需要覆盖到商品上的字段 */
39
43
  data: Record<string, any>;
40
44
  created_at?: string | null;
@@ -53,11 +57,30 @@ export interface DataVariantProduct {
53
57
  id?: number | string;
54
58
  [key: string]: any;
55
59
  }>;
60
+ /**
61
+ * 商品接口已经完成规则计算时返回的命中结果。
62
+ * 数组按 Data Variant 优先级从高到低排列;空数组表示已计算但无命中。
63
+ */
64
+ metadata?: {
65
+ data_variant_ids?: Array<number | string>;
66
+ [key: string]: any;
67
+ };
56
68
  /** 后端 /product/query with dataVariants 返回的智能定价变体列表 */
57
69
  data_variants?: DataVariantItem[];
70
+ /** 当前上下文解析后的可选结算货币,不覆盖商品原始多货币配置 */
71
+ available_settlement_options?: AvailableSettlementOption[];
58
72
  _dataVariant?: DataVariantAppliedInfo;
59
73
  [key: string]: any;
60
74
  }
75
+ export interface AvailableSettlementOption extends Record<string, any> {
76
+ settlement_type: 'legal_currency' | 'virtual_currency' | string;
77
+ currency_product_id?: number;
78
+ price: string;
79
+ data_variant_id?: number;
80
+ data_variant_rule_id: number | string;
81
+ available: boolean;
82
+ disabled_reason?: string;
83
+ }
61
84
  /**
62
85
  * 餐牌商品配置项。
63
86
  */
@@ -131,6 +154,8 @@ export interface MatchedDataVariant {
131
154
  */
132
155
  export interface DataVariantAppliedInfo {
133
156
  appliedRuleIds: string[];
157
+ /** 本地策略实际应用的 Data Variant ID,按优先级从高到低排列。 */
158
+ appliedVariantIds?: Array<number | string>;
134
159
  appliedFields: string[];
135
160
  original: DataVariantProduct;
136
161
  }
@@ -214,7 +214,6 @@ var WalletPassEvaluator = class {
214
214
  }
215
215
  const rawText = this.getText(reasonCode);
216
216
  const currency = failedField && AMOUNT_FIELDS.has(failedField) ? ((_g = (_f = this.otherParams) == null ? void 0 : _f.getData) == null ? void 0 : _g.call(_f, "shop_symbol")) || "" : "";
217
- console.log(currency, "currency12345");
218
217
  const reason = thresholdValue !== void 0 ? formatReason(rawText, thresholdValue, currency) : rawText;
219
218
  results.push({
220
219
  voucher,
@@ -416,7 +415,6 @@ var WalletPassEvaluator = class {
416
415
  * 计算适用商品的总金额和数量
417
416
  */
418
417
  calculateApplicableProducts(products, applicableProductIds, deductTaxAndFee) {
419
- console.log(products, "products1234");
420
418
  let total = 0;
421
419
  let count = 0;
422
420
  if (applicableProductIds !== null && applicableProductIds.length === 0) {
@@ -26,6 +26,11 @@ export interface Voucher {
26
26
  recommended_usage_amount: number;
27
27
  /** 后端计算推荐使用金额(仅商品) 不包含税费附加费 */
28
28
  recommended_pure_product_usage_amount: number;
29
+ /**
30
+ * 扫码资产自身可供前端重新计算的金额基准。
31
+ * 后端 recommended_usage_amount 会包含当前已选券上下文,取消勾选后不能继续把它当作资产永久上限。
32
+ */
33
+ _wallet_asset_recalculation_base_amount?: number;
29
34
  /** 实际抵扣金额 */
30
35
  actualDeduction: number;
31
36
  /** 抵扣详情 */
@@ -43,6 +43,21 @@ __export(utils_exports, {
43
43
  });
44
44
  module.exports = __toCommonJS(utils_exports);
45
45
  var import_decimal = __toESM(require("decimal.js"));
46
+ var DEFAULT_WALLET_PASS_CALCULATION_CONFIG = {
47
+ maxDeductionAmount: 99999,
48
+ maxUsagePerOrder: 0,
49
+ allowCrossProduct: true,
50
+ applicableProductLimit: 0,
51
+ deductTaxAndFee: true,
52
+ maxPassesPerItem: 0,
53
+ deductOptionPrice: false
54
+ };
55
+ function getWalletPassCalculationConfig(voucher) {
56
+ return {
57
+ ...DEFAULT_WALLET_PASS_CALCULATION_CONFIG,
58
+ ...(voucher == null ? void 0 : voucher.config) || {}
59
+ };
60
+ }
46
61
  var getProductQuantity = (product) => {
47
62
  return product.quantity || product.product_quantity || 1;
48
63
  };
@@ -105,9 +120,15 @@ function applyMaxPassUsageIncrements(usageMap, walletPassProductId, maxPassesPer
105
120
  });
106
121
  }
107
122
  var getRecommendedAmount = (voucher) => {
108
- console.log("voucher312", voucher);
109
123
  const { config, recommended_usage_amount, recommended_pure_product_usage_amount } = voucher;
110
124
  const deductTaxAndFee = (config == null ? void 0 : config.deductTaxAndFee) ?? true;
125
+ const rawRecalculationBaseAmount = voucher._wallet_asset_recalculation_base_amount;
126
+ if (rawRecalculationBaseAmount !== void 0 && rawRecalculationBaseAmount !== null) {
127
+ const recalculationBaseAmount = Number(rawRecalculationBaseAmount);
128
+ if (Number.isFinite(recalculationBaseAmount) && recalculationBaseAmount >= 0) {
129
+ return recalculationBaseAmount;
130
+ }
131
+ }
111
132
  return deductTaxAndFee ? recommended_usage_amount : recommended_pure_product_usage_amount ?? recommended_usage_amount;
112
133
  };
113
134
  var getApplicableProductIds = (voucher) => {
@@ -145,7 +166,6 @@ var getApplicableProducts = (voucher, productsData) => {
145
166
  return productsData.filter((p) => applicableProductIds.includes(p.product_id));
146
167
  };
147
168
  function processVouchers(applicableVouchers, orderTotalAmount, products) {
148
- console.log(products, "products123");
149
169
  const productsCopy = expandProductsWithBundleItems(products, true);
150
170
  let remainingOrderAmount = new import_decimal.default(orderTotalAmount);
151
171
  const getItemPassUsage = (usageMap, walletPassProductId, lineKey) => {
@@ -218,7 +238,7 @@ function processVouchers(applicableVouchers, orderTotalAmount, products) {
218
238
  return import_decimal.default.min(baseAmount, finalApplicableAmount, remainingOrderAmount);
219
239
  };
220
240
  const isVoucherAvailable = (voucher, productsData, usedVoucherCounts2, itemPassUsage) => {
221
- const { config, id, product_id } = voucher;
241
+ const { config, product_id } = voucher;
222
242
  const recommendedAmount = getRecommendedAmount(voucher);
223
243
  if (recommendedAmount <= 0) {
224
244
  return { isAvailable: false, reasonCode: "not_meet_the_required_conditions" };
@@ -453,6 +473,7 @@ function recalculateVouchers(allVouchers, selectedVouchers, orderTotalAmount, pr
453
473
  const productsForCalc = expandProductsWithBundleItems(products, true);
454
474
  let remainingOrderAmount = new import_decimal.default(orderTotalAmount);
455
475
  const selectedWithDetails = [];
476
+ const usedVoucherCounts = /* @__PURE__ */ new Map();
456
477
  const itemPassUsageMap = /* @__PURE__ */ new Map();
457
478
  const getItemPassUsageRecalc = (walletPassProductId, lineKey) => {
458
479
  var _a;
@@ -469,8 +490,29 @@ function recalculateVouchers(allVouchers, selectedVouchers, orderTotalAmount, pr
469
490
  });
470
491
  };
471
492
  selectedVouchers.forEach((selectedVoucher) => {
472
- const { config, id } = selectedVoucher;
473
- const { maxDeductionAmount, allowCrossProduct, applicableProductLimit, deductTaxAndFee = true, maxPassesPerItem = 0 } = config;
493
+ const { product_id } = selectedVoucher;
494
+ const config = getWalletPassCalculationConfig(selectedVoucher);
495
+ const {
496
+ maxDeductionAmount,
497
+ maxUsagePerOrder,
498
+ allowCrossProduct,
499
+ applicableProductLimit,
500
+ deductTaxAndFee,
501
+ maxPassesPerItem
502
+ } = config;
503
+ const usedCount = usedVoucherCounts.get(product_id) || 0;
504
+ if (maxUsagePerOrder > 0 && usedCount >= maxUsagePerOrder) {
505
+ selectedWithDetails.push({
506
+ ...selectedVoucher,
507
+ config,
508
+ actualDeduction: 0,
509
+ deductionDetails: [],
510
+ _available_max_amount: 0,
511
+ _unified_available_status: 0,
512
+ reasonCode: "usage_limit_reached"
513
+ });
514
+ return;
515
+ }
474
516
  const unitPriceField = deductTaxAndFee ? "unitPriceWithTax" : "unitPricePure";
475
517
  const amountField = deductTaxAndFee ? "remainingAmountWithTax" : "remainingAmountPure";
476
518
  let applicableProducts = getApplicableProducts(
@@ -481,6 +523,7 @@ function recalculateVouchers(allVouchers, selectedVouchers, orderTotalAmount, pr
481
523
  if (applicableProducts.length === 0) {
482
524
  selectedWithDetails.push({
483
525
  ...selectedVoucher,
526
+ config,
484
527
  actualDeduction: 0,
485
528
  deductionDetails: [],
486
529
  _available_max_amount: 0,
@@ -558,6 +601,7 @@ function recalculateVouchers(allVouchers, selectedVouchers, orderTotalAmount, pr
558
601
  applyMaxPassUsageIncrements(itemPassUsageMap, selectedVoucher.product_id, maxPassesPerItem, deductionDetails);
559
602
  selectedWithDetails.push({
560
603
  ...selectedVoucher,
604
+ config,
561
605
  actualDeduction: totalDeducted.toNumber(),
562
606
  // 转换为数字
563
607
  deductionDetails,
@@ -565,12 +609,11 @@ function recalculateVouchers(allVouchers, selectedVouchers, orderTotalAmount, pr
565
609
  // 转换为数字
566
610
  _unified_available_status: totalDeducted.greaterThan(0) ? 1 : 0
567
611
  });
612
+ if (totalDeducted.greaterThan(0)) {
613
+ usedVoucherCounts.set(product_id, usedCount + 1);
614
+ }
568
615
  });
569
616
  const selectedIds = new Set(selectedVouchers.map((v) => v.id));
570
- const usedVoucherCounts = /* @__PURE__ */ new Map();
571
- selectedVouchers.forEach((v) => {
572
- usedVoucherCounts.set(v.product_id, (usedVoucherCounts.get(v.product_id) || 0) + 1);
573
- });
574
617
  const allWithUpdatedStatus = allVouchers.map((voucher) => {
575
618
  if (selectedIds.has(voucher.id)) {
576
619
  const selectedDetail = selectedWithDetails.find(
@@ -578,8 +621,15 @@ function recalculateVouchers(allVouchers, selectedVouchers, orderTotalAmount, pr
578
621
  );
579
622
  return selectedDetail || voucher;
580
623
  }
581
- const { config, id, product_id } = voucher;
582
- const { maxDeductionAmount, allowCrossProduct, applicableProductLimit, deductTaxAndFee = true, maxPassesPerItem = 0 } = config;
624
+ const { product_id } = voucher;
625
+ const config = getWalletPassCalculationConfig(voucher);
626
+ const {
627
+ maxDeductionAmount,
628
+ allowCrossProduct,
629
+ applicableProductLimit,
630
+ deductTaxAndFee,
631
+ maxPassesPerItem
632
+ } = config;
583
633
  const unitPriceField = deductTaxAndFee ? "unitPriceWithTax" : "unitPricePure";
584
634
  const amountField = deductTaxAndFee ? "remainingAmountWithTax" : "remainingAmountPure";
585
635
  const recommendedAmount = getRecommendedAmount(voucher);
@@ -663,6 +713,7 @@ function recalculateVouchers(allVouchers, selectedVouchers, orderTotalAmount, pr
663
713
  }
664
714
  return {
665
715
  ...voucher,
716
+ config,
666
717
  _available_max_amount: calculatedMaxAmount.toNumber(),
667
718
  // 转换为数字
668
719
  _unified_available_status: isAvailable ? 1 : 0,
@@ -149,7 +149,9 @@ function mapOrderBundleToOtherBundle(bundle) {
149
149
  return bundle.map((item) => {
150
150
  return {
151
151
  ...item,
152
- original_price: item.original_price ?? item.bundle_selling_price ?? item.price
152
+ // 后端历史订单可能没有 original_price;此时 price 是折前毛价,
153
+ // bundle_selling_price 已包含折扣,不能把它当成编辑时的新原价。
154
+ original_price: item.original_price ?? item.product_price ?? item.price ?? item.bundle_selling_price
153
155
  };
154
156
  });
155
157
  }
@@ -200,6 +202,8 @@ function buildCacheItemFromOrderLine(input) {
200
202
  const productTitle = resolveProductTitle(product, booking, sourceProduct);
201
203
  const productCover = resolveProductCover(product, booking, sourceProduct);
202
204
  const productOptionString = (0, import_orderLineDisplay.buildProductOptionStringFromOrderLine)(product);
205
+ const hasBundle = Array.isArray(product.product_bundle) && product.product_bundle.length > 0;
206
+ const bundleEdit = product.bundle_edit ?? (hasBundle ? 0 : void 0);
203
207
  const other = {
204
208
  product_variant_id: product.product_variant_id ?? 0,
205
209
  option: (0, import_utils.getProductSkuOptions)(product),
@@ -232,6 +236,7 @@ function buildCacheItemFromOrderLine(input) {
232
236
  product.discount_list,
233
237
  (_c = product.metadata) == null ? void 0 : _c.product_discount_reason
234
238
  ) || void 0,
239
+ ...bundleEdit !== void 0 ? { bundle_edit: bundleEdit } : {},
235
240
  ...productOptionString ? { product_option_string: productOptionString } : {}
236
241
  };
237
242
  const manualDiscount = (product.discount_list || []).find((d) => (d == null ? void 0 : d.type) === "product");
@@ -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)
@@ -44,6 +44,10 @@ export declare class CustomerModule extends BaseModule implements Module, Custom
44
44
  * 用于按 ID 补齐客户等内部查询,避免把临时的 num/searchParams 污染到滚动列表。
45
45
  */
46
46
  queryCustomerList(params?: ShopGetCustomerListParams): Promise<ICustomerListResponse>;
47
+ /**
48
+ * 查询单个客户详情,不写入 customerList / pagination 状态。
49
+ */
50
+ queryCustomerDetail(id: string | number): Promise<ShopCustomer | null>;
47
51
  /**
48
52
  * 获取客户列表
49
53
  * @param params 查询参数
@@ -201,6 +201,17 @@ var CustomerModule = class extends import_BaseModule.BaseModule {
201
201
  pageSize
202
202
  };
203
203
  }
204
+ /**
205
+ * 查询单个客户详情,不写入 customerList / pagination 状态。
206
+ */
207
+ async queryCustomerDetail(id) {
208
+ var _a;
209
+ const result = await this.request.get(`/customer/info/${id}`, {
210
+ with_trashed: 1
211
+ });
212
+ const customer = ((_a = result == null ? void 0 : result.data) == null ? void 0 : _a.customer) || (result == null ? void 0 : result.data) || (result == null ? void 0 : result.customer) || null;
213
+ return customer && typeof customer === "object" ? customer : null;
214
+ }
204
215
  /**
205
216
  * 获取客户列表
206
217
  * @param params 查询参数
@@ -112,6 +112,8 @@ export interface CustomerModuleAPI {
112
112
  getCustomerList: (params?: ShopGetCustomerListParams) => Promise<ICustomerListResponse>;
113
113
  /** 查询客户列表但不更新列表分页状态 */
114
114
  queryCustomerList: (params?: ShopGetCustomerListParams) => Promise<ICustomerListResponse>;
115
+ /** 查询单个客户详情但不修改客户列表状态 */
116
+ queryCustomerDetail: (id: string | number) => Promise<ShopCustomer | null>;
115
117
  /** 设置客户列表 */
116
118
  setCustomerList: (customers: ShopCustomer[], total?: number) => void;
117
119
  /** 设置当前选择的客户 */
@@ -25,9 +25,11 @@ export declare class DiscountModule extends BaseModule implements Module, Discou
25
25
  with_wallet_pass_holder: 0 | 1;
26
26
  with_available_wallet_flag?: 0 | 1;
27
27
  with_available_wallet_pass_flag?: 0 | 1;
28
+ with_virtual_currency?: 0 | 1;
28
29
  request_timezone: string;
29
30
  order_id?: number | string;
30
31
  }): Promise<DiscountPrepareConfigResult>;
32
+ private normalizeVirtualCurrencyList;
31
33
  batchSearch(code: string, options?: {
32
34
  customerId?: number;
33
35
  orderId?: number;