liusheng
昨天 18f876d637cf1e9b0b264a4586e4a676a21f8166
ruoyi-task/src/main/resources/mapper/task/ServiceSubtaskMapper.xml
@@ -108,6 +108,11 @@
        <result property="loopCount" column="loop_count"/>
        <result property="isManuallyCalled" column="is_manually_called"/>
        <result property="campusid" column="campusid"/>
        <result property="diagType" column="diag_type"/>
        <!-- 2026-11:重要性标记编码(日间手术/日间诊疗/无…),建任务时取自 pat_med_inhosp -->
        <result property="importanceFlagCode" column="importance_flag_code"/>
        <!-- 2026-11:点位编码,建子任务时原样拷自 service_task.point_location -->
        <result property="pointLocation" column="point_location"/>
    </resultMap>
    <resultMap type="com.ruoyi.task.domain.ServiceSubtaskCount" id="ServiceSubtaskResult2">
@@ -243,6 +248,8 @@
               hospitaldistrictname,
               deptcode,
               deptname,
               importance_flag_code,
               point_location,
               stop_state,
               leavehospitaldistrictcode,
               leavehospitaldistrictname,
@@ -372,16 +379,34 @@
    <sql id="filterLeaveDimAnd">
        /* f-v3 */
        <if test="leavehospitaldistrictcodes != null and leavehospitaldistrictcodes.size() > 0">
            AND leavehospitaldistrictcode IN
            AND (
            leavehospitaldistrictcode IN
            <foreach collection="leavehospitaldistrictcodes" item="lvWardCodeItem" open="(" separator="," close=")">
                #{lvWardCodeItem}
            </foreach>
            <!-- 延续护理任务(service_type='99')可以在患者还没出院时生成:
                 这类记录只有「在院维度」(hospitaldistrictcode / deptcode),出院维度是 NULL。
                 NULL 不匹配任何 IN,任务会「提示生成成功、列表里却查不到」,
                 所以出院维度为空时用同一套编码体系的在院维度兜底。 -->
            OR (service_type = '99' AND (leavehospitaldistrictcode IS NULL OR leavehospitaldistrictcode = '')
                AND hospitaldistrictcode IN
                <foreach collection="leavehospitaldistrictcodes" item="lvWardCodeItem" open="(" separator="," close=")">
                    #{lvWardCodeItem}
                </foreach>)
            )
        </if>
        <if test="leaveldeptcodes != null and leaveldeptcodes.size() > 0">
            AND leaveldeptcode IN
            AND (
            leaveldeptcode IN
            <foreach collection="leaveldeptcodes" item="lvDeptCodeItem" open="(" separator="," close=")">
                #{lvDeptCodeItem}
            </foreach>
            OR (service_type = '99' AND (leaveldeptcode IS NULL OR leaveldeptcode = '')
                AND deptcode IN
                <foreach collection="leaveldeptcodes" item="lvDeptCodeItem" open="(" separator="," close=")">
                    #{lvDeptCodeItem}
                </foreach>)
            )
        </if>
    </sql>
