陈昶聿
6 天以前 e56e937f0b78152cc607ade75b3a0e1b2c1c2c4c
fix: 完成时状态与超时判断区分任务组

finishStateExpr、RyTask 和 RedisMqReceiver 的 isSubtaskDeadline 统一:非任务组按出院时间+send_day+补偿+1天;任务组按 schedule_benchmark 解析基准+day_offset+补偿+1天,不叠加 send_day。
已修改3个文件
213 ■■■■■ 文件已修改
ruoyi-admin/src/main/java/com/ruoyi/web/component/RedisMqReceiver.java 70 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-quartz/src/main/java/com/ruoyi/quartz/task/RyTask.java 70 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
smartor/src/main/resources/mapper/smartor/ServiceSubtaskMapper.xml 73 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/java/com/ruoyi/web/component/RedisMqReceiver.java
@@ -96,6 +96,12 @@
    private ServiceSubtaskPreachformMapper serviceSubtaskPreachformMapper;
    @Autowired
    private ServiceTaskScheduleMapper serviceTaskScheduleMapper;
    @Autowired
    private ServiceTaskScheduleDetailMapper serviceTaskScheduleDetailMapper;
    @Autowired
    private IServiceSubtaskRecordService serviceSubtaskRecordService;
    @Autowired
@@ -1188,21 +1194,65 @@
        if (Objects.isNull(serviceSubtask) || CollectionUtils.isEmpty(serviceSubtaskPreachforms)) {
            return false;
        }
        // 基准时刻:出院时间 + 发送日(sendDay 缺省按 1),并兜底避免 NPE/脏数据
        if (serviceSubtask.getEndtime() == null) {
            log.warn("【isSubtaskDeadline】subid:{} endtime为空,无法计算超时,按未超时处理", serviceSubtask.getId());
            return false;
        }
        ServiceTask serviceTask = serviceTaskMapper.selectServiceTaskByTaskid(serviceSubtask.getTaskid());
        int sendDay = serviceTask != null && serviceTask.getSendDay() != null ? serviceTask.getSendDay().intValue() : 1;
        Date deadline = DateUtils.addDays(serviceSubtask.getEndtime(), sendDay);
        int sumCompensateTime = CollectionUtils.isEmpty(serviceSubtaskPreachforms) ? 0 : serviceSubtaskPreachforms.stream()
        Date deadline;
        if (serviceTask != null && "5".equals(serviceTask.getAppltype())) {
            // 任务组:基准 = schedule_benchmark 解析出的时间 + day_offset,不叠加 send_day
            deadline = resolveTaskGroupDeadlineBase(serviceSubtask);
            if (deadline == null) {
                log.warn("【isSubtaskDeadline】subid:{} 任务组基准时间为空,无法计算超时,按未超时处理", serviceSubtask.getId());
                return false;
            }
        } else {
            // 非任务组:基准 = 出院时间 + 发送日(sendDay 缺省按 1)
            if (serviceSubtask.getEndtime() == null) {
                log.warn("【isSubtaskDeadline】subid:{} endtime为空,无法计算超时,按未超时处理", serviceSubtask.getId());
                return false;
            }
            int sendDay = serviceTask != null && serviceTask.getSendDay() != null ? serviceTask.getSendDay().intValue() : 1;
            deadline = DateUtils.addDays(serviceSubtask.getEndtime(), sendDay);
        }
        int sumCompensateTime = serviceSubtaskPreachforms.stream()
                .mapToInt(x -> NumberUtils.toInt(x.getCompensateTime(), 0))
                .sum();
        sumCompensateTime = sumCompensateTime + 1;
        // 只补偿最后一步自身的补偿时间(天),脏值一律按 0 兜底
        // 补偿时间脏值一律按 0 兜底,最后再加 1 天宽限
        deadline = DateUtils.addDays(deadline, sumCompensateTime);
        // 出院(发送)时间 + 本步补偿时间 小于当前时间 -> 超时
        return deadline.getTime() < new Date().getTime();
    }
    /**
     * 任务组基准时间:按 schedule_benchmark 解析后加 day_offset。
     * 0/其它-自定义时间 1-入院 2-出院 3-就诊 4-入组
     */
    private Date resolveTaskGroupDeadlineBase(ServiceSubtask serviceSubtask) {
        if (serviceSubtask.getScheduleId() == null || serviceSubtask.getScheduleDetailId() == null) {
            return null;
        }
        ServiceTaskSchedule schedule = serviceTaskScheduleMapper.selectServiceTaskScheduleById(serviceSubtask.getScheduleId());
        ServiceTaskScheduleDetail detail = serviceTaskScheduleDetailMapper.selectServiceTaskScheduleDetailById(serviceSubtask.getScheduleDetailId());
        if (schedule == null || detail == null || detail.getDayOffset() == null) {
            return null;
        }
        Date base;
        Integer benchmark = schedule.getScheduleBenchmark() == null ? 0 : schedule.getScheduleBenchmark();
        switch (benchmark) {
            case 1:
                base = serviceSubtask.getStarttime();
                break;
            case 2:
                base = serviceSubtask.getEndtime();
                break;
            case 3:
                base = serviceSubtask.getAdmindate();
                break;
            case 4:
                base = serviceSubtask.getCreateTime();
                break;
            default:
                base = schedule.getScheduleBenchmarkCustomTime();
                break;
        }
        return base == null ? null : DateUtils.addDays(base, detail.getDayOffset());
    }
}
ruoyi-quartz/src/main/java/com/ruoyi/quartz/task/RyTask.java
@@ -145,6 +145,12 @@
    private ServiceSubtaskMapper serviceSubtaskMapper;
    @Autowired
    private ServiceTaskScheduleMapper serviceTaskScheduleMapper;
    @Autowired
    private ServiceTaskScheduleDetailMapper serviceTaskScheduleDetailMapper;
    @Autowired
    private RedisCache redisCache;
    @Autowired
