liusheng
14 小时以前 c24d16a37523a49080392610357872ce580cbc38
ruoyi-task/src/main/java/com/ruoyi/task/service/impl/ServiceSubtaskServiceImpl.java
@@ -383,8 +383,8 @@
                patTaskRelevance.setDiagname(serviceSubtask1.getLeavediagname());
                patTaskRelevance.setPatid(serviceSubtask1.getPatid());
                patTaskRelevance.setSendStatus(serviceSubtask1.getSendstate());
                patTaskRelevance.setDeptcode(serviceSubtask1.getDeptcode());
                patTaskRelevance.setDeptName(serviceSubtask1.getDeptname());
                patTaskRelevance.setDeptcode(serviceSubtask1.getLeaveldeptcode());
                patTaskRelevance.setDeptName(serviceSubtask1.getLeaveldeptname());
                patTaskRelevance.setLeavehospitaldistrictcode(serviceSubtask1.getLeavehospitaldistrictcode());
                patTaskRelevance.setLeavehospitaldistrictname(serviceSubtask1.getLeavehospitaldistrictname());
                patTaskRelevance.setInhosptime(serviceSubtask1.getInhosptime());
@@ -404,8 +404,8 @@
                patTaskRelevance.setTelcode(serviceSubtask1.getPhone());
                patTaskRelevance.setFinishtime(serviceSubtask1.getFinishtime());
                patTaskRelevance.setAddr(serviceSubtask1.getAddr());
                patTaskRelevance.setDeptName(serviceSubtask1.getDeptname());
                patTaskRelevance.setDeptcode(serviceSubtask1.getDeptcode());
                patTaskRelevance.setDeptName(serviceSubtask1.getLeaveldeptname());
                patTaskRelevance.setDeptcode(serviceSubtask1.getLeaveldeptcode());
                patTaskRelevance.setBedNo(serviceSubtask1.getBedNo());
                patTaskRelevance.setDiagname(serviceSubtask1.getLeavediagname());
                patTaskRelevance.setPatid(serviceSubtask1.getPatid());
@@ -469,6 +469,12 @@
        List<ServiceSubtask> selectServiceSubtaskList = getServiceSubtasks(serviceSubtaskEntity);
        List<ServiceSubtaskRes> serviceSubtaskResList = new ArrayList<>();
        // 同一次请求(列表分页 / 全量导出)里,inhospid 与 patid 高度重复
        // (再次随访、同一患者多任务都会指向同一条住院记录/同一份档案)。
        // 原来每行都要 2 次单条查询,导出几千行就是几千×2 次往返;这里按 id 做进程内缓存,
        // 把「每行 2 次」压成「每个不同 id 1 次」,取数语义不变。
        Map<Long, PatMedInhosp> inhospCache = new HashMap<>();
        Map<Long, PatArchive> archiveCache = new HashMap<>();
        for (ServiceSubtask serviceSubtask : selectServiceSubtaskList) {
            ServiceSubtaskRes serviceSubtaskRes = DtoConversionUtils.sourceToTarget(serviceSubtask, ServiceSubtaskRes.class);
            if (ObjectUtils.isNotEmpty(serviceSubtask.getContinueContent())) {
@@ -486,9 +492,9 @@
                    serviceSubtaskRes.setCurrentPreachform(serviceSubtask.getPreachform().split(",")[0]);
                }
            }
            PatMedInhosp inhosp = inhospVisitFacade.selectPatMedInhospByInhospid(serviceSubtask.getInhospid());
            PatMedInhosp inhosp = loadInhospCached(inhospCache, serviceSubtask.getInhospid());
            if (ObjectUtils.isNotEmpty(inhosp)) {
                PatArchive patArchive = taskArchivePort.findByPatid(inhosp.getPatid());
                PatArchive patArchive = loadArchiveCached(archiveCache, inhosp.getPatid());
                if (ObjectUtils.isNotEmpty(patArchive)) {
                    Map<String, String> stringStringMap = calculateAge(patArchive.getBirthdate().toInstant().atZone(ZoneId.systemDefault()).toLocalDate(), LocalDate.now());
                    serviceSubtaskRes.setAge(StringUtils.isNotEmpty(stringStringMap.get("age")) ? Long.valueOf(stringStringMap.get("age")) : null);
@@ -502,6 +508,36 @@
            serviceSubtaskResList.add(serviceSubtaskRes);
        }
        return serviceSubtaskResList;
    }
    /**
     * 按 inhospid 取住院记录(带进程内缓存)。
     * <p>
     * 只是把「同一次请求内同一个 id 只查一次」这件事收口,<b>不改变取数语义</b>:
     * 查不到(null)也会缓存,避免同一批数据里对不存在的 id 反复打库。
     * 缓存生命周期 = 单次 {@code patItem} 调用,不跨请求、不共享。
     */
    private PatMedInhosp loadInhospCached(Map<Long, PatMedInhosp> cache, Long inhospid) {
        if (inhospid == null) {
            return null;
        }
        if (!cache.containsKey(inhospid)) {
            cache.put(inhospid, inhospVisitFacade.selectPatMedInhospByInhospid(inhospid));
        }
        return cache.get(inhospid);
    }
    /**
     * 按 patid 取患者档案(带进程内缓存),语义同 {@link #loadInhospCached}。
     */
    private PatArchive loadArchiveCached(Map<Long, PatArchive> cache, Long patid) {
        if (patid == null) {
            return null;
        }
        if (!cache.containsKey(patid)) {
            cache.put(patid, taskArchivePort.findByPatid(patid));
        }
        return cache.get(patid);
    }
    public List<ServiceSubtask> getServiceSubtasks(ServiceSubtaskEntity serviceSubtaskEntity) {
@@ -524,8 +560,63 @@
                }
            }
        }
        List<ServiceSubtask> selectServiceSubtaskList = this.selectServiceSubtaskList(serviceSubtaskEntity);
        return selectServiceSubtaskList;
        // 分页请求走「两阶段」:先只取当页 id,再按 id 回表。
        // 列表 select 约 100 列,深翻页时被 offset 跳过的每一行原本都要回表读整行;
        // 只取 id 的查询能走覆盖索引并提前截断,代价小得多。
        if (serviceSubtaskEntity.getPageNum() != null && serviceSubtaskEntity.getPageSize() != null) {
            return selectServiceSubtaskPage(serviceSubtaskEntity);
        }
        return this.selectServiceSubtaskList(serviceSubtaskEntity);
    }
    /**
     * 两阶段分页:① 只查 id(覆盖索引 + limit offset)② 按 id 回表取整行。
     * <p>
     * 第二阶段复用 {@code selectServiceSubtaskList} 的 subIdList 条件回表,
     * 必须把 pageNum/pageSize 摘掉 —— 否则会在「当页 id 集合」上再翻一次页。
     * <p>
     * 统计缓存(Controller 的 {@code getPatItemCountWithCache})用 ReflectUtils 比对整个
     * entity 作为缓存命中条件,{@code subIdList} 参与比对,所以用完必须原样还原,
     * 否则缓存永远不命中,每次请求都要重算一次聚合。
     */
    private List<ServiceSubtask> selectServiceSubtaskPage(ServiceSubtaskEntity serviceSubtaskEntity) {
        List<Long> pageIds = serviceSubtaskMapper.selectServiceSubtaskIdsPage(serviceSubtaskEntity);
        if (CollectionUtils.isEmpty(pageIds)) {
            return new ArrayList<>();
        }
        Integer pageNum = serviceSubtaskEntity.getPageNum();
        Integer pageSize = serviceSubtaskEntity.getPageSize();
        List<Long> originSubIdList = serviceSubtaskEntity.getSubIdList();
        serviceSubtaskEntity.setPageNum(null);
        serviceSubtaskEntity.setPageSize(null);
        serviceSubtaskEntity.setSubIdList(pageIds);
        List<ServiceSubtask> rows;
        try {
            rows = serviceSubtaskMapper.selectServiceSubtaskList(serviceSubtaskEntity);
        } finally {
            serviceSubtaskEntity.setPageNum(pageNum);
            serviceSubtaskEntity.setPageSize(pageSize);
            serviceSubtaskEntity.setSubIdList(originSubIdList);
        }
        if (CollectionUtils.isEmpty(rows)) {
            return new ArrayList<>();
        }
        // 以第一阶段的 id 顺序为准重排:第二阶段的 order by 只保证「同一页内」有序,
        // 遇到排序字段值相同的行时相对顺序不一定与第一阶段一致。
        Map<Long, ServiceSubtask> byId = new HashMap<>(Math.max(16, rows.size() * 2));
        for (ServiceSubtask row : rows) {
            if (row.getId() != null) {
                byId.put(row.getId(), row);
            }
        }
        List<ServiceSubtask> ordered = new ArrayList<>(pageIds.size());
        for (Long id : pageIds) {
            ServiceSubtask row = byId.get(id);
            if (row != null) {
                ordered.add(row);
            }
        }
        return ordered;
    }
    @Override
