diff --git a/ruoyi-api/hwmom-api-qms/src/main/java/org/dromara/qms/api/dto/InspectionCompleteNotification.java b/ruoyi-api/hwmom-api-qms/src/main/java/org/dromara/qms/api/dto/InspectionCompleteNotification.java index 322aa191..547f56a2 100644 --- a/ruoyi-api/hwmom-api-qms/src/main/java/org/dromara/qms/api/dto/InspectionCompleteNotification.java +++ b/ruoyi-api/hwmom-api-qms/src/main/java/org/dromara/qms/api/dto/InspectionCompleteNotification.java @@ -40,6 +40,14 @@ public class InspectionCompleteNotification implements Serializable { */ private String instockCode; + /** + * 批次号 + *

+ * 必填,用于 WMS 精确匹配批次记录 + * 同一入库单可能包含多个批次,必须通过批次号区分 + */ + private String batchCode; + /** * 质检结果 *

diff --git a/ruoyi-api/hwmom-api-wms/pom.xml b/ruoyi-api/hwmom-api-wms/pom.xml index 8a551c02..03ea15cf 100644 --- a/ruoyi-api/hwmom-api-wms/pom.xml +++ b/ruoyi-api/hwmom-api-wms/pom.xml @@ -23,6 +23,13 @@ ruoyi-common-core + + + org.dromara + hwmom-api-qms + ${revision} + + org.dromara ruoyi-common-excel diff --git a/ruoyi-api/hwmom-api-wms/src/main/java/org/dromara/wms/api/RemoteWmsInstockService.java b/ruoyi-api/hwmom-api-wms/src/main/java/org/dromara/wms/api/RemoteWmsInstockService.java new file mode 100644 index 00000000..eecc4e41 --- /dev/null +++ b/ruoyi-api/hwmom-api-wms/src/main/java/org/dromara/wms/api/RemoteWmsInstockService.java @@ -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 接口 + *

+ * 提供 QMS 与 WMS 集成的入库相关服务 + * + * @author zch + * @date 2026-1-14 + */ +public interface RemoteWmsInstockService { + + /** + * 质检完成通知入库(备用接口,暂不实现) + *

+ * 业务场景: + * 1. QMS 质检完成后调用此接口通知 WMS + * 2. WMS 根据质检结果决定是否完成入库 + * 3. 合格:完成入库,更新库存 + * 4. 不合格:标记拒收,生成不合格品记录 + *

+ * 说明: + * - 主要方式采用 REST API(POST /wsmApi/notifyInspectionComplete) + * - 此 Dubbo 接口作为备用方案,未来如需服务间直接调用可启用 + *

+ * 说明:租户ID和用户ID从当前调用上下文自动获取 + * + * @param notification 质检完成通知参数 + * @return 处理结果 + */ + R completeInstockAfterInspection(InspectionCompleteNotification notification); + +} diff --git a/ruoyi-modules/hwmom-qms/src/main/java/org/dromara/qms/dubbo/RemoteQmsInspectionServiceImpl.java b/ruoyi-modules/hwmom-qms/src/main/java/org/dromara/qms/dubbo/RemoteQmsInspectionServiceImpl.java new file mode 100644 index 00000000..b696a983 --- /dev/null +++ b/ruoyi-modules/hwmom-qms/src/main/java/org/dromara/qms/dubbo/RemoteQmsInspectionServiceImpl.java @@ -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 实现 + *

+ * 为 WMS 等外部服务提供质检相关功能的 RPC 调用接口 + *

+ * 说明:此 Dubbo 服务层仅负责处理跨服务调用的上下文传递(租户、用户), + * 具体业务逻辑委托给 {@link IQcWmsService} 实现 + * + * @author zch + * @date 2026-1-14 + */ +@Slf4j +@RequiredArgsConstructor +@Service +@DubboService +public class RemoteQmsInspectionServiceImpl implements RemoteQmsInspectionService { + + private final IQcWmsService qcWmsService; + + /** + * 为 WMS 入库单创建质检任务 + *

+ * 业务说明: + * 1. 从 Dubbo 调用上下文获取租户ID和用户ID + * 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 质检完成(预留接口,暂不实现) + *

+ * 说明:质检完成回调采用 REST API 方式实现(POST /wsmApi/notifyInspectionComplete) + * 此 Dubbo 接口作为备用方案,未来如需服务间直接调用可启用 + * + * @param notification 质检完成通知参数 + * @return 处理结果 + */ + @Override + public R notifyInspectionComplete(InspectionCompleteNotification notification) { + log.info("收到质检完成通知(Dubbo方式),质检单号: {}, 质检结果: {}", notification.getInspectionNo(), notification.getResult()); + log.warn("当前采用 REST API 方式回调,此 Dubbo 接口暂不实现"); + return R.ok("此接口暂未启用,请使用 REST API 方式回调"); + } +} diff --git a/ruoyi-modules/hwmom-qms/src/main/java/org/dromara/qms/service/impl/QcWmsServiceImpl.java b/ruoyi-modules/hwmom-qms/src/main/java/org/dromara/qms/service/impl/QcWmsServiceImpl.java new file mode 100644 index 00000000..95995f74 --- /dev/null +++ b/ruoyi-modules/hwmom-qms/src/main/java/org/dromara/qms/service/impl/QcWmsServiceImpl.java @@ -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; + +/** + * WMS仓储质检Service业务层处理 + *

+ * 为 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 入库单创建质检任务 + *

+ * 业务流程: + * 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; + } + + /** + * 生成质检结果子表 + *

+ * 根据模板中的检测项生成对应的质检结果记录 + */ + private void generateInspectionResults(Long inspectionId, Long templateId) { + QcTemplateItemBo itemBo = new QcTemplateItemBo(); + itemBo.setTemplateId(templateId); + List 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(); + } +} diff --git a/ruoyi-modules/hwmom-wms/src/main/java/org/dromara/wms/controller/WmsInstockPrintController.java b/ruoyi-modules/hwmom-wms/src/main/java/org/dromara/wms/controller/WmsInstockPrintController.java index f9f43c30..08315d02 100644 --- a/ruoyi-modules/hwmom-wms/src/main/java/org/dromara/wms/controller/WmsInstockPrintController.java +++ b/ruoyi-modules/hwmom-wms/src/main/java/org/dromara/wms/controller/WmsInstockPrintController.java @@ -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); } + /** + * 批量发起质检任务 + *

+ * 业务场景: + * 前端选择批次后,调用此接口创建质检任务 + * 服务层调用 QMS Dubbo 接口创建质检任务,并更新打印记录状态为"质检中" + *

+ * 说明: + * 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> createInspection(@RequestBody List prints) { + Map result = wmsInspectionInitiationService.createInspection(prints); + return R.ok(result); + } + } diff --git a/ruoyi-modules/hwmom-wms/src/main/java/org/dromara/wms/domain/bo/WmsInstockPrintBo.java b/ruoyi-modules/hwmom-wms/src/main/java/org/dromara/wms/domain/bo/WmsInstockPrintBo.java index a6fb4de2..f7266438 100644 --- a/ruoyi-modules/hwmom-wms/src/main/java/org/dromara/wms/domain/bo/WmsInstockPrintBo.java +++ b/ruoyi-modules/hwmom-wms/src/main/java/org/dromara/wms/domain/bo/WmsInstockPrintBo.java @@ -132,4 +132,15 @@ public class WmsInstockPrintBo extends BaseEntity { */ private String inspectionType; + /** + * 质检类型参数(前端传入,用于动态指定检验类型) + *

+ * 说明: + * 1. 此字段与 inspectionType(质检状态)不同,表示检验类型编码 + * 2. 检验类型字典:0=首检, 1=专检, 2=自检, 3=互检, 4=原材料检, 5=抽检, 6=成品检, 7=入库检, 8=出库检 + * 3. 前端通过路由参数传递(如 ?inspectionType=7),不传则默认为"7"(入库检) + * 4. 仅在创建质检任务时使用,不影响本地质检状态字段 + */ + private String inspectionTypeParam; + } diff --git a/ruoyi-modules/hwmom-wms/src/main/java/org/dromara/wms/dubbo/RemoteWmsInspectionCallbackServiceImpl.java b/ruoyi-modules/hwmom-wms/src/main/java/org/dromara/wms/dubbo/RemoteWmsInspectionCallbackServiceImpl.java new file mode 100644 index 00000000..35496052 --- /dev/null +++ b/ruoyi-modules/hwmom-wms/src/main/java/org/dromara/wms/dubbo/RemoteWmsInspectionCallbackServiceImpl.java @@ -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 服务实现 + *

