@pisell/pisellos 0.0.568 → 0.0.569

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 (413) hide show
  1. package/dist/model/strategy/adapter/promotion/evaluator.js +3 -2
  2. package/dist/model/strategy/adapter/walletPass/utils.js +7 -4
  3. package/dist/modules/BaseModule.d.ts +2 -2
  4. package/dist/modules/BaseModule.js +4 -4
  5. package/dist/modules/BookingContext/index.d.ts +48 -0
  6. package/dist/modules/BookingContext/index.js +566 -0
  7. package/dist/modules/BookingContext/types.d.ts +102 -0
  8. package/dist/modules/BookingContext/types.js +51 -0
  9. package/dist/modules/BookingContext/utils/buildCacheItemFromOrderLine.d.ts +16 -0
  10. package/dist/modules/BookingContext/utils/buildCacheItemFromOrderLine.js +302 -0
  11. package/dist/modules/BookingContext/utils/buildNormalProductCacheItemFromOrderLine.d.ts +8 -0
  12. package/dist/modules/BookingContext/utils/buildNormalProductCacheItemFromOrderLine.js +149 -0
  13. package/dist/modules/BookingContext/utils/cacheItemToBookingInput.d.ts +76 -0
  14. package/dist/modules/BookingContext/utils/cacheItemToBookingInput.js +249 -0
  15. package/dist/modules/BookingContext/utils/flexible.d.ts +28 -0
  16. package/dist/modules/BookingContext/utils/flexible.js +56 -0
  17. package/dist/modules/BookingContext/utils/formatResourceList.d.ts +8 -0
  18. package/dist/modules/BookingContext/utils/formatResourceList.js +38 -0
  19. package/dist/modules/BookingContext/utils/index.d.ts +11 -0
  20. package/dist/modules/BookingContext/utils/index.js +11 -0
  21. package/dist/modules/BookingContext/utils/noResource.d.ts +13 -0
  22. package/dist/modules/BookingContext/utils/noResource.js +77 -0
  23. package/dist/modules/BookingContext/utils/orderLineDisplay.d.ts +1 -0
  24. package/dist/modules/BookingContext/utils/orderLineDisplay.js +36 -0
  25. package/dist/modules/BookingContext/utils/productExtend.d.ts +72 -0
  26. package/dist/modules/BookingContext/utils/productExtend.js +339 -0
  27. package/dist/modules/BookingContext/utils/resourceErrors.d.ts +41 -0
  28. package/dist/modules/BookingContext/utils/resourceErrors.js +74 -0
  29. package/dist/modules/BookingContext/utils/resourceSelection.d.ts +7 -0
  30. package/dist/modules/BookingContext/utils/resourceSelection.js +24 -0
  31. package/dist/modules/BookingContext/utils/resources.d.ts +80 -0
  32. package/dist/modules/BookingContext/utils/resources.js +486 -0
  33. package/dist/modules/BookingContext/utils/serviceTimes.d.ts +55 -0
  34. package/dist/modules/BookingContext/utils/serviceTimes.js +151 -0
  35. package/dist/modules/BookingContext/utils/timeSlices.d.ts +42 -0
  36. package/dist/modules/BookingContext/utils/timeSlices.js +100 -0
  37. package/dist/modules/Cart/utils/cartProduct.js +26 -5
  38. package/dist/modules/Cart/utils/changePrice.js +27 -20
  39. package/dist/modules/Customer/index.d.ts +20 -0
  40. package/dist/modules/Customer/index.js +172 -83
  41. package/dist/modules/Customer/types.d.ts +2 -0
  42. package/dist/modules/Discount/index.d.ts +2 -1
  43. package/dist/modules/Discount/index.js +37 -9
  44. package/dist/modules/Discount/types.d.ts +1 -0
  45. package/dist/modules/OpenData/index.d.ts +8 -0
  46. package/dist/modules/OpenData/index.js +36 -16
  47. package/dist/modules/Order/index.d.ts +448 -23
  48. package/dist/modules/Order/index.js +5417 -561
  49. package/dist/modules/Order/types.d.ts +693 -88
  50. package/dist/modules/Order/types.js +68 -1
  51. package/dist/modules/Order/utils/bundleDiscountHydration.d.ts +5 -0
  52. package/dist/modules/Order/utils/bundleDiscountHydration.js +144 -0
  53. package/dist/modules/Order/utils/manualProductDiscount.d.ts +106 -0
  54. package/dist/modules/Order/utils/manualProductDiscount.js +210 -0
  55. package/dist/modules/Order/utils/orderCollectionIdentity.d.ts +53 -0
  56. package/dist/modules/Order/utils/orderCollectionIdentity.js +405 -0
  57. package/dist/modules/Order/utils.d.ts +167 -18
  58. package/dist/modules/Order/utils.js +960 -165
  59. package/dist/modules/Payment/index.d.ts +10 -9
  60. package/dist/modules/Payment/index.js +465 -703
  61. package/dist/modules/Payment/types.d.ts +40 -16
  62. package/dist/modules/Payment/utils.d.ts +1 -1
  63. package/dist/modules/Payment/utils.js +4 -3
  64. package/dist/modules/Payment/walletpass.js +44 -29
  65. package/dist/modules/Product/types.d.ts +29 -7
  66. package/dist/modules/ProductList/index.d.ts +17 -13
  67. package/dist/modules/ProductList/index.js +184 -74
  68. package/dist/modules/ProductList/types.d.ts +17 -0
  69. package/dist/modules/Quotation/index.d.ts +22 -2
  70. package/dist/modules/Quotation/index.js +108 -42
  71. package/dist/modules/Quotation/types.d.ts +19 -0
  72. package/dist/modules/ResourcePlanner/index.d.ts +24 -0
  73. package/dist/modules/ResourcePlanner/index.js +181 -0
  74. package/dist/modules/ResourcePlanner/planner.d.ts +14 -0
  75. package/dist/modules/ResourcePlanner/planner.js +1069 -0
  76. package/dist/modules/ResourcePlanner/types.d.ts +227 -0
  77. package/dist/modules/ResourcePlanner/types.js +1 -0
  78. package/dist/modules/Rules/index.d.ts +8 -10
  79. package/dist/modules/Rules/index.js +221 -109
  80. package/dist/modules/Rules/types.d.ts +14 -3
  81. package/dist/modules/SalesSummary/index.d.ts +23 -26
  82. package/dist/modules/SalesSummary/index.js +152 -46
  83. package/dist/modules/SalesSummary/types.d.ts +9 -1
  84. package/dist/modules/SalesSummary/utils.d.ts +3 -22
  85. package/dist/modules/SalesSummary/utils.js +803 -163
  86. package/dist/modules/Schedule/getDateIsInSchedule.js +14 -7
  87. package/dist/modules/Schedule/index.d.ts +18 -5
  88. package/dist/modules/Schedule/index.js +78 -24
  89. package/dist/modules/Schedule/types.d.ts +2 -0
  90. package/dist/modules/Summary/index.js +4 -3
  91. package/dist/modules/SurchargeList/index.d.ts +18 -0
  92. package/dist/modules/SurchargeList/index.js +185 -0
  93. package/dist/modules/SurchargeList/types.d.ts +15 -0
  94. package/dist/modules/SurchargeList/types.js +1 -0
  95. package/dist/modules/index.d.ts +3 -0
  96. package/dist/modules/index.js +4 -1
  97. package/dist/plugins/app.d.ts +2 -0
  98. package/dist/plugins/request.d.ts +1 -0
  99. package/dist/server/index.d.ts +462 -8
  100. package/dist/server/index.js +6112 -656
  101. package/dist/server/modules/floor-plan/index.d.ts +39 -0
  102. package/dist/server/modules/floor-plan/index.js +595 -0
  103. package/dist/server/modules/floor-plan/types.d.ts +43 -0
  104. package/dist/server/modules/floor-plan/types.js +13 -0
  105. package/dist/server/modules/index.d.ts +7 -2
  106. package/dist/server/modules/index.js +6 -0
  107. package/dist/server/modules/order/index.d.ts +207 -14
  108. package/dist/server/modules/order/index.js +2326 -379
  109. package/dist/server/modules/order/types.d.ts +76 -5
  110. package/dist/server/modules/order/types.js +14 -1
  111. package/dist/server/modules/order/utils/filterBookings.d.ts +7 -1
  112. package/dist/server/modules/order/utils/filterBookings.js +118 -11
  113. package/dist/server/modules/order/utils/filterOrders.js +242 -14
  114. package/dist/server/modules/payment/index.d.ts +28 -0
  115. package/dist/server/modules/payment/index.js +305 -0
  116. package/dist/server/modules/payment/types.d.ts +9 -0
  117. package/dist/server/modules/payment/types.js +1 -0
  118. package/dist/server/modules/products/index.d.ts +105 -14
  119. package/dist/server/modules/products/index.js +1393 -516
  120. package/dist/server/modules/products/types.d.ts +34 -1
  121. package/dist/server/modules/products/types.js +4 -0
  122. package/dist/server/modules/quotation/index.d.ts +39 -0
  123. package/dist/server/modules/quotation/index.js +278 -66
  124. package/dist/server/modules/resource/index.d.ts +2 -3
  125. package/dist/server/modules/resource/index.js +108 -86
  126. package/dist/server/modules/schedule/index.d.ts +6 -4
  127. package/dist/server/modules/schedule/index.js +116 -85
  128. package/dist/server/test.json +713 -0
  129. package/dist/server/types.d.ts +2 -0
  130. package/dist/server/utils/product.js +2 -2
  131. package/dist/server/utils/small-ticket.d.ts +71 -0
  132. package/dist/server/utils/small-ticket.js +963 -0
  133. package/dist/solution/BaseSales/index.d.ts +378 -0
  134. package/dist/solution/BaseSales/index.js +4006 -0
  135. package/dist/solution/BaseSales/types.d.ts +394 -0
  136. package/dist/solution/BaseSales/types.js +40 -0
  137. package/dist/solution/BaseSales/utils/cartPromotion.d.ts +287 -0
  138. package/dist/solution/BaseSales/utils/cartPromotion.js +1450 -0
  139. package/dist/solution/BaseSales/utils/parseSalesResponse.d.ts +144 -0
  140. package/dist/solution/BaseSales/utils/parseSalesResponse.js +157 -0
  141. package/dist/solution/BaseSales/utils/quotationPrice.d.ts +18 -0
  142. package/dist/solution/BaseSales/utils/quotationPrice.js +250 -0
  143. package/dist/solution/BaseSales/utils/transformBaseProductToOrderProduct.d.ts +63 -0
  144. package/dist/solution/BaseSales/utils/transformBaseProductToOrderProduct.js +218 -0
  145. package/dist/solution/BaseSales/utils.d.ts +40 -0
  146. package/dist/solution/BaseSales/utils.js +263 -0
  147. package/dist/solution/BookingByStep/index.d.ts +1 -1
  148. package/dist/solution/BookingTicket/index.d.ts +394 -25
  149. package/dist/solution/BookingTicket/index.js +2506 -443
  150. package/dist/solution/BookingTicket/types.d.ts +191 -6
  151. package/dist/solution/BookingTicket/types.js +83 -10
  152. package/dist/solution/BookingTicket/utils/addProductDecision.d.ts +117 -0
  153. package/dist/solution/BookingTicket/utils/addProductDecision.js +346 -0
  154. package/dist/solution/BookingTicket/utils/addTimeAvailability.d.ts +35 -0
  155. package/dist/solution/BookingTicket/utils/addTimeAvailability.js +95 -0
  156. package/dist/solution/BookingTicket/utils/bookingStatus.d.ts +77 -0
  157. package/dist/solution/BookingTicket/utils/bookingStatus.js +136 -0
  158. package/dist/solution/BookingTicket/utils/cartView.d.ts +29 -0
  159. package/dist/solution/BookingTicket/utils/cartView.js +209 -0
  160. package/dist/solution/BookingTicket/utils/exampleData.d.ts +123 -0
  161. package/dist/solution/BookingTicket/utils/exampleData.js +183 -0
  162. package/dist/solution/BookingTicket/utils/orderCustomer.d.ts +4 -0
  163. package/dist/solution/BookingTicket/utils/orderCustomer.js +31 -0
  164. package/dist/solution/BookingTicket/utils/resolveBestAddTimePlan.d.ts +185 -0
  165. package/dist/solution/BookingTicket/utils/resolveBestAddTimePlan.js +438 -0
  166. package/dist/solution/BookingTicket/utils/scan/applyGlobalScan.d.ts +36 -0
  167. package/dist/solution/BookingTicket/utils/scan/applyGlobalScan.js +400 -0
  168. package/dist/solution/BookingTicket/utils/scan/cloudSearch.d.ts +4 -0
  169. package/dist/solution/BookingTicket/utils/scan/cloudSearch.js +41 -1
  170. package/dist/solution/BookingTicket/utils/scan/formatGlobalScan.d.ts +32 -0
  171. package/dist/solution/BookingTicket/utils/scan/formatGlobalScan.js +56 -0
  172. package/dist/solution/BookingTicket/utils/scan/handleScan.d.ts +3 -1
  173. package/dist/solution/BookingTicket/utils/scan/handleScan.js +11 -3
  174. package/dist/solution/BookingTicket/utils/scan/index.d.ts +5 -0
  175. package/dist/solution/BookingTicket/utils/scan/index.js +20 -8
  176. package/dist/solution/BookingTicket/utils/scan/scanProductValues.d.ts +21 -0
  177. package/dist/solution/BookingTicket/utils/scan/scanProductValues.js +150 -0
  178. package/dist/solution/BookingTicket/utils/sessionCatalogStale.d.ts +23 -0
  179. package/dist/solution/BookingTicket/utils/sessionCatalogStale.js +78 -0
  180. package/dist/solution/Checkout/index.d.ts +1 -0
  181. package/dist/solution/Checkout/index.js +25 -34
  182. package/dist/solution/Checkout/utils/index.js +4 -4
  183. package/dist/solution/RegisterAndLogin/config.js +2 -10
  184. package/dist/solution/Sales/index.d.ts +50 -5
  185. package/dist/solution/Sales/index.js +484 -92
  186. package/dist/solution/Sales/types.d.ts +26 -2
  187. package/dist/solution/ScanOrder/index.d.ts +27 -21
  188. package/dist/solution/ScanOrder/index.js +499 -434
  189. package/dist/solution/ScanOrder/types.d.ts +33 -11
  190. package/dist/solution/ScanOrder/types.js +4 -5
  191. package/dist/solution/ScanOrder/utils.d.ts +4 -1
  192. package/dist/solution/ScanOrder/utils.js +109 -34
  193. package/dist/solution/ShopDiscount/index.js +4 -4
  194. package/dist/solution/UnifiedBookingSales/index.d.ts +183 -0
  195. package/dist/solution/UnifiedBookingSales/index.js +1871 -0
  196. package/dist/solution/UnifiedBookingSales/types.d.ts +143 -0
  197. package/dist/solution/UnifiedBookingSales/types.js +11 -0
  198. package/dist/solution/VenueBooking/index.d.ts +29 -20
  199. package/dist/solution/VenueBooking/index.js +301 -213
  200. package/dist/solution/VenueBooking/sales-section-4-annotated.json +0 -2
  201. package/dist/solution/VenueBooking/utils/slotMerge.d.ts +6 -0
  202. package/dist/solution/VenueBooking/utils/slotMerge.js +9 -3
  203. package/dist/solution/index.d.ts +2 -0
  204. package/dist/solution/index.js +3 -1
  205. package/dist/types/index.d.ts +11 -1
  206. package/lib/model/strategy/adapter/promotion/evaluator.js +3 -2
  207. package/lib/model/strategy/adapter/walletPass/utils.js +6 -4
  208. package/lib/modules/BaseModule.d.ts +2 -2
  209. package/lib/modules/BaseModule.js +4 -4
  210. package/lib/modules/BookingContext/index.d.ts +48 -0
  211. package/lib/modules/BookingContext/index.js +390 -0
  212. package/lib/modules/BookingContext/types.d.ts +102 -0
  213. package/lib/modules/BookingContext/types.js +48 -0
  214. package/lib/modules/BookingContext/utils/buildCacheItemFromOrderLine.d.ts +16 -0
  215. package/lib/modules/BookingContext/utils/buildCacheItemFromOrderLine.js +286 -0
  216. package/lib/modules/BookingContext/utils/buildNormalProductCacheItemFromOrderLine.d.ts +8 -0
  217. package/lib/modules/BookingContext/utils/buildNormalProductCacheItemFromOrderLine.js +148 -0
  218. package/lib/modules/BookingContext/utils/cacheItemToBookingInput.d.ts +76 -0
  219. package/lib/modules/BookingContext/utils/cacheItemToBookingInput.js +242 -0
  220. package/lib/modules/BookingContext/utils/flexible.d.ts +28 -0
  221. package/lib/modules/BookingContext/utils/flexible.js +63 -0
  222. package/lib/modules/BookingContext/utils/formatResourceList.d.ts +8 -0
  223. package/lib/modules/BookingContext/utils/formatResourceList.js +35 -0
  224. package/lib/modules/BookingContext/utils/index.d.ts +11 -0
  225. package/lib/modules/BookingContext/utils/index.js +126 -0
  226. package/lib/modules/BookingContext/utils/noResource.d.ts +13 -0
  227. package/lib/modules/BookingContext/utils/noResource.js +78 -0
  228. package/lib/modules/BookingContext/utils/orderLineDisplay.d.ts +1 -0
  229. package/lib/modules/BookingContext/utils/orderLineDisplay.js +29 -0
  230. package/lib/modules/BookingContext/utils/productExtend.d.ts +72 -0
  231. package/lib/modules/BookingContext/utils/productExtend.js +330 -0
  232. package/lib/modules/BookingContext/utils/resourceErrors.d.ts +41 -0
  233. package/lib/modules/BookingContext/utils/resourceErrors.js +80 -0
  234. package/lib/modules/BookingContext/utils/resourceSelection.d.ts +7 -0
  235. package/lib/modules/BookingContext/utils/resourceSelection.js +24 -0
  236. package/lib/modules/BookingContext/utils/resources.d.ts +80 -0
  237. package/lib/modules/BookingContext/utils/resources.js +419 -0
  238. package/lib/modules/BookingContext/utils/serviceTimes.d.ts +55 -0
  239. package/lib/modules/BookingContext/utils/serviceTimes.js +161 -0
  240. package/lib/modules/BookingContext/utils/timeSlices.d.ts +42 -0
  241. package/lib/modules/BookingContext/utils/timeSlices.js +114 -0
  242. package/lib/modules/Cart/utils/cartProduct.js +27 -5
  243. package/lib/modules/Cart/utils/changePrice.js +20 -14
  244. package/lib/modules/Customer/index.d.ts +20 -0
  245. package/lib/modules/Customer/index.js +63 -10
  246. package/lib/modules/Customer/types.d.ts +2 -0
  247. package/lib/modules/Discount/index.d.ts +2 -1
  248. package/lib/modules/Discount/index.js +18 -2
  249. package/lib/modules/Discount/types.d.ts +1 -0
  250. package/lib/modules/OpenData/index.d.ts +8 -0
  251. package/lib/modules/OpenData/index.js +21 -3
  252. package/lib/modules/Order/index.d.ts +448 -23
  253. package/lib/modules/Order/index.js +3641 -308
  254. package/lib/modules/Order/types.d.ts +693 -88
  255. package/lib/modules/Order/types.js +55 -1
  256. package/lib/modules/Order/utils/bundleDiscountHydration.d.ts +5 -0
  257. package/lib/modules/Order/utils/bundleDiscountHydration.js +106 -0
  258. package/lib/modules/Order/utils/manualProductDiscount.d.ts +106 -0
  259. package/lib/modules/Order/utils/manualProductDiscount.js +207 -0
  260. package/lib/modules/Order/utils/orderCollectionIdentity.d.ts +53 -0
  261. package/lib/modules/Order/utils/orderCollectionIdentity.js +353 -0
  262. package/lib/modules/Order/utils.d.ts +167 -18
  263. package/lib/modules/Order/utils.js +974 -138
  264. package/lib/modules/Payment/index.d.ts +10 -9
  265. package/lib/modules/Payment/index.js +30 -124
  266. package/lib/modules/Payment/types.d.ts +40 -16
  267. package/lib/modules/Payment/utils.d.ts +1 -1
  268. package/lib/modules/Payment/utils.js +4 -3
  269. package/lib/modules/Payment/walletpass.js +16 -6
  270. package/lib/modules/Product/types.d.ts +29 -7
  271. package/lib/modules/ProductList/index.d.ts +17 -13
  272. package/lib/modules/ProductList/index.js +92 -51
  273. package/lib/modules/ProductList/types.d.ts +17 -0
  274. package/lib/modules/Quotation/index.d.ts +22 -2
  275. package/lib/modules/Quotation/index.js +76 -20
  276. package/lib/modules/Quotation/types.d.ts +19 -0
  277. package/lib/modules/ResourcePlanner/index.d.ts +24 -0
  278. package/lib/modules/ResourcePlanner/index.js +147 -0
  279. package/lib/modules/ResourcePlanner/planner.d.ts +14 -0
  280. package/lib/modules/ResourcePlanner/planner.js +895 -0
  281. package/lib/modules/ResourcePlanner/types.d.ts +227 -0
  282. package/lib/modules/ResourcePlanner/types.js +5 -0
  283. package/lib/modules/Rules/index.d.ts +8 -10
  284. package/lib/modules/Rules/index.js +152 -60
  285. package/lib/modules/Rules/types.d.ts +14 -3
  286. package/lib/modules/SalesSummary/index.d.ts +23 -26
  287. package/lib/modules/SalesSummary/index.js +84 -16
  288. package/lib/modules/SalesSummary/types.d.ts +9 -1
  289. package/lib/modules/SalesSummary/utils.d.ts +3 -22
  290. package/lib/modules/SalesSummary/utils.js +616 -111
  291. package/lib/modules/Schedule/getDateIsInSchedule.js +14 -7
  292. package/lib/modules/Schedule/index.d.ts +18 -5
  293. package/lib/modules/Schedule/index.js +49 -14
  294. package/lib/modules/Schedule/types.d.ts +2 -0
  295. package/lib/modules/Summary/index.js +2 -2
  296. package/lib/modules/SurchargeList/index.d.ts +18 -0
  297. package/lib/modules/SurchargeList/index.js +95 -0
  298. package/lib/modules/SurchargeList/types.d.ts +15 -0
  299. package/lib/modules/SurchargeList/types.js +5 -0
  300. package/lib/modules/index.d.ts +3 -0
  301. package/lib/modules/index.js +33 -0
  302. package/lib/plugins/app.d.ts +2 -0
  303. package/lib/plugins/request.d.ts +1 -0
  304. package/lib/server/index.d.ts +462 -8
  305. package/lib/server/index.js +3901 -205
  306. package/lib/server/modules/floor-plan/index.d.ts +39 -0
  307. package/lib/server/modules/floor-plan/index.js +306 -0
  308. package/lib/server/modules/floor-plan/types.d.ts +43 -0
  309. package/lib/server/modules/floor-plan/types.js +19 -0
  310. package/lib/server/modules/index.d.ts +7 -2
  311. package/lib/server/modules/index.js +21 -0
  312. package/lib/server/modules/order/index.d.ts +207 -14
  313. package/lib/server/modules/order/index.js +1324 -141
  314. package/lib/server/modules/order/types.d.ts +76 -5
  315. package/lib/server/modules/order/types.js +11 -1
  316. package/lib/server/modules/order/utils/filterBookings.d.ts +7 -1
  317. package/lib/server/modules/order/utils/filterBookings.js +119 -11
  318. package/lib/server/modules/order/utils/filterOrders.js +238 -13
  319. package/lib/server/modules/payment/index.d.ts +28 -0
  320. package/lib/server/modules/payment/index.js +168 -0
  321. package/lib/server/modules/payment/types.d.ts +9 -0
  322. package/lib/server/modules/payment/types.js +5 -0
  323. package/lib/server/modules/products/index.d.ts +105 -14
  324. package/lib/server/modules/products/index.js +588 -97
  325. package/lib/server/modules/products/types.d.ts +34 -1
  326. package/lib/server/modules/products/types.js +3 -0
  327. package/lib/server/modules/quotation/index.d.ts +39 -0
  328. package/lib/server/modules/quotation/index.js +145 -1
  329. package/lib/server/modules/resource/index.d.ts +2 -3
  330. package/lib/server/modules/resource/index.js +71 -45
  331. package/lib/server/modules/schedule/index.d.ts +6 -4
  332. package/lib/server/modules/schedule/index.js +55 -44
  333. package/lib/server/test.json +713 -0
  334. package/lib/server/types.d.ts +2 -0
  335. package/lib/server/utils/product.js +2 -2
  336. package/lib/server/utils/small-ticket.d.ts +71 -0
  337. package/lib/server/utils/small-ticket.js +926 -0
  338. package/lib/solution/BaseSales/index.d.ts +378 -0
  339. package/lib/solution/BaseSales/index.js +2367 -0
  340. package/lib/solution/BaseSales/types.d.ts +394 -0
  341. package/lib/solution/BaseSales/types.js +47 -0
  342. package/lib/solution/BaseSales/utils/cartPromotion.d.ts +287 -0
  343. package/lib/solution/BaseSales/utils/cartPromotion.js +1155 -0
  344. package/lib/solution/BaseSales/utils/parseSalesResponse.d.ts +144 -0
  345. package/lib/solution/BaseSales/utils/parseSalesResponse.js +140 -0
  346. package/lib/solution/BaseSales/utils/quotationPrice.d.ts +18 -0
  347. package/lib/solution/BaseSales/utils/quotationPrice.js +231 -0
  348. package/lib/solution/BaseSales/utils/transformBaseProductToOrderProduct.d.ts +63 -0
  349. package/lib/solution/BaseSales/utils/transformBaseProductToOrderProduct.js +215 -0
  350. package/lib/solution/BaseSales/utils.d.ts +40 -0
  351. package/lib/solution/BaseSales/utils.js +259 -0
  352. package/lib/solution/BookingByStep/index.d.ts +1 -1
  353. package/lib/solution/BookingTicket/index.d.ts +394 -25
  354. package/lib/solution/BookingTicket/index.js +1350 -76
  355. package/lib/solution/BookingTicket/types.d.ts +191 -6
  356. package/lib/solution/BookingTicket/types.js +71 -10
  357. package/lib/solution/BookingTicket/utils/addProductDecision.d.ts +117 -0
  358. package/lib/solution/BookingTicket/utils/addProductDecision.js +353 -0
  359. package/lib/solution/BookingTicket/utils/addTimeAvailability.d.ts +35 -0
  360. package/lib/solution/BookingTicket/utils/addTimeAvailability.js +94 -0
  361. package/lib/solution/BookingTicket/utils/bookingStatus.d.ts +77 -0
  362. package/lib/solution/BookingTicket/utils/bookingStatus.js +136 -0
  363. package/lib/solution/BookingTicket/utils/cartView.d.ts +29 -0
  364. package/lib/solution/BookingTicket/utils/cartView.js +200 -0
  365. package/lib/solution/BookingTicket/utils/exampleData.d.ts +123 -0
  366. package/lib/solution/BookingTicket/utils/exampleData.js +185 -0
  367. package/lib/solution/BookingTicket/utils/orderCustomer.d.ts +4 -0
  368. package/lib/solution/BookingTicket/utils/orderCustomer.js +35 -0
  369. package/lib/solution/BookingTicket/utils/resolveBestAddTimePlan.d.ts +185 -0
  370. package/lib/solution/BookingTicket/utils/resolveBestAddTimePlan.js +387 -0
  371. package/lib/solution/BookingTicket/utils/scan/applyGlobalScan.d.ts +36 -0
  372. package/lib/solution/BookingTicket/utils/scan/applyGlobalScan.js +220 -0
  373. package/lib/solution/BookingTicket/utils/scan/cloudSearch.d.ts +4 -0
  374. package/lib/solution/BookingTicket/utils/scan/cloudSearch.js +24 -3
  375. package/lib/solution/BookingTicket/utils/scan/formatGlobalScan.d.ts +32 -0
  376. package/lib/solution/BookingTicket/utils/scan/formatGlobalScan.js +64 -0
  377. package/lib/solution/BookingTicket/utils/scan/handleScan.d.ts +3 -1
  378. package/lib/solution/BookingTicket/utils/scan/handleScan.js +4 -2
  379. package/lib/solution/BookingTicket/utils/scan/index.d.ts +5 -0
  380. package/lib/solution/BookingTicket/utils/scan/index.js +14 -2
  381. package/lib/solution/BookingTicket/utils/scan/scanProductValues.d.ts +21 -0
  382. package/lib/solution/BookingTicket/utils/scan/scanProductValues.js +115 -0
  383. package/lib/solution/BookingTicket/utils/sessionCatalogStale.d.ts +23 -0
  384. package/lib/solution/BookingTicket/utils/sessionCatalogStale.js +83 -0
  385. package/lib/solution/Checkout/index.d.ts +1 -0
  386. package/lib/solution/Checkout/index.js +18 -19
  387. package/lib/solution/Checkout/utils/index.js +4 -4
  388. package/lib/solution/RegisterAndLogin/config.js +2 -10
  389. package/lib/solution/Sales/index.d.ts +50 -5
  390. package/lib/solution/Sales/index.js +296 -39
  391. package/lib/solution/Sales/types.d.ts +26 -2
  392. package/lib/solution/ScanOrder/index.d.ts +27 -21
  393. package/lib/solution/ScanOrder/index.js +144 -103
  394. package/lib/solution/ScanOrder/types.d.ts +33 -11
  395. package/lib/solution/ScanOrder/types.js +4 -5
  396. package/lib/solution/ScanOrder/utils.d.ts +4 -1
  397. package/lib/solution/ScanOrder/utils.js +98 -26
  398. package/lib/solution/ShopDiscount/index.js +3 -3
  399. package/lib/solution/UnifiedBookingSales/index.d.ts +183 -0
  400. package/lib/solution/UnifiedBookingSales/index.js +1050 -0
  401. package/lib/solution/UnifiedBookingSales/types.d.ts +143 -0
  402. package/lib/solution/UnifiedBookingSales/types.js +14 -0
  403. package/lib/solution/VenueBooking/index.d.ts +29 -20
  404. package/lib/solution/VenueBooking/index.js +169 -94
  405. package/lib/solution/VenueBooking/sales-section-4-annotated.json +0 -2
  406. package/lib/solution/VenueBooking/utils/slotMerge.d.ts +6 -0
  407. package/lib/solution/VenueBooking/utils/slotMerge.js +9 -3
  408. package/lib/solution/index.d.ts +2 -0
  409. package/lib/solution/index.js +22 -0
  410. package/lib/types/index.d.ts +11 -1
  411. package/package.json +1 -1
  412. package/dist/solution/Checkout/appointmentDemo.json +0 -1
  413. package/lib/solution/Checkout/appointmentDemo.json +0 -1
