@pisell/pisellos 2.2.291 → 2.2.293

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 (174) hide show
  1. package/dist/core/index.d.ts +7 -0
  2. package/dist/core/index.js +108 -65
  3. package/dist/model/strategy/adapter/itemRule/adapter.d.ts +4 -1
  4. package/dist/model/strategy/adapter/itemRule/adapter.js +135 -35
  5. package/dist/model/strategy/adapter/itemRule/evaluator.d.ts +8 -1
  6. package/dist/model/strategy/adapter/itemRule/evaluator.js +25 -1
  7. package/dist/model/strategy/adapter/itemRule/type.d.ts +44 -0
  8. package/dist/model/strategy/adapter/itemRule/type.js +19 -1
  9. package/dist/model/strategy/adapter/promotion/index.js +0 -9
  10. package/dist/modules/BookingContext/index.d.ts +3 -0
  11. package/dist/modules/BookingContext/index.js +42 -13
  12. package/dist/modules/BookingContext/types.d.ts +4 -4
  13. package/dist/modules/BookingContext/types.js +3 -3
  14. package/dist/modules/OpenData/types.d.ts +1 -0
  15. package/dist/modules/Order/index.d.ts +18 -3
  16. package/dist/modules/Order/index.js +885 -485
  17. package/dist/modules/Order/salesNotes.d.ts +31 -0
  18. package/dist/modules/Order/salesNotes.js +492 -0
  19. package/dist/modules/Order/types.d.ts +93 -5
  20. package/dist/modules/Order/types.js +5 -0
  21. package/dist/modules/Order/utils.d.ts +12 -0
  22. package/dist/modules/Order/utils.js +96 -31
  23. package/dist/modules/Product/types.d.ts +2 -0
  24. package/dist/modules/ProductList/clientDataVariants.d.ts +50 -0
  25. package/dist/modules/ProductList/clientDataVariants.js +216 -0
  26. package/dist/modules/ProductList/index.d.ts +18 -3
  27. package/dist/modules/ProductList/index.js +220 -38
  28. package/dist/modules/ProductList/types.d.ts +10 -0
  29. package/dist/modules/Quotation/index.d.ts +22 -0
  30. package/dist/modules/Quotation/index.js +293 -28
  31. package/dist/modules/ResourcePlanner/index.d.ts +0 -22
  32. package/dist/modules/ResourcePlanner/index.js +1 -180
  33. package/dist/modules/ResourcePlanner/localClock.d.ts +33 -0
  34. package/dist/modules/ResourcePlanner/localClock.js +183 -0
  35. package/dist/modules/ResourcePlanner/planner.d.ts +5 -14
  36. package/dist/modules/ResourcePlanner/planner.js +2111 -958
  37. package/dist/modules/ResourcePlanner/types.d.ts +359 -187
  38. package/dist/modules/ResourcePlanner/types.js +33 -1
  39. package/dist/plugins/request.d.ts +1 -0
  40. package/dist/server/index.d.ts +17 -2
  41. package/dist/server/index.js +1134 -932
  42. package/dist/server/modules/floor-plan/index.d.ts +4 -0
  43. package/dist/server/modules/floor-plan/index.js +240 -98
  44. package/dist/server/modules/menu/index.js +48 -23
  45. package/dist/server/modules/order/index.d.ts +17 -7
  46. package/dist/server/modules/order/index.js +419 -207
  47. package/dist/server/modules/products/index.d.ts +10 -4
  48. package/dist/server/modules/products/index.js +177 -84
  49. package/dist/server/modules/resource/index.js +21 -13
  50. package/dist/server/utils/product.d.ts +1 -1
  51. package/dist/server/utils/product.js +186 -22
  52. package/dist/server/utils/small-ticket.d.ts +9 -1
  53. package/dist/server/utils/small-ticket.js +131 -84
  54. package/dist/solution/BaseSales/index.d.ts +26 -3
  55. package/dist/solution/BaseSales/index.js +1384 -905
  56. package/dist/solution/BaseSales/types.d.ts +2 -0
  57. package/dist/solution/BaseSales/utils/transformBaseProductToOrderProduct.d.ts +3 -0
  58. package/dist/solution/BaseSales/utils/transformBaseProductToOrderProduct.js +10 -3
  59. package/dist/solution/BaseSales/utils.js +6 -3
  60. package/dist/solution/BookingByStep/index.d.ts +4 -4
  61. package/dist/solution/BookingTicket/index.d.ts +1 -1
  62. package/dist/solution/BookingTicket/index.js +4 -2
  63. package/dist/solution/BookingTicket/utils/addProductDecision.d.ts +2 -1
  64. package/dist/solution/BookingTicket/utils/addProductDecision.js +6 -3
  65. package/dist/solution/BookingTicket/utils/orderCustomer.js +3 -2
  66. package/dist/solution/RegisterAndLogin/config.d.ts +9 -3
  67. package/dist/solution/RegisterAndLogin/config.js +95 -40
  68. package/dist/solution/RegisterAndLogin/index.js +4 -1
  69. package/dist/solution/ScanOrder/index.d.ts +1 -0
  70. package/dist/solution/ScanOrder/index.js +31 -27
  71. package/dist/solution/ScanOrder/types.d.ts +2 -0
  72. package/dist/solution/ScanOrder/utils.d.ts +1 -0
  73. package/dist/solution/ScanOrder/utils.js +8 -3
  74. package/dist/solution/UnifiedBookingSales/index.d.ts +128 -74
  75. package/dist/solution/UnifiedBookingSales/index.js +3877 -963
  76. package/dist/solution/UnifiedBookingSales/remoteOccupancyCache.d.ts +49 -0
  77. package/dist/solution/UnifiedBookingSales/remoteOccupancyCache.js +430 -0
  78. package/dist/solution/UnifiedBookingSales/types.d.ts +161 -77
  79. package/dist/solution/UnifiedBookingSales/types.js +4 -9
  80. package/dist/solution/VenueBooking/index.d.ts +3 -0
  81. package/dist/solution/VenueBooking/index.js +549 -497
  82. package/dist/solution/VenueBooking/utils/dateSummary.d.ts +1 -0
  83. package/dist/solution/VenueBooking/utils/dateSummary.js +3 -1
  84. package/dist/solution/VenueBooking/utils/slotMerge.d.ts +2 -0
  85. package/dist/solution/VenueBooking/utils/slotMerge.js +3 -1
  86. package/dist/utils/platform.d.ts +8 -0
  87. package/dist/utils/platform.js +15 -0
  88. package/lib/core/index.d.ts +7 -0
  89. package/lib/core/index.js +20 -2
  90. package/lib/model/strategy/adapter/itemRule/adapter.d.ts +4 -1
  91. package/lib/model/strategy/adapter/itemRule/adapter.js +73 -4
  92. package/lib/model/strategy/adapter/itemRule/evaluator.d.ts +8 -1
  93. package/lib/model/strategy/adapter/itemRule/evaluator.js +23 -1
  94. package/lib/model/strategy/adapter/itemRule/type.d.ts +44 -0
  95. package/lib/model/strategy/adapter/itemRule/type.js +19 -1
  96. package/lib/modules/BookingContext/index.d.ts +3 -0
  97. package/lib/modules/BookingContext/index.js +35 -8
  98. package/lib/modules/BookingContext/types.d.ts +4 -4
  99. package/lib/modules/BookingContext/types.js +3 -3
  100. package/lib/modules/OpenData/types.d.ts +1 -0
  101. package/lib/modules/Order/index.d.ts +18 -3
  102. package/lib/modules/Order/index.js +325 -28
  103. package/lib/modules/Order/salesNotes.d.ts +31 -0
  104. package/lib/modules/Order/salesNotes.js +382 -0
  105. package/lib/modules/Order/types.d.ts +93 -5
  106. package/lib/modules/Order/types.js +4 -0
  107. package/lib/modules/Order/utils.d.ts +12 -0
  108. package/lib/modules/Order/utils.js +85 -16
  109. package/lib/modules/Product/types.d.ts +2 -0
  110. package/lib/modules/ProductList/clientDataVariants.d.ts +50 -0
  111. package/lib/modules/ProductList/clientDataVariants.js +204 -0
  112. package/lib/modules/ProductList/index.d.ts +18 -3
  113. package/lib/modules/ProductList/index.js +137 -8
  114. package/lib/modules/ProductList/types.d.ts +10 -0
  115. package/lib/modules/Quotation/index.d.ts +22 -0
  116. package/lib/modules/Quotation/index.js +158 -11
  117. package/lib/modules/ResourcePlanner/index.d.ts +0 -22
  118. package/lib/modules/ResourcePlanner/index.js +9 -129
  119. package/lib/modules/ResourcePlanner/localClock.d.ts +33 -0
  120. package/lib/modules/ResourcePlanner/localClock.js +205 -0
  121. package/lib/modules/ResourcePlanner/planner.d.ts +5 -14
  122. package/lib/modules/ResourcePlanner/planner.js +1807 -783
  123. package/lib/modules/ResourcePlanner/types.d.ts +359 -187
  124. package/lib/modules/ResourcePlanner/types.js +13 -1
  125. package/lib/plugins/request.d.ts +1 -0
  126. package/lib/server/index.d.ts +17 -2
  127. package/lib/server/index.js +149 -21
  128. package/lib/server/modules/floor-plan/index.d.ts +4 -0
  129. package/lib/server/modules/floor-plan/index.js +60 -7
  130. package/lib/server/modules/menu/index.js +26 -4
  131. package/lib/server/modules/order/index.d.ts +17 -7
  132. package/lib/server/modules/order/index.js +194 -57
  133. package/lib/server/modules/products/index.d.ts +10 -4
  134. package/lib/server/modules/products/index.js +93 -23
  135. package/lib/server/modules/resource/index.js +12 -2
  136. package/lib/server/utils/product.d.ts +1 -1
  137. package/lib/server/utils/product.js +163 -3
  138. package/lib/server/utils/small-ticket.d.ts +9 -1
  139. package/lib/server/utils/small-ticket.js +123 -79
  140. package/lib/solution/BaseSales/index.d.ts +26 -3
  141. package/lib/solution/BaseSales/index.js +396 -70
  142. package/lib/solution/BaseSales/types.d.ts +2 -0
  143. package/lib/solution/BaseSales/utils/transformBaseProductToOrderProduct.d.ts +3 -0
  144. package/lib/solution/BaseSales/utils/transformBaseProductToOrderProduct.js +9 -2
  145. package/lib/solution/BaseSales/utils.js +3 -0
  146. package/lib/solution/BookingByStep/index.d.ts +4 -4
  147. package/lib/solution/BookingTicket/index.d.ts +1 -1
  148. package/lib/solution/BookingTicket/index.js +2 -1
  149. package/lib/solution/BookingTicket/utils/addProductDecision.d.ts +2 -1
  150. package/lib/solution/BookingTicket/utils/addProductDecision.js +5 -2
  151. package/lib/solution/BookingTicket/utils/orderCustomer.js +2 -1
  152. package/lib/solution/RegisterAndLogin/config.d.ts +9 -3
  153. package/lib/solution/RegisterAndLogin/config.js +49 -8
  154. package/lib/solution/RegisterAndLogin/index.js +4 -1
  155. package/lib/solution/ScanOrder/index.d.ts +1 -0
  156. package/lib/solution/ScanOrder/index.js +5 -1
  157. package/lib/solution/ScanOrder/types.d.ts +2 -0
  158. package/lib/solution/ScanOrder/utils.d.ts +1 -0
  159. package/lib/solution/ScanOrder/utils.js +4 -0
  160. package/lib/solution/UnifiedBookingSales/index.d.ts +128 -74
  161. package/lib/solution/UnifiedBookingSales/index.js +2592 -424
  162. package/lib/solution/UnifiedBookingSales/remoteOccupancyCache.d.ts +49 -0
  163. package/lib/solution/UnifiedBookingSales/remoteOccupancyCache.js +316 -0
  164. package/lib/solution/UnifiedBookingSales/types.d.ts +161 -77
  165. package/lib/solution/UnifiedBookingSales/types.js +4 -6
  166. package/lib/solution/VenueBooking/index.d.ts +3 -0
  167. package/lib/solution/VenueBooking/index.js +33 -9
  168. package/lib/solution/VenueBooking/utils/dateSummary.d.ts +1 -0
  169. package/lib/solution/VenueBooking/utils/dateSummary.js +3 -1
  170. package/lib/solution/VenueBooking/utils/slotMerge.d.ts +2 -0
  171. package/lib/solution/VenueBooking/utils/slotMerge.js +3 -1
  172. package/lib/utils/platform.d.ts +8 -0
  173. package/lib/utils/platform.js +22 -0
  174. package/package.json +1 -1
