mall - trade - 新增 TradeDeliveryPriceCalculator

This commit is contained in:
jason 2023-06-03 17:16:18 +08:00
parent 73a781cbbe
commit f1fa8eadd2
8 changed files with 271 additions and 3 deletions

View File

@ -141,7 +141,7 @@ public class ProductSpuDO extends BaseDO {
/**
* 物流配置模板编号
*
* 关联 { TradeDeliveryExpressTemplateDO#getId()}
* 对应 { TradeDeliveryExpressTemplateDO id 编号}
*/
private Long deliveryTemplateId;

View File

@ -1,5 +1,6 @@
package cn.iocoder.yudao.module.trade.enums.delivery;
import cn.hutool.core.util.ArrayUtil;
import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
import lombok.AllArgsConstructor;
import lombok.Getter;
@ -35,4 +36,8 @@ public enum DeliveryExpressChargeModeEnum implements IntArrayValuable {
return ARRAYS;
}
public static DeliveryExpressChargeModeEnum valueOf(Integer value) {
return ArrayUtil.firstMatch(chargeMode -> chargeMode.getType().equals(value), DeliveryExpressChargeModeEnum.values());
}
}

View File

@ -63,4 +63,11 @@ public interface DeliveryExpressTemplateService {
* @return 快递运费模板分页
*/
PageResult<DeliveryExpressTemplateDO> getDeliveryExpressTemplatePage(DeliveryExpressTemplatePageReqVO pageReqVO);
/**
* 校验快递运费模板
* @param templateId 模板编号
* @return DeliveryExpressTemplateDO 非空
*/
DeliveryExpressTemplateDO validateDeliveryExpressTemplate(Long templateId);
}

View File

@ -202,4 +202,13 @@ public class DeliveryExpressTemplateServiceImpl implements DeliveryExpressTempla
return expressTemplateMapper.selectPage(pageReqVO);
}
@Override
public DeliveryExpressTemplateDO validateDeliveryExpressTemplate(Long templateId) {
DeliveryExpressTemplateDO template = expressTemplateMapper.selectById(templateId);
if (template == null) {
throw exception(EXPRESS_TEMPLATE_NOT_EXISTS);
}
return template;
}
}

View File

@ -1,5 +1,7 @@
package cn.iocoder.yudao.module.trade.service.price.bo;
import cn.iocoder.yudao.module.trade.dal.dataobject.delivery.DeliveryExpressTemplateDO;
import cn.iocoder.yudao.module.trade.enums.delivery.DeliveryTypeEnum;
import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum;
import lombok.Data;
@ -44,6 +46,20 @@ public class TradePriceCalculateReqBO {
*/
private Long addressId;
/**
* 配送方式
*
* 枚举 {@link DeliveryTypeEnum}
*/
private Integer deliveryType;
/**
* 配送模板编号
*
* 关联 {@link DeliveryExpressTemplateDO#getId()}
*/
private Long templateId;
/**
* 商品 SKU 数组
*/
@ -82,5 +98,4 @@ public class TradePriceCalculateReqBO {
private Boolean selected;
}
}

View File

