Browse Source

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

YunaiV 1 year ago
parent
commit
7c4fbc63dc

+ 1 - 1
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;
 @Getter
 public enum DeliveryExpressChargeModeEnum implements IntArrayValuable {
 
-    PIECE(1, "按件"),
+    COUNT(1, "按件"),
     WEIGHT(2,"按重量"),
     VOLUME(3, "按体积");
 

+ 57 - 108
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;
 
 import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.lang.Assert;
+import cn.hutool.core.util.StrUtil;
 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
 import cn.iocoder.yudao.module.member.api.address.AddressApi;
 import cn.iocoder.yudao.module.member.api.address.dto.AddressRespDTO;
@@ -55,13 +56,13 @@ public class TradeDeliveryPriceCalculator implements TradePriceCalculator {
             return;
         }
         if (DeliveryTypeEnum.PICK_UP.getType().equals(param.getDeliveryType())) {
-            calculateByPickUp(param, result);
+            calculateByPickUp(param);
         } else if (DeliveryTypeEnum.EXPRESS.getType().equals(param.getDeliveryType())) {
             calculateExpress(param, result);
         }
     }
 
-    private void calculateByPickUp(TradePriceCalculateReqBO param, TradePriceCalculateRespBO result) {
+    private void calculateByPickUp(TradePriceCalculateReqBO param) {
         if (param.getPickUpStoreId() == null) {
             throw exception(PRICE_CALCULATE_DELIVERY_PRICE_PICK_UP_STORE_IS_EMPTY);
         }
@@ -82,12 +83,12 @@ public class TradeDeliveryPriceCalculator implements TradePriceCalculator {
         Assert.notNull(address, "收件人({})的地址,不能为空", param.getUserId());
 
         // 情况一:全局包邮
-        if (isGlobalExpressFree(param, result)) {
+        if (isGlobalExpressFree(result)) {
             return;
         }
 
-        // 情况二:
-        // 2.1 过滤出已选中的商品SKU
+        // 情况二:快递模版
+        // 2.1 过滤出已选中的商品 SKU
         List<OrderItem> selectedItem = filterList(result.getItems(), OrderItem::getSelected);
         Set<Long> deliveryTemplateIds = convertSet(selectedItem, OrderItem::getDeliveryTemplateId);
         Map<Long, DeliveryExpressTemplateRespBO> expressTemplateMap =
@@ -103,11 +104,10 @@ public class TradeDeliveryPriceCalculator implements TradePriceCalculator {
     /**
      * 是否全局包邮
      *
-     * @param param 计算信息
      * @param result 计算结果
      * @return 是否包邮
      */
-    private boolean isGlobalExpressFree(TradePriceCalculateReqBO param, TradePriceCalculateRespBO result) {
+    private boolean isGlobalExpressFree(TradePriceCalculateRespBO result) {
         TradeConfigDO config = tradeConfigService.getTradeConfig();
         return config != null
                 && Boolean.TRUE.equals(config.getDeliveryExpressFreeEnabled()) // 开启包邮
@@ -118,9 +118,9 @@ public class TradeDeliveryPriceCalculator implements TradePriceCalculator {
                                         Map<Long, DeliveryExpressTemplateRespBO> expressTemplateMap,
                                         TradePriceCalculateRespBO result) {
         // 按商品运费模板来计算商品的运费:相同的运费模板可能对应多条订单商品 SKU
-        Map<Long, List<OrderItem>> tplIdItemMap = convertMultiMap(selectedSkus, OrderItem::getDeliveryTemplateId);
+        Map<Long, List<OrderItem>> template2ItemMap = convertMultiMap(selectedSkus, OrderItem::getDeliveryTemplateId);
         // 依次计算快递运费
-        for (Map.Entry<Long, List<OrderItem>> entry : tplIdItemMap.entrySet()) {
+        for (Map.Entry<Long, List<OrderItem>> entry : template2ItemMap.entrySet()) {
             Long templateId  = entry.getKey();
             List<OrderItem> orderItems = entry.getValue();
             DeliveryExpressTemplateRespBO templateBO = expressTemplateMap.get(templateId);
@@ -128,30 +128,12 @@ public class TradeDeliveryPriceCalculator implements TradePriceCalculator {
                 log.error("[calculateDeliveryPrice][不能计算快递运费,找不到 templateId({}) 对应的运费模板配置]", templateId);
                 continue;
             }
-            // 总件数, 总金额, 总重量, 总体积
-            int totalCount = 0;
-            int totalPrice = 0;
-            double totalWeight = 0;
-            double totalVolume = 0;
-            for (OrderItem orderItem : orderItems) {
-                totalCount  += orderItem.getCount();
-                totalPrice  += orderItem.getPayPrice();
-                if (orderItem.getWeight() != null) {
-                    totalWeight += totalWeight + orderItem.getWeight() * orderItem.getCount();
-                }
-                if (orderItem.getVolume() != null) {
-                    totalVolume += totalVolume + orderItem.getVolume() * orderItem.getCount();
-                }
-            }
-            // 优先判断是否包邮. 如果包邮不计算快递运费
-            if (isExpressFree(templateBO.getChargeMode(), totalCount, totalWeight,
-                            totalVolume, totalPrice, templateBO.getFree())) {
+            // 1. 优先判断是否包邮。如果包邮不计算快递运费
+            if (isExpressTemplateFree(orderItems, templateBO.getChargeMode(), templateBO.getFree())) {
                 continue;
             }
-            // 计算快递运费
-            calculateExpressFeeByChargeMode(totalCount, totalWeight, totalVolume,
-                    templateBO.getChargeMode(), templateBO.getCharge(), orderItems);
-
+            // 2. 计算快递运费
+            calculateExpressFeeByChargeMode(orderItems, templateBO.getChargeMode(), templateBO.getCharge());
         }
         TradePriceCalculatorHelper.recountAllPrice(result);
     }
@@ -159,73 +141,44 @@ public class TradeDeliveryPriceCalculator implements TradePriceCalculator {
     /**
      * 按配送方式来计算运费
      *
-     * @param totalCount  总件数
-     * @param totalWeight 总重量
-     * @param totalVolume 总体积
+     * @param orderItems SKU 商品项目
      * @param chargeMode  配送计费方式
      * @param templateCharge 快递运费配置
-     * @param orderItems SKU 商品项目
      */
-    private void calculateExpressFeeByChargeMode(double totalCount, double totalWeight, double totalVolume,
-                                                 int chargeMode, DeliveryExpressTemplateRespBO.Charge templateCharge,
-                                                 List<OrderItem> orderItems) {
+    private void calculateExpressFeeByChargeMode(List<OrderItem> orderItems, Integer chargeMode,
+                                                 DeliveryExpressTemplateRespBO.Charge templateCharge) {
         if (templateCharge == null) {
             log.error("[calculateExpressFeeByChargeMode][计算快递运费时,找不到 SKU({}) 对应的运费模版]", orderItems);
             return;
         }
-        DeliveryExpressChargeModeEnum chargeModeEnum = DeliveryExpressChargeModeEnum.valueOf(chargeMode);
-        switch (chargeModeEnum) {
-            case PIECE: {
-                calculateExpressFee(totalCount, templateCharge, orderItems);
-                break;
-            }
-            case WEIGHT: {
-                calculateExpressFee(totalWeight, templateCharge, orderItems);
-                break;
-            }
-            case VOLUME: {
-                calculateExpressFee(totalVolume, templateCharge, orderItems);
-                break;
-            }
-        }
-    }
-
-    /**
-     * 计算 SKU 商品快递费用
-     *
-     * @param total          总件数/总重量/总体积
-     * @param templateCharge 快递运费配置
-     * @param orderItems     SKU 商品项目
-     */
-    private void calculateExpressFee(double total, DeliveryExpressTemplateRespBO.Charge templateCharge, List<OrderItem> orderItems) {
+        double totalChargeValue = getTotalChargeValue(orderItems, chargeMode);
+        // 1. 计算 SKU 商品快递费用
         int deliveryPrice;
-        if (total <= templateCharge.getStartCount()) {
+        if (totalChargeValue <= templateCharge.getStartCount()) {
             deliveryPrice = templateCharge.getStartPrice();
         } else {
-            double remainWeight = total - templateCharge.getStartCount();
+            double remainWeight = totalChargeValue - templateCharge.getStartCount();
             // 剩余重量/ 续件 = 续件的次数. 向上取整
             int extraNum = (int) Math.ceil(remainWeight / templateCharge.getExtraCount());
             int extraPrice = templateCharge.getExtraPrice() * extraNum;
             deliveryPrice = templateCharge.getStartPrice() + extraPrice;
         }
-        // 分摊快递费用到 SKU. 退费的时候,可能按照 SKU 考虑退费金额
-        divideDeliveryPrice(deliveryPrice, orderItems);
-    }
 
-    /**
-     * 快递运费分摊到每个 SKU 商品上
-     *
-     * @param deliveryPrice 快递运费
-     * @param orderItems    SKU 商品
-     */
-    private void divideDeliveryPrice(int deliveryPrice, List<OrderItem> orderItems) {
-        // TODO @jason:分摊的话,是不是要按照比例呀?重量、价格、数量等等,
-        //  按比例是不是有点复杂。后面看看是否需要;
-        // TODO 可以看看别的项目怎么搞的哈。
-        int dividePrice = deliveryPrice / orderItems.size();
-        for (OrderItem item : orderItems) {
+        // 2. 分摊快递费用到 SKU. 退费的时候,可能按照 SKU 考虑退费金额
+        int remainPrice = deliveryPrice;
+        for (int i = 0; i < orderItems.size(); i++) {
+            TradePriceCalculateRespBO.OrderItem item = orderItems.get(i);
+            int partPrice;
+            double chargeValue = getChargeValue(item, chargeMode);
+            if (i < orderItems.size() - 1) { // 减一的原因,是因为拆分时,如果按照比例,可能会出现.所以最后一个,使用反减
+                partPrice = (int) (deliveryPrice * (chargeValue / totalChargeValue));
+                remainPrice -= partPrice;
+            } else {
+                partPrice = remainPrice;
+            }
+            Assert.isTrue(partPrice >= 0, "分摊金额必须大于等于 0");
             // 更新快递运费
-            item.setDeliveryPrice(dividePrice);
+            item.setDeliveryPrice(partPrice);
             TradePriceCalculatorHelper.recountPayPrice(item);
         }
     }
@@ -234,42 +187,38 @@ public class TradeDeliveryPriceCalculator implements TradePriceCalculator {
      * 检查是否包邮
      *
      * @param chargeMode   配送计费方式
-     * @param totalCount   总件数
-     * @param totalWeight  总重量
-     * @param totalVolume  总体积
-     * @param totalPrice   总金额
      * @param templateFree 包邮配置
      */
-    private boolean isExpressFree(Integer chargeMode, int totalCount, double totalWeight,
-                                  double totalVolume, int totalPrice, DeliveryExpressTemplateRespBO.Free templateFree) {
+    private boolean isExpressTemplateFree(List<OrderItem> orderItems, Integer chargeMode,
+                                          DeliveryExpressTemplateRespBO.Free templateFree) {
         if (templateFree == null) {
             return false;
         }
+        double totalChargeValue = getTotalChargeValue(orderItems, chargeMode);
+        double totalPrice = TradePriceCalculatorHelper.calculateTotalPayPrice(orderItems);
+        return totalChargeValue >= templateFree.getFreeCount() && totalPrice >= templateFree.getFreePrice();
+    }
+
+    private double getTotalChargeValue(List<OrderItem> orderItems, Integer chargeMode) {
+        double total = 0;
+        for (OrderItem orderItem : orderItems) {
+            total += getChargeValue(orderItem, chargeMode);
+        }
+        return total;
+    }
+
+    private double getChargeValue(OrderItem orderItem, Integer chargeMode) {
         DeliveryExpressChargeModeEnum chargeModeEnum = DeliveryExpressChargeModeEnum.valueOf(chargeMode);
         switch (chargeModeEnum) {
-            case PIECE:
-                // 两个条件都满足才包邮
-                if (totalCount >= templateFree.getFreeCount() && totalPrice >= templateFree.getFreePrice()) {
-                    return true;
-                }
-                break;
+            case COUNT:
+                return orderItem.getCount();
             case WEIGHT:
-                // freeCount 是不是应该是 double ??
-                // TODO @jason:要不配置的时候,把它的单位和商品对齐?到底是 kg、还是斤
-                // TODO @芋艿 目前 包邮 件数/重量/体积 都用的是这个字段
-                // TODO @jason:那要不快递模版也改成 kg?这样是不是就不用 double ?
-                if (totalWeight >= templateFree.getFreeCount()
-                        && totalPrice >= templateFree.getFreePrice()) {
-                    return true;
-                }
-                break;
+                return orderItem.getWeight() != null ? orderItem.getWeight() * orderItem.getCount() : 0;
             case VOLUME:
-                if (totalVolume >= templateFree.getFreeCount()
-                        && totalPrice >= templateFree.getFreePrice()) {
-                    return true;
-                }
-                break;
+                return orderItem.getVolume() != null ? orderItem.getVolume() * orderItem.getCount() : 0;
+            default:
+                throw new IllegalArgumentException(StrUtil.format("未知的计费模式({})", chargeMode));
         }
-        return false;
     }
+
 }

+ 3 - 3
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 {
                 item -> item.setFreeCount(20).setFreePrice(100));
         // 准备 SP 运费模板数据
         templateRespBO = randomPojo(DeliveryExpressTemplateRespBO.class,
-                item -> item.setChargeMode(DeliveryExpressChargeModeEnum.PIECE.getType())
+                item -> item.setChargeMode(DeliveryExpressChargeModeEnum.COUNT.getType())
                         .setCharge(chargeBO).setFree(freeBO));
     }
 
@@ -144,11 +144,11 @@ public class TradeDeliveryPriceCalculatorTest  extends BaseMockitoUnitTest {
         // 断言:SKU1
         assertThat(resultBO.getItems().get(0))
                 .extracting("price", "count","discountPrice" ,"couponPrice", "pointPrice","deliveryPrice","payPrice")
-                .containsExactly(100, 2, 0, 0, 0, 1500, 1700);
+                .containsExactly(100, 2, 0, 0, 0, 500, 700);
         // 断言:SKU2
         assertThat(resultBO.getItems().get(1))
                 .extracting("price", "count","discountPrice" ,"couponPrice", "pointPrice","deliveryPrice","payPrice")
-                .containsExactly(200, 10, 0, 0, 0, 1500, 3500);
+                .containsExactly(200, 10, 0, 0, 0, 2500, 4500);
         // 断言:SKU3 未选中
         assertThat(resultBO.getItems().get(2))
                 .extracting("price", "count","discountPrice" ,"couponPrice", "pointPrice","deliveryPrice","payPrice")