@@ -422,20 +447,38 @@
                  or (leaveldeptcodes != null and leaveldeptcodes.size() > 0)">
            AND (
            <if test="leavehospitaldistrictcodes != null and leavehospitaldistrictcodes.size() > 0">
                (
                leavehospitaldistrictcode IN
                <foreach collection="leavehospitaldistrictcodes" item="lvWardCodeItem" open="(" separator="," close=")">
                    #{lvWardCodeItem}
                </foreach>
                <!-- 延续护理任务(service_type='99')可以在患者还没出院时生成:
                     这类记录只有「在院维度」(hospitaldistrictcode / deptcode),出院维度是 NULL。
                     NULL 不匹配任何 IN,任务会「提示生成成功、列表里却查不到」,
                     所以出院维度为空时用同一套编码体系的在院维度兜底。 -->
                OR (service_type = '99' AND (leavehospitaldistrictcode IS NULL OR leavehospitaldistrictcode = '')
                    AND hospitaldistrictcode IN
                    <foreach collection="leavehospitaldistrictcodes" item="lvWardCodeItem" open="(" separator="," close=")">
                        #{lvWardCodeItem}
                    </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="lvDeptCodeItem" open="(" separator="," close=")">
                    #{lvDeptCodeItem}
                </foreach>
                OR (service_type = '99' AND (leaveldeptcode IS NULL OR leaveldeptcode = '')
                    AND deptcode IN
                    <foreach collection="leaveldeptcodes" item="lvDeptCodeItem" open="(" separator="," close=")">
                        #{lvDeptCodeItem}
                    </foreach>)
                )
            </if>
            )
        </if>
@@ -672,6 +715,41 @@
        <if test="operatorNo != null  and operatorNo != ''">and operator_no = #{operatorNo}</if>
        <if test="wechat != null  and wechat != ''">and wechat = #{wechat}</if>
        <if test="hospType != null  and hospType != ''">and hosp_type = #{hospType}</if>
        <!--
          2026-11:重要性标记筛选(对应 service_subtask.importance_flag_code,建任务时取自
          pat_med_inhosp.importance_flag_code)。
            ① 模糊(/patItem 用这个)  importanceFlagCode    → LIKE '%值%'
               传「日间」即可命中「日间手术」「日间诊疗」「无,日间手术」多值行;
               传完整值(「日间手术」/「FH0165.01」)退化为精确匹配。
            ② 精确多值                 importanceFlagCodes → IN
          该列采集时就被拼成**描述串**(ImportFlagEnum 的 desc),多值用英文逗号拼接,
          所以「日间」这类跨取值查询只能靠模糊;描述与 FH0165.xx 编码两种口径都按原值比,
          不做值域归一。
          ⚠️ 同一条件必须同步加到 countByCondition(列表 total / 各状态统计走那条 SQL),
             否则会出现「列表筛出 N 行、总数还是全量」的错配。
        -->
        <if test="importanceFlagCode != null  and importanceFlagCode != ''">
            and importance_flag_code like concat('%', #{importanceFlagCode}, '%')
        </if>
        <if test="importanceFlagCodes != null and importanceFlagCodes.size() > 0">
            AND importance_flag_code IN
            <foreach collection="importanceFlagCodes" item="impFlagItem" open="(" separator="," close=")">
                #{impFlagItem}
            </foreach>
        </if>
        <!--
          2026-11:点位筛选(对应 service_subtask.point_location,建子任务时从 service_task 快照过来)。
          只有一条 IN 条件:单选/多选都在 ServiceSubtaskEntity#getPointLocations() 里归一
          (只传 pointLocation 会自动包成单元素集合),所以不再写等值分支。
          ⚠️ 同一条件必须同步加到 countByCondition(列表 total / 各状态统计走那条 SQL),
             否则会出现「列表筛出 N 行、总数还是全量」的错配。
        -->
        <if test="pointLocations != null and pointLocations.size() > 0">
            AND point_location IN
            <foreach collection="pointLocations" item="pointLocItem" open="(" separator="," close=")">
                #{pointLocItem}
            </foreach>
        </if>
        <if test="libtemplateid != null ">and libtemplateid = #{libtemplateid}</if>
        <if test="libtemplatename != null ">and libtemplatename = #{libtemplatename}</if>
        <if test="openid != null ">and openid = #{openid}</if>
@@ -1112,6 +1190,10 @@
            <if test="hospitaldistrictname != null  and hospitaldistrictname != ''">hospitaldistrictname,</if>
            <if test="deptcode != null  and deptcode != ''">deptcode,</if>
            <if test="deptname != null  and deptname != ''">deptname,</if>
            <!-- 2026-11:重要性标记编码(日间手术/日间诊疗…),建任务时从 pat_med_inhosp 同步 -->
            <if test="importanceFlagCode != null  and importanceFlagCode != ''">importance_flag_code,</if>
            <!-- 2026-11:点位编码,建子任务时从 service_task.point_location 同步 -->
            <if test="pointLocation != null  and pointLocation != ''">point_location,</if>
            <if test="labelstatus != null">labelstatus,</if>
            <if test="updateBy != null">update_by,</if>
            <if test="updateTime != null">update_time,</if>
@@ -1221,6 +1303,8 @@
            <if test="hospitaldistrictname != null  and hospitaldistrictname != ''">#{hospitaldistrictname},</if>
            <if test="deptcode != null  and deptcode != ''">#{deptcode},</if>
            <if test="deptname != null  and deptname != ''">#{deptname},</if>
            <if test="importanceFlagCode != null  and importanceFlagCode != ''">#{importanceFlagCode},</if>
            <if test="pointLocation != null  and pointLocation != ''">#{pointLocation},</if>
            <if test="labelstatus != null">#{labelstatus},</if>
            <if test="updateBy != null">#{updateBy},</if>
            <if test="updateTime != null">#{updateTime},</if>
@@ -1368,6 +1452,11 @@
            <if test="openid != null ">openid = #{openid},</if>
            <if test="longSendTime != null ">long_send_time = #{longSendTime},</if>
            <if test="sendstate != null ">sendstate = #{sendstate},</if>
            <!-- 完成时间不变量(2026-11):状态一旦不是"完成(6)",就显式清空完成时间与完成时状态。
                 上面 finishtime 那一段是 if(finishtime != null),传 null 会被整段跳过,
                 所以"清空"必须在这里用 SQL 的 NULL 写出来;否则 3 已发送 / 5 发送失败 / 7 超时
                 的行会一直带着历史完成时间("超时却有完成时间、及时率虚高"就是这么来的)。 -->
            <if test="sendstate != null and sendstate != 6">finishtime = NULL, finish_state = NULL,</if>
            <if test="sendstate != null and sendstate == 6">finish_state = <include refid="finishStateExpr"/>,</if>
            <if test="serviceType != null ">service_type = #{serviceType},</if>
            <if test="score != null ">score = #{score},</if>
@@ -1414,8 +1503,29 @@
            <if test="scheduleSeq != null ">schedule_seq = #{scheduleSeq},</if>
            <if test="loopCount != null ">loop_count = #{loopCount},</if>
            <if test="isManuallyCalled != null ">is_manually_called = #{isManuallyCalled},</if>
            <!-- 2026-11:点位编码(建子任务时的快照;任务上改点位不会自动回写) -->
            <if test="pointLocation != null  and pointLocation != ''">point_location = #{pointLocation},</if>
        </trim>
        where id = #{id}
    </update>
    <!-- 微信公众号已领取未填写按默认选项完成:只更新当前子任务。
         条件 sendstate=1 + 未提交,保证只处理当前被领取且未填写的子任务,不波及其他任务。 -->
    <update id="updateWechatDefaultComplete" parameterType="com.ruoyi.task.domain.ServiceSubtask">
        update service_subtask
        set sendstate = 6,
            finishtime = #{finishtime},
            result = #{result},
            remark = case
                         when remark is null or remark = '' then #{remark}
                         else concat(remark, ',', #{remark})
                     end,
            update_time = now(),
            finish_state = <include refid="finishStateExpr"/>
        where id = #{id}
          and orgid = #{orgid}
          and sendstate = 1
          and (submit is null or submit != 1)
    </update>
    <update id="updateServiceSubtaskByPatId" parameterType="com.ruoyi.task.domain.ServiceSubtask">
@@ -1474,6 +1584,11 @@
            <if test="openid != null ">openid = #{openid},</if>
            <if test="longSendTime != null ">long_send_time = #{longSendTime},</if>
            <if test="sendstate != null ">sendstate = #{sendstate},</if>
            <!-- 完成时间不变量(2026-11):状态一旦不是"完成(6)",就显式清空完成时间与完成时状态。
                 上面 finishtime 那一段是 if(finishtime != null),传 null 会被整段跳过,
                 所以"清空"必须在这里用 SQL 的 NULL 写出来;否则 3 已发送 / 5 发送失败 / 7 超时
                 的行会一直带着历史完成时间("超时却有完成时间、及时率虚高"就是这么来的)。 -->
            <if test="sendstate != null and sendstate != 6">finishtime = NULL, finish_state = NULL,</if>
            <if test="sendstate != null and sendstate == 6">finish_state = <include refid="finishStateExpr"/>,</if>
            <if test="serviceType != null ">service_type = #{serviceType},</if>
            <if test="score != null ">score = #{score},</if>
@@ -1532,6 +1647,11 @@
            <if test="sex != null">sex = #{sex},</if>
            <if test="delFlag != null">del_flag = #{delFlag},</if>
            <if test="sendstate != null ">sendstate = #{sendstate},</if>
            <!-- 完成时间不变量(2026-11):状态一旦不是"完成(6)",就显式清空完成时间与完成时状态。
                 上面 finishtime 那一段是 if(finishtime != null),传 null 会被整段跳过,
                 所以"清空"必须在这里用 SQL 的 NULL 写出来;否则 3 已发送 / 5 发送失败 / 7 超时
                 的行会一直带着历史完成时间("超时却有完成时间、及时率虚高"就是这么来的)。 -->
            <if test="sendstate != null and sendstate != 6">finishtime = NULL, finish_state = NULL,</if>
            <if test="sendstate != null and sendstate == 6">finish_state = <include refid="finishStateExpr"/>,</if>
            <if test="age != null">age = #{age},</if>
            <if test="sfzh != null">sfzh = #{sfzh},</if>
@@ -1645,6 +1765,11 @@
            <if test="phone != null">phone = #{phone},</if>
            <if test="sex != null">sex = #{sex},</if>
            <if test="sendstate != null ">sendstate = #{sendstate},</if>
            <!-- 完成时间不变量(2026-11):状态一旦不是"完成(6)",就显式清空完成时间与完成时状态。
                 上面 finishtime 那一段是 if(finishtime != null),传 null 会被整段跳过,
                 所以"清空"必须在这里用 SQL 的 NULL 写出来;否则 3 已发送 / 5 发送失败 / 7 超时
                 的行会一直带着历史完成时间("超时却有完成时间、及时率虚高"就是这么来的)。 -->
            <if test="sendstate != null and sendstate != 6">finishtime = NULL, finish_state = NULL,</if>
            <if test="sendstate != null and sendstate == 6">finish_state = <include refid="finishStateExpr"/>,</if>
            <if test="age != null">age = #{age},</if>
            <if test="sfzh != null">sfzh = #{sfzh},</if>
@@ -1806,6 +1931,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,
@@ -1854,6 +1982,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">
@@ -2100,6 +2235,13 @@
                #{hospitaldeptcode}
            </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">
            AND service_type IN
            <foreach collection="serviceType" item="serviceType" open="(" separator=","
@@ -2173,6 +2315,8 @@
    <resultMap type="com.ruoyi.task.domain.ServiceSubtaskStatistic" id="ServiceSubtaskScriptStatisticResult">
        <result property="groupCode" column="group_code"/>
        <result property="groupName" column="group_name"/>
        <result property="taskId" column="taskid"/>
        <result property="taskName" column="task_name"/>
        <result property="dischargeCount" column="discharge_count"/>
        <result property="firstVisitCount" column="first_visit_count"/>
        <result property="againVisitCount" column="again_visit_count"/>
@@ -2225,7 +2369,16 @@
        <!-- 分组键对应的显示名:病区统计时就是这一组的病区名。
             以前这一列是 MAX(hospitaldistrictname),一个组混了多个病区时会随机显示其中一个,
             于是出现“选血液内科病区却显示骨科中心三病区”。 -->
        MAX(IFNULL(leavehospitaldistrictname, hospitaldistrictname)) as group_name,
        <choose>
            <when test="groupKey != null and groupKey == 'taskid'">
                MAX(task_name) as group_name,
            </when>
            <otherwise>
                MAX(IFNULL(leavehospitaldistrictname, hospitaldistrictname)) as group_name,
            </otherwise>
        </choose>
        MAX(taskid) as taskid,
        MAX(task_name) as task_name,
        MAX(drcode) as drcode,
        MAX(drname) as drname,
        MAX(leaveldeptcode) as deptcode,
@@ -3103,6 +3256,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.ruoyi.task.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 &lt; 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,
@@ -3429,6 +3712,25 @@
        <if test="sendname != null and sendname != ''">
            AND sendname LIKE CONCAT('%', #{sendname}, '%')
        </if>
        <!--
          2026-11:重要性标记模糊筛选 —— 必须与 serviceSubtaskWhere 里的同名条件【完全一致】,
          否则 /patItem 的列表与 total/各状态统计会错配(列表筛了、总数没筛)。
          口径见 serviceSubtaskWhere 处的注释。
        -->
        <if test="importanceFlagCode != null  and importanceFlagCode != ''">
            AND importance_flag_code LIKE CONCAT('%', #{importanceFlagCode}, '%')
        </if>
        <!--
          2026-11:点位筛选 —— 必须与 serviceSubtaskWhere 里的同名条件【完全一致】,
          否则 /patItem 的列表与 total/各状态统计会错配(列表筛了、总数没筛)。
          单选/多选归一在 ServiceSubtaskEntity#getPointLocations(),这里只有一条 IN。
        -->
        <if test="pointLocations != null and pointLocations.size() > 0">
            AND point_location IN
            <foreach collection="pointLocations" item="pointLocItem" open="(" separator="," close=")">
                #{pointLocItem}
            </foreach>
        </if>
        <if test="phone != null and phone != ''">
            AND phone = #{phone}
        </if>