diff --git a/ruoyi-api/ruoyi-api-system/src/main/java/org/dromara/system/api/RemoteCodeRuleService.java b/ruoyi-api/ruoyi-api-system/src/main/java/org/dromara/system/api/RemoteCodeRuleService.java index cbab9e4f..f2ab485b 100644 --- a/ruoyi-api/ruoyi-api-system/src/main/java/org/dromara/system/api/RemoteCodeRuleService.java +++ b/ruoyi-api/ruoyi-api-system/src/main/java/org/dromara/system/api/RemoteCodeRuleService.java @@ -14,4 +14,14 @@ public interface RemoteCodeRuleService { */ String selectCodeRuleCode(String codeRuleCode); + /** + * 通过编码规则编码获取编码(支持Dubbo租户传递) + *
+ * 适用于上位机等无登录场景的跨服务调用 + * + * @param codeRuleCode 编码规则编码 + * @return currentCode + */ + String selectCodeRuleCodeWithTenant(String codeRuleCode); + } diff --git a/ruoyi-modules/hwmom-qms/qc通用模块.md b/ruoyi-modules/hwmom-qms/qc通用模块.md index 472496c5..7b29b516 100644 --- a/ruoyi-modules/hwmom-qms/qc通用模块.md +++ b/ruoyi-modules/hwmom-qms/qc通用模块.md @@ -53,4 +53,60 @@ qc_inspection_main (检验单主表) → [产生不合格] → qc_unqualified_re 1. **模板驱动**: 检验单关联模板,模板包含多个检测项定义 2. **级联继承**: 检验结果从检测项定义继承标准值、上下限等规格参数 3. **主子分离**: 主表记录总体信息(单号、物料、数量),子表记录明细(每个检测项的检测结果) -4. **不合格评审**: 当检验结果不合格时,触发评审流程,生成评审主表及记录子表 \ No newline at end of file +4. **不合格评审**: 当检验结果不合格时,触发评审流程,生成评审主表及记录子表 + +--- + +## 质检模板匹配策略(核心业务规则) + +**生成质检任务时,按以下4级降级优先级自动匹配模板:** + +``` +┌─────────────────────────────────────────────────────────────┐ +│ 质检模板匹配策略 │ +├─────────────────────────────────────────────────────────────┤ +│ │ +│ 第1级:物料 + 工位 + 工序 + 检测类型(精确匹配) │ +│ ↓ 未找到 │ +│ 第2级:物料 + 工位 + 检测类型 │ +│ ↓ 未找到 │ +│ 第3级:物料 + 检测类型 │ +│ ↓ 未找到 │ +│ 第4级:通用模板(仅检测类型 + isDefault=1) │ +│ ↓ 未找到 → 抛出异常:"无可用检测模板" │ +│ │ +└─────────────────────────────────────────────────────────────┘ +``` + +### 匹配条件说明 + +| 优先级 | 匹配条件 | 说明 | 业务场景 | +|-------|---------|------|---------| +| 1 | 物料 + 工位 + 工序 + 检测类型 | 最精确匹配 | 某物料在特定工位/工序有特殊质检要求 | +| 2 | 物料 + 工位 + 检测类型 | 特定工位(不限工序) | 某物料在特定工位的质检要求统一 | +| 3 | 物料 + 检测类型 | 特定物料(不限工位/工序) | 某物料的质检要求在任何工位/工序都一致 | +| 4 | 通用模板(仅检测类型) | 兜底方案 | 该检测类型的默认质检方案 | + +### 检测类型字典 + +| 代码 | 检测类型 | 说明 | +|------|---------|------| +| 0 | 首检 | 生产开始前首批产品的检验 | +| 1 | 专检 | 专职检验员的检验 | +| 2 | 自检 | 操作工自己检验 | +| 3 | 互检 | 相互检验(上下工序互检) | +| 4 | 原材料检 | 原材料入库检验 | +| 5 | 抽检 | 抽样检验 | +| 6 | 成品检 | 成品检验 | +| 7 | 入库检 | 产品入库前的检验 | + +### 通用模板规则 + +**定义**: +- **通用模板**:不绑定具体物料、工序或工位,仅按检测类型适用的默认质检方案 +- **标识字段**:`is_default`(0否/1是) + +**约束规则**: +1. **唯一性**:每种检测类型只能有一个通用模板 +2. **互斥性**:通用模板不能同时绑定物料、工序或工位 +3. **校验方式**:应用层校验(Service层代码校验,非数据库约束) diff --git a/ruoyi-modules/hwmom-qms/src/main/java/org/dromara/qms/controller/QcHMIController.java b/ruoyi-modules/hwmom-qms/src/main/java/org/dromara/qms/controller/QcHMIController.java new file mode 100644 index 00000000..1cce9d13 --- /dev/null +++ b/ruoyi-modules/hwmom-qms/src/main/java/org/dromara/qms/controller/QcHMIController.java @@ -0,0 +1,60 @@ +package org.dromara.qms.controller; + +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.dromara.common.core.domain.R; +import org.dromara.qms.domain.dto.QcInspectionMainTask; +import org.dromara.qms.service.IQcHMIService; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +/** + * 上位机接口 + *
+ * 提供上位机(HMI)调用质检相关功能的接口 + * + * @author zch + * @date 2026-01-14 + */ +@Validated +@RequiredArgsConstructor +@Slf4j +@RestController +@RequestMapping("/hmi") +public class QcHMIController { + + private final IQcHMIService qcHmiService; + + /** + * 上位机调用生成质检任务 + *
+ * 接口路径: POST /hmi/QcInspectionMainTask + *
+ * 请求参数示例: + *
+ * {
+ * "MaterialCode": "30109",
+ * "StationName": "内衬层#01",
+ * "InspectionQty": "12",
+ * "InspectionType": "7",
+ * "ProductionOrder": "20260112100834PL0005",
+ * "BatchNo": "123",
+ * "Barcode": "20260114NC001010006"
+ * }
+ *
+ *
+ * @param taskDto 上位机传入的任务DTO
+ * @return 质检单号
+ */
+ // 上位机接口不登录,不使用@Log注解(LogAspect会尝试获取用户信息导致异常)
+ @PostMapping("/QcInspectionMainTask")
+ public R+ * 注意:字段使用 @JsonProperty 注解匹配上位机传入的大写开头JSON字段名 + * + * @author zch + * @date 2026-1-13 + */ +@Data +public class QcInspectionMainTask { + + /** + * 物料编码 + */ + @JsonProperty("MaterialCode") + private String materialCode; + + /** + * 工位名称 + */ + @JsonProperty("StationName") + private String stationName; + + /** + * 检验数量 + */ + @JsonProperty("InspectionQty") + private String inspectionQty; + + /** + * 检验类型(0首检 1专检 2自检 3互检 4原材料检 5抽检 6成品检 7入库检) + * 必填,上位机写死传7入库检 + */ + @JsonProperty("InspectionType") + private String inspectionType; + + /** + * 生产订单号(业务来源单号) + * 上位机传的是prod plan info_2的plan_code + */ + @JsonProperty("ProductionOrder") + private String productionOrder; + + /** + * 批次号 + */ + @JsonProperty("BatchNo") + private String batchNo; + + /** + * 条码 + */ + @JsonProperty("Barcode") + private String barcode; + +} diff --git a/ruoyi-modules/hwmom-qms/src/main/java/org/dromara/qms/service/IQcHMIService.java b/ruoyi-modules/hwmom-qms/src/main/java/org/dromara/qms/service/IQcHMIService.java new file mode 100644 index 00000000..5804541e --- /dev/null +++ b/ruoyi-modules/hwmom-qms/src/main/java/org/dromara/qms/service/IQcHMIService.java @@ -0,0 +1,29 @@ +package org.dromara.qms.service; + +import org.dromara.qms.domain.dto.QcInspectionMainTask; + +/** + * 上位机接口Service接口 + * + * @author zch + * @date 2026-01-14 + */ +public interface IQcHMIService { + + /** + * 上位机调用生成质检任务 + *
+ * 业务流程: + * 1. 参数校验(物料编码、检验类型必填) + * 2. 根据工位名称查询工位编码 + * 3. 匹配质检模板(8级降级匹配策略) + * 4. 生成质检单号 + * 5. 构建并保存质检主表 + * 6. 根据模板生成质检结果子表 + * 7. 返回质检单号 + * + * @param taskDto 上位机传入的任务DTO + * @return 质检单号 + */ + String generateInspectionTask(QcInspectionMainTask taskDto); +} diff --git a/ruoyi-modules/hwmom-qms/src/main/java/org/dromara/qms/service/impl/QcHMIServiceImpl.java b/ruoyi-modules/hwmom-qms/src/main/java/org/dromara/qms/service/impl/QcHMIServiceImpl.java new file mode 100644 index 00000000..3edf11de --- /dev/null +++ b/ruoyi-modules/hwmom-qms/src/main/java/org/dromara/qms/service/impl/QcHMIServiceImpl.java @@ -0,0 +1,277 @@ +package org.dromara.qms.service.impl; + +import java.math.BigDecimal; +import java.util.Date; +import java.util.List; + +import cn.dev33.satoken.stp.StpUtil; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.dubbo.config.annotation.DubboReference; +import org.apache.dubbo.rpc.RpcContext; +import org.dromara.common.core.exception.ServiceException; +import org.dromara.common.core.utils.StringUtils; +import org.dromara.common.tenant.helper.TenantHelper; +import org.dromara.system.api.RemoteCodeRuleService; +import org.dromara.qms.domain.bo.QcInspectionMainBo; +import org.dromara.qms.domain.bo.QcInspectionResultBo; +import org.dromara.qms.domain.bo.ProdBaseStationInfoBo; +import org.dromara.qms.domain.bo.ProdBaseProcessInfoBo; +import org.dromara.qms.domain.bo.QcTemplateItemBo; +import org.dromara.qms.domain.dto.QcInspectionMainTask; +import org.dromara.qms.domain.vo.ProdBaseStationInfoVo; +import org.dromara.qms.domain.vo.ProdBaseProcessInfoVo; +import org.dromara.qms.domain.vo.QcInspectionTemplateVo; +import org.dromara.qms.domain.vo.QcTemplateItemVo; +import org.dromara.qms.service.IQcHMIService; +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.IProdBaseStationInfoService; +import org.dromara.qms.service.IProdBaseProcessInfoService; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +/** + * 上位机接口Service业务层处理 + * + * @author zch + * @date 2026-01-14 + */ +@Slf4j +@RequiredArgsConstructor +@Service +public class QcHMIServiceImpl implements IQcHMIService { + + private final IQcInspectionMainService qcInspectionMainService; + private final IQcInspectionResultService qcInspectionResultService; + private final IQcInspectionTemplateService qcInspectionTemplateService; + private final IQcTemplateItemService qcTemplateItemService; + private final IProdBaseStationInfoService prodBaseStationInfoService; + private final IProdBaseProcessInfoService prodBaseProcessInfoService; + + /** + * 编码规则服务(Dubbo 引用) + * 用于生成质检单号 + */ + @DubboReference(timeout = 300000) + private final RemoteCodeRuleService remoteCodeRuleService; + + /** + * 上位机默认租户ID + */ + private static final String HMI_DEFAULT_TENANT_ID = "000000"; + + /** + * 上位机默认创建人ID(系统管理员) + */ + private static final Long HMI_DEFAULT_CREATE_BY = 1L; + + /** + * 上位机调用生成质检任务 + *
+ * 业务流程: + * 1. 参数校验(物料编码、检验类型必填) + * 2. 根据工位名称查询工位编码 + * 3. 匹配质检模板(8级降级匹配策略) + * 4. 生成质检单号 + * 5. 构建并保存质检主表 + * 6. 根据模板生成质检结果子表 + * 7. 返回质检单号 + * + * @param taskDto 上位机传入的任务DTO + * @return 质检单号 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public String generateInspectionTask(QcInspectionMainTask taskDto) { + // 【重要】上位机不登录,模拟登录系统管理员,确保认证上下文和租户信息正确传递 + StpUtil.login(HMI_DEFAULT_CREATE_BY, "login"); + // 设置默认租户上下文 + TenantHelper.setDynamic(HMI_DEFAULT_TENANT_ID); + // 【关键】通过Dubbo隐式参数传递租户ID,确保远程服务能获取租户上下文 + RpcContext.getContext().setAttachment("tenantId", HMI_DEFAULT_TENANT_ID); + + try { + return doGenerateInspectionTask(taskDto); + } finally { + // 【清理】清理登录状态和租户上下文,避免线程池复用时的状态污染 + try { + StpUtil.logout(); + } catch (Exception ignored) { + } + try { + TenantHelper.clearDynamic(); + } catch (Exception ignored) { + } + } + } + + /** + * 实际执行生成质检任务的逻辑 + */ + private String doGenerateInspectionTask(QcInspectionMainTask taskDto) { + // 1. 参数校验 + if (StringUtils.isBlank(taskDto.getMaterialCode())) { + throw new ServiceException("物料编码不能为空"); + } + if (StringUtils.isBlank(taskDto.getInspectionType())) { + throw new ServiceException("检验类型不能为空"); + } + + log.info("上位机调用生成质检任务,物料编码: {}, 工位名称: {}, 检验类型: {}", + taskDto.getMaterialCode(), taskDto.getStationName(), taskDto.getInspectionType()); + + // 2. 匹配质检模板(调用已实现的8级降级匹配策略) + // FIXME: QcInspectionMainTask.stationName 是工位名称,需要查询工位编码 + // FIXME: 上位机不传工序信息,工序编码传 null + // TODO: 后期建议上位机直接传递编码,避免跨模块查询 + String materialCode = taskDto.getMaterialCode(); + String stationCode = getStationCodeByName(taskDto.getStationName()); // 根据工位名称查询工位编码 + QcInspectionTemplateVo templateVo = qcInspectionTemplateService.getMatchedTemplate( + materialCode, + stationCode, // 工位编码(通过名称查询) + null, // 工序编码(上位机不传) + taskDto.getInspectionType() + ); + + if (templateVo == null) { + throw new ServiceException("未找到匹配的质检模板,物料编码: " + taskDto.getMaterialCode()); + } + + log.info("质检模板匹配成功,模板ID: {}, 模板名称: {}", templateVo.getTemplateId(), templateVo.getTemplateName()); + + // 3. 生成质检单号(调用编码规则服务,使用支持租户传递的方法) + String inspectionNo = remoteCodeRuleService.selectCodeRuleCodeWithTenant("3"); + if (StringUtils.isBlank(inspectionNo)) { + throw new ServiceException("获取质检单号失败"); + } + + // 4. 构建质检主表BO + QcInspectionMainBo mainBo = buildInspectionMainBo(taskDto, 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; + } + + /** + * 构建质检主表 BO + */ + private QcInspectionMainBo buildInspectionMainBo(QcInspectionMainTask taskDto, + 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(taskDto.getMaterialCode()); + mainBo.setStationName(taskDto.getStationName()); + mainBo.setInspectionQty(new BigDecimal(taskDto.getInspectionQty())); + mainBo.setProductionOrder(taskDto.getProductionOrder()); + mainBo.setBatchNo(taskDto.getBatchNo()); + mainBo.setBarcode(taskDto.getBarcode()); + mainBo.setStatus("0"); // 未处理 + mainBo.setResult("0"); // 待判定 + + // 上位机不登录,手动设置审计字段 + mainBo.setCreateBy(HMI_DEFAULT_CREATE_BY); + mainBo.setCreateTime(new Date()); + return mainBo; + } + + /** + * 生成质检结果子表 + *
+ * 根据模板中的检测项生成对应的质检结果记录
+ */
+ private void generateInspectionResults(Long inspectionId, Long templateId) {
+ QcTemplateItemBo itemBo = new QcTemplateItemBo();
+ itemBo.setTemplateId(templateId);
+ List
+ * 该方法从Dubbo上下文获取租户ID并设置,确保租户过滤生效
+ * 适用于上位机等无登录场景的跨服务调用
+ *
+ * @param codeRuleCode 编码规则编码
+ * @return currentCode
+ */
+ @Override
+ public String selectCodeRuleCodeWithTenant(String codeRuleCode) {
+ // 从Dubbo上下文获取租户ID并设置,确保租户过滤生效
+ String tenantId = RpcContext.getContext().getAttachment("tenantId");
+ if (StringUtils.isNotBlank(tenantId)) {
+ TenantHelper.setDynamic(tenantId);
+ }
+ try {
+ SysCodeRuleBo bo = new SysCodeRuleBo();
+ bo.setCodeRuleCode(codeRuleCode);
+ return sysCodeRuleService.getRuleGenerateCode(bo);
+ } finally {
+ if (StringUtils.isNotBlank(tenantId)) {
+ TenantHelper.clearDynamic();
+ }
+ }
+ }
}