From 7c4fbc63dc21f71ad62ea9c9a05af3e8acafbf50 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Fri, 22 Sep 2023 21:56:09 +0800 Subject: [PATCH] =?UTF-8?q?trade=EF=BC=9A=E7=89=A9=E6=B5=81=E8=B4=B9?= =?UTF-8?q?=E7=94=A8=E8=AE=A1=E7=AE=97=EF=BC=8C=E5=A2=9E=E5=8A=A0=E9=87=91?= =?UTF-8?q?=E9=A2=9D=20divide=20=E5=88=86=E6=8B=86=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DeliveryExpressChargeModeEnum.java | 2 +- .../TradeDeliveryPriceCalculator.java | 165 ++++++------------ .../TradeDeliveryPriceCalculatorTest.java | 6 +- 3 files changed, 61 insertions(+), 112 deletions(-) diff --git a/yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/delivery/DeliveryExpressChargeModeEnum.java b/yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/delivery/DeliveryExpressChargeModeEnum.java index a4b5252ee..7503dd322 100644 --- a/yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/delivery/DeliveryExpressChargeModeEnum.java +++ b/yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/delivery/DeliveryExpressChargeModeEnum.java @@ -16,7 +16,7 @@ import java.util.Arrays; @Getter public enum DeliveryExpressChargeModeEnum implements IntArrayValuable { - PIECE(1, "按件"), + COUNT(1, "按件"), WEIGHT(2,"按重量"), VOLUME(3, "按体积"); 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 7a4669801..20c3feee3 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 @@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.trade.service.price.calculator; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.lang.Assert; +import cn.hutool.core.util.StrUtil; 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.dto.AddressRespDTO; @@ -55,13 +56,13 @@ public class TradeDeliveryPriceCalculator implements TradePriceCalculator { return; } if (DeliveryTypeEnum.PICK_UP.getType().equals(param.getDeliveryType())) { - calculateByPickUp(param, result); + calculateByPickUp(param); } else if (DeliveryTypeEnum.EXPRESS.getType().equals(param.getDeliveryType())) { calculateExpress(param, result); } } - private void calculateByPickUp(TradePriceCalculateReqBO param, TradePriceCalculateRespBO result) { + private void calculateByPickUp(TradePriceCalculateReqBO param) { if (param.getPickUpStoreId() == null) { 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()); // 情况一:全局包邮 - if (isGlobalExpressFree(param, result)) { + if (isGlobalExpressFree(result)) { return; } - // 情况二: - // 2.1 过滤出已选中的商品SKU + // 情况二:快递模版 + // 2.1 过滤出已选中的商品 SKU List selectedItem = filterList(result.getItems(), OrderItem::getSelected); Set deliveryTemplateIds = convertSet(selectedItem, OrderItem::getDeliveryTemplateId); Map expressTemplateMap = @@ -103,11 +104,10 @@ public class TradeDeliveryPriceCalculator implements TradePriceCalculator { /** * 是否全局包邮 * - * @param param 计算信息 * @param result 计算结果 * @return 是否包邮 */ - private boolean isGlobalExpressFree(TradePriceCalculateReqBO param, TradePriceCalculateRespBO result) { + private boolean isGlobalExpressFree(TradePriceCalculateRespBO result) { TradeConfigDO config = tradeConfigService.getTradeConfig(); return config != null && Boolean.TRUE.equals(config.getDeliveryExpressFreeEnabled()) // 开启包邮 @@ -118,9 +118,9 @@ public class TradeDeliveryPriceCalculator implements TradePriceCalculator { Map expressTemplateMap, TradePriceCalculateRespBO result) { // 按商品运费模板来计算商品的运费:相同的运费模板可能对应多条订单商品 SKU - Map> tplIdItemMap = convertMultiMap(selectedSkus, OrderItem::getDeliveryTemplateId); + Map> template2ItemMap = convertMultiMap(selectedSkus, OrderItem::getDeliveryTemplateId); // 依次计算快递运费 - for (Map.Entry> entry : tplIdItemMap.entrySet()) { + for (Map.Entry> entry : template2ItemMap.entrySet()) { Long templateId = entry.getKey(); List orderItems = entry.getValue(); DeliveryExpressTemplateRespBO templateBO = expressTemplateMap.get(templateId); @@ -128,30 +128,12 @@ public class TradeDeliveryPriceCalculator implements TradePriceCalculator { log.error("[calculateDeliveryPrice][不能计算快递运费,找不到 templateId({}) 对应的运费模板配置]", templateId); continue; } - // 总件数, 总金额, 总重量, 总体积 - int totalCount = 0; - 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())) { + // 1. 优先判断是否包邮。如果包邮不计算快递运费 + if (isExpressTemplateFree(orderItems, templateBO.getChargeMode(), templateBO.getFree())) { continue; } - // 计算快递运费 - calculateExpressFeeByChargeMode(totalCount, totalWeight, totalVolume, - templateBO.getChargeMode(), templateBO.getCharge(), orderItems); - + // 2. 计算快递运费 + calculateExpressFeeByChargeMode(orderItems, templateBO.getChargeMode(), templateBO.getCharge()); } TradePriceCalculatorHelper.recountAllPrice(result); } @@ -159,73 +141,44 @@ public class TradeDeliveryPriceCalculator implements TradePriceCalculator { /** * 按配送方式来计算运费 * - * @param totalCount 总件数 - * @param totalWeight 总重量 - * @param totalVolume 总体积 + * @param orderItems SKU 商品项目 * @param chargeMode 配送计费方式 * @param templateCharge 快递运费配置 - * @param orderItems SKU 商品项目 */ - private void calculateExpressFeeByChargeMode(double totalCount, double totalWeight, double totalVolume, - int chargeMode, DeliveryExpressTemplateRespBO.Charge templateCharge, - List orderItems) { + private void calculateExpressFeeByChargeMode(List orderItems, Integer chargeMode, + DeliveryExpressTemplateRespBO.Charge templateCharge) { if (templateCharge == null) { log.error("[calculateExpressFeeByChargeMode][计算快递运费时,找不到 SKU({}) 对应的运费模版]", orderItems); return; } - DeliveryExpressChargeModeEnum chargeModeEnum = DeliveryExpressChargeModeEnum.valueOf(chargeMode); - switch (chargeModeEnum) { - 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 orderItems) { + double totalChargeValue = getTotalChargeValue(orderItems, chargeMode); + // 1. 计算 SKU 商品快递费用 int deliveryPrice; - if (total <= templateCharge.getStartCount()) { + if (totalChargeValue <= templateCharge.getStartCount()) { deliveryPrice = templateCharge.getStartPrice(); } else { - double remainWeight = total - templateCharge.getStartCount(); + double remainWeight = totalChargeValue - templateCharge.getStartCount(); // 剩余重量/ 续件 = 续件的次数. 向上取整 int extraNum = (int) Math.ceil(remainWeight / templateCharge.getExtraCount()); int extraPrice = templateCharge.getExtraPrice() * extraNum; deliveryPrice = templateCharge.getStartPrice() + extraPrice; } - // 分摊快递费用到 SKU. 退费的时候,可能按照 SKU 考虑退费金额 - divideDeliveryPrice(deliveryPrice, orderItems); - } - /** - * 快递运费分摊到每个 SKU 商品上 - * - * @param deliveryPrice 快递运费 - * @param orderItems SKU 商品 - */ - private void divideDeliveryPrice(int deliveryPrice, List orderItems) { - // TODO @jason:分摊的话,是不是要按照比例呀?重量、价格、数量等等, - // 按比例是不是有点复杂。后面看看是否需要; - // TODO 可以看看别的项目怎么搞的哈。 - int dividePrice = deliveryPrice / orderItems.size(); - for (OrderItem item : orderItems) { + // 2. 分摊快递费用到 SKU. 退费的时候,可能按照 SKU 考虑退费金额 + int remainPrice = deliveryPrice; + for (int i = 0; i < orderItems.size(); i++) { + TradePriceCalculateRespBO.OrderItem item = orderItems.get(i); + int partPrice; + double chargeValue = getChargeValue(item, chargeMode); + if (i < orderItems.size() - 1) { // 减一的原因,是因为拆分时,如果按照比例,可能会出现.所以最后一个,使用反减 + partPrice = (int) (deliveryPrice * (chargeValue / totalChargeValue)); + remainPrice -= partPrice; + } else { + partPrice = remainPrice; + } + Assert.isTrue(partPrice >= 0, "分摊金额必须大于等于 0"); // 更新快递运费 - item.setDeliveryPrice(dividePrice); + item.setDeliveryPrice(partPrice); TradePriceCalculatorHelper.recountPayPrice(item); } } @@ -234,42 +187,38 @@ public class TradeDeliveryPriceCalculator implements TradePriceCalculator { * 检查是否包邮 * * @param chargeMode 配送计费方式 - * @param totalCount 总件数 - * @param totalWeight 总重量 - * @param totalVolume 总体积 - * @param totalPrice 总金额 * @param templateFree 包邮配置 */ - private boolean isExpressFree(Integer chargeMode, int totalCount, double totalWeight, - double totalVolume, int totalPrice, DeliveryExpressTemplateRespBO.Free templateFree) { + private boolean isExpressTemplateFree(List orderItems, Integer chargeMode, + DeliveryExpressTemplateRespBO.Free templateFree) { if (templateFree == null) { return false; } + double totalChargeValue = getTotalChargeValue(orderItems, chargeMode); + double totalPrice = TradePriceCalculatorHelper.calculateTotalPayPrice(orderItems); + return totalChargeValue >= templateFree.getFreeCount() && totalPrice >= templateFree.getFreePrice(); + } + + private double getTotalChargeValue(List 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); switch (chargeModeEnum) { - case PIECE: - // 两个条件都满足才包邮 - if (totalCount >= templateFree.getFreeCount() && totalPrice >= templateFree.getFreePrice()) { - return true; - } - break; + case COUNT: + return orderItem.getCount(); case WEIGHT: - // freeCount 是不是应该是 double ?? - // TODO @jason:要不配置的时候,把它的单位和商品对齐?到底是 kg、还是斤 - // TODO @芋艿 目前 包邮 件数/重量/体积 都用的是这个字段 - // TODO @jason:那要不快递模版也改成 kg?这样是不是就不用 double ? - if (totalWeight >= templateFree.getFreeCount() - && totalPrice >= templateFree.getFreePrice()) { - return true; - } - break; + return orderItem.getWeight() != null ? orderItem.getWeight() * orderItem.getCount() : 0; case VOLUME: - if (totalVolume >= templateFree.getFreeCount() - && totalPrice >= templateFree.getFreePrice()) { - return true; - } - break; + return orderItem.getVolume() != null ? orderItem.getVolume() * orderItem.getCount() : 0; + default: + throw new IllegalArgumentException(StrUtil.format("未知的计费模式({})", chargeMode)); } - return false; } + } 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 index 5aa75fd94..3c1c107ed 100644 --- 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 @@ -91,7 +91,7 @@ public class TradeDeliveryPriceCalculatorTest extends BaseMockitoUnitTest { item -> item.setFreeCount(20).setFreePrice(100)); // 准备 SP 运费模板数据 templateRespBO = randomPojo(DeliveryExpressTemplateRespBO.class, - item -> item.setChargeMode(DeliveryExpressChargeModeEnum.PIECE.getType()) + item -> item.setChargeMode(DeliveryExpressChargeModeEnum.COUNT.getType()) .setCharge(chargeBO).setFree(freeBO)); } @@ -144,11 +144,11 @@ public class TradeDeliveryPriceCalculatorTest extends BaseMockitoUnitTest { // 断言:SKU1 assertThat(resultBO.getItems().get(0)) .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 assertThat(resultBO.getItems().get(1)) .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 未选中 assertThat(resultBO.getItems().get(2)) .extracting("price", "count","discountPrice" ,"couponPrice", "pointPrice","deliveryPrice","payPrice")