@pisell/pisellos 0.0.567 → 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 (557) hide show
  1. package/dist/core/index.js +6 -9
  2. package/dist/model/strategy/adapter/promotion/evaluator.js +3 -2
  3. package/dist/model/strategy/adapter/walletPass/evaluator.js +0 -2
  4. package/dist/model/strategy/adapter/walletPass/example.js +3 -26
  5. package/dist/model/strategy/adapter/walletPass/utils.js +7 -7
  6. package/dist/model/strategy/strategy-example.js +5 -19
  7. package/dist/modules/BaseModule.d.ts +2 -2
  8. package/dist/modules/BaseModule.js +4 -4
  9. package/dist/modules/BookingContext/index.d.ts +48 -0
  10. package/dist/modules/BookingContext/index.js +566 -0
  11. package/dist/modules/BookingContext/types.d.ts +102 -0
  12. package/dist/modules/BookingContext/types.js +51 -0
  13. package/dist/modules/BookingContext/utils/buildCacheItemFromOrderLine.d.ts +16 -0
  14. package/dist/modules/BookingContext/utils/buildCacheItemFromOrderLine.js +302 -0
  15. package/dist/modules/BookingContext/utils/buildNormalProductCacheItemFromOrderLine.d.ts +8 -0
  16. package/dist/modules/BookingContext/utils/buildNormalProductCacheItemFromOrderLine.js +149 -0
  17. package/dist/modules/BookingContext/utils/cacheItemToBookingInput.d.ts +76 -0
  18. package/dist/modules/BookingContext/utils/cacheItemToBookingInput.js +249 -0
  19. package/dist/modules/BookingContext/utils/flexible.d.ts +28 -0
  20. package/dist/modules/BookingContext/utils/flexible.js +56 -0
  21. package/dist/modules/BookingContext/utils/formatResourceList.d.ts +8 -0
  22. package/dist/modules/BookingContext/utils/formatResourceList.js +38 -0
  23. package/dist/modules/BookingContext/utils/index.d.ts +11 -0
  24. package/dist/modules/BookingContext/utils/index.js +11 -0
  25. package/dist/modules/BookingContext/utils/noResource.d.ts +13 -0
  26. package/dist/modules/BookingContext/utils/noResource.js +77 -0
  27. package/dist/modules/BookingContext/utils/orderLineDisplay.d.ts +1 -0
  28. package/dist/modules/BookingContext/utils/orderLineDisplay.js +36 -0
  29. package/dist/modules/BookingContext/utils/productExtend.d.ts +72 -0
  30. package/dist/modules/BookingContext/utils/productExtend.js +339 -0
  31. package/dist/modules/BookingContext/utils/resourceErrors.d.ts +41 -0
  32. package/dist/modules/BookingContext/utils/resourceErrors.js +74 -0
  33. package/dist/modules/BookingContext/utils/resourceSelection.d.ts +7 -0
  34. package/dist/modules/BookingContext/utils/resourceSelection.js +24 -0
  35. package/dist/modules/BookingContext/utils/resources.d.ts +80 -0
  36. package/dist/modules/BookingContext/utils/resources.js +486 -0
  37. package/dist/modules/BookingContext/utils/serviceTimes.d.ts +55 -0
  38. package/dist/modules/BookingContext/utils/serviceTimes.js +151 -0
  39. package/dist/modules/BookingContext/utils/timeSlices.d.ts +42 -0
  40. package/dist/modules/BookingContext/utils/timeSlices.js +100 -0
  41. package/dist/modules/Cart/index.js +0 -3
  42. package/dist/modules/Cart/utils/cartProduct.js +26 -6
  43. package/dist/modules/Cart/utils/changePrice.js +27 -20
  44. package/dist/modules/Customer/index.d.ts +20 -0
  45. package/dist/modules/Customer/index.js +172 -83
  46. package/dist/modules/Customer/types.d.ts +2 -0
  47. package/dist/modules/Date/index.js +1 -5
  48. package/dist/modules/Discount/index.d.ts +2 -1
  49. package/dist/modules/Discount/index.js +37 -12
  50. package/dist/modules/Discount/types.d.ts +1 -0
  51. package/dist/modules/OpenData/index.d.ts +8 -0
  52. package/dist/modules/OpenData/index.js +35 -17
  53. package/dist/modules/Order/index.d.ts +449 -24
  54. package/dist/modules/Order/index.js +5437 -608
  55. package/dist/modules/Order/types.d.ts +693 -88
  56. package/dist/modules/Order/types.js +68 -1
  57. package/dist/modules/Order/utils/bundleDiscountHydration.d.ts +5 -0
  58. package/dist/modules/Order/utils/bundleDiscountHydration.js +144 -0
  59. package/dist/modules/Order/utils/manualProductDiscount.d.ts +106 -0
  60. package/dist/modules/Order/utils/manualProductDiscount.js +210 -0
  61. package/dist/modules/Order/utils/orderCollectionIdentity.d.ts +53 -0
  62. package/dist/modules/Order/utils/orderCollectionIdentity.js +405 -0
  63. package/dist/modules/Order/utils.d.ts +167 -18
  64. package/dist/modules/Order/utils.js +960 -165
  65. package/dist/modules/Payment/index.d.ts +10 -9
  66. package/dist/modules/Payment/index.js +508 -874
  67. package/dist/modules/Payment/types.d.ts +40 -16
  68. package/dist/modules/Payment/utils.d.ts +1 -1
  69. package/dist/modules/Payment/utils.js +4 -3
  70. package/dist/modules/Payment/walletpass.js +48 -34
  71. package/dist/modules/Product/types.d.ts +29 -7
  72. package/dist/modules/ProductList/index.d.ts +17 -13
  73. package/dist/modules/ProductList/index.js +184 -74
  74. package/dist/modules/ProductList/types.d.ts +17 -0
  75. package/dist/modules/Quotation/index.d.ts +22 -2
  76. package/dist/modules/Quotation/index.js +108 -42
  77. package/dist/modules/Quotation/types.d.ts +19 -0
  78. package/dist/modules/ResourcePlanner/index.d.ts +24 -0
  79. package/dist/modules/ResourcePlanner/index.js +181 -0
  80. package/dist/modules/ResourcePlanner/planner.d.ts +14 -0
  81. package/dist/modules/ResourcePlanner/planner.js +1069 -0
  82. package/dist/modules/ResourcePlanner/types.d.ts +227 -0
  83. package/dist/modules/ResourcePlanner/types.js +1 -0
  84. package/dist/modules/Rules/index.d.ts +8 -10
  85. package/dist/modules/Rules/index.js +219 -114
  86. package/dist/modules/Rules/types.d.ts +14 -3
  87. package/dist/modules/SalesSummary/index.d.ts +23 -26
  88. package/dist/modules/SalesSummary/index.js +152 -46
  89. package/dist/modules/SalesSummary/types.d.ts +9 -1
  90. package/dist/modules/SalesSummary/utils.d.ts +3 -22
  91. package/dist/modules/SalesSummary/utils.js +803 -163
  92. package/dist/modules/Schedule/getDateIsInSchedule.js +14 -7
  93. package/dist/modules/Schedule/index.d.ts +18 -5
  94. package/dist/modules/Schedule/index.js +78 -24
  95. package/dist/modules/Schedule/types.d.ts +2 -0
  96. package/dist/modules/Schedule/utils.js +0 -1
  97. package/dist/modules/Step/index.js +0 -1
  98. package/dist/modules/Summary/index.js +4 -3
  99. package/dist/modules/SurchargeList/index.d.ts +18 -0
  100. package/dist/modules/SurchargeList/index.js +185 -0
  101. package/dist/modules/SurchargeList/types.d.ts +15 -0
  102. package/dist/modules/SurchargeList/types.js +1 -0
  103. package/dist/modules/index.d.ts +3 -0
  104. package/dist/modules/index.js +4 -1
  105. package/dist/plugins/app.d.ts +2 -0
  106. package/dist/plugins/request.d.ts +1 -0
  107. package/dist/plugins/request.js +1 -4
  108. package/dist/plugins/window.js +2 -6
  109. package/dist/server/index.d.ts +462 -8
  110. package/dist/server/index.js +6137 -714
  111. package/dist/server/modules/floor-plan/index.d.ts +39 -0
  112. package/dist/server/modules/floor-plan/index.js +595 -0
  113. package/dist/server/modules/floor-plan/types.d.ts +43 -0
  114. package/dist/server/modules/floor-plan/types.js +13 -0
  115. package/dist/server/modules/index.d.ts +7 -2
  116. package/dist/server/modules/index.js +6 -0
  117. package/dist/server/modules/menu/index.js +32 -41
  118. package/dist/server/modules/order/index.d.ts +207 -14
  119. package/dist/server/modules/order/index.js +2326 -379
  120. package/dist/server/modules/order/types.d.ts +76 -5
  121. package/dist/server/modules/order/types.js +14 -1
  122. package/dist/server/modules/order/utils/filterBookings.d.ts +7 -1
  123. package/dist/server/modules/order/utils/filterBookings.js +118 -11
  124. package/dist/server/modules/order/utils/filterOrders.js +242 -14
  125. package/dist/server/modules/payment/index.d.ts +28 -0
  126. package/dist/server/modules/payment/index.js +305 -0
  127. package/dist/server/modules/payment/types.d.ts +9 -0
  128. package/dist/server/modules/payment/types.js +1 -0
  129. package/dist/server/modules/products/index.d.ts +105 -14
  130. package/dist/server/modules/products/index.js +1397 -540
  131. package/dist/server/modules/products/types.d.ts +34 -1
  132. package/dist/server/modules/products/types.js +4 -0
  133. package/dist/server/modules/quotation/index.d.ts +39 -0
  134. package/dist/server/modules/quotation/index.js +289 -86
  135. package/dist/server/modules/resource/index.d.ts +2 -3
  136. package/dist/server/modules/resource/index.js +105 -84
  137. package/dist/server/modules/schedule/index.d.ts +6 -4
  138. package/dist/server/modules/schedule/index.js +137 -114
  139. package/dist/server/test.json +713 -0
  140. package/dist/server/types.d.ts +2 -0
  141. package/dist/server/utils/product.js +2 -3
  142. package/dist/server/utils/small-ticket.d.ts +71 -0
  143. package/dist/server/utils/small-ticket.js +963 -0
  144. package/dist/solution/BaseSales/index.d.ts +378 -0
  145. package/dist/solution/BaseSales/index.js +4006 -0
  146. package/dist/solution/BaseSales/types.d.ts +394 -0
  147. package/dist/solution/BaseSales/types.js +40 -0
  148. package/dist/solution/BaseSales/utils/cartPromotion.d.ts +287 -0
  149. package/dist/solution/BaseSales/utils/cartPromotion.js +1450 -0
  150. package/dist/solution/BaseSales/utils/parseSalesResponse.d.ts +144 -0
  151. package/dist/solution/BaseSales/utils/parseSalesResponse.js +157 -0
  152. package/dist/solution/BaseSales/utils/quotationPrice.d.ts +18 -0
  153. package/dist/solution/BaseSales/utils/quotationPrice.js +250 -0
  154. package/dist/solution/BaseSales/utils/transformBaseProductToOrderProduct.d.ts +63 -0
  155. package/dist/solution/BaseSales/utils/transformBaseProductToOrderProduct.js +218 -0
  156. package/dist/solution/BaseSales/utils.d.ts +40 -0
  157. package/dist/solution/BaseSales/utils.js +263 -0
  158. package/dist/solution/BookingByStep/index.js +7 -59
  159. package/dist/solution/BookingByStep/utils/capacity.js +1 -11
  160. package/dist/solution/BookingByStep/utils/resources.js +1 -3
  161. package/dist/solution/BookingByStep/utils/timeslots.js +2 -43
  162. package/dist/solution/BookingTicket/index.d.ts +393 -24
  163. package/dist/solution/BookingTicket/index.js +2506 -445
  164. package/dist/solution/BookingTicket/types.d.ts +191 -6
  165. package/dist/solution/BookingTicket/types.js +83 -10
  166. package/dist/solution/BookingTicket/utils/addProductDecision.d.ts +117 -0
  167. package/dist/solution/BookingTicket/utils/addProductDecision.js +346 -0
  168. package/dist/solution/BookingTicket/utils/addTimeAvailability.d.ts +35 -0
  169. package/dist/solution/BookingTicket/utils/addTimeAvailability.js +95 -0
  170. package/dist/solution/BookingTicket/utils/bookingStatus.d.ts +77 -0
  171. package/dist/solution/BookingTicket/utils/bookingStatus.js +136 -0
  172. package/dist/solution/BookingTicket/utils/cartView.d.ts +29 -0
  173. package/dist/solution/BookingTicket/utils/cartView.js +209 -0
  174. package/dist/solution/BookingTicket/utils/exampleData.d.ts +123 -0
  175. package/dist/solution/BookingTicket/utils/exampleData.js +183 -0
  176. package/dist/solution/BookingTicket/utils/orderCustomer.d.ts +4 -0
  177. package/dist/solution/BookingTicket/utils/orderCustomer.js +31 -0
  178. package/dist/solution/BookingTicket/utils/resolveBestAddTimePlan.d.ts +185 -0
  179. package/dist/solution/BookingTicket/utils/resolveBestAddTimePlan.js +438 -0
  180. package/dist/solution/BookingTicket/utils/scan/applyGlobalScan.d.ts +36 -0
  181. package/dist/solution/BookingTicket/utils/scan/applyGlobalScan.js +400 -0
  182. package/dist/solution/BookingTicket/utils/scan/cloudSearch.d.ts +4 -0
  183. package/dist/solution/BookingTicket/utils/scan/cloudSearch.js +41 -1
  184. package/dist/solution/BookingTicket/utils/scan/formatGlobalScan.d.ts +32 -0
  185. package/dist/solution/BookingTicket/utils/scan/formatGlobalScan.js +56 -0
  186. package/dist/solution/BookingTicket/utils/scan/handleScan.d.ts +3 -1
  187. package/dist/solution/BookingTicket/utils/scan/handleScan.js +36 -34
  188. package/dist/solution/BookingTicket/utils/scan/index.d.ts +5 -0
  189. package/dist/solution/BookingTicket/utils/scan/index.js +26 -18
  190. package/dist/solution/BookingTicket/utils/scan/scanProductValues.d.ts +21 -0
  191. package/dist/solution/BookingTicket/utils/scan/scanProductValues.js +150 -0
  192. package/dist/solution/BookingTicket/utils/sessionCatalogStale.d.ts +23 -0
  193. package/dist/solution/BookingTicket/utils/sessionCatalogStale.js +78 -0
  194. package/dist/solution/BuyTickets/index.js +9 -12
  195. package/dist/solution/Checkout/index.d.ts +1 -0
  196. package/dist/solution/Checkout/index.js +199 -277
  197. package/dist/solution/Checkout/utils/index.js +8 -20
  198. package/dist/solution/RegisterAndLogin/config.js +2 -10
  199. package/dist/solution/RegisterAndLogin/index.js +30 -76
  200. package/dist/solution/Sales/index.d.ts +50 -5
  201. package/dist/solution/Sales/index.js +484 -92
  202. package/dist/solution/Sales/types.d.ts +26 -2
  203. package/dist/solution/ScanOrder/index.d.ts +27 -21
  204. package/dist/solution/ScanOrder/index.js +509 -454
  205. package/dist/solution/ScanOrder/types.d.ts +33 -11
  206. package/dist/solution/ScanOrder/types.js +4 -5
  207. package/dist/solution/ScanOrder/utils.d.ts +4 -1
  208. package/dist/solution/ScanOrder/utils.js +109 -34
  209. package/dist/solution/ShopDiscount/index.js +6 -11
  210. package/dist/solution/UnifiedBookingSales/index.d.ts +183 -0
  211. package/dist/solution/UnifiedBookingSales/index.js +1871 -0
  212. package/dist/solution/UnifiedBookingSales/types.d.ts +143 -0
  213. package/dist/solution/UnifiedBookingSales/types.js +11 -0
  214. package/dist/solution/VenueBooking/index.d.ts +34 -20
  215. package/dist/solution/VenueBooking/index.js +630 -505
  216. package/dist/solution/VenueBooking/sales-section-4-annotated.json +0 -2
  217. package/dist/solution/VenueBooking/utils/slotMerge.d.ts +6 -0
  218. package/dist/solution/VenueBooking/utils/slotMerge.js +9 -3
  219. package/dist/solution/index.d.ts +2 -0
  220. package/dist/solution/index.js +3 -1
  221. package/dist/types/index.d.ts +11 -1
  222. package/dist/utils/task.js +15 -18
  223. package/dist/utils/watch.js +0 -1
  224. package/lib/apis/picoding.js +2 -0
  225. package/lib/core/index.js +130 -131
  226. package/lib/effects/index.js +46 -57
  227. package/lib/index.js +92 -53
  228. package/lib/model/index.js +14 -21
  229. package/lib/model/strategy/adapter/dataVariant/adapter.js +38 -50
  230. package/lib/model/strategy/adapter/dataVariant/evaluator.js +162 -236
  231. package/lib/model/strategy/adapter/dataVariant/examples.js +229 -237
  232. package/lib/model/strategy/adapter/dataVariant/index.js +39 -34
  233. package/lib/model/strategy/adapter/dataVariant/type.js +57 -28
  234. package/lib/model/strategy/adapter/index.js +100 -64
  235. package/lib/model/strategy/adapter/itemRule/adapter.js +156 -144
  236. package/lib/model/strategy/adapter/itemRule/evaluator.js +36 -48
  237. package/lib/model/strategy/adapter/itemRule/examples.js +295 -225
  238. package/lib/model/strategy/adapter/itemRule/index.js +83 -57
  239. package/lib/model/strategy/adapter/itemRule/type.js +97 -36
  240. package/lib/model/strategy/adapter/promotion/adapter.js +84 -63
  241. package/lib/model/strategy/adapter/promotion/evaluator.js +383 -232
  242. package/lib/model/strategy/adapter/promotion/examples.js +139 -159
  243. package/lib/model/strategy/adapter/promotion/index.js +1 -49
  244. package/lib/model/strategy/adapter/promotion/type.js +199 -35
  245. package/lib/model/strategy/adapter/type.js +4 -16
  246. package/lib/model/strategy/adapter/walletPass/evaluator.js +180 -152
  247. package/lib/model/strategy/adapter/walletPass/example.js +217 -190
  248. package/lib/model/strategy/adapter/walletPass/index.js +75 -51
  249. package/lib/model/strategy/adapter/walletPass/locales.js +36 -58
  250. package/lib/model/strategy/adapter/walletPass/type.js +4 -16
  251. package/lib/model/strategy/adapter/walletPass/utils.js +654 -402
  252. package/lib/model/strategy/engine.js +270 -168
  253. package/lib/model/strategy/index.js +49 -34
  254. package/lib/model/strategy/strategy-example.js +243 -239
  255. package/lib/model/strategy/type.js +100 -40
  256. package/lib/modules/Account/index.js +39 -53
  257. package/lib/modules/Account/types.js +20 -33
  258. package/lib/modules/AccountList/index.js +116 -154
  259. package/lib/modules/AccountList/types.js +30 -31
  260. package/lib/modules/AccountList/utils.js +18 -30
  261. package/lib/modules/BaseModule.d.ts +2 -2
  262. package/lib/modules/BaseModule.js +26 -49
  263. package/lib/modules/BookingContext/index.d.ts +48 -0
  264. package/lib/modules/BookingContext/index.js +390 -0
  265. package/lib/modules/BookingContext/types.d.ts +102 -0
  266. package/lib/modules/BookingContext/types.js +48 -0
  267. package/lib/modules/BookingContext/utils/buildCacheItemFromOrderLine.d.ts +16 -0
  268. package/lib/modules/BookingContext/utils/buildCacheItemFromOrderLine.js +286 -0
  269. package/lib/modules/BookingContext/utils/buildNormalProductCacheItemFromOrderLine.d.ts +8 -0
  270. package/lib/modules/BookingContext/utils/buildNormalProductCacheItemFromOrderLine.js +148 -0
  271. package/lib/modules/BookingContext/utils/cacheItemToBookingInput.d.ts +76 -0
  272. package/lib/modules/BookingContext/utils/cacheItemToBookingInput.js +242 -0
  273. package/lib/modules/BookingContext/utils/flexible.d.ts +28 -0
  274. package/lib/modules/BookingContext/utils/flexible.js +63 -0
  275. package/lib/modules/BookingContext/utils/formatResourceList.d.ts +8 -0
  276. package/lib/modules/BookingContext/utils/formatResourceList.js +35 -0
  277. package/lib/modules/BookingContext/utils/index.d.ts +11 -0
  278. package/lib/modules/BookingContext/utils/index.js +126 -0
  279. package/lib/modules/BookingContext/utils/noResource.d.ts +13 -0
  280. package/lib/modules/BookingContext/utils/noResource.js +78 -0
  281. package/lib/modules/BookingContext/utils/orderLineDisplay.d.ts +1 -0
  282. package/lib/modules/BookingContext/utils/orderLineDisplay.js +29 -0
  283. package/lib/modules/BookingContext/utils/productExtend.d.ts +72 -0
  284. package/lib/modules/BookingContext/utils/productExtend.js +330 -0
  285. package/lib/modules/BookingContext/utils/resourceErrors.d.ts +41 -0
  286. package/lib/modules/BookingContext/utils/resourceErrors.js +80 -0
  287. package/lib/modules/BookingContext/utils/resourceSelection.d.ts +7 -0
  288. package/lib/modules/BookingContext/utils/resourceSelection.js +24 -0
  289. package/lib/modules/BookingContext/utils/resources.d.ts +80 -0
  290. package/lib/modules/BookingContext/utils/resources.js +419 -0
  291. package/lib/modules/BookingContext/utils/serviceTimes.d.ts +55 -0
  292. package/lib/modules/BookingContext/utils/serviceTimes.js +161 -0
  293. package/lib/modules/BookingContext/utils/timeSlices.d.ts +42 -0
  294. package/lib/modules/BookingContext/utils/timeSlices.js +114 -0
  295. package/lib/modules/Cart/index.js +170 -124
  296. package/lib/modules/Cart/types.js +46 -51
  297. package/lib/modules/Cart/utils/cartAccount.js +40 -39
  298. package/lib/modules/Cart/utils/cartDate.js +56 -61
  299. package/lib/modules/Cart/utils/cartDiscount.js +28 -33
  300. package/lib/modules/Cart/utils/cartNote.js +27 -32
  301. package/lib/modules/Cart/utils/cartProduct.js +331 -244
  302. package/lib/modules/Cart/utils/cartRelationForms.js +29 -35
  303. package/lib/modules/Cart/utils/cartResource.js +72 -78
  304. package/lib/modules/Cart/utils/changePrice.js +37 -67
  305. package/lib/modules/Cart/utils/index.js +106 -48
  306. package/lib/modules/Customer/constants.js +13 -34
  307. package/lib/modules/Customer/index.d.ts +20 -0
  308. package/lib/modules/Customer/index.js +270 -154
  309. package/lib/modules/Customer/types.d.ts +2 -0
  310. package/lib/modules/Customer/types.js +38 -35
  311. package/lib/modules/Date/index.js +116 -115
  312. package/lib/modules/Date/types.js +16 -28
  313. package/lib/modules/Date/utils.js +174 -123
  314. package/lib/modules/Discount/index.d.ts +2 -1
  315. package/lib/modules/Discount/index.js +133 -113
  316. package/lib/modules/Discount/types.d.ts +1 -0
  317. package/lib/modules/Discount/types.js +9 -31
  318. package/lib/modules/Guests/index.js +30 -56
  319. package/lib/modules/Guests/types.js +23 -33
  320. package/lib/modules/Holder/index.js +209 -189
  321. package/lib/modules/Holder/types.js +4 -16
  322. package/lib/modules/Holder/utils.js +20 -49
  323. package/lib/modules/OpenData/index.d.ts +8 -0
  324. package/lib/modules/OpenData/index.js +81 -74
  325. package/lib/modules/OpenData/types.js +4 -16
  326. package/lib/modules/OpenData/utils.js +44 -78
  327. package/lib/modules/Order/index.d.ts +449 -24
  328. package/lib/modules/Order/index.js +3834 -489
  329. package/lib/modules/Order/types.d.ts +693 -88
  330. package/lib/modules/Order/types.js +78 -33
  331. package/lib/modules/Order/utils/bundleDiscountHydration.d.ts +5 -0
  332. package/lib/modules/Order/utils/bundleDiscountHydration.js +106 -0
  333. package/lib/modules/Order/utils/manualProductDiscount.d.ts +106 -0
  334. package/lib/modules/Order/utils/manualProductDiscount.js +207 -0
  335. package/lib/modules/Order/utils/orderCollectionIdentity.d.ts +53 -0
  336. package/lib/modules/Order/utils/orderCollectionIdentity.js +353 -0
  337. package/lib/modules/Order/utils.d.ts +167 -18
  338. package/lib/modules/Order/utils.js +1247 -345
  339. package/lib/modules/Payment/cash.js +20 -33
  340. package/lib/modules/Payment/cashRecommendationAlgorithm.js +145 -76
  341. package/lib/modules/Payment/eftpos.js +16 -30
  342. package/lib/modules/Payment/index.d.ts +10 -9
  343. package/lib/modules/Payment/index.js +542 -502
  344. package/lib/modules/Payment/mx51.js +1 -0
  345. package/lib/modules/Payment/types.d.ts +40 -16
  346. package/lib/modules/Payment/types.js +216 -107
  347. package/lib/modules/Payment/utils.d.ts +1 -1
  348. package/lib/modules/Payment/utils.js +53 -49
  349. package/lib/modules/Payment/walletpass.js +231 -203
  350. package/lib/modules/Product/index.js +51 -46
  351. package/lib/modules/Product/types.d.ts +29 -7
  352. package/lib/modules/Product/types.js +4 -16
  353. package/lib/modules/Product/utils.js +32 -33
  354. package/lib/modules/ProductList/index.d.ts +17 -13
  355. package/lib/modules/ProductList/index.js +160 -118
  356. package/lib/modules/ProductList/types.d.ts +17 -0
  357. package/lib/modules/ProductList/types.js +9 -31
  358. package/lib/modules/Quotation/index.d.ts +22 -2
  359. package/lib/modules/Quotation/index.js +123 -89
  360. package/lib/modules/Quotation/types.d.ts +19 -0
  361. package/lib/modules/Quotation/types.js +4 -16
  362. package/lib/modules/Resource/index.js +27 -59
  363. package/lib/modules/Resource/types.js +22 -32
  364. package/lib/modules/Resource/utils.js +30 -38
  365. package/lib/modules/ResourcePlanner/index.d.ts +24 -0
  366. package/lib/modules/ResourcePlanner/index.js +147 -0
  367. package/lib/modules/ResourcePlanner/planner.d.ts +14 -0
  368. package/lib/modules/ResourcePlanner/planner.js +895 -0
  369. package/lib/modules/ResourcePlanner/types.d.ts +227 -0
  370. package/lib/modules/ResourcePlanner/types.js +5 -0
  371. package/lib/modules/Rules/index.d.ts +8 -10
  372. package/lib/modules/Rules/index.js +1286 -894
  373. package/lib/modules/Rules/types.d.ts +14 -3
  374. package/lib/modules/Rules/types.js +17 -40
  375. package/lib/modules/SalesSummary/index.d.ts +23 -26
  376. package/lib/modules/SalesSummary/index.js +130 -71
  377. package/lib/modules/SalesSummary/types.d.ts +9 -1
  378. package/lib/modules/SalesSummary/types.js +4 -16
  379. package/lib/modules/SalesSummary/utils.d.ts +3 -22
  380. package/lib/modules/SalesSummary/utils.js +738 -250
  381. package/lib/modules/ScanOrderLogger/index.js +59 -79
  382. package/lib/modules/ScanOrderLogger/providers/feishu.js +54 -78
  383. package/lib/modules/ScanOrderLogger/providers/grafana.js +13 -36
  384. package/lib/modules/ScanOrderLogger/types.js +4 -16
  385. package/lib/modules/Schedule/getDateIsInSchedule.js +345 -171
  386. package/lib/modules/Schedule/index.d.ts +18 -5
  387. package/lib/modules/Schedule/index.js +139 -114
  388. package/lib/modules/Schedule/type.js +4 -16
  389. package/lib/modules/Schedule/types.d.ts +2 -0
  390. package/lib/modules/Schedule/types.js +4 -16
  391. package/lib/modules/Schedule/utils.js +433 -291
  392. package/lib/modules/Step/index.js +40 -52
  393. package/lib/modules/Step/tyeps.js +4 -16
  394. package/lib/modules/Summary/index.js +66 -70
  395. package/lib/modules/Summary/types.js +4 -16
  396. package/lib/modules/Summary/utils.js +782 -429
  397. package/lib/modules/SurchargeList/index.d.ts +18 -0
  398. package/lib/modules/SurchargeList/index.js +95 -0
  399. package/lib/modules/SurchargeList/types.d.ts +15 -0
  400. package/lib/modules/SurchargeList/types.js +5 -0
  401. package/lib/modules/index.d.ts +3 -0
  402. package/lib/modules/index.js +245 -57
  403. package/lib/plugins/app.d.ts +2 -0
  404. package/lib/plugins/app.js +4 -16
  405. package/lib/plugins/index.js +36 -25
  406. package/lib/plugins/request.d.ts +1 -0
  407. package/lib/plugins/request.js +137 -126
  408. package/lib/plugins/shopStore.js +4 -16
  409. package/lib/plugins/user.js +4 -16
  410. package/lib/plugins/window.js +171 -179
  411. package/lib/server/index.d.ts +462 -8
  412. package/lib/server/index.js +4668 -713
  413. package/lib/server/modules/floor-plan/index.d.ts +39 -0
  414. package/lib/server/modules/floor-plan/index.js +306 -0
  415. package/lib/server/modules/floor-plan/types.d.ts +43 -0
  416. package/lib/server/modules/floor-plan/types.js +19 -0
  417. package/lib/server/modules/index.d.ts +7 -2
  418. package/lib/server/modules/index.js +106 -59
  419. package/lib/server/modules/menu/index.js +146 -122
  420. package/lib/server/modules/menu/types.js +18 -31
  421. package/lib/server/modules/order/index.d.ts +207 -14
  422. package/lib/server/modules/order/index.js +1463 -316
  423. package/lib/server/modules/order/types.d.ts +76 -5
  424. package/lib/server/modules/order/types.js +117 -32
  425. package/lib/server/modules/order/utils/filterBookings.d.ts +7 -1
  426. package/lib/server/modules/order/utils/filterBookings.js +288 -146
  427. package/lib/server/modules/order/utils/filterOrders.js +318 -64
  428. package/lib/server/modules/payment/index.d.ts +28 -0
  429. package/lib/server/modules/payment/index.js +168 -0
  430. package/lib/server/modules/payment/types.d.ts +9 -0
  431. package/lib/server/modules/payment/types.js +5 -0
  432. package/lib/server/modules/products/index.d.ts +105 -14
  433. package/lib/server/modules/products/index.js +969 -383
  434. package/lib/server/modules/products/types.d.ts +34 -1
  435. package/lib/server/modules/products/types.js +44 -34
  436. package/lib/server/modules/quotation/index.d.ts +39 -0
  437. package/lib/server/modules/quotation/index.js +220 -77
  438. package/lib/server/modules/quotation/types.js +21 -31
  439. package/lib/server/modules/resource/index.d.ts +2 -3
  440. package/lib/server/modules/resource/index.js +269 -214
  441. package/lib/server/modules/resource/types.js +42 -33
  442. package/lib/server/modules/schedule/index.d.ts +6 -4
  443. package/lib/server/modules/schedule/index.js +158 -132
  444. package/lib/server/modules/schedule/types.js +14 -21
  445. package/lib/server/modules/schedule/utils.js +334 -163
  446. package/lib/server/test.json +713 -0
  447. package/lib/server/types.d.ts +2 -0
  448. package/lib/server/types.js +4 -16
  449. package/lib/server/utils/index.js +25 -23
  450. package/lib/server/utils/product.js +197 -140
  451. package/lib/server/utils/schedule.js +34 -41
  452. package/lib/server/utils/small-ticket.d.ts +71 -0
  453. package/lib/server/utils/small-ticket.js +926 -0
  454. package/lib/server/utils/time.js +40 -49
  455. package/lib/solution/BaseSales/index.d.ts +378 -0
  456. package/lib/solution/BaseSales/index.js +2367 -0
  457. package/lib/solution/BaseSales/types.d.ts +394 -0
  458. package/lib/solution/BaseSales/types.js +47 -0
  459. package/lib/solution/BaseSales/utils/cartPromotion.d.ts +287 -0
  460. package/lib/solution/BaseSales/utils/cartPromotion.js +1155 -0
  461. package/lib/solution/BaseSales/utils/parseSalesResponse.d.ts +144 -0
  462. package/lib/solution/BaseSales/utils/parseSalesResponse.js +140 -0
  463. package/lib/solution/BaseSales/utils/quotationPrice.d.ts +18 -0
  464. package/lib/solution/BaseSales/utils/quotationPrice.js +231 -0
  465. package/lib/solution/BaseSales/utils/transformBaseProductToOrderProduct.d.ts +63 -0
  466. package/lib/solution/BaseSales/utils/transformBaseProductToOrderProduct.js +215 -0
  467. package/lib/solution/BaseSales/utils.d.ts +40 -0
  468. package/lib/solution/BaseSales/utils.js +259 -0
  469. package/lib/solution/BookingByStep/index.js +1530 -1343
  470. package/lib/solution/BookingByStep/types.js +40 -99
  471. package/lib/solution/BookingByStep/utils/capacity.js +192 -160
  472. package/lib/solution/BookingByStep/utils/products.js +42 -52
  473. package/lib/solution/BookingByStep/utils/resources.js +527 -330
  474. package/lib/solution/BookingByStep/utils/stock.js +65 -44
  475. package/lib/solution/BookingByStep/utils/timeslots.js +129 -131
  476. package/lib/solution/BookingTicket/index.d.ts +393 -24
  477. package/lib/solution/BookingTicket/index.js +1459 -160
  478. package/lib/solution/BookingTicket/types.d.ts +191 -6
  479. package/lib/solution/BookingTicket/types.js +101 -66
  480. package/lib/solution/BookingTicket/utils/addProductDecision.d.ts +117 -0
  481. package/lib/solution/BookingTicket/utils/addProductDecision.js +353 -0
  482. package/lib/solution/BookingTicket/utils/addTimeAvailability.d.ts +35 -0
  483. package/lib/solution/BookingTicket/utils/addTimeAvailability.js +94 -0
  484. package/lib/solution/BookingTicket/utils/bookingStatus.d.ts +77 -0
  485. package/lib/solution/BookingTicket/utils/bookingStatus.js +136 -0
  486. package/lib/solution/BookingTicket/utils/cartView.d.ts +29 -0
  487. package/lib/solution/BookingTicket/utils/cartView.js +200 -0
  488. package/lib/solution/BookingTicket/utils/exampleData.d.ts +123 -0
  489. package/lib/solution/BookingTicket/utils/exampleData.js +185 -0
  490. package/lib/solution/BookingTicket/utils/orderCustomer.d.ts +4 -0
  491. package/lib/solution/BookingTicket/utils/orderCustomer.js +35 -0
  492. package/lib/solution/BookingTicket/utils/resolveBestAddTimePlan.d.ts +185 -0
  493. package/lib/solution/BookingTicket/utils/resolveBestAddTimePlan.js +387 -0
  494. package/lib/solution/BookingTicket/utils/scan/applyGlobalScan.d.ts +36 -0
  495. package/lib/solution/BookingTicket/utils/scan/applyGlobalScan.js +220 -0
  496. package/lib/solution/BookingTicket/utils/scan/cloudSearch.d.ts +4 -0
  497. package/lib/solution/BookingTicket/utils/scan/cloudSearch.js +85 -80
  498. package/lib/solution/BookingTicket/utils/scan/formatGlobalScan.d.ts +32 -0
  499. package/lib/solution/BookingTicket/utils/scan/formatGlobalScan.js +64 -0
  500. package/lib/solution/BookingTicket/utils/scan/handleScan.d.ts +3 -1
  501. package/lib/solution/BookingTicket/utils/scan/handleScan.js +84 -80
  502. package/lib/solution/BookingTicket/utils/scan/index.d.ts +5 -0
  503. package/lib/solution/BookingTicket/utils/scan/index.js +103 -95
  504. package/lib/solution/BookingTicket/utils/scan/scanCache.js +90 -43
  505. package/lib/solution/BookingTicket/utils/scan/scanProductValues.d.ts +21 -0
  506. package/lib/solution/BookingTicket/utils/scan/scanProductValues.js +115 -0
  507. package/lib/solution/BookingTicket/utils/sessionCatalogStale.d.ts +23 -0
  508. package/lib/solution/BookingTicket/utils/sessionCatalogStale.js +83 -0
  509. package/lib/solution/BuyTickets/index.js +67 -70
  510. package/lib/solution/BuyTickets/types.js +22 -38
  511. package/lib/solution/Checkout/index.d.ts +1 -0
  512. package/lib/solution/Checkout/index.js +1461 -1169
  513. package/lib/solution/Checkout/types.js +91 -61
  514. package/lib/solution/Checkout/utils/index.js +228 -159
  515. package/lib/solution/PlaceOrder/index.js +55 -0
  516. package/lib/solution/RegisterAndLogin/config.js +495 -470
  517. package/lib/solution/RegisterAndLogin/index.js +633 -630
  518. package/lib/solution/RegisterAndLogin/types.js +189 -76
  519. package/lib/solution/RegisterAndLogin/utils.js +183 -125
  520. package/lib/solution/Sales/index.d.ts +50 -5
  521. package/lib/solution/Sales/index.js +495 -220
  522. package/lib/solution/Sales/types.d.ts +26 -2
  523. package/lib/solution/Sales/types.js +27 -33
  524. package/lib/solution/ScanOrder/index.d.ts +27 -21
  525. package/lib/solution/ScanOrder/index.js +908 -904
  526. package/lib/solution/ScanOrder/types.d.ts +33 -11
  527. package/lib/solution/ScanOrder/types.js +33 -34
  528. package/lib/solution/ScanOrder/utils.d.ts +4 -1
  529. package/lib/solution/ScanOrder/utils.js +457 -344
  530. package/lib/solution/ShopDiscount/index.js +230 -236
  531. package/lib/solution/ShopDiscount/types.js +15 -37
  532. package/lib/solution/ShopDiscount/utils.js +309 -172
  533. package/lib/solution/UnifiedBookingSales/index.d.ts +183 -0
  534. package/lib/solution/UnifiedBookingSales/index.js +1050 -0
  535. package/lib/solution/UnifiedBookingSales/types.d.ts +143 -0
  536. package/lib/solution/UnifiedBookingSales/types.js +14 -0
  537. package/lib/solution/VenueBooking/index.d.ts +34 -20
  538. package/lib/solution/VenueBooking/index.js +982 -950
  539. package/lib/solution/VenueBooking/sales-section-4-annotated.json +0 -2
  540. package/lib/solution/VenueBooking/types.js +20 -40
  541. package/lib/solution/VenueBooking/utils/dateSummary.js +22 -54
  542. package/lib/solution/VenueBooking/utils/resource.js +31 -54
  543. package/lib/solution/VenueBooking/utils/slotMerge.d.ts +6 -0
  544. package/lib/solution/VenueBooking/utils/slotMerge.js +96 -108
  545. package/lib/solution/VenueBooking/utils/smartPricing.js +112 -100
  546. package/lib/solution/VenueBooking/utils/timeSlot.js +133 -132
  547. package/lib/solution/VenueBooking/utils.js +130 -67
  548. package/lib/solution/index.d.ts +2 -0
  549. package/lib/solution/index.js +124 -37
  550. package/lib/store/createStore.js +32 -29
  551. package/lib/types/index.d.ts +11 -1
  552. package/lib/types/index.js +4 -16
  553. package/lib/utils/task.js +36 -36
  554. package/lib/utils/watch.js +81 -47
  555. package/package.json +2 -1
  556. package/dist/solution/Checkout/appointmentDemo.json +0 -1
  557. package/lib/solution/Checkout/appointmentDemo.json +0 -1
