feat(qualityMonitor): 实现质量监控检验任务完整流程
- 新增年度质量监控计划创建、提交审核及按年度查询接口
- 新增质量监控检验任务下发、查询及取消接口
- 新增质量监控检验评价提交与批准/退回流程
- 在检验订单中增加任务来源和质量监控明细ID字段
- 增加质控任务认领、原始记录填写、提交、复核等环节的权限校验
- 质控任务复核通过后发送评价通知给指定评价人
- 质控任务不生成检验报告,由评价环节完成归档
- 新增从已复核质控原始记录读取检测数据和上下文信息的方法
- 增加标准方法验证中实时查看所选设备校准证书的功能
- 补充质量监控计划明细的模板ID规范化及字段校验逻辑
- 在样品查询SQL中关联质控明细表返回任务来源和评价状态
已添加7个文件
已修改31个文件
1380 ■■■■■ 文件已修改
cnas-process/src/main/java/com/ruoyi/process/controller/ProcessMethodVerifyController.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cnas-process/src/main/java/com/ruoyi/process/controller/QualityMonitorController.java 165 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cnas-process/src/main/java/com/ruoyi/process/dto/QualityMonitorDetailsDto.java 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cnas-process/src/main/java/com/ruoyi/process/dto/QualityMonitorDetailsEvaluateDto.java 1 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cnas-process/src/main/java/com/ruoyi/process/dto/QualityMonitorDto.java 1 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cnas-process/src/main/java/com/ruoyi/process/excel/QualityMonitorDetailsUpload.java 38 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cnas-process/src/main/java/com/ruoyi/process/mapper/QualityMonitorEvaluateResultMapper.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cnas-process/src/main/java/com/ruoyi/process/mapper/QualityMonitorMapper.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cnas-process/src/main/java/com/ruoyi/process/mapper/QualityMonitorTaskCodeMapper.java 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cnas-process/src/main/java/com/ruoyi/process/pojo/QualityMonitor.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cnas-process/src/main/java/com/ruoyi/process/pojo/QualityMonitorDetails.java 41 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cnas-process/src/main/java/com/ruoyi/process/pojo/QualityMonitorDetailsEvaluate.java 75 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cnas-process/src/main/java/com/ruoyi/process/pojo/QualityMonitorDetailsEvaluateFile.java 1 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cnas-process/src/main/java/com/ruoyi/process/pojo/QualityMonitorDetailsRatify.java 1 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cnas-process/src/main/java/com/ruoyi/process/pojo/QualityMonitorEvaluateResult.java 49 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cnas-process/src/main/java/com/ruoyi/process/service/ProcessMethodVerifyService.java 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cnas-process/src/main/java/com/ruoyi/process/service/QualityMonitorInspectionEvaluationService.java 243 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cnas-process/src/main/java/com/ruoyi/process/service/QualityMonitorService.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cnas-process/src/main/java/com/ruoyi/process/service/QualityMonitorTaskService.java 222 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cnas-process/src/main/java/com/ruoyi/process/service/impl/ProcessMethodVerifyServiceImpl.java 35 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cnas-process/src/main/java/com/ruoyi/process/service/impl/QualityMonitorServiceImpl.java 162 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cnas-process/src/main/resources/mapper/QualityMonitorDetailsMapper.xml 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cnas-process/src/main/resources/mapper/QualityMonitorMapper.xml 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cnas-process/src/main/resources/mapper/QualityMonitorTaskCodeMapper.xml 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
inspect-server/src/main/java/com/ruoyi/inspect/dto/InsOrderPlanDTO.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
inspect-server/src/main/java/com/ruoyi/inspect/mapper/InsOrderMapper.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
inspect-server/src/main/java/com/ruoyi/inspect/pojo/InsOrder.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
inspect-server/src/main/java/com/ruoyi/inspect/pojo/InsOrderDeviceRecord.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
inspect-server/src/main/java/com/ruoyi/inspect/pojo/InsOrderStandardTemplate.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
inspect-server/src/main/java/com/ruoyi/inspect/pojo/InsOrderTemplateDevice.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
inspect-server/src/main/java/com/ruoyi/inspect/service/InsOrderPlanService.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
inspect-server/src/main/java/com/ruoyi/inspect/service/impl/InsOrderPlanServiceImpl.java 106 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
inspect-server/src/main/java/com/ruoyi/inspect/vo/IfsOrderVO.java 1 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
inspect-server/src/main/java/com/ruoyi/inspect/vo/InsOrderPlanTaskSwitchVo.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
inspect-server/src/main/java/com/ruoyi/inspect/vo/InsOrderPlanVO.java 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
inspect-server/src/main/java/com/ruoyi/inspect/vo/QualityMonitorRawContext.java 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
inspect-server/src/main/resources/mapper/InsOrderMapper.xml 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
inspect-server/src/main/resources/mapper/InsSampleMapper.xml 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cnas-process/src/main/java/com/ruoyi/process/controller/ProcessMethodVerifyController.java
@@ -5,6 +5,7 @@
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.common.core.domain.Result;
import com.ruoyi.process.dto.ProcessMethodVerifyDto;
import com.ruoyi.process.dto.ProcessMethodVerifyCalibrationsFileDto;
import com.ruoyi.process.pojo.ProcessMethodVerify;
import com.ruoyi.process.pojo.ProcessMethodVerifyMethodFile;
import com.ruoyi.process.pojo.ProcessMethodVerifyWorkFile;
@@ -81,6 +82,12 @@
        return Result.success(processMethodVerifyService.getCurrentWorkFiles(userIds));
    }
    @Operation(summary = "实时查看所选设备校准证书")
    @GetMapping("/getCurrentCalibrationFiles")
    public Result<List<ProcessMethodVerifyCalibrationsFileDto>> getCurrentCalibrationFiles(String deviceIds) {
        return Result.success(processMethodVerifyService.getCurrentCalibrationFiles(deviceIds));
    }
    /**
     * ä¿®æ”¹æ ‡å‡†æ–¹æ³•验证
cnas-process/src/main/java/com/ruoyi/process/controller/QualityMonitorController.java
@@ -8,9 +8,14 @@
import com.ruoyi.process.dto.QualityMonitorDetailsDto;
import com.ruoyi.process.dto.QualityMonitorDto;
import com.ruoyi.process.mapper.QualityMonitorDetailsEvaluateFileMapper;
import com.ruoyi.process.mapper.QualityMonitorDetailsEvaluateMapper;
import com.ruoyi.process.pojo.*;
import com.ruoyi.process.service.QualityMonitorDetailsService;
import com.ruoyi.process.service.QualityMonitorService;
import com.ruoyi.process.service.QualityMonitorTaskService;
import com.ruoyi.process.service.QualityMonitorInspectionEvaluationService;
import com.ruoyi.inspect.pojo.InsOrder;
import com.ruoyi.system.mapper.UserMapper;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Operation;
import lombok.AllArgsConstructor;
@@ -19,8 +24,11 @@
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletResponse;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
/**
@@ -35,6 +43,24 @@
@RequestMapping("/qualityMonitor")
public class QualityMonitorController {
    @Operation(summary = "新建年度质量控制计划")
    @PostMapping("/createAnnualPlan")
    public Result<QualityMonitorDto> createAnnualPlan(@RequestBody QualityMonitor qualityMonitor) {
        return Result.success(qualityMonitorService.createAnnualPlan(qualityMonitor));
    }
    @Operation(summary = "按年度查询当前质量控制计划")
    @GetMapping("/getAnnualPlan")
    public Result<QualityMonitorDto> getAnnualPlan(String monitorYear, Integer qualityMonitorId) {
        return Result.success(qualityMonitorService.getAnnualPlan(monitorYear, qualityMonitorId));
    }
    @Operation(summary = "提交年度质量控制计划审核")
    @PostMapping("/submitAnnualPlan")
    public Result submitAnnualPlan(@RequestBody Map<String, Integer> request) {
        return Result.success(qualityMonitorService.submitAnnualPlan(request.get("qualityMonitorId")));
    }
    @Resource
    private QualityMonitorService qualityMonitorService;
@@ -43,6 +69,58 @@
    @Resource
    private QualityMonitorDetailsEvaluateFileMapper qualityMonitorDetailsEvaluateFileMapper;
    @Resource
    private QualityMonitorDetailsEvaluateMapper qualityMonitorDetailsEvaluateMapper;
    @Resource
    private QualityMonitorTaskService qualityMonitorTaskService;
    @Resource
    private QualityMonitorInspectionEvaluationService inspectionEvaluationService;
    @Resource
    private UserMapper userMapper;
    @Operation(summary = "获取质量监控检验评价")
    @GetMapping("/getInspectionEvaluation")
    public Result<QualityMonitorDetailsEvaluate> getInspectionEvaluation(Integer qualityMonitorDetailsId) {
        return Result.success(inspectionEvaluationService.get(qualityMonitorDetailsId));
    }
    @Operation(summary = "提交质量监控检验评价")
    @PostMapping("/submitInspectionEvaluation")
    public Result<QualityMonitorDetailsEvaluate> submitInspectionEvaluation(@RequestBody QualityMonitorDetailsEvaluate evaluation) {
        return Result.success(inspectionEvaluationService.submit(evaluation));
    }
    @Operation(summary = "批准或退回质量监控检验评价")
    @PostMapping("/approveInspectionEvaluation")
    public Result approveInspectionEvaluation(@RequestBody Map<String, Object> request) {
        Integer detailId = (Integer) request.get("qualityMonitorDetailsId");
        boolean approved = Boolean.TRUE.equals(request.get("approved"));
        inspectionEvaluationService.approve(detailId, approved, (String) request.get("opinion"));
        return Result.success();
    }
    @Operation(summary = "下发质量监控检验任务")
    @PostMapping("/dispatchInspectionTask")
    public Result<InsOrder> dispatchInspectionTask(@RequestBody Map<String, Integer> request) {
        return Result.success(qualityMonitorTaskService.dispatch(request.get("qualityMonitorDetailsId")));
    }
    @Operation(summary = "查询质量监控明细关联的检验任务")
    @GetMapping("/getInspectionTask")
    public Result<InsOrder> getInspectionTask(Integer qualityMonitorDetailsId) {
        return Result.success(qualityMonitorTaskService.getTask(qualityMonitorDetailsId));
    }
    @Operation(summary = "取消未开始的质量监控检验任务")
    @PostMapping("/cancelInspectionTask")
    public Result cancelInspectionTask(@RequestBody Map<String, Integer> request) {
        qualityMonitorTaskService.cancel(request.get("qualityMonitorDetailsId"));
        return Result.success();
    }
    /**
     * å¯¼å…¥ç›‘控计划
@@ -116,6 +194,14 @@
        if (qualityMonitorDetails.getQualityMonitorId() == null) {
            throw new ErrorException("缺少监控计划主表id");
        }
        qualityMonitorService.validateDetailEditable(qualityMonitorDetails.getQualityMonitorId());
        normalizeTemplateIds(qualityMonitorDetails);
        validateAnnualDetail(qualityMonitorDetails);
        if (qualityMonitorDetails.getSortNo() == null) {
            qualityMonitorDetails.setSortNo((int) qualityMonitorDetailsService.count(
                    com.baomidou.mybatisplus.core.toolkit.Wrappers.<QualityMonitorDetails>lambdaQuery()
                            .eq(QualityMonitorDetails::getQualityMonitorId, qualityMonitorDetails.getQualityMonitorId())) + 1);
        }
        return Result.success(qualityMonitorDetailsService.save(qualityMonitorDetails));
    }
@@ -126,6 +212,15 @@
    @Operation(summary = "修改监控计划详情")
    @PostMapping("/updateQualityMonitorDetail")
    public Result updateQualityMonitorDetail(@RequestBody QualityMonitorDetails qualityMonitorDetails) {
        QualityMonitorDetails stored = qualityMonitorDetailsService.getById(qualityMonitorDetails.getQualityMonitorDetailsId());
        if (stored == null) throw new ErrorException("质控项目不存在");
        qualityMonitorDetails.setQualityMonitorId(stored.getQualityMonitorId());
        qualityMonitorService.validateDetailEditable(stored.getQualityMonitorId());
        normalizeTemplateIds(qualityMonitorDetails);
        validateAnnualDetail(qualityMonitorDetails);
        if (qualityMonitorTaskService.getTask(qualityMonitorDetails.getQualityMonitorDetailsId()) != null) {
            throw new ErrorException("已下发检验任务的计划明细不可直接修改");
        }
        return Result.success(qualityMonitorDetailsService.updateById(qualityMonitorDetails));
    }
@@ -136,7 +231,64 @@
    @Operation(summary = "删除监控计划详情")
    @DeleteMapping("/delQualityMonitorDetail")
    public Result delQualityMonitorDetail(Integer qualityMonitorDetailsId) {
        QualityMonitorDetails stored = qualityMonitorDetailsService.getById(qualityMonitorDetailsId);
        if (stored == null) throw new ErrorException("质控项目不存在");
        qualityMonitorService.validateDetailEditable(stored.getQualityMonitorId());
        if (qualityMonitorTaskService.getTask(qualityMonitorDetailsId) != null) {
            throw new ErrorException("已下发检验任务的计划明细不可删除");
        }
        return Result.success(qualityMonitorDetailsService.removeById(qualityMonitorDetailsId));
    }
    private void validateAnnualDetail(QualityMonitorDetails detail) {
        if (!org.springframework.util.StringUtils.hasText(detail.getMonitorProject())
                || !org.springframework.util.StringUtils.hasText(detail.getTechnicalStandardText())
                || !org.springframework.util.StringUtils.hasText(detail.getMonitorWay())
                || !org.springframework.util.StringUtils.hasText(detail.getCompletionMonth())
                || !detail.getCompletionMonth().matches("\\d{4}-\\d{2}")
                || detail.getResponsibleUserId() == null) {
            throw new ErrorException("请补齐质控项目、技术标准、质控方式、完成时限和负责人");
        }
        QualityMonitor plan = qualityMonitorService.getById(detail.getQualityMonitorId());
        if (plan == null || !detail.getCompletionMonth().startsWith(plan.getMonitorYear() + "-")) {
            throw new ErrorException("完成时限必须属于当前计划年度");
        }
        int month = Integer.parseInt(detail.getCompletionMonth().substring(5));
        if (month < 1 || month > 12) throw new ErrorException("完成时限月份不正确");
        if (userMapper.selectById(detail.getResponsibleUserId()) == null) {
            throw new ErrorException("负责人不存在");
        }
    }
    private void normalizeTemplateIds(QualityMonitorDetails detail) {
        String rawIds = detail.getTemplateIds();
        if (!org.springframework.util.StringUtils.hasText(rawIds) && detail.getTemplateId() != null) {
            rawIds = String.valueOf(detail.getTemplateId());
        }
        if (!org.springframework.util.StringUtils.hasText(rawIds)) {
            detail.setTemplateIds(null);
            detail.setTemplateId(null);
            return;
        }
        Set<Integer> templateIds = new LinkedHashSet<>();
        try {
            for (String rawId : rawIds.split(",")) {
                if (!org.springframework.util.StringUtils.hasText(rawId)) continue;
                int templateId = Integer.parseInt(rawId.trim());
                if (templateId <= 0) throw new NumberFormatException();
                templateIds.add(templateId);
            }
        } catch (NumberFormatException exception) {
            throw new ErrorException("原始记录模板参数不正确");
        }
        if (templateIds.size() > 50) throw new ErrorException("原始记录模板最多选择50个");
        if (templateIds.isEmpty()) {
            detail.setTemplateIds(null);
            detail.setTemplateId(null);
            return;
        }
        detail.setTemplateIds(templateIds.stream().map(String::valueOf).collect(Collectors.joining(",")));
        detail.setTemplateId(templateIds.iterator().next());
    }
    /**
@@ -211,6 +363,9 @@
    @Operation(summary = "新增监控评价")
    @PostMapping("/addQualityMonitorEvaluate")
    public Result addQualityMonitorEvaluate(@RequestBody QualityMonitorDetailsEvaluate qualityMonitorDetailsEvaluate) {
        if (qualityMonitorTaskService.getTask(qualityMonitorDetailsEvaluate.getQualityMonitorDetailsId()) != null) {
            throw new ErrorException("质量监控检验评价请使用检验评价入口");
        }
        return Result.success(qualityMonitorService.addQualityMonitorEvaluate(qualityMonitorDetailsEvaluate));
    }
@@ -231,6 +386,10 @@
    @Operation(summary = "监控评价批准意见")
    @PostMapping("/addMonitorEvaluateOpinion")
    public Result addMonitorEvaluateOpinion(@RequestBody QualityMonitorDetailsEvaluate qualityMonitorDetailsEvaluate) {
        QualityMonitorDetailsEvaluate stored = qualityMonitorDetailsEvaluateMapper.selectById(qualityMonitorDetailsEvaluate.getDetailsEvaluateId());
        if (stored != null && qualityMonitorTaskService.getTask(stored.getQualityMonitorDetailsId()) != null) {
            throw new ErrorException("质量监控检验评价请使用检验评价审批入口");
        }
        return Result.success(qualityMonitorService.addMonitorEvaluateOpinion(qualityMonitorDetailsEvaluate));
    }
@@ -279,6 +438,9 @@
    @Operation(summary = "上传监控完成报告")
    @PostMapping("/uploadFinishReport")
    public Result uploadFinishReport(MultipartFile file, Integer qualityMonitorDetailsId) {
        if (qualityMonitorTaskService.getTask(qualityMonitorDetailsId) != null) {
            throw new ErrorException("质量监控检验任务无需上传完成报告");
        }
        return Result.success(qualityMonitorService.uploadFinishReport(file, qualityMonitorDetailsId));
    }
@@ -290,6 +452,9 @@
    @Operation(summary = "批准完成报告")
    @PostMapping("/ratifyFinishReport")
    public Result ratifyFinishReport(@RequestBody QualityMonitorDetails qualityMonitorDetails) {
        if (qualityMonitorTaskService.getTask(qualityMonitorDetails.getQualityMonitorDetailsId()) != null) {
            throw new ErrorException("质量监控检验任务无需批准完成报告");
        }
        return Result.success(qualityMonitorService.ratifyFinishReport(qualityMonitorDetails));
    }
}
cnas-process/src/main/java/com/ruoyi/process/dto/QualityMonitorDetailsDto.java
@@ -9,8 +9,30 @@
 * @Date 2025/3/24
 */
