@pisell/pisellos 2.2.314 → 2.2.316

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 (268) hide show
  1. package/dist/index.d.ts +2 -0
  2. package/dist/index.js +3 -1
  3. package/dist/model/strategy/adapter/itemRule/adapter.d.ts +3 -1
  4. package/dist/model/strategy/adapter/itemRule/adapter.js +74 -7
  5. package/dist/model/strategy/adapter/itemRule/evaluator.d.ts +3 -1
  6. package/dist/model/strategy/adapter/itemRule/evaluator.js +13 -0
  7. package/dist/model/strategy/adapter/itemRule/type.d.ts +40 -1
  8. package/dist/model/strategy/adapter/itemRule/type.js +10 -0
  9. package/dist/model/strategy/adapter/promotion/evaluator.js +89 -104
  10. package/dist/modules/BookingContext/utils/buildNormalProductCacheItemFromOrderLine.d.ts +3 -1
  11. package/dist/modules/BookingContext/utils/buildNormalProductCacheItemFromOrderLine.js +28 -4
  12. package/dist/modules/BookingContext/utils/productExtend.d.ts +7 -0
  13. package/dist/modules/BookingContext/utils/productExtend.js +62 -18
  14. package/dist/modules/BookingContext/utils/resources.js +1 -1
  15. package/dist/modules/BusinessAvailability/index.d.ts +30 -0
  16. package/dist/modules/BusinessAvailability/index.js +204 -0
  17. package/dist/modules/Cart/index.js +4 -3
  18. package/dist/modules/Cart/types.d.ts +2 -0
  19. package/dist/modules/Cart/utils/cartProduct.d.ts +3 -1
  20. package/dist/modules/Cart/utils/cartProduct.js +59 -32
  21. package/dist/modules/FulfillmentTiming/index.d.ts +11 -0
  22. package/dist/modules/FulfillmentTiming/index.js +407 -0
  23. package/dist/modules/FulfillmentTiming/types.d.ts +67 -0
  24. package/dist/modules/FulfillmentTiming/types.js +1 -0
  25. package/dist/modules/Holder/utils.d.ts +2 -0
  26. package/dist/modules/Holder/utils.js +32 -2
  27. package/dist/modules/OpenData/contactInfo.d.ts +20 -0
  28. package/dist/modules/OpenData/contactInfo.js +70 -0
  29. package/dist/modules/OpenData/index.d.ts +1 -0
  30. package/dist/modules/OpenData/index.js +1 -0
  31. package/dist/modules/OpenData/types.d.ts +23 -0
  32. package/dist/modules/Order/holderRelations.d.ts +14 -0
  33. package/dist/modules/Order/holderRelations.js +154 -0
  34. package/dist/modules/Order/index.d.ts +16 -4
  35. package/dist/modules/Order/index.js +926 -511
  36. package/dist/modules/Order/localEftposState.d.ts +13 -0
  37. package/dist/modules/Order/localEftposState.js +39 -0
  38. package/dist/modules/Order/pickupTiming.d.ts +46 -0
  39. package/dist/modules/Order/pickupTiming.js +53 -0
  40. package/dist/modules/Order/types.d.ts +49 -3
  41. package/dist/modules/Order/types.js +2 -0
  42. package/dist/modules/Order/utils/discountProductLineIdentity.d.ts +1 -1
  43. package/dist/modules/Order/utils/discountProductLineIdentity.js +142 -7
  44. package/dist/modules/Order/utils/orderCollectionIdentity.js +24 -10
  45. package/dist/modules/Order/utils.d.ts +2 -1
  46. package/dist/modules/Order/utils.js +61 -17
  47. package/dist/modules/Payment/index.js +9 -10
  48. package/dist/modules/Product/types.d.ts +11 -0
  49. package/dist/modules/ProductList/index.js +1 -1
  50. package/dist/modules/ResourcePlanner/planner.d.ts +2 -0
  51. package/dist/modules/ResourcePlanner/planner.js +332 -23
  52. package/dist/modules/ResourcePlanner/types.d.ts +29 -0
  53. package/dist/modules/Rules/index.js +62 -61
  54. package/dist/modules/Schedule/index.d.ts +8 -0
  55. package/dist/modules/Schedule/index.js +141 -39
  56. package/dist/modules/index.d.ts +1 -0
  57. package/dist/modules/index.js +1 -0
  58. package/dist/plugins/request.d.ts +7 -0
  59. package/dist/server/index.d.ts +1 -0
  60. package/dist/server/index.js +1557 -1322
  61. package/dist/server/modules/form/index.d.ts +19 -0
  62. package/dist/server/modules/form/index.js +205 -0
  63. package/dist/server/modules/form/types.d.ts +13 -0
  64. package/dist/server/modules/form/types.js +3 -0
  65. package/dist/server/modules/index.d.ts +3 -0
  66. package/dist/server/modules/index.js +4 -0
  67. package/dist/server/modules/order/index.d.ts +19 -0
  68. package/dist/server/modules/order/index.js +1071 -774
  69. package/dist/server/modules/order/types.d.ts +4 -0
  70. package/dist/server/modules/products/index.d.ts +28 -7
  71. package/dist/server/modules/products/index.js +1297 -770
  72. package/dist/server/modules/products/replication.d.ts +8 -0
  73. package/dist/server/modules/products/replication.js +69 -0
  74. package/dist/server/modules/products/types.d.ts +12 -1
  75. package/dist/server/modules/products/types.js +4 -0
  76. package/dist/server/test.json +1 -1
  77. package/dist/server/utils/small-ticket.d.ts +1 -85
  78. package/dist/server/utils/small-ticket.js +2 -2542
  79. package/dist/shared/payment/loadPaymentMethods.d.ts +9 -0
  80. package/dist/shared/payment/loadPaymentMethods.js +66 -0
  81. package/dist/shared/receipt/customerInvoiceLanguages.d.ts +8 -0
  82. package/dist/shared/receipt/customerInvoiceLanguages.js +60 -0
  83. package/dist/shared/receipt/loadCustomerInvoiceLayout.d.ts +27 -0
  84. package/dist/shared/receipt/loadCustomerInvoiceLayout.js +184 -0
  85. package/dist/shared/receipt/small-ticket.d.ts +96 -0
  86. package/dist/shared/receipt/small-ticket.js +2763 -0
  87. package/dist/shared/receipt/templateVisibility.d.ts +26 -0
  88. package/dist/shared/receipt/templateVisibility.js +68 -0
  89. package/dist/shared/sales/loadSalesOrderSnapshot.d.ts +9 -0
  90. package/dist/shared/sales/loadSalesOrderSnapshot.js +21 -0
  91. package/dist/solution/BaseSales/fulfillmentItemRules.d.ts +27 -0
  92. package/dist/solution/BaseSales/fulfillmentItemRules.js +750 -0
  93. package/dist/solution/BaseSales/index.d.ts +43 -16
  94. package/dist/solution/BaseSales/index.js +2028 -1247
  95. package/dist/solution/BaseSales/pickupTiming.d.ts +47 -0
  96. package/dist/solution/BaseSales/pickupTiming.js +616 -0
  97. package/dist/solution/BaseSales/types.d.ts +12 -0
  98. package/dist/solution/BaseSales/utils/parseSalesResponse.d.ts +3 -1
  99. package/dist/solution/BaseSales/utils/parseSalesResponse.js +10 -3
  100. package/dist/solution/BaseSales/utils/transformBaseProductToOrderProduct.js +14 -4
  101. package/dist/solution/BookingByStep/index.d.ts +4 -2
  102. package/dist/solution/BookingByStep/index.js +8 -3
  103. package/dist/solution/BookingTicket/index.d.ts +23 -3
  104. package/dist/solution/BookingTicket/index.js +867 -507
  105. package/dist/solution/BookingTicket/utils/addProductDecision.d.ts +0 -1
  106. package/dist/solution/BookingTicket/utils/addProductDecision.js +1 -1
  107. package/dist/solution/BookingTicket/utils/cartView.js +3 -1
  108. package/dist/solution/OrderInvoicePreview/index.d.ts +66 -0
  109. package/dist/solution/OrderInvoicePreview/index.js +340 -0
  110. package/dist/solution/RegisterAndLogin/config.js +54 -11
  111. package/dist/solution/RegisterAndLogin/index.d.ts +2 -0
  112. package/dist/solution/RegisterAndLogin/index.js +1 -1
  113. package/dist/solution/RegisterAndLogin/utils.d.ts +12 -4
  114. package/dist/solution/RegisterAndLogin/utils.js +36 -16
  115. package/dist/solution/ScanOrder/index.js +65 -31
  116. package/dist/solution/ScanOrder/types.d.ts +3 -0
  117. package/dist/solution/UnifiedBookingSales/businessAvailability.d.ts +27 -0
  118. package/dist/solution/UnifiedBookingSales/businessAvailability.js +292 -0
  119. package/dist/solution/UnifiedBookingSales/index.d.ts +36 -4
  120. package/dist/solution/UnifiedBookingSales/index.js +1715 -906
  121. package/dist/solution/UnifiedBookingSales/types.d.ts +16 -2
  122. package/dist/solution/VenueBooking/index.d.ts +15 -2
  123. package/dist/solution/VenueBooking/index.js +1062 -785
  124. package/dist/solution/VenueBooking/types.d.ts +2 -1
  125. package/dist/solution/VenueBooking/types.js +1 -0
  126. package/dist/solution/VenueBooking/utils/dateSummary.d.ts +1 -0
  127. package/dist/solution/VenueBooking/utils/dateSummary.js +7 -4
  128. package/dist/solution/VenueBooking/utils/smartPricing.d.ts +71 -0
  129. package/dist/solution/VenueBooking/utils/smartPricing.js +273 -0
  130. package/dist/solution/VenueBooking/utils/timeSlot.d.ts +1 -0
  131. package/dist/solution/VenueBooking/utils/timeSlot.js +6 -3
  132. package/dist/solution/index.d.ts +1 -0
  133. package/dist/solution/index.js +2 -1
  134. package/lib/index.d.ts +2 -0
  135. package/lib/index.js +21 -1
  136. package/lib/model/strategy/adapter/itemRule/adapter.d.ts +3 -1
  137. package/lib/model/strategy/adapter/itemRule/adapter.js +54 -4
  138. package/lib/model/strategy/adapter/itemRule/evaluator.d.ts +3 -1
  139. package/lib/model/strategy/adapter/itemRule/evaluator.js +9 -0
  140. package/lib/model/strategy/adapter/itemRule/type.d.ts +40 -1
  141. package/lib/model/strategy/adapter/itemRule/type.js +10 -0
  142. package/lib/model/strategy/adapter/promotion/evaluator.js +9 -14
  143. package/lib/model/strategy/adapter/promotion/index.js +46 -1
  144. package/lib/modules/BookingContext/utils/buildNormalProductCacheItemFromOrderLine.d.ts +3 -1
  145. package/lib/modules/BookingContext/utils/buildNormalProductCacheItemFromOrderLine.js +17 -1
  146. package/lib/modules/BookingContext/utils/productExtend.d.ts +7 -0
  147. package/lib/modules/BookingContext/utils/productExtend.js +45 -2
  148. package/lib/modules/BookingContext/utils/resources.js +1 -1
  149. package/lib/modules/BusinessAvailability/index.d.ts +30 -0
  150. package/lib/modules/BusinessAvailability/index.js +146 -0
  151. package/lib/modules/Cart/index.js +4 -2
  152. package/lib/modules/Cart/types.d.ts +2 -0
  153. package/lib/modules/Cart/utils/cartProduct.d.ts +3 -1
  154. package/lib/modules/Cart/utils/cartProduct.js +39 -20
  155. package/lib/modules/FulfillmentTiming/index.d.ts +11 -0
  156. package/lib/modules/FulfillmentTiming/index.js +359 -0
  157. package/lib/modules/FulfillmentTiming/types.d.ts +67 -0
  158. package/lib/modules/FulfillmentTiming/types.js +5 -0
  159. package/lib/modules/Holder/utils.d.ts +2 -0
  160. package/lib/modules/Holder/utils.js +27 -1
  161. package/lib/modules/OpenData/contactInfo.d.ts +20 -0
  162. package/lib/modules/OpenData/contactInfo.js +56 -0
  163. package/lib/modules/OpenData/index.d.ts +1 -0
  164. package/lib/modules/OpenData/index.js +12 -0
  165. package/lib/modules/OpenData/types.d.ts +23 -0
  166. package/lib/modules/Order/holderRelations.d.ts +14 -0
  167. package/lib/modules/Order/holderRelations.js +98 -0
  168. package/lib/modules/Order/index.d.ts +16 -4
  169. package/lib/modules/Order/index.js +284 -50
  170. package/lib/modules/Order/localEftposState.d.ts +13 -0
  171. package/lib/modules/Order/localEftposState.js +44 -0
  172. package/lib/modules/Order/pickupTiming.d.ts +46 -0
  173. package/lib/modules/Order/pickupTiming.js +61 -0
  174. package/lib/modules/Order/types.d.ts +49 -3
  175. package/lib/modules/Order/types.js +1 -0
  176. package/lib/modules/Order/utils/discountProductLineIdentity.d.ts +1 -1
  177. package/lib/modules/Order/utils/discountProductLineIdentity.js +51 -2
  178. package/lib/modules/Order/utils/orderCollectionIdentity.js +5 -0
  179. package/lib/modules/Order/utils.d.ts +2 -1
  180. package/lib/modules/Order/utils.js +49 -11
  181. package/lib/modules/Payment/index.js +3 -4
  182. package/lib/modules/Product/types.d.ts +11 -0
  183. package/lib/modules/ProductList/index.js +1 -1
  184. package/lib/modules/ResourcePlanner/planner.d.ts +2 -0
  185. package/lib/modules/ResourcePlanner/planner.js +252 -8
  186. package/lib/modules/ResourcePlanner/types.d.ts +29 -0
  187. package/lib/modules/Rules/index.js +2 -1
  188. package/lib/modules/Schedule/index.d.ts +8 -0
  189. package/lib/modules/Schedule/index.js +68 -12
  190. package/lib/modules/index.d.ts +1 -0
  191. package/lib/modules/index.js +11 -0
  192. package/lib/plugins/request.d.ts +7 -0
  193. package/lib/server/index.d.ts +1 -0
  194. package/lib/server/index.js +166 -39
  195. package/lib/server/modules/form/index.d.ts +19 -0
  196. package/lib/server/modules/form/index.js +73 -0
  197. package/lib/server/modules/form/types.d.ts +13 -0
  198. package/lib/server/modules/form/types.js +9 -0
  199. package/lib/server/modules/index.d.ts +3 -0
  200. package/lib/server/modules/index.js +15 -1
  201. package/lib/server/modules/order/index.d.ts +19 -0
  202. package/lib/server/modules/order/index.js +160 -7
  203. package/lib/server/modules/order/types.d.ts +4 -0
  204. package/lib/server/modules/products/index.d.ts +28 -7
  205. package/lib/server/modules/products/index.js +226 -63
  206. package/lib/server/modules/products/replication.d.ts +8 -0
  207. package/lib/server/modules/products/replication.js +30 -0
  208. package/lib/server/modules/products/types.d.ts +12 -1
  209. package/lib/server/modules/products/types.js +3 -0
  210. package/lib/server/test.json +1 -1
  211. package/lib/server/utils/small-ticket.d.ts +1 -85
  212. package/lib/server/utils/small-ticket.js +9 -2305
  213. package/lib/shared/payment/loadPaymentMethods.d.ts +9 -0
  214. package/lib/shared/payment/loadPaymentMethods.js +40 -0
  215. package/lib/shared/receipt/customerInvoiceLanguages.d.ts +8 -0
  216. package/lib/shared/receipt/customerInvoiceLanguages.js +58 -0
  217. package/lib/shared/receipt/loadCustomerInvoiceLayout.d.ts +27 -0
  218. package/lib/shared/receipt/loadCustomerInvoiceLayout.js +124 -0
  219. package/lib/shared/receipt/small-ticket.d.ts +96 -0
  220. package/lib/shared/receipt/small-ticket.js +2555 -0
  221. package/lib/shared/receipt/templateVisibility.d.ts +26 -0
  222. package/lib/shared/receipt/templateVisibility.js +67 -0
  223. package/lib/shared/sales/loadSalesOrderSnapshot.d.ts +9 -0
  224. package/lib/shared/sales/loadSalesOrderSnapshot.js +22 -0
  225. package/lib/solution/BaseSales/fulfillmentItemRules.d.ts +27 -0
  226. package/lib/solution/BaseSales/fulfillmentItemRules.js +259 -0
  227. package/lib/solution/BaseSales/index.d.ts +43 -16
  228. package/lib/solution/BaseSales/index.js +401 -33
  229. package/lib/solution/BaseSales/pickupTiming.d.ts +47 -0
  230. package/lib/solution/BaseSales/pickupTiming.js +290 -0
  231. package/lib/solution/BaseSales/types.d.ts +12 -0
  232. package/lib/solution/BaseSales/utils/parseSalesResponse.d.ts +3 -1
  233. package/lib/solution/BaseSales/utils/parseSalesResponse.js +7 -0
  234. package/lib/solution/BaseSales/utils/transformBaseProductToOrderProduct.js +16 -8
  235. package/lib/solution/BookingByStep/index.d.ts +4 -2
  236. package/lib/solution/BookingByStep/index.js +7 -3
  237. package/lib/solution/BookingTicket/index.d.ts +23 -3
  238. package/lib/solution/BookingTicket/index.js +197 -9
  239. package/lib/solution/BookingTicket/utils/addProductDecision.d.ts +0 -1
  240. package/lib/solution/BookingTicket/utils/addProductDecision.js +1 -1
  241. package/lib/solution/BookingTicket/utils/cartView.js +3 -1
  242. package/lib/solution/OrderInvoicePreview/index.d.ts +66 -0
  243. package/lib/solution/OrderInvoicePreview/index.js +246 -0
  244. package/lib/solution/RegisterAndLogin/config.js +57 -11
  245. package/lib/solution/RegisterAndLogin/index.d.ts +2 -0
  246. package/lib/solution/RegisterAndLogin/index.js +9 -1
  247. package/lib/solution/RegisterAndLogin/utils.d.ts +12 -4
  248. package/lib/solution/RegisterAndLogin/utils.js +24 -17
  249. package/lib/solution/ScanOrder/index.js +59 -20
  250. package/lib/solution/ScanOrder/types.d.ts +3 -0
  251. package/lib/solution/UnifiedBookingSales/businessAvailability.d.ts +27 -0
  252. package/lib/solution/UnifiedBookingSales/businessAvailability.js +138 -0
  253. package/lib/solution/UnifiedBookingSales/index.d.ts +36 -4
  254. package/lib/solution/UnifiedBookingSales/index.js +562 -75
  255. package/lib/solution/UnifiedBookingSales/types.d.ts +16 -2
  256. package/lib/solution/VenueBooking/index.d.ts +15 -2
  257. package/lib/solution/VenueBooking/index.js +243 -96
  258. package/lib/solution/VenueBooking/types.d.ts +2 -1
  259. package/lib/solution/VenueBooking/types.js +1 -0
  260. package/lib/solution/VenueBooking/utils/dateSummary.d.ts +1 -0
  261. package/lib/solution/VenueBooking/utils/dateSummary.js +7 -4
  262. package/lib/solution/VenueBooking/utils/smartPricing.d.ts +71 -0
  263. package/lib/solution/VenueBooking/utils/smartPricing.js +224 -0
  264. package/lib/solution/VenueBooking/utils/timeSlot.d.ts +1 -0
  265. package/lib/solution/VenueBooking/utils/timeSlot.js +5 -2
  266. package/lib/solution/index.d.ts +1 -0
  267. package/lib/solution/index.js +11 -0
  268. package/package.json +1 -1
