@pisell/pisellos 0.10.7 → 0.10.9

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 (124) hide show
  1. package/dist/core/index.js +0 -1
  2. package/dist/model/strategy/adapter/dataVariant/evaluator.d.ts +18 -2
  3. package/dist/model/strategy/adapter/dataVariant/evaluator.js +287 -101
  4. package/dist/model/strategy/adapter/dataVariant/type.d.ts +25 -0
  5. package/dist/model/strategy/adapter/walletPass/evaluator.js +0 -2
  6. package/dist/model/strategy/adapter/walletPass/type.d.ts +5 -0
  7. package/dist/model/strategy/adapter/walletPass/utils.js +57 -26
  8. package/dist/modules/BookingContext/utils/buildCacheItemFromOrderLine.js +18 -12
  9. package/dist/modules/BookingContext/utils/productExtend.js +5 -9
  10. package/dist/modules/BookingContext/utils/timeSlices.js +24 -3
  11. package/dist/modules/Customer/index.d.ts +4 -0
  12. package/dist/modules/Customer/index.js +91 -59
  13. package/dist/modules/Customer/types.d.ts +2 -0
  14. package/dist/modules/Discount/index.d.ts +2 -0
  15. package/dist/modules/Discount/index.js +38 -5
  16. package/dist/modules/Discount/types.d.ts +11 -0
  17. package/dist/modules/Order/index.d.ts +32 -6
  18. package/dist/modules/Order/index.js +1165 -819
  19. package/dist/modules/Order/types.d.ts +32 -4
  20. package/dist/modules/Order/types.js +11 -0
  21. package/dist/modules/Order/utils/discountProductLineIdentity.d.ts +45 -0
  22. package/dist/modules/Order/utils/discountProductLineIdentity.js +227 -0
  23. package/dist/modules/Order/utils/virtualSettlement.d.ts +56 -0
  24. package/dist/modules/Order/utils/virtualSettlement.js +271 -0
  25. package/dist/modules/Order/utils.js +89 -48
  26. package/dist/modules/Payment/types.d.ts +2 -0
  27. package/dist/modules/Payment/utils.js +4 -1
  28. package/dist/modules/Payment/walletpass.d.ts +11 -0
  29. package/dist/modules/Payment/walletpass.js +235 -60
  30. package/dist/modules/Product/types.d.ts +23 -0
  31. package/dist/modules/ProductList/index.d.ts +15 -2
  32. package/dist/modules/ProductList/index.js +92 -13
  33. package/dist/modules/Rules/index.js +125 -52
  34. package/dist/modules/SalesSummary/index.d.ts +1 -0
  35. package/dist/modules/SalesSummary/index.js +4 -3
  36. package/dist/modules/SalesSummary/types.d.ts +1 -0
  37. package/dist/modules/SalesSummary/utils.d.ts +1 -0
  38. package/dist/modules/SalesSummary/utils.js +28 -2
  39. package/dist/server/index.d.ts +15 -0
  40. package/dist/server/index.js +894 -781
  41. package/dist/server/modules/order/index.d.ts +2 -0
  42. package/dist/server/modules/order/index.js +261 -96
  43. package/dist/server/modules/order/types.d.ts +1 -1
  44. package/dist/server/utils/small-ticket.js +2 -1
  45. package/dist/solution/BaseSales/index.d.ts +41 -7
  46. package/dist/solution/BaseSales/index.js +1405 -974
  47. package/dist/solution/BaseSales/utils/cartPromotion.js +26 -3
  48. package/dist/solution/BaseSales/utils/transformBaseProductToOrderProduct.d.ts +1 -0
  49. package/dist/solution/BaseSales/utils/transformBaseProductToOrderProduct.js +79 -13
  50. package/dist/solution/BaseSales/utils.js +31 -20
  51. package/dist/solution/BookingByStep/index.d.ts +3 -3
  52. package/dist/solution/BookingTicket/index.d.ts +5 -1
  53. package/dist/solution/BookingTicket/index.js +221 -162
  54. package/dist/solution/BookingTicket/types.d.ts +3 -0
  55. package/dist/solution/BookingTicket/utils/addProductDecision.js +2 -1
  56. package/dist/solution/BookingTicket/utils/cartView.js +44 -32
  57. package/dist/solution/Sales/index.d.ts +1 -0
  58. package/dist/solution/Sales/index.js +16 -2
  59. package/dist/solution/ScanOrder/index.js +25 -64
  60. package/dist/solution/ScanOrder/utils.js +31 -20
  61. package/dist/solution/UnifiedBookingSales/index.d.ts +5 -4
  62. package/dist/solution/UnifiedBookingSales/index.js +51 -39
  63. package/lib/core/index.js +0 -1
  64. package/lib/model/strategy/adapter/dataVariant/evaluator.d.ts +18 -2
  65. package/lib/model/strategy/adapter/dataVariant/evaluator.js +170 -7
  66. package/lib/model/strategy/adapter/dataVariant/type.d.ts +25 -0
  67. package/lib/model/strategy/adapter/walletPass/evaluator.js +0 -2
  68. package/lib/model/strategy/adapter/walletPass/type.d.ts +5 -0
  69. package/lib/model/strategy/adapter/walletPass/utils.js +62 -11
  70. package/lib/modules/BookingContext/utils/buildCacheItemFromOrderLine.js +6 -1
  71. package/lib/modules/BookingContext/utils/productExtend.js +4 -3
  72. package/lib/modules/BookingContext/utils/timeSlices.js +18 -3
  73. package/lib/modules/Customer/index.d.ts +4 -0
  74. package/lib/modules/Customer/index.js +11 -0
  75. package/lib/modules/Customer/types.d.ts +2 -0
  76. package/lib/modules/Discount/index.d.ts +2 -0
  77. package/lib/modules/Discount/index.js +37 -3
  78. package/lib/modules/Discount/types.d.ts +11 -0
  79. package/lib/modules/Order/index.d.ts +32 -6
  80. package/lib/modules/Order/index.js +518 -148
  81. package/lib/modules/Order/types.d.ts +32 -4
  82. package/lib/modules/Order/utils/discountProductLineIdentity.d.ts +45 -0
  83. package/lib/modules/Order/utils/discountProductLineIdentity.js +170 -0
  84. package/lib/modules/Order/utils/virtualSettlement.d.ts +56 -0
  85. package/lib/modules/Order/utils/virtualSettlement.js +300 -0
  86. package/lib/modules/Order/utils.js +65 -21
  87. package/lib/modules/Payment/types.d.ts +2 -0
  88. package/lib/modules/Payment/utils.js +5 -1
  89. package/lib/modules/Payment/walletpass.d.ts +11 -0
  90. package/lib/modules/Payment/walletpass.js +198 -17
  91. package/lib/modules/Product/types.d.ts +23 -0
  92. package/lib/modules/ProductList/index.d.ts +15 -2
  93. package/lib/modules/ProductList/index.js +85 -3
  94. package/lib/modules/Rules/index.js +100 -20
  95. package/lib/modules/SalesSummary/index.d.ts +1 -0
  96. package/lib/modules/SalesSummary/index.js +10 -2
  97. package/lib/modules/SalesSummary/types.d.ts +1 -0
  98. package/lib/modules/SalesSummary/utils.d.ts +1 -0
  99. package/lib/modules/SalesSummary/utils.js +30 -2
  100. package/lib/server/index.d.ts +15 -0
  101. package/lib/server/index.js +105 -14
  102. package/lib/server/modules/order/index.d.ts +2 -0
  103. package/lib/server/modules/order/index.js +75 -5
  104. package/lib/server/modules/order/types.d.ts +1 -1
  105. package/lib/server/utils/small-ticket.js +1 -1
  106. package/lib/solution/BaseSales/index.d.ts +41 -7
  107. package/lib/solution/BaseSales/index.js +325 -39
  108. package/lib/solution/BaseSales/utils/cartPromotion.js +26 -2
  109. package/lib/solution/BaseSales/utils/transformBaseProductToOrderProduct.d.ts +1 -0
  110. package/lib/solution/BaseSales/utils/transformBaseProductToOrderProduct.js +62 -12
  111. package/lib/solution/BaseSales/utils.js +29 -18
  112. package/lib/solution/BookingByStep/index.d.ts +3 -3
  113. package/lib/solution/BookingTicket/index.d.ts +5 -1
  114. package/lib/solution/BookingTicket/index.js +31 -6
  115. package/lib/solution/BookingTicket/types.d.ts +3 -0
  116. package/lib/solution/BookingTicket/utils/addProductDecision.js +2 -1
  117. package/lib/solution/BookingTicket/utils/cartView.js +43 -30
  118. package/lib/solution/Sales/index.d.ts +1 -0
  119. package/lib/solution/Sales/index.js +15 -3
  120. package/lib/solution/ScanOrder/index.js +17 -54
  121. package/lib/solution/ScanOrder/utils.js +29 -18
  122. package/lib/solution/UnifiedBookingSales/index.d.ts +5 -4
  123. package/lib/solution/UnifiedBookingSales/index.js +35 -26
  124. package/package.json +1 -1