@@ -1932,25 +1938,69 @@
        if (Objects.isNull(serviceSubtask) || CollectionUtils.isEmpty(serviceSubtaskPreachforms)) {
            return false;
        }
        // 基准时刻:出院时间 + 发送日(sendDay 缺省按 1),并兜底避免 NPE/脏数据
        if (serviceSubtask.getEndtime() == null) {
            log.warn("【isSubtaskDeadline】subid:{} endtime为空,无法计算超时,按未超时处理", serviceSubtask.getId());
            return false;
        }
        ServiceTask serviceTask = serviceTaskMapper.selectServiceTaskByTaskid(serviceSubtask.getTaskid());
        int sendDay = serviceTask != null && serviceTask.getSendDay() != null ? serviceTask.getSendDay().intValue() : 1;
        Date deadline = DateUtils.addDays(serviceSubtask.getEndtime(), sendDay);
        int sumCompensateTime = CollectionUtils.isEmpty(serviceSubtaskPreachforms) ? 0 : serviceSubtaskPreachforms.stream()
        Date deadline;
        if (serviceTask != null && "5".equals(serviceTask.getAppltype())) {
            // 任务组:基准 = schedule_benchmark 解析出的时间 + day_offset,不叠加 send_day
            deadline = resolveTaskGroupDeadlineBase(serviceSubtask);
            if (deadline == null) {
                log.warn("【isSubtaskDeadline】subid:{} 任务组基准时间为空,无法计算超时,按未超时处理", serviceSubtask.getId());
                return false;
            }
        } else {
            // 非任务组:基准 = 出院时间 + 发送日(sendDay 缺省按 1)
            if (serviceSubtask.getEndtime() == null) {
                log.warn("【isSubtaskDeadline】subid:{} endtime为空,无法计算超时,按未超时处理", serviceSubtask.getId());
                return false;
            }
            int sendDay = serviceTask != null && serviceTask.getSendDay() != null ? serviceTask.getSendDay().intValue() : 1;
            deadline = DateUtils.addDays(serviceSubtask.getEndtime(), sendDay);
        }
        int sumCompensateTime = serviceSubtaskPreachforms.stream()
                .mapToInt(x -> NumberUtils.toInt(x.getCompensateTime(), 0))
                .sum();
        sumCompensateTime = sumCompensateTime + 1;
        // 只补偿最后一步自身的补偿时间(天),脏值一律按 0 兜底
        // 补偿时间脏值一律按 0 兜底,最后再加 1 天宽限
        deadline = DateUtils.addDays(deadline, sumCompensateTime);
        // 出院(发送)时间 + 本步补偿时间 小于当前时间 -> 超时
        return deadline.getTime() < new Date().getTime();
    }
    /**
     * 任务组基准时间:按 schedule_benchmark 解析后加 day_offset。
     * 0/其它-自定义时间 1-入院 2-出院 3-就诊 4-入组
     */
    private Date resolveTaskGroupDeadlineBase(ServiceSubtask serviceSubtask) {
        if (serviceSubtask.getScheduleId() == null || serviceSubtask.getScheduleDetailId() == null) {
            return null;
        }
        ServiceTaskSchedule schedule = serviceTaskScheduleMapper.selectServiceTaskScheduleById(serviceSubtask.getScheduleId());
        ServiceTaskScheduleDetail detail = serviceTaskScheduleDetailMapper.selectServiceTaskScheduleDetailById(serviceSubtask.getScheduleDetailId());
        if (schedule == null || detail == null || detail.getDayOffset() == null) {
            return null;
        }
        Date base;
        Integer benchmark = schedule.getScheduleBenchmark() == null ? 0 : schedule.getScheduleBenchmark();
        switch (benchmark) {
            case 1:
                base = serviceSubtask.getStarttime();
                break;
            case 2:
                base = serviceSubtask.getEndtime();
                break;
            case 3:
                base = serviceSubtask.getAdmindate();
                break;
            case 4:
                base = serviceSubtask.getCreateTime();
                break;
            default:
                base = schedule.getScheduleBenchmarkCustomTime();
                break;
        }
        return base == null ? null : DateUtils.addDays(base, detail.getDayOffset());
    }
    /**
     * 处理超时随访任务
     * <p>
     * 扫描「出院满 deadlineDay 天仍未完成」的首次随访任务(sendstate=7 + visit_count=1),
smartor/src/main/resources/mapper/smartor/ServiceSubtaskMapper.xml
@@ -258,50 +258,61 @@
    </sql>
    <!-- 超时完成、延期完成逻辑 完成时状态(finish_state)计算:仅当本次更新把 sendstate 置为 6(随访完成)时使用。
         基准截止时间 base = endtime + send_day(缺省1) + (Σ service_subtask_preachform.compensate_time + 1) 天,
         与 RyTask/RedisMqReceiver 的 isSubtaskDeadline 口径保持一致;
         基准截止时间 base:
           非任务组(appltype != 5):endtime + send_day(缺省1) + (Σ service_subtask_preachform.compensate_time + 1) 天;
           任务组(appltype = 5):按 schedule_benchmark 解析基准时间
                                   (1-入院 2-出院 3-就诊 4-入组 0/其它-自定义时间)
                                   + day_offset + (Σ compensate_time + 1) 天(不叠加 send_day);
         与 RyTask/RedisMqReceiver 的 isSubtaskDeadline、selectTimelyRateBatch 口径保持一致;
         delay = COALESCE(service_task.delay_finish_days, 0);
         finish_time = COALESCE(入参 finishtime, 库中 finishtime, NOW()):
           finish_time <= base                                    -> 1 正常完成
           delay > 0 且 finish_time <= base + delay               -> 2 延期完成
           其余(含 delay 为 0 或 NULL、或已超出延期窗口)          -> 3 超时完成
         finishtime 为空时用 NOW() 兜底,保证完成时一定写入状态;
         仅当 endtime 为空导致无法计算时才保持原值不覆盖。 -->
    <sql id="finishStateExpr">
         base 为空(endtime / schedule / day_offset 缺失)时保持原值不覆盖。 -->
    <sql id="finishDeadlineExpr">
        DATE_ADD(
        CASE
        WHEN service_subtask.endtime IS NULL THEN finish_state
            WHEN COALESCE(st.appltype, '') = '5' THEN
                DATE_ADD(
                CASE sts.schedule_benchmark
                    WHEN 1 THEN service_subtask.starttime
                    WHEN 2 THEN service_subtask.endtime
                    WHEN 3 THEN service_subtask.admindate
                    WHEN 4 THEN service_subtask.create_time
                    ELSE sts.schedule_benchmark_custom_time
                END,
                INTERVAL std.day_offset DAY)
            ELSE
                DATE_ADD(service_subtask.endtime, INTERVAL COALESCE(st.send_day, 1) DAY)
        END,
        INTERVAL (COALESCE((SELECT SUM(CAST(COALESCE(NULLIF(TRIM(pf.compensate_time), ''), '0') AS SIGNED))
        FROM service_subtask_preachform pf
        WHERE pf.subid = service_subtask.id AND pf.del_flag = 0), 0) + 1) DAY)
    </sql>
    <sql id="finishStateExpr">
        COALESCE((
        SELECT
        CASE
        WHEN <include refid="finishDeadlineExpr"/> IS NULL THEN NULL
        WHEN COALESCE(#{finishtime,jdbcType=TIMESTAMP}, service_subtask.finishtime, NOW())
        &lt;= DATE_ADD(
        DATE_ADD(
        service_subtask.endtime,
        INTERVAL COALESCE((SELECT st.send_day FROM service_task st
        WHERE st.taskid = service_subtask.taskid AND st.del_flag = '0' LIMIT 1), 1) DAY
        ),
        INTERVAL (COALESCE((SELECT SUM(CAST(COALESCE(NULLIF(TRIM(pf.compensate_time), ''), '0') AS SIGNED))
        FROM service_subtask_preachform pf
        WHERE pf.subid = service_subtask.id AND pf.del_flag = 0), 0) + 1) DAY
        )
        &lt;= <include refid="finishDeadlineExpr"/>
        THEN 1
        WHEN COALESCE((SELECT st.delay_finish_days FROM service_task st
        WHERE st.taskid = service_subtask.taskid AND st.del_flag = '0' LIMIT 1), 0) &gt; 0
        WHEN COALESCE(st.delay_finish_days, 0) &gt; 0
        AND COALESCE(#{finishtime,jdbcType=TIMESTAMP}, service_subtask.finishtime, NOW())
        &lt;= DATE_ADD(
        DATE_ADD(
        DATE_ADD(
        service_subtask.endtime,
        INTERVAL COALESCE((SELECT st.send_day FROM service_task st
        WHERE st.taskid = service_subtask.taskid AND st.del_flag = '0' LIMIT 1), 1) DAY
        ),
        INTERVAL (COALESCE((SELECT SUM(CAST(COALESCE(NULLIF(TRIM(pf.compensate_time), ''), '0') AS SIGNED))
        FROM service_subtask_preachform pf
        WHERE pf.subid = service_subtask.id AND pf.del_flag = 0), 0) + 1) DAY
        ),
        INTERVAL COALESCE((SELECT st.delay_finish_days FROM service_task st
        WHERE st.taskid = service_subtask.taskid AND st.del_flag = '0' LIMIT 1), 0) DAY
        )
        &lt;= DATE_ADD(<include refid="finishDeadlineExpr"/>,
        INTERVAL COALESCE(st.delay_finish_days, 0) DAY)
        THEN 2
        ELSE 3
        END
        FROM (SELECT 1) dummy
        LEFT JOIN service_task st ON st.taskid = service_subtask.taskid
        LEFT JOIN service_task_schedule sts ON sts.id = service_subtask.schedule_id
        LEFT JOIN service_task_schedule_detail std ON std.id = service_subtask.schedule_detail_id
        LIMIT 1
        ), finish_state)
    </sql>
    <!-- 当前执行方式:首次随访 current_preachform 为空时,取 preachform 的第一个执行方式兜底 -->