소스 검색

review 价格运算修改.

jason 2 년 전
부모
커밋
36c45bd44e

+ 9 - 0
yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/spu/dto/ProductSpuRespDTO.java

@@ -103,6 +103,15 @@ public class ProductSpuRespDTO {
      */
     private Integer stock;
 
+    // ========== 物流相关字段 =========
+
+    /**
+     * 物流配置模板编号
+     *
+     * 对应 TradeDeliveryExpressTemplateDO 的 id 编号
+     */
+    private Long deliveryTemplateId;
+
     // ========== 统计相关字段 =========
 
     /**

+ 13 - 1
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/delivery/DeliveryExpressTemplateService.java

@@ -6,10 +6,12 @@ import cn.iocoder.yudao.module.trade.controller.admin.delivery.vo.expresstemplat
 import cn.iocoder.yudao.module.trade.controller.admin.delivery.vo.expresstemplate.DeliveryExpressTemplatePageReqVO;
 import cn.iocoder.yudao.module.trade.controller.admin.delivery.vo.expresstemplate.DeliveryExpressTemplateUpdateReqVO;
 import cn.iocoder.yudao.module.trade.dal.dataobject.delivery.DeliveryExpressTemplateDO;
+import cn.iocoder.yudao.module.trade.service.delivery.bo.SpuDeliveryExpressTemplateRespBO;
 
 import javax.validation.Valid;
 import java.util.Collection;
 import java.util.List;
+import java.util.Map;
 
 /**
  * 快递运费模板 Service 接口
@@ -73,11 +75,21 @@ public interface DeliveryExpressTemplateService {
 
     /**
      * 校验快递运费模板
-     *
+     * <p>
      * 如果校验不通过,抛出 {@link cn.iocoder.yudao.framework.common.exception.ServiceException} 异常
      *
      * @param templateId 模板编号
      * @return 快递运费模板
      */
     DeliveryExpressTemplateDO validateDeliveryExpressTemplate(Long templateId);
+
+    /**
+     * 基于指定的 SPU 编号数组和收件人地址区域编号. 获取匹配运费模板
+     *
+     * @param ids    SPU 编号列表
+     * @param areaId 区域编号
+     * @return Map (spuId -> 运费模板设置)
+     */
+    Map<Long, SpuDeliveryExpressTemplateRespBO> getExpressTemplateBySpuIdsAndArea(Collection<Long> ids, Integer areaId);
+
 }

+ 53 - 0
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/delivery/DeliveryExpressTemplateServiceImpl.java

@@ -1,7 +1,10 @@
 package cn.iocoder.yudao.module.trade.service.delivery;
 
 import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.lang.Assert;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.product.api.spu.ProductSpuApi;
+import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO;
 import cn.iocoder.yudao.module.trade.controller.admin.delivery.vo.expresstemplate.*;
 import cn.iocoder.yudao.module.trade.dal.dataobject.delivery.DeliveryExpressTemplateChargeDO;
 import cn.iocoder.yudao.module.trade.dal.dataobject.delivery.DeliveryExpressTemplateDO;
@@ -9,6 +12,7 @@ import cn.iocoder.yudao.module.trade.dal.dataobject.delivery.DeliveryExpressTemp
 import cn.iocoder.yudao.module.trade.dal.mysql.delivery.DeliveryExpressTemplateChargeMapper;
 import cn.iocoder.yudao.module.trade.dal.mysql.delivery.DeliveryExpressTemplateFreeMapper;
 import cn.iocoder.yudao.module.trade.dal.mysql.delivery.DeliveryExpressTemplateMapper;
+import cn.iocoder.yudao.module.trade.service.delivery.bo.SpuDeliveryExpressTemplateRespBO;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.validation.annotation.Validated;
@@ -17,6 +21,7 @@ import javax.annotation.Resource;
 import java.util.*;
 
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
 import static cn.iocoder.yudao.module.trade.convert.delivery.DeliveryExpressTemplateConvert.INSTANCE;
 import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.EXPRESS_TEMPLATE_NAME_DUPLICATE;
@@ -37,6 +42,8 @@ public class DeliveryExpressTemplateServiceImpl implements DeliveryExpressTempla
     private DeliveryExpressTemplateChargeMapper expressTemplateChargeMapper;
     @Resource
     private DeliveryExpressTemplateFreeMapper expressTemplateFreeMapper;
+    @Resource
+    private ProductSpuApi productSpuApi;
 
     @Override
     @Transactional(rollbackFor = Exception.class)
