feat(wms-qms): 实现WMS与QMS质检集成功能

- 在InspectionCompleteNotification中添加批次号字段用于WMS精确匹配
- 添加hwmom-api-qms依赖到wms模块以引用质检通知DTO
- 在WmsInstockPrintBo中添加inspectionTypeParam字段用于动态指定检验类型
- 在WmsInstockPrintController中添加createInspection接口用于批量发起质检任务
- 实现QcWmsServiceImpl提供WMS入库质检任务创建功能
- 实现RemoteQmsInspectionServiceImpl提供Dubbo质检服务接口
- 实现RemoteWmsInspectionCallbackServiceImpl提供质检回调Dubbo服务
- 创建RemoteWmsInstockService接口定义WMS入库服务Dubbo接口
- 添加WMS仓储模块和WMS-QMS质检集成文档说明
master
zangch@mesnac.com 3 days ago
parent 250c85b1ee
commit c415b2538f

@ -40,6 +40,14 @@ public class InspectionCompleteNotification implements Serializable {
*/
private String instockCode;
/**
*
* <p>
* WMS
*
*/
private String batchCode;
/**
*
* <p>

@ -23,6 +23,13 @@
<artifactId>ruoyi-common-core</artifactId>
</dependency>
<!-- Hwmom Api QMS - 用于引用 InspectionCompleteNotification -->
<dependency>
<groupId>org.dromara</groupId>
<artifactId>hwmom-api-qms</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-excel</artifactId>

@ -0,0 +1,36 @@
package org.dromara.wms.api;
import org.dromara.common.core.domain.R;
import org.dromara.qms.api.dto.InspectionCompleteNotification;
/**
* WMS Dubbo
* <p>
* QMS WMS
*
* @author zch
* @date 2026-1-14
*/
public interface RemoteWmsInstockService {
/**
*
* <p>
*
* 1. QMS WMS
* 2. WMS
* 3.
* 4.
* <p>
*
* - REST APIPOST /wsmApi/notifyInspectionComplete
* - Dubbo
* <p>
* IDID
*
* @param notification
* @return
*/
R<Void> completeInstockAfterInspection(InspectionCompleteNotification notification);
}

@ -0,0 +1,84 @@
package org.dromara.qms.dubbo;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboService;
import org.dromara.common.core.domain.R;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.satoken.utils.LoginHelper;
import org.dromara.common.tenant.helper.TenantHelper;
import org.dromara.qms.api.RemoteQmsInspectionService;
import org.dromara.qms.api.dto.InspectionCompleteNotification;
import org.dromara.qms.api.dto.WmsInspectionTaskRequest;
import org.dromara.qms.service.IQcWmsService;
import org.springframework.stereotype.Service;
/**
* QMS Dubbo
* <p>
* WMS RPC
* <p>
* Dubbo
* {@link IQcWmsService}
*
* @author zch
* @date 2026-1-14
*/
@Slf4j
@RequiredArgsConstructor
@Service
@DubboService
public class RemoteQmsInspectionServiceImpl implements RemoteQmsInspectionService {
private final IQcWmsService qcWmsService;
/**
* WMS
* <p>
*
* 1. Dubbo IDID
* 2. 使 TenantHelper.dynamic
* 3. IQcWmsService
*
* @param request WMS
* @return
*/
@Override
public String createInspectionTaskForWMS(WmsInspectionTaskRequest request) {
// 从当前上下文获取租户ID和用户ID由 Dubbo 调用链传递)
String tenantId = TenantHelper.getTenantId();
Long userId = LoginHelper.getUserId();
log.info("WMS调用QMS创建质检任务Dubbo入库单号: {}, 物料编码: {}, 租户ID: {}, 用户ID: {}",
request.getInstockCode(), request.getMaterialCode(), tenantId, userId);
// 使用 TenantHelper.dynamic 确保租户隔离
return TenantHelper.dynamic(tenantId, () -> {
try {
return qcWmsService.createInspectionTask(request);
} catch (ServiceException e) {
log.error("WMS调用QMS创建质检任务失败: {}", e.getMessage());
throw e;
} catch (Exception e) {
log.error("WMS调用QMS创建质检任务异常", e);
throw new ServiceException("创建质检任务异常: " + e.getMessage());
}
});
}
/**
* WMS
* <p>
* REST API POST /wsmApi/notifyInspectionComplete
* Dubbo
*
* @param notification
* @return
*/
@Override
public R<Void> notifyInspectionComplete(InspectionCompleteNotification notification) {
log.info("收到质检完成通知Dubbo方式质检单号: {}, 质检结果: {}", notification.getInspectionNo(), notification.getResult());
log.warn("当前采用 REST API 方式回调,此 Dubbo 接口暂不实现");
return R.ok("此接口暂未启用,请使用 REST API 方式回调");
}
}

@ -0,0 +1,200 @@
package org.dromara.qms.service.impl;
import java.math.BigDecimal;
import java.util.List;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboReference;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.system.api.RemoteCodeRuleService;
import org.dromara.qms.api.dto.WmsInspectionTaskRequest;
import org.dromara.qms.domain.bo.QcInspectionMainBo;
import org.dromara.qms.domain.bo.QcInspectionResultBo;
import org.dromara.qms.domain.bo.QcTemplateItemBo;
import org.dromara.qms.domain.vo.QcInspectionTemplateVo;
import org.dromara.qms.domain.vo.QcTemplateItemVo;
import org.dromara.qms.service.IQcInspectionMainService;
import org.dromara.qms.service.IQcInspectionResultService;
import org.dromara.qms.service.IQcInspectionTemplateService;
import org.dromara.qms.service.IQcTemplateItemService;
import org.dromara.qms.service.IQcWmsService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
/**
* WMSService
* <p>
* WMS
*
* @author zch
* @date 2026-01-14
*/
@Slf4j
@RequiredArgsConstructor
@Service
public class QcWmsServiceImpl implements IQcWmsService {
private final IQcInspectionMainService qcInspectionMainService;
private final IQcInspectionTemplateService qcInspectionTemplateService;
private final IQcInspectionResultService qcInspectionResultService;
private final IQcTemplateItemService qcTemplateItemService;
/**
* Dubbo
*
*/
@DubboReference(timeout = 300000)
private final RemoteCodeRuleService remoteCodeRuleService;
/**
* WMS
* <p>
*
* 1.
* 2. 8
* 3.
* 4.
* 5.
* 6.
*
* @param request WMS
* @return
*/
@Override
@Transactional(rollbackFor = Exception.class)
public String createInspectionTask(WmsInspectionTaskRequest request) {
log.info("WMS调用创建质检任务入库单号: {}, 物料编码: {}, 质检数量: {}, 检验类型: {}",
request.getInstockCode(), request.getMaterialCode(), request.getInspectionQty(), request.getInspectionType());
// 1. 参数校验
validateRequest(request);
// 2. 匹配质检模板调用已实现的8级降级匹配策略
QcInspectionTemplateVo templateVo = qcInspectionTemplateService.getMatchedTemplate(
request.getMaterialCode(),
request.getStationName(),
request.getProcessCode(),
request.getInspectionType()
);
if (templateVo == null) {
throw new ServiceException("未找到匹配的质检模板,物料编码: " + request.getMaterialCode());
}
log.info("质检模板匹配成功模板ID: {}, 模板名称: {}", templateVo.getTemplateId(), templateVo.getTemplateName());
// 3. 生成质检单号(调用编码规则服务)
String inspectionNo = remoteCodeRuleService.selectCodeRuleCode("3");
if (StringUtils.isBlank(inspectionNo)) {
throw new ServiceException("获取质检单号失败");
}
// 4. 构建质检主表BO
QcInspectionMainBo mainBo = buildInspectionMainBo(request, templateVo, inspectionNo);
// 5. 插入质检主表
Boolean insertResult = qcInspectionMainService.insertByBo(mainBo);
if (!insertResult) {
throw new ServiceException("质检主表保存失败");
}
log.info("质检主表创建成功,质检单号: {}, 质检ID: {}", inspectionNo, mainBo.getInspectionId());
// 6. 根据模板生成质检结果子表
generateInspectionResults(mainBo.getInspectionId(), templateVo.getTemplateId());
log.info("质检结果子表生成成功,共{}个检测项", getTemplateItemCount(templateVo.getTemplateId()));
// 7. 返回质检单号
return inspectionNo;
}
/**
*
*/
private void validateRequest(WmsInspectionTaskRequest request) {
if (StringUtils.isBlank(request.getInstockCode())) {
throw new ServiceException("入库单号不能为空");
}
if (StringUtils.isBlank(request.getMaterialCode())) {
throw new ServiceException("物料编码不能为空");
}
if (request.getInspectionQty() == null || request.getInspectionQty().compareTo(BigDecimal.ZERO) <= 0) {
throw new ServiceException("质检数量必须大于0");
}
if (StringUtils.isBlank(request.getInspectionType())) {
throw new ServiceException("检验类型不能为空");
}
}
/**
* BO
*/
private QcInspectionMainBo buildInspectionMainBo(WmsInspectionTaskRequest request,
QcInspectionTemplateVo templateVo,
String inspectionNo) {
QcInspectionMainBo mainBo = new QcInspectionMainBo();
mainBo.setInspectionNo(inspectionNo);
mainBo.setTemplateId(templateVo.getTemplateId());
mainBo.setTemplateName(templateVo.getTemplateName());
mainBo.setInspectionType(templateVo.getTypeId());
mainBo.setMaterialCode(request.getMaterialCode());
mainBo.setMaterialName(request.getMaterialName());
mainBo.setInspectionQty(request.getInspectionQty());
mainBo.setProductionOrder(request.getInstockCode()); // 入库单号作为业务来源单号
mainBo.setRemark(request.getInstockCode()); // 备注中存储入库单号,便于后续回调
mainBo.setBatchNo(request.getBatchCode());
mainBo.setStationName(request.getStationName());
mainBo.setWorkshop(request.getWorkshop());
mainBo.setSupplierName(request.getSupplierName());
mainBo.setStatus("0"); // 未处理
mainBo.setResult("2"); // 待判定
mainBo.setQualifiedQty(BigDecimal.ZERO);
mainBo.setUnqualifiedQty(BigDecimal.ZERO);
return mainBo;
}
/**
*
* <p>
*
*/
private void generateInspectionResults(Long inspectionId, Long templateId) {
QcTemplateItemBo itemBo = new QcTemplateItemBo();
itemBo.setTemplateId(templateId);
List<QcTemplateItemVo> itemList = qcTemplateItemService.queryList(itemBo);
for (QcTemplateItemVo item : itemList) {
QcInspectionResultBo resultBo = new QcInspectionResultBo();
resultBo.setInspectionId(inspectionId);
resultBo.setItemId(item.getItemId());
resultBo.setDetectResult("2"); // 未判定
resultBo.setItemCode(item.getItemCode());
resultBo.setItemName(item.getItemName());
resultBo.setInspectionPosition(item.getInspectionPosition());
resultBo.setCategoryName(item.getCategoryName());
resultBo.setDetectType(item.getDetectType());
resultBo.setControlType(item.getControlType());
resultBo.setStandardValue(item.getStandardValue());
resultBo.setUpperLimit(item.getUpperLimit());
resultBo.setLowerLimit(item.getLowerLimit());
resultBo.setSpecName(item.getSpecName());
resultBo.setSpecUpper(item.getSpecUpper());
resultBo.setSpecLower(item.getSpecLower());
resultBo.setDescription(item.getDescription());
resultBo.setTypeId(item.getInspectionType());
qcInspectionResultService.insertByBo(resultBo);
}
}
/**
*
*/
private int getTemplateItemCount(Long templateId) {
QcTemplateItemBo itemBo = new QcTemplateItemBo();
itemBo.setTemplateId(templateId);
return qcTemplateItemService.queryList(itemBo).size();
}
}

@ -4,6 +4,7 @@ import cn.dev33.satoken.annotation.SaCheckPermission;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import io.seata.spring.annotation.GlobalTransactional;
import lombok.RequiredArgsConstructor;
import org.dromara.common.core.domain.R;
import org.dromara.common.core.validate.AddGroup;
@ -17,11 +18,13 @@ import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.web.core.BaseController;
import org.dromara.wms.domain.bo.WmsInstockPrintBo;
import org.dromara.wms.domain.vo.WmsInstockPrintVo;
import org.dromara.wms.service.IWmsInspectionInitiationService;
import org.dromara.wms.service.IWmsInstockPrintService;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
/**
* -
@ -38,6 +41,8 @@ public class WmsInstockPrintController extends BaseController {
private final IWmsInstockPrintService wmsInstockPrintService;
private final IWmsInspectionInitiationService wmsInspectionInitiationService;
/**
* -
*/
@ -119,4 +124,29 @@ public class WmsInstockPrintController extends BaseController {
return wmsInstockPrintService.printCOde(vos);
}
/**
*
* <p>
*
*
* QMS Dubbo "质检中"
* <p>
*
* 1. inspectionRequest='0' inspectionType='0'
* 2. 使 @GlobalTransactional
* 3. WmsInspectionInitiationService
*
* @param prints
* @return Map<, >
*/
@SaCheckPermission("wms:instockPrint:createInspection")
@Log(title = "发起质检", businessType = BusinessType.INSERT)
@RepeatSubmit()
@PostMapping("/createInspection")
@GlobalTransactional(rollbackFor = Exception.class)
public R<Map<String, String>> createInspection(@RequestBody List<WmsInstockPrintBo> prints) {
Map<String, String> result = wmsInspectionInitiationService.createInspection(prints);
return R.ok(result);
}
}