@ -0,0 +1,228 @@
package cn.iocoder.yudao.module.trade.service.price.calculator;
import cn.iocoder.yudao.module.member.api.address.AddressApi;
import cn.iocoder.yudao.module.member.api.address.dto.AddressRespDTO;
import cn.iocoder.yudao.module.product.api.sku.ProductSkuApi;
import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO;
import cn.iocoder.yudao.module.trade.dal.dataobject.delivery.DeliveryExpressTemplateChargeDO;
import cn.iocoder.yudao.module.trade.dal.dataobject.delivery.DeliveryExpressTemplateDO;
import cn.iocoder.yudao.module.trade.dal.dataobject.delivery.DeliveryExpressTemplateFreeDO;
import cn.iocoder.yudao.module.trade.dal.mysql.delivery.DeliveryExpressTemplateChargeMapper;
import cn.iocoder.yudao.module.trade.dal.mysql.delivery.DeliveryExpressTemplateFreeMapper;
import cn.iocoder.yudao.module.trade.enums.delivery.DeliveryExpressChargeModeEnum;
import cn.iocoder.yudao.module.trade.enums.delivery.DeliveryTypeEnum;
import cn.iocoder.yudao.module.trade.service.delivery.DeliveryExpressTemplateService;
import cn.iocoder.yudao.module.trade.service.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 org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
/**
* 运费的 {@link TradePriceCalculator} 实现类
*
* @author jason
*/
@Component
@Order(TradePriceCalculator.ORDER_DELIVERY)
public class TradeDeliveryPriceCalculator implements TradePriceCalculator {
@Resource
private AddressApi addressApi;
@Resource
private ProductSkuApi productSkuApi;
@Resource
private DeliveryExpressTemplateService deliveryExpressTemplateService;
@Resource
private DeliveryExpressTemplateChargeMapper templateChargeMapper;
@Resource
private DeliveryExpressTemplateFreeMapper templateFreeMapper;
@Override
public void calculate(TradePriceCalculateReqBO param, TradePriceCalculateRespBO result) {
// 1.1 判断配送方式
if (param.getDeliveryType() == null || DeliveryTypeEnum.PICK_UP.getMode().equals(param.getDeliveryType())) {
return;
}
if (param.getTemplateId() == null || param.getAddressId() == null) {
return;
}
// 1.2 校验运费模板是否存在
DeliveryExpressTemplateDO template = deliveryExpressTemplateService.validateDeliveryExpressTemplate(param.getTemplateId());
// 得到包邮配置
List<DeliveryExpressTemplateFreeDO> expressTemplateFreeList = templateFreeMapper.selectListByTemplateId(template.getId());
Map<Integer, DeliveryExpressTemplateFreeDO> areaTemplateFreeMap = new HashMap<>();
expressTemplateFreeList.forEach(item -> {
for (Integer areaId : item.getAreaIds()) {
// TODO 需要保证 areaId 不能重复
if (!areaTemplateFreeMap.containsKey(areaId)) {
areaTemplateFreeMap.put(areaId, item);
}
}
});
// 得到快递运费配置
List<DeliveryExpressTemplateChargeDO> expressTemplateChargeList = templateChargeMapper.selectListByTemplateId(template.getId());
Map<Integer, DeliveryExpressTemplateChargeDO> areaTemplateChargeMap = new HashMap<>();
expressTemplateChargeList.forEach(item -> {
for (Integer areaId : item.getAreaIds()) {
// areaId 不能重复
if (!areaTemplateChargeMap.containsKey(areaId)) {
areaTemplateChargeMap.put(areaId, item);
}
}
});
// 得到收件地址区域
AddressRespDTO address = addressApi.getAddress(param.getAddressId(), param.getUserId());
// 1.3 计算快递费用
calculateDeliveryPrice(address.getAreaId(), template.getChargeMode(),
areaTemplateFreeMap, areaTemplateChargeMap, result);
}
/**
* 校验订单是否满足包邮条件
*
* @param receiverAreaId 收件人地区的区域编号
* @param chargeMode 配送计费方式
* @param areaTemplateFreeMap 运费模板包邮区域设置 Map
* @param areaTemplateChargeMap 运费模板快递费用设置 Map
*/
private void calculateDeliveryPrice(Integer receiverAreaId,
Integer chargeMode,
Map<Integer, DeliveryExpressTemplateFreeDO> areaTemplateFreeMap,
Map<Integer, DeliveryExpressTemplateChargeDO> areaTemplateChargeMap,
TradePriceCalculateRespBO result) {
// 过滤出已选中的商品SKU
List<OrderItem> selectedItem = filterList(result.getItems(), OrderItem::getSelected);
Set<Long> skuIds = convertSet(selectedItem, OrderItem::getSkuId);
// 得到SKU 详情得到 重量体积
Map<Long, ProductSkuRespDTO> skuRespMap = convertMap(productSkuApi.getSkuList(skuIds), ProductSkuRespDTO::getId);
// 一个 spuId 可能对应多条订单商品 SKU
Map<Long, List<OrderItem>> spuIdItemMap = convertMultiMap(selectedItem, OrderItem::getSpuId);
// 依次计算每个 SPU 的快递运费
for (Map.Entry<Long, List<OrderItem>> entry : spuIdItemMap.entrySet()) {
List<OrderItem> orderItems = entry.getValue();
// 总件数, 总金额, 总重量 总体积
int totalCount = 0, totalPrice = 0;
double totalWeight = 0;
double totalVolume = 0;
for (OrderItem orderItem : orderItems) {
totalCount += orderItem.getCount();
totalPrice += orderItem.getPrice();
ProductSkuRespDTO skuResp = skuRespMap.get(orderItem.getSkuId());
if (skuResp != null) {
totalWeight = totalWeight + skuResp.getWeight();
totalVolume = totalVolume + skuResp.getVolume();
}
}
// 优先判断是否包邮. 如果包邮不计算快递运费
if (areaTemplateFreeMap.containsKey(receiverAreaId) &&
checkExpressFree(chargeMode, totalCount, totalWeight,
totalVolume, totalPrice, areaTemplateFreeMap.get(receiverAreaId))) {
continue;
}
// 计算快递运费
if (areaTemplateChargeMap.containsKey(receiverAreaId)) {
DeliveryExpressTemplateChargeDO templateCharge = areaTemplateChargeMap.get(receiverAreaId);
DeliveryExpressChargeModeEnum chargeModeEnum = DeliveryExpressChargeModeEnum.valueOf(chargeMode);
switch (chargeModeEnum) {
case PIECE: {
calculateExpressFeeBySpu(totalCount, templateCharge, orderItems);
break;
}
case WEIGHT: {
calculateExpressFeeBySpu(totalWeight, templateCharge, orderItems);
break;
}
case VOLUME: {
calculateExpressFeeBySpu(totalVolume, templateCharge, orderItems);
break;
}
}
}
}
TradePriceCalculatorHelper.recountAllPrice(result);
}
/**
* spu 来计算快递费用
*
* @param total 总件数/总重量/总体积
* @param templateCharge 快递运费配置
* @param orderItems SKU 商品项目
*/
private void calculateExpressFeeBySpu(double total, DeliveryExpressTemplateChargeDO templateCharge, List<OrderItem> orderItems) {
int deliveryPrice;
if (total <= templateCharge.getStartCount()) {
deliveryPrice = templateCharge.getStartPrice();
} else {
double remainWeight = total - templateCharge.getStartCount();
// 剩余重量/ 续件 = 续件的次数. 向上取整
int extraNum = (int) Math.ceil(remainWeight / templateCharge.getExtraCount());
int extraPrice = templateCharge.getExtraPrice() * extraNum;
deliveryPrice = templateCharge.getStartPrice() + extraPrice;
}
//
// TODO @芋艿 分摊快递费用到 SKU. 是不是搞复杂了
divideDeliveryPrice(deliveryPrice, orderItems);
}
/**
* 快递运费分摊到每个 SKU 商品上
*
* @param deliveryPrice 快递运费
* @param orderItems SKU 商品
*/
private void divideDeliveryPrice(int deliveryPrice, List<OrderItem> orderItems) {
int dividePrice = deliveryPrice / orderItems.size();
for (OrderItem item : orderItems) {
// 更新快递运费
item.setDeliveryPrice(dividePrice);
}
}
/**
* 检查是否包邮
*
* @param chargeMode 配送计费方式
* @param totalCount 总件数
* @param totalWeight 总重量
* @param totalVolume 总体积
* @param totalPrice 总金额
* @param templateFree 包邮配置
*/
private boolean checkExpressFree(Integer chargeMode, int totalCount, double totalWeight,
double totalVolume, int totalPrice, DeliveryExpressTemplateFreeDO templateFree) {
DeliveryExpressChargeModeEnum chargeModeEnum = DeliveryExpressChargeModeEnum.valueOf(chargeMode);
switch (chargeModeEnum) {
case PIECE:
// 两个条件都满足才包邮
if (totalCount >= templateFree.getFreeCount() && totalPrice >= templateFree.getFreePrice()) {
return true;
}
break;
case WEIGHT:
// freeCount 是不是应该是 double ??
if (totalWeight >= templateFree.getFreeCount()
&& totalPrice >= templateFree.getFreePrice()) {
return true;
}
break;
case VOLUME:
if (totalVolume >= templateFree.getFreeCount()
&& totalPrice >= templateFree.getFreePrice()) {
return true;
}
break;
}
return false;
}
}

View File

@ -11,6 +11,10 @@ import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO;
public interface TradePriceCalculator {
int ORDER_DISCOUNT_ACTIVITY = 10;
/**
* TODO @芋艿 快递运费的计算在满减之前 例如有满多少包邮
*/
int ORDER_DELIVERY = 15;
int ORDER_REWARD_ACTIVITY = 20;
int ORDER_COUPON = 30;

View File

@ -39,7 +39,7 @@ public class MemberAddressDO extends BaseDO {
/**
* 地区编号
*/
private Long areaId;
private Integer areaId;
/**
* 收件详细地址
*/