@@ -5,16 +5,17 @@ Object.defineProperty(exports, "__esModule", {
5
5
  });
6
6
  exports.ProductsModule = void 0;
7
7
  var _BaseModule = require("../../../modules/BaseModule");
8
- var _plugins = require("../../../plugins");
8
+ var _Quotation = require("../../../modules/Quotation");
9
9
  var _types = require("./types");
10
10
  var _product = require("../../utils/product");
11
11
  /**
12
12
  * IndexDB 存储名称
13
13
  */
14
14
  const INDEXDB_STORE_NAME = 'products';
15
-
16
15
  /** 商品同步防抖时间(毫秒) */
17
16
  const PRODUCT_SYNC_DEBOUNCE_MS = 10_000;
17
+ /** silentRefresh 最小间隔,避免 preload 后立即全量重拉 */
18
+ const PRODUCT_SILENT_REFRESH_MIN_INTERVAL_MS = 30_000;
18
19
 
19
20
  /**
20
21
  * Products 模块 - 用于获取和管理完整的商品详细数据
@@ -38,13 +39,34 @@ class ProductsModule extends _BaseModule.BaseModule {
38
39
  // 商品格式化器列表
39
40
  formatters = [];
40
41
 
41
- // 是否已注册内置价格格式化器
42
- isPriceFormatterRegistered = false;
42
+ // 是否已注册内置商品格式化器
43
+ isBuiltinFormatterRegistered = false;
44
+ // 旧报价单格式化器默认关闭,仅显式开启时作为兼容兜底注册
45
+ isLegacyPriceFormatterEnabled = false;
46
+
47
+ // 普通层 QuotationModule 桥接器:本地计算报价单价格,替代 /product/query/price
48
+ quotationPriceBridge;
49
+ quotationBridgeChannel;
50
+ quotationBridgeLoadedKey = '';
51
+ quotationBridgeRefreshPromise;
52
+ quotationBridgeRefreshKey;
53
+ quotationScheduleCache = new Map();
54
+ quotationScheduleCacheSource = null;
55
+ quotationScheduleCacheLoaded = false;
43
56
 
44
57
  // ---- 商品数据同步相关 ----
45
58
  productDataSource;
46
59
  pendingSyncMessages = [];
47
60
  syncTimer;
61
+ /** 最近一次 SSE 全量拉取完成时间 */
62
+ lastServerFullFetchAtMs = 0;
63
+ /** 最近一次 preload 完成时间(含 IndexDB 命中) */
64
+ lastPreloadCompletedAtMs = 0;
65
+ /** IndexDB 写入串行队列,避免快照与 patch 并发交错 */
66
+ productIndexDBSaveQueue = Promise.resolve();
67
+
68
+ /** 最近一次智能定价评估提取的 schedule 变价时间点(HH:mm),供 Server 订阅定时刷新使用 */
69
+ lastScheduleTimePoints = [];
48
70
  constructor(name, version) {
49
71
  super(name, version);
50
72
  }