@@ -216,4 +223,50 @@ public class DeliveryExpressTemplateServiceImpl implements DeliveryExpressTempla
         return template;
     }
 
+    @Override
+    public Map<Long, SpuDeliveryExpressTemplateRespBO> getExpressTemplateBySpuIdsAndArea(Collection<Long> spuIds, Integer areaId) {
+        Assert.notNull(areaId, "区域编号 {} 不能为空", areaId);
+        List<ProductSpuRespDTO> spuList = productSpuApi.getSpuList(spuIds);
+        if (CollUtil.isEmpty(spuList)) {
+            return Collections.emptyMap();
+        }
+        Map<Long, ProductSpuRespDTO> spuMap = convertMap(spuList, ProductSpuRespDTO::getDeliveryTemplateId);
+        List<DeliveryExpressTemplateDO> templateList = expressTemplateMapper.selectBatchIds(spuMap.keySet());
+        Map<Long, SpuDeliveryExpressTemplateRespBO> result = new HashMap<>(templateList.size());
+        templateList.forEach(item -> {
+            if (spuMap.containsKey(item.getId())) {
+                ProductSpuRespDTO spuDTO = spuMap.get(item.getId());
+                SpuDeliveryExpressTemplateRespBO bo = new SpuDeliveryExpressTemplateRespBO()
+                        .setSpuId(spuDTO.getId()).setAreaId(areaId)
+                        .setChargeMode(item.getChargeMode())
+                        .setTemplateCharge(findMatchExpressTemplateCharge(item.getId(), areaId))
+                        .setTemplateFree(findMatchExpressTemplateFree(item.getId(), areaId));
+                result.put(spuDTO.getId(), bo);
+            }
+        });
+        return result;
+    }
+
+    private DeliveryExpressTemplateChargeDO findMatchExpressTemplateCharge(Long templateId, Integer areaId) {
+        List<DeliveryExpressTemplateChargeDO> list = expressTemplateChargeMapper.selectListByTemplateId(templateId);
+        for (DeliveryExpressTemplateChargeDO item : list) {
+            // 第一个匹配的返回。 areaId 不能重复
+            if (item.getAreaIds().contains(areaId)) {
+                return item;
+            }
+        }
+        return null;
+    }
+
+    private DeliveryExpressTemplateFreeDO findMatchExpressTemplateFree(Long templateId, Integer areaId) {
+        List<DeliveryExpressTemplateFreeDO> list = expressTemplateFreeMapper.selectListByTemplateId(templateId);
+        for (DeliveryExpressTemplateFreeDO item : list) {
+            // 第一个匹配的返回。 areaId 不能重复
+            if (item.getAreaIds().contains(areaId)) {
+                return item;
+            }
+        }
+        return null;
+    }
+
 }

+ 45 - 0
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/delivery/bo/SpuDeliveryExpressTemplateRespBO.java

@@ -0,0 +1,45 @@
+package cn.iocoder.yudao.module.trade.service.delivery.bo;
+
+import cn.iocoder.yudao.module.trade.dal.dataobject.delivery.DeliveryExpressTemplateChargeDO;
+import cn.iocoder.yudao.module.trade.dal.dataobject.delivery.DeliveryExpressTemplateFreeDO;
+import cn.iocoder.yudao.module.trade.enums.delivery.DeliveryExpressChargeModeEnum;
+import lombok.Data;
+
+/**
+ * SPU 运费模板配置 Resp BO
+ *
+ * @author jason
+ */
+@Data
+public class SpuDeliveryExpressTemplateRespBO {
+
+    /**
+     * 配送计费方式
+     * <p>
+     * 枚举 {@link DeliveryExpressChargeModeEnum}
+     */
+    private Integer chargeMode;
+
+    /**
+     * 运费模板快递运费设置
+     */
+    private DeliveryExpressTemplateChargeDO templateCharge;
+
+    /**
+     * 运费模板包邮设置
+     */
+    private DeliveryExpressTemplateFreeDO templateFree;
+
+    /**
+     * SPU 编号
+     * <p>
+     * 关联  ProductSpuDO 的 id 编号
+     */
+    private Long spuId;
+
+    /**
+     * 区域编号
+     */
+    private Integer areaId;
+
+}

+ 0 - 9
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/bo/TradePriceCalculateReqBO.java

@@ -1,6 +1,5 @@
 package cn.iocoder.yudao.module.trade.service.price.bo;
 
