Commit 66210e5ee2664a3c6cf20fe7f4be93c12d1ca911

Authored by YunaiV
2 parents bb8e44a2 dde89d51

Merge remote-tracking branch 'origin/feature/mall_product' into feature/mall_product

Showing 11 changed files with 304 additions and 95 deletions
yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/ErrorCodeConstants.java
... ... @@ -50,8 +50,10 @@ public interface ErrorCodeConstants {
50 50 // TODO @jason:最好每个模块一段哈。express 一个;exmpresstemplate 一个;pickup 一个
51 51 ErrorCode EXPRESS_CODE_DUPLICATE = new ErrorCode(1011003001, "已经存在该编码的快递公司");
52 52 ErrorCode EXPRESS_TEMPLATE_NOT_EXISTS = new ErrorCode(1011003002, "运费模板不存在");
53   - ErrorCode EXPRESS_TEMPLATE_NAME_DUPLICATE = new ErrorCode(1011003002, "已经存在该运费模板名");
54   - ErrorCode PICK_UP_STORE_NOT_EXISTS = new ErrorCode(1011003003, "自提门店不存在");
  53 + ErrorCode EXPRESS_TEMPLATE_NAME_DUPLICATE = new ErrorCode(1011003003, "已经存在该运费模板名");
  54 + ErrorCode DELIVERY_EXPRESS_USER_ADDRESS_IS_EMPTY = new ErrorCode(1011003004, "计算快递运费时,收件人地址编号为空");
  55 + ErrorCode PRODUCT_EXPRESS_TEMPLATE_NOT_FOUND = new ErrorCode(1011003005, "找不到到商品对应的运费模板");
  56 + ErrorCode PICK_UP_STORE_NOT_EXISTS = new ErrorCode(1011003006, "自提门店不存在");
55 57  
56 58 // ========== Price 相关 1011004000 ============
57 59 ErrorCode PRICE_CALCULATE_PAY_PRICE_ILLEGAL = new ErrorCode(1011004000, "支付价格计算异常,原因:价格小于等于 0");
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/convert/delivery/DeliveryExpressTemplateConvert.java
... ... @@ -6,6 +6,8 @@ import cn.iocoder.yudao.module.trade.controller.admin.delivery.vo.expresstemplat
6 6 import cn.iocoder.yudao.module.trade.dal.dataobject.delivery.DeliveryExpressTemplateChargeDO;
7 7 import cn.iocoder.yudao.module.trade.dal.dataobject.delivery.DeliveryExpressTemplateDO;
8 8 import cn.iocoder.yudao.module.trade.dal.dataobject.delivery.DeliveryExpressTemplateFreeDO;
  9 +import cn.iocoder.yudao.module.trade.service.delivery.bo.DeliveryExpressTemplateChargeBO;
  10 +import cn.iocoder.yudao.module.trade.service.delivery.bo.DeliveryExpressTemplateFreeBO;
9 11 import org.mapstruct.Mapper;
10 12 import org.mapstruct.factory.Mappers;
11 13  
... ... @@ -47,6 +49,8 @@ public interface DeliveryExpressTemplateConvert {
47 49  
48 50 DeliveryExpressTemplateChargeDO convertTemplateCharge(DeliveryExpressTemplateUpdateReqVO.ExpressTemplateChargeUpdateVO vo);
49 51  
  52 + DeliveryExpressTemplateChargeBO convertTemplateCharge(DeliveryExpressTemplateChargeDO bean);
  53 +
50 54 default List<DeliveryExpressTemplateChargeDO> convertTemplateChargeList(Long templateId, Integer chargeMode, List<ExpressTemplateChargeBaseVO> list) {
51 55 return CollectionUtils.convertList(list, vo -> convertTemplateCharge(templateId, chargeMode, vo));
52 56 }
... ... @@ -57,6 +61,8 @@ public interface DeliveryExpressTemplateConvert {
57 61  
58 62 DeliveryExpressTemplateFreeDO convertTemplateFree(DeliveryExpressTemplateUpdateReqVO.ExpressTemplateFreeUpdateVO vo);
59 63  
  64 + DeliveryExpressTemplateFreeBO convertTemplateFree(DeliveryExpressTemplateFreeDO bean);
  65 +
60 66 List<ExpressTemplateChargeBaseVO> convertTemplateChargeList(List<DeliveryExpressTemplateChargeDO> list);
61 67  
62 68 List<ExpressTemplateFreeBaseVO> convertTemplateFreeList(List<DeliveryExpressTemplateFreeDO> list);
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/delivery/DeliveryExpressTemplateService.java
... ... @@ -83,14 +83,13 @@ public interface DeliveryExpressTemplateService {
83 83 */
84 84 DeliveryExpressTemplateDO validateDeliveryExpressTemplate(Long templateId);
85 85  
86   - // TODO @jason 方法名可以改成 getExpressTemplateMapBySpuIdsAndArea
87 86 /**
88 87 * 基于指定的 SPU 编号数组和收件人地址区域编号. 获取匹配运费模板
89 88 *
90   - * @param ids SPU 编号列表 // TODO @jason:模版编号?
  89 + * @param spuIds SPU 编号列表
91 90 * @param areaId 区域编号
92 91 * @return Map (spuId -> 运费模板设置)
93 92 */
94   - Map<Long, SpuDeliveryExpressTemplateRespBO> getExpressTemplateBySpuIdsAndArea(Collection<Long> ids, Integer areaId);
  93 + Map<Long, SpuDeliveryExpressTemplateRespBO> getExpressTemplateMapBySpuIdsAndArea(Collection<Long> spuIds, Integer areaId);
95 94  
96 95 }
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/delivery/DeliveryExpressTemplateServiceImpl.java
... ... @@ -15,6 +15,8 @@ import cn.iocoder.yudao.module.trade.dal.dataobject.delivery.DeliveryExpressTemp
15 15 import cn.iocoder.yudao.module.trade.dal.mysql.delivery.DeliveryExpressTemplateChargeMapper;
16 16 import cn.iocoder.yudao.module.trade.dal.mysql.delivery.DeliveryExpressTemplateFreeMapper;
17 17 import cn.iocoder.yudao.module.trade.dal.mysql.delivery.DeliveryExpressTemplateMapper;
  18 +import cn.iocoder.yudao.module.trade.service.delivery.bo.DeliveryExpressTemplateChargeBO;
  19 +import cn.iocoder.yudao.module.trade.service.delivery.bo.DeliveryExpressTemplateFreeBO;
18 20 import cn.iocoder.yudao.module.trade.service.delivery.bo.SpuDeliveryExpressTemplateRespBO;
19 21 import org.springframework.stereotype.Service;
20 22 import org.springframework.transaction.annotation.Transactional;
... ... @@ -24,8 +26,7 @@ import javax.annotation.Resource;
24 26 import java.util.*;
25 27  
26 28 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
27   -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
28   -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
  29 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
29 30 import static cn.iocoder.yudao.module.trade.convert.delivery.DeliveryExpressTemplateConvert.INSTANCE;
30 31 import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.EXPRESS_TEMPLATE_NAME_DUPLICATE;
31 32 import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.EXPRESS_TEMPLATE_NOT_EXISTS;
... ... @@ -227,7 +228,7 @@ public class DeliveryExpressTemplateServiceImpl implements DeliveryExpressTempla
227 228 }
228 229  
229 230 @Override
230   - public Map<Long, SpuDeliveryExpressTemplateRespBO> getExpressTemplateBySpuIdsAndArea(Collection<Long> spuIds, Integer areaId) {
  231 + public Map<Long, SpuDeliveryExpressTemplateRespBO> getExpressTemplateMapBySpuIdsAndArea(Collection<Long> spuIds, Integer areaId) {
231 232 Assert.notNull(areaId, "区域编号 {} 不能为空", areaId);
232 233 List<ProductSpuRespDTO> spuList = productSpuApi.getSpuList(spuIds);
233 234 if (CollUtil.isEmpty(spuList)) {
... ... @@ -237,43 +238,32 @@ public class DeliveryExpressTemplateServiceImpl implements DeliveryExpressTempla
237 238 List<DeliveryExpressTemplateDO> templateList = expressTemplateMapper.selectBatchIds(spuMap.keySet());
238 239 Map<Long, SpuDeliveryExpressTemplateRespBO> result = new HashMap<>(templateList.size());
239 240 templateList.forEach(item -> {
240   - // TODO @jason:if return ,更简洁哈;
241   - if (spuMap.containsKey(item.getId())) {
242   - ProductSpuRespDTO spu = spuMap.get(item.getId());
243   - SpuDeliveryExpressTemplateRespBO bo = new SpuDeliveryExpressTemplateRespBO()
244   - .setSpuId(spu.getId()).setAreaId(areaId)
245   - .setChargeMode(item.getChargeMode())
246   - // TODO @jason:是不是只要查询到一个,就不用查询下一个了;TemplateCharge 和 TemplateFree
247   - .setTemplateCharge(findMatchExpressTemplateCharge(item.getId(), areaId))
248   - .setTemplateFree(findMatchExpressTemplateFree(item.getId(), areaId));
249   - result.put(spu.getId(), bo);
  241 + ProductSpuRespDTO spu = spuMap.get(item.getId());
  242 + if (spu == null) {
  243 + return;
250 244 }
  245 + SpuDeliveryExpressTemplateRespBO bo = new SpuDeliveryExpressTemplateRespBO()
  246 + .setChargeMode(item.getChargeMode())
  247 + // TODO @jason:是不是只要查询到一个,就不用查询下一个了;TemplateCharge 和 TemplateFree
  248 + // @芋艿 包邮的优先级> 费用的优先级 所以两个都要查询
  249 + .setTemplateCharge(findMatchExpressTemplateCharge(item.getId(), areaId))
  250 + .setTemplateFree(findMatchExpressTemplateFree(item.getId(), areaId));
  251 + result.put(spu.getId(), bo);
251 252 });
252 253 return result;
253 254 }
254 255  
255   - private DeliveryExpressTemplateChargeDO findMatchExpressTemplateCharge(Long templateId, Integer areaId) {
256   - List<DeliveryExpressTemplateChargeDO> list = expressTemplateChargeMapper.selectListByTemplateId(templateId);
257   - // TODO @jason:可以使用 CollectionUtils.findFirst()
258   - for (DeliveryExpressTemplateChargeDO item : list) {
259   - // 第一个匹配的返回。 areaId 不能重复
260   - if (item.getAreaIds().contains(areaId)) {
261   - return item;
262   - }
263   - }
264   - return null;
  256 + private DeliveryExpressTemplateChargeBO findMatchExpressTemplateCharge(Long templateId, Integer areaId) {
  257 + return INSTANCE.convertTemplateCharge(findFirst(
  258 + expressTemplateChargeMapper.selectListByTemplateId(templateId), item -> item.getAreaIds().contains(areaId)
  259 + )
  260 + );
265 261 }
266 262  
267   - private DeliveryExpressTemplateFreeDO findMatchExpressTemplateFree(Long templateId, Integer areaId) {
268   - List<DeliveryExpressTemplateFreeDO> list = expressTemplateFreeMapper.selectListByTemplateId(templateId);
269   - // TODO @jason:可以使用 CollectionUtils.findFirst()
270   - for (DeliveryExpressTemplateFreeDO item : list) {
271   - // 第一个匹配的返回。 areaId 不能重复
272   - if (item.getAreaIds().contains(areaId)) {
273   - return item;
274   - }
275   - }
276   - return null;
  263 + private DeliveryExpressTemplateFreeBO findMatchExpressTemplateFree(Long templateId, Integer areaId) {
  264 + return INSTANCE.convertTemplateFree(findFirst(
  265 + expressTemplateFreeMapper.selectListByTemplateId(templateId), item -> item.getAreaIds().contains(areaId)
  266 + ));
277 267 }
278 268  
279 269 }
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/delivery/bo/DeliveryExpressTemplateChargeBO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.trade.service.delivery.bo;
  2 +
  3 +import lombok.Data;
  4 +
  5 +/**
  6 + * 快递运费模板费用配置 BO
  7 + *
  8 + * @author jason
  9 + */
  10 +@Data
  11 +public class DeliveryExpressTemplateChargeBO {
  12 +
  13 + /**
  14 + * 首件数量(件数,重量,或体积)
  15 + */
  16 + private Double startCount;
  17 + /**
  18 + * 起步价,单位:分
  19 + */
  20 + private Integer startPrice;
  21 + /**
  22 + * 续件数量(件, 重量,或体积)
  23 + */
  24 + private Double extraCount;
  25 + /**
  26 + * 额外价,单位:分
  27 + */
  28 + private Integer extraPrice;
  29 +}
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/delivery/bo/DeliveryExpressTemplateFreeBO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.trade.service.delivery.bo;
  2 +
  3 +import lombok.Data;
  4 +
  5 +/**
  6 + * 快递运费模板包邮配置 BO
  7 + *
  8 + * @author jason
  9 + */
  10 +@Data
  11 +public class DeliveryExpressTemplateFreeBO {
  12 +
  13 + /**
  14 + * 包邮金额,单位:分
  15 + *
  16 + * 订单总金额 > 包邮金额时,才免运费
  17 + */
  18 + private Integer freePrice;
  19 +
  20 + /**
  21 + * 包邮件数
  22 + *
  23 + * 订单总件数 > 包邮件数时,才免运费
  24 + */
  25 + private Integer freeCount;
  26 +}
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/delivery/bo/SpuDeliveryExpressTemplateRespBO.java
1 1 package cn.iocoder.yudao.module.trade.service.delivery.bo;
2 2  
3   -import cn.iocoder.yudao.module.trade.dal.dataobject.delivery.DeliveryExpressTemplateChargeDO;
4   -import cn.iocoder.yudao.module.trade.dal.dataobject.delivery.DeliveryExpressTemplateFreeDO;
5 3 import cn.iocoder.yudao.module.trade.enums.delivery.DeliveryExpressChargeModeEnum;
6 4 import lombok.Data;
7 5  
... ... @@ -13,8 +11,6 @@ import lombok.Data;
13 11 @Data
14 12 public class SpuDeliveryExpressTemplateRespBO {
15 13  
16   - // TODO @jason:是不是可以简单一点;是否包邮;然后直接把 templateCharge、templateFree 需要的字段平铺开
17   -
18 14 /**
19 15 * 配送计费方式
20 16 *
... ... @@ -25,24 +21,11 @@ public class SpuDeliveryExpressTemplateRespBO {
25 21 /**
26 22 * 运费模板快递运费设置
27 23 */
28   - private DeliveryExpressTemplateChargeDO templateCharge;
  24 + private DeliveryExpressTemplateChargeBO templateCharge;
29 25  
30 26 /**
31 27 * 运费模板包邮设置
32 28 */
33   - private DeliveryExpressTemplateFreeDO templateFree;
34   -
35   - // TODO @jason:下面两个字段不用返回也可以呀
36   - /**
37   - * SPU 编号
38   - * <p>
39   - * 关联 ProductSpuDO 的 id 编号
40   - */
41   - private Long spuId;
42   -
43   - /**
44   - * 区域编号
45   - */
46   - private Integer areaId;
  29 + private DeliveryExpressTemplateFreeBO templateFree;
47 30  
48 31 }
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/bo/TradePriceCalculateRespBO.java
... ... @@ -164,6 +164,15 @@ public class TradePriceCalculateRespBO {
164 164 */
165 165 private Integer payPrice;
166 166  
  167 + /**
  168 + * 商品重量,单位:kg 千克
  169 + */
  170 + private Double weight;
  171 + /**
  172 + * 商品体积,单位:m^3 平米
  173 + */
  174 + private Double volume;
  175 +
167 176 // ========== 商品信息 ==========
168 177 /**
169 178 * 商品名
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeDeliveryPriceCalculator.java
... ... @@ -4,17 +4,16 @@ import cn.hutool.core.collection.CollUtil;
4 4 import cn.hutool.core.lang.Assert;
5 5 import cn.iocoder.yudao.module.member.api.address.AddressApi;
6 6 import cn.iocoder.yudao.module.member.api.address.dto.AddressRespDTO;
7   -import cn.iocoder.yudao.module.product.api.sku.ProductSkuApi;
8   -import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO;
9   -import cn.iocoder.yudao.module.trade.dal.dataobject.delivery.DeliveryExpressTemplateChargeDO;
10   -import cn.iocoder.yudao.module.trade.dal.dataobject.delivery.DeliveryExpressTemplateFreeDO;
11 7 import cn.iocoder.yudao.module.trade.enums.delivery.DeliveryExpressChargeModeEnum;
12 8 import cn.iocoder.yudao.module.trade.enums.delivery.DeliveryTypeEnum;
13 9 import cn.iocoder.yudao.module.trade.service.delivery.DeliveryExpressTemplateService;
  10 +import cn.iocoder.yudao.module.trade.service.delivery.bo.DeliveryExpressTemplateChargeBO;
  11 +import cn.iocoder.yudao.module.trade.service.delivery.bo.DeliveryExpressTemplateFreeBO;
14 12 import cn.iocoder.yudao.module.trade.service.delivery.bo.SpuDeliveryExpressTemplateRespBO;
15 13 import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO;
16 14 import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO;
17 15 import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO.OrderItem;
  16 +import lombok.extern.slf4j.Slf4j;
18 17 import org.springframework.core.annotation.Order;
19 18 import org.springframework.stereotype.Component;
20 19  
... ... @@ -23,7 +22,10 @@ import java.util.List;
23 22 import java.util.Map;
24 23 import java.util.Set;
25 24  
  25 +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
26 26 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
  27 +import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.DELIVERY_EXPRESS_USER_ADDRESS_IS_EMPTY;
  28 +import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.PRODUCT_EXPRESS_TEMPLATE_NOT_FOUND;
27 29  
28 30 /**
29 31 * 运费的 {@link TradePriceCalculator} 实现类
... ... @@ -32,13 +34,12 @@ import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.
32 34 */
33 35 @Component
34 36 @Order(TradePriceCalculator.ORDER_DELIVERY)
  37 +@Slf4j
35 38 public class TradeDeliveryPriceCalculator implements TradePriceCalculator {
36 39  
37 40 @Resource
38 41 private AddressApi addressApi;
39 42 @Resource
40   - private ProductSkuApi productSkuApi;
41   - @Resource
42 43 private DeliveryExpressTemplateService deliveryExpressTemplateService;
43 44  
44 45 @Override
... ... @@ -47,10 +48,10 @@ public class TradeDeliveryPriceCalculator implements TradePriceCalculator {
47 48 if (param.getDeliveryType() == null || DeliveryTypeEnum.PICK_UP.getMode().equals(param.getDeliveryType())) {
48 49 return;
49 50 }
50   - // 1.2 得到收件地址区域
51 51 if (param.getAddressId() == null) {
52   - return;
  52 + throw exception(DELIVERY_EXPRESS_USER_ADDRESS_IS_EMPTY);
53 53 }
  54 + // 1.2 得到收件地址区域
54 55 AddressRespDTO address = addressApi.getAddress(param.getAddressId(), param.getUserId());
55 56 Assert.notNull(address, "收件人({})的地址,不能为空", param.getUserId());
56 57  
... ... @@ -58,33 +59,27 @@ public class TradeDeliveryPriceCalculator implements TradePriceCalculator {
58 59 List<OrderItem> selectedItem = filterList(result.getItems(), OrderItem::getSelected);
59 60 Set<Long> spuIds = convertSet(selectedItem, OrderItem::getSpuId);
60 61 Map<Long, SpuDeliveryExpressTemplateRespBO> spuExpressTemplateMap =
61   - deliveryExpressTemplateService.getExpressTemplateBySpuIdsAndArea(spuIds, address.getAreaId());
62   -
  62 + deliveryExpressTemplateService.getExpressTemplateMapBySpuIdsAndArea(spuIds, address.getAreaId());
63 63 // 3. 计算配送费用
64   - // TODO @jason:这里应该不能判断空;如果找不到模版,就要报错;不然配送费就亏了
65   - if (CollUtil.isNotEmpty(spuExpressTemplateMap)) {
66   - calculateDeliveryPrice(selectedItem, spuExpressTemplateMap, result);
  64 + if (CollUtil.isEmpty(spuExpressTemplateMap)) {
  65 + log.error("找不到商品 SPU ID {}, area Id {} ,对应的运费模板", spuIds, address.getAreaId());
  66 + throw exception(PRODUCT_EXPRESS_TEMPLATE_NOT_FOUND);
67 67 }
  68 + calculateDeliveryPrice(selectedItem, spuExpressTemplateMap, result);
68 69 }
69 70  
70 71 private void calculateDeliveryPrice(List<OrderItem> selectedSkus,
71 72 Map<Long, SpuDeliveryExpressTemplateRespBO> spuExpressTemplateMap,
72 73 TradePriceCalculateRespBO result) {
73   - // 得到 SKU 详情
74   - // TODO @jason:可以去掉这里的读取;在 TradePriceCalculateRespBO 初始化的时候,把 weight、volume 拿到
75   - Set<Long> skuIds = convertSet(selectedSkus, OrderItem::getSkuId);
76   - Map<Long, ProductSkuRespDTO> skuRespMap = convertMap(productSkuApi.getSkuList(skuIds), ProductSkuRespDTO::getId);
77 74 // 按 SPU 来计算商品的运费:一个 spuId 可能对应多条订单商品 SKU
78 75 Map<Long, List<OrderItem>> spuIdItemMap = convertMultiMap(selectedSkus, OrderItem::getSpuId);
79   -
80 76 // 依次计算每个 SPU 的快递运费
81 77 for (Map.Entry<Long, List<OrderItem>> entry : spuIdItemMap.entrySet()) {
82 78 Long spuId = entry.getKey();
83 79 List<OrderItem> orderItems = entry.getValue();
84   - // TODO @jason:如果找不到,则打印 error log
85 80 SpuDeliveryExpressTemplateRespBO templateBO = spuExpressTemplateMap.get(spuId);
86 81 if (templateBO == null) {
87   - // 记录错误日志
  82 + log.error("不能计算快递运费。不能找到 spuId : {}. 对应的运费模板配置 Resp BO", spuId);
88 83 continue;
89 84 }
90 85 // 总件数, 总金额, 总重量, 总体积
... ... @@ -93,22 +88,16 @@ public class TradeDeliveryPriceCalculator implements TradePriceCalculator {
93 88 double totalWeight = 0;
94 89 double totalVolume = 0;
95 90 for (OrderItem orderItem : orderItems) {
96   - totalCount += orderItem.getCount();
97   - totalPrice += orderItem.getPayPrice(); // 先按应付总金额来算,后面确认一下 TODO @jason:是的哈
98   - ProductSkuRespDTO skuResp = skuRespMap.get(orderItem.getSkuId());
99   - // TODO @jason:是不是要保持风格统一,都用 +=
100   - totalWeight = totalWeight + skuResp.getWeight() * orderItem.getCount();
101   - totalVolume = totalVolume + skuResp.getVolume() * orderItem.getCount();
  91 + totalCount += orderItem.getCount();
  92 + totalPrice += orderItem.getPayPrice();
  93 + totalWeight += totalWeight + orderItem.getWeight() * orderItem.getCount();
  94 + totalVolume += totalVolume + orderItem.getVolume() * orderItem.getCount();
102 95 }
103 96 // 优先判断是否包邮. 如果包邮不计算快递运费
104   - if (checkExpressFree(templateBO.getChargeMode(), totalCount, totalWeight,
  97 + if (isExpressFree(templateBO.getChargeMode(), totalCount, totalWeight,
105 98 totalVolume, totalPrice, templateBO.getTemplateFree())) {
106 99 continue;
107 100 }
108   - // TODO @jason:这块判断,可以收到 calculateExpressFeeByChargeMode 里;另外找不到,要打 error log
109   - if (templateBO.getTemplateCharge() == null) {
110   - continue;
111   - }
112 101 // 计算快递运费
113 102 calculateExpressFeeByChargeMode(totalCount, totalWeight, totalVolume,
114 103 templateBO.getChargeMode(), templateBO.getTemplateCharge(), orderItems);
... ... @@ -128,8 +117,12 @@ public class TradeDeliveryPriceCalculator implements TradePriceCalculator {
128 117 * @param orderItems SKU 商品项目
129 118 */
130 119 private void calculateExpressFeeByChargeMode(double totalCount, double totalWeight, double totalVolume,
131   - int chargeMode, DeliveryExpressTemplateChargeDO templateCharge,
  120 + int chargeMode, DeliveryExpressTemplateChargeBO templateCharge,
132 121 List<OrderItem> orderItems) {
  122 + if (templateCharge == null) {
  123 + log.error("计算快递运费时,不能找到对应的快递运费模板费用配置。无法计算以下商品 SKU 项目运费: {}", orderItems);
  124 + return;
  125 + }
133 126 DeliveryExpressChargeModeEnum chargeModeEnum = DeliveryExpressChargeModeEnum.valueOf(chargeMode);
134 127 switch (chargeModeEnum) {
135 128 case PIECE: {
... ... @@ -154,7 +147,7 @@ public class TradeDeliveryPriceCalculator implements TradePriceCalculator {
154 147 * @param templateCharge 快递运费配置
155 148 * @param orderItems SKU 商品项目
156 149 */
157   - private void calculateExpressFee(double total, DeliveryExpressTemplateChargeDO templateCharge, List<OrderItem> orderItems) {
  150 + private void calculateExpressFee(double total, DeliveryExpressTemplateChargeBO templateCharge, List<OrderItem> orderItems) {
158 151 int deliveryPrice;
159 152 if (total <= templateCharge.getStartCount()) {
160 153 deliveryPrice = templateCharge.getStartPrice();
... ... @@ -176,11 +169,14 @@ public class TradeDeliveryPriceCalculator implements TradePriceCalculator {
176 169 * @param orderItems SKU 商品
177 170 */
178 171 private void divideDeliveryPrice(int deliveryPrice, List<OrderItem> orderItems) {
179   - // TODO @jason:分摊的话,是不是要按照比例呀?重量、价格、数量等等
  172 + // TODO @jason:分摊的话,是不是要按照比例呀?重量、价格、数量等等,
  173 + // 按比例是不是有点复杂。后面看看是否需要
180 174 int dividePrice = deliveryPrice / orderItems.size();
181 175 for (OrderItem item : orderItems) {
182 176 // 更新快递运费
183 177 item.setDeliveryPrice(dividePrice);
  178 +
  179 + TradePriceCalculatorHelper.recountPayPrice(item);
184 180 }
185 181 }
186 182  
... ... @@ -194,9 +190,8 @@ public class TradeDeliveryPriceCalculator implements TradePriceCalculator {
194 190 * @param totalPrice 总金额
195 191 * @param templateFree 包邮配置
196 192 */
197   - // TODO @jason:isExpressFree 更合适;因为 check 是一种校验,往往抛出异常;
198   - private boolean checkExpressFree(Integer chargeMode, int totalCount, double totalWeight,
199   - double totalVolume, int totalPrice, DeliveryExpressTemplateFreeDO templateFree) {
  193 + private boolean isExpressFree(Integer chargeMode, int totalCount, double totalWeight,
  194 + double totalVolume, int totalPrice, DeliveryExpressTemplateFreeBO templateFree) {
200 195 if (templateFree == null) {
201 196 return false;
202 197 }
... ... @@ -211,6 +206,7 @@ public class TradeDeliveryPriceCalculator implements TradePriceCalculator {
211 206 case WEIGHT:
212 207 // freeCount 是不是应该是 double ??
213 208 // TODO @jason:要不配置的时候,把它的单位和商品对齐?到底是 kg、还是斤
  209 + // TODO @芋艿 目前 包邮 件数/重量/体积 都用的是这个字段
214 210 if (totalWeight >= templateFree.getFreeCount()
215 211 && totalPrice >= templateFree.getFreePrice()) {
216 212 return true;
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradePriceCalculatorHelper.java
... ... @@ -53,7 +53,8 @@ public class TradePriceCalculatorHelper {
53 53 orderItem.setPrice(sku.getPrice()).setPayPrice(sku.getPrice() * item.getCount())
54 54 .setDiscountPrice(0).setDeliveryPrice(0).setCouponPrice(0).setPointPrice(0);
55 55 // sku 信息
56   - orderItem.setPicUrl(sku.getPicUrl()).setProperties(sku.getProperties());
  56 + orderItem.setPicUrl(sku.getPicUrl()).setProperties(sku.getProperties())
  57 + .setWeight(sku.getWeight()).setVolume(sku.getVolume());
57 58 // spu 信息
58 59 orderItem.setSpuName(spu.getName()).setCategoryId(spu.getCategoryId());
59 60 if (orderItem.getPicUrl() == null) {
... ...
yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeDeliveryPriceCalculatorTest.java 0 → 100644
  1 +package cn.iocoder.yudao.module.trade.service.price.calculator;
  2 +
  3 +import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
  4 +import cn.iocoder.yudao.module.member.api.address.AddressApi;
  5 +import cn.iocoder.yudao.module.member.api.address.dto.AddressRespDTO;
  6 +import cn.iocoder.yudao.module.trade.service.delivery.DeliveryExpressTemplateService;
  7 +import cn.iocoder.yudao.module.trade.service.delivery.bo.DeliveryExpressTemplateChargeBO;
  8 +import cn.iocoder.yudao.module.trade.service.delivery.bo.DeliveryExpressTemplateFreeBO;
  9 +import cn.iocoder.yudao.module.trade.service.delivery.bo.SpuDeliveryExpressTemplateRespBO;
  10 +import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO;
  11 +import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO;
  12 +import org.junit.jupiter.api.BeforeEach;
  13 +import org.junit.jupiter.api.DisplayName;
  14 +import org.junit.jupiter.api.Test;
  15 +import org.mockito.InjectMocks;
  16 +import org.mockito.Mock;
  17 +
  18 +import java.util.ArrayList;
  19 +import java.util.HashMap;
  20 +import java.util.Map;
  21 +
  22 +import static cn.iocoder.yudao.framework.common.util.collection.SetUtils.asSet;
  23 +import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
  24 +import static cn.iocoder.yudao.module.trade.enums.delivery.DeliveryExpressChargeModeEnum.PIECE;
  25 +import static cn.iocoder.yudao.module.trade.enums.delivery.DeliveryTypeEnum.EXPRESS;
  26 +import static java.util.Arrays.asList;
  27 +import static org.mockito.ArgumentMatchers.eq;
  28 +import static org.mockito.Mockito.when;
  29 +
  30 +import static org.assertj.core.api.Assertions.assertThat;
  31 +
  32 +/**
  33 + * @author jason
  34 + */
  35 +public class TradeDeliveryPriceCalculatorTest extends BaseMockitoUnitTest {
  36 +
  37 + @InjectMocks
  38 + private TradeDeliveryPriceCalculator calculator;
  39 + @Mock
  40 + private AddressApi addressApi;
  41 + @Mock
  42 + private DeliveryExpressTemplateService deliveryExpressTemplateService;
  43 +
  44 + private TradePriceCalculateReqBO reqBO;
  45 + private TradePriceCalculateRespBO resultBO;
  46 + private AddressRespDTO addressResp;
  47 + private DeliveryExpressTemplateChargeBO chargeBO;
  48 + private DeliveryExpressTemplateFreeBO freeBO;
  49 + private SpuDeliveryExpressTemplateRespBO spuTemplateRespBO;
  50 +
  51 + @BeforeEach
  52 + public void init(){
  53 + // 准备参数
  54 + reqBO = new TradePriceCalculateReqBO()
  55 + .setDeliveryType(EXPRESS.getMode())
  56 + .setAddressId(10L)
  57 + .setUserId(1L)
  58 + .setItems(asList(
  59 + new TradePriceCalculateReqBO.Item().setSkuId(10L).setCount(2).setSelected(true),
  60 + new TradePriceCalculateReqBO.Item().setSkuId(20L).setCount(10).setSelected(true),
  61 + new TradePriceCalculateReqBO.Item().setSkuId(30L).setCount(4).setSelected(false)
  62 + ));
  63 + resultBO = new TradePriceCalculateRespBO()
  64 + .setPrice(new TradePriceCalculateRespBO.Price())
  65 + .setPromotions(new ArrayList<>())
  66 + .setItems(asList(
  67 + new TradePriceCalculateRespBO.OrderItem().setSpuId(1L).setSkuId(10L).setCount(2).setSelected(true)
  68 + .setWeight(10d).setVolume(10d).setPrice(100),
  69 + new TradePriceCalculateRespBO.OrderItem().setSpuId(1L).setSkuId(20L).setCount(10).setSelected(true)
  70 + .setWeight(10d).setVolume(10d).setPrice(200),
  71 + new TradePriceCalculateRespBO.OrderItem().setSpuId(1L).setSkuId(30L).setCount(1).setSelected(false)
  72 + .setWeight(10d).setVolume(10d).setPrice(300)
  73 + ));
  74 + // 保证价格被初始化上
  75 + TradePriceCalculatorHelper.recountPayPrice(resultBO.getItems());
  76 + TradePriceCalculatorHelper.recountAllPrice(resultBO);
  77 + // 准备收件地址数据
  78 + addressResp = randomPojo(AddressRespDTO.class, item -> item.setAreaId(10));
  79 + // 准备运费模板费用配置数据
  80 + chargeBO = randomPojo(DeliveryExpressTemplateChargeBO.class,
  81 + item -> item.setStartCount(10D).setStartPrice(1000).setExtraCount(10D).setExtraPrice(2000));
  82 + // 准备运费模板包邮配置数据 订单总件数 < 包邮件数时 12 < 20
  83 + freeBO = randomPojo(DeliveryExpressTemplateFreeBO.class,
  84 + item -> item.setFreeCount(20).setFreePrice(100));
  85 + // 准备 SP 运费模板 数据
  86 + spuTemplateRespBO = randomPojo(SpuDeliveryExpressTemplateRespBO.class,
  87 + item -> item.setChargeMode(PIECE.getType())
  88 + .setTemplateCharge(chargeBO).setTemplateFree(freeBO));
  89 + }
  90 +
  91 + @Test
  92 + @DisplayName("按件计算运费不包邮的情况")
  93 + public void testCalculateByExpressTemplateCharge() {
  94 + // SKU 1 : 100 * 2 = 200
  95 + // SKU 2 :200 * 10 = 2000
  96 + // 运费 首件 1000 + 续件 2000 = 3000
  97 + Map<Long, SpuDeliveryExpressTemplateRespBO> respMap = new HashMap<>();
  98 + respMap.put(1L, spuTemplateRespBO);
  99 +
  100 + // mock 方法
  101 + when(addressApi.getAddress(eq(10L), eq(1L))).thenReturn(addressResp);
  102 + when(deliveryExpressTemplateService.getExpressTemplateMapBySpuIdsAndArea(eq(asSet(1L)), eq(10)))
  103 + .thenReturn(respMap);
  104 +
  105 + calculator.calculate(reqBO, resultBO);
  106 +
  107 + TradePriceCalculateRespBO.Price price = resultBO.getPrice();
  108 +
  109 + assertThat(price)
  110 + .extracting("totalPrice","discountPrice","couponPrice","pointPrice","deliveryPrice","payPrice")
  111 + .containsExactly(2200, 0, 0, 0, 3000, 5200);
  112 + // 断言:SKU
  113 + assertThat(resultBO.getItems()).hasSize(3);
  114 + // SKU1
  115 + assertThat(resultBO.getItems().get(0))
  116 + .extracting("price", "count","discountPrice" ,"couponPrice", "pointPrice","deliveryPrice","payPrice")
  117 + .containsExactly(100, 2, 0, 0, 0, 1500, 1700);
  118 + // SKU2
  119 + assertThat(resultBO.getItems().get(1))
  120 + .extracting("price", "count","discountPrice" ,"couponPrice", "pointPrice","deliveryPrice","payPrice")
  121 + .containsExactly(200, 10, 0, 0, 0, 1500, 3500);
  122 + // SKU3 未选中
  123 + assertThat(resultBO.getItems().get(2))
  124 + .extracting("price", "count","discountPrice" ,"couponPrice", "pointPrice","deliveryPrice","payPrice")
  125 + .containsExactly(300, 1, 0, 0, 0, 0, 300);
  126 + }
  127 +
  128 + @Test
  129 + @DisplayName("按件计算运费包邮的情况")
  130 + public void testCalculateByExpressTemplateFree() {
  131 + // SKU 1 : 100 * 2 = 200
  132 + // SKU 2 :200 * 10 = 2000
  133 + // 运费 0
  134 + Map<Long, SpuDeliveryExpressTemplateRespBO> respMap = new HashMap<>();
  135 + respMap.put(1L, spuTemplateRespBO);
  136 + // 准备运费模板包邮配置数据 包邮 订单总件数 > 包邮件数时 12 > 10
  137 + freeBO = randomPojo(DeliveryExpressTemplateFreeBO.class,
  138 + item -> item.setFreeCount(10).setFreePrice(1000));
  139 + spuTemplateRespBO.setTemplateFree(freeBO);
  140 + // mock 方法
  141 + when(addressApi.getAddress(eq(10L), eq(1L))).thenReturn(addressResp);
  142 + when(deliveryExpressTemplateService.getExpressTemplateMapBySpuIdsAndArea(eq(asSet(1L)), eq(10)))
  143 + .thenReturn(respMap);
  144 +
  145 + calculator.calculate(reqBO, resultBO);
  146 +
  147 + TradePriceCalculateRespBO.Price price = resultBO.getPrice();
  148 +
  149 + // 断言price
  150 + assertThat(price)
  151 + .extracting("totalPrice","discountPrice","couponPrice","pointPrice","deliveryPrice","payPrice")
  152 + .containsExactly(2200, 0, 0, 0, 0, 2200);
  153 + // 断言:SKU
  154 + assertThat(resultBO.getItems()).hasSize(3);
  155 + // SKU1
  156 + assertThat(resultBO.getItems().get(0))
  157 + .extracting("price", "count","discountPrice" ,"couponPrice", "pointPrice","deliveryPrice","payPrice")
  158 + .containsExactly(100, 2, 0, 0, 0, 0, 200);
  159 + // SKU2
  160 + assertThat(resultBO.getItems().get(1))
  161 + .extracting("price", "count","discountPrice" ,"couponPrice", "pointPrice","deliveryPrice","payPrice")
  162 + .containsExactly(200, 10, 0, 0, 0, 0, 2000);
  163 + // SKU3 未选中
  164 + assertThat(resultBO.getItems().get(2))
  165 + .extracting("price", "count","discountPrice" ,"couponPrice", "pointPrice","deliveryPrice","payPrice")
  166 + .containsExactly(300, 1, 0, 0, 0, 0, 300);
  167 + }
  168 +}
0 169 \ No newline at end of file
... ...