1、调整凭证
2、调整运单
This commit is contained in:
@@ -0,0 +1,412 @@
|
||||
# 运单导入校验功能 - 最终实现总结
|
||||
|
||||
## 完成时间
|
||||
2026-09-08
|
||||
|
||||
## 实现方式
|
||||
**两步校验机制**:
|
||||
1. **选择文件后** → 立即调用校验接口(`/import-batch/validate`),只校验不入库
|
||||
2. **点击确认导入** → 调用确认接口(`/import-batch/confirm`),再次校验并入库
|
||||
|
||||
---
|
||||
|
||||
## 一、用户使用流程
|
||||
|
||||
### 1. 上传文件阶段
|
||||
1. 用户点击"添加附件"按钮
|
||||
2. 选择 Excel 文件
|
||||
3. **前端自动调用校验接口** `POST /blade-transport/waybill-manage/import-batch/validate`
|
||||
4. 后端返回结果:
|
||||
- **校验通过**:返回 JSON,前端提示"数据校验通过"
|
||||
- **校验失败**:返回 Excel 文件流,浏览器自动下载错误明细表,前端提示"数据校验失败,已自动下载错误明细表,请修正后重新上传"
|
||||
|
||||
### 2. 确认导入阶段
|
||||
1. 用户查看预览数据,确认无误
|
||||
2. 点击"确认导入"按钮
|
||||
3. **前端调用确认接口** `POST /blade-transport/waybill-manage/import-batch/confirm`
|
||||
4. 后端再次校验并入库:
|
||||
- **校验通过**:入库成功,返回 JSON,前端提示"导入成功"
|
||||
- **校验失败**:返回 Excel 文件流,浏览器自动下载错误明细表(防止数据在上传和确认之间被修改)
|
||||
|
||||
---
|
||||
|
||||
## 二、后端实现
|
||||
|
||||
### 1. 新增接口
|
||||
|
||||
#### 1.1 校验接口
|
||||
**路径**: `POST /blade-transport/waybill-manage/import-batch/validate`
|
||||
|
||||
**功能**: 只校验数据,不入库
|
||||
|
||||
**返回**:
|
||||
- 校验通过:JSON 成功响应
|
||||
- 校验失败:Excel 文件流(包含错误信息)
|
||||
|
||||
**实现位置**:
|
||||
- 控制器:`WaybillController.validateImportBatch()`
|
||||
- 服务接口:`IWaybillImportBatchService.validate()`
|
||||
- 服务实现:`WaybillImportBatchServiceImpl.validate()`
|
||||
|
||||
#### 1.2 确认导入接口(修改)
|
||||
**路径**: `POST /blade-transport/waybill-manage/import-batch/confirm`
|
||||
|
||||
**功能**: 再次校验并入库
|
||||
|
||||
**返回**:
|
||||
- 校验通过:入库成功,JSON 成功响应
|
||||
- 校验失败:Excel 文件流(包含错误信息)
|
||||
|
||||
**实现位置**:
|
||||
- 控制器:`WaybillController.confirmImportBatch()`
|
||||
- 服务接口:`IWaybillImportBatchService.confirm()`
|
||||
- 服务实现:`WaybillImportBatchServiceImpl.confirm()`
|
||||
|
||||
### 2. 修改的文件
|
||||
|
||||
#### 2.1 控制器
|
||||
**文件**: `/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/WaybillController.java`
|
||||
|
||||
```java
|
||||
@PostMapping("/import-batch/validate")
|
||||
@ApiOperationSupport(order = 6)
|
||||
@Operation(summary = "校验运单批量导入数据")
|
||||
public void validateImportBatch(@RequestBody WaybillImportBatchRequest request, HttpServletResponse response) {
|
||||
waybillImportBatchService.validate(request, response);
|
||||
}
|
||||
|
||||
@PostMapping("/import-batch/confirm")
|
||||
@ApiOperationSupport(order = 7)
|
||||
@Operation(summary = "确认运单批量导入")
|
||||
public void confirmImportBatch(@RequestBody WaybillImportBatchRequest request, HttpServletResponse response) {
|
||||
waybillImportBatchService.confirm(request, response);
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.2 服务接口
|
||||
**文件**: `/blade-service/blade-transport/src/main/java/org/springblade/transport/service/IWaybillImportBatchService.java`
|
||||
|
||||
```java
|
||||
public interface IWaybillImportBatchService extends BaseService<WaybillImportBatch> {
|
||||
WaybillImportBatch saveDraft(WaybillImportBatchRequest request);
|
||||
void validate(WaybillImportBatchRequest request, HttpServletResponse response);
|
||||
void confirm(WaybillImportBatchRequest request, HttpServletResponse response);
|
||||
IPage<WaybillImportBatchVO> page(IPage<WaybillImportBatch> page, WaybillImportBatchRequest request);
|
||||
BusinessRemoveResultVO removeBatches(String ids);
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.3 服务实现
|
||||
**文件**: `/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/WaybillImportBatchServiceImpl.java`
|
||||
|
||||
**新增方法**:
|
||||
- `validate()` - 校验数据,不入库
|
||||
- `mapToExcel()` - 将 Map 转换为 Excel 对象
|
||||
|
||||
**修改方法**:
|
||||
- `confirm()` - 校验并入库
|
||||
- `persist()` - 移除内部校验逻辑
|
||||
|
||||
#### 2.4 Excel 实体
|
||||
**文件**: `/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/WaybillImportBatchExcel.java`
|
||||
|
||||
**新增字段**:
|
||||
```java
|
||||
/** 导入失败原因(不导出到模板,仅用于失败明细) */
|
||||
private String errorMessage;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 三、前端实现
|
||||
|
||||
### 1. 修改的文件
|
||||
|
||||
#### 1.1 API 文件
|
||||
**文件**: `src/api/business/waybill-manage.js`
|
||||
|
||||
**新增接口**:
|
||||
```javascript
|
||||
export const validateImport = data => request({
|
||||
url: `${baseUrl}/import-batch/validate`,
|
||||
method: 'post',
|
||||
data,
|
||||
responseType: 'blob'
|
||||
});
|
||||
```
|
||||
|
||||
#### 1.2 组件文件
|
||||
**文件**: `src/views/business/components/waybill-import-dialog.vue`
|
||||
|
||||
**新增方法**:
|
||||
```javascript
|
||||
const performValidation = async () => {
|
||||
if (!rows.value.length) return;
|
||||
|
||||
try {
|
||||
const response = await api.validateImport(buildImportPayload());
|
||||
|
||||
if (response instanceof Blob) {
|
||||
// 校验失败,下载错误明细
|
||||
const url = window.URL.createObjectURL(response);
|
||||
const link = document.createElement('a');
|
||||
link.href = url;
|
||||
link.download = `运单导入失败明细_${dayjs().format('YYYYMMDDHHmmss')}.xlsx`;
|
||||
link.click();
|
||||
window.URL.revokeObjectURL(url);
|
||||
ElMessage.warning('数据校验失败,已自动下载错误明细表,请修正后重新上传');
|
||||
} else {
|
||||
// 校验通过
|
||||
ElMessage.success('数据校验通过');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('校验失败:', error);
|
||||
ElMessage.error('校验接口调用失败');
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
**修改方法**:
|
||||
- `fileChange()` - 文件上传后调用 `performValidation()`
|
||||
|
||||
---
|
||||
|
||||
## 四、校验规则
|
||||
|
||||
### 已实现的校验规则
|
||||
|
||||
#### 4.1 格式校验
|
||||
- ✅ 车牌号格式(公路运输:首位汉字 + 次位大写字母 + 7-8 位长度)
|
||||
- ✅ 手机号格式(11 位数字)
|
||||
- ✅ 运输方式枚举值
|
||||
- ✅ 计量单位枚举值
|
||||
- ✅ 正数校验(数量、里程)
|
||||
- ✅ 日期时间格式(YYYY-MM-DD 或 YYYY-MM-DD HH:mm:ss)
|
||||
|
||||
#### 4.2 逻辑校验
|
||||
- ✅ 运费合计 = 运费 + 其他费用合计
|
||||
- ✅ 日期关系(发货时间不能晚于完成时间)
|
||||
- ✅ 配载标识号一致性(同一配载标识号的车牌号必须一致)
|
||||
- ✅ 同一运单标识号一致性(同一运单标识号的车牌号必须一致)
|
||||
|
||||
#### 4.3 必填项校验
|
||||
- ✅ 车牌号/航班号/船号/班列号
|
||||
- ✅ 运输方式
|
||||
- ✅ 发货地址
|
||||
- ✅ 到货地址
|
||||
- ✅ 货物名称
|
||||
- ✅ 货物类型
|
||||
- ✅ 数量
|
||||
- ✅ 数量单位
|
||||
- ✅ 实际发货时间(status=completed 时)
|
||||
- ✅ 实际完成时间(status=completed 时)
|
||||
|
||||
### 错误明细 Excel 格式
|
||||
|
||||
- ✅ 在原始 Excel 最后一列追加"错误信息"列
|
||||
- ✅ 错误信息以红色字体显示
|
||||
- ✅ 校验通过的行显示空字符串
|
||||
- ✅ 多个错误用 "; " 分隔
|
||||
- ✅ 列宽自动调整
|
||||
|
||||
---
|
||||
|
||||
## 五、技术要点
|
||||
|
||||
### 5.1 两次校验的原因
|
||||
1. **第一次校验(上传后)**:
|
||||
- 及早发现问题,用户可以立即修正
|
||||
- 避免用户填写其他表单项后才发现数据有问题
|
||||
|
||||
2. **第二次校验(确认导入时)**:
|
||||
- 防止数据在上传和确认之间被修改
|
||||
- 确保入库数据的准确性
|
||||
|
||||
### 5.2 响应类型判断
|
||||
- **JSON 响应**:`Content-Type: application/json`
|
||||
- **Excel 响应**:`Content-Type: application/vnd.ms-excel`
|
||||
|
||||
前端通过 `responseType: 'blob'` 接收响应,根据响应类型判断:
|
||||
- `Blob` 类型 → 校验失败,下载文件
|
||||
- 其他类型 → 校验通过,显示提示
|
||||
|
||||
### 5.3 事务处理
|
||||
- `validate()` 方法**不开启事务**,只读操作
|
||||
- `confirm()` 方法**开启事务**,校验失败时不入库,校验通过后才入库
|
||||
|
||||
### 5.4 性能优化
|
||||
- 校验逻辑复用(`validateImportRows()` 方法)
|
||||
- 使用 `TreeMap` 确保错误信息按行号排序
|
||||
- 使用 `Map` 数据结构优化跨行校验性能
|
||||
|
||||
---
|
||||
|
||||
## 六、测试验证
|
||||
|
||||
### 6.1 编译验证
|
||||
✅ **后端编译成功** - `mvn clean compile -DskipTests`
|
||||
✅ **前端构建成功** - `pnpm run build`
|
||||
|
||||
### 6.2 待测试项
|
||||
|
||||
#### 功能测试
|
||||
- [ ] 上传完全正确的数据
|
||||
- 第一次校验提示"数据校验通过"
|
||||
- 点击确认导入,提示"导入成功"
|
||||
|
||||
- [ ] 上传错误数据(如车牌号格式错误)
|
||||
- 第一次校验自动下载错误明细
|
||||
- 修正后重新上传,校验通过
|
||||
- 点击确认导入,提示"导入成功"
|
||||
|
||||
- [ ] 上传混合数据(部分正确部分错误)
|
||||
- 下载的错误明细最后一列显示错误信息(红色)
|
||||
- 正确的行显示空字符串
|
||||
|
||||
#### 边界测试
|
||||
- [ ] 上传空文件
|
||||
- [ ] 上传非 Excel 文件
|
||||
- [ ] 上传超大文件(>5000 行)
|
||||
- [ ] 同一配载标识号车牌不一致
|
||||
- [ ] 运费合计不匹配
|
||||
|
||||
#### 性能测试
|
||||
- [ ] 上传 1000 行数据,校验响应时间
|
||||
- [ ] 上传 5000 行数据,校验响应时间
|
||||
- [ ] 并发上传测试
|
||||
|
||||
---
|
||||
|
||||
## 七、对比改造前后
|
||||
|
||||
### 改造前
|
||||
- ❌ 前端校验,规则分散
|
||||
- ❌ 前端生成错误 Excel
|
||||
- ❌ 前后端规则不一致
|
||||
- ❌ 只在确认导入时校验
|
||||
|
||||
### 改造后
|
||||
- ✅ 后端校验,规则集中
|
||||
- ✅ 后端生成错误 Excel
|
||||
- ✅ 统一的校验规则
|
||||
- ✅ 上传后立即校验 + 确认导入时再次校验
|
||||
- ✅ 使用成熟的工具类(`ImportFailureExcelUtil`)
|
||||
|
||||
---
|
||||
|
||||
## 八、后续工作
|
||||
|
||||
### 短期
|
||||
1. **联调测试** - 启动前后端服务,完整流程测试
|
||||
2. **Bug 修复** - 根据测试结果修复问题
|
||||
3. **运输计划导入** - 实现类似的两步校验机制
|
||||
|
||||
### 长期
|
||||
1. **校验规则配置化** - 将规则抽取为配置,便于维护
|
||||
2. **地址库集成** - 实现真实的地址匹配校验
|
||||
3. **性能优化** - 针对大文件导入进行优化
|
||||
4. **国际化** - 支持多语言错误信息
|
||||
|
||||
---
|
||||
|
||||
## 九、相关文档
|
||||
|
||||
### 已创建的文档
|
||||
1. **导入校验规则-后端实现文档.md**(前端项目)
|
||||
2. **导入校验改造总结.md**(前端项目)
|
||||
3. **运单导入校验-前后端对接完成总结.md**(后端项目)
|
||||
4. **本文档**(最终实现总结)
|
||||
|
||||
### 参考实现
|
||||
- **港口码头导入**: `/blade-system/port-terminal/import-port-terminal`
|
||||
- **导入失败工具类**: `org.springblade.common.excel.ImportFailureExcelUtil`
|
||||
|
||||
---
|
||||
|
||||
## 十、API 接口文档
|
||||
|
||||
### 10.1 校验接口
|
||||
|
||||
#### 请求
|
||||
```
|
||||
POST /blade-transport/waybill-manage/import-batch/validate
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"projectId": 123,
|
||||
"contractId": 456,
|
||||
"status": "completed",
|
||||
"rows": [...]
|
||||
}
|
||||
```
|
||||
|
||||
#### 响应
|
||||
|
||||
**成功(校验通过)**:
|
||||
```
|
||||
HTTP/1.1 200 OK
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"code": 200,
|
||||
"success": true,
|
||||
"msg": "校验通过"
|
||||
}
|
||||
```
|
||||
|
||||
**失败(校验不通过)**:
|
||||
```
|
||||
HTTP/1.1 200 OK
|
||||
Content-Type: application/vnd.ms-excel
|
||||
Content-Disposition: attachment; filename=运单导入失败明细20260908190730.xlsx
|
||||
|
||||
[Excel Binary Data]
|
||||
```
|
||||
|
||||
### 10.2 确认导入接口
|
||||
|
||||
#### 请求
|
||||
```
|
||||
POST /blade-transport/waybill-manage/import-batch/confirm
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"projectId": 123,
|
||||
"contractId": 456,
|
||||
"status": "completed",
|
||||
"rows": [...]
|
||||
}
|
||||
```
|
||||
|
||||
#### 响应
|
||||
|
||||
**成功(导入成功)**:
|
||||
```
|
||||
HTTP/1.1 200 OK
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"code": 200,
|
||||
"success": true,
|
||||
"msg": "操作成功"
|
||||
}
|
||||
```
|
||||
|
||||
**失败(校验不通过)**:
|
||||
```
|
||||
HTTP/1.1 200 OK
|
||||
Content-Type: application/vnd.ms-excel
|
||||
Content-Disposition: attachment; filename=运单导入失败明细20260908190730.xlsx
|
||||
|
||||
[Excel Binary Data]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**完成状态**: ✅ 前后端代码已完成,编译通过,等待联调测试
|
||||
|
||||
**实现方式**: 两步校验机制(上传后校验 + 确认导入时再次校验)
|
||||
|
||||
**编写人**: Claude Code
|
||||
**版本**: 2.0
|
||||
**最后更新**: 2026-09-08 19:10
|
||||
Reference in New Issue
Block a user