-import cn.iocoder.yudao.module.trade.dal.dataobject.delivery.DeliveryExpressTemplateDO;
 import cn.iocoder.yudao.module.trade.enums.delivery.DeliveryTypeEnum;
 import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum;
 import lombok.Data;
@@ -53,14 +52,6 @@ public class TradePriceCalculateReqBO {
      */
     private Integer deliveryType;
 
-    /**
-     * 配送模板编号
-     *
-     * 关联 {@link DeliveryExpressTemplateDO#getId()}
-     */
-    // TODO @jason:运费模版,是不是每个 SKU 传入哈
-    private Long templateId;
-
     /**
      * 商品 SKU 数组
      */

+ 78 - 88
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeDeliveryPriceCalculator.java

@@ -1,17 +1,17 @@
 package cn.iocoder.yudao.module.trade.service.price.calculator;
 
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.lang.Assert;
 import cn.iocoder.yudao.module.member.api.address.AddressApi;
 import cn.iocoder.yudao.module.member.api.address.dto.AddressRespDTO;
 import cn.iocoder.yudao.module.product.api.sku.ProductSkuApi;
 import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO;
 import cn.iocoder.yudao.module.trade.dal.dataobject.delivery.DeliveryExpressTemplateChargeDO;
-import cn.iocoder.yudao.module.trade.dal.dataobject.delivery.DeliveryExpressTemplateDO;
 import cn.iocoder.yudao.module.trade.dal.dataobject.delivery.DeliveryExpressTemplateFreeDO;
-import cn.iocoder.yudao.module.trade.dal.mysql.delivery.DeliveryExpressTemplateChargeMapper;
-import cn.iocoder.yudao.module.trade.dal.mysql.delivery.DeliveryExpressTemplateFreeMapper;
 import cn.iocoder.yudao.module.trade.enums.delivery.DeliveryExpressChargeModeEnum;
 import cn.iocoder.yudao.module.trade.enums.delivery.DeliveryTypeEnum;
 import cn.iocoder.yudao.module.trade.service.delivery.DeliveryExpressTemplateService;
+import cn.iocoder.yudao.module.trade.service.delivery.bo.SpuDeliveryExpressTemplateRespBO;
 import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO;
 import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO;
 import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO.OrderItem;
@@ -19,7 +19,6 @@ import org.springframework.core.annotation.Order;
 import org.springframework.stereotype.Component;
 
 import javax.annotation.Resource;
-import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -39,14 +38,8 @@ public class TradeDeliveryPriceCalculator implements TradePriceCalculator {
     private AddressApi addressApi;
     @Resource
     private ProductSkuApi productSkuApi;
-
     @Resource
     private DeliveryExpressTemplateService deliveryExpressTemplateService;
-    // TODO @jason:走 Service 哈。Mapper 只允许自己的 Service 调用,保护好数据结构;
-    @Resource
-    private DeliveryExpressTemplateChargeMapper templateChargeMapper;
-    @Resource
-    private DeliveryExpressTemplateFreeMapper templateFreeMapper;
 
     @Override
     public void calculate(TradePriceCalculateReqBO param, TradePriceCalculateRespBO result) {
@@ -54,66 +47,45 @@ public class TradeDeliveryPriceCalculator implements TradePriceCalculator {
         if (param.getDeliveryType() == null || DeliveryTypeEnum.PICK_UP.getMode().equals(param.getDeliveryType())) {
             return;
         }
-
-        if (param.getTemplateId() == null || param.getAddressId() == null) {
+        // 1.2 得到收件地址区域
+        if (param.getAddressId() == null) {
             return;
         }
-        // 1.2 校验运费模板是否存在
-        DeliveryExpressTemplateDO template = deliveryExpressTemplateService.validateDeliveryExpressTemplate(param.getTemplateId());
-
-        // 得到包邮配置
-        List<DeliveryExpressTemplateFreeDO> expressTemplateFreeList = templateFreeMapper.selectListByTemplateId(template.getId());
-        Map<Integer, DeliveryExpressTemplateFreeDO> areaTemplateFreeMap = new HashMap<>();
-        expressTemplateFreeList.forEach(item -> {
-            for (Integer areaId : item.getAreaIds()) {
-                // TODO 需要保证 areaId 不能重复
-                if (!areaTemplateFreeMap.containsKey(areaId)) {
-                    areaTemplateFreeMap.put(areaId, item);
-                }
-            }
-        });
-        // 得到快递运费配置
-        List<DeliveryExpressTemplateChargeDO> expressTemplateChargeList = templateChargeMapper.selectListByTemplateId(template.getId());
-        Map<Integer, DeliveryExpressTemplateChargeDO> areaTemplateChargeMap = new HashMap<>();
-        expressTemplateChargeList.forEach(item -> {
-            for (Integer areaId : item.getAreaIds()) {
-                // areaId 不能重复
-                if (!areaTemplateChargeMap.containsKey(areaId)) {
-                    areaTemplateChargeMap.put(areaId, item);
-                }
-            }
-        });
-        // 得到收件地址区域
         AddressRespDTO address = addressApi.getAddress(param.getAddressId(), param.getUserId());
-        // 1.3 计算快递费用
-        calculateDeliveryPrice(address.getAreaId(), template.getChargeMode(),
-                areaTemplateFreeMap, areaTemplateChargeMap, result);
+        Assert.notNull(address, "收件人({})的地址,不能为空", param.getUserId());
+
+        //1.3 过滤出已选中的商品SKU
+        List<OrderItem> selectedItem = filterList(result.getItems(), OrderItem::getSelected);
+
+        Map<Long, SpuDeliveryExpressTemplateRespBO> spuExpressTemplateMap =
+                deliveryExpressTemplateService.getExpressTemplateBySpuIdsAndArea(
+                        convertSet(selectedItem, OrderItem::getSpuId), address.getAreaId());
+
+        // 1.4 计算配送费用
+        if (CollUtil.isNotEmpty(spuExpressTemplateMap)) {
+            calculateDeliveryPrice(selectedItem, spuExpressTemplateMap, result);
+        }
+
     }
 
-    /**
-     * 校验订单是否满足包邮条件
-     *
-     * @param receiverAreaId        收件人地区的区域编号
-     * @param chargeMode            配送计费方式
-     * @param areaTemplateFreeMap   运费模板包邮区域设置 Map
-     * @param areaTemplateChargeMap 运费模板快递费用设置 Map
-     */
-    private void calculateDeliveryPrice(Integer receiverAreaId,
-                                        Integer chargeMode,
-                                        Map<Integer, DeliveryExpressTemplateFreeDO> areaTemplateFreeMap,
-                                        Map<Integer, DeliveryExpressTemplateChargeDO> areaTemplateChargeMap,
+    private void calculateDeliveryPrice(List<OrderItem> selectedSkus,
+                                        Map<Long, SpuDeliveryExpressTemplateRespBO> spuExpressTemplateMap,
                                         TradePriceCalculateRespBO result) {
-        // 过滤出已选中的商品SKU
-        List<OrderItem> selectedItem = filterList(result.getItems(), OrderItem::getSelected);
-        Set<Long> skuIds = convertSet(selectedItem, OrderItem::getSkuId);
+        Set<Long> skuIds = convertSet(selectedSkus, OrderItem::getSkuId);
         // 得到SKU 详情。得到 重量体积
         Map<Long, ProductSkuRespDTO> skuRespMap = convertMap(productSkuApi.getSkuList(skuIds), ProductSkuRespDTO::getId);
-        // 一个 spuId 可能对应多条订单商品 SKU
-        // TODO @jason:得确认下,按照 sku 算,还是 spu 算;
-        Map<Long, List<OrderItem>> spuIdItemMap = convertMultiMap(selectedItem, OrderItem::getSpuId);
+        // 按spu 来计算商品的运费 一个 spuId 可能对应多条订单商品 SKU,
+        Map<Long, List<OrderItem>> spuIdItemMap = convertMultiMap(selectedSkus, OrderItem::getSpuId);
+
         // 依次计算每个 SPU 的快递运费
         for (Map.Entry<Long, List<OrderItem>> entry : spuIdItemMap.entrySet()) {
+            Long spuId  = entry.getKey();
             List<OrderItem> orderItems = entry.getValue();
+            SpuDeliveryExpressTemplateRespBO templateBO = spuExpressTemplateMap.get(spuId);
+            if (templateBO == null) {
+                // 记录错误日志
+                continue;
+            }
             // 总件数, 总金额, 总重量, 总体积
             int totalCount = 0;
             int totalPrice = 0;
@@ -121,51 +93,67 @@ public class TradeDeliveryPriceCalculator implements TradePriceCalculator {
             double totalVolume = 0;
             for (OrderItem orderItem : orderItems) {
                 totalCount += orderItem.getCount();
-                totalPrice += orderItem.getPrice(); // TODO jason:应该按照 payPrice?
+                totalPrice += orderItem.getPayPrice(); // 先按应付总金额来算,后面确认一下
                 ProductSkuRespDTO skuResp = skuRespMap.get(orderItem.getSkuId());
                 if (skuResp != null) {
-                    totalWeight = totalWeight + skuResp.getWeight(); // TODO @jason:* 数量
-                    totalVolume = totalVolume + skuResp.getVolume();
+                    totalWeight = totalWeight + skuResp.getWeight() * orderItem.getCount();
+                    totalVolume = totalVolume + skuResp.getVolume() * orderItem.getCount();
                 }
             }
             // 优先判断是否包邮. 如果包邮不计算快递运费
-            if (areaTemplateFreeMap.containsKey(receiverAreaId) &&
-                    checkExpressFree(chargeMode, totalCount, totalWeight,
-                            totalVolume, totalPrice, areaTemplateFreeMap.get(receiverAreaId))) {
+            if (checkExpressFree(templateBO.getChargeMode(), totalCount, totalWeight,
+                            totalVolume, totalPrice, templateBO.getTemplateFree())) {
                 continue;
             }
-            // 计算快递运费
-            // TODO @jason:貌似也可以抽成 checkExpressFree 类似方法
-            if (areaTemplateChargeMap.containsKey(receiverAreaId)) {
-                DeliveryExpressTemplateChargeDO templateCharge = areaTemplateChargeMap.get(receiverAreaId);
-                DeliveryExpressChargeModeEnum chargeModeEnum = DeliveryExpressChargeModeEnum.valueOf(chargeMode);
-                switch (chargeModeEnum) {
-                    case PIECE: {
-                        calculateExpressFeeBySpu(totalCount, templateCharge, orderItems);
-                        break;
-                    }
-                    case WEIGHT: {
-                        calculateExpressFeeBySpu(totalWeight, templateCharge, orderItems);
-                        break;
-                    }
-                    case VOLUME: {
-                        calculateExpressFeeBySpu(totalVolume, templateCharge, orderItems);
-                        break;
-                    }
-                }
+            if (templateBO.getTemplateCharge() == null) {
+                continue;
             }
+            // 计算快递运费
+            calculateExpressFeeByChargeMode(totalCount, totalWeight, totalVolume,
+                    templateBO.getChargeMode(), templateBO.getTemplateCharge(), orderItems);
+
         }
         TradePriceCalculatorHelper.recountAllPrice(result);
     }
 
     /**
-     * 按 spu 来计算快递费用
+     * 按配送方式来计算运费
+     *
+     * @param totalCount  总件数
+     * @param totalWeight 总重量
+     * @param totalVolume 总体积
+     * @param chargeMode  配送计费方式
+     * @param templateCharge 快递运费配置
+     * @param orderItems SKU 商品项目
+     */
+    private void calculateExpressFeeByChargeMode(double totalCount, double totalWeight, double totalVolume,
+                                                 int chargeMode, DeliveryExpressTemplateChargeDO templateCharge,
+                                                 List<OrderItem> orderItems) {
+        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 calculateExpressFeeBySpu(double total, DeliveryExpressTemplateChargeDO templateCharge, List<OrderItem> orderItems) {
+    private void calculateExpressFee(double total, DeliveryExpressTemplateChargeDO templateCharge, List<OrderItem> orderItems) {
         int deliveryPrice;
         if (total <= templateCharge.getStartCount()) {
             deliveryPrice = templateCharge.getStartPrice();
@@ -176,8 +164,7 @@ public class TradeDeliveryPriceCalculator implements TradePriceCalculator {
             int extraPrice = templateCharge.getExtraPrice() * extraNum;
             deliveryPrice = templateCharge.getStartPrice() + extraPrice;
         }
-        // TODO @芋艿 分摊快递费用到 SKU. 是不是搞复杂了;
-        // TODO @jason:因为退费的时候,可能按照 SKU 考虑退费金额
+        // 分摊快递费用到 SKU. 退费的时候,可能按照 SKU 考虑退费金额
         divideDeliveryPrice(deliveryPrice, orderItems);
     }
 
@@ -207,6 +194,9 @@ public class TradeDeliveryPriceCalculator implements TradePriceCalculator {
      */
     private boolean checkExpressFree(Integer chargeMode, int totalCount, double totalWeight,
                                      double totalVolume, int totalPrice, DeliveryExpressTemplateFreeDO templateFree) {
+        if (templateFree == null) {
+            return false;
+        }
         DeliveryExpressChargeModeEnum chargeModeEnum = DeliveryExpressChargeModeEnum.valueOf(chargeMode);
         switch (chargeModeEnum) {
             case PIECE: