Commit 7c4fbc63dc21f71ad62ea9c9a05af3e8acafbf50

Authored by YunaiV
1 parent 24111c15

trade:物流费用计算,增加金额 divide 分拆逻辑

yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/delivery/DeliveryExpressChargeModeEnum.java
... ... @@ -16,7 +16,7 @@ import java.util.Arrays;
16 16 @Getter
17 17 public enum DeliveryExpressChargeModeEnum implements IntArrayValuable {
18 18  
19   - PIECE(1, "按件"),
  19 + COUNT(1, "按件"),
20 20 WEIGHT(2,"按重量"),
21 21 VOLUME(3, "按体积");
22 22  
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeDeliveryPriceCalculator.java
... ... @@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.trade.service.price.calculator;
2 2  
3 3 import cn.hutool.core.collection.CollUtil;
4 4 import cn.hutool.core.lang.Assert;
  5 +import cn.hutool.core.util.StrUtil;
5 6 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
6 7 import cn.iocoder.yudao.module.member.api.address.AddressApi;
7 8 import cn.iocoder.yudao.module.member.api.address.dto.AddressRespDTO;
... ... @@ -55,13 +56,13 @@ public class TradeDeliveryPriceCalculator implements TradePriceCalculator {
55 56 return;
56 57 }
57 58 if (DeliveryTypeEnum.PICK_UP.getType().equals(param.getDeliveryType())) {
58   - calculateByPickUp(param, result);
  59 + calculateByPickUp(param);
59 60 } else if (DeliveryTypeEnum.EXPRESS.getType().equals(param.getDeliveryType())) {
60 61 calculateExpress(param, result);
61 62 }
62 63 }
63 64  
64   - private void calculateByPickUp(TradePriceCalculateReqBO param, TradePriceCalculateRespBO result) {
  65 + private void calculateByPickUp(TradePriceCalculateReqBO param) {
65 66 if (param.getPickUpStoreId() == null) {
66 67 throw exception(PRICE_CALCULATE_DELIVERY_PRICE_PICK_UP_STORE_IS_EMPTY);
67 68 }
... ... @@ -82,12 +83,12 @@ public class TradeDeliveryPriceCalculator implements TradePriceCalculator {
82 83 Assert.notNull(address, "收件人({})的地址,不能为空", param.getUserId());
83 84  
84 85 // 情况一:全局包邮
85   - if (isGlobalExpressFree(param, result)) {
  86 + if (isGlobalExpressFree(result)) {
86 87 return;
87 88 }
88 89  
89   - // 情况二:
90   - // 2.1 过滤出已选中的商品SKU
  90 + // 情况二:快递模版
  91 + // 2.1 过滤出已选中的商品 SKU
91 92 List<OrderItem> selectedItem = filterList(result.getItems(), OrderItem::getSelected);
92 93 Set<Long> deliveryTemplateIds = convertSet(selectedItem, OrderItem::getDeliveryTemplateId);
93 94 Map<Long, DeliveryExpressTemplateRespBO> expressTemplateMap =
... ... @@ -103,11 +104,10 @@ public class TradeDeliveryPriceCalculator implements TradePriceCalculator {
103 104 /**
104 105 * 是否全局包邮
105 106 *
106   - * @param param 计算信息
107 107 * @param result 计算结果
108 108 * @return 是否包邮
109 109 */
110   - private boolean isGlobalExpressFree(TradePriceCalculateReqBO param, TradePriceCalculateRespBO result) {
  110 + private boolean isGlobalExpressFree(TradePriceCalculateRespBO result) {
111 111 TradeConfigDO config = tradeConfigService.getTradeConfig();
112 112 return config != null
113 113 && Boolean.TRUE.equals(config.getDeliveryExpressFreeEnabled()) // 开启包邮
... ... @@ -118,9 +118,9 @@ public class TradeDeliveryPriceCalculator implements TradePriceCalculator {
118 118 Map<Long, DeliveryExpressTemplateRespBO> expressTemplateMap,
119 119 TradePriceCalculateRespBO result) {
120 120 // 按商品运费模板来计算商品的运费:相同的运费模板可能对应多条订单商品 SKU
121   - Map<Long, List<OrderItem>> tplIdItemMap = convertMultiMap(selectedSkus, OrderItem::getDeliveryTemplateId);
  121 + Map<Long, List<OrderItem>> template2ItemMap = convertMultiMap(selectedSkus, OrderItem::getDeliveryTemplateId);
122 122 // 依次计算快递运费
123   - for (Map.Entry<Long, List<OrderItem>> entry : tplIdItemMap.entrySet()) {
  123 + for (Map.Entry<Long, List<OrderItem>> entry : template2ItemMap.entrySet()) {
124 124 Long templateId = entry.getKey();
125 125 List<OrderItem> orderItems = entry.getValue();
126 126 DeliveryExpressTemplateRespBO templateBO = expressTemplateMap.get(templateId);
... ... @@ -128,30 +128,12 @@ public class TradeDeliveryPriceCalculator implements TradePriceCalculator {
128 128 log.error("[calculateDeliveryPrice][不能计算快递运费,找不到 templateId({}) 对应的运费模板配置]", templateId);
129 129 continue;
130 130 }
131   - // 总件数, 总金额, 总重量, 总体积
132   - int totalCount = 0;
133   - int totalPrice = 0;
134   - double totalWeight = 0;
135   - double totalVolume = 0;
136   - for (OrderItem orderItem : orderItems) {
137   - totalCount += orderItem.getCount();
138   - totalPrice += orderItem.getPayPrice();
139   - if (orderItem.getWeight() != null) {
140   - totalWeight += totalWeight + orderItem.getWeight() * orderItem.getCount();
141   - }
142   - if (orderItem.getVolume() != null) {
143   - totalVolume += totalVolume + orderItem.getVolume() * orderItem.getCount();
144   - }
145   - }
146   - // 优先判断是否包邮. 如果包邮不计算快递运费
147   - if (isExpressFree(templateBO.getChargeMode(), totalCount, totalWeight,
148   - totalVolume, totalPrice, templateBO.getFree())) {
  131 + // 1. 优先判断是否包邮。如果包邮不计算快递运费
  132 + if (isExpressTemplateFree(orderItems, templateBO.getChargeMode(), templateBO.getFree())) {
149 133 continue;
150 134 }
151   - // 计算快递运费
152   - calculateExpressFeeByChargeMode(totalCount, totalWeight, totalVolume,
153   - templateBO.getChargeMode(), templateBO.getCharge(), orderItems);
154   -
  135 + // 2. 计算快递运费
  136 + calculateExpressFeeByChargeMode(orderItems, templateBO.getChargeMode(), templateBO.getCharge());
155 137 }
156 138 TradePriceCalculatorHelper.recountAllPrice(result);
157 139 }
... ... @@ -159,73 +141,44 @@ public class TradeDeliveryPriceCalculator implements TradePriceCalculator {
159 141 /**
160 142 * 按配送方式来计算运费
161 143 *
162   - * @param totalCount 总件数
163   - * @param totalWeight 总重量
164   - * @param totalVolume 总体积
  144 + * @param orderItems SKU 商品项目
165 145 * @param chargeMode 配送计费方式
166 146 * @param templateCharge 快递运费配置
167   - * @param orderItems SKU 商品项目
168 147 */
169   - private void calculateExpressFeeByChargeMode(double totalCount, double totalWeight, double totalVolume,
170   - int chargeMode, DeliveryExpressTemplateRespBO.Charge templateCharge,
171   - List<OrderItem> orderItems) {
  148 + private void calculateExpressFeeByChargeMode(List<OrderItem> orderItems, Integer chargeMode,
  149 + DeliveryExpressTemplateRespBO.Charge templateCharge) {
172 150 if (templateCharge == null) {
173 151 log.error("[calculateExpressFeeByChargeMode][计算快递运费时,找不到 SKU({}) 对应的运费模版]", orderItems);
174 152 return;
175 153 }
176   - DeliveryExpressChargeModeEnum chargeModeEnum = DeliveryExpressChargeModeEnum.valueOf(chargeMode);
177   - switch (chargeModeEnum) {
178   - case PIECE: {
179   - calculateExpressFee(totalCount, templateCharge, orderItems);
180   - break;
181   - }
182   - case WEIGHT: {
183   - calculateExpressFee(totalWeight, templateCharge, orderItems);
184   - break;
185   - }
186   - case VOLUME: {
187   - calculateExpressFee(totalVolume, templateCharge, orderItems);
188   - break;
189   - }
190   - }
191   - }
192   -
193   - /**
194   - * 计算 SKU 商品快递费用
195   - *
196   - * @param total 总件数/总重量/总体积
197   - * @param templateCharge 快递运费配置
198   - * @param orderItems SKU 商品项目
199   - */
200   - private void calculateExpressFee(double total, DeliveryExpressTemplateRespBO.Charge templateCharge, List<OrderItem> orderItems) {
  154 + double totalChargeValue = getTotalChargeValue(orderItems, chargeMode);
  155 + // 1. 计算 SKU 商品快递费用
201 156 int deliveryPrice;
202   - if (total <= templateCharge.getStartCount()) {
  157 + if (totalChargeValue <= templateCharge.getStartCount()) {
203 158 deliveryPrice = templateCharge.getStartPrice();
204 159 } else {
205   - double remainWeight = total - templateCharge.getStartCount();
  160 + double remainWeight = totalChargeValue - templateCharge.getStartCount();
206 161 // 剩余重量/ 续件 = 续件的次数. 向上取整
207 162 int extraNum = (int) Math.ceil(remainWeight / templateCharge.getExtraCount());
208 163 int extraPrice = templateCharge.getExtraPrice() * extraNum;
209 164 deliveryPrice = templateCharge.getStartPrice() + extraPrice;
210 165 }
211   - // 分摊快递费用到 SKU. 退费的时候,可能按照 SKU 考虑退费金额
212   - divideDeliveryPrice(deliveryPrice, orderItems);
213   - }
214 166  
215   - /**
216   - * 快递运费分摊到每个 SKU 商品上
217   - *
218   - * @param deliveryPrice 快递运费
219   - * @param orderItems SKU 商品
220   - */
221   - private void divideDeliveryPrice(int deliveryPrice, List<OrderItem> orderItems) {
222   - // TODO @jason:分摊的话,是不是要按照比例呀?重量、价格、数量等等,
223   - // 按比例是不是有点复杂。后面看看是否需要;
224   - // TODO 可以看看别的项目怎么搞的哈。
225   - int dividePrice = deliveryPrice / orderItems.size();
226   - for (OrderItem item : orderItems) {
  167 + // 2. 分摊快递费用到 SKU. 退费的时候,可能按照 SKU 考虑退费金额
  168 + int remainPrice = deliveryPrice;
  169 + for (int i = 0; i < orderItems.size(); i++) {
  170 + TradePriceCalculateRespBO.OrderItem item = orderItems.get(i);
  171 + int partPrice;
  172 + double chargeValue = getChargeValue(item, chargeMode);
  173 + if (i < orderItems.size() - 1) { // 减一的原因,是因为拆分时,如果按照比例,可能会出现.所以最后一个,使用反减
  174 + partPrice = (int) (deliveryPrice * (chargeValue / totalChargeValue));
  175 + remainPrice -= partPrice;
  176 + } else {
  177 + partPrice = remainPrice;
  178 + }
  179 + Assert.isTrue(partPrice >= 0, "分摊金额必须大于等于 0");
227 180 // 更新快递运费
228   - item.setDeliveryPrice(dividePrice);
  181 + item.setDeliveryPrice(partPrice);
229 182 TradePriceCalculatorHelper.recountPayPrice(item);
230 183 }
231 184 }
... ... @@ -234,42 +187,38 @@ public class TradeDeliveryPriceCalculator implements TradePriceCalculator {
234 187 * 检查是否包邮
235 188 *
236 189 * @param chargeMode 配送计费方式
237   - * @param totalCount 总件数
238   - * @param totalWeight 总重量
239   - * @param totalVolume 总体积
240   - * @param totalPrice 总金额
241 190 * @param templateFree 包邮配置
242 191 */
243   - private boolean isExpressFree(Integer chargeMode, int totalCount, double totalWeight,
244   - double totalVolume, int totalPrice, DeliveryExpressTemplateRespBO.Free templateFree) {
  192 + private boolean isExpressTemplateFree(List<OrderItem> orderItems, Integer chargeMode,
  193 + DeliveryExpressTemplateRespBO.Free templateFree) {
245 194 if (templateFree == null) {
246 195 return false;
247 196 }
  197 + double totalChargeValue = getTotalChargeValue(orderItems, chargeMode);
  198 + double totalPrice = TradePriceCalculatorHelper.calculateTotalPayPrice(orderItems);
  199 + return totalChargeValue >= templateFree.getFreeCount() && totalPrice >= templateFree.getFreePrice();
  200 + }
  201 +
  202 + private double getTotalChargeValue(List<OrderItem> orderItems, Integer chargeMode) {
  203 + double total = 0;
  204 + for (OrderItem orderItem : orderItems) {
  205 + total += getChargeValue(orderItem, chargeMode);
  206 + }
  207 + return total;
  208 + }
  209 +
  210 + private double getChargeValue(OrderItem orderItem, Integer chargeMode) {
248 211 DeliveryExpressChargeModeEnum chargeModeEnum = DeliveryExpressChargeModeEnum.valueOf(chargeMode);
249 212 switch (chargeModeEnum) {
250   - case PIECE:
251   - // 两个条件都满足才包邮
252   - if (totalCount >= templateFree.getFreeCount() && totalPrice >= templateFree.getFreePrice()) {
253   - return true;
254   - }
255   - break;
  213 + case COUNT:
  214 + return orderItem.getCount();
256 215 case WEIGHT:
257   - // freeCount 是不是应该是 double ??
258   - // TODO @jason:要不配置的时候,把它的单位和商品对齐?到底是 kg、还是斤
259   - // TODO @芋艿 目前 包邮 件数/重量/体积 都用的是这个字段
260   - // TODO @jason:那要不快递模版也改成 kg?这样是不是就不用 double ?
261   - if (totalWeight >= templateFree.getFreeCount()
262   - && totalPrice >= templateFree.getFreePrice()) {
263   - return true;
264   - }
265   - break;
  216 + return orderItem.getWeight() != null ? orderItem.getWeight() * orderItem.getCount() : 0;
266 217 case VOLUME:
267   - if (totalVolume >= templateFree.getFreeCount()
268   - && totalPrice >= templateFree.getFreePrice()) {
269   - return true;
270   - }
271   - break;
  218 + return orderItem.getVolume() != null ? orderItem.getVolume() * orderItem.getCount() : 0;
  219 + default:
  220 + throw new IllegalArgumentException(StrUtil.format("未知的计费模式({})", chargeMode));
272 221 }
273   - return false;
274 222 }
  223 +
275 224 }
... ...
yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeDeliveryPriceCalculatorTest.java
... ... @@ -91,7 +91,7 @@ public class TradeDeliveryPriceCalculatorTest extends BaseMockitoUnitTest {
91 91 item -> item.setFreeCount(20).setFreePrice(100));
92 92 // 准备 SP 运费模板数据
93 93 templateRespBO = randomPojo(DeliveryExpressTemplateRespBO.class,
94   - item -> item.setChargeMode(DeliveryExpressChargeModeEnum.PIECE.getType())
  94 + item -> item.setChargeMode(DeliveryExpressChargeModeEnum.COUNT.getType())
95 95 .setCharge(chargeBO).setFree(freeBO));
96 96 }
97 97  
... ... @@ -144,11 +144,11 @@ public class TradeDeliveryPriceCalculatorTest extends BaseMockitoUnitTest {
144 144 // 断言:SKU1
145 145 assertThat(resultBO.getItems().get(0))
146 146 .extracting("price", "count","discountPrice" ,"couponPrice", "pointPrice","deliveryPrice","payPrice")
147   - .containsExactly(100, 2, 0, 0, 0, 1500, 1700);
  147 + .containsExactly(100, 2, 0, 0, 0, 500, 700);
148 148 // 断言:SKU2
149 149 assertThat(resultBO.getItems().get(1))
150 150 .extracting("price", "count","discountPrice" ,"couponPrice", "pointPrice","deliveryPrice","payPrice")
151   - .containsExactly(200, 10, 0, 0, 0, 1500, 3500);
  151 + .containsExactly(200, 10, 0, 0, 0, 2500, 4500);
152 152 // 断言:SKU3 未选中
153 153 assertThat(resultBO.getItems().get(2))
154 154 .extracting("price", "count","discountPrice" ,"couponPrice", "pointPrice","deliveryPrice","payPrice")
... ...