配送系统功能开发1205

This commit is contained in:
2025-12-05 16:03:29 +08:00
parent aa53f26d63
commit f1e7868ae1
22 changed files with 956 additions and 189 deletions

51
pom.xml
View File

@@ -34,12 +34,21 @@
<poi.version>4.1.2</poi.version> <poi.version>4.1.2</poi.version>
<oshi.version>6.8.3</oshi.version> <oshi.version>6.8.3</oshi.version>
<velocity.version>2.3</velocity.version> <velocity.version>2.3</velocity.version>
<!-- override dependency version --> <!-- override dependency version -->
<tomcat.version>9.0.108</tomcat.version> <tomcat.version>9.0.108</tomcat.version>
<logback.version>1.2.13</logback.version> <logback.version>1.2.13</logback.version>
<spring-security.version>5.7.14</spring-security.version> <spring-security.version>5.7.14</spring-security.version>
<spring-framework.version>5.3.39</spring-framework.version> <spring-framework.version>5.3.39</spring-framework.version>
<minio.version>7.1.4</minio.version> <minio.version>7.1.4</minio.version>
<!-- ======= 阿里云 OCR SDK 统一版本管理 ======= -->
<aliyun.ocr.version>3.1.0</aliyun.ocr.version>
<!-- 确保编译使用UTF-8编码 -->
<maven.compiler.encoding>UTF-8</maven.compiler.encoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties> </properties>
<dependencies> <dependencies>
@@ -180,7 +189,7 @@
</exclusions> </exclusions>
</dependency> </dependency>
<!-- 防止进入swagger页面报类型转换错误排除3.0.0中的引用手动增加1.6.2版本 --> <!-- 防止进入swagger页面报类型转换错误 -->
<dependency> <dependency>
<groupId>io.swagger</groupId> <groupId>io.swagger</groupId>
<artifactId>swagger-models</artifactId> <artifactId>swagger-models</artifactId>
@@ -239,12 +248,45 @@
</exclusion> </exclusion>
</exclusions> </exclusions>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.projectlombok</groupId> <groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId> <artifactId>lombok</artifactId>
<version>1.18.38</version> <version>1.18.38</version>
</dependency> </dependency>
<!-- ===== 阿里云 OCR SDK统一版本管理 ===== -->
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>ocr_api20210707</artifactId>
<version>${aliyun.ocr.version}</version>
</dependency>
<!-- Google Zxing 条形码识别 -->
<dependency>
<groupId>com.google.zxing</groupId>
<artifactId>core</artifactId>
<version>3.5.3</version>
</dependency>
<dependency>
<groupId>com.google.zxing</groupId>
<artifactId>javase</artifactId>
<version>3.5.3</version>
</dependency>
<!-- 阿里云通义千问VL模型SDK -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>dashscope-sdk-java</artifactId>
<version>2.16.2</version>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies> </dependencies>
<build> <build>
@@ -253,13 +295,6 @@
<plugin> <plugin>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId> <artifactId>spring-boot-maven-plugin</artifactId>
<!-- 如有自定义 mainClass请在这里配置默认可省略 -->
<!--
<configuration>
<mainClass>com.delivery.DeliveryApplication</mainClass>
<fork>true</fork>
</configuration>
-->
<configuration> <configuration>
<fork>true</fork> <fork>true</fork>
</configuration> </configuration>

View File

@@ -118,6 +118,7 @@ public class SecurityConfig
"/delivery/**", "/delivery/**",
"/document/vehicle/**", "/document/vehicle/**",
"/document/type/**", "/document/type/**",
"/ocr/**",
"/document/location/**", "/document/location/**",
"/document/mtd/**", "/document/mtd/**",
"/document/info/**", "/document/info/**",

View File

