liusheng
3 天以前 665cb08f5e74996d417f26ab2bad6d7fe210ea2b
1.修改省立同德满意度统计接口
已修改3个文件
160 ■■■■■ 文件已修改
ruoyi-admin/src/main/java/com/ruoyi/web/controller/smartor/ServiceSubtaskDetailController.java 101 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
smartor/src/main/java/com/smartor/service/impl/ServiceSubtaskServiceImpl.java 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
smartor/src/main/resources/mapper/smartor/ServiceSubtaskMapper.xml 39 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/java/com/ruoyi/web/controller/smartor/ServiceSubtaskDetailController.java
@@ -26,6 +26,7 @@
import org.springframework.web.bind.annotation.*;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.*;
import java.util.stream.Collectors;
@@ -204,9 +205,14 @@
    /**
     * 查询患者随访信息
     * 省立同德满意度数据统计(按题目统计选项数量/占比,并给出该题的综合得分)
     * <p>
     * 综合满意度 = Σ(选项分值 × 项数) / Σ项数 / 本题最高分值 × 100%,
     * 分值取 1、0.8、0.6、0.4、0.2 五档(很满意、满意、一般、不满意、很不满意);
     * 「不适用」、0 分、空值等既不进分子也不进分母。
     */
    @ApiOperation("省立同德满意度数据统计")
    @AddOrgId(field = "orgid", paramIndex = 0, campusField = "campusid")
    @PostMapping("/sltdMydTotalByScore")
    public Map<String, Object> sltdMydTotalByScore(@RequestBody SltdMydTotalVO sltdMydTotalVO) {
        List<ServiceSubtaskRes> serviceSubtaskResList = null;
@@ -220,6 +226,9 @@
                serviceSubtaskEntity.setDeptOrDistrict(sltdMydTotalVO.getDeptOrDistrict());
                serviceSubtaskEntity.setLeavehospitaldistrictcodes(sltdMydTotalVO.getLeavehospitaldistrictcodes());
                serviceSubtaskEntity.setLeaveldeptcodes(sltdMydTotalVO.getLeaveldeptcodes());
                //机构/院区由 @AddOrgId 注入,落到查询条件上,避免跨机构取数
                serviceSubtaskEntity.setOrgid(sltdMydTotalVO.getOrgid());
                serviceSubtaskEntity.setCampusid(sltdMydTotalVO.getCampusid());
                if (serviceSubtaskEntity != null) {
                    List<ServiceSubtask> serviceSubtasks = serviceSubtaskService.getServiceSubtasks(serviceSubtaskEntity);
                    List<ServiceSubtaskRes> serviceSubtaskRes = DtoConversionUtils.sourceToTarget(serviceSubtasks, ServiceSubtaskRes.class);
@@ -236,6 +245,10 @@
        if (CollectionUtils.isNotEmpty(serviceSubtaskResList)) {
            List<ServiceSubtaskDetailRatioExport> detailList = serviceSubtaskService.getDetailRatio(sltdMydTotalVO, serviceSubtaskResList);
            if (detailList == null) {
                //serviceType 组合不在支持范围内(如同时含随访 2 和满意度 6/14),给明确提示而不是悄悄返回空数组
                return error("当前 serviceType 组合暂不支持统计问题占比,请分开查询(仅支持随访 2 或满意度 6/14)");
            }
            if (CollectionUtils.isNotEmpty(detailList)) {
                Map<String, List<ServiceSubtaskDetailRatioExport>> groupedMap = detailList.stream().collect(Collectors.groupingBy(ServiceSubtaskDetailRatioExport::getQuestiontext));
@@ -243,17 +256,8 @@
                    QuestionResultDTO questionResultDTO = new QuestionResultDTO();
                    questionResultDTO.setQeustionText(questionText);
                    questionResultDTO.setSubtaskDetailRatioExportList(exportList);
                    BigDecimal allCount = new BigDecimal("0");
                    BigDecimal allScore = new BigDecimal("0.00");
                    for (ServiceSubtaskDetailRatioExport ssdre : exportList) {
                        if (StringUtils.isNotEmpty(ssdre.getCount()))
                            allCount.add(BigDecimal.valueOf(Long.valueOf(ssdre.getCount())));
                        allScore.add(BigDecimal.valueOf(Double.valueOf(ssdre.getScore())));
                    }
                    if (allCount.compareTo(BigDecimal.ZERO) > 0)
                        questionResultDTO.setAvgScore(allScore.divide(allCount, 2).toString());
                    //综合得分:Σ(分值×项数) / Σ项数 / 本题最高分值 × 100%,不适用/0分/空值不进分子也不进分母
                    questionResultDTO.setAvgScore(calcMydScore(exportList));
                    result.add(questionResultDTO);
                });
            }
@@ -287,4 +291,77 @@
        return success(serviceSubtaskService.mzMydScoreRank(mzMydScoreRankVO));
    }
    /**
     * 多选题的问题类型
     */
    private static final String VALUE_TYPE_MULTIPLE = "2";
    /**
     * 计算一道题的满意度综合得分(百分制,保留两位小数)
     * <p>
     * 综合满意度 = Σ(选项分值 × 项数) / Σ项数 / 本题最高分值 × 100%
     * 等价于 Σ(很满意×1 + 满意×0.8 + 一般×0.6 + 不满意×0.4 + 很不满意×0.2) / Σ(五档项数) × 100%。
     * 用本题最高分值归一化,兼容明细分值存 0-1 权重(1、0.8…)和存 5 分制(5、4…)两种口径。
     *
     * @param exportList 同一道题目的选项统计明细
     * @return 百分制综合得分,无有效项数时返回 "0.00"
     */
    private String calcMydScore(List<ServiceSubtaskDetailRatioExport> exportList) {
        if (CollectionUtils.isEmpty(exportList)) return "0.00";
        BigDecimal scoreSum = BigDecimal.ZERO;   //分子:Σ(选项分值 × 项数)
        BigDecimal countSum = BigDecimal.ZERO;   //分母:Σ项数
        BigDecimal maxScore = null;              //本题最高分值,用于归一化
        for (ServiceSubtaskDetailRatioExport detail : exportList) {
            //多选题一行是多个选项的分值之和,分母只算 1 项,会把得分算高,不计入综合得分
            if (VALUE_TYPE_MULTIPLE.equals(detail.getValueType())) continue;
            //「不适用」不在公式的五档里,不计入综合得分
            if (isNotApplicable(detail)) continue;
            BigDecimal score = parseDecimal(detail.getScore());
            BigDecimal count = parseDecimal(detail.getCount());
            if (score == null || count == null) continue;
            //0 分(未答/空答)、空数量:既不进分子也不进分母
            if (score.signum() <= 0 || count.signum() <= 0) continue;
            if (maxScore == null || score.compareTo(maxScore) > 0) maxScore = score;
            scoreSum = scoreSum.add(score.multiply(count));
            countSum = countSum.add(count);
        }
        if (maxScore == null || countSum.signum() <= 0) return "0.00";
        return scoreSum.divide(countSum, 6, RoundingMode.HALF_UP)
                .divide(maxScore, 6, RoundingMode.HALF_UP)
                .multiply(BigDecimal.valueOf(100))
                .setScale(2, RoundingMode.HALF_UP)
                .toPlainString();
    }
    /**
     * 是否「不适用」选项
     */
    private boolean isNotApplicable(ServiceSubtaskDetailRatioExport detail) {
        return containsNotApplicable(detail.getOptionresult()) || containsNotApplicable(detail.getTargetvalue());
    }
    private boolean containsNotApplicable(String optionText) {
        if (StringUtils.isEmpty(optionText)) return false;
        String text = optionText.trim();
        return text.contains("不适用") || text.contains("未答") || text.contains("拒答");
    }
    /**
     * 字符串转 BigDecimal,空值/非法值返回 null
     */
    private BigDecimal parseDecimal(String value) {
        if (StringUtils.isEmpty(value)) return null;
        try {
            return new BigDecimal(value.trim());
        } catch (NumberFormatException e) {
            return null;
        }
    }
}
smartor/src/main/java/com/smartor/service/impl/ServiceSubtaskServiceImpl.java
@@ -6181,17 +6181,29 @@
     */
    public List<ServiceSubtaskDetailRatioExport> getDetailRatio(SltdMydTotalVO sltdMydTotalVO, List<ServiceSubtaskRes> serviceSubtaskRes) {
        List<ServiceSubtaskDetailRatioExport> serviceSubtaskDetailRatioExports = null;
        if (CollectionUtils.isNotEmpty(sltdMydTotalVO.getServiceTypeList()) && sltdMydTotalVO.getServiceTypeList().contains("2") && (sltdMydTotalVO.getServiceTypeList().contains("6") || sltdMydTotalVO.getServiceTypeList().contains("14"))) {
            //serviceType,即有随访又有满意度,暂时没法统计
        List<String> serviceTypeList = sltdMydTotalVO.getServiceTypeList();
        if (CollectionUtils.isEmpty(serviceTypeList)) {
            log.warn("getDetailRatio 未传 serviceTypeList,无法统计问题占比");
            return serviceSubtaskDetailRatioExports;
        } else if (CollectionUtils.isNotEmpty(sltdMydTotalVO.getServiceTypeList()) && sltdMydTotalVO.getServiceTypeList().contains("2")) {
        }
        // 判断口径:2=随访(按 taskid 统计),6/14=满意度(按 subtask id 统计)
        boolean hasFollowUp = serviceTypeList.contains("2");
        boolean hasSatisfaction = serviceTypeList.contains("6") || serviceTypeList.contains("14");
        if (hasFollowUp && hasSatisfaction) {
            //serviceType,即有随访又有满意度,暂时没法统计
            log.warn("getDetailRatio 暂不支持同时统计随访(2)与满意度(6/14),serviceTypeList={}", serviceTypeList);
            return serviceSubtaskDetailRatioExports;
        }
        if (hasFollowUp) {
            //获取serviceSubtaskList中的所有taskid
            List<Long> taskIds = serviceSubtaskRes.stream().map(ServiceSubtaskRes::getTaskid).filter(ObjectUtils::isNotEmpty).distinct().collect(Collectors.toList());
            serviceSubtaskDetailRatioExports = statQuestionOption(taskIds, sltdMydTotalVO.getStartFinishTime(), sltdMydTotalVO.getEndFinishTime(), sltdMydTotalVO.getStartOutHospTime(), sltdMydTotalVO.getEndOutHospTime(), sltdMydTotalVO.getQuestionContent(), sltdMydTotalVO.getStartScore(), sltdMydTotalVO.getEndScore(), sltdMydTotalVO.getValueType());
        } else if (CollectionUtils.isNotEmpty(sltdMydTotalVO.getServiceTypeList()) && sltdMydTotalVO.getServiceTypeList().contains("6") || sltdMydTotalVO.getServiceTypeList().contains("14")) {
        } else if (hasSatisfaction) {
            //满意度统计 (通过 subtask 的 id 去统计)
            List<Long> subtaskIds = serviceSubtaskRes.stream().map(ServiceSubtaskRes::getId).collect(Collectors.toList());
            serviceSubtaskDetailRatioExports = statQuestionOptionBySubtaskIds(subtaskIds, sltdMydTotalVO.getStartOutHospTime(), sltdMydTotalVO.getEndOutHospTime(), sltdMydTotalVO.getStartFinishTime(), sltdMydTotalVO.getEndFinishTime(), sltdMydTotalVO.getQuestionContent(), sltdMydTotalVO.getStartScore(), sltdMydTotalVO.getEndScore(), sltdMydTotalVO.getValueType());
        } else {
            log.warn("getDetailRatio 暂不支持的 serviceTypeList={}(仅支持 2 或 6/14)", serviceTypeList);
        }
        return serviceSubtaskDetailRatioExports;
    }
smartor/src/main/resources/mapper/smartor/ServiceSubtaskMapper.xml
@@ -3329,6 +3329,14 @@
        service_task t
        INNER JOIN service_subtask s ON s.taskid = t.taskid
        INNER JOIN service_subtask_detail d ON d.sub_id = s.id
        <!-- 去重:同一子任务同一题目只保留最新一条,避免重答/重复回调把同一道题重复计入项数(与 mzMydScoreRank 口径一致) -->
        AND d.id = (
        SELECT MAX(d2.id)
        FROM service_subtask_detail d2
        WHERE d2.sub_id = d.sub_id
        AND d2.questiontext &lt;=&gt; d.questiontext
        AND d2.del_flag = '0'
        )
        INNER JOIN (
        SELECT
        d.questiontext,
@@ -3337,6 +3345,14 @@
        service_task t
        INNER JOIN service_subtask s ON s.taskid = t.taskid
        INNER JOIN service_subtask_detail d ON d.sub_id = s.id
        <!-- 去重:分母(该题有效答卷总数)与明细口径保持一致,保证各选项比例合计 100% -->
        AND d.id = (
        SELECT MAX(d4.id)
        FROM service_subtask_detail d4
        WHERE d4.sub_id = d.sub_id
        AND d4.questiontext &lt;=&gt; d.questiontext
        AND d4.del_flag = '0'
        )
        WHERE 1=1
        <if test="subtaskIds != null and subtaskIds.size() > 0">
            AND s.id IN
@@ -3345,16 +3361,37 @@
            </foreach>
        </if>
        <if test="valueType != null">AND d.value_type &gt;= #{valueType}</if>
        <if test="scoreStart != null">AND d.score = #{scoreStart}</if>
        <if test="scoreStart != null">AND d.score &gt;= #{scoreStart}</if>
        <if test="scoreEnd != null">AND d.score &lt;= #{scoreEnd}</if>
        <if test="questiontext != null  and questiontext != ''">and d.questiontext like concat('%', #{questiontext},
            '%')
        </if>
        AND s.sendstate = 6
        AND d.del_flag=0
        <!-- 分母与明细用同一套时间条件,否则选了日期区间后各选项比例合计不到 100% -->
        <if test="startOutHospTime != null">
            AND date_format(s.endtime,'%y%m%d') &gt;= date_format(#{startOutHospTime},'%y%m%d')
        </if>
        <if test="endOutHospTime != null">
            AND date_format(s.endtime,'%y%m%d') &lt;= date_format(#{endOutHospTime},'%y%m%d')
        </if>
        <if test="startFinishTime != null">
            AND date_format(s.finishtime,'%y%m%d') &gt;= date_format(#{startFinishTime},'%y%m%d')
        </if>
        <if test="endFinishTime != null">
            AND date_format(s.finishtime,'%y%m%d') &lt;= date_format(#{endFinishTime},'%y%m%d')
        </if>
        GROUP BY d.questiontext
        ) t2 ON t2.questiontext = d.questiontext
        WHERE 1=1
        <!-- 去重:明细行同一子任务同一题目只保留最新一条,重答不重复计入项数 -->
        AND d.id = (
        SELECT MAX(d3.id)
        FROM service_subtask_detail d3
        WHERE d3.sub_id = d.sub_id
        AND d3.questiontext &lt;=&gt; d.questiontext
        AND d3.del_flag = '0'
        )
        <if test="subtaskIds != null and subtaskIds.size() > 0">
            AND s.id IN
            <foreach collection="subtaskIds" item="subtaskId" open="(" separator="," close=")">