@@ -1,138 +1,364 @@
1
- var __create = Object.create;
2
- var __defProp = Object.defineProperty;
3
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
- var __getOwnPropNames = Object.getOwnPropertyNames;
5
- var __getProtoOf = Object.getPrototypeOf;
6
- var __hasOwnProp = Object.prototype.hasOwnProperty;
7
- var __export = (target, all) => {
8
- for (var name in all)
9
- __defProp(target, name, { get: all[name], enumerable: true });
10
- };
11
- var __copyProps = (to, from, except, desc) => {
12
- if (from && typeof from === "object" || typeof from === "function") {
13
- for (let key of __getOwnPropNames(from))
14
- if (!__hasOwnProp.call(to, key) && key !== except)
15
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
16
- }
17
- return to;
18
- };
19
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
20
- // If the importer is in node compatibility mode or this is not an ESM
21
- // file that has been converted to a CommonJS file using a Babel-
22
- // compatible transform (i.e. "__esModule" has not been set), then set
23
- // "default" to the CommonJS "module.exports" for node compatibility.
24
- isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
25
- mod
26
- ));
27
- var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
28
-
29
- // src/modules/Order/index.ts
30
- var Order_exports = {};
31
- __export(Order_exports, {
32
- OrderModule: () => OrderModule
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
33
5
  });