@@ -122,7 +122,7 @@ public class DeliveryOrderController extends BaseController
} }
/** 新增配送单据:同一单号多行写入 */ /** 发布配送单据:同一单号多行写入 */
@PreAuthorize("@ss.hasPermi('document:order:add')") @PreAuthorize("@ss.hasPermi('document:order:add')")
@Log(title = "配送单据", businessType = BusinessType.INSERT) @Log(title = "配送单据", businessType = BusinessType.INSERT)
@PostMapping("/batch") @PostMapping("/batch")
@@ -141,7 +141,7 @@ public class DeliveryOrderController extends BaseController
} }
/** 详情:按单号查询所有行 */ /** 详情:按单号查询所有行 */
@PreAuthorize("@ss.hasPermi('document:order:query')") // @PreAuthorize("@ss.hasPermi('document:order:query')")
@GetMapping("/detail/{orderNo}") @GetMapping("/detail/{orderNo}")
public AjaxResult detail(@PathVariable String orderNo) { public AjaxResult detail(@PathVariable String orderNo) {

View File

@@ -7,6 +7,7 @@ import com.delivery.project.document.domain.dto.CalcSuggestFeeDTO;
import com.delivery.project.document.domain.vo.CalcSuggestFeeVO; import com.delivery.project.document.domain.vo.CalcSuggestFeeVO;
import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.PutMapping;
@@ -15,6 +16,7 @@ import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import javax.validation.Valid;
import com.delivery.framework.aspectj.lang.annotation.Log; import com.delivery.framework.aspectj.lang.annotation.Log;
import com.delivery.framework.aspectj.lang.enums.BusinessType; import com.delivery.framework.aspectj.lang.enums.BusinessType;
import com.delivery.project.document.domain.VehicleType; import com.delivery.project.document.domain.VehicleType;
@@ -32,6 +34,7 @@ import com.delivery.framework.web.page.TableDataInfo;
*/ */
@RestController @RestController
@RequestMapping("/document/type") @RequestMapping("/document/type")
@Validated
public class VehicleTypeController extends BaseController public class VehicleTypeController extends BaseController
{ {
@Autowired @Autowired
@@ -107,7 +110,7 @@ public class VehicleTypeController extends BaseController
/** 计算建议费用(支持前端指定车型重算) */ /** 计算建议费用(支持前端指定车型重算) */
@PostMapping("/fee") @PostMapping("/fee")
public AjaxResult calcSuggestFee(@RequestBody CalcSuggestFeeDTO dto) { public AjaxResult calcSuggestFee(@Valid @RequestBody CalcSuggestFeeDTO dto) {
CalcSuggestFeeVO vo = vehicleTypeService.calcSuggestFee(dto); CalcSuggestFeeVO vo = vehicleTypeService.calcSuggestFee(dto);
return success(vo); return success(vo);
} }

View File

@@ -25,6 +25,25 @@ public class DeliveryOrder extends BaseEntity {
@Excel(name = "配送单据号") @Excel(name = "配送单据号")
private String orderNo; private String orderNo;
/** rk_info主键ID */
@Excel(name = "rk_info主键ID")
private Long rkInfoId;
/** 制单人用户ID */
@Excel(name = "制单人ID")
private Long makerId;
/** 制单人用户名(不入库,用于返回给前端) */
private String makerUserName;
/** 接收物资状态1数量齐全状态完好 2存在问题 */
@Excel(name = "接收物资状态", readConverterExp = "1=数量齐全状态完好,2=存在问题")
private Integer receiveStatus; // *** 修改:从 String 改为 Integer ***
/** 接收物资问题描述 */
@Excel(name = "存在问题描述")
private String receiveProblem;
/** 出库单据号 */ /** 出库单据号 */
@Excel(name = "出库单据号") @Excel(name = "出库单据号")
private String billNoCk; private String billNoCk;
@@ -148,9 +167,11 @@ public class DeliveryOrder extends BaseEntity {
/** 连表查询用:附件列表 */ /** 连表查询用:附件列表 */
private List<DeliveryAttachment> attachments; private List<DeliveryAttachment> attachments;
/** 查询用:多状态筛选 */
private List<String> orderStatusList; private List<String> orderStatusList;
// ===================== 费用与里程 ===================== // ===================== 费用与里程 =====================
/** 建议费用(按车型单价*里程的推荐值) */ /** 建议费用(按车型单价*里程的推荐值) */
@Excel(name = "建议费用") @Excel(name = "建议费用")
private BigDecimal suggestFee; private BigDecimal suggestFee;
@@ -174,8 +195,33 @@ public class DeliveryOrder extends BaseEntity {
public String getOrderNo() { return orderNo; } public String getOrderNo() { return orderNo; }
public void setOrderNo(String orderNo) { this.orderNo = orderNo; } public void setOrderNo(String orderNo) { this.orderNo = orderNo; }
public Long getRkInfoId() { return rkInfoId; }
public void setRkInfoId(Long rkInfoId) { this.rkInfoId = rkInfoId; }
public String getMakerUserName() {
return makerUserName;
}
public void setMakerUserName(String makerUserName) {
this.makerUserName = makerUserName;
}
public Long getMakerId() { return makerId; }
public void setMakerId(Long makerId) { this.makerId = makerId; }
public Integer getReceiveStatus() { // *** 修改 ***
return receiveStatus;
}
public void setReceiveStatus(Integer receiveStatus) { // *** 修改 ***
this.receiveStatus = receiveStatus;
}
public String getReceiveProblem() { return receiveProblem; }
public void setReceiveProblem(String receiveProblem) { this.receiveProblem = receiveProblem; }
public String getBillNoCk() { return billNoCk; } public String getBillNoCk() { return billNoCk; }
public void setBillNoCk(String billNoCk) { this.billNoCk = billNoCk; } public void setBillNoCk(String billNoCk) { this.billNoCk = billNoCk; }
public String getXmMs() { return xmMs; } public String getXmMs() { return xmMs; }
public void setXmMs(String xmMs) { this.xmMs = xmMs; } public void setXmMs(String xmMs) { this.xmMs = xmMs; }
@@ -266,6 +312,9 @@ public class DeliveryOrder extends BaseEntity {
public List<DeliveryAttachment> getAttachments() { return attachments; } public List<DeliveryAttachment> getAttachments() { return attachments; }
public void setAttachments(List<DeliveryAttachment> attachments) { this.attachments = attachments; } public void setAttachments(List<DeliveryAttachment> attachments) { this.attachments = attachments; }
public List<String> getOrderStatusList() { return orderStatusList; }
public void setOrderStatusList(List<String> orderStatusList) { this.orderStatusList = orderStatusList; }
public BigDecimal getSuggestFee() { return suggestFee; } public BigDecimal getSuggestFee() { return suggestFee; }
public void setSuggestFee(BigDecimal suggestFee) { this.suggestFee = suggestFee; } public void setSuggestFee(BigDecimal suggestFee) { this.suggestFee = suggestFee; }
@@ -278,19 +327,18 @@ public class DeliveryOrder extends BaseEntity {
public BigDecimal getTotalKm() { return totalKm; } public BigDecimal getTotalKm() { return totalKm; }
public void setTotalKm(BigDecimal totalKm) { this.totalKm = totalKm; } public void setTotalKm(BigDecimal totalKm) { this.totalKm = totalKm; }
public List<String> getOrderStatusList() { // ===================== toString =====================
return orderStatusList;
}
public void setOrderStatusList(List<String> orderStatusList) {
this.orderStatusList = orderStatusList;
}
@Override @Override
public String toString() { public String toString() {
return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE) return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE)
.append("id", getId()) .append("id", getId())
.append("orderNo", getOrderNo()) .append("orderNo", getOrderNo())
.append("rkInfoId", getRkInfoId())
.append("makerId", getMakerId())
.append("makerUserName", getMakerUserName())
.append("receiveStatus", getReceiveStatus()) // *** 类型已是 Integer ***
.append("receiveProblem", getReceiveProblem())
.append("billNoCk", getBillNoCk()) .append("billNoCk", getBillNoCk())
.append("xmMs", getXmMs()) .append("xmMs", getXmMs())
.append("xmNo", getXmNo()) .append("xmNo", getXmNo())

View File

@@ -2,6 +2,8 @@
package com.delivery.project.document.domain.dto; package com.delivery.project.document.domain.dto;
import lombok.Data; import lombok.Data;
import javax.validation.constraints.DecimalMin;
import javax.validation.constraints.DecimalMax;
import java.math.BigDecimal; import java.math.BigDecimal;
/** /**
@@ -11,12 +13,16 @@ import java.math.BigDecimal;
@Data @Data
public class CalcSuggestFeeDTO { public class CalcSuggestFeeDTO {
/** 货物重量(单位:吨) */ /** 货物重量(单位:吨) */
@DecimalMin(value = "0.00", message = "货物重量不能小于0")
private BigDecimal weightTon; private BigDecimal weightTon;
/** 货物体积(单位:立方米) */ /** 货物体积(单位:立方米) */
@DecimalMin(value = "0.00", message = "货物体积不能小于0")
private BigDecimal volumeM3; private BigDecimal volumeM3;
/** 行程距离(单位:公里) */ /** 行程距离(单位:公里) */
@DecimalMin(value = "0.00", message = "行程距离不能小于0")
@DecimalMax(value = "999999.99", message = "行程距离不能超过999999.99公里")
private BigDecimal distanceKm; private BigDecimal distanceKm;
/** 指定车型ID可选传入则按该车型计算建议费用 */ /** 指定车型ID可选传入则按该车型计算建议费用 */

View File

@@ -31,6 +31,15 @@ public class DeliveryExecuteBindDTO {
private BigDecimal actualFee; // 实际费用 private BigDecimal actualFee; // 实际费用
private BigDecimal tollFee; // 高速费用 private BigDecimal tollFee; // 高速费用
/**
* 接收物资状态:
* 0 待确认1 齐全完好2 存在问题
*/
private Integer receiveStatus;
/** 当 receiveStatus = 2 时,必须填写问题描述 */
private String receiveProblem;
/** 本次执行的附件条目URL 列表) */ /** 本次执行的附件条目URL 列表) */
@NotNull @NotNull
private List<DeliveryAttachItemDTO> attachments; private List<DeliveryAttachItemDTO> attachments;

View File

@@ -1,6 +1,5 @@
package com.delivery.project.document.domain.dto; package com.delivery.project.document.domain.dto;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data; import lombok.Data;
import java.math.BigDecimal; import java.math.BigDecimal;
@@ -17,14 +16,9 @@ import java.util.List;
@Data @Data
public class DeliveryOrderCreateDTO { public class DeliveryOrderCreateDTO {
/** /** 配送单据号(可空,后台自动生成) */
* 配送单据号,可传可不传;
* 不传时由后端自动生成(格式如 DO202510280001
*/
private String orderNo; private String orderNo;
// ==================== 头部信息(整单通用) ====================
/** 起始地点名称 */ /** 起始地点名称 */
private String originName; private String originName;
@@ -43,56 +37,70 @@ public class DeliveryOrderCreateDTO {
/** 目的地点纬度 */ /** 目的地点纬度 */
private BigDecimal destLat; private BigDecimal destLat;
/** 配送日期yyyy-MM-dd 格式) */ /** 配送日期 */
@JsonFormat(pattern = "yyyy-MM-dd")
private Date deliveryDate; private Date deliveryDate;
/** 配送车辆车牌号 */ /** 车牌号 */
private String plateNo; private String plateNo;
/** 发货人名 */ /** 发货人名 */
private String shipperName; private String shipperName;
/** 发货人联系电话 */ /** 发货人联系方式 */
private String shipperPhone; private String shipperPhone;
/** 收货人姓名 */ /** 接收人名称 */
private String receiverName; private String receiverName;
/** 收人联系电话 */ /** 收人联系方式 */
private String receiverPhone; private String receiverPhone;
/** 收货单位名称 */ /** 接收单位 */
private String receiverOrgName; private String receiverOrgName;
/** 配送吨位(单位:吨) */ /** 配送吨位 */
private BigDecimal deliveryTon; private BigDecimal deliveryTon;
/** 货物体积(单位:立方米) */ /** 货物尺寸 */
private BigDecimal goodsSize; private BigDecimal goodsSize;
/** 单据状态(默认 0待发货1起运2送达 */ /**
* 接收物资状态:
* 1数量齐全、状态完好默认
* 2存在问题
*/
private Integer receiveStatus; // *** 修改String -> Integer ***
/**
* 接收物资问题描述receiveStatus = 2 时必填)
*/
private String receiveProblem;
/** 制单人ID前端可传如果为空则后台自动取当前登录用户ID */
private Long makerId;
/** 配送状态默认1 已接单 / 后续会调整) */
private String orderStatus; private String orderStatus;
/** 车辆类型ID外键对应 vehicle_type 表主键) */ /** 车型 ID */
private Long vehicleTypeId; private Long vehicleTypeId;
/** 车辆类型名称小型面包车、9.6米货车等) */ /** 车型名称 */
private String vehicleTypeName; private String vehicleTypeName;
/** 系统建议运费(根据车型和里程计算) */ /** 建议费用 */
private BigDecimal suggestFee; private BigDecimal suggestFee;
/** 实际运费(人工调整或司机确认后) */ /** 实际费用 */
private BigDecimal actualFee; private BigDecimal actualFee;
/** 过路费(收费站、高速费等额外费用) */ /** 高速费用 */
private BigDecimal tollFee; private BigDecimal tollFee;
/** 配送总里程(单位:公里 */ /** 公里 */
private BigDecimal totalKm; private BigDecimal totalKm;
/** 备注说明(可填写其他补充信息) */ /** 备注 */
private String remark; private String remark;
// ==================== 行明细(多物料行) ==================== // ==================== 行明细(多物料行) ====================

View File

@@ -6,13 +6,38 @@ import java.math.BigDecimal;
@Data @Data
public class DeliveryOrderLineDTO { public class DeliveryOrderLineDTO {
private String billNoCk; // 出库单据号
private String xmMs; // 项目描述 /**
private String xmNo; // 项目号 * 对应智慧实物系统 rk_info 表的主键 ID
private String wlNo; // 物料号 * detailList 里的 id 就是这个值
private String wlMs; // 物料描述 */
private BigDecimal realQty; // 实际入库数量 private Long rkInfoId;
private String dw; // 计量单位
private String sapNo; // SAP订单编号 /** 出库单据号 */
private String gysMc; // 供应商名称 private String billNoCk;
/** 项目描述 */
private String xmMs;
/** 项目号 */
private String xmNo;
/** 物料号 */
private String wlNo;
/** 物料描述 */
private String wlMs;
/** 实际数量 */
private BigDecimal realQty;
/** 计量单位 */
private String dw;
/** SAP订单编号 */
private String sapNo;
/** 供应商名称 */
private String gysMc;
} }

View File

@@ -24,6 +24,12 @@ public class CalcSuggestFeeVO {
/** 建议费用单位四舍五入保留2位小数 */ /** 建议费用单位四舍五入保留2位小数 */
private BigDecimal suggestFee; private BigDecimal suggestFee;
/** 错误信息 */
private String errorMessage;
/** 是否有适配车型 */
private Boolean hasSuitableType;
/** 候选车型列表(按价格/容量排序) */ /** 候选车型列表(按价格/容量排序) */
private List<VehicleTypeOptionVO> candidates; private List<VehicleTypeOptionVO> candidates;
@@ -51,7 +57,7 @@ public class CalcSuggestFeeVO {
/** 载方下限(单位:立方米,含) */ /** 载方下限(单位:立方米,含) */
private BigDecimal volumeMinM3; private BigDecimal volumeMinM3;
/** 载方上限(单位:立方米,含) */ /** 载方上限(单位:,含) */
private BigDecimal volumeMaxM3; private BigDecimal volumeMaxM3;
} }
} }

View File

@@ -0,0 +1,21 @@
package com.delivery.project.document.domain.vo;
import com.delivery.project.document.domain.DeliveryOrder;
import lombok.Data;
import java.util.List;
/**
* 配送单据详情 VO带图片
* 继承 DeliveryOrder只做返回展示使用不参与持久化映射。
*/
@Data
public class DeliveryOrderDetailVO extends DeliveryOrder {
/** 当前登录用户ID只做前端展示用 */
private Long makerId;
/** 当前登录用户名(只做前端展示用) */
private String makerUserName;
}

View File

@@ -5,6 +5,7 @@ import com.delivery.project.document.domain.DeliveryOrder;
import com.delivery.project.document.domain.dto.DeliveryOrderCreateDTO; import com.delivery.project.document.domain.dto.DeliveryOrderCreateDTO;
import com.delivery.project.document.domain.dto.DeliveryOrderSaveDTO; import com.delivery.project.document.domain.dto.DeliveryOrderSaveDTO;
import com.delivery.project.document.domain.vo.DeliveryBillVO; import com.delivery.project.document.domain.vo.DeliveryBillVO;
import com.delivery.project.document.domain.vo.DeliveryOrderDetailVO;
import com.delivery.project.document.domain.vo.DeliveryOrderGroupVO; import com.delivery.project.document.domain.vo.DeliveryOrderGroupVO;
/** /**
@@ -92,7 +93,7 @@ public interface IDeliveryOrderService
* @return * @return
*/ */
/** 详情:按单号查行 */ /** 详情:按单号查行 */
List<DeliveryOrder> listByOrderNo(String orderNo); List<DeliveryOrderDetailVO> listByOrderNo(String orderNo);

View File

@@ -62,7 +62,7 @@ public interface IVehicleTypeService
public int deleteVehicleTypeById(Long id); public int deleteVehicleTypeById(Long id);
/** /**
* 计算建议运费 * 计算建议运费,推荐车型
* @param dto * @param dto
* @return * @return
*/ */

View File

@@ -7,6 +7,7 @@ import java.nio.file.Paths;
import java.nio.file.StandardCopyOption; import java.nio.file.StandardCopyOption;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.*; import java.util.*;
import java.util.stream.Collectors;
import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSON;
import com.delivery.common.exception.ServiceException; import com.delivery.common.exception.ServiceException;
@@ -139,25 +140,32 @@ public class DeliveryAttachmentServiceImpl implements IDeliveryAttachmentService
@Override @Override
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
public int executeBind(DeliveryExecuteBindDTO dto) { public int executeBind(DeliveryExecuteBindDTO dto) {
// 1) 校验订单存在 // 1) 校验订单存在(一个单号多行)
List<DeliveryOrder> existList = deliveryOrderMapper.selectDeliveryOrderByOrderNo(dto.getOrderNo()); List<DeliveryOrder> existList = deliveryOrderMapper.selectDeliveryOrderByOrderNo(dto.getOrderNo());
if (existList == null || existList.isEmpty()) { if (existList == null || existList.isEmpty()) {
throw new ServiceException("配送单不存在:" + dto.getOrderNo()); throw new ServiceException("配送单不存在:" + dto.getOrderNo());
} }
String billNoCk = existList.get(0).getBillNoCk(); // ====== 从配送单中拿到所有 rk_info_id 列表 ======
List<Long> rkInfoIdList = existList.stream()
.map(DeliveryOrder::getRkInfoId)
.filter(Objects::nonNull)
.distinct()
.collect(Collectors.toList());
if (StringUtils.isBlank(billNoCk)) { if (rkInfoIdList.isEmpty()) {
throw new ServiceException("配送单未绑定出库单号,无法回写库存状态!"); throw new ServiceException("配送单未绑定 rk_info_id,无法回写库存状态!");
} }
// ====================================================
if (dto.getAttachments() == null || dto.getAttachments().isEmpty()) { if (dto.getAttachments() == null || dto.getAttachments().isEmpty()) {
throw new ServiceException("附件列表不能为空"); throw new ServiceException("附件列表不能为空");
} }
// 2) 批量插入附件(只传 URL // 2) 批量插入附件(只传 URL
List<DeliveryAttachment> list = new ArrayList<>(); List<DeliveryAttachment> list = new ArrayList<>();
String username = getUsername();
// String username = "大爷的!!!"; String username = getUsername();
for (DeliveryAttachItemDTO it : dto.getAttachments()) { for (DeliveryAttachItemDTO it : dto.getAttachments()) {
if (it == null) { if (it == null) {
continue; continue;
@@ -215,24 +223,36 @@ public class DeliveryAttachmentServiceImpl implements IDeliveryAttachmentService
patch.setTollFee(dto.getTollFee()); patch.setTollFee(dto.getTollFee());
} }
Integer receiveStatus = dto.getReceiveStatus();
if (receiveStatus == null) {
throw new ServiceException("完成配送时必须选择接收物资状态");
}
if (receiveStatus != 0 && receiveStatus != 1 && receiveStatus != 2) {
throw new ServiceException("接收物资状态不合法");
}
patch.setReceiveStatus(receiveStatus);
if (receiveStatus == 2) {
// 有问题必须写说明
if (StringUtils.isBlank(dto.getReceiveProblem())) {
throw new ServiceException("存在问题时必须填写问题描述");
}
patch.setReceiveProblem(dto.getReceiveProblem());
}
patch.setOrderStatus("3"); // 已完成 patch.setOrderStatus("3"); // 已完成
} }
deliveryOrderMapper.updateDeliveryOrder(patch); deliveryOrderMapper.updateDeliveryOrder(patch);
// 4) ⭐ 如果是 DEST 场景,远程调用 WMS把 rk_info.is_delivery 改成 3 // 4) ⭐ 如果是 DEST 场景,远程调用 WMS这些 rk_info 记录的 is_delivery 改成 3
if ("DEST".equals(scene)) { if ("DEST".equals(scene)) {
if (StringUtils.isBlank(billNoCk)) { // 这里已经不再按 billNoCk 整单更新,而是按 rk_info_id 列表更新
throw new ServiceException("配送单缺少对应的出库单号 billNoCk无法回写库存状态"); boolean ok = updateWmsIsDeliveryByIds(rkInfoIdList, 3);
}
// rkInfoMapper.updateDeliveryStatus(billNoCk, 3);
boolean ok = updateWmsIsDelivery(billNoCk, 3);
if (!ok) { if (!ok) {
// 让整个事务回滚,附件 + 配送单状态都撤回 // 让整个事务回滚,附件 + 配送单状态都撤回
throw new ServiceException("回写 WMS 配送状态失败,出库单号" + billNoCk); throw new ServiceException("回写 WMS 配送状态失败,rk_info_id 列表" + rkInfoIdList);
} }
} }
@@ -240,24 +260,28 @@ public class DeliveryAttachmentServiceImpl implements IDeliveryAttachmentService
} }
/** /**
* 远程调用智慧实物管理系统,更新 rk_info.is_delivery 状态 * 远程调用智慧实物管理系统, rk_info 主键ID列表更新 is_delivery 状态
* 约定请求体结构:
* {
* "rkInfoIdList": [1, 2, 3],
* "isDelivery": 3
* }
*/ */
private boolean updateWmsIsDelivery(String billNoCk, int isDelivery) { private boolean updateWmsIsDeliveryByIds(List<Long> rkInfoIdList, int isDelivery) {
String url = wisdomBaseUrl + "/wisdom/stock/updateDeliveryStatus"; String url = wisdomBaseUrl + "/wisdom/stock/updateDeliveryStatus";
Map<String, Object> body = new HashMap<>(); Map<String, Object> body = new HashMap<>();
body.put("billNoCk", billNoCk); body.put("ids", rkInfoIdList);
body.put("isDelivery", isDelivery); body.put("isDelivery", isDelivery);
String json = JSON.toJSONString(body); String json = JSON.toJSONString(body);
try { try {
// 用你项目里的 HttpUtils 发 JSON POST
String resp = HttpUtils.sendPost(url, json, MediaType.APPLICATION_JSON_VALUE); String resp = HttpUtils.sendPost(url, json, MediaType.APPLICATION_JSON_VALUE);
if (StringUtils.isBlank(resp)) { if (StringUtils.isBlank(resp)) {
log.error("WMS 更新失败响应为空url={} billNoCk={}", url, billNoCk); log.error("WMS 更新失败响应为空url={} rkInfoIdList={}", url, rkInfoIdList);
return false; return false;
} }
@@ -269,16 +293,17 @@ public class DeliveryAttachmentServiceImpl implements IDeliveryAttachmentService
String msg = (result == null) String msg = (result == null)
? "响应为空" ? "响应为空"
: String.valueOf(result.get(AjaxResult.MSG_TAG)); : String.valueOf(result.get(AjaxResult.MSG_TAG));
log.error("WMS 更新失败,billNoCk={},原因={}", billNoCk, msg); log.error("WMS 更新失败,rkInfoIdList={},原因={}", rkInfoIdList, msg);
return false; return false;
} }
} catch (Exception e) { } catch (Exception e) {
log.error("调用 WMS 接口异常,billNoCk={}error={}", billNoCk, e.getMessage(), e); log.error("调用 WMS 接口异常,rkInfoIdList={}error={}", rkInfoIdList, e.getMessage(), e);
return false; return false;
} }
} }
// 保存目录 D:\delivery // 保存目录 D:\delivery
private static final String BASE_PATH = "D:/delivery"; private static final String BASE_PATH = "D:/delivery";

View File

@@ -20,6 +20,7 @@ import com.delivery.project.document.domain.dto.DeliveryOrderCreateDTO;
import com.delivery.project.document.domain.dto.DeliveryOrderLineDTO; import com.delivery.project.document.domain.dto.DeliveryOrderLineDTO;
import com.delivery.project.document.domain.dto.DeliveryOrderSaveDTO; import com.delivery.project.document.domain.dto.DeliveryOrderSaveDTO;
import com.delivery.project.document.domain.vo.DeliveryBillVO; import com.delivery.project.document.domain.vo.DeliveryBillVO;
import com.delivery.project.document.domain.vo.DeliveryOrderDetailVO;
import com.delivery.project.document.domain.vo.DeliveryOrderGroupVO; import com.delivery.project.document.domain.vo.DeliveryOrderGroupVO;
import com.delivery.project.document.mapper.DeliveryAttachmentMapper; import com.delivery.project.document.mapper.DeliveryAttachmentMapper;
import com.delivery.project.document.mapper.MtdMapper; import com.delivery.project.document.mapper.MtdMapper;
@@ -51,8 +52,6 @@ public class DeliveryOrderServiceImpl implements IDeliveryOrderService
@Autowired @Autowired
private DeliveryAttachmentMapper deliveryAttachmentMapper; private DeliveryAttachmentMapper deliveryAttachmentMapper;
@Autowired
private RkInfoMapper rkInfoMapper;
@Autowired @Autowired
private MtdMapper mtdMapper; private MtdMapper mtdMapper;
@@ -185,34 +184,67 @@ public class DeliveryOrderServiceImpl implements IDeliveryOrderService
public String createOrder(DeliveryOrderCreateDTO dto) { public String createOrder(DeliveryOrderCreateDTO dto) {
// ========== 0. 参数校验 ========== // ========== 0. 参数校验 ==========
if (dto == null || dto.getItems() == null || dto.getItems().isEmpty()) { if (dto == null || dto.getItems() == null || dto.getItems().isEmpty()) {
throw new ServiceException("物料明细不能为空"); throw new ServiceException("物料明细不能为空");
} }
// 所有明细应是同一出库单据号,这里简单从第一条取出 // 取第一条,仍然要求同一出库单据号(暂不支持多出库单合并)
String billNoCk = dto.getItems().get(0).getBillNoCk(); String billNoCk = dto.getItems().get(0).getBillNoCk();
if (billNoCk == null || billNoCk.trim().isEmpty()) { if (StringUtils.isBlank(billNoCk)) {
throw new ServiceException("明细行缺少出库单据号 billNoCk"); throw new ServiceException("明细行缺少出库单据号 billNoCk");
} }
// 收集 rk_info 主键 id用于回写 WMS
List<Long> rkInfoIds = dto.getItems().stream()
.map(DeliveryOrderLineDTO::getRkInfoId)
.filter(Objects::nonNull)
.distinct()
.collect(Collectors.toList());
if (rkInfoIds.isEmpty()) {
throw new ServiceException("明细行缺少 rk_info 主键 id无法回写配送状态");
}
// ========== 1. 生成配送单号 ========== // ========== 1. 生成配送单号 ==========
String orderNo = (dto.getOrderNo() == null || dto.getOrderNo().trim().isEmpty())
String orderNo = StringUtils.isBlank(dto.getOrderNo())
? "DO" + DateUtils.dateTimeNow("yyyyMMddHHmmssSSS") ? "DO" + DateUtils.dateTimeNow("yyyyMMddHHmmssSSS")
: dto.getOrderNo().trim(); : dto.getOrderNo().trim();
Date now = DateUtils.getNowDate(); Date now = DateUtils.getNowDate();
String username = SecurityUtils.getUsername(); String username = SecurityUtils.getUsername();
Long currentUserId = null;
try {
currentUserId = SecurityUtils.getUserId();
} catch (Exception e) {
log.warn("获取当前登录用户ID失败{}", e.getMessage());
}
// 制单人用户ID优先用前端传的 makerId没有则回退到当前登录用户
Long makerId = dto.getMakerId();
if (makerId == null) {
if (currentUserId == null) {
throw new ServiceException("无法确定制单人用户ID请重新登录后重试");
}
makerId = currentUserId;
}
List<DeliveryOrder> rows = new ArrayList<>(); List<DeliveryOrder> rows = new ArrayList<>();
// ========== 2. 遍历每一条物料明细 ========== // ========== 2. 遍历每一条物料明细,组装行记录 ==========
for (DeliveryOrderLineDTO it : dto.getItems()) { for (DeliveryOrderLineDTO it : dto.getItems()) {
// 这里如果允许多个出库单合并配送,可以去掉这个校验 // 仍然限制同一出库单(你后续要支持多出库单再放开)
if (!billNoCk.equals(it.getBillNoCk())) { if (!billNoCk.equals(it.getBillNoCk())) {
throw new ServiceException("当前接口暂不支持多张出库单合并配送,请确保所有明细 billNoCk 相同"); throw new ServiceException("当前接口暂不支持多张出库单合并配送,请确保所有明细 billNoCk 相同");
} }
if (it.getRkInfoId() == null) {
throw new ServiceException("明细行缺少 rk_info 主键 id");
}
DeliveryOrder row = new DeliveryOrder(); DeliveryOrder row = new DeliveryOrder();
// 2.1 公共头部字段(整单一致) // 2.1 公共头部字段(整单一致)
@@ -225,6 +257,7 @@ public class DeliveryOrderServiceImpl implements IDeliveryOrderService
row.setDestLat(dto.getDestLat()); row.setDestLat(dto.getDestLat());
row.setDeliveryDate(dto.getDeliveryDate()); row.setDeliveryDate(dto.getDeliveryDate());
row.setPlateNo(dto.getPlateNo()); row.setPlateNo(dto.getPlateNo());
row.setShipperName(dto.getShipperName()); row.setShipperName(dto.getShipperName());
row.setShipperPhone(dto.getShipperPhone()); row.setShipperPhone(dto.getShipperPhone());
row.setReceiverName(dto.getReceiverName()); row.setReceiverName(dto.getReceiverName());
@@ -232,9 +265,15 @@ public class DeliveryOrderServiceImpl implements IDeliveryOrderService
row.setReceiverOrgName(dto.getReceiverOrgName()); row.setReceiverOrgName(dto.getReceiverOrgName());
row.setDeliveryTon(dto.getDeliveryTon()); row.setDeliveryTon(dto.getDeliveryTon());
row.setGoodsSize(dto.getGoodsSize()); row.setGoodsSize(dto.getGoodsSize());
row.setOrderStatus(
StringUtils.isBlank(dto.getOrderStatus()) ? "1" : dto.getOrderStatus().trim() // 制单人用户ID
); row.setMakerId(makerId);
// 配送状态:前端不传时默认 1已接单 / 待起运)
String orderStatus = StringUtils.isBlank(dto.getOrderStatus())
? "1" : dto.getOrderStatus().trim();
row.setOrderStatus(orderStatus);
row.setVehicleTypeId(dto.getVehicleTypeId()); row.setVehicleTypeId(dto.getVehicleTypeId());
row.setVehicleTypeName(dto.getVehicleTypeName()); row.setVehicleTypeName(dto.getVehicleTypeName());
row.setSuggestFee(dto.getSuggestFee()); row.setSuggestFee(dto.getSuggestFee());
@@ -244,6 +283,7 @@ public class DeliveryOrderServiceImpl implements IDeliveryOrderService
row.setRemark(dto.getRemark()); row.setRemark(dto.getRemark());
// 2.2 明细字段 // 2.2 明细字段
row.setRkInfoId(it.getRkInfoId()); // 关联 rk_info 主键ID
row.setBillNoCk(it.getBillNoCk()); row.setBillNoCk(it.getBillNoCk());
row.setXmMs(it.getXmMs()); row.setXmMs(it.getXmMs());
row.setXmNo(it.getXmNo()); row.setXmNo(it.getXmNo());
@@ -263,17 +303,16 @@ public class DeliveryOrderServiceImpl implements IDeliveryOrderService
} }
// ========== 3. 批量落库 ========== // ========== 3. 批量落库 ==========
if (!rows.isEmpty()) { if (!rows.isEmpty()) {
deliveryOrderMapper.batchInsert(rows); deliveryOrderMapper.batchInsert(rows);
} }
// rkInfoMapper.updateDeliveryStatus(billNoCk, 2); // ========== 4. 回写 WMS rk_info.id 更新 is_delivery = 2配送中 ==========
// ========== 4. 回写 WMSrk_info.is_delivery = 2配送中 ========== boolean ok = updateWmsIsDeliveryByIds(rkInfoIds, 2);
// 按出库单据号整单回写
boolean ok = updateWmsIsDelivery(billNoCk, 2);
if (!ok) { if (!ok) {
// 这里直接抛异常让当前事务回滚避免出现“配送单已生成WMS 状态没改”的脏数据 // 回写失败,整单回滚,避免脏数据
throw new ServiceException("已生成配送单,但回写 WMS 配送状态失败"); throw new ServiceException("已生成配送单,但回写 WMS 配送状态失败");
} }
@@ -281,34 +320,29 @@ public class DeliveryOrderServiceImpl implements IDeliveryOrderService
} }
/** /**
* 远程调用 WMS出库单据号修改 rk_info.is_delivery 状态 * 远程调用 WMS按 rk_info 主键 ID 列表修改 is_delivery 状态
* *
* 请求方式示例: * 请求方式示例:
* POST ${delivery.wms-base-url}/delivery/rkInfo/updateDeliveryStatus * POST ${delivery.wisdom-base-url}/wisdom/stock/updateDeliveryStatus
* Body: { "billNoCk": "CK202511200001", "isDelivery": 2 } * Body: { "ids": [1, 2, 3], "isDelivery": 2 }
*/ */
private boolean updateWmsIsDelivery(String billNoCk, int isDelivery) { private boolean updateWmsIsDeliveryByIds(List<Long> rkInfoIds, int isDelivery) {
String url = wisdomBaseUrl + "/wisdom/stock/updateDeliveryStatus"; String url = wisdomBaseUrl + "/wisdom/stock/updateDeliveryStatus";
Map<String, Object> map = new HashMap<>(); Map<String, Object> map = new HashMap<>();
map.put("billNoCk", billNoCk); map.put("ids", rkInfoIds);
map.put("isDelivery", isDelivery); map.put("isDelivery", isDelivery);
String json = JSON.toJSONString(map); String json = JSON.toJSONString(map);
try { try {
// 发送 JSON POST你刚加的 sendJsonPost
String resp = HttpUtils.sendJsonPost(url, json); String resp = HttpUtils.sendJsonPost(url, json);
// 解析为 AjaxResult
AjaxResult result = JSON.parseObject(resp, AjaxResult.class); AjaxResult result = JSON.parseObject(resp, AjaxResult.class);
if (result != null && result.isSuccess()) { if (result != null && result.isSuccess()) {
// code == 200
return true; return true;
} else { } else {
// 取 msg从 Map 里按 key 取
String msg = (result != null) String msg = (result != null)
? String.valueOf(result.get(AjaxResult.MSG_TAG)) ? String.valueOf(result.get(AjaxResult.MSG_TAG))
: "响应为空"; : "响应为空";
@@ -317,7 +351,7 @@ public class DeliveryOrderServiceImpl implements IDeliveryOrderService
} }
} catch (Exception e) { } catch (Exception e) {
log.error("WMS 调用异常 billNoCk={} error={}", billNoCk, e.getMessage(), e); log.error("WMS 调用异常 rkInfoIds={} error={}", rkInfoIds, e.getMessage(), e);
return false; return false;
} }
} }
@@ -329,8 +363,24 @@ public class DeliveryOrderServiceImpl implements IDeliveryOrderService
} }
@Override @Override
public List<DeliveryOrder> listByOrderNo(String orderNo) { public List<DeliveryOrderDetailVO> listByOrderNo(String orderNo) {
return deliveryOrderMapper.selectByOrderNo(orderNo);
// 1. 直接查出带附件、带 makerUserName 的明细列表
// 使用的是 resultMap="DeliveryOrderWithAttachResult"
List<DeliveryOrder> list = deliveryOrderMapper.selectByOrderNo(orderNo);
if (list == null || list.isEmpty()) {
return Collections.emptyList();
}
// 2. 转成 VO 列表(继承自 DeliveryOrder自动带上 makerId / makerUserName / attachments 等所有字段)
List<DeliveryOrderDetailVO> result = new ArrayList<>(list.size());
for (DeliveryOrder o : list) {
DeliveryOrderDetailVO vo = new DeliveryOrderDetailVO();
BeanUtils.copyProperties(o, vo);
result.add(vo);
}
return result;
} }
/** /**
@@ -419,7 +469,6 @@ public class DeliveryOrderServiceImpl implements IDeliveryOrderService
} }
} }
} }
return list; return list;
} }

View File

@@ -143,7 +143,37 @@ public class MtdServiceImpl implements IMtdService
@Override @Override
public TotalWvVO calcTotalWv(CalcTotalWvDTO dto) { public TotalWvVO calcTotalWv(CalcTotalWvDTO dto) {
if (dto == null || dto.getWlNos() == null || dto.getWlNos().isEmpty()) { if (dto == null) {
TotalWvVO empty = new TotalWvVO();
empty.setTotalWeightKg(BigDecimal.ZERO);
empty.setTotalVolumeM3(BigDecimal.ZERO);
return empty;
}
// 使用wlNos和items列表来统计物料
Map<String, BigDecimal> wlNoQtyMap = new LinkedHashMap<>();
// 如果提供了items列表优先使用items包含具体数量
if (dto.getItems() != null && !dto.getItems().isEmpty()) {
for (CalcTotalWvDTO.Item item : dto.getItems()) {
if (item.getWlNo() != null && !item.getWlNo().trim().isEmpty()) {
BigDecimal qty = item.getQty();
if (qty == null || qty.compareTo(BigDecimal.ZERO) <= 0) {
qty = BigDecimal.ONE; // 默认为1
}
wlNoQtyMap.put(item.getWlNo(), wlNoQtyMap.getOrDefault(item.getWlNo(), BigDecimal.ZERO).add(qty));
}
}
} else if (dto.getWlNos() != null && !dto.getWlNos().isEmpty()) {
// 如果只有wlNos列表则每个物料号计数为1
for (String wlNo : dto.getWlNos()) {
if (wlNo != null && !wlNo.trim().isEmpty()) {
wlNoQtyMap.put(wlNo, wlNoQtyMap.getOrDefault(wlNo, BigDecimal.ZERO).add(BigDecimal.ONE));
}
}
}
if (wlNoQtyMap.isEmpty()) {
TotalWvVO empty = new TotalWvVO(); TotalWvVO empty = new TotalWvVO();
empty.setTotalWeightKg(BigDecimal.ZERO); empty.setTotalWeightKg(BigDecimal.ZERO);
empty.setTotalVolumeM3(BigDecimal.ZERO); empty.setTotalVolumeM3(BigDecimal.ZERO);
@@ -151,17 +181,20 @@ public class MtdServiceImpl implements IMtdService
} }
// 查询数据库中所有匹配的物料信息 // 查询数据库中所有匹配的物料信息
List<Mtd> list = mtdMapper.selectByWlNos(dto.getWlNos()); List<Mtd> list = mtdMapper.selectByWlNos(new ArrayList<>(wlNoQtyMap.keySet()));
BigDecimal totalWeight = BigDecimal.ZERO; BigDecimal totalWeight = BigDecimal.ZERO;
BigDecimal totalVolume = BigDecimal.ZERO; BigDecimal totalVolume = BigDecimal.ZERO;
for (Mtd m : list) { for (Mtd m : list) {
BigDecimal w = m.getWeightKg() == null ? BigDecimal.ZERO : m.getWeightKg(); if (m.getWlNo() != null && wlNoQtyMap.containsKey(m.getWlNo())) {
BigDecimal v = m.getVolumeM3() == null ? BigDecimal.ZERO : m.getVolumeM3(); BigDecimal qty = wlNoQtyMap.get(m.getWlNo());
BigDecimal w = m.getWeightKg() == null ? BigDecimal.ZERO : m.getWeightKg();
BigDecimal v = m.getVolumeM3() == null ? BigDecimal.ZERO : m.getVolumeM3();
totalWeight = totalWeight.add(w); totalWeight = totalWeight.add(w.multiply(qty));
totalVolume = totalVolume.add(v); totalVolume = totalVolume.add(v.multiply(qty));
}
} }
TotalWvVO vo = new TotalWvVO(); TotalWvVO vo = new TotalWvVO();

View File

@@ -100,6 +100,11 @@ public class VehicleTypeServiceImpl implements IVehicleTypeService
return vehicleTypeMapper.deleteVehicleTypeById(id); return vehicleTypeMapper.deleteVehicleTypeById(id);
} }
/**
* 计算建议运费,推荐车型
* @param dto
* @return
*/
@Override @Override
public CalcSuggestFeeVO calcSuggestFee(CalcSuggestFeeDTO dto) public CalcSuggestFeeVO calcSuggestFee(CalcSuggestFeeDTO dto)
{ {
@@ -113,8 +118,55 @@ public class VehicleTypeServiceImpl implements IVehicleTypeService
if (candidates == null || candidates.isEmpty()) { if (candidates == null || candidates.isEmpty()) {
candidates = vehicleTypeMapper.selectFallbackTypes(w, v); candidates = vehicleTypeMapper.selectFallbackTypes(w, v);
} }
// ========== 2) 检查是否存在适配车型 ==========
if (candidates == null || candidates.isEmpty()) { if (candidates == null || candidates.isEmpty()) {
throw new RuntimeException("未找到适配车型,检查车型配置或输入参数。"); // 如果没有适配车型,检查系统中是否至少存在一个车型配置
List<VehicleType> allVehicleTypes = vehicleTypeMapper.selectVehicleTypeList(new VehicleType());
if (allVehicleTypes == null || allVehicleTypes.isEmpty()) {
CalcSuggestFeeVO vo = new CalcSuggestFeeVO();
vo.setErrorMessage("系统中暂无车型配置,请先配置车型信息。");
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);
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());
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);
}
}
vo.setCandidates(list);
return vo;
} else {
CalcSuggestFeeVO vo = new CalcSuggestFeeVO();
vo.setErrorMessage("未找到适配车型,请检查车型配置或输入参数。");
vo.setHasSuitableType(false);
return vo;
}
} }
// 稳定排序:单价升序 -> 承重上限升序 -> 载方上限升序 -> id 升序(数据库已排序,这里再兜一层) // 稳定排序:单价升序 -> 承重上限升序 -> 载方上限升序 -> id 升序(数据库已排序,这里再兜一层)
@@ -124,7 +176,7 @@ public class VehicleTypeServiceImpl implements IVehicleTypeService
.thenComparing(VehicleType::getVolumeMaxM3, Comparator.nullsLast(BigDecimal::compareTo)) .thenComparing(VehicleType::getVolumeMaxM3, Comparator.nullsLast(BigDecimal::compareTo))
.thenComparing(VehicleType::getId, Comparator.nullsLast(Long::compareTo))); .thenComparing(VehicleType::getId, Comparator.nullsLast(Long::compareTo)));
// ========== 2) 选定车型:前端指定 or 系统推荐 ========== // ========== 3) 选定车型:前端指定 or 系统推荐 ==========
VehicleType chosen; VehicleType chosen;
if (dto.getVehicleTypeId() != null) { if (dto.getVehicleTypeId() != null) {
// 前端点名车型:优先在候选中找,找不到则直接按主键查 // 前端点名车型:优先在候选中找,找不到则直接按主键查
@@ -139,19 +191,20 @@ public class VehicleTypeServiceImpl implements IVehicleTypeService
chosen = candidates.get(0); chosen = candidates.get(0);
} }
// ========== 3) 计算建议费用:单价 × 公里数保留2位 ========== // ========== 4) 计算建议费用:单价 × 公里数保留2位 ==========
BigDecimal price = chosen.getUnitPricePerKm(); BigDecimal price = chosen.getUnitPricePerKm();
if (price == null) { if (price == null) {
throw new RuntimeException("车型【" + chosen.getTypeName() + "】未配置每公里单价unit_price_per_km 为空)"); throw new RuntimeException("车型【" + chosen.getTypeName() + "】未配置每公里单价unit_price_per_km 为空)");
} }
BigDecimal fee = price.multiply(km).setScale(2, RoundingMode.HALF_UP); BigDecimal fee = price.multiply(km).setScale(2, RoundingMode.HALF_UP);
// ========== 4) 组装返回 ========== // ========== 5) 组装返回 ==========
CalcSuggestFeeVO vo = new CalcSuggestFeeVO(); CalcSuggestFeeVO vo = new CalcSuggestFeeVO();
vo.setVehicleTypeId(chosen.getId()); vo.setVehicleTypeId(chosen.getId());
vo.setVehicleTypeName(chosen.getTypeName()); vo.setVehicleTypeName(chosen.getTypeName());
vo.setUnitPricePerKm(price); vo.setUnitPricePerKm(price);
vo.setSuggestFee(fee); vo.setSuggestFee(fee);
vo.setHasSuitableType(true); // 有适配车型
// 候选项 // 候选项
List<CalcSuggestFeeVO.VehicleTypeOptionVO> list = new ArrayList<>(candidates.size()); List<CalcSuggestFeeVO.VehicleTypeOptionVO> list = new ArrayList<>(candidates.size());
@@ -170,6 +223,33 @@ public class VehicleTypeServiceImpl implements IVehicleTypeService
return vo; return vo;
} }
/**
* 构建无匹配车型的错误信息
*/
private String buildNoMatchErrorMessage(BigDecimal weight, BigDecimal volume, VehicleType maxCapacityType) {
StringBuilder sb = new StringBuilder();
sb.append("无适配车型,原因:");
boolean weightExceeds = weight.compareTo(maxCapacityType.getWeightMaxTon()) > 0;
boolean volumeExceeds = volume.compareTo(maxCapacityType.getVolumeMaxM3()) > 0;
if (weightExceeds && volumeExceeds) {
sb.append("货物重量(").append(weight).append("吨)超过最大承重(")
.append(maxCapacityType.getWeightMaxTon()).append("吨),且货物体积(")
.append(volume).append("立方米)超过最大载方(")
.append(maxCapacityType.getVolumeMaxM3()).append("立方米)。");
} else if (weightExceeds) {
sb.append("货物重量(").append(weight).append("吨)超过最大承重(")
.append(maxCapacityType.getWeightMaxTon()).append("吨)。");
} else if (volumeExceeds) {
sb.append("货物体积(").append(volume).append("立方米)超过最大载方(")
.append(maxCapacityType.getVolumeMaxM3()).append("立方米)。");
}
sb.append("最大承载车型为:").append(maxCapacityType.getTypeName());
return sb.toString();
}
private static BigDecimal nvl(BigDecimal x) { private static BigDecimal nvl(BigDecimal x) {
return x == null ? BigDecimal.ZERO : x; return x == null ? BigDecimal.ZERO : x;
} }

View File

@@ -0,0 +1,51 @@
package com.delivery.project.ocr.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Data
@Component
@ConfigurationProperties(prefix = "qwen")
public class QwenProperties {
/**
* 通义千问 API Key
*/
private String apiKey;
/**
* OpenAI 兼容 base url例如
* https://dashscope.aliyuncs.com/compatible-mode/v1
*/
private String baseUrl;
/**
* 模型名称例如qwen-vl-ocr-latest
*/
private String model;
public String getApiKey() {
return apiKey;
}
public void setApiKey(String apiKey) {
this.apiKey = apiKey;
}
public String getBaseUrl() {
return baseUrl;
}
public void setBaseUrl(String baseUrl) {
this.baseUrl = baseUrl;
}
public String getModel() {
return model;
}
public void setModel(String model) {
this.model = model;
}
}

View File

@@ -0,0 +1,70 @@
package com.delivery.project.ocr.controller;
import com.delivery.project.ocr.service.QwenOcrService;
import lombok.Data;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.server.ResponseStatusException;
import java.util.HashMap;
import java.util.Map;
@RestController
@RequestMapping("/ocr")
public class OcrController {
@Autowired
private QwenOcrService qwenOcrService;
/**
* 方式一:上传文件(本地测试)
*/
@PostMapping("/extractErp")
public Map<String, Object> extractErpOrder(@RequestParam("file") MultipartFile file) {
if (file == null || file.isEmpty()) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "上传文件不能为空");
}
String erpOrderNo = qwenOcrService.extractErpOrderNo(file);
Map<String, Object> result = new HashMap<>();
result.put("success", true);
result.put("found", StringUtils.hasText(erpOrderNo));
result.put("erpOrderNo", erpOrderNo);
return result;
}
/**
* 方式二:智慧实物管理系统调用 —— 直接传 Base64 图片
*
* JSON 示例:
* {
* "imageBase64": "data:image/jpeg;base64,xxxxxx"
* }
*/
@PostMapping("/extractErpByBase64")
public Map<String, Object> extractErpByBase64(@RequestBody OcrBase64Request request) {
if (request == null || !StringUtils.hasText(request.getImageBase64())) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "imageBase64 不能为空");
}
String erpOrderNo = qwenOcrService.extractErpOrderNoFromBase64(request.getImageBase64());
Map<String, Object> result = new HashMap<>();
result.put("success", true);
result.put("found", StringUtils.hasText(erpOrderNo));
result.put("erpOrderNo", erpOrderNo);
return result;
}
/**
* 接收 Base64 JSON 的 DTO
*/
@Data
public static class OcrBase64Request {
private String imageBase64; // 支持带前缀的 data:image/jpeg;base64,xxxx
}
}

