@pisell/pisellos 2.3.100 → 2.3.102

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 (56) hide show
  1. package/dist/model/strategy/adapter/dataVariant/evaluator.d.ts +18 -2
  2. package/dist/model/strategy/adapter/dataVariant/evaluator.js +287 -101
  3. package/dist/model/strategy/adapter/dataVariant/type.d.ts +25 -0
  4. package/dist/model/strategy/adapter/promotion/index.js +9 -0
  5. package/dist/modules/Discount/index.d.ts +2 -0
  6. package/dist/modules/Discount/index.js +38 -5
  7. package/dist/modules/Discount/types.d.ts +11 -0
  8. package/dist/modules/Order/index.d.ts +14 -0
  9. package/dist/modules/Order/index.js +617 -368
  10. package/dist/modules/Order/types.d.ts +11 -3
  11. package/dist/modules/Order/utils/virtualSettlement.d.ts +63 -0
  12. package/dist/modules/Order/utils/virtualSettlement.js +284 -0
  13. package/dist/modules/Order/utils.d.ts +1 -0
  14. package/dist/modules/Order/utils.js +63 -24
  15. package/dist/modules/Product/types.d.ts +23 -0
  16. package/dist/modules/Rules/index.d.ts +5 -0
  17. package/dist/modules/Rules/index.js +27 -12
  18. package/dist/modules/SalesSummary/index.d.ts +1 -0
  19. package/dist/modules/SalesSummary/index.js +4 -3
  20. package/dist/modules/SalesSummary/types.d.ts +1 -0
  21. package/dist/modules/SalesSummary/utils.d.ts +1 -0
  22. package/dist/modules/SalesSummary/utils.js +21 -1
  23. package/dist/solution/BaseSales/index.d.ts +10 -1
  24. package/dist/solution/BaseSales/index.js +1099 -784
  25. package/dist/solution/BaseSales/utils/transformBaseProductToOrderProduct.d.ts +1 -0
  26. package/dist/solution/BaseSales/utils/transformBaseProductToOrderProduct.js +72 -9
  27. package/dist/solution/BookingByStep/index.d.ts +2 -2
  28. package/dist/solution/BookingTicket/utils/addProductDecision.js +2 -1
  29. package/lib/model/strategy/adapter/dataVariant/evaluator.d.ts +18 -2
  30. package/lib/model/strategy/adapter/dataVariant/evaluator.js +137 -6
  31. package/lib/model/strategy/adapter/dataVariant/type.d.ts +25 -0
  32. package/lib/modules/Discount/index.d.ts +2 -0
  33. package/lib/modules/Discount/index.js +31 -2
  34. package/lib/modules/Discount/types.d.ts +11 -0
  35. package/lib/modules/Order/index.d.ts +14 -0
  36. package/lib/modules/Order/index.js +195 -15
  37. package/lib/modules/Order/types.d.ts +11 -3
  38. package/lib/modules/Order/utils/virtualSettlement.d.ts +63 -0
  39. package/lib/modules/Order/utils/virtualSettlement.js +262 -0
  40. package/lib/modules/Order/utils.d.ts +1 -0
  41. package/lib/modules/Order/utils.js +57 -20
  42. package/lib/modules/Product/types.d.ts +23 -0
  43. package/lib/modules/Rules/index.d.ts +5 -0
  44. package/lib/modules/Rules/index.js +20 -11
  45. package/lib/modules/SalesSummary/index.d.ts +1 -0
  46. package/lib/modules/SalesSummary/index.js +4 -2
  47. package/lib/modules/SalesSummary/types.d.ts +1 -0
  48. package/lib/modules/SalesSummary/utils.d.ts +1 -0
  49. package/lib/modules/SalesSummary/utils.js +17 -1
  50. package/lib/solution/BaseSales/index.d.ts +10 -1
  51. package/lib/solution/BaseSales/index.js +211 -9
  52. package/lib/solution/BaseSales/utils/transformBaseProductToOrderProduct.d.ts +1 -0
  53. package/lib/solution/BaseSales/utils/transformBaseProductToOrderProduct.js +38 -0
  54. package/lib/solution/BookingByStep/index.d.ts +2 -2
  55. package/lib/solution/BookingTicket/utils/addProductDecision.js +1 -0
  56. package/package.json +1 -1
