Commit 8f018e3b9956a2574e0c746c4e2caa0ec4e382b0

Authored by 舒洪凡ShuHongFan
1 parent 50f0b9fb

feature:运费模块 计算运费 保存新增模板 查询模板

sl-express-ms-carriage-service/src/main/java/com/sl/ms/carriage/service/impl/CarriageServiceImpl.java 0 → 100644
  1 +package com.sl.ms.carriage.service.impl;
  2 +
  3 +import cn.hutool.core.collection.CollUtil;
  4 +import cn.hutool.core.util.ArrayUtil;
  5 +import cn.hutool.core.util.EnumUtil;
  6 +import cn.hutool.core.util.NumberUtil;
  7 +import cn.hutool.core.util.StrUtil;
  8 +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
  9 +import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
  10 +import com.baomidou.mybatisplus.core.toolkit.Wrappers;
  11 +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
  12 +import com.sl.ms.base.api.common.AreaFeign;
  13 +import com.sl.ms.carriage.domain.constant.CarriageConstant;
  14 +import com.sl.ms.carriage.domain.dto.CarriageDTO;
  15 +import com.sl.ms.carriage.domain.dto.WaybillDTO;
  16 +import com.sl.ms.carriage.domain.enums.EconomicRegionEnum;
  17 +import com.sl.ms.carriage.entity.CarriageEntity;
  18 +import com.sl.ms.carriage.enums.CarriageExceptionEnum;
  19 +import com.sl.ms.carriage.mapper.CarriageMapper;
  20 +import com.sl.ms.carriage.service.CarriageService;
  21 +import com.sl.ms.carriage.utils.CarriageUtils;
  22 +import com.sl.transport.common.exception.SLException;
  23 +import com.sl.transport.common.util.ObjectUtil;
  24 +import org.jetbrains.annotations.NotNull;
  25 +import org.springframework.stereotype.Service;
  26 +
  27 +import javax.annotation.Resource;
  28 +import java.math.BigDecimal;
  29 +import java.math.RoundingMode;
  30 +import java.util.Arrays;
  31 +import java.util.Collection;
  32 +import java.util.LinkedHashMap;
  33 +import java.util.List;
  34 +import java.util.stream.Collectors;
  35 +
  36 +@Service
  37 +public class CarriageServiceImpl extends ServiceImpl<CarriageMapper, CarriageEntity> implements CarriageService {
  38 +
  39 + @Resource
  40 + private AreaFeign areaFeign;
  41 +
  42 + private CarriageEntity carriageEntity;
  43 +
  44 + /**
  45 + * 新增/修改运费模板
  46 + *
  47 + * @param carriageDto 新增/修改运费对象
  48 + * 必填字段:templateType、transportType
  49 + * 更新时传入id字段
  50 + */
  51 + @Override
  52 + public CarriageDTO saveOrUpdate(CarriageDTO carriageDto) {
  53 +// 校验运费模板是否存在,如果不存在直接插入(查询条件:模板类型 运输类型 如果是修改排除当前id)
  54 + LambdaQueryWrapper<CarriageEntity> queryWrapper = Wrappers.lambdaQuery();
  55 + queryWrapper.eq(CarriageEntity::getTemplateType, carriageDto.getTemplateType());
  56 + queryWrapper.eq(CarriageEntity::getTransportType, carriageDto.getTransportType());
  57 + queryWrapper.ne(ObjectUtils.isNotEmpty(carriageDto.getId()), CarriageEntity::getId, carriageDto.getId());
  58 +
  59 + List<CarriageEntity> carriageEntityList = super.list(queryWrapper);
  60 +
  61 +// 如果没有重复的模板,可以直接插入或更新操作(DTo转entity 保存成功 entity转DTO)
  62 + if (CollUtil.isEmpty(carriageEntityList)) {
  63 + return saveOrUpdateCarriage(carriageDto);
  64 + }
  65 +
  66 +// 如果存在重复模板,需要判断此次插入的是否为经济区互寄,非经济区互寄不可以重复
  67 + if (ObjectUtil.notEqual(carriageDto.getTemplateType(), CarriageConstant.ECONOMIC_ZONE)) {
  68 + throw new SLException(CarriageExceptionEnum.NOT_ECONOMIC_ZONE_REPEAT);
  69 + }
  70 +
  71 +// 如果是京经济区互寄类型,需要进一步判断关联城市是否重复,通过集合取交集判断是否重复
  72 + List<String> associatedCityList = carriageEntityList.stream().map(CarriageEntity::getAssociatedCity)
  73 + .map(associatedCity -> StrUtil.splitToArray(associatedCity, ","))
  74 + .flatMap(Arrays::stream)
  75 + .collect(Collectors.toList());
  76 + Collection<String> intersection = CollUtil.intersection(associatedCityList, carriageDto.getAssociatedCityList());
  77 + if (CollUtil.isNotEmpty(intersection)) {
  78 + throw new SLException(CarriageExceptionEnum.ECONOMIC_ZONE_CITY_REPEAT);
  79 + }
  80 +
  81 +// 如果没有重复,可以新增或更新(DTO转Entity 保存成功 entity转DTO)
  82 + return saveOrUpdateCarriage(carriageDto);
  83 + }
  84 +
  85 + private CarriageDTO saveOrUpdateCarriage(CarriageDTO carriageDto) {
  86 + CarriageEntity carriageEntity = CarriageUtils.toEntity(carriageDto);
  87 + super.saveOrUpdate(carriageEntity);
  88 + return CarriageUtils.toDTO(carriageEntity);
  89 + }
  90 +
  91 + /**
  92 + * 获取全部运费模板
  93 + *
  94 + * @return 运费模板对象列表
  95 + */
  96 + @Override
  97 + public List<CarriageDTO> findAll() {
  98 +// 构造查询条件,按创建时间倒叙
  99 + LambdaQueryWrapper<CarriageEntity> queryWrapper = Wrappers.lambdaQuery();
  100 + queryWrapper.orderByDesc(CarriageEntity::getCreated);
  101 +
  102 +// 查询数据库
  103 + List<CarriageEntity> list = super.list(queryWrapper);
  104 +
  105 +// 将结果转换为DTO类型,使用CarriageUtils工具类
  106 + return list.stream().map(CarriageUtils::toDTO).collect(Collectors.toList());
  107 + }
  108 +
  109 + /**
  110 + * 运费计算
  111 + *
  112 + * @param waybillDTO 运费计算对象
  113 + * @return 运费模板对象,不仅包含模板数据还包含:computeWeight、expense 字段
  114 + */
  115 + @Override
  116 + public CarriageDTO compute(WaybillDTO waybillDTO) {
  117 +// 根据参数查找运费模板 调用findCarriage方法
  118 + CarriageEntity carriage = findCarriage(waybillDTO);
  119 +
  120 +// 计算重量,最小重量为1KG,调用getComputedWeight方法
  121 + double computeWeight = getComputeWeight(waybillDTO, carriage);
  122 +
  123 +// 计算运费 运费=首重价格 + (实际重量 - 1) * 续重加格
  124 + double price = carriage.getFirstWeight() + (computeWeight - 1) * carriage.getContinuousWeight();
  125 +
  126 +// 结果保留一位小数
  127 + BigDecimal expense = NumberUtil.round(price, 1);
  128 +
  129 +// 封装运费和计算重量到CarriageDTO,并返回
  130 + CarriageDTO carriageDTO = CarriageUtils.toDTO(carriage);
  131 + carriageDTO.setExpense(expense.doubleValue());
  132 + carriageDTO.setComputeWeight(computeWeight);
  133 +
  134 + return carriageDTO;
  135 + }
  136 +
  137 + /**
  138 + * 计算重量
  139 + * @param waybillDTO
  140 + * @param carriage
  141 + * @return
  142 + */
  143 + private double getComputeWeight(WaybillDTO waybillDTO, CarriageEntity carriage) {
  144 +// 计算体积,如果传入体积则不需要计算
  145 + Integer volume = waybillDTO.getVolume();
  146 + if (ObjectUtil.isEmpty(volume)) {
  147 + try {
  148 + volume = waybillDTO.getMeasureHigh() * waybillDTO.getMeasureLong() * waybillDTO.getMeasureWidth();
  149 + } catch (Exception e) {
  150 + volume = 0;
  151 + }
  152 + }
  153 +
  154 +// 计算体积重量 = 体积 / 轻抛系数 tips:使用NumberUtil工具类计算 保留一位小数
  155 + BigDecimal volumeWeight = NumberUtil.div(volume, carriage.getLightThrowingCoefficient(), 1);
  156 +
  157 +// 重量取最大值 = 体积重量和实际重量 tips:使用NumberUtil工具类计算 保留一位小数
  158 + double computeWeight = NumberUtil.max(volumeWeight, NumberUtil.round(waybillDTO.getWeight(), 1)).doubleValue();
  159 +
  160 +// 计算续重,规则:不满1kg,按1kg计费
  161 + if (computeWeight <= 1) {
  162 + return 1;
  163 + }
  164 +
  165 +// 10KG一下续重以0.1kg计量保留1位小数
  166 + if (computeWeight <= 10) {
  167 + return computeWeight;
  168 + }
  169 +
  170 +// 100KG 以上四舍五入取整,举例108.4kg按照108收费 108.5kg 按照109KG收费
  171 +// tips:使用NumberUtil工具类计算
  172 + if (computeWeight >= 100) {
  173 + return NumberUtil.round(computeWeight, 0).doubleValue();
  174 + }
  175 +
  176 +// 10-100kg续重以0.5kg计量保留1位小数
  177 + int intValue = NumberUtil.round(computeWeight, 0, RoundingMode.DOWN).intValue();
  178 +
  179 +// 0.5为一个计量单位,举例:18.8kg按照19收费,18.4kg按照18.5收费,18.1kg按照18.5kg收费
  180 + double sub = NumberUtil.sub(computeWeight, intValue);
  181 + if (sub == 0) {
  182 + return intValue;
  183 + }
  184 + if (sub < 0.5) {
  185 + return NumberUtil.add(intValue, 0.5);
  186 + }
  187 + return NumberUtil.add(intValue, 0.5);
  188 + }
  189 +
  190 + /**
  191 + * 根据参数查找运费模板
  192 + * @param waybillDTO
  193 + * @return
  194 + */
  195 + private CarriageEntity findCarriage(WaybillDTO waybillDTO) {
  196 + Long senderCityId = waybillDTO.getSenderCityId();
  197 + Long receiverCityId = waybillDTO.getReceiverCityId();
  198 +
  199 +// 如果 发件的城市id 和 收件的城市id相同,查询同城模板 调用findByTemplateType方法
  200 + if (ObjectUtil.equal(senderCityId, receiverCityId)) {
  201 + CarriageEntity carriageEntity = findByTemplateType(CarriageConstant.SAME_CITY);
  202 + if (ObjectUtil.isNotEmpty(carriageEntity)) {
  203 + return carriageEntity;
  204 + }
  205 + }
  206 +
  207 +// 如果没有查到或不是同城,则获取收寄地址同省id,使用AreaFeign结构查询
  208 + Long senderProvinceId = areaFeign.get(senderCityId).getParentId();
  209 + Long receiverProvinceId = areaFeign.get(receiverCityId).getParentId();
  210 +
  211 +// 如果 收发件的省份id相同,查询同省的模板,调用findByTemplate方法
  212 + if (ObjectUtil.equal(senderProvinceId, receiverProvinceId)) {
  213 + CarriageEntity carriageEntity = findByTemplateType(CarriageConstant.SAME_PROVINCE);
  214 + if (ObjectUtil.isNotEmpty(carriageEntity)) {
  215 + return carriageEntity;
  216 + }
  217 + }
  218 +
  219 +// 如果没有查到或不是同省,则查询是否为经济区互寄 调用findEconomicCarriage方法查询
  220 + CarriageEntity carriageEntity = findEconomicCarriage(senderProvinceId, receiverProvinceId);
  221 + if (ObjectUtil.isNotEmpty(carriageEntity)) {
  222 + return carriageEntity;
  223 + }
  224 +
  225 +// 如果没有查到或不是经济区互寄,直接查跨省运费模板
  226 + carriageEntity = findByTemplateType(CarriageConstant.TRANS_PROVINCE);
  227 + if (ObjectUtil.isNotEmpty(carriageEntity)) {
  228 + return carriageEntity;
  229 + }
  230 +
  231 +// 如果最后没有查到,直接抛自定义异常,提示模板未找到
  232 + throw new SLException(CarriageExceptionEnum.NOT_FOUND);
  233 + }
  234 +
  235 + /**
  236 + * 查询是否为经济区互寄
  237 + * @param senderProvinceId
  238 + * @param receiverProvinceId
  239 + * @return
  240 + */
  241 + private CarriageEntity findEconomicCarriage(Long senderProvinceId, Long receiverProvinceId) {
  242 +// 通过工具类EnumUtil 获取经济区城市配置枚举
  243 + LinkedHashMap<String, EconomicRegionEnum> enumMap = EnumUtil.getEnumMap(EconomicRegionEnum.class);
  244 +
  245 +// 遍历所有经济区枚举值
  246 + EconomicRegionEnum economicRegionEnum = null;
  247 + for (EconomicRegionEnum regionEnum : enumMap.values()) {
  248 +// 通过ArrayUtil工具类 判断发件网点 和 收件网点是否在同一经济区
  249 + boolean containsAll = ArrayUtil.containsAll(regionEnum.getValue(), receiverProvinceId, senderProvinceId);
  250 +
  251 +// 如果在得到对应经济区枚举
  252 + if (containsAll) {
  253 + economicRegionEnum = regionEnum;
  254 + break;
  255 + }
  256 + }
  257 +// 循环遍历未发现所属经济区,方法直接返回null
  258 + if (ObjectUtil.isNull(economicRegionEnum)) {
  259 + return null;
  260 + }
  261 +
  262 +// 如果有经济区 根据 模板类型=经济区,运输类型=普快 关联城市=枚举的code值 查询
  263 + LambdaQueryWrapper<CarriageEntity> queryWrapper = Wrappers.lambdaQuery();
  264 + queryWrapper.eq(CarriageEntity::getTemplateType, CarriageConstant.ECONOMIC_ZONE);
  265 + queryWrapper.eq(CarriageEntity::getTransportType, economicRegionEnum.getCode());
  266 + return super.getOne(queryWrapper);
  267 + }
  268 +
  269 +
  270 + /**
  271 + * 根据模板类型查询模板,经济区互寄不通过该方法查询模板
  272 + *
  273 + * @param templateType 模板类型:1-同城寄,2-省内寄,4-跨省
  274 + * @return 运费模板
  275 + */
  276 + @Override
  277 + public CarriageEntity findByTemplateType(Integer templateType) {
  278 +// 根据模板类型,及运输类型 = CarriageConst.REGULAR_FAST查询模板
  279 + LambdaQueryWrapper<CarriageEntity> queryWrapper = Wrappers.lambdaQuery();
  280 + queryWrapper.eq(CarriageEntity::getTemplateType, templateType);
  281 + queryWrapper.eq(CarriageEntity::getTransportType, CarriageConstant.REGULAR_FAST);
  282 + return super.getOne(queryWrapper);
  283 + }
  284 +}