View File

@@ -0,0 +1,187 @@
package com.delivery.project.ocr.service;
import com.delivery.project.ocr.config.QwenProperties;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import okhttp3.*;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@Service
@RequiredArgsConstructor
public class QwenOcrService {
private final QwenProperties qwenProperties;
private final ObjectMapper objectMapper = new ObjectMapper();
// OkHttp 可复用并加超时
private final OkHttpClient httpClient = new OkHttpClient.Builder()
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(25, TimeUnit.SECONDS)
.writeTimeout(25, TimeUnit.SECONDS)
.build();
/**
* MultipartFile → OCR
*/
public String extractErpOrderNo(MultipartFile file) {
try {
byte[] bytes = file.getBytes();
String fileName = file.getOriginalFilename();
String contentType = file.getContentType();
return doExtractErpOrderNo(bytes, fileName, contentType);
} catch (IOException e) {
throw new RuntimeException("读取上传图片失败:" + e.getMessage(), e);
}
}
/**
* Base64 → OCR
* 支持含前缀data:image/jpeg;base64,xxxx
* 也支持纯 Base64 字串
*/
public String extractErpOrderNoFromBase64(String base64Str) {
// 1. 去掉 data:image/jpeg;base64, 前缀
String cleanBase64 = base64Str;
String contentType = "image/jpeg";
if (base64Str.startsWith("data:")) {
int commaIndex = base64Str.indexOf(",");
String meta = base64Str.substring(5, commaIndex); // image/jpeg;base64
cleanBase64 = base64Str.substring(commaIndex + 1);
int semiIndex = meta.indexOf(";");
if (semiIndex > 0) {
contentType = meta.substring(0, semiIndex);
} else {
contentType = meta;
}
}
byte[] bytes = Base64.getDecoder().decode(cleanBase64);
return doExtractErpOrderNo(bytes, "upload.jpg", contentType);
}
/**
* 核心 OCR 调用逻辑
*/
private String doExtractErpOrderNo(byte[] bytes, String fileName, String contentType) {
try {
if (contentType == null || contentType.isEmpty()) {
contentType = "image/jpeg";
}
// bytes → base64 → data-url
String base64 = Base64.getEncoder().encodeToString(bytes);
String dataUrl = "data:" + contentType + ";base64," + base64;
// ------------ 拼接 Qwen 请求体 ------------
Map<String, Object> root = new HashMap<String, Object>();
root.put("model", qwenProperties.getModel());
List<Map<String, Object>> messages = new ArrayList<Map<String, Object>>();
Map<String, Object> message = new HashMap<String, Object>();
message.put("role", "user");
List<Object> contentList = new ArrayList<Object>();
// 图片部分
Map<String, Object> imagePart = new HashMap<String, Object>();
imagePart.put("type", "image_url");
Map<String, Object> imageUrlMap = new HashMap<String, Object>();
imageUrlMap.put("url", dataUrl);
imagePart.put("image_url", imageUrlMap);
contentList.add(imagePart);
// 文本提示部分
Map<String, Object> textPart = new HashMap<String, Object>();
textPart.put("type", "text");
textPart.put("text",
"你是一个票据识别助手,请严格按以下要求识别:\n" +
"1. 找到“采购订单号(ERP)”对应的数字。\n" +
"2. 如果其下方是条形码,请返回条形码下面的数字。\n" +
"3. 图片中可能同时存在 ERP 与“采购订单号(电商交易专区)”,必须忽略电商交易专区的编号。\n" +
"4. 只返回数字,不要返回任何文字、标点或空格。\n" +
"5. 如果找不到,请返回空字符串。"
);
contentList.add(textPart);
message.put("content", contentList);
messages.add(message);
root.put("messages", messages);
String json = objectMapper.writeValueAsString(root);
// ⚠ 这里参数顺序:先 MediaType 再 jsonJDK8 + OkHttp3 正确用法)
RequestBody body = RequestBody.create(MediaType.parse("application/json"), json);
String url = qwenProperties.getBaseUrl() + "/chat/completions";
Request request = new Request.Builder()
.url(url)
.header("Authorization", "Bearer " + qwenProperties.getApiKey())
.header("Content-Type", "application/json")
.post(body)
.build();
Response response = httpClient.newCall(request).execute();
try {
if (!response.isSuccessful()) {
String errorBody = response.body() != null ? response.body().string() : "no error body";
throw new RuntimeException("调用通义千问失败:" + response.code() + ",错误信息:" + errorBody);
}
String resp = Objects.requireNonNull(response.body()).string();
JsonNode rootNode = objectMapper.readTree(resp);
JsonNode choices = rootNode.get("choices");
if (choices == null || !choices.isArray() || choices.size() == 0) {
return "";
}
String content = choices.get(0).get("message").get("content").asText("");
return extractDigits(content);
} finally {
response.close();
}
} catch (Exception e) {
throw new RuntimeException("调用通义千问异常:" + e.getMessage(), e);
}
}
/**
* 正则提取最像 ERP 的数字串
*/
private String extractDigits(String text) {
if (text == null || text.isEmpty()) {
return "";
}
Matcher m = Pattern.compile("\\d+").matcher(text);
List<String> nums = new ArrayList<String>();
while (m.find()) {
nums.add(m.group());
}
if (nums.isEmpty()) {
return "";
}
// 根据长度筛选一遍(可以根据你实际 ERP 号长度调整)
for (String s : nums) {
if (s.length() >= 8 && s.length() <= 12) {
return s;
}
}
// 兜底:返回第一段数字
return nums.get(0);
}
}

