liding
2026-10-23 56fe897d3e06bfa1e1105980d0044e6e67b2a484
inspect-server/src/main/java/com/ruoyi/inspect/service/impl/InsOrderPlanServiceImpl.java
@@ -359,6 +359,9 @@
                        "模板ID " + snapshot.getTemplateId());
                if (snapshot.getDetectionTime() == null) {
                    collect.add("请选择“" + templateName + "”的检测时间");
                } else if (snapshot.getDetectionEndTime() != null
                        && snapshot.getDetectionEndTime().isBefore(snapshot.getDetectionTime())) {
                    collect.add("“" + templateName + "”的检测结束日期不能早于开始日期");
                }
                if (StringUtils.isBlank(snapshot.getDetectionPlace())) {
                    collect.add("请填写“" + templateName + "”的检测地点");
@@ -1655,6 +1658,8 @@
            refreshed.setInsOrderId(orderId);
            refreshed.setTemplateId(templateId);
            refreshed.setNumber(standardTemplate == null ? oldSnapshot.getNumber() : standardTemplate.getNumber());
            refreshed.setStandardRequirement(oldSnapshot == null
                    ? standardTemplate.getStandardRequirement() : oldSnapshot.getStandardRequirement());
            refreshed.setName(standardTemplate == null ? oldSnapshot.getName() : standardTemplate.getName());
            refreshed.setRemark(standardTemplate == null ? oldSnapshot.getRemark() : standardTemplate.getRemark());
            refreshed.setThing(standardTemplate == null ? oldSnapshot.getThing() : standardTemplate.getThing());
@@ -1665,6 +1670,7 @@
            refreshed.setHumidity(templateHumidityMap.get(templateId));
            // 刷新模板结构时必须保留检验阶段填写的业务数据,避免进入复核后回显为空。
            refreshed.setDetectionTime(oldSnapshot == null ? null : oldSnapshot.getDetectionTime());
            refreshed.setDetectionEndTime(oldSnapshot == null ? null : oldSnapshot.getDetectionEndTime());
            refreshed.setDetectionPlace(oldSnapshot == null ? null : oldSnapshot.getDetectionPlace());
            refreshed.setInsResult(oldSnapshot == null ? null : oldSnapshot.getInsResult());
            Integer templateSort = templateSortMap.get(templateId);
@@ -2004,7 +2010,8 @@
        if (Objects.equals(report.getIsRatify(), 1)) {
            throw new ErrorException("已批准报告不能重新生成");
        }
        if (StringUtils.isNotBlank(report.getUrlS())) {
        // urlS 同时用于人工上传和审核页面保存的 Word;只有人工上传需要先手动切回系统报告。
        if (StringUtils.isNotBlank(report.getUrlS()) && !isBrowserEditedReportUrl(report.getUrlS())) {
            throw new ErrorException("当前报告已手动上传,请先还原为系统报告后再重新生成");
        }
        if (report.getInsOrderId() == null) {
@@ -2048,7 +2055,12 @@
                .eq(InsReport::getId, reportId)
                .set(InsReport::getWriteTime, report.getWriteTime())
                .set(InsReport::getIsPass, report.getIsPass())
                .set(InsReport::getUrlS, null)
                .set(InsReport::getTempUrlPdf, "/word/" + tempPdfName));
    }
    private static boolean isBrowserEditedReportUrl(String url) {
        return url != null && url.matches("(?i)^/word/(?:edited|reviewed)-[0-9a-f]{8}(?:-[0-9a-f]{4}){3}-[0-9a-f]{12}\\.docx$");
    }
    private Map<String, Object> buildExistingReportMarks(InsReport report) {
@@ -3613,7 +3625,8 @@
                        .eq(InsOrderStandardTemplate::getInsOrderId, orderId)
                        .orderByAsc(InsOrderStandardTemplate::getSort, InsOrderStandardTemplate::getId));
        List<LocalDate> detectionDates = templateSnapshots.stream()
                .map(InsOrderStandardTemplate::getDetectionTime)
                .flatMap(snapshot -> java.util.stream.Stream.of(
                        snapshot.getDetectionTime(), snapshot.getDetectionEndTime()))
                .filter(Objects::nonNull)
                .collect(Collectors.toList());
        String insTime = formatReportTestDate(detectionDates);
@@ -3629,7 +3642,7 @@
        List<Map<String, String>> finalDeviceList = deviceList;
        String finalModelStr = modelStr;
        // 直绑模板订单没有 ins_product,检测项目、单位和结果均从订单模板快照中获取。
        // 直绑模板订单没有 ins_product,检测项目、标准要求值和结果均从订单模板快照中获取。
        List<Map<String, String>> detectionResultRows = directTemplateOrder
                ? buildTemplateDetectionResultRows(orderId)
                : buildDetectionResultRows(samples);
@@ -3811,7 +3824,7 @@
    }
    /**
     * 新版检测报告的结果表固定为四列:序号、检测项目、单位、检测结果。
     * 新版检测、检验报告共用结果数据;标准要求值取自原始记录模板配置。
     */
    private List<Map<String, String>> buildDetectionResultRows(List<SampleProductDto> samples) {
        List<Map<String, String>> rows = new ArrayList<>();
@@ -3820,6 +3833,7 @@
        }
        boolean multipleSamples = samples.size() > 1;
        Map<Integer, String> requirementsByTemplate = new HashMap<>();
        int sequence = 1;
        for (SampleProductDto sample : samples) {
            if (CollectionUtils.isEmpty(sample.getInsProduct())) {
@@ -3840,8 +3854,15 @@
                Map<String, String> row = new LinkedHashMap<>();
                row.put("seq", String.valueOf(sequence++));
                row.put("itemName", itemName);
                row.put("itemName", formatReportItemWithUnit(itemName, product.getUnit()));
                row.put("unit", defaultReportText(product.getUnit(), "/"));
                String requirement = product.getTemplateId() == null ? null
                        : requirementsByTemplate.computeIfAbsent(product.getTemplateId(), templateId -> {
                            StandardTemplate sourceTemplate = standardTemplateService.getById(templateId);
                            return defaultReportText(sourceTemplate == null ? null
                                    : sourceTemplate.getStandardRequirement(), "/");
                        });
                row.put("standardRequirement", defaultReportText(requirement, "/"));
                row.put("result", defaultReportText(product.getLastValue(), "/"));
                row.put("inspectionConclusion", formatInspectionConclusion(product.getInsResult()));
                rows.add(row);
@@ -3867,7 +3888,8 @@
                Wrappers.<InsOrderStandardTemplate>lambdaQuery()
                        .eq(InsOrderStandardTemplate::getInsOrderId, orderId)
                        .orderByAsc(InsOrderStandardTemplate::getSort, InsOrderStandardTemplate::getId));
        context.setDetectionDates(snapshots.stream().map(InsOrderStandardTemplate::getDetectionTime)
        context.setDetectionDates(snapshots.stream().flatMap(snapshot -> java.util.stream.Stream.of(
                        snapshot.getDetectionTime(), snapshot.getDetectionEndTime()))
                .filter(Objects::nonNull).distinct().sorted().collect(Collectors.toList()));
        List<Integer> deviceIds = insOrderTemplateDeviceMapper.selectDeviceIdsByOrderId(orderId);
        if (CollectionUtils.isNotEmpty(deviceIds)) {
@@ -3895,6 +3917,12 @@
        List<Map<String, String>> rows = new ArrayList<>();
        int sequence = 1;
        for (InsOrderStandardTemplate snapshot : snapshots) {
            String standardRequirement = snapshot.getStandardRequirement();
            // 兼容新增该字段前下单的旧记录;有快照时始终以订单保存的值为准。
            if (StringUtils.isBlank(standardRequirement) && snapshot.getTemplateId() != null) {
                StandardTemplate sourceTemplate = standardTemplateService.getById(snapshot.getTemplateId());
                standardRequirement = sourceTemplate == null ? null : sourceTemplate.getStandardRequirement();
            }
            List<TemplateExportCell> exportCells = getTemplateExportCells(snapshot.getThing());
            JSONObject recordValues = StrUtil.isBlank(snapshot.getRecordValues())
                    ? new JSONObject() : JSON.parseObject(snapshot.getRecordValues());
@@ -3913,8 +3941,10 @@
                row.put("seq", String.valueOf(sequence++));
                String reportItemName = defaultReportText(itemName,
                        defaultReportText(snapshot.getName(), "模板ID " + snapshot.getTemplateId()));
                row.put("itemName", formatExportItemName(reportItemName));
                row.put("itemName", formatReportItemWithUnit(
                        formatExportItemName(reportItemName), unit));
                row.put("unit", defaultReportText(unit, "/"));
                row.put("standardRequirement", defaultReportText(standardRequirement, "/"));
                row.put("result", result);
                row.put("inspectionConclusion", formatInspectionConclusion(snapshot.getInsResult()));
                rows.add(row);
@@ -4090,6 +4120,14 @@
        return itemName.replaceAll("平均值\\s*[,,]?\\s*", "");
    }
    private static String formatReportItemWithUnit(String itemName, String unit) {
        if (StringUtils.isBlank(unit) || "/".equals(unit.trim())
                || itemName.contains(unit.trim())) {
            return itemName;
        }
        return itemName + "," + unit.trim();
    }
    private static String getMapText(Map<String, String> map, String... keys) {
        if (map == null) {
            return null;