# 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-需求概述) - [2. 业务流程](#2-业务流程) - [3. 数据模型](#3-数据模型) - [4. 接口设计](#4-接口设计) - [5. 新增文件清单](#5-新增文件清单) - [6. 技术实现方案](#6-技术实现方案) - [7. 前端改造](#7-前端改造) - [8. 分布式事务处理](#8-分布式事务处理) - [9. 让步接收方案(待讨论)](#9-让步接收方案待讨论) --- ## 1. 需求概述 ### 1.1 背景 WMS(仓储管理系统)与 QMS(质量管理系统)需要实现质检业务闭环,支持以下场景: 1. **WMS 主动发起质检**:通过前端界面手动选择批次,调用 QMS 创建质检任务 2. **QMS 质检完成回调**:质检完成后通知 WMS,更新批次质检状态 3. **PDA 扫码入库**:根据质检状态,PDA 扫码完成实际入库操作 4. **让步接收闭环**:不合格品让步接收后,支持 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 发起质检接口 #### 接口定义 ```java /** * 批量发起质检任务 * 前端选择批次后调用此接口创建质检任务 * * @param prints 选中的打印记录列表 * @return Map<批次号, 质检单号或错误信息> */ @PostMapping("/createInspection") public R> createInspection(@RequestBody List prints) ``` #### 请求参数 ```json [ { "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" } ] ``` #### 响应结果 ```json { "code": 200, "msg": "操作成功", "data": { "BATCH001": "QC202501150001", "BATCH002": "QC202501150002" } } ``` ### 4.2 QMS 质检完成回调接口(WMS 暴露给 QMS) #### Dubbo 接口定义 ```java /** * WMS 暴露给 QMS 的质检完成回调接口 * QMS 质检完成后调用此接口通知 WMS */ public interface RemoteWmsInspectionService { /** * 质检完成回调 * @param notification 质检完成通知 */ void notifyInspectionComplete(InspectionCompleteNotification notification); } ``` #### 回调参数 ```java 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文件,不修改现有文件,便于查看和调试 1. **模块化设计**:将WMS-QMS集成相关的业务逻辑抽离到独立的服务类 2. **复用现有服务**:新文件可调用现有服务方法,避免重复代码 3. **便于维护**:独立文件便于定位问题、调试和后续扩展 4. **职责清晰**:每个文件职责单一,符合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发起质检服务实现 ```java // WmsInspectionInitiationServiceImpl.java @Service public class WmsInspectionInitiationServiceImpl implements IWmsInspectionInitiationService { @DubboReference(timeout = 300000) private RemoteQmsInspectionService remoteQmsInspectionService; private final IWmsInstockPrintService instockPrintService; @Override public Map createInspection(List prints) { Map 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服务实现 ```java // 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 result = remoteWmsInstockService.completeInstockAfterInspection(notification); return result != null && result.getCode() == R.SUCCESS; } } ``` #### WMS接收QMS回调服务实现(本地服务) ```java // 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服务实现(独立类) ```java // RemoteWmsInspectionCallbackServiceImpl.java @Service @DubboService public class RemoteWmsInspectionCallbackServiceImpl implements RemoteWmsInstockService { private final IWmsInspectionCallbackService wmsInspectionCallbackService; @Override public R 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 新增按钮 ```vue 发起质检 ``` ### 7.3 按钮点击处理 ```typescript /** 发起质检按钮操作 */ 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 定义 ```typescript // 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 创建质检任务 ```java // WMS 模块 @Service public class WmsInstockPrintServiceImpl { @DubboReference private RemoteQmsInspectionService remoteQmsInspectionService; @Override @GlobalTransactional(rollbackFor = Exception.class) // ← 调用方加注解 public Map createInspection(List prints) { Map 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 质检完成回调 ```java // 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 配置 ```yaml # 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 业务需求概述 **业务定义**:让步接收是指产品质量不完全符合标准,但经过评审后认为可以使用,允许按合格品入库的特殊场景。 **核心原则**: 1. **PDA驱动入库**:QMS回调只更新状态,不自动入库,入库由PDA扫码完成 2. **状态驱动**:PDA入库条件基于 `inspectionRequest`(质检要求)和 `inspectionType`(质检状态)组合判断 - **允许入库**: - `inspectionRequest='1'`(免检)→ 直接允许入库 - `inspectionRequest='0'`(必检)且 `inspectionType='2'`(合格)→ 允许入库 - **不允许入库**: - `inspectionRequest='0'`(必检)且 `inspectionType='0'`(未发起)→ 提示"需要先发起质检" - `inspectionRequest='0'`(必检)且 `inspectionType='1'`(质检中)→ 提示"物料质检中,无法入库" - `inspectionRequest='0'`(必检)且 `inspectionType='3'`(不合格)→ 提示"物料质检不合格,请联系质量部门进行让步接收评审" 3. **批次号一致**:全流程使用同一批次号,确保可追溯 4. **数量可调整**:让步接收不通过时,可调整入库数量 ### 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行 **修改前**: ```java // WMS 回调:质检完成后通知 WMS 更新状态 QcInspectionMainVo updatedMain = qcInspectionMainService.queryById(inspectionId); if (qcWmsCallbackService.isFromWmsInspection(updatedMain)) { qcWmsCallbackService.notifyWmsForQualified(updatedMain); } ``` **修改后**: ```java // 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` **方法抽离说明**: 以下方法已独立抽离,便于: 1. 替换为其他Dubbo服务或REST API 2. 注释掉不使用(如业务变更) 3. 根据检测类型(inspectionType)判断是否需要回调 **检测类型字典**(用于判断是否回调WMS): | inspectionType | 名称 | 说明 | |---------------|------|------| | 0 | 首检 | 生产首件检验 | | 1 | 专检 | 专职检验 | | 2 | 自检 | 操作工自检 | | 3 | 互检 | 互检 | | **4** | **原材料检** | **WMS入库默认使用,需回调WMS** | | 5 | 抽检 | 抽样检验 | | 6 | 成品检 | 成品出厂检验 | | 7 | 入库检 | 入库时检验 | | 8 | 出库检 | 出库时检验 | **关键**:当前WMS发起质检时默认传 `inspectionType=4`(原材料检),因此回调时也只对原材料检进行WMS状态更新。 **新增方法1:质检完成回调** ```java /** * 【可配置】质检完成回调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 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:让步接收通过后回调** ```java /** * 【可配置】让步接收评审通过后回调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 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:让步接收不通过后回调** ```java /** * 【可配置】让步接收评审不通过后回调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 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() - 支持数量传递** ```java /** * 构建质检完成通知参数 * * @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()方法** ```java @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 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` **新增字段**: ```java /** * 是否更新数量标记 *