@@ -18,6 +18,19 @@ export declare class ProductList extends BaseModule implements Module {
18
18
  * productList.updateOtherParams({ channel: 'pos' });
19
19
  */
20
20
  updateOtherParams(params: Record<string, any>): void;
21
+ /**
22
+ * 判断当前商品查询是否会被本地 OS Server 接管。
23
+ *
24
+ * 不能只看请求参数中的 osServer=true:H5 RequestPlugin 会透传该参数,但仍然直接请求远端接口。
25
+ * 这里按 RequestPlugin 实际使用的 baseUrl 检查已注册路由,避免 OS Server 与客户端重复执行
26
+ * Data Variant 评估。
27
+ */
28
+ private isProductQueryHandledByOsServer;
29
+ /**
30
+ * 未启用商品 OS 路由时,在客户端补做 Data Variant 解析。
31
+ * OS Server 已接管时直接信任服务端结果,确保同一批商品只评估一次。
32
+ */
33
+ private resolveClientDataVariants;
21
34
  storeChange(path?: string, value?: any): Promise<void>;
22
35
  /**
23
36
  * 获取加时商品列表。
@@ -25,11 +38,11 @@ export declare class ProductList extends BaseModule implements Module {
25
38
  * @example
26
39
  * const products = await productList.getAddTimeProducts({ schedule_date: '2026-06-16' });
27
40
  */
28
- getAddTimeProducts(params?: ProductListLoadProductsParams): Promise<any>;
41
+ getAddTimeProducts(params?: ProductListLoadProductsParams): Promise<ProductData[]>;
29
42
  loadProducts({ category_ids, product_ids, collection, menu_list_ids, customer_id: paramsCustomerId, with_count, schedule_datetime, schedule_date, cacheId, with_schedule, extension_type, status, strategy_context, }?: ProductListLoadProductsParams, options?: {
30
43
  callback?: (result: any) => void;
31
44
  subscriberId?: string;
32
- }): Promise<any>;
45
+ }): Promise<ProductData[]>;
33
46
  loadProductsPrice({ ids, customer_id, schedule_date, channel, }: {
34
47
  ids?: number[];
35
48
  customer_id?: number;
@@ -67,6 +67,62 @@ var ProductList = class extends import_BaseModule.BaseModule {
67
67
  updateOtherParams(params) {
68
68
  this.otherParams = params || {};
69
69
  }
70
+ /**
71
+ * 判断当前商品查询是否会被本地 OS Server 接管。
72
+ *
73
+ * 不能只看请求参数中的 osServer=true:H5 RequestPlugin 会透传该参数,但仍然直接请求远端接口。
74
+ * 这里按 RequestPlugin 实际使用的 baseUrl 检查已注册路由,避免 OS Server 与客户端重复执行
75
+ * Data Variant 评估。
76
+ */
77
+ isProductQueryHandledByOsServer() {
78
+ var _a, _b;
79
+ const server = (_a = this.core) == null ? void 0 : _a.server;
80
+ if (!server || typeof server.hasRoute !== "function")
81
+ return false;
82
+ const requestBaseUrl = String(
83
+ ((_b = this.request) == null ? void 0 : _b.baseUrl) || "/shop"
84
+ ).replace(/\/+$/, "");
85
+ const routePath = `${requestBaseUrl}/product/query`;
86
+ return server.hasRoute("post", routePath) === true;
87
+ }
88
+ /**
89
+ * 未启用商品 OS 路由时,在客户端补做 Data Variant 解析。
90
+ * OS Server 已接管时直接信任服务端结果,确保同一批商品只评估一次。
91
+ */
92
+ resolveClientDataVariants(products, queryPayload, handledByOsServer) {
93
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i;
94
+ if (handledByOsServer || products.length === 0)
95
+ return products;
96
+ const evaluator = (_b = (_a = this.core) == null ? void 0 : _a.context) == null ? void 0 : _b.dataVariantEvaluator;
97
+ if (!evaluator || typeof evaluator.resolveProducts !== "function") {
98
+ return products;
99
+ }
100
+ const strategyContext = queryPayload.strategy_context && typeof queryPayload.strategy_context === "object" ? queryPayload.strategy_context : {};
101
+ const fatherModule = String(((_c = this.otherParams) == null ? void 0 : _c.fatherModule) || "").trim();
102
+ const scheduleModule = (fatherModule ? this.core.getModule(`${fatherModule}_schedule`) : null) || this.core.getModule("schedule");
103
+ const scheduleList = (_d = scheduleModule == null ? void 0 : scheduleModule.getScheduleList) == null ? void 0 : _d.call(scheduleModule);
104
+ const contextScheduleList = (_f = (_e = this.core) == null ? void 0 : _e.context) == null ? void 0 : _f.scheduleList;
105
+ const contextMenuList = (_h = (_g = this.core) == null ? void 0 : _g.context) == null ? void 0 : _h.menuList;
106
+ const businessData = {
107
+ products,
108
+ scheduleDateTime: queryPayload.schedule_datetime || queryPayload.schedule_date,
109
+ scheduleList: Array.isArray(scheduleList) ? scheduleList : Array.isArray(contextScheduleList) ? contextScheduleList : [],
110
+ menuList: Array.isArray((_i = this.otherParams) == null ? void 0 : _i.menuList) ? this.otherParams.menuList : Array.isArray(contextMenuList) ? contextMenuList : [],
111
+ customerId: queryPayload.customer_id,
112
+ channel: strategyContext.channel,
113
+ orderType: strategyContext.orderType || strategyContext.order_type,
114
+ businessCode: strategyContext.business_code ?? strategyContext.businessCode,
115
+ availableWalletIds: strategyContext.available_wallet_ids,
116
+ custom: strategyContext
117
+ };
118
+ try {
119
+ const result = evaluator.resolveProducts(businessData);
120
+ return Array.isArray(result == null ? void 0 : result.products) ? result.products : products;
121
+ } catch (error) {
122
+ console.error("[ProductList] Data Variant 客户端解析失败", error);
123
+ return products;
124
+ }
125
+ }
70
126
  async storeChange(path, value) {
71
127
  }
72
128
  /**
@@ -151,15 +207,41 @@ var ProductList = class extends import_BaseModule.BaseModule {
151
207
  is_eject: 1,
152
208
  with_schedule,
153
209
  schedule_datetime,
154
- extension_type
210
+ extension_type,
211
+ strategy_context
155
212
  };
213
+ const handledByOsServer = this.isProductQueryHandledByOsServer();
214
+ const callback = !handledByOsServer && typeof (options == null ? void 0 : options.callback) === "function" ? (result) => {
215
+ var _a2, _b2, _c;
216
+ const callbackList = (_a2 = result == null ? void 0 : result.data) == null ? void 0 : _a2.list;
217
+ if (!Array.isArray(callbackList)) {
218
+ (_b2 = options.callback) == null ? void 0 : _b2.call(options, result);
219
+ return;
220
+ }
221
+ (_c = options.callback) == null ? void 0 : _c.call(options, {
222
+ ...result,
223
+ data: {
224
+ ...result.data,
225
+ list: this.resolveClientDataVariants(
226
+ callbackList,
227
+ queryPayload,
228
+ false
229
+ )
230
+ }
231
+ });
232
+ } : options == null ? void 0 : options.callback;
156
233
  const productsData = await this.request.post(
157
234
  `/product/query`,
158
235
  queryPayload,
159
- { osServer: true, callback: options == null ? void 0 : options.callback, subscriberId: options == null ? void 0 : options.subscriberId, customToast: () => {
236
+ { osServer: true, callback, subscriberId: options == null ? void 0 : options.subscriberId, customToast: () => {
160
237
  } }
161
238
  );
162
- const sortedList = (productsData.data.list || []).slice().sort((a, b) => Number(b.sort) - Number(a.sort));
239
+ const resolvedList = this.resolveClientDataVariants(
240
+ productsData.data.list || [],
241
+ queryPayload,
242
+ handledByOsServer
243
+ );
244
+ const sortedList = resolvedList.slice().sort((a, b) => Number(b.sort) - Number(a.sort));
163
245
  this.addProduct(sortedList);
164
246
  return sortedList;
165
247
  }
@@ -243,7 +243,7 @@ var RulesModule = class extends import_BaseModule.BaseModule {
243
243
  const discountType = selectedDiscount.tag || selectedDiscount.type;
244
244
  return selectedDiscount.isEditMode === true && selectedDiscount.isSelected === true && ["good_pass", "discount_card", "product_discount_card"].includes(discountType);
245
245
  };
246
- const resolveReapplyEditModeDiscountPercent = (originProduct, selectedDiscount) => {
246
+ const resolveReapplyBookingDiscountPercent = (originProduct, selectedDiscount) => {
247
247
  var _a2;
248
248
  if (!shouldReapplyBookingDiscount(originProduct, selectedDiscount))
249
249
  return void 0;
@@ -318,6 +318,9 @@ var RulesModule = class extends import_BaseModule.BaseModule {
318
318
  bundleIndex,
319
319
  // 虚拟商品属性
320
320
  price: Number(bundleItem.price || 0),
321
+ // 新增商品运行态通常带 _bundle_product_id;订单 hydrate 后只有
322
+ // bundle_product_id。Change time 必须兼容两种结构,否则 bundle
323
+ // 子项无法命中折扣卡的 limited_relation_product_data.product_ids。
321
324
  id: bundleItem.bundle_product_id ?? bundleItem._bundle_product_id ?? bundleItem.product_id,
322
325
  _id: `${product._id}_bundle_${bundleIndex}`,
323
326
  parentId: product._id,
@@ -779,13 +782,6 @@ var RulesModule = class extends import_BaseModule.BaseModule {
779
782
  if (isPersistedProduct && hasExistingWalletDiscount) {
780
783
  if (flatItem.type === "main") {
781
784
  processedProductsMap.set(product._id, [originProduct]);
782
- } else {
783
- processedFlatItemsMap.set(flatItem._id, [
784
- {
785
- ...flatItem,
786
- processed: true
787
- }
788
- ]);
789
785
  }
790
786
  return;
791
787
  }
@@ -862,15 +858,46 @@ var RulesModule = class extends import_BaseModule.BaseModule {
862
858
  }
863
859
  return false;
864
860
  });
865
- const explicitlySelectedDiscountCard = (options == null ? void 0 : options.isSelected) === true && options.discountId !== void 0 ? applicableDiscounts.find((discount) => {
861
+ const explicitlySelectedDiscount = (options == null ? void 0 : options.isSelected) === true && options.discountId !== void 0 ? applicableDiscounts.find((discount) => {
866
862
  const discountType = discount.tag || discount.type;
867
- return String(discount.id) === String(options.discountId) && ["discount_card", "product_discount_card"].includes(discountType);
863
+ return String(discount.id) === String(options.discountId) && ["good_pass", "discount_card", "product_discount_card"].includes(
864
+ discountType
865
+ );
868
866
  }) : void 0;
867
+ const explicitlySelectedDiscountCard = explicitlySelectedDiscount && (explicitlySelectedDiscount.tag || explicitlySelectedDiscount.type) !== "good_pass" ? explicitlySelectedDiscount : void 0;
868
+ const appliedDiscountCardIds = new Set(
869
+ (product.discount_list || []).filter(
870
+ (item) => ["discount_card", "product_discount_card"].includes(
871
+ (item == null ? void 0 : item.tag) || (item == null ? void 0 : item.type)
872
+ )
873
+ ).map(
874
+ (item) => {
875
+ var _a3;
876
+ return ((_a3 = item == null ? void 0 : item.discount) == null ? void 0 : _a3.resource_id) ?? (item == null ? void 0 : item.resource_id) ?? (item == null ? void 0 : item.id);
877
+ }
878
+ ).filter((id) => id !== void 0 && id !== null).map(String)
879
+ );
880
+ const appliedDiscountCard = (options == null ? void 0 : options.scan) ? void 0 : applicableDiscounts.find((discount) => {
881
+ const discountType = discount.tag || discount.type;
882
+ return discount.isSelected === true && ["discount_card", "product_discount_card"].includes(discountType) && appliedDiscountCardIds.has(String(discount.id));
883
+ });
869
884
  const scannedSelectedDiscountCard = applicableDiscounts.find(
870
885
  (n) => n.isScan && n.isSelected && (n.tag || n.type) !== "good_pass"
871
886
  );
872
- const selectedDiscountCard = explicitlySelectedDiscountCard || scannedSelectedDiscountCard;
873
- const selectedDiscount = selectedDiscountCard || applicableDiscounts[0];
887
+ const selectedDiscountCard = explicitlySelectedDiscountCard || appliedDiscountCard || scannedSelectedDiscountCard;
888
+ const selectedDiscount = explicitlySelectedDiscount || selectedDiscountCard || applicableDiscounts[0];
889
+ const prioritizedApplicableDiscounts = explicitlySelectedDiscount && (explicitlySelectedDiscount.tag || explicitlySelectedDiscount.type) === "good_pass" ? [
890
+ explicitlySelectedDiscount,
891
+ ...applicableDiscounts.filter(
892
+ (discount) => {
893
+ const discountType = discount.tag || discount.type;
894
+ return discountType === "good_pass" && String(discount.id) !== String(explicitlySelectedDiscount.id);
895
+ }
896
+ ),
897
+ ...applicableDiscounts.filter(
898
+ (discount) => (discount.tag || discount.type) !== "good_pass"
899
+ )
900
+ ] : applicableDiscounts;
874
901
  let isManualDiscount = false;
875
902
  let isItemRewardProductDiscount = false;
876
903
  if (flatItem.type === "main") {
@@ -1049,21 +1076,65 @@ var RulesModule = class extends import_BaseModule.BaseModule {
1049
1076
  const arr = [];
1050
1077
  if (flatItem.type === "main") {
1051
1078
  if (splitCount < totalQuantity && isNeedSplit) {
1079
+ const remainingQuantity = totalQuantity - splitCount;
1080
+ const applicableNonGoodPassById = new Map(
1081
+ applicableDiscounts.filter((discount) => (discount.tag || discount.type) !== "good_pass").map((discount) => [String(discount.id), discount])
1082
+ );
1083
+ const remainderDiscountList = (product.discount_list || []).filter((discount) => {
1084
+ var _a3;
1085
+ const discountType = discount.tag || discount.type;
1086
+ if (discountType === "promotion")
1087
+ return true;
1088
+ if (discountType === "good_pass")
1089
+ return false;
1090
+ const resourceId = ((_a3 = discount.discount) == null ? void 0 : _a3.resource_id) ?? discount.id;
1091
+ return resourceId !== void 0 && resourceId !== null && applicableNonGoodPassById.has(String(resourceId));
1092
+ }).map((discount) => {
1093
+ var _a3;
1094
+ if ((discount.tag || discount.type) === "promotion")
1095
+ return discount;
1096
+ const resourceId = ((_a3 = discount.discount) == null ? void 0 : _a3.resource_id) ?? discount.id;
1097
+ const matchedDiscount = applicableNonGoodPassById.get(String(resourceId));
1098
+ if (matchedDiscount) {
1099
+ usedDiscounts.set(matchedDiscount.id, true);
1100
+ const appliedProducts = appliedDiscountProducts.get(matchedDiscount.id) || [];
1101
+ appliedProducts.push({
1102
+ ...discount,
1103
+ _num: remainingQuantity
1104
+ });
1105
+ appliedDiscountProducts.set(matchedDiscount.id, appliedProducts);
1106
+ }
1107
+ return {
1108
+ ...discount,
1109
+ // _num 是 Rules 运行态的行数量;Order 写回 tempOrder 时会裁剪该字段。
1110
+ _num: remainingQuantity
1111
+ };
1112
+ });
1113
+ const hasRetainedWalletDiscount = remainderDiscountList.some(
1114
+ (discount) => !["promotion", "good_pass"].includes(discount.tag || discount.type)
1115
+ );
1052
1116
  let total = product.origin_total ?? product.total;
1053
1117
  if ((product.discount_list || []).some((item) => item.type === "promotion")) {
1054
1118
  total = product.total ?? product.origin_total;
1055
1119
  }
1056
1120
  arr.push(
1057
1121
  this.hooks.setProduct(originProduct, {
1058
- discount_list: this.filterDiscountListByType(product.discount_list, "promotion"),
1059
- quantity: totalQuantity - splitCount,
1122
+ // 商品券只替换被拆出的 splitCount 件。余量行原本仍有效的折扣卡
1123
+ // 不能随 good_pass 拆分一起清除;已取消或已失效的折扣不会进入此列表。
1124
+ discount_list: remainderDiscountList,
1125
+ quantity: remainingQuantity,
1060
1126
  _id: product._id.split("___")[0],
1061
- total
1127
+ total,
1128
+ // 保留余量行当前的折后主价;Order.applyProductDiscountPrices 随后会基于
1129
+ // source_product_price + discount_list 再归一化,避免出现重复折扣。
1130
+ ...hasRetainedWalletDiscount ? {
1131
+ main_product_selling_price: product.main_product_selling_price ?? product.price
1132
+ } : {}
1062
1133
  })
1063
1134
  );
1064
1135
  }
1065
1136
  for (let i = 0; i < splitCount; i++) {
1066
- const selectedDiscount2 = selectedDiscountCard || applicableDiscounts[i];
1137
+ const selectedDiscount2 = isNeedSplit ? prioritizedApplicableDiscounts[i] : selectedDiscountCard || applicableDiscounts[i];
1067
1138
  usedDiscounts.set(selectedDiscount2.id, true);
1068
1139
  if ((selectedDiscount2.tag || selectedDiscount2.type) === "good_pass") {
1069
1140
  const currentCount = usedProductIdCounts.get(selectedDiscount2.product_id) || 0;
@@ -1091,8 +1162,8 @@ var RulesModule = class extends import_BaseModule.BaseModule {
1091
1162
  amount = productAllocation.discountAmount;
1092
1163
  productDiscountDifference = productAllocation.difference;
1093
1164
  mainProductSellingPrice = Math.max(new import_decimal.default(product.price).minus(amount).toNumber(), 0);
1094
- } else if (resolveReapplyEditModeDiscountPercent(originProduct, selectedDiscount2) !== void 0) {
1095
- const percent = resolveReapplyEditModeDiscountPercent(originProduct, selectedDiscount2);
1165
+ } else if (resolveReapplyBookingDiscountPercent(originProduct, selectedDiscount2) !== void 0) {
1166
+ const percent = resolveReapplyBookingDiscountPercent(originProduct, selectedDiscount2);
1096
1167
  reapplyDiscountPercent = new import_decimal.default(percent).toFixed(2);
1097
1168
  mainProductSellingPrice = new import_decimal.default(product.price || 0).mul(new import_decimal.default(100).minus(percent)).div(100).toDecimalPlaces(2).toNumber();
1098
1169
  amount = new import_decimal.default(product.price).minus(mainProductSellingPrice).toNumber();
@@ -1190,7 +1261,7 @@ var RulesModule = class extends import_BaseModule.BaseModule {
1190
1261
  const discountNum = splitCount;
1191
1262
  const normalNum = totalQuantity - discountNum;
1192
1263
  for (let i = 0; i < discountNum; i++) {
1193
- const selectedDiscount2 = applicableDiscounts[i];
1264
+ const selectedDiscount2 = prioritizedApplicableDiscounts[i];
1194
1265
  usedDiscounts.set(selectedDiscount2.id, true);
1195
1266
  if ((selectedDiscount2.tag || selectedDiscount2.type) === "good_pass") {
1196
1267
  const currentCount = usedProductIdCounts.get(selectedDiscount2.product_id) || 0;
@@ -1267,10 +1338,19 @@ var RulesModule = class extends import_BaseModule.BaseModule {
1267
1338
  let targetProductTotal;
1268
1339
  let fixedAmountPerItem;
1269
1340
  let productDiscountDifference;
1341
+ let reapplyDiscountPercent;
1270
1342
  if (isOrderLevel && productAllocation) {
1271
1343
  fixedAmountPerItem = productAllocation.discountAmount;
1272
1344
  productDiscountDifference = productAllocation.difference;
1273
1345
  targetProductTotal = Math.max(new import_decimal.default(productOriginTotal).minus(fixedAmountPerItem).toNumber(), 0);
1346
+ } else if (resolveReapplyBookingDiscountPercent(originProduct, selectedDiscount2) !== void 0) {
1347
+ const percent = resolveReapplyBookingDiscountPercent(
1348
+ originProduct,
1349
+ selectedDiscount2
1350
+ );
1351
+ reapplyDiscountPercent = new import_decimal.default(percent).toFixed(2);
1352
+ targetProductTotal = new import_decimal.default(productOriginTotal).mul(new import_decimal.default(100).minus(percent)).div(100).toDecimalPlaces(2).toNumber();
1353
+ fixedAmountPerItem = new import_decimal.default(productOriginTotal).minus(targetProductTotal).toNumber();
1274
1354
  } else {
1275
1355
  targetProductTotal = (0, import_utils.getDiscountAmount)(
1276
1356
  selectedDiscount2,
@@ -1292,7 +1372,7 @@ var RulesModule = class extends import_BaseModule.BaseModule {
1292
1372
  title: selectedDiscount2.format_title,
1293
1373
  original_amount: product.original_price,
1294
1374
  product_id: product.id || product.product_id,
1295
- percent: selectedDiscount2.par_value,
1375
+ percent: reapplyDiscountPercent ?? selectedDiscount2.par_value,
1296
1376
  discount_product_id: selectedDiscount2.product_id
1297
1377
  },
1298
1378
  metadata: {
@@ -30,6 +30,7 @@ export declare class SalesSummaryModule extends BaseModule implements Module, Sa
30
30
  taxRate?: number;
31
31
  shopDiscount?: string | number;
32
32
  channel?: string;
33
+ virtualSettlementMode?: boolean;
33
34
  }): Promise<{
34
35
  tax_title: string;
35
36
  tax_rate: number | undefined;
@@ -126,7 +126,14 @@ var SalesSummaryModule = class extends import_BaseModule.BaseModule {
126
126
  }
127
127
  async getSummary(params) {
128
128
  var _a, _b;
129
- const { products = [], isPriceIncludeTax, taxRate, shopDiscount, channel } = params;
129
+ const {
130
+ products = [],
131
+ isPriceIncludeTax,
132
+ taxRate,
133
+ shopDiscount,
134
+ channel,
135
+ virtualSettlementMode = false
136
+ } = params;
130
137
  const summaryChannel = channel || this.resolveChannel();
131
138
  if (summaryChannel !== this.surchargeListChannel) {
132
139
  await this.getSurchargeList(summaryChannel);
@@ -150,7 +157,8 @@ var SalesSummaryModule = class extends import_BaseModule.BaseModule {
150
157
  surchargeList: this.store.surchargeList,
151
158
  scheduleById,
152
159
  isInScheduleByDate: import_Schedule.ScheduleModule.isInScheduleByDate,
153
- shopDiscount
160
+ shopDiscount,
161
+ virtualSettlementMode
154
162
  });
155
163
  const summaryWithTaxMeta = {
156
164
  ...summary,
@@ -64,5 +64,6 @@ export interface SalesSummaryModuleAPI {
64
64
  taxRate?: number;
65
65
  shopDiscount?: string | number;
66
66
  channel?: string;
67
+ virtualSettlementMode?: boolean;
67
68
  }): Promise<SalesSummaryData>;
68
69
  }
@@ -8,4 +8,5 @@ export declare function calculateSalesSummary(params: {
8
8
  scheduleById?: Record<string, any>;
9
9
  isInScheduleByDate?: any;
10
10
  shopDiscount?: string | number;
11
+ virtualSettlementMode?: boolean;
11
12
  }): SalesSummaryData;
@@ -99,7 +99,11 @@ function getUnitOriginalTotal(product) {
99
99
  function isGeneratedVoucherProduct(product) {
100
100
  const record = product;
101
101
  const metadata = (record == null ? void 0 : record.metadata) || {};
102
- return (record == null ? void 0 : record.extension_type) === "product_voucher" || metadata.parent_order_detail_id !== void 0 || metadata.gift_card_type !== void 0;
102
+ const isGeneratedChild = metadata.parent_order_detail_id !== void 0 || metadata.gift_card_type !== void 0;
103
+ if (isGeneratedChild)
104
+ return true;
105
+ const isPersisted = (record == null ? void 0 : record.order_detail_id) !== void 0 && (record == null ? void 0 : record.order_detail_id) !== null;
106
+ return (record == null ? void 0 : record.extension_type) === "product_voucher" && !isPersisted;
103
107
  }
104
108
  function buildSurchargeServiceItems(products) {
105
109
  return products.map((product) => {
@@ -886,13 +890,37 @@ function calculateSalesSummary(params) {
886
890
  surchargeList,
887
891
  scheduleById = {},
888
892
  isInScheduleByDate,
889
- shopDiscount
893
+ shopDiscount,
894
+ virtualSettlementMode = false
890
895
  } = params;
891
896
  if (!(products == null ? void 0 : products.length))
892
897
  return createEmptySalesSummary();
893
898
  const summaryProducts = products.filter((product) => !isGeneratedVoucherProduct(product));
894
899
  if (!summaryProducts.length)
895
900
  return createEmptySalesSummary();
901
+ if (virtualSettlementMode) {
902
+ const productQuantity2 = summaryProducts.reduce(
903
+ (sum, item) => sum + getSafeNum(item.num),
904
+ 0
905
+ );
906
+ const productAmount2 = summaryProducts.reduce(
907
+ (sum, item) => sum.plus(
908
+ toDecimal(item.selling_price).times(getSafeNum(item.num))
909
+ ),
910
+ new import_decimal.default(0)
911
+ );
912
+ const amount = toFixed2(productAmount2);
913
+ return {
914
+ ...createEmptySalesSummary(),
915
+ product_quantity: productQuantity2,
916
+ product_original_amount: amount,
917
+ product_amount: amount,
918
+ product_expect_amount: amount,
919
+ expect_amount: amount,
920
+ total_amount: amount,
921
+ is_price_include_tax: isPriceIncludeTax
922
+ };
923
+ }
896
924
  const productQuantity = summaryProducts.reduce(
897
925
  (sum, item) => sum + getSafeNum(item.num),
898
926
  0
@@ -164,6 +164,12 @@ declare class Server {
164
164
  * 不影响当前界面展示,适用于切回前台、定时刷新等场景
165
165
  */
166
166
  refreshOrdersInBackground(): Promise<void>;
167
+ /**
168
+ * 编辑正式订单时强制查询 Server 最新快照并覆盖本地缓存。
169
+ * 商品成员增删由 OrderModule 在 SQLite/Store 更新完成后广播,
170
+ * UI 可复用 order:onRemoteProductMembershipChanged 处理冲突。
171
+ */
172
+ checkRemoteOrderProductMembership(orderId: string | number): Promise<void>;
167
173
  /**
168
174
  * 清空所有server模块的IndexedDB缓存
169
175
  * @returns Promise<void>
@@ -257,6 +263,11 @@ declare class Server {
257
263
  * 对单个商品 query 订阅者重算并推送(定时路径;不新建 schedule timer)。
258
264
  */
259
265
  private recomputeAndNotifySubscriber;
266
+ /**
267
+ * 根据餐牌 ID 获取其绑定日程中的全部时间点。
268
+ * 商品查询定时器与 schedule-time-points 路由共用此逻辑。
269
+ */
270
+ private getMenuScheduleTimePoints;
260
271
  /**
261
272
  * query 完成后为 subscriber 同步 schedule 定时器(先 clear 再 schedule)。
262
273
  */
@@ -512,6 +523,10 @@ declare class Server {
512
523
  private handleOrderDraftSubmit;
513
524
  private handlePendingSyncCheckoutOrder;
514
525
  private buildPendingSyncCheckoutOrder;
526
+ /**
527
+ * Android 结账自动小票由当前设备设置控制;其它平台保持原有行为。
528
+ */
529
+ private shouldAutoPrintCheckoutReceipt;
515
530
  private shouldBuildSmallTicketData;
516
531
  private shouldPrintSyncedOrder;
517
532
  private hasPaymentStatus;