Commit 66210e5ee2664a3c6cf20fe7f4be93c12d1ca911
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 | ... | ... |