@ -132,4 +132,15 @@ public class WmsInstockPrintBo extends BaseEntity {
*/
private String inspectionType;
/**
*
* <p>
*
* 1. inspectionType
* 2. 0=, 1=, 2=, 3=, 4=, 5=, 6=, 7=, 8=
* 3. ?inspectionType=7"7"
* 4. 使
*/
private String inspectionTypeParam;
}

@ -0,0 +1,70 @@
package org.dromara.wms.dubbo;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboService;
import org.dromara.common.core.domain.R;
import org.dromara.qms.api.dto.InspectionCompleteNotification;
import org.dromara.wms.api.RemoteWmsInstockService;
import org.dromara.wms.service.IWmsInspectionCallbackService;
import org.springframework.stereotype.Service;
/**
* WMS Dubbo
* <p>
* Dubbo RemoteWmsInstockService
* IWmsInspectionCallbackService
* <p>
*
* 1. Dubbo
* 2. WmsInspectionCallbackServiceImpl
* 3. QMS WMS
*
* @author zch
* @date 2026-01-15
*/
@Slf4j
@RequiredArgsConstructor
@Service
@DubboService
public class RemoteWmsInspectionCallbackServiceImpl implements RemoteWmsInstockService {
/**
*
*/
private final IWmsInspectionCallbackService wmsInspectionCallbackService;
/**
*
* <p>
* QMS WMS
* <p>
*
* 1. QMS @GlobalTransactional
* 2. PDA
* 3. inspectionType 0=, 1=, 2=, 3=
*
* @param notification
* @return
*/
@Override
public R<Void> completeInstockAfterInspection(InspectionCompleteNotification notification) {
log.info("收到 QMS Dubbo 回调,质检单号: {}, 入库单号: {}, 批次号: {}, 结果: {}",
notification.getInspectionNo(),
notification.getInstockCode(),
notification.getBatchCode(),
notification.getResult());
try {
boolean success = wmsInspectionCallbackService.handleInspectionComplete(notification);
if (success) {
return R.ok("质检回调处理成功");
} else {
return R.fail("质检回调处理失败");
}
} catch (Exception e) {
log.error("质检回调处理异常: {}", e.getMessage(), e);
return R.fail("质检回调处理异常: " + e.getMessage());
}
}
}