View File

@@ -52,8 +52,8 @@ spring:
active: druid active: druid
servlet: servlet:
multipart: multipart:
max-file-size: 10MB max-file-size: 20MB
max-request-size: 20MB max-request-size: 50MB
devtools: devtools:
restart: restart:
enabled: false enabled: false
@@ -128,3 +128,28 @@ upload:
wisdom: wisdom:
base-url: http://192.168.1.251:8086 base-url: http://192.168.1.251:8086
# =============== OCR 服务核心配置 ===============
ocr:
enabled: true
# 阿里云OCR配置
aliyun:
access-key: LTAI5tMi6fjhiQfDdzNpWavx
secret-key: PpDsoh11EptdQXcFDm9HkTB4Us4cd7
# 阿里云文档“服务接入点”里的正式 OCR endpoint
endpoint: ocr-api.cn-hangzhou.aliyuncs.com
region-id: cn-hangzhou
# 服务类型:通用文字识别 RecognizeGeneral
service-type: general
# 通义千问VL模型配置
qwen:
# 重要请替换为有效的通义千问API密钥
# 注意生产环境中应通过环境变量等方式配置避免暴露API密钥
api-key: "sk-3dfb3f7b049d4010bddaff3ceee08f94"
base-url: "https://dashscope.aliyuncs.com/compatible-mode/v1"
model: "qwen-vl-ocr-latest"

