smartor/src/main/java/com/smartor/service/impl/ServiceSubtaskServiceImpl.java
@@ -448,7 +448,7 @@
            if (StringUtils.isEmpty(serviceSubtask.getCurrentPreachform())) {
                if (StringUtils.isNotEmpty(serviceSubtask.getPreachform())) {
                    //默认取第一个
                    serviceSubtaskRes.setCurrentPreachform(serviceSubtask.getPreachform().split(",")[0]);
                    serviceSubtaskRes.setCurrentPreachform(serviceSubtask.getPreachform().split(",")[0].trim());
                }
            }
            PatMedInhosp inhosp = patMedInhospMapper.selectPatMedInhospByInhospid(serviceSubtask.getInhospid());
@@ -1221,7 +1221,8 @@
        sysDeptVo.setDeptType("1");
        sysDeptVo.setOrgid(orgid);
        sysDeptVo.setDeptCode(centerDept);
        List<SysDept> sysDepts = sysDeptService.selectDeptList(sysDeptVo);
        // 定时任务线程没有登录上下文,不能调用带 @DataScope 的 selectDeptList,否则会抛“获取用户信息异常”。
        List<SysDept> sysDepts = sysDeptService.selectSysDeptList(sysDeptVo);
        return CollectionUtils.isEmpty(sysDepts) ? null : sysDepts.get(0);
    }
@@ -3426,6 +3427,9 @@
            } else {
                groupKey = "deptcode";
            }
        } else if (serviceSubtaskCountReq.getStatisticaltype() != null && serviceSubtaskCountReq.getStatisticaltype() == 3) {
            // 病种统计:按任务ID分组,每个任务即一个病种随访任务
            groupKey = "taskid";
        }
        List<ServiceSubtaskStatistic> serviceSubtaskStatistics = new ArrayList<>();
        // 根据条件进行分组
@@ -3451,6 +3455,9 @@
            case "leavehospitaldistrictcode":
                collect = rawData.stream().collect(Collectors.groupingBy(subtask -> Optional.ofNullable(subtask.getLeavehospitaldistrictcode()).orElse("Unknown")));
                break;
            case "taskid":
                collect = rawData.stream().collect(Collectors.groupingBy(subtask -> Optional.ofNullable(subtask.getTaskid()).map(String::valueOf).orElse("Unknown")));
                break;
            default:
                collect = rawData.stream().collect(Collectors.groupingBy(subtask -> Optional.ofNullable(subtask.getDrcode()).orElse("Unknown")));
                break;
@@ -3463,6 +3470,9 @@
                continue;
            }
            ServiceSubtaskStatistic statistic = getStatistic(serviceSubtaskList, serviceSubtaskCountReq.getFollowUpCountStyle(), serviceSubtaskCountReq.getVisitCount());
            if ("taskid".equals(groupKey)) {
                applyTaskGroupInfo(statistic, serviceSubtaskList);
            }
            if (ObjectUtils.isNotEmpty(jsRates)) {
                Map<String, Object> jsRateMap = jsRates.get(collectKey);
                BigDecimal jsRate = new BigDecimal("0");
@@ -3539,7 +3549,13 @@
        ssRate.setRateDay(serviceSubtaskCountReq.getRateDay());
        Map<String, Map<String, Object>> jsRates = new HashMap<>();
        if (!collect.isEmpty()) {
            jsRates = serviceSubtaskMapper.selectTimelyRateBatch(ssRate);
            Map<String, Map<String, Object>> rateResult = serviceSubtaskMapper.selectTimelyRateBatch(ssRate);
            if (rateResult != null && !rateResult.isEmpty()) {
                // taskid 为数值类型,MyBatis 返回的 Map key 可能是 Long,这里统一转成 String 与分组 key 对齐
                for (Map.Entry<String, Map<String, Object>> entry : rateResult.entrySet()) {
                    jsRates.put(String.valueOf(entry.getKey()), entry.getValue());
                }
            }
        }
        return jsRates;
    }
@@ -3552,6 +3568,9 @@
            ServiceSubtaskStatistic totalRow = calculateTotalRow(sfStatistics);
            totalRow.setLeavehospitaldistrictname("总计");
            totalRow.setDeptname("总计");
            if (serviceSubtaskCountReq.getStatisticaltype() != null && serviceSubtaskCountReq.getStatisticaltype() == 3) {
                totalRow.setTaskName("总计");
            }
            sfStatistics.add(totalRow);
        }
