diff --git a/yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/ErrorCodeConstants.java b/yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/ErrorCodeConstants.java index 37306465f..43a9d62e6 100644 --- a/yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/ErrorCodeConstants.java +++ b/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 { // TODO @jason:最好每个模块一段哈。express 一个;exmpresstemplate 一个;pickup 一个 ErrorCode EXPRESS_CODE_DUPLICATE = new ErrorCode(1011003001, "已经存在该编码的快递公司"); ErrorCode EXPRESS_TEMPLATE_NOT_EXISTS = new ErrorCode(1011003002, "运费模板不存在"); - ErrorCode EXPRESS_TEMPLATE_NAME_DUPLICATE = new ErrorCode(1011003002, "已经存在该运费模板名"); - ErrorCode PICK_UP_STORE_NOT_EXISTS = new ErrorCode(1011003003, "自提门店不存在"); + ErrorCode EXPRESS_TEMPLATE_NAME_DUPLICATE = new ErrorCode(1011003003, "已经存在该运费模板名"); + ErrorCode DELIVERY_EXPRESS_USER_ADDRESS_IS_EMPTY = new ErrorCode(1011003004, "计算快递运费时,收件人地址编号为空"); + ErrorCode PRODUCT_EXPRESS_TEMPLATE_NOT_FOUND = new ErrorCode(1011003005, "找不到到商品对应的运费模板"); + ErrorCode PICK_UP_STORE_NOT_EXISTS = new ErrorCode(1011003006, "自提门店不存在"); // ========== Price 相关 1011004000 ============ ErrorCode PRICE_CALCULATE_PAY_PRICE_ILLEGAL = new ErrorCode(1011004000, "支付价格计算异常,原因:价格小于等于 0"); diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/convert/delivery/DeliveryExpressTemplateConvert.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/convert/delivery/DeliveryExpressTemplateConvert.java index aadaf42f3..6ac562658 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/convert/delivery/DeliveryExpressTemplateConvert.java +++ b/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 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.service.delivery.bo.DeliveryExpressTemplateChargeBO; +import cn.iocoder.yudao.module.trade.service.delivery.bo.DeliveryExpressTemplateFreeBO; import org.mapstruct.Mapper; import org.mapstruct.factory.Mappers; @@ -47,6 +49,8 @@ public interface DeliveryExpressTemplateConvert { DeliveryExpressTemplateChargeDO convertTemplateCharge(DeliveryExpressTemplateUpdateReqVO.ExpressTemplateChargeUpdateVO vo); + DeliveryExpressTemplateChargeBO convertTemplateCharge(DeliveryExpressTemplateChargeDO bean); + default List convertTemplateChargeList(Long templateId, Integer chargeMode, List list) { return CollectionUtils.convertList(list, vo -> convertTemplateCharge(templateId, chargeMode, vo)); } @@ -57,6 +61,8 @@ public interface DeliveryExpressTemplateConvert { DeliveryExpressTemplateFreeDO convertTemplateFree(DeliveryExpressTemplateUpdateReqVO.ExpressTemplateFreeUpdateVO vo); + DeliveryExpressTemplateFreeBO convertTemplateFree(DeliveryExpressTemplateFreeDO bean); + List convertTemplateChargeList(List list); List convertTemplateFreeList(List list); diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/delivery/DeliveryExpressTemplateService.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/delivery/DeliveryExpressTemplateService.java index 3480f4072..e037dbc29 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/delivery/DeliveryExpressTemplateService.java +++ b/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 { */ DeliveryExpressTemplateDO validateDeliveryExpressTemplate(Long templateId); - // TODO @jason 方法名可以改成 getExpressTemplateMapBySpuIdsAndArea /** * 基于指定的 SPU 编号数组和收件人地址区域编号. 获取匹配运费模板 * - * @param ids SPU 编号列表 // TODO @jason:模版编号? + * @param spuIds SPU 编号列表 * @param areaId 区域编号 * @return Map (spuId -> 运费模板设置) */ - Map getExpressTemplateBySpuIdsAndArea(Collection ids, Integer areaId); + Map getExpressTemplateMapBySpuIdsAndArea(Collection spuIds, Integer areaId); } diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/delivery/DeliveryExpressTemplateServiceImpl.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/delivery/DeliveryExpressTemplateServiceImpl.java index 07e80b5f3..7aa591388 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/delivery/DeliveryExpressTemplateServiceImpl.java +++ b/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 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.DeliveryExpressTemplateChargeBO; +import cn.iocoder.yudao.module.trade.service.delivery.bo.DeliveryExpressTemplateFreeBO; import cn.iocoder.yudao.module.trade.service.delivery.bo.SpuDeliveryExpressTemplateRespBO; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -24,8 +26,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.framework.common.util.collection.CollectionUtils.*; import static cn.iocoder.yudao.module.trade.convert.delivery.DeliveryExpressTemplateConvert.INSTANCE; import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.EXPRESS_TEMPLATE_NAME_DUPLICATE; import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.EXPRESS_TEMPLATE_NOT_EXISTS; @@ -227,7 +228,7 @@ public class DeliveryExpressTemplateServiceImpl implements DeliveryExpressTempla } @Override - public Map getExpressTemplateBySpuIdsAndArea(Collection spuIds, Integer areaId) { + public Map getExpressTemplateMapBySpuIdsAndArea(Collection spuIds, Integer areaId) { Assert.notNull(areaId, "区域编号 {} 不能为空", areaId); List spuList = productSpuApi.getSpuList(spuIds); if (CollUtil.isEmpty(spuList)) { @@ -237,43 +238,32 @@ public class DeliveryExpressTemplateServiceImpl implements DeliveryExpressTempla List templateList = expressTemplateMapper.selectBatchIds(spuMap.keySet()); Map result = new HashMap<>(templateList.size()); templateList.forEach(item -> { - // TODO @jason:if return ,更简洁哈; - if (spuMap.containsKey(item.getId())) { - ProductSpuRespDTO spu = spuMap.get(item.getId()); - SpuDeliveryExpressTemplateRespBO bo = new SpuDeliveryExpressTemplateRespBO() - .setSpuId(spu.getId()).setAreaId(areaId) - .setChargeMode(item.getChargeMode()) - // TODO @jason:是不是只要查询到一个,就不用查询下一个了;TemplateCharge 和 TemplateFree - .setTemplateCharge(findMatchExpressTemplateCharge(item.getId(), areaId)) - .setTemplateFree(findMatchExpressTemplateFree(item.getId(), areaId)); - result.put(spu.getId(), bo); + ProductSpuRespDTO spu = spuMap.get(item.getId()); + if (spu == null) { + return; } + SpuDeliveryExpressTemplateRespBO bo = new SpuDeliveryExpressTemplateRespBO() + .setChargeMode(item.getChargeMode()) + // TODO @jason:是不是只要查询到一个,就不用查询下一个了;TemplateCharge 和 TemplateFree + // @芋艿 包邮的优先级> 费用的优先级 所以两个都要查询 + .setTemplateCharge(findMatchExpressTemplateCharge(item.getId(), areaId)) + .setTemplateFree(findMatchExpressTemplateFree(item.getId(), areaId)); + result.put(spu.getId(), bo); }); return result; } - private DeliveryExpressTemplateChargeDO findMatchExpressTemplateCharge(Long templateId, Integer areaId) { - List list = expressTemplateChargeMapper.selectListByTemplateId(templateId); - // TODO @jason:可以使用 CollectionUtils.findFirst() - for (DeliveryExpressTemplateChargeDO item : list) { - // 第一个匹配的返回。 areaId 不能重复 - if (item.getAreaIds().contains(areaId)) { - return item; - } - } - return null; + private DeliveryExpressTemplateChargeBO findMatchExpressTemplateCharge(Long templateId, Integer areaId) { + return INSTANCE.convertTemplateCharge(findFirst( + expressTemplateChargeMapper.selectListByTemplateId(templateId), item -> item.getAreaIds().contains(areaId) + ) + ); } - private DeliveryExpressTemplateFreeDO findMatchExpressTemplateFree(Long templateId, Integer areaId) { - List list = expressTemplateFreeMapper.selectListByTemplateId(templateId); - // TODO @jason:可以使用 CollectionUtils.findFirst() - for (DeliveryExpressTemplateFreeDO item : list) { - // 第一个匹配的返回。 areaId 不能重复 - if (item.getAreaIds().contains(areaId)) { - return item; - } - } - return null; + private DeliveryExpressTemplateFreeBO findMatchExpressTemplateFree(Long templateId, Integer areaId) { + return INSTANCE.convertTemplateFree(findFirst( + expressTemplateFreeMapper.selectListByTemplateId(templateId), item -> item.getAreaIds().contains(areaId) + )); } } diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/delivery/bo/DeliveryExpressTemplateChargeBO.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/delivery/bo/DeliveryExpressTemplateChargeBO.java new file mode 100644 index 000000000..9dd908c41 --- /dev/null +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/delivery/bo/DeliveryExpressTemplateChargeBO.java @@ -0,0 +1,29 @@ +package cn.iocoder.yudao.module.trade.service.delivery.bo; + +import lombok.Data; + +/** + * 快递运费模板费用配置 BO + * + * @author jason + */ +@Data +public class DeliveryExpressTemplateChargeBO { + + /** + * 首件数量(件数,重量,或体积) + */ + private Double startCount; + /** + * 起步价,单位:分 + */ + private Integer startPrice; + /** + * 续件数量(件, 重量,或体积) + */ + private Double extraCount; + /** + * 额外价,单位:分 + */ + private Integer extraPrice; +} diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/delivery/bo/DeliveryExpressTemplateFreeBO.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/delivery/bo/DeliveryExpressTemplateFreeBO.java new file mode 100644 index 000000000..6bccb038e --- /dev/null +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/delivery/bo/DeliveryExpressTemplateFreeBO.java @@ -0,0 +1,26 @@ +package cn.iocoder.yudao.module.trade.service.delivery.bo; + +import lombok.Data; + +/** + * 快递运费模板包邮配置 BO + * + * @author jason + */ +@Data +public class DeliveryExpressTemplateFreeBO { + + /** + * 包邮金额,单位:分 + * + * 订单总金额 > 包邮金额时,才免运费 + */ + private Integer freePrice; + + /** + * 包邮件数 + * + * 订单总件数 > 包邮件数时,才免运费 + */ + private Integer freeCount; +} diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/delivery/bo/SpuDeliveryExpressTemplateRespBO.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/delivery/bo/SpuDeliveryExpressTemplateRespBO.java index 2d5568915..5fd11f030 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/delivery/bo/SpuDeliveryExpressTemplateRespBO.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/delivery/bo/SpuDeliveryExpressTemplateRespBO.java @@ -1,7 +1,5 @@ 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; @@ -13,8 +11,6 @@ import lombok.Data; @Data public class SpuDeliveryExpressTemplateRespBO { - // TODO @jason:是不是可以简单一点;是否包邮;然后直接把 templateCharge、templateFree 需要的字段平铺开 - /** * 配送计费方式 * @@ -25,24 +21,11 @@ public class SpuDeliveryExpressTemplateRespBO { /** * 运费模板快递运费设置 */ - private DeliveryExpressTemplateChargeDO templateCharge; + private DeliveryExpressTemplateChargeBO templateCharge; /** * 运费模板包邮设置 */ - private DeliveryExpressTemplateFreeDO templateFree; - - // TODO @jason:下面两个字段不用返回也可以呀 - /** - * SPU 编号 - *

- * 关联 ProductSpuDO 的 id 编号 - */ - private Long spuId; - - /** - * 区域编号 - */ - private Integer areaId; + private DeliveryExpressTemplateFreeBO templateFree; } diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/bo/TradePriceCalculateRespBO.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/bo/TradePriceCalculateRespBO.java index fb006b44b..5a6a762a9 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/bo/TradePriceCalculateRespBO.java +++ b/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 { */ private Integer payPrice; + /** + * 商品重量,单位:kg 千克 + */ + private Double weight; + /** + * 商品体积,单位:m^3 平米 + */ + private Double volume; + // ========== 商品信息 ========== /** * 商品名 diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeDeliveryPriceCalculator.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeDeliveryPriceCalculator.java index 95e45182d..222acab9e 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeDeliveryPriceCalculator.java +++ b/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; 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.DeliveryExpressTemplateFreeDO; 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.DeliveryExpressTemplateChargeBO; +import cn.iocoder.yudao.module.trade.service.delivery.bo.DeliveryExpressTemplateFreeBO; 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; +import lombok.extern.slf4j.Slf4j; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; @@ -23,7 +22,10 @@ import java.util.List; import java.util.Map; import java.util.Set; +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; +import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.DELIVERY_EXPRESS_USER_ADDRESS_IS_EMPTY; +import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.PRODUCT_EXPRESS_TEMPLATE_NOT_FOUND; /** * 运费的 {@link TradePriceCalculator} 实现类 @@ -32,13 +34,12 @@ import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils. */ @Component @Order(TradePriceCalculator.ORDER_DELIVERY) +@Slf4j public class TradeDeliveryPriceCalculator implements TradePriceCalculator { @Resource private AddressApi addressApi; @Resource - private ProductSkuApi productSkuApi; - @Resource private DeliveryExpressTemplateService deliveryExpressTemplateService; @Override @@ -47,10 +48,10 @@ public class TradeDeliveryPriceCalculator implements TradePriceCalculator { if (param.getDeliveryType() == null || DeliveryTypeEnum.PICK_UP.getMode().equals(param.getDeliveryType())) { return; } - // 1.2 得到收件地址区域 if (param.getAddressId() == null) { - return; + throw exception(DELIVERY_EXPRESS_USER_ADDRESS_IS_EMPTY); } + // 1.2 得到收件地址区域 AddressRespDTO address = addressApi.getAddress(param.getAddressId(), param.getUserId()); Assert.notNull(address, "收件人({})的地址,不能为空", param.getUserId()); @@ -58,33 +59,27 @@ public class TradeDeliveryPriceCalculator implements TradePriceCalculator { List selectedItem = filterList(result.getItems(), OrderItem::getSelected); Set spuIds = convertSet(selectedItem, OrderItem::getSpuId); Map spuExpressTemplateMap = - deliveryExpressTemplateService.getExpressTemplateBySpuIdsAndArea(spuIds, address.getAreaId()); - + deliveryExpressTemplateService.getExpressTemplateMapBySpuIdsAndArea(spuIds, address.getAreaId()); // 3. 计算配送费用 - // TODO @jason:这里应该不能判断空;如果找不到模版,就要报错;不然配送费就亏了 - if (CollUtil.isNotEmpty(spuExpressTemplateMap)) { - calculateDeliveryPrice(selectedItem, spuExpressTemplateMap, result); + if (CollUtil.isEmpty(spuExpressTemplateMap)) { + log.error("找不到商品 SPU ID {}, area Id {} ,对应的运费模板", spuIds, address.getAreaId()); + throw exception(PRODUCT_EXPRESS_TEMPLATE_NOT_FOUND); } + calculateDeliveryPrice(selectedItem, spuExpressTemplateMap, result); } private void calculateDeliveryPrice(List selectedSkus, Map spuExpressTemplateMap, TradePriceCalculateRespBO result) { - // 得到 SKU 详情 - // TODO @jason:可以去掉这里的读取;在 TradePriceCalculateRespBO 初始化的时候,把 weight、volume 拿到 - Set skuIds = convertSet(selectedSkus, OrderItem::getSkuId); - Map skuRespMap = convertMap(productSkuApi.getSkuList(skuIds), ProductSkuRespDTO::getId); // 按 SPU 来计算商品的运费:一个 spuId 可能对应多条订单商品 SKU Map> spuIdItemMap = convertMultiMap(selectedSkus, OrderItem::getSpuId); - // 依次计算每个 SPU 的快递运费 for (Map.Entry> entry : spuIdItemMap.entrySet()) { Long spuId = entry.getKey(); List orderItems = entry.getValue(); - // TODO @jason:如果找不到,则打印 error log SpuDeliveryExpressTemplateRespBO templateBO = spuExpressTemplateMap.get(spuId); if (templateBO == null) { - // 记录错误日志 + log.error("不能计算快递运费。不能找到 spuId : {}. 对应的运费模板配置 Resp BO", spuId); continue; } // 总件数, 总金额, 总重量, 总体积 @@ -93,22 +88,16 @@ public class TradeDeliveryPriceCalculator implements TradePriceCalculator { double totalWeight = 0; double totalVolume = 0; for (OrderItem orderItem : orderItems) { - totalCount += orderItem.getCount(); - totalPrice += orderItem.getPayPrice(); // 先按应付总金额来算,后面确认一下 TODO @jason:是的哈 - ProductSkuRespDTO skuResp = skuRespMap.get(orderItem.getSkuId()); - // TODO @jason:是不是要保持风格统一,都用 += - totalWeight = totalWeight + skuResp.getWeight() * orderItem.getCount(); - totalVolume = totalVolume + skuResp.getVolume() * orderItem.getCount(); + totalCount += orderItem.getCount(); + totalPrice += orderItem.getPayPrice(); + totalWeight += totalWeight + orderItem.getWeight() * orderItem.getCount(); + totalVolume += totalVolume + orderItem.getVolume() * orderItem.getCount(); } // 优先判断是否包邮. 如果包邮不计算快递运费 - if (checkExpressFree(templateBO.getChargeMode(), totalCount, totalWeight, + if (isExpressFree(templateBO.getChargeMode(), totalCount, totalWeight, totalVolume, totalPrice, templateBO.getTemplateFree())) { continue; } - // TODO @jason:这块判断,可以收到 calculateExpressFeeByChargeMode 里;另外找不到,要打 error log - if (templateBO.getTemplateCharge() == null) { - continue; - } // 计算快递运费 calculateExpressFeeByChargeMode(totalCount, totalWeight, totalVolume, templateBO.getChargeMode(), templateBO.getTemplateCharge(), orderItems); @@ -128,8 +117,12 @@ public class TradeDeliveryPriceCalculator implements TradePriceCalculator { * @param orderItems SKU 商品项目 */ private void calculateExpressFeeByChargeMode(double totalCount, double totalWeight, double totalVolume, - int chargeMode, DeliveryExpressTemplateChargeDO templateCharge, + int chargeMode, DeliveryExpressTemplateChargeBO templateCharge, List orderItems) { + if (templateCharge == null) { + log.error("计算快递运费时,不能找到对应的快递运费模板费用配置。无法计算以下商品 SKU 项目运费: {}", orderItems); + return; + } DeliveryExpressChargeModeEnum chargeModeEnum = DeliveryExpressChargeModeEnum.valueOf(chargeMode); switch (chargeModeEnum) { case PIECE: { @@ -154,7 +147,7 @@ public class TradeDeliveryPriceCalculator implements TradePriceCalculator { * @param templateCharge 快递运费配置 * @param orderItems SKU 商品项目 */ - private void calculateExpressFee(double total, DeliveryExpressTemplateChargeDO templateCharge, List orderItems) { + private void calculateExpressFee(double total, DeliveryExpressTemplateChargeBO templateCharge, List orderItems) { int deliveryPrice; if (total <= templateCharge.getStartCount()) { deliveryPrice = templateCharge.getStartPrice(); @@ -176,11 +169,14 @@ public class TradeDeliveryPriceCalculator implements TradePriceCalculator { * @param orderItems SKU 商品 */ private void divideDeliveryPrice(int deliveryPrice, List orderItems) { - // TODO @jason:分摊的话,是不是要按照比例呀?重量、价格、数量等等 + // TODO @jason:分摊的话,是不是要按照比例呀?重量、价格、数量等等, + // 按比例是不是有点复杂。后面看看是否需要 int dividePrice = deliveryPrice / orderItems.size(); for (OrderItem item : orderItems) { // 更新快递运费 item.setDeliveryPrice(dividePrice); + + TradePriceCalculatorHelper.recountPayPrice(item); } } @@ -194,9 +190,8 @@ public class TradeDeliveryPriceCalculator implements TradePriceCalculator { * @param totalPrice 总金额 * @param templateFree 包邮配置 */ - // TODO @jason:isExpressFree 更合适;因为 check 是一种校验,往往抛出异常; - private boolean checkExpressFree(Integer chargeMode, int totalCount, double totalWeight, - double totalVolume, int totalPrice, DeliveryExpressTemplateFreeDO templateFree) { + private boolean isExpressFree(Integer chargeMode, int totalCount, double totalWeight, + double totalVolume, int totalPrice, DeliveryExpressTemplateFreeBO templateFree) { if (templateFree == null) { return false; } @@ -211,6 +206,7 @@ public class TradeDeliveryPriceCalculator implements TradePriceCalculator { case WEIGHT: // freeCount 是不是应该是 double ?? // TODO @jason:要不配置的时候,把它的单位和商品对齐?到底是 kg、还是斤 + // TODO @芋艿 目前 包邮 件数/重量/体积 都用的是这个字段 if (totalWeight >= templateFree.getFreeCount() && totalPrice >= templateFree.getFreePrice()) { return true; diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradePriceCalculatorHelper.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradePriceCalculatorHelper.java index d171e50cf..7945c72eb 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradePriceCalculatorHelper.java +++ b/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 { orderItem.setPrice(sku.getPrice()).setPayPrice(sku.getPrice() * item.getCount()) .setDiscountPrice(0).setDeliveryPrice(0).setCouponPrice(0).setPointPrice(0); // sku 信息 - orderItem.setPicUrl(sku.getPicUrl()).setProperties(sku.getProperties()); + orderItem.setPicUrl(sku.getPicUrl()).setProperties(sku.getProperties()) + .setWeight(sku.getWeight()).setVolume(sku.getVolume()); // spu 信息 orderItem.setSpuName(spu.getName()).setCategoryId(spu.getCategoryId()); if (orderItem.getPicUrl() == null) { diff --git a/yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeDeliveryPriceCalculatorTest.java b/yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeDeliveryPriceCalculatorTest.java new file mode 100644 index 000000000..e6af92430 --- /dev/null +++ b/yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeDeliveryPriceCalculatorTest.java @@ -0,0 +1,168 @@ +package cn.iocoder.yudao.module.trade.service.price.calculator; + +import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; +import cn.iocoder.yudao.module.member.api.address.AddressApi; +import cn.iocoder.yudao.module.member.api.address.dto.AddressRespDTO; +import cn.iocoder.yudao.module.trade.service.delivery.DeliveryExpressTemplateService; +import cn.iocoder.yudao.module.trade.service.delivery.bo.DeliveryExpressTemplateChargeBO; +import cn.iocoder.yudao.module.trade.service.delivery.bo.DeliveryExpressTemplateFreeBO; +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 org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; + +import static cn.iocoder.yudao.framework.common.util.collection.SetUtils.asSet; +import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo; +import static cn.iocoder.yudao.module.trade.enums.delivery.DeliveryExpressChargeModeEnum.PIECE; +import static cn.iocoder.yudao.module.trade.enums.delivery.DeliveryTypeEnum.EXPRESS; +import static java.util.Arrays.asList; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.when; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author jason + */ +public class TradeDeliveryPriceCalculatorTest extends BaseMockitoUnitTest { + + @InjectMocks + private TradeDeliveryPriceCalculator calculator; + @Mock + private AddressApi addressApi; + @Mock + private DeliveryExpressTemplateService deliveryExpressTemplateService; + + private TradePriceCalculateReqBO reqBO; + private TradePriceCalculateRespBO resultBO; + private AddressRespDTO addressResp; + private DeliveryExpressTemplateChargeBO chargeBO; + private DeliveryExpressTemplateFreeBO freeBO; + private SpuDeliveryExpressTemplateRespBO spuTemplateRespBO; + + @BeforeEach + public void init(){ + // 准备参数 + reqBO = new TradePriceCalculateReqBO() + .setDeliveryType(EXPRESS.getMode()) + .setAddressId(10L) + .setUserId(1L) + .setItems(asList( + new TradePriceCalculateReqBO.Item().setSkuId(10L).setCount(2).setSelected(true), + new TradePriceCalculateReqBO.Item().setSkuId(20L).setCount(10).setSelected(true), + new TradePriceCalculateReqBO.Item().setSkuId(30L).setCount(4).setSelected(false) + )); + resultBO = new TradePriceCalculateRespBO() + .setPrice(new TradePriceCalculateRespBO.Price()) + .setPromotions(new ArrayList<>()) + .setItems(asList( + new TradePriceCalculateRespBO.OrderItem().setSpuId(1L).setSkuId(10L).setCount(2).setSelected(true) + .setWeight(10d).setVolume(10d).setPrice(100), + new TradePriceCalculateRespBO.OrderItem().setSpuId(1L).setSkuId(20L).setCount(10).setSelected(true) + .setWeight(10d).setVolume(10d).setPrice(200), + new TradePriceCalculateRespBO.OrderItem().setSpuId(1L).setSkuId(30L).setCount(1).setSelected(false) + .setWeight(10d).setVolume(10d).setPrice(300) + )); + // 保证价格被初始化上 + TradePriceCalculatorHelper.recountPayPrice(resultBO.getItems()); + TradePriceCalculatorHelper.recountAllPrice(resultBO); + // 准备收件地址数据 + addressResp = randomPojo(AddressRespDTO.class, item -> item.setAreaId(10)); + // 准备运费模板费用配置数据 + chargeBO = randomPojo(DeliveryExpressTemplateChargeBO.class, + item -> item.setStartCount(10D).setStartPrice(1000).setExtraCount(10D).setExtraPrice(2000)); + // 准备运费模板包邮配置数据 订单总件数 < 包邮件数时 12 < 20 + freeBO = randomPojo(DeliveryExpressTemplateFreeBO.class, + item -> item.setFreeCount(20).setFreePrice(100)); + // 准备 SP 运费模板 数据 + spuTemplateRespBO = randomPojo(SpuDeliveryExpressTemplateRespBO.class, + item -> item.setChargeMode(PIECE.getType()) + .setTemplateCharge(chargeBO).setTemplateFree(freeBO)); + } + + @Test + @DisplayName("按件计算运费不包邮的情况") + public void testCalculateByExpressTemplateCharge() { + // SKU 1 : 100 * 2 = 200 + // SKU 2 :200 * 10 = 2000 + // 运费 首件 1000 + 续件 2000 = 3000 + Map respMap = new HashMap<>(); + respMap.put(1L, spuTemplateRespBO); + + // mock 方法 + when(addressApi.getAddress(eq(10L), eq(1L))).thenReturn(addressResp); + when(deliveryExpressTemplateService.getExpressTemplateMapBySpuIdsAndArea(eq(asSet(1L)), eq(10))) + .thenReturn(respMap); + + calculator.calculate(reqBO, resultBO); + + TradePriceCalculateRespBO.Price price = resultBO.getPrice(); + + assertThat(price) + .extracting("totalPrice","discountPrice","couponPrice","pointPrice","deliveryPrice","payPrice") + .containsExactly(2200, 0, 0, 0, 3000, 5200); + // 断言:SKU + assertThat(resultBO.getItems()).hasSize(3); + // SKU1 + assertThat(resultBO.getItems().get(0)) + .extracting("price", "count","discountPrice" ,"couponPrice", "pointPrice","deliveryPrice","payPrice") + .containsExactly(100, 2, 0, 0, 0, 1500, 1700); + // SKU2 + assertThat(resultBO.getItems().get(1)) + .extracting("price", "count","discountPrice" ,"couponPrice", "pointPrice","deliveryPrice","payPrice") + .containsExactly(200, 10, 0, 0, 0, 1500, 3500); + // SKU3 未选中 + assertThat(resultBO.getItems().get(2)) + .extracting("price", "count","discountPrice" ,"couponPrice", "pointPrice","deliveryPrice","payPrice") + .containsExactly(300, 1, 0, 0, 0, 0, 300); + } + + @Test + @DisplayName("按件计算运费包邮的情况") + public void testCalculateByExpressTemplateFree() { + // SKU 1 : 100 * 2 = 200 + // SKU 2 :200 * 10 = 2000 + // 运费 0 + Map respMap = new HashMap<>(); + respMap.put(1L, spuTemplateRespBO); + // 准备运费模板包邮配置数据 包邮 订单总件数 > 包邮件数时 12 > 10 + freeBO = randomPojo(DeliveryExpressTemplateFreeBO.class, + item -> item.setFreeCount(10).setFreePrice(1000)); + spuTemplateRespBO.setTemplateFree(freeBO); + // mock 方法 + when(addressApi.getAddress(eq(10L), eq(1L))).thenReturn(addressResp); + when(deliveryExpressTemplateService.getExpressTemplateMapBySpuIdsAndArea(eq(asSet(1L)), eq(10))) + .thenReturn(respMap); + + calculator.calculate(reqBO, resultBO); + + TradePriceCalculateRespBO.Price price = resultBO.getPrice(); + + // 断言price + assertThat(price) + .extracting("totalPrice","discountPrice","couponPrice","pointPrice","deliveryPrice","payPrice") + .containsExactly(2200, 0, 0, 0, 0, 2200); + // 断言:SKU + assertThat(resultBO.getItems()).hasSize(3); + // SKU1 + assertThat(resultBO.getItems().get(0)) + .extracting("price", "count","discountPrice" ,"couponPrice", "pointPrice","deliveryPrice","payPrice") + .containsExactly(100, 2, 0, 0, 0, 0, 200); + // SKU2 + assertThat(resultBO.getItems().get(1)) + .extracting("price", "count","discountPrice" ,"couponPrice", "pointPrice","deliveryPrice","payPrice") + .containsExactly(200, 10, 0, 0, 0, 0, 2000); + // SKU3 未选中 + assertThat(resultBO.getItems().get(2)) + .extracting("price", "count","discountPrice" ,"couponPrice", "pointPrice","deliveryPrice","payPrice") + .containsExactly(300, 1, 0, 0, 0, 0, 300); + } +} \ No newline at end of file