67 KiB
WMS-QMS 质检集成需求与方案文档
文档版本历史
| 版本 | 日期 | 修订内容 | 作者 |
|---|---|---|---|
| 1.0 | 2026-01-15 | 初始版本,完整需求与方案设计 | zch |
| 1.1 | 2026-01-15 | 新增独立文件实现,添加代码实现章节 | zch |
| 1.2 | 2026-01-15 | 修复Code Review问题:创建独立 Dubbo 实现类、修复状态映射、添加批次号过滤 | zch |
| 1.3 | 2026-01-15 | 重构QMS回调WMS逻辑:1.入库改由PDA扫码完成 2.回调只更新质检状态 3.抽离回调方法便于替换/注释 4.支持检测类型4(原材料检)和7(入库检) 5.新增让步接收回调 | zch |
目录
1. 需求概述
1.1 背景
WMS(仓储管理系统)与 QMS(质量管理系统)需要实现质检业务闭环,支持以下场景:
- WMS 主动发起质检:通过前端界面手动选择批次,调用 QMS 创建质检任务
- QMS 质检完成回调:质检完成后通知 WMS,更新批次质检状态
- PDA 扫码入库:根据质检状态,PDA 扫码完成实际入库操作
- 让步接收闭环:不合格品让步接收后,支持 PDA 扫码入库
1.2 核心需求
| 需求点 | 说明 | 优先级 |
|---|---|---|
| 手动发起质检 | 打印标签后不自动创建质检任务,通过前端界面手动选择批次发起 | 高 |
| 质检完成通知 | QMS 质检完成后回调 WMS,更新批次质检状态 | 高 |
| 合格数量入库 | 质检合格的批次,PDA 扫码完成入库 | 高 |
| 让步接收入库 | 不合格品让步接收后,PDA 扫码完成入库 | 中 |
| 批次追溯 | 全流程保持批次号一致性,确保可追溯 | 高 |
1.3 核心数据表
- 质检qms模块通用模块版本只关注qc_inspection_item、qc_inspection_item_category、qc_inspection_main、qc_inspection_main_file_relation、qc_inspection_result、qc_inspection_template、qc_template_item、qc_unqualified_record、qc_unqualified_review!
- hwmom-wms核心关注base_measurement_unit_info、base_supplier_info1、base_material_category、base_material_type、base_material_info_copy1 / base_material_info、wms_base_customer、wms_base_area、wms_base_location、wms_base_warehouse、wms_allocate_order、wms_allocate_order_detail、wms_allocate_task、wms_check_task、wms_purchase_order、wms_purchase_order_detail、wms_instock_detail、wms_instock_order、wms_instock_print、wms_instock_record、wms_inventory、wms_inventory_check、wms_inventory_check_record、wms_outstock_detail、wms_outstock_order、wms_outstock_record!
2. 业务流程
2.1 整体业务流程图
┌─────────────────────────────────────────────────────────────────┐
│ WMS-QMS 质检闭环完整流程 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 【步骤1】打印标签 │
│ ├─ 生成 wms_instock_print 记录 │
│ ├─ inspection_type='0'(未发起) │
│ └─ inspection_request='0/1'(必检/免检) │
│ │
│ 【步骤2】前端发起质检(新功能) │
│ ├─ qc.vue 界面选择批次 │
│ ├─ 过滤:inspection_request='0'且 inspection_type='0' │
│ ├─ 点击"发起质检"按钮 │
│ └─ POST /wms/instockPrint/createInspection │
│ │
│ 【步骤3】WMS 调用 QMS 创建质检任务 │
│ ├─ @GlobalTransactional(调用方加注解) │
│ ├─ remoteQmsInspectionService.createInspectionTaskForWMS() │
│ └─ 更新打印记录:inspection_type='1'(质检中) │
│ │
│ 【步骤4】PDA 质检执行 │
│ ├─ 质检员填写检测结果 │
│ ├─ 保存到 qc_inspection_result │
│ └─ 更新 qc_inspection_main:status='1', result='0/1' │
│ qualifiedQty=?, unqualifiedQty=? │
│ │
│ 【步骤5】QMS 回调 WMS(质检完成) │
│ ├─ @GlobalTransactional(调用方加注解) │
│ ├─ remoteWmsInspectionService.notifyInspectionComplete() │
│ ├─ 传递批次号和合格/不合格数量 │
│ └─ 只更新质检状态,❌ 不创建入库记录 │
│ ├─ 合格:inspection_type='2' │
│ └─ 不合格:inspection_type='3' │
│ │
│ 【步骤6】PDA 扫码入库(合格批次) │
│ ├─ POST /pda/instock/submit │
│ ├─ 过滤:inspection_type='2'(合格) │
│ ├─ 创建 wms_instock_record 记录 │
│ ├─ 更新库存 wms_inventory │
│ └─ 更新打印记录:inbound_status='1'(已入库) │
│ │
│ 【步骤7】让步接收(待讨论) │
│ ├─ 不合格品评审 → 让步接收 │
│ ├─ QMS 回调 WMS(与步骤5类似) │
│ ├─ 更新质检状态:inspection_type='2'(合格) │
│ └─ PDA 扫码入库(与步骤6类似) │
│ │
└─────────────────────────────────────────────────────────────────┘
2.2 状态流转说明
inspection_type(质检状态)字段
| 值 | 说明 | 触发条件 | 后续操作 |
|---|---|---|---|
| 0 | 未发起 | 打印标签后默认 | 前端发起质检 |
| 1 | 质检中 | WMS 调用 QMS 创建质检任务后 | PDA 质检执行 |
| 2 | 合格 | QMS 回调 WMS 更新状态后 | PDA 扫码入库 |
| 3 | 不合格 | QMS 回调 WMS 更新状态后 | 不合格品评审 |
inbound_status(入库状态)字段
| 值 | 说明 | 触发条件 |
|---|---|---|
| 0 | 待入库 | 打印标签后默认 |
| 1 | 已入库 | PDA 扫码入库成功 |
| 2 | 入库中 | 预留,当前未使用 |
| 3 | 拒收 | 预留,当前未使用 |
3. 数据模型
3.1 核心数据表
wms_instock_print(入库打印记录表)
| 字段 | 类型 | 说明 | 示例 |
|---|---|---|---|
| instock_print_id | bigint | 主键 | 123 |
| instock_code | varchar(64) | 入库单号 | RK202501150001 |
| batch_code | varchar(64) | 批次号 | BATCH001 |
| material_code | varchar(64) | 物料编码 | MAT001 |
| material_name | varchar(128) | 物料名称 | 橡胶 |
| apportion_qty | decimal(16,2) | 条码数量(分包数量) | 100.00 |
| inspection_request | char(1) | 质检要求(0=必检,1=免检) | 0 |
| inspection_type | char(1) | 质检状态(0未发起,1质检中,2合格,3不合格) | 1 |
| inbound_status | char(1) | 入库状态(0待入库,1已入库) | 0 |
| actual_inbound_time | datetime | 实际入库时间 | 2026-01-15 10:00:00 |
wms_instock_record(入库记录表)
| 字段 | 类型 | 说明 | 示例 |
|---|---|---|---|
| instock_record_id | bigint | 主键 | 456 |
| instock_code | varchar(64) | 入库单号 | RK202501150001 |
| batch_code | varchar(64) | 批次号 | BATCH001 |
| material_code | varchar(64) | 物料编码 | MAT001 |
| instock_qty | decimal(16,2) | 入库数量 | 100.00 |
| instock_time | datetime | 入库时间 | 2026-01-15 10:00:00 |
wms_inventory(库存表)
| 字段 | 类型 | 说明 |
|---|---|---|
| inventory_id | bigint | 主键 |
| batch_code | varchar(64) | 批次号 |
| material_id | bigint | 物料ID |
| store_id | bigint | 仓库ID |
| location_code | varchar(64) | 库位编码 |
| inventory_qty | decimal(16,2) | 库存数量 |
| lock_state | char(1) | 锁定状态(0未锁定,1锁定) |
| inventory_status | char(1) | 库存状态(0归零,1正常) |
qc_inspection_main(质检主表)
| 字段 | 类型 | 说明 | 关联关系 |
|---|---|---|---|
| inspection_id | bigint | 质检主键 | - |
| inspection_no | varchar(64) | 质检单号 | - |
| template_id | bigint | 模板ID | - |
| material_code | varchar(64) | 物料编码 | 关联 wms_instock_print |
| inspection_qty | decimal(16,2) | 质检数量 | - |
| qualified_qty | decimal(16,2) | 合格数量 | - |
| unqualified_qty | decimal(16,2) | 不合格数量 | - |
| result | char(1) | 质检结果(0合格,1不合格) | - |
| status | char(1) | 单据状态(0未处理,1完成) | - |
| batch_no | varchar(64) | 批次号 | 关联 wms_instock_print.batch_code |
| production_order | varchar(64) | 生产订单号/业务来源单号 | 可存储入库单号 |
| remark | varchar(512) | 备注 | 可存储入库单号 |
4. 接口设计
4.1 WMS 发起质检接口
接口定义
/**
* 批量发起质检任务
* 前端选择批次后调用此接口创建质检任务
*
* @param prints 选中的打印记录列表
* @return Map<批次号, 质检单号或错误信息>
*/
@PostMapping("/createInspection")
public R<Map<String, String>> createInspection(@RequestBody List<WmsInstockPrintBo> prints)
请求参数
[
{
"instockPrintId": 123,
"instockCode": "RK202501150001",
"batchCode": "BATCH001",
"materialCode": "MAT001",
"materialName": "橡胶",
"apportionQty": 100.00,
"inspectionRequest": "0"
},
{
"instockPrintId": 124,
"instockCode": "RK202501150001",
"batchCode": "BATCH002",
"materialCode": "MAT001",
"materialName": "橡胶",
"apportionQty": 50.00,
"inspectionRequest": "0"
}
]
响应结果
{
"code": 200,
"msg": "操作成功",
"data": {
"BATCH001": "QC202501150001",
"BATCH002": "QC202501150002"
}
}
4.2 QMS 质检完成回调接口(WMS 暴露给 QMS)
Dubbo 接口定义
/**
* WMS 暴露给 QMS 的质检完成回调接口
* QMS 质检完成后调用此接口通知 WMS
*/
public interface RemoteWmsInspectionService {
/**
* 质检完成回调
* @param notification 质检完成通知
*/
void notifyInspectionComplete(InspectionCompleteNotification notification);
}
回调参数
public class InspectionCompleteNotification implements Serializable {
private String inspectionNo; // 质检单号(必填)
private String instockCode; // 入库单号(必填)
private String batchCode; // 批次号(必填,精确匹配)
private String result; // 质检结果:0=合格,1=不合格(必填)
private String status; // 质检状态:0=未处理,1=已完成(必填)
private BigDecimal qualifiedQty; // 合格数量(必填)
private BigDecimal unqualifiedQty; // 不合格数量(必填)
private String materialCode; // 物料编码(可选)
private String inspectionEndTime; // 质检完成时间(可选)
}
5. 新增文件清单
5.1 设计原则
核心原则:独立新建Java文件,不修改现有文件,便于查看和调试
- 模块化设计:将WMS-QMS集成相关的业务逻辑抽离到独立的服务类
- 复用现有服务:新文件可调用现有服务方法,避免重复代码
- 便于维护:独立文件便于定位问题、调试和后续扩展
- 职责清晰:每个文件职责单一,符合SOLID原则
5.2 WMS模块新增文件
| 文件路径 | 类型 | 说明 |
|---|---|---|
service/IWmsInspectionInitiationService.java |
接口 | WMS发起质检服务接口 |
service/impl/WmsInspectionInitiationServiceImpl.java |
实现 | WMS发起质检服务实现 |
service/IWmsInspectionCallbackService.java |
接口 | WMS接收QMS回调服务接口 |
service/impl/WmsInspectionCallbackServiceImpl.java |
实现 | WMS接收QMS回调服务实现(本地服务) |
dubbo/RemoteWmsInspectionCallbackServiceImpl.java |
实现 | WMS Dubbo服务实现(实现RemoteWmsInstockService) |
controller/api/apiController.java |
修改 | REST API回调接口(添加@Transactional、批次号过滤、状态映射) |
controller/WmsInstockPrintController.java |
修改 | 添加createInspection端点 |
5.3 QMS模块新增文件
| 文件路径 | 类型 | 说明 |
|---|---|---|
service/IQcWmsCallbackService.java |
接口 | QMS回调WMS服务接口 |
service/impl/QcWmsCallbackServiceImpl.java |
实现 | QMS回调WMS服务实现 |
5.4 API模块修改文件
| 文件路径 | 类型 | 说明 |
|---|---|---|
hwmom-api-qms/src/main/java/org/dromara/qms/api/dto/InspectionCompleteNotification.java |
修改 | 添加batchCode字段 |
5.5 文件依赖关系图
┌─────────────────────────────────────────────────────────────────┐
│ 新增文件依赖关系 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 【WMS模块】 │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ WmsInstockPrintController │ │
│ │ └── @PostMapping("/createInspection") │ │
│ │ └── IWmsInspectionInitiationService │ │
│ └──────────────────────────────────────────────────────────┘ │
│ ↓ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ WmsInspectionInitiationServiceImpl │ │
│ │ ├── @DubboReference RemoteQmsInspectionService │ │
│ │ └── 复用 IWmsInstockPrintService │ │
│ └──────────────────────────────────────────────────────────┘ │
│ ↓ │
│ 【调用QMS Dubbo】 │
│ │
│ 【QMS模块】 │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ QcPDAServiceImpl (现有文件,复用) │ │
│ │ └── submitInspection() │ │
│ │ └── 调用 IQcWmsCallbackService.notifyWmsForQualified│ │ │
│ └──────────────────────────────────────────────────────────┘ │
│ ↓ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ QcWmsCallbackServiceImpl (新增) │ │
│ │ ├── @DubboReference RemoteWmsInspectionService │ │
│ │ └── 复用 IQcInspectionMainService │ │
│ └──────────────────────────────────────────────────────────┘ │
│ ↓ │
│ 【回调WMS Dubbo】 │
│ │
│ 【WMS模块】 │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ WmsInspectionCallbackServiceImpl (@DubboService) │ │
│ │ └── handleInspectionComplete() │ │
│ │ └── 复用 IWmsInstockPrintService.updateByBo() │ │
│ └──────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
5.6 关键代码示例
WMS发起质检服务实现
// WmsInspectionInitiationServiceImpl.java
@Service
public class WmsInspectionInitiationServiceImpl implements IWmsInspectionInitiationService {
@DubboReference(timeout = 300000)
private RemoteQmsInspectionService remoteQmsInspectionService;
private final IWmsInstockPrintService instockPrintService;
@Override
public Map<String, String> createInspection(List<WmsInstockPrintBo> prints) {
Map<String, String> result = new LinkedHashMap<>();
for (WmsInstockPrintBo print : prints) {
// 1. 校验是否可以发起质检
String reason = getCannotInitiateReason(print);
if (reason != null) {
result.put(print.getBatchCode(), "跳过: " + reason);
continue;
}
// 2. 调用 QMS Dubbo 接口
String inspectionNo = remoteQmsInspectionService.createInspectionTaskForWMS(request);
// 3. 更新本地状态
updateInspectionType(print.getInstockPrintId(), "1");
result.put(print.getBatchCode(), inspectionNo);
}
return result;
}
}
QMS回调WMS服务实现
// QcWmsCallbackServiceImpl.java
@Service
public class QcWmsCallbackServiceImpl implements IQcWmsCallbackService {
@DubboReference(timeout = 300000)
private RemoteWmsInstockService remoteWmsInstockService;
private final IQcInspectionMainService qcInspectionMainService;
@Override
public boolean notifyWmsForQualified(QcInspectionMainVo main) {
// 1. 检查是否来自WMS入库
if (!isFromWmsInspection(main)) {
return false;
}
// 2. 构建通知参数
InspectionCompleteNotification notification = buildNotification(main, null, false);
// 3. 调用 WMS Dubbo 接口
R<Void> result = remoteWmsInstockService.completeInstockAfterInspection(notification);
return result != null && result.getCode() == R.SUCCESS;
}
}
WMS接收QMS回调服务实现(本地服务)
// WmsInspectionCallbackServiceImpl.java
@Service
public class WmsInspectionCallbackServiceImpl implements IWmsInspectionCallbackService {
private final IWmsInstockPrintService instockPrintService;
@Override
public boolean handleInspectionComplete(InspectionCompleteNotification notification) {
// 1. 精确查询打印记录(instockCode + batchCode)
WmsInstockPrintVo printVo = queryByInstockAndBatch(
notification.getInstockCode(),
notification.getBatchCode()
);
// 2. 根据质检结果更新状态(inspectionType: 2=合格, 3=不合格)
if ("0".equals(notification.getResult())) {
return updateToQualified(notification.getInstockCode(), notification.getBatchCode());
} else {
return updateToUnqualified(notification.getInstockCode(), notification.getBatchCode());
}
}
}
WMS Dubbo服务实现(独立类)
// RemoteWmsInspectionCallbackServiceImpl.java
@Service
@DubboService
public class RemoteWmsInspectionCallbackServiceImpl implements RemoteWmsInstockService {
private final IWmsInspectionCallbackService wmsInspectionCallbackService;
@Override
public R<Void> completeInstockAfterInspection(InspectionCompleteNotification notification) {
// 委托给本地服务处理
boolean success = wmsInspectionCallbackService.handleInspectionComplete(notification);
return success ? R.ok("质检回调处理成功") : R.fail("质检回调处理失败");
}
}
6. 技术实现方案
6.1 模块依赖关系
┌─────────────────────────────────────────────────────────────────┐
│ 模块依赖关系 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ WMS │────────>│ QMS │<────────│ PDA │ │
│ │ 模块 │ Dubbo │ 模块 │ Dubbo │ 扫码入库 │ │
│ └─────────┘ └─────────┘ └─────────┘ │
│ ▲ ▲ │
│ │ HTTP │ │
│ ┌────┴────┐ ┌────┴────┐ │
│ │前端 qc.vue│ │ PDA设备 │ │
│ └─────────┘ └─────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
6.2 技术栈
| 技术点 | 使用方式 | 说明 |
|---|---|---|
| 微服务框架 | Spring Cloud Alibaba + Dubbo 3.x | 服务间 RPC 调用 |
| 分布式事务 | Seata @GlobalTransactional | 跨服务数据一致性 |
| 认证授权 | Sa-Token | 用户身份验证 |
| 多租户 | TenantHelper | 租户隔离 |
6.3 核心代码结构
WMS 模块
hwmom/ruoyi-modules/hwmom-wms/
├── controller/
│ └── api/
│ ├── WmsPdaApiController.java # PDA 入库接口
│ └── WmsInstockPrintController.java # 打印记录管理接口
├── service/
│ ├── IWmsInstockPrintService.java # 打印记录服务接口
│ └── impl/
│ └── WmsInstockPrintServiceImpl.java # 打印记录服务实现
└── dubbo/
└── RemoteQmsInspectionService.java # QMS Dubbo 接口引用
QMS 模块
hwmom/ruoyi-modules/hwmom-qms/
├── controller/
│ └── QcPDAController.java # PDA 质检接口
├── service/
│ ├── IQcPDAService.java # PDA 质检服务接口
│ ├── IQcWmsService.java # WMS 仓储质检服务接口
│ └── impl/
│ ├── QcPDAServiceImpl.java # PDA 质检服务实现
│ └── QcWmsServiceImpl.java # WMS 仓储质检服务实现
└── dubbo/
└── RemoteQmsInspectionServiceImpl.java # QMS Dubbo 服务实现
7. 前端改造
7.1 界面文件
文件路径:hwmom-ui/src/views/wms/instockPrint/qc.vue
7.2 新增按钮
<!-- 在工具栏添加"发起质检"按钮 -->
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
type="primary"
plain
icon="VideoPlay"
:disabled="multiple"
@click="handleCreateInspection"
v-hasPermi="['wms:instockPrint:createInspection']">
发起质检
</el-button>
</el-col>
</el-row>
7.3 按钮点击处理
/** 发起质检按钮操作 */
const handleCreateInspection = async () => {
// 1. 过滤:只处理必检(inspectionRequest='0')且未发起(inspectionType='0'或null)的批次
const validRecords = vos.value.filter(item =>
item.inspectionRequest === '0' &&
(!item.inspectionType || item.inspectionType === '0')
);
if (validRecords.length === 0) {
proxy?.$modal.msgWarning("请选择需要发起质检的批次(必检且未发起)");
return;
}
// 2. 确认提示
await proxy?.$modal.confirm(
`确认为选中的 ${validRecords.length} 个批次发起质检吗?`
);
// 3. 调用接口
loading.value = true;
try {
const res = await createInspection(validRecords);
proxy?.$modal.msgSuccess("质检任务创建成功");
await getList();
} catch (error) {
console.error("发起质检失败", error);
} finally {
loading.value = false;
}
};
7.4 API 定义
// api/wms/instockPrint/index.ts
/**
* 批量发起质检任务
* @param prints 选中的打印记录列表
*/
export const createInspection = (prints: InstockPrintVO[]) => {
return request({
url: '/wms/instockPrint/createInspection',
method: 'post',
data: prints
});
};
8. 分布式事务处理
8.1 Dubbo 调用事务规范
重要原则:调用方(发起方)需要添加 @GlobalTransactional 注解
场景1:WMS 调用 QMS 创建质检任务
// WMS 模块
@Service
public class WmsInstockPrintServiceImpl {
@DubboReference
private RemoteQmsInspectionService remoteQmsInspectionService;
@Override
@GlobalTransactional(rollbackFor = Exception.class) // ← 调用方加注解
public Map<String, String> createInspection(List<WmsInstockPrintBo> prints) {
Map<String, String> result = new LinkedHashMap<>();
for (WmsInstockPrintBo print : prints) {
try {
// 1. 构建请求参数
WmsInspectionTaskRequest request = new WmsInspectionTaskRequest();
request.setInstockCode(print.getInstockCode());
request.setMaterialCode(print.getMaterialCode());
request.setInspectionQty(print.getApportionQty());
request.setBatchCode(print.getBatchCode());
request.setInspectionType("7"); // 入库检
// 2. 调用 QMS Dubbo 接口(远程事务)
String inspectionNo = remoteQmsInspectionService.createInspectionTaskForWMS(request);
log.info("批次 {} 质检任务创建成功,质检单号: {}", print.getBatchCode(), inspectionNo);
// 3. 更新本地状态(只有远程调用成功才更新)
WmsInstockPrintBo updateBo = new WmsInstockPrintBo();
updateBo.setInstockPrintId(print.getInstockPrintId());
updateBo.setInspectionType("1"); // 质检中
instockPrintService.updateByBo(updateBo);
result.put(print.getBatchCode(), inspectionNo);
} catch (Exception e) {
log.error("批次 {} 创建质检任务失败: {}", print.getBatchCode(), e.getMessage(), e);
result.put(print.getBatchCode(), "失败: " + e.getMessage());
// 全局事务会自动回滚
throw new ServiceException("创建质检任务失败: " + e.getMessage());
}
}
return result;
}
}
场景2:QMS 调用 WMS 质检完成回调
// QMS 模块
@Service
public class QcPDAServiceImpl {
@DubboReference
private RemoteWmsInspectionService remoteWmsInspectionService;
/**
* 质检完成后的回调方法(抽离独立方法)
*/
private void notifyWmsForQualified(QcInspectionMainVo main) {
// 如果没有合格数量,不回调
if (main.getQualifiedQty().compareTo(BigDecimal.ZERO) <= 0) {
log.info("质检单 {} 无合格数量,跳过回调", main.getInspectionNo());
return;
}
try {
// 1. 构建回调参数
InspectionCompleteNotification notification = buildNotification(main);
// 2. 调用 WMS Dubbo 接口(远程事务)
remoteWmsInspectionService.notifyInspectionComplete(notification);
log.info("质检单 {} 回调 WMS 成功", main.getInspectionNo());
} catch (Exception e) {
log.error("质检单 {} 回调 WMS 失败: {}", main.getInspectionNo(), e.getMessage(), e);
// 根据业务需求决定是否抛出异常
// 如果不影响主流程,可以只记录日志
}
}
/**
* 构建回调通知参数
*/
private InspectionCompleteNotification buildNotification(QcInspectionMainVo main) {
InspectionCompleteNotification notification = new InspectionCompleteNotification();
notification.setInspectionNo(main.getInspectionNo());
notification.setInstockCode(main.getRemark()); // remark 字段存储入库单号
notification.setBatchCode(main.getBatchNo()); // 批次号
notification.setQualifiedQty(main.getQualifiedQty());
notification.setUnqualifiedQty(main.getUnqualifiedQty());
notification.setMaterialCode(main.getMaterialCode());
notification.setResult("0"); // 合格
notification.setStatus("1"); // 已完成
notification.setInspectionEndTime(new Date());
return notification;
}
}
7.2 事务回滚场景
| 场景 | 回滚范围 | 说明 |
|---|---|---|
| QMS 创建质检任务失败 | WMS 本地状态不更新 | @GlobalTransactional 保证 |
| WMS 接收回调失败 | 不影响 QMS 主流程 | 捕获异常,记录日志 |
| 网络超时 | Seata 自动重试 | 配置超时时间和重试次数 |
7.3 Seata 配置
# application.yml
seata:
enabled: true
application-id: hwmom-wms
tx-service-group: default_tx_group
registry:
type: nacos
nacos:
server-addr: 127.0.0.1:8848
namespace: public
group: SEATA_GROUP
username: nacos
password: nacos
config:
type: nacos
nacos:
server-addr: 127.0.0.1:8848
namespace: public
group: SEATA_GROUP
username: nacos
password: nacos
9. 让步接收方案
9.1 业务需求概述
业务定义:让步接收是指产品质量不完全符合标准,但经过评审后认为可以使用,允许按合格品入库的特殊场景。
核心原则:
- PDA驱动入库:QMS回调只更新状态,不自动入库,入库由PDA扫码完成
- 状态驱动:PDA入库条件基于
inspectionRequest(质检要求)和inspectionType(质检状态)组合判断- 允许入库:
inspectionRequest='1'(免检)→ 直接允许入库inspectionRequest='0'(必检)且inspectionType='2'(合格)→ 允许入库
- 不允许入库:
inspectionRequest='0'(必检)且inspectionType='0'(未发起)→ 提示"需要先发起质检"inspectionRequest='0'(必检)且inspectionType='1'(质检中)→ 提示"物料质检中,无法入库"inspectionRequest='0'(必检)且inspectionType='3'(不合格)→ 提示"物料质检不合格,请联系质量部门进行让步接收评审"
- 允许入库:
- 批次号一致:全流程使用同一批次号,确保可追溯
- 数量可调整:让步接收不通过时,可调整入库数量
9.2 业务场景分类
场景A:全部合格(无需让步接收)
批次 BATCH001,总数100件
├─ PDA质检完成:100合格 + 0不合格
│ ├─ qualified_qty=100, unqualified_qty=0
│ └─ result='0'(合格)
│
└─ QMS回调WMS
└─ inspection_type='2'(合格)
└─ PDA扫码入库100件
场景B:部分不合格(需要让步接收评审)
批次 BATCH002,总数100件
├─ PDA质检完成:80合格 + 20不合格
│ ├─ qualified_qty=80, unqualified_qty=20
│ └─ result='1'(不合格)
│
├─ QMS回调WMS
│ └─ inspection_type='1'(质检中) ⭐保持等待评审
│ └─ 创建不合格品评审
│
└─ 不合格品评审
├─ 让步接收通过(review_result='4')
│ └─ QMS回调WMS: inspection_type='2'
│ └─ PDA扫码入库100件(80合格+20让步)
│
└─ 让步接收不通过
└─ QMS回调WMS: inspection_type='2', apportion_qty=80
└─ PDA扫码入库80件(仅合格部分)
场景C:全部不合格(需要让步接收评审)
批次 BATCH003,总数100件
├─ PDA质检完成:0合格 + 100不合格
│ ├─ qualified_qty=0, unqualified_qty=100
│ └─ result='1'(不合格)
│
├─ QMS回调WMS
│ └─ inspection_type='3'(不合格) ⭐保持等待评审
│ └─ 创建不合格品评审
│
└─ 不合格品评审
├─ 让步接收通过(review_result='4')
│ └─ QMS回调WMS: inspection_type='2'
│ └─ PDA扫码入库100件
│
└─ 让步接收不通过
└─ QMS回调WMS: inspection_type='2', apportion_qty=0
└─ 无法入库(数量为0)
9.3 状态流转规则
9.3.1 inspection_type 状态流转
【正常流程】
'0'(未发起) → '1'(质检中) → '2'(合格) → PDA入库
【部分不合格流程】
'0' → '1' → '1'(保持质检中) → [让步接收评审] → '2' → PDA入库
↑ ↓
└─────── 等待评审 ──────┘
【全部不合格流程】
'0' → '1' → '3'(不合格) → [让步接收评审] → '2' → PDA入库
↑ ↓
└────── 等待评审 ─────┘
9.3.2 状态判断规则
| PDA质检结果 | qualified_qty | unqualified_qty | WMS更新为 | 说明 |
|---|---|---|---|---|
| 全部合格 | =总数 | =0 | '2' |
直接入库 |
| 部分不合格 | >0 | >0 | '1' |
等待让步接收评审 |
| 全部不合格 | =0 | =总数 | '3' |
等待让步接收评审 |
9.3.3 让步接收评审后的更新规则
| 评审结果 | review_result | WMS更新为 | apportion_qty | 入库数量 |
|---|---|---|---|---|
| 让步接收通过 | '4' |
'2' |
不变 | 全部数量 |
| 让步接收不通过 | 其他 | '2' |
更新为qualified_qty | 只有合格部分 |
9.4 技术实现方案
9.4.1 需要修改的文件清单
| 序号 | 模块 | 文件 | 修改内容 | 优先级 |
|---|---|---|---|---|
| 1 | QMS | QcPDAServiceImpl.java |
修改质检完成回调逻辑 | 高 |
| 2 | QMS | QcWmsCallbackServiceImpl.java |
新增3个回调方法 | 高 |
| 3 | QMS | QcUnqualifiedReviewServiceImpl.java |
评审完成处理逻辑 | 高 |
| 4 | QMS API | InspectionCompleteNotification.java |
新增updateQty字段 | 中 |
| 5 | WMS | WmsInspectionCallbackServiceImpl.java |
支持更新apportion_qty | 中 |
| 6 | PDA | RawInActivity.java |
允许inspection_type='2'入库 | 高 |
9.4.2 修改1:QcPDAServiceImpl.java - 质检完成回调
文件位置:hwmom/ruoyi-modules/hwmom-qms/src/main/java/org/dromara/qms/service/impl/QcPDAServiceImpl.java
修改位置:第370-374行
修改前:
// WMS 回调:质检完成后通知 WMS 更新状态
QcInspectionMainVo updatedMain = qcInspectionMainService.queryById(inspectionId);
if (qcWmsCallbackService.isFromWmsInspection(updatedMain)) {
qcWmsCallbackService.notifyWmsForQualified(updatedMain);
}
修改后:
// WMS 回调:质检完成后通知 WMS 更新状态
QcInspectionMainVo updatedMain = qcInspectionMainService.queryById(inspectionId);
if (qcWmsCallbackService.isFromWmsInspection(updatedMain)) {
// 调用新的回调方法:根据质检结果决定状态更新
qcWmsCallbackService.notifyWmsInspectionComplete(updatedMain);
}
9.4.3 修改2:QcWmsCallbackServiceImpl.java - 新增回调方法
文件位置:hwmom/ruoyi-modules/hwmom-qms/src/main/java/org/dromara/qms/service/impl/QcWmsCallbackServiceImpl.java
方法抽离说明: 以下方法已独立抽离,便于:
- 替换为其他Dubbo服务或REST API
- 注释掉不使用(如业务变更)
- 根据检测类型(inspectionType)判断是否需要回调
检测类型字典(用于判断是否回调WMS):
| inspectionType | 名称 | 说明 |
|---|---|---|
| 0 | 首检 | 生产首件检验 |
| 1 | 专检 | 专职检验 |
| 2 | 自检 | 操作工自检 |
| 3 | 互检 | 互检 |
| 4 | 原材料检 | WMS入库默认使用,需回调WMS |
| 5 | 抽检 | 抽样检验 |
| 6 | 成品检 | 成品出厂检验 |
| 7 | 入库检 | 入库时检验 |
| 8 | 出库检 | 出库时检验 |
关键:当前WMS发起质检时默认传 inspectionType=4(原材料检),因此回调时也只对原材料检进行WMS状态更新。
新增方法1:质检完成回调
/**
* 【可配置】质检完成回调WMS
*
* 说明:此方法已独立抽离,便于:
* 1. 替换为其他Dubbo服务或REST API
* 2. 注释掉不使用(如业务变更)
* 3. 根据inspectionType判断是否需要回调
*
* 根据质检结果更新WMS状态:
* - 全部合格 → inspection_type='2'
* - 部分不合格 → inspection_type='1'(等待评审)
* - 全部不合格 → inspection_type='3'(等待评审)
*
* @param main 质检主表
* @param inspectionType 检测类型(4=原材料检,7=入库检等)
* @return 是否成功
*/
private boolean callbackWmsAfterInspection(QcInspectionMainVo main, String inspectionType) {
// 当前配置:只有原材料检(4)需要回调WMS
// 可根据业务需求修改此判断条件
if (!"4".equals(inspectionType)) {
log.info("检测类型 {} 不需要回调WMS,跳过", inspectionType);
return false;
}
try {
InspectionCompleteNotification notification = buildNotification(main, null, false);
// 【可替换】调用WMS Dubbo接口
// 可替换为:REST API调用、其他Dubbo服务、或直接注释掉
R<Void> result = remoteWmsInspectionService.completeInstockAfterInspection(notification);
if (result == null || result.getCode() != R.SUCCESS) {
log.error("质检单 {} 回调WMS失败: {}", main.getInspectionNo(),
result != null ? result.getMsg() : "null");
return false;
}
log.info("质检单 {} 回调WMS成功,结果: {}, 合格数量: {}",
main.getInspectionNo(), main.getResult(), main.getQualifiedQty());
return true;
} catch (Exception e) {
log.error("质检完成回调WMS失败: {}", e.getMessage(), e);
return false;
}
}
/**
* 质检完成后回调WMS(公共入口)
*
* @param main 质检主表
* @return 是否成功
*/
@Override
public boolean notifyWmsInspectionComplete(QcInspectionMainVo main) {
if (!isFromWmsInspection(main)) {
log.info("质检单 {} 不是来自WMS入库,跳过回调", main.getInspectionNo());
return false;
}
// 获取检测类型(从remark字段或其他方式获取)
String inspectionType = main.getRemark(); // 或从其他字段获取
// 调用抽离的回调方法
return callbackWmsAfterInspection(main, inspectionType);
}
新增方法2:让步接收通过后回调
/**
* 【可配置】让步接收评审通过后回调WMS
*
* 说明:此方法已独立抽离,便于:
* 1. 替换为其他Dubbo服务或REST API
* 2. 注释掉不使用(如业务变更)
*
* 业务逻辑:
* 1. 更新质检主表:累加让步接收数量到合格数量
* 2. 回调WMS:更新状态为'2'(合格)
* 3. apportion_qty保持不变
*
* @param review 不合格品评审记录
* @return 是否成功
*/
private boolean callbackWmsForConcessionAccepted(QcUnqualifiedReviewVo review) {
// 可在此添加业务开关
// if (!ENABLE_CONCESSION_CALLBACK) { return false; }
try {
// 1. 获取质检主表
QcInspectionMainVo main = queryByInspectionNo(review.getInspectionNo());
if (main == null || !isFromWmsInspection(main)) {
log.warn("质检单 {} 不是来自WMS入库,跳过回调", review.getInspectionNo());
return false;
}
// 2. 更新质检主表:累加让步接收数量
BigDecimal concessionQty = review.getInspectionQty();
main.setQualifiedQty(main.getQualifiedQty().add(concessionQty));
main.setUnqualifiedQty(main.getUnqualifiedQty().subtract(concessionQty));
main.setResult("0"); // 更新为合格
qcInspectionMainService.updateById(main);
log.info("质检单 {} 让步接收数量已更新: {} + {} = {}",
review.getInspectionNo(),
main.getQualifiedQty().subtract(concessionQty),
concessionQty,
main.getQualifiedQty());
// 3. 【可替换】回调WMS:更新状态为'2'
InspectionCompleteNotification notification = buildNotification(main, null, true);
// 可替换为:REST API调用、其他Dubbo服务、或直接注释掉
R<Void> result = remoteWmsInstockService.completeInstockAfterInspection(notification);
if (result == null || result.getCode() != R.SUCCESS) {
log.error("让步接收回调WMS失败: {}", result != null ? result.getMsg() : "null");
return false;
}
log.info("让步接收回调WMS成功,质检单号: {}, 批次号: {}, 数量: {}",
review.getInspectionNo(), review.getBatchNo(), concessionQty);
return true;
} catch (Exception e) {
log.error("让步接收回调WMS失败: {}", e.getMessage(), e);
return false;
}
}
/**
* 让步接收评审通过后回调WMS(公共入口)
*
* @param review 不合格品评审记录
* @return 是否成功
*/
@Override
public boolean notifyWmsForConcessionAccepted(QcUnqualifiedReviewVo review) {
return callbackWmsForConcessionAccepted(review);
}
新增方法3:让步接收不通过后回调
/**
* 【可配置】让步接收评审不通过后回调WMS
*
* 说明:此方法已独立抽离,便于:
* 1. 替换为其他Dubbo服务或REST API
* 2. 注释掉不使用(如业务变更)
*
* 业务逻辑:
* 1. 更新质检结果为合格(允许合格部分入库)
* 2. 回调WMS:更新状态为'2',同时更新apportion_qty为合格数量
*
* @param review 不合格品评审记录
* @param qualifiedQty 合格数量
* @return 是否成功
*/
private boolean callbackWmsForConcessionRejected(QcUnqualifiedReviewVo review, BigDecimal qualifiedQty) {
// 可在此添加业务开关
// if (!ENABLE_CONCESSION_CALLBACK) { return false; }
try {
// 1. 获取质检主表
QcInspectionMainVo main = queryByInspectionNo(review.getInspectionNo());
if (main == null || !isFromWmsInspection(main)) {
log.warn("质检单 {} 不是来自WMS入库,跳过回调", review.getInspectionNo());
return false;
}
// 2. 【可替换】回调WMS:更新状态为'2',同时更新数量
InspectionCompleteNotification notification = buildNotification(main, qualifiedQty, false);
notification.setUpdateQty("1"); // 标记需要更新数量
// 可替换为:REST API调用、其他Dubbo服务、或直接注释掉
R<Void> result = remoteWmsInstockService.completeInstockAfterInspection(notification);
if (result == null || result.getCode() != R.SUCCESS) {
log.error("让步接收不通过回调WMS失败: {}", result != null ? result.getMsg() : "null");
return false;
}
log.info("让步接收不通过回调WMS成功,质检单号: {}, 批次号: {}, 合格数量: {}",
review.getInspectionNo(), review.getBatchNo(), qualifiedQty);
return true;
} catch (Exception e) {
log.error("让步接收不通过回调WMS失败: {}", e.getMessage(), e);
return false;
}
}
/**
* 让步接收评审不通过后回调WMS(公共入口)
*
* @param review 不合格品评审记录
* @param qualifiedQty 合格数量
* @return 是否成功
*/
@Override
public boolean notifyWmsForConcessionRejected(QcUnqualifiedReviewVo review, BigDecimal qualifiedQty) {
return callbackWmsForConcessionRejected(review, qualifiedQty);
}
修改方法:buildNotification() - 支持数量传递
/**
* 构建质检完成通知参数
*
* @param main 质检主表
* @param qty 数量(null=全部合格数量,非null=指定数量)
* @param isConcession 是否让步接收
* @return 通知参数
*/
@Override
public Object buildNotification(QcInspectionMainVo main, BigDecimal qty, boolean isConcession) {
InspectionCompleteNotification notification = new InspectionCompleteNotification();
// 基本信息
notification.setInspectionNo(main.getInspectionNo());
notification.setMaterialCode(main.getMaterialCode());
notification.setInstockCode(main.getRemark()); // remark存储入库单号
notification.setBatchCode(main.getBatchNo()); // 批次号
// 质检结果
if (isConcession) {
// 让步接收视为合格
notification.setResult("0");
notification.setQualifiedQty(qty != null ? qty : main.getQualifiedQty());
notification.setUnqualifiedQty(BigDecimal.ZERO);
} else {
// 正常质检结果
notification.setResult(main.getResult());
notification.setQualifiedQty(main.getQualifiedQty());
notification.setUnqualifiedQty(main.getUnqualifiedQty());
}
// 质检状态
notification.setStatus("1"); // 已完成
// 质检完成时间
notification.setInspectionEndTime(main.getInspectionEndTime() != null
? DateUtils.formatDateTime(main.getInspectionEndTime())
: DateUtils.getTime());
return notification;
}
9.4.4 修改3:QcUnqualifiedReviewServiceImpl.java - 评审完成处理
文件位置:hwmom/ruoyi-modules/hwmom-qms/src/main/java/org/dromara/qms/service/impl/QcUnqualifiedReviewServiceImpl.java
修改completeTask()方法
@Override
@GlobalTransactional(rollbackFor = Exception.class)
public Boolean completeTask(QcUnqualifiedReviewBo bo) {
QcUnqualifiedReview review = baseMapper.selectById(bo.getReviewId());
if (review == null) {
throw new ServiceException("不合格品待评审不存在");
}
if (ObjectUtils.isNotEmpty(review.getReviewEndTime())) {
throw new ServiceException("该任务已处理完成");
}
// 1. 完成工作流审批(现有逻辑)
Long currentTaskId = remoteWorkflowService.getCurrentTaskIdByInstanceId(
review.getProcessInstanceId()
);
if (currentTaskId == null) {
throw new ServiceException("任务不存在");
}
review.setReviewer(LoginHelper.getUsername());
review.setReviewerId(LoginHelper.getUserId());
review.setReviewEndTime(new Date());
RemoteCompleteTask completeTask = new RemoteCompleteTask();
completeTask.setTaskId(currentTaskId);
List<String> messageTypes = new ArrayList<>();
messageTypes.add("1");
completeTask.setMessageType(messageTypes);
completeTask.setMessage("审批通过");
remoteWorkflowService.completeTask(completeTask);
// 2. 根据评审结果处理WMS回调
if ("4".equals(review.getReviewResult())) {
// 让步接收通过
qcWmsCallbackService.notifyWmsForConcessionAccepted(review);
} else {
// 让步接收不通过或其他结果
// 获取质检主表的合格数量
QcInspectionMainVo main = qcInspectionMainService.queryByInspectionNo(
review.getInspectionNo()
);
if (main != null) {
qcWmsCallbackService.notifyWmsForConcessionRejected(review, main.getQualifiedQty());
}
}
return baseMapper.updateById(review) > 0;
}
9.4.5 修改4:InspectionCompleteNotification.java - 新增字段
文件位置:hwmom/ruoyi-api/hwmom-api-qms/src/main/java/org/dromara/qms/api/dto/InspectionCompleteNotification.java
新增字段:
/**
* 是否更新数量标记
* <p>
* 说明:
* 0=不更新,1=更新为qualifiedQty
* <p>
* 使用场景:让步接收评审不通过时,需要更新入库数量为实际合格数量
*/
private String updateQty;
public String getUpdateQty() {
return updateQty;
}
public void setUpdateQty(String updateQty) {
this.updateQty = updateQty;
}
9.4.6 修改5:WmsInspectionCallbackServiceImpl.java - 支持更新数量
文件位置:hwmom/ruoyi-modules/hwmom-wms/src/main/java/org/dromara/wms/service/impl/WmsInspectionCallbackServiceImpl.java
修改handleInspectionComplete()方法
@Override
public boolean handleInspectionComplete(InspectionCompleteNotification notification) {
// 1. 参数校验
if (StringUtils.isBlank(notification.getInstockCode()) ||
StringUtils.isBlank(notification.getBatchCode())) {
log.warn("入库单号或批次号为空");
return false;
}
// 2. 查询打印记录
WmsInstockPrintVo printVo = queryByInstockAndBatch(
notification.getInstockCode(),
notification.getBatchCode()
);
if (printVo == null) {
log.warn("未找到入库打印记录: instockCode={}, batchCode={}",
notification.getInstockCode(), notification.getBatchCode());
return false;
}
// 3. 根据质检结果更新状态
if ("0".equals(notification.getResult())) {
// 合格
return updateToQualified(notification);
} else {
// 不合格:判断是部分不合格还是全部不合格
if (notification.getQualifiedQty().compareTo(BigDecimal.ZERO) > 0) {
// 部分不合格 → 保持'1'(质检中),等待评审
return updateToInProgress(notification);
} else {
// 全部不合格 → 更新为'3'(不合格),等待评审
return updateToUnqualified(notification);
}
}
}
/**
* 更新为质检中状态(部分不合格,等待评审)
*/
private boolean updateToInProgress(InspectionCompleteNotification notification) {
WmsInstockPrintBo updateBo = new WmsInstockPrintBo();
updateBo.setInstockCode(notification.getInstockCode());
updateBo.setBatchCode(notification.getBatchCode());
updateBo.setInspectionType("1"); // 质检中
boolean success = instockPrintService.updateByBo(updateBo) > 0;
if (success) {
log.info("批次 {} 状态已更新为'质检中',等待让步接收评审",
notification.getBatchCode());
}
return success;
}
/**
* 更新为合格状态(支持更新数量)
*/
private boolean updateToQualified(InspectionCompleteNotification notification) {
WmsInstockPrintBo updateBo = new WmsInstockPrintBo();
updateBo.setInstockCode(notification.getInstockCode());
updateBo.setBatchCode(notification.getBatchCode());
updateBo.setInspectionType("2"); // 合格
// 如果标记需要更新数量(让步接收不通过场景)
if ("1".equals(notification.getUpdateQty())) {
updateBo.setApportionQty(notification.getQualifiedQty());
log.info("批次 {} 数量已更新为: {}",
notification.getBatchCode(), notification.getQualifiedQty());
}
boolean success = instockPrintService.updateByBo(updateBo) > 0;
if (success) {
log.info("批次 {} 状态已更新为'合格'", notification.getBatchCode());
}
return success;
}
9.4.7 修改6:RawInActivity.java (PDA) - PDA入库条件判断
文件位置:mom-pad/app/src/main/java/com/example/haiwei_mom/wms/raw/RawInActivity.java
字段说明:
inspectionRequest(质检要求):0=必检,1=免检inspectionType(质检状态):0=未发起,1=质检中,2=合格,3=不合格
修改质检提交方法
// 质检提交
public void rawinSubmit2(View view) {
if (!inCheck()) {
return;
}
String inspectionRequest = rawInstock.getInspectionRequest(); // 质检要求
String inspectionType = rawInstock.getInspectionType(); // 质检状态
// 【修改】基于 inspectionRequest 和 inspectionType 组合判断
// 允许入库:inspectionRequest='1'(免检) 或 (inspectionRequest='0' 且 inspectionType='2')
// 不允许入库:其他情况
boolean canInstock = "1".equals(inspectionRequest); // 免检直接允许
if (!canInstock && "0".equals(inspectionRequest)) {
// 必检时,只有合格(2)才能入库
canInstock = "2".equals(inspectionType);
}
if (!canInstock) {
String message = "";
if ("0".equals(inspectionType)) {
message = "物料需要先发起质检";
} else if ("1".equals(inspectionType)) {
message = "物料质检中,无法入库";
} else if ("3".equals(inspectionType)) {
message = "物料质检不合格,请联系质量部门进行让步接收评审";
} else {
message = "物料状态异常,无法入库";
}
Toast.makeText(context, message, Toast.LENGTH_SHORT).show();
return;
}
rawinSubmit();
}
9.5 数据一致性保证
9.5.1 数据流转示例
示例1:部分不合格,让步接收通过
批次:BATCH001,总数:100件
【步骤1】PDA质检完成
qc_inspection_main:
- qualified_qty = 80
- unqualified_qty = 20
- result = '1' (不合格)
wms_instock_print:
- inspection_type = '1' (质检中) ← 保持等待
【步骤2】让步接收评审通过
qc_unqualified_review:
- review_result = '4' (让步接收)
- inspection_qty = 20
qc_inspection_main:
- qualified_qty = 80 + 20 = 100
- unqualified_qty = 0
- result = '0' (合格)
wms_instock_print:
- inspection_type = '2' (合格)
- apportion_qty = 100 (不变)
【步骤3】PDA扫码入库
wms_instock_record:
- instock_qty = 100
wms_inventory:
- inventory_qty = 100
示例2:部分不合格,让步接收不通过
批次:BATCH002,总数:100件
【步骤1】PDA质检完成
qc_inspection_main:
- qualified_qty = 80
- unqualified_qty = 20
wms_instock_print:
- inspection_type = '1' (质检中)
【步骤2】让步接收评审不通过
qc_unqualified_review:
- review_result = '0' (报废)
wms_instock_print:
- inspection_type = '2' (合格)
- apportion_qty = 80 ← 更新为合格数量
【步骤3】PDA扫码入库
wms_instock_record:
- instock_qty = 80 (只能入库80件)
wms_inventory:
- inventory_qty = 80
9.5.2 数据一致性验证表
| 场景 | 质检结果 | PDA质检后WMS状态 | 评审结果 | 评审后WMS状态 | apportion_qty | 可入库数量 |
|---|---|---|---|---|---|---|
| 全部合格 | 100合格+0不合格 | '2' | - | - | 100 | 100 |
| 部分不合格+通过 | 80合格+20不合格 | '1' | 通过 | '2' | 100 | 100 |
| 部分不合格+不通过 | 80合格+20不合格 | '1' | 不通过 | '2' | 80 | 80 |
| 全部不合格+通过 | 0合格+100不合格 | '3' | 通过 | '2' | 100 | 100 |
| 全部不合格+不通过 | 0合格+100不合格 | '3' | 不通过 | '2' | 0 | 0 |
9.6 接口时序图
┌─────────────────────────────────────────────────────────────────┐
│ 让步接收完整时序图 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 【角色】 │
│ PDA → PDA设备 │
│ QMS → 质量管理系统 │
│ WMS → 仓储管理系统 │
│ │
│ 【场景:部分不合格(80+20),让步接收通过】 │
│ │
│ PDA QMS WMS │
│ │ │ │ │
│ │────质检完成──>│ │ │
│ │ │ │ │
│ │ ├─80合格+20不合格 │
│ │ │ │ │
│ │ └─────Dubbo回调───────>│ │
│ │ │ │ │
│ │ │ ├─ inspection_type='1' │
│ │ │ │ (保持质检中) │
│ │ │ │ │
│ │ ├─创建不合格评审───────>│ │
│ │ │ │ │
│ │ │ │ │
│ 用户 │ │ │
│ │ │ │ │
│ │────评审通过──>│ │ │
│ │ │ │ │
│ │ ├─累加合格数量(80+20=100) │
│ │ │ │ │
│ │ └─────Dubbo回调───────>│ │
│ │ │ │ │
│ │ │ ├─ inspection_type='2' │
│ │ │ │ (合格) │
│ │ │ │ │
│ │ │ │ │
│ │────扫码入库──>│ │ │
│ │ │ │ │
│ │ │ ├─ inspection_type='2' ✓ │
│ │ │ ├─ 创建入库记录 │
│ │ │ └─ 更新库存(100) │
│ │ │ │ │
└─────────────────────────────────────────────────────────────────┘
9.7 接口定义更新
9.7.1 IQcWmsCallbackService 接口新增方法
/**
* QMS回调WMS服务接口
*/
public interface IQcWmsCallbackService {
/**
* 质检完成后回调WMS
*
* @param main 质检主表
* @return 是否成功
*/
boolean notifyWmsInspectionComplete(QcInspectionMainVo main);
/**
* 让步接收评审通过后回调WMS
*
* @param review 不合格品评审记录
* @return 是否成功
*/
boolean notifyWmsForConcessionAccepted(QcUnqualifiedReviewVo review);
/**
* 让步接收评审不通过后回调WMS
*
* @param review 不合格品评审记录
* @param qualifiedQty 合格数量
* @return 是否成功
*/
boolean notifyWmsForConcessionRejected(QcUnqualifiedReviewVo review, BigDecimal qualifiedQty);
// ... 其他现有方法
}
9.8 关键注意事项
- 状态保持原则:部分不合格或全部不合格时,WMS状态保持为'1'或'3',等待让步接收评审
- 数量更新时机:只在让步接收不通过时更新apportion_qty
- PDA入库前提:基于
inspectionRequest(质检要求)和inspectionType(质检状态)组合判断inspectionRequest='1'(免检)→ 允许入库inspectionRequest='0'(必检)且inspectionType='2'(合格)→ 允许入库- 其他情况不允许入库
- 分布式事务:让步接收处理使用@GlobalTransactional保证一致性
- 批次号一致性:全流程使用同一批次号,确保可追溯
- 方法抽离与可配置性:
- 质检完成回调方法已抽离为
callbackWmsAfterInspection(),便于替换或禁用 - 让步接收回调方法已抽离为
callbackWmsForConcessionAccepted()和callbackWmsForConcessionRejected() - 可通过修改检测类型判断逻辑(如
if (!"4".equals(inspectionType)))来控制回调行为 - 可通过添加业务开关(如
ENABLE_CONCESSION_CALLBACK)来启用/禁用让步接收回调 - Dubbo调用可替换为REST API或其他服务调用方式
- 质检完成回调方法已抽离为
10. 附录
10.1 字典值定义
wms_inspection_type(质检状态)
| 字典值 | 字典标签 | 说明 |
|---|---|---|
| 0 | 未发起 | 打印后默认状态 |
| 1 | 质检中 | 已创建质检任务,等待执行 |
| 2 | 合格 | 质检合格 |
| 3 | 不合格 | 质检不合格 |
inspection_request(质检要求)
| 字典值 | 字典标签 | 说明 |
|---|---|---|
| 0 | 必检 | 需要质检 |
| 1 | 免检 | 不需要质检 |
qc_inspection_type(检测类型)
| 字典值 | 字典标签 | 说明 |
|---|---|---|
| 0 | 首检 | 生产首件检验 |
| 1 | 专检 | 专职检验 |
| 2 | 自检 | 操作工自检 |
| 3 | 互检 | 互检 |
| 4 | 原材料检 | WMS入库默认使用,需回调WMS |
| 5 | 抽检 | 抽样检验 |
| 6 | 成品检 | 成品出厂检验 |
| 7 | 入库检 | 入库时检验 |
| 8 | 出库检 | 出库时检验 |
wms_inbound_status(入库状态)
| 字典值 | 字典标签 | 说明 |
|---|---|---|
| 0 | 待入库 | 初始状态 |
| 1 | 已入库 | PDA 扫码入库成功 |
| 2 | 入库中 | 预留,当前未使用 |
| 3 | 拒收 | 预留,当前未使用 |
10.2 关键文件路径清单
| 模块 | 文件路径 | 说明 |
|---|---|---|
| WMS | hwmom/ruoyi-modules/hwmom-wms/src/main/java/org/dromara/wms/controller/api/WmsInstockPrintController.java |
打印记录控制器 |
| WMS | hwmom/ruoyi-modules/hwmom-wms/src/main/java/org/dromara/wms/service/IWmsInstockPrintService.java |
打印记录服务接口 |
| WMS | hwmom/ruoyi-modules/hwmom-wms/src/main/java/org/dromara/wms/service/impl/WmsInstockPrintServiceImpl.java |
打印记录服务实现 |
| WMS | hwmom/ruoyi-modules/hwmom-wms/src/main/java/org/dromara/wms/dubbo/RemoteWmsInspectionService.java |
WMS 暴露给 QMS 的接口(新增) |
| QMS | hwmom/ruoyi-api/hwmom-api-qms/src/main/java/org/dromara/qms/api/RemoteQmsInspectionService.java |
QMS Dubbo 接口定义 |
| QMS | hwmom/ruoyi-modules/hwmom-qms/src/main/java/org/dromara/qms/dubbo/RemoteQmsInspectionServiceImpl.java |
QMS Dubbo 服务实现 |
| QMS | hwmom/ruoyi-modules/hwmom-qms/src/main/java/org/dromara/qms/service/IQcWmsService.java |
WMS 仓储质检服务接口 |
| QMS | hwmom/ruoyi-modules/hwmom-qms/src/main/java/org/dromara/qms/service/impl/QcWmsServiceImpl.java |
WMS 仓储质检服务实现 |
| QMS | hwmom/ruoyi-modules/hwmom-qms/src/main/java/org/dromara/qms/service/impl/QcPDAServiceImpl.java |
PDA 质检服务实现 |
| API | hwmom/ruoyi-api/hwmom-api-qms/src/main/java/org/dromara/qms/api/dto/InspectionCompleteNotification.java |
质检完成通知 DTO |
| API | hwmom/ruoyi-api/hwmom-api-qms/src/main/java/org/dromara/qms/api/dto/WmsInspectionTaskRequest.java |
WMS 入库质检请求 DTO |
| 前端 | hwmom-ui/src/views/wms/instockPrint/qc.vue |
质检入库任务界面 |
| 前端 | hwmom-ui/src/api/wms/instockPrint/index.ts |
打印记录前端 API |
11. 待确认事项
批次号传递方式✓ 已确认:必须传递batchCode入库时机✓ 已确认:由 PDA 扫码入库,回调只更新状态分布式事务✓ 已确认:调用方加@GlobalTransactional- 让步接收具体实现方式(待讨论)
- 是否需要新增 WMS 暴露给 QMS 的 Dubbo 接口(待确认)
文档结束