@@ -0,0 +1,262 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.VirtualCurrencyBalanceError = exports.VirtualCurrencyAssetNotFoundError = exports.OrderSettlementConflictError = void 0;
7
+ exports.applyTempOrderSettlementState = applyTempOrderSettlementState;
8
+ exports.buildCurrencyDisplaySnapshot = buildCurrencyDisplaySnapshot;
9
+ exports.getSelectedSettlementSnapshot = getSelectedSettlementSnapshot;
10
+ exports.getSettlementKey = getSettlementKey;
11
+ exports.getTempOrderSettlementKey = getTempOrderSettlementKey;
12
+ exports.getTempOrderSettlementSnapshot = getTempOrderSettlementSnapshot;
13
+ exports.isVirtualSettlementDisplayOrder = isVirtualSettlementDisplayOrder;
14
+ exports.isVirtualSettlementOrder = isVirtualSettlementOrder;
15
+ exports.isVirtualSettlementProduct = isVirtualSettlementProduct;
16
+ exports.normalizeVirtualSettlementOrderProduct = normalizeVirtualSettlementOrderProduct;
17
+ exports.normalizeVirtualSettlementProducts = normalizeVirtualSettlementProducts;
18
+ exports.releaseTempOrderSettlementState = releaseTempOrderSettlementState;
19
+ exports.resolveSettlementDisplayValue = resolveSettlementDisplayValue;
20
+ var _decimal = _interopRequireDefault(require("decimal.js"));
21
+ var _lodashEs = require("lodash-es");
22
+ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
23
+ function buildCurrencyDisplaySnapshot(snapshot) {
24
+ return (0, _lodashEs.cloneDeep)({
25
+ amount_format: String(snapshot.amount_format || '').trim(),
26
+ symbol: snapshot.symbol ?? {},
27
+ unit_label: snapshot.unit_label ?? {}
28
+ });
29
+ }
30
+ function resolveSettlementDisplayValue(value) {
31
+ if (value === undefined || value === null) return '';
32
+ if (typeof value === 'string' || typeof value === 'number') {
33
+ return String(value);
34
+ }
35
+ const localized = value;
36
+ return String(localized.auto ?? localized.default ?? localized.original ?? localized.en ?? localized['zh-CN'] ?? localized['zh-HK'] ?? '');
37
+ }
38
+ class OrderSettlementConflictError extends Error {
39
+ code = 'ORDER_SETTLEMENT_CONFLICT';
40
+ constructor(currentSettlementKey, incomingSettlementKey) {
41
+ super('Different currencies and WalletPass units cannot be mixed in one cart.');
42
+ this.currentSettlementKey = currentSettlementKey;
43
+ this.incomingSettlementKey = incomingSettlementKey;
44
+ this.name = 'OrderSettlementConflictError';
45
+ }
46
+ }
47
+ exports.OrderSettlementConflictError = OrderSettlementConflictError;
48
+ class VirtualCurrencyAssetNotFoundError extends Error {
49
+ code = 'VIRTUAL_CURRENCY_ASSET_NOT_FOUND';
50
+ constructor(currencyProductId) {
51
+ super('No matching virtual currency card is available.');
52
+ this.currencyProductId = currencyProductId;
53
+ this.name = 'VirtualCurrencyAssetNotFoundError';
54
+ }
55
+ }
56
+ exports.VirtualCurrencyAssetNotFoundError = VirtualCurrencyAssetNotFoundError;
57
+ class VirtualCurrencyBalanceError extends Error {
58
+ code = 'VIRTUAL_CURRENCY_BALANCE_INSUFFICIENT';
59
+ constructor(balance, requiredAmount) {
60
+ super('Insufficient virtual currency balance.');
61
+ this.balance = balance;
62
+ this.requiredAmount = requiredAmount;
63
+ this.name = 'VirtualCurrencyBalanceError';
64
+ }
65
+ }
66
+ exports.VirtualCurrencyBalanceError = VirtualCurrencyBalanceError;
67
+ function getSelectedSettlementSnapshot(product) {
68
+ const snapshot = product?.metadata?.selected_settlement_snapshot;
69
+ if (!snapshot || typeof snapshot !== 'object') return null;
70
+ if (snapshot.settlement_type !== 'legal_currency' && snapshot.settlement_type !== 'virtual_currency') {
71
+ return null;
72
+ }
73
+ return snapshot;
74
+ }
75
+ function getTempOrderSettlementSnapshot(tempOrder) {
76
+ const snapshot = tempOrder?.metadata?.cart_settlement_snapshot;
77
+ if (snapshot && typeof snapshot === 'object' && (snapshot.settlement_type === 'legal_currency' || snapshot.settlement_type === 'virtual_currency')) {
78
+ return snapshot;
79
+ }
80
+ return getSelectedSettlementSnapshot(tempOrder?.products?.[0]);
81
+ }
82
+ function isVirtualSettlementProduct(product) {
83
+ return getSelectedSettlementSnapshot(product)?.settlement_type === 'virtual_currency';
84
+ }
85
+ function getSettlementKey(snapshot) {
86
+ if (!snapshot) return null;
87
+ if (snapshot.settlement_type === 'legal_currency') return 'legal_currency';
88
+ const currencyProductId = Number(snapshot.currency_product_id);
89
+ return Number.isFinite(currencyProductId) && currencyProductId > 0 ? `virtual_currency:${currencyProductId}` : null;
90
+ }
91
+ function getTempOrderSettlementKey(tempOrder) {
92
+ if (!tempOrder?.products?.length) return null;
93
+ const storedKey = tempOrder.metadata?.cart_settlement_key;
94
+ if (typeof storedKey === 'string' && storedKey) return storedKey;
95
+ return getSettlementKey(getTempOrderSettlementSnapshot(tempOrder)) || 'legal_currency';
96
+ }
97
+ function isVirtualSettlementOrder(tempOrder) {
98
+ return getTempOrderSettlementKey(tempOrder)?.startsWith('virtual_currency:') === true;
99
+ }
100
+
101
+ /**
102
+ * 详情展示识别同时支持已提交订单的 currency_settlement_config。
103
+ * cart_settlement_* 是交易过程内部状态,服务端详情不保证返回。
104
+ */
105
+ function isVirtualSettlementDisplayOrder(tempOrder) {
106
+ return tempOrder?.metadata?.currency_settlement_config?.settlement_type === 'virtual_currency' || isVirtualSettlementOrder(tempOrder);
107
+ }
108
+ function getLegalCurrencySnapshot(tempOrder) {
109
+ const snapshot = tempOrder.metadata?.legal_currency_snapshot;
110
+ if (!snapshot || typeof snapshot !== 'object') return null;
111
+ return {
112
+ currency_code: String(snapshot.currency_code || ''),
113
+ currency_symbol: String(snapshot.currency_symbol || ''),
114
+ currency_format: String(snapshot.currency_format || 'symbol_first')
115
+ };
116
+ }
117
+ function captureLegalCurrencySnapshot(tempOrder) {
118
+ tempOrder.metadata = tempOrder.metadata || {};
119
+ if (getLegalCurrencySnapshot(tempOrder)) return;
120
+ tempOrder.metadata.legal_currency_snapshot = {
121
+ currency_code: String(tempOrder.currency_code || ''),
122
+ currency_symbol: String(tempOrder.currency_symbol || ''),
123
+ currency_format: String(tempOrder.currency_format || 'symbol_first')
124
+ };
125
+ }
126
+
127
+ /**
128
+ * 将购物车锁定的结算单位提升为订单级状态。
129
+ * 商品行快照同时负责价格归一化和生成订单级多语言展示快照;
130
+ * 交易中的即时展示仍统一读取 tempOrder.currency_*。
131
+ */
132
+ function applyTempOrderSettlementState(tempOrder, snapshot) {
133
+ tempOrder.metadata = tempOrder.metadata || {};
134
+ const settlementKey = getSettlementKey(snapshot) || 'legal_currency';
135
+ if (snapshot.settlement_type === 'virtual_currency') {
136
+ captureLegalCurrencySnapshot(tempOrder);
137
+ const currencyProductId = Number(snapshot.currency_product_id);
138
+ const existingDisplaySnapshot = tempOrder.metadata.currency_settlement_config?.currency_display_snapshot;
139
+ tempOrder.metadata.currency_settlement_config = {
140
+ settlement_type: 'virtual_currency',
141
+ settlement_ids: Number.isFinite(currencyProductId) && currencyProductId > 0 ? [currencyProductId] : [],
142
+ currency_display_snapshot: existingDisplaySnapshot ? (0, _lodashEs.cloneDeep)(existingDisplaySnapshot) : buildCurrencyDisplaySnapshot(snapshot)
143
+ };
144
+ } else {
145
+ delete tempOrder.metadata.currency_settlement_config;
146
+ }
147
+ tempOrder.metadata.cart_settlement_key = settlementKey;
148
+ tempOrder.metadata.cart_settlement_snapshot = {
149
+ ...snapshot
150
+ };
151
+ tempOrder.currency_code = resolveSettlementDisplayValue(snapshot.unit_label) || tempOrder.currency_code;
152
+ tempOrder.currency_symbol = resolveSettlementDisplayValue(snapshot.symbol) || tempOrder.currency_symbol;
153
+ tempOrder.currency_format = String(snapshot.amount_format || tempOrder.currency_format || 'symbol_first');
154
+ }
155
+
156
+ /** 清空购物车结算锁,并恢复进入虚拟币结算前的店铺法币。 */
157
+ function releaseTempOrderSettlementState(tempOrder) {
158
+ tempOrder.metadata = tempOrder.metadata || {};
159
+ const legalCurrency = getLegalCurrencySnapshot(tempOrder);
160
+ if (legalCurrency) {
161
+ tempOrder.currency_code = legalCurrency.currency_code;
162
+ tempOrder.currency_symbol = legalCurrency.currency_symbol;
163
+ tempOrder.currency_format = legalCurrency.currency_format;
164
+ }
165
+ delete tempOrder.metadata.cart_settlement_key;
166
+ delete tempOrder.metadata.cart_settlement_snapshot;
167
+ delete tempOrder.metadata.currency_settlement_config;
168
+ }
169
+ function toMoney(value) {
170
+ try {
171
+ return new _decimal.default(String(value ?? 0)).toDecimalPlaces(2).toFixed(2);
172
+ } catch {
173
+ return '0.00';
174
+ }
175
+ }
176
+ function zeroOptions(options) {
177
+ if (!Array.isArray(options)) return [];
178
+ return options.map(option => ({
179
+ ...option,
180
+ add_price: 0
181
+ }));
182
+ }
183
+ function zeroBundles(bundles) {
184
+ if (!Array.isArray(bundles)) return [];
185
+ return bundles.map(bundle => ({
186
+ ...bundle,
187
+ price: 0,
188
+ custom_price: 0,
189
+ bundle_selling_price: 0,
190
+ bundle_payment_price: 0,
191
+ surcharge_fee: 0,
192
+ tax_fee: 0,
193
+ original_tax_fee: 0,
194
+ is_charge_tax: 0,
195
+ discount_list: [],
196
+ relation_surcharge_ids: [],
197
+ option: zeroOptions(bundle?.option),
198
+ metadata: {
199
+ ...(bundle?.metadata || {}),
200
+ surcharge_fee: '0.00',
201
+ relation_surcharge_ids: [],
202
+ product_discount_difference: '',
203
+ tax_fee_rounding_remainder: '0.00',
204
+ surcharge_rounding_remainder: '0.00'
205
+ }
206
+ }));
207
+ }
208
+
209
+ /**
210
+ * 将已选择虚拟币的订单商品行固化为最终价语义。
211
+ * 函数幂等,可安全用于加车、合并、hydrate 和提交前兜底。
212
+ */
213
+ function normalizeVirtualSettlementOrderProduct(product) {
214
+ const snapshot = getSelectedSettlementSnapshot(product);
215
+ if (snapshot?.settlement_type !== 'virtual_currency') return product;
216
+ const virtualPrice = toMoney(snapshot.price);
217
+ const metadata = {
218
+ ...(product.metadata || {}),
219
+ source_product_price: virtualPrice,
220
+ main_product_original_price: virtualPrice,
221
+ main_product_selling_price: virtualPrice,
222
+ main_product_attached_bundle_selling_price: virtualPrice,
223
+ main_product_attached_bundle_payment_price: virtualPrice,
224
+ main_product_attached_bundle_surcharge_fee: '0.00',
225
+ main_product_attached_bundle_tax_fee: '0.00',
226
+ surcharge_rounding_remainder: '0.00',
227
+ tax_fee_rounding_remainder: '0.00',
228
+ relation_surcharge_ids: [],
229
+ average_discount_amount_rate: '1',
230
+ product_discount_difference: '',
231
+ discountable_flag: 0,
232
+ price_schema_version: 2,
233
+ selected_settlement_snapshot: {
234
+ ...snapshot,
235
+ price: virtualPrice
236
+ }
237
+ };
238
+ return {
239
+ ...product,
240
+ selling_price: virtualPrice,
241
+ original_price: virtualPrice,
242
+ payment_price: virtualPrice,
243
+ discount_list: [],
244
+ is_charge_tax: 0,
245
+ tax_fee: '0.00',
246
+ original_tax_fee: 0,
247
+ tax_fee_rounding_remainder: 0,
248
+ original_tax_fee_rounding_remainder: 0,
249
+ surcharge_fee: '0.00',
250
+ surcharge_rounding_remainder: 0,
251
+ relation_surcharge_ids: [],
252
+ product_sku: {
253
+ ...(product.product_sku || {}),
254
+ option: zeroOptions(product.product_sku?.option)
255
+ },
256
+ product_bundle: zeroBundles(product.product_bundle),
257
+ metadata
258
+ };
259
+ }
260
+ function normalizeVirtualSettlementProducts(products) {
261
+ return (products || []).map(product => normalizeVirtualSettlementOrderProduct(product));
262
+ }
@@ -232,6 +232,7 @@ export declare function buildSubmitPayload(params: {
232
232
  type?: string;
233
233
  summary?: OrderSummary | null;
234
234
  request_unique_idempotency_token?: string;
235
+ includePaymentStatus?: boolean;
235
236
  enhance?: SubmitPayloadEnhancer;
236
237
  }): OrderSubmitPayload;
