liusheng
7 小时以前 c24d16a37523a49080392610357872ce580cbc38
ruoyi-satisfaction/src/main/resources/mapper/satisfaction/SatisfactionMapper.xml
@@ -26,10 +26,10 @@
        </if>
        <if test="type != null and type != ''">and type = #{type}</if>
        <if test="startTime != null">
            and visit_time &gt;= #{startTime}
            and IFNULL(visit_time, IFNULL(finishtime, create_time)) &gt;= #{startTime}
        </if>
        <if test="endTime != null">
            and visit_time &lt; date_add(#{endTime}, interval 1 day)
            and IFNULL(visit_time, IFNULL(finishtime, create_time)) &lt; date_add(#{endTime}, interval 1 day)
        </if>
        <if test="libTemplateIds != null and libTemplateIds.size() > 0">
            and libtemplateid IN
@@ -51,9 +51,29 @@
                OR
            </if>
            <if test="deptcodes != null and deptcodes.size() > 0">
                deptcode IN
                leaveldeptcode IN
                <foreach collection="deptcodes" item="deptcode" open="(" separator="," close=")">
                    #{deptcode}
                </foreach>
            </if>
            )
        </if>
        <!-- 离院满意度:按离院科室/病区过滤(科室 OR 病区,与页面口径一致) -->
        <if test="(leavehospitaldistrictcodes != null and leavehospitaldistrictcodes.size() > 0) or (leaveldeptcodes != null and leaveldeptcodes.size() > 0)">
            AND (
            <if test="leavehospitaldistrictcodes != null and leavehospitaldistrictcodes.size() > 0">
                leavehospitaldistrictcode IN
                <foreach collection="leavehospitaldistrictcodes" item="leavehospitaldistrictcode" open="(" separator="," close=")">
                    #{leavehospitaldistrictcode}
                </foreach>
            </if>
            <if test="(leavehospitaldistrictcodes != null and leavehospitaldistrictcodes.size() > 0) and (leaveldeptcodes != null and leaveldeptcodes.size() > 0)">
                OR
            </if>
            <if test="leaveldeptcodes != null and leaveldeptcodes.size() > 0">
                leaveldeptcode IN
                <foreach collection="leaveldeptcodes" item="leaveldeptcode" open="(" separator="," close=")">
                    #{leaveldeptcode}
                </foreach>
            </if>
            )
@@ -67,18 +87,57 @@
        from service_subtask
        where del_flag = 0
        <if test="questionType != null">and type = #{questionType}</if>
        <if test="hospitaldistrictcodes != null and hospitaldistrictcodes.size() > 0">
            AND leavehospitaldistrictcode IN
            <foreach collection="hospitaldistrictcodes" item="hospitaldistrictcode" open="(" separator=","
                     close=")">
                #{hospitaldistrictcode}
            </foreach>
        <!--
            2026-10-21 修正(南华 /statistics 查不到数据):
            原来是「leavehospitaldistrictcode IN(...) AND deptcode IN(...)」两条独立条件,
            而满意度子任务(service_subtask.type=2 + service_type 6/14/15/16)
            通常只写 deptcode、leavehospitaldistrictcode 为 NULL(南华 3803 条全部为 NULL),
            两个条件永远不可能同时满足 → 该方法返回空 → 后面 hasVoice/hasQuestionnaire 都为 false
            → 整个统计接口返回空。
            这里改成与 selectSatisfactionSendCount / selectSatisfactionFillPersonCountByQuestion
            完全一致的「科室 OR 病区」口径(同一个页面,四处 SQL 必须同口径,否则会出现
            "有类型没数据"或"有数据没类型"的割裂)。
        -->
        <if test="(hospitaldistrictcodes != null and hospitaldistrictcodes.size() > 0) or (deptcodes != null and deptcodes.size() > 0)">
            AND (
            <if test="hospitaldistrictcodes != null and hospitaldistrictcodes.size() > 0">
                leavehospitaldistrictcode IN
                <foreach collection="hospitaldistrictcodes" item="hospitaldistrictcode" open="("
                         separator="," close=")">
                    #{hospitaldistrictcode}
                </foreach>
            </if>
            <if test="hospitaldistrictcodes != null and hospitaldistrictcodes.size() > 0 and deptcodes != null and deptcodes.size() > 0">
                OR
            </if>
            <if test="deptcodes != null and deptcodes.size() > 0">
                leaveldeptcode IN
                <foreach collection="deptcodes" item="deptcode" open="(" separator="," close=")">
                    #{deptcode}
                </foreach>
            </if>
            )
        </if>
        <if test="deptcodes != null and deptcodes.size() > 0">
            AND deptcode IN
            <foreach collection="deptcodes" item="deptcode" open="(" separator="," close=")">
                #{deptcode}
            </foreach>
        <!-- 离院满意度:按离院科室/病区过滤(科室 OR 病区,与页面口径一致) -->
        <if test="(leavehospitaldistrictcodes != null and leavehospitaldistrictcodes.size() > 0) or (leaveldeptcodes != null and leaveldeptcodes.size() > 0)">
            AND (
            <if test="leavehospitaldistrictcodes != null and leavehospitaldistrictcodes.size() > 0">
                leavehospitaldistrictcode IN
                <foreach collection="leavehospitaldistrictcodes" item="leavehospitaldistrictcode" open="(" separator="," close=")">
                    #{leavehospitaldistrictcode}
                </foreach>
            </if>
            <if test="(leavehospitaldistrictcodes != null and leavehospitaldistrictcodes.size() > 0) and (leaveldeptcodes != null and leaveldeptcodes.size() > 0)">
                OR
            </if>
            <if test="leaveldeptcodes != null and leaveldeptcodes.size() > 0">
                leaveldeptcode IN
                <foreach collection="leaveldeptcodes" item="leaveldeptcode" open="(" separator="," close=")">
                    #{leaveldeptcode}
                </foreach>
            </if>
            )
        </if>
        <if test="serviceTypes != null and serviceTypes.size() > 0">
            AND service_type IN
@@ -86,9 +145,83 @@
                #{serviceType}
            </foreach>
        </if>
        <!--
            时间口径:满意度子任务(type=2)的 visit_time 经常为空(南华 3803 条全为 NULL),
            只按 visit_time 过滤会把这些数据整批排除。这里按
            visit_time -> finishtime -> create_time 依次兜底,与其余三条统计 SQL 保持一致。
        -->
        <if test="startTime != null and endTime != null">
            AND visit_time &gt;= #{startTime}
            AND visit_time &lt; date_add(#{endTime}, interval 1 day)
            AND IFNULL(visit_time, IFNULL(finishtime, create_time)) &gt;= #{startTime}
            AND IFNULL(visit_time, IFNULL(finishtime, create_time)) &lt; date_add(#{endTime}, interval 1 day)
        </if>
    </select>
    <!-- ================================================
         本次条件下「真正下发过任务」的模板 id(去重)
         用途:把题库题目(svy_lib_template_script / ivr_liba_template_script)
         裁剪成本期真正在用的模板,避免「题库里挂着一堆从来没下发过的历史模板」
         导致 /statistics 多出一整批 0 分区间的行(实测 69 行 → 应只有 25 行)。
         ⚠️ 条件必须与 selectSatisfactionSubtaskTypes 逐条一致(只是多了 type 与 libtemplateid 两列),
            否则会出现「有类型的模板被裁掉 / 裁剪后没有数据」的割裂。
         ================================================ -->
    <select id="selectSatisfactionActiveTemplateIds" resultType="java.lang.Long">
        select distinct libtemplateid
        from service_subtask
        where del_flag = 0
        and libtemplateid is not null
        <if test="type != null and type != ''">and type = #{type}</if>
        <if test="(q.hospitaldistrictcodes != null and q.hospitaldistrictcodes.size() > 0) or (q.deptcodes != null and q.deptcodes.size() > 0)">
            AND (
            <if test="q.hospitaldistrictcodes != null and q.hospitaldistrictcodes.size() > 0">
                leavehospitaldistrictcode IN
                <foreach collection="q.hospitaldistrictcodes" item="hospitaldistrictcode" open="("
                         separator="," close=")">
                    #{hospitaldistrictcode}
                </foreach>
            </if>
            <if test="q.hospitaldistrictcodes != null and q.hospitaldistrictcodes.size() > 0 and q.deptcodes != null and q.deptcodes.size() > 0">
                OR
            </if>
            <if test="q.deptcodes != null and q.deptcodes.size() > 0">
                leaveldeptcode IN
                <foreach collection="q.deptcodes" item="deptcode" open="(" separator="," close=")">
                    #{deptcode}
                </foreach>
            </if>
            )
        </if>
        <!-- 离院满意度:按离院科室/病区过滤(科室 OR 病区,与页面口径一致) -->
        <if test="(q.leavehospitaldistrictcodes != null and q.leavehospitaldistrictcodes.size() > 0) or (q.leaveldeptcodes != null and q.leaveldeptcodes.size() > 0)">
            AND (
            <if test="q.leavehospitaldistrictcodes != null and q.leavehospitaldistrictcodes.size() > 0">
                leavehospitaldistrictcode IN
                <foreach collection="q.leavehospitaldistrictcodes" item="leavehospitaldistrictcode" open="(" separator="," close=")">
                    #{leavehospitaldistrictcode}
                </foreach>
            </if>
            <if test="(q.leavehospitaldistrictcodes != null and q.leavehospitaldistrictcodes.size() > 0) and (q.leaveldeptcodes != null and q.leaveldeptcodes.size() > 0)">
                OR
            </if>
            <if test="q.leaveldeptcodes != null and q.leaveldeptcodes.size() > 0">
                leaveldeptcode IN
                <foreach collection="q.leaveldeptcodes" item="leaveldeptcode" open="(" separator="," close=")">
                    #{leaveldeptcode}
                </foreach>
            </if>
            )
        </if>
        <if test="q.serviceTypes != null and q.serviceTypes.size() > 0">
            AND service_type IN
            <foreach collection="q.serviceTypes" item="serviceType" open="(" separator="," close=")">
                #{serviceType}
            </foreach>
        </if>
        <!-- 时间口径:visit_time -> finishtime -> create_time 依次兜底,与其余四条统计 SQL 保持一致 -->
        <if test="q.startTime != null and q.endTime != null">
            AND IFNULL(visit_time, IFNULL(finishtime, create_time)) &gt;= #{q.startTime}
            AND IFNULL(visit_time, IFNULL(finishtime, create_time)) &lt; date_add(#{q.endTime}, interval 1 day)
        </if>
    </select>
@@ -303,10 +436,10 @@
        and d.del_flag=0
        <!-- 分母必须和明细用同一套时间条件,否则选了时间范围后比例会按全量历史被稀释 -->
        <if test="startOutHospTime != null">
            AND date_format(s.endtime,'%y%m%d') &gt;= date_format(#{startOutHospTime},'%y%m%d')
            AND date_format(IFNULL(s.endtime, IFNULL(s.finishtime, s.create_time)),'%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')
            AND date_format(IFNULL(s.endtime, IFNULL(s.finishtime, s.create_time)),'%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')
@@ -340,10 +473,10 @@
        AND s.sendstate = 6
        and d.del_flag=0
        <if test="startOutHospTime != null">
            AND date_format(s.endtime,'%y%m%d') &gt;= date_format(#{startOutHospTime},'%y%m%d')
            AND date_format(IFNULL(s.endtime, IFNULL(s.finishtime, s.create_time)),'%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')
            AND date_format(IFNULL(s.endtime, IFNULL(s.finishtime, s.create_time)),'%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')
@@ -371,8 +504,8 @@
        MAX(t.task_name) AS taskName,
        d.asrtext AS optionresult,
        d.score AS score,
        MAX(s.deptcode) AS deptcode,
        MAX(s.deptname) AS deptname,
        MAX(s.leaveldeptcode) AS deptcode,
        MAX(s.leaveldeptname) AS deptname,
        MAX(s.leavehospitaldistrictcode) AS leavehospitaldistrictcode,
        MAX(s.leavehospitaldistrictname) AS leavehospitaldistrictname,
        d.targetvalue AS targetvalue,
@@ -430,10 +563,10 @@
        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')
            AND date_format(IFNULL(s.endtime, IFNULL(s.finishtime, s.create_time)),'%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')
            AND date_format(IFNULL(s.endtime, IFNULL(s.finishtime, s.create_time)),'%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')
@@ -473,10 +606,10 @@
            '%')
        </if>
        <if test="startOutHospTime != null">
            AND date_format(s.endtime,'%y%m%d') &gt;= date_format(#{startOutHospTime},'%y%m%d')
            AND date_format(IFNULL(s.endtime, IFNULL(s.finishtime, s.create_time)),'%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')
            AND date_format(IFNULL(s.endtime, IFNULL(s.finishtime, s.create_time)),'%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')
@@ -500,8 +633,8 @@
    <select id="statQuestionOptionByKsOrBq" resultType="com.ruoyi.satisfaction.domain.dto.QuestionCountKsOrBqDTO"
            parameterType="com.ruoyi.satisfaction.domain.vo.QuestionCountKsOrBqVO">
        SELECT
        s.deptname AS deptname,
        s.leavehospitaldistrictname AS leavehospitaldistrictname,
        IFNULL(s.leaveldeptname, s.deptname) AS deptname,
        IFNULL(s.leavehospitaldistrictname, s.hospitaldistrictname) AS leavehospitaldistrictname,
        d.questiontext AS questionText,
        d.targetvalue AS targetvalue, -- 所有选项拼接
        d.asrtext AS asrtext, -- 用户实际选中的选项
@@ -530,29 +663,59 @@
                </foreach>
            </if>
            <if test=" leaveldeptcodes != null and leaveldeptcodes.size()>0">
                AND deptcode IN
                AND leaveldeptcode IN
                <foreach collection="leaveldeptcodes" item="leaveldeptcode" open="(" separator=","
                         close=")">
                    #{leaveldeptcode}
                </foreach>
            </if>
            <!-- 在院满意度:按在院病区/在院科室列过滤(只在传了这组参数时生效) -->
            <if test="hospitaldistrictcodes != null and hospitaldistrictcodes.size() > 0">
                AND hospitaldistrictcode IN
                <foreach collection="hospitaldistrictcodes" item="hospitaldistrictcode" open="("
                         separator=","
                         close=")">
                    #{hospitaldistrictcode}
                </foreach>
            </if>
            <if test="deptcodes != null and deptcodes.size() > 0">
                AND deptcode IN
                <foreach collection="deptcodes" item="deptcode" open="(" separator=","
                         close=")">
                    #{deptcode}
                </foreach>
            </if>
        </if>
        <!--
            2026-10-21 修正:原来 deptOrDistrict==2 时写的是
              「AND leavehospitaldistrictcode IN (...) OR deptcode IN (...)」,
            没有括号 —— SQL 里 AND 优先级高于 OR,实际等价于
              (全部 AND 条件 AND 病区 IN ...) OR 科室 IN ...
            于是「科室命中」那一支完全绕过了 service_type / 日期 / del_flag 等所有条件,
            南华(病区为空、按科室查)会捞回全库历史数据 → 数据明显不对。
            这里补齐括号,让 2 = 科室 OR 病区。
        -->
        <if test="deptOrDistrict ==2">
            AND (
            <if test="leavehospitaldistrictcodes != null and leavehospitaldistrictcodes.size() > 0">
                AND leavehospitaldistrictcode IN
                leavehospitaldistrictcode IN
                <foreach collection="leavehospitaldistrictcodes" item="leavehospitaldistrictcode" open="("
                         separator=","
                         close=")">
                    #{leavehospitaldistrictcode}
                </foreach>
            </if>
            <if test="leavehospitaldistrictcodes != null and leavehospitaldistrictcodes.size() > 0 and leaveldeptcodes != null and leaveldeptcodes.size() > 0">
                OR
            </if>
            <if test=" leaveldeptcodes != null and leaveldeptcodes.size()>0">
                OR deptcode IN
                leaveldeptcode IN
                <foreach collection="leaveldeptcodes" item="leaveldeptcode" open="(" separator=","
                         close=")">
                    #{leaveldeptcode}
                </foreach>
            </if>
            )
        </if>
    </select>
@@ -571,8 +734,8 @@
                s.leavehospitaldistrictname AS name,
            </when>
            <otherwise>
                s.deptcode AS code,
                s.deptname AS name,
                s.leaveldeptcode AS code,
                s.leaveldeptname AS name,
            </otherwise>
        </choose>
        CAST(d.score AS DECIMAL(10,4)) AS w
@@ -625,10 +788,10 @@
                </if>
            </when>
            <otherwise>
                AND s.deptcode IS NOT NULL AND s.deptcode &lt;&gt; ''
                AND s.deptname IS NOT NULL AND s.deptname &lt;&gt; ''
                AND s.leaveldeptcode IS NOT NULL AND s.leaveldeptcode &lt;&gt; ''
                AND s.leaveldeptname IS NOT NULL AND s.leaveldeptname &lt;&gt; ''
                <if test="deptCodes != null and deptCodes.size() > 0">
                    AND s.deptcode IN
                    AND s.leaveldeptcode IN
                    <foreach collection="deptCodes" item="deptCode" open="(" separator="," close=")">
                        #{deptCode}
                    </foreach>
@@ -661,10 +824,10 @@
        </if>
        <if test="type != null and type != ''">and ss.type = #{type}</if>
        <if test="startTime != null">
            and ss.visit_time &gt;= #{startTime}
            and IFNULL(ss.visit_time, IFNULL(ss.finishtime, ss.create_time)) &gt;= #{startTime}
        </if>
        <if test="endTime != null">
            and ss.visit_time &lt; date_add(#{endTime}, interval 1 day)
            and IFNULL(ss.visit_time, IFNULL(ss.finishtime, ss.create_time)) &lt; date_add(#{endTime}, interval 1 day)
        </if>
        <if test="libTemplateIds != null and libTemplateIds.size() > 0">
            and ss.libtemplateid IN
@@ -686,9 +849,29 @@
                OR
            </if>
            <if test="deptcodes != null and deptcodes.size() > 0">
                ss.deptcode IN
                ss.leaveldeptcode IN
                <foreach collection="deptcodes" item="deptcode" open="(" separator="," close=")">
                    #{deptcode}
                </foreach>
            </if>
            )
        </if>
        <!-- 离院满意度:按离院科室/病区过滤(科室 OR 病区,与页面口径一致) -->
        <if test="(leavehospitaldistrictcodes != null and leavehospitaldistrictcodes.size() > 0) or (leaveldeptcodes != null and leaveldeptcodes.size() > 0)">
            AND (
            <if test="leavehospitaldistrictcodes != null and leavehospitaldistrictcodes.size() > 0">
                ss.leavehospitaldistrictcode IN
                <foreach collection="leavehospitaldistrictcodes" item="leavehospitaldistrictcode" open="(" separator="," close=")">
                    #{leavehospitaldistrictcode}
                </foreach>
            </if>
            <if test="(leavehospitaldistrictcodes != null and leavehospitaldistrictcodes.size() > 0) and (leaveldeptcodes != null and leaveldeptcodes.size() > 0)">
                OR
            </if>
            <if test="leaveldeptcodes != null and leaveldeptcodes.size() > 0">
                ss.leaveldeptcode IN
                <foreach collection="leaveldeptcodes" item="leaveldeptcode" open="(" separator="," close=")">
                    #{leaveldeptcode}
                </foreach>
            </if>
            )
@@ -716,10 +899,10 @@
        </if>
        <if test="type != null and type != ''">and ss.type = #{type}</if>
        <if test="startTime != null">
            and ss.visit_time &gt;= #{startTime}
            and IFNULL(ss.visit_time, IFNULL(ss.finishtime, ss.create_time)) &gt;= #{startTime}
        </if>
        <if test="endTime != null">
            and ss.visit_time &lt; date_add(#{endTime}, interval 1 day)
            and IFNULL(ss.visit_time, IFNULL(ss.finishtime, ss.create_time)) &lt; date_add(#{endTime}, interval 1 day)
        </if>
        <if test="libTemplateIds != null and libTemplateIds.size() > 0">
            and ss.libtemplateid IN
@@ -741,9 +924,29 @@
                OR
            </if>
            <if test="deptcodes != null and deptcodes.size() > 0">
                ss.deptcode IN
                ss.leaveldeptcode IN
                <foreach collection="deptcodes" item="deptcode" open="(" separator="," close=")">
                    #{deptcode}
                </foreach>
            </if>
            )
        </if>
        <!-- 离院满意度:按离院科室/病区过滤(科室 OR 病区,与页面口径一致) -->
        <if test="(leavehospitaldistrictcodes != null and leavehospitaldistrictcodes.size() > 0) or (leaveldeptcodes != null and leaveldeptcodes.size() > 0)">
            AND (
            <if test="leavehospitaldistrictcodes != null and leavehospitaldistrictcodes.size() > 0">
                ss.leavehospitaldistrictcode IN
                <foreach collection="leavehospitaldistrictcodes" item="leavehospitaldistrictcode" open="(" separator="," close=")">
                    #{leavehospitaldistrictcode}
                </foreach>
            </if>
            <if test="(leavehospitaldistrictcodes != null and leavehospitaldistrictcodes.size() > 0) and (leaveldeptcodes != null and leaveldeptcodes.size() > 0)">
                OR
            </if>
            <if test="leaveldeptcodes != null and leaveldeptcodes.size() > 0">
                ss.leaveldeptcode IN
                <foreach collection="leaveldeptcodes" item="leaveldeptcode" open="(" separator="," close=")">
                    #{leaveldeptcode}
                </foreach>
            </if>
            )
@@ -767,10 +970,10 @@
        </if>
        <if test="type != null and type != ''">and ss.type = #{type}</if>
        <if test="startTime != null">
            and ss.visit_time &gt;= #{startTime}
            and IFNULL(ss.visit_time, IFNULL(ss.finishtime, ss.create_time)) &gt;= #{startTime}
        </if>
        <if test="endTime != null">
            and ss.visit_time &lt; date_add(#{endTime}, interval 1 day)
            and IFNULL(ss.visit_time, IFNULL(ss.finishtime, ss.create_time)) &lt; date_add(#{endTime}, interval 1 day)
        </if>
        <if test="libTemplateIds != null and libTemplateIds.size() > 0">
            and ss.libtemplateid IN
@@ -792,9 +995,29 @@
                OR
            </if>
            <if test="deptcodes != null and deptcodes.size() > 0">
                ss.deptcode IN
                ss.leaveldeptcode IN
                <foreach collection="deptcodes" item="deptcode" open="(" separator="," close=")">
                    #{deptcode}
                </foreach>
            </if>
            )
        </if>
        <!-- 离院满意度:按离院科室/病区过滤(科室 OR 病区,与页面口径一致) -->
        <if test="(leavehospitaldistrictcodes != null and leavehospitaldistrictcodes.size() > 0) or (leaveldeptcodes != null and leaveldeptcodes.size() > 0)">
            AND (
            <if test="leavehospitaldistrictcodes != null and leavehospitaldistrictcodes.size() > 0">
                ss.leavehospitaldistrictcode IN
                <foreach collection="leavehospitaldistrictcodes" item="leavehospitaldistrictcode" open="(" separator="," close=")">
                    #{leavehospitaldistrictcode}
                </foreach>
            </if>
            <if test="(leavehospitaldistrictcodes != null and leavehospitaldistrictcodes.size() > 0) and (leaveldeptcodes != null and leaveldeptcodes.size() > 0)">
                OR
            </if>
            <if test="leaveldeptcodes != null and leaveldeptcodes.size() > 0">
                ss.leaveldeptcode IN
                <foreach collection="leaveldeptcodes" item="leaveldeptcode" open="(" separator="," close=")">
                    #{leaveldeptcode}
                </foreach>
            </if>
            )
@@ -832,10 +1055,10 @@
        </if>
        <if test="type != null and type != ''">and ss.type = #{type}</if>
        <if test="startTime != null">
            and ss.visit_time &gt;= #{startTime}
            and IFNULL(ss.visit_time, IFNULL(ss.finishtime, ss.create_time)) &gt;= #{startTime}
        </if>
        <if test="endTime != null">
            and ss.visit_time &lt; date_add(#{endTime}, interval 1 day)
            and IFNULL(ss.visit_time, IFNULL(ss.finishtime, ss.create_time)) &lt; date_add(#{endTime}, interval 1 day)
        </if>
        <if test="libTemplateIds != null and libTemplateIds.size() > 0">
            and ss.libtemplateid IN
@@ -857,9 +1080,29 @@
                OR
            </if>
            <if test="deptcodes != null and deptcodes.size() > 0">
                ss.deptcode IN
                ss.leaveldeptcode IN
                <foreach collection="deptcodes" item="deptcode" open="(" separator="," close=")">
                    #{deptcode}
                </foreach>
            </if>
            )
        </if>
        <!-- 离院满意度:按离院科室/病区过滤(科室 OR 病区,与页面口径一致) -->
        <if test="(leavehospitaldistrictcodes != null and leavehospitaldistrictcodes.size() > 0) or (leaveldeptcodes != null and leaveldeptcodes.size() > 0)">
            AND (
            <if test="leavehospitaldistrictcodes != null and leavehospitaldistrictcodes.size() > 0">
                ss.leavehospitaldistrictcode IN
                <foreach collection="leavehospitaldistrictcodes" item="leavehospitaldistrictcode" open="(" separator="," close=")">
                    #{leavehospitaldistrictcode}
                </foreach>
            </if>
            <if test="(leavehospitaldistrictcodes != null and leavehospitaldistrictcodes.size() > 0) and (leaveldeptcodes != null and leaveldeptcodes.size() > 0)">
                OR
            </if>
            <if test="leaveldeptcodes != null and leaveldeptcodes.size() > 0">
                ss.leaveldeptcode IN
                <foreach collection="leaveldeptcodes" item="leaveldeptcode" open="(" separator="," close=")">
                    #{leaveldeptcode}
                </foreach>
            </if>
            )
@@ -918,24 +1161,125 @@
            AND service_type = #{q.serviceType}
        </if>
        <if test="q.startOutHospTime != null">
            AND date_format(endtime,'%y%m%d') &gt;= date_format(#{q.startOutHospTime},'%y%m%d')
            AND date_format(IFNULL(endtime, IFNULL(finishtime, create_time)),'%y%m%d') &gt;= date_format(#{q.startOutHospTime},'%y%m%d')
        </if>
        <if test="q.endOutHospTime != null">
            AND date_format(endtime,'%y%m%d') &lt;= date_format(#{q.endOutHospTime},'%y%m%d')
            AND date_format(IFNULL(endtime, IFNULL(finishtime, create_time)),'%y%m%d') &lt;= date_format(#{q.endOutHospTime},'%y%m%d')
        </if>
        <!--
            2026-10-21 新增:按「完成时间」过滤(页面提交的 startFinishTime / endFinishTime)。
            口径与 statQuestionOptionBySubtaskIds 里的
            date_format(s.finishtime,'%y%m%d') 区间完全等价(参数都是 yyyy-MM-dd 零点),
            但这里刻意写成 finishtime 的裸区间而不用 date_format():
            列上套函数会让索引失效,裸区间才能走 (…, finishtime) 索引。
            上线前这里完全没有时间条件,service_subtask 会被整表扫描,
            下游再把所有历史满意度子任务 id 灌进 IN 列表 —— 这是该接口慢的主因。
        -->
        <if test="q.startFinishTime != null">
            AND finishtime &gt;= #{q.startFinishTime}
        </if>
        <if test="q.endFinishTime != null">
            AND finishtime &lt; DATE_ADD(#{q.endFinishTime}, INTERVAL 1 DAY)
        </if>
        <!--
            科室 / 病区口径(2026-10-21 重构:过滤哪一列由入参决定)
            ==================================================================
            四个参数各自对应一列,谁传进来就过滤谁,不再一律按「离院病区」过滤:
              hospitaldistrictcodes      → hospitaldistrictcode      在院/当前病区
              leavehospitaldistrictcodes → leavehospitaldistrictcode 离院病区
              deptcodes                  → deptcode                 在院/当前科室
              leaveldeptcodes            → leaveldeptcode           离院科室
            同一维度里两个参数都传(少见)时按 OR —— 它们是同一件事的两个列写法;
            病区维度与科室维度之间由 deptOrDistrict 决定:1/3 = AND,2/4 = OR。
            背景:南华这批数据的病区写在 hospitaldistrictcode(实测 40003514 有 409 条),
            leavehospitaldistrictcode 基本为空;只认离院病区会让统计只剩 5 条。
        -->
        <!-- 1/3:病区 AND 科室 -->
        <if test="q.deptOrDistrict==1">
            <if test="q.leavehospitaldistrictcodes != null and q.leavehospitaldistrictcodes.size() > 0">
                AND leavehospitaldistrictcode IN
                <foreach collection="q.leavehospitaldistrictcodes" item="districtcode" open="(" separator="," close=")">
                    #{districtcode}
                </foreach>
            <if test="(q.hospitaldistrictcodes != null and q.hospitaldistrictcodes.size() > 0) or (q.leavehospitaldistrictcodes != null and q.leavehospitaldistrictcodes.size() > 0)">
                AND (
                <trim prefixOverrides="OR">
                    <if test="q.hospitaldistrictcodes != null and q.hospitaldistrictcodes.size() > 0">
                        hospitaldistrictcode IN
                        <foreach collection="q.hospitaldistrictcodes" item="districtcode" open="(" separator="," close=")">
                            #{districtcode}
                        </foreach>
                    </if>
                    <if test="q.leavehospitaldistrictcodes != null and q.leavehospitaldistrictcodes.size() > 0">
                        OR leavehospitaldistrictcode IN
                        <foreach collection="q.leavehospitaldistrictcodes" item="districtcode" open="(" separator="," close=")">
                            #{districtcode}
                        </foreach>
                    </if>
                </trim>
                )
            </if>
            <if test="(q.deptcodes != null and q.deptcodes.size() > 0) or (q.leaveldeptcodes != null and q.leaveldeptcodes.size() > 0)">
                AND (
                <trim prefixOverrides="OR">
                    <if test="q.deptcodes != null and q.deptcodes.size() > 0">
                        deptcode IN
                        <foreach collection="q.deptcodes" item="deptcode" open="(" separator="," close=")">
                            #{deptcode}
                        </foreach>
                    </if>
                    <if test="q.leaveldeptcodes != null and q.leaveldeptcodes.size() > 0">
                        OR leaveldeptcode IN
                        <foreach collection="q.leaveldeptcodes" item="deptcode" open="(" separator="," close=")">
                            #{deptcode}
                        </foreach>
                    </if>
                </trim>
                )
            </if>
        </if>
        <if test="q.leaveldeptcodes != null and q.leaveldeptcodes.size() > 0">
            AND deptcode IN
            <foreach collection="q.leaveldeptcodes" item="deptcode" open="(" separator="," close=")">
                #{deptcode}
            </foreach>
        <!-- 2/4:病区 OR 科室 -->
        <if test="q.deptOrDistrict==2">
            <if test="(q.hospitaldistrictcodes != null and q.hospitaldistrictcodes.size() > 0) or (q.leavehospitaldistrictcodes != null and q.leavehospitaldistrictcodes.size() > 0) or (q.deptcodes != null and q.deptcodes.size() > 0) or (q.leaveldeptcodes != null and q.leaveldeptcodes.size() > 0)">
                AND (
                <trim prefixOverrides="OR">
                    <if test="q.hospitaldistrictcodes != null and q.hospitaldistrictcodes.size() > 0">
                        hospitaldistrictcode IN
                        <foreach collection="q.hospitaldistrictcodes" item="districtcode" open="(" separator="," close=")">
                            #{districtcode}
                        </foreach>
                    </if>
                    <if test="q.leavehospitaldistrictcodes != null and q.leavehospitaldistrictcodes.size() > 0">
                        OR leavehospitaldistrictcode IN
                        <foreach collection="q.leavehospitaldistrictcodes" item="districtcode" open="(" separator="," close=")">
                            #{districtcode}
                        </foreach>
                    </if>
                    <if test="q.deptcodes != null and q.deptcodes.size() > 0">
                        OR deptcode IN
                        <foreach collection="q.deptcodes" item="deptcode" open="(" separator="," close=")">
                            #{deptcode}
                        </foreach>
                    </if>
                    <if test="q.leaveldeptcodes != null and q.leaveldeptcodes.size() > 0">
                        OR leaveldeptcode IN
                        <foreach collection="q.leaveldeptcodes" item="deptcode" open="(" separator="," close=")">
                            #{deptcode}
                        </foreach>
                    </if>
                </trim>
                )
            </if>
        </if>
        <!-- 其它取值:保持旧行为,只看科室(列同样按入参区分) -->
        <if test="q.deptOrDistrict != null and q.deptOrDistrict != '1' and q.deptOrDistrict != '2'">
            <if test="q.deptcodes != null and q.deptcodes.size() > 0">
                AND deptcode IN
                <foreach collection="q.deptcodes" item="deptcode" open="(" separator="," close=")">
                    #{deptcode}
                </foreach>
            </if>
            <if test="q.leaveldeptcodes != null and q.leaveldeptcodes.size() > 0">
                AND leaveldeptcode IN
                <foreach collection="q.leaveldeptcodes" item="deptcode" open="(" separator="," close=")">
                    #{deptcode}
                </foreach>
            </if>
        </if>
    </select>