@pisell/pisellos 0.10.43 → 0.10.44

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 (87) hide show
  1. package/dist/model/strategy/adapter/itemRule/adapter.d.ts +4 -1
  2. package/dist/model/strategy/adapter/itemRule/adapter.js +135 -35
  3. package/dist/model/strategy/adapter/itemRule/evaluator.d.ts +8 -1
  4. package/dist/model/strategy/adapter/itemRule/evaluator.js +25 -1
  5. package/dist/model/strategy/adapter/itemRule/type.d.ts +44 -0
  6. package/dist/model/strategy/adapter/itemRule/type.js +19 -1
  7. package/dist/modules/Order/index.d.ts +41 -9
  8. package/dist/modules/Order/index.js +1438 -878
  9. package/dist/modules/Order/salesNotes.d.ts +31 -0
  10. package/dist/modules/Order/salesNotes.js +492 -0
  11. package/dist/modules/Order/types.d.ts +105 -8
  12. package/dist/modules/Order/types.js +6 -1
  13. package/dist/modules/Order/utils.d.ts +3 -1
  14. package/dist/modules/Order/utils.js +191 -71
  15. package/dist/modules/Payment/cashRecommendationAlgorithm.d.ts +4 -5
  16. package/dist/modules/Payment/cashRecommendationAlgorithm.js +17 -367
  17. package/dist/modules/ProductList/index.d.ts +2 -1
  18. package/dist/modules/ProductList/index.js +100 -20
  19. package/dist/modules/ProductList/types.d.ts +10 -0
  20. package/dist/server/index.d.ts +7 -0
  21. package/dist/server/index.js +45 -3
  22. package/dist/server/modules/order/index.js +6 -0
  23. package/dist/server/modules/order/types.d.ts +9 -0
  24. package/dist/server/utils/small-ticket.d.ts +1 -0
  25. package/dist/server/utils/small-ticket.js +142 -14
  26. package/dist/solution/BaseSales/index.d.ts +15 -1
  27. package/dist/solution/BaseSales/index.js +1240 -877
  28. package/dist/solution/BookingByStep/index.d.ts +1 -1
  29. package/dist/solution/BookingTicket/index.d.ts +1 -0
  30. package/dist/solution/BookingTicket/index.js +37 -16
  31. package/dist/solution/BookingTicket/utils/orderCustomer.js +3 -2
  32. package/dist/solution/RegisterAndLogin/config.d.ts +9 -3
  33. package/dist/solution/RegisterAndLogin/config.js +95 -40
  34. package/dist/solution/RegisterAndLogin/index.js +4 -1
  35. package/dist/solution/ScanOrder/index.d.ts +1 -0
  36. package/dist/solution/ScanOrder/index.js +31 -27
  37. package/dist/solution/ScanOrder/utils.d.ts +1 -0
  38. package/dist/solution/ScanOrder/utils.js +2 -0
  39. package/dist/solution/UnifiedBookingSales/index.d.ts +15 -2
  40. package/dist/solution/UnifiedBookingSales/index.js +777 -510
  41. package/dist/solution/UnifiedBookingSales/types.d.ts +14 -1
  42. package/dist/solution/VenueBooking/index.d.ts +1 -0
  43. package/dist/solution/VenueBooking/index.js +8 -4
  44. package/lib/model/strategy/adapter/itemRule/adapter.d.ts +4 -1
  45. package/lib/model/strategy/adapter/itemRule/adapter.js +73 -4
  46. package/lib/model/strategy/adapter/itemRule/evaluator.d.ts +8 -1
  47. package/lib/model/strategy/adapter/itemRule/evaluator.js +23 -1
  48. package/lib/model/strategy/adapter/itemRule/type.d.ts +44 -0
  49. package/lib/model/strategy/adapter/itemRule/type.js +19 -1
  50. package/lib/modules/Order/index.d.ts +41 -9
  51. package/lib/modules/Order/index.js +434 -61
  52. package/lib/modules/Order/salesNotes.d.ts +31 -0
  53. package/lib/modules/Order/salesNotes.js +382 -0
  54. package/lib/modules/Order/types.d.ts +105 -8
  55. package/lib/modules/Order/types.js +5 -1
  56. package/lib/modules/Order/utils.d.ts +3 -1
  57. package/lib/modules/Order/utils.js +114 -1
  58. package/lib/modules/Payment/cashRecommendationAlgorithm.d.ts +4 -5
  59. package/lib/modules/Payment/cashRecommendationAlgorithm.js +9 -354
  60. package/lib/modules/ProductList/index.d.ts +2 -1
  61. package/lib/modules/ProductList/index.js +47 -2
  62. package/lib/modules/ProductList/types.d.ts +10 -0
  63. package/lib/server/index.d.ts +7 -0
  64. package/lib/server/index.js +39 -3
  65. package/lib/server/modules/order/index.js +6 -0
  66. package/lib/server/modules/order/types.d.ts +9 -0
  67. package/lib/server/utils/small-ticket.d.ts +1 -0
  68. package/lib/server/utils/small-ticket.js +136 -12
  69. package/lib/solution/BaseSales/index.d.ts +15 -1
  70. package/lib/solution/BaseSales/index.js +192 -22
  71. package/lib/solution/BookingByStep/index.d.ts +1 -1
  72. package/lib/solution/BookingTicket/index.d.ts +1 -0
  73. package/lib/solution/BookingTicket/index.js +23 -5
  74. package/lib/solution/BookingTicket/utils/orderCustomer.js +2 -1
  75. package/lib/solution/RegisterAndLogin/config.d.ts +9 -3
  76. package/lib/solution/RegisterAndLogin/config.js +49 -8
  77. package/lib/solution/RegisterAndLogin/index.js +4 -1
  78. package/lib/solution/ScanOrder/index.d.ts +1 -0
  79. package/lib/solution/ScanOrder/index.js +5 -1
  80. package/lib/solution/ScanOrder/utils.d.ts +1 -0
  81. package/lib/solution/ScanOrder/utils.js +1 -0
  82. package/lib/solution/UnifiedBookingSales/index.d.ts +15 -2
  83. package/lib/solution/UnifiedBookingSales/index.js +125 -1
  84. package/lib/solution/UnifiedBookingSales/types.d.ts +14 -1
  85. package/lib/solution/VenueBooking/index.d.ts +1 -0
  86. package/lib/solution/VenueBooking/index.js +5 -1
  87. package/package.json +1 -1
