@pisell/pisellos 2.3.101 → 2.3.103

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/BookingContext/utils/buildCacheItemFromOrderLine.js +10 -7
  6. package/dist/modules/Discount/index.d.ts +2 -0
  7. package/dist/modules/Discount/index.js +38 -5
  8. package/dist/modules/Discount/types.d.ts +11 -0
  9. package/dist/modules/Order/index.d.ts +29 -3
  10. package/dist/modules/Order/index.js +1105 -703
  11. package/dist/modules/Order/types.d.ts +14 -6
  12. package/dist/modules/Order/types.js +1 -1
  13. package/dist/modules/Order/utils/virtualSettlement.d.ts +63 -0
  14. package/dist/modules/Order/utils/virtualSettlement.js +284 -0
  15. package/dist/modules/Order/utils.d.ts +1 -0
  16. package/dist/modules/Order/utils.js +63 -24
  17. package/dist/modules/Product/types.d.ts +23 -0
  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 +1074 -746
  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/BookingContext/utils/buildCacheItemFromOrderLine.js +4 -0
  33. package/lib/modules/Discount/index.d.ts +2 -0
  34. package/lib/modules/Discount/index.js +31 -2
  35. package/lib/modules/Discount/types.d.ts +11 -0
  36. package/lib/modules/Order/index.d.ts +29 -3
  37. package/lib/modules/Order/index.js +326 -51
  38. package/lib/modules/Order/types.d.ts +14 -6
  39. package/lib/modules/Order/types.js +1 -1
  40. package/lib/modules/Order/utils/virtualSettlement.d.ts +63 -0
  41. package/lib/modules/Order/utils/virtualSettlement.js +262 -0
  42. package/lib/modules/Order/utils.d.ts +1 -0
  43. package/lib/modules/Order/utils.js +57 -20
  44. package/lib/modules/Product/types.d.ts +23 -0
  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 +228 -20
  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
@@ -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
  }