@@ -74,8 +96,8 @@ class ProductsModule extends _BaseModule.BaseModule {
74
96
  }
75
97
  }
76
98
 
77
- // 注册内置的价格格式化器(作为第一个格式化器)
78
- this.registerBuiltinPriceFormatter();
99
+ // 注册内置商品格式化器;智能定价默认替代报价单价格逻辑
100
+ this.registerBuiltinProductFormatters();
79
101
 
80
102
  // 初始化 ProductDataSource 实例
81
103
  this.initProductDataSource();
@@ -152,33 +174,30 @@ class ProductsModule extends _BaseModule.BaseModule {
152
174
  */
153
175
  async loadProductsPrice({
154
176
  ids = [],
155
- customer_id,
156
- schedule_date
177
+ schedule_date,
178
+ schedule_datetime,
179
+ customer_id
157
180
  }) {
158
181
  this.logInfo('开始加载商品价格', {
159
182
  productCount: ids.length,
160
183
  schedule_date,
184
+ schedule_datetime,
161
185
  customer_id
162
186
  });
163
187
  const startTime = Date.now();
188
+ const datetime = schedule_datetime || schedule_date;
164
189
  try {
165
- const productsData = await this.request.post(`/product/query/price`, {
166
- ids,
167
- customer_id,
168
- schedule_date
169
- }, {
170
- cache: {
171
- mode: _plugins.RequestModeENUM.REMOTE_LOCAL,
172
- type: "memory"
173
- }
174
- });
190
+ await this.ensureQuotationBridgeReady(customer_id);
191
+ const idSet = new Set(ids);
192
+ const products = this.getProductsRef().filter(product => idSet.has(product.id));
193
+ const priceData = products.map(product => this.buildPriceDataFromQuotation(product, datetime, customer_id));
175
194
  const duration = Date.now() - startTime;
176
195
  this.logInfo('加载商品价格成功', {
177
196
  productCount: ids.length,
178
- priceDataCount: productsData.data?.length ?? 0,
197
+ priceDataCount: priceData.length,
179
198
  duration: `${duration}ms`
180
199
  });
181
- return productsData.data;
200
+ return priceData;
182
201
  } catch (error) {
183
202
  const duration = Date.now() - startTime;
184
203
  const errorMessage = error instanceof Error ? error.message : String(error);
@@ -191,6 +210,179 @@ class ProductsModule extends _BaseModule.BaseModule {
191
210
  }
192
211
  }
193
212
 
213
+ /**
214
+ * 设置报价单价格桥接器。
215
+ * Server 层负责传入完整 schedule 模块,Products 只负责把报价单计算结果转成价格响应形状。
216
+ */
217
+ async setupQuotationPriceBridge(params) {
218
+ if (!this.core) {
219
+ this.logWarning('setupQuotationPriceBridge: core 尚未初始化');
220
+ return;
221
+ }
222
+ const channel = params.channel || this.otherParams?.channel;
223
+ this.quotationBridgeChannel = channel;
224
+ if (!this.quotationPriceBridge) {
225
+ const quotation = new _Quotation.QuotationModule(`${this.name}_quotation_price_bridge`);
226
+ await quotation.initialize(this.core, {});
227
+ this.quotationPriceBridge = quotation;
228
+ }
229
+ if (params.scheduleModule) {
230
+ if (this.quotationScheduleCacheSource !== params.scheduleModule) {
231
+ this.quotationScheduleCache.clear();
232
+ this.quotationScheduleCacheLoaded = false;
233
+ this.quotationScheduleCacheSource = params.scheduleModule;
234
+ }
235
+ this.quotationPriceBridge.setScheduleResolver(id => {
236
+ const scheduleId = String(id);
237
+ if (this.quotationScheduleCache.has(scheduleId)) {
238
+ return this.quotationScheduleCache.get(scheduleId);
239
+ }
240
+ if (!this.quotationScheduleCacheLoaded) {
241
+ const scheduleList = params.scheduleModule.getScheduleList?.() || [];
242
+ for (const schedule of scheduleList) {
243
+ this.quotationScheduleCache.set(String(schedule.id), schedule);
244
+ }
245
+ this.quotationScheduleCacheLoaded = true;
246
+ }
247
+ if (this.quotationScheduleCache.has(scheduleId)) {
248
+ return this.quotationScheduleCache.get(scheduleId);
249
+ }
250
+ const schedules = params.scheduleModule.getScheduleListByIds?.([id]) || params.scheduleModule.getScheduleByIds?.([id]) || [];
251
+ if (schedules[0]) this.quotationScheduleCache.set(scheduleId, schedules[0]);
252
+ return schedules[0];
253
+ });
254
+ }
255
+ await this.refreshQuotationPriceBridge({
256
+ customer_id: params.customer_id
257
+ });
258
+ }
259
+
260
+ /**
261
+ * 刷新桥接器内的报价单列表。
262
+ */
263
+ async refreshQuotationPriceBridge(params) {
264
+ if (!this.quotationPriceBridge) return;
265
+ const refreshKey = this.getQuotationBridgeScopeKey();
266
+ if (this.quotationBridgeRefreshPromise && this.quotationBridgeRefreshKey === refreshKey) {
267
+ await this.quotationBridgeRefreshPromise;
268
+ return;
269
+ }
270
+ if (this.quotationBridgeRefreshPromise) {
271
+ await this.quotationBridgeRefreshPromise;
272
+ }
273
+ this.quotationBridgeRefreshKey = refreshKey;
274
+ this.quotationBridgeRefreshPromise = this.quotationPriceBridge.loadQuotations({
275
+ channel: this.quotationBridgeChannel
276
+ }).then(() => {
277
+ this.quotationBridgeLoadedKey = refreshKey;
278
+ }).finally(() => {
279
+ this.quotationBridgeRefreshPromise = undefined;
280
+ this.quotationBridgeRefreshKey = undefined;
281
+ });
282
+ await this.quotationBridgeRefreshPromise;
283
+ }
284
+ async ensureQuotationBridgeReady(customerId) {
285
+ if (!this.quotationPriceBridge) {
286
+ await this.setupQuotationPriceBridge({
287
+ channel: this.otherParams?.channel,
288
+ customer_id: customerId
289
+ });
290
+ return;
291
+ }
292
+ if (this.quotationBridgeRefreshPromise) {
293
+ await this.quotationBridgeRefreshPromise;
294
+ }
295
+ if (this.getQuotationBridgeScopeKey() !== this.quotationBridgeLoadedKey) {
296
+ await this.refreshQuotationPriceBridge({
297
+ customer_id: customerId
298
+ });
299
+ }
300
+ }
301
+ getQuotationBridgeScopeKey() {
302
+ return this.quotationBridgeChannel || '';
303
+ }
304
+ getQuotedPrice(params) {
305
+ const quotation = this.quotationPriceBridge;
306
+ if (!quotation) return null;
307
+ const query = {
308
+ productId: params.productId,
309
+ variantId: params.variantId,
310
+ datetime: params.datetime,
311
+ customer_id: params.customerId
312
+ };
313
+ if (typeof quotation.getQuotationMatchForProduct === 'function') {
314
+ return quotation.getQuotationMatchForProduct(query)?.price ?? null;
315
+ }
316
+ if (typeof quotation.getPriceForProduct === 'function') {
317
+ return quotation.getPriceForProduct(query);
318
+ }
319
+ return null;
320
+ }
321
+ buildPriceDataFromQuotation(product, datetime, customerId) {
322
+ const quotedPrice = this.getQuotedPrice({
323
+ productId: product.id,
324
+ datetime,
325
+ customerId
326
+ });
327
+ const productPrice = quotedPrice ?? product.price;
328
+ const productBasePrice = quotedPrice ?? product.base_price ?? product.price ?? 0;
329
+ return {
330
+ id: product.id,
331
+ price: String(productPrice ?? 0),
332
+ base_price: String(productBasePrice),
333
+ variant: this.buildVariantPriceData(product, datetime, customerId),
334
+ bundle_group: this.buildBundleGroupPriceData(product, datetime, customerId)
335
+ };
336
+ }
337
+ buildVariantPriceData(product, datetime, customerId) {
338
+ if (!Array.isArray(product.variant)) return [];
339
+ return product.variant.map(variant => {
340
+ const quotedPrice = this.getQuotedPrice({
341
+ productId: product.id,
342
+ variantId: Number(variant.id),
343
+ datetime,
344
+ customerId
345
+ });
346
+ const price = quotedPrice ?? variant.price ?? product.price ?? 0;
347
+ const basePrice = quotedPrice ?? variant.base_price ?? variant.price ?? product.base_price ?? product.price ?? 0;
348
+ return {
349
+ id: Number(variant.id),
350
+ product_id: Number(variant.product_id ?? product.id),
351
+ base_price: Number(basePrice),
352
+ price: Number(price)
353
+ };
354
+ });
355
+ }
356
+ buildBundleGroupPriceData(product, datetime, customerId) {
357
+ if (!Array.isArray(product.bundle_group)) return [];
358
+ return product.bundle_group.map(group => ({
359
+ id: Number(group.id),
360
+ bundle_item: (group.bundle_item || []).map(item => {
361
+ const bundleProductId = Number(item.bundle_product_id);
362
+ const bundleVariantId = Number(item.bundle_variant_id || 0);
363
+ const quotedPrice = this.getQuotedPrice({
364
+ productId: bundleProductId,
365
+ variantId: bundleVariantId,
366
+ datetime,
367
+ customerId
368
+ });
369
+ const price = quotedPrice ?? item.price ?? 0;
370
+ const basePrice = quotedPrice ?? item.base_price ?? item.price ?? 0;
371
+ return {
372
+ id: Number(item.id),
373
+ product_id: Number(item.product_id ?? product.id),
374
+ group_id: Number(item.group_id ?? group.id),
375
+ bundle_product_id: bundleProductId,
376
+ bundle_variant_id: bundleVariantId,
377
+ price: Number(price),
378
+ base_price: basePrice,
379
+ price_type: item.price_type,
380
+ price_type_ext: item.price_type_ext
381
+ };
382
+ })
383
+ }));
384
+ }
385
+
194
386
  /**
195
387
  * 获取应用了价格的商品列表(带缓存)
196
388
  * 使用日期作为缓存 key,同一天的商品价格会被缓存
@@ -199,12 +391,15 @@ class ProductsModule extends _BaseModule.BaseModule {
199
391
  * @param extraContext 额外的上下文数据(可选,由 Server 层传入)
200
392
  * @param options 可选参数
201
393
  * @param options.changedIds 变更的商品 IDs,非空时仅对这些商品增量执行 prepare 并更新缓存
394
+ * @param options.productIds 指定商品 IDs,仅对这些商品执行价格计算;不传则处理全量
202
395
  * @returns 应用了价格的商品列表
203
396
  */
204
397
  async getProductsWithPrice(schedule_date, extraContext, options) {
205
398
  const t0 = performance.now();
206
- const cacheKey = schedule_date;
207
- const changedIds = options?.changedIds;
399
+ const scopedProductIds = this.normalizeProductIds(options?.productIds);
400
+ const cacheKey = this.getProductsPriceCacheKey(schedule_date, extraContext, scopedProductIds);
401
+ const scopedProductIdSet = scopedProductIds ? new Set(scopedProductIds) : undefined;
402
+ const changedIds = scopedProductIdSet ? options?.changedIds?.filter(id => scopedProductIdSet.has(id)) : options?.changedIds;
208
403
  if (this.productsPriceCache.has(cacheKey)) {
209
404
  const cachedProducts = this.productsPriceCache.get(cacheKey);
210
405
 
@@ -274,9 +469,12 @@ class ProductsModule extends _BaseModule.BaseModule {
274
469
  return cachedProducts;
275
470
  }
276
471
  this.logInfo('商品价格缓存未命中,准备获取', {
277
- cacheKey
472
+ cacheKey,
473
+ scopedProductCount: scopedProductIds?.length
278
474
  });
279
- const result = await this.prepareProductsWithPrice(schedule_date, extraContext);
475
+ const result = await this.prepareProductsWithPrice(schedule_date, extraContext, scopedProductIds ? {
476
+ productIds: scopedProductIds
477
+ } : undefined);
280
478
  this.productsPriceCache.set(cacheKey, result);
281
479
  this.logInfo('商品价格已缓存', {
282
480
  cacheKey,
@@ -302,7 +500,7 @@ class ProductsModule extends _BaseModule.BaseModule {
302
500
  async prepareProductsWithPrice(schedule_date, extraContext, options) {
303
501
  const tTotal = performance.now();
304
502
  const targetIds = options?.productIds;
305
- const isIncremental = targetIds && targetIds.length > 0;
503
+ const isIncremental = Array.isArray(targetIds);
306
504
  this.logInfo('prepareProductsWithPrice 开始处理', {
307
505
  schedule_date,
308
506
  mode: isIncremental ? 'incremental' : 'full',
@@ -313,8 +511,7 @@ class ProductsModule extends _BaseModule.BaseModule {
313
511
  let products;
314
512
  let ids;
315
513
  if (isIncremental) {
316
- const idSet = new Set(targetIds);
317
- products = this.getProductsRef().filter(p => idSet.has(p.id));
514
+ products = this.getProductsRefByIds(targetIds);
318
515
  ids = products.map(p => p.id);
319
516
  } else {
320
517
  products = this.getProductsRef();
@@ -331,22 +528,33 @@ class ProductsModule extends _BaseModule.BaseModule {
331
528
  productCount: products.length
332
529
  });
333
530
 
334
- // 2. 获取价格数据(网络请求)
335
- const tPrice = performance.now();
336
- const priceData = await this.loadProductsPrice({
337
- ids,
338
- schedule_date
339
- });
340
- (0, _product.perfMark)('prepareProducts.loadPrice', performance.now() - tPrice, {
341
- count: ids.length
342
- });
343
- this.logInfo('获取商品报价单价格成功', {
344
- priceDataCount: priceData?.length ?? 0
345
- });
531
+ // 2. 仅在 legacy 报价单 formatter 启用时加载报价单价格;默认由智能定价覆盖商品价格。
532
+ let priceData = [];
533
+ if (this.isLegacyPriceFormatterEnabled) {
534
+ const tPrice = performance.now();
535
+ priceData = await this.loadProductsPrice({
536
+ ids,
537
+ schedule_date,
538
+ schedule_datetime: extraContext?.schedule_datetime,
539
+ customer_id: extraContext?.customer_id
540
+ });
541
+ (0, _product.perfMark)('prepareProducts.loadPrice', performance.now() - tPrice, {
542
+ count: ids.length
543
+ });
544
+ this.logInfo('获取商品报价单价格成功', {
545
+ priceDataCount: priceData?.length ?? 0
546
+ });
547
+ }
346
548
  const context = {
347
549
  schedule_date,
550
+ schedule_datetime: extraContext?.schedule_datetime,
551
+ customer_id: extraContext?.customer_id,
348
552
  priceData,
349
553
  locale: this.core?.context?.locale,
554
+ dataVariantEvaluator: extraContext?.dataVariantEvaluator || this.core?.context?.dataVariantEvaluator,
555
+ menuList: extraContext?.menuList || [],
556
+ scheduleList: extraContext?.scheduleList || [],
557
+ strategy_context: extraContext?.strategy_context,
350
558
  ...extraContext
351
559
  };
352
560
 
@@ -381,6 +589,53 @@ class ProductsModule extends _BaseModule.BaseModule {
381
589
  }
382
590
  return [];
383
591
  }
592
+ getProductsPriceCacheKey(schedule_date, extraContext, productIds) {
593
+ const datetime = extraContext?.schedule_datetime || schedule_date;
594
+ const productScope = productIds ? `:products:${productIds.join(',')}` : '';
595
+ const strategyScope = this.stringifyStable(extraContext?.strategy_context || {});
596
+ return `${datetime}:customer:${extraContext?.customer_id ?? ''}:strategy:${strategyScope}${productScope}`;
597
+ }
598
+
599
+ /**
600
+ * 稳定序列化策略上下文,避免对象 key 顺序不同导致缓存 key 抖动。
601
+ */
602
+ stringifyStable(value) {
603
+ if (value == null) return '';
604
+ if (Array.isArray(value)) return `[${value.map(item => this.stringifyStable(item)).join(',')}]`;
605
+ if (typeof value === 'object') {
606
+ return `{${Object.keys(value).sort().map(key => `${key}:${this.stringifyStable(value[key])}`).join(',')}}`;
607
+ }
608
+ return String(value);
609
+ }
610
+ normalizeProductIds(productIds) {
611
+ if (!Array.isArray(productIds)) return undefined;
612
+ return Array.from(new Set(productIds.map(id => Number(id)).filter(id => Number.isFinite(id)))).sort((a, b) => a - b);
613
+ }
614
+
615
+ /**
616
+ * 按轻量范围返回商品 ID,不做 structuredClone。
617
+ */
618
+ getProductIdsByScope(scope) {
619
+ const productIds = this.normalizeProductIds(scope.productIds) || [];
620
+ const collectionIds = this.normalizeProductIds(scope.collectionIds) || [];
621
+ if (scope.isAllProducts) {
622
+ return this.getProductsRef().map(product => product.id);
623
+ }
624
+ if (productIds.length === 0 && collectionIds.length === 0) return [];
625
+ const result = new Set();
626
+ for (const id of productIds) {
627
+ if (this.store.map.has(id)) result.add(id);
628
+ }
629
+ if (collectionIds.length === 0) return Array.from(result);
630
+ const collectionIdSet = new Set(collectionIds);
631
+ for (const product of this.getProductsRef()) {
632
+ if (result.has(product.id)) continue;
633
+ if (!Array.isArray(product.collection)) continue;
634
+ const isInCollection = product.collection.some(collection => collectionIdSet.has(collection.id));
635
+ if (isInCollection) result.add(product.id);
636
+ }
637
+ return Array.from(result);
638
+ }
384
639
 
385
640
  /**
386
641
  * 应用所有已注册的格式化器
@@ -430,18 +685,46 @@ class ProductsModule extends _BaseModule.BaseModule {
430
685
  }
431
686
 
432
687
  /**
433
- * 注册内置的价格格式化器
434
- * 这个格式化器负责将价格数据应用到商品上
688
+ * 注册内置商品格式化器。
689
+ *
690
+ * 智能定价默认替代报价单逻辑;报价单 formatter 仅在 enableLegacyPriceFormatter=true 时启用。
435
691
  * @private
436
692
  */
437
- registerBuiltinPriceFormatter() {
438
- if (this.isPriceFormatterRegistered) {
439
- console.warn('[ProductsModule] 内置价格格式化器已注册,跳过');
693
+ registerBuiltinProductFormatters() {
694
+ if (this.isBuiltinFormatterRegistered) {
695
+ console.warn('[ProductsModule] 内置商品格式化器已注册,跳过');
440
696
  return;
441
697
  }
698
+ this.isLegacyPriceFormatterEnabled = this.shouldEnableLegacyPriceFormatter();
699
+ const dataVariantFormatter = (products, context) => {
700
+ const evaluator = context.dataVariantEvaluator || this.core?.context?.dataVariantEvaluator;
701
+ if (!evaluator || typeof evaluator.resolveProducts !== 'function') {
702
+ this.lastScheduleTimePoints = [];
703
+ this.logWarning('智能定价评估器未注入,跳过 DataVariant 格式化', {
704
+ productCount: products.length
705
+ });
706
+ return products;
707
+ }
708
+ const strategyContext = context.strategy_context || {};
709
+ const businessData = {
710
+ products,
711
+ scheduleDateTime: context.schedule_datetime || context.schedule_date,
712
+ scheduleList: context.scheduleList || [],
713
+ menuList: context.menuList || [],
714
+ customerId: context.customer_id,
715
+ channel: strategyContext.channel,
716
+ orderType: strategyContext.orderType || strategyContext.order_type,
717
+ businessCode: strategyContext.business_code ?? strategyContext.businessCode,
718
+ availableWalletIds: strategyContext.available_wallet_ids,
719
+ custom: strategyContext
720
+ };
721
+ const result = evaluator.resolveProducts(businessData);
722
+ this.lastScheduleTimePoints = Array.isArray(result?.scheduleTimePoints) ? [...result.scheduleTimePoints] : [];
723
+ return Array.isArray(result?.products) ? result.products : products;
724
+ };
442
725
 
443
- // 创建价格格式化器
444
- const priceFormatter = (products, context) => {
726
+ // 创建 legacy 报价单格式化器
727
+ const legacyPriceFormatter = (products, context) => {
445
728
  if (!context.priceData || context.priceData.length === 0) {
446
729
  return products;
447
730
  }
@@ -453,17 +736,35 @@ class ProductsModule extends _BaseModule.BaseModule {
453
736
  const detailValueFormatter = (products, context) => {
454
737
  return (0, _product.applyDetailValueToProducts)(products, context);
455
738
  };
456
-
457
- // 将价格格式化器注册为第一个格式化器
458
- this.formatters.unshift(priceFormatter);
739
+ if (this.isLegacyPriceFormatterEnabled) {
740
+ this.formatters.push(legacyPriceFormatter);
741
+ }
742
+ this.formatters.push(dataVariantFormatter);
459
743
  this.formatters.push(i18nFormatter);
460
744
  this.formatters.push(detailValueFormatter);
461
- this.isPriceFormatterRegistered = true;
745
+ this.isBuiltinFormatterRegistered = true;
746
+ }
747
+
748
+ /**
749
+ * 是否启用旧报价单价格 formatter。
750
+ *
751
+ * 默认关闭,避免报价单逻辑覆盖智能定价结果。
752
+ */
753
+ shouldEnableLegacyPriceFormatter() {
754
+ return this.otherParams?.enableLegacyPriceFormatter === true || this.core?.context?.enableLegacyPriceFormatter === true;
755
+ }
756
+
757
+ /**
758
+ * 返回最近一次 prepareProductsWithPrice / DataVariant 评估提取的变价时间点(HH:mm)。
759
+ * 供 OS Server 商品 query 订阅定时刷新使用。
760
+ */
761
+ getLastScheduleTimePoints() {
762
+ return [...this.lastScheduleTimePoints];
462
763
  }
463
764
 
464
765
  /**
465
766
  * 注册商品格式化器
466
- * 格式化器会按注册顺序依次执行(内置价格格式化器始终是第一个)
767
+ * 格式化器会按注册顺序依次执行;legacy 启用时先兜底报价,再由智能定价覆盖。
467
768
  * @param formatter 格式化函数
468
769
  * @param prepend 是否插入到队列开头(默认 false,追加到末尾)
469
770
  * @example
@@ -478,8 +779,8 @@ class ProductsModule extends _BaseModule.BaseModule {
478
779
  */
479
780
  registerFormatter(formatter, prepend = false) {
480
781
  if (prepend) {
481
- // 插入到价格格式化器之后(索引 1 的位置)
482
- const insertIndex = this.isPriceFormatterRegistered ? 1 : 0;
782
+ // 插入到内置智能定价格式化器之后;legacy 报价单启用时需跳过它。
783
+ const insertIndex = this.isBuiltinFormatterRegistered ? this.isLegacyPriceFormatterEnabled ? 2 : 1 : 0;
483
784
  this.formatters.splice(insertIndex, 0, formatter);
484
785
  } else {
485
786
  // 追加到末尾
@@ -488,16 +789,17 @@ class ProductsModule extends _BaseModule.BaseModule {
488
789
  }
489
790
 
490
791
  /**
491
- * 清空所有格式化器(包括内置价格格式化器)
492
- * @param keepBuiltin 是否保留内置价格格式化器(默认 true)
792
+ * 清空所有格式化器(包括内置智能定价格式化器)
793
+ * @param keepBuiltin 是否保留内置智能定价格式化器(默认 true)
493
794
  */
494
795
  clearFormatters(keepBuiltin = true) {
495
- if (keepBuiltin && this.isPriceFormatterRegistered) {
496
- // 只保留第一个格式化器(内置价格格式化器)
497
- this.formatters = [this.formatters[0]];
796
+ if (keepBuiltin && this.isBuiltinFormatterRegistered) {
797
+ // 保留 legacy 兜底(如果启用)和智能定价格式化器。
798
+ const builtinCount = this.isLegacyPriceFormatterEnabled ? 2 : 1;
799
+ this.formatters = this.formatters.slice(0, builtinCount);
498
800
  } else {
499
801
  this.formatters = [];
500
- this.isPriceFormatterRegistered = false;
802
+ this.isBuiltinFormatterRegistered = false;
501
803
  }
502
804
  }
503
805
 
@@ -545,7 +847,8 @@ class ProductsModule extends _BaseModule.BaseModule {
545
847
  productCount: list.length,
546
848
  duration: `${Math.round(performance.now() - t0)}ms`
547
849
  });
548
- await this.saveProductsToIndexDB(list);
850
+ await this.replaceProductsSnapshotInIndexDB(list, 'loadProductsByServer');
851
+ this.lastServerFullFetchAtMs = Date.now();
549
852
  await this.core.effects.emit(_types.ProductsHooks.onProductsLoaded, list);
550
853
  (0, _product.perfMark)('loadProductsByServer', performance.now() - t0, {
551
854
  count: list.length
@@ -588,7 +891,7 @@ class ProductsModule extends _BaseModule.BaseModule {
588
891
  open_bundle: 1,
589
892
  exclude_extension_type: ['product_party', 'product_event', 'product_series_event', 'product_package_ticket', 'ticket', 'event_item'],
590
893
  force_ignore_cache_flag: 1,
591
- with: ['category', 'collection', 'resourceRelation', 'bundleGroup.bundleItem', 'optionGroup.optionItem', 'variantGroup.variantItem'],
894
+ with: ['category', 'collection', 'resourceRelation', 'bundleGroup.bundleItem', 'optionGroup.optionItem', 'variantGroup.variantItem', 'dataVariants'],
592
895
  open_deposit: 1,
593
896
  is_append_product_description: 1,
594
897
  with_count: ['bundleGroup', 'optionGroup'],
@@ -631,7 +934,8 @@ class ProductsModule extends _BaseModule.BaseModule {
631
934
  async loadProductsByServerHttp(params) {
632
935
  const productList = await this.fetchProductsByHttp(params);
633
936
  if (productList.length > 0) {
634
- await this.saveProductsToIndexDB(productList);
937
+ await this.replaceProductsSnapshotInIndexDB(productList, 'loadProductsByServerHttp');
938
+ this.lastServerFullFetchAtMs = Date.now();
635
939
  await this.core.effects.emit(_types.ProductsHooks.onProductsLoaded, productList);
636
940
  }
637
941
  return productList;
@@ -649,6 +953,25 @@ class ProductsModule extends _BaseModule.BaseModule {
649
953
  return result;
650
954
  }
651
955
 
956
+ /**
957
+ * 批量获取商品轻量快照,供订单详情等响应补齐展示协议字段。
958
+ */
959
+ async getProductSnapshotsByIds(ids) {
960
+ const snapshots = {};
961
+ const seen = new Set();
962
+ for (const id of ids) {
963
+ if (!Number.isFinite(id) || seen.has(id)) continue;
964
+ seen.add(id);
965
+ const product = this.store.map.get(id);
966
+ if (!product) continue;
967
+ snapshots[String(id)] = {
968
+ cover: product.cover,
969
+ holder_config: product.holder_config
970
+ };
971
+ }
972
+ return snapshots;
973
+ }
974
+
652
975
  /**
653
976
  * 内部获取商品列表的直接引用(无拷贝)
654
977
  * 仅供内部 formatter 流程使用,因为 formatter 会创建新对象
@@ -657,6 +980,18 @@ class ProductsModule extends _BaseModule.BaseModule {
657
980
  return this.store.list;
658
981
  }
659
982
 
983
+ /**
984
+ * 按 ID 从 Map 取商品引用,供内部 formatter 流程使用。
985
+ */
986
+ getProductsRefByIds(ids) {
987
+ const products = [];
988
+ for (const id of ids) {
989
+ const product = this.store.map.get(id);
990
+ if (product) products.push(product);
991
+ }
992
+ return products;
993
+ }
994
+
660
995
  /**
661
996
  * 根据ID获取单个商品(从内存缓存)
662
997
  * 使用 Map 快速查询,时间复杂度 O(1)
@@ -784,37 +1119,105 @@ class ProductsModule extends _BaseModule.BaseModule {
784
1119
  }
785
1120
 
786
1121
  /**
787
- * 保存商品数据到 IndexDB(平铺存储)
788
- * @private
1122
+ * 串行执行商品 IndexDB 写入,避免快照替换与增量 patch 并发交错。
1123
+ *
1124
+ * @example
1125
+ * await this.runInProductIndexDBSaveQueue(async () => {
1126
+ * await this.dbManager.bulkUpdate(INDEXDB_STORE_NAME, products)
1127
+ * })
789
1128
  */
790
- async saveProductsToIndexDB(products) {
1129
+ async runInProductIndexDBSaveQueue(task) {
1130
+ const queuedTask = this.productIndexDBSaveQueue.then(task, task);
1131
+ this.productIndexDBSaveQueue = queuedTask.then(() => undefined, () => undefined);
1132
+ return queuedTask;
1133
+ }
1134
+
1135
+ /**
1136
+ * 全量快照写入 IndexDB:clear + bulkUpdate,仅用于 SSE/HTTP 全量拉取。
1137
+ *
1138
+ * @example
1139
+ * await this.replaceProductsSnapshotInIndexDB(list, 'loadProductsByServer')
1140
+ */
1141
+ async replaceProductsSnapshotInIndexDB(products, source) {
791
1142
  if (!this.dbManager) {
792
- this.logWarning('saveProductsToIndexDB: dbManager 不可用');
1143
+ this.logWarning('replaceProductsSnapshotInIndexDB: dbManager 不可用');
793
1144
  return;
794
1145
  }
795
- this.logInfo('开始保存商品数据到 IndexDB', {
796
- productCount: products.length
797
- });
798
1146
  try {
799
- const t0 = performance.now();
800
- await this.dbManager.clear(INDEXDB_STORE_NAME);
801
- await this.dbManager.bulkUpdate(INDEXDB_STORE_NAME, products);
802
- (0, _product.perfMark)('saveProductsToIndexDB', performance.now() - t0, {
803
- count: products.length
804
- });
805
- this.logInfo('商品数据已保存到 IndexDB', {
806
- productCount: products.length
1147
+ await this.runInProductIndexDBSaveQueue(async () => {
1148
+ this.logInfo('replaceProductsSnapshotInIndexDB 开始', {
1149
+ source,
1150
+ productCount: products.length
1151
+ });
1152
+ const t0 = performance.now();
1153
+ await this.dbManager.clear(INDEXDB_STORE_NAME);
1154
+ if (products.length > 0) {
1155
+ await this.dbManager.bulkUpdate(INDEXDB_STORE_NAME, products);
1156
+ }
1157
+ (0, _product.perfMark)('replaceProductsSnapshotInIndexDB', performance.now() - t0, {
1158
+ count: products.length
1159
+ });
1160
+ this.logInfo('replaceProductsSnapshotInIndexDB 完成', {
1161
+ source,
1162
+ productCount: products.length
1163
+ });
807
1164
  });
808
1165
  } catch (error) {
809
1166
  const errorMessage = error instanceof Error ? error.message : String(error);
810
- console.error('[Products] 保存数据到 IndexDB 失败:', error);
811
- this.logError('保存数据到 IndexDB 失败', {
1167
+ this.logError('全量保存商品到 IndexDB 失败', {
1168
+ source,
812
1169
  productCount: products.length,
813
1170
  error: errorMessage
814
1171
  });
815
1172
  }
816
1173
  }
817
1174
 
1175
+ /**
1176
+ * 增量 upsert 商品到 IndexDB,不清空表;用于 pubsub 批次合并后的变更商品。
1177
+ *
1178
+ * @example
1179
+ * await this.patchProductsInIndexDB(changedProducts, 'pubsub.processProductSyncMessages', mergeActions)
1180
+ */
1181
+ async patchProductsInIndexDB(products, source, mergeActions = {}) {
1182
+ if (!this.dbManager || products.length === 0) {
1183
+ return;
1184
+ }
1185
+ const validProducts = products.filter(p => p?.id !== undefined && p?.id !== null);
1186
+ if (validProducts.length === 0) {
1187
+ return;
1188
+ }
1189
+ try {
1190
+ await this.runInProductIndexDBSaveQueue(async () => {
1191
+ this.logInfo('patchProductsInIndexDB 开始', {
1192
+ source,
1193
+ count: validProducts.length
1194
+ });
1195
+ const t0 = performance.now();
1196
+ if (typeof this.dbManager.bulkUpdate === 'function') {
1197
+ await this.dbManager.bulkUpdate(INDEXDB_STORE_NAME, validProducts);
1198
+ } else if (typeof this.dbManager.update === 'function') {
1199
+ for (const product of validProducts) {
1200
+ await this.dbManager.update(INDEXDB_STORE_NAME, product);
1201
+ }
1202
+ }
1203
+ (0, _product.perfMark)('patchProductsInIndexDB', performance.now() - t0, {
1204
+ count: validProducts.length
1205
+ });
1206
+ this.logInfo('patchProductsInIndexDB 完成', {
1207
+ source,
1208
+ count: validProducts.length
1209
+ });
1210
+ });
1211
+ } catch (error) {
1212
+ const errorMessage = error instanceof Error ? error.message : String(error);
1213
+ this.logError('增量保存商品到 IndexDB 失败', {
1214
+ source,
1215
+ error: errorMessage,
1216
+ productCount: validProducts.length
1217
+ });
1218
+ }
1219
+ }
1220
+
818
1221
  /**
819
1222
  * 同步更新商品 Map 缓存
820
1223
  * 将 list 中的商品同步到 map,以 id 为 key
@@ -862,6 +1265,7 @@ class ProductsModule extends _BaseModule.BaseModule {
862
1265
  duration: `${Math.round(performance.now() - tTotal)}ms`,
863
1266
  source: 'IndexDB'
864
1267
  });
1268
+ this.lastPreloadCompletedAtMs = Date.now();
865
1269
  return;
866
1270
  }
867
1271
  this.logInfo('IndexDB 中没有缓存数据,准备从服务器加载');
@@ -895,6 +1299,7 @@ class ProductsModule extends _BaseModule.BaseModule {
895
1299
  duration: `${Math.round(performance.now() - tTotal)}ms`,
896
1300
  source: 'Server'
897
1301
  });
1302
+ this.lastPreloadCompletedAtMs = Date.now();
898
1303
  } else {
899
1304
  (0, _product.perfMark)('preload(empty)', performance.now() - tTotal, {
900
1305
  source: 'empty'
@@ -956,9 +1361,10 @@ class ProductsModule extends _BaseModule.BaseModule {
956
1361
  this.productDataSource.run({
957
1362
  pubsub: {
958
1363
  callback: res => {
959
- this.logInfo('sse_products_callback: 收到同步消息', {
960
- data: res?.data
961
- });
1364
+ // console.log('sse_products_callback', res)
1365
+ // this.logInfo('sse_products_callback: 收到同步消息', {
1366
+ // data: res?.data,
1367
+ // });
962
1368
  if (!res?.data) return;
963
1369
  const data = res.data;
964
1370
  const channelKey = data.module || 'product';
@@ -1049,6 +1455,10 @@ class ProductsModule extends _BaseModule.BaseModule {
1049
1455
  if (msg.relation_product_ids?.length) {
1050
1456
  sseRefreshIds.push(...msg.relation_product_ids);
1051
1457
  }
1458
+ } else if (['product_data_variant'].includes(channelKey)) {
1459
+ if (msg.product_ids?.length) {
1460
+ sseRefreshIds.push(...msg.product_ids);
1461
+ }
1052
1462
  }
1053
1463
  }
1054
1464
  const uniqueDeleteIds = [...new Set(deleteIds)];
@@ -1060,22 +1470,51 @@ class ProductsModule extends _BaseModule.BaseModule {
1060
1470
  await this.removeProductsByIds(uniqueDeleteIds);
1061
1471
  }
1062
1472
 
1063
- // 2. 处理 body 直接覆盖(store.list 更新,价格缓存不清除)
1473
+ /** 本批次待 patch 的商品(按 id 去重,SSE 覆盖 body) */
1474
+ const patchProductMap = new Map();
1475
+ const patchMergeActions = {};
1476
+ let storeMutated = uniqueDeleteIds.length > 0;
1477
+
1478
+ // 2. 处理 body 直接覆盖(仅内存,IndexDB 批次末统一 patch)
1064
1479
  if (bodyUpdates.size > 0) {
1065
- await this.applyBodyUpdatesToStore(bodyUpdates);
1480
+ const bodyResult = await this.applyBodyUpdatesToStore(bodyUpdates, {
1481
+ skipIndexDB: true,
1482
+ skipEmit: true
1483
+ });
1484
+ for (const product of bodyResult.changedProducts) {
1485
+ patchProductMap.set(product.id, product);
1486
+ patchMergeActions[product.id] = bodyResult.mergeActions[product.id];
1487
+ }
1488
+ storeMutated = true;
1066
1489
  }
1067
1490
 
1068
- // 3. 处理 SSE 增量拉取(store.list 更新,价格缓存不清除)
1491
+ // 3. 处理 SSE 增量拉取(仅内存,IndexDB 批次末统一 patch)
1492
+ let freshProducts = [];
1069
1493
  if (uniqueSSEIds.length > 0) {
1070
- const freshProducts = await this.fetchProductsBySSE(uniqueSSEIds);
1494
+ freshProducts = await this.fetchProductsBySSE(uniqueSSEIds);
1071
1495
  if (freshProducts.length > 0) {
1072
- await this.mergeProductsToStore(freshProducts);
1496
+ const mergeResult = await this.mergeProductsToStore(freshProducts, {
1497
+ skipIndexDB: true,
1498
+ skipEmit: true
1499
+ });
1500
+ for (const product of mergeResult.changedProducts) {
1501
+ patchProductMap.set(product.id, product);
1502
+ patchMergeActions[product.id] = mergeResult.mergeActions[product.id];
1503
+ }
1504
+ storeMutated = true;
1073
1505
  }
1074
1506
  this.logInfo('processProductSyncMessages: SSE 增量更新完成123', {
1075
1507
  requestedCount: uniqueSSEIds.length,
1076
1508
  receivedCount: freshProducts.length
1077
1509
  });
1078
1510
  }
1511
+ const patchProducts = [...patchProductMap.values()];
1512
+ if (patchProducts.length > 0) {
1513
+ await this.patchProductsInIndexDB(patchProducts, 'pubsub.processProductSyncMessages', patchMergeActions);
1514
+ }
1515
+ if (storeMutated) {
1516
+ this.core.effects.emit(_types.ProductsHooks.onProductsChanged, this.store.list);
1517
+ }
1079
1518
 
1080
1519
  // 收集所有非 delete 的变更 IDs(body + SSE + price),用于增量更新价格缓存
1081
1520
  const allChangedIds = [...new Set([...Array.from(bodyUpdates.keys()), ...uniqueSSEIds, ...uniquePriceIds])];
@@ -1095,6 +1534,14 @@ class ProductsModule extends _BaseModule.BaseModule {
1095
1534
 
1096
1535
  // 报价单变更 → 清除全量价格缓存,后续走全量重建
1097
1536
  if (shouldClearPriceCache) {
1537
+ try {
1538
+ await this.refreshQuotationPriceBridge();
1539
+ } catch (error) {
1540
+ const errorMessage = error instanceof Error ? error.message : String(error);
1541
+ this.logError('报价单桥接刷新失败,将继续清理价格缓存', {
1542
+ error: errorMessage
1543
+ });
1544
+ }
1098
1545
  this.clearPriceCache();
1099
1546
  }
1100
1547
  await this.core.effects.emit(_types.ProductsHooks.onProductsSyncCompleted, {
@@ -1148,21 +1595,28 @@ class ProductsModule extends _BaseModule.BaseModule {
1148
1595
  /**
1149
1596
  * 将 body 完整数据直接覆盖到本地 store(不调用报价单接口)
1150
1597
  * 已存在的 → 直接替换;不存在的 → 追加
1151
- * 同时更新 Map 缓存、IndexDB,触发 onProductsChanged
1152
- * 价格缓存由 processProductSyncMessages 末尾统一清除
1598
+ *
1599
+ * @example
1600
+ * await this.applyBodyUpdatesToStore(bodyUpdates, { skipIndexDB: true })
1153
1601
  */
1154
- async applyBodyUpdatesToStore(bodyUpdates) {
1602
+ async applyBodyUpdatesToStore(bodyUpdates, options) {
1603
+ const changedProductIds = [...bodyUpdates.keys()];
1155
1604
  this.logInfo('applyBodyUpdatesToStore: 开始', {
1156
1605
  count: bodyUpdates.size
1157
1606
  });
1158
1607
  let updatedCount = 0;
1159
1608
  let newCount = 0;
1160
1609
  const appliedIds = new Set();
1610
+ const mergeActions = {};
1611
+ const changedProducts = [];
1161
1612
  this.store.list = this.store.list.map(p => {
1162
1613
  if (bodyUpdates.has(p.id)) {
1163
1614
  updatedCount++;
1164
1615
  appliedIds.add(p.id);
1165
- return bodyUpdates.get(p.id);
1616
+ mergeActions[p.id] = 'update';
1617
+ const next = bodyUpdates.get(p.id);
1618
+ changedProducts.push(next);
1619
+ return next;
1166
1620
  }
1167
1621
  return p;
1168
1622
  });
@@ -1170,32 +1624,50 @@ class ProductsModule extends _BaseModule.BaseModule {
1170
1624
  if (!appliedIds.has(id)) {
1171
1625
  this.store.list.push(body);
1172
1626
  newCount++;
1627
+ mergeActions[id] = 'insert';
1628
+ changedProducts.push(body);
1173
1629
  }
1174
1630
  }
1175
1631
  this.syncProductsMap();
1176
- await this.saveProductsToIndexDB(this.store.list);
1177
- this.core.effects.emit(_types.ProductsHooks.onProductsChanged, this.store.list);
1632
+ if (!options?.skipIndexDB) {
1633
+ await this.patchProductsInIndexDB(changedProducts, 'pubsub.bodyUpdate', mergeActions);
1634
+ }
1635
+ if (!options?.skipEmit) {
1636
+ this.core.effects.emit(_types.ProductsHooks.onProductsChanged, this.store.list);
1637
+ }
1178
1638
  this.logInfo('applyBodyUpdatesToStore: 完成', {
1179
1639
  updatedCount,
1180
1640
  newCount,
1181
1641
  totalCount: this.store.list.length
1182
1642
  });
1643
+ return {
1644
+ changedProducts,
1645
+ mergeActions,
1646
+ updatedCount,
1647
+ newCount
1648
+ };
1183
1649
  }
1184
1650
 
1185
1651
  /**
1186
1652
  * 将增量拉取的商品合并到 store
1187
1653
  * 已存在的 → 替换;新的 → 追加
1188
- * 同时更新 store.map、IndexDB,触发 onProductsChanged
1654
+ *
1655
+ * @example
1656
+ * await this.mergeProductsToStore(freshProducts, { skipIndexDB: true })
1189
1657
  */
1190
- async mergeProductsToStore(freshProducts) {
1658
+ async mergeProductsToStore(freshProducts, options) {
1191
1659
  const freshMap = new Map();
1192
1660
  for (const p of freshProducts) {
1193
1661
  freshMap.set(p.id, p);
1194
1662
  }
1663
+ const mergeActions = {};
1664
+ const changedProducts = [];
1195
1665
  const updatedList = this.store.list.map(p => {
1196
1666
  if (freshMap.has(p.id)) {
1197
1667
  const fresh = freshMap.get(p.id);
1198
1668
  freshMap.delete(p.id);
1669
+ mergeActions[p.id] = 'update';
1670
+ changedProducts.push(fresh);
1199
1671
  return fresh;
1200
1672
  }
1201
1673
  return p;
@@ -1205,17 +1677,30 @@ class ProductsModule extends _BaseModule.BaseModule {
1205
1677
  const newCount = freshMap.size;
1206
1678
  for (const p of freshMap.values()) {
1207
1679
  updatedList.push(p);
1680
+ mergeActions[p.id] = 'insert';
1681
+ changedProducts.push(p);
1208
1682
  }
1209
1683
  const updatedCount = freshProducts.length - newCount;
1684
+ const changedProductIds = freshProducts.map(p => p.id);
1210
1685
  this.store.list = updatedList;
1211
1686
  this.syncProductsMap();
1212
- await this.saveProductsToIndexDB(this.store.list);
1687
+ if (!options?.skipIndexDB) {
1688
+ await this.patchProductsInIndexDB(changedProducts, 'pubsub.mergeProductsToStore', mergeActions);
1689
+ }
1690
+ if (!options?.skipEmit) {
1691
+ this.core.effects.emit(_types.ProductsHooks.onProductsChanged, this.store.list);
1692
+ }
1213
1693
  this.logInfo('mergeProductsToStore: 合并完成', {
1214
1694
  updatedCount,
1215
1695
  newCount,
1216
1696
  totalCount: this.store.list.length
1217
1697
  });
1218
- this.core.effects.emit(_types.ProductsHooks.onProductsChanged, this.store.list);
1698
+ return {
1699
+ changedProducts,
1700
+ mergeActions,
1701
+ updatedCount,
1702
+ newCount
1703
+ };
1219
1704
  }
1220
1705
 
1221
1706
  /**
@@ -1225,6 +1710,12 @@ class ProductsModule extends _BaseModule.BaseModule {
1225
1710
  */
1226
1711
  async silentRefresh() {
1227
1712
  const t0 = performance.now();
1713
+ const now = Date.now();
1714
+ const elapsedSincePreload = now - this.lastPreloadCompletedAtMs;
1715
+ const elapsedSinceFullFetch = now - this.lastServerFullFetchAtMs;
1716
+ if (this.lastPreloadCompletedAtMs > 0 && elapsedSincePreload < PRODUCT_SILENT_REFRESH_MIN_INTERVAL_MS || this.lastServerFullFetchAtMs > 0 && elapsedSinceFullFetch < PRODUCT_SILENT_REFRESH_MIN_INTERVAL_MS) {
1717
+ return this.store.list;
1718
+ }
1228
1719
  this.logInfo('silentRefresh 开始');
1229
1720
  try {
1230
1721
  const products = await this.loadProductsByServer();