@@ -302,7 +302,57 @@ function mergeBaseSalesUniqueArray(currentValue, loadedValue, metadataKey) {
302
302
  loadedValue.forEach(appendOrReplaceByUid);
303
303
  return result;
304
304
  }
305
+
306
+ // checkout / realtime snapshots may reorder protocol arrays or append persistence fields.
307
+ // Compare only the Sales Note protocol fields so a matching remote echo can acknowledge dirty state.
308
+ function normalizeBaseSalesNotesForComparison(value, options) {
309
+ if (!Array.isArray(value)) return null;
310
+ const unresolvedOrderTargetUuid = String(options?.unresolvedOrderTargetUuid ?? '').trim();
311
+ return value.map(note => {
312
+ if (!note || typeof note !== 'object' || Array.isArray(note)) {
313
+ return (0, _lodashEs.cloneDeep)(note);
314
+ }
315
+ const noteRecord = note;
316
+ const relations = Array.isArray(noteRecord.note_relations) ? noteRecord.note_relations.map(relation => {
317
+ if (!relation || typeof relation !== 'object' || Array.isArray(relation)) {
318
+ return (0, _lodashEs.cloneDeep)(relation);
319
+ }
320
+ const shouldResolveOrderTarget = relation.is_primary === 0 && relation.relation_type === 'related_to' && relation.target_type === 'order' && String(relation.target_uuid ?? '').trim() === '' && unresolvedOrderTargetUuid.length > 0;
321
+ return {
322
+ uuid: relation.uuid,
323
+ relation_type: relation.relation_type,
324
+ target_type: relation.target_type,
325
+ target_uuid: shouldResolveOrderTarget ? unresolvedOrderTargetUuid : relation.target_uuid,
326
+ is_primary: relation.is_primary
327
+ };
328
+ }).sort((left, right) => String(left?.uuid ?? '').localeCompare(String(right?.uuid ?? ''))) : (0, _lodashEs.cloneDeep)(noteRecord.note_relations);
329
+ return {
330
+ uuid: noteRecord.uuid,
331
+ note_type: noteRecord.note_type,
332
+ content: {
333
+ text: (0, _lodashEs.cloneDeep)(noteRecord.content?.text)
334
+ },
335
+ importance: noteRecord.importance,
336
+ note_relations: relations
337
+ };
338
+ }).sort((left, right) => String(left?.uuid ?? '').localeCompare(String(right?.uuid ?? '')));
339
+ }
340
+ function areBaseSalesNotesEquivalent(currentValue, loadedValue, options) {
341
+ const currentNotes = normalizeBaseSalesNotesForComparison(currentValue, {
342
+ unresolvedOrderTargetUuid: options?.currentOrderTargetUuid
343
+ });
344
+ const loadedNotes = normalizeBaseSalesNotesForComparison(loadedValue);
345
+ return currentNotes !== null && loadedNotes !== null && (0, _lodashEs.isEqual)(currentNotes, loadedNotes);
346
+ }
305
347
  function mergeSalesDetailRecordWithTempOrder(currentTempOrder, loadedRecord, strategy) {
348
+ const loadedHasOrderMetaList = Object.prototype.hasOwnProperty.call(loadedRecord || {}, 'order_meta_list');
349
+ const loadedHasSalesNotes = Object.prototype.hasOwnProperty.call(loadedRecord || {}, 'notes');
350
+ const hasDirtyLocalSalesNotes = currentTempOrder?._extend?.salesNotesState?.dirty === true;
351
+ const incomingSalesNotesMatchLocal = loadedHasSalesNotes && areBaseSalesNotesEquivalent(currentTempOrder?.notes, loadedRecord.notes, {
352
+ currentOrderTargetUuid: loadedRecord?.shop_order_number || currentTempOrder?.shop_order_number
353
+ });
354
+ const shouldAwaitAuthoritativeSalesNotes = strategy === 'authoritative-incoming' && hasDirtyLocalSalesNotes && (Number(loadedRecord?.need_sync) === 1 || !incomingSalesNotesMatchLocal);
355
+ const preserveDirtyLocalSalesNotes = hasDirtyLocalSalesNotes && (strategy === 'preserve-local' || shouldAwaitAuthoritativeSalesNotes);
306
356
  const normalizedLoadedRecord = normalizeBaseSalesRecordCollections(loadedRecord || {}, {
307
357
  ensureUid: false
308
358
  });
@@ -314,6 +364,18 @@ function mergeSalesDetailRecordWithTempOrder(currentTempOrder, loadedRecord, str
314
364
  });
315
365
  const uidRemaps = [];
