@pisell/pisellos 2.2.302 → 2.2.304
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.
- package/dist/model/strategy/adapter/dataVariant/evaluator.d.ts +18 -2
- package/dist/model/strategy/adapter/dataVariant/evaluator.js +287 -101
- package/dist/model/strategy/adapter/dataVariant/type.d.ts +25 -0
- package/dist/model/strategy/adapter/itemRule/adapter.d.ts +4 -1
- package/dist/model/strategy/adapter/itemRule/adapter.js +135 -35
- package/dist/model/strategy/adapter/itemRule/evaluator.d.ts +8 -1
- package/dist/model/strategy/adapter/itemRule/evaluator.js +25 -1
- package/dist/model/strategy/adapter/itemRule/type.d.ts +44 -0
- package/dist/model/strategy/adapter/itemRule/type.js +19 -1
- package/dist/model/strategy/adapter/promotion/index.js +0 -9
- package/dist/modules/BookingContext/utils/buildCacheItemFromOrderLine.js +10 -7
- package/dist/modules/Discount/index.d.ts +2 -0
- package/dist/modules/Discount/index.js +41 -5
- package/dist/modules/Discount/types.d.ts +21 -0
- package/dist/modules/OpenData/index.d.ts +15 -2
- package/dist/modules/OpenData/index.js +307 -19
- package/dist/modules/OpenData/types.d.ts +55 -0
- package/dist/modules/OpenData/types.js +7 -1
- package/dist/modules/OpenData/utils.d.ts +21 -1
- package/dist/modules/OpenData/utils.js +138 -0
- package/dist/modules/Order/index.d.ts +50 -7
- package/dist/modules/Order/index.js +1690 -815
- package/dist/modules/Order/salesNotes.d.ts +31 -0
- package/dist/modules/Order/salesNotes.js +492 -0
- package/dist/modules/Order/types.d.ts +117 -10
- package/dist/modules/Order/types.js +6 -1
- package/dist/modules/Order/utils/virtualSettlement.d.ts +63 -0
- package/dist/modules/Order/utils/virtualSettlement.js +284 -0
- package/dist/modules/Order/utils.d.ts +5 -1
- package/dist/modules/Order/utils.js +138 -33
- package/dist/modules/Payment/cashRecommendationAlgorithm.d.ts +4 -5
- package/dist/modules/Payment/cashRecommendationAlgorithm.js +17 -367
- package/dist/modules/Payment/walletpass.js +20 -21
- package/dist/modules/Product/types.d.ts +23 -0
- package/dist/modules/ProductList/index.d.ts +2 -1
- package/dist/modules/ProductList/index.js +100 -20
- package/dist/modules/ProductList/types.d.ts +10 -0
- package/dist/modules/Rules/index.d.ts +5 -0
- package/dist/modules/Rules/index.js +30 -14
- package/dist/modules/SalesSummary/index.d.ts +1 -0
- package/dist/modules/SalesSummary/index.js +4 -3
- package/dist/modules/SalesSummary/types.d.ts +1 -0
- package/dist/modules/SalesSummary/utils.d.ts +1 -0
- package/dist/modules/SalesSummary/utils.js +21 -1
- package/dist/plugins/request.d.ts +2 -1
- package/dist/plugins/request.js +4 -2
- package/dist/server/index.d.ts +21 -0
- package/dist/server/index.js +1774 -1285
- package/dist/server/modules/order/types.d.ts +50 -0
- package/dist/server/modules/order/types.js +2 -0
- package/dist/server/utils/small-ticket.d.ts +4 -1
- package/dist/server/utils/small-ticket.js +461 -96
- package/dist/solution/BaseSales/index.d.ts +24 -3
- package/dist/solution/BaseSales/index.js +1484 -873
- package/dist/solution/BaseSales/utils/parseSalesResponse.d.ts +2 -0
- package/dist/solution/BaseSales/utils/parseSalesResponse.js +1 -1
- package/dist/solution/BaseSales/utils/transformBaseProductToOrderProduct.d.ts +1 -0
- package/dist/solution/BaseSales/utils/transformBaseProductToOrderProduct.js +72 -9
- package/dist/solution/BookingByStep/index.d.ts +1 -1
- package/dist/solution/BookingTicket/index.d.ts +22 -5
- package/dist/solution/BookingTicket/index.js +558 -443
- package/dist/solution/BookingTicket/utils/addProductDecision.js +2 -1
- package/dist/solution/BookingTicket/utils/orderCustomer.js +3 -2
- package/dist/solution/RegisterAndLogin/config.d.ts +9 -3
- package/dist/solution/RegisterAndLogin/config.js +95 -40
- package/dist/solution/RegisterAndLogin/index.js +4 -1
- package/dist/solution/Sales/index.d.ts +16 -1
- package/dist/solution/Sales/index.js +338 -63
- package/dist/solution/Sales/types.d.ts +10 -0
- package/dist/solution/ScanOrder/index.d.ts +1 -0
- package/dist/solution/ScanOrder/index.js +31 -27
- package/dist/solution/ScanOrder/utils.d.ts +1 -0
- package/dist/solution/ScanOrder/utils.js +2 -0
- package/dist/solution/UnifiedBookingSales/index.d.ts +20 -6
- package/dist/solution/UnifiedBookingSales/index.js +874 -527
- package/dist/solution/UnifiedBookingSales/types.d.ts +21 -2
- package/dist/solution/VenueBooking/index.d.ts +1 -0
- package/dist/solution/VenueBooking/index.js +8 -4
- package/lib/model/strategy/adapter/dataVariant/evaluator.d.ts +18 -2
- package/lib/model/strategy/adapter/dataVariant/evaluator.js +137 -6
- package/lib/model/strategy/adapter/dataVariant/type.d.ts +25 -0
- package/lib/model/strategy/adapter/itemRule/adapter.d.ts +4 -1
- package/lib/model/strategy/adapter/itemRule/adapter.js +73 -4
- package/lib/model/strategy/adapter/itemRule/evaluator.d.ts +8 -1
- package/lib/model/strategy/adapter/itemRule/evaluator.js +23 -1
- package/lib/model/strategy/adapter/itemRule/type.d.ts +44 -0
- package/lib/model/strategy/adapter/itemRule/type.js +19 -1
- package/lib/model/strategy/adapter/promotion/index.js +1 -46
- package/lib/modules/BookingContext/utils/buildCacheItemFromOrderLine.js +4 -0
- package/lib/modules/Discount/index.d.ts +2 -0
- package/lib/modules/Discount/index.js +34 -2
- package/lib/modules/Discount/types.d.ts +21 -0
- package/lib/modules/OpenData/index.d.ts +15 -2
- package/lib/modules/OpenData/index.js +230 -3
- package/lib/modules/OpenData/types.d.ts +55 -0
- package/lib/modules/OpenData/types.js +9 -1
- package/lib/modules/OpenData/utils.d.ts +21 -1
- package/lib/modules/OpenData/utils.js +126 -0
- package/lib/modules/Order/index.d.ts +50 -7
- package/lib/modules/Order/index.js +704 -64
- package/lib/modules/Order/salesNotes.d.ts +31 -0
- package/lib/modules/Order/salesNotes.js +382 -0
- package/lib/modules/Order/types.d.ts +117 -10
- package/lib/modules/Order/types.js +5 -1
- package/lib/modules/Order/utils/virtualSettlement.d.ts +63 -0
- package/lib/modules/Order/utils/virtualSettlement.js +262 -0
- package/lib/modules/Order/utils.d.ts +5 -1
- package/lib/modules/Order/utils.js +127 -21
- package/lib/modules/Payment/cashRecommendationAlgorithm.d.ts +4 -5
- package/lib/modules/Payment/cashRecommendationAlgorithm.js +9 -354
- package/lib/modules/Payment/walletpass.js +14 -11
- package/lib/modules/Product/types.d.ts +23 -0
- package/lib/modules/ProductList/index.d.ts +2 -1
- package/lib/modules/ProductList/index.js +47 -2
- package/lib/modules/ProductList/types.d.ts +10 -0
- package/lib/modules/Rules/index.d.ts +5 -0
- package/lib/modules/Rules/index.js +22 -12
- package/lib/modules/SalesSummary/index.d.ts +1 -0
- package/lib/modules/SalesSummary/index.js +4 -2
- package/lib/modules/SalesSummary/types.d.ts +1 -0
- package/lib/modules/SalesSummary/utils.d.ts +1 -0
- package/lib/modules/SalesSummary/utils.js +17 -1
- package/lib/plugins/request.d.ts +2 -1
- package/lib/plugins/request.js +3 -2
- package/lib/server/index.d.ts +21 -0
- package/lib/server/index.js +492 -108
- package/lib/server/modules/order/types.d.ts +50 -0
- package/lib/server/modules/order/types.js +1 -0
- package/lib/server/utils/small-ticket.d.ts +4 -1
- package/lib/server/utils/small-ticket.js +427 -72
- package/lib/solution/BaseSales/index.d.ts +24 -3
- package/lib/solution/BaseSales/index.js +433 -30
- package/lib/solution/BaseSales/utils/parseSalesResponse.d.ts +2 -0
- package/lib/solution/BaseSales/utils/parseSalesResponse.js +1 -1
- package/lib/solution/BaseSales/utils/transformBaseProductToOrderProduct.d.ts +1 -0
- package/lib/solution/BaseSales/utils/transformBaseProductToOrderProduct.js +38 -0
- package/lib/solution/BookingByStep/index.d.ts +1 -1
- package/lib/solution/BookingTicket/index.d.ts +22 -5
- package/lib/solution/BookingTicket/index.js +60 -15
- package/lib/solution/BookingTicket/utils/addProductDecision.js +1 -0
- package/lib/solution/BookingTicket/utils/orderCustomer.js +2 -1
- package/lib/solution/RegisterAndLogin/config.d.ts +9 -3
- package/lib/solution/RegisterAndLogin/config.js +49 -8
- package/lib/solution/RegisterAndLogin/index.js +4 -1
- package/lib/solution/Sales/index.d.ts +16 -1
- package/lib/solution/Sales/index.js +168 -1
- package/lib/solution/Sales/types.d.ts +10 -0
- package/lib/solution/ScanOrder/index.d.ts +1 -0
- package/lib/solution/ScanOrder/index.js +5 -1
- package/lib/solution/ScanOrder/utils.d.ts +1 -0
- package/lib/solution/ScanOrder/utils.js +1 -0
- package/lib/solution/UnifiedBookingSales/index.d.ts +20 -6
- package/lib/solution/UnifiedBookingSales/index.js +169 -16
- package/lib/solution/UnifiedBookingSales/types.d.ts +21 -2
- package/lib/solution/VenueBooking/index.d.ts +1 -0
- package/lib/solution/VenueBooking/index.js +5 -1
- package/package.json +1 -1
|
@@ -9,10 +9,9 @@ exports.recommendOptimalPayments = recommendOptimalPayments;
|
|
|
9
9
|
* 现金支付推荐算法
|
|
10
10
|
*
|
|
11
11
|
* 核心原理:
|
|
12
|
-
* 1.
|
|
13
|
-
* 2.
|
|
14
|
-
* 3.
|
|
15
|
-
* 4. 根据组合判断去重,避免扩展组合
|
|
12
|
+
* 1. 第一项始终是精确应付金额
|
|
13
|
+
* 2. 其余推荐只能是严格大于应付金额的真实币种面额
|
|
14
|
+
* 3. 最多推荐三个更高面额
|
|
16
15
|
*/
|
|
17
16
|
|
|
18
17
|
/**
|
|
@@ -48,364 +47,20 @@ const CURRENCY_DENOMINATIONS = exports.CURRENCY_DENOMINATIONS = {
|
|
|
48
47
|
'DEFAULT': [100, 50, 20, 10, 5, 1, 0.5, 0.25, 0.1, 0.05, 0.01]
|
|
49
48
|
};
|
|
50
49
|
|
|
51
|
-
/**
|
|
52
|
-
* 支付组合类型定义
|
|
53
|
-
*/
|
|
54
|
-
|
|
55
50
|
/**
|
|
56
51
|
* 最优支付金额推荐算法
|
|
57
|
-
*
|
|
52
|
+
* 返回精确应付金额,以及最多三个更高的真实币种面额
|
|
58
53
|
*
|
|
59
54
|
* @param targetAmount 目标金额
|
|
60
55
|
* @param denominations 币种面值数组
|
|
61
56
|
* @returns 推荐支付金额数组
|
|
62
57
|
*/
|
|
63
58
|
function recommendOptimalPayments(targetAmount, denominations) {
|
|
64
|
-
|
|
65
|
-
if (targetAmount <= 0 || !isFinite(targetAmount) || isNaN(targetAmount)) {
|
|
59
|
+
if (targetAmount <= 0 || !Number.isFinite(targetAmount)) {
|
|
66
60
|
return [];
|
|
67
61
|
}
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
// 过滤有效面额并限制搜索空间以提高性能
|
|
73
|
-
const maxReasonableDenom = targetAmount * 5; // 适度限制搜索空间
|
|
74
|
-
const validDenoms = denominations.filter(denom => denom > 0 && isFinite(denom) && !isNaN(denom) && denom <= maxReasonableDenom).slice(0, 8); // 限制面额数量以提高性能
|
|
75
|
-
|
|
76
|
-
if (validDenoms.length === 0) {
|
|
77
|
-
// 如果没有合适的面额,返回最接近的整数金额
|
|
78
|
-
return [Math.ceil(targetAmount)];
|
|
79
|
-
}
|
|
80
|
-
try {
|
|
81
|
-
// 处理精度问题,将金额转换为整数计算
|
|
82
|
-
const precision = 100; // 假设最小单位是0.01
|
|
83
|
-
const target = Math.round(targetAmount * precision);
|
|
84
|
-
const denoms = validDenoms.map(d => Math.round(d * precision)).sort((a, b) => b - a);
|
|
85
|
-
|
|
86
|
-
// 存储不同的支付组合方案
|
|
87
|
-
const paymentOptions = [];
|
|
88
|
-
|
|
89
|
-
// 首先尝试找到精确匹配的组合
|
|
90
|
-
const exactCombination = findExactCombination(target, denoms, precision);
|
|
91
|
-
if (exactCombination) {
|
|
92
|
-
paymentOptions.push(exactCombination);
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
// 为了提高效率,我们将按币种数量来生成组合
|
|
96
|
-
// 1币种组合:只用一种面额
|
|
97
|
-
for (let i = 0; i < Math.min(denoms.length, 6); i++) {
|
|
98
|
-
const denom = denoms[i];
|
|
99
|
-
const count = Math.ceil(target / denom);
|
|
100
|
-
const sum = count * denom;
|
|
101
|
-
if (sum >= target && count <= 12) {
|
|
102
|
-
// 限制硬币数量以提高性能
|
|
103
|
-
paymentOptions.push({
|
|
104
|
-
combination: Array(count).fill(denom / precision),
|
|
105
|
-
sum: sum / precision,
|
|
106
|
-
coinCount: count,
|
|
107
|
-
denomTypes: 1
|
|
108
|
-
});
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
// 2币种组合:使用两种面额(大幅限制搜索范围以提高性能)
|
|
113
|
-
for (let i = 0; i < Math.min(denoms.length - 1, 5); i++) {
|
|
114
|
-
for (let j = i + 1; j < Math.min(denoms.length, 6); j++) {
|
|
115
|
-
const denom1 = denoms[i];
|
|
116
|
-
const denom2 = denoms[j];
|
|
117
|
-
|
|
118
|
-
// 大幅限制搜索范围,基于目标金额动态调整
|
|
119
|
-
const maxCount1 = Math.min(Math.ceil(target / denom1) + 1, 8);
|
|
120
|
-
const maxCount2 = Math.min(Math.ceil(target / denom2) + 1, 10);
|
|
121
|
-
for (let count1 = 1; count1 <= maxCount1; count1++) {
|
|
122
|
-
for (let count2 = 1; count2 <= maxCount2; count2++) {
|
|
123
|
-
const sum = count1 * denom1 + count2 * denom2;
|
|
124
|
-
if (sum >= target) {
|
|
125
|
-
const combination = [...Array(count1).fill(denom1 / precision), ...Array(count2).fill(denom2 / precision)];
|
|
126
|
-
paymentOptions.push({
|
|
127
|
-
combination: combination,
|
|
128
|
-
sum: sum / precision,
|
|
129
|
-
coinCount: count1 + count2,
|
|
130
|
-
denomTypes: 2
|
|
131
|
-
});
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
// 3币种组合:使用三种面额(大幅限制搜索以提高效率)
|
|
139
|
-
for (let i = 0; i < Math.min(denoms.length - 2, 3); i++) {
|
|
140
|
-
for (let j = i + 1; j < Math.min(denoms.length - 1, 4); j++) {
|
|
141
|
-
for (let k = j + 1; k < Math.min(denoms.length, 5); k++) {
|
|
142
|
-
const denom1 = denoms[i];
|
|
143
|
-
const denom2 = denoms[j];
|
|
144
|
-
const denom3 = denoms[k];
|
|
145
|
-
|
|
146
|
-
// 大幅限制搜索范围
|
|
147
|
-
const maxCount1 = Math.min(Math.ceil(target / denom1) + 1, 5);
|
|
148
|
-
const maxCount2 = Math.min(Math.ceil(target / denom2) + 1, 6);
|
|
149
|
-
const maxCount3 = Math.min(Math.ceil(target / denom3) + 1, 8);
|
|
150
|
-
for (let count1 = 1; count1 <= maxCount1; count1++) {
|
|
151
|
-
for (let count2 = 1; count2 <= maxCount2; count2++) {
|
|
152
|
-
for (let count3 = 1; count3 <= maxCount3; count3++) {
|
|
153
|
-
const sum = count1 * denom1 + count2 * denom2 + count3 * denom3;
|
|
154
|
-
if (sum >= target) {
|
|
155
|
-
const combination = [...Array(count1).fill(denom1 / precision), ...Array(count2).fill(denom2 / precision), ...Array(count3).fill(denom3 / precision)];
|
|
156
|
-
paymentOptions.push({
|
|
157
|
-
combination: combination,
|
|
158
|
-
sum: sum / precision,
|
|
159
|
-
coinCount: count1 + count2 + count3,
|
|
160
|
-
denomTypes: 3
|
|
161
|
-
});
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
// 4币种组合:使用四种面额(极度限制搜索以提高性能)
|
|
171
|
-
for (let i = 0; i < Math.min(denoms.length - 3, 2); i++) {
|
|
172
|
-
for (let j = i + 1; j < Math.min(denoms.length - 2, 3); j++) {
|
|
173
|
-
for (let k = j + 1; k < Math.min(denoms.length - 1, 4); k++) {
|
|
174
|
-
for (let l = k + 1; l < Math.min(denoms.length, 5); l++) {
|
|
175
|
-
const denom1 = denoms[i];
|
|
176
|
-
const denom2 = denoms[j];
|
|
177
|
-
const denom3 = denoms[k];
|
|
178
|
-
const denom4 = denoms[l];
|
|
179
|
-
|
|
180
|
-
// 极度限制搜索范围
|
|
181
|
-
const maxCount1 = Math.min(Math.ceil(target / denom1) + 1, 3);
|
|
182
|
-
const maxCount2 = Math.min(Math.ceil(target / denom2) + 1, 4);
|
|
183
|
-
const maxCount3 = Math.min(Math.ceil(target / denom3) + 1, 5);
|
|
184
|
-
const maxCount4 = Math.min(Math.ceil(target / denom4) + 1, 6);
|
|
185
|
-
for (let count1 = 1; count1 <= maxCount1; count1++) {
|
|
186
|
-
for (let count2 = 1; count2 <= maxCount2; count2++) {
|
|
187
|
-
for (let count3 = 1; count3 <= maxCount3; count3++) {
|
|
188
|
-
for (let count4 = 1; count4 <= maxCount4; count4++) {
|
|
189
|
-
const sum = count1 * denom1 + count2 * denom2 + count3 * denom3 + count4 * denom4;
|
|
190
|
-
if (sum >= target) {
|
|
191
|
-
const combination = [...Array(count1).fill(denom1 / precision), ...Array(count2).fill(denom2 / precision), ...Array(count3).fill(denom3 / precision), ...Array(count4).fill(denom4 / precision)];
|
|
192
|
-
paymentOptions.push({
|
|
193
|
-
combination: combination,
|
|
194
|
-
sum: sum / precision,
|
|
195
|
-
coinCount: count1 + count2 + count3 + count4,
|
|
196
|
-
denomTypes: 4
|
|
197
|
-
});
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
// 如果没有找到任何组合,提供默认建议
|
|
209
|
-
if (paymentOptions.length === 0) {
|
|
210
|
-
// 找到大于等于目标金额的最小面额
|
|
211
|
-
const minValidDenom = validDenoms.find(denom => denom >= targetAmount);
|
|
212
|
-
if (minValidDenom) {
|
|
213
|
-
return [minValidDenom];
|
|
214
|
-
} else {
|
|
215
|
-
// 使用最大面额的组合
|
|
216
|
-
const maxDenom = Math.max(...validDenoms);
|
|
217
|
-
const count = Math.ceil(targetAmount / maxDenom);
|
|
218
|
-
return [count * maxDenom];
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
// 移除重复和扩展组合
|
|
223
|
-
const uniqueCombinations = removeDuplicateAndExtendedCombinations(paymentOptions, targetAmount);
|
|
224
|
-
|
|
225
|
-
// 按币种类型数量排序,然后按总硬币数量排序,最后按金额排序
|
|
226
|
-
uniqueCombinations.sort((a, b) => {
|
|
227
|
-
if (a.denomTypes !== b.denomTypes) {
|
|
228
|
-
return a.denomTypes - b.denomTypes;
|
|
229
|
-
}
|
|
230
|
-
if (a.coinCount !== b.coinCount) {
|
|
231
|
-
return a.coinCount - b.coinCount;
|
|
232
|
-
}
|
|
233
|
-
return a.sum - b.sum;
|
|
234
|
-
});
|
|
235
|
-
|
|
236
|
-
// 按金额去重并返回推荐的支付金额
|
|
237
|
-
const uniqueAmounts = new Set();
|
|
238
|
-
const finalResults = [];
|
|
239
|
-
for (const item of uniqueCombinations) {
|
|
240
|
-
const roundedAmount = Math.round(item.sum * 100) / 100; // 处理浮点数精度
|
|
241
|
-
if (!uniqueAmounts.has(roundedAmount) && finalResults.length < 10) {
|
|
242
|
-
uniqueAmounts.add(roundedAmount);
|
|
243
|
-
finalResults.push(roundedAmount);
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
return finalResults.sort((a, b) => a - b);
|
|
247
|
-
} catch (error) {
|
|
248
|
-
// 发生错误时返回安全的默认值
|
|
249
|
-
console.warn('推荐支付金额计算出错:', error);
|
|
250
|
-
// 返回最接近的整数金额作为兜底
|
|
251
|
-
const safeAmount = Math.ceil(targetAmount);
|
|
252
|
-
return [targetAmount, safeAmount];
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
/**
|
|
257
|
-
* 尝试找到精确匹配目标金额的组合
|
|
258
|
-
*/
|
|
259
|
-
function findExactCombination(target, denoms, precision) {
|
|
260
|
-
// 简单的深度优先搜索,限制搜索深度
|
|
261
|
-
function dfs(remaining, denomIndex, currentCombination) {
|
|
262
|
-
if (remaining === 0) {
|
|
263
|
-
return currentCombination;
|
|
264
|
-
}
|
|
265
|
-
if (remaining < 0 || denomIndex >= denoms.length || currentCombination.length > 10) {
|
|
266
|
-
return null;
|
|
267
|
-
}
|
|
268
|
-
const denom = denoms[denomIndex];
|
|
269
|
-
|
|
270
|
-
// 尝试使用当前面额 0 到 maxCount 次
|
|
271
|
-
const maxCount = Math.min(Math.floor(remaining / denom), 8);
|
|
272
|
-
for (let count = maxCount; count >= 0; count--) {
|
|
273
|
-
const newCombination = [...currentCombination, ...Array(count).fill(denom)];
|
|
274
|
-
const result = dfs(remaining - count * denom, denomIndex + 1, newCombination);
|
|
275
|
-
if (result) {
|
|
276
|
-
return result;
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
return null;
|
|
280
|
-
}
|
|
281
|
-
const exactMatch = dfs(target, 0, []);
|
|
282
|
-
if (exactMatch && exactMatch.length > 0) {
|
|
283
|
-
const denomTypes = new Set(exactMatch).size;
|
|
284
|
-
return {
|
|
285
|
-
combination: exactMatch.map(d => d / precision),
|
|
286
|
-
sum: target / precision,
|
|
287
|
-
coinCount: exactMatch.length,
|
|
288
|
-
denomTypes: denomTypes
|
|
289
|
-
};
|
|
290
|
-
}
|
|
291
|
-
return null;
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
/**
|
|
295
|
-
* 移除重复和扩展组合
|
|
296
|
-
* 核心原理:如果组合A包含组合B的所有硬币,并且还有额外硬币,则A是B的扩展,应该被移除
|
|
297
|
-
*/
|
|
298
|
-
function removeDuplicateAndExtendedCombinations(combinations, targetAmount) {
|
|
299
|
-
const result = [];
|
|
300
|
-
|
|
301
|
-
// 按币种类型数量、硬币数量、金额排序,确保较简单的组合在前面
|
|
302
|
-
combinations.sort((a, b) => {
|
|
303
|
-
if (a.denomTypes !== b.denomTypes) {
|
|
304
|
-
return a.denomTypes - b.denomTypes;
|
|
305
|
-
}
|
|
306
|
-
if (a.coinCount !== b.coinCount) {
|
|
307
|
-
return a.coinCount - b.coinCount;
|
|
308
|
-
}
|
|
309
|
-
return a.sum - b.sum;
|
|
310
|
-
});
|
|
311
|
-
for (let i = 0; i < combinations.length; i++) {
|
|
312
|
-
const current = combinations[i];
|
|
313
|
-
let shouldSkip = false;
|
|
314
|
-
|
|
315
|
-
// 检查当前组合是否与已添加的组合重复或者是扩展
|
|
316
|
-
for (let j = 0; j < result.length; j++) {
|
|
317
|
-
const existing = result[j];
|
|
318
|
-
|
|
319
|
-
// 如果完全相同,跳过
|
|
320
|
-
if (isSameCombination(current.combination, existing.combination)) {
|
|
321
|
-
shouldSkip = true;
|
|
322
|
-
break;
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
// 如果当前组合是已存在组合的扩展,跳过
|
|
326
|
-
if (isExtensionOf(current.combination, existing.combination)) {
|
|
327
|
-
shouldSkip = true;
|
|
328
|
-
break;
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
// 检查是否有其他组合是当前组合的扩展,如果有,移除那些扩展
|
|
333
|
-
if (!shouldSkip) {
|
|
334
|
-
// 移除所有当前组合的扩展
|
|
335
|
-
for (let j = result.length - 1; j >= 0; j--) {
|
|
336
|
-
if (isExtensionOf(result[j].combination, current.combination)) {
|
|
337
|
-
result.splice(j, 1);
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
result.push(current);
|
|
341
|
-
}
|
|
342
|
-
}
|
|
343
|
-
return result;
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
/**
|
|
347
|
-
* 检查组合A是否是组合B的扩展(即A包含B的所有硬币,并且还有额外的硬币)
|
|
348
|
-
*/
|
|
349
|
-
function isExtensionOf(combinationA, combinationB) {
|
|
350
|
-
// 如果A的硬币数量少于等于B,A不可能是B的扩展
|
|
351
|
-
if (combinationA.length <= combinationB.length) {
|
|
352
|
-
return false;
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
// 统计每个面额的数量
|
|
356
|
-
const countA = {};
|
|
357
|
-
const countB = {};
|
|
358
|
-
combinationA.forEach(coin => {
|
|
359
|
-
countA[coin] = (countA[coin] || 0) + 1;
|
|
360
|
-
});
|
|
361
|
-
combinationB.forEach(coin => {
|
|
362
|
-
countB[coin] = (countB[coin] || 0) + 1;
|
|
363
|
-
});
|
|
364
|
-
|
|
365
|
-
// 检查B的所有面额在A中是否都有足够的数量
|
|
366
|
-
for (const coin in countB) {
|
|
367
|
-
if (!countA[coin] || countA[coin] < countB[coin]) {
|
|
368
|
-
return false;
|
|
369
|
-
}
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
// 检查A是否有B没有的额外硬币
|
|
373
|
-
let hasExtra = false;
|
|
374
|
-
for (const coin in countA) {
|
|
375
|
-
if (countA[coin] > (countB[coin] || 0)) {
|
|
376
|
-
hasExtra = true;
|
|
377
|
-
break;
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
|
-
return hasExtra;
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
/**
|
|
384
|
-
* 检查两个组合是否本质相同(相同的面额组合)
|
|
385
|
-
*/
|
|
386
|
-
function isSameCombination(combinationA, combinationB) {
|
|
387
|
-
if (combinationA.length !== combinationB.length) {
|
|
388
|
-
return false;
|
|
389
|
-
}
|
|
390
|
-
const countA = {};
|
|
391
|
-
const countB = {};
|
|
392
|
-
combinationA.forEach(coin => {
|
|
393
|
-
countA[coin] = (countA[coin] || 0) + 1;
|
|
394
|
-
});
|
|
395
|
-
combinationB.forEach(coin => {
|
|
396
|
-
countB[coin] = (countB[coin] || 0) + 1;
|
|
397
|
-
});
|
|
398
|
-
|
|
399
|
-
// 检查两个组合是否有相同的面额和数量
|
|
400
|
-
for (const coin in countA) {
|
|
401
|
-
if (countA[coin] !== (countB[coin] || 0)) {
|
|
402
|
-
return false;
|
|
403
|
-
}
|
|
404
|
-
}
|
|
405
|
-
for (const coin in countB) {
|
|
406
|
-
if (countB[coin] !== (countA[coin] || 0)) {
|
|
407
|
-
return false;
|
|
408
|
-
}
|
|
409
|
-
}
|
|
410
|
-
return true;
|
|
62
|
+
const precision = 100;
|
|
63
|
+
const normalizedTarget = Math.round(targetAmount * precision) / precision;
|
|
64
|
+
const higherDenominations = [...new Set((Array.isArray(denominations) ? denominations : []).filter(denomination => denomination > 0 && Number.isFinite(denomination)).map(denomination => Math.round(denomination * precision) / precision))].filter(denomination => denomination > normalizedTarget).sort((a, b) => a - b).slice(0, 3);
|
|
65
|
+
return [normalizedTarget, ...higherDenominations];
|
|
411
66
|
}
|
|
@@ -650,17 +650,20 @@ class WalletPassPaymentImpl {
|
|
|
650
650
|
this.paymentModule.logInfo('[WalletPass] 商品列表为空,跳过获取用户识别码列表');
|
|
651
651
|
return [];
|
|
652
652
|
}
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
653
|
+
|
|
654
|
+
// const platform = this.paymentModule.getPlatform();
|
|
655
|
+
// if (platform === 'pc' || platform === 'h5') {
|
|
656
|
+
// this.userIdentificationCodes = [];
|
|
657
|
+
// this.emitEvent(WalletPassHooks.OnUserIdentificationCodesUpdated, {
|
|
658
|
+
// userIdentificationCodes: [],
|
|
659
|
+
// } as OnUserIdentificationCodesUpdatedEventData);
|
|
660
|
+
// this.paymentModule.logInfo(
|
|
661
|
+
// '[WalletPass] 用户端平台跳过获取用户识别码列表',
|
|
662
|
+
// { platform },
|
|
663
|
+
// );
|
|
664
|
+
// return [];
|
|
665
|
+
// }
|
|
666
|
+
|
|
664
667
|
if (typeof newParams.payment_order_id === 'string' && newParams.payment_order_id.startsWith('LOCAL_')) {
|
|
665
668
|
newParams.payment_order_id = undefined;
|
|
666
669
|
}
|
|
@@ -23,12 +23,25 @@ export interface ProductDataVariant {
|
|
|
23
23
|
module_data_id: number;
|
|
24
24
|
/** 关联 data_variant_rule.id */
|
|
25
25
|
data_variant_rule_id: number;
|
|
26
|
+
/** 多货币结算等业务类型 */
|
|
27
|
+
business_type?: 'multi_currency_settlement' | string;
|
|
28
|
+
/** 数据状态;多货币结算仅处理 enable 记录 */
|
|
29
|
+
status?: 'enable' | 'disable' | string;
|
|
26
30
|
/** 命中策略后覆盖到商品上的字段 */
|
|
27
31
|
data: Record<string, any>;
|
|
28
32
|
created_at: string | null;
|
|
29
33
|
updated_at: string | null;
|
|
30
34
|
deleted_at: string | null;
|
|
31
35
|
}
|
|
36
|
+
export interface ProductSettlementOption extends Record<string, any> {
|
|
37
|
+
settlement_type: 'legal_currency' | 'virtual_currency' | string;
|
|
38
|
+
currency_product_id?: number;
|
|
39
|
+
price: string;
|
|
40
|
+
data_variant_id?: number;
|
|
41
|
+
data_variant_rule_id: number | string;
|
|
42
|
+
available: boolean;
|
|
43
|
+
disabled_reason?: string;
|
|
44
|
+
}
|
|
32
45
|
/**
|
|
33
46
|
* 商品基本信息接口
|
|
34
47
|
*/
|
|
@@ -61,6 +74,8 @@ export interface ProductData {
|
|
|
61
74
|
sum_stock: number;
|
|
62
75
|
/** 商品价格 */
|
|
63
76
|
price: string;
|
|
77
|
+
/** 当前上下文解析出的可选结算货币 */
|
|
78
|
+
available_settlement_options?: ProductSettlementOption[];
|
|
64
79
|
/** 商品原价 */
|
|
65
80
|
original_price: string;
|
|
66
81
|
/** 商品成本价 */
|
|
@@ -239,6 +254,14 @@ export interface ProductData {
|
|
|
239
254
|
option_group_count: number;
|
|
240
255
|
/** 智能定价变体列表,/product/query with dataVariants 返回 */
|
|
241
256
|
data_variants?: ProductDataVariant[];
|
|
257
|
+
/**
|
|
258
|
+
* 商品接口规则计算结果;data_variant_ids 按优先级从高到低排列。
|
|
259
|
+
* 空数组表示规则已经计算完成但没有命中 Data Variant。
|
|
260
|
+
*/
|
|
261
|
+
metadata?: {
|
|
262
|
+
data_variant_ids?: Array<number | string>;
|
|
263
|
+
[key: string]: any;
|
|
264
|
+
};
|
|
242
265
|
/** 服务时长 */
|
|
243
266
|
service_times?: any;
|
|
244
267
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Module, PisellCore } from '../../types';
|
|
2
2
|
import { BaseModule } from '../BaseModule';
|
|
3
|
-
import { ProductListLoadProductsParams } from './types';
|
|
3
|
+
import { ProductListLoadProductsParams, ProductListStoreQueryParams } from './types';
|
|
4
4
|
import { ProductData } from '../Product/types';
|
|
5
5
|
export * from './types';
|
|
6
6
|
export declare class ProductList extends BaseModule implements Module {
|
|
@@ -52,6 +52,7 @@ export declare class ProductList extends BaseModule implements Module {
|
|
|
52
52
|
getRawProductPriceSources(ids: number[]): Map<number, ProductData>;
|
|
53
53
|
getProducts(): Promise<ProductData[]>;
|
|
54
54
|
getProduct(id: number): Promise<ProductData | undefined>;
|
|
55
|
+
queryProducts(params?: ProductListStoreQueryParams): Promise<ProductData[]>;
|
|
55
56
|
getProductByIds(ids: number[]): Promise<ProductData[] | undefined>;
|
|
56
57
|
addProduct(products: ProductData[]): Promise<void>;
|
|
57
58
|
selectProducts(products: ProductData[]): Promise<void>;
|
|
@@ -25,6 +25,15 @@ Object.keys(_types).forEach(function (key) {
|
|
|
25
25
|
});
|
|
26
26
|
});
|
|
27
27
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
28
|
+
function normalizeQuerySet(values) {
|
|
29
|
+
if (!Array.isArray(values) || values.length === 0) return null;
|
|
30
|
+
const normalized = values.map(value => String(value ?? '').trim()).filter(Boolean);
|
|
31
|
+
return normalized.length ? new Set(normalized) : null;
|
|
32
|
+
}
|
|
33
|
+
function matchesAny(values, querySet) {
|
|
34
|
+
if (!querySet) return true;
|
|
35
|
+
return values.some(value => querySet.has(String(value ?? '').trim()));
|
|
36
|
+
}
|
|
28
37
|
class ProductList extends _BaseModule.BaseModule {
|
|
29
38
|
defaultName = 'productList';
|
|
30
39
|
defaultVersion = '1.0.0';
|
|
@@ -281,10 +290,46 @@ class ProductList extends _BaseModule.BaseModule {
|
|
|
281
290
|
const product = this.store.list.find(product => product.id === id);
|
|
282
291
|
return product ? (0, _lodashEs.cloneDeep)(product) : undefined;
|
|
283
292
|
}
|
|
293
|
+
async queryProducts(params = {}) {
|
|
294
|
+
const includeIds = normalizeQuerySet(params.includeIds);
|
|
295
|
+
const excludeIds = normalizeQuerySet(params.excludeIds);
|
|
296
|
+
const categoryIds = normalizeQuerySet(params.categoryIds);
|
|
297
|
+
const collectionIds = normalizeQuerySet(params.collectionIds);
|
|
298
|
+
const extensionTypes = normalizeQuerySet(params.extensionTypes);
|
|
299
|
+
const statuses = normalizeQuerySet(params.statuses);
|
|
300
|
+
const channelApplications = normalizeQuerySet(params.channelApplications);
|
|
301
|
+
const codeOrBarcode = typeof params.codeOrBarcode === 'string' ? params.codeOrBarcode.trim() : '';
|
|
302
|
+
const products = (this.store.list || []).filter(product => {
|
|
303
|
+
const productId = String(product.id ?? '').trim();
|
|
304
|
+
if (includeIds && !includeIds.has(productId)) return false;
|
|
305
|
+
if (excludeIds && excludeIds.has(productId)) return false;
|
|
306
|
+
if (categoryIds) {
|
|
307
|
+
const productCategoryIds = (product.categories || []).flatMap(category => [category?.id, category?.pivot?.product_category_id]);
|
|
308
|
+
if (!matchesAny(productCategoryIds, categoryIds)) return false;
|
|
309
|
+
}
|
|
310
|
+
if (collectionIds) {
|
|
311
|
+
const productCollectionIds = (product.collection || []).map(collection => collection?.id);
|
|
312
|
+
if (!matchesAny(productCollectionIds, collectionIds)) return false;
|
|
313
|
+
}
|
|
314
|
+
if (extensionTypes && !extensionTypes.has(String(product.extension_type ?? '').trim())) return false;
|
|
315
|
+
if (statuses && !statuses.has(String(product.status ?? '').trim())) return false;
|
|
316
|
+
if (channelApplications) {
|
|
317
|
+
if (!matchesAny(product.channel_application || [], channelApplications)) return false;
|
|
318
|
+
}
|
|
319
|
+
if (codeOrBarcode) {
|
|
320
|
+
const code = typeof product.code === 'string' ? product.code.trim() : '';
|
|
321
|
+
const barcode = typeof product.barcode === 'string' ? product.barcode.trim() : '';
|
|
322
|
+
if (code !== codeOrBarcode && barcode !== codeOrBarcode) return false;
|
|
323
|
+
}
|
|
324
|
+
return true;
|
|
325
|
+
});
|
|
326
|
+
return (0, _lodashEs.cloneDeep)(products);
|
|
327
|
+
}
|
|
284
328
|
async getProductByIds(ids) {
|
|
285
329
|
await this.core.effects.emit(`${this.name}:onGetProductByIds`, this.store.list);
|
|
286
|
-
|
|
287
|
-
|
|
330
|
+
return this.queryProducts({
|
|
331
|
+
includeIds: ids
|
|
332
|
+
});
|
|
288
333
|
}
|
|
289
334
|
async addProduct(products) {
|
|
290
335
|
// list 需要根据 id 去重
|
|
@@ -15,6 +15,16 @@ export interface ProductListLoadProductsParams {
|
|
|
15
15
|
/** 智能定价条件上下文,仅影响策略命中,不参与商品集合筛选 */
|
|
16
16
|
strategy_context?: Record<string, any>;
|
|
17
17
|
}
|
|
18
|
+
export interface ProductListStoreQueryParams {
|
|
19
|
+
includeIds?: Array<number | string>;
|
|
20
|
+
excludeIds?: Array<number | string>;
|
|
21
|
+
categoryIds?: Array<number | string>;
|
|
22
|
+
collectionIds?: Array<number | string>;
|
|
23
|
+
extensionTypes?: string[];
|
|
24
|
+
statuses?: ProductData['status'][];
|
|
25
|
+
channelApplications?: string[];
|
|
26
|
+
codeOrBarcode?: string;
|
|
27
|
+
}
|
|
18
28
|
export interface ProductListData {
|
|
19
29
|
list: ProductData[];
|
|
20
30
|
selectProducts: ProductData[];
|
|
@@ -14,6 +14,11 @@ export declare class RulesModule extends BaseModule implements Module, RulesModu
|
|
|
14
14
|
setRulesList(rulesList: Rules[]): Promise<void>;
|
|
15
15
|
getRulesList(): Rules[];
|
|
16
16
|
getWalletPassEvaluator(): import("../..").WalletPassEvaluator | undefined;
|
|
17
|
+
private normalizeHolderRecordIds;
|
|
18
|
+
/**
|
|
19
|
+
* 特定 form Holder 卡只授权给绑定同一 Holder Record 的商品行。
|
|
20
|
+
* required/isNeedHolder 仅控制是否强制选择 Holder,不参与优惠资格。
|
|
21
|
+
*/
|
|
17
22
|
private checkHolderMatch;
|
|
18
23
|
isDiscountListAvailable({ oldDiscountList, newDiscountList, productList, orderTotalAmount, holders, isFormSubject, }: {
|
|
19
24
|
oldDiscountList: Discount[];
|
|
@@ -38,21 +38,30 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
38
38
|
getWalletPassEvaluator() {
|
|
39
39
|
return this.window.getWalletPassEvaluator?.();
|
|
40
40
|
}
|
|
41
|
+
normalizeHolderRecordIds(value) {
|
|
42
|
+
const records = Array.isArray(value) ? value : [value];
|
|
43
|
+
return Array.from(new Set(records.map(record => {
|
|
44
|
+
if (record && typeof record === 'object') {
|
|
45
|
+
return record.form_record_id;
|
|
46
|
+
}
|
|
47
|
+
return record;
|
|
48
|
+
}).filter(record => record !== undefined && record !== null && record !== '' && record !== 0 && record !== '0').map(String)));
|
|
49
|
+
}
|
|
41
50
|
|
|
42
|
-
|
|
51
|
+
/**
|
|
52
|
+
* 特定 form Holder 卡只授权给绑定同一 Holder Record 的商品行。
|
|
53
|
+
* required/isNeedHolder 仅控制是否强制选择 Holder,不参与优惠资格。
|
|
54
|
+
*/
|
|
43
55
|
checkHolderMatch(discount, product, holders) {
|
|
44
56
|
// 非表单类型 holder 视为匹配
|
|
45
57
|
if (discount.holder?.holder_type !== 'form') return true;
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
const
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
if (!orderHolderId && !productHolderId) return false;
|
|
54
|
-
// 最终直接匹配 holder 是否相同
|
|
55
|
-
return (productHolderId || orderHolderId) === discount.holder.holder_id;
|
|
58
|
+
const discountHolderIds = this.normalizeHolderRecordIds(discount.holder.holder_id);
|
|
59
|
+
if (!discountHolderIds.length) return false;
|
|
60
|
+
const productHolderIds = this.normalizeHolderRecordIds(product.holder_id);
|
|
61
|
+
const candidateHolderIds = productHolderIds.length ? productHolderIds : this.normalizeHolderRecordIds(holders);
|
|
62
|
+
if (!candidateHolderIds.length) return false;
|
|
63
|
+
const discountHolderIdSet = new Set(discountHolderIds);
|
|
64
|
+
return candidateHolderIds.some(holderId => discountHolderIdSet.has(holderId));
|
|
56
65
|
}
|
|
57
66
|
|
|
58
67
|
// 判断discountList 是否可以对当前productList生效
|
|
@@ -1119,7 +1128,8 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
1119
1128
|
processedFlatItemsMap.set(flatItem._id, [{
|
|
1120
1129
|
...flatItem,
|
|
1121
1130
|
discount_list: isManualDiscount ? this.resolveDiscountListForManualOverride(flatItem.bundleItem?.discount_list || []) : this.filterDiscountListByType(flatItem.bundleItem?.discount_list || [], 'promotion'),
|
|
1122
|
-
price:
|
|
1131
|
+
price: flatItem.bundleItem.price,
|
|
1132
|
+
bundle_selling_price: flatItem.bundleItem.bundle_selling_price ?? (isManualDiscount ? flatItem.bundleItem.price : flatItem.bundleItem.original_price),
|
|
1123
1133
|
processed: true
|
|
1124
1134
|
}]);
|
|
1125
1135
|
}
|
|
@@ -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,8 @@ class SalesSummaryModule extends _BaseModule.BaseModule {
|
|
|
126
126
|
isPriceIncludeTax,
|
|
127
127
|
taxRate,
|
|
128
128
|
shopDiscount,
|
|
129
|
-
channel
|
|
129
|
+
channel,
|
|
130
|
+
virtualSettlementMode = false
|
|
130
131
|
} = params;
|
|
131
132
|
const summaryChannel = channel || this.resolveChannel();
|
|
132
133
|
if (summaryChannel !== this.surchargeListChannel) {
|
|
@@ -151,7 +152,8 @@ class SalesSummaryModule extends _BaseModule.BaseModule {
|
|
|
151
152
|
surchargeList: this.store.surchargeList,
|
|
152
153
|
scheduleById,
|
|
153
154
|
isInScheduleByDate: _Schedule.ScheduleModule.isInScheduleByDate,
|
|
154
|
-
shopDiscount
|
|
155
|
+
shopDiscount,
|
|
156
|
+
virtualSettlementMode
|
|
155
157
|
});
|
|
156
158
|
const summaryWithTaxMeta = {
|
|
157
159
|
...summary,
|