+ * 独立的 Dubbo 服务实现类,实现 RemoteWmsInstockService 接口 + * 内部委托给 IWmsInspectionCallbackService 处理具体业务逻辑 + *

+ * 说明: + * 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; + + /** + * 质检完成后回调入库完成 + *

+ * 业务场景:QMS 质检完成后调用此接口通知 WMS + *

+ * 说明: + * 1. 调用方(QMS)需要添加 @GlobalTransactional 注解开启分布式事务 + * 2. 此方法只更新质检状态,不创建入库记录(入库记录由 PDA 扫码创建) + * 3. inspectionType 状态:0=未发起, 1=质检中, 2=合格, 3=不合格 + * + * @param notification 质检完成通知参数 + * @return 处理结果 + */ + @Override + public R 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()); + } + } +} diff --git a/ruoyi-modules/hwmom-wms/src/main/java/org/dromara/wms/service/impl/WmsInspectionCallbackServiceImpl.java b/ruoyi-modules/hwmom-wms/src/main/java/org/dromara/wms/service/impl/WmsInspectionCallbackServiceImpl.java new file mode 100644 index 00000000..ce036041 --- /dev/null +++ b/ruoyi-modules/hwmom-wms/src/main/java/org/dromara/wms/service/impl/WmsInspectionCallbackServiceImpl.java @@ -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 质检回调服务实现 + *

