陈昶聿
2026-11-03 17c8a50365c4607c2b001fd651231807a4d4bc5c
smartor/src/main/java/com/smartor/service/impl/ServiceSubtaskServiceImpl.java
@@ -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);
        }
@@ -3744,6 +3763,22 @@
        return map;
    }
    /**
     * 病种统计(按 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();
@@ -4100,13 +4135,20 @@
    }
    /**
     * 市一统计口径:再次待随访、已发送及人工电话超时。
     * 再次随访附加统计:待随访、已发送、人工电话超时及回院复诊复查(suggest=6)。
     */
    private void applyAgainFollowUpExtraStatistics(ServiceSubtaskStatistic statistic, ServiceSubtask serviceSubtask) {
        if (serviceSubtask.getVisitCount() == null || serviceSubtask.getVisitCount() <= 1 || serviceSubtask.getSendstate() == null) {
        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);
        }
@@ -4912,6 +4954,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
     *
     */
@@ -4952,6 +4995,7 @@
        serviceSubtaskCountReq.setExcep(null);
        serviceSubtaskCountReq.setTaskSituation(null);
        serviceSubtaskCountReq.setFinishState(null);
        serviceSubtaskCountReq.setSuggest(null);
        switch (hyperLinkInfoType) {
            case "dischargeCountInfo":
@@ -5095,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;
@@ -5196,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);
@@ -5210,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")));
@@ -5230,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");
@@ -5416,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
@@ -5535,6 +5675,7 @@
        total.setSentAgain(0L);
        total.setFollowUpSuccessAgain(0L);
        total.setFollowUpFailAgain(0L);
        total.setSuggest6CountAgain(0L);
        total.setManualAgain(0L);
        total.setSmsAgain(0L);
        total.setWeChatAgain(0L);
@@ -5579,6 +5720,7 @@
            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));