@ -0,0 +1,149 @@
package org.dromara.wms.service.impl;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.qms.api.dto.InspectionCompleteNotification;
import org.dromara.wms.domain.bo.WmsInstockPrintBo;
import org.dromara.wms.domain.vo.WmsInstockPrintVo;
import org.dromara.wms.service.IWmsInspectionCallbackService;
import org.dromara.wms.service.IWmsInstockPrintService;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* WMS
* <p>
* QMS WMS
* <p>
*
* 1. Dubbo RemoteWmsInspectionCallbackServiceImpl
* 2. 便
* 3. PDA
*
* @author zch
* @date 2026-01-15
*/
@Slf4j
@RequiredArgsConstructor
@Service
public class WmsInspectionCallbackServiceImpl implements IWmsInspectionCallbackService {
/**
*
*/
private final IWmsInstockPrintService instockPrintService;
/**
*
* <p>
* QMS @GlobalTransactional
*/
@Override
public boolean handleInspectionComplete(InspectionCompleteNotification notification) {
// 1. 参数校验
if (!"1".equals(notification.getStatus())) {
log.warn("质检未完成,跳过处理。质检单号: {}, 状态: {}",
notification.getInspectionNo(), notification.getStatus());
return false;
}
String instockCode = notification.getInstockCode();
String batchCode = notification.getBatchCode();
String result = notification.getResult();
log.info("收到 QMS 质检完成回调,质检单号: {}, 入库单号: {}, 批次号: {}, 结果: {}",
notification.getInspectionNo(), instockCode, batchCode, result);
// 2. 根据 instockCode + batchCode 精确查询打印记录
WmsInstockPrintVo printVo = queryByInstockAndBatch(instockCode, batchCode);
if (printVo == null) {
log.error("未找到入库打印记录。入库单号: {}, 批次号: {}", instockCode, batchCode);
return false;
}
// 3. 根据质检结果更新状态
if ("0".equals(result)) {
// 合格
return updateToQualified(instockCode, batchCode);
} else {
// 不合格
return updateToUnqualified(instockCode, batchCode);
}
}
/**
*
* <p>
* QMS @GlobalTransactional
*/
@Override
public boolean updateToQualified(String instockCode, String batchCode) {
WmsInstockPrintVo printVo = queryByInstockAndBatch(instockCode, batchCode);
if (printVo == null) {
log.error("未找到入库打印记录。入库单号: {}, 批次号: {}", instockCode, batchCode);
return false;
}
WmsInstockPrintBo updateBo = new WmsInstockPrintBo();
updateBo.setInstockPrintId(printVo.getInstockPrintId());
updateBo.setInspectionType("2"); // 合格
boolean result = instockPrintService.updateByBo(updateBo);
if (result) {
log.info("批次质检状态已更新为'合格'。入库单号: {}, 批次号: {}", instockCode, batchCode);
}
return result;
}
/**
*
* <p>
* QMS @GlobalTransactional
*/
@Override
public boolean updateToUnqualified(String instockCode, String batchCode) {
WmsInstockPrintVo printVo = queryByInstockAndBatch(instockCode, batchCode);
if (printVo == null) {
log.error("未找到入库打印记录。入库单号: {}, 批次号: {}", instockCode, batchCode);
return false;
}
WmsInstockPrintBo updateBo = new WmsInstockPrintBo();
updateBo.setInstockPrintId(printVo.getInstockPrintId());
updateBo.setInspectionType("3"); // 不合格
boolean result = instockPrintService.updateByBo(updateBo);
if (result) {
log.info("批次质检状态已更新为'不合格'。入库单号: {}, 批次号: {}", instockCode, batchCode);
}
return result;
}
/**
*
* <p>
* instockPrintService.queryList()
*/
@Override
public WmsInstockPrintVo queryByInstockAndBatch(String instockCode, String batchCode) {
if (StringUtils.isBlank(instockCode) || StringUtils.isBlank(batchCode)) {
log.warn("入库单号或批次号为空");
return null;
}
// 复用现有服务方法查询
WmsInstockPrintBo queryBo = new WmsInstockPrintBo();
queryBo.setInstockCode(instockCode);
queryBo.setBatchCode(batchCode);
List<WmsInstockPrintVo> list = instockPrintService.queryList(queryBo);
if (list == null || list.isEmpty()) {
return null;
}
// 精确匹配,应该只有一条记录
return list.get(0);
}
}