@@ -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));
@@ -4,7 +4,7 @@ import { BaseSalesOrderProduct, BaseSalesOrderProductIdentity, BaseSalesPaymentS
4
4
  import { OrderModule } from '../../modules/Order';
5
5
  import type { AddProductBookingInput, AddProductToOrderOptions, LoadSalesDetailParams, OrderPaymentData, OrderPaymentUpdateOptions, OrderPaymentUpdateResult, OrderPaymentSource, SendCustomerPayLinkParams, SyncPaymentsToOrderParams, SyncPaymentsToOrderResult, UpdateTempOrderCustomerInput, UpdateOrderBookingParams, UpdateOrderProductParams, OrderTempOrder, OrderSummary, OrderPromotionEvaluator, OrderGiftSelectResolver, OrderUnfulfilledPromotion, OrderLastGiftActions, RemoveProductsFromOrderOptions, ReplaceTempOrderOptions, SalesNote, SalesNotesMutationSnapshot, SetSalesNoteInput, PickupReferenceMode, ShopServiceData, ShopServiceType } from '../../modules/Order/types';
6
6
  import type { SubmitPayloadEnhancer } from '../../modules/Order/utils';
7
- import type { Discount } from '../../modules/Discount/types';
7
+ import type { Discount, VirtualCurrencyAsset } from '../../modules/Discount/types';
8
8
  import { RequestPlugin, WindowPlugin } from '../../plugins';
9
9
  export * from './types';
10
10
  import { ProductList } from '../../modules/ProductList';
@@ -193,6 +193,9 @@ export declare class BaseSalesImpl extends BaseModule implements Module {
193
193
  protected setPaymentNumberDevicePrefix(prefix: unknown): void;
194
194
  private getPaymentNumberDevicePrefixSync;
195
195
  private getPaymentNumberDevicePrefixAsync;
196
+ private isMultiCurrencySettlementEnabled;
197
+ private isActiveVirtualSettlementOrder;
198
+ private isHistoricalVirtualSettlementOrder;
196
199
  private syncAppDataToTempOrder;
197
200
  initialize(core: PisellCore, options?: ModuleOptions): Promise<void>;
198
201
  destroy(): Promise<void>;
@@ -253,6 +256,8 @@ export declare class BaseSalesImpl extends BaseModule implements Module {
253
256
  * 同步清空内存中的 salesDetail 商品/预约视图,避免编辑态仍读到旧 bookings。
254
257
  */
255
258
  clearOrderCartLines(): Promise<OrderTempOrder>;
259
+ /** 切换法币 / WalletPass 单位时清空商品、折扣与支付状态,保留客户。 */
260
+ clearOrderForSettlementSwitch(): Promise<OrderTempOrder>;
256
261
  getOrderProducts(): import("../../modules/Order/types").OrderProduct[];
257
262
  getSummary(): Promise<OrderSummary>;
258
263
  private getHistoricalProductDiscountList;
@@ -263,12 +268,15 @@ export declare class BaseSalesImpl extends BaseModule implements Module {
263
268
  loadDiscountConfig(params?: {
264
269
  customerId?: number;
265
270
  action?: 'create' | 'edit';
271
+ force?: boolean;
266
272
  }): Promise<{
267
273
  productList: Record<string, any>[];
268
274
  discountList: Discount[];
269
275
  availableWalletIds: number[];
276
+ virtualCurrencyList: VirtualCurrencyAsset[];
270
277
  }>;
271
278
  getAvailableWalletIds(): number[];
279
+ getVirtualCurrencyList(): VirtualCurrencyAsset[];
272
280
  bestDiscount(cb?: (result: any) => void): Promise<{
273
281
  productList: Record<string, any>[];
274
282
  discountList: Discount[];
@@ -300,6 +308,7 @@ export declare class BaseSalesImpl extends BaseModule implements Module {
300
308
  confirmPendingVoucherPayments?: boolean;
301
309
  enhancePayload?: SubmitPayloadEnhancer;
302
310
  }): Promise<T>;
311
+ private buildVirtualSettlementSubmitData;
303
312
  /**
304
313
  * 提交当前 Sales 订单。
305
314
  *
@@ -25,6 +25,7 @@ var _decimal = _interopRequireDefault(require("decimal.js"));
25
25
  var _lodashEs = require("lodash-es");
26
26
  var _types2 = require("../BookingByStep/types");
27
27
  var _utils = require("../../modules/Order/utils");
28
+ var _virtualSettlement = require("../../modules/Order/utils/virtualSettlement");
28
29
  var _paymentUtils = require("../../modules/Order/payment-utils");
29
30
  var _paymentNumber = require("../../utils/payment-number");
30
31
  var _orderCollectionIdentity = require("../../modules/Order/utils/orderCollectionIdentity");
@@ -1253,11 +1254,24 @@ class BaseSalesImpl extends _BaseModule.BaseModule {
1253
1254
  }
1254
1255
  return (0, _paymentNumber.resolvePaymentNumberDevicePrefix)(key => this.getAppData(key));
1255
1256
  }
1257
+ isMultiCurrencySettlementEnabled() {
1258
+ return this.otherParams?.enableMultiCurrencySettlement === true;
1259
+ }
1260
+ isActiveVirtualSettlementOrder(tempOrder) {
1261
+ return this.isMultiCurrencySettlementEnabled() && (0, _virtualSettlement.isVirtualSettlementOrder)(tempOrder);
1262
+ }
1263
+ isHistoricalVirtualSettlementOrder(tempOrder) {
1264
+ return tempOrder?.metadata?.currency_settlement_config?.settlement_type === 'virtual_currency';
1265
+ }
1256
1266
  syncAppDataToTempOrder(tempOrder) {
1257
1267
  const isPriceIncludeTax = this.getAppData('is_price_include_tax');
1258
1268
  const taxCountryCode = this.getAppData('tax_country_code');
1259
1269
  const currencyCode = this.getAppData('shop_currency_code');
1260
1270
  const currencySymbol = this.getAppData('shop_symbol');
1271
+ // 历史虚拟币订单不依赖交易开关;普通 POS 不从商品行残留快照推断订单币种。
1272
+ const isHistoricalVirtualSettlement = this.isHistoricalVirtualSettlementOrder(tempOrder);
1273
+ const isVirtualSettlement = this.isActiveVirtualSettlementOrder(tempOrder) || isHistoricalVirtualSettlement;
1274
+ const shouldManageLegalCurrencySnapshot = this.isMultiCurrencySettlementEnabled() || isHistoricalVirtualSettlement;
1261
1275
  let isChanged = false;
1262
1276
  if (isPriceIncludeTax !== undefined) {
1263
1277
  const nextIsPriceIncludeTax = Number(isPriceIncludeTax);
@@ -1273,14 +1287,26 @@ class BaseSalesImpl extends _BaseModule.BaseModule {
1273
1287
  isChanged = true;
1274
1288
  }
1275
1289
  }
1276
- if (currencyCode !== undefined) {
1290
+ tempOrder.metadata = tempOrder.metadata || {};
1291
+ if (shouldManageLegalCurrencySnapshot) {
1292
+ const legalCurrencySnapshot = {
1293
+ currency_code: String(currencyCode ?? tempOrder.metadata.legal_currency_snapshot?.currency_code ?? tempOrder.currency_code ?? ''),
1294
+ currency_symbol: String(currencySymbol ?? tempOrder.metadata.legal_currency_snapshot?.currency_symbol ?? tempOrder.currency_symbol ?? ''),
1295
+ currency_format: String(tempOrder.metadata.legal_currency_snapshot?.currency_format || (isVirtualSettlement ? 'symbol_first' : tempOrder.currency_format) || 'symbol_first')
1296
+ };
1297
+ if (JSON.stringify(tempOrder.metadata.legal_currency_snapshot || {}) !== JSON.stringify(legalCurrencySnapshot)) {
1298
+ tempOrder.metadata.legal_currency_snapshot = legalCurrencySnapshot;
1299
+ isChanged = true;
1300
+ }
1301
+ }
1302
+ if (!isVirtualSettlement && currencyCode !== undefined) {
1277
1303
  const nextCurrencyCode = String(currencyCode || '');
1278
1304
  if (tempOrder.currency_code !== nextCurrencyCode) {
1279
1305
  tempOrder.currency_code = nextCurrencyCode;
1280
1306
  isChanged = true;
1281
1307
  }
1282
1308
  }
1283
- if (currencySymbol !== undefined) {
1309
+ if (!isVirtualSettlement && currencySymbol !== undefined) {
1284
1310
  const nextCurrencySymbol = String(currencySymbol || '');
1285
1311
  if (tempOrder.currency_symbol !== nextCurrencySymbol) {
1286
1312
  tempOrder.currency_symbol = nextCurrencySymbol;
@@ -1654,6 +1680,24 @@ class BaseSalesImpl extends _BaseModule.BaseModule {
1654
1680
  throw error;
1655
1681
  }
1656
1682
  }
1683
+
1684
+ /** 切换法币 / WalletPass 单位时清空商品、折扣与支付状态,保留客户。 */
1685
+ async clearOrderForSettlementSwitch() {
1686
+ if (!this.store.order) throw new Error('order 模块未初始化');
1687
+ const tempOrder = await this.store.order.clearOrderForSettlementSwitch();
1688
+ if (this.currentSalesDetail) {
1689
+ this.currentSalesDetail = {
1690
+ ...this.currentSalesDetail,
1691
+ products: [],
1692
+ bookings: [],
1693
+ rootBooking: null,
1694
+ bookingsByProductUid: {},
1695
+ discount_list: [],
1696
+ payments: []
1697
+ };
1698
+ }
1699
+ return tempOrder;
1700
+ }
1657
1701
  getOrderProducts() {
1658
1702
  if (!this.store.order) throw new Error('order 模块未初始化');
1659
1703
  const products = this.store.order.getOrderProducts();
@@ -1779,9 +1823,14 @@ class BaseSalesImpl extends _BaseModule.BaseModule {
1779
1823
  const hasManualDiscountSelection = this.hasManualDiscountSelection || currentDiscountList.some(discount => discount.isManualSelect);
1780
1824
  const discountAction = params?.action || (orderId ? 'edit' : 'create');
1781
1825
  const discountConfigCacheKey = [customerId, discountAction, Number(orderId) || 0].join(':');
1782
- let preparedDiscountList = [];
1826
+
1827
+ // Walk-In 不请求 prepare config;当前搜索/扫码券必须继续参与本次重算。
1828
+ // current 优先于 original,保留搜索后的最新选择与可用状态。
1829
+ const searchedDiscountList = currentDiscountList.filter(discount => discount.isScan);
1830
+ const searchedDiscountIds = new Set(searchedDiscountList.map(discount => String(discount.id)));
1831
+ let preparedDiscountList = [...searchedDiscountList, ...originalDiscountList.filter(discount => !searchedDiscountIds.has(String(discount.id)))];
1783
1832
  if (customerId && customerId !== 1) {
1784
- if (this.discountConfigCacheKey === discountConfigCacheKey) {
1833
+ if (!params?.force && this.discountConfigCacheKey === discountConfigCacheKey) {
1785
1834
  preparedDiscountList = originalDiscountList;
1786
1835
  } else {
1787
1836
  preparedDiscountList = await this.store.order.loadDiscountConfig({
@@ -1794,11 +1843,24 @@ class BaseSalesImpl extends _BaseModule.BaseModule {
1794
1843
  await discountModule?.setOriginalDiscountList?.(preparedDiscountList);
1795
1844
  }
1796
1845
  }
1846
+ if (this.isActiveVirtualSettlementOrder(tempOrder)) {
1847
+ await discountModule?.setOriginalDiscountList?.([]);
1848
+ await discountModule?.setDiscountList?.([]);
1849
+ tempOrder.discount_list = [];
1850
+ tempOrder.shop_discount = '0.00';
1851
+ return {
1852
+ productList: tempOrder.products || [],
1853
+ discountList: [],
1854
+ availableWalletIds: this.getAvailableWalletIds(),
1855
+ virtualCurrencyList: this.store.order.getVirtualCurrencyList()
1856
+ };
1857
+ }
1797
1858
  if (hasManualDiscountSelection && currentDiscountList.length) {
1798
1859
  return {
1799
1860
  productList: tempOrder.products || [],
1800
1861
  discountList: currentDiscountList,
1801
- availableWalletIds: this.getAvailableWalletIds()
1862
+ availableWalletIds: this.getAvailableWalletIds(),
1863
+ virtualCurrencyList: this.store.order.getVirtualCurrencyList()
1802
1864
  };
1803
1865
  }
1804
1866
  let nextDiscountList = this.mergeHistoricalDiscountList(preparedDiscountList || discountModule?.getDiscountList?.() || [], tempOrder.products || []);
@@ -1824,7 +1886,8 @@ class BaseSalesImpl extends _BaseModule.BaseModule {
1824
1886
  return {
1825
1887
  productList: tempOrder.products || [],
1826
1888
  discountList: nextDiscountList,
1827
- availableWalletIds: this.getAvailableWalletIds()
1889
+ availableWalletIds: this.getAvailableWalletIds(),
1890
+ virtualCurrencyList: this.store.order.getVirtualCurrencyList()
1828
1891
  };
1829
1892
  }
1830
1893
  if (rulesModule) {
@@ -1880,16 +1943,29 @@ class BaseSalesImpl extends _BaseModule.BaseModule {
1880
1943
  return {
1881
1944
  productList: tempOrder.products || [],
1882
1945
  discountList: nextDiscountList,
1883
- availableWalletIds: this.getAvailableWalletIds()
1946
+ availableWalletIds: this.getAvailableWalletIds(),
1947
+ virtualCurrencyList: this.store.order.getVirtualCurrencyList()
1884
1948
  };
1885
1949
  }
1886
1950
  getAvailableWalletIds() {
1887
1951
  if (!this.store.order?.getAvailableWalletIds) return [];
1888
1952
  return this.store.order.getAvailableWalletIds();
1889
1953
  }
1954
+ getVirtualCurrencyList() {
1955
+ if (!this.store.order?.getVirtualCurrencyList) return [];
1956
+ return this.store.order.getVirtualCurrencyList();
1957
+ }
1890
1958
  async bestDiscount(cb) {
1891
1959
  if (!this.store.order) throw new Error('order 模块未初始化');
1892
1960
  const tempOrder = this.store.order.ensureTempOrder();
1961
+ if (this.isActiveVirtualSettlementOrder(tempOrder)) {
1962
+ const result = {
1963
+ productList: tempOrder.products || [],
1964
+ discountList: []
1965
+ };
1966
+ cb?.(result);
1967
+ return result;
1968
+ }
1893
1969
  const orderStore = this.store.order.store || {};
1894
1970
  const discountModule = orderStore.discount;
1895
1971
  const rulesModule = orderStore.rules;
@@ -1997,6 +2073,17 @@ class BaseSalesImpl extends _BaseModule.BaseModule {
1997
2073
  async setDiscountSelected(params) {
1998
2074
  try {
1999
2075
  if (!this.store.order) throw new Error('order 模块未初始化');
2076
+ const currentOrder = this.store.order.ensureTempOrder();
2077
+ if (this.isActiveVirtualSettlementOrder(currentOrder)) {
2078
+ currentOrder.discount_list = [];
2079
+ currentOrder.shop_discount = '0.00';
2080
+ await this.store.order.store?.discount?.setDiscountList?.([]);
2081
+ await this.store.order.recalculateSummary({
2082
+ createIfMissing: true
2083
+ });
2084
+ this.store.order.persistTempOrder();
2085
+ return;
2086
+ }
2000
2087
  const list = this.store.order.getDiscountList();
2001
2088
  const updated = list.map(d => d.id === params.discountId && !isBaseSalesHistoricalDiscountEntry(d) ? {
2002
2089
  ...d,
@@ -2135,6 +2222,101 @@ class BaseSalesImpl extends _BaseModule.BaseModule {
2135
2222
  // Deprecated compatibility alias. New BaseSales callers should use submitSalesOrder.
2136
2223
  return this.submitSalesOrder(params);
2137
2224
  }
2225
+ async buildVirtualSettlementSubmitData() {
2226
+ if (!this.store.order) return null;
2227
+ const tempOrder = this.store.order.getTempOrder();
2228
+ if (!this.isActiveVirtualSettlementOrder(tempOrder)) return null;
2229
+ const snapshot = (0, _virtualSettlement.getTempOrderSettlementSnapshot)(tempOrder);
2230
+ const currencyProductId = Number(tempOrder?.metadata?.currency_settlement_config?.settlement_ids?.[0] ?? snapshot?.currency_product_id);
2231
+ if (!snapshot || !Number.isFinite(currencyProductId) || currencyProductId <= 0) {
2232
+ throw new Error('Virtual currency settlement configuration is invalid.');
2233
+ }
2234
+ const summary = await this.store.order.recalculateSummary({
2235
+ createIfMissing: true
2236
+ });
2237
+ const amount = new _decimal.default(summary?.total_amount || 0).toDecimalPlaces(2);
2238
+ const customerId = Number(tempOrder?.customer_id || 0);
2239
+ if (amount.gt(0) && customerId > 0 && customerId !== 1) {
2240
+ await this.store.order.loadDiscountConfig({
2241
+ customerId,
2242
+ action: tempOrder?.order_id ? 'edit' : 'create',
2243
+ orderId: Number(tempOrder?.order_id) || undefined,
2244
+ apply: false
2245
+ });
2246
+ }
2247
+ const asset = this.store.order.getVirtualCurrencyList().find(item => Number(item?.product_id) === currencyProductId);
2248
+ const currencyCode = String(tempOrder?.currency_code || '');
2249
+ const currencySymbol = String(tempOrder?.currency_symbol || '');
2250
+ const currencyFormat = String(tempOrder?.currency_format || 'symbol_first');
2251
+ if (amount.lte(0)) {
2252
+ return {
2253
+ payments: [],
2254
+ snapshot,
2255
+ currencyProductId,
2256
+ currencyCode,
2257
+ currencySymbol,
2258
+ currencyFormat
2259
+ };
2260
+ }
2261
+ if (!asset) {
2262
+ throw new Error('No matching virtual currency card is available.');
2263
+ }
2264
+ const balance = new _decimal.default(asset.balance || 0);
2265
+ if (balance.lt(amount)) {
2266
+ throw new Error('Insufficient virtual currency balance.');
2267
+ }
2268
+ const amountText = amount.toFixed(2);
2269
+ const paymentNumber = `LOCAL_${(0, _utils.createUuidV4)()}`;
2270
+ let payment;
2271
+ if (this.store.order.isOnlineStorePlatform()) {
2272
+ payment = {
2273
+ payment_number: paymentNumber,
2274
+ amount: amount.toNumber(),
2275
+ metadata: {
2276
+ unique_payment_number: (0, _utils.createUuidV4)()
2277
+ },
2278
+ tag: asset.tag,
2279
+ voucher_id: asset.id,
2280
+ wallet_pass_usage_unit: {
2281
+ price: 1,
2282
+ points: 1
2283
+ },
2284
+ wallet_pass_use_value: amountText
2285
+ };
2286
+ } else {
2287
+ const paymentMethods = await this.getPaymentMethodsAsync();
2288
+ const paymentMethod = paymentMethods.find(method => String(method.code || '').toUpperCase() === 'PRODUCTVOUCHER');
2289
+ if (!paymentMethod) {
2290
+ throw new Error('PRODUCTVOUCHER payment method is unavailable.');
2291
+ }
2292
+ payment = {
2293
+ payment_number: paymentNumber,
2294
+ amount: amountText,
2295
+ origin_amount: amountText,
2296
+ code: paymentMethod.code,
2297
+ custom_payment_id: paymentMethod.id,
2298
+ custom_payment_type: paymentMethod.custom_payment_type ?? paymentMethod.type,
2299
+ name: paymentMethod.name,
2300
+ type: 'normal',
2301
+ voucher_id: asset.id,
2302
+ order_payment_type: 'normal',
2303
+ wallet_pass_usage_unit: {
2304
+ price: 1,
2305
+ points: 1
2306
+ },
2307
+ wallet_pass_use_value: amountText,
2308
+ metadata: {}
2309
+ };
2310
+ }
2311
+ return {
2312
+ payments: [payment],
2313
+ snapshot,
2314
+ currencyProductId,
2315
+ currencyCode,
2316
+ currencySymbol,
2317
+ currencyFormat
2318
+ };
2319
+ }
2138
2320
 
2139
2321
  /**
2140
2322
  * 提交当前 Sales 订单。
@@ -2146,6 +2328,7 @@ class BaseSalesImpl extends _BaseModule.BaseModule {
2146
2328
  if (!this.store.order) {
2147
2329
  throw new Error('BaseSales 解决方案需要 order 模块支持');
2148
2330
  }
2331
+ const virtualSettlement = await this.buildVirtualSettlementSubmitData();
2149
2332
  // this.store.order.persistTempOrder();
2150
2333
  const inferredPaymentStatus = this.getSubmitPaymentStatus(params?.paymentStatus);
2151
2334
  const smallTicketDataFlag = params?.smallTicketDataFlag ?? this.getDefaultCheckoutSmallTicketDataFlag();
@@ -2157,7 +2340,9 @@ class BaseSalesImpl extends _BaseModule.BaseModule {
2157
2340
  channel: this.getSubmitOrderSalesChannel(),
2158
2341
  type: this.otherParams?.type,
2159
2342
  request_unique_idempotency_token: this.store.order.generateIdempotencyToken(),
2160
- ...(params?.payments !== undefined ? {
2343
+ ...(virtualSettlement ? {
2344
+ payments: virtualSettlement.payments
2345
+ } : params?.payments !== undefined ? {
2161
2346
  payments: params.payments
2162
2347
  } : {}),
2163
2348
  ...(inferredPaymentStatus !== undefined ? {
@@ -2166,10 +2351,32 @@ class BaseSalesImpl extends _BaseModule.BaseModule {
2166
2351
  ...(smallTicketDataFlag !== undefined ? {
2167
2352
  smallTicketDataFlag
2168
2353
  } : {}),
2169
- ...(params?.confirmPendingVoucherPayments !== undefined ? {
2354
+ ...(virtualSettlement ? {
2355
+ confirmPendingVoucherPayments: false
2356
+ } : params?.confirmPendingVoucherPayments !== undefined ? {
2170
2357
  confirmPendingVoucherPayments: params.confirmPendingVoucherPayments
2171
2358
  } : {}),
2172
- ...(enhancePayload !== undefined ? {
2359
+ ...(virtualSettlement ? {
2360
+ enhancePayload: (payload, ctx) => {
2361
+ const callerPayload = enhancePayload ? enhancePayload(payload, ctx) : payload;
2362
+ const currencySettlementConfig = callerPayload.metadata?.currency_settlement_config || {};
2363
+ return {
2364
+ ...callerPayload,
2365
+ metadata: {
2366
+ ...(callerPayload.metadata || {}),
2367
+ currency_settlement_config: {
2368
+ ...currencySettlementConfig,
2369
+ settlement_type: 'virtual_currency',
2370
+ settlement_ids: [virtualSettlement.currencyProductId],
2371
+ currency_display_snapshot: (0, _lodashEs.cloneDeep)(currencySettlementConfig.currency_display_snapshot ?? (0, _virtualSettlement.buildCurrencyDisplaySnapshot)(virtualSettlement.snapshot))
2372
+ }
2373
+ },
2374
+ currency_code: virtualSettlement.currencyCode,
2375
+ currency_symbol: virtualSettlement.currencySymbol,
2376
+ currency_format: virtualSettlement.currencyFormat
2377
+ };
2378
+ }
2379
+ } : enhancePayload !== undefined ? {
2173
2380
  enhancePayload
2174
2381
  } : {})
2175
2382
  };
@@ -2581,7 +2788,7 @@ class BaseSalesImpl extends _BaseModule.BaseModule {
2581
2788
  if (this.store.payment) {
2582
2789
  const committed = this.getCommittedWalletAssets();
2583
2790
  const state = this.store.payment.wallet.setCommittedWalletAssets(committed.ids, committed.assets);
2584
- void this.publishWalletAssetsState(state);
2791
+ void this.publishWalletAssetsState(state, 'local');
2585
2792
  }
2586
2793
  return payments;
2587
2794
  }
@@ -2727,9 +2934,10 @@ class BaseSalesImpl extends _BaseModule.BaseModule {
2727
2934
  })
2728
2935
  };
2729
2936
  }
2730
- async publishWalletAssetsState(state) {
2937
+ async publishWalletAssetsState(state, changeSource = 'derived') {
2731
2938
  await this.effectsEmit(_types.BaseSalesHookNames.onWalletAssetsStateChanged, {
2732
- state
2939
+ state,
2940
+ changeSource
2733
2941
  });
2734
2942
  return state;
2735
2943
  }
@@ -2862,7 +3070,7 @@ class BaseSalesImpl extends _BaseModule.BaseModule {
2862
3070
  source: params.source
2863
3071
  });
2864
3072
  await this.syncSelectedWalletAssets(state);
2865
- return this.publishWalletAssetsState(state);
3073
+ return this.publishWalletAssetsState(state, 'local');
2866
3074
  }
2867
3075
 
2868
3076
  /** 更新已选钱包资产的手动使用金额,并同步 pending payment。 */
@@ -2870,7 +3078,7 @@ class BaseSalesImpl extends _BaseModule.BaseModule {
2870
3078
  if (!this.store.payment) throw new Error('payment 模块未初始化');
2871
3079
  const state = this.store.payment.wallet.updateWalletAssetAmount(params.assetId, params.amount);
2872
3080
  await this.syncSelectedWalletAssets(state);
2873
- return this.publishWalletAssetsState(state);
3081
+ return this.publishWalletAssetsState(state, 'local');
2874
3082
  }
2875
3083
 
2876
3084
  /** 扫码精确查券;仅可用资产会被自动选中。 */
@@ -2885,7 +3093,7 @@ class BaseSalesImpl extends _BaseModule.BaseModule {
2885
3093
  const result = await wallet.scanWalletAssetAsync(code);
2886
3094
  if (result.type === 'normalCode') {
2887
3095
  await this.syncSelectedWalletAssets(result.state);
2888
- await this.publishWalletAssetsState(result.state);
3096
+ await this.publishWalletAssetsState(result.state, 'local');
2889
3097
  }
2890
3098
  return result;
2891
3099
  }
@@ -2895,7 +3103,7 @@ class BaseSalesImpl extends _BaseModule.BaseModule {
2895
3103
  if (!this.store.payment) throw new Error('payment 模块未初始化');
2896
3104
  const state = this.store.payment.wallet.clearWalletAssetSelection();
2897
3105
  await this.syncSelectedWalletAssets(state);
2898
- return this.publishWalletAssetsState(state);
3106
+ return this.publishWalletAssetsState(state, 'local');
2899
3107
  }
2900
3108
 
2901
3109
  /**
@@ -3025,7 +3233,7 @@ class BaseSalesImpl extends _BaseModule.BaseModule {
3025
3233
  try {
3026
3234
  const committed = this.getCommittedWalletAssets();
3027
3235
  const walletState = this.store.payment.wallet.setCommittedWalletAssets(committed.ids, committed.assets);
3028
- await this.publishWalletAssetsState(walletState);
3236
+ await this.publishWalletAssetsState(walletState, 'local');
3029
3237
  } catch (error) {
3030
3238
  this.logWarning('confirmWalletPayment: Wallet committed 状态同步失败', {
3031
3239
  error: error instanceof Error ? error.message : String(error)
@@ -3109,7 +3317,7 @@ class BaseSalesImpl extends _BaseModule.BaseModule {
3109
3317
  if (this.store.payment) {
3110
3318
  const committed = this.getCommittedWalletAssets();
3111
3319
  const walletState = this.store.payment.wallet.setCommittedWalletAssets(committed.ids, committed.assets);
3112
- await this.publishWalletAssetsState(walletState);
3320
+ await this.publishWalletAssetsState(walletState, 'local');
3113
3321
  }
3114
3322
  return {
3115
3323
  payment: payment || {},
@@ -3215,7 +3423,7 @@ class BaseSalesImpl extends _BaseModule.BaseModule {
3215
3423
  if (this.store.payment) {
3216
3424
  const committed = this.getCommittedWalletAssets();
3217
3425
  const walletState = this.store.payment.wallet.setCommittedWalletAssets(committed.ids, committed.assets);
3218
- await this.publishWalletAssetsState(walletState);
3426
+ await this.publishWalletAssetsState(walletState, 'local');
3219
3427
  }
3220
3428
  return {
3221
3429
  payment: payment || {},
@@ -46,6 +46,7 @@ export interface BaseProductDetailPayload {
46
46
  original_price?: number | string;
47
47
  tax_fee?: number | string;
48
48
  is_charge_tax?: number;
49
+ metadata?: Record<string, any>;
49
50
  note?: string;
50
51
  [key: string]: any;
51
52
  }
@@ -37,6 +37,34 @@ function isBlankValue(value) {
37
37
  function firstNonBlank(...values) {
38
38
  return values.find(value => !isBlankValue(value));
39
39
  }
40
+
41
+ /**
42
+ * 取第一份接口权威 Data Variant 命中结果并复制数组。
43
+ * 空数组表示规则已计算但无命中,必须与字段缺失区分。
44
+ */
45
+ function cloneFirstDataVariantIds(...values) {
46
+ const ids = values.find(value => Array.isArray(value));
47
+ return Array.isArray(ids) ? [...ids] : undefined;
48
+ }
49
+
50
+ /**
51
+ * 在接口权威命中结果后追加本地应用及最终选中的 Data Variant ID。
52
+ * 以字符串身份去重,同时将新增 ID 归一为正整数。
53
+ */
54
+ function appendDataVariantIds(base, ...values) {
55
+ const result = Array.isArray(base) ? [...base] : [];
56
+ const seen = new Set(result.map(id => String(id)));
57
+ for (const value of values) {
58
+ const candidates = Array.isArray(value) ? value : [value];
59
+ for (const candidate of candidates) {
60
+ const id = Number(candidate);
61
+ if (!Number.isFinite(id) || id <= 0 || seen.has(String(id))) continue;
62
+ result.push(id);
63
+ seen.add(String(id));
64
+ }
65
+ }
66
+ return Array.isArray(base) || result.length ? result : undefined;
67
+ }
40
68
  function normalizeOptionItems(optionItems) {
41
69
  if (!Array.isArray(optionItems)) return [];
42
70
  return optionItems.map(optionItem => {
@@ -111,6 +139,10 @@ function transformBaseProductToOrderProduct(params) {
111
139
  ...(sourceProduct || {}),
112
140
  ...(callbackOrigin || {})
113
141
  };
142
+ const authoritativeDataVariantIds = cloneFirstDataVariantIds(payload.metadata?.data_variant_ids, sourceProduct?.metadata?.data_variant_ids, callbackOrigin?.metadata?.data_variant_ids, origin?.metadata?.data_variant_ids);
143
+ const selectedSettlementSnapshot = firstNonBlank(payload.selected_settlement_snapshot, payload.metadata?.selected_settlement_snapshot, payload._extend?.metadata?.selected_settlement_snapshot, sourceProduct?.metadata?.selected_settlement_snapshot, sourceProduct?._extend?.metadata?.selected_settlement_snapshot, callbackOrigin?.metadata?.selected_settlement_snapshot, callbackOrigin?._extend?.metadata?.selected_settlement_snapshot);
144
+ const locallyAppliedVariantIds = authoritativeDataVariantIds === undefined ? cloneFirstDataVariantIds(payload._dataVariant?.appliedVariantIds, sourceProduct?._dataVariant?.appliedVariantIds, callbackOrigin?._dataVariant?.appliedVariantIds, origin?._dataVariant?.appliedVariantIds) : undefined;
145
+ const dataVariantIds = appendDataVariantIds(authoritativeDataVariantIds, locallyAppliedVariantIds, selectedSettlementSnapshot?.data_variant_id);
114
146
  const matchedVariant = findVariantById(callbackOrigin, productVariantId) ?? findVariantById(sourceProduct, productVariantId) ?? findVariantById(origin, productVariantId);
115
147
  const productionCode = normalizeProductionCode(Object.prototype.hasOwnProperty.call(payload, 'production_code') ? payload.production_code : productVariantId > 0 ? matchedVariant?.production_code : origin?.production_code ?? sourceProduct?.production_code);
116
148
  const hasPriceOverride = payload.price_override !== undefined && payload.price_override !== null && payload.price_override !== '';
@@ -179,6 +211,12 @@ function transformBaseProductToOrderProduct(params) {
179
211
  main_product_original_price: mainProductOriginalPrice,
180
212
  price_schema_version: 2,
181
213
  holder_config: sourceProduct?.holder_config,
214
+ ...(dataVariantIds !== undefined ? {
215
+ data_variant_ids: dataVariantIds
216
+ } : {}),
217
+ ...(selectedSettlementSnapshot ? {
218
+ selected_settlement_snapshot: selectedSettlementSnapshot
219
+ } : {}),
182
220
  ...(hasPriceOverride ? {
183
221
  price_override: String(payload.price_override),
184
222
  is_manual_discount: 1