配送页面字段内容调整

This commit is contained in:
2026-03-19 15:37:39 +08:00
parent d322fe830e
commit 8f1ab4e9c6
13 changed files with 548 additions and 237 deletions

View File

@@ -102,7 +102,7 @@ public class VehicleTypeController extends BaseController
*/
@PreAuthorize("@ss.hasPermi('document:type:remove')")
@Log(title = "车型定义", businessType = BusinessType.DELETE)
@DeleteMapping("/{ids}")
@DeleteMapping("/{ids:\\d+}")
public AjaxResult remove(@PathVariable Long[] ids)
{
return toAjax(vehicleTypeService.deleteVehicleTypeByIds(ids));

View File

@@ -152,7 +152,7 @@ public class DeliveryOrder extends BaseEntity {
private String orderStatus;
/** 车辆类型ID */
private Long vehicleTypeId;
private String vehicleTypeId;
/** 车辆类型名称 */
@Excel(name = "车辆类型名称")
@@ -312,8 +312,8 @@ public class DeliveryOrder extends BaseEntity {
public String getOrderStatus() { return orderStatus; }
public void setOrderStatus(String orderStatus) { this.orderStatus = orderStatus; }
public Long getVehicleTypeId() { return vehicleTypeId; }
public void setVehicleTypeId(Long vehicleTypeId) { this.vehicleTypeId = vehicleTypeId; }
public String getVehicleTypeId() { return vehicleTypeId; }
public void setVehicleTypeId(String vehicleTypeId) { this.vehicleTypeId = vehicleTypeId; }
public String getVehicleTypeName() { return vehicleTypeName; }
public void setVehicleTypeName(String vehicleTypeName) { this.vehicleTypeName = vehicleTypeName; }

View File

@@ -26,5 +26,6 @@ public class CalcSuggestFeeDTO {
private BigDecimal distanceKm;
/** 指定车型ID可选传入则按该车型计算建议费用 */
private Long vehicleTypeId;
private String vehicleTypeId;
}

View File

@@ -83,7 +83,7 @@ public class DeliveryOrderCreateDTO {
private String orderStatus;
/** 车型 ID */
private Long vehicleTypeId;
private String vehicleTypeId;
/** 车型名称 */
private String vehicleTypeName;

View File

@@ -1,4 +1,3 @@
// src/main/java/com/delivery/project/document/domain/vo/CalcSuggestFeeVO.java
package com.delivery.project.document.domain.vo;
import lombok.Data;
@@ -6,58 +5,93 @@ import java.math.BigDecimal;
import java.util.List;
/**
* 计算建议费用 - 响应体 VO
* 返回推荐/选定的车型、单价、建议费用,以及候选车型列表。
* 计算建议费用 返回VO
*/
@Data
public class CalcSuggestFeeVO {
/** 选定/推荐车型ID */
private Long vehicleTypeId;
/** 选定/推荐车型名称 */
private String vehicleTypeName;
/** 每公里单价(单位:元/公里) */
private BigDecimal unitPricePerKm;
/** 建议费用单位四舍五入保留2位小数 */
private BigDecimal suggestFee;
/** 错误信息 */
private String errorMessage;
/** 是否有适配车型 */
/** 是否存在可用车型 */
private Boolean hasSuitableType;
/** 候选车型列表(按价格/容量排序 */
/** 错误信息(无车型时返回 */
private String errorMessage;
/** 推荐总费用 */
private BigDecimal suggestFee;
/** 总车辆数 */
private Integer totalVehicleCount;
/** 货物重量 */
private BigDecimal totalWeightTon;
/** 货物体积 */
private BigDecimal totalVolumeM3;
/** 推荐车辆组合方案 */
private List<VehiclePlanVO> bestPlan;
/** 候选车型列表 */
private List<VehicleTypeOptionVO> candidates;
/** 提示信息(不阻断) */
private String warningMessage;
/**
* 候选车型项 VO
* 表示一条可选车型的基本信息。
* 推荐车辆组合
*/
@Data
public static class VehiclePlanVO {
/** 车型ID */
private String vehicleTypeId;
/** 车型名称 */
private String vehicleTypeName;
/** 车辆数量 */
private Integer vehicleCount;
/** 每公里单价 */
private BigDecimal unitPricePerKm;
/** 单车费用 */
private BigDecimal singleVehicleFee;
/** 该车型总费用 */
private BigDecimal totalFee;
/** 单车最大承重 */
private BigDecimal weightMaxTon;
/** 单车最大体积 */
private BigDecimal volumeMaxM3;
}
/**
* 候选车型
*/
@Data
public static class VehicleTypeOptionVO {
/** 车型ID */
private Long id;
private String id;
/** 车型名称 */
private String name;
/** 每公里单价(单位:元/公里) */
/** 每公里单价 */
private BigDecimal unitPricePerKm;
/** 承重下限(单位:吨,含) */
/** 最小承重 */
private BigDecimal weightMinTon;
/** 承重上限(单位:吨,含) */
/** 最大承重 */
private BigDecimal weightMaxTon;
/** 载方下限(单位:立方米,含) */
/** 最小体积 */
private BigDecimal volumeMinM3;
/** 载方上限(单位:吨,含) */
/** 最大体积 */
private BigDecimal volumeMaxM3;
}
}

View File

@@ -86,7 +86,7 @@ public class DeliveryOrderVo {
private String orderStatus;
/** 车型 ID */
private Long vehicleTypeId;
private String vehicleTypeId;
/** 车型名称 */
private String vehicleTypeName;

View File

@@ -113,4 +113,6 @@ public interface DeliveryOrderMapper
/** 配送单VO列表 */
List<DeliveryOrderVo> selectDeliveryOrderVoList(DeliveryOrder deliveryOrder);
/** 防止重复创建配送单 */
List<Long> selectExistsRkRecordIds(@Param("rkRecordIds") List<Long> rkRecordIds);
}

View File

@@ -77,4 +77,8 @@ public interface VehicleTypeMapper
* @return 批量结果
*/
List<VehicleType> selectFallbackTypes(BigDecimal w, BigDecimal v);
/**
* 1 查询所有可用车型
* */
List<VehicleType> selectUsableTypes();
}

View File

@@ -67,4 +67,6 @@ public interface IVehicleTypeService
* @return
*/
CalcSuggestFeeVO calcSuggestFee(CalcSuggestFeeDTO dto);
}

View File

@@ -186,18 +186,38 @@ public class DeliveryOrderServiceImpl implements IDeliveryOrderService
throw new ServiceException("物料明细不能为空");
}
String billNo = dto.getItems().get(0).getBillNo();
boolean fromWms = StringUtils.isNotBlank(billNo);
List<DeliveryOrderLineDTO> items = dto.getItems();
List<Long> rkRecordIds = dto.getItems().stream()
boolean fromWms = items.stream().anyMatch(it -> StringUtils.isNotBlank(it.getBillNo()));
List<Long> rkRecordIds = items.stream()
.map(DeliveryOrderLineDTO::getRkRecordId)
.filter(Objects::nonNull)
.distinct()
.collect(Collectors.toList());
if (fromWms && rkRecordIds.isEmpty()) {
if (fromWms) {
for (DeliveryOrderLineDTO it : items) {
if (StringUtils.isBlank(it.getBillNo())) {
throw new ServiceException("WMS来源明细缺少 billNo");
}
if (it.getRkRecordId() == null) {
throw new ServiceException("WMS来源明细缺少 rk_record_id");
}
}
if (rkRecordIds.isEmpty()) {
throw new ServiceException("明细行缺少 rk_record 主键 id");
}
}
// ===== 新增:防止重复创建配送单 =====
if (!rkRecordIds.isEmpty()) {
List<Long> existIds = deliveryOrderMapper.selectExistsRkRecordIds(rkRecordIds);
if (existIds != null && !existIds.isEmpty()) {
throw new ServiceException("存在已生成配送单的明细不能重复创建明细ID" + existIds);
}
}
String orderNo = StringUtils.isBlank(dto.getOrderNo())
? "DO" + DateUtils.dateTimeNow("yyyyMMddHHmmssSSS")
@@ -208,19 +228,9 @@ public class DeliveryOrderServiceImpl implements IDeliveryOrderService
Long currentUserId = SecurityUtils.getUserId();
Long makerId = dto.getMakerId() != null ? dto.getMakerId() : currentUserId;
List<DeliveryOrder> rows = new ArrayList<>();
for (DeliveryOrderLineDTO it : dto.getItems()) {
if (fromWms) {
if (!billNo.equals(it.getBillNo())) {
throw new ServiceException("暂不支持多单合并配送");
}
if (it.getRkRecordId() == null) {
throw new ServiceException("明细缺少 rk_record_id");
}
}
List<DeliveryOrder> rows = new ArrayList<>(items.size());
for (DeliveryOrderLineDTO it : items) {
DeliveryOrder row = new DeliveryOrder();
row.setOrderNo(orderNo);
@@ -244,7 +254,6 @@ public class DeliveryOrderServiceImpl implements IDeliveryOrderService
row.setMakerId(makerId);
row.setReceiveStatus(dto.getReceiveStatus());
row.setReceiveProblem(dto.getReceiveProblem());
row.setOrderStatus(StringUtils.defaultIfBlank(dto.getOrderStatus(), "1"));
row.setVehicleTypeId(dto.getVehicleTypeId());
@@ -255,10 +264,8 @@ public class DeliveryOrderServiceImpl implements IDeliveryOrderService
row.setTotalKm(dto.getTotalKm());
row.setRemark(dto.getRemark());
// 🔥 关键改造点
row.setRkRecordId(it.getRkRecordId());
row.setBillNo(it.getBillNo());
row.setXmMs(it.getXmMs());
row.setXmNo(it.getXmNo());
row.setWlNo(it.getWlNo());
@@ -276,7 +283,10 @@ public class DeliveryOrderServiceImpl implements IDeliveryOrderService
}
if (!rows.isEmpty()) {
deliveryOrderMapper.batchInsert(rows);
int insertRows = deliveryOrderMapper.batchInsert(rows);
if (insertRows <= 0) {
throw new ServiceException("配送单保存失败");
}
}
if (fromWms) {

View File

@@ -1,216 +1,307 @@
package com.delivery.project.document.service.impl;
import com.delivery.common.exception.ServiceException;
import com.delivery.common.utils.StringUtils;
import com.delivery.project.document.domain.VehicleType;
import com.delivery.project.document.domain.dto.CalcSuggestFeeDTO;
import com.delivery.project.document.domain.vo.CalcSuggestFeeVO;
import com.delivery.project.document.mapper.VehicleTypeMapper;
import com.delivery.project.document.service.IVehicleTypeService;
import lombok.Data;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import com.delivery.common.utils.DateUtils;
import com.delivery.project.document.domain.dto.CalcSuggestFeeDTO;
import com.delivery.project.document.domain.vo.CalcSuggestFeeVO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.delivery.project.document.mapper.VehicleTypeMapper;
import com.delivery.project.document.domain.VehicleType;
import com.delivery.project.document.service.IVehicleTypeService;
import java.util.*;
import java.util.stream.Collectors;
/**
* 车型定义Service业务层处理
*
* @author delivery
* @date 2025-10-23
*/
@Service
public class VehicleTypeServiceImpl implements IVehicleTypeService
{
public class VehicleTypeServiceImpl implements IVehicleTypeService {
@Autowired
private VehicleTypeMapper vehicleTypeMapper;
/**
* 查询车型定义
*
* @param id 车型定义主键
* @return 车型定义
*/
@Override
public VehicleType selectVehicleTypeById(Long id)
{
public VehicleType selectVehicleTypeById(Long id) {
return vehicleTypeMapper.selectVehicleTypeById(id);
}
/**
* 查询车型定义列表
*
* @param vehicleType 车型定义
* @return 车型定义
*/
@Override
public List<VehicleType> selectVehicleTypeList(VehicleType vehicleType)
{
public List<VehicleType> selectVehicleTypeList(VehicleType vehicleType) {
return vehicleTypeMapper.selectVehicleTypeList(vehicleType);
}
/**
* 新增车型定义
*
* @param vehicleType 车型定义
* @return 结果
*/
@Override
public int insertVehicleType(VehicleType vehicleType)
{
vehicleType.setCreateTime(DateUtils.getNowDate());
public int insertVehicleType(VehicleType vehicleType) {
return vehicleTypeMapper.insertVehicleType(vehicleType);
}
/**
* 修改车型定义
*
* @param vehicleType 车型定义
* @return 结果
*/
@Override
public int updateVehicleType(VehicleType vehicleType)
{
vehicleType.setUpdateTime(DateUtils.getNowDate());
public int updateVehicleType(VehicleType vehicleType) {
return vehicleTypeMapper.updateVehicleType(vehicleType);
}
/**
* 批量删除车型定义
*
* @param ids 需要删除的车型定义主键
* @return 结果
*/
@Override
public int deleteVehicleTypeByIds(Long[] ids)
{
public int deleteVehicleTypeByIds(Long[] ids) {
return vehicleTypeMapper.deleteVehicleTypeByIds(ids);
}
/**
* 删除车型定义信息
*
* @param id 车型定义主键
* @return 结果
*/
@Override
public int deleteVehicleTypeById(Long id)
{
public int deleteVehicleTypeById(Long id) {
return vehicleTypeMapper.deleteVehicleTypeById(id);
}
/**
* 计算建议运费,推荐车型
* @param dto
* @return
* 计算建议费用
*/
@Override
public CalcSuggestFeeVO calcSuggestFee(CalcSuggestFeeDTO dto)
{
// ========== 0) 入参兜底 ==========
BigDecimal w = nvl(dto.getWeightTon()); // 货物重量(吨)
BigDecimal v = nvl(dto.getVolumeM3()); // 货物体积(立方米)
BigDecimal km = nvl(dto.getDistanceKm()); // 行程公里数(公里)
public CalcSuggestFeeVO calcSuggestFee(CalcSuggestFeeDTO dto) {
// ========== 1) 候选车型:严格匹配,若无则兜底匹配 ==========
List<VehicleType> candidates = vehicleTypeMapper.selectMatchTypes(w, v);
if (candidates == null || candidates.isEmpty()) {
candidates = vehicleTypeMapper.selectFallbackTypes(w, v);
BigDecimal weight = nvl(dto.getWeightTon());
BigDecimal volume = nvl(dto.getVolumeM3());
BigDecimal km = nvl(dto.getDistanceKm());
if (km.compareTo(BigDecimal.ZERO) <= 0) {
throw new ServiceException("公里数必须大于0");
}
// ========== 2) 检查是否存在适配车型 ==========
if (candidates == null || candidates.isEmpty()) {
// 如果没有适配车型,检查系统中是否至少存在一个车型配置
List<VehicleType> allVehicleTypes = vehicleTypeMapper.selectVehicleTypeList(new VehicleType());
if (allVehicleTypes == null || allVehicleTypes.isEmpty()) {
List<VehicleType> vehicleTypes = vehicleTypeMapper.selectUsableTypes();
if (vehicleTypes == null || vehicleTypes.isEmpty()) {
throw new ServiceException("没有配置车型");
}
// 构建算法节点
List<VehicleNode> nodes = vehicleTypes.stream()
.map(v -> buildNode(v, km))
.filter(Objects::nonNull)
.collect(Collectors.toList());
// 按费用排序
nodes.sort(Comparator.comparing(VehicleNode::getSingleFee));
SearchContext ctx = new SearchContext(weight, volume);
dfs(nodes,
0,
BigDecimal.ZERO,
BigDecimal.ZERO,
BigDecimal.ZERO,
0,
new ArrayList<>(),
ctx);
CalcSuggestFeeVO calcSuggestFeeVO = buildResult(ctx,dto);
return calcSuggestFeeVO;
}
/**
* DFS搜索最佳组合最优费用 + 同价最少车)
*/
private void dfs(List<VehicleNode> nodes,
int index,
BigDecimal weight,
BigDecimal volume,
BigDecimal fee,
int vehicleCount,
List<VehiclePlan> plan,
SearchContext ctx) {
// 1. 剪枝:当前费用已经更差
if (ctx.bestFee != null && fee.compareTo(ctx.bestFee) > 0) {
return;
}
// 2. 满足需求
if (weight.compareTo(ctx.targetWeight) >= 0 &&
volume.compareTo(ctx.targetVolume) >= 0) {
if (ctx.bestFee == null
|| fee.compareTo(ctx.bestFee) < 0
|| (fee.compareTo(ctx.bestFee) == 0
&& vehicleCount < ctx.bestVehicleCount)) {
ctx.bestFee = fee;
ctx.bestVehicleCount = vehicleCount;
ctx.bestPlan = new ArrayList<>(plan);
}
return;
}
// 3. 递归结束
if (index >= nodes.size()) {
return;
}
VehicleNode node = nodes.get(index);
int max = calculateMaxCount(node, ctx.targetWeight, ctx.targetVolume);
// 4. 从多到少尝试
for (int i = max; i >= 0; i--) {
BigDecimal addWeight = node.weightMaxTon.multiply(BigDecimal.valueOf(i));
BigDecimal addVolume = node.volumeMaxM3.multiply(BigDecimal.valueOf(i));
BigDecimal addFee = node.singleFee.multiply(BigDecimal.valueOf(i));
List<VehiclePlan> newPlan = new ArrayList<>(plan);
if (i > 0) {
VehiclePlan p = new VehiclePlan();
p.vehicleTypeId = node.id;
p.vehicleTypeName = node.typeName;
p.count = i;
p.singleFee = node.singleFee;
p.totalFee = addFee;
p.weightMaxTon = node.weightMaxTon;
p.volumeMaxM3 = node.volumeMaxM3;
newPlan.add(p);
}
dfs(nodes,
index + 1,
weight.add(addWeight),
volume.add(addVolume),
fee.add(addFee),
vehicleCount + i,
newPlan,
ctx);
}
}
/**
* 计算最多车辆数
*/
private int calculateMaxCount(VehicleNode node,
BigDecimal weight,
BigDecimal volume) {
int w = 0;
int v = 0;
if (node.weightMaxTon.compareTo(BigDecimal.ZERO) > 0) {
w = weight.divide(node.weightMaxTon, 0, RoundingMode.CEILING).intValue();
}
if (node.volumeMaxM3.compareTo(BigDecimal.ZERO) > 0) {
v = volume.divide(node.volumeMaxM3, 0, RoundingMode.CEILING).intValue();
}
return Math.max(w, v) + 2;
}
/**
* 构建节点
*/
private VehicleNode buildNode(VehicleType v, BigDecimal km) {
if (!"0".equals(v.getStatus()) || "1".equals(v.getIsDelete())) {
return null;
}
if (v.getUnitPricePerKm() == null) {
return null;
}
VehicleNode node = new VehicleNode();
node.id = v.getId();
node.typeName = v.getTypeName();
node.unitPricePerKm = v.getUnitPricePerKm();
node.singleFee = v.getUnitPricePerKm()
.multiply(km)
.setScale(2, RoundingMode.HALF_UP);
node.weightMaxTon = nvl(v.getWeightMaxTon());
node.volumeMaxM3 = nvl(v.getVolumeMaxM3());
return node;
}
/**
* 构建返回结果
*/
private CalcSuggestFeeVO buildResult(SearchContext ctx,CalcSuggestFeeDTO dto) {
CalcSuggestFeeVO vo = new CalcSuggestFeeVO();
vo.setErrorMessage("系统中暂无车型配置,请先配置车型信息。");
if (ctx.bestPlan == null) {
vo.setHasSuitableType(false);
return vo;
}
// 找到最大承载能力的车型
VehicleType maxCapacityType = allVehicleTypes.stream()
.filter(type -> "0".equals(type.getStatus()) && !"1".equals(type.getIsDelete()))
.max(Comparator
.comparing(VehicleType::getWeightMaxTon, Comparator.nullsLast(BigDecimal::compareTo))
.thenComparing(VehicleType::getVolumeMaxM3, Comparator.nullsLast(BigDecimal::compareTo)))
.orElse(null);
vo.setHasSuitableType(true);
vo.setSuggestFee(ctx.bestFee);
vo.setTotalVehicleCount(ctx.bestVehicleCount);
if (maxCapacityType != null) {
// 检查最大容量车型是否仍无法满足要求
String errorMsg = buildNoMatchErrorMessage(w, v, maxCapacityType);
CalcSuggestFeeVO vo = new CalcSuggestFeeVO();
vo.setErrorMessage(errorMsg);
vo.setHasSuitableType(false);
// 仍然返回候选车型列表供参考
List<CalcSuggestFeeVO.VehicleTypeOptionVO> list = new ArrayList<>(allVehicleTypes.size());
List<CalcSuggestFeeVO.VehiclePlanVO> bestPlanList =
ctx.bestPlan.stream()
.map(p -> {
CalcSuggestFeeVO.VehiclePlanVO item =
new CalcSuggestFeeVO.VehiclePlanVO();
item.setVehicleTypeId(String.valueOf(p.vehicleTypeId));
item.setVehicleTypeName(p.vehicleTypeName);
item.setVehicleCount(p.count);
item.setSingleVehicleFee(p.singleFee);
item.setTotalFee(p.totalFee);
return item;
}).collect(Collectors.toList());
vo.setBestPlan(bestPlanList);
// ========== 1) 候选车型:严格匹配,若无则兜底匹配 ==========
// 候选车型:同时满足“体积 >= 推荐最大体积” 且 “承重 >= 推荐最大承重”;否则返回全部启用车型
// 2. 查询全部车型
List<VehicleType> allVehicleTypes = vehicleTypeMapper.selectVehicleTypeList(new VehicleType());
if (bestPlanList!=null&&bestPlanList.size()==1) {
// 1. 取推荐方案中的最大体积、最大承重
BigDecimal recommendMaxVolume = ctx.bestPlan.stream()
.map(p -> p.volumeMaxM3)
.filter(Objects::nonNull)
.max(BigDecimal::compareTo)
.orElse(BigDecimal.ZERO);
BigDecimal recommendMaxWeight = ctx.bestPlan.stream()
.map(p -> p.weightMaxTon)
.filter(Objects::nonNull)
.max(BigDecimal::compareTo)
.orElse(BigDecimal.ZERO);
// 3. 过滤启用且未删除的车型
List<VehicleType> enabledTypes = new ArrayList<>();
if (allVehicleTypes != null) {
for (VehicleType t : allVehicleTypes) {
if ("0".equals(t.getStatus()) && !"1".equals(t.getIsDelete())) {
CalcSuggestFeeVO.VehicleTypeOptionVO o = new CalcSuggestFeeVO.VehicleTypeOptionVO();
o.setId(t.getId());
o.setName(t.getTypeName());
o.setUnitPricePerKm(t.getUnitPricePerKm());
o.setWeightMinTon(t.getWeightMinTon());
o.setWeightMaxTon(t.getWeightMaxTon());
o.setVolumeMinM3(t.getVolumeMinM3());
o.setVolumeMaxM3(t.getVolumeMaxM3());
list.add(o);
enabledTypes.add(t);
}
}
vo.setCandidates(list);
return vo;
} else {
CalcSuggestFeeVO vo = new CalcSuggestFeeVO();
vo.setErrorMessage("未找到适配车型,请检查车型配置或输入参数。");
vo.setHasSuitableType(false);
return vo;
}
}
// 稳定排序:单价升序 -> 承重上限升序 -> 载方上限升序 -> id 升序(数据库已排序,这里再兜一层)
candidates.sort(Comparator
.comparing(VehicleType::getUnitPricePerKm, Comparator.nullsLast(BigDecimal::compareTo))
.thenComparing(VehicleType::getWeightMaxTon, Comparator.nullsLast(BigDecimal::compareTo))
.thenComparing(VehicleType::getVolumeMaxM3, Comparator.nullsLast(BigDecimal::compareTo))
.thenComparing(VehicleType::getId, Comparator.nullsLast(Long::compareTo)));
// ========== 3) 选定车型:前端指定 or 系统推荐 ==========
VehicleType chosen;
if (dto.getVehicleTypeId() != null) {
// 前端点名车型:优先在候选中找,找不到则直接按主键查
chosen = candidates.stream()
.filter(t -> dto.getVehicleTypeId().equals(t.getId()))
.findFirst()
.orElseGet(() -> vehicleTypeMapper.selectVehicleTypeById(dto.getVehicleTypeId()));
if (chosen == null) {
throw new RuntimeException("指定车型不存在或已被禁用id=" + dto.getVehicleTypeId());
// 4. 筛选满足条件的车型
List<VehicleType> candidates = new ArrayList<>();
for (VehicleType t : enabledTypes) {
if (t.getWeightMaxTon() != null
&& t.getVolumeMaxM3() != null
&& t.getWeightMaxTon().compareTo(recommendMaxWeight) >= 0
&& t.getVolumeMaxM3().compareTo(recommendMaxVolume) >= 0) {
candidates.add(t);
}
} else {
chosen = candidates.get(0);
}
// ========== 4) 计算建议费用:单价 × 公里数保留2位 ==========
BigDecimal price = chosen.getUnitPricePerKm();
if (price == null) {
throw new RuntimeException("车型【" + chosen.getTypeName() + "】未配置每公里单价unit_price_per_km 为空)");
// 5. 如果没有符合条件的,返回全部启用车型
if (candidates.isEmpty()) {
candidates = enabledTypes;
}
BigDecimal fee = price.multiply(km).setScale(2, RoundingMode.HALF_UP);
// ========== 5) 组装返回 ==========
CalcSuggestFeeVO vo = new CalcSuggestFeeVO();
vo.setVehicleTypeId(chosen.getId());
vo.setVehicleTypeName(chosen.getTypeName());
vo.setUnitPricePerKm(price);
vo.setSuggestFee(fee);
vo.setHasSuitableType(true); // 有适配车型
// 候选项
List<CalcSuggestFeeVO.VehicleTypeOptionVO> list = new ArrayList<>(candidates.size());
List<CalcSuggestFeeVO.VehicleTypeOptionVO> list = new ArrayList<>();
for (VehicleType t : candidates) {
CalcSuggestFeeVO.VehicleTypeOptionVO o = new CalcSuggestFeeVO.VehicleTypeOptionVO();
o.setId(t.getId());
o.setId(String.valueOf(t.getId()));
o.setName(t.getTypeName());
o.setUnitPricePerKm(t.getUnitPricePerKm());
o.setWeightMinTon(t.getWeightMinTon());
@@ -220,9 +311,163 @@ public class VehicleTypeServiceImpl implements IVehicleTypeService
list.add(o);
}
vo.setCandidates(list);
}else {
List<CalcSuggestFeeVO.VehicleTypeOptionVO> list = new ArrayList<>();
for (VehicleType t : allVehicleTypes) {
CalcSuggestFeeVO.VehicleTypeOptionVO o = new CalcSuggestFeeVO.VehicleTypeOptionVO();
o.setId(String.valueOf(t.getId()));
o.setName(t.getTypeName());
o.setUnitPricePerKm(t.getUnitPricePerKm());
o.setWeightMinTon(t.getWeightMinTon());
o.setWeightMaxTon(t.getWeightMaxTon());
o.setVolumeMinM3(t.getVolumeMinM3());
o.setVolumeMaxM3(t.getVolumeMaxM3());
list.add(o);
}
vo.setCandidates(list);
}
// ========== 3) 选定车型:前端指定 or 系统推荐 ==========
List<VehicleType> chosenList;
if (StringUtils.isNotBlank(dto.getVehicleTypeId())) {
List<Long> idList = Arrays.stream(dto.getVehicleTypeId().split(","))
.map(String::trim)
.filter(s -> !s.isEmpty())
.map(Long::valueOf)
.collect(Collectors.toList());
chosenList = allVehicleTypes.stream()
.filter(t -> idList.contains(t.getId()))
.collect(Collectors.toList());
// ========== 先判断:前端指定车型组合能否装下 ==========
BigDecimal totalWeightCapacity = BigDecimal.ZERO;
BigDecimal totalVolumeCapacity = BigDecimal.ZERO;
for (VehicleType t : chosenList) {
totalWeightCapacity = totalWeightCapacity.add(
t.getWeightMaxTon() == null ? BigDecimal.ZERO : t.getWeightMaxTon()
);
totalVolumeCapacity = totalVolumeCapacity.add(
t.getVolumeMaxM3() == null ? BigDecimal.ZERO : t.getVolumeMaxM3()
);
}
boolean weightEnough = totalWeightCapacity.compareTo(dto.getWeightTon()) >= 0;
boolean volumeEnough = totalVolumeCapacity.compareTo(dto.getVolumeM3()) >= 0;
if (!weightEnough || !volumeEnough) {
StringBuilder sb = new StringBuilder("所选车型可能无法装下当前货物");
sb.append(",总承重=").append(totalWeightCapacity).append("");
sb.append(",总载方=").append(totalVolumeCapacity).append("");
sb.append(",货物重量=").append(dto.getWeightTon()).append("");
sb.append(",货物体积=").append(dto.getVolumeM3()).append("");
vo.setWarningMessage(sb.toString());
}
// ========== 4) 多车型计算建议费用 ==========
BigDecimal totalFee = BigDecimal.ZERO;
for (VehicleType t : chosenList) {
if (t.getUnitPricePerKm() == null) {
throw new ServiceException("车型【" + t.getTypeName() + "】未配置单价");
}
// 单车费用
BigDecimal singleFee = t.getUnitPricePerKm()
.multiply(dto.getDistanceKm())
.setScale(2, RoundingMode.HALF_UP);
// 这里默认每个指定车型 1 辆
int count = 1;
BigDecimal subTotal = singleFee.multiply(BigDecimal.valueOf(count));
totalFee = totalFee.add(subTotal);
}
vo.setSuggestFee(totalFee);
}
// // ========== 5) 组装返回 ==========
// CalcSuggestFeeVO vo = new CalcSuggestFeeVO();
// vo.setVehicleTypeId(chosen.getId());
// vo.setVehicleTypeName(chosen.getTypeName());
// vo.setUnitPricePerKm(price);
// vo.setHasSuitableType(true); // 有适配车型
return vo;
}
/**
* 空值转0
*/
private BigDecimal nvl(BigDecimal v) {
return v == null ? BigDecimal.ZERO : v;
}
/**
* 算法节点
*/
@Data
private static class VehicleNode {
Long id;
String typeName;
BigDecimal unitPricePerKm;
BigDecimal singleFee;
BigDecimal weightMaxTon;
BigDecimal volumeMaxM3;
}
/**
* 组合方案
*/
private static class VehiclePlan {
Long vehicleTypeId;
String vehicleTypeName;
int count;
BigDecimal singleFee;
BigDecimal totalFee;
BigDecimal weightMaxTon;
BigDecimal volumeMaxM3;
}
/**
* 搜索上下文
*/
private static class SearchContext {
BigDecimal targetWeight;
BigDecimal targetVolume;
BigDecimal bestFee;
int bestVehicleCount;
List<VehiclePlan> bestPlan;
SearchContext(BigDecimal w, BigDecimal v) {
targetWeight = w;
targetVolume = v;
}
}
/**
* 构建无匹配车型的错误信息
*/
@@ -249,9 +494,4 @@ public class VehicleTypeServiceImpl implements IVehicleTypeService
sb.append("最大承载车型为:").append(maxCapacityType.getTypeName());
return sb.toString();
}
private static BigDecimal nvl(BigDecimal x) {
return x == null ? BigDecimal.ZERO : x;
}
}

View File

@@ -553,7 +553,7 @@
<!-- 车型 -->
<if test="vehicleTypeId != null">
AND dor.vehicle_type_id = #{vehicleTypeId}
AND FIND_IN_SET(#{vehicleTypeId}, dor.vehicle_type_id)
</if>
<if test="vehicleTypeName != null and vehicleTypeName != ''">
AND dor.vehicle_type_name LIKE CONCAT('%', #{vehicleTypeName}, '%')
@@ -945,6 +945,7 @@
<!-- 车辆类型 -->
<if test="q.vehicleTypeId != null">
AND FIND_IN_SET(#{q.vehicleTypeId}, dor.vehicle_type_id)
AND dor.vehicle_type_id = #{q.vehicleTypeId}
</if>
@@ -1054,4 +1055,16 @@
GROUP BY DATE_FORMAT(dor.delivery_date, '%Y-%m')
ORDER BY statMonth ASC
</select>
<select id="selectExistsRkRecordIds" resultType="java.lang.Long">
SELECT DISTINCT rk_record_id
FROM delivery_order
WHERE is_delete = '0'
AND rk_record_id IS NOT NULL
AND order_status IN ('1','2','3')
AND rk_record_id IN
<foreach collection="rkRecordIds" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</select>
</mapper>

View File

@@ -140,5 +140,10 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
and volume_max_m3 &gt;= #{v}
order by unit_price_per_km asc, weight_max_ton asc, volume_max_m3 asc, id asc
</select>
<select id="selectUsableTypes" resultMap="VehicleTypeResult">
SELECT *
FROM vehicle_type
WHERE status = '0'
AND is_delete = '0'
</select>
</mapper>