@ -0,0 +1,912 @@
# WMS-QMS 质检集成需求与方案文档
## 文档版本历史
| 版本 | 日期 | 修订内容 | 作者 |
|-----|------|---------|------|
| 1.0 | 2026-01-15 | 初始版本,完整需求与方案设计 | zch |
| 1.1 | 2026-01-15 | 新增独立文件实现,添加代码实现章节 | zch |
| 1.2 | 2026-01-15 | 修复Code Review问题创建独立 Dubbo 实现类、修复状态映射、添加批次号过滤 | 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_mainstatus='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<Map<String, String>> createInspection(@RequestBody List<WmsInstockPrintBo> 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<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服务实现
```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<Void> 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<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 新增按钮
```vue
<!-- 在工具栏添加"发起质检"按钮 -->
<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 按钮点击处理
```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` 注解
#### 场景1WMS 调用 QMS 创建质检任务
```java
// 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;
}
}
```
#### 场景2QMS 调用 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 业务场景
```
批次 BATCH001总数100
├─ 质检完成80合格 + 20不合格
│ ├─ 合格80 → QMS 回调 WMS → inspection_type='2'
│ └─ 不合格20 → 创建不合格品评审
└─ 不合格品评审 → 让步接收reviewResult='4'
└─ QMS 回调 WMS → inspection_type='2'(让步接收视为合格)
└─ PDA 扫码入库 20 件
```
### 9.2 技术方案(待讨论)
#### 方案A让步接收时单独回调推荐
```java
// QMS 模块 - 不合格品评审服务
@Service
public class QcUnqualifiedReviewServiceImpl {
@Override
@GlobalTransactional(rollbackFor = Exception.class)
public Boolean completeTask(QcUnqualifiedReviewBo bo) {
QcUnqualifiedReview review = baseMapper.selectById(bo.getReviewId());
// 1. 完成工作流审批
// ... 现有逻辑 ...
// 2. 【新增】让步接收回调 WMS
if ("4".equals(review.getReviewResult())) {
notifyWmsForConcession(review);
}
return baseMapper.updateById(review) > 0;
}
/**
* 让步接收完成后回调 WMS单独抽离的方法
* 后续可改为其他实现方式REST API / 消息队列)
*/
private void notifyWmsForConcession(QcUnqualifiedReview review) {
// 从质检单号获取质检主表信息
QcInspectionMainVo inspectionMain = qcInspectionMainService.queryByInspectionNo(review.getInspectionNo());
// 构建回调参数
InspectionCompleteNotification notification = new InspectionCompleteNotification();
notification.setInspectionNo(review.getInspectionNo());
notification.setInstockCode(inspectionMain.getRemark()); // remark 存储入库单号
notification.setBatchCode(review.getBatchNo()); // 批次号
notification.setQualifiedQty(review.getInspectionQty()); // 让步接收数量
notification.setUnqualifiedQty(BigDecimal.ZERO);
notification.setResult("0"); // 让步接收视为合格
notification.setStatus("1"); // 已完成
// 调用 WMS Dubbo 接口
remoteWmsInspectionService.notifyInspectionComplete(notification);
log.info("让步接收回调 WMS 成功,批次号: {}, 数量: {}", review.getBatchNo(), review.getInspectionQty());
}
}
```
#### 方案B与合格数量回调合并
将让步接收也调用 `notifyWmsForQualified()` 方法,统一处理。
### 9.3 数据一致性保证
| 场景 | 合格数量 | 让步接收数量 | 累计入库 | 最终状态 |
|-----|---------|-------------|---------|---------|
| 场景1 | 80 | 20 | 100 | 已入库 |
| 场景2 | 100 | 0 | 100 | 已入库 |
| 场景3 | 0 | 100 | 100 | 已入库 |
---
## 10. 附录
### 10.1 字典值定义
#### wms_inspection_type质检状态
| 字典值 | 字典标签 | 说明 |
|-------|---------|------|
| 0 | 未发起 | 打印后默认状态 |
| 1 | 质检中 | 已创建质检任务,等待执行 |
| 2 | 合格 | 质检合格 |
| 3 | 不合格 | 质检不合格 |
#### inspection_request质检要求
| 字典值 | 字典标签 | 说明 |
|-------|---------|------|
| 0 | 必检 | 需要质检 |
| 1 | 免检 | 不需要质检 |
#### 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 接口(待确认)
---
**文档结束**