* 说明: * 0=不更新,1=更新为qualifiedQty *

* 使用场景:让步接收评审不通过时,需要更新入库数量为实际合格数量 */ 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()方法** ```java @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=不合格 **修改质检提交方法** ```java // 质检提交 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 接口新增方法 ```java /** * 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 关键注意事项 1. **状态保持原则**:部分不合格或全部不合格时,WMS状态保持为'1'或'3',等待让步接收评审 2. **数量更新时机**:只在让步接收不通过时更新apportion_qty 3. **PDA入库前提**:基于 `inspectionRequest`(质检要求)和 `inspectionType`(质检状态)组合判断 - `inspectionRequest='1'`(免检)→ 允许入库 - `inspectionRequest='0'`(必检)且 `inspectionType='2'`(合格)→ 允许入库 - 其他情况不允许入库 4. **分布式事务**:让步接收处理使用@GlobalTransactional保证一致性 5. **批次号一致性**:全流程使用同一批次号,确保可追溯 6. **方法抽离与可配置性**: - 质检完成回调方法已抽离为 `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. 待确认事项 1. ~~批次号传递方式~~ ✓ 已确认:必须传递 `batchCode` 2. ~~入库时机~~ ✓ 已确认:由 PDA 扫码入库,回调只更新状态 3. ~~分布式事务~~ ✓ 已确认:调用方加 `@GlobalTransactional` 4. 让步接收具体实现方式(待讨论) 5. 是否需要新增 WMS 暴露给 QMS 的 Dubbo 接口(待确认) --- **文档结束**