237
238
  export declare function mapPaymentItemToOrderPayment(paymentItem: OrderPaymentSource): OrderPaymentData;
@@ -864,7 +864,7 @@ function normalizeSubmitProductionCode(value) {
864
864
  }
865
865
 
866
866
  // 后端 checkout 协议字段 option_group_item_id;tempOrder 内部也保持同一字段名。
867
- function formatSubmitOptionItems(options) {
867
+ function formatSubmitOptionItems(options, formatOptions = {}) {
868
868
  if (!Array.isArray(options)) return [];
869
869
  return options.map(d => {
870
870
  const option = {
@@ -877,7 +877,11 @@ function formatSubmitOptionItems(options) {
877
877
  delete option.production_code;
878
878
  if (productionCode !== undefined) option.production_code = productionCode;
879
879
  const addPrice = d?.add_price ?? d?.price;
880
- if (addPrice !== undefined) option.add_price = String(addPrice);
880
+ if (formatOptions.virtualSettlementMode) {
881
+ option.add_price = 0;
882
+ } else if (addPrice !== undefined) {
883
+ option.add_price = String(addPrice);
884
+ }
881
885
  delete option.price;
882
886
  return option;
883
887
  });
@@ -894,7 +898,7 @@ function toMoneyString(value) {
894
898
  }
895
899
 
896
900
  // 出站补齐:只输出后端 checkout 协议字段,运行时/详情展示字段不进入 bundle payload。
897
- function formatSubmitBundleItems(bundle) {
901
+ function formatSubmitBundleItems(bundle, formatOptions = {}) {
898
902
  if (!Array.isArray(bundle)) return [];
899
903
  return bundle.map(b => {
900
904
  const rawBundle = b && typeof b === 'object' ? b : {};
@@ -906,10 +910,10 @@ function formatSubmitBundleItems(bundle) {
906
910
 
907
911
  // bundle_selling_price 出站存「正净额」:markdown 为 |price| − Σoptions(如 3.70),
908
912
  // markup/原价维持原值;与后端口径一致(price 仍为毛价)。
909
- const sellingPrice = getBundleSellingMagnitude(rawBundle).toFixed(2);
913
+ const sellingPrice = formatOptions.virtualSettlementMode ? '0.00' : getBundleSellingMagnitude(rawBundle).toFixed(2);
910
914
  // payment 价由整单折扣均摊层产出;缺省(无折扣)时与 selling 净额同义。
911
- const paymentPrice = rawBundle.bundle_payment_price !== undefined && rawBundle.bundle_payment_price !== null && rawBundle.bundle_payment_price !== '' ? toMoneyString(rawBundle.bundle_payment_price) : sellingPrice;
912
- const priceValue = rawBundle.price ?? rawBundle.custom_price ?? rawBundle.bundle_selling_price;
915
+ const paymentPrice = formatOptions.virtualSettlementMode ? '0.00' : rawBundle.bundle_payment_price !== undefined && rawBundle.bundle_payment_price !== null && rawBundle.bundle_payment_price !== '' ? toMoneyString(rawBundle.bundle_payment_price) : sellingPrice;
916
+ const priceValue = formatOptions.virtualSettlementMode ? 0 : rawBundle.price ?? rawBundle.custom_price ?? rawBundle.bundle_selling_price;
913
917
  const relationSurchargeIds = Array.isArray(rawBundle.relation_surcharge_ids) ? rawBundle.relation_surcharge_ids : Array.isArray(existedMetadata.relation_surcharge_ids) ? existedMetadata.relation_surcharge_ids : [];
914
918
  const surchargeFee = toMoneyString(rawBundle.surcharge_fee ?? existedMetadata.surcharge_fee);
915
919
  const productDiscountDifference = toBundleNumber(existedMetadata.product_discount_difference, 0);
@@ -918,7 +922,7 @@ function formatSubmitBundleItems(bundle) {
918
922
  const hasSubmitBundleDiscounts = Array.isArray(rawBundle.discount_list) && rawBundle.discount_list.length > 0;
919
923
  const productionCode = normalizeSubmitProductionCode(rawBundle.production_code);
920
924
  return {
921
- is_charge_tax: rawBundle.is_charge_tax ?? 0,
925
+ is_charge_tax: formatOptions.virtualSettlementMode ? 0 : rawBundle.is_charge_tax ?? 0,
922
926
  bundle_variant_id: rawBundle.bundle_variant_id ?? 0,
923
927
  ...(productionCode !== undefined ? {
924
928
  production_code: productionCode
@@ -932,7 +936,7 @@ function formatSubmitBundleItems(bundle) {
932
936
  price_type_ext: rawBundle.price_type_ext ?? rawBundle.custom_price_type_ext ?? '',
933
937
  bundle_selling_price: sellingPrice,
934
938
  bundle_payment_price: paymentPrice,
935
- option: formatSubmitOptionItems(rawBundle.option),
939
+ option: formatSubmitOptionItems(rawBundle.option, formatOptions),
936
940
  bundle_group_id: rawBundle?.bundle_group_id ?? rawBundle?.group_id,
937
941
  bundle_id: rawBundle?.bundle_id ?? rawBundle?.id,
938
942
  bundle_product_id: rawBundle?.bundle_product_id ?? rawBundle?._bundle_product_id,
@@ -942,9 +946,9 @@ function formatSubmitBundleItems(bundle) {
942
946
  ...(hasSubmitBundleDiscounts && bundleMapId !== undefined ? {
943
947
  custom_product_bundle_map_id: bundleMapId
944
948
  } : {}),
945
- surcharge_fee: surchargeFee,
946
- relation_surcharge_ids: relationSurchargeIds,
947
- product_discount_difference: productDiscountDifference
949
+ surcharge_fee: formatOptions.virtualSettlementMode ? '0.00' : surchargeFee,
950
+ relation_surcharge_ids: formatOptions.virtualSettlementMode ? [] : relationSurchargeIds,
951
+ product_discount_difference: formatOptions.virtualSettlementMode ? '' : productDiscountDifference
948
952
  }
949
953
  };
950
954
  });
@@ -964,9 +968,9 @@ function formatSubmitBundleItems(bundle) {
964
968
  * - `metadata.main_product_selling_price`:含 option、含主商品折扣。
965
969
  * - `metadata.price_schema_version`:schema 版本号(当前 = 2),用于跨端协商价格口径。
966
970
  *
967
- * 本函数不再改造价格字段形状,仅做字段/metadata 裁剪。
971
+ * 法币仅做字段/metadata 裁剪;虚拟币在出站前再次执行零加价与固定单价断言。
968
972
  */
969
- function normalizeSubmitProduct(product) {
973
+ function normalizeSubmitProduct(product, formatOptions = {}) {
970
974
  const {
971
975
  unique_identification_number: _runtimeUid,
972
976
  ...submitProduct
@@ -994,7 +998,7 @@ function normalizeSubmitProduct(product) {
994
998
  if (rawMetadata.unique_identification_number) {
995
999
  cleanMetadata.unique_identification_number = rawMetadata.unique_identification_number;
996
1000
  }
997
- const priceMetaKeys = ['main_product_original_price', 'main_product_selling_price', 'main_product_attached_bundle_selling_price', 'main_product_attached_bundle_payment_price', 'average_discount_amount_rate', 'source_product_price', 'main_product_attached_bundle_surcharge_fee', 'main_product_attached_bundle_tax_fee', 'surcharge_rounding_remainder', 'tax_fee_rounding_remainder', 'relation_surcharge_ids', 'discountable_flag', 'parent_unique_identification_number', 'product_add_schedule_time'];
1001
+ const priceMetaKeys = ['main_product_original_price', 'main_product_selling_price', 'main_product_attached_bundle_selling_price', 'main_product_attached_bundle_payment_price', 'average_discount_amount_rate', 'source_product_price', 'main_product_attached_bundle_surcharge_fee', 'main_product_attached_bundle_tax_fee', 'surcharge_rounding_remainder', 'tax_fee_rounding_remainder', 'relation_surcharge_ids', 'discountable_flag', 'price_schema_version', 'parent_unique_identification_number', 'product_add_schedule_time', 'data_variant_ids'];
998
1002
  for (const key of priceMetaKeys) {
999
1003
  if (rawMetadata[key] !== undefined) {
1000
1004
  cleanMetadata[key] = rawMetadata[key];
@@ -1010,12 +1014,36 @@ function normalizeSubmitProduct(product) {
1010
1014
  if (rawMetadata.holder_config !== undefined) {
1011
1015
  cleanMetadata.holder_config = rawMetadata.holder_config;
1012
1016
  }
1017
+ const virtualPrice = toMoneyString(rawMetadata.selected_settlement_snapshot?.price ?? submitProduct.selling_price);
1018
+ if (formatOptions.virtualSettlementMode) {
1019
+ Object.assign(cleanMetadata, {
1020
+ source_product_price: virtualPrice,
1021
+ main_product_original_price: virtualPrice,
1022
+ main_product_selling_price: virtualPrice,
1023
+ main_product_attached_bundle_selling_price: virtualPrice,
1024
+ main_product_attached_bundle_payment_price: virtualPrice,
1025
+ main_product_attached_bundle_surcharge_fee: '0.00',
1026
+ main_product_attached_bundle_tax_fee: '0.00',
1027
+ surcharge_rounding_remainder: '0.00',
1028
+ tax_fee_rounding_remainder: '0.00',
1029
+ relation_surcharge_ids: [],
1030
+ average_discount_amount_rate: '1',
1031
+ product_discount_difference: '',
1032
+ discountable_flag: 0,
1033
+ price_schema_version: 2
1034
+ });
1035
+ }
1013
1036
  const productSku = {
1014
1037
  ...(submitProduct.product_sku && typeof submitProduct.product_sku === 'object' ? submitProduct.product_sku : {}),
1015
- option: formatSubmitOptionItems(submitProduct.product_sku?.option)
1038
+ option: formatSubmitOptionItems(submitProduct.product_sku?.option, formatOptions)
1016
1039
  };
1017
1040
  return {
1018
1041
  ...productRest,
1042
+ ...(formatOptions.virtualSettlementMode ? {
1043
+ selling_price: virtualPrice,
1044
+ original_price: virtualPrice,
1045
+ is_charge_tax: 0
1046
+ } : {}),
1019
1047
  ...(productionCode !== undefined ? {
1020
1048
  production_code: productionCode
1021
1049
  } : {}),
@@ -1027,12 +1055,12 @@ function normalizeSubmitProduct(product) {
1027
1055
  } : {}),
1028
1056
  product_quantity: toBundleNumber(num ?? submitProduct.product_quantity, 1),
1029
1057
  product_sku: productSku,
1030
- discount_list: collectSubmitProductDiscountList(submitProduct),
1031
- product_bundle: formatSubmitBundleItems(submitProduct.product_bundle),
1058
+ discount_list: formatOptions.virtualSettlementMode ? [] : collectSubmitProductDiscountList(submitProduct),
1059
+ product_bundle: formatSubmitBundleItems(submitProduct.product_bundle, formatOptions),
1032
1060
  metadata: cleanMetadata,
1033
1061
  // 出站兼容:后端消费 payment_price 字段(行 composite 已减整单折扣均摊)。
1034
1062
  // 由整单折扣均摊层产出;缺省(无折扣)时与 selling_price 同义。
1035
- payment_price: submitProduct.payment_price ?? submitProduct.selling_price
1063
+ payment_price: formatOptions.virtualSettlementMode ? virtualPrice : submitProduct.payment_price ?? submitProduct.selling_price
1036
1064
  };
1037
1065
  }
1038
1066
  const SUBMIT_BOOKING_METADATA_WHITELIST = ['unique_identification_number', 'collect_pax', 'capacity', 'holder_id', 'resource_select_type', 'holder'];
@@ -1205,6 +1233,7 @@ function buildSubmitPayload(params) {
1205
1233
  } = params;
1206
1234
  const scheduleDate = tempOrder.schedule_date || tempOrder.created_at || formatDateTime(now);
1207
1235
  const bookingUuid = (0, _orderCollectionIdentity.createUuidV4)();
1236
+ const virtualSettlementMode = tempOrder.metadata?.currency_settlement_config?.settlement_type === 'virtual_currency';
1208
1237
  const {
1209
1238
  _extend: _extend,
1210
1239
  relation_id: _relationId,
@@ -1224,6 +1253,7 @@ function buildSubmitPayload(params) {
1224
1253
  total_refund_amount: _totalRefundAmount,
1225
1254
  create_date: _createDate,
1226
1255
  holder_id: _holderId,
1256
+ payment_status: _paymentStatus,
1227
1257
  notes: _notes,
1228
1258
  ...tempOrderRest
1229
1259
  } = tempOrder;
@@ -1253,7 +1283,9 @@ function buildSubmitPayload(params) {
1253
1283
  sales_channel: tempOrder.sales_channel || 'my_pisel',
1254
1284
  order_sales_channel: channel ?? tempOrder.order_sales_channel ?? 'online_store',
1255
1285
  status: tempOrder.status || 'normal',
1256
- payment_status: tempOrder.payment_status || 'payment_processing',
1286
+ ...(params.includePaymentStatus !== false ? {
1287
+ payment_status: tempOrder.payment_status || 'payment_processing'
1288
+ } : {}),
1257
1289
  // shipping_status: tempOrder.shipping_status || 'unfulfilled',
1258
1290
  is_price_include_tax: tempOrder.is_price_include_tax ?? 1,
1259
1291
  currency_format: tempOrder.currency_format || 'symbol_first',
@@ -1276,6 +1308,9 @@ function buildSubmitPayload(params) {
1276
1308
  const {
1277
1309
  collect_pax: _collectPax,
1278
1310
  table_occupancy_duration: _tableOccupancyDuration,
1311
+ cart_settlement_key: _cartSettlementKey,
1312
+ cart_settlement_snapshot: _cartSettlementSnapshot,
1313
+ legal_currency_snapshot: _legalCurrencySnapshot,
1279
1314
  ...rest
1280
1315
  } = tempOrder.metadata || {};
1281
1316
  return {
@@ -1287,7 +1322,9 @@ function buildSubmitPayload(params) {
1287
1322
  allowUnresolvedOrderTarget: true
1288
1323
  })
1289
1324
  } : {}),
1290
- products: (tempOrder.products || []).map(product => normalizeSubmitProduct(product))
1325
+ products: (tempOrder.products || []).map(product => normalizeSubmitProduct(product, {
1326
+ virtualSettlementMode
1327
+ }))
1291
1328
  };
1292
1329
  if (!enhance) return payload;
1293
1330
  const enhancedPayload = enhance(payload, {
@@ -23,12 +23,25 @@ export interface ProductDataVariant {
23
23
  module_data_id: number;
24
24
  /** 关联 data_variant_rule.id */
25
25
  data_variant_rule_id: number;
26
+ /** 多货币结算等业务类型 */
27
+ business_type?: 'multi_currency_settlement' | string;
28
+ /** 数据状态;多货币结算仅处理 enable 记录 */
29
+ status?: 'enable' | 'disable' | string;
26
30
  /** 命中策略后覆盖到商品上的字段 */
27
31
  data: Record<string, any>;
28
32
  created_at: string | null;
29
33
  updated_at: string | null;
30
34
  deleted_at: string | null;
31
35
  }
36
+ export interface ProductSettlementOption extends Record<string, any> {
37
+ settlement_type: 'legal_currency' | 'virtual_currency' | string;
38
+ currency_product_id?: number;
39
+ price: string;
40
+ data_variant_id?: number;
41
+ data_variant_rule_id: number | string;
42
+ available: boolean;
43
+ disabled_reason?: string;
44
+ }
32
45
  /**
33
46
  * 商品基本信息接口
34
47
  */
@@ -61,6 +74,8 @@ export interface ProductData {
61
74
  sum_stock: number;
62
75
  /** 商品价格 */
63
76
  price: string;
77
+ /** 当前上下文解析出的可选结算货币 */
78
+ available_settlement_options?: ProductSettlementOption[];
64
79
  /** 商品原价 */
65
80
  original_price: string;
66
81
  /** 商品成本价 */
@@ -239,6 +254,14 @@ export interface ProductData {
239
254
  option_group_count: number;
240
255
  /** 智能定价变体列表,/product/query with dataVariants 返回 */
241
256
  data_variants?: ProductDataVariant[];
257
+ /**
258
+ * 商品接口规则计算结果;data_variant_ids 按优先级从高到低排列。
259
+ * 空数组表示规则已经计算完成但没有命中 Data Variant。
260
+ */
261
+ metadata?: {
262
+ data_variant_ids?: Array<number | string>;
263
+ [key: string]: any;
264
+ };
242
265
  /** 服务时长 */
243
266
  service_times?: any;
244
267
  }
@@ -14,6 +14,11 @@ export declare class RulesModule extends BaseModule implements Module, RulesModu
14
14
  setRulesList(rulesList: Rules[]): Promise<void>;
15
15
  getRulesList(): Rules[];
16
16
  getWalletPassEvaluator(): import("../..").WalletPassEvaluator | undefined;
17
+ private normalizeHolderRecordIds;
18
+ /**
19
+ * 特定 form Holder 卡只授权给绑定同一 Holder Record 的商品行。
20
+ * required/isNeedHolder 仅控制是否强制选择 Holder,不参与优惠资格。
21
+ */
17
22
  private checkHolderMatch;
18
23
  isDiscountListAvailable({ oldDiscountList, newDiscountList, productList, orderTotalAmount, holders, isFormSubject, }: {
19
24
  oldDiscountList: Discount[];
@@ -38,21 +38,30 @@ class RulesModule extends _BaseModule.BaseModule {
38
38
  getWalletPassEvaluator() {
39
39
  return this.window.getWalletPassEvaluator?.();
40
40
  }
41
+ normalizeHolderRecordIds(value) {
42
+ const records = Array.isArray(value) ? value : [value];
43
+ return Array.from(new Set(records.map(record => {
44
+ if (record && typeof record === 'object') {
45
+ return record.form_record_id;
46
+ }
47
+ return record;
48
+ }).filter(record => record !== undefined && record !== null && record !== '' && record !== 0 && record !== '0').map(String)));
49
+ }
41
50
 
42
- // 商品不需要holder,则不需要判断,直接返回true,商品需要holder但是还没填写,那么暂时不使用带有holder的券,直到填写才去匹配
51
+ /**
52
+ * 特定 form Holder 卡只授权给绑定同一 Holder Record 的商品行。
53
+ * required/isNeedHolder 仅控制是否强制选择 Holder,不参与优惠资格。
54
+ */
43
55
  checkHolderMatch(discount, product, holders) {
44
56
  // 非表单类型 holder 视为匹配
45
57
  if (discount.holder?.holder_type !== 'form') return true;
46
- // 主预约holder, 目前(20251124)默认只考虑单个holder的情况
47
- // 旧版 holders[0].form_record_id 新版 holders[0]
48
- const orderHolderId = Array.isArray(holders) && holders.length > 0 ? holders[0].form_record_id || holders[0] : undefined;
49
- const productHolderId = Array.isArray(product.holder_id) ? product.holder_id[0] : product.holder_id;
50
- // 商品不需要选holder,则不对比holder,直接返回true,
51
- if (!product.isNeedHolder) return true;
52
- // 商品需要holder,但父预约及商品都无holder,返回false
53
- if (!orderHolderId && !productHolderId) return false;
54
- // 最终直接匹配 holder 是否相同
55
- return (productHolderId || orderHolderId) === discount.holder.holder_id;
58
+ const discountHolderIds = this.normalizeHolderRecordIds(discount.holder.holder_id);
59
+ if (!discountHolderIds.length) return false;
60
+ const productHolderIds = this.normalizeHolderRecordIds(product.holder_id);
61
+ const candidateHolderIds = productHolderIds.length ? productHolderIds : this.normalizeHolderRecordIds(holders);
62
+ if (!candidateHolderIds.length) return false;
63
+ const discountHolderIdSet = new Set(discountHolderIds);
64
+ return candidateHolderIds.some(holderId => discountHolderIdSet.has(holderId));
56
65
  }
57
66
 
58
67
  // 判断discountList 是否可以对当前productList生效
@@ -30,6 +30,7 @@ export declare class SalesSummaryModule extends BaseModule implements Module, Sa
30
30
  taxRate?: number;
31
31
  shopDiscount?: string | number;
32
32
  channel?: string;
33
+ virtualSettlementMode?: boolean;
33
34
  }): Promise<{
34
35
  tax_title: string;
35
36
  tax_rate: number | undefined;
@@ -126,7 +126,8 @@ class SalesSummaryModule extends _BaseModule.BaseModule {
126
126
  isPriceIncludeTax,
127
127
  taxRate,
128
128
  shopDiscount,
129
- channel
129
+ channel,
130
+ virtualSettlementMode = false
130
131
  } = params;
131
132
  const summaryChannel = channel || this.resolveChannel();
132
133
  if (summaryChannel !== this.surchargeListChannel) {
@@ -151,7 +152,8 @@ class SalesSummaryModule extends _BaseModule.BaseModule {
151
152
  surchargeList: this.store.surchargeList,
152
153
  scheduleById,
153
154
  isInScheduleByDate: _Schedule.ScheduleModule.isInScheduleByDate,
154
- shopDiscount
155
+ shopDiscount,
156
+ virtualSettlementMode
155
157
  });
156
158
  const summaryWithTaxMeta = {
157
159
  ...summary,
@@ -64,5 +64,6 @@ export interface SalesSummaryModuleAPI {
64
64
  taxRate?: number;
65
65
  shopDiscount?: string | number;
66
66
  channel?: string;
67
+ virtualSettlementMode?: boolean;
67
68
  }): Promise<SalesSummaryData>;
68
69
  }
@@ -8,4 +8,5 @@ export declare function calculateSalesSummary(params: {
8
8
  scheduleById?: Record<string, any>;
9
9
  isInScheduleByDate?: any;
10
10
  shopDiscount?: string | number;
11
+ virtualSettlementMode?: boolean;
11
12
  }): SalesSummaryData;
@@ -837,11 +837,27 @@ function calculateSalesSummary(params) {
837
837
  surchargeList,
838
838
  scheduleById = {},
839
839
  isInScheduleByDate,
840
- shopDiscount
840
+ shopDiscount,
841
+ virtualSettlementMode = false
841
842
  } = params;
842
843
  if (!products?.length) return createEmptySalesSummary();
843
844
  const summaryProducts = products.filter(product => !isGeneratedVoucherProduct(product));
844
845
  if (!summaryProducts.length) return createEmptySalesSummary();
846
+ if (virtualSettlementMode) {
847
+ const productQuantity = summaryProducts.reduce((sum, item) => sum + getSafeNum(item.num), 0);
848
+ const productAmount = summaryProducts.reduce((sum, item) => sum.plus(toDecimal(item.selling_price).times(getSafeNum(item.num))), new _decimal.default(0));
849
+ const amount = toFixed2(productAmount);
850
+ return {
851
+ ...createEmptySalesSummary(),
852
+ product_quantity: productQuantity,
853
+ product_original_amount: amount,
854
+ product_amount: amount,
855
+ product_expect_amount: amount,
856
+ expect_amount: amount,
857
+ total_amount: amount,
858
+ is_price_include_tax: isPriceIncludeTax
859
+ };
860
+ }
845
861
  const productQuantity = summaryProducts.reduce((sum, item) => sum + getSafeNum(item.num), 0);
846
862
  const productOriginalAmount = summaryProducts.reduce((sum, item) => sum.plus(getUnitOriginalTotal(item).times(getSafeNum(item.num))), new _decimal.default(0));
847
863
  const productAmount = summaryProducts.reduce((sum, item) => sum.plus(getUnitPaymentTotal(item).times(getSafeNum(item.num))), new _decimal.default(0));