@@ -27,6 +27,7 @@ var _filterBookings = require("./modules/order/utils/filterBookings");
27
27
  var _smallTicket = require("./utils/small-ticket");
28
28
  var _BaseSales = require("../solution/BaseSales");
29
29
  var _paymentUtils = require("../modules/Order/payment-utils");
30
+ var _salesNotes = require("../modules/Order/salesNotes");
30
31
  var _modules = require("./modules");
31
32
  Object.keys(_modules).forEach(function (key) {
32
33
  if (key === "default" || key === "__esModule") return;
@@ -99,6 +100,8 @@ class Server {
99
100
  productQueryScheduleTimers = new Map();
100
101
  /** 餐牌筛选为空时才触发的菜单/日程缓存自愈,避免每次商品查询都请求远端。 */
101
102
  menuScheduleRefreshInFlight = null;
103
+ /** 启动阶段模块预加载任务;预渲染商品查询复用该任务,避免缓存尚未 ready 时误触发远端自愈。 */
104
+ startupModulePreloadInFlight = null;
102
105
  lastMenuScheduleRefreshAt = 0;
103
106
  MENU_SCHEDULE_REFRESH_COOLDOWN_MS = 30 * 1000;
104
107
 
@@ -501,7 +504,15 @@ class Server {
501
504
 
502
505
  // 自动预加载数据
503
506
  if (autoPreload) {
504
- await this.preloadModulesData(registeredModules, options);
507
+ const preloadTask = this.preloadModulesData(registeredModules, options);
508
+ this.startupModulePreloadInFlight = preloadTask;
509
+ try {
510
+ await preloadTask;
511
+ } finally {
512
+ if (this.startupModulePreloadInFlight === preloadTask) {
513
+ this.startupModulePreloadInFlight = null;
514
+ }
515
+ }
505
516
  } else {
506
517
  this.logInfo('跳过自动预加载', {
507
518
  autoPreload
@@ -619,12 +630,16 @@ class Server {
619
630
  const numberedData = await this.ensureCheckoutOrderNumbers(normalizedData, title);
620
631
  const checkoutData = this.applyCheckoutFulfillmentBuzzer(numberedData, title);
621
632
  const remoteCheckoutData = this.buildRemoteCheckoutData(checkoutData, title);
633
+ const deviceHeaders = await this.buildCheckoutDeviceHeaders(title);
622
634
  if (!this.app?.request?.post) {
623
635
  throw new Error('app.request 不可用');
624
636
  }
625
637
  const response = await this.app.request.post(backendPath, remoteCheckoutData, {
626
638
  isShopApi: true,
627
- customToast: () => {}
639
+ customToast: () => {},
640
+ ...(Object.keys(deviceHeaders).length > 0 ? {
641
+ headers: deviceHeaders
642
+ } : {})
628
643
  });
629
644
  if (this.isCheckoutResponseRejected(response)) {
630
645
  return {
@@ -687,6 +702,7 @@ class Server {
687
702
  if (!next || typeof next !== 'object') return next;
688
703
  const record = next;
689
704
  delete record[OS_FULFILLMENT_REF_MODE_FIELD];
705
+ this.applyKioskCheckoutOperator(record);
690
706
  if (!Array.isArray(record.payments)) return next;
691
707
  const originalPayments = record.payments;
692
708
  const filteredPayments = originalPayments.filter(payment => !(0, _paymentUtils.isPendingOrderPayment)(payment));
@@ -701,6 +717,44 @@ class Server {
701
717
  });
702
718
  return next;
703
719
  }
720
+ async buildCheckoutDeviceHeaders(title) {
721
+ const getAsyncIotDeviceInfo = this.getAppData('async_iot_device_info');
722
+ if (!getAsyncIotDeviceInfo) return {};
723
+ let deviceInfo;
724
+ try {
725
+ deviceInfo = typeof getAsyncIotDeviceInfo === 'function' ? await getAsyncIotDeviceInfo() : getAsyncIotDeviceInfo;
726
+ } catch (error) {
727
+ this.logWarning(`${title}: 读取 async_iot_device_info 失败,跳过 checkout 设备请求头`, {
728
+ error: this.getUnknownErrorMessage(error)
729
+ });
730
+ return {};
731
+ }
732
+ if (!deviceInfo || typeof deviceInfo !== 'object') return {};
733
+ const headers = {};
734
+ const assignHeader = (key, value) => {
735
+ if (this.isBlankCheckoutValue(value)) return;
736
+ headers[key] = String(value);
737
+ };
738
+ assignHeader('Device-ID', deviceInfo.id);
739
+ assignHeader('Device-Type-Code', deviceInfo.type_code);
740
+ assignHeader('Device-Model-Code', deviceInfo.model_code);
741
+ return headers;
742
+ }
743
+ applyKioskCheckoutOperator(record) {
744
+ if (record.platform !== 'kiosk') return;
745
+ record.operator_type = 'customer';
746
+ record.operator_id = this.resolveKioskCheckoutOperatorId(record.customer_id);
747
+ }
748
+ resolveKioskCheckoutOperatorId(customerId) {
749
+ if (customerId === undefined || customerId === null) return 1;
750
+ if (typeof customerId === 'string') {
751
+ const trimmed = customerId.trim();
752
+ if (trimmed === '' || trimmed === '0' || trimmed === '1') return 1;
753
+ return customerId;
754
+ }
755
+ if (customerId === 0 || customerId === 1) return 1;
756
+ return customerId;
757
+ }
704
758
  mergeCheckoutSyncPayments(checkoutData, fresh, title) {
705
759
  const localPayments = Array.isArray(checkoutData?.payments) ? checkoutData.payments : [];
706
760
  const freshRecord = fresh;
@@ -802,11 +856,24 @@ class Server {
802
856
  return;
803
857
  }
804
858
  try {
805
- await this.schedule.loadAllSchedule();
806
859
  this.products.clearPriceCache();
807
860
  await this.products.setupQuotationPriceBridge({
808
861
  scheduleModule: this.schedule,
809
- channel: this.core.context?.channel
862
+ channel: this.core.context?.channel,
863
+ onQuotationUpdated: () => {
864
+ void this.recomputeAndNotifyProductQuery();
865
+ }
866
+ });
867
+
868
+ // Schedule 已在模块 preload 阶段加载完成。价格桥接先使用本地数据,
869
+ // 远端刷新转为后台校准,避免二次启动重复阻塞请求。
870
+ void this.schedule.loadAllSchedule().then(() => {
871
+ this.products?.clearPriceCache();
872
+ void this.recomputeAndNotifyProductQuery();
873
+ }).catch(error => {
874
+ this.logWarning('Products 报价单价格桥接后台刷新 Schedule 失败,继续使用本地数据', {
875
+ error: error instanceof Error ? error.message : String(error)
876
+ });
810
877
  });
811
878
  this.logInfo('Products 报价单价格桥接初始化完成');
812
879
  } catch (error) {
@@ -865,17 +932,23 @@ class Server {
865
932
  * 重新拉取全量 SSE 接口,更新本地数据后触发 onProductsSyncCompleted
866
933
  * 不影响当前界面展示,适用于切回前台、定时刷新等场景
867
934
  */
868
- async refreshProductsInBackground() {
935
+ async refreshProductsInBackground(options = {}) {
869
936
  if (!this.products) {
870
937
  this.logWarning('refreshProductsInBackground: Products 模块未注册');
871
938
  return;
872
939
  }
873
- this.logInfo('refreshProductsInBackground 开始');
940
+ const trigger = options.trigger || 'background';
941
+ this.logInfo('refreshProductsInBackground 开始', {
942
+ trigger
943
+ });
874
944
  const startTime = Date.now();
875
945
  try {
876
- await this.products.silentRefresh();
946
+ await this.products.silentRefresh({
947
+ trigger
948
+ });
877
949
  const duration = Date.now() - startTime;
878
950
  this.logInfo('refreshProductsInBackground 完成', {
951
+ trigger,
879
952
  duration: `${duration}ms`
880
953
  });
881
954
  } catch (error) {
@@ -883,6 +956,7 @@ class Server {
883
956
  const errorMessage = error instanceof Error ? error.message : String(error);
884
957
  console.error('[Server] refreshProductsInBackground 失败:', error);
885
958
  this.logError('refreshProductsInBackground 失败', {
959
+ trigger,
886
960
  duration: `${duration}ms`,
887
961
  error: errorMessage
888
962
  });
@@ -894,17 +968,23 @@ class Server {
894
968
  * 重新拉取全量 SSE 接口,更新本地数据后触发 onOrdersSyncCompleted
895
969
  * 不影响当前界面展示,适用于切回前台、定时刷新等场景
896
970
  */
897
- async refreshOrdersInBackground() {
971
+ async refreshOrdersInBackground(options = {}) {
898
972
  if (!this.order) {
899
973
  this.logWarning('refreshOrdersInBackground: Order 模块未注册');
900
974
  return;
901
975
  }
902
- this.logInfo('refreshOrdersInBackground 开始');
976
+ const trigger = options.trigger || 'background';
977
+ this.logInfo('refreshOrdersInBackground 开始', {
978
+ trigger
979
+ });
903
980
  const startTime = Date.now();
904
981
  try {
905
- await this.order.silentRefresh();
982
+ await this.order.silentRefresh({
983
+ trigger
984
+ });
906
985
  const duration = Date.now() - startTime;
907
986
  this.logInfo('refreshOrdersInBackground 完成', {
987
+ trigger,
908
988
  duration: `${duration}ms`
909
989
  });
910
990
  } catch (error) {
@@ -912,6 +992,7 @@ class Server {
912
992
  const errorMessage = error instanceof Error ? error.message : String(error);
913
993
  console.error('[Server] refreshOrdersInBackground 失败:', error);
914
994
  this.logError('refreshOrdersInBackground 失败', {
995
+ trigger,
915
996
  duration: `${duration}ms`,
916
997
  error: errorMessage
917
998
  });
@@ -1448,7 +1529,7 @@ class Server {
1448
1529
  // timerIds.push(timerId);
1449
1530
  // }
1450
1531
  setInterval(() => {
1451
- const currentTime = (0, _dayjs.default)().format('hh:mm');
1532
+ const currentTime = (0, _dayjs.default)().format('HH:mm');
1452
1533
  if (timePoints.includes(currentTime)) {
1453
1534
  this.onScheduleTimePointTimerFire(subscriberId);
1454
1535
  }
@@ -3507,14 +3588,16 @@ class Server {
3507
3588
  };
3508
3589
  const needsShopOrderNumber = next.shop_order_number === undefined || next.shop_order_number === null || next.shop_order_number === '';
3509
3590
  const needsShopFullOrderNumber = next.shop_full_order_number === undefined || next.shop_full_order_number === null || next.shop_full_order_number === '';
3510
- if (!needsShopOrderNumber && !needsShopFullOrderNumber) return next;
3591
+ if (!needsShopOrderNumber && !needsShopFullOrderNumber) {
3592
+ return this.finalizeCheckoutSalesNotes(next, title);
3593
+ }
3511
3594
  const idGenerator = this.getIdGeneratorPlugin();
3512
3595
  if (!idGenerator) {
3513
3596
  this.logWarning(`${title}: idGenerator 插件不可用,无法生成订单号`, {
3514
3597
  needsShopOrderNumber,
3515
3598
  needsShopFullOrderNumber
3516
3599
  });
3517
- return next;
3600
+ return this.finalizeCheckoutSalesNotes(next, title);
3518
3601
  }
3519
3602
  try {
3520
3603
  if (needsShopOrderNumber && idGenerator.generateShopOrderNumber) {
@@ -3536,7 +3619,38 @@ class Server {
3536
3619
  error: error instanceof Error ? error.message : String(error)
3537
3620
  });
3538
3621
  }
3539
- return next;
3622
+ return this.finalizeCheckoutSalesNotes(next, title);
3623
+ }
3624
+
3625
+ /**
3626
+ * Notes 允许在本地草稿阶段暂存空订单目标;checkout 的统一出口负责用最终
3627
+ * shop_order_number 完成绑定并校验,保证后端请求、pending、设备任务和打印
3628
+ * 都使用同一份完整快照。
3629
+ */
3630
+ finalizeCheckoutSalesNotes(data, title) {
3631
+ if (!data || typeof data !== 'object') return data;
3632
+ if (!Object.prototype.hasOwnProperty.call(data, 'notes')) return data;
3633
+ if (data.notes === undefined || data.notes === null) return data;
3634
+ if (!Array.isArray(data.notes)) {
3635
+ throw new Error('[Order Notes] checkout notes 必须为数组');
3636
+ }
3637
+ if (data.notes.length === 0) return data;
3638
+ const productTargetUuids = (Array.isArray(data.products) ? data.products : []).map(product => product?.metadata?.unique_identification_number ?? product?.unique_identification_number).filter(targetUuid => targetUuid !== undefined && targetUuid !== null && String(targetUuid).trim().length > 0).map(targetUuid => String(targetUuid));
3639
+ const shopOrderNumber = String(data.shop_order_number ?? '').trim();
3640
+ const notes = (0, _salesNotes.finalizeSalesNotesForCheckout)({
3641
+ notes: data.notes,
3642
+ shopOrderNumber,
3643
+ productTargetUuids
3644
+ });
3645
+ this.logInfo(`${title}: checkout Notes 已绑定订单号`, {
3646
+ shop_order_number: shopOrderNumber,
3647
+ note_count: notes.length
3648
+ });
3649
+ return {
3650
+ ...data,
3651
+ shop_order_number: shopOrderNumber,
3652
+ notes
3653
+ };
3540
3654
  }
3541
3655
  async dispatchCheckoutSyncTask(params) {
3542
3656
  this.registerDeviceTaskActions();
@@ -3593,7 +3707,8 @@ class Server {
3593
3707
  order_id: printIdentity,
3594
3708
  small_ticket_data: params.syncedOrder?.payment_info?.small_ticket_data,
3595
3709
  skip_wristband: true,
3596
- skip_voucher: true
3710
+ skip_voucher: true,
3711
+ skip_discount: true
3597
3712
  } : {
3598
3713
  order_id: printIdentity,
3599
3714
  machine_code_print_info: params.syncedOrder?.payment_info?.machine_code_print_info,
@@ -5387,12 +5502,6 @@ class Server {
5387
5502
  strategy_context
5388
5503
  } = context;
5389
5504
  const queryIds = Array.isArray(ids) ? ids.map(id => Number(id)).filter(id => Number.isFinite(id)) : [];
5390
- const formatterContext = this.buildProductFormatterContext({
5391
- schedule_date,
5392
- schedule_datetime,
5393
- customer_id,
5394
- strategy_context
5395
- });
5396
5505
  this.logInfo('computeProductQueryResult 开始', {
5397
5506
  menuListIdsCount: menu_list_ids?.length ?? 0,
5398
5507
  ids: queryIds,
@@ -5404,6 +5513,23 @@ class Server {
5404
5513
  strategyContextKeys: strategy_context ? Object.keys(strategy_context) : [],
5405
5514
  changedIds: options?.changedIds
5406
5515
  });
5516
+
5517
+ // 预渲染可能在 PisellOS 顺序 preload 尚未完成时发起商品查询。此时 Menu/Schedule
5518
+ // 暂时为空不代表缓存失效;等待当前 preload 后再按原逻辑查询,避免并发远端刷新先 clear
5519
+ // SQLite/IndexDB,造成 preload 读到空数据并重复请求。正常运行期该字段为 null,不增加等待。
5520
+ const startupPreloadTask = this.startupModulePreloadInFlight;
5521
+ if (startupPreloadTask) {
5522
+ await startupPreloadTask;
5523
+ }
5524
+
5525
+ // Menu/Schedule preload 会替换 store 中的数组;必须在等待完成后再读取,
5526
+ // 避免 formatterContext 持有预加载前的空数组快照。
5527
+ const formatterContext = this.buildProductFormatterContext({
5528
+ schedule_date,
5529
+ schedule_datetime,
5530
+ customer_id,
5531
+ strategy_context
5532
+ });
5407
5533
  if (!this.products) {
5408
5534
  this.logError('computeProductQueryResult: Products 模块未注册');
5409
5535
  return {
@@ -5616,6 +5742,8 @@ class Server {
5616
5742
  schedule_datetime: context.schedule_datetime,
5617
5743
  customer_id: context.customer_id,
5618
5744
  strategy_context: context.strategy_context,
5745
+ // 固定本次商品查询使用的语言,确保格式化与缓存使用同一份 locale 快照。
5746
+ locale: this.core.context?.locale,
5619
5747
  menuList: this.menu?.getMenuList?.() || [],
5620
5748
  scheduleList: scheduleModule?.getScheduleList?.() || [],
5621
5749
  dataVariantEvaluator: this.core.context?.dataVariantEvaluator
@@ -10,6 +10,7 @@ export declare class FloorPlanModule extends BaseModule implements Module {
10
10
  private store;
11
11
  private request;
12
12
  private logger;
13
+ private dbManager;
13
14
  private floorPlanDataSource;
14
15
  private pendingSyncMessages;
15
16
  private syncTimer?;
@@ -28,6 +29,9 @@ export declare class FloorPlanModule extends BaseModule implements Module {
28
29
  private mergeItems;
29
30
  private removeByIds;
30
31
  private loadListFromServer;
32
+ private loadListFromSQLite;
33
+ private saveListToSQLite;
34
+ private refreshListFromServer;
31
35
  fetchByIdFromServer(id: number | string): Promise<FloorPlanItem | null>;
32
36
  fetchByCodeFromServer(code: string): Promise<FloorPlanItem | null>;
33
37
  preload(): Promise<void>;
@@ -7,6 +7,7 @@ exports.FloorPlanModule = void 0;
7
7
  var _BaseModule = require("../../../modules/BaseModule");
8
8
  var _types = require("./types");
9
9
  const FLOOR_PLAN_SYNC_DEBOUNCE_MS = 3000;
10
+ const FLOOR_PLAN_STORE_NAME = 'floor_plans';
10
11
 
11
12
  /**
12
13
  * 平面图模块:预加载列表、pubsub 同步、供 OS Server 路由读本地 store
@@ -17,6 +18,7 @@ class FloorPlanModule extends _BaseModule.BaseModule {
17
18
  store;
18
19
  request;
19
20
  logger;
21
+ dbManager;
20
22
  floorPlanDataSource;
21
23
  pendingSyncMessages = [];
22
24
  syncTimer;
@@ -40,6 +42,7 @@ class FloorPlanModule extends _BaseModule.BaseModule {
40
42
  if (appPlugin) {
41
43
  const app = appPlugin.getApp();
42
44
  this.logger = app.logger;
45
+ this.dbManager = app.sqlite || app.dbManager;
43
46
  }
44
47
  this.initFloorPlanDataSource();
45
48
  this.setupFloorPlanSync();
@@ -147,6 +150,45 @@ class FloorPlanModule extends _BaseModule.BaseModule {
147
150
  return [];
148
151
  }
149
152
  }
153
+ async loadListFromSQLite() {
154
+ if (!this.dbManager) return [];
155
+ try {
156
+ const list = await this.dbManager.getAll(FLOOR_PLAN_STORE_NAME);
157
+ return Array.isArray(list) ? list : [];
158
+ } catch (error) {
159
+ this.logWarning('从 SQLite 读取平面图失败', {
160
+ error: error instanceof Error ? error.message : String(error)
161
+ });
162
+ return [];
163
+ }
164
+ }
165
+ async saveListToSQLite(list) {
166
+ if (!this.dbManager) return;
167
+ try {
168
+ await this.dbManager.clear(FLOOR_PLAN_STORE_NAME);
169
+ if (list.length > 0) {
170
+ await this.dbManager.bulkAdd(FLOOR_PLAN_STORE_NAME, list);
171
+ }
172
+ } catch (error) {
173
+ this.logWarning('保存平面图到 SQLite 失败', {
174
+ count: list.length,
175
+ error: error instanceof Error ? error.message : String(error)
176
+ });
177
+ }
178
+ }
179
+ async refreshListFromServer() {
180
+ const list = await this.loadListFromServer();
181
+ if (list.length === 0) {
182
+ this.logWarning('远端平面图刷新完成但列表为空');
183
+ return;
184
+ }
185
+ this.replaceStore(list);
186
+ await this.saveListToSQLite(list);
187
+ this.core.effects.emit(_types.FloorPlanHooks.onFloorPlansChanged, this.getSortedList());
188
+ this.logInfo('远端平面图刷新完成', {
189
+ count: list.length
190
+ });
191
+ }
150
192
  async fetchByIdFromServer(id) {
151
193
  try {
152
194
  const res = await this.request.get(`/schedule/floor-plan/${id}`, {}, {
@@ -174,16 +216,25 @@ class FloorPlanModule extends _BaseModule.BaseModule {
174
216
  }
175
217
  async preload() {
176
218
  this.logInfo('开始预加载平面图列表');
177
- const list = await this.loadListFromServer();
178
- if (list.length > 0) {
179
- this.replaceStore(list);
219
+ const cachedList = await this.loadListFromSQLite();
220
+ if (cachedList.length > 0) {
221
+ this.replaceStore(cachedList);
180
222
  this.core.effects.emit(_types.FloorPlanHooks.onFloorPlansChanged, this.getSortedList());
181
- this.logInfo('预加载完成', {
182
- count: list.length
223
+ this.logInfo('预加载完成(SQLite)', {
224
+ count: cachedList.length
183
225
  });
184
- } else {
185
- this.logWarning('预加载完成但列表为空');
226
+
227
+ // 二次启动立即使用本地平面图,远端刷新完成后自动更新 store 与订阅者。
228
+ void this.refreshListFromServer().catch(error => {
229
+ this.logWarning('后台刷新平面图失败,继续使用本地数据', {
230
+ error: error instanceof Error ? error.message : String(error)
231
+ });
232
+ });
233
+ return;
186
234
  }
235
+
236
+ // 首次启动没有缓存时仍等待一次远端请求,并为后续启动建立缓存。
237
+ await this.refreshListFromServer();
187
238
  }
188
239
  initFloorPlanDataSource() {
189
240
  const Ctor = this.core.serverOptions?.All_DATA_SOURCES?.FloorPlanDataSource;
@@ -282,6 +333,7 @@ class FloorPlanModule extends _BaseModule.BaseModule {
282
333
  }
283
334
  }
284
335
  if (changed) {
336
+ await this.saveListToSQLite(this.store.list);
285
337
  this.core.effects.emit(_types.FloorPlanHooks.onFloorPlansChanged, this.getSortedList());
286
338
  this.core.effects.emit(_types.FloorPlanHooks.onFloorPlanSyncCompleted, null);
287
339
  }
@@ -290,6 +342,7 @@ class FloorPlanModule extends _BaseModule.BaseModule {
290
342
  this.store.list = [];
291
343
  this.store.map.clear();
292
344
  this.store.codeMap.clear();
345
+ await this.saveListToSQLite([]);
293
346
  }
294
347
  destroy() {
295
348
  if (this.syncTimer) {
@@ -190,17 +190,20 @@ class MenuModule extends _BaseModule.BaseModule {
190
190
  this.logWarning('loadMenuFromIndexDB: dbManager 不可用');
191
191
  return [];
192
192
  }
193
+ const startedAt = performance.now();
193
194
  try {
194
195
  // 直接获取所有餐牌记录
195
196
  const menuList = await this.dbManager.getAll(INDEXDB_STORE_NAME);
196
197
  this.logInfo('从 IndexDB 加载餐牌数据', {
197
- menuCount: menuList?.length ?? 0
198
+ menuCount: menuList?.length ?? 0,
199
+ durationMs: +(performance.now() - startedAt).toFixed(2)
198
200
  });
199
201
  return menuList || [];
200
202
  } catch (error) {
201
203
  const errorMessage = error instanceof Error ? error.message : String(error);
202
204
  console.error('[Menu] 从 IndexDB 读取数据失败:', error);
203
205
  this.logError('从 IndexDB 读取数据失败', {
206
+ durationMs: +(performance.now() - startedAt).toFixed(2),
204
207
  error: errorMessage
205
208
  });
206
209
  return [];
@@ -216,24 +219,43 @@ class MenuModule extends _BaseModule.BaseModule {
216
219
  this.logWarning('saveMenuToIndexDB: dbManager 不可用');
217
220
  return;
218
221
  }
222
+ const startedAt = performance.now();
223
+ let clearDurationMs = 0;
224
+ let writeDurationMs = 0;
219
225
  this.logInfo('开始保存餐牌数据到 IndexDB', {
220
226
  menuCount: menuList.length
221
227
  });
222
228
  try {
223
229
  // 先清空现有的餐牌数据
224
- await this.dbManager.clear(INDEXDB_STORE_NAME);
230
+ const clearStartedAt = performance.now();
231
+ try {
232
+ await this.dbManager.clear(INDEXDB_STORE_NAME);
233
+ } finally {
234
+ clearDurationMs = +(performance.now() - clearStartedAt).toFixed(2);
235
+ }
225
236
 
226
237
  // 逐个保存餐牌(每个餐牌是独立的记录)
238
+ const writeStartedAt = performance.now();
227
239
  const savePromises = menuList.map(menu => this.dbManager.add(INDEXDB_STORE_NAME, menu));
228
- await Promise.all(savePromises);
240
+ try {
241
+ await Promise.all(savePromises);
242
+ } finally {
243
+ writeDurationMs = +(performance.now() - writeStartedAt).toFixed(2);
244
+ }
229
245
  this.logInfo('餐牌数据已保存到 IndexDB', {
230
- menuCount: menuList.length
246
+ menuCount: menuList.length,
247
+ clearDurationMs,
248
+ writeDurationMs,
249
+ totalDurationMs: +(performance.now() - startedAt).toFixed(2)
231
250
  });
232
251
  } catch (error) {
233
252
  const errorMessage = error instanceof Error ? error.message : String(error);
234
253
  console.error('[Menu] 保存数据到 IndexDB 失败:', error);
235
254
  this.logError('保存数据到 IndexDB 失败', {
236
255
  menuCount: menuList.length,
256
+ clearDurationMs,
257
+ writeDurationMs,
258
+ totalDurationMs: +(performance.now() - startedAt).toFixed(2),
237
259
  error: errorMessage
238
260
  });
239
261
  }
@@ -127,14 +127,23 @@ export declare class OrderModule extends BaseModule implements Module {
127
127
  payment: Record<string, any>;
128
128
  previousPayment: Record<string, any>;
129
129
  } | null>;
130
- loadOrdersByServer(): Promise<OrderData[]>;
130
+ loadOrdersByServer(options?: {
131
+ trigger?: string;
132
+ }): Promise<OrderData[]>;
131
133
  /**
132
134
  * 静默全量刷新:后台重新拉取全量 SSE 订单数据并更新本地
133
135
  * 拿到完整数据后一次性替换 store,触发订单变更与同步完成事件
134
136
  * @returns 刷新后的订单列表
135
137
  */
136
- silentRefresh(): Promise<OrderData[]>;
138
+ silentRefresh(options?: {
139
+ trigger?: string;
140
+ }): Promise<OrderData[]>;
137
141
  private normalizeOrderForMemoryList;
142
+ /**
143
+ * 将已经完成集合规范化的订单转换为内存身份,不重复扫描 products/bookings/payments。
144
+ */
145
+ private prepareNormalizedOrderForMemoryList;
146
+ private prepareNormalizedOrdersForMemoryList;
138
147
  private normalizeOrdersForMemoryList;
139
148
  private hasRealOrderId;
140
149
  private shouldKeepOrderInMemory;
@@ -299,13 +308,14 @@ export declare class OrderModule extends BaseModule implements Module {
299
308
  private logDuplicateOrders;
300
309
  private compactOrderListByIdentity;
301
310
  /**
302
- * 当前订单拉取窗口:
303
- * - 优先使用 createdAtQuery.sales_time_between(已按营业日边界初始化)
304
- * - 无法解析时退化为自然日窗口
311
+ * 当前订单缓存可复用窗口。
312
+ * start_time 模式使用精确的营业日起止时间;配置缺失或异常时退化为自然日。
305
313
  */
306
- private getCurrentPullWindow;
314
+ private getCurrentOrderCacheWindow;
307
315
  private hasPulledOrdersToday;
308
316
  private markOrderPulledAtNow;
317
+ private clearOrderSnapshotMarker;
318
+ private getCurrentShopId;
309
319
  private getStorageItem;
310
320
  private setStorageItem;
311
321
  private loadOrdersFromSQLite;
@@ -343,7 +353,7 @@ export declare class OrderModule extends BaseModule implements Module {
343
353
  * 用于 SSE 全量拉取与营业日窗口裁剪。
344
354
  *
345
355
  * @example
346
- * await this.replaceOrdersSnapshotInSQLite(orderList)
356
+ * const { orders, persisted } = await this.replaceOrdersSnapshotInSQLite(orderList, 'refresh')
347
357
  */
348
358
  private replaceOrdersSnapshotInSQLite;
349
359
  /**