@@ -8,6 +8,7 @@ var _exportNames = {
8
8
  };
9
9
  exports.BaseSalesImpl = void 0;
10
10
  var _BaseModule = require("../../modules/BaseModule");
11
+ var _localEftposState = require("../../modules/Order/localEftposState");
11
12
  var _types = require("./types");
12
13
  Object.keys(_types).forEach(function (key) {
13
14
  if (key === "default" || key === "__esModule") return;
@@ -21,6 +22,11 @@ Object.keys(_types).forEach(function (key) {
21
22
  });
22
23
  });
23
24
  var _Order = require("../../modules/Order");
25
+ var _pickupTiming = require("../../modules/Order/pickupTiming");
26
+ var _pickupTiming2 = require("./pickupTiming");
27
+ var _fulfillmentItemRules = require("./fulfillmentItemRules");
28
+ var _holderRelations = require("../../modules/Order/holderRelations");
29
+ var _contactInfo = require("../../modules/OpenData/contactInfo");
24
30
  var _decimal = _interopRequireDefault(require("decimal.js"));
25
31
  var _lodashEs = require("lodash-es");
26
32
  var _types2 = require("../BookingByStep/types");
@@ -345,6 +351,38 @@ function areBaseSalesNotesEquivalent(currentValue, loadedValue, options) {
345
351
  const loadedNotes = normalizeBaseSalesNotesForComparison(loadedValue);
346
352
  return currentNotes !== null && loadedNotes !== null && (0, _lodashEs.isEqual)(currentNotes, loadedNotes);
347
353
  }