@@ -3745,6 +3764,22 @@
    }
    /**
     * 病种统计(按 taskid 分组)时补齐任务ID、任务名称和疾病病种。
     *
     * @param statistic            统计结果
     * @param serviceSubtaskList   当前任务分组下的子任务
     */
    private void applyTaskGroupInfo(ServiceSubtaskStatistic statistic, List<ServiceSubtask> serviceSubtaskList) {
        if (statistic == null || CollectionUtils.isEmpty(serviceSubtaskList)) {
            return;
        }
        ServiceSubtask first = serviceSubtaskList.get(0);
        statistic.setTaskId(first.getTaskid());
        statistic.setTaskName(first.getTaskName());
        statistic.setDiagType(first.getDiagType());
    }
    private ServiceSubtaskStatistic getStatistic(List<ServiceSubtask> serviceSubtaskList, String followUpCountStyle, Integer visitCount) {
        ServiceSubtaskStatistic serviceSubtaskStatistic = new ServiceSubtaskStatistic();
@@ -3801,6 +3836,7 @@
                //首次出院随访
                if (serviceSubtask.getVisitCount() != null && serviceSubtask.getVisitCount() == 1) {
                    applyFirstFollowUpExtraStatistics(serviceSubtaskStatistic, serviceSubtask);
                    //首次应随访
                    if (serviceSubtask.getSendstate() != null && !serviceSubtask.getSendstate().equals(4L)) {
                        serviceSubtaskStatistic.setNeedFollowUp(serviceSubtaskStatistic.getNeedFollowUp() + 1L);
@@ -3922,6 +3958,7 @@
                    }
                }//二次出院随访
                else if (serviceSubtask.getVisitCount() != null && serviceSubtask.getVisitCount() > 1) {
                    applyAgainFollowUpExtraStatistics(serviceSubtaskStatistic, serviceSubtask);
                    //再次应随访
                    if (serviceSubtask.getSendstate() != null && serviceSubtask.getSendstate() != 4) {
                        serviceSubtaskStatistic.setNeedFollowUpAgain(serviceSubtaskStatistic.getNeedFollowUpAgain() + 1L);
@@ -4073,6 +4110,67 @@
    }
    /**
     * 市一统计口径:首次待随访、已发送及人工电话超时。
     */
    private void applyFirstFollowUpExtraStatistics(ServiceSubtaskStatistic statistic, ServiceSubtask serviceSubtask) {
        if (serviceSubtask.getVisitCount() == null || serviceSubtask.getVisitCount() != 1 || serviceSubtask.getSendstate() == null) {
            return;
        }
        Long sendstate = serviceSubtask.getSendstate();
        if (sendstate.equals(3L)) {
            statistic.setSent(statistic.getSent() + 1L);
        }
        if (sendstate.equals(7L) && isManuallyNotCalled(serviceSubtask)) {
            statistic.setManualCallTimeout(statistic.getManualCallTimeout() + 1L);
        }
        if (sendstate.equals(1L) || sendstate.equals(2L) || sendstate.equals(3L) || sendstate.equals(7L)) {
            statistic.setPendingFollowUpAll(statistic.getPendingFollowUpAll() + 1L);
        }
        if (sendstate.equals(1L) || sendstate.equals(2L)) {
            statistic.setPendingFollowUpWaitSend(statistic.getPendingFollowUpWaitSend() + 1L);
        }
        if (sendstate.equals(3L) || sendstate.equals(7L)) {
            statistic.setPendingFollowUpSent(statistic.getPendingFollowUpSent() + 1L);
        }
    }
    /**
     * 再次随访附加统计:待随访、已发送、人工电话超时及回院复诊复查(suggest=6)。
     */
    private void applyAgainFollowUpExtraStatistics(ServiceSubtaskStatistic statistic, ServiceSubtask serviceSubtask) {
        if (serviceSubtask.getVisitCount() == null || serviceSubtask.getVisitCount() <= 1) {
            return;
        }
        if (serviceSubtask.getSendstate() == null) {
            return;
        }
        Long sendstate = serviceSubtask.getSendstate();
        // 回院复诊复查:再次随访中按处理意见 suggest=6 统计
        if (StringUtils.equals("6", StringUtils.trim(serviceSubtask.getSuggest()))) {
            statistic.setSuggest6CountAgain(statistic.getSuggest6CountAgain() + 1L);
        }
        if (sendstate.equals(3L)) {
            statistic.setSentAgain(statistic.getSentAgain() + 1L);
        }
        if (sendstate.equals(7L) && isManuallyNotCalled(serviceSubtask)) {
            statistic.setManualCallTimeoutAgain(statistic.getManualCallTimeoutAgain() + 1L);
        }
        if (sendstate.equals(1L) || sendstate.equals(2L) || sendstate.equals(3L) || sendstate.equals(7L)) {
            statistic.setPendingFollowUpAllAgain(statistic.getPendingFollowUpAllAgain() + 1L);
        }
        if (sendstate.equals(1L) || sendstate.equals(2L)) {
            statistic.setPendingFollowUpWaitSendAgain(statistic.getPendingFollowUpWaitSendAgain() + 1L);
        }
        if (sendstate.equals(3L) || sendstate.equals(7L)) {
            statistic.setPendingFollowUpSentAgain(statistic.getPendingFollowUpSentAgain() + 1L);
        }
    }
    private boolean isManuallyNotCalled(ServiceSubtask serviceSubtask) {
        return serviceSubtask.getIsManuallyCalled() != null && serviceSubtask.getIsManuallyCalled().equals(0L);
    }
    /**
     * 首次随访统计
     *
     * @param serviceSubtaskList
@@ -4093,6 +4191,7 @@
        serviceSubtaskStatistic.setDischargeCount(serviceSubtaskList.size());
        for (ServiceSubtask serviceSubtask : serviceSubtaskList) {
            applyFirstFollowUpExtraStatistics(serviceSubtaskStatistic, serviceSubtask);
            //无需随访人次
            if (serviceSubtask.getSendstate() != null && serviceSubtask.getSendstate() == 4) {
                serviceSubtaskStatistic.setNonFollowUp(serviceSubtaskStatistic.getNonFollowUp() + 1L);
@@ -4266,6 +4365,7 @@
        //出院人次
        serviceSubtaskStatistic.setDischargeCount(serviceSubtaskList.size());
        for (ServiceSubtask serviceSubtask : serviceSubtaskList) {
            applyAgainFollowUpExtraStatistics(serviceSubtaskStatistic, serviceSubtask);
            //无需随访人次
            if (serviceSubtask.getSendstate() != null && serviceSubtask.getSendstate() == 4) {
                serviceSubtaskStatistic.setNonFollowUp(serviceSubtaskStatistic.getNonFollowUp() + 1L);
@@ -4844,6 +4944,17 @@
     * taskSituation5Info - taskSituation = 5
     * taskSituation6Info - taskSituation = 7
     * manuallyCalledInfo - isManuallyCalled = 1L
     * manualCallTimeoutInfo - visitCount = 1 && sendstate = 7 && isManuallyCalled = 0
     * manualCallTimeoutAgainInfo - visitCount > 1 && sendstate = 7 && isManuallyCalled = 0
     * pendingFollowUpAllInfo - visitCount = 1 && sendstate in (1,2,3,7)
     * pendingFollowUpAllAgainInfo - visitCount > 1 && sendstate in (1,2,3,7)
     * pendingFollowUpWaitSendInfo - visitCount = 1 && sendstate in (1,2)
     * pendingFollowUpWaitSendAgainInfo - visitCount > 1 && sendstate in (1,2)
     * pendingFollowUpSentInfo - visitCount = 1 && sendstate in (3,7)
     * pendingFollowUpSentAgainInfo - visitCount > 1 && sendstate in (3,7)
     * sentInfo - visitCount = 1 && sendstate = 3
     * sentAgainInfo - visitCount > 1 && sendstate = 3
     * suggest6CountAgainInfo - visitCount > 1 && suggest = 6
     * filterCountList - taskSituation = 6 && sendstate = 4
     *
     */
@@ -4884,6 +4995,7 @@
        serviceSubtaskCountReq.setExcep(null);
        serviceSubtaskCountReq.setTaskSituation(null);
        serviceSubtaskCountReq.setFinishState(null);
        serviceSubtaskCountReq.setSuggest(null);
        switch (hyperLinkInfoType) {
            case "dischargeCountInfo":
@@ -5027,6 +5139,11 @@
                serviceSubtaskCountReq.setVisitCount(2);
                serviceSubtaskCountReq.setExcep("1");
                break;
            case "suggest6CountAgainInfo":
                // 再次随访回院复诊复查:visitCount > 1 且 suggest = 6
                serviceSubtaskCountReq.setVisitCount(2);
                serviceSubtaskCountReq.setSuggest("6");
                break;
            case "taskSituation1Info":
                serviceSubtaskCountReq.setTaskSituation(1);
                break;
@@ -5051,6 +5168,48 @@
                break;
            case "manuallyCalledInfo":
                serviceSubtaskCountReq.setIsManuallyCalled(1L);
                break;
            case "manualCallTimeoutInfo":
                serviceSubtaskCountReq.setVisitCount(1);
                serviceSubtaskCountReq.setSendstates(new ArrayList<>(Collections.singletonList(7L)));
                serviceSubtaskCountReq.setIsManuallyCalled(0L);
                break;
            case "manualCallTimeoutAgainInfo":
                serviceSubtaskCountReq.setVisitCount(2);
                serviceSubtaskCountReq.setSendstates(new ArrayList<>(Collections.singletonList(7L)));
                serviceSubtaskCountReq.setIsManuallyCalled(0L);
                break;
            case "pendingFollowUpAllInfo":
                serviceSubtaskCountReq.setVisitCount(1);
                serviceSubtaskCountReq.setSendstates(new ArrayList<>(Arrays.asList(1L, 2L, 3L, 7L)));
                break;
            case "pendingFollowUpAllAgainInfo":
                serviceSubtaskCountReq.setVisitCount(2);
                serviceSubtaskCountReq.setSendstates(new ArrayList<>(Arrays.asList(1L, 2L, 3L, 7L)));
                break;
            case "pendingFollowUpWaitSendInfo":
                serviceSubtaskCountReq.setVisitCount(1);
                serviceSubtaskCountReq.setSendstates(new ArrayList<>(Arrays.asList(1L, 2L)));
                break;
            case "pendingFollowUpWaitSendAgainInfo":
                serviceSubtaskCountReq.setVisitCount(2);
                serviceSubtaskCountReq.setSendstates(new ArrayList<>(Arrays.asList(1L, 2L)));
                break;
            case "pendingFollowUpSentInfo":
                serviceSubtaskCountReq.setVisitCount(1);
                serviceSubtaskCountReq.setSendstates(new ArrayList<>(Arrays.asList(3L, 7L)));
                break;
            case "pendingFollowUpSentAgainInfo":
                serviceSubtaskCountReq.setVisitCount(2);
                serviceSubtaskCountReq.setSendstates(new ArrayList<>(Arrays.asList(3L, 7L)));
                break;
            case "sentInfo":
                serviceSubtaskCountReq.setVisitCount(1);
                serviceSubtaskCountReq.setSendstates(new ArrayList<>(Collections.singletonList(3L)));
                break;
            case "sentAgainInfo":
                serviceSubtaskCountReq.setVisitCount(2);
                serviceSubtaskCountReq.setSendstates(new ArrayList<>(Collections.singletonList(3L)));
                break;
            default:
                break;
@@ -5086,6 +5245,9 @@
            } else {
                groupKey = "deptcode";
            }
        } else if (serviceSubtaskCountReq.getStatisticaltype() != null && serviceSubtaskCountReq.getStatisticaltype() == 3) {
            // 病种统计:按任务ID分组
            groupKey = "taskid";
        }
        serviceSubtaskCountReq.setGroupKey(groupKey);
        List<String> groupKeyList = serviceSubtaskMapper.getSfStatisticsGroupKey(serviceSubtaskCountReq);
@@ -5100,6 +5262,9 @@
                break;
            case "leavehospitaldistrictcode":
                collect = rawData.stream().collect(Collectors.groupingBy(subtask -> Optional.ofNullable(subtask.getLeavehospitaldistrictcode()).orElse("Unknown")));
                break;
            case "taskid":
                collect = rawData.stream().collect(Collectors.groupingBy(subtask -> Optional.ofNullable(subtask.getTaskid()).map(String::valueOf).orElse("Unknown")));
                break;
            default:
                collect = rawData.stream().collect(Collectors.groupingBy(subtask -> Optional.ofNullable(subtask.getDrcode()).orElse("Unknown")));
@@ -5120,6 +5285,9 @@
                continue;
            }
            ServiceSubtaskStatistic statistic = getStatisticCategory(serviceSubtaskList, serviceSubtaskCountReq, subCounts);
            if ("taskid".equals(groupKey)) {
                applyTaskGroupInfo(statistic, serviceSubtaskList);
            }
            if (ObjectUtils.isNotEmpty(jsRates)) {
                Map<String, Object> jsRateMap = jsRates.get(collectKey);
                BigDecimal jsRate = new BigDecimal("0");
@@ -5306,6 +5474,88 @@
        return result;
    }
    /**
     * 问题选项数字下钻:返回选择了指定选项的 service_subtask 列表。
     * <p>
     * 统计明细中的选项数量来自 service_subtask_detail 的
     * IFNULL(matchedtext, asrtext),因此下钻必须使用同一题目的相同选项进行匹配。
     *
     * @param serviceSubtaskCountReq 下钻条件
     * @return service_subtask 列表
     */
    @Override
    public List<ServiceSubtask> getSfStatisticsScriptDetailsHyperlink(ServiceSubtaskCountReq serviceSubtaskCountReq) {
        log.info("getSfStatisticsScriptDetailsHyperlink的入参为:{}", serviceSubtaskCountReq);
        if (serviceSubtaskCountReq == null) {
            return new ArrayList<>();
        }
        // 兼容前端直接回传统计明细行中的 subtaskType
        if (StringUtils.isBlank(serviceSubtaskCountReq.getType())
                && StringUtils.isNotBlank(serviceSubtaskCountReq.getSubtaskType())) {
            serviceSubtaskCountReq.setType(serviceSubtaskCountReq.getSubtaskType());
        }
        resolveScriptContentAndType(serviceSubtaskCountReq);
        if (StringUtils.isBlank(serviceSubtaskCountReq.getScriptContent())
                && serviceSubtaskCountReq.getScriptid() == null) {
            log.warn("getSfStatisticsScriptDetailsHyperlink-题目scriptid和scriptContent均为空,返回空列表");
            return new ArrayList<>();
        }
        if (serviceSubtaskCountReq.getOptionText() == null) {
            log.warn("getSfStatisticsScriptDetailsHyperlink-选项optionText为空,返回空列表");
            return new ArrayList<>();
        }
        return serviceSubtaskMapper.getSfStatisticsScriptDetailsHyperlink(serviceSubtaskCountReq);
    }
    /**
     * 根据 scriptid 补齐题目内容和任务类型。
     * <p>
     * 统计明细按“题目内容 + 任务类型”合并,同一道题复制到多个任务模板时 scriptid 可能不同,
     * 因此优先按题目内容下钻;只有查不到题目内容时才退化为 scriptid 精确匹配。
     */
    private void resolveScriptContentAndType(ServiceSubtaskCountReq serviceSubtaskCountReq) {
        if (serviceSubtaskCountReq.getScriptid() == null) {
            return;
        }
        // 已有题目内容和任务类型时无需再查模板问题表
        if (StringUtils.isNotBlank(serviceSubtaskCountReq.getScriptContent())
                && StringUtils.isNotBlank(serviceSubtaskCountReq.getType())) {
            return;
        }
        String type = serviceSubtaskCountReq.getType();
        if (StringUtils.isBlank(type) || "1".equals(type)) {
            IvrTaskTemplateScript ivrTaskTemplateScript =
                    iIvrTaskTemplateScriptService.selectIvrTaskTemplateScriptByID(serviceSubtaskCountReq.getScriptid());
            if (ObjectUtils.isNotEmpty(ivrTaskTemplateScript)) {
                if (StringUtils.isBlank(serviceSubtaskCountReq.getType())) {
                    serviceSubtaskCountReq.setType("1");
                }
                if (StringUtils.isBlank(serviceSubtaskCountReq.getScriptContent())) {
                    serviceSubtaskCountReq.setScriptContent(ivrTaskTemplateScript.getScriptContent());
                }
                return;
            }
            // 明确指定了语音类型时不再尝试问卷表
            if (StringUtils.isNotBlank(type)) {
                return;
            }
        }
        if (StringUtils.isBlank(serviceSubtaskCountReq.getType()) || "2".equals(type)) {
            SvyTaskTemplateScript svyTaskTemplateScript =
                    svyTaskTemplateScriptService.selectSvyTaskTemplateScriptByID(serviceSubtaskCountReq.getScriptid());
            if (ObjectUtils.isNotEmpty(svyTaskTemplateScript)) {
                if (StringUtils.isBlank(serviceSubtaskCountReq.getType())) {
                    serviceSubtaskCountReq.setType("2");
                }
                if (StringUtils.isBlank(serviceSubtaskCountReq.getScriptContent())) {
                    serviceSubtaskCountReq.setScriptContent(svyTaskTemplateScript.getScriptContent());
                }
            }
        }
    }
    /**
     * @param serviceSubtask
@@ -5413,8 +5663,19 @@
        total.setAbnormal(0L);
        total.setNeedFollowUpAgain(0L);
        total.setPendingFollowUpAgain(0L);
        total.setManualCallTimeout(0L);
        total.setManualCallTimeoutAgain(0L);
        total.setPendingFollowUpAll(0L);
        total.setPendingFollowUpAllAgain(0L);
        total.setPendingFollowUpWaitSend(0L);
        total.setPendingFollowUpWaitSendAgain(0L);
        total.setPendingFollowUpSent(0L);
        total.setPendingFollowUpSentAgain(0L);
        total.setSent(0L);
        total.setSentAgain(0L);
        total.setFollowUpSuccessAgain(0L);
        total.setFollowUpFailAgain(0L);
        total.setSuggest6CountAgain(0L);
        total.setManualAgain(0L);
        total.setSmsAgain(0L);
        total.setWeChatAgain(0L);
@@ -5447,8 +5708,19 @@
            total.setAbnormal(total.getAbnormal() + (stat.getAbnormal() != null ? stat.getAbnormal() : 0L));
            total.setNeedFollowUpAgain(total.getNeedFollowUpAgain() + (stat.getNeedFollowUpAgain() != null ? stat.getNeedFollowUpAgain() : 0L));
            total.setPendingFollowUpAgain(total.getPendingFollowUpAgain() + (stat.getPendingFollowUpAgain() != null ? stat.getPendingFollowUpAgain() : 0L));
            total.setManualCallTimeout(total.getManualCallTimeout() + (stat.getManualCallTimeout() != null ? stat.getManualCallTimeout() : 0L));
            total.setManualCallTimeoutAgain(total.getManualCallTimeoutAgain() + (stat.getManualCallTimeoutAgain() != null ? stat.getManualCallTimeoutAgain() : 0L));
            total.setPendingFollowUpAll(total.getPendingFollowUpAll() + (stat.getPendingFollowUpAll() != null ? stat.getPendingFollowUpAll() : 0L));
            total.setPendingFollowUpAllAgain(total.getPendingFollowUpAllAgain() + (stat.getPendingFollowUpAllAgain() != null ? stat.getPendingFollowUpAllAgain() : 0L));
            total.setPendingFollowUpWaitSend(total.getPendingFollowUpWaitSend() + (stat.getPendingFollowUpWaitSend() != null ? stat.getPendingFollowUpWaitSend() : 0L));
            total.setPendingFollowUpWaitSendAgain(total.getPendingFollowUpWaitSendAgain() + (stat.getPendingFollowUpWaitSendAgain() != null ? stat.getPendingFollowUpWaitSendAgain() : 0L));
            total.setPendingFollowUpSent(total.getPendingFollowUpSent() + (stat.getPendingFollowUpSent() != null ? stat.getPendingFollowUpSent() : 0L));
            total.setPendingFollowUpSentAgain(total.getPendingFollowUpSentAgain() + (stat.getPendingFollowUpSentAgain() != null ? stat.getPendingFollowUpSentAgain() : 0L));
            total.setSent(total.getSent() + (stat.getSent() != null ? stat.getSent() : 0L));
            total.setSentAgain(total.getSentAgain() + (stat.getSentAgain() != null ? stat.getSentAgain() : 0L));
            total.setFollowUpSuccessAgain(total.getFollowUpSuccessAgain() + (stat.getFollowUpSuccessAgain() != null ? stat.getFollowUpSuccessAgain() : 0L));
            total.setFollowUpFailAgain(total.getFollowUpFailAgain() + (stat.getFollowUpFailAgain() != null ? stat.getFollowUpFailAgain() : 0L));
            total.setSuggest6CountAgain(total.getSuggest6CountAgain() + (stat.getSuggest6CountAgain() != null ? stat.getSuggest6CountAgain() : 0L));
            total.setManualAgain(total.getManualAgain() + (stat.getManualAgain() != null ? stat.getManualAgain() : 0L));
            total.setSmsAgain(total.getSmsAgain() + (stat.getSmsAgain() != null ? stat.getSmsAgain() : 0L));
            total.setWeChatAgain(total.getWeChatAgain() + (stat.getWeChatAgain() != null ? stat.getWeChatAgain() : 0L));
@@ -5654,6 +5926,184 @@
        }
    }
    /**
     * 微信公众号已领取未填写:按题目默认选项补齐当前子任务,并将当前子任务完成。
     * <p>
     * 所有查询、明细写入、状态更新都只使用当前 subid,避免影响其他任务/子任务。
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean completeWechatClaimedByDefault(ServiceSubtask serviceSubtask) {
        if (serviceSubtask == null || serviceSubtask.getId() == null) {
            return false;
        }
        // 重新读取当前子任务,所有判断只针对这一个 subid
        ServiceSubtask current = serviceSubtaskMapper.selectServiceSubtaskById(serviceSubtask.getId());
        if (current == null) {
            return false;
        }
        // 只处理:丽水中医院、问卷、当前方式公众号、已领取、未提交
        // 其他医院 orgid 不匹配时直接返回 false,继续走原补偿逻辑。
        if (!IServiceSubtaskService.WECHAT_DEFAULT_COMPLETE_ORGID.equals(current.getOrgid())
                || !"2".equals(current.getType())
                || !"5".equals(current.getCurrentPreachform())
                || !Long.valueOf(1L).equals(current.getSendstate())
                || Long.valueOf(1L).equals(current.getSubmit())) {
            return false;
        }
        // 查询当前子任务自己的公众号发送方式,必须是已发送(2)或已领取(3)
        ServiceSubtaskPreachform query = new ServiceSubtaskPreachform();
        query.setSubid(current.getId());
        query.setTaskid(current.getTaskid());
        query.setPreachform("5");
        List<ServiceSubtaskPreachform> preachforms = serviceSubtaskPreachformMapper.selectServiceSubtaskPreachformList(query);
        if (CollectionUtils.isEmpty(preachforms)) {
            return false;
        }
        ServiceSubtaskPreachform currentPreachform = preachforms.stream()
                .filter(item -> "2".equals(item.getSendstate()) || "3".equals(item.getSendstate()))
                .findFirst().orElse(null);
        if (currentPreachform == null) {
            return false;
        }
        // 只给当前 subid 补齐默认选项;至少有一题默认选项才继续
        int filled = fillWechatDefaultDetailForCurrentSubtask(current);
        if (filled <= 0) {
            log.info("【微信公众号默认完成】subid:{} 没有可填充的默认选项,跳过", current.getId());
            return false;
        }
        // 当前公众号发送方式:已发送/已领取 -> 完成(9)
        ServiceSubtaskPreachform updatePreachform = new ServiceSubtaskPreachform();
        updatePreachform.setId(currentPreachform.getId());
        updatePreachform.setSubid(current.getId());
        updatePreachform.setPreachform("5");
        updatePreachform.setOrgid(current.getOrgid());
        updatePreachform.setRemark("已领取未填写,按默认选项完成");
        int preachformUpdated = serviceSubtaskPreachformMapper.updateWechatPreachformComplete(updatePreachform);
        if (preachformUpdated == 0) {
            throw new BaseException("微信公众号默认完成失败:发送方式状态已变化,subid=" + current.getId());
        }
        // 当前子任务:已领取(1) -> 完成(6),只更新这一个 subid
        ServiceSubtask complete = new ServiceSubtask();
        complete.setId(current.getId());
        complete.setOrgid(current.getOrgid());
        complete.setFinishtime(new Date());
        complete.setResult("success");
        complete.setRemark("已领取未填写,按默认选项完成");
        int subtaskUpdated = serviceSubtaskMapper.updateWechatDefaultComplete(complete);
        if (subtaskUpdated == 0) {
            throw new BaseException("微信公众号默认完成失败:子任务状态已变化,subid=" + current.getId());
        }
        log.info("【微信公众号默认完成】subid:{},已按默认选项完成,填充题目数:{}", current.getId(), filled);
        return true;
    }
    /**
     * 仅给当前子任务补齐问卷单选/多选题中 default_value=2 的选项。
     * 已有非空答案的题目跳过,不覆盖患者已填写内容。
     *
     * @return 实际填充(或此前已由系统默认填充)的题目数
     */
    private int fillWechatDefaultDetailForCurrentSubtask(ServiceSubtask serviceSubtask) {
        Long templateId = serviceSubtask.getTemplateid();
        ServiceTask serviceTask = serviceTaskService.selectServiceTaskByTaskid(serviceSubtask.getTaskid());
        if (templateId == null && serviceTask != null) {
            templateId = serviceTask.getTemplateid();
        }
        if (templateId == null) {
            return 0;
        }
        SvyTaskTemplateScript scriptQuery = new SvyTaskTemplateScript();
        scriptQuery.setTemplateID(templateId);
        List<SvyTaskTemplateScript> scripts = svyTaskTemplateScriptService.selectSvyTaskTemplateScriptList(scriptQuery);
        if (CollectionUtils.isEmpty(scripts)) {
            return 0;
        }
        int filled = 0;
        for (SvyTaskTemplateScript script : scripts) {
            SvyTaskTemplateScriptVO scriptVO = DtoConversionUtils.sourceToTarget(script, SvyTaskTemplateScriptVO.class);
            SvyTaskTemplateScriptVO scriptWithOptions = svyTaskTemplateScriptService.selectInfoByCondition(scriptVO);
            if (scriptWithOptions == null
                    || scriptWithOptions.getId() == null
                    || CollectionUtils.isEmpty(scriptWithOptions.getSvyTaskTemplateTargetoptions())
                    || (!"1".equals(scriptWithOptions.getScriptType()) && !"2".equals(scriptWithOptions.getScriptType()))) {
                continue;
            }
            List<SvyTaskTemplateTargetoption> options = scriptWithOptions.getSvyTaskTemplateTargetoptions();
            List<SvyTaskTemplateTargetoption> defaultOptions = options.stream()
                    .filter(option -> option.getDefaultValue() != null && option.getDefaultValue() == 2L)
                    .filter(option -> StringUtils.isNotBlank(option.getOptioncontent()))
                    .collect(Collectors.toList());
            if (CollectionUtils.isEmpty(defaultOptions)) {
                continue;
            }
            // 已有非空答案则不覆盖,避免影响患者已填写内容
            ServiceSubtaskDetail detailQuery = new ServiceSubtaskDetail();
            detailQuery.setSubId(serviceSubtask.getId());
            detailQuery.setScriptid(scriptWithOptions.getId());
            List<ServiceSubtaskDetail> existingDetails = serviceSubtaskDetailMapper.selectServiceSubtaskDetailList(detailQuery);
            ServiceSubtaskDetail existing = CollectionUtils.isNotEmpty(existingDetails) ? existingDetails.get(0) : null;
            if (existing != null
                    && (StringUtils.isNotBlank(existing.getAsrtext()) || StringUtils.isNotBlank(existing.getMatchedtext()))) {
                // 之前已由系统默认填充过的,计入已填充,保证重试幂等
                if ("DEFAULT".equals(existing.getOperate())) {
                    filled++;
                }
                continue;
            }
            String targetValue = options.stream()
                    .map(SvyTaskTemplateTargetoption::getOptioncontent)
                    .filter(StringUtils::isNotBlank)
                    .collect(Collectors.joining("&"));
            String defaultValue = defaultOptions.stream()
                    .map(SvyTaskTemplateTargetoption::getOptioncontent)
                    .collect(Collectors.joining("&"));
            ServiceSubtaskDetail detail = new ServiceSubtaskDetail();
            detail.setSubId(serviceSubtask.getId());
            detail.setTaskid(serviceSubtask.getTaskid());
            detail.setPatid(serviceSubtask.getPatid());
            detail.setScriptid(scriptWithOptions.getId());
            detail.setTemplatequestionnum(scriptWithOptions.getId());
            detail.setQuestiontext(scriptWithOptions.getScriptContent());
            detail.setCategoryid(scriptWithOptions.getCategoryid());
            detail.setTargetid(scriptWithOptions.getTargetid() != null ? scriptWithOptions.getTargetid().intValue() : null);
            detail.setValueType(scriptWithOptions.getScriptType());
            detail.setTargetvalue(targetValue);
            detail.setAsrtext(defaultValue);
            detail.setMatchedtext(defaultValue);
            detail.setOperate("DEFAULT");
            detail.setTemplateType(2);
            detail.setLibTemplateid(serviceSubtask.getLibtemplateid() != null ? serviceSubtask.getLibtemplateid().intValue() : null);
            detail.setTemplateid(templateId.toString());
            detail.setGuid(scriptWithOptions.getGuid());
            detail.setOrgid(StringUtils.isNotEmpty(scriptWithOptions.getOrgid()) ? scriptWithOptions.getOrgid()
                    : (serviceTask != null ? serviceTask.getOrgid() : null));
            detail.setCreateTime(new Date());
            detail.setUpdateTime(new Date());
            if (existing != null) {
                detail.setId(existing.getId());
                serviceSubtaskDetailMapper.updateServiceSubtaskDetail(detail);
            } else {
                serviceSubtaskDetailMapper.insertServiceSubtaskDetail(detail);
            }
            filled++;
        }
        return filled;
    }
    @Override
    public Map<String, Object> getHeLibraryCount(@RequestBody HeLibraryCountVO heLibraryCountVO) {
        if (ObjectUtils.isEmpty(heLibraryCountVO)) {