@@ -1164,8 +1255,8 @@
                    serviceSubtask.setInhospid(patTaskRelevance.getHospid());
                    //新建完成就是待发送状态
                    serviceSubtask.setSendstate(2L);
                    serviceSubtask.setDeptcode(StringUtils.isNotEmpty(patTaskRelevance.getDeptcode()) ? patTaskRelevance.getDeptcode() : serviceTaskVO.getDeptcode());
                    serviceSubtask.setDeptname(StringUtils.isNotEmpty(patTaskRelevance.getDeptName()) ? patTaskRelevance.getDeptName() : serviceTaskVO.getDeptname());
                    serviceSubtask.setLeaveldeptcode(StringUtils.isNotEmpty(patTaskRelevance.getDeptcode()) ? patTaskRelevance.getDeptcode() : serviceTaskVO.getDeptcode());
                    serviceSubtask.setLeaveldeptname(StringUtils.isNotEmpty(patTaskRelevance.getDeptName()) ? patTaskRelevance.getDeptName() : serviceTaskVO.getDeptname());
                    serviceSubtask.setLeavehospitaldistrictcode(patTaskRelevance.getLeavehospitaldistrictcode());
                    serviceSubtask.setLeavehospitaldistrictname(patTaskRelevance.getLeavehospitaldistrictname());
                    serviceSubtask.setType(serviceTaskVO.getType());
@@ -1530,10 +1621,10 @@
                    serviceSubtask.setAddr(patTaskRelevance.getAddr());
                    serviceSubtask.setPatid(patTaskRelevance.getPatid());
                    serviceSubtask.setOpenid(patTaskRelevance.getOpenid());
                    serviceSubtask.setDeptcode(patTaskRelevance.getDeptcode());
                    serviceSubtask.setLeaveldeptcode(patTaskRelevance.getDeptcode());
                    serviceSubtask.setLeavehospitaldistrictname(patTaskRelevance.getLeavehospitaldistrictname());
                    serviceSubtask.setLeavehospitaldistrictcode(patTaskRelevance.getLeavehospitaldistrictcode());
                    serviceSubtask.setDeptname(patTaskRelevance.getDeptName());
                    serviceSubtask.setLeaveldeptname(patTaskRelevance.getDeptName());
                    serviceSubtask.setType(serviceTaskVO.getType());
                    serviceSubtask.setCreateTime(DateUtils.getNowDate());
                    serviceSubtask.setDrname(patTaskRelevance.getDrname());
@@ -1787,7 +1878,7 @@
                collect = rawData.stream().collect(Collectors.groupingBy(subtask -> Optional.ofNullable(subtask.getManagementDoctorCode()).orElse("Unknown")));
                break;
            case "deptcode":
                collect = rawData.stream().collect(Collectors.groupingBy(subtask -> Optional.ofNullable(subtask.getDeptcode()).orElse("Unknown")));
                collect = rawData.stream().collect(Collectors.groupingBy(subtask -> Optional.ofNullable(subtask.getLeaveldeptcode()).orElse("Unknown")));
                break;
            case "leavehospitaldistrictcode":
                collect = rawData.stream().collect(Collectors.groupingBy(subtask -> Optional.ofNullable(subtask.getLeavehospitaldistrictcode()).orElse("Unknown")));
@@ -1852,6 +1943,21 @@
     * @return
     */
    public Map<String, Map<String, Object>> getSsRateForSfStatistics(ServiceSubtaskCountReq serviceSubtaskCountReq, Map<String, List<ServiceSubtask>> collect) {
        if (ObjectUtils.isEmpty(serviceSubtaskCountReq) || ObjectUtils.isEmpty(collect)) {
            return null;
        }
        return getSsRateForSfStatistics(serviceSubtaskCountReq, new ArrayList<>(collect.keySet()));
    }
    /**
     * 随访统计-及时率统计(分组键直接传值的重载)
     * <p>
     * 统计下推到 SQL 的 {@code getSfStatisticsScript} 不再把明细行捞回内存,拿不到
     * {@code Map<分组, List<明细>>},因此直接传分组键集合;查询语句与原来完全同一个。
     *
     * @param groupKeyList 分组键集合(drcode / leaveldeptcode / leavehospitaldistrictcode 的实际取值)
     */
    public Map<String, Map<String, Object>> getSsRateForSfStatistics(ServiceSubtaskCountReq serviceSubtaskCountReq, List<String> groupKeyList) {
        if (ObjectUtils.isEmpty(serviceSubtaskCountReq)) {
            return null;
        }
@@ -1859,7 +1965,6 @@
        if (StringUtils.isEmpty(groupKey)) {
            return null;
        }
        List<String> groupKeyList = new ArrayList<>(collect.keySet());
        if (ObjectUtils.isEmpty(groupKeyList)) {
            return null;
        }
@@ -1878,11 +1983,21 @@
        ssRate.setGroupKey(groupKey);
        ssRate.setGroupKeyList(groupKeyList);
        ssRate.setRateDay(serviceSubtaskCountReq.getRateDay());
        Map<String, Map<String, Object>> jsRates = new HashMap<>();
        if (!collect.isEmpty()) {
            jsRates = serviceSubtaskMapper.selectTimelyRateBatch(ssRate);
        }
        return jsRates;
        return serviceSubtaskMapper.selectTimelyRateBatch(ssRate);
    }
    /**
     * 空值兜底:SUM(CASE ...) 理论上不会是 null,这里只做防御,避免拆箱 NPE。
     */
    private long nvlLong(Long value) {
        return value == null ? 0L : value;
    }
    /**
     * 把数据库聚合值(BigDecimal / Long / Integer 等)转成 int。
     */
    private int toIntValue(Object value) {
        return value instanceof Number ? ((Number) value).intValue() : 0;
    }
    public List<ServiceSubtaskStatistic> getSfStatisticsExport(ServiceSubtaskCountReq serviceSubtaskCountReq) {
@@ -1930,7 +2045,7 @@
            if (serviceSubtaskCountReq.getDrcode() != null && !serviceSubtaskCountReq.getDrcode().isEmpty()) {
                collect = serviceSubtaskMapper.getSfStatistics(serviceSubtaskCountReq).stream().collect(Collectors.groupingBy(subtask -> Optional.ofNullable(subtask.getDrname()).orElse("Unknown")));
            } else {
                collect = serviceSubtaskMapper.getSfStatistics(serviceSubtaskCountReq).stream().collect(Collectors.groupingBy(subtask -> Optional.ofNullable(subtask.getDeptname()).orElse("Unknown")));
                collect = serviceSubtaskMapper.getSfStatistics(serviceSubtaskCountReq).stream().collect(Collectors.groupingBy(subtask -> Optional.ofNullable(subtask.getLeaveldeptname()).orElse("Unknown")));
            }
        }
        List<ServiceSubtaskCount> joySubCount = serviceSubtaskMapper.getSfStatisticsJoy(serviceSubtaskCountReq);