+ * 负责接收 QMS 质检完成通知并更新 WMS 批次状态 + *

+ * 说明: + * 1. 此服务为本地服务实现,Dubbo 服务由 RemoteWmsInspectionCallbackServiceImpl 单独实现 + * 2. 此服务为独立抽离的新文件,不修改现有文件,便于维护和调试 + * 3. 只更新质检状态,不创建入库记录(入库记录由 PDA 扫码创建) + * + * @author zch + * @date 2026-01-15 + */ +@Slf4j +@RequiredArgsConstructor +@Service +public class WmsInspectionCallbackServiceImpl implements IWmsInspectionCallbackService { + + /** + * 复用现有的入库打印服务 + */ + private final IWmsInstockPrintService instockPrintService; + + /** + * 处理质检完成通知 + *

+ * 说明:调用方(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); + } + } + + /** + * 更新批次质检状态为合格 + *

+ * 说明:调用方(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; + } + + /** + * 更新批次质检状态为不合格 + *

+ * 说明:调用方(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; + } + + /** + * 根据入库单号和批次号精确查询打印记录 + *

+ * 说明:复用现有 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 list = instockPrintService.queryList(queryBo); + if (list == null || list.isEmpty()) { + return null; + } + + // 精确匹配,应该只有一条记录 + return list.get(0); + } +} diff --git a/ruoyi-modules/hwmom-wms/wms-qms.md b/ruoyi-modules/hwmom-wms/wms-qms.md new file mode 100644 index 00000000..5225f267 --- /dev/null +++ b/ruoyi-modules/hwmom-wms/wms-qms.md @@ -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_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 业务场景 + +``` +批次 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 接口(待确认) + +--- + + + +**文档结束** diff --git a/ruoyi-modules/hwmom-wms/wms.md b/ruoyi-modules/hwmom-wms/wms.md new file mode 100644 index 00000000..79cb692a --- /dev/null +++ b/ruoyi-modules/hwmom-wms/wms.md @@ -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) |