cnas-process/src/main/java/com/ruoyi/process/service/impl/ProcessMethodVerifyServiceImpl.java
@@ -1,7 +1,10 @@
package com.ruoyi.process.service.impl;
import cn.hutool.core.bean.BeanUtil;
import  cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
@@ -29,6 +32,11 @@
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.apache.poi.xwpf.usermodel.XWPFParagraph;
import org.apache.poi.xwpf.usermodel.XWPFRun;
import org.apache.poi.xwpf.usermodel.XWPFTable;
import org.apache.poi.xwpf.usermodel.XWPFTableCell;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletResponse;
@@ -41,6 +49,9 @@
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.LinkedHashSet;
import java.util.Objects;
import java.util.Set;
/**
 * 标准方法验证
@@ -103,6 +114,7 @@
        if (methodVerifyDto.getOperationType() == null) {
            throw new ErrorException("请选择是变更还是验证");
        }
        validateWordVerification(methodVerifyDto);
        // 新增
        baseMapper.insert(methodVerifyDto);
@@ -148,6 +160,37 @@
        return processMethodVerifyDto;
    }
    @Override
    public List<ProcessMethodVerifyWorkFile> getCurrentWorkFiles(String userIds) {
        List<ProcessMethodVerifyWorkFile> files = new ArrayList<>();
        if (StringUtils.isBlank(userIds)) return files;
        Set<Integer> ids = new LinkedHashSet<>();
        for (String value : StrUtil.split(userIds, ",")) {
            try {
                ids.add(Integer.valueOf(value.trim()));
            } catch (NumberFormatException e) {
                throw new ErrorException("检测人员ID无效");
            }
        }
        if (ids.size() > 5) throw new ErrorException("最多选择5名检测人员");
        for (Integer userId : ids) {
            ProcessMethodVerifyWorkFile file = processMethodVerifyWorkFileMapper.selectLatestAuthorization(userId);
            if (file == null) continue;
            file.setUserId(userId);
            if (StringUtils.isBlank(file.getFileUrl())) {
                file.setFileName("未上传上岗证书");
                file.setType(0);
            } else {
                file.setFileName(file.getFileUrl());
                file.setType(file.getFileUrl().matches("(?i).*\\.(png|jpe?g|gif|bmp|webp)$") ? 1 : 2);
            }
            files.add(file);
        }
        return files;
    }
    /**
     * 修改标准方法验证
     *
@@ -156,6 +199,7 @@
     */
    @Override
    public boolean updateMethodVerify(ProcessMethodVerifyDto methodVerifyDto) {
        validateWordVerification(methodVerifyDto);
        baseMapper.updateById(methodVerifyDto);
        // 删除上岗证
@@ -288,6 +332,14 @@
    public void exportMethodVerify(Integer methodVerifyId, HttpServletResponse response) {
        // 获取标准方法更新验证数据
        ProcessMethodVerify processMethodVerify = baseMapper.selectById(methodVerifyId);
        if (processMethodVerify == null) {
            throw new ErrorException("方法验证记录不存在");
        }
        if (Objects.equals(processMethodVerify.getOperationType(), 1)
                && StringUtils.isNotBlank(processMethodVerify.getVerificationContent())) {
            exportWordVerification(processMethodVerify, response);
            return;
        }
        // 将内部字段转换成展示到word中的对象
        ProcessMethodVerifyExportWordDto exportWordTemplate = getExportWordTemplate(processMethodVerify);
@@ -352,6 +404,118 @@
        }
    }
    /** 按客户提供的横向方法验证表导出;原始记录和验证结果仍作为附件保存。 */
    private void exportWordVerification(ProcessMethodVerify verify, HttpServletResponse response) {
        JSONObject content = JSONUtil.parseObj(verify.getVerificationContent());
        String[] checkKeys = {"personnel", "equipment", "reagents", "conditions", "sample", "steps",
                "processing", "qualityControl", "result"};
        InputStream templateStream = getClass().getResourceAsStream("/static/method-verification-customer.docx");
        if (templateStream == null) throw new IllegalStateException("方法验证导出模板不存在");
        try (InputStream inputStream = templateStream; XWPFDocument document = new XWPFDocument(inputStream)) {
            XWPFTable table = document.getTables().get(0);
            setTemplateCell(table, 0, 1, verify.getStandardName());
            setTemplateCell(table, 0, 3, verify.getStandardCode());
            setTemplateCell(table, 1, 1, verify.getVerifyItem());
            JSONArray entries = content.getJSONArray("checks");
            for (int i = 0; i < checkKeys.length; i++) {
                JSONObject entry = findCheck(entries, checkKeys[i]);
                Integer satisfied = entry == null ? null : entry.getInt("satisfied");
                int row = i + 3;
                if (row <= 8) {
                    setTemplateCell(table, row, 1, field(entry, "requirement"));
                    setTemplateCell(table, row, 2, field(entry, "capability"));
                    setTemplateCell(table, row, 3, satisfiedText(satisfied));
                } else {
                    setTemplateCell(table, row, 1, mergedCheckText(entry));
                    setTemplateCell(table, row, 2, satisfiedText(satisfied));
                }
            }
            setTemplateCell(table, 12, 1, field(content, "conclusion"));
            setTemplateCell(table, 13, 1, decisionText(field(content, "reviewDecision"),
                    "评审人员", field(content, "reviewer"), field(content, "reviewDate")));
            setTemplateCell(table, 14, 1, decisionText(field(content, "approvalDecision"),
                    "技术负责人", field(content, "approver"), field(content, "approvalDate")));
            response.setContentType("application/vnd.openxmlformats-officedocument.wordprocessingml.document");
            String fileName = URLEncoder.encode("方法验证记录", "UTF-8");
            response.setHeader("Content-disposition", "attachment;filename=" + fileName + ".docx");
            document.write(response.getOutputStream());
        } catch (Exception e) {
            throw new RuntimeException("导出方法验证记录失败", e);
        }
    }
    private JSONObject findCheck(JSONArray entries, String key) {
        if (entries == null) return null;
        for (Object value : entries) {
            JSONObject check = JSONUtil.parseObj(value);
            if (key.equals(check.getStr("key"))) return check;
        }
        return null;
    }
    private String field(JSONObject object, String key) {
        return object == null ? "" : StrUtil.nullToEmpty(object.getStr(key));
    }
    private String satisfiedText(Integer satisfied) {
        return satisfied == null ? "" : satisfied == 1 ? "是" : "否";
    }
    private String mergedCheckText(JSONObject entry) {
        String requirement = field(entry, "requirement").trim();
        String capability = field(entry, "capability").trim();
        if (requirement.isEmpty()) return capability;
        if (capability.isEmpty() || requirement.equals(capability)) return requirement;
        return "方法要求:" + requirement + "\n实验室能力:" + capability;
    }
    private String decisionText(String decision, String personLabel, String person, String date) {
        StringBuilder value = new StringBuilder();
        for (String choice : new String[]{"同意使用", "不能使用", "限制使用"}) {
            if (value.length() > 0) value.append("  ");
            value.append(choice.equals(decision) ? "☑" : "□").append(choice);
        }
        value.append("\n").append(personLabel).append(":").append(person);
        if (StringUtils.isNotBlank(date)) {
            String[] parts = date.split("-");
            value.append("    日期:");
            value.append(parts.length == 3 ? parts[0] + "年" + parts[1] + "月" + parts[2] + "日" : date);
        }
        return value.toString();
    }
    private void setTemplateCell(XWPFTable table, int row, int cellIndex, String value) {
        XWPFTableCell cell = table.getRow(row).getCell(cellIndex);
        XWPFParagraph paragraph = cell.getParagraphs().get(0);
        XWPFRun run = paragraph.getRuns().get(0);
        String[] lines = StrUtil.nullToEmpty(value).replace("\r\n", "\n").split("\n", -1);
        run.setText(lines[0], 0);
        for (int i = 1; i < lines.length; i++) {
            run.addBreak();
            run.setText(lines[i]);
        }
    }
    private void validateWordVerification(ProcessMethodVerifyDto verify) {
        // 没有 verificationContent 的旧记录继续由旧表单维护。
        if (!Objects.equals(verify.getOperationType(), 1)
                || StringUtils.isBlank(verify.getVerificationContent())) return;
        if (StringUtils.isBlank(verify.getStandardName())
                || StringUtils.isBlank(verify.getStandardCode())
                || StringUtils.isBlank(verify.getVerifyItem())) {
            throw new ErrorException("请填写标准名称、标准编号和验证项目");
        }
        try {
            JSONUtil.parseObj(verify.getVerificationContent());
        } catch (Exception e) {
            throw new ErrorException("方法验证内容格式错误");
        }
        // 保留旧列供现有依赖 method_name 的查询使用。
        verify.setMethodName(verify.getStandardName());
    }
    private ProcessMethodVerifyExportWordDto getExportWordTemplate(ProcessMethodVerify processMethodVerify) {
        ProcessMethodVerifyExportWordDto processMethodVerifyExportWordDto = new ProcessMethodVerifyExportWordDto();
        BeanUtil.copyProperties(processMethodVerify, processMethodVerifyExportWordDto);
@@ -389,7 +553,22 @@
        }
        // 确认日期
        processMethodVerifyExportWordDto.setConfirmDateStr(processMethodVerify.getConfirmDate() != null ? processMethodVerify.getConfirmDate().format(DateTimeFormatter.ofPattern("yyyy-MM-dd")): null);
        // 评审意见
        processMethodVerifyExportWordDto.setReviewResultStr(buildOpinionCheckBox(processMethodVerify.getReviewResult()));
        processMethodVerifyExportWordDto.setReviewDateStr(processMethodVerify.getReviewDate() != null ? processMethodVerify.getReviewDate().format(DateTimeFormatter.ofPattern("yyyy年MM月dd日")): null);
        // 审批意见
        processMethodVerifyExportWordDto.setApprovalResultStr(buildOpinionCheckBox(processMethodVerify.getApprovalResult()));
        processMethodVerifyExportWordDto.setApprovalDateStr(processMethodVerify.getApprovalDate() != null ? processMethodVerify.getApprovalDate().format(DateTimeFormatter.ofPattern("yyyy年MM月dd日")): null);
        return processMethodVerifyExportWordDto;
    }
    /**
     * 拼接"同意使用/不能使用/限制使用"勾选项展示串
     */
    private String buildOpinionCheckBox(String result) {
        return ("同意使用".equals(result) ? "☑" : "□") + "同意使用 "
                + ("不能使用".equals(result) ? "☑" : "□") + "不能使用 "
                + ("限制使用".equals(result) ? "☑" : "□") + "限制使用";
    }
@@ -409,7 +588,7 @@
                User user = userMapper.selectById(userId);
                ProcessMethodVerifyWorkFile workFile = new ProcessMethodVerifyWorkFile();
                workFile.setMethodVerifyId(methodVerifyDto.getMethodVerifyId());
                workFile.setType(1);
                workFile.setType(workName.matches("(?i).*\\.(png|jpe?g|gif|bmp|webp)$") ? 1 : 2);
                workFile.setFileUrl(workName);
                workFile.setFileName(workName);
                workFile.setUserId(user.getId());