@@ -1951,7 +2066,7 @@
                if (CollectionUtils.isNotEmpty(serviceSubtaskCountReq.getLeavehospitaldistrictcodes())) {
                    ss.setLeavehospitaldistrictcode(serviceSubtaskList.get(0).getLeavehospitaldistrictcode());
                } else if (CollectionUtils.isNotEmpty(serviceSubtaskCountReq.getDeptcodes())) {
                    ss.setDeptcode(serviceSubtaskList.get(0).getDeptcode());
                    ss.setLeaveldeptcode(serviceSubtaskList.get(0).getLeaveldeptcode());
                }
            }
@@ -2000,8 +2115,8 @@
        } else {
            //格式化为两位小数的百分比
            DecimalFormat percentFormat = new DecimalFormat("##.##%");
            serviceSubtaskStatistic.setDeptname(serviceSubtaskList.get(0).getDeptname());
            serviceSubtaskStatistic.setDeptcode(serviceSubtaskList.get(0).getDeptcode());
            serviceSubtaskStatistic.setDeptname(serviceSubtaskList.get(0).getLeaveldeptname());
            serviceSubtaskStatistic.setDeptcode(serviceSubtaskList.get(0).getLeaveldeptcode());
            serviceSubtaskStatistic.setLeavehospitaldistrictname(serviceSubtaskList.get(0).getLeavehospitaldistrictname());
            serviceSubtaskStatistic.setLeavehospitaldistrictcode(serviceSubtaskList.get(0).getLeavehospitaldistrictcode());
            serviceSubtaskStatistic.setDrcode(serviceSubtaskList.get(0).getDrcode());
@@ -2292,8 +2407,8 @@
        ServiceSubtaskStatistic serviceSubtaskStatistic = new ServiceSubtaskStatistic();
        //格式化为两位小数的百分比
        DecimalFormat percentFormat = new DecimalFormat("##.##%");
        serviceSubtaskStatistic.setDeptname(serviceSubtaskList.get(0).getDeptname());
        serviceSubtaskStatistic.setDeptcode(serviceSubtaskList.get(0).getDeptcode());
        serviceSubtaskStatistic.setDeptname(serviceSubtaskList.get(0).getLeaveldeptname());
        serviceSubtaskStatistic.setDeptcode(serviceSubtaskList.get(0).getLeaveldeptcode());
        serviceSubtaskStatistic.setLeavehospitaldistrictname(serviceSubtaskList.get(0).getLeavehospitaldistrictname());
        serviceSubtaskStatistic.setLeavehospitaldistrictcode(serviceSubtaskList.get(0).getLeavehospitaldistrictcode());
        serviceSubtaskStatistic.setDrcode(serviceSubtaskList.get(0).getDrcode());
@@ -2462,8 +2577,8 @@
        ServiceSubtaskStatistic serviceSubtaskStatistic = new ServiceSubtaskStatistic();
        //格式化为两位小数的百分比
        DecimalFormat percentFormat = new DecimalFormat("##.##%");
        serviceSubtaskStatistic.setDeptname(serviceSubtaskList.get(0).getDeptname());
        serviceSubtaskStatistic.setDeptcode(serviceSubtaskList.get(0).getDeptcode());
        serviceSubtaskStatistic.setDeptname(serviceSubtaskList.get(0).getLeaveldeptname());
        serviceSubtaskStatistic.setDeptcode(serviceSubtaskList.get(0).getLeaveldeptcode());
        serviceSubtaskStatistic.setLeavehospitaldistrictname(serviceSubtaskList.get(0).getLeavehospitaldistrictname());
        serviceSubtaskStatistic.setLeavehospitaldistrictcode(serviceSubtaskList.get(0).getLeavehospitaldistrictcode());
        serviceSubtaskStatistic.setDrcode(serviceSubtaskList.get(0).getDrcode());
@@ -2624,8 +2739,8 @@
        ServiceSubtaskStatistic serviceSubtaskStatistic = new ServiceSubtaskStatistic();
        //格式化为两位小数的百分比
        DecimalFormat percentFormat = new DecimalFormat("##.##%");
        serviceSubtaskStatistic.setDeptname(serviceSubtaskList.get(0).getDeptname());
        serviceSubtaskStatistic.setDeptcode(serviceSubtaskList.get(0).getDeptcode());
        serviceSubtaskStatistic.setDeptname(serviceSubtaskList.get(0).getLeaveldeptname());
        serviceSubtaskStatistic.setDeptcode(serviceSubtaskList.get(0).getLeaveldeptcode());
        serviceSubtaskStatistic.setLeavehospitaldistrictname(serviceSubtaskList.get(0).getLeavehospitaldistrictname());
        serviceSubtaskStatistic.setLeavehospitaldistrictcode(serviceSubtaskList.get(0).getLeavehospitaldistrictcode());
        serviceSubtaskStatistic.setDrcode(serviceSubtaskList.get(0).getDrcode());
@@ -2729,8 +2844,8 @@
        ServiceSubtaskStatistic serviceSubtaskStatistic = new ServiceSubtaskStatistic();
        //格式化为两位小数的百分比
        DecimalFormat percentFormat = new DecimalFormat("##.##%");
        serviceSubtaskStatistic.setDeptname(serviceSubtaskList.get(0).getDeptname());
        serviceSubtaskStatistic.setDeptcode(serviceSubtaskList.get(0).getDeptcode());
        serviceSubtaskStatistic.setDeptname(serviceSubtaskList.get(0).getLeaveldeptname());
        serviceSubtaskStatistic.setDeptcode(serviceSubtaskList.get(0).getLeaveldeptcode());
        serviceSubtaskStatistic.setLeavehospitaldistrictname(serviceSubtaskList.get(0).getLeavehospitaldistrictname());
        serviceSubtaskStatistic.setLeavehospitaldistrictcode(serviceSubtaskList.get(0).getLeavehospitaldistrictcode());
        serviceSubtaskStatistic.setDrcode(serviceSubtaskList.get(0).getDrcode());
@@ -3074,67 +3189,111 @@
        log.info("getSfStatisticsScript的入参为:{}", serviceSubtaskCountReq);
        List<ServiceSubtaskStatistic> serviceSubtaskStatistics = new ArrayList<>();
        // 根据条件进行分组
        Map<String, List<ServiceSubtask>> collect = new HashMap<>();
        // 根据条件决定分组维度:离院维度按 leaveldeptcode / leavehospitaldistrictcode(含历史 deptcodes 入参),
        // 在院维度按 deptcode / hospitaldistrictcode
        String groupKey = "drcode";
        boolean hasDrcode = serviceSubtaskCountReq.getDrcode() != null && !serviceSubtaskCountReq.getDrcode().isEmpty();
        if (serviceSubtaskCountReq.getLeavehospitaldistrictcodes() != null && !serviceSubtaskCountReq.getLeavehospitaldistrictcodes().isEmpty()) {
            if (serviceSubtaskCountReq.getDrcode() != null && !serviceSubtaskCountReq.getDrcode().isEmpty()) {
                groupKey = "drcode";
            } else {
                groupKey = "leavehospitaldistrictcode";
            }
            groupKey = hasDrcode ? "drcode" : "leavehospitaldistrictcode";
        } else if (serviceSubtaskCountReq.getLeaveldeptcodes() != null && !serviceSubtaskCountReq.getLeaveldeptcodes().isEmpty()) {
            groupKey = hasDrcode ? "drcode" : "leaveldeptcode";
        } else if (serviceSubtaskCountReq.getHospitaldistrictcodes() != null && !serviceSubtaskCountReq.getHospitaldistrictcodes().isEmpty()) {
            // 在院满意度:按在院病区分组(hospitaldistrictcode)
            groupKey = hasDrcode ? "drcode" : "hospitaldistrictcode";
        } else if (serviceSubtaskCountReq.getHospitaldeptcodes() != null && !serviceSubtaskCountReq.getHospitaldeptcodes().isEmpty()) {
            // 在院满意度:按在院科室分组(deptcode)
            groupKey = hasDrcode ? "drcode" : "deptcode";
        } else if (serviceSubtaskCountReq.getDeptcodes() != null && !serviceSubtaskCountReq.getDeptcodes().isEmpty()) {
            if (serviceSubtaskCountReq.getDrcode() != null && !serviceSubtaskCountReq.getDrcode().isEmpty()) {
                groupKey = "drcode";
            } else {
                groupKey = "deptcode";
            }
            // 历史入参 deptcodes 表示离院科室
            groupKey = hasDrcode ? "drcode" : "leaveldeptcode";
        }
        serviceSubtaskCountReq.setGroupKey(groupKey);
        // 分组列与过滤列保持一致:离院科室 leaveldeptcode、在院科室 deptcode
        serviceSubtaskCountReq.setGroupColumn(groupKey);
        //分组键集合:这一步保持不变(分页参数照旧作用在该查询上),保证“参与统计的分组”与改造前一致
        List<String> groupKeyList = serviceSubtaskMapper.getSfStatisticsGroupKey(serviceSubtaskCountReq);
        /*
         * 分组列可能为 NULL:满意度子任务(service_type 6/14/15/16)的 leavehospitaldistrictcode
         * 大量为 NULL,getSfStatisticsGroupKey 会把 NULL 原样带回来,
         * 拼进 SQL 就变成 `AND xxx IN (null)`(恒不成立),整张统计表直接空。
         * 这里统一成 'Unknown',与聚合 SQL 的 GROUP BY IFNULL(列,'Unknown') 对齐。
         */
        if (groupKeyList != null) {
            groupKeyList = groupKeyList.stream()
                    .map(key -> key == null ? "Unknown" : key)
                    .distinct()
                    .collect(Collectors.toList());
        }
        serviceSubtaskCountReq.setGroupKeyList(groupKeyList);
        List<ServiceSubtask> rawData = serviceSubtaskMapper.getSfStatistics(serviceSubtaskCountReq);
        switch (groupKey) {
            case "deptcode":
                collect = rawData.stream().collect(Collectors.groupingBy(subtask -> Optional.ofNullable(subtask.getDeptcode()).orElse("Unknown")));
                break;
            case "drcode":
                collect = rawData.stream().collect(Collectors.groupingBy(subtask -> Optional.ofNullable(subtask.getDrcode()).orElse("Unknown")));
                break;
            case "leavehospitaldistrictcode":
                collect = rawData.stream().collect(Collectors.groupingBy(subtask -> Optional.ofNullable(subtask.getLeavehospitaldistrictcode()).orElse("Unknown")));
                break;
            default:
                collect = rawData.stream().collect(Collectors.groupingBy(subtask -> Optional.ofNullable(subtask.getDrcode()).orElse("Unknown")));
                break;
        /*
         * 统计下推到数据库:一次 GROUP BY 只返回“分组数”行。
         * 改造前是 serviceSubtaskMapper.getSfStatistics() 把命中的全部明细行(39 个字段,
         * 含 continue_content 等大字段)捞回 JVM 后逐行累加,几千行时传输和遍历都很慢。
         * 这里的聚合口径与原 getStatisticCategory 逐条对齐(见 Mapper 中 getSfStatisticsScriptSum 的注释)。
         */
        List<ServiceSubtaskStatistic> sumStatistics = serviceSubtaskMapper.getSfStatisticsScriptSum(serviceSubtaskCountReq);
        if (CollectionUtils.isEmpty(sumStatistics)) {
            return serviceSubtaskStatistics;
        }
        //获取及时率统计合集
        Map<String, Map<String, Object>> jsRates = getSsRateForSfStatistics(serviceSubtaskCountReq, collect);
        //一次性查询所有分组的题目统计,避免每个分组一次SQL(N+1)
        List<Long> allSubTaskIds = rawData.stream().map(ServiceSubtask::getId).filter(Objects::nonNull).distinct().collect(Collectors.toList());
        List<ServiceSubtaskCount> subCounts = new ArrayList<>();
        if (CollectionUtils.isNotEmpty(allSubTaskIds)) {
            serviceSubtaskCountReq.setSubTaskIds(allSubTaskIds);
            subCounts = serviceSubtaskMapper.getSfStatisticsCategory(serviceSubtaskCountReq);
        }
        for (String collectKey : collect.keySet()) {
            List<ServiceSubtask> serviceSubtaskList = collect.get(collectKey);
            if (CollectionUtils.isEmpty(serviceSubtaskList)) {
                continue;
            }
            ServiceSubtaskStatistic statistic = getStatisticCategory(serviceSubtaskList, serviceSubtaskCountReq, subCounts);
            if (ObjectUtils.isNotEmpty(jsRates)) {
                Map<String, Object> jsRateMap = jsRates.get(collectKey);
                BigDecimal jsRate = new BigDecimal("0");
                if (ObjectUtils.isNotEmpty(jsRateMap) && ObjectUtils.isNotEmpty(jsRateMap.get("rate"))) {
                    jsRate = new BigDecimal(jsRateMap.get("rate").toString());
        /*
         * 满意度题目量:也改成按分组在库里聚合。
         * 改造前是先把本次命中的全部子任务 id 灌进 getSfStatisticsCategory 的 IN 列表查一遍,
         * 再在内存里对每个子任务做一次 subCounts.stream().filter(...)(N*M 次比较)。
         */
        Map<String, Integer> joyCountMap = new HashMap<>();
        Map<String, Integer> joyAllCountMap = new HashMap<>();
        if (StringUtils.isNotEmpty(serviceSubtaskCountReq.getConfigValue())) {
            List<Map<String, Object>> joySums = serviceSubtaskMapper.getSfStatisticsScriptJoySum(serviceSubtaskCountReq);
            if (CollectionUtils.isNotEmpty(joySums)) {
                for (Map<String, Object> joySum : joySums) {
                    String joyGroupCode = joySum.get("groupCode") == null ? "Unknown" : joySum.get("groupCode").toString();
                    joyCountMap.put(joyGroupCode, toIntValue(joySum.get("joyCount")));
                    joyAllCountMap.put(joyGroupCode, toIntValue(joySum.get("joyAllCount")));
                }
                Double jsRateDouble = (jsRate.setScale(4, RoundingMode.HALF_UP)).doubleValue();
                statistic.setRate(jsRateDouble);
            }
            if (ObjectUtils.isNotEmpty(statistic)) {
                serviceSubtaskStatistics.add(statistic);
        }
        //随访率:口径与原 getStatisticCategory 一致(首次率的判断沿用原来的 needFollowUp、再次率沿用原来的分母)
        DecimalFormat percentFormat = new DecimalFormat("##.##%");
        List<String> statisticsGroupKeys = new ArrayList<>();
        for (ServiceSubtaskStatistic statistic : sumStatistics) {
            String statisticGroupCode = statistic.getGroupCode() == null ? "Unknown" : statistic.getGroupCode();
            statistic.setJoyCount(joyCountMap.getOrDefault(statisticGroupCode, 0));
            statistic.setJoyAllCount(joyAllCountMap.getOrDefault(statisticGroupCode, 0));
            //首次随访率:只有该分组存在首次随访记录时才会被赋值(不存在则是 null,与改造前一致)
            if (statistic.getFirstVisitCount() != null && statistic.getFirstVisitCount() > 0) {
                if (statistic.getNeedFollowUp() != null && statistic.getNeedFollowUp() > 0) {
                    double rate = (double) (nvlLong(statistic.getFollowUpSuccess()) + nvlLong(statistic.getFollowUpFail())) / statistic.getNeedFollowUp();
                    statistic.setFollowUpRate(percentFormat.format(rate));
                } else {
                    statistic.setFollowUpRate("0.00%");
                }
            }
            //再次随访率:判断条件与原实现一致(原来判断的是 needFollowUp,这里照搬以免口径变化)
            if (statistic.getAgainVisitCount() != null && statistic.getAgainVisitCount() > 0) {
                if (statistic.getNeedFollowUp() != null && statistic.getNeedFollowUp() > 0) {
                    double rateAgain = (double) (nvlLong(statistic.getFollowUpSuccessAgain()) + nvlLong(statistic.getFollowUpFailAgain())) / nvlLong(statistic.getNeedFollowUpAgain());
                    statistic.setFollowUpRateAgain(percentFormat.format(rateAgain));
                } else {
                    statistic.setFollowUpRateAgain("0.00%");
                }
            }
            statisticsGroupKeys.add(statisticGroupCode);
            serviceSubtaskStatistics.add(statistic);
        }
        //获取及时率统计合集(一次批量查询,不按分组循环查)
        Map<String, Map<String, Object>> jsRates = getSsRateForSfStatistics(serviceSubtaskCountReq, statisticsGroupKeys);
        for (ServiceSubtaskStatistic statistic : serviceSubtaskStatistics) {
            String statisticGroupCode = statistic.getGroupCode() == null ? "Unknown" : statistic.getGroupCode();
            Map<String, Object> jsRateMap = jsRates == null ? null : jsRates.get(statisticGroupCode);
            BigDecimal jsRate = new BigDecimal("0");
            if (ObjectUtils.isNotEmpty(jsRateMap) && ObjectUtils.isNotEmpty(jsRateMap.get("rate"))) {
                jsRate = new BigDecimal(jsRateMap.get("rate").toString());
            }
            statistic.setRate((jsRate.setScale(4, RoundingMode.HALF_UP)).doubleValue());
        }
        //统计随访题目分类
@@ -3401,7 +3560,7 @@
            diagname.setPhone(subtask.getPhone());
            diagname.setNurseName(subtask.getNurseName());
            diagname.setDrname(subtask.getDrname());
            diagname.setDeptname(subtask.getDeptname());
            diagname.setDeptname(subtask.getLeaveldeptname());
            diagname.setLeavehospitaldistrictname(subtask.getLeavehospitaldistrictname());
            diagname.setTemplatename(subtask.getTemplatename());
            diagname.setPreachform(subtask.getPreachform());
@@ -3508,7 +3667,6 @@
        //3.如果查出来的集合不为空,则进行循环
        for (ServiceSubtask serviceSubtask : serviceSubtasks) {
            Integer preachformSize = 0;
            Boolean flag = false;
            ServiceSubtaskPreachform serviceSubtaskPreachform = new ServiceSubtaskPreachform();
            serviceSubtaskPreachform.setSubid(serviceSubtask.getId());
            List<ServiceSubtaskPreachform> serviceSubtaskPreachforms = taskPreachformPort.selectServiceSubtaskPreachformList(serviceSubtaskPreachform);
@@ -3516,31 +3674,100 @@
                preachformSize = serviceSubtaskPreachforms.size();
            if (serviceSubtask.getUpdateTime() != null && DateUtils.differentDaysByMillisecond(new Date(), serviceSubtask.getUpdateTime()) >= failDay) {
                //3.1 判断当前serviceSubtask的updateTime是不是为空,如果不为空,则判断当前时间是不是与updateTime差距大于等于failDay
                flag = saveServiceSubtaskDetail(serviceSubtask);
                log.debug("【失败任务默认值】子任务 {} 达到失败天数(updateTime)", serviceSubtask.getId());
            } else if (serviceSubtask.getVisitTime() != null && DateUtils.differentDaysByMillisecond(new Date(), serviceSubtask.getVisitTime()) >= failDay + preachformSize) {
                //3.2 如果当前serviceSubtask的updateTime为空,根据当前serviceSubtask的subid去查service_subtask_preachform得到size(如果size为空,则默认0),则根据当前时间是不是与当前serviceSubtask的visitTime差距大于等于failDay + size
                flag = saveServiceSubtaskDetail(serviceSubtask);
                log.debug("【失败任务默认值】子任务 {} 达到失败天数(visitTime+preachformSize)", serviceSubtask.getId());
            } else {
                //3.3 如果2.1且2.2都为false,则continue
                continue;
            }
            //4.如果flag为true,则更新service_subtask表中的sendstate为6,preachform为1
            if (flag) {
                serviceSubtask.setSendstate(6L);
                serviceSubtask.setPreachform("1");
                serviceSubtaskMapper.updateServiceSubtask(serviceSubtask);
            //4. ⚠️ 2026-10-21 加固:先"算完"再"落库",彻底去掉"先插入、失败再删除"的写法。
            //   旧逻辑是边插边判断,只要有一题没配默认值,就把这个 sub_id 下**所有**明细(含患者真实作答、
            //   甚至刚插进去的那批)统统置 del_flag='1' —— nhfy 2026-09-08 那 10 位患者的满意度作答
            //   就是这样被删掉、导致导出「问题详情占比」370 而「随访信息表单」380 的。
            //   现在:① 清单为空(=有题没配默认值)→ 整份跳过,不写也不删;
            //         ② 清单齐全 → 统一落库(存在则更新并复活,不存在则插入)。
            List<ServiceSubtaskDetail> defaultDetails = buildDefaultDetails(serviceSubtask);
            if (CollectionUtils.isEmpty(defaultDetails)) {
                log.info("【失败任务默认值】任务 {} 子任务 {} 有题目未配置默认值,整份跳过(不插入、不删除任何明细)",
                        serviceSubtask.getTaskid(), serviceSubtask.getId());
                continue;
            }
            persistDefaultDetails(defaultDetails);
            //5.全部落库成功后再把任务置为已完成
            serviceSubtask.setSendstate(6L);
            serviceSubtask.setPreachform("1");
            serviceSubtaskMapper.updateServiceSubtask(serviceSubtask);
        }
    }
    /**
     * 按模板构造"默认值明细"清单(<b>只计算、不落库</b>)
     * <p>
     * 2026-10-21 新增:把原来的 {@code saveServiceSubtaskDetail}(边算边插、失败再删)拆成
     * 「build(本方法)」+「persist({@link #persistDefaultDetails})」两步,
     * 保证"默认值不全"时**一个字节都不会写、更不会删**。
     *
     * @param serviceSubtask 子任务
     * @return 明细清单;任一题没配默认值(或模板为空)时返回 {@code null},表示整份不处理
     */
    private List<ServiceSubtaskDetail> buildDefaultDetails(ServiceSubtask serviceSubtask) {
        if (serviceSubtask.getType() == null) {
            return null;
        }
        List<ServiceSubtaskDetail> details = new ArrayList<>();
        if (serviceSubtask.getType().equals("1")) {
            //3.3.1 如果type是 1,则用当前serviceSubtask的templateid去ivr_task_temaplate_script中获取问题和选项
            IvrTaskTemplateScript ivrTaskTemplateScript = new IvrTaskTemplateScript();
            ivrTaskTemplateScript.setTemplateID(serviceSubtask.getTemplateid());
            List<IvrTaskTemplateScript> ivrTaskTemplateScripts = iIvrTaskTemplateScriptService.selectIvrTaskTemplateScriptList(ivrTaskTemplateScript);
            if (CollectionUtils.isEmpty(ivrTaskTemplateScripts)) return null;
            for (IvrTaskTemplateScript its : ivrTaskTemplateScripts) {
                IvrTaskTemplateScriptVO ivrTaskTemplateScriptVO = DtoConversionUtils.sourceToTarget(its, IvrTaskTemplateScriptVO.class);
                IvrTaskTemplateTargetoption ivrTaskTemplateTargetoption = new IvrTaskTemplateTargetoption();
                ivrTaskTemplateTargetoption.setScriptid(ivrTaskTemplateScriptVO.getId());
                List<IvrTaskTemplateTargetoption> ivrTaskTemplateTargetoptions = iIvrTaskTemplateTargetoptionService.selectIvrTaskTemplateTargetoptionList(ivrTaskTemplateTargetoption);
                ivrTaskTemplateScriptVO.setIvrTaskScriptTargetoptionList(ivrTaskTemplateTargetoptions);
                ServiceSubtaskDetail detail = buildSFDetail(ivrTaskTemplateScriptVO, serviceSubtask);
                if (detail == null) return null;
                details.add(detail);
            }
        } else if (serviceSubtask.getType().equals("2")) {
            //3.3.2 如果type是 2,则用当前serviceSubtask的templateid去svy_task_temaplate_script中获取问题和选项
            SvyTaskTemplateScript svyTaskTemplateScript = new SvyTaskTemplateScript();
            svyTaskTemplateScript.setTemplateID(serviceSubtask.getTemplateid());
            List<SvyTaskTemplateScript> svyTaskTemplateScripts = svyTaskTemplateScriptService.selectSvyTaskTemplateScriptList(svyTaskTemplateScript);
            if (CollectionUtils.isEmpty(svyTaskTemplateScripts)) return null;
            for (SvyTaskTemplateScript taskTemplateScript : svyTaskTemplateScripts) {
                SvyTaskTemplateScriptVO svyTaskTemplateScriptVO = DtoConversionUtils.sourceToTarget(taskTemplateScript, SvyTaskTemplateScriptVO.class);
                SvyTaskTemplateScriptVO svyTaskTemplateScriptVO1 = svyTaskTemplateScriptService.selectInfoByCondition(svyTaskTemplateScriptVO);
                ServiceSubtaskDetail detail = buildWJDetail(svyTaskTemplateScriptVO1, serviceSubtask);
                if (detail == null) return null;
                details.add(detail);
            }
        } else {
            return null;
        }
        return details;
    }
    /**
     * 落库"默认值明细":存在则更新并复活,不存在则插入(<b>只增不删</b>)
     */
    private void persistDefaultDetails(List<ServiceSubtaskDetail> details) {
        for (ServiceSubtaskDetail detail : details) {
            ServiceSubtaskDetailDTO exist = serviceSubtaskDetailMapper.isExistServiceSubtaskDetail(detail.getSubId(), detail.getScriptid());
            if (ObjectUtils.isNotEmpty(exist)) {
                detail.setId(exist.getId());
                detail.setUpdateTime(new Date());
                serviceSubtaskDetailMapper.updateServiceSubtaskDetail(detail);
                //命中历史被删副本时置回 del_flag='0',避免再插一份新副本
                serviceSubtaskDetailMapper.reviveServiceSubtaskDetail(exist.getId());
            } else {
                //删除误新增detail里的数据
                ServiceSubtaskDetail serviceSubtaskDetail = new ServiceSubtaskDetail();
                serviceSubtaskDetail.setSubId(serviceSubtask.getId());
                List<ServiceSubtaskDetail> serviceSubtaskDetails = serviceSubtaskDetailMapper.selectServiceSubtaskDetailList(serviceSubtaskDetail);
                if (CollectionUtils.isNotEmpty(serviceSubtaskDetails)) {
                    for (ServiceSubtaskDetail serviceSubtaskDetail1 : serviceSubtaskDetails) {
                        serviceSubtaskDetail1.setDelFlag("1");
                        serviceSubtaskDetailMapper.updateServiceSubtaskDetail(serviceSubtaskDetail1);
                    }
                }
                serviceSubtaskDetailMapper.insertServiceSubtaskDetail(detail);
            }
        }
    }
@@ -3740,44 +3967,30 @@
        return resultMap;
    }
    private Boolean saveServiceSubtaskDetail(ServiceSubtask serviceSubtask) {
        Boolean result = true;
        //3.4 如果3.1或3.2为true的话,根据type判断什么类型
        if (serviceSubtask.getType().equals("1")) {
            //3.3.1    如果type是 1 ,则用当前serviceSubtask的templateid去ivr_task_temaplate_script中获取问题和选项,将问题和选项的默认值放到service_detail中
            IvrTaskTemplateScript ivrTaskTemplateScript = new IvrTaskTemplateScript();
            ivrTaskTemplateScript.setTemplateID(serviceSubtask.getTemplateid());
            List<IvrTaskTemplateScript> ivrTaskTemplateScripts = iIvrTaskTemplateScriptService.selectIvrTaskTemplateScriptList(ivrTaskTemplateScript);
            for (IvrTaskTemplateScript its : ivrTaskTemplateScripts) {
                IvrTaskTemplateScriptVO ivrTaskTemplateScriptVO = DtoConversionUtils.sourceToTarget(its, IvrTaskTemplateScriptVO.class);
                IvrTaskTemplateTargetoption ivrTaskTemplateTargetoption = new IvrTaskTemplateTargetoption();
                ivrTaskTemplateTargetoption.setScriptid(ivrTaskTemplateScriptVO.getId());
                List<IvrTaskTemplateTargetoption> ivrTaskTemplateTargetoptions = iIvrTaskTemplateTargetoptionService.selectIvrTaskTemplateTargetoptionList(ivrTaskTemplateTargetoption);
                ivrTaskTemplateScriptVO.setIvrTaskScriptTargetoptionList(ivrTaskTemplateTargetoptions);
                Integer integer = setSFDetailInfo(ivrTaskTemplateScriptVO, serviceSubtask);
                if (Objects.isNull(integer) || integer == 0) result = false;
            }
    /*
     * 2026-10-21 已删除 saveServiceSubtaskDetail(...)。
     * 它原来的语义是"边算默认值边落库,最后返回布尔值",调用方在返回 false 时把该 sub_id 下的
     * 全部明细置 del_flag='1'(连患者真实作答一起删)。
     * 现在拆成 buildDefaultDetails(...)(只算不写) + persistDefaultDetails(...)(只增不删),
     * 任何一题缺默认值就整份跳过,不会再产生"删除"动作。
     */
        } else if (serviceSubtask.getType().equals("2")) {
            //3.3.2    如果type是 2,则用当前serviceSubtask的templateid去svy_task_temaplate_script中获取问题和选项,将问题和选项的默认值放到service_detail中
            SvyTaskTemplateScript svyTaskTemplateScript = new SvyTaskTemplateScript();
            svyTaskTemplateScript.setTemplateID(serviceSubtask.getTemplateid());
            List<SvyTaskTemplateScript> svyTaskTemplateScripts = svyTaskTemplateScriptService.selectSvyTaskTemplateScriptList(svyTaskTemplateScript);
            for (SvyTaskTemplateScript taskTemplateScript : svyTaskTemplateScripts) {
                SvyTaskTemplateScriptVO svyTaskTemplateScriptVO = DtoConversionUtils.sourceToTarget(taskTemplateScript, SvyTaskTemplateScriptVO.class);
                SvyTaskTemplateScriptVO svyTaskTemplateScriptVO1 = svyTaskTemplateScriptService.selectInfoByCondition(svyTaskTemplateScriptVO);
                Integer integer = setWJDetailInfo(svyTaskTemplateScriptVO1, serviceSubtask);
                if (Objects.isNull(integer) || integer == 0) result = false;
            }
    /**
     * 按「问卷模板」构造一条"默认值明细"(<b>只计算、不落库</b>)
     * <p>
     * 2026-10-21 由 {@code setWJDetailInfo}(算完就直接 insert/update,失败返回 0)改造而来:
     * 现在只负责把实体拼出来,由 {@link #persistDefaultDetails} 统一落库。
     *
     * @return 明细;该题没配默认值(单/多选题一个默认选项都没有)时返回 {@code null},表示整份问卷不处理
     */
    private ServiceSubtaskDetail buildWJDetail(SvyTaskTemplateScriptVO svyTaskTemplateScriptVO, ServiceSubtask serviceSubtask) {
        if (svyTaskTemplateScriptVO == null) {
            return null;
        }
        return result;
    }
    private Integer setWJDetailInfo(SvyTaskTemplateScriptVO svyTaskTemplateScriptVO, ServiceSubtask serviceSubtask) {
        log.info("-----setWJDetailInfo的入参为:{}", svyTaskTemplateScriptVO);
        log.info("-----buildWJDetail的入参为:{}", svyTaskTemplateScriptVO);
        ServiceSubtaskDetailVO serviceSubtaskDetailVO = new ServiceSubtaskDetailVO();
        if (svyTaskTemplateScriptVO.getScriptType().equals("1") || svyTaskTemplateScriptVO.getScriptType().equals("2")) {
        if ("1".equals(svyTaskTemplateScriptVO.getScriptType()) || "2".equals(svyTaskTemplateScriptVO.getScriptType())) {
            //1、2为单选或多选
            for (SvyTaskTemplateTargetoption svyTaskTemplateTargetoption : svyTaskTemplateScriptVO.getSvyTaskTemplateTargetoptions()) {
                //将问题选项记录下来
@@ -3792,9 +4005,9 @@
                }
            }
            //如果asrtext和matchedtext都为空,则没有默认值,则不进行处理
            //如果asrtext和matchedtext都为空,则没有默认值,则不进行处理(返回 null = 整份问卷跳过)
            if (StringUtils.isEmpty(serviceSubtaskDetailVO.getAsrtext()) && StringUtils.isEmpty(serviceSubtaskDetailVO.getMatchedtext())) {
                return 0;
                return null;
            }
        }
        ServiceTask serviceTask = serviceTaskService.selectServiceTaskByTaskid(serviceSubtask.getTaskid());
@@ -3817,21 +4030,16 @@
        }
        serviceSubtaskDetailVO.setSubId(serviceSubtask.getId());
        serviceSubtaskDetailVO.setTemplatequestionnum(svyTaskTemplateScriptVO.getId());
        ServiceSubtaskDetail serviceSubtaskDetail = DtoConversionUtils.sourceToTarget(serviceSubtaskDetailVO, ServiceSubtaskDetail.class);
        //先用subid和scriptid查询一下是否存在,如果存在则更新,不存在则插入
        ServiceSubtaskDetailDTO existServiceSubtaskDetail = serviceSubtaskDetailMapper.isExistServiceSubtaskDetail(serviceSubtaskDetailVO.getSubId(), serviceSubtaskDetailVO.getScriptid());
        if (ObjectUtils.isNotEmpty(existServiceSubtaskDetail)) {
            serviceSubtaskDetail.setId(existServiceSubtaskDetail.getId());
            serviceSubtaskDetail.setUpdateTime(new Date());
            return serviceSubtaskDetailMapper.updateServiceSubtaskDetail(serviceSubtaskDetail);
        } else {
            return serviceSubtaskDetailMapper.insertServiceSubtaskDetail(serviceSubtaskDetail);
        }
        return DtoConversionUtils.sourceToTarget(serviceSubtaskDetailVO, ServiceSubtaskDetail.class);
    }
    private Integer setSFDetailInfo(IvrTaskTemplateScriptVO ivrTaskTemplateScriptVO, ServiceSubtask serviceSubtask) {
    /**
     * 按「电话模板」构造一条"默认值明细"(<b>只计算、不落库</b>),语义同 {@link #buildWJDetail}
     */
    private ServiceSubtaskDetail buildSFDetail(IvrTaskTemplateScriptVO ivrTaskTemplateScriptVO, ServiceSubtask serviceSubtask) {
        if (ivrTaskTemplateScriptVO == null) {
            return null;
        }
        ServiceSubtaskDetailVO serviceSubtaskDetailVO = new ServiceSubtaskDetailVO();
        if (ivrTaskTemplateScriptVO.getScriptType() != null && (ivrTaskTemplateScriptVO.getScriptType().equals("1") || ivrTaskTemplateScriptVO.getScriptType().equals("2"))) {
            //1、2为单选或多选
@@ -3848,10 +4056,12 @@
                    serviceSubtaskDetailVO.setMatchedtext(StringUtils.isEmpty(serviceSubtaskDetailVO.getMatchedtext()) ? ivrTaskTemplateTargetoption.getTargetvalue() : serviceSubtaskDetailVO.getMatchedtext() + "&" + ivrTaskTemplateTargetoption.getTargetvalue());
                }
            }
            //如果asrtext和matchedtext都为空,则没有默认值,不进行处理
            //如果asrtext和matchedtext都为空,则没有默认值,不进行处理(返回 null = 整份问卷跳过)
            if (StringUtils.isEmpty(serviceSubtaskDetailVO.getAsrtext()) && StringUtils.isEmpty(serviceSubtaskDetailVO.getMatchedtext())) {
                return 0;
                return null;
            }
        } else if (ivrTaskTemplateScriptVO.getScriptType() == null) {
            return null;
        }
        serviceSubtaskDetailVO.setTargetid(ivrTaskTemplateScriptVO.getTargetid() != null ? ivrTaskTemplateScriptVO.getTargetid().intValue() : null);
@@ -3871,17 +4081,9 @@
        serviceSubtaskDetailVO.setTemplateType(1);
        serviceSubtaskDetailVO.setTemplateid(ivrTaskTemplateScriptVO.getTemplateID() == null ? null : ivrTaskTemplateScriptVO.getTemplateID().toString());
        serviceSubtaskDetailVO.setLibTemplateid(serviceSubtask.getLibtemplateid() != null ? serviceSubtask.getLibtemplateid().intValue() : null);
        ServiceSubtaskDetail serviceSubtaskDetail = DtoConversionUtils.sourceToTarget(serviceSubtaskDetailVO, ServiceSubtaskDetail.class);
        //先用subid和scriptid查询一下是否存在,如果存在则更新,不存在则插入
        ServiceSubtaskDetailDTO existServiceSubtaskDetail = serviceSubtaskDetailMapper.isExistServiceSubtaskDetail(serviceSubtaskDetailVO.getSubId(), serviceSubtaskDetailVO.getScriptid());
        if (ObjectUtils.isNotEmpty(existServiceSubtaskDetail)) {
            serviceSubtaskDetail.setId(existServiceSubtaskDetail.getId());
            serviceSubtaskDetail.setUpdateTime(new Date());
            return serviceSubtaskDetailMapper.updateServiceSubtaskDetail(serviceSubtaskDetail);
        } else {
            return serviceSubtaskDetailMapper.insertServiceSubtaskDetail(serviceSubtaskDetail);
        }
        return DtoConversionUtils.sourceToTarget(serviceSubtaskDetailVO, ServiceSubtaskDetail.class);
    }
    public String scanGenerateSubtask(ServiceTask serviceTaskVo) {
        String content = "";
@@ -3953,8 +4155,8 @@
//        serviceSubtask.setDrcode(patMedInhosp1.getDrcode());
//        serviceSubtask.setDrname(patMedInhosp1.getDrname());
        if (StringUtils.isNotEmpty(serviceTaskdept.getDeptType()) && serviceTaskdept.getDeptType().equals("1")) {
            serviceSubtask.setDeptcode(serviceTaskdept.getDeptCode());
            serviceSubtask.setDeptname(serviceTaskdept.getDeptName());
            serviceSubtask.setLeaveldeptcode(serviceTaskdept.getDeptCode());
            serviceSubtask.setLeaveldeptname(serviceTaskdept.getDeptName());
        } else {
            serviceSubtask.setLeavehospitaldistrictcode(serviceTaskdept.getDeptCode());
            serviceSubtask.setLeavehospitaldistrictname(serviceTaskdept.getDeptName());
@@ -3990,7 +4192,7 @@
        // 这里用任务配置的病区(其次科室)反查 sys_dept.campusid 落库,保证问卷统计能按院区取到。
        String campusIdByDept = queryCampusIdByDeptCode(serviceSubtask.getLeavehospitaldistrictcode(), serviceTask.getOrgid());
        if (StringUtils.isEmpty(campusIdByDept)) {
            campusIdByDept = queryCampusIdByDeptCode(serviceSubtask.getDeptcode(), serviceTask.getOrgid());
            campusIdByDept = queryCampusIdByDeptCode(serviceSubtask.getLeaveldeptcode(), serviceTask.getOrgid());
        }
        if (StringUtils.isNotEmpty(campusIdByDept)) {
            serviceSubtask.setCampusid(campusIdByDept);
@@ -3999,7 +4201,7 @@
        if (StringUtils.isEmpty(serviceSubtask.getLeavehospitaldistrictcode())
                && ("6".equals(serviceSubtask.getServiceType()) || "14".equals(serviceSubtask.getServiceType()))) {
            log.warn("【扫码生成任务】满意度子任务没有病区,按病区筛选的报表将统计不到该答卷:taskid={}, deptcode={}, campusid={}",
                    serviceSubtask.getTaskid(), serviceSubtask.getDeptcode(), serviceSubtask.getCampusid());
                    serviceSubtask.getTaskid(), serviceSubtask.getLeaveldeptcode(), serviceSubtask.getCampusid());
        }
        return serviceSubtask;
    }