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()) <= 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 ) <= <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) > 0 WHEN COALESCE(st.delay_finish_days, 0) > 0 AND COALESCE(#{finishtime,jdbcType=TIMESTAMP}, service_subtask.finishtime, NOW()) <= 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 ) <= 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 的第一个执行方式兜底 -->