trade:物流费用计算,增加金额 divide 分拆逻辑

This commit is contained in:
YunaiV 2023-09-22 21:56:09 +08:00
parent 24111c1548
commit 7c4fbc63dc
3 changed files with 61 additions and 112 deletions

View File

@ -16,7 +16,7 @@ import java.util.Arrays;
@Getter @Getter
public enum DeliveryExpressChargeModeEnum implements IntArrayValuable { public enum DeliveryExpressChargeModeEnum implements IntArrayValuable {
PIECE(1, "按件"), COUNT(1, "按件"),
WEIGHT(2,"按重量"), WEIGHT(2,"按重量"),
VOLUME(3, "按体积"); VOLUME(3, "按体积");

View File

@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.trade.service.price.calculator;
import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Assert; import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.module.member.api.address.AddressApi; import cn.iocoder.yudao.module.member.api.address.AddressApi;
import cn.iocoder.yudao.module.member.api.address.dto.AddressRespDTO; import cn.iocoder.yudao.module.member.api.address.dto.AddressRespDTO;
@ -55,13 +56,13 @@ public class TradeDeliveryPriceCalculator implements TradePriceCalculator {
return; return;
} }
if (DeliveryTypeEnum.PICK_UP.getType().equals(param.getDeliveryType())) { if (DeliveryTypeEnum.PICK_UP.getType().equals(param.getDeliveryType())) {
calculateByPickUp(param, result); calculateByPickUp(param);
} else if (DeliveryTypeEnum.EXPRESS.getType().equals(param.getDeliveryType())) { } else if (DeliveryTypeEnum.EXPRESS.getType().equals(param.getDeliveryType())) {
calculateExpress(param, result); calculateExpress(param, result);
} }
} }
private void calculateByPickUp(TradePriceCalculateReqBO param, TradePriceCalculateRespBO result) { private void calculateByPickUp(TradePriceCalculateReqBO param) {
if (param.getPickUpStoreId() == null) { if (param.getPickUpStoreId() == null) {
throw exception(PRICE_CALCULATE_DELIVERY_PRICE_PICK_UP_STORE_IS_EMPTY); throw exception(PRICE_CALCULATE_DELIVERY_PRICE_PICK_UP_STORE_IS_EMPTY);
} }
@ -82,12 +83,12 @@ public class TradeDeliveryPriceCalculator implements TradePriceCalculator {
Assert.notNull(address, "收件人({})的地址,不能为空", param.getUserId()); Assert.notNull(address, "收件人({})的地址,不能为空", param.getUserId());
// 情况一全局包邮 // 情况一全局包邮
if (isGlobalExpressFree(param, result)) { if (isGlobalExpressFree(result)) {
return; return;
} }
// 情况二 // 情况二快递模版
// 2.1 过滤出已选中的商品SKU // 2.1 过滤出已选中的商品 SKU
List<OrderItem> selectedItem = filterList(result.getItems(), OrderItem::getSelected); List<OrderItem> selectedItem = filterList(result.getItems(), OrderItem::getSelected);
Set<Long> deliveryTemplateIds = convertSet(selectedItem, OrderItem::getDeliveryTemplateId); Set<Long> deliveryTemplateIds = convertSet(selectedItem, OrderItem::getDeliveryTemplateId);
Map<Long, DeliveryExpressTemplateRespBO> expressTemplateMap = Map<Long, DeliveryExpressTemplateRespBO> expressTemplateMap =
@ -103,11 +104,10 @@ public class TradeDeliveryPriceCalculator implements TradePriceCalculator {
/** /**
* 是否全局包邮 * 是否全局包邮
* *
* @param param 计算信息
* @param result 计算结果 * @param result 计算结果
* @return 是否包邮 * @return 是否包邮
*/ */
private boolean isGlobalExpressFree(TradePriceCalculateReqBO param, TradePriceCalculateRespBO result) { private boolean isGlobalExpressFree(TradePriceCalculateRespBO result) {
TradeConfigDO config = tradeConfigService.getTradeConfig(); TradeConfigDO config = tradeConfigService.getTradeConfig();
return config != null return config != null
&& Boolean.TRUE.equals(config.getDeliveryExpressFreeEnabled()) // 开启包邮 && Boolean.TRUE.equals(config.getDeliveryExpressFreeEnabled()) // 开启包邮
@ -118,9 +118,9 @@ public class TradeDeliveryPriceCalculator implements TradePriceCalculator {
Map<Long, DeliveryExpressTemplateRespBO> expressTemplateMap, Map<Long, DeliveryExpressTemplateRespBO> expressTemplateMap,
TradePriceCalculateRespBO result) { TradePriceCalculateRespBO result) {
// 按商品运费模板来计算商品的运费相同的运费模板可能对应多条订单商品 SKU // 按商品运费模板来计算商品的运费相同的运费模板可能对应多条订单商品 SKU
Map<Long, List<OrderItem>> tplIdItemMap = convertMultiMap(selectedSkus, OrderItem::getDeliveryTemplateId); Map<Long, List<OrderItem>> template2ItemMap = convertMultiMap(selectedSkus, OrderItem::getDeliveryTemplateId);
// 依次计算快递运费 // 依次计算快递运费
for (Map.Entry<Long, List<OrderItem>> entry : tplIdItemMap.entrySet()) { for (Map.Entry<Long, List<OrderItem>> entry : template2ItemMap.entrySet()) {
Long templateId = entry.getKey(); Long templateId = entry.getKey();
List<OrderItem> orderItems = entry.getValue(); List<OrderItem> orderItems = entry.getValue();
DeliveryExpressTemplateRespBO templateBO = expressTemplateMap.get(templateId); DeliveryExpressTemplateRespBO templateBO = expressTemplateMap.get(templateId);
@ -128,30 +128,12 @@ public class TradeDeliveryPriceCalculator implements TradePriceCalculator {
log.error("[calculateDeliveryPrice][不能计算快递运费,找不到 templateId({}) 对应的运费模板配置]", templateId); log.error("[calculateDeliveryPrice][不能计算快递运费,找不到 templateId({}) 对应的运费模板配置]", templateId);
continue; continue;
} }
// 总件数, 总金额, 总重量 总体积 // 1. 优先判断是否包邮如果包邮不计算快递运费
int totalCount = 0; if (isExpressTemplateFree(orderItems, templateBO.getChargeMode(), templateBO.getFree())) {
int totalPrice = 0;
double totalWeight = 0;
double totalVolume = 0;
for (OrderItem orderItem : orderItems) {
totalCount += orderItem.getCount();
totalPrice += orderItem.getPayPrice();
if (orderItem.getWeight() != null) {
totalWeight += totalWeight + orderItem.getWeight() * orderItem.getCount();
}
if (orderItem.getVolume() != null) {
totalVolume += totalVolume + orderItem.getVolume() * orderItem.getCount();
}
}
// 优先判断是否包邮. 如果包邮不计算快递运费
if (isExpressFree(templateBO.getChargeMode(), totalCount, totalWeight,
totalVolume, totalPrice, templateBO.getFree())) {
continue; continue;
} }
// 计算快递运费 // 2. 计算快递运费
calculateExpressFeeByChargeMode(totalCount, totalWeight, totalVolume, calculateExpressFeeByChargeMode(orderItems, templateBO.getChargeMode(), templateBO.getCharge());
templateBO.getChargeMode(), templateBO.getCharge(), orderItems);
} }
TradePriceCalculatorHelper.recountAllPrice(result); TradePriceCalculatorHelper.recountAllPrice(result);
} }
@ -159,73 +141,44 @@ public class TradeDeliveryPriceCalculator implements TradePriceCalculator {
/** /**
* 按配送方式来计算运费 * 按配送方式来计算运费
* *
* @param totalCount 总件数 * @param orderItems SKU 商品项目
* @param totalWeight 总重量
* @param totalVolume 总体积
* @param chargeMode 配送计费方式 * @param chargeMode 配送计费方式
* @param templateCharge 快递运费配置 * @param templateCharge 快递运费配置
* @param orderItems SKU 商品项目
*/ */
private void calculateExpressFeeByChargeMode(double totalCount, double totalWeight, double totalVolume, private void calculateExpressFeeByChargeMode(List<OrderItem> orderItems, Integer chargeMode,
int chargeMode, DeliveryExpressTemplateRespBO.Charge templateCharge, DeliveryExpressTemplateRespBO.Charge templateCharge) {
List<OrderItem> orderItems) {
if (templateCharge == null) { if (templateCharge == null) {
log.error("[calculateExpressFeeByChargeMode][计算快递运费时,找不到 SKU({}) 对应的运费模版]", orderItems); log.error("[calculateExpressFeeByChargeMode][计算快递运费时,找不到 SKU({}) 对应的运费模版]", orderItems);
return; return;
} }
DeliveryExpressChargeModeEnum chargeModeEnum = DeliveryExpressChargeModeEnum.valueOf(chargeMode); double totalChargeValue = getTotalChargeValue(orderItems, chargeMode);
switch (chargeModeEnum) { // 1. 计算 SKU 商品快递费用
case PIECE: {
calculateExpressFee(totalCount, templateCharge, orderItems);
break;
}
case WEIGHT: {
calculateExpressFee(totalWeight, templateCharge, orderItems);
break;
}
case VOLUME: {
calculateExpressFee(totalVolume, templateCharge, orderItems);
break;
}
}
}
/**
* 计算 SKU 商品快递费用
*
* @param total 总件数/总重量/总体积
* @param templateCharge 快递运费配置
* @param orderItems SKU 商品项目
*/
private void calculateExpressFee(double total, DeliveryExpressTemplateRespBO.Charge templateCharge, List<OrderItem> orderItems) {
int deliveryPrice; int deliveryPrice;
if (total <= templateCharge.getStartCount()) { if (totalChargeValue <= templateCharge.getStartCount()) {
deliveryPrice = templateCharge.getStartPrice(); deliveryPrice = templateCharge.getStartPrice();
} else { } else {
double remainWeight = total - templateCharge.getStartCount(); double remainWeight = totalChargeValue - templateCharge.getStartCount();
// 剩余重量/ 续件 = 续件的次数. 向上取整 // 剩余重量/ 续件 = 续件的次数. 向上取整
int extraNum = (int) Math.ceil(remainWeight / templateCharge.getExtraCount()); int extraNum = (int) Math.ceil(remainWeight / templateCharge.getExtraCount());
int extraPrice = templateCharge.getExtraPrice() * extraNum; int extraPrice = templateCharge.getExtraPrice() * extraNum;
deliveryPrice = templateCharge.getStartPrice() + extraPrice; deliveryPrice = templateCharge.getStartPrice() + extraPrice;
} }
// 分摊快递费用到 SKU. 退费的时候可能按照 SKU 考虑退费金额
divideDeliveryPrice(deliveryPrice, orderItems);
}
/** // 2. 分摊快递费用到 SKU. 退费的时候可能按照 SKU 考虑退费金额
* 快递运费分摊到每个 SKU 商品上 int remainPrice = deliveryPrice;
* for (int i = 0; i < orderItems.size(); i++) {
* @param deliveryPrice 快递运费 TradePriceCalculateRespBO.OrderItem item = orderItems.get(i);
* @param orderItems SKU 商品 int partPrice;
*/ double chargeValue = getChargeValue(item, chargeMode);
private void divideDeliveryPrice(int deliveryPrice, List<OrderItem> orderItems) { if (i < orderItems.size() - 1) { // 减一的原因是因为拆分时如果按照比例可能会出现.所以最后一个使用反减
// TODO @jason分摊的话是不是要按照比例呀重量价格数量等等, partPrice = (int) (deliveryPrice * (chargeValue / totalChargeValue));
// 按比例是不是有点复杂后面看看是否需要 remainPrice -= partPrice;
// TODO 可以看看别的项目怎么搞的哈 } else {
int dividePrice = deliveryPrice / orderItems.size(); partPrice = remainPrice;
for (OrderItem item : orderItems) { }
Assert.isTrue(partPrice >= 0, "分摊金额必须大于等于 0");
// 更新快递运费 // 更新快递运费
item.setDeliveryPrice(dividePrice); item.setDeliveryPrice(partPrice);
TradePriceCalculatorHelper.recountPayPrice(item); TradePriceCalculatorHelper.recountPayPrice(item);
} }
} }
@ -234,42 +187,38 @@ public class TradeDeliveryPriceCalculator implements TradePriceCalculator {
* 检查是否包邮 * 检查是否包邮
* *
* @param chargeMode 配送计费方式 * @param chargeMode 配送计费方式
* @param totalCount 总件数
* @param totalWeight 总重量
* @param totalVolume 总体积
* @param totalPrice 总金额
* @param templateFree 包邮配置 * @param templateFree 包邮配置
*/ */
private boolean isExpressFree(Integer chargeMode, int totalCount, double totalWeight, private boolean isExpressTemplateFree(List<OrderItem> orderItems, Integer chargeMode,
double totalVolume, int totalPrice, DeliveryExpressTemplateRespBO.Free templateFree) { DeliveryExpressTemplateRespBO.Free templateFree) {
if (templateFree == null) { if (templateFree == null) {
return false; return false;
} }
double totalChargeValue = getTotalChargeValue(orderItems, chargeMode);
double totalPrice = TradePriceCalculatorHelper.calculateTotalPayPrice(orderItems);
return totalChargeValue >= templateFree.getFreeCount() && totalPrice >= templateFree.getFreePrice();
}
private double getTotalChargeValue(List<OrderItem> orderItems, Integer chargeMode) {
double total = 0;
for (OrderItem orderItem : orderItems) {
total += getChargeValue(orderItem, chargeMode);
}
return total;
}
private double getChargeValue(OrderItem orderItem, Integer chargeMode) {
DeliveryExpressChargeModeEnum chargeModeEnum = DeliveryExpressChargeModeEnum.valueOf(chargeMode); DeliveryExpressChargeModeEnum chargeModeEnum = DeliveryExpressChargeModeEnum.valueOf(chargeMode);
switch (chargeModeEnum) { switch (chargeModeEnum) {
case PIECE: case COUNT:
// 两个条件都满足才包邮 return orderItem.getCount();
if (totalCount >= templateFree.getFreeCount() && totalPrice >= templateFree.getFreePrice()) {
return true;
}
break;
case WEIGHT: case WEIGHT:
// freeCount 是不是应该是 double ?? return orderItem.getWeight() != null ? orderItem.getWeight() * orderItem.getCount() : 0;
// TODO @jason要不配置的时候把它的单位和商品对齐到底是 kg还是斤
// TODO @芋艿 目前 包邮 件数/重量/体积 都用的是这个字段
// TODO @jason那要不快递模版也改成 kg这样是不是就不用 double
if (totalWeight >= templateFree.getFreeCount()
&& totalPrice >= templateFree.getFreePrice()) {
return true;
}
break;
case VOLUME: case VOLUME:
if (totalVolume >= templateFree.getFreeCount() return orderItem.getVolume() != null ? orderItem.getVolume() * orderItem.getCount() : 0;
&& totalPrice >= templateFree.getFreePrice()) { default:
return true; throw new IllegalArgumentException(StrUtil.format("未知的计费模式({})", chargeMode));
}
break;
} }
return false;
} }
} }

View File

@ -91,7 +91,7 @@ public class TradeDeliveryPriceCalculatorTest extends BaseMockitoUnitTest {
item -> item.setFreeCount(20).setFreePrice(100)); item -> item.setFreeCount(20).setFreePrice(100));
// 准备 SP 运费模板数据 // 准备 SP 运费模板数据
templateRespBO = randomPojo(DeliveryExpressTemplateRespBO.class, templateRespBO = randomPojo(DeliveryExpressTemplateRespBO.class,
item -> item.setChargeMode(DeliveryExpressChargeModeEnum.PIECE.getType()) item -> item.setChargeMode(DeliveryExpressChargeModeEnum.COUNT.getType())
.setCharge(chargeBO).setFree(freeBO)); .setCharge(chargeBO).setFree(freeBO));
} }
@ -144,11 +144,11 @@ public class TradeDeliveryPriceCalculatorTest extends BaseMockitoUnitTest {
// 断言SKU1 // 断言SKU1
assertThat(resultBO.getItems().get(0)) assertThat(resultBO.getItems().get(0))
.extracting("price", "count","discountPrice" ,"couponPrice", "pointPrice","deliveryPrice","payPrice") .extracting("price", "count","discountPrice" ,"couponPrice", "pointPrice","deliveryPrice","payPrice")
.containsExactly(100, 2, 0, 0, 0, 1500, 1700); .containsExactly(100, 2, 0, 0, 0, 500, 700);
// 断言SKU2 // 断言SKU2
assertThat(resultBO.getItems().get(1)) assertThat(resultBO.getItems().get(1))
.extracting("price", "count","discountPrice" ,"couponPrice", "pointPrice","deliveryPrice","payPrice") .extracting("price", "count","discountPrice" ,"couponPrice", "pointPrice","deliveryPrice","payPrice")
.containsExactly(200, 10, 0, 0, 0, 1500, 3500); .containsExactly(200, 10, 0, 0, 0, 2500, 4500);
// 断言SKU3 未选中 // 断言SKU3 未选中
assertThat(resultBO.getItems().get(2)) assertThat(resultBO.getItems().get(2))
.extracting("price", "count","discountPrice" ,"couponPrice", "pointPrice","deliveryPrice","payPrice") .extracting("price", "count","discountPrice" ,"couponPrice", "pointPrice","deliveryPrice","payPrice")