ruoyi-admin/src/main/java/com/ruoyi/web/controller/smartor/ServiceSubtaskController.java
@@ -848,26 +848,35 @@ @AddOrgId(field = "orgid", paramIndex = 0, campusField = "campusid") @PostMapping("/getSfStatisticsCountDetailsHyperLink") public AjaxResult getSfStatisticsCountDetailsHyperLink(@RequestBody ServiceSubtaskCountReq serviceSubtaskCountReq) { Integer statisticaltype = serviceSubtaskCountReq.getStatisticaltype(); if (statisticaltype != null && statisticaltype != 1 && statisticaltype != 2 && statisticaltype != 3) { return error("统计维度statisticaltype仅支持:1-按病区 2-按科室 3-按taskid"); } if (serviceSubtaskCountReq.getScriptid() == null && StringUtils.isEmpty(serviceSubtaskCountReq.getScriptContent())) { return error("问题ID(scriptid)不能为空"); } if (serviceSubtaskCountReq.getOptionText() == null) { return error("问题选项(optionText)不能为空"); } LoginUser loginUser = getLoginUser(); String orgid = loginUser.getUser().getOrgid(); serviceSubtaskCountReq.setOrgid(orgid); // 与 getSfStatisticsCountDetails 保持相同的配置筛选口径 String configKey = serviceSubtaskCountReq.getConfigKey(); if (StringUtils.isNotEmpty(configKey)) { String configValue = configService.selectConfigByKey(configKey, orgid); serviceSubtaskCountReq.setConfigValue(configValue); if (StringUtils.isEmpty(configValue)) { String logInfo = "getSfStatisticsCount-参数 " + configKey + " 未配置, 请配置好后重试"; String logInfo = "getSfStatisticsCountDetailsHyperLink-参数 " + configKey + " 未配置, 请配置好后重试"; log.error(logInfo); return error(logInfo); } } String followUpCountStyle = configService.selectConfigByKey("followUpCountStyle", orgid); if (ObjectUtils.isNotEmpty(followUpCountStyle)) { serviceSubtaskCountReq.setFollowUpCountStyle(followUpCountStyle); } else { serviceSubtaskCountReq.setFollowUpCountStyle("1"); } return success(serviceSubtaskService.getSfStatisticsHyperlink(serviceSubtaskCountReq)); return success(serviceSubtaskService.getSfStatisticsScriptDetailsHyperlink(serviceSubtaskCountReq)); } /** * 发送短信模板 smartor/src/main/java/com/smartor/domain/ServiceSubtaskCountReq.java
@@ -1,6 +1,7 @@ package com.smartor.domain; import com.alibaba.fastjson.JSONObject; import com.fasterxml.jackson.annotation.JsonAlias; import com.fasterxml.jackson.annotation.JsonFormat; import com.ruoyi.common.annotation.Excel; import io.swagger.annotations.ApiModel; @@ -69,7 +70,8 @@ @ApiModelProperty(value = "子任务Id集合") private List<Long> subTaskIds; @ApiModelProperty(value = "科室病区:1,病区统计 2,科室统计 3. 病种统计") @ApiModelProperty(value = "统计维度:1-按病区(leavehospitaldistrictcode) 2-按科室(deptcode) 3-按taskid") @JsonAlias("statisticalType") private Integer statisticaltype; private String visitDeptCode; @@ -127,6 +129,33 @@ */ @ApiModelProperty(value = "问题编码") private String scriptids; /** * 问题ID(题目明细行返回的 scriptid),用于选项数字下钻 */ @ApiModelProperty(value = "问题ID") @JsonAlias("scriptId") private Long scriptid; /** * 问题内容。传入后按题目内容匹配,兼容同一道题被复制到多个任务模板的场景 */ @ApiModelProperty(value = "问题内容") private String scriptContent; /** * 选项文本(题目明细行返回的 optionText) */ @ApiModelProperty(value = "选项文本") @JsonAlias({"optiontext", "option", "optionContent"}) private String optionText; /** * 题目明细中的任务类型(subtaskType),用于下钻时区分语音/问卷 */ @ApiModelProperty(value = "任务类型(题目明细中的 subtaskType)") @JsonAlias("subTaskType") private String subtaskType; /** * 分组类型 drCode, deptCode, leaveHospitalDistrictCode @@ -207,6 +236,7 @@ private String hyperLinkInfoType; @ApiModelProperty(value = "任务ID", required = true) @JsonAlias("taskid") private Long taskId; @ApiModelProperty(value = "taskIds集合") smartor/src/main/java/com/smartor/mapper/ServiceSubtaskMapper.java
@@ -221,6 +221,14 @@ */ public List<ServiceSubtaskScriptCount> getSfStatisticsScriptItemDetails(ServiceSubtaskCountReq serviceSubtaskCountReq); /** * 问题选项数字下钻:查询选择了指定选项的 service_subtask 列表 * * @param serviceSubtaskCountReq 统计维度、科室/病区、任务、题目及选项条件 * @return service_subtask 列表 */ public List<ServiceSubtask> getSfStatisticsScriptDetailsHyperlink(ServiceSubtaskCountReq serviceSubtaskCountReq); public List<PatMedRes> getDeptRanking(PatMedReq patMedReq); smartor/src/main/java/com/smartor/service/IServiceSubtaskService.java
@@ -202,6 +202,14 @@ public Map<String, Object> getSfStatisticsScriptDetails(ServiceSubtaskCountReq serviceSubtaskCountReq); /** * 问题选项数字下钻:查询选择了指定选项的 service_subtask 列表 * * @param serviceSubtaskCountReq 统计维度、科室/病区、任务、题目及选项条件 * @return service_subtask 列表 */ public List<ServiceSubtask> getSfStatisticsScriptDetailsHyperlink(ServiceSubtaskCountReq serviceSubtaskCountReq); public List<PatArchiveSrmVO> sendMsgTemplate(List<PatArchiveSrmVO> patArchiveSrmVOS); public List<ServiceSubtaskDiagname> convertToDiagnameList(List<ServiceSubtask> subtaskList); smartor/src/main/java/com/smartor/service/impl/ServiceSubtaskServiceImpl.java
@@ -3427,6 +3427,9 @@ } else { groupKey = "deptcode"; } } else if (serviceSubtaskCountReq.getStatisticaltype() != null && serviceSubtaskCountReq.getStatisticaltype() == 3) { // 病种统计:按任务ID分组,每个任务即一个病种随访任务 groupKey = "taskid"; } List<ServiceSubtaskStatistic> serviceSubtaskStatistics = new ArrayList<>(); // 根据条件进行分组 @@ -3452,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; @@ -3464,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"); @@ -3540,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; } @@ -3553,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 +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(); @@ -5211,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); @@ -5225,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"))); @@ -5245,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"); @@ -5431,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 smartor/src/main/resources/mapper/smartor/ServiceSubtaskMapper.xml
@@ -103,6 +103,7 @@ <result property="loopCount" column="loop_count"/> <result property="isManuallyCalled" column="is_manually_called"/> <result property="campusid" column="campusid"/> <result property="diagType" column="diag_type"/> </resultMap> <resultMap type="com.smartor.domain.ServiceSubtaskCount" id="ServiceSubtaskResult2"> @@ -1706,6 +1707,9 @@ nurse_name, taskid, task_name, <if test="groupKey != null and groupKey == 'taskid'"> (SELECT st.diag_type FROM service_task st WHERE st.taskid = service_subtask.taskid LIMIT 1) AS diag_type, </if> visit_time, visit_dept_code, finishtime, @@ -1749,6 +1753,13 @@ <foreach collection="deptcodes" item="deptcode" open="(" separator="," close=")"> #{deptcode} </foreach> </if> <if test="taskIds != null and taskIds.size() > 0"> AND taskid IN <foreach collection="taskIds" item="taskId" open="(" separator="," close=")"> #{taskId} </foreach> </if> <if test="serviceType != null and serviceType.size() > 0"> @@ -1932,6 +1943,13 @@ <foreach collection="deptcodes" item="deptcode" open="(" separator="," close=")"> #{deptcode} </foreach> </if> <if test="taskIds != null and taskIds.size() > 0"> AND taskid IN <foreach collection="taskIds" item="taskId" open="(" separator="," close=")"> #{taskId} </foreach> </if> <if test="serviceType != null and serviceType.size() > 0"> @@ -2475,6 +2493,136 @@ group by a.script_content, ss.type, IFNULL(ssd.matchedtext,ssd.asrtext) </select> <!-- 问题选项数字下钻:返回选择了指定选项的 service_subtask 列表。 过滤口径与 getSfStatisticsScriptDetails / getSfStatisticsScriptItemDetails 保持一致, 额外支持 statisticaltype:1-病区、2-科室、3-病种。 --> <select id="getSfStatisticsScriptDetailsHyperlink" parameterType="com.smartor.domain.ServiceSubtaskCountReq" resultMap="ServiceSubtaskResult"> SELECT ss.* FROM service_subtask ss WHERE ss.del_flag = 0 <if test="orgid != null and orgid != ''"> AND ss.orgid = #{orgid} </if> <if test="campusid != null and campusid != ''"> AND ss.campusid = #{campusid} </if> <if test="leavehospitaldistrictcodes != null and leavehospitaldistrictcodes.size() > 0"> AND ss.leavehospitaldistrictcode IN <foreach collection="leavehospitaldistrictcodes" item="leavehospitaldistrictcode" open="(" separator="," close=")"> #{leavehospitaldistrictcode} </foreach> </if> <if test="deptcodes != null and deptcodes.size() > 0"> AND ss.deptcode IN <foreach collection="deptcodes" item="deptcode" open="(" separator="," close=")"> #{deptcode} </foreach> </if> <if test="serviceType != null and serviceType.size() > 0"> AND ss.service_type IN <foreach collection="serviceType" item="serviceType" open="(" separator="," close=")"> #{serviceType} </foreach> </if> <if test="startTime != null and endTime != null"> AND ss.visit_time >= DATE(#{startTime}) AND ss.visit_time < DATE_ADD(DATE(#{endTime}), INTERVAL 1 DAY) </if> <if test="type != null and type != ''"> AND ss.type = #{type} </if> <!-- 统计维度:1-按病区 2-按科室 3-按taskid --> <if test="statisticaltype != null and statisticaltype == 1"> <choose> <when test="deptcode != null and deptcode != ''"> AND ss.leavehospitaldistrictcode = #{deptcode} </when> <when test="leavehospitaldistrictcode != null and leavehospitaldistrictcode != ''"> AND ss.leavehospitaldistrictcode = #{leavehospitaldistrictcode} </when> </choose> </if> <if test="statisticaltype != null and statisticaltype == 2 and deptcode != null and deptcode != ''"> AND ss.deptcode = #{deptcode} </if> <if test="statisticaltype != null and statisticaltype == 3 and taskId != null"> AND ss.taskid = #{taskId} </if> <!-- 兼容不传 statisticaltype 时按单值条件过滤 --> <if test="statisticaltype == null"> <if test="leavehospitaldistrictcode != null and leavehospitaldistrictcode != ''"> AND ss.leavehospitaldistrictcode = #{leavehospitaldistrictcode} </if> <if test="deptcode != null and deptcode != ''"> AND ss.deptcode = #{deptcode} </if> <if test="taskId != null"> AND ss.taskid = #{taskId} </if> </if> <!-- 题目 + 选项匹配:只保留回答中选择了该选项的子任务 --> AND EXISTS ( SELECT 1 FROM service_subtask_detail ssd WHERE ssd.sub_id = ss.id AND ssd.del_flag = 0 <if test="configValue != null and configValue != ''"> AND ssd.categoryid IN (${configValue}) </if> <choose> <when test="optionText != null"> AND IFNULL(ssd.matchedtext, ssd.asrtext) = #{optionText} </when> <otherwise> AND 1 = 0 </otherwise> </choose> <choose> <!-- 优先按题目内容匹配:同一道题复制到多个任务模板时 scriptid 不同,但 script_content 相同 --> <when test="scriptContent != null and scriptContent != ''"> AND ssd.scriptid IN ( <choose> <when test="type == '1'"> SELECT its.id FROM ivr_task_template_script its WHERE its.script_content = #{scriptContent} </when> <when test="type == '2'"> SELECT sts.id FROM svy_task_template_script sts WHERE sts.script_content = #{scriptContent} </when> <otherwise> SELECT its.id FROM ivr_task_template_script its WHERE its.script_content = #{scriptContent} UNION SELECT sts.id FROM svy_task_template_script sts WHERE sts.script_content = #{scriptContent} </otherwise> </choose> ) </when> <when test="scriptid != null"> AND ssd.scriptid = #{scriptid} </when> <otherwise> AND 1 = 0 </otherwise> </choose> ) ORDER BY ss.endtime DESC, ss.id DESC <if test="pageSize != null and pageSize > 0"> LIMIT #{pageSize} <if test="pageNum != null and pageNum >= 0"> OFFSET #{pageNum} </if> </if> </select> <select id="getDataByTime" resultMap="ServiceSubtaskResult"> select id,