gaoluyang
昨天 2eb261814a2407b3481dfe7b995c58d413a18cff
cnas-process/src/main/java/com/ruoyi/process/service/impl/QualitySuperviseServiceImpl.java
@@ -56,6 +56,8 @@
import java.time.YearMonth;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.Base64;
import java.util.HashMap;
import java.util.LinkedHashSet;
@@ -178,8 +180,8 @@
                        throw new ErrorException("未找到被监督员:" + superviseDetails.getSupervisee());
                    }
                    superviseDetails.setSupervisedUserId(user.getId());
                    // 格式化时间
                    superviseDetails.setSuperviseTime(detailsUpload.getSuperviseTime());
                    // 统一日期格式,避免 Excel 把 2026-03-15 存成 2026/3/15 后无法解析
                    superviseDetails.setSuperviseTime(normalizeSuperviseTime(detailsUpload.getSuperviseTime()));
                    detailsUploadList.add(superviseDetails);
                }
@@ -432,6 +434,59 @@
    }
    /**
     * 统一监督日期格式为 yyyy.M:
     * 2026-03-15 / 2026/3/15 / 2026.3.15 / 2026年3月15日 -> 2026.3;
     * 已经是 2026.3 或 "3月、12月" 等写法保持原样。
     */
    private static String normalizeSuperviseTime(String superviseTime) {
        if (StringUtils.isBlank(superviseTime)) {
            return superviseTime;
        }
        String text = superviseTime.trim();
        Matcher matcher = Pattern.compile("^(\\d{4})\\s*[./年-]\\s*(\\d{1,2})(?:\\s*[./月-]\\s*(\\d{1,2})\\s*日?)?$")
                .matcher(text);
        if (matcher.matches()) {
            int month = Integer.parseInt(matcher.group(2));
            if (month >= 1 && month <= 12) {
                return matcher.group(1) + "." + month;
            }
        }
        return text;
    }
    /**
     * 解析监督日期对应的年月,兼容历史数据中的完整日期写法。
     */
    private static YearMonth parseSuperviseYearMonth(String superviseTime, String fallbackYear) {
        if (StringUtils.isNotBlank(superviseTime)) {
            String first = superviseTime.split("[、,,;;]+")[0].trim();
            Matcher fullDate = Pattern.compile("(\\d{4})\\s*[./年-]\\s*(\\d{1,2})").matcher(first);
            if (fullDate.find()) {
                int month = Integer.parseInt(fullDate.group(2));
                if (month >= 1 && month <= 12) {
                    return YearMonth.of(Integer.parseInt(fullDate.group(1)), month);
                }
            }
            // "3月、12月" 这类没有年份的写法,使用计划年度
            Matcher monthOnly = Pattern.compile("^(\\d{1,2})").matcher(first);
            if (monthOnly.find()) {
                int month = Integer.parseInt(monthOnly.group(1));
                if (month >= 1 && month <= 12) {
                    int year = LocalDate.now().getYear();
                    if (StringUtils.isNotBlank(fallbackYear)) {
                        Matcher yearMatcher = Pattern.compile("(\\d{4})").matcher(fallbackYear);
                        if (yearMatcher.find()) {
                            year = Integer.parseInt(yearMatcher.group(1));
                        }
                    }
                    return YearMonth.of(year, month);
                }
            }
        }
        return YearMonth.now();
    }
    /**
     * 监督频次: 按监督时间中的月份个数计算, 如 "2026.3" -> "1次/年", "3月、12月" -> "2次/年"
     */
    private static String buildSuperviseFrequency(String superviseTime) {
@@ -439,7 +494,7 @@
            return "";
        }
        int count = 0;
        for (String part : superviseTime.split("[、,,;;/\\s]+")) {
        for (String part : normalizeSuperviseTime(superviseTime).split("[、,,;;/\\s]+")) {
            if (part.matches(".*\\d.*")) {
                count++;
            }
@@ -456,7 +511,7 @@
        }
        StringBuilder sb = new StringBuilder();
        for (String part : superviseTime.split("[、,,;;]+")) {
            String s = part.trim();
            String s = normalizeSuperviseTime(part.trim());
            if (s.isEmpty()) {
                continue;
            }
@@ -491,9 +546,11 @@
        if (detailsRecord == null) {
            // 查询详情信息
            QualitySuperviseDetails qualitySuperviseDetails = qualitySuperviseDetailsMapper.selectById(superviseDetailsId);
            QualitySupervise qualitySupervise = baseMapper.selectById(qualitySuperviseDetails.getSuperviseId());
            // 查询上一个月第一条的记录信息, 判断被监督人去另外一个
            DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy.M");
            YearMonth yearMonth = YearMonth.parse(qualitySuperviseDetails.getSuperviseTime(), formatter);
            // 监督日期兼容 2026.3 / 2026-03-15 / 2026/3/15 / 2026年3月15日 等写法
            YearMonth yearMonth = parseSuperviseYearMonth(qualitySuperviseDetails.getSuperviseTime(),
                    qualitySupervise == null ? null : qualitySupervise.getSuperviseYear());
            YearMonth previousYearMonth = yearMonth.minusMonths(1);
            LocalDate startDate = previousYearMonth.atDay(1);
@@ -515,21 +572,17 @@
            String supervisor = null;
            if (laseRecord != null) {
                // 查询计划获取另一个监督员
                QualitySupervise qualitySupervise = baseMapper.selectById(qualitySuperviseDetails.getSuperviseId());
                if (StringUtils.isNotBlank(qualitySupervise.getRecordUserIds())) {
                    List<String> recordUserIds = StrUtil.split(qualitySupervise.getRecordUserIds(), ',');
                    List<User> users = userMapper.selectList(Wrappers.<User>lambdaQuery()
                            .in(User::getId, recordUserIds));
                    if (CollectionUtils.isNotEmpty(users) && users.size() == 2) {
                        // 判断是否一样, 一样获取另外一个
                        if (users.get(0).getName().equals(laseRecord.getSupervisor())) {
                            supervisor = users.get(1).getName();
                        } else {
                            supervisor = users.get(0).getName();
                        }
            // 计划指定的监督员:首次填写时默认带出第一位;有上月记录时两位监督员交替
            if (qualitySupervise != null && StringUtils.isNotBlank(qualitySupervise.getRecordUserIds())) {
                List<String> recordUserIds = StrUtil.split(qualitySupervise.getRecordUserIds(), ',');
                List<User> users = userMapper.selectList(Wrappers.<User>lambdaQuery()
                        .in(User::getId, recordUserIds));
                if (CollectionUtils.isNotEmpty(users)) {
                    if (laseRecord != null && users.size() > 1
                            && users.get(0).getName().equals(laseRecord.getSupervisor())) {
                        supervisor = users.get(1).getName();
                    } else {
                        supervisor = users.get(0).getName();
                    }
                }
            }
@@ -643,6 +696,47 @@
    }
    /**
     * 被监督人员确认监督记录(程序4.2.5: 被监督对象对监督记录真实性提出确认意见)
     */
    @Override
    public boolean confirmSuperviseRecord(QualitySuperviseDetailsRecord input) {
        if (input == null || input.getSuperviseDetailsId() == null) {
            throw new ErrorException("缺少监督详细信息id");
        }
        QualitySuperviseDetailsRecord record = qualitySuperviseDetailsRecordMapper.selectOne(
                Wrappers.<QualitySuperviseDetailsRecord>lambdaQuery()
                        .eq(QualitySuperviseDetailsRecord::getSuperviseDetailsId, input.getSuperviseDetailsId())
                        .last("limit 1"));
        if (record == null) {
            throw new ErrorException("监督员尚未提交监督记录,无法确认");
        }
        Integer userId = SecurityUtils.getUserId().intValue();
        User user = userMapper.selectById(userId);
        if (user == null) {
            throw new ErrorException("当前用户不存在");
        }
        // 只有被监督人员本人才可以确认
        boolean isSupervisee = StrUtil.split(record.getTestMember(), '、').stream()
                .map(String::trim)
                .anyMatch(name -> name.equals(user.getName()));
        if (!isSupervisee) {
            QualitySuperviseDetails detail = qualitySuperviseDetailsMapper.selectById(input.getSuperviseDetailsId());
            isSupervisee = detail != null && userId.equals(detail.getSupervisedUserId());
        }
        if (!isSupervisee) {
            throw new ErrorException("只有被监督人员可以确认监督记录");
        }
        String opinion = StringUtils.isBlank(input.getConfirmOpinion())
                ? "对监督记录真实性表示认可。" : input.getConfirmOpinion().trim();
        qualitySuperviseDetailsRecordMapper.update(null,
                Wrappers.<QualitySuperviseDetailsRecord>lambdaUpdate()
                        .eq(QualitySuperviseDetailsRecord::getSuperviseDetailsRecordId,
                                record.getSuperviseDetailsRecordId())
                        .set(QualitySuperviseDetailsRecord::getConfirmOpinion, opinion));
        return true;
    }
    /**
     * 导出监督记录表(日常监督检查记录表)
     * @param superviseDetailsId
     * @param response
@@ -670,6 +764,11 @@
                    put("qualityUrl", signatureOrTransparent(recordDto.getRatifyUserId()));
                    put("qualityDateStr", recordDto.getRatifyTime() != null ?
                            recordDto.getRatifyTime().format(RECORD_DATE_FORMATTER) : "");
                    // 程序4.2.5: 被监督人员对监督记录真实性的确认意见, 未确认时沿用纸张表的默认表述
                    put("confirmOpinionText", StringUtils.isNotBlank(recordDto.getConfirmOpinion())
                            ? recordDto.getConfirmOpinion() : "对监督记录真实性表示认可。");
                    put("superviseMethodText", StringUtils.isNotBlank(recordDto.getSuperviseMethod())
                            ? recordDto.getSuperviseMethod() : "");
                }});
        try {
@@ -690,7 +789,7 @@
    }
    /**
     * 监督内容(9项, 按纸张表横排顺序)
     * 监督内容(9项, 与受控记录表 LMSY-CX29-JL02 的横排顺序、文字完全一致)
     */
    private static final String[] SUPERVISE_CONTENT_ITEMS = {
            "人员持证及培训", "设施及环境条件", "检测方法",