@Data
@Schema(description = "质量监控计划明细及任务评价状态")
public class QualityMonitorDetailsDto extends QualityMonitorDetails {
    @Schema(description = "负责人姓名")
    private String responsibleName;
    @Schema(description = "评价人姓名")
    private String evaluatorName;
    @Schema(description = "关联检验任务ID")
    private Integer inspectionTaskId;
    @Schema(description = "质量监控任务编号")
    private String inspectionTaskCode;
    @Schema(description = "检验任务状态")
    private Integer inspectionTaskState;
    private Integer inspectionTaskOrderState;
    private Integer planRatifyStatus;
    private Integer planRatifyUserId;
    @Schema(description = "实施状态, 0: æœªå¼€å§‹, 1:待批准, 2:不批准, 3:已批准")
    private Integer detailsRatifyStatus;
cnas-process/src/main/java/com/ruoyi/process/dto/QualityMonitorDetailsEvaluateDto.java
@@ -10,6 +10,7 @@
 * Description:
 */
@Data
@Schema(description = "质量控制评价记录导出数据")
public class QualityMonitorDetailsEvaluateDto extends QualityMonitorDetailsEvaluate {
    @Schema(description = "批准时间")
    private String ratifyTimeStr;
cnas-process/src/main/java/com/ruoyi/process/dto/QualityMonitorDto.java
@@ -9,6 +9,7 @@
 * @Date 2024/11/6
 */
@Data
@Schema(description = "年度质量监控计划及编制审核人员信息")
public class QualityMonitorDto extends QualityMonitor {
    @Schema(description = "编制人")
cnas-process/src/main/java/com/ruoyi/process/excel/QualityMonitorDetailsUpload.java
@@ -1,6 +1,7 @@
package com.ruoyi.process.excel;
import com.alibaba.excel.annotation.ExcelProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
/**
@@ -8,26 +9,51 @@
 * @Date 2024/11/8
 */
@Data
@Schema(description = "年度质量监控计划明细导入行")
public class QualityMonitorDetailsUpload {
    @ExcelProperty("监控目的")
    @Schema(description = "计划内排序号")
    @ExcelProperty("序号")
    private Integer sortNo;
    @Schema(description = "质控项目")
    @ExcelProperty("质控项目")
    private String monitorProject;
    @Schema(description = "技术标准")
    @ExcelProperty("技术标准")
    private String technicalStandardText;
    @Schema(description = "质控方式")
    @ExcelProperty("质控方式")
    private String monitorWay;
    @Schema(description = "计划完成月份")
    @ExcelProperty("完成时限")
    private String completionMonth;
    @Schema(description = "负责人姓名")
    @ExcelProperty("负责人")
    private String responsibleName;
    @Schema(description = "监控目的,兼容历史导入列")
    @ExcelProperty(value = "监控目的", index = -1)
    private String monitorPurpose;
    @Schema(description = "计划开展时间,兼容历史导入列")
    @ExcelProperty("计划开展时间")
    private String plannedTime;
    @ExcelProperty("监控项目")
    private String monitorProject;
    @Schema(description = "参加单位或人员")
    @ExcelProperty("参加单位(人员)")
    private String participant;
    @Schema(description = "预算金额")
    @ExcelProperty("预算(元)")
    private String budget;
    @Schema(description = "组织人员")
    @ExcelProperty("组织人员")
    private String organization;
    @ExcelProperty("监控方式")
    private String monitorWay;
}
cnas-process/src/main/java/com/ruoyi/process/mapper/QualityMonitorEvaluateResultMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,9 @@
package com.ruoyi.process.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ruoyi.process.pojo.QualityMonitorEvaluateResult;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface QualityMonitorEvaluateResultMapper extends BaseMapper<QualityMonitorEvaluateResult> {
}
cnas-process/src/main/java/com/ruoyi/process/mapper/QualityMonitorMapper.java
@@ -24,5 +24,7 @@
     */
    IPage<QualityMonitorDto> pageQualityMonitor(Page page, @Param("ew") QueryWrapper<QualityMonitor> qualityMonitorQueryWrapper);
    QualityMonitorDto selectPlanDtoById(@Param("id") Integer id);
}
cnas-process/src/main/java/com/ruoyi/process/mapper/QualityMonitorTaskCodeMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,15 @@
package com.ruoyi.process.mapper;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
@Mapper
public interface QualityMonitorTaskCodeMapper {
    void ensureYear(@Param("year") int year);
    int lockCurrent(@Param("year") int year);
    void updateCurrent(@Param("year") int year, @Param("value") int value);
    int countStandardTemplateBinding(@Param("standardId") int standardId, @Param("templateId") int templateId);
}
cnas-process/src/main/java/com/ruoyi/process/pojo/QualityMonitor.java
@@ -18,6 +18,7 @@
@Schema(description = "质量监控计划主表")
public class QualityMonitor {
    @Schema(description = "年度质量监控计划ID")
    @TableId(type = IdType.AUTO)
    private Integer qualityMonitorId;
@@ -27,6 +28,12 @@
    @Schema(description = "计划年份")
    private String monitorYear;
    @Schema(description = "流程状态:DRAFT/PENDING_REVIEW/REVIEW_REJECTED/PENDING_APPROVAL/APPROVAL_REJECTED/APPROVED")
    private String workflowStatus;
    @Schema(description = "兼容旧数据的版本标记,新流程不再使用")
    private Integer isCurrent;
    @Schema(description = "外键:用户id(编制人)")
    private Integer writeUserId;
cnas-process/src/main/java/com/ruoyi/process/pojo/QualityMonitorDetails.java
@@ -6,6 +6,7 @@
import lombok.Data;
import java.time.LocalDateTime;
import java.time.LocalDate;
/**
 * è´¨é‡ç›‘控计划详情表
@@ -18,11 +19,24 @@
@Schema(description = "质量监控计划详情表")
public class QualityMonitorDetails {
    @Schema(description = "质量监控计划明细ID")
    @TableId(type = IdType.AUTO)
    private Integer qualityMonitorDetailsId;
    @Schema(description = "监控计划id")
    private Integer qualityMonitorId;
    @Schema(description = "计划内排序号")
    private Integer sortNo;
    @Schema(description = "年度计划表中的技术标准文本")
    private String technicalStandardText;
    @Schema(description = "完成月份,格式yyyy-MM")
    private String completionMonth;
    @Schema(description = "负责人用户ID")
    private Integer responsibleUserId;
    @Schema(description = "监控目的")
    private String monitorPurpose;
@@ -44,6 +58,32 @@
    @Schema(description = "监控方式")
    private String monitorWay;
    @Schema(description = "事项类别:INSPECTION检测类,OTHER非检测类")
    private String itemKind;
    @Schema(description = "检测标准ID")
    private Integer standardMethodId;
    @Schema(description = "兼容旧数据的首个原始记录模板ID")
    private Integer templateId;
    @Schema(description = "原始记录模板ID集合,多个ID使用英文逗号分隔")
    private String templateIds;
    @Schema(description = "检测员用户ID")
    private Integer inspectorUserId;
    @Schema(description = "历史计划复核人用户ID,新流程不使用")
    private Integer reviewerUserId;
    @Schema(description = "评价人用户ID")
    private Integer evaluatorUserId;
    @Schema(description = "检测样品名称")
    private String sampleName;
    @Schema(description = "检测样品规格")
    private String sampleSpec;
    @Schema(description = "质控检验任务截止日期")
    @JsonFormat(pattern = "yyyy-MM-dd")
    private LocalDate dueDate;
    @Schema(description = "完成报告地址")
    private String finishReportUrl;
@@ -85,6 +125,7 @@
    private LocalDateTime updateTime;
    // å¯¼å‡ºä½¿ç”¨
    @Schema(description = "导出行序号,非数据库字段")
    @TableField(select = false, exist = false)
    private Integer index;
cnas-process/src/main/java/com/ruoyi/process/pojo/QualityMonitorDetailsEvaluate.java
@@ -6,6 +6,8 @@
import lombok.Data;
import java.time.LocalDateTime;
import java.time.LocalDate;
import java.util.List;
/**
 * è´¨é‡ç›‘控计划详情评价表
@@ -18,12 +20,85 @@
@Schema(description = "质量监控计划详情评价表")
public class QualityMonitorDetailsEvaluate {
    @Schema(description = "质量控制评价记录ID")
    @TableId(type = IdType.AUTO)
    private Integer detailsEvaluateId;
    @Schema(description = "监控计划详情id")
    private Integer qualityMonitorDetailsId;
    @Schema(description = "关联的质控检验任务ID")
    private Integer insOrderId;
    @Schema(description = "监督员用户ID,与评价人分开")
    private Integer supervisorUserId;
    @Schema(description = "被监督检测员用户ID")
    private Integer inspectorUserId;
    @Schema(description = "检测样品名称")
    private String sampleName;
    @Schema(description = "检测样品规格")
    private String sampleSpec;
    @Schema(description = "历史单一检测时间;新评价页面从原始记录读取日期")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime inspectionTime;
    @Schema(description = "历史设备名称;新评价页面从原始记录读取所用设备")
    private String equipmentDescription;
    @Schema(description = "历史设备规格;新评价页面从原始记录读取所用设备")
    private String equipmentSpec;
    @Schema(description = "历史设备编号;新评价页面从原始记录读取所用设备")
    private String equipmentCode;
    @Schema(description = "检测情况及评价")
    private String evaluationConclusion;
    @Schema(description = "纠正措施及预防措施")
    private String correctiveAction;
    @Schema(description = "验证情况")
    private String verificationResult;
    @Schema(description = "纠正措施责任人用户ID")
    private Integer correctiveResponsibleUserId;
    @Schema(description = "纠正措施完成日期")
    @JsonFormat(pattern = "yyyy-MM-dd")
    private LocalDate correctiveDueDate;
    @Schema(description = "验证责任人用户ID")
    private Integer verificationResponsibleUserId;
    @Schema(description = "验证日期")
    @JsonFormat(pattern = "yyyy-MM-dd")
    private LocalDate verificationDate;
    @Schema(description = "检测数据与盲样值对照行,非数据库字段")
    @TableField(exist = false)
    private List<QualityMonitorEvaluateResult> resultRows;
    @Schema(description = "评价人已核对原始记录检测数据,提交校验使用")
    @TableField(exist = false)
    private Boolean measurementConfirmed;
    @Schema(description = "当前登录用户能否编写评价,非数据库字段")
    @TableField(exist = false)
    private Boolean evaluationEditable;
    @Schema(description = "当前不能编写评价的原因,非数据库字段")
    @TableField(exist = false)
    private String evaluationEditReason;
    @Schema(description = "质控检验任务状态,非数据库字段")
    @TableField(exist = false)
    private Integer inspectionTaskState;
    @Schema(description = "已复核原始记录中的检测日期集合,非数据库字段")
    @TableField(exist = false)
    private List<String> rawInspectionDates;
    @Schema(description = "已复核原始记录中的设备名称,非数据库字段")
    @TableField(exist = false)
    private String rawEquipmentDescription;
    @Schema(description = "已复核原始记录中的设备规格,非数据库字段")
    @TableField(exist = false)
    private String rawEquipmentSpec;
    @Schema(description = "已复核原始记录中的设备编号,非数据库字段")
    @TableField(exist = false)
    private String rawEquipmentCode;
    @Schema(description = "评审目的")
    private String reviewPurpose;
cnas-process/src/main/java/com/ruoyi/process/pojo/QualityMonitorDetailsEvaluateFile.java
@@ -18,6 +18,7 @@
@Schema(description = "质量监控计划详情评价附件表")
public class QualityMonitorDetailsEvaluateFile {
    @Schema(description = "评价附件ID")
    @TableId(type = IdType.AUTO)
    private Integer evaluateFileId;
cnas-process/src/main/java/com/ruoyi/process/pojo/QualityMonitorDetailsRatify.java
@@ -18,6 +18,7 @@
@Schema(description = "质量监控计划详情批准表")
public class QualityMonitorDetailsRatify {
    @Schema(description = "质量监控计划明细批准记录ID")
    @TableId(type = IdType.AUTO)
    private Integer detailsRatifyId;
cnas-process/src/main/java/com/ruoyi/process/pojo/QualityMonitorEvaluateResult.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,49 @@
package com.ruoyi.process.pojo;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.annotation.TableField;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Data
@TableName("cnas_quality_monitor_evaluate_result")
@Schema(description = "质量控制评价记录的检测数据与盲样值对照")
public class QualityMonitorEvaluateResult {
    @Schema(description = "评价数据行ID")
    @TableId(type = IdType.AUTO)
    private Integer id;
    @Schema(description = "质量控制评价记录ID")
    private Integer detailsEvaluateId;
    @Schema(description = "被监督检测员用户ID")
    private Integer inspectorUserId;
    @Schema(description = "从已复核原始记录带入的当日检测数据")
    private String measuredValue;
    @Schema(description = "评价人录入的盲样值")
    private String referenceValue;
    @Schema(description = "检测数据单位")
    private String unit;
    @Schema(description = "判定规则,兼容历史评价记录")
    private String acceptanceRule;
    @Schema(description = "检测值与参考值偏差,兼容历史评价记录")
    private String deviation;
    @Schema(description = "单行判定结果,兼容历史评价记录")
    private String result;
    @Schema(description = "数据行排序号")
    private Integer sortNo;
    @Schema(description = "原始记录中的检测项目名称,仅用于展示")
    @TableField(exist = false)
    private String itemName;
}
cnas-process/src/main/java/com/ruoyi/process/service/ProcessMethodVerifyService.java
@@ -4,6 +4,7 @@
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
import com.ruoyi.process.dto.ProcessMethodVerifyDto;
import com.ruoyi.process.dto.ProcessMethodVerifyCalibrationsFileDto;
import com.ruoyi.process.pojo.ProcessMethodVerify;
import com.ruoyi.process.pojo.ProcessMethodVerifyMethodFile;
import com.ruoyi.process.pojo.ProcessMethodVerifyWorkFile;
@@ -45,6 +46,9 @@
    /** å®žæ—¶è¯»å–所选检测人员的任职授权证书。 */
    List<ProcessMethodVerifyWorkFile> getCurrentWorkFiles(String userIds);
    /** å®žæ—¶è¯»å–所选设备的校准证书。 */
    List<ProcessMethodVerifyCalibrationsFileDto> getCurrentCalibrationFiles(String deviceIds);
    /**
     * ä¿®æ”¹æ ‡å‡†æ–¹æ³•验证
     * @param methodVerifyDto
cnas-process/src/main/java/com/ruoyi/process/service/QualityMonitorInspectionEvaluationService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,243 @@
package com.ruoyi.process.service;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.framework.exception.ErrorException;
import com.ruoyi.inspect.mapper.InsOrderStateMapper;
import com.ruoyi.inspect.pojo.InsOrder;
import com.ruoyi.inspect.pojo.InsOrderState;
import com.ruoyi.inspect.service.InsOrderPlanService;
import com.ruoyi.inspect.vo.QualityMonitorRawContext;
import com.ruoyi.process.mapper.QualityMonitorDetailsEvaluateMapper;
import com.ruoyi.process.mapper.QualityMonitorDetailsMapper;
import com.ruoyi.process.mapper.QualityMonitorEvaluateResultMapper;
import com.ruoyi.process.mapper.QualityMonitorMapper;
import com.ruoyi.process.pojo.QualityMonitor;
import com.ruoyi.process.pojo.QualityMonitorDetails;
import com.ruoyi.process.pojo.QualityMonitorDetailsEvaluate;
import com.ruoyi.process.pojo.QualityMonitorEvaluateResult;
import com.ruoyi.system.mapper.UserMapper;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
@Service
public class QualityMonitorInspectionEvaluationService {
    @Resource
    private QualityMonitorTaskService taskService;
    @Resource
    private QualityMonitorDetailsMapper detailsMapper;
    @Resource
    private QualityMonitorMapper monitorMapper;
    @Resource
    private QualityMonitorDetailsEvaluateMapper evaluateMapper;
    @Resource
    private QualityMonitorEvaluateResultMapper resultMapper;
    @Resource
    private InsOrderStateMapper stateMapper;
    @Resource
    private UserMapper userMapper;
    @Resource
    private InsOrderPlanService insOrderPlanService;
    public QualityMonitorDetailsEvaluate get(Integer detailId) {
        QualityMonitorDetails detail = requireDetail(detailId);
        QualityMonitorDetailsEvaluate evaluation = evaluateMapper.selectOne(Wrappers.<QualityMonitorDetailsEvaluate>lambdaQuery()
                .eq(QualityMonitorDetailsEvaluate::getQualityMonitorDetailsId, detailId).last("limit 1"));
        InsOrder task = taskService.getTask(detailId);
        if (evaluation == null) {
            evaluation = new QualityMonitorDetailsEvaluate();
            evaluation.setQualityMonitorDetailsId(detailId);
            evaluation.setInspectorUserId(detail.getInspectorUserId());
            evaluation.setSampleName(detail.getSampleName());
            evaluation.setSampleSpec(detail.getSampleSpec());
        }
        List<QualityMonitorEvaluateResult> rows = evaluation.getDetailsEvaluateId() == null
                ? List.of() : resultMapper.selectList(Wrappers.<QualityMonitorEvaluateResult>lambdaQuery()
                                                      .eq(QualityMonitorEvaluateResult::getDetailsEvaluateId, evaluation.getDetailsEvaluateId())
                                                      .orderByAsc(QualityMonitorEvaluateResult::getSortNo, QualityMonitorEvaluateResult::getId));
        if (task != null && Integer.valueOf(5).equals(task.getInsState())) {
            List<Map<String, String>> rawRows = insOrderPlanService.getQualityMonitorRawResults(task.getId());
            fillRawContext(evaluation, insOrderPlanService.getQualityMonitorRawContext(task.getId()));
            if (!rows.isEmpty() && rows.size() != rawRows.size())
                throw new ErrorException("原始记录检测数据已变化,请重新核对评价记录");
            if (rows.isEmpty()) {
                rows = new java.util.ArrayList<>();
                for (int i = 0; i < rawRows.size(); i++) {
                    QualityMonitorEvaluateResult row = new QualityMonitorEvaluateResult();
                    row.setInspectorUserId(detail.getInspectorUserId());
                    row.setMeasuredValue(rawRows.get(i).get("result"));
                    row.setUnit(rawRows.get(i).get("unit"));
                    row.setSortNo(i);
                    rows.add(row);
                }
            }
            for (int i = 0; i < rows.size(); i++) rows.get(i).setItemName(rawRows.get(i).get("itemName"));
        }
        evaluation.setResultRows(rows);
        evaluation.setInspectionTaskState(task == null ? null : task.getInsState());
        if (Integer.valueOf(1).equals(evaluation.getIsFinish())) {
            evaluation.setEvaluationEditReason("评价已批准归档,只能查看");
        } else if (task == null) {
            evaluation.setEvaluationEditReason("质控检验任务尚未下发,暂不能填写评价");
        } else if (!Integer.valueOf(5).equals(task.getInsState())) {
            evaluation.setEvaluationEditReason("原始记录复核通过后,指定评价人才可以填写");
        } else if (detail.getEvaluatorUserId() == null) {
            evaluation.setEvaluationEditReason("计划项目尚未指定评价人");
        } else if (!Integer.valueOf(SecurityUtils.getUserId().intValue()).equals(detail.getEvaluatorUserId())) {
            var evaluator = userMapper.selectById(detail.getEvaluatorUserId());
            evaluation.setEvaluationEditReason("当前账号不是指定评价人("
                    + (evaluator == null ? "用户ID " + detail.getEvaluatorUserId() : evaluator.getName())
                    + "),只能查看");
        } else {
            evaluation.setEvaluationEditable(true);
        }
        return evaluation;
    }
    @Transactional(rollbackFor = Exception.class)
    public QualityMonitorDetailsEvaluate submit(QualityMonitorDetailsEvaluate input) {
        if (input == null) throw new ErrorException("评价内容不能为空");
        QualityMonitorDetails detail = requireDetail(input.getQualityMonitorDetailsId());
        InsOrder task = taskService.getTask(detail.getQualityMonitorDetailsId());
        if (task == null || !QualityMonitorTaskService.SOURCE.equals(task.getTaskSource()))
            throw new ErrorException("质量监控检验任务不存在");
        if (!Integer.valueOf(5).equals(task.getInsState()))
            throw new ErrorException("原始记录复核通过后才能评价");
        if (!Integer.valueOf(SecurityUtils.getUserId().intValue()).equals(detail.getEvaluatorUserId()))
            throw new ErrorException("只有指定评价人可以填写评价");
        if (input.getSupervisorUserId() == null
                || input.getSupervisorUserId().equals(detail.getInspectorUserId())
                || input.getSupervisorUserId().equals(detail.getEvaluatorUserId())
                || userMapper.selectById(input.getSupervisorUserId()) == null)
            throw new ErrorException("请选择与检测员、评价人不同的监督员");
        Long pendingCount = stateMapper.selectCount(Wrappers.<InsOrderState>lambdaQuery()
                .eq(InsOrderState::getInsOrderId, task.getId()).ne(InsOrderState::getInsState, 5));
        if (pendingCount > 0) throw new ErrorException("原始记录复核通过后才能评价");
        List<Map<String, String>> rawRows = insOrderPlanService.getQualityMonitorRawResults(task.getId());
        QualityMonitorRawContext rawContext = insOrderPlanService.getQualityMonitorRawContext(task.getId());
        if (rawContext.getDetectionDates().isEmpty() || rawContext.getDevices().isEmpty())
            throw new ErrorException("原始记录缺少检测时间或所用设备,请先补齐并复核");
        if (!StringUtils.hasText(input.getEvaluationConclusion()) || input.getResultRows() == null
                || input.getResultRows().size() != rawRows.size())
            throw new ErrorException("请填写评价内容并核对全部原始记录检测数据");
        if (!Boolean.TRUE.equals(input.getMeasurementConfirmed()))
            throw new ErrorException("请先核对已复核原始记录的检测数据");
        QualityMonitorDetailsEvaluate existing = evaluateMapper.selectOne(Wrappers.<QualityMonitorDetailsEvaluate>lambdaQuery()
                .eq(QualityMonitorDetailsEvaluate::getQualityMonitorDetailsId, detail.getQualityMonitorDetailsId())
                .last("FOR UPDATE"));
        if (existing != null && Integer.valueOf(1).equals(existing.getIsFinish()))
            throw new ErrorException("已批准的评价不可修改");
        QualityMonitor plan = monitorMapper.selectById(detail.getQualityMonitorId());
        QualityMonitorDetailsEvaluate saved = existing == null ? new QualityMonitorDetailsEvaluate() : existing;
        saved.setQualityMonitorDetailsId(detail.getQualityMonitorDetailsId());
        saved.setInsOrderId(task.getId());
        saved.setInspectorUserId(detail.getInspectorUserId());
        saved.setImplementUserId(detail.getEvaluatorUserId());
        saved.setRatifyUserId(plan.getRatifyUserId());
        saved.setSupervisorUserId(input.getSupervisorUserId());
        saved.setSampleName(detail.getSampleName());
        saved.setSampleSpec(detail.getSampleSpec());
        // æ—¶é—´å’Œè®¾å¤‡å§‹ç»ˆç”±å·²å¤æ ¸åŽŸå§‹è®°å½•è¯»å–ï¼Œè¯„ä»·è¯·æ±‚ä¸­çš„åŒåå­—æ®µä¸å‚ä¸Žä¿å­˜ã€‚
        saved.setImplementCondition(input.getEvaluationConclusion());
        saved.setImplementResult(input.getImplementResult());
        saved.setEvaluationConclusion(input.getEvaluationConclusion());
        saved.setCorrectiveAction(input.getCorrectiveAction());
        saved.setVerificationResult(input.getVerificationResult());
        saved.setCorrectiveResponsibleUserId(input.getCorrectiveResponsibleUserId());
        saved.setCorrectiveDueDate(input.getCorrectiveDueDate());
        saved.setVerificationResponsibleUserId(input.getVerificationResponsibleUserId());
        saved.setVerificationDate(input.getVerificationDate());
        saved.setReviewPurpose(detail.getMonitorPurpose());
        saved.setReviewUser(userMapper.selectById(detail.getEvaluatorUserId()).getName());
        saved.setReviewTime(LocalDateTime.now().toLocalDate().toString());
        if (!StringUtils.hasText(saved.getImplementResult())) {
            saved.setImplementResult(input.getEvaluationConclusion());
        }
        saved.setIsFinish(0);
        if (existing == null) evaluateMapper.insert(saved);
        else evaluateMapper.updateById(saved);
        resultMapper.delete(Wrappers.<QualityMonitorEvaluateResult>lambdaQuery()
                .eq(QualityMonitorEvaluateResult::getDetailsEvaluateId, saved.getDetailsEvaluateId()));
        int index = 0;
        for (QualityMonitorEvaluateResult row : input.getResultRows()) {
            Map<String, String> rawRow = rawRows.get(index);
            if (!Objects.equals(row.getMeasuredValue(), rawRow.get("result")))
                throw new ErrorException("检测数据与已复核原始记录不一致,请刷新后重新核对");
            if (!StringUtils.hasText(row.getReferenceValue())) throw new ErrorException("请填写盲样值");
            row.setId(null);
            row.setDetailsEvaluateId(saved.getDetailsEvaluateId());
            row.setInspectorUserId(detail.getInspectorUserId());
            row.setMeasuredValue(rawRow.get("result"));
            row.setUnit(rawRow.get("unit"));
            row.setSortNo(index++);
            resultMapper.insert(row);
        }
        return get(detail.getQualityMonitorDetailsId());
    }
    @Transactional(rollbackFor = Exception.class)
    public void approve(Integer detailId, boolean approved, String opinion) {
        QualityMonitorDetails detail = requireDetail(detailId);
        QualityMonitor plan = monitorMapper.selectById(detail.getQualityMonitorId());
        int current = SecurityUtils.getUserId().intValue();
        if (plan == null || !Integer.valueOf(current).equals(plan.getRatifyUserId())
                || Integer.valueOf(current).equals(detail.getInspectorUserId()))
            throw new ErrorException("只有指定批准人可以审批评价");
        QualityMonitorDetailsEvaluate evaluation = evaluateMapper.selectOne(Wrappers.<QualityMonitorDetailsEvaluate>lambdaQuery()
                .eq(QualityMonitorDetailsEvaluate::getQualityMonitorDetailsId, detailId).last("FOR UPDATE"));
        if (evaluation == null || Integer.valueOf(1).equals(evaluation.getIsFinish()))
            throw new ErrorException("评价不存在或已批准");
        if (approved && StringUtils.hasText(evaluation.getCorrectiveAction())
                && (!StringUtils.hasText(evaluation.getVerificationResult())
                || evaluation.getCorrectiveResponsibleUserId() == null
                || evaluation.getCorrectiveDueDate() == null
                || evaluation.getVerificationResponsibleUserId() == null
                || evaluation.getVerificationDate() == null))
            throw new ErrorException("请补齐纠正措施责任人、完成日期和验证信息后再批准");
        evaluation.setRatifyOpinion(opinion);
        evaluation.setRatifyTime(LocalDateTime.now());
        evaluation.setIsFinish(approved ? 1 : 0);
        evaluateMapper.updateById(evaluation);
    }
    private QualityMonitorDetails requireDetail(Integer detailId) {
        QualityMonitorDetails detail = detailId == null ? null : detailsMapper.selectById(detailId);
        if (detail == null || !"INSPECTION".equals(detail.getItemKind()))
            throw new ErrorException("质量监控检测明细不存在");
        return detail;
    }
    private void fillRawContext(QualityMonitorDetailsEvaluate evaluation, QualityMonitorRawContext context) {
        evaluation.setRawInspectionDates(context.getDetectionDates().stream()
                .map(date -> date.format(DateTimeFormatter.ISO_LOCAL_DATE)).collect(Collectors.toList()));
        List<String> names = new ArrayList<>();
        List<String> specs = new ArrayList<>();
        List<String> codes = new ArrayList<>();
        for (Map<String, String> device : context.getDevices()) {
            names.add(deviceText(device, "device_name", "deviceName"));
            specs.add(deviceText(device, "specification_model", "specificationModel"));
            String managementNumber = deviceText(device, "management_number", "managementNumber");
            codes.add("-".equals(managementNumber)
                    ? deviceText(device, "factory_no", "factoryNo") : managementNumber);
        }
        evaluation.setRawEquipmentDescription(String.join("\n", names));
        evaluation.setRawEquipmentSpec(String.join("\n", specs));
        evaluation.setRawEquipmentCode(String.join("\n", codes));
    }
    private String deviceText(Map<String, String> device, String snakeCase, String camelCase) {
        String value = device.get(snakeCase);
        if (!StringUtils.hasText(value)) value = device.get(camelCase);
        return StringUtils.hasText(value) ? value : "-";
    }
}
cnas-process/src/main/java/com/ruoyi/process/service/QualityMonitorService.java
@@ -19,6 +19,14 @@
 */
public interface QualityMonitorService extends IService<QualityMonitor> {
    QualityMonitorDto createAnnualPlan(QualityMonitor qualityMonitor);
    QualityMonitorDto getAnnualPlan(String monitorYear, Integer qualityMonitorId);
    boolean submitAnnualPlan(Integer qualityMonitorId);
    void validateDetailEditable(Integer qualityMonitorId);
    /**
     * å¯¼å…¥ç›‘控计划
     * @param file
cnas-process/src/main/java/com/ruoyi/process/service/QualityMonitorTaskService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,222 @@
package com.ruoyi.process.service;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.ruoyi.basic.pojo.StandardMethod;
import com.ruoyi.basic.pojo.StandardTemplate;
import com.ruoyi.basic.service.StandardMethodService;
import com.ruoyi.basic.service.StandardTemplateService;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.core.domain.entity.InformationNotification;
import com.ruoyi.system.service.InformationNotificationService;
import com.ruoyi.framework.exception.ErrorException;
import com.ruoyi.inspect.mapper.InsOrderMapper;
import com.ruoyi.inspect.mapper.InsOrderStandardTemplateMapper;
import com.ruoyi.inspect.mapper.InsSampleMapper;
import com.ruoyi.inspect.mapper.InsOrderStateMapper;
import com.ruoyi.inspect.pojo.InsOrder;
import com.ruoyi.inspect.pojo.InsOrderStandardTemplate;
import com.ruoyi.inspect.pojo.InsSample;
import com.ruoyi.inspect.pojo.InsOrderState;
import com.ruoyi.inspect.service.InsOrderService;
import com.ruoyi.process.mapper.QualityMonitorDetailsMapper;
import com.ruoyi.process.mapper.QualityMonitorMapper;
import com.ruoyi.process.mapper.QualityMonitorTaskCodeMapper;
import com.ruoyi.process.pojo.QualityMonitor;
import com.ruoyi.process.pojo.QualityMonitorDetails;
import com.ruoyi.system.mapper.UserMapper;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.YearMonth;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
@Service
public class QualityMonitorTaskService {
    public static final String SOURCE = "QUALITY_MONITOR";
    private static final String LABORATORY = "新聚";
    @Resource private QualityMonitorDetailsMapper detailsMapper;
    @Resource private QualityMonitorMapper monitorMapper;
    @Resource private QualityMonitorTaskCodeMapper codeMapper;
    @Resource private InsOrderMapper orderMapper;
    @Resource private InsSampleMapper sampleMapper;
    @Resource private InsOrderStateMapper stateMapper;
    @Resource private InsOrderStandardTemplateMapper snapshotMapper;
    @Resource private InsOrderService orderService;
    @Resource private StandardMethodService standardMethodService;
    @Resource private StandardTemplateService standardTemplateService;
    @Resource private UserMapper userMapper;
    @Resource private InformationNotificationService notificationService;
    @Transactional(rollbackFor = Exception.class)
    public InsOrder dispatch(Integer detailsId) {
        if (detailsId == null) throw new ErrorException("缺少质量监控计划明细ID");
        QualityMonitorDetails detail = detailsMapper.selectOne(Wrappers.<QualityMonitorDetails>lambdaQuery()
                .eq(QualityMonitorDetails::getQualityMonitorDetailsId, detailsId).last("FOR UPDATE"));
        if (detail == null) throw new ErrorException("质量监控计划明细不存在");
        InsOrder existing = getTask(detailsId);
        if (existing != null) return existing;
        QualityMonitor plan = monitorMapper.selectById(detail.getQualityMonitorId());
        if (plan == null || !Integer.valueOf(1).equals(plan.getExamineStatus())
                || !Integer.valueOf(1).equals(plan.getRatifyStatus())) {
            throw new ErrorException("质量监控计划审核、批准通过后才能下发");
        }
        if (!"INSPECTION".equals(detail.getItemKind())) throw new ErrorException("仅检测类明细可下发检验任务");
        if (!StringUtils.hasText(detail.getTechnicalStandardText())
                || !StringUtils.hasText(detail.getMonitorWay())
                || !StringUtils.hasText(detail.getCompletionMonth())
                || detail.getResponsibleUserId() == null) {
            throw new ErrorException("请补齐质控项目、技术标准、质控方式、完成时限和负责人");
        }
        List<Integer> templateIds = resolveTemplateIds(detail);
        if (detail.getStandardMethodId() == null || templateIds.isEmpty()
                || detail.getInspectorUserId() == null
                || detail.getEvaluatorUserId() == null || detail.getDueDate() == null
                || !StringUtils.hasText(detail.getSampleName())) {
            throw new ErrorException("请补齐标准、原始记录模板、检测员、评价人、样品和完成期限");
        }
        if (detail.getInspectorUserId().equals(detail.getEvaluatorUserId())) {
            throw new ErrorException("检测员不能同时担任评价人");
        }
        if (userMapper.selectById(detail.getInspectorUserId()) == null
                || userMapper.selectById(detail.getEvaluatorUserId()) == null) {
            throw new ErrorException("检测员或评价人不存在");
        }
        StandardMethod standard = standardMethodService.getById(detail.getStandardMethodId());
        if (standard == null) throw new ErrorException("检测标准不存在");
        List<StandardTemplate> templates = new ArrayList<>();
        for (Integer templateId : templateIds) {
            StandardTemplate template = standardTemplateService.getById(templateId);
            if (template == null || !StringUtils.hasText(template.getThing())) {
                throw new ErrorException("原始记录模板不存在或内容为空:" + templateId);
            }
            if (codeMapper.countStandardTemplateBinding(standard.getId(), template.getId()) == 0) {
                throw new ErrorException("原始记录模板未绑定当前检测标准:" + template.getName());
            }
            templates.add(template);
        }
        LocalDate today = LocalDate.now(ZoneId.of("Asia/Shanghai"));
        YearMonth completionMonth;
        try {
            completionMonth = YearMonth.parse(detail.getCompletionMonth());
        } catch (Exception exception) {
            throw new ErrorException("计划完成时限格式不正确");
        }
        if (YearMonth.from(detail.getDueDate()).isAfter(completionMonth)) {
            throw new ErrorException("检验任务截止日不能晚于计划完成月份");
        }
        int year = today.getYear();
        codeMapper.ensureYear(year);
        int sequence = codeMapper.lockCurrent(year) + 1;
        codeMapper.updateCurrent(year, sequence);
        String code = String.format("QC-%d-%04d", year, sequence);
        InsOrder order = new InsOrder();
        order.setEntrustCode(code);
        order.setTaskSource(SOURCE);
        order.setQualityMonitorDetailsId(detailsId);
        order.setOrderType(SOURCE);
        order.setTypeSource(-1);
        order.setState(1);
        order.setInsState(0);
        order.setLaboratory(LABORATORY);
        order.setSample(detail.getSampleName());
        order.setPartDetail(detail.getSampleSpec());
        order.setAppointed(detail.getDueDate());
        order.setExamineTime(LocalDateTime.now());
        orderMapper.insert(order);
        InsSample sample = new InsSample();
        sample.setInsOrderId(order.getId());
        sample.setSampleCode(code);
        sample.setLaboratory(LABORATORY);
        sample.setSample(detail.getSampleName());
        sample.setQuantity(1);
        sampleMapper.insert(sample);
        for (int index = 0; index < templates.size(); index++) {
            StandardTemplate template = templates.get(index);
            InsOrderStandardTemplate snapshot = new InsOrderStandardTemplate();
            snapshot.setInsOrderId(order.getId());
            snapshot.setTemplateId(template.getId());
            snapshot.setName(template.getName());
            snapshot.setNumber(template.getNumber());
            snapshot.setRemark(template.getRemark());
            snapshot.setThing(template.getThing());
            snapshot.setSort(index);
            snapshotMapper.insert(snapshot);
        }
        orderService.upInsOrder(order.getId(), sample.getId(), detail.getDueDate().toString(),
                detail.getInspectorUserId(), LABORATORY);
        InformationNotification notice = new InformationNotification();
        notice.setCreateUser(userMapper.selectById(SecurityUtils.getUserId().intValue()).getName());
        notice.setSenderId(SecurityUtils.getUserId().intValue());
        notice.setConsigneeId(detail.getInspectorUserId());
        notice.setMessageType("2");
        notice.setTheme("质量监控检验任务");
        notice.setContent("质量监控任务 " + code + " å¾…检测,请查看评价要求和原始记录模板");
        notice.setJumpPath("InspectionTask");
        notificationService.addInformationNotification(notice);
        return orderMapper.selectById(order.getId());
    }
    private List<Integer> resolveTemplateIds(QualityMonitorDetails detail) {
        String rawIds = detail.getTemplateIds();
        if (!StringUtils.hasText(rawIds) && detail.getTemplateId() != null) {
            rawIds = String.valueOf(detail.getTemplateId());
        }
        Set<Integer> result = new LinkedHashSet<>();
        if (!StringUtils.hasText(rawIds)) return new ArrayList<>();
        try {
            for (String rawId : rawIds.split(",")) {
                if (!StringUtils.hasText(rawId)) continue;
                int templateId = Integer.parseInt(rawId.trim());
                if (templateId <= 0) throw new NumberFormatException();
                result.add(templateId);
            }
        } catch (NumberFormatException exception) {
            throw new ErrorException("原始记录模板参数不正确");
        }
        if (result.size() > 50) throw new ErrorException("原始记录模板最多选择50个");
        return new ArrayList<>(result);
    }
    public InsOrder getTask(Integer detailsId) {
        if (detailsId == null) return null;
        return orderMapper.selectOne(Wrappers.<InsOrder>lambdaQuery()
                .eq(InsOrder::getQualityMonitorDetailsId, detailsId).last("limit 1"));
    }
    @Transactional(rollbackFor = Exception.class)
    public void cancel(Integer detailsId) {
        InsOrder task = getTask(detailsId);
        if (task == null || !SOURCE.equals(task.getTaskSource()) || !Integer.valueOf(1).equals(task.getState())) {
            throw new ErrorException("质量监控任务不存在或已取消");
        }
        QualityMonitorDetails detail = detailsMapper.selectById(detailsId);
        QualityMonitor plan = detail == null ? null : monitorMapper.selectById(detail.getQualityMonitorId());
        int current = SecurityUtils.getUserId().intValue();
        if (plan == null || !(Integer.valueOf(current).equals(plan.getRatifyUserId())
                || Integer.valueOf(current).equals(task.getCreateUser()))) {
            throw new ErrorException("只有下发人或计划批准人可以取消任务");
        }
        Long startedStates = stateMapper.selectCount(Wrappers.<InsOrderState>lambdaQuery()
                .eq(InsOrderState::getInsOrderId, task.getId()).ne(InsOrderState::getInsState, 0));
        if (startedStates > 0) throw new ErrorException("任务已开始,不能取消");
        Long filledSnapshots = snapshotMapper.selectCount(Wrappers.<InsOrderStandardTemplate>lambdaQuery()
                .eq(InsOrderStandardTemplate::getInsOrderId, task.getId())
                .isNotNull(InsOrderStandardTemplate::getRecordValues));
        if (filledSnapshots > 0) throw new ErrorException("原始记录已填写,不能取消任务");
        task.setState(5);
        orderMapper.updateById(task);
    }
}
cnas-process/src/main/java/com/ruoyi/process/service/impl/ProcessMethodVerifyServiceImpl.java
@@ -17,10 +17,12 @@
import com.ruoyi.common.core.domain.entity.User;
import com.ruoyi.common.utils.QueryWrappers;
import com.ruoyi.device.mapper.DeviceMapper;
import com.ruoyi.device.pojo.Device;
import com.ruoyi.framework.exception.ErrorException;
import com.ruoyi.inspect.util.HackLoopTableRenderPolicy;
import com.ruoyi.inspect.util.UserUtils;
import com.ruoyi.process.dto.ProcessMethodVerifyDto;
import com.ruoyi.process.dto.ProcessMethodVerifyCalibrationsFileDto;
import com.ruoyi.process.dto.ProcessMethodVerifyExportWordDto;
import com.ruoyi.process.mapper.ProcessMethodVerifyCalibrationsFileMapper;
import com.ruoyi.process.mapper.ProcessMethodVerifyMapper;
@@ -191,6 +193,39 @@
        return files;
    }
    @Override
    public List<ProcessMethodVerifyCalibrationsFileDto> getCurrentCalibrationFiles(String deviceIds) {
        List<ProcessMethodVerifyCalibrationsFileDto> files = new ArrayList<>();
        if (StringUtils.isBlank(deviceIds)) return files;
        Set<Integer> ids = new LinkedHashSet<>();
        for (String value : StrUtil.split(deviceIds, ",")) {
            try {
                ids.add(Integer.valueOf(value.trim()));
            } catch (NumberFormatException e) {
                throw new ErrorException("设备ID无效");
            }
        }
        if (ids.size() > 50) throw new ErrorException("最多查看50台设备的校准证书");
        for (Integer deviceId : ids) {
            String fileName = processMethodVerifyCalibrationsFileMapper.selectCalibrationsFile(deviceId);
            if (StringUtils.isBlank(fileName)) continue;
            Device device = deviceMapper.selectById(deviceId);
            if (device == null) continue;
            ProcessMethodVerifyCalibrationsFileDto file = new ProcessMethodVerifyCalibrationsFileDto();
            file.setDeviceId(deviceId);
            file.setDeviceName(device.getDeviceName());
            file.setManagementNumber(device.getManagementNumber());
            file.setFileName(fileName);
            file.setFileUrl(fileName);
            file.setType(fileName.matches("(?i).*\\.(png|jpe?g|gif|bmp|webp)$") ? 1 : 2);
            files.add(file);
        }
        return files;
    }
    /**
     * ä¿®æ”¹æ ‡å‡†æ–¹æ³•验证
     *
cnas-process/src/main/java/com/ruoyi/process/service/impl/QualityMonitorServiceImpl.java
@@ -70,6 +70,9 @@
@Transactional(rollbackFor = Exception.class)
public class QualityMonitorServiceImpl extends ServiceImpl<QualityMonitorMapper, QualityMonitor> implements QualityMonitorService {
    private static final Set<String> EDITABLE_PLAN_STATES = new HashSet<>(Arrays.asList(
            "DRAFT", "REVIEW_REJECTED", "APPROVAL_REJECTED"));
    @Resource
    private QualityMonitorDetailsService qualityMonitorDetailsService;
    @Resource
@@ -96,6 +99,111 @@
    @Value("${wordUrl}")
    private String wordUrl;
    @Override
    public QualityMonitorDto createAnnualPlan(QualityMonitor input) {
        if (input == null || StringUtils.isBlank(input.getMonitorYear())
                || !input.getMonitorYear().matches("\\d{4}")) {
            throw new ErrorException("请选择正确的计划年度");
        }
        if (input.getExamineUserId() == null || userMapper.selectById(input.getExamineUserId()) == null) {
            throw new ErrorException("请选择有效的审核人");
        }
        if (StringUtils.isBlank(input.getMonitorName())) {
            throw new ErrorException("请填写计划名称");
        }
        int userId = SecurityUtils.getUserId().intValue();
        QualityMonitor plan = new QualityMonitor();
        plan.setMonitorYear(input.getMonitorYear());
        plan.setMonitorName(input.getMonitorName().trim());
        plan.setWriteUserId(userId);
        plan.setWriteTime(LocalDateTime.now());
        plan.setExamineUserId(input.getExamineUserId());
        plan.setWorkflowStatus("DRAFT");
        baseMapper.insert(plan);
        return baseMapper.selectPlanDtoById(plan.getQualityMonitorId());
    }
    @Override
    public QualityMonitorDto getAnnualPlan(String monitorYear, Integer qualityMonitorId) {
        QualityMonitor plan;
        if (qualityMonitorId != null) {
            plan = baseMapper.selectById(qualityMonitorId);
            if (plan == null || (StringUtils.isNotBlank(monitorYear)
                    && !monitorYear.equals(plan.getMonitorYear()))) return null;
        } else {
            if (StringUtils.isBlank(monitorYear)) throw new ErrorException("请选择计划年度");
            plan = baseMapper.selectOne(Wrappers.<QualityMonitor>lambdaQuery()
                    .eq(QualityMonitor::getMonitorYear, monitorYear)
                    .orderByDesc(QualityMonitor::getCreateTime, QualityMonitor::getQualityMonitorId)
                    .last("limit 1"));
        }
        return plan == null ? null : baseMapper.selectPlanDtoById(plan.getQualityMonitorId());
    }
    @Override
    public boolean submitAnnualPlan(Integer qualityMonitorId) {
        QualityMonitor plan = requirePlan(qualityMonitorId);
        int current = SecurityUtils.getUserId().intValue();
        if (!Integer.valueOf(current).equals(plan.getWriteUserId())) {
            throw new ErrorException("只有计划编制人可以提交审核");
        }
        String state = effectiveStatus(plan);
        if (!EDITABLE_PLAN_STATES.contains(state)) throw new ErrorException("当前计划状态不能提交审核");
        if (qualityMonitorDetailsMapper.selectCount(Wrappers.<QualityMonitorDetails>lambdaQuery()
                .eq(QualityMonitorDetails::getQualityMonitorId, qualityMonitorId)) == 0) {
            throw new ErrorException("请至少新增一条质控项目后再提交审核");
        }
        baseMapper.update(null, Wrappers.<QualityMonitor>lambdaUpdate()
                .eq(QualityMonitor::getQualityMonitorId, qualityMonitorId)
                .set(QualityMonitor::getWorkflowStatus, "PENDING_REVIEW")
                .set(QualityMonitor::getExamineStatus, null)
                .set(QualityMonitor::getExamineRemark, null)
                .set(QualityMonitor::getRatifyStatus, null));
        sendPlanNotice(plan, plan.getExamineUserId(), "CNAS质量监控计划审核通知", "质量监控计划待审核");
        return true;
    }
    @Override
    public void validateDetailEditable(Integer qualityMonitorId) {
        QualityMonitor plan = requirePlan(qualityMonitorId);
        if (!Integer.valueOf(SecurityUtils.getUserId().intValue()).equals(plan.getWriteUserId())) {
            throw new ErrorException("只有计划编制人可以维护质控项目");
        }
        if (!EDITABLE_PLAN_STATES.contains(effectiveStatus(plan))) {
            throw new ErrorException("当前计划已提交,不能修改质控项目");
        }
    }
    private QualityMonitor requirePlan(Integer id) {
        QualityMonitor plan = id == null ? null : baseMapper.selectById(id);
        if (plan == null) throw new ErrorException("年度质量控制计划不存在");
        return plan;
    }
    private String effectiveStatus(QualityMonitor plan) {
        if (StringUtils.isNotBlank(plan.getWorkflowStatus())) return plan.getWorkflowStatus();
        if (Integer.valueOf(1).equals(plan.getRatifyStatus())) return "APPROVED";
        if (Integer.valueOf(0).equals(plan.getRatifyStatus())) return "APPROVAL_REJECTED";
        if (Integer.valueOf(1).equals(plan.getExamineStatus())) return "PENDING_APPROVAL";
        if (Integer.valueOf(0).equals(plan.getExamineStatus())) return "REVIEW_REJECTED";
        return "DRAFT";
    }
    private void sendPlanNotice(QualityMonitor plan, Integer receiverId, String theme, String suffix) {
        if (receiverId == null) return;
        int sender = SecurityUtils.getUserId().intValue();
        User senderUser = userMapper.selectById(sender);
        InformationNotification info = new InformationNotification();
        info.setCreateUser(senderUser == null ? "" : senderUser.getName());
        info.setMessageType("6");
        info.setTheme(theme);
        info.setContent(plan.getMonitorName() + suffix);
        info.setSenderId(sender);
        info.setConsigneeId(receiverId);
        info.setJumpPath(MenuJumpPathConstants.QUALITY_MONITOR);
        informationNotificationService.addInformationNotification(info);
    }
    /**
     * å¯¼å…¥ç›‘控计划
@@ -108,17 +216,18 @@
        Integer userId = SecurityUtils.getUserId().intValue();
        // æ–‡ä»¶åç§°
        String fileName = file.getOriginalFilename().substring(0, file.getOriginalFilename().lastIndexOf("."));
        if (monitor.getMonitorYear() == null || !monitor.getMonitorYear().matches("\\d{4}")) {
            throw new ErrorException("请选择正确的计划年度");
        }
        if (monitor.getExamineUserId() == null) throw new ErrorException("缺少审核人");
        QualityMonitor qualityMonitor = new QualityMonitor();
        qualityMonitor.setMonitorName(fileName);
        qualityMonitor.setMonitorYear(monitor.getMonitorYear());
        qualityMonitor.setWriteUserId(userId);
        qualityMonitor.setExamineUserId(monitor.getExamineUserId());
        qualityMonitor.setWriteTime(LocalDateTime.now());
        qualityMonitor.setWorkflowStatus("PENDING_REVIEW");
        baseMapper.insert(qualityMonitor);
        if (monitor.getExamineUserId() == null) {
            throw new ErrorException("缺少审核人");
        }
        User user = userMapper.selectById(userId);
        // æ¶ˆæ¯å‘送
@@ -159,14 +268,27 @@
            EasyExcel.read(file.getInputStream(), QualityMonitorDetailsUpload.class, new AnalysisEventListener<QualityMonitorDetailsUpload>() {
                @Override
                public void invoke(QualityMonitorDetailsUpload detailsUpload, AnalysisContext analysisContext) {
                    // åˆ¤æ–­æ˜¯å¦ä¸ºç©º
                    if (StringUtils.isBlank(detailsUpload.getPlannedTime())) {
                        throw new ErrorException("计划开展时间不能为空");
                    if (StringUtils.isBlank(detailsUpload.getMonitorProject())
                            || StringUtils.isBlank(detailsUpload.getTechnicalStandardText())
                            || StringUtils.isBlank(detailsUpload.getMonitorWay())
                            || StringUtils.isBlank(detailsUpload.getCompletionMonth())) {
                        throw new ErrorException("质控项目、技术标准、质控方式和完成时限不能为空");
                    }
                        // å¯¹è±¡å¤åˆ¶
                    QualityMonitorDetails monitorDetails = new QualityMonitorDetails();
                    BeanUtils.copyProperties(detailsUpload, monitorDetails);
                    monitorDetails.setQualityMonitorId(qualityMonitor.getQualityMonitorId());
                    if (!detailsUpload.getCompletionMonth().matches("\\d{4}-\\d{2}")) {
                        throw new ErrorException("完成时限格式应为yyyy-MM");
                    }
                    if (StringUtils.isBlank(detailsUpload.getResponsibleName())) {
                        throw new ErrorException("负责人不能为空");
                    }
                    List<User> users = userMapper.selectList(Wrappers.<User>lambdaQuery()
                            .eq(User::getName, detailsUpload.getResponsibleName()));
                    if (users.size() != 1) throw new ErrorException("负责人姓名不存在或不唯一:" + detailsUpload.getResponsibleName());
                    monitorDetails.setResponsibleUserId(users.get(0).getId());
                    monitorDetails.setItemKind("OTHER");
                    detailsUploadList.add(monitorDetails);
                }
@@ -191,8 +313,16 @@
     */
    @Override
    public boolean examineQualityMonitor(QualityMonitor qualityMonitor) {
        if (qualityMonitor.getRatifyUserId() == null) {
            throw new ErrorException("缺少批准人");
        QualityMonitor stored = requirePlan(qualityMonitor.getQualityMonitorId());
        int current = SecurityUtils.getUserId().intValue();
        if (!Integer.valueOf(current).equals(stored.getExamineUserId())) {
            throw new ErrorException("只有指定审核人可以审核计划");
        }
        if (!"PENDING_REVIEW".equals(effectiveStatus(stored))) {
            throw new ErrorException("当前计划不在待审核状态");
        }
        if (Integer.valueOf(1).equals(qualityMonitor.getExamineStatus()) && qualityMonitor.getRatifyUserId() == null) {
            throw new ErrorException("审核通过时必须指定批准人");
        }
        // å½“前登录用户
@@ -202,7 +332,11 @@
                .set(QualityMonitor::getExamineRemark, qualityMonitor.getExamineRemark())
                .set(QualityMonitor::getExamineStatus, qualityMonitor.getExamineStatus())
                .set(QualityMonitor::getExamineTime, LocalDateTime.now())
                .set(QualityMonitor::getWorkflowStatus,
                        Integer.valueOf(1).equals(qualityMonitor.getExamineStatus()) ? "PENDING_APPROVAL" : "REVIEW_REJECTED")
        );
        if (!Integer.valueOf(1).equals(qualityMonitor.getExamineStatus())) return true;
        QualityMonitor monitor = baseMapper.selectById(qualityMonitor.getQualityMonitorId());
@@ -247,12 +381,22 @@
     */
    @Override
    public boolean ratifyQualityMonitor(QualityMonitor qualityMonitor) {
        QualityMonitor stored = requirePlan(qualityMonitor.getQualityMonitorId());
        int current = SecurityUtils.getUserId().intValue();
        if (!Integer.valueOf(current).equals(stored.getRatifyUserId())) {
            throw new ErrorException("只有指定批准人可以批准计划");
        }
        if (!"PENDING_APPROVAL".equals(effectiveStatus(stored))) {
            throw new ErrorException("当前计划不在待批准状态");
        }
        // å½“前登录用户
        baseMapper.update(null, Wrappers.<QualityMonitor>lambdaUpdate()
                .eq(QualityMonitor::getQualityMonitorId, qualityMonitor.getQualityMonitorId())
                .set(QualityMonitor::getRatifyRemark, qualityMonitor.getRatifyRemark())
                .set(QualityMonitor::getRatifyStatus, qualityMonitor.getRatifyStatus())
                .set(QualityMonitor::getRatifyTime, LocalDateTime.now())
                .set(QualityMonitor::getWorkflowStatus,
                        Integer.valueOf(1).equals(qualityMonitor.getRatifyStatus()) ? "APPROVED" : "APPROVAL_REJECTED")
        );
        return true;
    }
cnas-process/src/main/resources/mapper/QualityMonitorDetailsMapper.xml
@@ -4,12 +4,21 @@
    <select id="pageQualityMonitorDetail" resultType="com.ruoyi.process.dto.QualityMonitorDetailsDto">
        select * from (
        SELECT cqmd.*,
        ru.name responsible_name,
        eu.name evaluator_name,
        qot.id inspection_task_id,
        qot.entrust_code inspection_task_code,
        qot.ins_state inspection_task_state,
        qot.state inspection_task_order_state,
        qmp.ratify_status plan_ratify_status,
        qmp.ratify_user_id plan_ratify_user_id,
        CASE
        WHEN cqmder.is_finish = 1 THEN 3
        WHEN cqmder.is_finish = 0 THEN 2
        WHEN cqmder.quality_monitor_details_id IS NOT NULL THEN 1
        ELSE 0 END details_ratify_status,
        CASE
        WHEN cqmd.item_kind = 'INSPECTION' THEN -1
        WHEN cqmd.ratify_status = 1 THEN 3
        WHEN cqmd.ratify_status = 0 THEN 2
        WHEN cqmd.finish_report_url IS NOT NULL THEN 1
@@ -18,8 +27,13 @@
        WHEN cqmde.is_finish = 1 THEN 3
        WHEN cqmde.ratify_user_id IS NOT NULL THEN 2
        WHEN cqmde.implement_user_id IS NOT NULL and cqmde.details_evaluate_id is not null THEN 1
        WHEN qot.ins_state = 5 THEN 1
        ELSE 0 END evaluate_status
        FROM cnas_quality_monitor_details cqmd
        LEFT JOIN user ru ON ru.id = cqmd.responsible_user_id
        LEFT JOIN user eu ON eu.id = cqmd.evaluator_user_id
        LEFT JOIN cnas_quality_monitor qmp ON qmp.quality_monitor_id = cqmd.quality_monitor_id
        LEFT JOIN ins_order qot ON qot.quality_monitor_details_id = cqmd.quality_monitor_details_id
        LEFT JOIN cnas_quality_monitor_details_evaluate cqmde
        ON cqmd.quality_monitor_details_id = cqmde.quality_monitor_details_id
        LEFT JOIN cnas_quality_monitor_details_ratify cqmder
@@ -29,12 +43,6 @@
        <if test="ew.customSqlSegment != null and ew.customSqlSegment != ''">
            ${ew.customSqlSegment}
        </if>
        order by quality_monitor_id, monitor_purpose, -- æå–年份
        CAST(SUBSTRING(planned_time, 1, 4) AS SIGNED) ASC,
        -- æå–月份
        CASE
        WHEN planned_time LIKE '% - %' THEN 1
        ELSE CAST(SUBSTRING_INDEX(SUBSTRING(planned_time, 6), '月', 1) AS SIGNED)
        END ASC
        order by quality_monitor_id, COALESCE(sort_no, 2147483647), quality_monitor_details_id
    </select>
</mapper>
cnas-process/src/main/resources/mapper/QualityMonitorMapper.xml
@@ -17,4 +17,13 @@
            ${ew.customSqlSegment}
        </if>
    </select>
    <select id="selectPlanDtoById" resultType="com.ruoyi.process.dto.QualityMonitorDto">
        SELECT cqm.*, u1.name write_name, u2.name examine_name, u3.name ratify_name
        FROM cnas_quality_monitor cqm
        LEFT JOIN user u1 ON u1.id = cqm.write_user_id
        LEFT JOIN user u2 ON u2.id = cqm.examine_user_id
        LEFT JOIN user u3 ON u3.id = cqm.ratify_user_id
        WHERE cqm.quality_monitor_id = #{id}
    </select>
</mapper>
cnas-process/src/main/resources/mapper/QualityMonitorTaskCodeMapper.xml
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.ruoyi.process.mapper.QualityMonitorTaskCodeMapper">
    <insert id="ensureYear">
        INSERT INTO cnas_quality_monitor_task_code_seq(task_year, current_value)
        VALUES(#{year}, 0)
        ON DUPLICATE KEY UPDATE task_year = task_year
    </insert>
    <select id="lockCurrent" resultType="java.lang.Integer">
        SELECT current_value
        FROM cnas_quality_monitor_task_code_seq
        WHERE task_year = #{year}
        FOR UPDATE
    </select>
    <update id="updateCurrent">
        UPDATE cnas_quality_monitor_task_code_seq
        SET current_value = #{value}
        WHERE task_year = #{year}
    </update>
    <select id="countStandardTemplateBinding" resultType="java.lang.Integer">
        SELECT COUNT(*)
        FROM standard_tree st
        JOIN standard_tree_template_binding b ON b.standard_tree_id = st.id
        WHERE st.standard_method_id = #{standardId}
          AND b.template_id = #{templateId}
    </select>
</mapper>
inspect-server/src/main/java/com/ruoyi/inspect/dto/InsOrderPlanDTO.java
@@ -27,6 +27,9 @@
    @Schema(description = "委托编号")
    private String entrustCode;
    @Schema(description = "任务来源:ENTRUSTED或QUALITY_MONITOR")
    private String taskSource;
    @Schema(description = "检验类型")
    private Integer typeSource;
inspect-server/src/main/java/com/ruoyi/inspect/mapper/InsOrderMapper.java
@@ -29,6 +29,11 @@
@Mapper
public interface InsOrderMapper extends BaseMapper<InsOrder> {
    Integer selectQualityMonitorEvaluatorId(@Param("orderId") Integer orderId);
    Integer selectQualityMonitorInspectorId(@Param("orderId") Integer orderId);
    //获取检验下单数据
    IPage<SampleOrderDto> selectInsOrderPage(@Param("page") IPage<InsOrder> page, @Param("ew") QueryWrapper<SampleOrderDto> ew, @Param("laboratory") String laboratory, @Param("isOrderAll") String isOrderAll, @Param("receiptSubmitted") String receiptSubmitted);
inspect-server/src/main/java/com/ruoyi/inspect/pojo/InsOrder.java
@@ -36,6 +36,12 @@
    @Schema(description = "检验类别")
    private String orderType;
    @Schema(description = "任务来源:ENTRUSTED委托,QUALITY_MONITOR质量监控")
    private String taskSource;
    @Schema(description = "质量监控计划明细ID")
    private Integer qualityMonitorDetailsId;
    @Schema(description = "下单客户")
    private String custom;
inspect-server/src/main/java/com/ruoyi/inspect/pojo/InsOrderDeviceRecord.java
@@ -16,8 +16,10 @@
 */
@Data
@TableName("device_record")
@Schema(description = "检验任务设备使用记录")
public class InsOrderDeviceRecord {
    @Schema(description = "设备使用记录ID")
    @TableId(value = "id", type = IdType.AUTO)
    private Integer id;
inspect-server/src/main/java/com/ruoyi/inspect/pojo/InsOrderStandardTemplate.java
@@ -17,8 +17,10 @@
 */
@Data
@TableName(value = "ins_order_standard_template")
@Schema(description = "检验任务原始记录模板快照")
public class InsOrderStandardTemplate {
    @Schema(description = "模板快照ID")
    @TableId(type = IdType.AUTO)
    private Integer id;
inspect-server/src/main/java/com/ruoyi/inspect/pojo/InsOrderTemplateDevice.java
@@ -9,8 +9,10 @@
/** è®¢å•模板与检验设备关系。 */
@Data
@TableName("ins_order_template_device")
@Schema(description = "原始记录模板选用设备关系")
public class InsOrderTemplateDevice {
    @Schema(description = "模板设备关系ID")
    @TableId(type = IdType.AUTO)
    private Integer id;
inspect-server/src/main/java/com/ruoyi/inspect/service/InsOrderPlanService.java
@@ -13,6 +13,7 @@
import com.ruoyi.inspect.pojo.InsProduct;
import com.ruoyi.inspect.vo.InsOrderPlanTaskSwitchVo;
import com.ruoyi.inspect.vo.InsOrderPlanVO;
import com.ruoyi.inspect.vo.QualityMonitorRawContext;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.multipart.MultipartFile;
@@ -37,6 +38,12 @@
    void saveOrderDevices(SaveOrderDevicesDto saveOrderDevicesDto);
    /** ä»Žå·²å¤æ ¸çš„质控任务原始记录读取导出检测数据。 */
    List<Map<String, String>> getQualityMonitorRawResults(Integer orderId);
    /** ä»Žå·²å¤æ ¸çš„原始记录获取每个模板的检测日期及所选设备。 */
    QualityMonitorRawContext getQualityMonitorRawContext(Integer orderId);
    Map<String, Object> doInsOrder(Integer id, String laboratory);
    int upPlanUser(Integer userId, Integer orderId,String sonLaboratory);
inspect-server/src/main/java/com/ruoyi/inspect/service/impl/InsOrderPlanServiceImpl.java
@@ -49,6 +49,7 @@
import com.ruoyi.inspect.util.ReportSignaturePictureUtils;
import com.ruoyi.inspect.vo.InsOrderPlanTaskSwitchVo;
import com.ruoyi.inspect.vo.InsOrderPlanVO;
import com.ruoyi.inspect.vo.QualityMonitorRawContext;
import com.ruoyi.inspect.vo.InsSampleUserVO;
import com.ruoyi.performance.mapper.AuxiliaryOutputWorkingHoursMapper;
import com.ruoyi.performance.pojo.AuxiliaryOutputWorkingHours;
@@ -223,6 +224,10 @@
    public boolean claimInsOrderPlan(InsOrderPlanDTO entity) {
        if (Objects.isNull(entity)) {
            return false;
        }
        InsOrder claimOrder = insOrderMapper.selectById(entity.getInsSampleId());
        if (claimOrder != null && "QUALITY_MONITOR".equals(claimOrder.getTaskSource())) {
            throw new ErrorException("质量监控任务由计划指定检测员,不能认领");
        }
        Integer userId = SecurityUtils.getUserId().intValue();
        InsSampleUser insSampleUser = new InsSampleUser(entity.getInsSampleId().intValue(), userId, 0, entity.getSonLaboratory());
@@ -612,6 +617,16 @@
    public void saveTemplateContext(SaveTemplateContextDto dto) {
        if (dto.getOrderId() == null || dto.getSampleId() == null || dto.getTemplateId() == null) {
            throw new ErrorException("订单、样品和模板不能为空");
        }
        InsOrder sourceOrder = insOrderMapper.selectById(dto.getOrderId());
        if (sourceOrder != null && "QUALITY_MONITOR".equals(sourceOrder.getTaskSource())
                && !Objects.equals(sourceOrder.getState(), 1)) {
            throw new ErrorException("已取消的质量监控任务不能填写原始记录");
        }
        if (sourceOrder != null && "QUALITY_MONITOR".equals(sourceOrder.getTaskSource())
                && !Objects.equals(SecurityUtils.getUserId().intValue(),
                    insOrderMapper.selectQualityMonitorInspectorId(dto.getOrderId()))) {
            throw new ErrorException("只有计划指定的检测员可以填写质量监控原始记录");
        }
        InsSample sample = insSampleMapper.selectById(dto.getSampleId());
        if (sample == null || !Objects.equals(sample.getInsOrderId(), dto.getOrderId())) {
@@ -1027,6 +1042,10 @@
    //交接
    @Override
    public int upPlanUser(Integer userId, Integer orderId, String sonLaboratory) {
        InsOrder handoffOrder = insOrderMapper.selectById(orderId);
        if (handoffOrder != null && "QUALITY_MONITOR".equals(handoffOrder.getTaskSource())) {
            throw new ErrorException("质量监控任务检测员由计划指定,不能直接交接");
        }
        InsSampleUser insSampleUser = new InsSampleUser();
        insSampleUser.setUserId(userId);
        insSampleUser.setInsSampleId(orderId);
@@ -1211,6 +1230,23 @@
        InsOrder order = insOrderMapper.selectOne(Wrappers.<InsOrder>lambdaQuery()
                .eq(InsOrder::getId, orderId)
                .last("FOR UPDATE"));
        if (order == null) throw new ErrorException("检验任务不存在");
        if ("QUALITY_MONITOR".equals(order.getTaskSource()) && !Objects.equals(order.getState(), 1)) {
            throw new ErrorException("已取消的质量监控任务不能提交");
        }
        if ("QUALITY_MONITOR".equals(order.getTaskSource())
                && (verifyUser == null || userMapper.selectById(verifyUser) == null)) {
            throw new ErrorException("请选择质量监控任务复核人");
        }
        if ("QUALITY_MONITOR".equals(order.getTaskSource())
                && Objects.equals(verifyUser, insOrderMapper.selectQualityMonitorInspectorId(orderId))) {
            throw new ErrorException("检测员不能复核自己的质量监控原始记录");
        }
        if ("QUALITY_MONITOR".equals(order.getTaskSource())
                && !Objects.equals(SecurityUtils.getUserId().intValue(),
                    insOrderMapper.selectQualityMonitorInspectorId(orderId))) {
            throw new ErrorException("只有计划指定的检测员可以提交质量监控任务");
        }
        boolean isInspectionReport = Objects.equals(INSPECTION_REPORT, reportType);
        registerInsResults = isInspectionReport && Boolean.TRUE.equals(registerInsResults);
        // 1. å§”托编号全局唯一,提交复核时再次校验,防止并发或历史数据绕过生成校验。
@@ -1675,11 +1711,25 @@
        if (orderId == null || laboratory == null || type == null) {
            throw new ErrorException("复核参数不完整");
        }
        InsOrder verifyOrder = insOrderMapper.selectById(orderId);
        if (verifyOrder == null) throw new ErrorException("检验任务不存在");
        if ("QUALITY_MONITOR".equals(verifyOrder.getTaskSource()) && !Objects.equals(verifyOrder.getState(), 1)) {
            throw new ErrorException("已取消的质量监控任务不能复核");
        }
        ensureDirectTemplateOrderState(orderId);
        InsOrderState currentState = insOrderStateMapper.selectOne(Wrappers.<InsOrderState>lambdaQuery()
                .eq(InsOrderState::getInsOrderId, orderId)
                .eq(InsOrderState::getLaboratory, laboratory)
                .last("limit 1"));
        if ("QUALITY_MONITOR".equals(verifyOrder.getTaskSource())
                && (currentState == null || currentState.getVerifyUser() == null
                    || !Objects.equals(SecurityUtils.getUserId().intValue(), currentState.getVerifyUser()))) {
            throw new ErrorException("只有提交时指定的复核人可以复核质量监控任务");
        }
        if ("QUALITY_MONITOR".equals(verifyOrder.getTaskSource()) && Objects.equals(type, 1)
                && currentState != null && Objects.equals(currentState.getInsState(), 5)) {
            return 1;
        }
        InsReport existingReport = insReportMapper.selectOne(Wrappers.<InsReport>lambdaQuery()
                .eq(InsReport::getInsOrderId, orderId)
                .last("limit 1"));
@@ -1738,6 +1788,24 @@
            insOrderMapper.update(null, Wrappers.<InsOrder>lambdaUpdate()
                    .eq(InsOrder::getId, orderId)
                    .set(InsOrder::getInsState, 5));
            // è´¨é‡ç›‘控任务只保留原始记录,后续由质量监控评价完成归档。
            if ("QUALITY_MONITOR".equals(order.getTaskSource())) {
                Integer evaluatorId = insOrderMapper.selectQualityMonitorEvaluatorId(orderId);
                if (evaluatorId != null) {
                    InformationNotification notice = new InformationNotification();
                    notice.setCreateUser(insProductMapper.selectUserById(SecurityUtils.getUserId().intValue()).get("name"));
                    notice.setSenderId(SecurityUtils.getUserId().intValue());
                    notice.setConsigneeId(evaluatorId);
                    notice.setMessageType("2");
                    notice.setTheme("质量监控评价通知");
                    notice.setContent("质量监控任务 " + order.getEntrustCode() + " çš„原始记录已复核,请填写评价记录");
                    notice.setViewStatus(false);
                    notice.setJumpPath(MenuJumpPathConstants.QUALITY_MONITOR);
                    informationNotificationService.addInformationNotification(notice);
                }
                return 1;
            }
            // æ£€éªŒäººå‘˜æäº¤å¤æ ¸æ—¶ä¸ç”ŸæˆæŠ¥å‘Šï¼›å…¨éƒ¨è¯•验室复核通过后生成报告。
            if (report == null || StringUtils.isBlank(report.getUrl())) {
@@ -2009,6 +2077,10 @@
     * @param orderId
     */
    private void generateReport(Integer orderId, Integer reportType, Integer writeUserId) {
        InsOrder sourceOrder = insOrderMapper.selectById(orderId);
        if (sourceOrder != null && "QUALITY_MONITOR".equals(sourceOrder.getTaskSource())) {
            throw new ErrorException("质量监控检验任务不生成报告");
        }
        /*样品下的项目只要有一个项目不合格则检验结果为0,否则为1*/
        //这里的insSamples是订单下的所有样品包括("/")
        List<InsSample> insSamples = insSampleMapper.selectList(Wrappers.<InsSample>lambdaQuery().eq(InsSample::getInsOrderId, orderId));
@@ -3774,6 +3846,40 @@
    /**
     * æ–°èšç›´ç»‘模板订单:模板中每一个“导出值”批注都对应报告中的一条检测项目。
     */
    @Override
    public List<Map<String, String>> getQualityMonitorRawResults(Integer orderId) {
        requireReviewedQualityMonitorOrder(orderId);
        return buildTemplateDetectionResultRows(orderId);
    }
    @Override
    public QualityMonitorRawContext getQualityMonitorRawContext(Integer orderId) {
        requireReviewedQualityMonitorOrder(orderId);
        QualityMonitorRawContext context = new QualityMonitorRawContext();
        List<InsOrderStandardTemplate> snapshots = insOrderStandardTemplateService.list(
                Wrappers.<InsOrderStandardTemplate>lambdaQuery()
                        .eq(InsOrderStandardTemplate::getInsOrderId, orderId)
                        .orderByAsc(InsOrderStandardTemplate::getSort, InsOrderStandardTemplate::getId));
        context.setDetectionDates(snapshots.stream().map(InsOrderStandardTemplate::getDetectionTime)
                .filter(Objects::nonNull).distinct().sorted().collect(Collectors.toList()));
        List<Integer> deviceIds = insOrderTemplateDeviceMapper.selectDeviceIdsByOrderId(orderId);
        if (CollectionUtils.isNotEmpty(deviceIds)) {
            context.setDevices(insOrderMapper.selectDeviceListByIds(new LinkedHashSet<>(deviceIds)));
        }
        return context;
    }
    private void requireReviewedQualityMonitorOrder(Integer orderId) {
        InsOrder order = insOrderMapper.selectById(orderId);
        if (order == null || !"QUALITY_MONITOR".equals(order.getTaskSource())
                || !Objects.equals(order.getInsState(), 5)) {
            throw new ErrorException("质控任务原始记录复核通过后才能读取检测数据");
        }
        Long pendingCount = insOrderStateMapper.selectCount(Wrappers.<InsOrderState>lambdaQuery()
                .eq(InsOrderState::getInsOrderId, orderId).ne(InsOrderState::getInsState, 5));
        if (pendingCount > 0) throw new ErrorException("质控任务仍有未复核的原始记录");
    }
    private List<Map<String, String>> buildTemplateDetectionResultRows(Integer orderId) {
        List<InsOrderStandardTemplate> snapshots = insOrderStandardTemplateService.list(
                Wrappers.<InsOrderStandardTemplate>lambdaQuery()
inspect-server/src/main/java/com/ruoyi/inspect/vo/IfsOrderVO.java
@@ -77,4 +77,5 @@
    @Schema(description = "各原始记录模板的环境条件")
    private List<TemplateConditionVo> templateConditions;
}
inspect-server/src/main/java/com/ruoyi/inspect/vo/InsOrderPlanTaskSwitchVo.java
@@ -9,6 +9,8 @@
    private String entrustCode;
    private String taskSource;
    private String sampleType;
    @Schema(description = "紧急程度")
inspect-server/src/main/java/com/ruoyi/inspect/vo/InsOrderPlanVO.java
@@ -12,6 +12,19 @@
    @Schema(description = "委托编号")
    private String entrustCode;
    private String taskSource;
    private Integer qualityMonitorDetailsId;
    private String monitorProject;
    private String dueDate;
    private String monitorPurpose;
    private String monitorWay;
    private String sampleSpec;
    private String standardCode;
    private Integer evaluatorUserId;
    private Integer planRatifyUserId;
    private Integer evaluateStatus;
    private String technicalStandardText;
    @Schema(description = "样品名称")
    private String sample;
inspect-server/src/main/java/com/ruoyi/inspect/vo/QualityMonitorRawContext.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,23 @@
package com.ruoyi.inspect.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
 * å·²å¤æ ¸çš„质控原始记录中用于评价表的日期和设备。
 */
@Data
@Schema(description = "已复核质控原始记录中的检测日期与所用设备")
public class QualityMonitorRawContext {
    @Schema(description = "各原始记录模板的检测日期,去重并按日期升序排列")
    private List<LocalDate> detectionDates = new ArrayList<>();
    @Schema(description = "任务实际选用的设备信息,按设备选择顺序排列")
    private List<Map<String, String>> devices = new ArrayList<>();
}
inspect-server/src/main/resources/mapper/InsOrderMapper.xml
@@ -4,6 +4,22 @@
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ruoyi.inspect.mapper.InsOrderMapper">
    <select id="selectQualityMonitorEvaluatorId" resultType="java.lang.Integer">
        SELECT cqmd.evaluator_user_id
        FROM ins_order io
        JOIN cnas_quality_monitor_details cqmd
          ON cqmd.quality_monitor_details_id = io.quality_monitor_details_id
        WHERE io.id = #{orderId}
    </select>
    <select id="selectQualityMonitorInspectorId" resultType="java.lang.Integer">
        SELECT cqmd.inspector_user_id
        FROM ins_order io
        JOIN cnas_quality_monitor_details cqmd
          ON cqmd.quality_monitor_details_id = io.quality_monitor_details_id
        WHERE io.id = #{orderId}
    </select>
    <resultMap id="selectSampleDefectsMap" type="com.ruoyi.inspect.vo.SampleDefectsFatherVo">
        <id column="id" property="id"/>
        <result column="sample" property="sample"/>
inspect-server/src/main/resources/mapper/InsSampleMapper.xml
@@ -205,6 +205,24 @@
        io.appointed,
        io.send_time,
        io.order_type,
        io.task_source,
        io.quality_monitor_details_id,
        cqmd.monitor_project,
        cqmd.due_date,
        cqmd.monitor_purpose,
        cqmd.monitor_way,
        cqmd.sample_spec,
        cqmd.evaluator_user_id,
        cqmd.technical_standard_text,
        qmp.ratify_user_id plan_ratify_user_id,
        CASE
            WHEN cqmde.is_finish = 1 THEN 3
            WHEN cqmde.ratify_user_id IS NOT NULL THEN 2
            WHEN cqmde.details_evaluate_id IS NOT NULL THEN 1
            WHEN io.ins_state = 5 THEN 1
            ELSE 0
        END evaluate_status,
        qsm.code standard_code,
        case when
        io.type_source = 0
        then io.sample_view
@@ -230,6 +248,10 @@
        iiq.material_prop AS material_prop
        FROM
        ins_order io
        LEFT JOIN cnas_quality_monitor_details cqmd ON cqmd.quality_monitor_details_id = io.quality_monitor_details_id
        LEFT JOIN cnas_quality_monitor qmp ON qmp.quality_monitor_id = cqmd.quality_monitor_id
        LEFT JOIN cnas_quality_monitor_details_evaluate cqmde ON cqmde.quality_monitor_details_id = cqmd.quality_monitor_details_id
        LEFT JOIN standard_method qsm ON qsm.id = cqmd.standard_method_id
        LEFT JOIN ins_sample isa ON isa.ins_order_id = io.id
        LEFT JOIN ins_report ira ON ira.ins_order_id = io.id
        LEFT JOIN ifs_inventory_quantity iiq ON iiq.id = io.ifs_inventory_id
@@ -310,6 +332,7 @@
        SELECT
        io.id,
        io.entrust_code,
        io.task_source,
        io.type,
        io.appointed,
        io.send_time,