354
+ function mergeBaseSalesHolderRelations(currentRecord, incomingRecord, strategy) {
355
+ if (!Array.isArray(incomingRecord.holder_relations)) {
356
+ return (0, _lodashEs.cloneDeep)(currentRecord.holder_relations);
357
+ }
358
+
359
+ // 仅保留商品集合合并实际留下的本地行关系;匹配的行沿用 incoming 快照。
360
+ // 未提供 products 的稀疏包不能据此判断哪些行已被删除或被本地保留。
361
+ const retainedProductUids = new Set();
362
+ if (Array.isArray(incomingRecord.products)) {
363
+ for (const product of currentRecord.products || []) {
364
+ const retained = !incomingRecord.products.some(incomingProduct => (0, _orderCollectionIdentity.isSameOrderCollectionItem)('product', product, incomingProduct)) && (strategy === 'preserve-local' || !(0, _orderCollectionIdentity.getOrderCollectionPersistentId)('product', product));
365
+ const uid = (0, _orderCollectionIdentity.getOrderCollectionReferenceUid)('product', product);
366
+ if (retained && uid) retainedProductUids.add(uid);
367
+ }
368
+ }
369
+ const belongsToRetainedProduct = relation => relation?.relation_model === 'order_detail' && retainedProductUids.has(String(relation.relation_uuid));
370
+ const selectedRelations = [...incomingRecord.holder_relations.filter(relation => !belongsToRetainedProduct(relation)), ...(Array.isArray(currentRecord.holder_relations) ? currentRecord.holder_relations.filter(belongsToRetainedProduct) : [])];
371
+ const result = [];
372
+ const indexByIdentity = new Map();
373
+ for (const relation of selectedRelations) {
374
+ const identityParts = [relation?.relation_model, relation?.relation_uuid, relation?.holder_type, relation?.holder_id];
375
+ const identity = identityParts.every(value => value !== undefined && value !== null) ? JSON.stringify(identityParts) : null;
376
+ const existingIndex = identity === null ? undefined : indexByIdentity.get(identity);
377
+ if (existingIndex !== undefined) {
378
+ result[existingIndex] = (0, _lodashEs.cloneDeep)(relation);
379
+ } else {
380
+ if (identity !== null) indexByIdentity.set(identity, result.length);
381
+ result.push((0, _lodashEs.cloneDeep)(relation));
382
+ }
383
+ }
384
+ return result;
385
+ }
348
386
  function mergeSalesDetailRecordWithTempOrder(currentTempOrder, loadedRecord, strategy) {
349
387
  const loadedHasOrderMetaList = Object.prototype.hasOwnProperty.call(loadedRecord || {}, 'order_meta_list');
350
388
  const loadedHasSalesNotes = Object.prototype.hasOwnProperty.call(loadedRecord || {}, 'notes');
@@ -363,8 +401,12 @@ function mergeSalesDetailRecordWithTempOrder(currentTempOrder, loadedRecord, str
363
401
  const normalizedCurrentTempOrder = normalizeBaseSalesRecordCollections(currentTempOrder, {
364
402
  ensureUid: false
365
403
  });
404
+ const mergedHolderRelations = mergeBaseSalesHolderRelations(normalizedCurrentTempOrder, normalizedLoadedRecord, strategy);
366
405
  const uidRemaps = [];
367
- const mergedRecord = (0, _lodashEs.mergeWith)(normalizedCurrentTempOrder, normalizedLoadedRecord, (currentValue, loadedValue, key) => {
406
+ const mergedRecord = (0, _lodashEs.mergeWith)(normalizedCurrentTempOrder, normalizedLoadedRecord, (currentValue, loadedValue, key, object) => {
407
+ if (key === 'holder_relations' && object === normalizedCurrentTempOrder) {
408
+ return (0, _lodashEs.cloneDeep)(mergedHolderRelations);
409
+ }
368
410
  // order_meta_list 是服务端/本地订单携带的完整业务快照。若 incoming
369
411
  // 明确提供它,必须整体替换;若省略,则保留当前值,避免 snapshot
370
412
  // 数组递归拼接后出现同 ID 的旧数据。
@@ -499,6 +541,9 @@ class BaseSalesImpl extends _BaseModule.BaseModule {
499
541
  serverOrderChangeHydrateInFlight = null;
500
542
  queuedServerOrderChanges = [];
501
543
  serverOrderChangeSyncEnabled = false;
544
+ pickupTimingEnabled = false;
545
+ pickupTimingController;
546
+ fulfillmentItemRulesController;
502
547
  constructor(name, version) {
503
548
  super(name, version);
504
549
  }
@@ -584,8 +629,12 @@ class BaseSalesImpl extends _BaseModule.BaseModule {
584
629
  }
585
630
  this.queuedServerOrderChanges.push(snapshot);
586
631
  }
632
+ shouldSyncServerOrderChanges() {
633
+ const payments = this.store.order?.getTempOrder()?.payments || [];
634
+ return this.serverOrderChangeSyncEnabled || payments.some(_localEftposState.isLocalFinalizedPayment) && !payments.some(_paymentUtils.isPendingOrderPayment);
635
+ }
587
636
  async drainQueuedServerOrderChanges() {
588
- if (!this.serverOrderChangeSyncEnabled || this.salesDetailLoadInFlightCount > 0 || this.serverOrderChangeHydrateInFlight || this.queuedServerOrderChanges.length === 0) return;
637
+ if (!this.shouldSyncServerOrderChanges() || this.salesDetailLoadInFlightCount > 0 || this.serverOrderChangeHydrateInFlight || this.queuedServerOrderChanges.length === 0) return;
589
638
  const changedOrders = this.queuedServerOrderChanges;
590
639
  this.queuedServerOrderChanges = [];
591
640
  await this.syncCurrentSalesDetailFromServerOrderChange({
@@ -593,8 +642,9 @@ class BaseSalesImpl extends _BaseModule.BaseModule {
593
642
  });
594
643
  }
595
644
  async syncCurrentSalesDetailFromServerOrderChange(payload) {
596
- const changedOrders = Array.isArray(payload) ? payload : Array.isArray(payload?.changedOrders) ? payload.changedOrders : payload?.data && typeof payload.data === 'object' ? [payload.data] : [];
597
- if (!this.serverOrderChangeSyncEnabled) return;
645
+ const fullSnapshotAfterFinalization = Array.isArray(payload?.list) && this.store.order?.getTempOrder()?.payments?.some(_localEftposState.isLocalFinalizedPayment);
646
+ const changedOrders = Array.isArray(payload) ? payload : Array.isArray(payload?.changedOrders) && payload.changedOrders.length > 0 ? payload.changedOrders : fullSnapshotAfterFinalization ? payload.list : payload?.data && typeof payload.data === 'object' ? [payload.data] : [];
647
+ if (!this.shouldSyncServerOrderChanges()) return;
598
648
 
599
649
  // 显式详情加载期间 current 可能仍是上一单,先按订单身份 latest-only 缓存;
600
650
  // load 完成后再用新的 current 过滤,避免恰好到达的最新支付快照丢失。
@@ -1037,7 +1087,30 @@ class BaseSalesImpl extends _BaseModule.BaseModule {
1037
1087
  customerId: params.customerId,
1038
1088
  channel: params.channel
1039
1089
  });
1040
- return productIds.map(productId => productId === null ? null : productsById.get(productId) || null);
1090
+ const shouldAlignSelectedVariantPrice = handledByOsServer && this.preferAuthoritativeProductQueryPrice();
1091
+ return productIds.map((productId, index) => {
1092
+ const authoritativeProduct = productId === null ? null : productsById.get(productId) || null;
1093
+ if (!shouldAlignSelectedVariantPrice || !authoritativeProduct) return authoritativeProduct;
1094
+ const selectedProduct = selectedProducts[index];
1095
+ const selectedVariantId = this.getSelectedProductVariantId(selectedProduct);
1096
+ if (!selectedVariantId || this.getSelectedProductVariantId(authoritativeProduct) === selectedVariantId) {
1097
+ return authoritativeProduct;
1098
+ }
1099
+
1100
+ // OS 返回的是目录商品;已命中的智能价格不能被规格基础价覆盖。
1101
+ const appliedFields = authoritativeProduct._dataVariant?.appliedFields;
1102
+ if (Array.isArray(appliedFields) && ['price', 'selling_price', 'original_price', 'base_price'].some(field => appliedFields.includes(field))) {
1103
+ return authoritativeProduct;
1104
+ }
1105
+ const selectedVariant = Array.isArray(authoritativeProduct.variant) ? authoritativeProduct.variant.find(variant => Number(variant?.id) === selectedVariantId) : undefined;
1106
+ const selectedPrice = [selectedVariant?.price, selectedVariant?.selling_price, selectedVariant?.base_price, params.priceQueries[index].fallback_price, selectedProduct.price, selectedProduct.selling_price, selectedProduct.original_price].find(value => (typeof value === 'number' || typeof value === 'string' && value.trim() !== '') && Number.isFinite(Number(value)));
1107
+
1108
+ // 每行独立保留所选规格价格(含 0 元),避免同商品多规格共用目录对象串价。
1109
+ return selectedPrice === undefined ? authoritativeProduct : {
1110
+ ...authoritativeProduct,
1111
+ price: selectedPrice
1112
+ };
1113
+ });
1041
1114
  }
1042
1115
  const queryPayload = this.buildPriceQueryPayload({
1043
1116
  ids,
@@ -1157,6 +1230,98 @@ class BaseSalesImpl extends _BaseModule.BaseModule {
1157
1230
  return this.otherParams?.channel;
1158
1231
  }
1159
1232
 
1233
+ /** Only solutions opting into Pickup Timing supply effective OpenData. */
1234
+ async loadPickupTimingOpenData(_force) {
1235
+ return {};
1236
+ }
1237
+ async loadFulfillmentItemRuleOpenData() {
1238
+ const openData = this.otherParams?.openData ?? this.otherParams?.dineInConfig;
1239
+ return openData && typeof openData === 'object' && !Array.isArray(openData) ? openData : null;
1240
+ }
1241
+ getFulfillmentItemRulesController() {
1242
+ if (!this.fulfillmentItemRulesController) {
1243
+ this.fulfillmentItemRulesController = new _fulfillmentItemRules.FulfillmentItemRulesController({
1244
+ getOrder: () => {
1245
+ if (!this.store.order) throw new Error('order 模块未初始化');
1246
+ return this.store.order;
1247
+ },
1248
+ getProducts: () => this.store.products,
1249
+ loadOpenData: () => this.loadFulfillmentItemRuleOpenData(),
1250
+ getScope: () => this.getPickupTimingScope(),
1251
+ getOtherParams: () => this.otherParams,
1252
+ fetchStrategyModels: ids => this.request.get('/promotion', {
1253
+ skip: 1,
1254
+ num: 99,
1255
+ status: 'active',
1256
+ ids
1257
+ }),
1258
+ reportError: error => this.logWarning('履约费用策略执行失败,保留服务类型设置', {
1259
+ error: error instanceof Error ? error.message : String(error)
1260
+ })
1261
+ });
1262
+ }
1263
+ return this.fulfillmentItemRulesController;
1264
+ }
1265
+ isPickupTimingEnabled() {
1266
+ return this.pickupTimingEnabled && this.otherParams?.enablePickupTiming !== false;
1267
+ }
1268
+ getPickupTimingContextData(key) {
1269
+ const context = this.core?.context;
1270
+ const getData = context?.getData;
1271
+ return typeof getData === 'function' ? getData.call(context, key) : undefined;
1272
+ }
1273
+ getPickupTimingScope() {
1274
+ return JSON.stringify({
1275
+ shop: this.getPickupTimingContextData('shop_id'),
1276
+ businessCode: this.otherParams?.businessCode ?? this.otherParams?.business_code,
1277
+ channel: this.getSubmitOrderSalesChannel(),
1278
+ timezone: this.getPickupTimingContextData('timezone'),
1279
+ platform: (0, _platform.normalizeRuntimePlatform)(this.otherParams?.platform),
1280
+ cacheId: this.cacheId
1281
+ });
1282
+ }
1283
+ resetPickupTimingCache() {
1284
+ this.pickupTimingController?.invalidate();
1285
+ }
1286
+ getPickupTimingController() {
1287
+ if (!this.pickupTimingController) {
1288
+ this.pickupTimingController = new _pickupTiming2.PickupTimingController({
1289
+ getOrder: () => {
1290
+ if (!this.store.order) throw new Error('order 模块未初始化');
1291
+ return this.store.order;
1292
+ },
1293
+ setServiceType: serviceType => this.setServiceType(serviceType),
1294
+ getSchedule: () => this.store.schedule,
1295
+ loadOpenData: force => this.loadPickupTimingOpenData(force),
1296
+ getTimezone: () => String(this.getPickupTimingContextData('timezone') ?? ''),
1297
+ getScope: () => this.getPickupTimingScope(),
1298
+ isPos: () => (0, _platform.normalizeRuntimePlatform)(this.otherParams?.platform) === 'pos'
1299
+ });
1300
+ }
1301
+ return this.pickupTimingController;
1302
+ }
1303
+ getPickupTimingAvailability(params) {
1304
+ return this.getPickupTimingController().getAvailability(params);
1305
+ }
1306
+ getPickupTimingSelection() {
1307
+ return this.getPickupTimingController().getSelection();
1308
+ }
1309
+ setPickupTimingSelection(selection) {
1310
+ return this.getPickupTimingController().setSelection(selection);
1311
+ }
1312
+ validatePickupTimingSelection(selection) {
1313
+ return this.getPickupTimingController().validate(selection);
1314
+ }
1315
+ preparePickupTimingForCheckout(options) {
1316
+ if (!this.isPickupTimingEnabled()) {
1317
+ return Promise.resolve({
1318
+ serviceType: String(this.getTempOrder()?.metadata?.shop_service_data?.service_type || ''),
1319
+ mode: 'asap'
1320
+ });
1321
+ }
1322
+ return this.getPickupTimingController().prepare(options);
1323
+ }
1324
+
1160
1325
  /** Subclasses with target-scoped OpenData override this guard before reusing the cache. */
1161
1326
  isInvoiceOpenDataCacheCurrent() {
1162
1327
  return true;
@@ -1438,6 +1603,7 @@ class BaseSalesImpl extends _BaseModule.BaseModule {
1438
1603
  return isChanged;
1439
1604
  }
1440
1605
  async initialize(core, options = {}) {
1606
+ this.resetPickupTimingCache();
1441
1607
  this.core = core;
1442
1608
  this.initializeOptions = options || {};
1443
1609
  this.paymentNumberDevicePrefix = null;
@@ -1490,6 +1656,8 @@ class BaseSalesImpl extends _BaseModule.BaseModule {
1490
1656
  this.core.effects.emit(`${this.name}:onInited`, {});
1491
1657
  }
1492
1658
  async destroy() {
1659
+ this.resetPickupTimingCache();
1660
+ this.fulfillmentItemRulesController?.invalidate();
1493
1661
  this.serverOrderChangeUnsubscribe?.();
1494
1662
  this.serverOrderChangeUnsubscribe = null;
1495
1663
  this.paymentMethodsChangedUnsubscribe?.();
@@ -1589,9 +1757,12 @@ class BaseSalesImpl extends _BaseModule.BaseModule {
1589
1757
  /** 获取根 booking(is_next === 0 / is_root === 1),多个时取第一个。 */
1590
1758
  getRootBooking() {
1591
1759
  if (!this.currentSalesDetail) return null;
1592
- if (this.currentSalesDetail.rootBooking) return this.currentSalesDetail.rootBooking;
1760
+ if (this.currentSalesDetail.rootBooking && !(0, _pickupTiming.isFulfillmentTimingBooking)(this.currentSalesDetail.rootBooking)) {
1761
+ return this.currentSalesDetail.rootBooking;
1762
+ }
1593
1763
  const bookings = this.currentSalesDetail.bookings || [];
1594
1764
  return bookings.find(booking => {
1765
+ if ((0, _pickupTiming.isFulfillmentTimingBooking)(booking)) return false;
1595
1766
  const parentId = booking?.parent_id;
1596
1767
  return parentId === 0 || parentId === null || parentId === undefined;
1597
1768
  }) || null;
@@ -1704,14 +1875,54 @@ class BaseSalesImpl extends _BaseModule.BaseModule {
1704
1875
  if (!this.store.order) throw new Error('order 模块未初始化');
1705
1876
  return this.store.order.updateTempOrderPickupReferenceMode(mode);
1706
1877
  }
1707
- setServiceType(serviceType) {
1878
+ async setServiceType(serviceType) {
1708
1879
  if (!this.store.order) throw new Error('order 模块未初始化');
1709
- return this.store.order.updateTempOrderServiceType(serviceType);
1880
+ const serviceData = this.store.order.updateTempOrderServiceType(serviceType);
1881
+ await this.getFulfillmentItemRulesController().apply(serviceType);
1882
+ // 费用规则可能增删商品;即使没有普通促销,也要同步页面的购物车和金额快照。
1883
+ const tempOrder = this.getTempOrder();
1884
+ if (tempOrder) await this.effectsEmit('onTempOrderReplaced', {
1885
+ tempOrder
1886
+ });
1887
+ return serviceData;
1888
+ }
1889
+ getPickupTimingBooking() {
1890
+ return this.store.order?.getPickupTimingBooking() || null;
1891
+ }
1892
+ setPickupTimingBooking(input) {
1893
+ if (!this.store.order) throw new Error('order 模块未初始化');
1894
+ return this.store.order.setPickupTimingBooking(input);
1895
+ }
1896
+ clearPickupTimingBooking() {
1897
+ this.store.order?.clearPickupTimingBooking();
1710
1898
  }
1711
1899
  updateTempOrderContactsInfo(contactsInfo) {
1712
1900
  if (!this.store.order) throw new Error('order 模块未初始化');
1713
1901
  return this.store.order.updateTempOrderContactsInfo(contactsInfo);
1714
1902
  }
1903
+ async loadContactInfoOpenData() {
1904
+ return this.otherParams?.openData ?? null;
1905
+ }
1906
+ async getContactInfoConfig() {
1907
+ const scope = this.getPickupTimingScope();
1908
+ const data = await this.loadContactInfoOpenData();
1909
+ if (scope !== this.getPickupTimingScope()) throw new Error('Store settings changed. Please try again.');
1910
+ return (0, _contactInfo.resolveContactInfoConfig)(data);
1911
+ }
1912
+ async validateContactInfo() {
1913
+ const order = this.getTempOrder();
1914
+ const customerId = order?.customer_id;
1915
+ // Missing customer IDs and the reserved Walk-In IDs do not require contact info.
1916
+ if (!Number.isFinite(Number(customerId)) || Number(customerId) <= 1) return;
1917
+ const identity = order?._extend?.localId || order?.external_sale_number || order?.order_id || order;
1918
+ const config = await this.getContactInfoConfig();
1919
+ const current = this.getTempOrder();
1920
+ if (identity !== (current?._extend?.localId || current?.external_sale_number || current?.order_id || current) || customerId !== current?.customer_id) {
1921
+ throw new Error('The order changed. Please try again.');
1922
+ }
1923
+ const missing = (0, _contactInfo.getMissingContactInfoFields)(config, current?.contacts_info);
1924
+ if (missing.length) throw new _contactInfo.ContactInfoRequiredError(missing);
1925
+ }
1715
1926
 
1716
1927
  /** @deprecated OrderModule 不再负责 tempOrder localStorage 持久化。 */
1717
1928
  setEnableTempOrderPersist(enabled) {
@@ -1788,6 +1999,7 @@ class BaseSalesImpl extends _BaseModule.BaseModule {
1788
1999
  async clearOrderCartLines() {
1789
2000
  try {
1790
2001
  if (!this.store.order) throw new Error('order 模块未初始化');
2002
+ this.fulfillmentItemRulesController?.invalidate();
1791
2003
  const tempOrder = await this.store.order.clearOrderCartLines();
1792
2004
  if (this.currentSalesDetail) {
1793
2005
  this.currentSalesDetail = {
@@ -1815,6 +2027,7 @@ class BaseSalesImpl extends _BaseModule.BaseModule {
1815
2027
  /** 切换法币 / WalletPass 单位时清空商品、折扣与支付状态,保留客户。 */
1816
2028
  async clearOrderForSettlementSwitch() {
1817
2029
  if (!this.store.order) throw new Error('order 模块未初始化');
2030
+ this.fulfillmentItemRulesController?.invalidate();
1818
2031
  const tempOrder = await this.store.order.clearOrderForSettlementSwitch();
1819
2032
  if (this.currentSalesDetail) {
1820
2033
  this.currentSalesDetail = {
@@ -2402,7 +2615,7 @@ class BaseSalesImpl extends _BaseModule.BaseModule {
2402
2615
  // Deprecated compatibility alias. New BaseSales callers should use submitSalesOrder.
2403
2616
  return this.submitSalesOrder(params);
2404
2617
  }
2405
- async buildVirtualSettlementSubmitData() {
2618
+ buildVirtualSettlementOrderData() {
2406
2619
  if (!this.store.order) return null;
2407
2620
  const tempOrder = this.store.order.getTempOrder();
2408
2621
  if (!this.isActiveVirtualSettlementOrder(tempOrder)) return null;
@@ -2411,6 +2624,21 @@ class BaseSalesImpl extends _BaseModule.BaseModule {
2411
2624
  if (!snapshot || !Number.isFinite(currencyProductId) || currencyProductId <= 0) {
2412
2625
  throw new Error('Virtual currency settlement configuration is invalid.');
2413
2626
  }
2627
+ return {
2628
+ snapshot,
2629
+ currencyProductId,
2630
+ currencyCode: String(tempOrder?.currency_code || ''),
2631
+ currencySymbol: String(tempOrder?.currency_symbol || ''),
2632
+ currencyFormat: String(tempOrder?.currency_format || 'symbol_first')
2633
+ };
2634
+ }
2635
+ async buildVirtualSettlementSubmitData() {
2636
+ const settlement = this.buildVirtualSettlementOrderData();
2637
+ if (!settlement || !this.store.order) return null;
2638
+ const tempOrder = this.store.order.getTempOrder();
2639
+ const {
2640
+ currencyProductId
2641
+ } = settlement;
2414
2642
  const summary = await this.store.order.recalculateSummary({
2415
2643
  createIfMissing: true
2416
2644
  });
@@ -2425,17 +2653,10 @@ class BaseSalesImpl extends _BaseModule.BaseModule {
2425
2653
  });
2426
2654
  }
2427
2655
  const asset = this.store.order.getVirtualCurrencyList().find(item => Number(item?.product_id) === currencyProductId);
2428
- const currencyCode = String(tempOrder?.currency_code || '');
2429
- const currencySymbol = String(tempOrder?.currency_symbol || '');
2430
- const currencyFormat = String(tempOrder?.currency_format || 'symbol_first');
2431
2656
  if (amount.lte(0)) {
2432
2657
  return {
2433
- payments: [],
2434
- snapshot,
2435
- currencyProductId,
2436
- currencyCode,
2437
- currencySymbol,
2438
- currencyFormat
2658
+ ...settlement,
2659
+ payments: []
2439
2660
  };
2440
2661
  }
2441
2662
  if (!asset) {
@@ -2489,12 +2710,8 @@ class BaseSalesImpl extends _BaseModule.BaseModule {
2489
2710
  };
2490
2711
  }
2491
2712
  return {
2492
- payments: [payment],
2493
- snapshot,
2494
- currencyProductId,
2495
- currencyCode,
2496
- currencySymbol,
2497
- currencyFormat
2713
+ ...settlement,
2714
+ payments: [payment]
2498
2715
  };
2499
2716
  }
2500
2717
 
@@ -2508,11 +2725,49 @@ class BaseSalesImpl extends _BaseModule.BaseModule {
2508
2725
  if (!this.store.order) {
2509
2726
  throw new Error('BaseSales 解决方案需要 order 模块支持');
2510
2727
  }
2511
- const virtualSettlement = await this.buildVirtualSettlementSubmitData();
2728
+ await this.validateContactInfo();
2729
+ if (this.isPickupTimingEnabled()) await this.preparePickupTimingForCheckout({
2730
+ freeze: true
2731
+ });
2732
+ const isDeferredPayment = params?.paymentExecution === 'deferred';
2733
+ const virtualPaymentSettlement = isDeferredPayment ? null : await this.buildVirtualSettlementSubmitData();
2734
+ const virtualSettlement = isDeferredPayment ? this.buildVirtualSettlementOrderData() : virtualPaymentSettlement;
2512
2735
  // this.store.order.persistTempOrder();
2513
- const inferredPaymentStatus = this.getSubmitPaymentStatus(params?.paymentStatus);
2736
+ const inferredPaymentStatus = isDeferredPayment ? undefined : this.getSubmitPaymentStatus(params?.paymentStatus);
2514
2737
  const smallTicketDataFlag = params?.smallTicketDataFlag ?? this.getDefaultCheckoutSmallTicketDataFlag();
2515
- const enhancePayload = this.buildSubmitPayloadEnhancerWithOpenDataConfig(params?.enhancePayload);
2738
+ const virtualSettlementEnhancePayload = virtualSettlement ? (payload, ctx) => {
2739
+ const callerPayload = params?.enhancePayload ? params.enhancePayload(payload, ctx) : payload;
2740
+ const currencySettlementConfig = callerPayload.metadata?.currency_settlement_config || {};
2741
+ return {
2742
+ ...callerPayload,
2743
+ metadata: {
2744
+ ...(callerPayload.metadata || {}),
2745
+ currency_settlement_config: {
2746
+ ...currencySettlementConfig,
2747
+ settlement_type: 'virtual_currency',
2748
+ settlement_ids: [virtualSettlement.currencyProductId],
2749
+ currency_display_snapshot: (0, _lodashEs.cloneDeep)(currencySettlementConfig.currency_display_snapshot ?? (0, _virtualSettlement.buildCurrencyDisplaySnapshot)(virtualSettlement.snapshot))
2750
+ }
2751
+ },
2752
+ currency_code: virtualSettlement.currencyCode,
2753
+ currency_symbol: virtualSettlement.currencySymbol,
2754
+ currency_format: virtualSettlement.currencyFormat
2755
+ };
2756
+ } : params?.enhancePayload;
2757
+ const configuredEnhancePayload = this.buildSubmitPayloadEnhancerWithOpenDataConfig(virtualSettlementEnhancePayload);
2758
+ // Apply this last so neither a stale draft status nor a caller enhancer can
2759
+ // turn deferred submission into payment. The live wallet selection is untouched.
2760
+ const enhancePayload = isDeferredPayment ? (payload, ctx) => {
2761
+ const enhancedPayload = configuredEnhancePayload ? configuredEnhancePayload(payload, ctx) : payload;
2762
+ const {
2763
+ payment_status: _paymentStatus,
2764
+ ...orderPayload
2765
+ } = enhancedPayload;
2766
+ return {
2767
+ ...orderPayload,
2768
+ payments: []
2769
+ };
2770
+ } : configuredEnhancePayload;
2516
2771
  const submitParams = {
2517
2772
  cacheId: this.cacheId,
2518
2773
  platform: this.otherParams?.platform,
@@ -2520,8 +2775,10 @@ class BaseSalesImpl extends _BaseModule.BaseModule {
2520
2775
  channel: this.getSubmitOrderSalesChannel(),
2521
2776
  type: this.otherParams?.type,
2522
2777
  request_unique_idempotency_token: this.store.order.generateIdempotencyToken(),
2523
- ...(virtualSettlement ? {
2524
- payments: virtualSettlement.payments
2778
+ ...(isDeferredPayment ? {
2779
+ payments: []
2780
+ } : virtualPaymentSettlement ? {
2781
+ payments: virtualPaymentSettlement.payments
2525
2782
  } : params?.payments !== undefined ? {
2526
2783
  payments: params.payments
2527
2784
  } : {}),
@@ -2531,7 +2788,7 @@ class BaseSalesImpl extends _BaseModule.BaseModule {
2531
2788
  ...(smallTicketDataFlag !== undefined ? {
2532
2789
  smallTicketDataFlag
2533
2790
  } : {}),
2534
- ...(virtualSettlement ? {
2791
+ ...(isDeferredPayment || virtualSettlement ? {
2535
2792
  confirmPendingVoucherPayments: false
2536
2793
  } : params?.confirmPendingVoucherPayments !== undefined ? {
2537
2794
  confirmPendingVoucherPayments: params.confirmPendingVoucherPayments
@@ -2562,10 +2819,110 @@ class BaseSalesImpl extends _BaseModule.BaseModule {
2562
2819
  };
2563
2820
  return this.shouldUseCheckoutSyncTask() ? this.store.order.submitTempOrder(submitParams) : this.store.order.submitTempOrderAsync(submitParams);
2564
2821
  }
2822
+ pendingLocalEftposFinalizations = new Map();
2823
+ async finalizeLocalEftposPayment(params) {
2824
+ const key = `${params.saleId}:${params.paymentNumber}`;
2825
+ // If SQLite rejects the write, retry this known result before another payment can start.
2826
+ this.pendingLocalEftposFinalizations.set(key, params);
2827
+ const response = await this.request.post('/local/finalizeLocalEftposPayment', {
2828
+ sale_id: params.saleId,
2829
+ payment_number: params.paymentNumber,
2830
+ result: params.result,
2831
+ failed_transaction_numbers: params.failedTransactionNumbers
2832
+ }, {
2833
+ osServer: true,
2834
+ customToast: () => {}
2835
+ });
2836
+ if (response?.code !== 200 || !response?.data?.order) {
2837
+ throw new Error(response?.message || '本地支付收尾失败,请刷新订单后重试');
2838
+ }
2839
+ this.pendingLocalEftposFinalizations.delete(key);
2840
+ const {
2841
+ order,
2842
+ payment
2843
+ } = response.data;
2844
+ const temp = this.getTempOrder();
2845
+ const isCurrentOrder = String(temp?.external_sale_number || temp?.order_id) === String(params.saleId) || temp?.order_id && String(temp.order_id) === String(order.order_id);
2846
+ if (isCurrentOrder) {
2847
+ // Persisted by the local route already. Saving a draft here would re-enable protection.
2848
+ if (temp?.payments?.some(item => String(item.payment_number) === params.paymentNumber)) {
2849
+ await this.updateOrderPayment(params.paymentNumber, payment, {
2850
+ persistDraft: false,
2851
+ submitWhenPaid: false
2852
+ });
2853
+ }
2854
+ if ((0, _localEftposState.requiresLocalPaymentRefresh)(order)) {
2855
+ // Set the in-memory gate before the GET so a failed refresh also blocks this page.
2856
+ const currentTemp = this.getTempOrder();
2857
+ if (currentTemp) currentTemp.metadata = {
2858
+ ...currentTemp.metadata,
2859
+ ...order.metadata
2860
+ };
2861
+ try {
2862
+ await this.loadSalesDetail({
2863
+ orderId: order.order_id,
2864
+ forceRemote: true,
2865
+ merge: false
2866
+ });
2867
+ } catch {
2868
+ throw new Error(_localEftposState.PAYMENT_REFRESH_MESSAGE);
2869
+ }
2870
+ }
2871
+ } else if ((0, _localEftposState.requiresLocalPaymentRefresh)(order)) {
2872
+ const refreshed = await this.store.order?.getSalesOrderByLookup({
2873
+ lookup: order.order_id,
2874
+ forceRemote: true
2875
+ });
2876
+ if (refreshed?.code !== 200) throw new Error(_localEftposState.PAYMENT_REFRESH_MESSAGE);
2877
+ }
2878
+ return response.data;
2879
+ }
2880
+
2881
+ /** Entry gate also reads SQLite state, so stale sessions/restarts cannot bypass it. */
2882
+ async ensureSalesOrderReadyForPayment() {
2883
+ const temp = this.getTempOrder();
2884
+ const unfinished = [...this.pendingLocalEftposFinalizations.values()].filter(params => String(params.saleId) === String(temp?.external_sale_number) || String(params.saleId) === String(temp?.order_id));
2885
+ if (unfinished.length > 0) {
2886
+ for (const params of unfinished) await this.finalizeLocalEftposPayment(params);
2887
+ throw new Error('订单已更新,请确认商品和金额后重新付款');
2888
+ }
2889
+ await this.validateContactInfo();
2890
+ const lookup = temp?.order_id || temp?.external_sale_number;
2891
+ let localOrder = null;
2892
+ if (lookup && this.core?.server?.hasRoute?.('post', '/shop/local/orderPaymentReadiness')) {
2893
+ const response = await this.request.post('/local/orderPaymentReadiness', {
2894
+ sale_id: lookup
2895
+ }, {
2896
+ osServer: true,
2897
+ customToast: () => {}
2898
+ });
2899
+ if (response?.code !== 200) throw new Error(_localEftposState.PAYMENT_REFRESH_MESSAGE);
2900
+ localOrder = response.data;
2901
+ }
2902
+ if (!(0, _localEftposState.requiresLocalPaymentRefresh)(temp) && !(0, _localEftposState.requiresLocalPaymentRefresh)(localOrder)) return;
2903
+ const orderId = localOrder?.order_id || temp?.order_id;
2904
+ if (!orderId || String(orderId) === String(temp?.external_sale_number)) {
2905
+ throw new Error(_localEftposState.PAYMENT_REFRESH_MESSAGE);
2906
+ }
2907
+ try {
2908
+ await this.loadSalesDetail({
2909
+ orderId,
2910
+ forceRemote: true,
2911
+ merge: false
2912
+ });
2913
+ } catch {
2914
+ throw new Error(_localEftposState.PAYMENT_REFRESH_MESSAGE);
2915
+ }
2916
+ // The initiating UI may have captured the old amount before this await.
2917
+ throw new Error('订单已刷新,请确认商品和金额后重新付款');
2918
+ }
2565
2919
  async saveSalesOrderDraft(params) {
2566
2920
  if (!this.store.order) {
2567
2921
  throw new Error('BaseSales 解决方案需要 order 模块支持');
2568
2922
  }
2923
+ if (this.isPickupTimingEnabled()) await this.preparePickupTimingForCheckout({
2924
+ freeze: false
2925
+ });
2569
2926
  return this.store.order.saveTempOrderAsDraft({
2570
2927
  cacheId: this.cacheId,
2571
2928
  platform: params?.platform ?? this.otherParams?.platform,
@@ -2581,6 +2938,10 @@ class BaseSalesImpl extends _BaseModule.BaseModule {
2581
2938
  if (!this.store.order) {
2582
2939
  throw new Error('BaseSales 解决方案需要 order 模块支持');
2583
2940
  }
2941
+ await this.validateContactInfo();
2942
+ if (this.isPickupTimingEnabled()) await this.preparePickupTimingForCheckout({
2943
+ freeze: true
2944
+ });
2584
2945
  const inferredPaymentStatus = this.getSubmitPaymentStatus(params?.paymentStatus);
2585
2946
  const smallTicketDataFlag = params?.smallTicketDataFlag ?? this.getDefaultCheckoutSmallTicketDataFlag();
2586
2947
  const enhancePayload = this.buildSubmitPayloadEnhancerWithOpenDataConfig(params?.enhancePayload);
@@ -2996,7 +3357,7 @@ class BaseSalesImpl extends _BaseModule.BaseModule {
2996
3357
 
2997
3358
  const walletProducts = tempOrder?.products.map(product => {
2998
3359
  const metadata = product.metadata || {};
2999
- const rawHolderId = product.holder_id ?? metadata.holder_id;
3360
+ const rawHolderId = (0, _holderRelations.hasProductBooking)(product) ? product.holder_id ?? metadata.holder_id : (0, _holderRelations.getProductHolderRelations)(tempOrder, product).filter(relation => relation.holder_type === 'form').map(relation => relation.holder_id);
3000
3361
  const holderIdValue = Array.isArray(rawHolderId) ? rawHolderId[0] : rawHolderId;
3001
3362
  const holder_id = Number.isInteger(Number(holderIdValue)) ? Number(holderIdValue) : undefined;
3002
3363
  return {
@@ -3412,6 +3773,7 @@ class BaseSalesImpl extends _BaseModule.BaseModule {
3412
3773
  */
3413
3774
  async confirmWalletPayment() {
3414
3775
  if (!this.store.order) throw new Error('order 模块未初始化');
3776
+ await this.validateContactInfo();
3415
3777
  const beforePayments = (0, _lodashEs.cloneDeep)(this.store.order.getOrderPayments());
3416
3778
  const pendingWalletAmount = this.getPendingWalletPaymentAmount(beforePayments);
3417
3779
  const amountSnapshot = this.store.order.getOrderAmountSnapshot?.();
@@ -3483,6 +3845,7 @@ class BaseSalesImpl extends _BaseModule.BaseModule {
3483
3845
  */
3484
3846
  async payCash(params) {
3485
3847
  if (!this.store.order) throw new Error('order 模块未初始化');
3848
+ await this.validateContactInfo();
3486
3849
  const amount = Number(params.amount || 0);
3487
3850
  if (!Number.isFinite(amount) || amount <= 0) {
3488
3851
  throw new Error('现金支付金额必须大于 0');
@@ -3508,6 +3871,9 @@ class BaseSalesImpl extends _BaseModule.BaseModule {
3508
3871
  if (!Number.isFinite(actualPaidAmount) || actualPaidAmount + 0.01 < amount) {
3509
3872
  throw new Error('顾客实收现金不能小于支付金额');
3510
3873
  }
3874
+ if (this.isPickupTimingEnabled()) await this.preparePickupTimingForCheckout({
3875
+ freeze: true
3876
+ });
3511
3877
  this.window?.interaction?.utils?.postMessageToApp?.({
3512
3878
  module: 'till',
3513
3879
  key: 'open_till',
@@ -3887,7 +4253,7 @@ class BaseSalesImpl extends _BaseModule.BaseModule {
3887
4253
  async addProductToOrder(product, booking, options) {
3888
4254
  try {
3889
4255
  if (!this.store.order) throw new Error('order 模块未初始化');
3890
- const products = await this.store.order.addProductToOrder(product, booking, options);
4256
+ const products = options === undefined ? await this.store.order.addProductToOrder(product, booking) : await this.store.order.addProductToOrder(product, booking, options);
3891
4257
  // // 需要叠加
3892
4258
  // if (this.hooks.onAddProductOverlay) {
3893
4259
  // const res = this.hooks.onAddProductOverlay(是否叠加, product)
@@ -3976,7 +4342,7 @@ class BaseSalesImpl extends _BaseModule.BaseModule {
3976
4342
  async removeProductsFromOrder(identities, options) {
3977
4343
  try {
3978
4344
  if (!this.store.order) throw new Error('order 模块未初始化');
3979
- return await this.store.order.removeProductsFromOrder(identities, options);
4345
+ return options === undefined ? await this.store.order.removeProductsFromOrder(identities) : await this.store.order.removeProductsFromOrder(identities, options);
3980
4346
  } catch (error) {
3981
4347
  throw error;
3982
4348
  }
@@ -4061,6 +4427,7 @@ class BaseSalesImpl extends _BaseModule.BaseModule {
4061
4427
  async setOtherParams(params, {
4062
4428
  cover = false
4063
4429
  } = {}) {
4430
+ const previousPickupTimingScope = this.getPickupTimingScope();
4064
4431
  if (cover) {
4065
4432
  this.otherParams = {
4066
4433
  ...params
@@ -4072,6 +4439,7 @@ class BaseSalesImpl extends _BaseModule.BaseModule {
4072
4439
  };
4073
4440
  }
4074
4441
  this.cacheId = this.otherParams?.cacheId;
4442
+ if (previousPickupTimingScope !== this.getPickupTimingScope()) this.resetPickupTimingCache();
4075
4443
  await this.syncOtherParamsToSubModules(params, {
4076
4444
  cover
4077
4445
  });