34
- module.exports = __toCommonJS(Order_exports);
35
- var import_BaseModule = require("../BaseModule");
36
- var import_utils = require("./utils");
37
- var import_utils2 = require("../Product/utils");
38
- var import_dayjs = __toESM(require("dayjs"));
39
- var import_utils3 = require("../../solution/ScanOrder/utils");
40
- var import_Discount = require("../Discount");
41
- var import_Rules = require("../Rules");
42
- var import_types = require("../Rules/types");
43
- var import_decimal = __toESM(require("decimal.js"));
44
- var OrderModule = class extends import_BaseModule.BaseModule {
45
- constructor(name, version) {
46
- super(name || "order", version);
47
- this.defaultName = "order";
48
- this.defaultVersion = "1.0.0";
6
+ exports.OrderModule = void 0;
7
+ var _BaseModule = require("../BaseModule");
8
+ var _types = require("./types");
9
+ var _utils = require("./utils");
10
+ var _utils2 = require("../Product/utils");
11
+ var _dayjs = _interopRequireDefault(require("dayjs"));
12
+ var _cartPromotion = require("../../solution/BaseSales/utils/cartPromotion");
13
+ var _utils3 = require("../../solution/ScanOrder/utils");
14
+ var _manualProductDiscount = require("./utils/manualProductDiscount");
15
+ var _bundleDiscountHydration = require("./utils/bundleDiscountHydration");
16
+ var _orderCollectionIdentity = require("./utils/orderCollectionIdentity");
17
+ var _Discount = require("../Discount");
18
+ var _Rules = require("../Rules");
19
+ var _types2 = require("../Rules/types");
20
+ var _decimal = _interopRequireDefault(require("decimal.js"));
21
+ var _lodashEs = require("lodash-es");
22
+ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
23
+ function normalizeDiscountListSignature(discountList) {
24
+ return (discountList || []).map(item => ({
25
+ type: item?.type ?? '',
26
+ discount_id: item?.discount_id ?? null,
27
+ order_discount_id: item?.order_discount_id ?? null,
28
+ amount: new _decimal.default(Number(item?.amount) || 0).toFixed(2)
29
+ }));
30
+ }
31
+ function isSameDiscountList(left, right) {
32
+ if (!Array.isArray(left) || !Array.isArray(right)) return false;
33
+ return JSON.stringify(normalizeDiscountListSignature(left)) === JSON.stringify(normalizeDiscountListSignature(right));
34
+ }
35
+ function isManualProductDiscountProduct(product) {
36
+ return product?.metadata?.is_manual_discount === true || product?.metadata?.is_manual_discount === 1 || (product?.discount_list || []).some(item => item?.type === 'product');
37
+ }
38
+ const ORDER_PAYMENT_STATUS_PAID = 'paid';
39
+ const ORDER_PAYMENT_STATUS_PENDING = 'payment_pending';
40
+ function isVoucherPaymentRecord(payment) {
41
+ return payment?.voucher_id !== undefined && Number(payment.voucher_id) !== 0;
42
+ }
43
+ function hasSyncedOrderPaymentRecord(payment) {
44
+ return payment?.order_payment_id !== undefined && payment.order_payment_id !== null;
45
+ }
46
+ function isPendingVoucherPaymentRecord(payment) {
47
+ return isVoucherPaymentRecord(payment) && payment?.status === ORDER_PAYMENT_STATUS_PENDING;
48
+ }
49
+ function isPaidPaymentRecord(payment) {
50
+ return payment?.status === ORDER_PAYMENT_STATUS_PAID;
51
+ }
52
+ function isCommittedVoucherPaymentRecord(payment) {
53
+ return isVoucherPaymentRecord(payment) && payment?.status !== 'voided' && (hasSyncedOrderPaymentRecord(payment) || isPaidPaymentRecord(payment));
54
+ }
55
+ class OrderModule extends _BaseModule.BaseModule {
56
+ defaultName = 'order';
57
+ defaultVersion = '1.0.0';
58
+ store;
59
+ request;
60
+ logger; // LoggerManager 实例
61
+ window;
62
+ cacheId;
63
+ salesSummaryModuleName;
64
+ rulesHooksOverride;
65
+ moduleHooksOverride;
66
+ orderHooks;
67
+ otherParams;
68
+ // ─── 促销/赠品 ────────────────────────────────────
69
+ /** 评估器引用;由 SalesSdkProvider 注入,未注入时 applyPromotion 静默跳过 */
70
+ promotionEvaluator = null;
71
+ /** 赠品选择 resolver;由 SDK host bridge 注入 */
72
+ giftSelectResolver = null;
73
+ /** applyPromotion 递归保护 flag(写路径同步调 applyPromotion,禁止重入) */
74
+ isApplyingPromotion = false;
75
+ /** 商品曾应用过客户券卡,客户变更剥离后需强制跑一次 calcDiscount 复位价格 */
76
+ hasActiveCustomerBoundDiscount = false;
77
+ /** 最近一次 applyPromotion 的未满足促销提示 */
78
+ lastUnfulfilledPromotions = [];
79
+ /** 最近一次 applyPromotion 的赠品操作 diff(debug / SDK 读取使用) */
80
+ lastGiftActions = null;
81
+ /**
82
+ * 是否将 tempOrder 写入 IndexedDB 草稿(orders store)。
83
+ * BigSale 弹窗等短生命周期场景通过 setEnableTempOrderPersist(false) 关闭。
84
+ */
85
+ draftPersistEnabled = true;
86
+ collectHydratedEditDiscounts(products) {
87
+ const discountMap = new Map();
88
+ for (const product of (0, _bundleDiscountHydration.expandHydratedProductsForDiscountCollection)(products)) {
89
+ for (const item of product?.discount_list || []) {
90
+ const type = item?.type ?? item?.tag;
91
+ if (!type || type === 'product') continue;
92
+ const resourceId = item?.discount?.resource_id ?? item?.resource_id ?? item?.discount_id ?? item?.order_discount_id;
93
+ if (resourceId === undefined || resourceId === null || resourceId === '') continue;
94
+ const id = Number(resourceId);
95
+ const key = Number.isFinite(id) ? id : String(resourceId);
96
+ const title = item?.discount?.title ?? {};
97
+ const amount = Number(item?.amount || 0);
98
+ const detail = {
99
+ amount: String(item?.amount ?? '0'),
100
+ type,
101
+ resource_id: key,
102
+ title,
103
+ original_amount: String(item?.discount?.original_amount ?? item?.amount ?? '0'),
104
+ num: Number(item?.metadata?.num ?? product?.num ?? product?.product_quantity ?? 1) || 1,
105
+ metadata: item?.metadata,
106
+ discount: item?.discount,
107
+ _num: Number(item?.metadata?.num ?? product?.num ?? product?.product_quantity ?? 1) || 1
108
+ };
109
+ const existed = discountMap.get(key);
110
+ if (existed) {
111
+ existed.amount = Number(existed.amount || 0) + amount;
112
+ existed.used_par_value = String(Number(existed.used_par_value || 0) + amount);
113
+ existed.balance = String(Number(existed.balance || 0) + amount);
114
+ const productIds = Array.isArray(existed.limited_relation_product_data?.product_ids) ? existed.limited_relation_product_data.product_ids : [];
115
+ if (product?.product_id !== undefined && product?.product_id !== null && !productIds.includes(product.product_id)) {
116
+ existed.limited_relation_product_data = {
117
+ ...(existed.limited_relation_product_data || {}),
118
+ product_ids: [...productIds, product.product_id]
119
+ };
120
+ }
121
+ existed.applicableProductDetails = [...(existed.applicableProductDetails || []), detail];
122
+ existed.appliedProductDetails = [...(existed.appliedProductDetails || []), detail];
123
+ continue;
124
+ }
125
+ discountMap.set(key, {
126
+ id: key,
127
+ product_name: '',
128
+ encoded: '',
129
+ code: '',
130
+ tag: type,
131
+ product_id: item?.discount?.discount_product_id ?? product?.product_id ?? 0,
132
+ relation_type: '',
133
+ par_value: String(item?.discount?.original_amount ?? item?.amount ?? '0'),
134
+ used_par_value: String(item?.amount ?? '0'),
135
+ limit_status: 'enable',
136
+ limited_relation_product_data: {
137
+ id: 0,
138
+ type: 'products',
139
+ product_ids: [product?.product_id].filter(value => value !== undefined && value !== null),
140
+ product_collection_id: [],
141
+ filter: 0,
142
+ exclude_product_ids: []
143
+ },
144
+ balance: String(item?.amount ?? '0'),
145
+ format_title: title,
146
+ product: {
147
+ id: product?.product_id ?? 0,
148
+ title: '',
149
+ is_charge_tax: product?.is_charge_tax ?? 0,
150
+ base_price: String(product?.original_price ?? product?.selling_price ?? '0')
151
+ },
152
+ type,
153
+ resource_id: key,
154
+ isEditMode: true,
155
+ isSelected: true,
156
+ isAvailable: true,
157
+ isUsed: true,
158
+ isDisabled: true,
159
+ amount,
160
+ discount: {
161
+ ...(item?.discount || {}),
162
+ resource_id: key,
163
+ discount_product_id: item?.discount?.discount_product_id ?? product?.product_id ?? 0,
164
+ discount_calculation_mode: item?.discount?.discount_calculation_mode ?? 'item_level'
165
+ },
166
+ applicableProductDetails: [detail],
167
+ appliedProductDetails: [detail]
168
+ });
169
+ }
170
+ }
171
+ return Array.from(discountMap.values());
49
172
  }
173
+
50
174
  /**
51
175
  * Populate `savedAmount` on each discount based on product-level discount details.
52
176
  * Only selected discounts get a non-zero value.
53
177
  */
54
178
  static populateSavedAmounts(productList, discountList) {
55
- var _a, _b;
56
- const savedMap = /* @__PURE__ */ new Map();
179
+ const savedMap = new Map();
180
+ const editModeSavedMap = new Map();
181
+ const runtimeSavedMap = new Map();
182
+ const editModeDiscountKeys = new Set((discountList || []).filter(discount => discount?.isEditMode).map(discount => discount?.id).filter(id => id !== undefined && id !== null));
183
+ const addDiscountAmount = (targetMap, discountKey, amount) => {
184
+ targetMap.set(discountKey, (targetMap.get(discountKey) || new _decimal.default(0)).plus(amount));
185
+ };
186
+ const addProductDiscountAmount = (discount, quantity, isPersistedProduct) => {
187
+ const discountKey = discount.discount?.resource_id || discount.id;
188
+ if (discountKey == null) return;
189
+ const qty = Number(quantity) || 1;
190
+ const amount = new _decimal.default(discount.amount || 0).times(qty).plus(discount.metadata?.product_discount_difference || 0);
191
+ addDiscountAmount(savedMap, discountKey, amount);
192
+ addDiscountAmount(isPersistedProduct || discount.order_discount_id != null ? editModeSavedMap : runtimeSavedMap, discountKey, amount);
193
+ };
57
194
  for (const product of productList) {
58
195
  const qty = product.num || 1;
196
+ const isPersistedProduct = Boolean(product.order_detail_id || product.orderDetailId || product.booking_id);
59
197
  for (const pd of product.discount_list || []) {
60
- const discountKey = ((_a = pd.discount) == null ? void 0 : _a.resource_id) || pd.id;
61
- if (discountKey == null)
62
- continue;
63
- const amount = new import_decimal.default(pd.amount || 0).times(qty).plus(((_b = pd.metadata) == null ? void 0 : _b.product_discount_difference) || 0);
64
- savedMap.set(discountKey, (savedMap.get(discountKey) || new import_decimal.default(0)).plus(amount));
198
+ addProductDiscountAmount(pd, qty, isPersistedProduct);
199
+ }
200
+ for (const bundle of product.product_bundle || []) {
201
+ const isPersistedBundleProduct = isPersistedProduct || Boolean(bundle?.order_detail_id || bundle?.orderDetailId || bundle?.booking_id);
202
+ const bundleQty = new _decimal.default(Number(qty) || 1).times(Number(bundle?.num ?? bundle?.quantity ?? 1) || 1).toNumber();
203
+ for (const pd of bundle?.discount_list || []) {
204
+ addProductDiscountAmount(pd, bundleQty, isPersistedBundleProduct);
205
+ }
65
206
  }
66
207
  }
67
208
  for (const d of discountList) {
68
209
  const key = d.id;
69
- d.savedAmount = d.isSelected && key != null && savedMap.has(key) ? savedMap.get(key).toNumber() : 0;
210
+ const sourceMap = d.isEditMode ? editModeSavedMap : editModeDiscountKeys.has(key) ? runtimeSavedMap : savedMap;
211
+ d.savedAmount = d.isSelected && key != null && sourceMap.has(key) ? sourceMap.get(key).toNumber() : 0;
70
212
  }
71
213
  }
214
+ applyProductDiscountPrices(products) {
215
+ return (products || []).map(product => {
216
+ if (isManualProductDiscountProduct(product)) return product;
217
+ const discountItems = Array.isArray(product.discount_list) ? product.discount_list : [];
218
+ const hasOnlyPersistedDiscountSnapshots = product.order_detail_id !== undefined && product.order_detail_id !== null && discountItems.length > 0 && discountItems.every(discount => discount?.order_discount_id !== undefined && discount?.order_discount_id !== null);
219
+ if (hasOnlyPersistedDiscountSnapshots) {
220
+ return product;
221
+ }
222
+ const totalPerUnitDiscount = (0, _utils.resolveEffectivePerUnitDiscount)(product);
223
+ const optionSum = (0, _utils.sumOptionUnitPrice)((0, _utils.getProductSkuOptions)(product));
224
+ const sourcePrice = product.metadata?.source_product_price ?? (product.metadata?.main_product_original_price != null ? new _decimal.default(Number(product.metadata.main_product_original_price) || 0).minus(optionSum).toFixed(2) : product.original_price ?? '0');
225
+ const newSourceSellingPrice = new _decimal.default(Number(sourcePrice) || 0).minus(totalPerUnitDiscount).toDecimalPlaces(2).toString();
226
+ const newMainSellingPrice = new _decimal.default(Number(newSourceSellingPrice) || 0).plus(optionSum).toDecimalPlaces(2).toFixed(2);
227
+ if (product.metadata) {
228
+ product.metadata.main_product_selling_price = newMainSellingPrice;
229
+ product.metadata.price_schema_version = 2;
230
+ }
231
+ if (Array.isArray(product.product_bundle)) {
232
+ product.product_bundle = product.product_bundle.map(bundle => {
233
+ const hasBundleDiscount = Array.isArray(bundle?.discount_list) && bundle.discount_list.length > 0;
234
+ if (hasBundleDiscount) return bundle;
235
+ const restoredPrice = bundle?.original_price ?? bundle?.product_price ?? bundle?.price;
236
+ if (restoredPrice === undefined || restoredPrice === null || restoredPrice === '') return bundle;
237
+ return {
238
+ ...bundle,
239
+ price: restoredPrice,
240
+ bundle_selling_price: restoredPrice
241
+ };
242
+ });
243
+ }
244
+ product.selling_price = (0, _utils.composeLinePrice)({
245
+ mainPrice: newMainSellingPrice,
246
+ bundle: product.product_bundle
247
+ });
248
+ return product;
249
+ });
250
+ }
251
+ constructor(name, version) {
252
+ super(name || 'order', version);
253
+ }
72
254
  async initialize(core, options) {
73
- var _a;
74
255
  this.core = core;
75
256
  this.store = options.store;
76
257
  if (!this.store.tempOrder) {
77
258
  this.store.tempOrder = null;
78
259
  }
260
+ if (!this.store.summary) {
261
+ this.store.summary = (0, _utils.createEmptySummary)();
262
+ }
79
263
  if (!Array.isArray(this.store.availableWalletIds)) {
80
264
  this.store.availableWalletIds = [];
81
265
  }
82
- this.request = this.core.getPlugin("request");
83
- this.window = this.core.getPlugin("window");
266
+ this.request = this.core.getPlugin('request');
267
+ const appPlugin = this.core.getPlugin('app');
268
+ if (!appPlugin) {
269
+ console.error('Order 模块需要 app 插件支持');
270
+ }
271
+ const app = appPlugin?.getApp?.();
272
+ this.logger = app?.logger;
273
+ this.window = this.core.getPlugin('window');
84
274
  const otherParams = options.otherParams || {};
85
275
  this.cacheId = otherParams.cacheId;
86
276
  this.salesSummaryModuleName = otherParams.salesSummaryModuleName;
87
- this.rulesHooksOverride = (_a = otherParams.rules) == null ? void 0 : _a.hooks;
277
+ this.rulesHooksOverride = otherParams.rules?.hooks;
278
+ this.moduleHooksOverride = options.hooks;
279
+ this.orderHooks = this.moduleHooksOverride || otherParams.order?.hooks || otherParams.hooks?.order;
280
+ this.otherParams = otherParams;
88
281
  this.registerDiscountModules(options);
89
- this.restoreTempOrderFromStorage();
90
- const appPlugin = this.core.getPlugin("app");
91
- if (!appPlugin) {
92
- console.warn("Order 模块需要 app 插件支持");
282
+ this.logInfo('OrderModule initialized successfully');
283
+ }
284
+ async destroy() {
285
+ const childModules = [this.store?.discount, this.store?.rules];
286
+ for (const childModule of childModules) {
287
+ if (!childModule || typeof childModule.destroy !== 'function') continue;
288
+ try {
289
+ // Order 内部注册的子模块需要跟随父模块释放,避免 core 中残留独立模块。
290
+ await childModule.destroy();
291
+ } catch (error) {
292
+ const errorMessage = error instanceof Error ? error.message : String(error);
293
+ this.logWarning('Destroy child module failed', {
294
+ module: childModule.name,
295
+ error: errorMessage
296
+ });
297
+ }
298
+ }
299
+ if (this.store) {
300
+ this.store.discount = null;
301
+ this.store.rules = null;
93
302
  }
94
- const app = appPlugin == null ? void 0 : appPlugin.getApp();
95
- if (app)
96
- this.logger = app.logger;
97
- this.logInfo("OrderModule initialized successfully");
303
+ super.destroy();
304
+ }
305
+
306
+ /**
307
+ * 更新父级解决方案透传的运行态参数,并同步 Order 内部依赖的派生字段。
308
+ *
309
+ * @example
310
+ * orderModule.updateOtherParams({ cacheId: 'cache-2', channel: 'pos' });
311
+ */
312
+ updateOtherParams(params) {
313
+ const otherParams = params || {};
314
+ this.cacheId = otherParams.cacheId;
315
+ this.salesSummaryModuleName = otherParams.salesSummaryModuleName;
316
+ this.rulesHooksOverride = otherParams.rules?.hooks;
317
+ this.orderHooks = this.moduleHooksOverride || otherParams.order?.hooks || otherParams.hooks?.order;
318
+ this.otherParams = otherParams;
98
319
  }
320
+
99
321
  /**
100
322
  * 记录信息日志
101
323
  */
102
324
  logInfo(title, metadata) {
103
325
  if (this.logger) {
104
326
  this.logger.addLog({
105
- type: "info",
327
+ type: 'info',
106
328
  title: `[OrderModule] ${title}`,
107
329
  metadata: metadata || {}
108
330
  });
109
331
  }
110
332
  }
333
+
111
334
  /**
112
335
  * 记录警告日志
113
336
  */
114
337
  logWarning(title, metadata) {
115
338
  if (this.logger) {
116
339
  this.logger.addLog({
117
- type: "warning",
340
+ type: 'warning',
118
341
  title: `[OrderModule] ${title}`,
119
342
  metadata: metadata || {}
120
343
  });
121
344
  }
122
345
  }
346
+
123
347
  /**
124
348
  * 记录错误日志
125
349
  */
126
350
  logError(title, metadata) {
127
351
  if (this.logger) {
128
352
  this.logger.addLog({
129
- type: "error",
353
+ type: 'error',
130
354
  title: `[OrderModule] ${title}`,
131
355
  metadata: metadata || {}
132
356
  });
133
357
  }
134
358
  }
359
+
135
360
  // ─── Discount: 子模块注册 ───
361
+
136
362
  registerDiscountModules(options) {
137
363
  let targetCacheData = {};
138
364
  if (this.cacheId && this.window) {
@@ -140,14 +366,15 @@ var OrderModule = class extends import_BaseModule.BaseModule {
140
366
  if (sessionData) {
141
367
  try {
142
368
  const data = JSON.parse(sessionData);
143
- targetCacheData = (data == null ? void 0 : data[this.cacheId]) || {};
369
+ targetCacheData = data?.[this.cacheId] || {};
144
370
  } catch {
371
+ // sessionStorage 损坏则忽略,按空 initialState 走
145
372
  }
146
373
  }
147
374
  }
148
- const discount = new import_Discount.DiscountModule(`${this.name}_discount`);
375
+ const discount = new _Discount.DiscountModule(`${this.name}_discount`);
149
376
  this.core.registerModule(discount, {
150
- initialState: targetCacheData == null ? void 0 : targetCacheData[discount.name],
377
+ initialState: targetCacheData?.[discount.name],
151
378
  otherParams: {
152
379
  fatherModule: this.name,
153
380
  openCache: !!this.cacheId,
@@ -155,192 +382,400 @@ var OrderModule = class extends import_BaseModule.BaseModule {
155
382
  }
156
383
  });
157
384
  this.store.discount = discount;
158
- const rules = new import_Rules.RulesModule(`${this.name}_rules`);
385
+ const rules = new _Rules.RulesModule(`${this.name}_rules`);
159
386
  this.core.registerModule(rules, {
160
387
  hooks: this.rulesHooksOverride || this.createDefaultRulesHooks()
161
388
  });
162
389
  this.store.rules = rules;
163
390
  }
164
391
  createDefaultRulesHooks() {
165
- return (0, import_utils.createDefaultOrderRulesHooks)();
392
+ return (0, _utils.createDefaultOrderRulesHooks)();
166
393
  }
394
+
167
395
  // ─── Discount: 公共 API ───
396
+
168
397
  async loadDiscountConfig(params) {
169
- var _a, _b, _c, _d;
170
- const prepareConfig = await ((_a = this.store.discount) == null ? void 0 : _a.loadPrepareConfig({
398
+ const orderId = params.orderId || this.store.tempOrder?.order_id || undefined;
399
+ const prepareConfigResult = await this.store.discount?.loadPrepareConfig({
171
400
  customer_id: params.customerId,
172
- action: params.action || "create",
401
+ action: params.action || (orderId ? 'edit' : 'create'),
173
402
  with_good_pass: 1,
174
403
  with_discount_card: 1,
175
404
  with_wallet_pass_holder: 1,
176
405
  with_available_wallet_flag: 1,
177
406
  with_available_wallet_pass_flag: 1,
178
- request_timezone: Intl.DateTimeFormat().resolvedOptions().timeZone
179
- }));
180
- const discountList = prepareConfig == null ? void 0 : prepareConfig.discountList;
181
- this.store.availableWalletIds = (prepareConfig == null ? void 0 : prepareConfig.availableWalletIds) || [];
407
+ request_timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
408
+ ...(orderId ? {
409
+ order_id: orderId
410
+ } : {})
411
+ });
412
+ const discountList = prepareConfigResult?.discountList;
413
+ this.store.availableWalletIds = prepareConfigResult?.availableWalletIds || [];
182
414
  if (discountList) {
183
- (_b = this.store.discount) == null ? void 0 : _b.setDiscountList(discountList);
415
+ const currentDiscountList = this.store.discount?.getDiscountList() || [];
416
+ const currentEditDiscounts = currentDiscountList.filter(discount => discount?.isEditMode);
417
+ const currentScanDiscounts = currentDiscountList.filter(discount => discount?.isScan);
418
+ const currentScanDiscountIds = new Set(currentScanDiscounts.map(discount => discount?.id));
419
+ const runtimeDiscountIds = new Set();
420
+ const runtimeDiscounts = discountList.filter(discount => {
421
+ if (currentScanDiscountIds.has(discount?.id)) return false;
422
+ if (runtimeDiscountIds.has(discount?.id)) return false;
423
+ runtimeDiscountIds.add(discount?.id);
424
+ return true;
425
+ });
426
+ const mergedDiscountList = [...currentEditDiscounts, ...currentScanDiscounts, ...runtimeDiscounts];
427
+ await this.store.discount?.setOriginalDiscountList(mergedDiscountList);
428
+ await this.store.discount?.setDiscountList(mergedDiscountList);
184
429
  }
185
- if ((_d = (_c = this.store.tempOrder) == null ? void 0 : _c.products) == null ? void 0 : _d.length) {
430
+ if (params.apply !== false && this.store.tempOrder?.products?.length) {
186
431
  this.applyDiscount();
187
432
  }
433
+ return this.getDiscountList();
188
434
  }
189
435
  getDiscountList() {
190
- var _a;
191
- return ((_a = this.store.discount) == null ? void 0 : _a.getDiscountList()) || [];
436
+ return this.store.discount?.getDiscountList() || [];
192
437
  }
193
438
  getAvailableWalletIds() {
194
- return this.store.availableWalletIds || [];
439
+ return Array.isArray(this.store.availableWalletIds) ? [...this.store.availableWalletIds] : [];
440
+ }
441
+ async syncScannedDiscountsToOriginalDiscountList(discountList) {
442
+ const scanDiscounts = (discountList || []).filter(discount => discount?.isScan);
443
+ if (!scanDiscounts.length) return;
444
+ const scanDiscountIds = new Set(scanDiscounts.map(discount => discount?.id));
445
+ const originalDiscounts = this.store.discount?.getOriginalDiscountList?.() || [];
446
+ const nextOriginalDiscounts = [...scanDiscounts, ...originalDiscounts.filter(discount => !scanDiscountIds.has(discount?.id))];
447
+ await this.store.discount?.setOriginalDiscountList?.(nextOriginalDiscounts);
448
+ }
449
+ async ensureEditDiscountConfigForProductChange(tempOrder) {
450
+ const orderId = tempOrder.order_id;
451
+ const customerId = Number(tempOrder.customer_id || tempOrder.customer?.id || tempOrder._extend?.customerSnapshot?.id || tempOrder._extend?.customerSnapshot?.customer_id || 0);
452
+ if (!orderId || !customerId || customerId === 1) return;
453
+ const currentDiscountList = this.store.discount?.getDiscountList() || [];
454
+ const hasRuntimeDiscounts = currentDiscountList.some(discount => !discount?.isEditMode);
455
+ if (hasRuntimeDiscounts) return;
456
+ await this.loadDiscountConfig({
457
+ customerId,
458
+ action: 'edit',
459
+ orderId: Number(orderId),
460
+ apply: false
461
+ });
195
462
  }
196
463
  async scanCode(code, customerId) {
197
- var _a, _b, _c, _d, _e;
198
- const resultDiscountWithReason = await ((_a = this.store.discount) == null ? void 0 : _a.batchSearch(code, { customerId })) || { discountList: [], unavailableReasonKey: null };
199
- const { discountList: resultDiscountList, unavailableReasonKey } = resultDiscountWithReason;
464
+ const resultDiscountWithReason = (await this.store.discount?.batchSearch(code, {
465
+ customerId
466
+ })) || {
467
+ discountList: [],
468
+ unavailableReasonKey: null
469
+ };
470
+ const {
471
+ discountList: resultDiscountList,
472
+ unavailableReasonKey
473
+ } = resultDiscountWithReason;
200
474
  const rulesModule = this.store.rules;
201
475
  if (!rulesModule) {
202
476
  return {
203
- type: "clientCalc",
477
+ type: 'clientCalc',
204
478
  isAvailable: false,
205
479
  discountList: this.getDiscountList(),
206
- unavailableReason: import_types.UnavailableReason.Unknown
480
+ scannedDiscountList: resultDiscountList,
481
+ unavailableReason: _types2.UnavailableReason.Unknown
207
482
  };
208
483
  }
209
484
  if (!resultDiscountList.length) {
210
485
  return {
211
- type: "server",
486
+ type: 'server',
212
487
  isAvailable: false,
213
488
  discountList: this.getDiscountList(),
489
+ scannedDiscountList: resultDiscountList,
214
490
  unavailableReasonKey
215
491
  };
216
492
  }
217
- const withScanList = resultDiscountList.map((item) => ({ ...item, isScan: true }));
218
- const currentSelected = this.getDiscountList().filter((n) => n.isSelected);
219
- if (currentSelected.length && currentSelected.some((n) => withScanList.some((m) => m.id === n.id))) {
493
+ const withScanList = resultDiscountList.map(item => ({
494
+ ...item,
495
+ isScan: true
496
+ }));
497
+ const currentSelected = this.getDiscountList().filter(n => n.isSelected);
498
+ if (currentSelected.length && currentSelected.some(n => withScanList.some(m => m.id === n.id))) {
499
+ await this.syncScannedDiscountsToOriginalDiscountList(this.getDiscountList());
220
500
  return {
221
- type: "clientCalc",
501
+ type: 'clientCalc',
222
502
  isAvailable: true,
223
- discountList: this.getDiscountList()
503
+ discountList: this.getDiscountList(),
504
+ scannedDiscountList: resultDiscountList
224
505
  };
225
506
  }
226
507
  const tempOrder = this.store.tempOrder;
227
- const holders = ((_b = tempOrder == null ? void 0 : tempOrder.holder) == null ? void 0 : _b.form_record_id) ? [{ form_record_id: tempOrder.holder.form_record_id }] : [];
228
- const { isAvailable, discountList: newDiscountList, unavailableReason } = rulesModule.isDiscountListAvailable({
229
- productList: (tempOrder == null ? void 0 : tempOrder.products) || [],
508
+ const holders = tempOrder?.holder?.form_record || [];
509
+ const {
510
+ isAvailable,
511
+ discountList: newDiscountList,
512
+ unavailableReason
513
+ } = rulesModule.isDiscountListAvailable({
514
+ productList: tempOrder?.products || [],
230
515
  oldDiscountList: this.getDiscountList(),
231
516
  newDiscountList: withScanList,
517
+ orderTotalAmount: Number(this.store.summary?.total_amount || 0),
232
518
  holders,
233
- isFormSubject: !!((_c = tempOrder == null ? void 0 : tempOrder.holder) == null ? void 0 : _c.type) && tempOrder.holder.type === "form",
234
- orderTotalAmount: Number(((_d = tempOrder == null ? void 0 : tempOrder.summary) == null ? void 0 : _d.total_amount) || 0)
235
- }) || { isAvailable: false, discountList: this.getDiscountList() };
519
+ isFormSubject: !!tempOrder?.holder?.type && tempOrder.holder.type === 'form'
520
+ }) || {
521
+ isAvailable: false,
522
+ discountList: this.getDiscountList()
523
+ };
236
524
  if (isAvailable && newDiscountList) {
237
- (_e = this.store.discount) == null ? void 0 : _e.setDiscountList(newDiscountList);
525
+ this.store.discount?.setDiscountList(newDiscountList);
238
526
  this.applyDiscount();
527
+ await this.syncScannedDiscountsToOriginalDiscountList(this.getDiscountList());
239
528
  }
240
529
  return {
241
- type: "clientCalc",
530
+ type: 'clientCalc',
242
531
  isAvailable: isAvailable || false,
243
532
  discountList: newDiscountList || this.getDiscountList(),
533
+ scannedDiscountList: resultDiscountList,
244
534
  unavailableReason
245
535
  };
246
536
  }
247
- applyDiscount() {
248
- var _a, _b, _c, _d, _e;
537
+ applyDiscount(options) {
249
538
  const tempOrder = this.store.tempOrder;
250
- if (!tempOrder)
251
- return;
539
+ // 任意 products CRUD 后都应当重算 discount 状态;即使 products 为空,
540
+ // 也需要让 Rules.calcDiscount 把 DiscountModule 的 isSelected / isAvailable /
541
+ // appliedProductDetails / savedAmount 复位。
542
+ if (!tempOrder) return;
543
+ this.syncLinkedBookingHoldersToProducts(tempOrder);
544
+ delete tempOrder.discount_list;
545
+ const discountList = this.store.discount?.getDiscountList() || [];
546
+ const shouldSkipDiscountCalculation = this.shouldSkipDiscountCalculation(tempOrder);
547
+ if (shouldSkipDiscountCalculation) return;
252
548
  const rulesModule = this.store.rules;
253
- if (!rulesModule)
254
- return;
255
- const discountList = ((_a = this.store.discount) == null ? void 0 : _a.getDiscountList()) || [];
256
- const holders = ((_b = tempOrder.holder) == null ? void 0 : _b.form_record_id) ? [{ form_record_id: tempOrder.holder.form_record_id }] : [];
549
+ if (!rulesModule) return;
257
550
  const result = rulesModule.calcDiscount({
258
551
  productList: tempOrder.products,
259
552
  discountList,
260
- holders,
261
- isFormSubject: !!((_c = tempOrder.holder) == null ? void 0 : _c.type) && tempOrder.holder.type === "form",
262
- orderTotalAmount: Number(((_d = tempOrder == null ? void 0 : tempOrder.summary) == null ? void 0 : _d.total_amount) || 0)
553
+ holders: [],
554
+ isFormSubject: product => (0, _utils.resolveIsFormSubject)(tempOrder, product),
555
+ orderTotalAmount: Number(this.store.summary?.total_amount || 0)
556
+ }, {
557
+ reapplyBookingDiscountProductUids: options?.reapplyBookingDiscountProductUids
263
558
  });
264
- if (result == null ? void 0 : result.productList) {
265
- tempOrder.products = result.productList;
559
+ if (result?.productList) {
560
+ const previousProductsByUid = (0, _utils.indexOrderProductsByUid)(tempOrder.products);
561
+ tempOrder.products = this.applyProductDiscountPrices((result.productList || []).map((product, index) => {
562
+ const uid = (0, _utils.getOrderProductLineUid)(product);
563
+ const previous = (uid ? previousProductsByUid.get(uid) : undefined) ?? previousProductsByUid.get(`__index__:${index}`);
564
+ if (isManualProductDiscountProduct(previous)) {
565
+ return previous;
566
+ }
567
+ const mergedProduct = previous ? (0, _utils.mergeOrderProductDisplayFields)(previous, product) : product;
568
+ return {
569
+ ...mergedProduct,
570
+ discount_list: (0, _utils.normalizeOrderProductDiscountList)((0, _manualProductDiscount.syncManualProductDiscountProductNum)(mergedProduct.discount_list, mergedProduct.num))
571
+ };
572
+ }));
266
573
  }
267
- if (result == null ? void 0 : result.discountList) {
574
+ if (result?.discountList) {
268
575
  OrderModule.populateSavedAmounts(result.productList || tempOrder.products, result.discountList);
269
- (_e = this.store.discount) == null ? void 0 : _e.setDiscountList(result.discountList);
270
- tempOrder.discount_list = result.discountList.filter((d) => d.isSelected);
576
+ this.store.discount?.setDiscountList(result.discountList);
271
577
  }
578
+ this.hasActiveCustomerBoundDiscount = (tempOrder.products || []).some(product => this.productHasCustomerBoundDiscount(product));
272
579
  }
273
- // ─── TempOrder: 初始化入口 ───
274
- initTempOrder(params) {
275
- if (params.cacheId !== void 0)
276
- this.cacheId = params.cacheId;
277
- if (params.salesSummaryModuleName !== void 0)
278
- this.salesSummaryModuleName = params.salesSummaryModuleName;
279
- this.restoreTempOrderFromStorage();
280
- }
281
- // ─── TempOrder: localStorage 持久化 ───
282
- getTempOrderStorageKey() {
283
- if (!this.cacheId)
284
- return null;
285
- return `scanOrder:tempOrder:${this.cacheId}`;
580
+
581
+ // ─── 促销/赠品 ──────────────────────────────────────
582
+
583
+ /**
584
+ * 注入促销评估器。
585
+ *
586
+ * @example
587
+ * order.setPromotionEvaluator(appHelper.utils.promotionEvaluator);
588
+ */
589
+ setPromotionEvaluator(evaluator) {
590
+ this.promotionEvaluator = evaluator || null;
591
+ }
592
+
593
+ /**
594
+ * 注入赠品选择 resolver。OS 需要补赠品时会 await 调用 resolver;缺省时跳过新增并 console.warn。
595
+ *
596
+ * @example
597
+ * order.setGiftSelectResolver(async (ctx) => {
598
+ * // SDK 内部决定弹窗 or 自动选第一项,返回完整 OrderProduct[]
599
+ * return giftSelectBridge.request(ctx);
600
+ * });
601
+ */
602
+ setGiftSelectResolver(resolver) {
603
+ this.giftSelectResolver = resolver || null;
604
+ }
605
+
606
+ /**
607
+ * 为商品目录追加促销标签,供上层 Sales 门面复用。
608
+ *
609
+ * @example
610
+ * const products = order.appendPromotionTags(catalogProducts);
611
+ */
612
+ appendPromotionTags(products) {
613
+ return (0, _cartPromotion.appendPromotionTags)(products, this.promotionEvaluator);
286
614
  }
287
- restoreTempOrderFromStorage() {
288
- var _a, _b;
289
- const key = this.getTempOrderStorageKey();
290
- if (!key)
615
+
616
+ /**
617
+ * 应用购物车促销:计算 → 差异化赠品 → 写回 tempOrder.products → emit onPromotionApplied。
618
+ *
619
+ * 调用时机:写路径(add/update/remove/clear/setCustomer)末尾自动触发;外部一般无需手动调。
620
+ *
621
+ * 关键约束:
622
+ * - 不调用任何公开 CRUD API(如 removeProductFromOrder),全部内部 mutate tempOrder.products,避免事件风暴;
623
+ * - 用 `isApplyingPromotion` 防递归;
624
+ * - 多选项赠品依赖 giftSelectResolver,未注入则跳过且 console.warn;
625
+ * - 不主动调用 applyDiscount / recalculateSummary,调用方负责跟进。
626
+ */
627
+ async applyPromotion() {
628
+ if (this.isApplyingPromotion) {
629
+ return;
630
+ }
631
+ if (!this.promotionEvaluator) {
291
632
  return;
292
- if (!this.window)
633
+ }
634
+ const tempOrder = this.store.tempOrder;
635
+ if (!tempOrder) {
293
636
  return;
294
- const cachedData = this.window.localStorage.getItem(key);
295
- if (!cachedData)
637
+ }
638
+ const strategyConfigs = this.promotionEvaluator.getStrategyConfigs?.();
639
+ if (Array.isArray(strategyConfigs) && strategyConfigs.length === 0) {
296
640
  return;
641
+ }
642
+ this.isApplyingPromotion = true;
297
643
  try {
298
- const parsedData = JSON.parse(cachedData);
299
- if (!(0, import_utils.isTempOrder)(parsedData)) {
300
- this.window.localStorage.removeItem(key);
301
- return;
302
- }
303
- if (Array.isArray(parsedData.products)) {
304
- for (let i = 0; i < parsedData.products.length; i++) {
305
- const p = parsedData.products[i];
306
- if (!p || typeof p !== "object")
307
- continue;
308
- if (!Array.isArray(p.product_option_item)) {
309
- p.product_option_item = [];
644
+ const result = (0, _cartPromotion.processCartPromotion)(tempOrder.products, this.promotionEvaluator);
645
+
646
+ // step 1: toRemove —— 直接过滤
647
+ if (result.giftActions.toRemove.length > 0) {
648
+ const removeSet = new Set(result.giftActions.toRemove.map(String));
649
+ result.products = result.products.filter(p => {
650
+ const uid = (0, _cartPromotion.getOrderProductUid)(p);
651
+ return uid ? !removeSet.has(String(uid)) : true;
652
+ });
653
+ }
654
+
655
+ // step 2: toReduce —— 调整数量
656
+ for (const reduce of result.giftActions.toReduce) {
657
+ for (const item of reduce.items) {
658
+ const product = result.products.find(p => (0, _cartPromotion.getOrderProductUid)(p) === item.uid);
659
+ if (product) {
660
+ product.num = item.newQuantity;
310
661
  }
311
- if (!Array.isArray(p.product_bundle)) {
312
- p.product_bundle = [];
662
+ }
663
+ }
664
+
665
+ // step 3: toAdd —— 补赠品
666
+ if (result.giftActions.toAdd.length > 0) {
667
+ if (!this.giftSelectResolver) {
668
+ if (result.giftActions.toAdd.some(g => g.giftOptions.length > 1)) {
669
+ // 多选项必须走 resolver;单选项也建议走(避免 OS 自造 stub),但允许跳过
670
+ console.warn('[OrderModule] giftSelectResolver not set, skip applying gifts', result.giftActions.toAdd);
671
+ } else {
672
+ console.warn('[OrderModule] giftSelectResolver not set, skip applying single-option gifts', result.giftActions.toAdd);
313
673
  }
314
- const row = p;
315
- if (typeof row.identity_key !== "string" || row.identity_key.length === 0) {
316
- const newKey = (0, import_utils.createUuidV4)();
317
- row.identity_key = newKey;
318
- if (!row.metadata || typeof row.metadata !== "object") {
319
- row.metadata = {};
674
+ } else {
675
+ for (const addAction of result.giftActions.toAdd) {
676
+ // 单选普通赠品可累加;预约赠品必须新增独立 booking,不能只改 num。
677
+ if (addAction.giftOptions.length === 1) {
678
+ const sameStrategy = result.products.find(p => p.metadata?._giftInfo?.strategyId === addAction.strategyId);
679
+ if (sameStrategy && !sameStrategy.booking_uid && !sameStrategy.metadata?.booking_uid) {
680
+ sameStrategy.num = (Number(sameStrategy.num) || 0) + (addAction.giftCount || 1);
681
+ continue;
682
+ }
320
683
  }
321
- if (!row.metadata.unique_identification_number) {
322
- row.metadata.unique_identification_number = newKey;
684
+ try {
685
+ const giftResult = await this.giftSelectResolver({
686
+ strategyId: addAction.strategyId,
687
+ strategyName: addAction.strategyName,
688
+ giftCount: addAction.giftCount,
689
+ giftOptions: addAction.giftOptions,
690
+ sourceProductIds: addAction.sourceProductIds
691
+ });
692
+ const giftProducts = Array.isArray(giftResult) ? giftResult : giftResult?.products;
693
+ const giftBookings = Array.isArray(giftResult) ? [] : giftResult?.bookings || [];
694
+ if (Array.isArray(giftProducts) && giftProducts.length > 0) {
695
+ for (const [index, gp] of giftProducts.entries()) {
696
+ if (!gp) continue;
697
+ const booking = giftBookings[index];
698
+ if (booking) {
699
+ const linked = this.createLinkedProductAndBooking({
700
+ product: gp,
701
+ booking
702
+ });
703
+ result.products.push(linked.product);
704
+ tempOrder.bookings = [...(tempOrder.bookings || []), linked.booking];
705
+ continue;
706
+ }
707
+ // normalize 略过:调用方应保证返回完整 OrderProduct。
708
+ result.products.push(gp);
709
+ }
710
+ }
711
+ } catch (error) {
712
+ // 用户取消 / 选择失败 → 视为放弃本次新增
713
+ console.warn('[OrderModule] giftSelectResolver rejected, skip add gift', addAction.strategyId, error);
323
714
  }
324
715
  }
325
- parsedData.products[i] = (0, import_utils3.normalizeOrderProduct)(row);
326
716
  }
327
717
  }
328
- this.store.tempOrder = parsedData;
329
- } catch {
330
- (_b = (_a = this.window) == null ? void 0 : _a.localStorage) == null ? void 0 : _b.removeItem(key);
718
+ tempOrder.products = result.products;
719
+ this.lastUnfulfilledPromotions = result.unfulfilledPromotions;
720
+ this.lastGiftActions = result.giftActions;
721
+ const payload = {
722
+ unfulfilledPromotions: result.unfulfilledPromotions,
723
+ giftActions: result.giftActions,
724
+ gifts: result.gifts.map(g => ({
725
+ strategyId: g.strategyId,
726
+ strategyName: g.strategyName,
727
+ giftCount: g.giftCount,
728
+ giftOptions: g.giftOptions
729
+ })),
730
+ products: tempOrder.products
731
+ };
732
+ this.effectsEmit('onPromotionApplied', payload);
733
+ } catch (error) {
734
+ console.error('[OrderModule] applyPromotion failed', error);
735
+ } finally {
736
+ this.isApplyingPromotion = false;
331
737
  }
332
738
  }
739
+
740
+ /** 最近一次 applyPromotion 输出的未满足促销列表 */
741
+ getUnfulfilledPromotions() {
742
+ return this.lastUnfulfilledPromotions;
743
+ }
744
+
745
+ /** 最近一次 applyPromotion 输出的赠品操作 diff */
746
+ getLastGiftActions() {
747
+ return this.lastGiftActions;
748
+ }
749
+
750
+ // ─── TempOrder: 初始化入口 ───
751
+
752
+ initTempOrder(params) {
753
+ if (params.cacheId !== undefined) this.cacheId = params.cacheId;
754
+ if (params.salesSummaryModuleName !== undefined) this.salesSummaryModuleName = params.salesSummaryModuleName;
755
+ }
756
+
757
+ /**
758
+ * 控制 IndexedDB 草稿(saveDraft)是否写入。
759
+ * localStorage 版 tempOrder 持久化已废弃,此方法仅影响 saveDraft。
760
+ *
761
+ * @example
762
+ * order.setEnableTempOrderPersist(false); // BigSale 弹窗:跳过 ~1s 的 dbUpdate
763
+ */
764
+ setEnableTempOrderPersist(enabled) {
765
+ this.draftPersistEnabled = enabled;
766
+ }
767
+
768
+ /** 是否允许 saveDraft 写入 IndexedDB 草稿。 */
769
+ isTempOrderPersistEnabled() {
770
+ return this.draftPersistEnabled;
771
+ }
772
+
773
+ /** @deprecated OrderModule 不再负责 tempOrder localStorage 持久化;草稿保存请使用 IndexedDB saveDraft。 */
333
774
  persistTempOrder() {
334
- const key = this.getTempOrderStorageKey();
335
- if (!key || !this.store.tempOrder)
336
- return;
337
- if (!this.window)
338
- return;
339
- this.window.localStorage.setItem(key, JSON.stringify(this.store.tempOrder));
775
+ // no-op: OrderModule 的刷新恢复不再依赖 localStorage。
340
776
  }
341
777
  notifyTempOrderChanged() {
342
- if (!this.store.tempOrder)
343
- return;
778
+ if (!this.store.tempOrder) return;
344
779
  this.store.tempOrder = {
345
780
  ...this.store.tempOrder,
346
781
  products: [...this.store.tempOrder.products],
@@ -348,309 +783,2783 @@ var OrderModule = class extends import_BaseModule.BaseModule {
348
783
  };
349
784
  this.persistTempOrder();
350
785
  }
786
+
351
787
  // ─── TempOrder: 创建 & 获取 ───
788
+
352
789
  createDefaultTempOrderInstance() {
353
- return (0, import_utils.createDefaultTempOrder)({
354
- now: (0, import_utils.formatDateTime)(/* @__PURE__ */ new Date()),
355
- summary: (0, import_utils.createEmptySummary)()
790
+ return (0, _utils.createDefaultTempOrder)({
791
+ now: (0, _utils.formatDateTime)(new Date())
356
792
  });
357
793
  }
358
- ensureTempOrder() {
359
- if (this.store.tempOrder)
360
- return this.store.tempOrder;
361
- const newOrder = this.createDefaultTempOrderInstance();
362
- this.store.tempOrder = newOrder;
363
- this.persistTempOrder();
364
- return newOrder;
794
+ createExternalSaleNumber() {
795
+ const timestamp = (0, _dayjs.default)().format('YYYYMMDDHHmmssSSS');
796
+ const random6 = String(Math.floor(Math.random() * 1000000)).padStart(6, '0');
797
+ const parts = ['LOCAL'];
798
+ const context = this.core?.context;
799
+ const getData = context?.getData;
800
+ if (typeof getData === 'function') {
801
+ const shopId = getData.call(context, 'shop_id');
802
+ const deviceId = getData.call(context, 'device_id');
803
+ if (shopId !== undefined && shopId !== null && shopId !== '') {
804
+ parts.push(String(shopId));
805
+ }
806
+ if (deviceId !== undefined && deviceId !== null && deviceId !== '') {
807
+ parts.push(String(deviceId).slice(-8));
808
+ }
809
+ } else {
810
+ this.logWarning('external_sale_number context getData unavailable', {
811
+ key: 'shop_id'
812
+ });
813
+ this.logWarning('external_sale_number context getData unavailable', {
814
+ key: 'device_id'
815
+ });
816
+ }
817
+ parts.push(timestamp, random6);
818
+ return parts.join('_');
365
819
  }
366
- restoreOrder() {
367
- const freshTempOrder = this.createDefaultTempOrderInstance();
368
- this.store.tempOrder = freshTempOrder;
369
- this.persistTempOrder();
370
- return freshTempOrder;
820
+ ensureExternalSaleNumber(tempOrder) {
821
+ if (!tempOrder.external_sale_number) {
822
+ tempOrder.external_sale_number = this.createExternalSaleNumber();
823
+ }
824
+ this.ensureRequestUniqueIdempotencyToken(tempOrder);
825
+ return tempOrder.external_sale_number;
371
826
  }
372
- getTempOrder() {
373
- return this.store.tempOrder;
827
+ ensureRequestUniqueIdempotencyToken(tempOrder) {
828
+ if (!tempOrder.request_unique_idempotency_token) {
829
+ tempOrder.request_unique_idempotency_token = `${tempOrder.external_sale_number}_${Date.now()}`;
830
+ }
831
+ return tempOrder.request_unique_idempotency_token;
374
832
  }
375
- async addNewOrder() {
376
- const tempOrder = this.ensureTempOrder();
377
- await this.recalculateSummary({ createIfMissing: true });
378
- this.persistTempOrder();
379
- return tempOrder;
833
+ buildPaymentSyncIdempotencyToken(tempOrder, payments) {
834
+ const baseToken = this.ensureRequestUniqueIdempotencyToken(tempOrder);
835
+ const paidPaymentsCount = (payments || []).filter(payment => {
836
+ return String(payment?.status || '').toLowerCase() === 'paid';
837
+ }).length;
838
+ const paidCount = paidPaymentsCount > 0 ? paidPaymentsCount : payments.length;
839
+ return `${baseToken}_payment_${paidCount}`;
380
840
  }
381
- getOrderProducts() {
841
+ async saveDraft() {
842
+ // no-op: 普通 OrderModule 不再负责订单草稿或本地数据库持久化。
843
+ }
844
+ saveDraftInBackground() {
845
+ void this.saveDraft();
846
+ }
847
+ async hydrateTempOrderFromLocalRecord(record) {
848
+ return this.hydrateTempOrderFromRecord(record);
849
+ }
850
+ extractOrderIdFromSubmitResult(result) {
851
+ return result?.data?.order_id ?? result?.order_id ?? result?.data?.id ?? result?.id ?? null;
852
+ }
853
+ calculatePaymentEffectiveAmount(payment) {
854
+ const amount = new _decimal.default(payment.amount || 0);
855
+ const originAmount = new _decimal.default(payment.origin_amount ?? payment.amount ?? 0);
856
+ const serviceFee = new _decimal.default(payment.service_fee || 0);
857
+ // TODO: 后端允许 amount 表示顾客实际支付总额后,移除这个兼容分支。
858
+ const effectiveAmount = serviceFee.gt(0) && amount.eq(originAmount) ? amount.plus(serviceFee) : amount;
859
+ const rounding = new _decimal.default(payment.rounding_amount || 0);
860
+ return effectiveAmount.minus(rounding);
861
+ }
862
+ calculatePaymentTotal(payments) {
863
+ return (0, _utils.mapPaymentItemsToOrderPayments)(payments, {
864
+ includeVoided: false
865
+ }).reduce((sum, payment) => {
866
+ if (!isPaidPaymentRecord(payment)) return sum;
867
+ return sum.plus(this.calculatePaymentEffectiveAmount(payment));
868
+ }, new _decimal.default(0));
869
+ }
870
+ calculateOrderPaidPaymentTotal(payments) {
871
+ return (0, _utils.mapPaymentItemsToOrderPayments)(payments, {
872
+ includeVoided: false
873
+ }).reduce((sum, payment) => {
874
+ if (!isPaidPaymentRecord(payment)) return sum;
875
+ return sum.plus(this.calculatePaymentGapAmount(payment));
876
+ }, new _decimal.default(0));
877
+ }
878
+ getDepositAmount(tempOrder, summary) {
879
+ return new _decimal.default(summary.deposit_amount || tempOrder.deposit_amount || '0.00');
880
+ }
881
+ normalizeDepositAmount(amount) {
882
+ return _decimal.default.max(new _decimal.default(Number(amount) || 0), 0).toDecimalPlaces(2).toFixed(2);
883
+ }
884
+ getLastOrderDepositSnapshot() {
885
+ const lastOrderInfo = this.store.lastOrderInfo;
886
+ if (!lastOrderInfo || typeof lastOrderInfo !== 'object') return null;
887
+ const rawDepositAmount = lastOrderInfo.deposit_amount ?? lastOrderInfo.summary?.deposit_amount;
888
+ const depositAmount = this.normalizeDepositAmount(rawDepositAmount ?? 0);
889
+ const hasDepositAmount = new _decimal.default(depositAmount).gt(0);
890
+ const isDeposit = Number(lastOrderInfo.is_deposit) === 1 || hasDepositAmount;
891
+ if (!isDeposit || !hasDepositAmount) return null;
892
+ return {
893
+ isDeposit: 1,
894
+ depositAmount
895
+ };
896
+ }
897
+ getLastOrderSubmitSnapshot() {
898
+ const lastOrderInfo = this.store.lastOrderInfo;
899
+ if (!lastOrderInfo || typeof lastOrderInfo !== 'object') return {};
900
+ const businessCode = lastOrderInfo.business_code;
901
+ const type = lastOrderInfo.type;
902
+ return {
903
+ ...(typeof businessCode === 'string' && businessCode ? {
904
+ businessCode
905
+ } : {}),
906
+ ...(typeof type === 'string' && type ? {
907
+ type
908
+ } : {})
909
+ };
910
+ }
911
+ applyDepositAmountToSummary(tempOrder, summary, amount) {
912
+ const depositAmount = this.normalizeDepositAmount(amount);
913
+ const hasDepositAmount = new _decimal.default(depositAmount).gt(0);
914
+ const existedDeposit = summary.deposit && typeof summary.deposit === 'object' ? summary.deposit : undefined;
915
+ const nextSummary = {
916
+ ...summary,
917
+ deposit_amount: depositAmount,
918
+ deposit: hasDepositAmount ? {
919
+ ...(existedDeposit || {}),
920
+ total: depositAmount,
921
+ deposit_policy_ids: existedDeposit?.deposit_policy_ids || [],
922
+ hasDeposit: true
923
+ } : undefined
924
+ };
925
+ tempOrder.deposit_amount = depositAmount;
926
+ tempOrder.is_deposit = hasDepositAmount ? 1 : 0;
927
+ return nextSummary;
928
+ }
929
+ applyLastOrderDepositSnapshot(tempOrder, summary) {
930
+ const snapshot = this.getLastOrderDepositSnapshot();
931
+ if (!snapshot) return summary;
932
+ const existedDeposit = summary.deposit && typeof summary.deposit === 'object' ? summary.deposit : undefined;
933
+ const nextSummary = {
934
+ ...summary,
935
+ deposit_amount: snapshot.depositAmount,
936
+ deposit: {
937
+ ...(existedDeposit || {}),
938
+ total: snapshot.depositAmount,
939
+ deposit_policy_ids: existedDeposit?.deposit_policy_ids || [],
940
+ hasDeposit: true
941
+ }
942
+ };
943
+ tempOrder.deposit_amount = snapshot.depositAmount;
944
+ tempOrder.is_deposit = tempOrder.is_deposit === 0 && new _decimal.default(tempOrder.deposit_amount || 0).gt(0) ? 0 : snapshot.isDeposit;
945
+ return nextSummary;
946
+ }
947
+ isDepositOrder(tempOrder, depositAmount) {
948
+ return tempOrder.is_deposit === 1 && depositAmount.gt(0);
949
+ }
950
+ getOrderExpectedAmount(summary) {
951
+ return new _decimal.default(summary.expect_amount || summary.total_amount || 0);
952
+ }
953
+ isDepositCovered(params) {
954
+ const depositAmount = this.getDepositAmount(params.tempOrder, params.summary);
955
+ if (!this.isDepositOrder(params.tempOrder, depositAmount)) return false;
956
+ if (depositAmount.lte(0)) return false;
957
+ return params.paidAmount.gte(depositAmount);
958
+ }
959
+ getPaymentCompletion(payments) {
382
960
  const tempOrder = this.ensureTempOrder();
383
- return tempOrder.products;
961
+ const summary = this.store.summary || (0, _utils.createEmptySummary)();
962
+ const paymentList = payments || tempOrder.payments || [];
963
+ const refundAmount = this.getSummaryRefundAmount(summary);
964
+ const netPaidAmount = _decimal.default.max(this.calculateOrderPaidPaymentTotal(paymentList).minus(refundAmount), 0);
965
+ const depositAmount = this.getDepositAmount(tempOrder, summary);
966
+ const orderExpectedAmount = this.getOrderExpectedAmount(summary);
967
+ const isDepositFullyPaid = this.isDepositCovered({
968
+ tempOrder,
969
+ summary,
970
+ paidAmount: netPaidAmount
971
+ });
972
+ const isOrderFullyPaid = orderExpectedAmount.lte(0) ? true : netPaidAmount.gte(orderExpectedAmount);
973
+ const paymentStage = this.isDepositOrder(tempOrder, depositAmount) && !isDepositFullyPaid ? 'deposit' : 'normal';
974
+ return {
975
+ isFullyPaid: isOrderFullyPaid,
976
+ isOrderFullyPaid,
977
+ isDepositFullyPaid,
978
+ paymentStage,
979
+ depositAmount: depositAmount.toFixed(2),
980
+ orderExpectedAmount: orderExpectedAmount.toFixed(2)
981
+ };
384
982
  }
385
- // ─── TempOrder: 金额汇总 ───
386
- getSalesSummary() {
387
- if (!this.salesSummaryModuleName)
388
- return null;
389
- return this.core.getModule(this.salesSummaryModuleName);
983
+ calculateDepositPaidAmount(payments) {
984
+ return (payments || []).reduce((sum, payment) => {
985
+ const paymentRecord = payment;
986
+ if (!isPaidPaymentRecord(paymentRecord)) return sum;
987
+ const isDepositPayment = paymentRecord.type === 'deposit' || paymentRecord.order_payment_type === 'deposit';
988
+ if (!isDepositPayment) return sum;
989
+ return sum.plus(this.calculatePaymentGapAmount(paymentRecord));
990
+ }, new _decimal.default(0));
390
991
  }
391
- async recalculateSummary(options) {
392
- const tempOrder = (options == null ? void 0 : options.createIfMissing) ? this.ensureTempOrder() : this.store.tempOrder;
393
- if (!tempOrder)
394
- return null;
395
- const salesSummary = this.getSalesSummary();
396
- if (!salesSummary) {
397
- tempOrder.summary = (0, import_utils.createEmptySummary)();
398
- tempOrder.surcharge_fee = tempOrder.summary.surcharge_fee;
399
- return tempOrder.summary;
992
+ resolveNextOrderPaymentType(tempOrder) {
993
+ const depositAmount = new _decimal.default(tempOrder.deposit_amount || '0.00');
994
+ if (!this.isDepositOrder(tempOrder, depositAmount)) return 'normal';
995
+ if (depositAmount.lte(0)) return 'normal';
996
+ const depositPaidAmount = this.calculateDepositPaidAmount(tempOrder.payments || []);
997
+ return depositPaidAmount.lt(depositAmount) ? 'deposit' : 'normal';
998
+ }
999
+ normalizePaymentSource(payment, options) {
1000
+ const paymentRecord = payment;
1001
+ const metadata = paymentRecord.metadata && typeof paymentRecord.metadata === 'object' ? {
1002
+ ...paymentRecord.metadata
1003
+ } : {};
1004
+ if (!metadata.unique_payment_number) {
1005
+ metadata.unique_payment_number = (0, _utils.createUuidV4)();
1006
+ }
1007
+ const normalized = {
1008
+ ...paymentRecord,
1009
+ metadata,
1010
+ origin_amount: paymentRecord.origin_amount ?? paymentRecord.amount ?? '0.00'
1011
+ };
1012
+ if (paymentRecord.status !== undefined) {
1013
+ normalized.status = paymentRecord.status;
1014
+ } else if (options?.defaultPaid !== false) {
1015
+ normalized.status = 'paid';
1016
+ }
1017
+ return normalized;
1018
+ }
1019
+ updateTempOrderPaymentStatus(tempOrder, options) {
1020
+ const summary = options?.summary || this.store.summary || (0, _utils.createEmptySummary)();
1021
+ const paymentTotal = this.calculatePaymentTotal(tempOrder.payments || []);
1022
+ const orderPaymentTotal = this.calculateOrderPaidPaymentTotal(tempOrder.payments || []);
1023
+ const refundAmount = this.getSummaryRefundAmount(summary);
1024
+ const netPaymentTotal = _decimal.default.max(orderPaymentTotal.minus(refundAmount), 0);
1025
+ const targetAmount = this.calculatePaymentTargetAmount(tempOrder, summary, netPaymentTotal);
1026
+ if (this.shouldKeepPaymentStatusAfterAmountRecalc(tempOrder.payment_status, {
1027
+ netPaymentTotal,
1028
+ paymentTotal,
1029
+ refundAmount,
1030
+ targetAmount,
1031
+ previousSummary: options?.previousSummary,
1032
+ tempOrder
1033
+ })) {
1034
+ return;
1035
+ }
1036
+ if (targetAmount.lte(0)) {
1037
+ tempOrder.payment_status = 'paid';
1038
+ return;
1039
+ }
1040
+ if (netPaymentTotal.lte(0)) {
1041
+ tempOrder.payment_status = 'payment_processing';
1042
+ return;
1043
+ }
1044
+ tempOrder.payment_status = netPaymentTotal.gte(targetAmount) ? 'paid' : 'partially_paid';
1045
+ }
1046
+ shouldKeepPaymentStatusAfterAmountRecalc(paymentStatus, params) {
1047
+ const normalizedStatus = String(paymentStatus || '').trim().toLowerCase();
1048
+ if (normalizedStatus !== 'refunded' && normalizedStatus !== 'partially_refunded') {
1049
+ return false;
1050
+ }
1051
+ if (normalizedStatus === 'refunded' && params.paymentTotal.gt(0) && params.refundAmount.gte(params.paymentTotal)) {
1052
+ return true;
1053
+ }
1054
+ if (params.previousSummary) {
1055
+ const previousTargetAmount = this.calculatePaymentTargetAmount(params.tempOrder, params.previousSummary, params.netPaymentTotal);
1056
+ if (!previousTargetAmount.eq(params.targetAmount)) return false;
1057
+ }
1058
+ return true;
1059
+ }
1060
+ resolveWalletPassUseValue(payment, amount) {
1061
+ const usageUnit = payment.wallet_pass_usage_unit && typeof payment.wallet_pass_usage_unit === 'object' ? payment.wallet_pass_usage_unit : null;
1062
+ if (!usageUnit) return payment.wallet_pass_use_value;
1063
+ const price = new _decimal.default(usageUnit.price || 0);
1064
+ const points = new _decimal.default(usageUnit.points || 0);
1065
+ if (price.lte(0) || points.lte(0)) return payment.wallet_pass_use_value;
1066
+ return amount.div(price).times(points).toDecimalPlaces(2).toFixed(2);
1067
+ }
1068
+ adjustVoucherFundRecord(params) {
1069
+ const fundRecord = params.voucher.fund_record && typeof params.voucher.fund_record === 'object' && !Array.isArray(params.voucher.fund_record) ? {
1070
+ ...params.voucher.fund_record
1071
+ } : null;
1072
+ if (!fundRecord || fundRecord.using_before_value === undefined || fundRecord.using_before_value === null) {
1073
+ return fundRecord || undefined;
1074
+ }
1075
+ const code = String(params.voucher.code || '').toUpperCase();
1076
+ const isPointCard = code === 'POINTCARD' || String(params.voucher.fund_record?.tag || '').toLowerCase() === 'point_card';
1077
+ const usingValue = isPointCard ? new _decimal.default(params.walletPassUseValue || 0) : params.amount;
1078
+ fundRecord.using_after_value = new _decimal.default(fundRecord.using_before_value || 0).minus(usingValue).toDecimalPlaces(2).toFixed(2);
1079
+ return fundRecord;
1080
+ }
1081
+ capVoucherPaymentsByRemainingAmount(params) {
1082
+ const targetAmount = this.getPaymentTargetAmount();
1083
+ const nonVoucherTotal = this.calculateOrderPaidPaymentTotal(params.nonVoucherPayments);
1084
+ if (targetAmount.lte(0)) return [];
1085
+ let remainingAmount = _decimal.default.max(targetAmount.minus(nonVoucherTotal), 0);
1086
+ const cappedVouchers = [];
1087
+ for (const voucher of params.vouchers) {
1088
+ if (remainingAmount.lte(0)) break;
1089
+ const voucherAmount = _decimal.default.max(new _decimal.default(voucher.amount || 0), 0);
1090
+ if (voucherAmount.lte(0)) continue;
1091
+ const cappedAmount = _decimal.default.min(voucherAmount, remainingAmount);
1092
+ const amountText = cappedAmount.toDecimalPlaces(2).toFixed(2);
1093
+ const walletPassUseValue = this.resolveWalletPassUseValue(voucher, cappedAmount);
1094
+ cappedVouchers.push({
1095
+ ...voucher,
1096
+ amount: amountText,
1097
+ origin_amount: amountText,
1098
+ wallet_pass_use_value: walletPassUseValue,
1099
+ fund_record: this.adjustVoucherFundRecord({
1100
+ voucher: voucher,
1101
+ amount: cappedAmount,
1102
+ walletPassUseValue
1103
+ })
1104
+ });
1105
+ remainingAmount = remainingAmount.minus(cappedAmount);
400
1106
  }
401
- const summary = await salesSummary.getSummary({
402
- products: tempOrder.products
1107
+ return cappedVouchers;
1108
+ }
1109
+ calculatePaidPaymentSummary(payments) {
1110
+ return (payments || []).reduce((summary, payment) => {
1111
+ const paymentRecord = payment;
1112
+ if (!isPaidPaymentRecord(paymentRecord)) return summary;
1113
+ return {
1114
+ customerPaidAmount: summary.customerPaidAmount.plus(paymentRecord.amount || 0),
1115
+ orderPaidAmount: summary.orderPaidAmount.plus(this.calculatePaymentOrderAmount(paymentRecord)),
1116
+ gapPaidAmount: summary.gapPaidAmount.plus(this.calculatePaymentGapAmount(paymentRecord)),
1117
+ payServiceChargeAmount: summary.payServiceChargeAmount.plus(this.calculatePaymentServiceFee(paymentRecord)),
1118
+ roundingAmount: summary.roundingAmount.plus(this.calculatePaymentRoundingAmount(paymentRecord))
1119
+ };
1120
+ }, {
1121
+ customerPaidAmount: new _decimal.default(0),
1122
+ orderPaidAmount: new _decimal.default(0),
1123
+ gapPaidAmount: new _decimal.default(0),
1124
+ payServiceChargeAmount: new _decimal.default(0),
1125
+ roundingAmount: new _decimal.default(0)
403
1126
  });
404
- tempOrder.summary = summary;
405
- if (summary.is_price_include_tax !== void 0) {
406
- tempOrder.is_price_include_tax = summary.is_price_include_tax;
1127
+ }
1128
+ calculatePaymentServiceFee(payment) {
1129
+ if (payment.service_fee !== undefined && payment.service_fee !== null && payment.service_fee !== '') {
1130
+ return new _decimal.default(payment.service_fee || 0);
407
1131
  }
408
- if (typeof summary.tax_title === "string") {
409
- tempOrder.tax_title = summary.tax_title;
1132
+ const serviceCharge = payment.service_charge && typeof payment.service_charge === 'object' ? payment.service_charge : {};
1133
+ const percentage = new _decimal.default(serviceCharge.percentage || 0);
1134
+ const fixedAmount = new _decimal.default(serviceCharge.amount || 0);
1135
+ return new _decimal.default(payment.amount || 0).times(percentage).plus(fixedAmount);
1136
+ }
1137
+ calculatePaymentGapAmount(payment) {
1138
+ if (payment.origin_amount !== undefined && payment.origin_amount !== null && payment.origin_amount !== '') {
1139
+ const rounding = new _decimal.default(payment.rounding_amount || 0);
1140
+ return new _decimal.default(payment.origin_amount || 0).minus(rounding);
410
1141
  }
411
- tempOrder.surcharge_fee = summary.surcharge_fee;
412
- tempOrder.surcharges = summary.surcharges || [];
413
- tempOrder.deposit_amount = summary.deposit_amount || "0.00";
414
- tempOrder.is_deposit = summary.deposit_amount !== "0.00" ? 1 : 0;
415
- tempOrder.shop_discount = summary.discount_amount || "0.00";
416
- return tempOrder.summary;
417
- }
418
- async getScanOrderSummary() {
419
- const summary = await this.recalculateSummary({ createIfMissing: true });
420
- if (!summary)
421
- return (0, import_utils.createEmptySummary)();
422
- this.persistTempOrder();
423
- return summary;
1142
+ const rounding = new _decimal.default(payment.rounding_amount || 0);
1143
+ const amount = new _decimal.default(payment.amount || 0);
1144
+ const serviceFee = this.calculatePaymentServiceFee(payment);
1145
+ if (serviceFee.lte(0)) return amount.minus(rounding);
1146
+ if (!hasSyncedOrderPaymentRecord(payment)) return new _decimal.default(0);
1147
+ return _decimal.default.max(amount.minus(serviceFee), 0).minus(rounding);
424
1148
  }
425
- // ─── TempOrder: 备注 ───
426
- updateTempOrderNote(note) {
427
- const tempOrder = this.ensureTempOrder();
428
- tempOrder.note = String(note || "");
429
- this.persistTempOrder();
430
- return tempOrder.note;
1149
+ calculatePaymentOrderAmount(payment) {
1150
+ if (payment.origin_amount !== undefined && payment.origin_amount !== null && payment.origin_amount !== '') {
1151
+ return new _decimal.default(payment.origin_amount || 0);
1152
+ }
1153
+ const amount = new _decimal.default(payment.amount || 0);
1154
+ const serviceFee = this.calculatePaymentServiceFee(payment);
1155
+ if (serviceFee.lte(0)) return amount;
1156
+ if (!hasSyncedOrderPaymentRecord(payment)) return new _decimal.default(0);
1157
+ return _decimal.default.max(amount.minus(serviceFee), 0);
431
1158
  }
432
- updateTempOrderBuzzer(buzzer) {
433
- const tempOrder = this.ensureTempOrder();
434
- tempOrder.buzzer = String(buzzer || "");
435
- this.persistTempOrder();
436
- return tempOrder.buzzer;
1159
+ calculatePaymentRoundingAmount(payment) {
1160
+ if (payment.rounding_amount === undefined || payment.rounding_amount === null || payment.rounding_amount === '') {
1161
+ return new _decimal.default(0);
1162
+ }
1163
+ return new _decimal.default(payment.rounding_amount || 0);
437
1164
  }
438
- updateTempOrderContactsInfo(contactsInfo) {
439
- const tempOrder = this.ensureTempOrder();
440
- tempOrder.contacts_info = Array.isArray(contactsInfo) ? contactsInfo : [];
441
- this.persistTempOrder();
442
- return tempOrder.contacts_info;
1165
+ formatSummaryMetadataMoney(value) {
1166
+ if (value === undefined || value === null || value === '') return undefined;
1167
+ if (value instanceof _decimal.default) return value.toFixed(2);
1168
+ return new _decimal.default(String(value || 0)).toFixed(2);
443
1169
  }
444
- // ─── TempOrder: 商品 CRUD ───
445
- async addProductToOrder(product) {
446
- const tempOrder = this.ensureTempOrder();
447
- const hasExplicitIdentityKey = typeof product.identity_key === "string" && product.identity_key.length > 0;
448
- if (hasExplicitIdentityKey) {
449
- const normalizedProduct = (0, import_utils3.normalizeOrderProduct)(product);
450
- const productIndex = (0, import_utils3.getProductIdentityIndex)(
451
- tempOrder.products,
452
- normalizedProduct
453
- );
454
- if (productIndex === -1) {
455
- tempOrder.products.push(normalizedProduct);
456
- } else {
457
- const targetProduct = tempOrder.products[productIndex];
458
- tempOrder.products[productIndex] = {
459
- ...targetProduct,
460
- ...normalizedProduct,
461
- num: (0, import_utils3.getSafeProductNum)(targetProduct.num + normalizedProduct.num),
462
- _origin: normalizedProduct._origin || targetProduct._origin
463
- };
1170
+ syncSummaryProductMetadata(products) {
1171
+ const moneyKeys = ['main_product_attached_bundle_surcharge_fee', 'main_product_attached_bundle_tax_fee', 'surcharge_rounding_remainder', 'tax_fee_rounding_remainder'];
1172
+ // 整单折扣均摊层产出的 payment 维度字段,直接由计算层写入 metadata,
1173
+ // 这里需把它们一并回写到商品 metadata 以便持久化/出站。
1174
+ const metadataMoneyKeys = ['main_product_attached_bundle_selling_price', 'main_product_attached_bundle_payment_price'];
1175
+ for (const product of products || []) {
1176
+ const productRecord = product;
1177
+ const metadata = productRecord.metadata && typeof productRecord.metadata === 'object' ? {
1178
+ ...productRecord.metadata
1179
+ } : {};
1180
+ let hasMetadataPatch = false;
1181
+ for (const key of moneyKeys) {
1182
+ const value = this.formatSummaryMetadataMoney(productRecord[key]);
1183
+ if (value === undefined) continue;
1184
+ metadata[key] = value;
1185
+ hasMetadataPatch = true;
464
1186
  }
465
- } else {
466
- const incomingFingerprint = (0, import_utils3.buildProductLineFingerprint)(
467
- product.product_option_item,
468
- product.product_bundle
469
- );
470
- const matchedIndex = tempOrder.products.findIndex((item) => {
471
- if (item.product_id !== product.product_id)
472
- return false;
473
- if (item.product_variant_id !== product.product_variant_id)
474
- return false;
475
- const existedFingerprint = (0, import_utils3.buildProductLineFingerprint)(
476
- item.product_option_item,
477
- item.product_bundle
478
- );
479
- return existedFingerprint === incomingFingerprint;
480
- });
481
- if (matchedIndex === -1) {
482
- const normalizedProduct = (0, import_utils3.normalizeOrderProduct)(product);
483
- tempOrder.products.push(normalizedProduct);
484
- } else {
485
- const targetProduct = tempOrder.products[matchedIndex];
486
- const normalizedProduct = (0, import_utils3.normalizeOrderProduct)({
487
- ...product,
488
- identity_key: targetProduct.identity_key
489
- });
490
- tempOrder.products[matchedIndex] = {
491
- ...targetProduct,
492
- ...normalizedProduct,
493
- identity_key: targetProduct.identity_key,
494
- num: (0, import_utils3.getSafeProductNum)(targetProduct.num + normalizedProduct.num),
495
- _origin: normalizedProduct._origin || targetProduct._origin
496
- };
1187
+ for (const key of metadataMoneyKeys) {
1188
+ const value = this.formatSummaryMetadataMoney(metadata[key]);
1189
+ if (value === undefined) continue;
1190
+ metadata[key] = value;
1191
+ hasMetadataPatch = true;
1192
+ }
1193
+ if (metadata.average_discount_amount_rate !== undefined) {
1194
+ hasMetadataPatch = true;
1195
+ }
1196
+ if (Array.isArray(productRecord.relation_surcharge_ids)) {
1197
+ metadata.relation_surcharge_ids = productRecord.relation_surcharge_ids;
1198
+ hasMetadataPatch = true;
1199
+ }
1200
+ const productPaymentPrice = this.formatSummaryMetadataMoney(productRecord.payment_price);
1201
+ if (productPaymentPrice !== undefined) {
1202
+ productRecord.payment_price = productPaymentPrice;
1203
+ }
1204
+ if (hasMetadataPatch) productRecord.metadata = metadata;
1205
+ if (!Array.isArray(productRecord.product_bundle)) continue;
1206
+ for (const bundle of productRecord.product_bundle) {
1207
+ const bundleRecord = bundle;
1208
+ const bundleMetadata = bundleRecord.metadata && typeof bundleRecord.metadata === 'object' ? {
1209
+ ...bundleRecord.metadata
1210
+ } : {};
1211
+ let hasBundleMetadataPatch = false;
1212
+ const bundleSurchargeFee = this.formatSummaryMetadataMoney(bundleRecord.surcharge_fee);
1213
+ if (bundleSurchargeFee !== undefined) {
1214
+ bundleMetadata.surcharge_fee = bundleSurchargeFee;
1215
+ hasBundleMetadataPatch = true;
1216
+ }
1217
+ const bundlePaymentPrice = this.formatSummaryMetadataMoney(bundleRecord.bundle_payment_price);
1218
+ if (bundlePaymentPrice !== undefined) {
1219
+ bundleRecord.bundle_payment_price = bundlePaymentPrice;
1220
+ }
1221
+ if (Array.isArray(bundleRecord.relation_surcharge_ids)) {
1222
+ bundleMetadata.relation_surcharge_ids = bundleRecord.relation_surcharge_ids;
1223
+ hasBundleMetadataPatch = true;
1224
+ }
1225
+ if (hasBundleMetadataPatch) bundleRecord.metadata = bundleMetadata;
497
1226
  }
498
1227
  }
499
- this.applyDiscount();
500
- await this.recalculateSummary({ createIfMissing: true });
501
- this.persistTempOrder();
502
- return tempOrder.products;
503
1228
  }
504
- async updateProductInOrder(params) {
505
- var _a, _b, _c;
1229
+ calculateSummaryAmountGap(params) {
506
1230
  const {
507
- product_id,
508
- product_variant_id,
509
- updates,
510
- identity_key,
511
- product_option_item,
512
- product_bundle
1231
+ tempOrder,
1232
+ summary,
1233
+ orderPaidAmount
1234
+ } = params;
1235
+ const refundAmount = this.getSummaryRefundAmount(summary);
1236
+ const netOrderPaidAmount = _decimal.default.max(orderPaidAmount.minus(refundAmount), 0);
1237
+ const targetAmount = this.calculatePaymentTargetAmount(tempOrder, summary, netOrderPaidAmount);
1238
+ return targetAmount.minus(netOrderPaidAmount).toFixed(2);
1239
+ }
1240
+ getSummaryRefundAmount(summary) {
1241
+ return _decimal.default.max(new _decimal.default(summary?.total_refund_amount || 0), 0);
1242
+ }
1243
+ calculatePaymentTargetAmount(tempOrder, summary, paidAmount = new _decimal.default(0)) {
1244
+ const depositAmount = this.getDepositAmount(tempOrder, summary);
1245
+ if (!this.isDepositOrder(tempOrder, depositAmount)) {
1246
+ return this.getOrderExpectedAmount(summary);
1247
+ }
1248
+ if (this.isDepositCovered({
1249
+ tempOrder,
1250
+ summary,
1251
+ paidAmount
1252
+ })) {
1253
+ return this.getOrderExpectedAmount(summary);
1254
+ }
1255
+ return depositAmount;
1256
+ }
1257
+ normalizeFingerprintMoney(value) {
1258
+ if (value === undefined || value === null || value === '') return '';
1259
+ try {
1260
+ const parsed = new _decimal.default(String(value || 0));
1261
+ return parsed.toFixed(4);
1262
+ } catch {
1263
+ return String(value);
1264
+ }
1265
+ }
1266
+ normalizeFingerprintValue(value) {
1267
+ if (Array.isArray(value)) {
1268
+ return value.map(item => this.normalizeFingerprintValue(item));
1269
+ }
1270
+ if (!value || typeof value !== 'object') return value ?? '';
1271
+ return Object.keys(value).sort().reduce((result, key) => {
1272
+ result[key] = this.normalizeFingerprintValue(value[key]);
1273
+ return result;
1274
+ }, {});
1275
+ }
1276
+ buildProductFingerprintItem(product) {
1277
+ const sku = product?.product_sku || {};
1278
+ const metadata = product?.metadata || {};
1279
+ const discountTypes = (product?.discount_list || []).map(discount => discount?.type ?? discount?.tag);
1280
+ const extensionType = product?.extension_type || (discountTypes.includes('good_pass') ? 'good_pass' : 'normal');
1281
+ return {
1282
+ product_id: product?.product_id ?? null,
1283
+ product_variant_id: product?.product_variant_id ?? null,
1284
+ extension_type: extensionType,
1285
+ num: (0, _utils3.getSafeProductNum)(product?.num ?? product?.product_quantity),
1286
+ selling_price: this.normalizeFingerprintMoney(product?.selling_price),
1287
+ original_price: this.normalizeFingerprintMoney(product?.original_price),
1288
+ payment_price: this.normalizeFingerprintMoney(product?.payment_price ?? product?.selling_price),
1289
+ metadata: {
1290
+ source_product_price: this.normalizeFingerprintMoney(metadata.source_product_price),
1291
+ main_product_selling_price: this.normalizeFingerprintMoney(metadata.main_product_selling_price),
1292
+ main_product_original_price: this.normalizeFingerprintMoney(metadata.main_product_original_price),
1293
+ product_discount_difference: this.normalizeFingerprintMoney(metadata.product_discount_difference)
1294
+ },
1295
+ product_sku: this.normalizeFingerprintValue({
1296
+ code: sku.code ?? '',
1297
+ barcode: sku.barcode ?? '',
1298
+ variant_id: sku.variant_id ?? product?.product_variant_id ?? 0,
1299
+ variant: sku.variant || [],
1300
+ option: (0, _utils.getProductSkuOptions)(product || {}).map(option => ({
1301
+ id: option?.id ?? option?.option_group_item_id ?? null,
1302
+ option_group_id: option?.option_group_id ?? null,
1303
+ option_group_item_id: option?.option_group_item_id ?? option?.id ?? null,
1304
+ num: Number(option?.num ?? 1) || 1,
1305
+ add_price: this.normalizeFingerprintMoney(option?.add_price ?? option?.price)
1306
+ }))
1307
+ }),
1308
+ product_bundle: this.normalizeFingerprintValue((product?.product_bundle || []).map(bundle => ({
1309
+ bundle_product_id: bundle?.bundle_product_id ?? bundle?._bundle_product_id ?? bundle?.product_id ?? null,
1310
+ bundle_variant_id: bundle?.bundle_variant_id ?? bundle?.product_variant_id ?? 0,
1311
+ num: (0, _utils3.getSafeProductNum)(bundle?.num ?? bundle?.quantity),
1312
+ price: this.normalizeFingerprintMoney(bundle?.price),
1313
+ bundle_selling_price: this.normalizeFingerprintMoney(bundle?.bundle_selling_price),
1314
+ price_type: bundle?.price_type ?? '',
1315
+ price_type_ext: bundle?.price_type_ext ?? ''
1316
+ }))),
1317
+ discount_list: this.normalizeFingerprintValue((product?.discount_list || []).map(discount => ({
1318
+ type: discount?.type ?? discount?.tag ?? '',
1319
+ discount_id: discount?.discount_id ?? discount?.order_discount_id ?? discount?.discount?.resource_id ?? null,
1320
+ amount: this.normalizeFingerprintMoney(discount?.amount)
1321
+ })))
1322
+ };
1323
+ }
1324
+ buildOrderProductsFingerprint(products) {
1325
+ const items = (products || []).map(product => this.buildProductFingerprintItem(product));
1326
+ items.sort((left, right) => JSON.stringify(left).localeCompare(JSON.stringify(right)));
1327
+ return JSON.stringify(items);
1328
+ }
1329
+ buildProductStructuralFingerprintItem(product) {
1330
+ const sku = product?.product_sku || {};
1331
+ const metadata = product?.metadata || {};
1332
+ const discountTypes = (product?.discount_list || []).map(discount => discount?.type ?? discount?.tag);
1333
+ const extensionType = product?.extension_type || (discountTypes.includes('good_pass') ? 'good_pass' : 'normal');
1334
+ return {
1335
+ product_id: product?.product_id ?? null,
1336
+ product_variant_id: product?.product_variant_id ?? null,
1337
+ extension_type: extensionType,
1338
+ num: (0, _utils3.getSafeProductNum)(product?.num ?? product?.product_quantity),
1339
+ metadata: {
1340
+ unique_identification_number: metadata.unique_identification_number ?? product?.unique_identification_number ?? '',
1341
+ price_override: metadata.price_override ?? '',
1342
+ is_manual_discount: metadata.is_manual_discount ?? '',
1343
+ product_discount_reason: metadata.product_discount_reason ?? ''
1344
+ },
1345
+ product_sku: this.normalizeFingerprintValue({
1346
+ code: sku.code ?? '',
1347
+ barcode: sku.barcode ?? '',
1348
+ variant_id: sku.variant_id ?? product?.product_variant_id ?? 0,
1349
+ variant: sku.variant || [],
1350
+ option: (0, _utils.getProductSkuOptions)(product || {}).map(option => ({
1351
+ id: option?.id ?? option?.option_group_item_id ?? null,
1352
+ option_group_id: option?.option_group_id ?? null,
1353
+ option_group_item_id: option?.option_group_item_id ?? option?.id ?? null,
1354
+ num: Number(option?.num ?? 1) || 1,
1355
+ add_price: this.normalizeFingerprintMoney(option?.add_price ?? option?.price)
1356
+ }))
1357
+ }),
1358
+ product_bundle: this.normalizeFingerprintValue((product?.product_bundle || []).map(bundle => ({
1359
+ bundle_id: bundle?.bundle_id ?? bundle?.id ?? null,
1360
+ bundle_group_id: bundle?.bundle_group_id ?? bundle?.group_id ?? null,
1361
+ bundle_product_id: bundle?.bundle_product_id ?? bundle?._bundle_product_id ?? bundle?.product_id ?? null,
1362
+ bundle_variant_id: bundle?.bundle_variant_id ?? bundle?.product_variant_id ?? 0,
1363
+ num: (0, _utils3.getSafeProductNum)(bundle?.num ?? bundle?.quantity),
1364
+ price_type: bundle?.price_type ?? '',
1365
+ price_type_ext: bundle?.price_type_ext ?? '',
1366
+ option: this.normalizeFingerprintValue((bundle?.option || []).map(option => ({
1367
+ id: option?.id ?? option?.option_group_item_id ?? null,
1368
+ option_group_id: option?.option_group_id ?? null,
1369
+ option_group_item_id: option?.option_group_item_id ?? option?.id ?? null,
1370
+ num: Number(option?.num ?? 1) || 1,
1371
+ add_price: this.normalizeFingerprintMoney(option?.add_price ?? option?.price)
1372
+ })))
1373
+ }))),
1374
+ discount_list: this.normalizeFingerprintValue((product?.discount_list || []).map(discount => ({
1375
+ type: discount?.type ?? discount?.tag ?? '',
1376
+ discount_id: discount?.discount_id ?? discount?.order_discount_id ?? discount?.discount?.resource_id ?? null,
1377
+ amount: this.normalizeFingerprintMoney(discount?.amount)
1378
+ })))
1379
+ };
1380
+ }
1381
+ buildOrderProductsStructuralFingerprint(products) {
1382
+ const items = (products || []).map(product => this.buildProductStructuralFingerprintItem(product));
1383
+ items.sort((left, right) => JSON.stringify(left).localeCompare(JSON.stringify(right)));
1384
+ return JSON.stringify(items);
1385
+ }
1386
+ getManualDepositOverride(tempOrder) {
1387
+ const override = tempOrder._extend?.manualDepositOverride;
1388
+ if (!override || typeof override !== 'object') return null;
1389
+ if (typeof override.amount !== 'string') return null;
1390
+ if (typeof override.productStructuralFingerprint !== 'string') return null;
1391
+ return override;
1392
+ }
1393
+ setManualDepositOverride(tempOrder, amount) {
1394
+ tempOrder._extend = {
1395
+ ...(tempOrder._extend || {}),
1396
+ manualDepositOverride: {
1397
+ amount,
1398
+ productStructuralFingerprint: this.buildOrderProductsStructuralFingerprint(tempOrder.products)
1399
+ }
1400
+ };
1401
+ }
1402
+ clearManualDepositOverride(tempOrder) {
1403
+ if (!tempOrder._extend?.manualDepositOverride) return;
1404
+ tempOrder._extend = {
1405
+ ...tempOrder._extend
1406
+ };
1407
+ delete tempOrder._extend.manualDepositOverride;
1408
+ }
1409
+ getOriginalSalesSnapshot(tempOrder) {
1410
+ const snapshot = tempOrder._extend?.originalSalesSnapshot;
1411
+ if (!snapshot || typeof snapshot !== 'object') return null;
1412
+ if (typeof snapshot.productFingerprint !== 'string') return null;
1413
+ return snapshot;
1414
+ }
1415
+ isSnapshotPayableAmountUnchanged(tempOrder, snapshot, snapshotSummary) {
1416
+ const currentPaidPaymentSummary = this.calculatePaidPaymentSummary(tempOrder.payments || []);
1417
+ const currentAmountGap = this.calculateSummaryAmountGap({
1418
+ tempOrder,
1419
+ summary: snapshotSummary,
1420
+ orderPaidAmount: currentPaidPaymentSummary.gapPaidAmount
1421
+ });
1422
+ const snapshotPaidPaymentSummary = this.calculatePaidPaymentSummary(Array.isArray(snapshot.payments) ? snapshot.payments : []);
1423
+ const snapshotAmountGap = this.calculateSummaryAmountGap({
1424
+ tempOrder,
1425
+ summary: snapshotSummary,
1426
+ orderPaidAmount: snapshotPaidPaymentSummary.gapPaidAmount
1427
+ });
1428
+ return currentAmountGap === snapshotAmountGap;
1429
+ }
1430
+ hasShopDiscountChangedFromSnapshot(tempOrder, snapshot) {
1431
+ const snapshotSummary = snapshot.summary && typeof snapshot.summary === 'object' ? snapshot.summary : {};
1432
+ const snapshotShopDiscount = snapshot.shop_discount ?? snapshotSummary.shop_discount;
1433
+ if (snapshotShopDiscount === undefined || snapshotShopDiscount === null || snapshotShopDiscount === '') {
1434
+ return !new _decimal.default(tempOrder.shop_discount || 0).equals(0);
1435
+ }
1436
+ return !new _decimal.default(tempOrder.shop_discount || 0).equals(snapshotShopDiscount || 0);
1437
+ }
1438
+ restoreOriginalSnapshotIfProductsUnchanged(tempOrder, snapshot) {
1439
+ const currentFingerprint = this.buildOrderProductsFingerprint(tempOrder.products);
1440
+ const manualDepositOverride = this.getManualDepositOverride(tempOrder);
1441
+ if (currentFingerprint !== snapshot.productFingerprint) {
1442
+ const currentStructuralFingerprint = this.buildOrderProductsStructuralFingerprint(tempOrder.products);
1443
+ const snapshotStructuralFingerprint = this.buildOrderProductsStructuralFingerprint(Array.isArray(snapshot.products) ? snapshot.products : []);
1444
+ if (currentStructuralFingerprint !== snapshotStructuralFingerprint) return null;
1445
+ }
1446
+ let summary = {
1447
+ ...(0, _utils.createEmptySummary)(),
1448
+ ...(0, _lodashEs.cloneDeep)(snapshot.summary || {})
1449
+ };
1450
+ const shouldUseManualDepositOverride = Boolean(manualDepositOverride && manualDepositOverride.productStructuralFingerprint === this.buildOrderProductsStructuralFingerprint(tempOrder.products));
1451
+ if (shouldUseManualDepositOverride) {
1452
+ summary = this.applyDepositAmountToSummary(tempOrder, summary, manualDepositOverride.amount);
1453
+ } else if (manualDepositOverride) {
1454
+ this.clearManualDepositOverride(tempOrder);
1455
+ }
1456
+ const isPayableAmountUnchanged = this.isSnapshotPayableAmountUnchanged(tempOrder, snapshot, summary);
1457
+ if (!isPayableAmountUnchanged) return null;
1458
+ if (Array.isArray(snapshot.products)) {
1459
+ tempOrder.products = (0, _lodashEs.cloneDeep)(snapshot.products);
1460
+ }
1461
+ if (Array.isArray(snapshot.surcharges)) {
1462
+ tempOrder.surcharges = (0, _lodashEs.cloneDeep)(snapshot.surcharges);
1463
+ }
1464
+ tempOrder.surcharge_fee = summary.surcharge_fee || tempOrder.surcharge_fee;
1465
+ tempOrder.deposit_amount = summary.deposit_amount || '0.00';
1466
+ tempOrder.is_deposit = summary.deposit_amount !== '0.00' ? 1 : 0;
1467
+ const previousSummary = this.store.summary;
1468
+ this.store.summary = summary;
1469
+ this.updateTempOrderPaymentStatus(tempOrder, {
1470
+ previousSummary,
1471
+ summary
1472
+ });
1473
+ return summary;
1474
+ }
1475
+ clearPersistedAmountFieldsForFullRecalc(products) {
1476
+ const moneyKeys = ['surcharge_fee', 'main_product_attached_bundle_surcharge_fee', 'surcharge_rounding_remainder', 'main_product_attached_bundle_tax_fee', 'tax_fee', 'original_tax_fee', 'tax_fee_rounding_remainder', 'original_tax_fee_rounding_remainder'];
1477
+ const metadataMoneyKeys = ['main_product_attached_bundle_surcharge_fee', 'surcharge_rounding_remainder', 'main_product_attached_bundle_tax_fee', 'tax_fee_rounding_remainder'];
1478
+ for (const product of products || []) {
1479
+ const record = product;
1480
+ for (const key of moneyKeys) delete record[key];
1481
+ if (record.metadata && typeof record.metadata === 'object') {
1482
+ record.metadata = {
1483
+ ...record.metadata
1484
+ };
1485
+ for (const key of metadataMoneyKeys) delete record.metadata[key];
1486
+ }
1487
+ for (const bundle of record.product_bundle || []) {
1488
+ for (const key of moneyKeys) delete bundle[key];
1489
+ if (bundle.metadata && typeof bundle.metadata === 'object') {
1490
+ bundle.metadata = {
1491
+ ...bundle.metadata
1492
+ };
1493
+ for (const key of metadataMoneyKeys) delete bundle.metadata[key];
1494
+ }
1495
+ }
1496
+ }
1497
+ }
1498
+ getPaymentTargetAmount() {
1499
+ const tempOrder = this.ensureTempOrder();
1500
+ const summary = this.store.summary || (0, _utils.createEmptySummary)();
1501
+ const paidPaymentSummary = this.calculatePaidPaymentSummary(tempOrder.payments || []);
1502
+ const refundAmount = this.getSummaryRefundAmount(summary);
1503
+ const netPaidAmount = _decimal.default.max(paidPaymentSummary.gapPaidAmount.minus(refundAmount), 0);
1504
+ return this.calculatePaymentTargetAmount(tempOrder, summary, netPaidAmount);
1505
+ }
1506
+ ensureTempOrder() {
1507
+ if (this.store.tempOrder) {
1508
+ this.ensureExternalSaleNumber(this.store.tempOrder);
1509
+ return this.store.tempOrder;
1510
+ }
1511
+ const newOrder = this.createDefaultTempOrderInstance();
1512
+ this.ensureExternalSaleNumber(newOrder);
1513
+ this.store.tempOrder = newOrder;
1514
+ this.persistTempOrder();
1515
+ return newOrder;
1516
+ }
1517
+ restoreOrder() {
1518
+ this.logInfo('restoreOrder start', {});
1519
+ const freshTempOrder = this.createDefaultTempOrderInstance();
1520
+ this.ensureExternalSaleNumber(freshTempOrder);
1521
+ this.store.tempOrder = freshTempOrder;
1522
+ this.store.summary = (0, _utils.createEmptySummary)();
1523
+ this.store.lastOrderInfo = undefined;
1524
+ this.store.syncState = undefined;
1525
+ void this.store.discount?.setOriginalDiscountList([]);
1526
+ void this.store.discount?.setDiscountList([]);
1527
+ this.persistTempOrder();
1528
+ return freshTempOrder;
1529
+ }
1530
+ getTempOrder() {
1531
+ return this.store.tempOrder;
1532
+ }
1533
+ async replaceTempOrder(tempOrder, options) {
1534
+ if (!(0, _utils.isTempOrder)(tempOrder)) {
1535
+ throw new Error('无效的 tempOrder 数据');
1536
+ }
1537
+ const nextTempOrder = this.normalizeTempOrderForRuntime((0, _lodashEs.cloneDeep)(tempOrder), {
1538
+ ensureIdentity: false
1539
+ });
1540
+ this.store.tempOrder = nextTempOrder;
1541
+ if (options?.recalculateSummary !== false) {
1542
+ await this.recalculateSummary({
1543
+ createIfMissing: false
1544
+ });
1545
+ } else {
1546
+ this.store.summary = (0, _utils.createEmptySummary)();
1547
+ }
1548
+ if (options?.persist !== false) {
1549
+ this.persistTempOrder();
1550
+ }
1551
+ if (options?.saveDraft) {
1552
+ await this.saveDraft();
1553
+ }
1554
+ return nextTempOrder;
1555
+ }
1556
+ getOrderIdentity() {
1557
+ const tempOrder = this.store.tempOrder;
1558
+ if (!tempOrder) return null;
1559
+ const lastOrderInfo = this.store.lastOrderInfo || {};
1560
+ const orderId = tempOrder.order_id ?? lastOrderInfo.order_id ?? lastOrderInfo.id ?? null;
1561
+ return {
1562
+ localId: this.cacheId || tempOrder._extend?.localId,
1563
+ orderId,
1564
+ externalSaleNumber: tempOrder.external_sale_number || lastOrderInfo.external_sale_number || '',
1565
+ orderNumber: tempOrder.order_number || lastOrderInfo.order_number || '',
1566
+ shopOrderNumber: tempOrder.shop_order_number || lastOrderInfo.shop_order_number || '',
1567
+ shopFullOrderNumber: tempOrder.shop_full_order_number || lastOrderInfo.shop_full_order_number || '',
1568
+ isSubmitted: orderId !== null && orderId !== undefined
1569
+ };
1570
+ }
1571
+ getOrderSyncState() {
1572
+ const tempOrder = this.store.tempOrder;
1573
+ if (!tempOrder) return 'local';
1574
+ if (this.store.syncState) return this.store.syncState;
1575
+ if (this.store.lastOrderInfo) return 'remoteLoaded';
1576
+ if (tempOrder.order_id) return 'submitted';
1577
+ return 'local';
1578
+ }
1579
+ getOrderAmountSnapshot() {
1580
+ const tempOrder = this.store.tempOrder;
1581
+ if (!tempOrder) return null;
1582
+ const summary = this.store.summary || (0, _utils.createEmptySummary)();
1583
+ const depositAmount = tempOrder.deposit_amount || summary.deposit_amount || '0.00';
1584
+ const paidPaymentSummary = this.calculatePaidPaymentSummary(tempOrder.payments || []);
1585
+ const amountGap = this.calculateSummaryAmountGap({
1586
+ tempOrder,
1587
+ summary,
1588
+ orderPaidAmount: paidPaymentSummary.gapPaidAmount
1589
+ });
1590
+ return {
1591
+ isDeposit: tempOrder.is_deposit === 1,
1592
+ depositAmount,
1593
+ expectAmount: summary.expect_amount || '0.00',
1594
+ totalAmount: summary.total_amount || '0.00',
1595
+ amountGap,
1596
+ summary
1597
+ };
1598
+ }
1599
+ getOrderSnapshot() {
1600
+ const tempOrder = this.store.tempOrder;
1601
+ if (!tempOrder) return null;
1602
+ const identity = this.getOrderIdentity();
1603
+ const amount = this.getOrderAmountSnapshot();
1604
+ if (!identity || !amount) return null;
1605
+ return {
1606
+ identity,
1607
+ syncState: this.getOrderSyncState(),
1608
+ amount,
1609
+ status: tempOrder.status || '',
1610
+ paymentStatus: tempOrder.payment_status || '',
1611
+ shippingStatus: tempOrder.shipping_status || '',
1612
+ note: tempOrder.note || '',
1613
+ customer: {
1614
+ customerId: tempOrder.customer_id ?? null,
1615
+ customerName: tempOrder.customer_name || '',
1616
+ phone: tempOrder.phone || '',
1617
+ email: tempOrder.email || ''
1618
+ },
1619
+ productCount: tempOrder.products?.length || 0,
1620
+ lastOrderInfo: this.store.lastOrderInfo,
1621
+ tempOrder
1622
+ };
1623
+ }
1624
+ async addNewOrder() {
1625
+ const tempOrder = this.ensureTempOrder();
1626
+ this.ensureExternalSaleNumber(tempOrder);
1627
+ await this.recalculateSummary({
1628
+ createIfMissing: true
1629
+ });
1630
+ this.persistTempOrder();
1631
+ if (this.draftPersistEnabled) {
1632
+ await this.saveDraft();
1633
+ }
1634
+ return tempOrder;
1635
+ }
1636
+ getOrderProducts() {
1637
+ const tempOrder = this.ensureTempOrder();
1638
+ return tempOrder.products;
1639
+ }
1640
+ getProductUid(product) {
1641
+ const metadata = product.metadata && typeof product.metadata === 'object' ? product.metadata : {};
1642
+ const uid = metadata.unique_identification_number || product.unique_identification_number || (0, _utils.createUuidV4)();
1643
+ metadata.unique_identification_number = String(uid);
1644
+ product.metadata = metadata;
1645
+ return String(uid);
1646
+ }
1647
+ sanitizeOrderProductForTempOrder(product) {
1648
+ if (product.metadata && typeof product.metadata === 'object') {
1649
+ delete product.metadata.origin;
1650
+ }
1651
+ return product;
1652
+ }
1653
+ sanitizeTempOrderProducts(tempOrder) {
1654
+ tempOrder.products = (tempOrder.products || []).map(product => this.sanitizeOrderProductForTempOrder(product));
1655
+ }
1656
+ ensureExtend(tempOrder) {
1657
+ if (!tempOrder._extend || typeof tempOrder._extend !== 'object') {
1658
+ tempOrder._extend = {
1659
+ productsByUid: {}
1660
+ };
1661
+ }
1662
+ if (!tempOrder._extend.productsByUid) {
1663
+ tempOrder._extend.productsByUid = {};
1664
+ }
1665
+ return tempOrder._extend;
1666
+ }
1667
+ captureProductRuntime(tempOrder, rawProduct, normalizedProduct, existedUid) {
1668
+ const uid = existedUid || this.getProductUid(normalizedProduct);
1669
+ const extend = this.ensureExtend(tempOrder);
1670
+ const existed = extend.productsByUid?.[uid] || {};
1671
+ const origin = rawProduct._origin ?? normalizedProduct._origin ?? existed.origin;
1672
+ extend.productsByUid[uid] = {
1673
+ ...existed,
1674
+ ...(origin !== undefined ? {
1675
+ origin
1676
+ } : {})
1677
+ };
1678
+ delete normalizedProduct._origin;
1679
+ delete normalizedProduct.unique_identification_number;
1680
+ }
1681
+ createLinkedProductAndBooking(params) {
1682
+ const productMetadata = params.product.metadata && typeof params.product.metadata === 'object' ? {
1683
+ ...params.product.metadata
1684
+ } : {};
1685
+ const bookingMetadata = params.booking.metadata && typeof params.booking.metadata === 'object' ? {
1686
+ ...params.booking.metadata
1687
+ } : {};
1688
+ const productUid = String(productMetadata.unique_identification_number || params.product.unique_identification_number || (0, _utils.createUuidV4)());
1689
+ const bookingUid = String(bookingMetadata.unique_identification_number || (0, _utils.createUuidV4)());
1690
+ productMetadata.unique_identification_number = productUid;
1691
+ bookingMetadata.unique_identification_number = bookingUid;
1692
+ this.syncBookingMetadataHolder(params.booking, bookingMetadata);
1693
+ return {
1694
+ productUid,
1695
+ bookingUid,
1696
+ product: {
1697
+ ...params.product,
1698
+ unique_identification_number: productUid,
1699
+ metadata: {
1700
+ ...productMetadata,
1701
+ booking_uid: bookingUid
1702
+ },
1703
+ booking_uid: bookingUid
1704
+ },
1705
+ booking: {
1706
+ ...params.booking,
1707
+ is_all: (0, _utils.normalizeBookingIsAll)(params.booking),
1708
+ product_uid: productUid,
1709
+ sub_type: (0, _utils.normalizeBookingSubType)(params.booking),
1710
+ metadata: bookingMetadata
1711
+ }
1712
+ };
1713
+ }
1714
+
1715
+ /**
1716
+ * booking.holder 是提交真源;metadata.holder 作为详情/回显冗余字段同步保存。
1717
+ *
1718
+ * @example
1719
+ * this.syncBookingMetadataHolder(booking, metadata);
1720
+ */
1721
+ syncBookingMetadataHolder(booking, metadata) {
1722
+ if (booking?.holder !== undefined) {
1723
+ metadata.holder = booking.holder;
1724
+ }
1725
+ }
1726
+
1727
+ /**
1728
+ * 从单条 booking 的 holder / metadata 中收集 holder form_record。
1729
+ *
1730
+ * @example
1731
+ * const ids = this.collectBookingHolderRecords(booking);
1732
+ */
1733
+ collectBookingHolderRecords(booking) {
1734
+ const holder = booking?.holder && typeof booking.holder === 'object' ? booking.holder : null;
1735
+ const source = holder?.form_record ?? booking?.metadata?.holder_id;
1736
+ const records = Array.isArray(source) ? source : [source];
1737
+ return records.filter(record => record !== undefined && record !== null && record !== '');
1738
+ }
1739
+ setProductHolderFromBooking(product, booking) {
1740
+ if (!product || !booking) return;
1741
+ const holderRecords = this.collectBookingHolderRecords(booking);
1742
+ if (!product.metadata || typeof product.metadata !== 'object') {
1743
+ product.metadata = {};
1744
+ }
1745
+ product.metadata.holder_id = holderRecords.length > 0 ? holderRecords : null;
1746
+ }
1747
+ syncLinkedBookingHoldersToProducts(tempOrder) {
1748
+ const bookings = tempOrder.bookings || [];
1749
+ const products = tempOrder.products || [];
1750
+ if (!bookings.length || !products.length) return;
1751
+ const bookingByProductUid = new Map();
1752
+ const bookingByBookingUid = new Map();
1753
+ bookings.forEach(booking => {
1754
+ if (!booking || typeof booking !== 'object') return;
1755
+ const productUid = booking.product_uid || booking.metadata?.product_uid;
1756
+ if (productUid !== undefined && productUid !== null && String(productUid) !== '') {
1757
+ bookingByProductUid.set(String(productUid), booking);
1758
+ }
1759
+ const bookingUid = booking.metadata?.unique_identification_number || booking.booking_uid;
1760
+ if (bookingUid !== undefined && bookingUid !== null && String(bookingUid) !== '') {
1761
+ bookingByBookingUid.set(String(bookingUid), booking);
1762
+ }
1763
+ });
1764
+ products.forEach(product => {
1765
+ if (!product || typeof product !== 'object') return;
1766
+ const productUid = product.metadata?.unique_identification_number || product.unique_identification_number;
1767
+ const bookingUid = product.booking_uid || product.metadata?.booking_uid;
1768
+ const linkedBooking = (productUid !== undefined && productUid !== null && String(productUid) !== '' ? bookingByProductUid.get(String(productUid)) : undefined) || (bookingUid !== undefined && bookingUid !== null && String(bookingUid) !== '' ? bookingByBookingUid.get(String(bookingUid)) : undefined);
1769
+ if (linkedBooking) {
1770
+ this.setProductHolderFromBooking(product, linkedBooking);
1771
+ }
1772
+ });
1773
+ }
1774
+
1775
+ /**
1776
+ * 按 form_record 顺序读取 booking holder 的主字段名称。
1777
+ *
1778
+ * @example
1779
+ * const names = this.collectBookingHolderNames(booking);
1780
+ */
1781
+ collectBookingHolderNames(booking) {
1782
+ const holder = booking?.holder && typeof booking.holder === 'object' ? booking.holder : null;
1783
+ const rawName = holder?.name;
1784
+ if (Array.isArray(rawName)) {
1785
+ return rawName.map(name => String(name || '').trim());
1786
+ }
1787
+ if (typeof rawName === 'string') {
1788
+ return rawName.split(',').map(name => name.trim());
1789
+ }
1790
+ return [];
1791
+ }
1792
+
1793
+ /**
1794
+ * 按老 ticketBooking `formatHolder` 语义汇总订单顶层 holder。
1795
+ *
1796
+ * @example
1797
+ * const holder = this.buildTempOrderHolderFromBookings(tempOrder);
1798
+ */
1799
+ buildTempOrderHolderFromBookings(tempOrder) {
1800
+ const holderRecords = [];
1801
+ const holderNames = [];
1802
+ let baseHolder = null;
1803
+ const seen = new Set();
1804
+ for (const booking of tempOrder.bookings || []) {
1805
+ const holder = booking?.holder && typeof booking.holder === 'object' ? booking.holder : null;
1806
+ if (!baseHolder && holder) {
1807
+ baseHolder = holder;
1808
+ }
1809
+ const records = this.collectBookingHolderRecords(booking);
1810
+ const names = this.collectBookingHolderNames(booking);
1811
+ for (const [index, record] of records.entries()) {
1812
+ const key = String(record);
1813
+ if (seen.has(key)) continue;
1814
+ seen.add(key);
1815
+ holderRecords.push(record);
1816
+ if (names[index]) {
1817
+ holderNames.push(names[index]);
1818
+ }
1819
+ }
1820
+ }
1821
+ if (!holderRecords.length) {
1822
+ return null;
1823
+ }
1824
+ const nextHolder = {
1825
+ customer_id: baseHolder?.customer_id ?? tempOrder.customer_id,
1826
+ form_id: baseHolder?.form_id ?? tempOrder.holder?.form_id ?? 0,
1827
+ form_record: holderRecords
1828
+ };
1829
+ if (holderNames.length) {
1830
+ nextHolder.name = holderNames.join(',');
1831
+ }
1832
+ if (baseHolder?.is_source_shop || this.otherParams?.isFranchisee) {
1833
+ nextHolder.is_source_shop = 1;
1834
+ }
1835
+ return nextHolder;
1836
+ }
1837
+
1838
+ /**
1839
+ * 将 booking 级 holder 汇总到 tempOrder 顶层 holder。
1840
+ *
1841
+ * @example
1842
+ * this.syncTempOrderHolderFromBookings(tempOrder);
1843
+ */
1844
+ syncTempOrderHolderFromBookings(tempOrder) {
1845
+ const bookings = tempOrder.bookings || [];
1846
+ const hasHolderSource = bookings.some(booking => {
1847
+ return booking?.holder !== undefined || booking?.metadata?.holder_id !== undefined;
1848
+ });
1849
+ const nextHolder = this.buildTempOrderHolderFromBookings(tempOrder);
1850
+ if (nextHolder || hasHolderSource || bookings.length === 0) {
1851
+ tempOrder.holder = nextHolder;
1852
+ }
1853
+ this.syncLinkedBookingHoldersToProducts(tempOrder);
1854
+ }
1855
+ getBookingUniqueIdentificationNumber(booking) {
1856
+ const uid = booking?.metadata?.unique_identification_number;
1857
+ if (uid === undefined || uid === null || uid === '') return null;
1858
+ return String(uid);
1859
+ }
1860
+ findBookingIndexByUniqueIdentificationNumber(tempOrder, uniqueIdentificationNumber) {
1861
+ return (tempOrder.bookings || []).findIndex(booking => {
1862
+ return this.getBookingUniqueIdentificationNumber(booking) === uniqueIdentificationNumber;
1863
+ });
1864
+ }
1865
+ removeLinkedBookingsForProduct(tempOrder, product) {
1866
+ const productUid = product?.metadata?.unique_identification_number || product?.unique_identification_number;
1867
+ const bookingUid = product?.booking_uid || product?.metadata?.booking_uid;
1868
+ if (!productUid && !bookingUid) return;
1869
+ tempOrder.bookings = (tempOrder.bookings || []).filter(booking => {
1870
+ const currentBookingUid = this.getBookingUniqueIdentificationNumber(booking);
1871
+ if (bookingUid && currentBookingUid === String(bookingUid)) return false;
1872
+ if (productUid && String(booking?.product_uid || '') === String(productUid)) return false;
1873
+ return true;
1874
+ });
1875
+ }
1876
+
1877
+ // ─── TempOrder: 金额汇总 ───
1878
+
1879
+ getSalesSummary() {
1880
+ if (!this.salesSummaryModuleName) return null;
1881
+ return this.core.getModule(this.salesSummaryModuleName);
1882
+ }
1883
+ applyOrderProductsDepositSummary(tempOrder, summary) {
1884
+ const depositSummary = (0, _utils.calculateOrderProductsDeposit)(tempOrder.products || [], {
1885
+ productsByUid: tempOrder._extend?.productsByUid
1886
+ });
1887
+ const manualDepositOverride = this.getManualDepositOverride(tempOrder);
1888
+ const currentStructuralFingerprint = manualDepositOverride ? this.buildOrderProductsStructuralFingerprint(tempOrder.products) : '';
1889
+ const shouldUseManualDepositOverride = Boolean(manualDepositOverride && manualDepositOverride.productStructuralFingerprint === currentStructuralFingerprint);
1890
+ if (manualDepositOverride && !shouldUseManualDepositOverride) {
1891
+ this.clearManualDepositOverride(tempOrder);
1892
+ }
1893
+ const depositAmount = shouldUseManualDepositOverride ? manualDepositOverride.amount : depositSummary.totalText;
1894
+ const hasDepositAmount = new _decimal.default(depositAmount).gt(0);
1895
+ const nextSummary = {
1896
+ ...summary,
1897
+ deposit_amount: depositAmount,
1898
+ deposit: hasDepositAmount ? {
1899
+ total: depositAmount,
1900
+ deposit_policy_ids: depositSummary.deposit_policy_ids,
1901
+ deposit_policy_data: depositSummary.protocols,
1902
+ hasDeposit: true
1903
+ } : undefined
1904
+ };
1905
+ const isExistingDepositDisabled = tempOrder.is_deposit === 0 && new _decimal.default(tempOrder.deposit_amount || 0).gt(0);
1906
+ tempOrder.deposit_amount = depositAmount;
1907
+ tempOrder.is_deposit = hasDepositAmount && !isExistingDepositDisabled ? 1 : 0;
1908
+ return this.applyLastOrderDepositSnapshot(tempOrder, nextSummary);
1909
+ }
1910
+ async recalculateSummary(options) {
1911
+ const tempOrder = options?.createIfMissing ? this.ensureTempOrder() : this.store.tempOrder;
1912
+ if (!tempOrder) return null;
1913
+ this.sanitizeTempOrderProducts(tempOrder);
1914
+ const originalSnapshot = this.getOriginalSalesSnapshot(tempOrder);
1915
+ const hasShopDiscountChanged = originalSnapshot ? this.hasShopDiscountChangedFromSnapshot(tempOrder, originalSnapshot) : false;
1916
+ const shouldReuseOriginalSnapshot = originalSnapshot && !hasShopDiscountChanged;
1917
+ const snapshotSummary = shouldReuseOriginalSnapshot ? this.restoreOriginalSnapshotIfProductsUnchanged(tempOrder, originalSnapshot) : null;
1918
+ if (snapshotSummary) return snapshotSummary;
1919
+ const shouldFullRecalculateFromSnapshot = Boolean(originalSnapshot);
1920
+ const summaryProducts = shouldFullRecalculateFromSnapshot ? (0, _lodashEs.cloneDeep)(tempOrder.products || []) : tempOrder.products;
1921
+ if (shouldFullRecalculateFromSnapshot) {
1922
+ this.clearPersistedAmountFieldsForFullRecalc(summaryProducts || []);
1923
+ }
1924
+ const salesSummary = this.getSalesSummary();
1925
+ const existedSummary = this.store.summary || (0, _utils.createEmptySummary)();
1926
+ const totalRefundAmount = existedSummary.total_refund_amount || '0.00';
1927
+ const paidPaymentSummary = this.calculatePaidPaymentSummary(tempOrder.payments || []);
1928
+ if (!salesSummary) {
1929
+ const emptyBaseSummary = {
1930
+ ...(0, _utils.createEmptySummary)(),
1931
+ ...existedSummary,
1932
+ total_refund_amount: totalRefundAmount
1933
+ };
1934
+ const emptySummaryBase = {
1935
+ ...emptyBaseSummary,
1936
+ customer_paid_amount: paidPaymentSummary.customerPaidAmount.toFixed(2),
1937
+ order_paid_amount: paidPaymentSummary.orderPaidAmount.toFixed(2),
1938
+ pay_service_charge_amount: paidPaymentSummary.payServiceChargeAmount.toFixed(2),
1939
+ rounding_amount: paidPaymentSummary.roundingAmount.toFixed(2)
1940
+ };
1941
+ const emptySummaryWithDeposit = this.applyOrderProductsDepositSummary(tempOrder, emptySummaryBase);
1942
+ const emptySummary = {
1943
+ ...emptySummaryWithDeposit,
1944
+ amount_gap: this.calculateSummaryAmountGap({
1945
+ tempOrder,
1946
+ summary: emptySummaryWithDeposit,
1947
+ orderPaidAmount: paidPaymentSummary.gapPaidAmount
1948
+ })
1949
+ };
1950
+ tempOrder.surcharge_fee = emptySummary.surcharge_fee;
1951
+ this.store.summary = emptySummary;
1952
+ this.updateTempOrderPaymentStatus(tempOrder, {
1953
+ previousSummary: existedSummary,
1954
+ summary: emptySummary
1955
+ });
1956
+ return this.store.summary;
1957
+ }
1958
+ const salesSummaryResult = await salesSummary.getSummary({
1959
+ products: summaryProducts,
1960
+ isPriceIncludeTax: tempOrder.is_price_include_tax,
1961
+ shopDiscount: tempOrder.shop_discount,
1962
+ ...(this.otherParams?.channel ? {
1963
+ channel: this.otherParams.channel
1964
+ } : {})
1965
+ });
1966
+ if (shouldFullRecalculateFromSnapshot) {
1967
+ tempOrder.products = summaryProducts;
1968
+ }
1969
+ this.syncSummaryProductMetadata(tempOrder.products || []);
1970
+ const roundingAmount = paidPaymentSummary.roundingAmount;
1971
+ const payServiceChargeAmount = paidPaymentSummary.payServiceChargeAmount;
1972
+ const hasSalesSummaryAmount = salesSummaryResult.expect_amount !== undefined || salesSummaryResult.total_amount !== undefined;
1973
+ const expectAmount = hasSalesSummaryAmount ? new _decimal.default(salesSummaryResult.expect_amount ?? salesSummaryResult.total_amount ?? 0) : null;
1974
+ const amountPaymentPatch = hasSalesSummaryAmount || !roundingAmount.eq(0) || !payServiceChargeAmount.eq(0) ? {
1975
+ rounding_amount: roundingAmount.toFixed(2),
1976
+ ...(expectAmount ? {
1977
+ total_amount: _decimal.default.max(0, expectAmount.plus(roundingAmount).plus(payServiceChargeAmount)).toFixed(2)
1978
+ } : {})
1979
+ } : {};
1980
+ const normalizedExpectAmountPatch = expectAmount && salesSummaryResult.expect_amount === undefined ? {
1981
+ expect_amount: expectAmount.toFixed(2)
1982
+ } : {};
1983
+ const summaryForGap = this.applyOrderProductsDepositSummary(tempOrder, {
1984
+ ...salesSummaryResult,
1985
+ total_refund_amount: totalRefundAmount,
1986
+ ...amountPaymentPatch,
1987
+ ...normalizedExpectAmountPatch
1988
+ });
1989
+ const summary = {
1990
+ ...summaryForGap,
1991
+ total_refund_amount: totalRefundAmount,
1992
+ customer_paid_amount: paidPaymentSummary.customerPaidAmount.toFixed(2),
1993
+ order_paid_amount: paidPaymentSummary.orderPaidAmount.toFixed(2),
1994
+ ...amountPaymentPatch,
1995
+ ...normalizedExpectAmountPatch,
1996
+ amount_gap: this.calculateSummaryAmountGap({
1997
+ tempOrder,
1998
+ summary: summaryForGap,
1999
+ orderPaidAmount: paidPaymentSummary.gapPaidAmount
2000
+ }),
2001
+ pay_service_charge_amount: payServiceChargeAmount.toFixed(2)
2002
+ };
2003
+ this.store.summary = summary;
2004
+ if (summary.is_price_include_tax !== undefined) {
2005
+ tempOrder.is_price_include_tax = summary.is_price_include_tax;
2006
+ }
2007
+ if (typeof summary.tax_title === 'string' && summary.tax_title) {
2008
+ tempOrder.tax_title = summary.tax_title;
2009
+ }
2010
+ tempOrder.surcharge_fee = summary.surcharge_fee;
2011
+ tempOrder.surcharges = summary.surcharges || [];
2012
+ this.updateTempOrderPaymentStatus(tempOrder, {
2013
+ previousSummary: existedSummary,
2014
+ summary
2015
+ });
2016
+ return this.store.summary;
2017
+ }
2018
+ async getOrderSummary() {
2019
+ const summary = await this.recalculateSummary({
2020
+ createIfMissing: true
2021
+ });
2022
+ if (!summary) return (0, _utils.createEmptySummary)();
2023
+ this.persistTempOrder();
2024
+ return summary;
2025
+ }
2026
+
2027
+ // ─── TempOrder: 备注 ───
2028
+
2029
+ updateTempOrderNote(note, sync = false) {
2030
+ const tempOrder = this.ensureTempOrder();
2031
+ tempOrder.note = String(note || '');
2032
+ this.persistTempOrder();
2033
+ if (sync) {
2034
+ const orderId = tempOrder.order_id;
2035
+ if (!orderId) return tempOrder.note;
2036
+ return this.syncTempOrderNoteToRemote(orderId, tempOrder.note);
2037
+ }
2038
+ return tempOrder.note;
2039
+ }
2040
+ async syncTempOrderNoteToRemote(orderId, note) {
2041
+ await this.request.put(`/order/order/${orderId}/note`, {
2042
+ note
2043
+ });
2044
+ return note;
2045
+ }
2046
+ getTempOrderNote() {
2047
+ return this.store.tempOrder?.note || '';
2048
+ }
2049
+ updateTempOrderShopDiscount(amount) {
2050
+ const tempOrder = this.ensureTempOrder();
2051
+ const discount = _decimal.default.max(new _decimal.default(Number(amount) || 0), 0).toDecimalPlaces(2).toFixed(2);
2052
+ tempOrder.shop_discount = discount;
2053
+ this.persistTempOrder();
2054
+ return tempOrder.shop_discount;
2055
+ }
2056
+ updateTempOrderDepositAmount(amount) {
2057
+ const tempOrder = this.ensureTempOrder();
2058
+ const depositAmount = this.normalizeDepositAmount(amount);
2059
+ const previousSummary = this.store.summary || (0, _utils.createEmptySummary)();
2060
+ const summaryWithDeposit = this.applyDepositAmountToSummary(tempOrder, {
2061
+ ...(0, _utils.createEmptySummary)(),
2062
+ ...previousSummary
2063
+ }, depositAmount);
2064
+ this.setManualDepositOverride(tempOrder, depositAmount);
2065
+ const paidPaymentSummary = this.calculatePaidPaymentSummary(tempOrder.payments || []);
2066
+ const summary = {
2067
+ ...summaryWithDeposit,
2068
+ amount_gap: this.calculateSummaryAmountGap({
2069
+ tempOrder,
2070
+ summary: summaryWithDeposit,
2071
+ orderPaidAmount: paidPaymentSummary.gapPaidAmount
2072
+ })
2073
+ };
2074
+ this.store.summary = summary;
2075
+ this.updateTempOrderPaymentStatus(tempOrder, {
2076
+ previousSummary,
2077
+ summary
2078
+ });
2079
+ this.persistTempOrder();
2080
+ return summary;
2081
+ }
2082
+ updateTempOrderBuzzer(buzzer) {
2083
+ const tempOrder = this.ensureTempOrder();
2084
+ tempOrder.buzzer = String(buzzer || '');
2085
+ this.persistTempOrder();
2086
+ return tempOrder.buzzer;
2087
+ }
2088
+ updateTempOrderContactsInfo(contactsInfo) {
2089
+ const tempOrder = this.ensureTempOrder();
2090
+ tempOrder.contacts_info = contactsInfo && typeof contactsInfo === 'object' && !Array.isArray(contactsInfo) ? contactsInfo : null;
2091
+ this.persistTempOrder();
2092
+ return tempOrder.contacts_info;
2093
+ }
2094
+ buildTempOrderCustomer(params) {
2095
+ if (!params.customerId) return null;
2096
+ const source = params.source ? (0, _lodashEs.cloneDeep)(params.source) : {};
2097
+ const name = source.name ?? source.display_name ?? source.customerName ?? source.customer_name ?? params.customerName;
2098
+ const customerName = source.customer_name ?? source.customerName ?? source.display_name ?? source.name ?? params.customerName;
2099
+ return {
2100
+ ...source,
2101
+ id: source.id ?? params.customerId,
2102
+ customer_id: source.customer_id ?? params.customerId,
2103
+ name: String(name || ''),
2104
+ customer_name: String(customerName || ''),
2105
+ country_calling_code: String(source.country_calling_code ?? source.countryCallingCode ?? params.countryCallingCode),
2106
+ phone: String(source.phone ?? params.phone),
2107
+ email: String(source.email ?? params.email)
2108
+ };
2109
+ }
2110
+ isCustomerBoundDiscount(discount) {
2111
+ const type = discount?.type ?? discount?.tag;
2112
+ return type === 'good_pass' || type === 'discount_card' || type === 'product_discount_card';
2113
+ }
2114
+ shouldKeepCustomerBoundDiscountAfterCustomerChange(discount) {
2115
+ return Boolean(discount?.isScan || discount?.isEditMode);
2116
+ }
2117
+ productHasCustomerBoundDiscount(product) {
2118
+ if ((product?.discount_list || []).some(discount => this.isCustomerBoundDiscount(discount))) {
2119
+ return true;
2120
+ }
2121
+ return (product?.product_bundle || []).some(bundle => (bundle?.discount_list || []).some(discount => this.isCustomerBoundDiscount(discount)));
2122
+ }
2123
+ shouldSkipDiscountCalculation(tempOrder) {
2124
+ if (this.hasActiveCustomerBoundDiscount) return false;
2125
+ const discountList = this.store.discount?.getDiscountList?.() || [];
2126
+ if (discountList.length > 0) return false;
2127
+ return !(tempOrder.products || []).some(product => this.productHasCustomerBoundDiscount(product));
2128
+ }
2129
+ clearCustomerBoundDiscounts(tempOrder) {
2130
+ let didRemoveCustomerBoundDiscount = false;
2131
+ const filterDiscountList = discountList => {
2132
+ if (!Array.isArray(discountList)) return [];
2133
+ return discountList.filter(discount => {
2134
+ const shouldRemove = this.isCustomerBoundDiscount(discount);
2135
+ if (shouldRemove) didRemoveCustomerBoundDiscount = true;
2136
+ return !shouldRemove;
2137
+ });
2138
+ };
2139
+ tempOrder.products = (tempOrder.products || []).map(product => {
2140
+ const nextProduct = {
2141
+ ...product,
2142
+ discount_list: filterDiscountList(product.discount_list)
2143
+ };
2144
+ if (Array.isArray(nextProduct.product_bundle)) {
2145
+ nextProduct.product_bundle = nextProduct.product_bundle.map(bundle => ({
2146
+ ...bundle,
2147
+ discount_list: filterDiscountList(bundle.discount_list)
2148
+ }));
2149
+ }
2150
+ return nextProduct;
2151
+ });
2152
+ delete tempOrder.discount_list;
2153
+ if (didRemoveCustomerBoundDiscount) {
2154
+ this.hasActiveCustomerBoundDiscount = true;
2155
+ }
2156
+ const discountModule = this.store.discount;
2157
+ if (!discountModule) return;
2158
+ const currentDiscounts = discountModule.getDiscountList?.() || [];
2159
+ const currentOriginalDiscounts = discountModule.getOriginalDiscountList?.() || [];
2160
+ const nextDiscounts = currentDiscounts.filter(discount => {
2161
+ if (this.shouldKeepCustomerBoundDiscountAfterCustomerChange(discount)) return true;
2162
+ return !this.isCustomerBoundDiscount(discount);
2163
+ });
2164
+ const nextOriginalDiscounts = currentOriginalDiscounts.filter(discount => {
2165
+ if (this.shouldKeepCustomerBoundDiscountAfterCustomerChange(discount)) return true;
2166
+ return !this.isCustomerBoundDiscount(discount);
2167
+ });
2168
+ void discountModule.setDiscountList?.(nextDiscounts);
2169
+ void discountModule.setOriginalDiscountList?.(nextOriginalDiscounts);
2170
+ }
2171
+
2172
+ /**
2173
+ * 更新当前 tempOrder 的客户协议字段。
2174
+ *
2175
+ * PaymentModal 只需要修改订单里的客户事实;如果外部还有客户模块需要同步,
2176
+ * 应由调用方通过回调处理,不能把外部 UI 状态写进 tempOrder。
2177
+ */
2178
+ updateTempOrderCustomer(customer) {
2179
+ const tempOrder = this.ensureTempOrder();
2180
+ const previousCustomerId = tempOrder.customer_id ?? null;
2181
+ const customerId = customer.customer_id ?? customer.customerId ?? customer.id ?? null;
2182
+ const customerName = customer.customer_name ?? customer.customerName ?? customer.display_name ?? customer.name ?? '';
2183
+ tempOrder.customer_id = customerId === null || customerId === undefined || customerId === '' ? null : Number(customerId);
2184
+ tempOrder.customer_name = String(customerName || '');
2185
+ tempOrder.country_calling_code = String(customer.country_calling_code ?? customer.countryCallingCode ?? '');
2186
+ tempOrder.phone = String(customer.phone || '');
2187
+ tempOrder.email = String(customer.email || '');
2188
+ tempOrder.customer = this.buildTempOrderCustomer({
2189
+ source: customer,
2190
+ customerId: tempOrder.customer_id,
2191
+ customerName: tempOrder.customer_name,
2192
+ countryCallingCode: tempOrder.country_calling_code,
2193
+ phone: tempOrder.phone,
2194
+ email: tempOrder.email
2195
+ });
2196
+ const nextCustomerId = tempOrder.customer_id ?? null;
2197
+ this.persistTempOrder();
2198
+ return {
2199
+ customerId: tempOrder.customer_id,
2200
+ customerName: tempOrder.customer_name,
2201
+ phone: tempOrder.phone,
2202
+ email: tempOrder.email
2203
+ };
2204
+ }
2205
+ setOrderCustomer(patch, snapshot) {
2206
+ if (!patch) {
2207
+ this.clearOrderCustomer();
2208
+ return;
2209
+ }
2210
+ const tempOrder = this.ensureTempOrder();
2211
+ const previousCustomerId = tempOrder.customer_id ?? null;
2212
+ tempOrder.customer_id = patch.customer_id || patch.id || null;
2213
+ tempOrder.customer_name = String(patch.customer_name || '');
2214
+ tempOrder.country_calling_code = String(patch.country_calling_code || '');
2215
+ tempOrder.phone = String(patch.phone || '');
2216
+ tempOrder.email = String(patch.email || '');
2217
+ tempOrder.customer = this.buildTempOrderCustomer({
2218
+ source: snapshot || null,
2219
+ customerId: tempOrder.customer_id,
2220
+ customerName: tempOrder.customer_name,
2221
+ countryCallingCode: tempOrder.country_calling_code,
2222
+ phone: tempOrder.phone,
2223
+ email: tempOrder.email
2224
+ });
2225
+ if (snapshot !== undefined) {
2226
+ const extend = this.ensureExtend(tempOrder);
2227
+ if (snapshot === null) delete extend.customerSnapshot;else extend.customerSnapshot = (0, _lodashEs.cloneDeep)(snapshot);
2228
+ }
2229
+ const nextCustomerId = tempOrder.customer_id ?? null;
2230
+ if (String(previousCustomerId ?? '') !== String(nextCustomerId ?? '')) {
2231
+ this.store.availableWalletIds = [];
2232
+ (0, _utils.clearTempOrderHolderAssignments)(tempOrder);
2233
+ this.clearCustomerBoundDiscounts(tempOrder);
2234
+ this.applyDiscount();
2235
+ }
2236
+ this.persistTempOrder();
2237
+ this.saveDraftInBackground();
2238
+ if (String(previousCustomerId ?? '') !== String(nextCustomerId ?? '')) {
2239
+ this.emitOrderCustomerChange();
2240
+ }
2241
+ }
2242
+ getOrderCustomer() {
2243
+ const tempOrder = this.store.tempOrder;
2244
+ if (!tempOrder || !tempOrder.customer_id) {
2245
+ return null;
2246
+ }
2247
+ return {
2248
+ customer_id: tempOrder.customer_id,
2249
+ customer_name: tempOrder.customer_name || '',
2250
+ country_calling_code: tempOrder.country_calling_code || '',
2251
+ phone: tempOrder.phone || '',
2252
+ email: tempOrder.email || ''
2253
+ };
2254
+ }
2255
+ getOrderCustomerSnapshot() {
2256
+ const snapshot = this.store.tempOrder?._extend?.customerSnapshot;
2257
+ return snapshot ? (0, _lodashEs.cloneDeep)(snapshot) : null;
2258
+ }
2259
+ clearOrderCustomer() {
2260
+ const tempOrder = this.ensureTempOrder();
2261
+ (0, _utils.clearTempOrderHolderAssignments)(tempOrder);
2262
+ tempOrder.customer_id = null;
2263
+ tempOrder.customer_name = '';
2264
+ tempOrder.country_calling_code = '';
2265
+ tempOrder.phone = '';
2266
+ tempOrder.email = '';
2267
+ tempOrder.customer = null;
2268
+ if (tempOrder._extend) delete tempOrder._extend.customerSnapshot;
2269
+ this.store.availableWalletIds = [];
2270
+ this.clearCustomerBoundDiscounts(tempOrder);
2271
+ this.persistTempOrder();
2272
+ this.saveDraftInBackground();
2273
+ this.core.effects.emit(_types.OrderHooks.OnOrderCustomerChange, null);
2274
+ }
2275
+ emitOrderCustomerChange() {
2276
+ const patch = this.getOrderCustomer();
2277
+ if (!patch) {
2278
+ this.core.effects.emit(_types.OrderHooks.OnOrderCustomerChange, null);
2279
+ return;
2280
+ }
2281
+ const payload = {
2282
+ patch,
2283
+ snapshot: this.getOrderCustomerSnapshot()
2284
+ };
2285
+ this.core.effects.emit(_types.OrderHooks.OnOrderCustomerChange, payload);
2286
+ }
2287
+
2288
+ /**
2289
+ * 读取当前订单的 Sales 协议支付项。
2290
+ *
2291
+ * 这里返回的是 tempOrder.payments,而不是旧 PaymentModule 的本地
2292
+ * payment order / tender session。Sales checkout 以该数组为提交事实来源。
2293
+ */
2294
+ getOrderPayments() {
2295
+ const tempOrder = this.ensureTempOrder();
2296
+ return tempOrder.payments || [];
2297
+ }
2298
+
2299
+ /**
2300
+ * 用一组支付来源覆盖当前订单支付项。
2301
+ *
2302
+ * 入参允许来自 PaymentModule 的 PaymentItem,内部统一映射为 Sales 协议:
2303
+ * uuid -> metadata.unique_payment_number,id -> custom_payment_id,
2304
+ * isSynced 等运行态字段不会进入 tempOrder.payments。
2305
+ */
2306
+ setOrderPayments(payments) {
2307
+ const tempOrder = this.ensureTempOrder();
2308
+ tempOrder.payments = (0, _utils.mapPaymentItemsToOrderPayments)(payments);
2309
+ this.persistTempOrder();
2310
+ return tempOrder.payments;
2311
+ }
2312
+
2313
+ /** 追加单个支付项,并立即持久化当前 tempOrder。 */
2314
+ addOrderPayment(payment) {
2315
+ this.logInfo('addOrderPayment', {
2316
+ payment
2317
+ });
2318
+ const tempOrder = this.ensureTempOrder();
2319
+ const orderPaymentType = this.resolveNextOrderPaymentType(tempOrder);
2320
+ const mapped = (0, _utils.mapPaymentItemsToOrderPayments)([this.normalizePaymentSource({
2321
+ ...payment,
2322
+ type: orderPaymentType,
2323
+ order_payment_type: orderPaymentType
2324
+ }, {
2325
+ defaultPaid: true
2326
+ })]);
2327
+ tempOrder.payments = [...(tempOrder.payments || []), ...mapped];
2328
+ this.persistTempOrder();
2329
+ void this.recalculateSummary({
2330
+ createIfMissing: true
2331
+ }).catch(error => {
2332
+ this.logError('recalculate summary after addOrderPayment failed', {
2333
+ error: error instanceof Error ? error.message : String(error)
2334
+ });
2335
+ });
2336
+ return tempOrder.payments;
2337
+ }
2338
+
2339
+ /**
2340
+ * 按支付唯一号、后端支付 ID、自定义支付方式 ID 或旧 uuid 更新支付项。
2341
+ *
2342
+ * 该方法服务 PaymentModal 的 void / 编辑场景;更新 metadata 时会做浅合并,
2343
+ * 避免覆盖 unique_payment_number、现金实付/找零等已有元数据。
2344
+ */
2345
+ updateOrderPayment(paymentIdentity, updates) {
2346
+ const tempOrder = this.ensureTempOrder();
2347
+ const identity = String(paymentIdentity);
2348
+ tempOrder.payments = (tempOrder.payments || []).map(payment => {
2349
+ const uniquePaymentNumber = payment?.metadata?.unique_payment_number;
2350
+ const orderPaymentId = payment?.order_payment_id;
2351
+ const customPaymentId = payment?.custom_payment_id;
2352
+ const uuid = payment?.uuid;
2353
+ const matched = [uniquePaymentNumber, orderPaymentId, customPaymentId, uuid].some(value => value !== undefined && String(value) === identity);
2354
+ if (!matched) return payment;
2355
+ return {
2356
+ ...payment,
2357
+ ...updates,
2358
+ metadata: {
2359
+ ...(payment.metadata || {}),
2360
+ ...(updates.metadata || {})
2361
+ }
2362
+ };
2363
+ });
2364
+ this.persistTempOrder();
2365
+ return tempOrder.payments;
2366
+ }
2367
+
2368
+ /** 从当前订单支付项中移除指定支付项。 */
2369
+ deleteOrderPayment(paymentIdentity) {
2370
+ const tempOrder = this.ensureTempOrder();
2371
+ const identity = String(paymentIdentity);
2372
+ tempOrder.payments = (tempOrder.payments || []).filter(payment => {
2373
+ const uniquePaymentNumber = payment?.metadata?.unique_payment_number;
2374
+ const orderPaymentId = payment?.order_payment_id;
2375
+ const customPaymentId = payment?.custom_payment_id;
2376
+ const uuid = payment?.uuid;
2377
+ return ![uniquePaymentNumber, orderPaymentId, customPaymentId, uuid].some(value => value !== undefined && String(value) === identity);
2378
+ });
2379
+ this.persistTempOrder();
2380
+ return tempOrder.payments;
2381
+ }
2382
+ confirmPendingVoucherPaymentsInList(payments) {
2383
+ return (payments || []).map(payment => {
2384
+ if (!isPendingVoucherPaymentRecord(payment)) return payment;
2385
+ return {
2386
+ ...payment,
2387
+ status: ORDER_PAYMENT_STATUS_PAID
2388
+ };
2389
+ });
2390
+ }
2391
+ discardPendingVoucherPaymentsFromList(payments) {
2392
+ return (payments || []).filter(payment => !isPendingVoucherPaymentRecord(payment));
2393
+ }
2394
+ prepareVoucherPaymentsForSubmit(payments, confirmPendingVoucherPayments = true) {
2395
+ const mappedPayments = (0, _utils.mapPaymentItemsToOrderPayments)(payments || []);
2396
+ return confirmPendingVoucherPayments ? this.confirmPendingVoucherPaymentsInList(mappedPayments) : this.discardPendingVoucherPaymentsFromList(mappedPayments);
2397
+ }
2398
+ applyPaymentLifecycleChange(tempOrder, options) {
2399
+ this.updateTempOrderPaymentStatus(tempOrder);
2400
+ if (options?.persist !== false) {
2401
+ this.persistTempOrder();
2402
+ }
2403
+ if (options?.recalculateSummary === false) return;
2404
+ const logContext = options?.logContext || 'payment lifecycle change';
2405
+ void this.recalculateSummary({
2406
+ createIfMissing: true
2407
+ }).catch(error => {
2408
+ this.logError(`recalculate summary after ${logContext} failed`, {
2409
+ error: error instanceof Error ? error.message : String(error)
2410
+ });
2411
+ });
2412
+ }
2413
+ confirmPendingVoucherPayments(options) {
2414
+ const tempOrder = this.ensureTempOrder();
2415
+ const payments = tempOrder.payments || [];
2416
+ const hasPendingVoucher = payments.some(payment => isPendingVoucherPaymentRecord(payment));
2417
+ if (!hasPendingVoucher) return payments;
2418
+ tempOrder.payments = this.confirmPendingVoucherPaymentsInList(payments);
2419
+ this.applyPaymentLifecycleChange(tempOrder, {
2420
+ ...options,
2421
+ logContext: 'confirmPendingVoucherPayments'
2422
+ });
2423
+ // this.recalculateSummary();
2424
+ return tempOrder.payments;
2425
+ }
2426
+ discardPendingVoucherPayments(options) {
2427
+ const tempOrder = this.ensureTempOrder();
2428
+ const payments = tempOrder.payments || [];
2429
+ const nextPayments = this.discardPendingVoucherPaymentsFromList(payments);
2430
+ if (nextPayments.length === payments.length) return payments;
2431
+ tempOrder.payments = nextPayments;
2432
+ this.applyPaymentLifecycleChange(tempOrder, {
2433
+ ...options,
2434
+ logContext: 'discardPendingVoucherPayments'
2435
+ });
2436
+ return tempOrder.payments;
2437
+ }
2438
+
2439
+ /**
2440
+ * 覆盖当前订单中的 wallet pass 支付项。
2441
+ *
2442
+ * 判断口径与 PaymentModal 保持一致:voucher_id 存在且不为 0 的支付项
2443
+ * 视为 wallet pass;其它支付项保持不变。
2444
+ */
2445
+ updateVoucherOrderPayments(payments, options) {
2446
+ const tempOrder = this.ensureTempOrder();
2447
+ const isOrderPaymentType = value => {
2448
+ return value === 'normal' || value === 'deposit';
2449
+ };
2450
+ const voucherOrderPaymentType = this.resolveNextOrderPaymentType(tempOrder);
2451
+ const withVoucherPaymentType = payment => {
2452
+ const paymentRecord = payment;
2453
+ if (!isVoucherPaymentRecord(paymentRecord)) return payment;
2454
+ const explicitPaymentType = isOrderPaymentType(paymentRecord.order_payment_type) ? paymentRecord.order_payment_type : isOrderPaymentType(paymentRecord.type) ? paymentRecord.type : undefined;
2455
+ const nextPaymentType = explicitPaymentType || voucherOrderPaymentType;
2456
+ const shouldPreserveCustomPaymentType = paymentRecord.custom_payment_type === undefined && paymentRecord.type !== undefined && !isOrderPaymentType(paymentRecord.type);
2457
+ return {
2458
+ ...paymentRecord,
2459
+ ...(shouldPreserveCustomPaymentType ? {
2460
+ custom_payment_type: paymentRecord.type
2461
+ } : {}),
2462
+ type: nextPaymentType,
2463
+ order_payment_type: nextPaymentType
2464
+ };
2465
+ };
2466
+ const normalizeVoucherPaymentStatus = payment => {
2467
+ const paymentRecord = payment;
2468
+ if (!isVoucherPaymentRecord(paymentRecord)) return payment;
2469
+ if (hasSyncedOrderPaymentRecord(paymentRecord) && (paymentRecord.status === undefined || paymentRecord.status === 'active')) {
2470
+ return {
2471
+ ...paymentRecord,
2472
+ status: ORDER_PAYMENT_STATUS_PAID
2473
+ };
2474
+ }
2475
+ if (paymentRecord.status !== undefined && paymentRecord.status !== 'active') {
2476
+ return payment;
2477
+ }
2478
+ return {
2479
+ ...paymentRecord,
2480
+ status: options?.status || ORDER_PAYMENT_STATUS_PENDING
2481
+ };
2482
+ };
2483
+ const mappedVouchers = (0, _utils.mapPaymentItemsToOrderPayments)(payments.map(payment => this.normalizePaymentSource(normalizeVoucherPaymentStatus(withVoucherPaymentType(payment)), {
2484
+ defaultPaid: false
2485
+ }))).filter(payment => {
2486
+ return isVoucherPaymentRecord(payment);
2487
+ });
2488
+ const nonVoucherPayments = (tempOrder.payments || []).filter(payment => {
2489
+ return !isVoucherPaymentRecord(payment);
2490
+ });
2491
+ const normalizeCommittedVoucherPayment = payment => {
2492
+ if (hasSyncedOrderPaymentRecord(payment) && (payment.status === undefined || payment.status === 'active')) {
2493
+ return {
2494
+ ...payment,
2495
+ status: ORDER_PAYMENT_STATUS_PAID
2496
+ };
2497
+ }
2498
+ return payment;
2499
+ };
2500
+ const committedVoucherPayments = (tempOrder.payments || []).filter(payment => isCommittedVoucherPaymentRecord(payment)).map(payment => normalizeCommittedVoucherPayment(payment));
2501
+ const committedVoucherPaymentIds = new Set(committedVoucherPayments.filter(payment => hasSyncedOrderPaymentRecord(payment)).map(payment => String(payment.order_payment_id)));
2502
+ const committedVoucherIds = new Set(committedVoucherPayments.map(payment => String(payment.voucher_id)));
2503
+ const replaceableVouchers = mappedVouchers.filter(payment => {
2504
+ if (hasSyncedOrderPaymentRecord(payment) && committedVoucherPaymentIds.has(String(payment.order_payment_id))) {
2505
+ return false;
2506
+ }
2507
+ if (payment.voucher_id !== undefined && committedVoucherIds.has(String(payment.voucher_id))) {
2508
+ return false;
2509
+ }
2510
+ return true;
2511
+ });
2512
+ const cappedVouchers = this.capVoucherPaymentsByRemainingAmount({
2513
+ vouchers: replaceableVouchers,
2514
+ nonVoucherPayments: [...nonVoucherPayments, ...committedVoucherPayments]
2515
+ });
2516
+ tempOrder.payments = [...nonVoucherPayments, ...committedVoucherPayments, ...cappedVouchers];
2517
+ this.updateTempOrderPaymentStatus(tempOrder);
2518
+ this.persistTempOrder();
2519
+ void this.recalculateSummary({
2520
+ createIfMissing: true
2521
+ }).catch(error => {
2522
+ this.logError('recalculate summary after updateVoucherOrderPayments failed', {
2523
+ error: error instanceof Error ? error.message : String(error)
2524
+ });
2525
+ });
2526
+ return tempOrder.payments;
2527
+ }
2528
+ async shouldMergeProductToOrder(params) {
2529
+ const onBeforeMergeProductToOrder = this.orderHooks?.onBeforeMergeProductToOrder;
2530
+ if (!onBeforeMergeProductToOrder) return true;
2531
+ const result = await onBeforeMergeProductToOrder({
2532
+ ...params,
2533
+ defaultShouldMerge: true
2534
+ });
2535
+ if (typeof result === 'boolean') return result;
2536
+ if (result && typeof result === 'object' && typeof result.shouldMerge === 'boolean') {
2537
+ return result.shouldMerge;
2538
+ }
2539
+ return true;
2540
+ }
2541
+ hasOrderProductLineNote(product) {
2542
+ return typeof product?.note === 'string' && product.note.trim().length > 0;
2543
+ }
2544
+
2545
+ /**
2546
+ * 判断商品行是否显式绑定了 booking。
2547
+ *
2548
+ * @example
2549
+ * this.hasLinkedBookingUid({ metadata: { booking_uid: 'booking-1' } });
2550
+ */
2551
+ hasLinkedBookingUid(product) {
2552
+ const bookingUid = product?.booking_uid ?? product?.metadata?.booking_uid;
2553
+ return bookingUid !== undefined && bookingUid !== null && String(bookingUid) !== '';
2554
+ }
2555
+
2556
+ // ─── TempOrder: 商品 CRUD ───
2557
+
2558
+ /**
2559
+ * 预约商品加车后的统一收尾:促销、折扣、summary 重算与持久化(批量拆行时只调用一次)。
2560
+ *
2561
+ * @example
2562
+ * await this.finalizeProductOrderMutation(tempOrder);
2563
+ */
2564
+ async finalizeProductOrderMutation(tempOrder, options) {
2565
+ this.syncTempOrderHolderFromBookings(tempOrder);
2566
+ if (!options?.skipEditDiscountConfigRefresh) {
2567
+ await this.ensureEditDiscountConfigForProductChange(tempOrder);
2568
+ }
2569
+ if (!options?.skipDiscountRecalculation) {
2570
+ await this.applyPromotion();
2571
+ }
2572
+ if (!options?.skipDiscountRecalculation) {
2573
+ this.applyDiscount();
2574
+ }
2575
+ this.sanitizeTempOrderProducts(tempOrder);
2576
+ await this.recalculateSummary({
2577
+ createIfMissing: true
2578
+ });
2579
+ this.persistTempOrder();
2580
+ }
2581
+
2582
+ /**
2583
+ * 向 tempOrder 追加一条商品行(及可选 booking),不触发 summary 重算。
2584
+ * 带 booking 且 num>1 时由 `addProductToOrder` 拆成多次本方法调用(每次 num=1)。
2585
+ *
2586
+ * @example
2587
+ * await this.appendProductLineToTempOrder(tempOrder, { product_id: 1, num: 1 }, booking);
2588
+ */
2589
+ async appendProductLineToTempOrder(tempOrder, product, booking) {
2590
+ const linked = booking ? this.createLinkedProductAndBooking({
2591
+ product,
2592
+ booking
2593
+ }) : null;
2594
+ const productToAdd = linked?.product || product;
2595
+ const incomingFingerprint = (0, _utils3.buildProductLineFingerprint)((0, _utils.getProductSkuOptions)(productToAdd), productToAdd.product_bundle);
2596
+ const normalizedIncoming = (0, _utils.mergeOrderProductDisplayFields)(productToAdd, (0, _utils3.normalizeOrderProduct)(productToAdd));
2597
+ const incomingBookingUid = productToAdd.booking_uid ?? productToAdd.metadata?.booking_uid;
2598
+ if (incomingBookingUid !== undefined && incomingBookingUid !== null && String(incomingBookingUid) !== '') {
2599
+ normalizedIncoming.booking_uid = String(incomingBookingUid);
2600
+ normalizedIncoming.metadata = {
2601
+ ...(normalizedIncoming.metadata || {}),
2602
+ booking_uid: String(incomingBookingUid)
2603
+ };
2604
+ }
2605
+ normalizedIncoming.discount_list = (0, _utils.normalizeOrderProductDiscountList)(normalizedIncoming.discount_list);
2606
+ const hasIncomingGoodPass = this.hasGoodPassDiscount(normalizedIncoming);
2607
+ const hasIncomingProductNote = this.hasOrderProductLineNote(normalizedIncoming);
2608
+ const hasIncomingBookingUid = this.hasLinkedBookingUid(productToAdd) || this.hasLinkedBookingUid(normalizedIncoming);
2609
+ const shouldForceNewLine = !!booking || hasIncomingProductNote || hasIncomingBookingUid;
2610
+ const matchedIndex = hasIncomingGoodPass || shouldForceNewLine ? -1 : tempOrder.products.findIndex(item => {
2611
+ if (this.hasGoodPassDiscount(item)) return false;
2612
+ if (this.hasOrderProductLineNote(item)) return false;
2613
+ if (item.order_detail_id !== undefined && item.order_detail_id !== null) return false;
2614
+ if (item.metadata?.is_edit_for_runtime) return false;
2615
+ if (item.product_id !== productToAdd.product_id) return false;
2616
+ if (item.product_variant_id !== productToAdd.product_variant_id) return false;
2617
+ const existedFingerprint = (0, _utils3.buildProductLineFingerprint)((0, _utils.getProductSkuOptions)(item), item.product_bundle);
2618
+ return existedFingerprint === incomingFingerprint;
2619
+ });
2620
+ const matchedProduct = matchedIndex === -1 ? null : tempOrder.products[matchedIndex];
2621
+ const existedFingerprint = matchedProduct ? (0, _utils3.buildProductLineFingerprint)((0, _utils.getProductSkuOptions)(matchedProduct), matchedProduct.product_bundle) : '';
2622
+ const shouldMerge = matchedProduct ? await this.shouldMergeProductToOrder({
2623
+ incomingProduct: productToAdd,
2624
+ normalizedIncoming,
2625
+ matchedProduct,
2626
+ matchedIndex,
2627
+ incomingFingerprint,
2628
+ existedFingerprint,
2629
+ tempOrder,
2630
+ booking
2631
+ }) : false;
2632
+ if (matchedIndex === -1 || !matchedProduct || !shouldMerge) {
2633
+ this.captureProductRuntime(tempOrder, productToAdd, normalizedIncoming);
2634
+ if (linked) {
2635
+ normalizedIncoming.booking_uid = linked.bookingUid;
2636
+ normalizedIncoming.metadata = {
2637
+ ...(normalizedIncoming.metadata || {}),
2638
+ booking_uid: linked.bookingUid
2639
+ };
2640
+ }
2641
+ // tempOrder.products.push(normalizedIncoming);
2642
+ tempOrder.products = [...tempOrder.products, normalizedIncoming];
2643
+ } else {
2644
+ const targetProduct = matchedProduct;
2645
+ const targetUid = targetProduct.metadata?.unique_identification_number;
2646
+ const normalizedProduct = (0, _utils.mergeOrderProductDisplayFields)(targetProduct, (0, _utils3.normalizeOrderProduct)({
2647
+ ...productToAdd,
2648
+ metadata: {
2649
+ ...(productToAdd.metadata || {}),
2650
+ unique_identification_number: targetUid
2651
+ }
2652
+ }));
2653
+ normalizedProduct.discount_list = (0, _utils.normalizeOrderProductDiscountList)(normalizedProduct.discount_list);
2654
+ const nextNum = (0, _utils3.getSafeProductNum)(targetProduct.num + normalizedProduct.num);
2655
+ const targetIsManualProductDiscount = isManualProductDiscountProduct(targetProduct);
2656
+ const nextDiscountList = targetIsManualProductDiscount ? (0, _utils.normalizeOrderProductDiscountList)((0, _manualProductDiscount.syncManualProductDiscountProductNum)(targetProduct.discount_list, nextNum)) : normalizedProduct.discount_list;
2657
+ tempOrder.products[matchedIndex] = {
2658
+ ...targetProduct,
2659
+ ...normalizedProduct,
2660
+ discount_list: nextDiscountList,
2661
+ metadata: {
2662
+ ...(targetProduct.metadata || {}),
2663
+ ...(normalizedProduct.metadata || {}),
2664
+ unique_identification_number: targetUid
2665
+ },
2666
+ product_sku: {
2667
+ ...(targetProduct.product_sku || {
2668
+ option: []
2669
+ }),
2670
+ ...(normalizedProduct.product_sku || {
2671
+ option: []
2672
+ }),
2673
+ option: (0, _utils.getProductSkuOptions)(normalizedProduct)
2674
+ },
2675
+ ...(targetIsManualProductDiscount ? {
2676
+ selling_price: targetProduct.selling_price,
2677
+ original_price: targetProduct.original_price,
2678
+ payment_price: targetProduct.payment_price,
2679
+ metadata: {
2680
+ ...(normalizedProduct.metadata || {}),
2681
+ ...(targetProduct.metadata || {}),
2682
+ unique_identification_number: targetUid
2683
+ }
2684
+ } : {}),
2685
+ note: targetProduct.note,
2686
+ order_detail_id: targetProduct.order_detail_id,
2687
+ num: nextNum
2688
+ };
2689
+ this.captureProductRuntime(tempOrder, productToAdd, tempOrder.products[matchedIndex], targetUid);
2690
+ }
2691
+ if (linked) {
2692
+ tempOrder.bookings = [...(tempOrder.bookings || []), linked.booking];
2693
+ }
2694
+ }
2695
+
2696
+ /**
2697
+ * 添加商品行。带 booking 时强制独立行;num>1 时拆成多条 num=1 的 product+booking(对齐 BookingByStep / ticketBooking addService)。
2698
+ *
2699
+ * @example
2700
+ * await order.addProductToOrder({ product_id: 1, num: 3 }, { start_time: '10:00' });
2701
+ * // → tempOrder.products 新增 3 行,每行 num=1,bookings 3 条
2702
+ */
2703
+ async addProductToOrder(product, booking) {
2704
+ const tempOrder = this.ensureTempOrder();
2705
+ if (booking) {
2706
+ const productRecord = product;
2707
+ const splitCount = (0, _utils3.getSafeProductNum)(productRecord.num ?? productRecord.product_quantity ?? 1);
2708
+ if (splitCount > 1) {
2709
+ for (let i = 0; i < splitCount; i += 1) {
2710
+ const singleLine = (0, _lodashEs.cloneDeep)(productRecord);
2711
+ singleLine.num = 1;
2712
+ delete singleLine.product_quantity;
2713
+ await this.appendProductLineToTempOrder(tempOrder, singleLine, (0, _lodashEs.cloneDeep)(booking));
2714
+ }
2715
+ await this.finalizeProductOrderMutation(tempOrder);
2716
+ return tempOrder.products;
2717
+ }
2718
+ }
2719
+ await this.appendProductLineToTempOrder(tempOrder, product, booking);
2720
+ await this.finalizeProductOrderMutation(tempOrder);
2721
+ this.logInfo('addProductToOrder success', {
2722
+ product_id: product.product_id,
2723
+ with_booking: !!booking
2724
+ });
2725
+ return tempOrder.products;
2726
+ }
2727
+
2728
+ /**
2729
+ * 批量添加商品行,所有行追加完成后只触发一次促销、折扣、summary 重算与持久化。
2730
+ *
2731
+ * @example
2732
+ * await order.addProductsToOrder([
2733
+ * { product: { product_id: 1, product_variant_id: 0, num: 1 } },
2734
+ * { product: { product_id: 2, product_variant_id: 0, num: 1 } },
2735
+ * ]);
2736
+ */
2737
+ async addProductsToOrder(items, options) {
2738
+ const tempOrder = this.ensureTempOrder();
2739
+ if (!Array.isArray(items) || items.length === 0) return tempOrder.products;
2740
+ for (const item of items || []) {
2741
+ const {
2742
+ product,
2743
+ booking
2744
+ } = item;
2745
+ if (!product) continue;
2746
+ if (booking) {
2747
+ const productRecord = product;
2748
+ const splitCount = (0, _utils3.getSafeProductNum)(productRecord.num ?? productRecord.product_quantity ?? 1);
2749
+ if (splitCount > 1) {
2750
+ for (let i = 0; i < splitCount; i += 1) {
2751
+ const singleLine = (0, _lodashEs.cloneDeep)(productRecord);
2752
+ singleLine.num = 1;
2753
+ delete singleLine.product_quantity;
2754
+ await this.appendProductLineToTempOrder(tempOrder, singleLine, (0, _lodashEs.cloneDeep)(booking));
2755
+ }
2756
+ continue;
2757
+ }
2758
+ }
2759
+ await this.appendProductLineToTempOrder(tempOrder, product, booking);
2760
+ }
2761
+ await this.finalizeProductOrderMutation(tempOrder, options);
2762
+ this.logInfo('addProductsToOrder success', {
2763
+ itemsCount: items.length
2764
+ });
2765
+ return tempOrder.products;
2766
+ }
2767
+ hasGoodPassDiscount(product) {
2768
+ const hasMainGoodPass = (product.discount_list || []).some(item => item?.type === 'good_pass' || item?.tag === 'good_pass');
2769
+ if (hasMainGoodPass) return true;
2770
+ const bundleDiscountList = [];
2771
+ (product.product_bundle || []).forEach(item => {
2772
+ bundleDiscountList.push(...(item?.discount_list || []));
2773
+ });
2774
+ (product?._extend?.other?.bundle || []).forEach(item => {
2775
+ bundleDiscountList.push(...(item?.discount_list || []));
2776
+ });
2777
+ return bundleDiscountList.some(item => item?.type === 'good_pass' || item?.tag === 'good_pass');
2778
+ }
2779
+ getBundleRuntimeIdentity(bundle) {
2780
+ if (!bundle || typeof bundle !== 'object') return '';
2781
+ return [bundle.bundle_id ?? bundle.id ?? '', bundle.bundle_group_id ?? bundle.group_id ?? '', bundle.bundle_product_id ?? bundle._bundle_product_id ?? bundle.product_id ?? '', bundle.bundle_variant_id ?? bundle.product_variant_id ?? 0].join('|');
2782
+ }
2783
+ preservePersistedBundlePriceFields(previousBundles, nextBundles) {
2784
+ if (!Array.isArray(nextBundles)) return nextBundles;
2785
+ if (!Array.isArray(previousBundles) || previousBundles.length === 0) return nextBundles;
2786
+ const previousByIdentity = new Map();
2787
+ previousBundles.forEach(bundle => {
2788
+ const identity = this.getBundleRuntimeIdentity(bundle);
2789
+ if (identity) previousByIdentity.set(identity, bundle);
2790
+ });
2791
+ return nextBundles.map((bundle, index) => {
2792
+ if (!bundle || typeof bundle !== 'object') return bundle;
2793
+ const previous = previousByIdentity.get(this.getBundleRuntimeIdentity(bundle)) || previousBundles[index];
2794
+ if (!previous || typeof previous !== 'object') return bundle;
2795
+ return {
2796
+ ...bundle,
2797
+ ...((bundle.cover === undefined || bundle.cover === null || bundle.cover === '') && previous.cover !== undefined && previous.cover !== null && previous.cover !== '' ? {
2798
+ cover: previous.cover
2799
+ } : {}),
2800
+ price: previous.price,
2801
+ custom_price: previous.custom_price,
2802
+ bundle_payment_price: previous.bundle_payment_price,
2803
+ bundle_selling_price: previous.bundle_selling_price,
2804
+ original_price: previous.original_price,
2805
+ product_price: previous.product_price
2806
+ };
2807
+ });
2808
+ }
2809
+ applyOrderProductUpdateToTempOrder(tempOrder, params) {
2810
+ const {
2811
+ product_id,
2812
+ product_variant_id,
2813
+ updates,
2814
+ unique_identification_number,
2815
+ identity_key,
2816
+ product_sku,
2817
+ product_bundle,
2818
+ booking
2819
+ } = params;
2820
+ const identityLookup = {
2821
+ product_id,
2822
+ product_variant_id
2823
+ };
2824
+ if (unique_identification_number !== undefined) {
2825
+ identityLookup.unique_identification_number = unique_identification_number;
2826
+ if (identity_key === undefined) {
2827
+ identityLookup.identity_key = unique_identification_number;
2828
+ }
2829
+ }
2830
+ if (identity_key !== undefined) {
2831
+ identityLookup.identity_key = identity_key;
2832
+ }
2833
+ if (product_sku !== undefined) identityLookup.product_sku = product_sku;
2834
+ if (product_bundle !== undefined) identityLookup.product_bundle = product_bundle;
2835
+ let productIndex = -1;
2836
+ if (unique_identification_number !== undefined) {
2837
+ const targetUid = String(unique_identification_number);
2838
+ productIndex = tempOrder.products.findIndex(item => {
2839
+ return String(item.metadata?.unique_identification_number || item.unique_identification_number || '') === targetUid;
2840
+ });
2841
+ }
2842
+ if (productIndex === -1) {
2843
+ productIndex = (0, _utils3.getProductIdentityIndex)(tempOrder.products, identityLookup);
2844
+ }
2845
+ if (productIndex === -1) {
2846
+ throw new Error('[Order] 目标商品不存在,无法更新');
2847
+ }
2848
+ const targetProduct = tempOrder.products[productIndex];
2849
+ const nextProduct = {
2850
+ ...targetProduct,
2851
+ ...updates,
2852
+ product_id,
2853
+ product_variant_id,
2854
+ metadata: {
2855
+ ...(targetProduct.metadata || {}),
2856
+ ...(updates?.metadata || {}),
2857
+ unique_identification_number: targetProduct.metadata?.unique_identification_number || unique_identification_number
2858
+ },
2859
+ product_sku: {
2860
+ ...(targetProduct.product_sku || {
2861
+ option: []
2862
+ }),
2863
+ ...(updates?.product_sku && typeof updates.product_sku === 'object' ? updates.product_sku : {}),
2864
+ option: Array.isArray(updates?.product_sku?.option) ? updates.product_sku.option : (0, _utils.getProductSkuOptions)(targetProduct)
2865
+ }
2866
+ };
2867
+ nextProduct.num = (0, _utils3.getSafeProductNum)(nextProduct.num);
2868
+ // 兜底:caller 通过 updates 直接改了 selling_price / original_price 时,
2869
+ // 视为 "重新给 source-level 主价"(caller 侧旧约定仍是 main-only 单价)。
2870
+ // 清掉 metadata 里的 source_product_price / main_product_* / price_schema_version,
2871
+ // 让 normalize 把顶层 selling_price 当作 source 来重算 main_original(=source+options) /
2872
+ // main_selling / composite;否则旧 metadata 会反压顶层新值,触发口径不一致。
2873
+ // 如果 caller 走推荐路径(updates.metadata.* 显式指定主价),保留 metadata 由 normalize 认它。
2874
+ const callerUpdatesTopPrice = Object.prototype.hasOwnProperty.call(updates || {}, 'selling_price') || Object.prototype.hasOwnProperty.call(updates || {}, 'original_price');
2875
+ const callerUpdatesMainMeta = updates?.metadata?.main_product_selling_price !== undefined || updates?.metadata?.main_product_original_price !== undefined || updates?.metadata?.source_product_price !== undefined;
2876
+ const callerUpdatesSourcePrice = updates?.metadata?.source_product_price !== undefined;
2877
+ const callerUpdatesMainSellingPrice = updates?.metadata?.main_product_selling_price !== undefined;
2878
+ const callerUpdatesMainOriginalPrice = updates?.metadata?.main_product_original_price !== undefined;
2879
+ const previousLineFingerprint = (0, _utils3.buildProductLineFingerprint)((0, _utils.getProductSkuOptions)(targetProduct), targetProduct.product_bundle);
2880
+ const nextLineFingerprint = (0, _utils3.buildProductLineFingerprint)((0, _utils.getProductSkuOptions)(nextProduct), nextProduct.product_bundle);
2881
+ const callerChangesVariant = Number(product_variant_id || 0) !== Number(targetProduct.product_variant_id || 0);
2882
+ const callerChangesLineConfiguration = callerChangesVariant || previousLineFingerprint !== nextLineFingerprint;
2883
+ const isPersistedOrderLine = targetProduct.order_detail_id !== undefined && targetProduct.order_detail_id !== null;
2884
+ const callerChangesDiscountList = Array.isArray(updates?.discount_list) && updates.discount_list.length > 0 && !isSameDiscountList(updates.discount_list, targetProduct.discount_list);
2885
+ const callerUpdatesManualPrice = updates?.metadata?.price_override !== undefined || updates?.metadata?.is_manual_discount !== undefined || updates?.metadata?.product_discount_reason !== undefined || callerChangesDiscountList;
2886
+ const callerAllowsBookingReprice = (() => {
2887
+ if (params.allow_booking_reprice !== true || !booking || !callerUpdatesTopPrice || !callerUpdatesMainMeta) {
2888
+ return false;
2889
+ }
2890
+ const hasChangedPrice = (nextValue, currentValue) => {
2891
+ if (nextValue === undefined || nextValue === null || nextValue === '') {
2892
+ return false;
2893
+ }
2894
+ return !new _decimal.default(Number(nextValue) || 0).equals(new _decimal.default(Number(currentValue) || 0));
2895
+ };
2896
+ return hasChangedPrice(updates.selling_price, targetProduct.selling_price) || hasChangedPrice(updates.original_price, targetProduct.original_price);
2897
+ })();
2898
+ if (isPersistedOrderLine && !callerChangesLineConfiguration && !callerUpdatesManualPrice && !callerAllowsBookingReprice) {
2899
+ nextProduct.selling_price = targetProduct.selling_price;
2900
+ nextProduct.original_price = targetProduct.original_price;
2901
+ nextProduct.payment_price = targetProduct.payment_price;
2902
+ nextProduct.metadata = {
2903
+ ...(nextProduct.metadata || {}),
2904
+ source_product_price: targetProduct.metadata?.source_product_price,
2905
+ main_product_selling_price: targetProduct.metadata?.main_product_selling_price,
2906
+ main_product_original_price: targetProduct.metadata?.main_product_original_price,
2907
+ price_schema_version: targetProduct.metadata?.price_schema_version ?? 2
2908
+ };
2909
+ nextProduct.product_bundle = this.preservePersistedBundlePriceFields(targetProduct.product_bundle, nextProduct.product_bundle);
2910
+ } else if (callerChangesLineConfiguration && callerUpdatesSourcePrice && (!callerUpdatesMainSellingPrice || !callerUpdatesMainOriginalPrice)) {
2911
+ // 新配置只传了 source 时,不能把未传的 main_product_* 从旧行继承下来。
2912
+ // 以新 source + 新 option 重建券前价;券后价未显式提供时保留旧主商品折扣额。
2913
+ const previousMainOriginal = new _decimal.default(Number(targetProduct.metadata?.main_product_original_price) || 0);
2914
+ const previousMainSelling = new _decimal.default(Number(targetProduct.metadata?.main_product_selling_price) || previousMainOriginal.toNumber());
2915
+ const preservedDiscount = previousMainOriginal.minus(previousMainSelling);
2916
+ const nextMainOriginal = new _decimal.default(Number(updates.metadata.source_product_price) || 0).plus((0, _utils.sumOptionUnitPrice)((0, _utils.getProductSkuOptions)(nextProduct)));
2917
+ nextProduct.metadata = {
2918
+ ...(nextProduct.metadata || {}),
2919
+ source_product_price: String(updates.metadata.source_product_price),
2920
+ main_product_original_price: callerUpdatesMainOriginalPrice ? String(updates.metadata.main_product_original_price) : nextMainOriginal.toDecimalPlaces(2).toFixed(2),
2921
+ main_product_selling_price: callerUpdatesMainSellingPrice ? String(updates.metadata.main_product_selling_price) : nextMainOriginal.minus(preservedDiscount).toDecimalPlaces(2).toFixed(2),
2922
+ price_schema_version: 2
2923
+ };
2924
+ } else if (callerChangesVariant && !callerUpdatesTopPrice && !callerUpdatesMainMeta) {
2925
+ // variant 已变化但 caller 没有显式报价时,旧 v2 metadata 不能继续充当权威价格源。
2926
+ // 清理后由 normalize 优先读取 metadata.origin.variant 中新 variant 的目录价;
2927
+ // 顶层 original/selling 的旧差额仍会作为 legacy discount 迁移到新价格。
2928
+ const existedMeta = nextProduct.metadata || {};
2929
+ nextProduct.metadata = {
2930
+ ...existedMeta
2931
+ };
2932
+ delete nextProduct.metadata.source_product_price;
2933
+ delete nextProduct.metadata.main_product_selling_price;
2934
+ delete nextProduct.metadata.main_product_original_price;
2935
+ delete nextProduct.metadata.price_schema_version;
2936
+ } else if (callerChangesLineConfiguration && !callerUpdatesTopPrice && !callerUpdatesMainMeta) {
2937
+ // 仅 option / bundle 变化时,保留历史 source 和主商品折扣金额,
2938
+ // 但必须让 original 与 selling 一起随新的 option 金额移动。
2939
+ const previousOptionSum = (0, _utils.sumOptionUnitPrice)((0, _utils.getProductSkuOptions)(targetProduct));
2940
+ const nextOptionSum = (0, _utils.sumOptionUnitPrice)((0, _utils.getProductSkuOptions)(nextProduct));
2941
+ const previousMainOriginal = new _decimal.default(Number(targetProduct.metadata?.main_product_original_price ?? targetProduct.original_price ?? 0) || 0);
2942
+ const previousMainSelling = new _decimal.default(Number(targetProduct.metadata?.main_product_selling_price ?? targetProduct.selling_price ?? previousMainOriginal.toNumber()) || 0);
2943
+ const preservedDiscount = previousMainOriginal.minus(previousMainSelling);
2944
+ const previousSource = targetProduct.metadata?.source_product_price != null ? new _decimal.default(Number(targetProduct.metadata.source_product_price) || 0) : previousMainOriginal.minus(previousOptionSum);
2945
+ const nextMainOriginal = previousSource.plus(nextOptionSum);
2946
+ nextProduct.metadata = {
2947
+ ...(nextProduct.metadata || {}),
2948
+ source_product_price: previousSource.toDecimalPlaces(2).toFixed(2),
2949
+ main_product_original_price: nextMainOriginal.toDecimalPlaces(2).toFixed(2),
2950
+ main_product_selling_price: nextMainOriginal.minus(preservedDiscount).toDecimalPlaces(2).toFixed(2),
2951
+ price_schema_version: 2
2952
+ };
2953
+ } else if (callerUpdatesTopPrice && !callerUpdatesMainMeta) {
2954
+ const existedMeta = nextProduct.metadata || {};
2955
+ nextProduct.metadata = {
2956
+ ...existedMeta
2957
+ };
2958
+ delete nextProduct.metadata.source_product_price;
2959
+ delete nextProduct.metadata.main_product_selling_price;
2960
+ delete nextProduct.metadata.main_product_original_price;
2961
+ delete nextProduct.metadata.price_schema_version;
2962
+ }
2963
+ // normalizeOrderProduct 幂等:v2 metadata 存在 → 保留 main_product_* 折扣重算 composite;
2964
+ // 否则 → 把顶层 selling_price 视作 source,派生 main_product_* 并写 price_schema_version: 2。
2965
+ let normalizedProduct = (0, _utils.mergeOrderProductDisplayFields)(nextProduct, (0, _utils3.normalizeOrderProduct)(nextProduct));
2966
+ normalizedProduct.discount_list = (0, _utils.normalizeOrderProductDiscountList)(normalizedProduct.discount_list);
2967
+ const preservedBookingUid = nextProduct.booking_uid || nextProduct.metadata?.booking_uid || targetProduct.booking_uid || targetProduct.metadata?.booking_uid;
2968
+ if (!booking && preservedBookingUid) {
2969
+ normalizedProduct.booking_uid = preservedBookingUid;
2970
+ normalizedProduct.metadata = {
2971
+ ...(normalizedProduct.metadata || {}),
2972
+ booking_uid: preservedBookingUid
2973
+ };
2974
+ }
2975
+ if (booking) {
2976
+ const productUid = String(normalizedProduct.metadata?.unique_identification_number || targetProduct.metadata?.unique_identification_number || unique_identification_number || (0, _utils.createUuidV4)());
2977
+ normalizedProduct.metadata = {
2978
+ ...(normalizedProduct.metadata || {}),
2979
+ unique_identification_number: productUid
2980
+ };
2981
+ const linked = this.createLinkedProductAndBooking({
2982
+ product: {
2983
+ ...normalizedProduct,
2984
+ unique_identification_number: productUid
2985
+ },
2986
+ booking
2987
+ });
2988
+ this.removeLinkedBookingsForProduct(tempOrder, targetProduct);
2989
+ normalizedProduct = {
2990
+ ...normalizedProduct,
2991
+ booking_uid: linked.bookingUid,
2992
+ metadata: {
2993
+ ...(normalizedProduct.metadata || {}),
2994
+ booking_uid: linked.bookingUid,
2995
+ unique_identification_number: productUid
2996
+ }
2997
+ };
2998
+ tempOrder.bookings = [...(tempOrder.bookings || []), linked.booking];
2999
+ }
3000
+ tempOrder.products[productIndex] = normalizedProduct;
3001
+ this.syncTempOrderHolderFromBookings(tempOrder);
3002
+ this.captureProductRuntime(tempOrder, updates, tempOrder.products[productIndex], tempOrder.products[productIndex].metadata?.unique_identification_number);
3003
+ return tempOrder.products[productIndex].metadata?.unique_identification_number;
3004
+ }
3005
+ async updateOrderProduct(params) {
3006
+ const tempOrder = this.ensureTempOrder();
3007
+ const updatedProductUid = this.applyOrderProductUpdateToTempOrder(tempOrder, params);
3008
+ await this.applyPromotion();
3009
+ this.applyDiscount({
3010
+ reapplyBookingDiscountProductUids: params.allow_booking_discount_reapply && updatedProductUid ? [String(updatedProductUid)] : undefined
3011
+ });
3012
+ this.sanitizeTempOrderProducts(tempOrder);
3013
+ await this.recalculateSummary({
3014
+ createIfMissing: true
3015
+ });
3016
+ this.persistTempOrder();
3017
+ this.logInfo('updateOrderProduct success', {
3018
+ params
3019
+ });
3020
+ return tempOrder.products;
3021
+ }
3022
+ async updateOrderProducts(paramsList) {
3023
+ const tempOrder = this.ensureTempOrder();
3024
+ if (!paramsList.length) {
3025
+ return tempOrder.products;
3026
+ }
3027
+ const reapplyBookingDiscountProductUids = paramsList.reduce((acc, params) => {
3028
+ const updatedProductUid = this.applyOrderProductUpdateToTempOrder(tempOrder, params);
3029
+ // 批量改预约时间时,延迟到最后一次折扣计算里统一恢复已选预约折扣卡。
3030
+ if (params.allow_booking_discount_reapply && updatedProductUid) {
3031
+ acc.push(String(updatedProductUid));
3032
+ }
3033
+ return acc;
3034
+ }, []);
3035
+ await this.finalizeAfterProductsMutation(tempOrder, {
3036
+ reapplyBookingDiscountProductUids: reapplyBookingDiscountProductUids.length ? reapplyBookingDiscountProductUids : undefined
3037
+ });
3038
+ this.logInfo('updateOrderProduct success', {
3039
+ paramsList
3040
+ });
3041
+ return tempOrder.products;
3042
+ }
3043
+ async updateOrderProductQuantity(params) {
3044
+ const {
3045
+ product_id,
3046
+ product_variant_id,
3047
+ num,
3048
+ unique_identification_number,
3049
+ identity_key,
3050
+ product_sku,
3051
+ product_bundle
513
3052
  } = params;
514
3053
  const tempOrder = this.ensureTempOrder();
515
3054
  const identityLookup = {
516
3055
  product_id,
517
3056
  product_variant_id
518
3057
  };
519
- if (identity_key !== void 0)
520
- identityLookup.identity_key = identity_key;
521
- if (product_option_item !== void 0)
522
- identityLookup.product_option_item = product_option_item;
523
- if (product_bundle !== void 0)
524
- identityLookup.product_bundle = product_bundle;
525
- const productIndex = (0, import_utils3.getProductIdentityIndex)(tempOrder.products, identityLookup);
3058
+ if (unique_identification_number !== undefined) {
3059
+ identityLookup.unique_identification_number = unique_identification_number;
3060
+ if (identity_key === undefined) {
3061
+ identityLookup.identity_key = unique_identification_number;
3062
+ }
3063
+ }
3064
+ if (identity_key !== undefined) identityLookup.identity_key = identity_key;
3065
+ if (product_sku !== undefined) identityLookup.product_sku = product_sku;
3066
+ if (product_bundle !== undefined) identityLookup.product_bundle = product_bundle;
3067
+ let productIndex = -1;
3068
+ if (unique_identification_number !== undefined) {
3069
+ const targetUid = String(unique_identification_number);
3070
+ productIndex = tempOrder.products.findIndex(item => {
3071
+ return String(item.metadata?.unique_identification_number || item.unique_identification_number || '') === targetUid;
3072
+ });
3073
+ }
3074
+ if (productIndex === -1) {
3075
+ productIndex = (0, _utils3.getProductIdentityIndex)(tempOrder.products, identityLookup);
3076
+ }
526
3077
  if (productIndex === -1) {
527
- throw new Error("[Order] 目标商品不存在,无法更新");
3078
+ throw new Error('[Order] 目标商品不存在,无法更新数量');
528
3079
  }
529
3080
  const targetProduct = tempOrder.products[productIndex];
530
- const nextProduct = {
3081
+ const normalizedProduct = (0, _utils3.normalizeOrderProduct)({
531
3082
  ...targetProduct,
3083
+ num: (0, _utils3.getSafeProductNum)(num),
3084
+ metadata: {
3085
+ ...(targetProduct.metadata || {}),
3086
+ unique_identification_number: targetProduct.metadata?.unique_identification_number || unique_identification_number
3087
+ }
3088
+ });
3089
+ normalizedProduct.discount_list = (0, _utils.normalizeOrderProductDiscountList)(normalizedProduct.discount_list);
3090
+ tempOrder.products[productIndex] = normalizedProduct;
3091
+ await this.applyPromotion();
3092
+ this.applyDiscount();
3093
+ this.sanitizeTempOrderProducts(tempOrder);
3094
+ await this.recalculateSummary({
3095
+ createIfMissing: true
3096
+ });
3097
+ this.persistTempOrder();
3098
+ this.logInfo('updateOrderProductQuantity success', {
3099
+ params
3100
+ });
3101
+ return tempOrder.products;
3102
+ }
3103
+ updateOrderBooking(params) {
3104
+ const {
3105
+ unique_identification_number,
3106
+ updates
3107
+ } = params;
3108
+ const tempOrder = this.ensureTempOrder();
3109
+ const bookingUid = String(unique_identification_number || '');
3110
+ const bookingIndex = this.findBookingIndexByUniqueIdentificationNumber(tempOrder, bookingUid);
3111
+ if (!bookingUid || bookingIndex === -1) {
3112
+ throw new Error('[Order] 目标 booking 不存在,无法更新');
3113
+ }
3114
+ const targetBooking = tempOrder.bookings[bookingIndex] || {};
3115
+ const nextBooking = {
3116
+ ...targetBooking,
532
3117
  ...updates,
533
- product_id,
534
- product_variant_id
3118
+ metadata: {
3119
+ ...(targetBooking.metadata || {}),
3120
+ ...(updates.metadata || {}),
3121
+ unique_identification_number: bookingUid
3122
+ }
535
3123
  };
536
- nextProduct.num = (0, import_utils3.getSafeProductNum)(nextProduct.num);
537
- const callerUpdatesTopPrice = Object.prototype.hasOwnProperty.call(updates || {}, "selling_price") || Object.prototype.hasOwnProperty.call(updates || {}, "original_price");
538
- const callerUpdatesMainMeta = ((_a = updates == null ? void 0 : updates.metadata) == null ? void 0 : _a.main_product_selling_price) !== void 0 || ((_b = updates == null ? void 0 : updates.metadata) == null ? void 0 : _b.main_product_original_price) !== void 0 || ((_c = updates == null ? void 0 : updates.metadata) == null ? void 0 : _c.source_product_price) !== void 0;
539
- if (callerUpdatesTopPrice && !callerUpdatesMainMeta) {
540
- const existedMeta = nextProduct.metadata || {};
541
- nextProduct.metadata = { ...existedMeta };
542
- delete nextProduct.metadata.source_product_price;
543
- delete nextProduct.metadata.main_product_selling_price;
544
- delete nextProduct.metadata.main_product_original_price;
545
- delete nextProduct.metadata.price_schema_version;
546
- }
547
- tempOrder.products[productIndex] = (0, import_utils3.normalizeOrderProduct)(nextProduct);
548
- this.applyDiscount();
549
- await this.recalculateSummary({ createIfMissing: true });
3124
+ this.syncBookingMetadataHolder(nextBooking, nextBooking.metadata);
3125
+ nextBooking.is_all = (0, _utils.normalizeBookingIsAll)(nextBooking);
3126
+ nextBooking.sub_type = (0, _utils.normalizeBookingSubType)(nextBooking);
3127
+ tempOrder.bookings[bookingIndex] = nextBooking;
3128
+ this.syncTempOrderHolderFromBookings(tempOrder);
550
3129
  this.persistTempOrder();
3130
+ return tempOrder.bookings;
3131
+ }
3132
+
3133
+ /**
3134
+ * 商品行写路径收尾:促销 → 折扣 → 清洗 → summary → 持久化。
3135
+ * 批量删除等多行变更场景只应调用一次,避免 N 次 applyPromotion。
3136
+ *
3137
+ * @example
3138
+ * await this.finalizeAfterProductsMutation(tempOrder);
3139
+ */
3140
+ async finalizeAfterProductsMutation(tempOrder, options) {
3141
+ this.syncTempOrderHolderFromBookings(tempOrder);
3142
+ await this.applyPromotion();
3143
+ this.applyDiscount({
3144
+ reapplyBookingDiscountProductUids: options?.reapplyBookingDiscountProductUids
3145
+ });
3146
+ this.sanitizeTempOrderProducts(tempOrder);
3147
+ await this.recalculateSummary({
3148
+ createIfMissing: true
3149
+ });
3150
+ this.persistTempOrder();
3151
+ }
3152
+
3153
+ /**
3154
+ * 批量按 identity 删除购物车行,末尾仅一次促销重算。
3155
+ *
3156
+ * @example
3157
+ * await order.removeProductsFromOrder([identityA, identityB]);
3158
+ */
3159
+ async removeProductsFromOrder(identities) {
3160
+ const tempOrder = this.ensureTempOrder();
3161
+ if (!identities.length) {
3162
+ return tempOrder.products;
3163
+ }
3164
+ const productsBeforeRemove = tempOrder.products || [];
3165
+ const bookingsBeforeRemove = tempOrder.bookings || [];
3166
+ const matchedProducts = productsBeforeRemove.filter(item => {
3167
+ return identities.some(identity => (0, _utils3.isIdentityMatch)(item, identity));
3168
+ });
3169
+ const removedProducts = [];
3170
+ tempOrder.products = tempOrder.products.filter(item => {
3171
+ const shouldRemove = identities.some(identity => (0, _utils3.isIdentityMatch)(item, identity));
3172
+ if (shouldRemove) removedProducts.push(item);
3173
+ return !shouldRemove;
3174
+ });
3175
+ for (const product of removedProducts) {
3176
+ this.removeLinkedBookingsForProduct(tempOrder, product);
3177
+ }
3178
+ const linkedBookingUidsToRemove = new Set();
3179
+ for (const product of matchedProducts) {
3180
+ const productUid = product?.metadata?.unique_identification_number || product?.unique_identification_number;
3181
+ const productBookingUid = product?.booking_uid || product?.metadata?.booking_uid;
3182
+ const isAddTimeProduct = product?.metadata?.product_add_schedule_time !== undefined && product?.metadata?.product_add_schedule_time !== null;
3183
+ if (isAddTimeProduct) continue;
3184
+ if (productBookingUid !== undefined && productBookingUid !== null && productBookingUid !== '') {
3185
+ linkedBookingUidsToRemove.add(String(productBookingUid));
3186
+ }
3187
+ if (productUid !== undefined && productUid !== null && productUid !== '') {
3188
+ bookingsBeforeRemove.forEach(booking => {
3189
+ const bookingUid = this.getBookingUniqueIdentificationNumber(booking);
3190
+ if (bookingUid && String(booking?.product_uid || '') === String(productUid)) {
3191
+ linkedBookingUidsToRemove.add(bookingUid);
3192
+ }
3193
+ });
3194
+ }
3195
+ }
3196
+ if (linkedBookingUidsToRemove.size > 0) {
3197
+ // 删除主预约商品时,同步移除挂在同一 booking_uid 下的加时商品,避免残留孤儿行。
3198
+ tempOrder.products = (tempOrder.products || []).filter(line => {
3199
+ const lineBookingUid = line?.metadata?.booking_uid || line?.booking_uid;
3200
+ const isAddTimeProduct = line?.metadata?.product_add_schedule_time !== undefined && line?.metadata?.product_add_schedule_time !== null;
3201
+ return !(isAddTimeProduct && lineBookingUid !== undefined && lineBookingUid !== null && linkedBookingUidsToRemove.has(String(lineBookingUid)));
3202
+ });
3203
+ }
3204
+ await this.finalizeAfterProductsMutation(tempOrder);
3205
+ this.logInfo('removeProductsFromOrder success', {
3206
+ identities
3207
+ });
551
3208
  return tempOrder.products;
552
3209
  }
553
3210
  async removeProductFromOrder(identity) {
3211
+ return this.removeProductsFromOrder([identity]);
3212
+ }
3213
+
3214
+ /**
3215
+ * 仅清空购物车行(products / bookings),不重置整单。
3216
+ * 用于「仅清空商品」;客户、备注、支付等协议字段保持不变。
3217
+ */
3218
+ async clearOrderCartLines() {
554
3219
  const tempOrder = this.ensureTempOrder();
555
- tempOrder.products = tempOrder.products.filter(
556
- (item) => !(0, import_utils3.isIdentityMatch)(item, identity)
557
- );
3220
+ tempOrder.products = [];
3221
+ tempOrder.bookings = [];
3222
+ this.syncTempOrderHolderFromBookings(tempOrder);
3223
+ if (tempOrder._extend && typeof tempOrder._extend === 'object') {
3224
+ tempOrder._extend = {
3225
+ ...tempOrder._extend,
3226
+ productsByUid: {}
3227
+ };
3228
+ }
3229
+ await this.applyPromotion();
558
3230
  this.applyDiscount();
559
- await this.recalculateSummary({ createIfMissing: true });
3231
+ this.sanitizeTempOrderProducts(tempOrder);
3232
+ await this.recalculateSummary({
3233
+ createIfMissing: true
3234
+ });
560
3235
  this.persistTempOrder();
561
- return tempOrder.products;
3236
+ this.logInfo('clearOrderCartLines success', {});
3237
+ return tempOrder;
562
3238
  }
3239
+
563
3240
  // ─── TempOrder: 提交 ───
3241
+
3242
+ buildCurrentSubmitPayloadForLocalPrint(params) {
3243
+ const tempOrder = this.ensureTempOrder();
3244
+ this.persistTempOrder();
3245
+ const payload = (0, _utils.buildSubmitPayload)({
3246
+ tempOrder,
3247
+ cacheId: params?.cacheId ?? this.cacheId,
3248
+ platform: params?.platform || this.otherParams?.platform,
3249
+ businessCode: params?.businessCode ?? this.otherParams?.businessCode ?? this.otherParams?.business_code,
3250
+ channel: params?.channel,
3251
+ type: params?.type,
3252
+ summary: this.store.summary || (0, _utils.createEmptySummary)(),
3253
+ enhance: nextPayload => ({
3254
+ ...nextPayload,
3255
+ order_number: tempOrder.order_number ?? nextPayload.order_number ?? null,
3256
+ shop_order_number: tempOrder.shop_order_number ?? nextPayload.shop_order_number ?? null,
3257
+ shop_full_order_number: tempOrder.shop_full_order_number ?? nextPayload.shop_full_order_number ?? null,
3258
+ small_ticket_data_flag: 1
3259
+ })
3260
+ });
3261
+ payload.created_at = undefined;
3262
+ return payload;
3263
+ }
3264
+ async applyLocalPrintOrderNumbers(order) {
3265
+ const tempOrder = this.ensureTempOrder();
3266
+ if (!order || typeof order !== 'object') return tempOrder;
3267
+ const shopOrderNumber = order.shop_order_number;
3268
+ const shopFullOrderNumber = order.shop_full_order_number;
3269
+ if (shopOrderNumber !== undefined && shopOrderNumber !== null && shopOrderNumber !== '') {
3270
+ tempOrder.shop_order_number = String(shopOrderNumber);
3271
+ }
3272
+ if (shopFullOrderNumber !== undefined && shopFullOrderNumber !== null && shopFullOrderNumber !== '') {
3273
+ tempOrder.shop_full_order_number = String(shopFullOrderNumber);
3274
+ }
3275
+ this.persistTempOrder();
3276
+ await this.saveDraft();
3277
+ return tempOrder;
3278
+ }
3279
+
3280
+ /**
3281
+ * 提交当前 Sales tempOrder。
3282
+ *
3283
+ * smallTicketDataFlag 用于 B 端 checkout 小票数据返回。PaymentModal 在支付提交时
3284
+ * 应传 1,确保 /shop/order/sales/checkout 返回打印所需数据。
3285
+ */
564
3286
  async submitTempOrder(params) {
3287
+ return this.runSubmitTempOrder(params);
3288
+ }
3289
+ async submitTempOrderAsync(params) {
3290
+ return this.runSubmitTempOrder({
3291
+ ...params,
3292
+ syncMode: true
3293
+ });
3294
+ }
3295
+ async runSubmitTempOrder(params) {
565
3296
  const tempOrder = this.ensureTempOrder();
3297
+ const shouldConfirmPendingVoucherPayments = params?.confirmPendingVoucherPayments !== false;
3298
+ const hasPaymentOverride = params?.payments !== undefined;
3299
+ const preparedPaymentOverride = hasPaymentOverride ? this.prepareVoucherPaymentsForSubmit(params?.payments || [], shouldConfirmPendingVoucherPayments) : undefined;
3300
+ if (!hasPaymentOverride) {
3301
+ if (shouldConfirmPendingVoucherPayments) {
3302
+ this.confirmPendingVoucherPayments({
3303
+ persist: false,
3304
+ recalculateSummary: false
3305
+ });
3306
+ } else {
3307
+ this.discardPendingVoucherPayments({
3308
+ persist: false,
3309
+ recalculateSummary: false
3310
+ });
3311
+ }
3312
+ }
566
3313
  this.persistTempOrder();
567
- const effectiveCacheId = (params == null ? void 0 : params.cacheId) ?? this.cacheId;
568
- const payload = (0, import_utils.buildSubmitPayload)({
3314
+ const latestSummary = (await this.recalculateSummary({
3315
+ createIfMissing: true
3316
+ })) || this.store.summary || (0, _utils.createEmptySummary)();
3317
+ const effectiveCacheId = params?.cacheId ?? this.cacheId;
3318
+ const hasPaymentStatusOverride = params?.paymentStatus !== undefined;
3319
+ const hasSmallTicketDataFlagOverride = params?.smallTicketDataFlag !== undefined;
3320
+ const enhancePayload = hasPaymentOverride || hasPaymentStatusOverride || hasSmallTicketDataFlagOverride || params?.enhancePayload ? (payload, ctx) => {
3321
+ const nextPayload = {
3322
+ ...payload,
3323
+ ...(hasPaymentOverride ? {
3324
+ payments: preparedPaymentOverride || []
3325
+ } : {}),
3326
+ ...(hasPaymentStatusOverride && !shouldConfirmPendingVoucherPayments ? {
3327
+ payment_status: params?.paymentStatus
3328
+ } : {}),
3329
+ ...(hasSmallTicketDataFlagOverride ? {
3330
+ small_ticket_data_flag: params?.smallTicketDataFlag
3331
+ } : {})
3332
+ };
3333
+ const enhancedPayload = params?.enhancePayload ? params.enhancePayload(nextPayload, ctx) : nextPayload;
3334
+ return enhancedPayload;
3335
+ } : undefined;
3336
+ const submitSnapshot = this.getLastOrderSubmitSnapshot();
3337
+ const payload = (0, _utils.buildSubmitPayload)({
569
3338
  tempOrder,
570
3339
  cacheId: effectiveCacheId,
571
- platform: params == null ? void 0 : params.platform,
572
- businessCode: params == null ? void 0 : params.businessCode,
573
- channel: params == null ? void 0 : params.channel,
574
- type: params == null ? void 0 : params.type,
575
- enhance: params == null ? void 0 : params.enhancePayload
3340
+ platform: params?.platform || this.otherParams?.platform,
3341
+ businessCode: submitSnapshot.businessCode ?? params?.businessCode ?? this.otherParams?.businessCode ?? this.otherParams?.business_code,
3342
+ channel: params?.channel,
3343
+ type: submitSnapshot.type ?? params?.type,
3344
+ summary: latestSummary,
3345
+ request_unique_idempotency_token: params?.request_unique_idempotency_token,
3346
+ enhance: enhancePayload
576
3347
  });
3348
+ if (params?.syncMode) {
3349
+ payload.sync_mode = true;
3350
+ }
3351
+ // TODO: 前端生成的时间现在是有问题的,后面要统一优化
3352
+ if (!payload.created_at) {
3353
+ payload.created_at = (0, _dayjs.default)().format('YYYY-MM-DD HH:mm:ss');
3354
+ }
3355
+ await this.saveDraft();
577
3356
  let result;
578
- if (tempOrder.order_id) {
579
- const products = (0, import_utils.filterProductsForScanOrderMore)(payload.products);
580
- const moreResult = await this.scanOrderMore({
581
- query: {
582
- order_id: tempOrder.order_id,
583
- products,
584
- request_unique_idempotency_token: payload.request_unique_idempotency_token
585
- }
3357
+ try {
3358
+ this.logInfo('submitTempOrder calling sales checkout', {
3359
+ orderId: payload.order_id,
3360
+ externalSaleNumber: payload.external_sale_number,
3361
+ requestUniqueIdempotencyToken: payload.request_unique_idempotency_token,
3362
+ paymentStatus: payload.payment_status,
3363
+ paymentsCount: payload.payments?.length || 0,
3364
+ smallTicketDataFlag: payload.small_ticket_data_flag,
3365
+ syncMode: payload.sync_mode
586
3366
  });
587
- if (moreResult.code === 200) {
588
- result = {
589
- data: {
590
- order_id: tempOrder.order_id
591
- }
592
- };
593
- }
594
- } else {
595
- result = await this.submitScanOrder({
3367
+ result = await this.submitSalesOrder({
596
3368
  query: payload
597
3369
  });
3370
+ } catch (error) {
3371
+ const backendErrorResponse = this.extractSubmitErrorResponse(error);
3372
+ if (backendErrorResponse) {
3373
+ this.store.syncState = 'failed';
3374
+ this.logSubmitBackendRejected(backendErrorResponse, payload);
3375
+ return backendErrorResponse;
3376
+ }
3377
+ this.store.syncState = 'failed';
3378
+ this.logError('submitTempOrder failed', {
3379
+ error: error instanceof Error ? error.message : String(error),
3380
+ orderId: payload.order_id,
3381
+ externalSaleNumber: payload.external_sale_number,
3382
+ paymentStatus: payload.payment_status,
3383
+ paymentsCount: payload.payments?.length || 0
3384
+ });
3385
+ throw error;
3386
+ }
3387
+ if (this.isSubmitResponseRejected(result)) {
3388
+ this.store.syncState = 'failed';
3389
+ this.logSubmitBackendRejected(result, payload);
3390
+ return result;
3391
+ }
3392
+ const submittedOrderId = this.extractOrderIdFromSubmitResult(result);
3393
+ this.logInfo('runSubmitTempOrder: 提交成功', {
3394
+ submittedOrderId,
3395
+ result,
3396
+ tempOrder: {
3397
+ ...tempOrder,
3398
+ _extend: undefined
3399
+ }
3400
+ });
3401
+ if (submittedOrderId !== null && submittedOrderId !== undefined) {
3402
+ tempOrder.order_id = submittedOrderId;
3403
+ const resultRecord = result;
3404
+ await this.markLocalOrderSynced(submittedOrderId, resultRecord?.data || resultRecord || {});
3405
+ } else if (this.isLocalPendingSubmitResult(result)) {
3406
+ this.store.syncState = 'local';
3407
+ } else {
3408
+ this.store.syncState = 'submitted';
598
3409
  }
599
3410
  return result;
600
3411
  }
3412
+ async markLocalOrderSynced(orderId, remoteOrder) {
3413
+ const tempOrder = this.ensureTempOrder();
3414
+ tempOrder.order_id = orderId;
3415
+ this.applyRemoteOrderNumbersToTempOrder(tempOrder, remoteOrder);
3416
+ this.store.lastOrderInfo = remoteOrder;
3417
+ this.store.syncState = 'submitted';
3418
+ this.persistTempOrder();
3419
+ await this.saveDraft();
3420
+ }
3421
+ applyRemoteOrderNumbersToTempOrder(tempOrder, remoteOrder) {
3422
+ const applyOrderNumber = key => {
3423
+ const value = remoteOrder?.[key];
3424
+ if (value !== undefined && value !== null && value !== '') {
3425
+ tempOrder[key] = String(value);
3426
+ }
3427
+ };
3428
+ applyOrderNumber('order_number');
3429
+ applyOrderNumber('shop_order_number');
3430
+ applyOrderNumber('shop_full_order_number');
3431
+ }
3432
+ isSubmitResponseRejected(response) {
3433
+ const isErrorCode = response?.code !== undefined && response.code !== 200 && response.code !== '200';
3434
+ const isErrorStatus = response?.status === false || typeof response?.status === 'number' && response.status >= 400;
3435
+ const isErrorSuccess = response?.success === false;
3436
+ return isErrorCode || isErrorStatus || isErrorSuccess;
3437
+ }
3438
+ isLocalPendingSubmitResult(response) {
3439
+ const data = response?.data && typeof response.data === 'object' ? response.data : response;
3440
+ return Number(data?.need_sync) === 1;
3441
+ }
3442
+ isSubmitErrorResponse(response) {
3443
+ if (!response || typeof response !== 'object') return false;
3444
+ return this.isSubmitResponseRejected(response) || response.message !== undefined || response.msg !== undefined || response.error !== undefined;
3445
+ }
3446
+ extractSubmitErrorResponse(error) {
3447
+ const candidates = [error?.response?.data, error?.data, error?.response?.body, error?.body];
3448
+ return candidates.find(candidate => this.isSubmitErrorResponse(candidate)) || null;
3449
+ }
3450
+ logSubmitBackendRejected(response, payload) {
3451
+ this.logError('submitTempOrder backend rejected', {
3452
+ response,
3453
+ payload: {
3454
+ order_id: payload.order_id,
3455
+ external_sale_number: payload.external_sale_number,
3456
+ payment_status: payload.payment_status,
3457
+ paymentsCount: payload.payments?.length || 0
3458
+ }
3459
+ });
3460
+ }
3461
+ generateIdempotencyToken() {
3462
+ const tempOrder = this.ensureTempOrder();
3463
+ const token = this.buildPaymentSyncIdempotencyToken(tempOrder, tempOrder.payments);
3464
+ return `${token}_${Date.now()}`;
3465
+ }
3466
+ async syncPaymentsToOrder(params) {
3467
+ const tempOrder = this.ensureTempOrder();
3468
+ const mappedPayments = (0, _utils.mapPaymentItemsToOrderPayments)(params.payments || []);
3469
+ const shouldConfirmPendingVoucherPayments = params.confirmPendingVoucherPayments ?? params.submitWhenPaid === true;
3470
+ const effectivePayments = shouldConfirmPendingVoucherPayments ? this.confirmPendingVoucherPaymentsInList(mappedPayments) : params.confirmPendingVoucherPayments === false ? this.discardPendingVoucherPaymentsFromList(mappedPayments) : mappedPayments;
3471
+ const completion = this.getPaymentCompletion(effectivePayments);
3472
+ const orderPaidPaymentTotal = this.calculateOrderPaidPaymentTotal(effectivePayments);
3473
+ const paymentStatus = completion.isOrderFullyPaid ? params.paymentStatus || 'paid' : orderPaidPaymentTotal.gt(0) ? 'partially_paid' : 'payment_processing';
3474
+ tempOrder.payments = effectivePayments;
3475
+ tempOrder.payment_status = paymentStatus;
3476
+ this.persistTempOrder();
3477
+ await this.saveDraft();
3478
+ if (!params.submitWhenPaid) {
3479
+ return completion;
3480
+ }
3481
+ if (this.store.syncState === 'submitting') {
3482
+ return completion;
3483
+ }
3484
+ this.store.syncState = 'submitting';
3485
+ const paymentSyncIdempotencyToken = this.buildPaymentSyncIdempotencyToken(tempOrder, effectivePayments);
3486
+ const submitParams = {
3487
+ payments: effectivePayments,
3488
+ paymentStatus,
3489
+ smallTicketDataFlag: params.smallTicketDataFlag,
3490
+ businessCode: params.businessCode,
3491
+ channel: params.channel,
3492
+ confirmPendingVoucherPayments: true,
3493
+ enhancePayload: payload => {
3494
+ const nextPayload = {
3495
+ ...payload,
3496
+ request_unique_idempotency_token: paymentSyncIdempotencyToken
3497
+ };
3498
+ return nextPayload;
3499
+ }
3500
+ };
3501
+ const submitResult = params.checkoutSyncTaskEnabled === false ? await this.submitTempOrderAsync(submitParams) : await this.submitTempOrder(submitParams);
3502
+ return {
3503
+ ...completion,
3504
+ submitResult
3505
+ };
3506
+ }
601
3507
  createOrder(params) {
602
- var _a;
603
3508
  const order = {
604
- type: (params == null ? void 0 : params.type) || "appointment_booking",
3509
+ type: params?.type || 'appointment_booking',
605
3510
  // 要从外面拿,virtual
606
- platform: (params == null ? void 0 : params.platform) === "pc" ? "PC" : "H5",
607
- sales_channel: "my_pisel",
608
- order_sales_channel: "online_store",
3511
+ platform: params?.platform === 'pc' ? 'PC' : 'H5',
3512
+ sales_channel: 'my_pisel',
3513
+ order_sales_channel: 'online_store',
609
3514
  bookings: [],
610
- shop_note: "",
611
- schedule_date: "",
3515
+ shop_note: '',
3516
+ schedule_date: '',
612
3517
  is_deposit: 0,
613
3518
  relation_products: [],
614
3519
  relation_forms: [],
615
- business_code: params == null ? void 0 : params.business_code,
616
- ...(params == null ? void 0 : params.extraData) || {}
3520
+ business_code: params?.business_code,
3521
+ ...(params?.extraData || {})
617
3522
  };
618
- let is_deposit = 0;
3523
+ let is_deposit = 0; // 是否存在定金,0 不存在,1 存在
619
3524
  if (params.cartItems.length > 0) {
620
- params.cartItems.forEach((item) => {
621
- var _a2;
3525
+ params.cartItems.forEach(item => {
622
3526
  if (!item._origin.duration) {
623
- const { duration, durationType } = (0, import_utils.generateDuration)(item);
3527
+ const {
3528
+ duration,
3529
+ durationType
3530
+ } = (0, _utils.generateDuration)(item);
624
3531
  item._origin.duration = duration;
625
3532
  item._origin.sub_type = durationType;
626
3533
  }
627
- const discountList = (0, import_utils.getAllDiscountList)(item);
3534
+ item._origin.sub_type = (0, _utils.normalizeBookingSubType)(item._origin);
3535
+ const discountList = (0, _utils.getAllDiscountList)(item);
628
3536
  item._origin.product.discount_list = discountList;
629
- if ((_a2 = params == null ? void 0 : params.extraData) == null ? void 0 : _a2.is_add_holder_info) {
630
- (0, import_utils.ensureCartItemOriginHolder)(item, this.window);
3537
+
3538
+ // 为预约补齐 holder 信息
3539
+ if (params?.extraData?.is_add_holder_info) {
3540
+ (0, _utils.ensureCartItemOriginHolder)(item, this.window);
631
3541
  }
632
- if ((0, import_utils2.isNormalProduct)(item._origin)) {
3542
+
3543
+ // 购物车是否为普通商品
3544
+ if ((0, _utils2.isNormalProduct)(item._origin)) {
633
3545
  order.relation_products.push(item._origin.product);
634
3546
  const relationForms = item._origin.relation_forms || [];
635
3547
  order.relation_forms.push(...relationForms);
636
3548
  delete item._origin.relation_forms;
637
3549
  } else {
638
- const relationForms = (0, import_utils.mergeRelationForms)(
639
- item._origin.relation_forms || []
640
- );
3550
+ // 合并关联表单
3551
+ const relationForms = (0, _utils.mergeRelationForms)(item._origin.relation_forms || []);
641
3552
  item._origin.relation_forms = relationForms;
642
3553
  order.bookings.push(item._origin);
643
3554
  }
644
- if (item == null ? void 0 : item.deposit) {
3555
+ if (item?.deposit) {
645
3556
  is_deposit = 1;
646
3557
  }
647
3558
  });
648
- if (order.type === "appointment_booking") {
649
- const firstAppointmentCartItem = (_a = params.cartItems.filter(
650
- (n) => !(0, import_utils2.isNormalProduct)(n._productOrigin)
651
- )) == null ? void 0 : _a[0];
3559
+ if (order.type === 'appointment_booking') {
3560
+ const firstAppointmentCartItem = params.cartItems.filter(n => !(0, _utils2.isNormalProduct)(n._productOrigin))?.[0];
652
3561
  if (firstAppointmentCartItem) {
653
- order.schedule_date = firstAppointmentCartItem.start_date + " " + firstAppointmentCartItem.start_time + ":00";
3562
+ order.schedule_date = firstAppointmentCartItem.start_date + ' ' + firstAppointmentCartItem.start_time + ':00';
654
3563
  }
655
3564
  }
656
3565
  order.is_deposit = is_deposit;
@@ -658,14 +3567,17 @@ var OrderModule = class extends import_BaseModule.BaseModule {
658
3567
  return order;
659
3568
  }
660
3569
  checkBeforeSubmitOrder(params) {
661
- var _a;
662
- this.logInfo("checkBeforeSubmitOrder called", {
663
- cartItemsCount: ((_a = params.cartItems) == null ? void 0 : _a.length) || 0,
3570
+ this.logInfo('checkBeforeSubmitOrder called', {
3571
+ cartItemsCount: params.cartItems?.length || 0,
664
3572
  type: params.type
665
3573
  });
666
- const { cartItems, type } = params;
667
- if (type === "holder") {
668
- const hasNoHolderId = cartItems.some((item) => !item.holder_id);
3574
+ const {
3575
+ cartItems,
3576
+ type
3577
+ } = params;
3578
+ if (type === 'holder') {
3579
+ // 判断cartItems中是否有不存在holder_id的商品,如果有则返回false
3580
+ const hasNoHolderId = cartItems.some(item => !item.holder_id);
669
3581
  if (hasNoHolderId) {
670
3582
  return false;
671
3583
  }
@@ -673,170 +3585,603 @@ var OrderModule = class extends import_BaseModule.BaseModule {
673
3585
  return true;
674
3586
  }
675
3587
  async submitOrder(order) {
676
- var _a, _b, _c;
677
- this.logInfo("submitOrder called", {
3588
+ this.logInfo('submitOrder called', {
678
3589
  url: order.url,
679
3590
  orderType: order.query.type,
680
3591
  platform: order.query.platform,
681
- cartItemsCount: ((_a = order.query.cartItems) == null ? void 0 : _a.length) || 0
3592
+ cartItemsCount: order.query.cartItems?.length || 0
682
3593
  });
683
- const { url, query } = order;
684
- const fetchUrl = url || "/order/appointment";
3594
+ const {
3595
+ url,
3596
+ query
3597
+ } = order;
3598
+ const fetchUrl = url || '/order/appointment';
685
3599
  const params = this.createOrder(query);
686
- this.logInfo("Calling backend order API", {
3600
+ this.logInfo('Calling backend order API', {
687
3601
  url: fetchUrl,
688
3602
  orderType: params.type,
689
3603
  platform: params.platform,
690
3604
  isDeposit: params.is_deposit,
691
- bookingsCount: ((_b = params.bookings) == null ? void 0 : _b.length) || 0,
692
- relationProductsCount: ((_c = params.relation_products) == null ? void 0 : _c.length) || 0,
3605
+ bookingsCount: params.bookings?.length || 0,
3606
+ relationProductsCount: params.relation_products?.length || 0,
693
3607
  scheduleDate: params.schedule_date
694
3608
  });
695
3609
  return this.request.post(fetchUrl, params);
696
3610
  }
3611
+ async cancelOrder(params) {
3612
+ if (!Array.isArray(params.order_ids) || params.order_ids.length === 0) {
3613
+ throw new Error('取消订单失败:order_ids 不能为空');
3614
+ }
3615
+ const payload = {
3616
+ order_ids: params.order_ids,
3617
+ status: 'cancelled',
3618
+ change_schedule_event_appointment_status: 1,
3619
+ is_void: 1
3620
+ };
3621
+ this.logInfo('cancelOrder called', {
3622
+ url: '/order/update-status',
3623
+ method: 'PUT',
3624
+ orderIdsCount: params.order_ids.length
3625
+ });
3626
+ return this.request.put('/order/update-status', payload, {
3627
+ isShopApi: true
3628
+ });
3629
+ }
3630
+ async changeBookingStatus(params) {
3631
+ if (!params.booking_id) {
3632
+ throw new Error('变更预约状态失败:booking_id 不能为空');
3633
+ }
3634
+ if (!params.appointment_status) {
3635
+ throw new Error('变更预约状态失败:appointment_status 不能为空');
3636
+ }
3637
+ const url = `/schedule/booking/appointment-status/${params.booking_id}`;
3638
+ const payload = {
3639
+ appointment_status: params.appointment_status
3640
+ };
3641
+ this.logInfo('changeBookingStatus called', {
3642
+ url,
3643
+ method: 'PUT',
3644
+ bookingId: params.booking_id,
3645
+ appointmentStatus: params.appointment_status
3646
+ });
3647
+ return this.request.put(url, payload, {
3648
+ isShopApi: true
3649
+ });
3650
+ }
3651
+
697
3652
  /**
698
3653
  * Checkout 专用:创建订单到后端
699
- *
3654
+ *
700
3655
  * 专门为 Checkout 模块设计的订单创建方法,
701
- * 直接调用 /order/checkout 接口创建订单
3656
+ * 通过 OS Server 调用 checkout 接口创建订单
702
3657
  * 接收已经处理好的订单数据,无需再处理购物车
703
- *
3658
+ *
704
3659
  * @param params Checkout 订单参数(已处理的订单数据)
705
3660
  * @returns 后端返回的订单数据(包含订单ID等)
706
3661
  */
707
3662
  async createOrderByCheckout(params) {
708
- var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q, _r;
709
- const onlineStorePaymentCodeList = ["WXPAY", "WECHAT", "ALIPAY", "APPLE_PAY", "CREDIT_CARD_3DS", "CREDIT_CARD_TOKEN", "GOOGLE_PAY", "GOOGLE_PAY_3DS", "CREDIT_CARD"];
710
- params.payments = (_a = params.payments) == null ? void 0 : _a.filter((p) => !onlineStorePaymentCodeList.includes(p.code));
711
- this.logInfo("createOrderByCheckout called", {
3663
+ // 过滤掉由在线店铺下单的 payment 支付数据
3664
+ const onlineStorePaymentCodeList = ['WXPAY', 'WECHAT', 'ALIPAY', 'APPLE_PAY', 'CREDIT_CARD_3DS', 'CREDIT_CARD_TOKEN', 'GOOGLE_PAY', 'GOOGLE_PAY_3DS', 'CREDIT_CARD'];
3665
+ params.payments = params.payments?.filter(p => !onlineStorePaymentCodeList.includes(p.code));
3666
+ this.logInfo('createOrderByCheckout called', {
712
3667
  type: params.type,
713
3668
  platform: params.platform,
714
3669
  is_deposit: params.is_deposit,
715
3670
  customer_id: params.customer_id,
716
- bookingsCount: ((_b = params.bookings) == null ? void 0 : _b.length) || 0,
717
- relationProductsCount: ((_c = params.relation_products) == null ? void 0 : _c.length) || 0,
718
- paymentsCount: ((_d = params.payments) == null ? void 0 : _d.length) || 0,
3671
+ bookingsCount: params.bookings?.length || 0,
3672
+ relationProductsCount: params.relation_products?.length || 0,
3673
+ paymentsCount: params.payments?.length || 0,
719
3674
  depositAmount: params.deposit_amount,
720
3675
  productTaxFee: params.product_tax_fee,
721
3676
  note: params.note,
722
3677
  scheduleDate: params.schedule_date,
723
3678
  hasOrderId: !!params.order_id,
724
3679
  orderIdIncluded: params.order_id,
725
- paymentMethods: ((_e = params.payments) == null ? void 0 : _e.map((p) => p.code)) || []
726
- });
727
- console.log("[Order] createOrderByCheckout 开始创建订单:", {
728
- type: params.type,
729
- platform: params.platform,
730
- is_deposit: params.is_deposit,
731
- customer_id: params.customer_id,
732
- bookingsCount: ((_f = params.bookings) == null ? void 0 : _f.length) || 0,
733
- relationProductsCount: ((_g = params.relation_products) == null ? void 0 : _g.length) || 0,
734
- paymentsCount: ((_h = params.payments) == null ? void 0 : _h.length) || 0
3680
+ paymentMethods: params.payments?.map(p => p.code) || []
735
3681
  });
736
3682
  try {
3683
+ // 构建订单数据,设置默认值并允许 params 覆盖
737
3684
  const orderData = {
738
- sales_channel: "my_pisel",
739
- order_sales_channel: "online_store",
740
- shop_note: "",
741
- schedule_date: "",
3685
+ sales_channel: 'my_pisel',
3686
+ order_sales_channel: 'online_store',
3687
+ shop_note: '',
3688
+ schedule_date: '',
742
3689
  is_deposit: 0,
743
3690
  bookings: [],
744
3691
  relation_products: [],
745
3692
  relation_forms: [],
746
3693
  payments: [],
747
3694
  is_full_overwrite_flag: 1,
748
- ...params
749
- // 使用传入的参数覆盖默认值
3695
+ ...params // 使用传入的参数覆盖默认值
750
3696
  };
751
- if ((_i = orderData.payments) == null ? void 0 : _i.length) {
3697
+ if (orderData.payments?.length) {
752
3698
  orderData.small_ticket_data_flag = 1;
753
3699
  }
3700
+
3701
+ // 兜底策略,如果是第一次下单,没有 order_id,且没有 schedule_date,则设置为当前时间
754
3702
  if (!params.order_id && !orderData.schedule_date) {
755
- orderData.schedule_date = (0, import_dayjs.default)().format("YYYY-MM-DD HH:mm:ss");
3703
+ orderData.schedule_date = (0, _dayjs.default)().format('YYYY-MM-DD HH:mm:ss');
756
3704
  }
757
- console.log("[Order] 调用后端接口创建订单:", {
758
- url: "/order/checkout",
759
- orderType: orderData.type,
760
- platform: orderData.platform,
761
- isDeposit: orderData.is_deposit,
762
- customerId: orderData.customer_id,
763
- bookingsCount: ((_j = orderData.bookings) == null ? void 0 : _j.length) || 0,
764
- relationProductsCount: ((_k = orderData.relation_products) == null ? void 0 : _k.length) || 0,
765
- paymentsCount: ((_l = orderData.payments) == null ? void 0 : _l.length) || 0,
766
- paymentsMethods: ((_m = orderData.payments) == null ? void 0 : _m.map((p) => p.code)) || []
767
- });
768
- this.logInfo("Calling backend checkout API", {
769
- url: "/order/checkout",
3705
+ // 记录接口调用详情
3706
+ this.logInfo('Calling backend checkout API', {
3707
+ url: '/shop/order/checkout',
770
3708
  orderType: orderData.type,
771
3709
  platform: orderData.platform,
772
3710
  isDeposit: orderData.is_deposit,
773
3711
  customerId: orderData.customer_id,
774
3712
  depositAmount: orderData.deposit_amount,
775
- bookingsCount: ((_n = orderData.bookings) == null ? void 0 : _n.length) || 0,
776
- relationProductsCount: ((_o = orderData.relation_products) == null ? void 0 : _o.length) || 0,
777
- paymentsCount: ((_p = orderData.payments) == null ? void 0 : _p.length) || 0,
778
- paymentMethods: ((_q = orderData.payments) == null ? void 0 : _q.map((p) => ({
3713
+ bookingsCount: orderData.bookings?.length || 0,
3714
+ relationProductsCount: orderData.relation_products?.length || 0,
3715
+ paymentsCount: orderData.payments?.length || 0,
3716
+ paymentMethods: orderData.payments?.map(p => ({
779
3717
  code: p.code,
780
3718
  amount: p.amount,
781
3719
  type: p.type,
782
3720
  hasVoucherId: !!p.voucher_id,
783
3721
  orderPaymentType: p.order_payment_type
784
- }))) || [],
3722
+ })) || [],
785
3723
  productTaxFee: orderData.product_tax_fee,
786
3724
  note: orderData.note,
787
3725
  scheduleDate: orderData.schedule_date,
788
3726
  hasOrderId: !!orderData.order_id,
789
3727
  smallTicketDataFlag: orderData.small_ticket_data_flag
790
3728
  });
791
- const response = await this.request.post("/order/checkout", orderData);
792
- this.logInfo("Order API called successfully", {
793
- response
3729
+ const response = await this.request.post('/order/checkout', orderData, {
3730
+ osServer: true,
3731
+ customToast: () => {}
794
3732
  });
795
- console.log("[Order] 订单创建成功,后端响应:", {
796
- success: !!response,
797
- hasOrderId: !!(((_r = response == null ? void 0 : response.data) == null ? void 0 : _r.order_id) || (response == null ? void 0 : response.order_id))
3733
+ this.logInfo('Order API called successfully', {
3734
+ response
798
3735
  });
799
3736
  return response;
800
3737
  } catch (error) {
801
- console.error("[Order] createOrderByCheckout 创建订单失败:", error);
802
- this.logInfo("Order API called failed", {
3738
+ console.error('[Order] createOrderByCheckout 创建订单失败:', error);
3739
+ this.logInfo('Order API called failed', {
803
3740
  error: error instanceof Error ? error.message : String(error)
804
3741
  });
805
3742
  throw error;
806
3743
  }
807
3744
  }
808
- async submitScanOrder(params) {
809
- const { url, query } = params;
810
- const fetchUrl = url || "/order/sales/checkout";
811
- return this.request.post(fetchUrl, query, { customToast: () => {
812
- } });
3745
+ async submitSalesOrder(params) {
3746
+ const {
3747
+ url,
3748
+ query
3749
+ } = params;
3750
+ const fetchUrl = url || '/order/sales/checkout';
3751
+ return this.request.post(fetchUrl, query, {
3752
+ osServer: true,
3753
+ customToast: () => {}
3754
+ });
813
3755
  }
814
- async scanOrderMore(params) {
815
- const { url, query } = params;
816
- const fetchUrl = url || `/order/order/product/${query.order_id}`;
817
- const requestBody = {
818
- products: query.products,
819
- request_unique_idempotency_token: query.request_unique_idempotency_token
820
- };
821
- return this.request.put(fetchUrl, requestBody, { customToast: () => {
822
- } });
3756
+
3757
+ /**
3758
+ * 发送支付链接邮件。
3759
+ *
3760
+ * 调用前必须已经有远端 order_id。BaseSales 会在需要时先提交本地订单,
3761
+ * OrderModule 只负责校验 order_ids 并调用 /order/batch-email。
3762
+ */
3763
+ async sendCustomerPayLink(params) {
3764
+ const orderIds = params.order_ids || params.orderIds || [];
3765
+ if (!orderIds.length) {
3766
+ throw new Error('发送支付链接前必须先提交本地订单并取得订单 ID');
3767
+ }
3768
+ if (!params.emails?.length) {
3769
+ throw new Error('发送支付链接需要至少一个邮箱');
3770
+ }
3771
+ return this.request.post('/order/batch-email', {
3772
+ order_ids: orderIds,
3773
+ notify_action: params.notify_action || 'order_payment_reminder',
3774
+ emails: params.emails
3775
+ });
823
3776
  }
824
- // TODO 获取详情的接口
825
- async getOrderInfoByRemote(order_id) {
826
- const res = await this.request.get(`/order/sales/${order_id}`, {
827
- with: ["products", "scheduleEvents"]
3777
+ async getSalesOrderByLookup(params) {
3778
+ const lookup = params.lookup === undefined || params.lookup === null ? '' : String(params.lookup).trim();
3779
+ if (!lookup) {
3780
+ throw new Error('加载销售详情需要 lookup');
3781
+ }
3782
+ // 如果 lookup 是external_sale_number,则forceRemote强制为 false
3783
+ if (lookup.startsWith('LOCAL_')) {
3784
+ params.forceRemote = false;
3785
+ }
3786
+ const res = await this.request.get(`/order/sales/${encodeURIComponent(lookup)}`, {
3787
+ with: ['products', 'bookings', 'payments', 'customer', 'summary', 'contacts_info'],
3788
+ ...(params.forceRemote ? {
3789
+ forceRemote: true
3790
+ } : {})
3791
+ }, {
3792
+ osServer: true
828
3793
  });
829
- if (res.code === 200 && this.store.tempOrder) {
830
- this.store.tempOrder.lastOrderInfo = res.data;
3794
+ if (res.code === 200) {
3795
+ this.store.lastOrderInfo = res.data;
831
3796
  }
832
3797
  return res;
833
3798
  }
3799
+
3800
+ /**
3801
+ * 把 sqlite / 后端返回的订单记录直接恢复为 tempOrder。
3802
+ *
3803
+ * 数据源应尽量返回 tempOrder 所需结构;这里仅做 SDK 运行必须字段兜底,
3804
+ * 例如数组字段、商品行唯一值、价格 metadata、summary 派生状态和 _extend。
3805
+ */
3806
+ async hydrateTempOrderFromRecord(record, options) {
3807
+ const sourceRaw = record && typeof record === 'object' && record.data && record.order_id == null ? record.data : record || {};
3808
+ const identityReadySource = this.seedAnonymousBookingUidsFromProducts(sourceRaw);
3809
+ const normalizedCollections = (0, _orderCollectionIdentity.normalizeOrderCollections)(identityReadySource);
3810
+ const raw = normalizedCollections.order;
3811
+ const hasIdentityChanges = Object.values(normalizedCollections.stats).some(stats => stats.backfilledUidCount > 0 || stats.collapsedDuplicateCount > 0 || stats.uidConflictCount > 0 || stats.anonymousRowCount > 0);
3812
+ if (hasIdentityChanges) {
3813
+ const identityLogMetadata = {
3814
+ collections: normalizedCollections.stats,
3815
+ uidRemapCount: normalizedCollections.uidRemaps.length
3816
+ };
3817
+ const hasIdentityWarnings = Object.values(normalizedCollections.stats).some(stats => stats.uidConflictCount > 0 || stats.anonymousRowCount > 0);
3818
+ if (hasIdentityWarnings) {
3819
+ this.logWarning('Normalized hydrated order collection identities', identityLogMetadata);
3820
+ } else {
3821
+ this.logInfo('Normalized hydrated order collection identities', identityLogMetadata);
3822
+ }
3823
+ }
3824
+ const nextTempOrder = this.normalizeTempOrderForRuntime(raw, {
3825
+ makeEditMark: true
3826
+ });
3827
+ const rawSummary = raw.summary && typeof raw.summary === 'object' ? raw.summary : {};
3828
+ const summarySurcharges = Array.isArray(rawSummary.surcharges) ? rawSummary.surcharges.map(s => ({
3829
+ ...(s || {})
3830
+ })) : (0, _lodashEs.cloneDeep)(nextTempOrder.surcharges || []);
3831
+ this.store.tempOrder = nextTempOrder;
3832
+ this.store.summary = {
3833
+ ...(0, _utils.createEmptySummary)(),
3834
+ ...rawSummary,
3835
+ surcharges: summarySurcharges
3836
+ };
3837
+ nextTempOrder._extend = {
3838
+ ...(nextTempOrder._extend || {}),
3839
+ originalSalesSnapshot: {
3840
+ summary: (0, _lodashEs.cloneDeep)(this.store.summary),
3841
+ products: (0, _lodashEs.cloneDeep)(nextTempOrder.products || []),
3842
+ bookings: (0, _lodashEs.cloneDeep)(nextTempOrder.bookings || []),
3843
+ payments: (0, _lodashEs.cloneDeep)(nextTempOrder.payments || []),
3844
+ surcharges: (0, _lodashEs.cloneDeep)(nextTempOrder.surcharges || []),
3845
+ shop_discount: nextTempOrder.shop_discount || '0.00',
3846
+ productFingerprint: this.buildOrderProductsFingerprint(nextTempOrder.products || [])
3847
+ }
3848
+ };
3849
+ // lastOrderInfo carries the exact backend snapshot used by deposit/type recovery.
3850
+ // Runtime collection repair lives in tempOrder/originalSalesSnapshot and must not
3851
+ // rewrite this protocol snapshot as a side effect of hydration.
3852
+ this.store.lastOrderInfo = sourceRaw.lastOrderInfo || sourceRaw;
3853
+ const hydratedEditDiscounts = this.collectHydratedEditDiscounts(nextTempOrder.products);
3854
+ const currentDiscounts = typeof this.store.discount?.getDiscountList === 'function' ? this.store.discount.getDiscountList() || [] : [];
3855
+ const currentScanDiscounts = currentDiscounts.filter(discount => discount?.isScan);
3856
+ const hydratedDiscountIds = new Set(hydratedEditDiscounts.map(discount => discount?.id));
3857
+ const retainedScanDiscounts = currentScanDiscounts.filter(discount => !hydratedDiscountIds.has(discount?.id));
3858
+ const nextDiscounts = [...hydratedEditDiscounts, ...retainedScanDiscounts];
3859
+ const shouldSkipEmptyDiscountSync = nextDiscounts.length === 0 && currentDiscounts.length === 0;
3860
+ if (!shouldSkipEmptyDiscountSync) {
3861
+ OrderModule.populateSavedAmounts(nextTempOrder.products, nextDiscounts);
3862
+ await this.store.discount?.setOriginalDiscountList(nextDiscounts);
3863
+ await this.store.discount?.setDiscountList(nextDiscounts);
3864
+ }
3865
+ if (options?.recalcOnHydrate) {
3866
+ await this.recalculateSummary({
3867
+ createIfMissing: false
3868
+ });
3869
+ }
3870
+ this.persistTempOrder();
3871
+ return nextTempOrder;
3872
+ }
3873
+
3874
+ /**
3875
+ * 兼容后端详情将 holder 放在 metadata.holder 的历史形态,统一补到运行时展示路径。
3876
+ *
3877
+ * @example
3878
+ * this.normalizeHydratedBookingHolder({ metadata: { holder: { form_record: [1] } } });
3879
+ */
3880
+ normalizeHydratedBookingHolder(booking) {
3881
+ const next = {
3882
+ ...(booking || {})
3883
+ };
3884
+ const metadata = next.metadata && typeof next.metadata === 'object' && !Array.isArray(next.metadata) ? {
3885
+ ...next.metadata
3886
+ } : {};
3887
+ const metadataHolder = metadata.holder && typeof metadata.holder === 'object' && !Array.isArray(metadata.holder) ? metadata.holder : null;
3888
+
3889
+ // 编辑详情接口可能只返回 metadata.holder;UI 与提交链路统一读取 booking.holder。
3890
+ if (!next.holder && metadataHolder) {
3891
+ next.holder = {
3892
+ ...metadataHolder
3893
+ };
3894
+ }
3895
+ const holder = next.holder && typeof next.holder === 'object' && !Array.isArray(next.holder) ? next.holder : null;
3896
+ const holderRecords = Array.isArray(holder?.form_record) ? holder.form_record.filter(id => id !== undefined && id !== null && id !== '') : [];
3897
+ const hasMetadataHolderId = metadata.holder_id !== undefined && metadata.holder_id !== null && metadata.holder_id !== '' && (!Array.isArray(metadata.holder_id) || metadata.holder_id.length > 0);
3898
+
3899
+ // 卡片和编辑抽屉会从 holder_id 进入展示/表单回显,缺失时由 holder.form_record 补齐。
3900
+ if (!hasMetadataHolderId && holderRecords.length > 0) {
3901
+ metadata.holder_id = holderRecords;
3902
+ }
3903
+ if (Object.keys(metadata).length > 0) {
3904
+ next.metadata = metadata;
3905
+ }
3906
+ return next;
3907
+ }
3908
+
3909
+ /**
3910
+ * 匿名 booking 没有服务端行 ID 时,商品侧已有的 booking_uid 就是唯一可用的强引用。
3911
+ * 在统一规范化生成 UUID 前先复用它;已落库 booking 仍按 schedule_event_id 派生稳定 UID。
3912
+ */
3913
+ seedAnonymousBookingUidsFromProducts(raw) {
3914
+ if (!raw || typeof raw !== 'object') return {};
3915
+ if (!Array.isArray(raw.bookings) || !Array.isArray(raw.products)) return raw;
3916
+ const bookingUidsByProductUid = new Map();
3917
+ for (const product of raw.products) {
3918
+ const productUid = (0, _orderCollectionIdentity.getOrderCollectionUid)('product', product);
3919
+ const bookingUid = product?.booking_uid || product?.metadata?.booking_uid;
3920
+ if (!productUid || bookingUid === undefined || bookingUid === null || bookingUid === '') {
3921
+ continue;
3922
+ }
3923
+ const candidates = bookingUidsByProductUid.get(productUid) || new Set();
3924
+ candidates.add(String(bookingUid));
3925
+ bookingUidsByProductUid.set(productUid, candidates);
3926
+ }
3927
+ if (bookingUidsByProductUid.size === 0) return raw;
3928
+ const anonymousBookingCountByProductUid = new Map();
3929
+ for (const booking of raw.bookings) {
3930
+ if ((0, _orderCollectionIdentity.getOrderCollectionPersistentId)('booking', booking) || (0, _orderCollectionIdentity.getOrderCollectionUid)('booking', booking)) {
3931
+ continue;
3932
+ }
3933
+ const productUid = booking?.product_uid || booking?.metadata?.product_uid;
3934
+ if (productUid === undefined || productUid === null || productUid === '') continue;
3935
+ const key = String(productUid);
3936
+ anonymousBookingCountByProductUid.set(key, (anonymousBookingCountByProductUid.get(key) || 0) + 1);
3937
+ }
3938
+ return {
3939
+ ...raw,
3940
+ bookings: raw.bookings.map(booking => {
3941
+ if ((0, _orderCollectionIdentity.getOrderCollectionPersistentId)('booking', booking) || (0, _orderCollectionIdentity.getOrderCollectionUid)('booking', booking)) {
3942
+ return booking;
3943
+ }
3944
+ const productUid = booking?.product_uid || booking?.metadata?.product_uid;
3945
+ if (productUid === undefined || productUid === null || productUid === '') {
3946
+ return booking;
3947
+ }
3948
+ const productUidKey = String(productUid);
3949
+ // 多条匿名 booking 共用同一商品引用时身份仍然不明确,不能借商品字段猜测合并。
3950
+ if (anonymousBookingCountByProductUid.get(productUidKey) !== 1) {
3951
+ return booking;
3952
+ }
3953
+ const bookingUidCandidates = bookingUidsByProductUid.get(productUidKey);
3954
+ const bookingUid = bookingUidCandidates?.size === 1 ? [...bookingUidCandidates][0] : null;
3955
+ return bookingUid ? (0, _orderCollectionIdentity.setOrderCollectionUid)('booking', booking, bookingUid) : booking;
3956
+ })
3957
+ };
3958
+ }
3959
+ normalizeTempOrderForRuntime(raw, options) {
3960
+ const nextTempOrder = {
3961
+ ...this.createDefaultTempOrderInstance(),
3962
+ ...(raw || {})
3963
+ };
3964
+ // ES 列表详情使用 id 表示订单主键;OS 详情态统一依赖 order_id 驱动操作按钮。
3965
+ if ((nextTempOrder.order_id === undefined || nextTempOrder.order_id === null) && raw?.id !== undefined && raw?.id !== null) {
3966
+ nextTempOrder.order_id = raw.id;
3967
+ }
3968
+ delete nextTempOrder.summary;
3969
+ delete nextTempOrder.lastOrderInfo;
3970
+ delete nextTempOrder.discount_list;
3971
+ delete nextTempOrder.shop_id;
3972
+ delete nextTempOrder.create_date;
3973
+ delete nextTempOrder.total_amount;
3974
+ delete nextTempOrder.expect_amount;
3975
+ delete nextTempOrder.paid_amount;
3976
+ delete nextTempOrder.shop_note;
3977
+ delete nextTempOrder.start_time;
3978
+ delete nextTempOrder.tax_fee;
3979
+ delete nextTempOrder.total_refund_amount;
3980
+ delete nextTempOrder.holder_id;
3981
+ nextTempOrder.metadata = raw.metadata && typeof raw.metadata === 'object' ? {
3982
+ ...raw.metadata
3983
+ } : {};
3984
+ nextTempOrder.holder = raw.holder && typeof raw.holder === 'object' ? {
3985
+ ...raw.holder
3986
+ } : null;
3987
+ const rawProducts = Array.isArray(raw.products) ? raw.products : [];
3988
+ nextTempOrder.products = rawProducts.length > 0 ? rawProducts.map(p => this.normalizeHydratedOrderProduct(p, {
3989
+ makeEditMark: options?.makeEditMark
3990
+ })) : [];
3991
+ nextTempOrder.bookings = Array.isArray(raw.bookings) ? raw.bookings.map(b => {
3992
+ const booking = this.normalizeHydratedBookingHolder(b || {});
3993
+ return {
3994
+ ...booking,
3995
+ is_all: (0, _utils.normalizeBookingIsAll)(booking),
3996
+ sub_type: (0, _utils.normalizeBookingSubType)(booking)
3997
+ };
3998
+ }) : [];
3999
+ nextTempOrder.payments = Array.isArray(raw.payments) ? raw.payments.map(p => ({
4000
+ ...(p || {})
4001
+ })) : [];
4002
+ nextTempOrder.surcharges = Array.isArray(raw.surcharges) ? raw.surcharges.map(s => ({
4003
+ ...(s || {})
4004
+ })) : [];
4005
+ nextTempOrder.relation_forms = Array.isArray(raw.relation_forms) ? raw.relation_forms.map(f => ({
4006
+ ...(f || {})
4007
+ })) : [];
4008
+ nextTempOrder.contacts = Array.isArray(raw.contacts) ? raw.contacts.map(c => ({
4009
+ ...(c || {})
4010
+ })) : [];
4011
+ nextTempOrder.contacts_info = raw.contacts_info && typeof raw.contacts_info === 'object' && !Array.isArray(raw.contacts_info) ? {
4012
+ ...raw.contacts_info
4013
+ } : null;
4014
+ nextTempOrder._extend = raw._extend && typeof raw._extend === 'object' ? {
4015
+ ...raw._extend,
4016
+ productsByUid: raw._extend.productsByUid || {}
4017
+ } : {
4018
+ productsByUid: {}
4019
+ };
4020
+ const tempOrderExtend = nextTempOrder._extend;
4021
+ const productsByUid = tempOrderExtend.productsByUid || {};
4022
+ for (const [index, product] of nextTempOrder.products.entries()) {
4023
+ const uid = product.metadata?.unique_identification_number;
4024
+ if (!uid) continue;
4025
+ const existed = productsByUid[uid] && typeof productsByUid[uid] === 'object' ? productsByUid[uid] : {};
4026
+ const rawProduct = rawProducts[index] && typeof rawProducts[index] === 'object' ? rawProducts[index] : product;
4027
+ const origin = existed.origin ?? rawProduct._origin ?? rawProduct.metadata?.origin ?? rawProduct;
4028
+ const originSnapshot = (0, _lodashEs.cloneDeep)(origin);
4029
+ const productOptionString = this.buildHydratedProductOptionString(rawProduct) || this.buildHydratedProductOptionString(product);
4030
+ if (productOptionString && originSnapshot && typeof originSnapshot === 'object') {
4031
+ if (originSnapshot.product_option_string === undefined) {
4032
+ originSnapshot.product_option_string = productOptionString;
4033
+ }
4034
+ if (originSnapshot.product_options_stirng === undefined) {
4035
+ originSnapshot.product_options_stirng = productOptionString;
4036
+ }
4037
+ }
4038
+ productsByUid[uid] = {
4039
+ ...existed,
4040
+ origin: originSnapshot
4041
+ };
4042
+ }
4043
+ tempOrderExtend.productsByUid = productsByUid;
4044
+ this.hydrateLinkedBookingIdentity(nextTempOrder);
4045
+ if (options?.ensureIdentity !== false) {
4046
+ this.ensureExternalSaleNumber(nextTempOrder);
4047
+ }
4048
+ nextTempOrder.vouchers = raw.vouchers || [];
4049
+ return nextTempOrder;
4050
+ }
4051
+ hydrateLinkedBookingIdentity(tempOrder) {
4052
+ const bookingUidByProductUid = new Map();
4053
+ const productByUid = new Map();
4054
+ (tempOrder.products || []).forEach(product => {
4055
+ const productUid = product?.metadata?.unique_identification_number || product?.unique_identification_number;
4056
+ if (productUid !== undefined && productUid !== null && String(productUid) !== '') {
4057
+ productByUid.set(String(productUid), product);
4058
+ }
4059
+ });
4060
+ (tempOrder.bookings || []).forEach(booking => {
4061
+ if (!booking || typeof booking !== 'object') return;
4062
+ if (!booking.metadata || typeof booking.metadata !== 'object') {
4063
+ booking.metadata = {};
4064
+ }
4065
+ const productUid = booking.product_uid || booking.metadata.product_uid;
4066
+ if (productUid === undefined || productUid === null || String(productUid) === '') return;
4067
+ const matchedProduct = productByUid.get(String(productUid));
4068
+ const existedProductBookingUid = matchedProduct?.booking_uid || matchedProduct?.metadata?.booking_uid;
4069
+ // 客户端订单详情可能缺少 booking 自身 uid,这里补齐后才能反写到商品行。
4070
+ const bookingUid = String(booking.metadata.unique_identification_number || existedProductBookingUid || (0, _utils.createUuidV4)());
4071
+ booking.metadata.unique_identification_number = bookingUid;
4072
+ bookingUidByProductUid.set(String(productUid), bookingUid);
4073
+ });
4074
+ (tempOrder.products || []).forEach(product => {
4075
+ const productUid = product?.metadata?.unique_identification_number || product?.unique_identification_number;
4076
+ if (productUid === undefined || productUid === null || String(productUid) === '') return;
4077
+ const bookingUid = bookingUidByProductUid.get(String(productUid));
4078
+ if (!bookingUid) return;
4079
+ if (!product.metadata || typeof product.metadata !== 'object') {
4080
+ product.metadata = {};
4081
+ }
4082
+ // booking 已经过统一身份规范化;以它为准同步商品侧旧引用,避免两侧 UID 断链。
4083
+ const linkedBookingUid = String(bookingUid);
4084
+ product.booking_uid = linkedBookingUid;
4085
+ product.metadata.booking_uid = linkedBookingUid;
4086
+ });
4087
+ this.syncLinkedBookingHoldersToProducts(tempOrder);
4088
+ }
4089
+ pickHydratedDisplayText(value) {
4090
+ if (value === undefined || value === null) return '';
4091
+ if (typeof value === 'string') return value;
4092
+ if (typeof value !== 'object') return String(value);
4093
+ return value.auto || value.original || value.en || value['zh-CN'] || value['zh-HK'] || '';
4094
+ }
4095
+ formatHydratedNamePair(group, item) {
4096
+ const groupText = this.pickHydratedDisplayText(group);
4097
+ const itemText = this.pickHydratedDisplayText(item);
4098
+ if (groupText && itemText) return `${groupText}:${itemText}`;
4099
+ return groupText || itemText;
4100
+ }
4101
+ buildHydratedProductOptionString(product) {
4102
+ const segments = [];
4103
+ const sku = product?.product_sku || {};
4104
+ if (Array.isArray(sku.variant)) {
4105
+ for (const variant of sku.variant) {
4106
+ const segment = this.formatHydratedNamePair(variant?.group, variant?.item);
4107
+ if (segment) segments.push(segment);
4108
+ }
4109
+ }
4110
+ return segments.join(', ');
4111
+ }
4112
+ normalizeHydratedProductOptionItem(optionItem) {
4113
+ const rawNum = optionItem?.num ?? optionItem?.quantity ?? 1;
4114
+ const parsedNum = Number(rawNum);
4115
+ const rawPrice = optionItem?.add_price ?? optionItem?.price ?? 0;
4116
+ const parsedPrice = Number(rawPrice);
4117
+ const normalized = {
4118
+ ...optionItem,
4119
+ option_group_item_id: optionItem?.option_group_item_id,
4120
+ option_group_id: optionItem?.option_group_id,
4121
+ num: Number.isFinite(parsedNum) && parsedNum > 0 ? parsedNum : 1,
4122
+ add_price: Number.isFinite(parsedPrice) ? parsedPrice : 0
4123
+ };
4124
+ delete normalized.price;
4125
+ if (optionItem?.name !== undefined) normalized.name = optionItem.name;
4126
+ if (optionItem?.group_name !== undefined) normalized.group_name = optionItem.group_name;
4127
+ if (optionItem?.id !== undefined) normalized.id = optionItem.id;
4128
+ return normalized;
4129
+ }
4130
+ normalizeHydratedOrderProduct(product, options) {
4131
+ let row = {
4132
+ ...(product || {})
4133
+ };
4134
+ if (row.num === undefined && row.product_quantity !== undefined) {
4135
+ row.num = row.product_quantity;
4136
+ }
4137
+ const productSku = (0, _utils.ensureProductSku)(row);
4138
+ row.product_sku = {
4139
+ ...productSku,
4140
+ option: (0, _utils.normalizeProductSkuOptions)(productSku.option.map(optionItem => this.normalizeHydratedProductOptionItem(optionItem || {})))
4141
+ };
4142
+ delete row[`product_${'option'}_item`];
4143
+ if (!Array.isArray(row.product_bundle)) row.product_bundle = [];
4144
+ if (!row.metadata || typeof row.metadata !== 'object') row.metadata = {};
4145
+ if (row.booking_uid && !row.metadata.booking_uid) {
4146
+ row.metadata.booking_uid = row.booking_uid;
4147
+ }
4148
+ row = (0, _bundleDiscountHydration.restoreHydratedBundleDiscounts)(row, _utils.normalizeOrderProductDiscountList);
4149
+ const existingIdentity = row.metadata.unique_identification_number || row.unique_identification_number;
4150
+ if (!existingIdentity) {
4151
+ row.metadata.unique_identification_number = (0, _utils.createUuidV4)();
4152
+ }
4153
+ // 从云端、本地数据库中获取到的商品数据,先打上 runtime 标记,提供给 UI 做 saved items.
4154
+ if (options?.makeEditMark) {
4155
+ row.metadata.is_edit_for_runtime = true;
4156
+ }
4157
+ const normalized = (0, _utils3.normalizeOrderProduct)(row);
4158
+ normalized.discount_list = (0, _utils.normalizeOrderProductDiscountList)(normalized.discount_list);
4159
+ const result = {
4160
+ ...row,
4161
+ ...normalized,
4162
+ product_sku: normalized.product_sku,
4163
+ metadata: {
4164
+ ...row.metadata,
4165
+ ...normalized.metadata
4166
+ }
4167
+ };
4168
+ if (result.metadata) delete result.metadata.price_schema_version;
4169
+ const restored = (0, _bundleDiscountHydration.restoreHydratedBundleDiscounts)(result, _utils.normalizeOrderProductDiscountList);
4170
+ return this.sanitizeOrderProductForTempOrder(restored);
4171
+ }
834
4172
  getLastOrderInfo() {
835
- var _a, _b;
836
- return (_b = (_a = this.store) == null ? void 0 : _a.tempOrder) == null ? void 0 : _b.lastOrderInfo;
4173
+ return this.store?.lastOrderInfo;
837
4174
  }
838
- };
839
- // Annotate the CommonJS export names for ESM import in node:
840
- 0 && (module.exports = {
841
- OrderModule
842
- });
4175
+ async getOrderInfo(order_id) {
4176
+ const res = await this.request.get(`/order/sales/${order_id}`, {
4177
+ with: ['products', 'bookings']
4178
+ }, {
4179
+ osServer: true
4180
+ });
4181
+ return res;
4182
+ }
4183
+ getVouchers() {
4184
+ return this.store.tempOrder?.vouchers || [];
4185
+ }
4186
+ }
4187
+ exports.OrderModule = OrderModule;