316
366
  const mergedRecord = (0, _lodashEs.mergeWith)(normalizedCurrentTempOrder, normalizedLoadedRecord, (currentValue, loadedValue, key) => {
367
+ // order_meta_list 是服务端/本地订单携带的完整业务快照。若 incoming
368
+ // 明确提供它,必须整体替换;若省略,则保留当前值,避免 snapshot
369
+ // 数组递归拼接后出现同 ID 的旧数据。
370
+ if (key === 'order_meta_list') {
371
+ return (0, _lodashEs.cloneDeep)(loadedHasOrderMetaList ? loadedValue : currentValue);
372
+ }
373
+ if (key === 'notes') {
374
+ if (preserveDirtyLocalSalesNotes && Array.isArray(currentValue)) {
375
+ return (0, _lodashEs.cloneDeep)(currentValue);
376
+ }
377
+ if (Array.isArray(loadedValue)) return (0, _lodashEs.cloneDeep)(loadedValue);
378
+ }
317
379
  // small_ticket_data 是由 Server 一次性生成的完整展示快照,不能递归进入
318
380
  // locale 内部继续套用订单集合或普通数组的合并规则。
319
381
  if (key === 'small_ticket_data') {
@@ -342,7 +404,24 @@ function mergeSalesDetailRecordWithTempOrder(currentTempOrder, loadedRecord, str
342
404
  return undefined;
343
405
  });
344
406
  delete mergedRecord.request_unique_idempotency_token;
345
- return normalizeBaseSalesRecordCollections((0, _orderCollectionIdentity.applyOrderCollectionUidRemaps)(mergedRecord, uidRemaps));
407
+ const normalizedMergedRecord = normalizeBaseSalesRecordCollections((0, _orderCollectionIdentity.applyOrderCollectionUidRemaps)(mergedRecord, uidRemaps));
408
+ normalizedMergedRecord._extend = {
409
+ ...(normalizedMergedRecord._extend || {}),
410
+ salesNotesState: preserveDirtyLocalSalesNotes ? {
411
+ loaded: true,
412
+ dirty: true
413
+ } : loadedHasSalesNotes ? {
414
+ loaded: true,
415
+ dirty: normalizedLoadedRecord._extend?.salesNotesState?.dirty === true
416
+ } : {
417
+ loaded: false,
418
+ dirty: false
419
+ }
420
+ };
421
+ if (!loadedHasSalesNotes && !preserveDirtyLocalSalesNotes) {
422
+ delete normalizedMergedRecord.notes;
423
+ }
424
+ return normalizedMergedRecord;
346
425
  }
347
426
  function filterPendingPaymentsFromLoadedSalesDetail(sourceRecord) {
348
427
  if (!sourceRecord || typeof sourceRecord !== 'object') return sourceRecord;
@@ -1518,6 +1597,30 @@ class BaseSalesImpl extends _BaseModule.BaseModule {
1518
1597
  if (!this.store.order) return '';
1519
1598
  return this.store.order.getTempOrderNote();
1520
1599
  }
1600
+ getSalesNotes() {
1601
+ if (!this.store.order) return [];
1602
+ return this.store.order.getSalesNotes();
1603
+ }
1604
+ createSalesNotesMutationSnapshot() {
1605
+ if (!this.store.order) throw new Error('order 模块未初始化');
1606
+ return this.store.order.createSalesNotesMutationSnapshot();
1607
+ }
1608
+ restoreSalesNotesMutationSnapshot(snapshot) {
1609
+ if (!this.store.order) throw new Error('order 模块未初始化');
1610
+ this.store.order.restoreSalesNotesMutationSnapshot(snapshot);
1611
+ }
1612
+ async setSalesNote(input) {
1613
+ if (!this.store.order) throw new Error('order 模块未初始化');
1614
+ return this.store.order.setSalesNote(input);
1615
+ }
1616
+ async removeSalesNote(uuid) {
1617
+ if (!this.store.order) throw new Error('order 模块未初始化');
1618
+ await this.store.order.removeSalesNote(uuid);
1619
+ }
1620
+ async clearSalesNotes() {
1621
+ if (!this.store.order) throw new Error('order 模块未初始化');
1622
+ await this.store.order.clearSalesNotes();
1623
+ }
1521
1624
  updateTempOrderNote(note, sync = false) {
1522
1625
  try {
1523
1626
  if (!this.store.order) throw new Error('order 模块未初始化');
@@ -1545,6 +1648,14 @@ class BaseSalesImpl extends _BaseModule.BaseModule {
1545
1648
  if (!this.store.order) throw new Error('order 模块未初始化');
1546
1649
  return this.store.order.updateTempOrderDepositAmount(amount);
1547
1650
  }
1651
+ setPickupReferenceMode(mode) {
1652
+ if (!this.store.order) throw new Error('order 模块未初始化');
1653
+ return this.store.order.updateTempOrderPickupReferenceMode(mode);
1654
+ }
1655
+ setServiceType(serviceType) {
1656
+ if (!this.store.order) throw new Error('order 模块未初始化');
1657
+ return this.store.order.updateTempOrderServiceType(serviceType);
1658
+ }
1548
1659
  updateTempOrderContactsInfo(contactsInfo) {
1549
1660
  if (!this.store.order) throw new Error('order 模块未初始化');
1550
1661
  return this.store.order.updateTempOrderContactsInfo(contactsInfo);
@@ -1778,44 +1889,84 @@ class BaseSalesImpl extends _BaseModule.BaseModule {
1778
1889
  return discount;
1779
1890
  });
1780
1891
  }
1892
+ resolveDiscountConfigCustomerId(tempOrder) {
1893
+ return Number(tempOrder?.customer_id || tempOrder?.customer?.id || tempOrder?._extend?.customerSnapshot?.id || tempOrder?._extend?.customerSnapshot?.customer_id || 0);
1894
+ }
1895
+
1896
+ /**
1897
+ * Stable across notifyTempOrderChanged's immutable object replacement, while
1898
+ * still distinguishing two local orders for the same customer.
1899
+ */
1900
+ getDiscountConfigOrderIdentityKey(tempOrder) {
1901
+ const identity = this.store.order?.getOrderIdentity?.();
1902
+ return JSON.stringify([identity?.orderId ?? tempOrder?.order_id ?? '', tempOrder?.request_unique_idempotency_token ?? '', identity?.externalSaleNumber ?? tempOrder?.external_sale_number ?? '', identity?.localId ?? tempOrder?._extend?.localId ?? this.cacheId ?? '']);
1903
+ }
1781
1904
  async loadDiscountConfig(params) {
1782
1905
  if (!this.store.order) throw new Error('order 模块未初始化');
1783
1906
  const tempOrder = this.store.order.ensureTempOrder();
1784
1907
  const orderStore = this.store.order.store || {};
1785
1908
  const discountModule = orderStore.discount;
1786
1909
  const rulesModule = orderStore.rules;
1787
- const orderId = tempOrder.order_id || this.getOrderIdentity?.()?.orderId;
1910
+ const orderId = tempOrder.order_id || this.store.order.getOrderIdentity?.()?.orderId;
1788
1911
  const customerId = Number(params?.customerId || tempOrder.customer_id || tempOrder.customer?.id || tempOrder._extend?.customerSnapshot?.id || tempOrder._extend?.customerSnapshot?.customer_id || 0);
1789
1912
  const currentDiscountList = discountModule?.getDiscountList?.() || [];
1790
1913
  const originalDiscountList = discountModule?.getOriginalDiscountList?.() || [];
1791
1914
  const hasManualDiscountSelection = this.hasManualDiscountSelection || currentDiscountList.some(discount => discount.isManualSelect);
1792
1915
  const discountAction = params?.action || (orderId ? 'edit' : 'create');
1793
- const discountConfigCacheKey = [customerId, discountAction, Number(orderId) || 0].join(':');
1794
- let preparedDiscountList = [];
1916
+ const requestOrderIdentityKey = this.getDiscountConfigOrderIdentityKey(tempOrder);
1917
+ const discountConfigCacheKey = JSON.stringify([customerId, discountAction, Number(orderId) || 0, requestOrderIdentityKey]);
1918
+ let requestEpoch = this.store.order.getCustomerWalletPrepareConfigEpoch?.();
1919
+ const isCurrentRequest = () => {
1920
+ const currentTempOrder = this.store.order?.getTempOrder?.();
1921
+ return Boolean(currentTempOrder && (requestEpoch === undefined || this.store.order?.getCustomerWalletPrepareConfigEpoch?.() === requestEpoch) && this.resolveDiscountConfigCustomerId(currentTempOrder) === customerId && this.getDiscountConfigOrderIdentityKey(currentTempOrder) === requestOrderIdentityKey);
1922
+ };
1923
+ const getCurrentResult = () => {
1924
+ const currentTempOrder = this.store.order?.getTempOrder?.();
1925
+ return {
1926
+ productList: currentTempOrder?.products || [],
1927
+ discountList: discountModule?.getDiscountList?.() || [],
1928
+ availableWalletIds: this.getAvailableWalletIds(),
1929
+ virtualCurrencyList: this.getVirtualCurrencyList()
1930
+ };
1931
+ };
1932
+
1933
+ // Walk-In 不请求 prepare config;当前搜索/扫码券必须继续参与本次重算。
1934
+ // current 优先于 original,保留搜索后的最新选择与可用状态。
1935
+ const searchedDiscountList = currentDiscountList.filter(discount => discount.isScan);
1936
+ const searchedDiscountIds = new Set(searchedDiscountList.map(discount => String(discount.id)));
1937
+ let preparedDiscountList = [...searchedDiscountList, ...originalDiscountList.filter(discount => !searchedDiscountIds.has(String(discount.id)))];
1795
1938
  if (customerId && customerId !== 1) {
1796
1939
  if (!params?.force && this.discountConfigCacheKey === discountConfigCacheKey) {
1797
1940
  preparedDiscountList = originalDiscountList;
1798
1941
  } else {
1799
- preparedDiscountList = await this.store.order.loadDiscountConfig({
1942
+ const prepareConfigPromise = this.store.order.loadDiscountConfig({
1800
1943
  customerId,
1801
1944
  action: discountAction,
1802
1945
  orderId: Number(orderId) || undefined,
1803
1946
  apply: false
1804
1947
  });
1805
- this.discountConfigCacheKey = discountConfigCacheKey;
1948
+ // OrderModule increments this epoch synchronously before its first await.
1949
+ requestEpoch = this.store.order.getCustomerWalletPrepareConfigEpoch?.();
1950
+ preparedDiscountList = await prepareConfigPromise;
1951
+ if (!isCurrentRequest()) return getCurrentResult();
1806
1952
  await discountModule?.setOriginalDiscountList?.(preparedDiscountList);
1953
+ if (!isCurrentRequest()) return getCurrentResult();
1954
+ this.discountConfigCacheKey = discountConfigCacheKey;
1807
1955
  }
1808
1956
  }
1809
1957
  if (this.isActiveVirtualSettlementOrder(tempOrder)) {
1958
+ if (!isCurrentRequest()) return getCurrentResult();
1810
1959
  await discountModule?.setOriginalDiscountList?.([]);
1960
+ if (!isCurrentRequest()) return getCurrentResult();
1811
1961
  await discountModule?.setDiscountList?.([]);
1962
+ if (!isCurrentRequest()) return getCurrentResult();
1812
1963
  tempOrder.discount_list = [];
1813
1964
  tempOrder.shop_discount = '0.00';
1814
1965
  return {
1815
1966
  productList: tempOrder.products || [],
1816
1967
  discountList: [],
1817
1968
  availableWalletIds: this.getAvailableWalletIds(),
1818
- virtualCurrencyList: this.store.order.getVirtualCurrencyList()
1969
+ virtualCurrencyList: this.getVirtualCurrencyList()
1819
1970
  };
1820
1971
  }
1821
1972
  if (hasManualDiscountSelection && currentDiscountList.length) {
@@ -1823,12 +1974,14 @@ class BaseSalesImpl extends _BaseModule.BaseModule {
1823
1974
  productList: tempOrder.products || [],
1824
1975
  discountList: currentDiscountList,
1825
1976
  availableWalletIds: this.getAvailableWalletIds(),
1826
- virtualCurrencyList: this.store.order.getVirtualCurrencyList()
1977
+ virtualCurrencyList: this.getVirtualCurrencyList()
1827
1978
  };
1828
1979
  }
1829
1980
  let nextDiscountList = this.mergeHistoricalDiscountList(preparedDiscountList || discountModule?.getDiscountList?.() || [], tempOrder.products || []);
1830
1981
  nextDiscountList = this.mergeCurrentDiscountSelectionState(nextDiscountList, currentDiscountList);
1982
+ if (!isCurrentRequest()) return getCurrentResult();
1831
1983
  await discountModule?.setDiscountList(nextDiscountList);
1984
+ if (!isCurrentRequest()) return getCurrentResult();
1832
1985
  const shouldSkipAutoApplyFetchedDiscounts = this.shouldSkipAutoApplyFetchedDiscountsInEditMode({
1833
1986
  discountAction,
1834
1987
  products: tempOrder.products || [],
@@ -1836,24 +1989,28 @@ class BaseSalesImpl extends _BaseModule.BaseModule {
1836
1989
  nextDiscountList
1837
1990
  });
1838
1991
  if (shouldSkipAutoApplyFetchedDiscounts) {
1839
- const summary = await this.store.order.recalculateSummary({
1992
+ await this.store.order.recalculateSummary({
1840
1993
  createIfMissing: true
1841
1994
  });
1995
+ if (!isCurrentRequest()) return getCurrentResult();
1842
1996
  this.store.order.persistTempOrder();
1997
+ if (!isCurrentRequest()) return getCurrentResult();
1843
1998
  await this.effectsEmit('onDiscountApplied', {
1844
1999
  productList: tempOrder.products || [],
1845
2000
  discountList: nextDiscountList,
1846
2001
  selectedDiscountList: tempOrder.discount_list || [],
1847
2002
  tempOrder
1848
2003
  });
2004
+ if (!isCurrentRequest()) return getCurrentResult();
1849
2005
  return {
1850
2006
  productList: tempOrder.products || [],
1851
2007
  discountList: nextDiscountList,
1852
2008
  availableWalletIds: this.getAvailableWalletIds(),
1853
- virtualCurrencyList: this.store.order.getVirtualCurrencyList()
2009
+ virtualCurrencyList: this.getVirtualCurrencyList()
1854
2010
  };
1855
2011
  }
1856
2012
  if (rulesModule) {
2013
+ if (!isCurrentRequest()) return getCurrentResult();
1857
2014
  const orderTotalAmount = Number(orderStore.summary?.total_amount || 0);
1858
2015
  const result = rulesModule.calcDiscount({
1859
2016
  productList: tempOrder.products,
@@ -1889,25 +2046,31 @@ class BaseSalesImpl extends _BaseModule.BaseModule {
1889
2046
  if (result.discountList) {
1890
2047
  nextDiscountList = this.mergeHistoricalDiscountList(result.discountList, tempOrder.products || []);
1891
2048
  _Order.OrderModule.populateSavedAmounts(tempOrder.products, nextDiscountList);
2049
+ if (!isCurrentRequest()) return getCurrentResult();
1892
2050
  await discountModule?.setDiscountList(nextDiscountList);
2051
+ if (!isCurrentRequest()) return getCurrentResult();
1893
2052
  tempOrder.discount_list = nextDiscountList.filter(discount => discount.isSelected);
1894
2053
  }
1895
2054
  }
1896
- const summary = await this.store.order.recalculateSummary({
2055
+ if (!isCurrentRequest()) return getCurrentResult();
2056
+ await this.store.order.recalculateSummary({
1897
2057
  createIfMissing: true
1898
2058
  });
2059
+ if (!isCurrentRequest()) return getCurrentResult();
1899
2060
  this.store.order.persistTempOrder();
2061
+ if (!isCurrentRequest()) return getCurrentResult();
1900
2062
  await this.effectsEmit('onDiscountApplied', {
1901
2063
  productList: tempOrder.products || [],
1902
2064
  discountList: nextDiscountList,
1903
2065
  selectedDiscountList: tempOrder.discount_list || [],
1904
2066
  tempOrder
1905
2067
  });
2068
+ if (!isCurrentRequest()) return getCurrentResult();
1906
2069
  return {
1907
2070
  productList: tempOrder.products || [],
1908
2071
  discountList: nextDiscountList,
1909
2072
  availableWalletIds: this.getAvailableWalletIds(),
1910
- virtualCurrencyList: this.store.order.getVirtualCurrencyList()
2073
+ virtualCurrencyList: this.getVirtualCurrencyList()
1911
2074
  };
1912
2075
  }
1913
2076
  getAvailableWalletIds() {
@@ -2761,7 +2924,7 @@ class BaseSalesImpl extends _BaseModule.BaseModule {
2761
2924
  if (this.store.payment) {
2762
2925
  const committed = this.getCommittedWalletAssets();
2763
2926
  const state = this.store.payment.wallet.setCommittedWalletAssets(committed.ids, committed.assets);
2764
- void this.publishWalletAssetsState(state);
2927
+ void this.publishWalletAssetsState(state, 'local');
2765
2928
  }
2766
2929
  return payments;
2767
2930
  }
@@ -2907,9 +3070,10 @@ class BaseSalesImpl extends _BaseModule.BaseModule {
2907
3070
  })
2908
3071
  };
2909
3072
  }
2910
- async publishWalletAssetsState(state) {
3073
+ async publishWalletAssetsState(state, changeSource = 'derived') {
2911
3074
  await this.effectsEmit(_types.BaseSalesHookNames.onWalletAssetsStateChanged, {
2912
- state
3075
+ state,
3076
+ changeSource
2913
3077
  });
2914
3078
  return state;
2915
3079
  }
@@ -3043,7 +3207,7 @@ class BaseSalesImpl extends _BaseModule.BaseModule {
3043
3207
  source: params.source
3044
3208
  });
3045
3209
  await this.syncSelectedWalletAssets(state);
3046
- return this.publishWalletAssetsState(state);
3210
+ return this.publishWalletAssetsState(state, 'local');
3047
3211
  }
3048
3212
 
3049
3213
  /** 更新已选钱包资产的手动使用金额,并同步 pending payment。 */
@@ -3051,7 +3215,7 @@ class BaseSalesImpl extends _BaseModule.BaseModule {
3051
3215
  if (!this.store.payment) throw new Error('payment 模块未初始化');
3052
3216
  const state = this.store.payment.wallet.updateWalletAssetAmount(params.assetId, params.amount);
3053
3217
  await this.syncSelectedWalletAssets(state);
3054
- return this.publishWalletAssetsState(state);
3218
+ return this.publishWalletAssetsState(state, 'local');
3055
3219
  }
3056
3220
 
3057
3221
  /** 扫码精确查券;仅可用资产会被自动选中。 */
@@ -3066,7 +3230,7 @@ class BaseSalesImpl extends _BaseModule.BaseModule {
3066
3230
  const result = await wallet.scanWalletAssetAsync(code);
3067
3231
  if (result.type === 'normalCode') {
3068
3232
  await this.syncSelectedWalletAssets(result.state);
3069
- await this.publishWalletAssetsState(result.state);
3233
+ await this.publishWalletAssetsState(result.state, 'local');
3070
3234
  }
3071
3235
  return result;
3072
3236
  }
@@ -3076,7 +3240,7 @@ class BaseSalesImpl extends _BaseModule.BaseModule {
3076
3240
  if (!this.store.payment) throw new Error('payment 模块未初始化');
3077
3241
  const state = this.store.payment.wallet.clearWalletAssetSelection();
3078
3242
  await this.syncSelectedWalletAssets(state);
3079
- return this.publishWalletAssetsState(state);
3243
+ return this.publishWalletAssetsState(state, 'local');
3080
3244
  }
3081
3245
 
3082
3246
  /**
@@ -3087,7 +3251,13 @@ class BaseSalesImpl extends _BaseModule.BaseModule {
3087
3251
  */
3088
3252
  updateOrderCustomer(customer) {
3089
3253
  if (!this.store.order) throw new Error('order 模块未初始化');
3254
+ const previousCustomerId = this.resolveDiscountConfigCustomerId(this.store.order.getTempOrder?.());
3090
3255
  const result = this.store.order.updateTempOrderCustomer(customer);
3256
+ const nextCustomerId = this.resolveDiscountConfigCustomerId(this.store.order.getTempOrder?.());
3257
+ if (String(previousCustomerId) !== String(nextCustomerId)) {
3258
+ this.discountConfigCacheKey = null;
3259
+ this.hasManualDiscountSelection = false;
3260
+ }
3091
3261
  this.store.order.recalculateSummary({
3092
3262
  createIfMissing: true
3093
3263
  });
@@ -3220,7 +3390,7 @@ class BaseSalesImpl extends _BaseModule.BaseModule {
3220
3390
  try {
3221
3391
  const committed = this.getCommittedWalletAssets();
3222
3392
  const walletState = this.store.payment.wallet.setCommittedWalletAssets(committed.ids, committed.assets);
3223
- await this.publishWalletAssetsState(walletState);
3393
+ await this.publishWalletAssetsState(walletState, 'local');
3224
3394
  } catch (error) {
3225
3395
  this.logWarning('confirmWalletPayment: Wallet committed 状态同步失败', {
3226
3396
  error: error instanceof Error ? error.message : String(error)
@@ -3304,7 +3474,7 @@ class BaseSalesImpl extends _BaseModule.BaseModule {
3304
3474
  if (this.store.payment) {
3305
3475
  const committed = this.getCommittedWalletAssets();
3306
3476
  const walletState = this.store.payment.wallet.setCommittedWalletAssets(committed.ids, committed.assets);
3307
- await this.publishWalletAssetsState(walletState);
3477
+ await this.publishWalletAssetsState(walletState, 'local');
3308
3478
  }
3309
3479
  return {
3310
3480
  payment: payment || {},
@@ -3410,7 +3580,7 @@ class BaseSalesImpl extends _BaseModule.BaseModule {
3410
3580
  if (this.store.payment) {
3411
3581
  const committed = this.getCommittedWalletAssets();
3412
3582
  const walletState = this.store.payment.wallet.setCommittedWalletAssets(committed.ids, committed.assets);
3413
- await this.publishWalletAssetsState(walletState);
3583
+ await this.publishWalletAssetsState(walletState, 'local');
3414
3584
  }
3415
3585
  return {
3416
3586
  payment: payment || {},
@@ -326,7 +326,7 @@ export declare class BookingByStepImpl extends BaseModule implements Module {
326
326
  date: string;
327
327
  status: string;
328
328
  week: string;
329
- weekNum: 0 | 2 | 1 | 3 | 4 | 5 | 6;
329
+ weekNum: 0 | 2 | 1 | 6 | 4 | 5 | 3;
330
330
  }[]>;
331
331
  submitTimeSlot(timeSlots: TimeSliceItem): void;
332
332
  private getScheduleDataByIds;
@@ -29,6 +29,7 @@ export declare class BookingTicketImpl extends BaseSalesImpl implements Module {
29
29
  private addTimeProductsCatalog;
30
30
  private orderCustomerDiscountRefreshInFlight;
31
31
  private orderCustomerDiscountRefreshCustomerId;
32
+ private orderCustomerDiscountRefreshTarget;
32
33
  private orderCustomerPromotionRefreshInFlight;
33
34
  protected loadOpenDataInFlight: Promise<OpenDataConfig> | null;
34
35
  protected loadOpenDataInFlightTarget: string | null;
@@ -160,6 +160,7 @@ class BookingTicketImpl extends _BaseSales.BaseSalesImpl {
160
160
  addTimeProductsCatalog = [];
161
161
  orderCustomerDiscountRefreshInFlight = null;
162
162
  orderCustomerDiscountRefreshCustomerId = null;
163
+ orderCustomerDiscountRefreshTarget = null;
163
164
  orderCustomerPromotionRefreshInFlight = null;
164
165
  loadOpenDataInFlight = null;
165
166
  loadOpenDataInFlightTarget = null;
@@ -1123,7 +1124,13 @@ class BookingTicketImpl extends _BaseSales.BaseSalesImpl {
1123
1124
  }
1124
1125
  refreshDiscountConfigAfterOrderCustomerChange(customerId) {
1125
1126
  if (!customerId || customerId === 1) return Promise.resolve();
1126
- if (this.orderCustomerDiscountRefreshInFlight && this.orderCustomerDiscountRefreshCustomerId === customerId) {
1127
+ const tempOrder = this.store.order.getTempOrder?.();
1128
+ const orderIdentity = this.getOrderIdentity?.();
1129
+ const orderId = tempOrder?.order_id || orderIdentity?.orderId;
1130
+ const action = !orderId || String(orderId).startsWith('local_order') ? 'create' : 'edit';
1131
+ const orderTarget = orderId ? `order:${String(orderId)}` : `external:${String(tempOrder?.external_sale_number || orderIdentity?.externalSaleNumber || orderIdentity?.localId || 'new')}`;
1132
+ const refreshTarget = `${customerId}:${action}:${orderTarget}`;
1133
+ if (this.orderCustomerDiscountRefreshInFlight && this.orderCustomerDiscountRefreshTarget === refreshTarget) {
1127
1134
  return this.orderCustomerDiscountRefreshInFlight;
1128
1135
  }
1129
1136
  const previousRefresh = this.orderCustomerDiscountRefreshInFlight;
@@ -1135,22 +1142,33 @@ class BookingTicketImpl extends _BaseSales.BaseSalesImpl {
1135
1142
  if (!currentCustomer || Number(currentCustomer.customer_id) !== customerId) {
1136
1143
  return;
1137
1144
  }
1138
- const tempOrder = this.store.order.getTempOrder?.();
1139
- const orderId = tempOrder?.order_id || this.getOrderIdentity?.()?.orderId;
1140
- const action = !orderId || String(orderId).startsWith('local_order') ? 'create' : 'edit';
1145
+ const currentTempOrder = this.store.order.getTempOrder?.();
1146
+ const currentOrderIdentity = this.getOrderIdentity?.();
1147
+ const currentOrderId = currentTempOrder?.order_id || currentOrderIdentity?.orderId;
1148
+ const currentAction = !currentOrderId || String(currentOrderId).startsWith('local_order') ? 'create' : 'edit';
1149
+ const currentOrderTarget = currentOrderId ? `order:${String(currentOrderId)}` : `external:${String(currentTempOrder?.external_sale_number || currentOrderIdentity?.externalSaleNumber || currentOrderIdentity?.localId || 'new')}`;
1150
+ if (`${customerId}:${currentAction}:${currentOrderTarget}` !== refreshTarget) {
1151
+ return;
1152
+ }
1153
+
1154
+ // 上一个订单的迟到请求可能在客户切换后重新写入 BaseSales cache key;
1155
+ // 客户变更触发的目标订单刷新必须绕过该缓存。
1141
1156
  await this.loadDiscountConfig({
1142
1157
  customerId,
1143
- action
1158
+ action,
1159
+ force: true
1144
1160
  });
1145
1161
  })();
1146
1162
  this.orderCustomerDiscountRefreshInFlight = refreshTask;
1147
1163
  this.orderCustomerDiscountRefreshCustomerId = customerId;
1164
+ this.orderCustomerDiscountRefreshTarget = refreshTarget;
1148
1165
  refreshTask.catch(error => {
1149
1166
  console.error('Failed to load discount config after customer change:', error);
1150
1167
  }).finally(() => {
1151
1168
  if (this.orderCustomerDiscountRefreshInFlight !== refreshTask) return;
1152
1169
  this.orderCustomerDiscountRefreshInFlight = null;
1153
1170
  this.orderCustomerDiscountRefreshCustomerId = null;
1171
+ this.orderCustomerDiscountRefreshTarget = null;
1154
1172
  });
1155
1173
  return refreshTask;
1156
1174
  }
@@ -23,7 +23,8 @@ function resolveCustomerDisplayName(customer) {
23
23
 
24
24
  /** 将 ICustomer 格式化为写入 tempOrder 的协议字段。 */
25
25
  function formatOrderCustomerPatch(customer) {
26
- const rawId = customer.id;
26
+ const c = customer;
27
+ const rawId = c.customer_id ?? c.customerId ?? customer.id;
27
28
  const numericId = typeof rawId === 'number' ? rawId : rawId !== undefined && rawId !== null && rawId !== '' ? Number(rawId) : null;
28
29
  return {
29
30
  customer_id: Number.isFinite(numericId) ? numericId : null,
@@ -10,7 +10,11 @@ export interface ApiConfig {
10
10
  method: 'GET' | 'POST' | 'PUT' | 'DELETE';
11
11
  transformParams?: (params: any) => any;
12
12
  transformResponse?: (response: any) => any;
13
- options?: Record<string, any>;
13
+ options?: Record<string, any> | ((params: any) => Record<string, any>);
14
+ }
15
+ export interface ApiCallerOptions {
16
+ baseUrl?: string;
17
+ shopDomain?: string;
14
18
  }
15
19
  /**
16
20
  * 渠道配置接口
@@ -65,12 +69,14 @@ export declare function getChannelConfig(channel: string): ChannelConfig;
65
69
  export declare class ApiCaller {
66
70
  private request;
67
71
  private config;
72
+ private options;
68
73
  constructor(request: {
69
74
  get: <T = any>(url: string, data?: any, options?: any) => Promise<T>;
70
75
  post: <T = any>(url: string, data?: any, options?: any) => Promise<T>;
71
76
  put: <T = any>(url: string, data?: any, options?: any) => Promise<T>;
72
77
  delete: <T = any>(url: string, options?: any) => Promise<T>;
73
- }, config: ChannelConfig);
78
+ }, config: ChannelConfig, options?: ApiCallerOptions);
79
+ private resolveUrl;
74
80
  /**
75
81
  * 执行 API 调用
76
82
  */
@@ -84,4 +90,4 @@ export declare function createApiCaller(request: {
84
90
  post: <T = any>(url: string, data?: any, options?: any) => Promise<T>;
85
91
  put: <T = any>(url: string, data?: any, options?: any) => Promise<T>;
86
92
  delete: <T = any>(url: string, options?: any) => Promise<T>;
87
- }, channel: string): ApiCaller;
93
+ }, channel: string, options?: ApiCallerOptions): ApiCaller;
@@ -15,6 +15,8 @@ exports.getChannelConfig = getChannelConfig;
15
15
  * API 配置接口
16
16
  */
17
17
 
18
+ const h5CustomerAuthApiNames = new Set(['sendEmailVerificationCode', 'sendEmailLoginCode', 'sendSmsRegisterCode', 'sendEmailRegisterLink', 'verifyEmailRegistrationLink', 'emailPasswordLogin', 'emailCodeRegister', 'phoneCodeRegister', 'resendEmailRegisterLink', 'checkEmailLinkCode', 'sendSmsLoginCode', 'phoneCodeLogin', 'guestLogin', 'checkEmailExists', 'checkEmailCode', 'checkMobileCode', 'phonePasswordLogin', 'sendPasswordResetEmail', 'sendPasswordResetSms', 'sendResetPasswordLink', 'checkResetPasswordCode', 'resetPasswordByCode', 'resetPasswordByEmail', 'resetPasswordByPhone', 'verifyCode', 'register', 'login', 'oauthLogin', 'facebookLogin', 'appleLogin']);
19
+
18
20
  /**
19
21
  * 渠道配置接口
20
22
  */
@@ -278,6 +280,31 @@ const onlineStoreConfig = {
278
280
  })
279
281
  }
280
282
  };
283
+ function withH5AuthPrefix(config) {
284
+ const nextConfig = {};
285
+ Object.entries(config).forEach(([key, apiConfig]) => {
286
+ if (!apiConfig.url.startsWith('/auth/')) {
287
+ nextConfig[key] = apiConfig;
288
+ return;
289
+ }
290
+ nextConfig[key] = {
291
+ ...apiConfig,
292
+ url: apiConfig.url.startsWith('/h5/') ? apiConfig.url : `/h5${apiConfig.url}`,
293
+ options: params => {
294
+ const {
295
+ withCredentials,
296
+ ...baseOptions
297
+ } = (typeof apiConfig.options === 'function' ? apiConfig.options(params) : apiConfig.options) || {};
298
+ return {
299
+ ...baseOptions,
300
+ prefix: false
301
+ };
302
+ }
303
+ };
304
+ });
305
+ return nextConfig;
306
+ }
307
+ const h5OnlineStoreConfig = withH5AuthPrefix(onlineStoreConfig);
281
308
 
282
309
  /**
283
310
  * 默认配置(通用配置)
@@ -1066,6 +1093,7 @@ const shopWebposConfig = {
1066
1093
  */
1067
1094
  const channelConfigMap = exports.channelConfigMap = {
1068
1095
  'online-store': onlineStoreConfig,
1096
+ 'online-store-h5': h5OnlineStoreConfig,
1069
1097
  'default': defaultConfig,
1070
1098
  'webpos': webposConfig,
1071
1099
  'shop.webpos': shopWebposConfig
@@ -1083,9 +1111,16 @@ function getChannelConfig(channel) {
1083
1111
  * 负责根据配置执行 API 调用
1084
1112
  */
1085
1113
  class ApiCaller {
1086
- constructor(request, config) {
1114
+ constructor(request, config, options = {}) {
1087
1115
  this.request = request;
1088
1116
  this.config = config;
1117
+ this.options = options;
1118
+ }
1119
+ resolveUrl(url) {
1120
+ if (!this.options.baseUrl || !url.startsWith('/h5/auth/')) {
1121
+ return url;
1122
+ }
1123
+ return `${this.options.baseUrl.replace(/\/$/, '')}${url}`;
1089
1124
  }
1090
1125
 
1091
1126
  /**
@@ -1111,6 +1146,12 @@ class ApiCaller {
1111
1146
  ...options,
1112
1147
  ...additionalOptions
1113
1148
  };
1149
+ if (apiConfig.url.startsWith('/h5/auth/') && h5CustomerAuthApiNames.has(apiName)) {
1150
+ requestOptions.h5CustomerAuth = true;
1151
+ if (this.options.shopDomain) {
1152
+ requestOptions.h5AuthShopDomain = this.options.shopDomain;
1153
+ }
1154
+ }
1114
1155
 
1115
1156
  // 处理动态 headers
1116
1157
  if (requestOptions.headers && typeof requestOptions.headers === 'function') {
@@ -1123,19 +1164,19 @@ class ApiCaller {
1123
1164
  switch (apiConfig.method) {
1124
1165
  case 'GET':
1125
1166
  if (transformedParams && Object.keys(transformedParams).length > 0) {
1126
- response = await this.request.get(apiConfig.url, transformedParams, requestOptions);
1167
+ response = await this.request.get(this.resolveUrl(apiConfig.url), transformedParams, requestOptions);
1127
1168
  } else {
1128
- response = await this.request.get(apiConfig.url, undefined, requestOptions);
1169
+ response = await this.request.get(this.resolveUrl(apiConfig.url), undefined, requestOptions);
1129
1170
  }
1130
1171
  break;
1131
1172
  case 'POST':
1132
- response = await this.request.post(apiConfig.url, transformedParams, requestOptions);
1173
+ response = await this.request.post(this.resolveUrl(apiConfig.url), transformedParams, requestOptions);
1133
1174
  break;
1134
1175
  case 'PUT':
1135
- response = await this.request.put(apiConfig.url, transformedParams, requestOptions);
1176
+ response = await this.request.put(this.resolveUrl(apiConfig.url), transformedParams, requestOptions);
1136
1177
  break;
1137
1178
  case 'DELETE':
1138
- response = await this.request.delete(apiConfig.url, requestOptions);
1179
+ response = await this.request.delete(this.resolveUrl(apiConfig.url), requestOptions);
1139
1180
  break;
1140
1181
  default:
1141
1182
  throw new Error(`不支持的 HTTP 方法: ${apiConfig.method}`);
@@ -1150,7 +1191,7 @@ class ApiCaller {
1150
1191
  * 创建 API 调用器
1151
1192
  */
1152
1193
  exports.ApiCaller = ApiCaller;
1153
- function createApiCaller(request, channel) {
1194
+ function createApiCaller(request, channel, options) {
1154
1195
  const config = getChannelConfig(channel);
1155
- return new ApiCaller(request, config);
1196
+ return new ApiCaller(request, config, options);
1156
1197
  }
@@ -227,7 +227,10 @@ class RegisterAndLoginImpl extends _BaseModule.BaseModule {
227
227
  }
228
228
 
229
229
  // 创建 API 调用器
230
- this.apiCaller = (0, _config.createApiCaller)(this.request, this.channel);
230
+ this.apiCaller = (0, _config.createApiCaller)(this.request, this.channel, {
231
+ baseUrl: this.otherParams.h5AuthBaseUrl,
232
+ shopDomain: this.otherParams.h5AuthShopDomain
233
+ });
231
234
 
232
235
  // 尝试从本地存储恢复登录状态
233
236
  await this.restoreLoginState();