... ...
sl-express-ms-carriage-service/src/test/java/com/sl/ms/carriage/service/impl/CarriageServiceImplTest.java 0 → 100644
  1 +package com.sl.ms.carriage.service.impl;
  2 +
  3 +import com.sl.ms.carriage.domain.dto.CarriageDTO;
  4 +import com.sl.ms.carriage.domain.dto.WaybillDTO;
  5 +import com.sl.ms.carriage.service.CarriageService;
  6 +import org.junit.jupiter.api.Test;
  7 +import org.springframework.boot.test.context.SpringBootTest;
  8 +
  9 +import javax.annotation.Resource;
  10 +
  11 +import java.util.List;
  12 +
  13 +import static org.junit.jupiter.api.Assertions.*;
  14 +
  15 +@SpringBootTest
  16 +class CarriageServiceImplTest {
  17 +
  18 + @Resource
  19 + private CarriageService carriageService;
  20 +
  21 + @Test
  22 + void saveOrUpdate() {
  23 + }
  24 +
  25 + @Test
  26 + void findAll() {
  27 + List<CarriageDTO> all = carriageService.findAll();
  28 + all.forEach(System.out::println);
  29 + }
  30 +
  31 + @Test
  32 + void compute() {
  33 + WaybillDTO waybillDTO = new WaybillDTO();
  34 + waybillDTO.setReceiverCityId(161793L); //上海
  35 + waybillDTO.setSenderCityId(2L); //北京
  36 + waybillDTO.setWeight(0.8); //重量
  37 + waybillDTO.setVolume(1); //体积
  38 +
  39 + CarriageDTO compute = this.carriageService.compute(waybillDTO);
  40 + System.out.println(compute);
  41 + }
  42 +
  43 + @Test
  44 + void findByTemplateType() {
  45 + }
  46 +}
0 47 \ No newline at end of file
... ...