@ -0,0 +1,122 @@
# WMS仓储模块
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
## 核心数据表
### 基础数据表(除外)
- `base_measurement_unit_info` - 计量单位
- `base_supplier_info1` - 供应商
- `base_material_category` - 物料分类
- `base_material_type` - 物料类型
- `base_material_info_copy1` / `base_material_info` - 物料信息(通过@TableName决定使用哪个
### WMS核心关注表
- `wms_base_customer` - 客户
- `wms_base_area` - 仓库区域
- `wms_base_location` - 库位
- `wms_base_warehouse` - 仓库
- `wms_allocate_order` - 调拨单主表
- `wms_allocate_order_detail` - 调拨单明细
- `wms_allocate_task` - 调拨任务(AGV)
- `wms_check_task` - 盘点任务配置
- `wms_purchase_order` - 采购订单
- `wms_purchase_order_detail` - 采购订单明细
- `wms_instock_detail` - 入库单明细
- `wms_instock_order` - 入库单主表
- `wms_instock_print` - 入库打印配置
- `wms_instock_record` - 入库记录(PDA扫描)
- `wms_inventory` - 实时库存
- `wms_inventory_check` - 盘点单主表
- `wms_outstock_detail` - 出库单明细
- `wms_outstock_order` - 出库单主表
- `wms_outstock_record` - 出库记录(PDA扫描)
---
## 主子表关系详解
```
【仓库层级结构】
wms_base_warehouse (仓库)
wms_base_area (仓库区域)
wms_base_location (库位)
【采购入库流程】
wms_purchase_order (采购订单)
wms_purchase_order_detail (采购明细)
wms_instock_order (入库单)
wms_instock_detail (入库明细) → 打印标签 → wms_instock_record (PDA扫描入库)
更新 wms_inventory (库存+)
【调拨流程】
wms_allocate_order (调拨单)
wms_allocate_order_detail (调拨明细)
├─→ wms_outstock_order (出库单) → wms_outstock_record → 库存-
├─→ wms_instock_order (入库单) → wms_instock_record → 库存+
└─→ wms_allocate_task (AGV搬运任务)
【销售出库流程】
销售订单(ERP) → wms_outstock_order (出库单)
wms_outstock_detail (出库明细) → wms_outstock_record (扫描出库)
更新 wms_inventory (库存-)
【盘点流程】
wms_check_task (定时任务) → wms_inventory_check (盘点单)
wms_inventory_check_record (盘点记录) → 差异审核 → 更新 wms_inventory
```
---
## 主子表对
| 主表 | 子表 | 关联字段 | 说明 |
|-----|-----|---------|-----|
| `wms_purchase_order` | `wms_purchase_order_detail` | `po_no` | 采购订单包含多个采购明细 |
| `wms_instock_order` | `wms_instock_detail` | `instock_id` / `instock_code` | 入库单包含多个入库明细 |
| `wms_outstock_order` | `wms_outstock_detail` | `outstock_id` / `outstock_code` | 出库单包含多个出库明细 |
| `wms_allocate_order` | `wms_allocate_order_detail` | `allocate_code` | 调拨单包含多个调拨明细 |
| `wms_inventory_check` | `wms_inventory_check_record` | `check_code` | 盘点单包含多个盘点记录 |
---
## 基础数据关联
| 表 | 关联表 | 关联字段 | 说明 |
|---|--------|---------|-----|
| `wms_base_area` | `wms_base_warehouse` | `warehouse_id` | 区域归属仓库 |
| `wms_base_location` | `wms_base_area` | `area_id` | 库位归属区域 |
| `wms_instock_detail` | `wms_purchase_order_detail` | `po_d_id` | 入库明细追溯采购明细 |
| `wms_allocate_task` | `wms_allocate_order_detail` | `ao_d_id` | 调拨任务关联调拨明细 |
---
## 关键设计特点
1. **仓库层级**: 仓库 → 区域 → 库位,三级层级结构
2. **单据驱动**: 所有库存变动都通过单据(入库/出库/调拨/盘点)触发
3. **主子分离**: 主表记录总体信息(单号、仓库、供应商/客户),子表记录明细(物料、数量、批次)
4. **实时库存**: `wms_inventory`表实时更新,所有入库/出库记录汇总
5. **AGV集成**: 支持`instock_methond`和`is_agv`字段控制AGV任务生成
---
## 核心业务字段说明
| 字段 | 说明 |
|-----|------|
| `instock_type` | 入库类型 (1采购订单, 2生产订单, 3手工, 4系统生成) |
| `outstock_type` | 出库类型 (1销售出库, 2生产领料, 3调拨出库, 4其他) |
| `special_type` | 特殊标识 (0调拨, 1退库) |
| `audit_status` | 审核状态 (0待审核, 1审核通过, 2审核未通过) |
| `inspection_request` | 质检要求 (0必检, 1免检) - 必检则需经QC流程后入库 |
| `instock_methond` | 入库方式 (0PDA, 1AGV, 2质检后PDA, 3质检后AGV) |
Loading…
Cancel
Save