View File

@@ -11,6 +11,16 @@
<!-- 单号 --> <!-- 单号 -->
<result property="orderNo" column="order_no"/> <result property="orderNo" column="order_no"/>
<result property="billNoCk" column="bill_no_ck"/> <result property="billNoCk" column="bill_no_ck"/>
<!-- 新增rk_info 主键ID -->
<result property="rkInfoId" column="rk_info_id"/>
<!-- 新增制单人ID、接收物资状态、问题描述 -->
<result property="makerId" column="maker_id"/>
<result property="makerUserName" column="maker_user_name"/>
<result property="receiveStatus" column="receive_status"/>
<result property="receiveProblem" column="receive_problem"/>
<!-- 基础字段 --> <!-- 基础字段 -->
<result property="xmMs" column="xm_ms"/> <result property="xmMs" column="xm_ms"/>
<result property="xmNo" column="xm_no"/> <result property="xmNo" column="xm_no"/>
@@ -31,7 +41,7 @@
<result property="destLat" column="dest_lat" jdbcType="DECIMAL"/> <result property="destLat" column="dest_lat" jdbcType="DECIMAL"/>
<result property="deliveryDate" column="delivery_date"/> <result property="deliveryDate" column="delivery_date"/>
<result property="plateNo" column="plate_no"/> <result property="plateNo" column="plate_no"/>
<!-- 司机 --> <!-- 司机 -->
<result property="driverName" column="driver_name"/> <result property="driverName" column="driver_name"/>
@@ -96,58 +106,78 @@
<!-- ======================== 带附件的查询列(别名统一 dor ======================== --> <!-- ======================== 带附件的查询列(别名统一 dor ======================== -->
<sql id="selectDeliveryOrderVoWithAttach"> <sql id="selectDeliveryOrderVoWithAttach">
select select
dor.id, dor.order_no, dor.id,
dor.xm_ms, dor.xm_no, dor.wl_no, dor.wl_ms, dor.real_qty, dor.dw, dor.sap_no, dor.gys_mc, dor.order_no,
dor.remark, dor.origin_name, dor.origin_lng, dor.origin_lat, dor.bill_no_ck,
dor.dest_name, dor.dest_lng, dor.dest_lat, dor.rk_info_id,
dor.delivery_date, dor.plate_no, dor.order_status, dor.maker_id,
dor.driver_name, dor.driver_phone, -- 制单人用户名(连表 sys_user
su.user_name AS maker_user_name,
dor.receive_status,
dor.receive_problem,
dor.xm_ms, dor.xm_no, dor.wl_no, dor.wl_ms, dor.real_qty, dor.dw, dor.sap_no, dor.gys_mc,
dor.remark,
dor.origin_name, dor.origin_lng, dor.origin_lat,
dor.dest_name, dor.dest_lng, dor.dest_lat,
dor.delivery_date, dor.plate_no, dor.order_status,
dor.driver_name, dor.driver_phone,
dor.shipper_name, dor.shipper_phone,
dor.receiver_name, dor.receiver_phone, dor.receiver_org_name,
dor.delivery_ton,
dor.goods_size,
-- 车型外键与名称
dor.vehicle_type_id, dor.vehicle_type_name,
-- 费用/里程
dor.suggest_fee, dor.actual_fee, dor.toll_fee, dor.total_km,
dor.create_by, dor.create_time, dor.update_by, dor.update_time, dor.is_delete,
dor.shipper_name, dor.shipper_phone, -- 附件列att_ 前缀)
dor.receiver_name, dor.receiver_phone, dor.receiver_org_name, da.id AS att_id,
dor.delivery_ton, da.order_no AS att_order_no,
dor.goods_size, da.scene AS att_scene,
-- 车型外键与名称 da.biz_type AS att_biz_type,
dor.vehicle_type_id, dor.vehicle_type_name, da.url AS att_url,
-- 费用/里程 da.status AS att_status,
dor.suggest_fee, dor.actual_fee, dor.toll_fee, dor.total_km, da.sort_no AS att_sort_no,
dor.create_by, dor.create_time, dor.update_by, dor.update_time, dor.is_delete, da.remark AS att_remark,
da.create_by AS att_create_by,
-- 附件列att_ 前缀) da.create_time AS att_create_time,
da.id AS att_id, da.update_by AS att_update_by,
da.order_no AS att_order_no, da.update_time AS att_update_time,
da.scene AS att_scene, da.is_delete AS att_is_delete
da.biz_type AS att_biz_type,
da.url AS att_url,
da.status AS att_status,
da.sort_no AS att_sort_no,
da.remark AS att_remark,
da.create_by AS att_create_by,
da.create_time AS att_create_time,
da.update_by AS att_update_by,
da.update_time AS att_update_time,
da.is_delete AS att_is_delete
from delivery_order dor from delivery_order dor
left join delivery_attachment da left join delivery_attachment da
on da.order_no = dor.order_no on da.order_no = dor.order_no
and da.is_delete = '0' and da.is_delete = '0'
<!-- 报表/列表查询连表 sys_user按制单人ID 取用户名 -->
left join sys_user su
on su.user_id = dor.maker_id
</sql> </sql>
<!-- ======================== 不带附件的查询列 ======================== --> <!-- ======================== 不带附件的查询列 ======================== -->
<sql id="selectDeliveryOrderVo"> <sql id="selectDeliveryOrderVo">
select id, order_no, xm_ms, xm_no, wl_no, wl_ms, real_qty, dw, sap_no, gys_mc, remark, select
origin_name, origin_lng, origin_lat, dest_name, dest_lng, dest_lat, id,
delivery_date, plate_no, order_no,
-- 司机 bill_no_ck,
driver_name, driver_phone, rk_info_id,
shipper_name, shipper_phone, receiver_name, maker_id,
receiver_phone, receiver_org_name, delivery_ton, goods_size, order_status, receive_status,
-- 车型外键与名称 receive_problem,
vehicle_type_id, vehicle_type_name, xm_ms, xm_no, wl_no, wl_ms, real_qty, dw, sap_no, gys_mc, remark,
-- 费用/里程 origin_name, origin_lng, origin_lat,
suggest_fee, actual_fee, toll_fee, total_km, dest_name, dest_lng, dest_lat,
create_by, create_time, update_by, update_time, is_delete delivery_date, plate_no,
-- 司机
driver_name, driver_phone,
shipper_name, shipper_phone, receiver_name,
receiver_phone, receiver_org_name, delivery_ton, goods_size, order_status,
-- 车型外键与名称
vehicle_type_id, vehicle_type_name,
-- 费用/里程
suggest_fee, actual_fee, toll_fee, total_km,
create_by, create_time, update_by, update_time, is_delete
from delivery_order from delivery_order
</sql> </sql>
@@ -166,6 +196,14 @@
</otherwise> </otherwise>
</choose> </choose>
<!-- 制单人ID / 接收状态筛选(可选) -->
<if test="makerId != null">
and dor.maker_id = #{makerId}
</if>
<if test="receiveStatus != null">
and dor.receive_status = #{receiveStatus}
</if>
<if test="xmMs != null and xmMs != ''"> and dor.xm_ms = #{xmMs}</if> <if test="xmMs != null and xmMs != ''"> and dor.xm_ms = #{xmMs}</if>
<if test="xmNo != null and xmNo != ''"> and dor.xm_no = #{xmNo}</if> <if test="xmNo != null and xmNo != ''"> and dor.xm_no = #{xmNo}</if>
<if test="wlNo != null and wlNo != ''"> and dor.wl_no = #{wlNo}</if> <if test="wlNo != null and wlNo != ''"> and dor.wl_no = #{wlNo}</if>
@@ -175,10 +213,14 @@
<if test="sapNo != null and sapNo != ''"> and dor.sap_no = #{sapNo}</if> <if test="sapNo != null and sapNo != ''"> and dor.sap_no = #{sapNo}</if>
<if test="gysMc != null and gysMc != ''"> and dor.gys_mc = #{gysMc}</if> <if test="gysMc != null and gysMc != ''"> and dor.gys_mc = #{gysMc}</if>
<if test="originName != null and originName != ''"> and dor.origin_name like concat('%', #{originName}, '%')</if> <if test="originName != null and originName != ''">
and dor.origin_name like concat('%', #{originName}, '%')
</if>
<if test="originLng != null "> and dor.origin_lng = #{originLng}</if> <if test="originLng != null "> and dor.origin_lng = #{originLng}</if>
<if test="originLat != null "> and dor.origin_lat = #{originLat}</if> <if test="originLat != null "> and dor.origin_lat = #{originLat}</if>
<if test="destName != null and destName != ''"> and dor.dest_name like concat('%', #{destName}, '%')</if> <if test="destName != null and destName != ''">
and dor.dest_name like concat('%', #{destName}, '%')
</if>
<if test="destLng != null "> and dor.dest_lng = #{destLng}</if> <if test="destLng != null "> and dor.dest_lng = #{destLng}</if>
<if test="destLat != null "> and dor.dest_lat = #{destLat}</if> <if test="destLat != null "> and dor.dest_lat = #{destLat}</if>
@@ -186,18 +228,34 @@
<if test="plateNo != null and plateNo != ''"> and dor.plate_no = #{plateNo}</if> <if test="plateNo != null and plateNo != ''"> and dor.plate_no = #{plateNo}</if>
<!-- 司机筛选 --> <!-- 司机筛选 -->
<if test="driverName != null and driverName != ''"> and dor.driver_name like concat('%', #{driverName}, '%')</if> <if test="driverName != null and driverName != ''">
<if test="driverPhone != null and driverPhone != ''"> and dor.driver_phone = #{driverPhone}</if> and dor.driver_name like concat('%', #{driverName}, '%')
</if>
<if test="driverPhone != null and driverPhone != ''">
and dor.driver_phone = #{driverPhone}
</if>
<if test="shipperName != null and shipperName != ''"> and dor.shipper_name like concat('%', #{shipperName}, '%')</if> <if test="shipperName != null and shipperName != ''">
<if test="shipperPhone != null and shipperPhone != ''"> and dor.shipper_phone = #{shipperPhone}</if> and dor.shipper_name like concat('%', #{shipperName}, '%')
<if test="receiverName != null and receiverName != ''"> and dor.receiver_name like concat('%', #{receiverName}, '%')</if> </if>
<if test="receiverPhone != null and receiverPhone != ''"> and dor.receiver_phone = #{receiverPhone}</if> <if test="shipperPhone != null and shipperPhone != ''">
<if test="receiverOrgName != null and receiverOrgName != ''"> and dor.receiver_org_name like concat('%', #{receiverOrgName}, '%')</if> and dor.shipper_phone = #{shipperPhone}
</if>
<if test="receiverName != null and receiverName != ''">
and dor.receiver_name like concat('%', #{receiverName}, '%')
</if>
<if test="receiverPhone != null and receiverPhone != ''">
and dor.receiver_phone = #{receiverPhone}
</if>
<if test="receiverOrgName != null and receiverOrgName != ''">
and dor.receiver_org_name like concat('%', #{receiverOrgName}, '%')
</if>
<if test="deliveryTon != null "> and dor.delivery_ton = #{deliveryTon}</if> <if test="deliveryTon != null "> and dor.delivery_ton = #{deliveryTon}</if>
<if test="goodsSize != null"> and dor.goods_size = #{goodsSize}</if> <if test="goodsSize != null"> and dor.goods_size = #{goodsSize}</if>
<if test="orderStatus != null and orderStatus != ''"> and dor.order_status = #{orderStatus}</if> <if test="orderStatus != null and orderStatus != ''">
and dor.order_status = #{orderStatus}
</if>
<!-- 车型筛选 --> <!-- 车型筛选 -->
<if test="vehicleTypeId != null"> and dor.vehicle_type_id = #{vehicleTypeId}</if> <if test="vehicleTypeId != null"> and dor.vehicle_type_id = #{vehicleTypeId}</if>
@@ -250,7 +308,7 @@
</foreach> </foreach>
</if> </if>
<!-- 没有多选时,再走单个状态查询,兼容原有 App 逻辑 --> <!-- 没有多选时,再走单个状态查询 -->
<if test="(orderStatusList == null or orderStatusList.size == 0) <if test="(orderStatusList == null or orderStatusList.size == 0)
and orderStatus != null and orderStatus != ''"> and orderStatus != null and orderStatus != ''">
AND dor.order_status = #{orderStatus} AND dor.order_status = #{orderStatus}
@@ -267,21 +325,23 @@
ORDER BY MAX(dor.create_time) DESC ORDER BY MAX(dor.create_time) DESC
</select> </select>
<!-- 详情:同单号所有行 --> <!-- 详情:同单号所有行(带附件 + makerUserName -->
<select id="selectByOrderNo" parameterType="string" resultMap="DeliveryOrderResult"> <select id="selectByOrderNo"
SELECT * FROM delivery_order parameterType="string"
WHERE order_no = #{orderNo,jdbcType=VARCHAR} resultMap="DeliveryOrderWithAttachResult">
AND (is_delete = '0' OR is_delete = 0 OR is_delete IS NULL) <include refid="selectDeliveryOrderVoWithAttach"/>
ORDER BY id WHERE dor.order_no = #{orderNo}
AND (dor.is_delete = '0' OR dor.is_delete = 0 OR dor.is_delete IS NULL)
ORDER BY dor.id
</select> </select>
<!-- 单条查询:按单号 --> <!-- 单条查询:按单号(不带附件,简单版本) -->
<select id="selectDeliveryOrderByOrderNo" <select id="selectDeliveryOrderByOrderNo"
parameterType="string" parameterType="string"
resultMap="DeliveryOrderResult"> resultMap="DeliveryOrderResult">
SELECT * SELECT *
FROM delivery_order FROM delivery_order
WHERE order_no = #{orderNo,jdbcType=VARCHAR} WHERE order_no = #{orderNo}
AND (is_delete = '0' OR is_delete = 0 OR is_delete IS NULL) AND (is_delete = '0' OR is_delete = 0 OR is_delete IS NULL)
ORDER BY id ASC ORDER BY id ASC
</select> </select>
@@ -289,10 +349,18 @@
<!-- ======================== 插入 ======================== --> <!-- ======================== 插入 ======================== -->
<!-- 单条插入 --> <!-- 单条插入 -->
<insert id="insertDeliveryOrder" parameterType="DeliveryOrder" useGeneratedKeys="true" keyProperty="id"> <insert id="insertDeliveryOrder"
parameterType="com.delivery.project.document.domain.DeliveryOrder"
useGeneratedKeys="true" keyProperty="id">
insert into delivery_order insert into delivery_order
<trim prefix="(" suffix=")" suffixOverrides=","> <trim prefix="(" suffix=")" suffixOverrides=",">
<if test="orderNo != null">order_no,</if> <if test="orderNo != null">order_no,</if>
<if test="billNoCk != null">bill_no_ck,</if>
<if test="rkInfoId != null">rk_info_id,</if>
<if test="makerId != null">maker_id,</if>
<if test="receiveStatus != null">receive_status,</if>
<if test="receiveProblem != null">receive_problem,</if>
<if test="xmMs != null">xm_ms,</if> <if test="xmMs != null">xm_ms,</if>
<if test="xmNo != null">xm_no,</if> <if test="xmNo != null">xm_no,</if>
<if test="wlNo != null">wl_no,</if> <if test="wlNo != null">wl_no,</if>
@@ -339,6 +407,12 @@
</trim> </trim>
<trim prefix="values (" suffix=")" suffixOverrides=","> <trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="orderNo != null">#{orderNo,jdbcType=VARCHAR},</if> <if test="orderNo != null">#{orderNo,jdbcType=VARCHAR},</if>
<if test="billNoCk != null">#{billNoCk},</if>
<if test="rkInfoId != null">#{rkInfoId},</if>
<if test="makerId != null">#{makerId},</if>
<if test="receiveStatus != null">#{receiveStatus},</if>
<if test="receiveProblem != null">#{receiveProblem},</if>
<if test="xmMs != null">#{xmMs},</if> <if test="xmMs != null">#{xmMs},</if>
<if test="xmNo != null">#{xmNo},</if> <if test="xmNo != null">#{xmNo},</if>
<if test="wlNo != null">#{wlNo},</if> <if test="wlNo != null">#{wlNo},</if>
@@ -388,7 +462,7 @@
<!-- 批量插入 --> <!-- 批量插入 -->
<insert id="batchInsert" parameterType="java.util.List"> <insert id="batchInsert" parameterType="java.util.List">
INSERT INTO delivery_order INSERT INTO delivery_order
(order_no, bill_no_ck, (order_no, bill_no_ck, rk_info_id, maker_id, receive_status, receive_problem,
xm_ms, xm_no, wl_no, wl_ms, real_qty, dw, sap_no, gys_mc, remark, xm_ms, xm_no, wl_no, wl_ms, real_qty, dw, sap_no, gys_mc, remark,
origin_name, origin_lng, origin_lat, origin_name, origin_lng, origin_lat,
dest_name, dest_lng, dest_lat, dest_name, dest_lng, dest_lat,
@@ -404,6 +478,10 @@
( (
#{it.orderNo}, #{it.orderNo},
#{it.billNoCk}, #{it.billNoCk},
#{it.rkInfoId},
#{it.makerId},
#{it.receiveStatus},
#{it.receiveProblem},
#{it.xmMs}, #{it.xmNo}, #{it.wlNo}, #{it.wlMs}, #{it.realQty}, #{it.dw}, #{it.xmMs}, #{it.xmNo}, #{it.wlNo}, #{it.wlMs}, #{it.realQty}, #{it.dw},
#{it.sapNo}, #{it.gysMc}, #{it.remark}, #{it.sapNo}, #{it.gysMc}, #{it.remark},
@@ -424,11 +502,17 @@
</foreach> </foreach>
</insert> </insert>
<!-- ======================== 更新 ======================== --> <!-- ======================== 更新 ======================== -->
<update id="updateDeliveryOrder" parameterType="com.delivery.project.document.domain.DeliveryOrder"> <update id="updateDeliveryOrder"
parameterType="com.delivery.project.document.domain.DeliveryOrder">
UPDATE delivery_order UPDATE delivery_order
<trim prefix="SET" suffixOverrides=","> <trim prefix="SET" suffixOverrides=",">
<if test="billNoCk != null">bill_no_ck = #{billNoCk},</if>
<if test="rkInfoId != null">rk_info_id = #{rkInfoId},</if>
<if test="makerId != null">maker_id = #{makerId},</if>
<if test="receiveStatus != null">receive_status = #{receiveStatus},</if>
<if test="receiveProblem != null">receive_problem = #{receiveProblem},</if>
<if test="xmMs != null">xm_ms = #{xmMs},</if> <if test="xmMs != null">xm_ms = #{xmMs},</if>
<if test="xmNo != null">xm_no = #{xmNo},</if> <if test="xmNo != null">xm_no = #{xmNo},</if>
<if test="wlNo != null">wl_no = #{wlNo},</if> <if test="wlNo != null">wl_no = #{wlNo},</if>
@@ -479,11 +563,11 @@
</update> </update>
<!-- ======================== 删除 ======================== --> <!-- ======================== 删除 ======================== -->
<delete id="deleteDeliveryOrderById" parameterType="Long"> <delete id="deleteDeliveryOrderById" parameterType="java.lang.Long">
delete from delivery_order where id = #{id} delete from delivery_order where id = #{id}
</delete> </delete>
<delete id="deleteDeliveryOrderByIds" parameterType="String"> <delete id="deleteDeliveryOrderByIds" parameterType="java.lang.String">
delete from delivery_order where id in delete from delivery_order where id in
<foreach item="id" collection="array" open="(" separator="," close=")"> <foreach item="id" collection="array" open="(" separator="," close=")">
#{id} #{id}