陈昶聿
2026-07-01 af3ec57db40f80fba5903aaedae5e66bf46a7ed9
【景宁】任务组开发
已修改11个文件
已添加11个文件
1697 ■■■■■ 文件已修改
ruoyi-quartz/src/main/java/com/ruoyi/quartz/task/RyTask.java 49 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
smartor/src/main/java/com/smartor/common/ScheduleSubtaskBuilder.java 193 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
smartor/src/main/java/com/smartor/domain/AppendPatientsReq.java 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
smartor/src/main/java/com/smartor/domain/PatMedInhosp.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
smartor/src/main/java/com/smartor/domain/ServiceSubtask.java 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
smartor/src/main/java/com/smartor/domain/ServiceTaskSchedule.java 118 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
smartor/src/main/java/com/smartor/domain/ServiceTaskScheduleDetail.java 102 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
smartor/src/main/java/com/smartor/domain/ServiceTaskScheduleVO.java 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
smartor/src/main/java/com/smartor/domain/ServiceTaskVO.java 12 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
smartor/src/main/java/com/smartor/domain/ServiceTaskdept.java 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
smartor/src/main/java/com/smartor/domain/ServiceTaskdiag.java 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
smartor/src/main/java/com/smartor/mapper/ServiceSubtaskMapper.java 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
smartor/src/main/java/com/smartor/mapper/ServiceTaskScheduleDetailMapper.java 67 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
smartor/src/main/java/com/smartor/mapper/ServiceTaskScheduleMapper.java 59 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
smartor/src/main/java/com/smartor/service/IServiceTaskScheduleService.java 60 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
smartor/src/main/java/com/smartor/service/impl/ServiceSubtaskServiceImpl.java 148 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
smartor/src/main/java/com/smartor/service/impl/ServiceTaskScheduleServiceImpl.java 356 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
smartor/src/main/resources/mapper/smartor/PatMedInhospMapper.xml 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
smartor/src/main/resources/mapper/smartor/ServiceOutPathMapper.xml 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
smartor/src/main/resources/mapper/smartor/ServiceSubtaskMapper.xml 39 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
smartor/src/main/resources/mapper/smartor/ServiceTaskScheduleDetailMapper.xml 186 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
smartor/src/main/resources/mapper/smartor/ServiceTaskScheduleMapper.xml 176 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-quartz/src/main/java/com/ruoyi/quartz/task/RyTask.java
@@ -179,6 +179,9 @@
    @Autowired
    private SysConfigMapper sysConfigMapper;
    @Autowired
    private IServiceTaskScheduleService serviceTaskScheduleService;
    public void ryMultipleParams(String s, Boolean b, Long l, Double d, Integer i) {
        System.out.println(StringUtils.format("执行多参方法: å­—符串类型{},布尔类型{},长整型{},浮点型{},整形{}", s, b, l, d, i));
    }
@@ -559,20 +562,20 @@
        }
    }
    /**
     * å¤„理患者信息,进入子任务表
     */
    public void dealOutHospInfoByDimension() {
        String config = configService.selectConfigByKey("visit.early.day");
        if (port.equals("8095")) {
            //出院表
            iPatMedInhospService.dealOutHospInfoByDimension(config);
            //门诊表(新华的暂时不做门诊随访)
            if (!active.equals("xh")) {
                //iPatMedOuthospService.dealOutpatientInfo();
            }
        }
    }
//    /**
//     * å¤„理患者信息,进入子任务表
//     */
//    public void dealOutHospInfoByDimension() {
//        String config = configService.selectConfigByKey("visit.early.day");
//        if (port.equals("8095")) {
//            //出院表
//            iPatMedInhospService.dealOutHospInfoByDimension(config);
//            //门诊表(新华的暂时不做门诊随访)
//            if (!active.equals("xh")) {
//                //iPatMedOuthospService.dealOutpatientInfo();
//            }
//        }
//    }
    /**
@@ -1497,4 +1500,22 @@
        int result = QwenLLMUtil.matchRegex(questionText, voiceText, value, regexText);
        return result;
    }
    /**
     * ä»»åŠ¡ç»„ï¼ˆappltype=5)循环任务滚动调度。
     * <p>
     * æ‰«ææ‰€æœ‰ schedule_style=1 çš„计划,若本轮 subtask å…¨éƒ¨ finalize(sendstate âˆ‰ 1/2/3),
     * åˆ™å…‹éš†æœ¬è½®æ‚£è€…到下一轮,推进 current_loop_count ä¸Ž visit_time。
     * <p>
     * å»ºè®®æ¯å°æ—¶æˆ–每半小时执行一次,避免轮次积压。
     */
    public void rollTaskGroupCycle() {
        log.info("【rollTaskGroupCycle】开始扫描循环任务组");
        try {
            int rolled = serviceTaskScheduleService.rollCycleTasks();
            log.info("【rollTaskGroupCycle】本次推进 {} ä¸ªè®¡åˆ’", rolled);
        } catch (Exception e) {
            log.error("【rollTaskGroupCycle】滚动调度异常", e);
        }
    }
}
smartor/src/main/java/com/smartor/common/ScheduleSubtaskBuilder.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,193 @@
package com.smartor.common;
import com.alibaba.fastjson2.JSON;
import com.ruoyi.common.exception.base.BaseException;
import com.ruoyi.common.utils.DateUtils;
import com.smartor.domain.*;
import com.smartor.mapper.ServiceSubtaskMapper;
import com.smartor.mapper.ServiceSubtaskPreachformMapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.Map;
/**
 * ä»»åŠ¡ç»„ï¼ˆappltype=5)subtask ç”Ÿæˆå™¨
 * <p>
 * å•一职责:根据 schedule + detailList ä¸ºæ¯ä¸ªæ‚£è€…派生 subtask。
 * åŒæ—¶æœåŠ¡ä¸¤æ¡è·¯å¾„ï¼š
 * (a) å‰ç«¯æ‰‹åŠ¨å‹¾é€‰æ‚£è€…ï¼ˆinsertOrUpdateTask) â€”— æ—§ 6 å‚入口;
 * (b) HIS è‡ªåŠ¨çº³å…¥æ–°å‡ºé™¢æ‚£è€…ï¼ˆPatMedInhospServiceImpl.writeInSubTask) â€”— æ–° 5 å‚入口。
 * ä¸¤æ¡è·¯å¾„统一从 baseSubtask ä¸Šå–基准日字段,避免生成器与 PatTaskRelevance/ServiceTaskVO è€¦åˆã€‚
 *
 * @author smartor
 * @date 2026-06-30
 */
@Slf4j
@Component
public class ScheduleSubtaskBuilder {
    @Autowired
    private ServiceSubtaskMapper serviceSubtaskMapper;
    @Autowired
    private ServiceSubtaskPreachformMapper serviceSubtaskPreachformMapper;
    /**
     * ä¸ºå•个患者派生 subtask(appltype=5)—— è§£è€¦å…¥å£ï¼ˆè‡ªåŠ¨çº³å…¥ & æ‰‹åŠ¨å‹¾é€‰ç»Ÿä¸€èµ°è¿™é‡Œï¼‰ã€‚
     * <p>
     * éžå¾ªçŽ¯ï¼šæŒ‰ detailList ä¸€æ¬¡æ€§é“ºå¹³ N æ¡ï¼›
     * å¾ªçޝ  ï¼šæŒ‰ schedule.currentLoopCount ç”Ÿæˆå½“前轮 N æ¡ï¼ŒåŽç»­è½®æ¬¡ç”±è°ƒåº¦å™¨æ»šåŠ¨å»ºã€‚
     *
     * @param serviceTask     ä¸»ä»»åŠ¡ï¼ˆå·²è½åº“ï¼‰
     * @param schedule        å·²è½åº“的主计划
     * @param detailList      æ˜Žç»†åˆ—表(按 seq å‡åºï¼‰
     * @param baseSubtask     æ¨¡æ¿ subtask(已填充患者基础信息 + åŸºå‡†æ—¥å­—段),方法内会深拷贝
     * @param preachformList  å‘送方式(调用方已解析好,可为空)
     */
    public void buildSubtasksForPatient(ServiceTask serviceTask,
                                        ServiceTaskSchedule schedule,
                                        List<ServiceTaskScheduleDetail> detailList,
                                        ServiceSubtask baseSubtask,
                                        List<Map<String, Object>> preachformList) {
        if (CollectionUtils.isEmpty(detailList)) {
            throw new BaseException("任务组明细为空,无法生成 subtask, taskid=" + serviceTask.getTaskid());
        }
        Date benchmark = resolveBenchmark(schedule, baseSubtask);
        long currentLoop = schedule.getCurrentLoopCount() == null ? 0L : schedule.getCurrentLoopCount();
        for (ServiceTaskScheduleDetail detail : detailList) {
            ServiceSubtask subtask = copyFromBase(baseSubtask);
            // å›žæº¯å­—段
            subtask.setScheduleId(schedule.getId());
            subtask.setScheduleDetailId(detail.getId());
            subtask.setScheduleSeq(detail.getSeq());
            subtask.setLoopCount(currentLoop);
            // æ¨¡æ¿ï¼šdetail ä¼˜å…ˆäºŽ task
            if (detail.getTemplateid() != null) {
                subtask.setTemplateid(detail.getTemplateid());
                subtask.setTemplatename(detail.getTemplatename());
            }
            if (detail.getLibtemplateid() != null) {
                subtask.setLibtemplateid(detail.getLibtemplateid());
            }
            // æ—¶é—´ï¼šåŸºå‡†æ—¥ + dayOffset
            Date visitTime = addDays(benchmark, detail.getDayOffset());
            subtask.setVisitTime(visitTime);
            subtask.setSenddate(visitTime);
            subtask.setLongSendTime(visitTime);
            // çŠ¶æ€ï¼šä¸ŽåŽŸæµç¨‹ä¸€è‡´ï¼Œæ–°å»ºåŽç½®ä¸ºå¾…å‘é€
            subtask.setSendstate(2L);
            subtask.setCreateTime(DateUtils.getNowDate());
            subtask.setUpdateTime(DateUtils.getNowDate());
            serviceSubtaskMapper.insertServiceSubtask(subtask);
            insertPreachform(serviceTask, preachformList, subtask);
            log.info("[任务组] taskid={} patid={} ç”Ÿæˆ subtask id={} seq={} dayOffset={} visitTime={}",
                    serviceTask.getTaskid(), baseSubtask.getPatid(),
                    subtask.getId(), detail.getSeq(), detail.getDayOffset(), visitTime);
        }
    }
    /**
     * å…¼å®¹å…¥å£ï¼šå‰ç«¯æ‰‹åŠ¨å‹¾é€‰æ‚£è€…ï¼ˆinsertOrUpdateTask)。
     * æŠŠ patTaskRelevance çš„基准日字段回填到 baseSubtask åŽï¼Œå§”托给解耦入口。
     */
    public void buildSubtasksForPatient(ServiceTask serviceTask,
                                        ServiceTaskVO serviceTaskVO,
                                        ServiceTaskSchedule schedule,
                                        List<ServiceTaskScheduleDetail> detailList,
                                        PatTaskRelevance patTaskRelevance,
                                        ServiceSubtask baseSubtask) {
        // å›žå¡«åŸºå‡†æ—¥å­—段(手动路径的 baseSubtask ç”± VO æ‹¼è£…,基准日字段可能未填全)
        if (baseSubtask.getStarttime() == null) {
            baseSubtask.setStarttime(patTaskRelevance.getInhosptime() != null
                    ? patTaskRelevance.getInhosptime() : patTaskRelevance.getStarttime());
        }
        if (baseSubtask.getEndtime() == null) {
            baseSubtask.setEndtime(patTaskRelevance.getEndtime());
        }
        if (baseSubtask.getAdmindate() == null) {
            baseSubtask.setAdmindate(patTaskRelevance.getVisittime());
        }
        buildSubtasksForPatient(serviceTask, schedule, detailList, baseSubtask,
                serviceTaskVO.getPreachformList());
    }
    /**
     * æ ¹æ® scheduleBenchmark è§£æžåŸºå‡†æ—¶é—´ï¼š
     * 0-自定义时间 1-入院时间 2-出院时间 3-就诊时间 4-入组时间
     * å…¨éƒ¨ä»Ž baseSubtask å–,取不到兜底当前时间。
     */
    private Date resolveBenchmark(ServiceTaskSchedule schedule, ServiceSubtask base) {
        Integer benchmark = schedule.getScheduleBenchmark() == null ? 0 : schedule.getScheduleBenchmark();
        Date now = DateUtils.getNowDate();
        switch (benchmark) {
            case 1:
                return base.getStarttime() != null ? base.getStarttime() : now;
            case 2:
                return base.getEndtime() != null ? base.getEndtime() : now;
            case 3:
                return base.getAdmindate() != null ? base.getAdmindate() : now;
            case 4:
                return base.getCreateTime() != null ? base.getCreateTime() : now;
            case 0:
            default:
                return schedule.getScheduleBenchmarkCustomTime() != null
                        ? schedule.getScheduleBenchmarkCustomTime() : now;
        }
    }
    private Date addDays(Date base, int dayOffset) {
        Calendar c = Calendar.getInstance();
        c.setTime(base);
        c.add(Calendar.DAY_OF_MONTH, dayOffset);
        return c.getTime();
    }
    /**
     * å†™ service_subtask_preachform,发送方式由调用方备好。
     */
    private void insertPreachform(ServiceTask serviceTask, List<Map<String, Object>> pfList, ServiceSubtask subtask) {
        if (CollectionUtils.isEmpty(pfList)) {
            return;
        }
        for (Map<String, Object> map : pfList) {
            ServiceSubtaskPreachform pf = new ServiceSubtaskPreachform();
            pf.setSort(ObjectUtils.isEmpty(map.get("sort")) ? 0 : Long.valueOf(map.get("sort").toString()));
            pf.setPreachform(ObjectUtils.isEmpty(map.get("preachform")) ? "" : map.get("preachform").toString());
            pf.setCompensateTime(ObjectUtils.isEmpty(map.get("compensateTime")) ? "" : map.get("compensateTime").toString());
            pf.setTaskid(serviceTask.getTaskid());
            pf.setSubid(subtask.getId());
            pf.setSendstate("1");
            pf.setOrgid(serviceTask.getOrgid());
            pf.setCreateTime(new Date());
            serviceSubtaskPreachformMapper.insertServiceSubtaskPreachform(pf);
        }
    }
    /**
     * æ·±æ‹·è´ baseSubtask åˆ°ä¸€ä¸ªæ–°å¯¹è±¡ï¼ˆæ¯æ¬¡ insert åŽ id ä¼šå›žå†™ï¼Œå¿…须独立实例)。
     * ç”¨ fastjson åšæ·±æ‹·è´ï¼Œçœä¸€å † set。
     */
    private ServiceSubtask copyFromBase(ServiceSubtask base) {
        String json = JSON.toJSONString(base);
        ServiceSubtask copy = JSON.parseObject(json, ServiceSubtask.class);
        // ä¸»é”®ã€id å¿…须清掉,否则 useGeneratedKeys ä»ä¼šç”¨ä¸Šè½®çš„ id è§¦å‘主键冲突
        copy.setId(null);
        return copy;
    }
}
smartor/src/main/java/com/smartor/domain/AppendPatientsReq.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,21 @@
package com.smartor.domain;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.List;
/**
 * å‘已有任务追加患者的请求体(用于拆包解决413问题)
 */
@Data
@ApiModel(value = "AppendPatientsReq", description = "向已有任务追加患者")
public class AppendPatientsReq {
    @ApiModelProperty(value = "任务ID(insertOrUpdateTask ç¬¬ä¸€æ¬¡è°ƒç”¨åŽè¿”回的 taskId)", required = true)
    private Long taskId;
    @ApiModelProperty(value = "本批次患者列表")
    private List<PatTaskRelevance> patTaskRelevances;
}
smartor/src/main/java/com/smartor/domain/PatMedInhosp.java
@@ -309,6 +309,13 @@
    private String diagcheckFlag;
    /**
     * ä»»åŠ¡ç»„ï¼ˆappltype=5)首轮纳入标记:0未处理  1已生成首轮  2无匹配/失败
     * ç‹¬ç«‹äºŽ dept/ward/diag check_flag,专供任务组独立扫描使用;后续轮次由 rollTaskGroupCycle è´Ÿè´£ã€‚
     */
    @ApiModelProperty(value = "任务组(appltype=5)首轮纳入标记:0未处理 1已生成首轮 2无匹配/失败")
    private String scheduleCheckFlag;
    /**
     * å…¥é™¢ç§‘室长期任务,是否处理:0未处理   1处理
     */
    @ApiModelProperty(value = "入院科室长期任务,是否处理:0未处理   1处理成功    2处理失败 ")
smartor/src/main/java/com/smartor/domain/ServiceSubtask.java
@@ -722,4 +722,30 @@
    @JsonFormat(pattern = "yyyy-MM-dd")
    @ApiModelProperty(value = "结束发送日期")
    private Date endSendDateTime;
    // ===================== ä»»åŠ¡ç»„ï¼ˆappltype=5)回溯字段 =====================
    /**
     * å…³è” service_task_schedule.id(appltype=5 æ—¶ä½¿ç”¨ï¼‰
     */
    @ApiModelProperty(value = "关联 service_task_schedule.id(appltype=5)")
    private Long scheduleId;
    /**
     * å…³è” service_task_schedule_detail.id
     */
    @ApiModelProperty(value = "关联 service_task_schedule_detail.id")
    private Long scheduleDetailId;
    /**
     * æœ¬æ¡ subtask åœ¨è®¡åˆ’中的顺序号
     */
    @ApiModelProperty(value = "本条 subtask åœ¨è®¡åˆ’中的顺序号")
    private Integer scheduleSeq;
    /**
     * æ‰€åœ¨å¾ªçŽ¯è½®æ¬¡ï¼ˆéžå¾ªçŽ¯ä»»åŠ¡ä¸º0)
     */
    @ApiModelProperty(value = "所在循环轮次(非循环任务为0)")
    private Long loopCount;
}
smartor/src/main/java/com/smartor/domain/ServiceTaskSchedule.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,118 @@
package com.smartor.domain;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.ruoyi.common.annotation.Excel;
import com.ruoyi.common.core.domain.BaseEntity;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.Date;
/**
 * éšè®¿ä»»åŠ¡è®¡åˆ’ä¸»è¡¨ service_task_schedule
 * <p>
 * ç”¨äºŽ appltype=5(按任务组):一个 service_task å¯¹åº”一条 schedule + å¤šæ¡ detail,
 * åŒä¸€å‡ºé™¢è®°å½•据此可生成多个 service_subtask。
 *
 * @author smartor
 * @date 2026-06-30
 */
@Data
@ApiModel(value = "ServiceTaskSchedule", description = "随访任务计划主表")
public class ServiceTaskSchedule extends BaseEntity {
    private static final long serialVersionUID = 1L;
    /**
     * ä¸»é”®
     */
    @ApiModelProperty("主键id")
    private Long id;
    /**
     * å…³è” service_task.taskid
     */
    @ApiModelProperty("关联 service_task.taskid")
    @Excel(name = "任务id")
    private Long taskid;
    /**
     * æœ€å¤§å¾ªçŽ¯æ¬¡æ•°ï¼ˆå¾ªçŽ¯ä»»åŠ¡ç”¨ï¼ŒNULL或0表示无限)
     */
    @ApiModelProperty("最大循环次数(循环任务用,NULL或0表示无限)")
    private Long maxLoopCount;
    /**
     * å½“前已完成循环次数
     */
    @ApiModelProperty("当前已完成循环次数")
    private Long currentLoopCount;
    /**
     * å½“前执行顺序号
     */
    @ApiModelProperty("当前执行顺序号")
    private Integer currentSeq;
    /**
     * ä¸‹ä¸€æ‰§è¡Œé¡ºåºå·
     */
    @ApiModelProperty("下一执行顺序号")
    private Integer nextSeq;
    /**
     * æ‰§è¡Œå‘¨æœŸæ–¹å¼(1-循环 0-非循环)
     */
    @ApiModelProperty("执行周期方式(1-循环 0-非循环)")
    private Integer scheduleStyle;
    /**
     * å¾ªçŽ¯æ—¶é—´æ•°
     */
    @ApiModelProperty("循环时间数")
    private Integer scheduleTimeNum;
    /**
     * å¾ªçŽ¯æ—¶é—´å•ä½; 1-天 2-周 3-月 4-å¹´
     */
    @ApiModelProperty("循环时间单位; 1-天 2-周 3-月 4-å¹´")
    private Integer scheduleTimeUnit;
    /**
     * ä¸‹æ¬¡æ‰§è¡Œæ—¶é—´
     */
    @ApiModelProperty("下次执行时间")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private Date nextExecuteTime;
    /**
     * å‘¨æœŸåŸºå‡†ç±»åž‹; 0-自定义时间 1-入院时间 2-出院时间 3-就诊时间 4-入组时间
     */
    @ApiModelProperty("周期基准类型; 0-自定义时间 1-入院时间 2-出院时间 3-就诊时间 4-入组时间")
    private Integer scheduleBenchmark;
    /**
     * å‘¨æœŸåŸºå‡†è‡ªå®šä¹‰æ—¶é—´
     */
    @ApiModelProperty("周期基准自定义时间")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private Date scheduleBenchmarkCustomTime;
    /**
     * åˆ é™¤æ ‡è®°ï¼ˆ0存在 1删除)
     */
    @ApiModelProperty("删除标记(0存在 1删除)")
    private String delFlag;
    /**
     * æœºæž„ID
     */
    @ApiModelProperty("机构ID")
    private String orgid;
    /**
     * é™¢åŒºID
     */
    @ApiModelProperty("院区ID")
    private String campusid;
}
smartor/src/main/java/com/smartor/domain/ServiceTaskScheduleDetail.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,102 @@
package com.smartor.domain;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.ruoyi.common.annotation.Excel;
import com.ruoyi.common.core.domain.BaseEntity;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.Date;
/**
 * éšè®¿ä»»åŠ¡è®¡åˆ’æ˜Žç»†è¡¨ service_task_schedule_detail
 *
 * @author smartor
 * @date 2026-06-30
 */
@Data
@ApiModel(value = "ServiceTaskScheduleDetail", description = "随访任务计划明细表")
public class ServiceTaskScheduleDetail extends BaseEntity {
    private static final long serialVersionUID = 1L;
    /**
     * ä¸»é”®
     */
    @ApiModelProperty("主键")
    private Long id;
    /**
     * å…³è” service_task.taskid
     */
    @ApiModelProperty("关联 service_task.taskid")
    @Excel(name = "任务id")
    private Long taskid;
    /**
     * å…³è” service_task_schedule.id
     */
    @ApiModelProperty("关联 service_task_schedule.id")
    private Long scheduleid;
    /**
     * é¡ºåºå·ï¼Œä»Ž1开始
     */
    @ApiModelProperty("顺序号,从1开始")
    private Integer seq;
    /**
     * é—´éš”天数(固定任务:相对基准日;循环任务:相对上一次完成日)
     */
    @ApiModelProperty("间隔天数(固定任务:相对基准日;循环任务:相对上一次完成日)")
    private Integer dayOffset;
    /**
     * è¯¥æ¬¡ä½¿ç”¨çš„æ¨¡æ¿ID
     */
    @ApiModelProperty("该次使用的模板ID")
    private Long templateid;
    /**
     * æ¨¡æ¿åº“ID
     */
    @ApiModelProperty("模板库ID")
    private Long libtemplateid;
    /**
     * æ¨¡æ¿åç§°
     */
    @ApiModelProperty("模板名称")
    private String templatename;
    /**
     * è¯¥æ¬¡è‡ªå®šä¹‰å†…容(JSON:kcb/jsy/text_param等)
     */
    @ApiModelProperty("该次自定义内容(JSON:kcb/jsy/text_param等)")
    private String content;
    /**
     * ä¸‹æ¬¡æ‰§è¡Œæ—¶é—´
     */
    @ApiModelProperty("下次执行时间")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private Date nextExecuteTime;
    /**
     * åˆ é™¤æ ‡è®°ï¼ˆ0存在 1删除)
     */
    @ApiModelProperty("删除标记(0存在 1删除)")
    private String delFlag;
    /**
     * æœºæž„ID
     */
    @ApiModelProperty("机构ID")
    private String orgid;
    /**
     * é™¢åŒºID
     */
    @ApiModelProperty("院区ID")
    private String campusid;
}
smartor/src/main/java/com/smartor/domain/ServiceTaskScheduleVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,28 @@
package com.smartor.domain;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.util.List;
/**
 * éšè®¿ä»»åŠ¡è®¡åˆ’ VO(主计划 + æ˜Žç»†åˆ—表)
 * ç”¨äºŽ appltype=5 ä»»åŠ¡ç»„åˆ›å»º/修改的入参。
 *
 * @author smartor
 * @date 2026-06-30
 */
@Data
@EqualsAndHashCode(callSuper = true)
@ApiModel(value = "ServiceTaskScheduleVO", description = "随访任务计划")
public class ServiceTaskScheduleVO extends ServiceTaskSchedule {
    private static final long serialVersionUID = 1L;
    /**
     * è®¡åˆ’明细列表(按 seq æŽ’序)
     */
    @ApiModelProperty("计划明细列表(按 seq æŽ’序)")
    private List<ServiceTaskScheduleDetail> detailList;
}
smartor/src/main/java/com/smartor/domain/ServiceTaskVO.java
@@ -434,10 +434,10 @@
    @Excel(name = "手术等级编码")
    private String oplevelcode;
    /**
     * é€‚用条件类型:1、按科室、2、按病区、3、按疾病诊断、4、按手术类型
     * é€‚用条件类型:1、按科室、2、按病区、3、按疾病诊断、4、按手术类型、5、按任务组
     */
    @ApiModelProperty("适用条件类型:1、按科室、2、按病区、3、按疾病诊断、4、按手术类型")
    @Excel(name = "适用条件类型:1、按科室、2、按病区、3、按疾病诊断、4、按手术类型")
    @ApiModelProperty("适用条件类型:1、按科室、2、按病区、3、按疾病诊断、4、按手术类型、5、按任务组")
    @Excel(name = "适用条件类型:1、按科室、2、按病区、3、按疾病诊断、4、按手术类型、5、按任务组")
    private String appltype;
    @ApiModelProperty(value = "随访部门编码")
@@ -452,4 +452,10 @@
    @ApiModelProperty(value = "疾病病种")
    @Excel(name = " ç–¾ç—…病种")
    private String diagType;
    /**
     * ä»»åŠ¡ç»„è®¡åˆ’ï¼ˆappltype=5 å¿…填,主计划 + æ˜Žç»†åˆ—表)
     */
    @ApiModelProperty(value = "任务组计划(appltype=5 å¿…填,主计划+明细)")
    private ServiceTaskScheduleVO serviceTaskScheduleVO;
}
smartor/src/main/java/com/smartor/domain/ServiceTaskdept.java
@@ -84,5 +84,17 @@
    @ApiModelProperty("删除标志(0:未删除 1:已删除)")
    private String delFlag;
    /**
     * æŸ¥è¯¢ç”¨ï¼ˆéžè¡¨å­—段):排除的 appltype。主扫描传 "5" ä»¥æŽ’除任务组。
     */
    @ApiModelProperty("查询用:排除的appltype(如5,主扫描排除任务组)")
    private String excludeAppltype;
    /**
     * æŸ¥è¯¢ç”¨ï¼ˆéžè¡¨å­—段):限定的 appltype。任务组扫描传 "5" åªæŸ¥ä»»åŠ¡ç»„ã€‚
     */
    @ApiModelProperty("查询用:限定的appltype(如5,只查任务组)")
    private String queryAppltype;
}
smartor/src/main/java/com/smartor/domain/ServiceTaskdiag.java
@@ -135,5 +135,17 @@
    @Excel(name = "父ID")
    private Long pid;
    /**
     * æŸ¥è¯¢ç”¨ï¼ˆéžè¡¨å­—段):排除的 appltype。主扫描传 "5" ä»¥æŽ’除任务组。
     */
    @ApiModelProperty("查询用:排除的appltype(如5,主扫描排除任务组)")
    private String excludeAppltype;
    /**
     * æŸ¥è¯¢ç”¨ï¼ˆéžè¡¨å­—段):限定的 appltype。任务组扫描传 "5" åªæŸ¥ä»»åŠ¡ç»„ã€‚
     */
    @ApiModelProperty("查询用:限定的appltype(如5,只查任务组)")
    private String queryAppltype;
}
smartor/src/main/java/com/smartor/mapper/ServiceSubtaskMapper.java
@@ -68,6 +68,23 @@
    public int insertServiceSubtask(ServiceSubtask ServiceSubtask);
    /**
     * ä»»åŠ¡ç»„ï¼ˆappltype=5):统计某一轮次内仍在运行的 subtask æ•°é‡ã€‚
     * ä»åœ¨è¿è¡Œ = sendstate in (1,2,3) ä¸”未删除。
     * ç”¨äºŽåˆ¤æ–­æœ¬è½®æ˜¯å¦å·²å…¨éƒ¨ finalize(成功/失败/不执行/超时)。
     *
     * @param scheduleId è®¡åˆ’主键
     * @param loopCount  è½®æ¬¡ï¼ˆ0=首轮)
     */
    public int countActiveByScheduleIdAndLoop(@Param("scheduleId") Long scheduleId,
                                              @Param("loopCount") Long loopCount);
    /**
     * ä»»åŠ¡ç»„ï¼ˆappltype=5):拉取某一轮次的全部 subtask(用于克隆到下一轮)。
     */
    public List<ServiceSubtask> selectByScheduleIdAndLoop(@Param("scheduleId") Long scheduleId,
                                                          @Param("loopCount") Long loopCount);
    /**
     * éšè®¿åŠæ—¶çŽ‡ï¼šåº”éšè®¿æ—¶é—´<实际随访时间
     *
     * @param ServiceSubtask
smartor/src/main/java/com/smartor/mapper/ServiceTaskScheduleDetailMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,67 @@
package com.smartor.mapper;
import com.smartor.domain.ServiceTaskScheduleDetail;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
 * éšè®¿ä»»åŠ¡è®¡åˆ’æ˜Žç»† Mapper æŽ¥å£
 *
 * @author smartor
 * @date 2026-06-30
 */
@Mapper
public interface ServiceTaskScheduleDetailMapper {
    /**
     * æŸ¥è¯¢æ˜Žç»†
     */
    ServiceTaskScheduleDetail selectServiceTaskScheduleDetailById(Long id);
    /**
     * æŸ¥è¯¢æ˜Žç»†åˆ—表
     */
    List<ServiceTaskScheduleDetail> selectServiceTaskScheduleDetailList(ServiceTaskScheduleDetail detail);
    /**
     * æŒ‰ scheduleid æŸ¥è¯¢æ˜Žç»†åˆ—表(按 seq å‡åºï¼‰
     */
    List<ServiceTaskScheduleDetail> selectByScheduleid(@Param("scheduleid") Long scheduleid);
    /**
     * æŒ‰ taskid æŸ¥è¯¢æ˜Žç»†åˆ—表(按 seq å‡åºï¼‰
     */
    List<ServiceTaskScheduleDetail> selectByTaskid(@Param("taskid") Long taskid);
    /**
     * æ–°å¢žæ˜Žç»†
     */
    int insertServiceTaskScheduleDetail(ServiceTaskScheduleDetail detail);
    /**
     * æ‰¹é‡æ–°å¢žæ˜Žç»†
     */
    int batchInsertServiceTaskScheduleDetail(@Param("list") List<ServiceTaskScheduleDetail> list);
    /**
     * ä¿®æ”¹æ˜Žç»†
     */
    int updateServiceTaskScheduleDetail(ServiceTaskScheduleDetail detail);
    /**
     * é€»è¾‘删除(按 id)
     */
    int deleteServiceTaskScheduleDetailById(Long id);
    /**
     * é€»è¾‘删除(按 scheduleid):修改任务时清空旧明细
     */
    int deleteByScheduleid(@Param("scheduleid") Long scheduleid);
    /**
     * é€»è¾‘删除(按 taskid)
     */
    int deleteByTaskid(@Param("taskid") Long taskid);
}
smartor/src/main/java/com/smartor/mapper/ServiceTaskScheduleMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,59 @@
package com.smartor.mapper;
import com.smartor.domain.ServiceTaskSchedule;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
 * éšè®¿ä»»åŠ¡è®¡åˆ’ä¸»è¡¨ Mapper æŽ¥å£
 *
 * @author smartor
 * @date 2026-06-30
 */
@Mapper
public interface ServiceTaskScheduleMapper {
    /**
     * æŸ¥è¯¢è®¡åˆ’详情
     */
    ServiceTaskSchedule selectServiceTaskScheduleById(Long id);
    /**
     * é€šè¿‡ taskid æŸ¥è¯¢ä»»åŠ¡å¯¹åº”çš„æœ‰æ•ˆè®¡åˆ’ï¼ˆappltype=5 ä¸€å¯¹ä¸€ï¼‰
     */
    ServiceTaskSchedule selectByTaskid(@Param("taskid") Long taskid);
    /**
     * æŸ¥è¯¢è®¡åˆ’列表
     */
    List<ServiceTaskSchedule> selectServiceTaskScheduleList(ServiceTaskSchedule serviceTaskSchedule);
    /**
     * æ–°å¢žè®¡åˆ’
     */
    int insertServiceTaskSchedule(ServiceTaskSchedule serviceTaskSchedule);
    /**
     * ä¿®æ”¹è®¡åˆ’
     */
    int updateServiceTaskSchedule(ServiceTaskSchedule serviceTaskSchedule);
    /**
     * é€»è¾‘删除(按 id)
     */
    int deleteServiceTaskScheduleById(Long id);
    /**
     * é€»è¾‘删除(按 taskid):修改任务时清空旧计划
     */
    int deleteByTaskid(@Param("taskid") Long taskid);
    /**
     * ä»»åŠ¡ç»„ï¼ˆappltype=5)滚动调度用:查询所有未达上限的循环计划。
     * æ¡ä»¶ï¼šschedule_style=1 ä¸” (max_loop_count is null or max_loop_count=0 or current_loop_count &lt; max_loop_count)
     * é™åˆ¶è¿”回条数避免长事务。
     */
    List<ServiceTaskSchedule> selectActiveCycleSchedules(@Param("limit") int limit);
}
smartor/src/main/java/com/smartor/service/IServiceTaskScheduleService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,60 @@
package com.smartor.service;
import com.smartor.domain.ServiceTaskSchedule;
import com.smartor.domain.ServiceTaskScheduleDetail;
import com.smartor.domain.ServiceTaskScheduleVO;
import java.util.List;
/**
 * éšè®¿ä»»åŠ¡è®¡åˆ’ Service æŽ¥å£
 *
 * @author smartor
 * @date 2026-06-30
 */
public interface IServiceTaskScheduleService {
    /**
     * ä¿å­˜è®¡åˆ’(含明细)。
     * è‹¥ taskid å·²æœ‰è®¡åˆ’:先软删旧的,再新建。
     *
     * @param vo ä¸»è®¡åˆ’ + æ˜Žç»†
     * @return ä¿å­˜åŽçš„主计划(含主键)
     */
    ServiceTaskSchedule saveScheduleWithDetail(ServiceTaskScheduleVO vo);
    /**
     * æŒ‰ taskid æŸ¥è¯¢è®¡åˆ’详情(含明细)
     */
    ServiceTaskScheduleVO getScheduleByTaskid(Long taskid);
    /**
     * é€šè¿‡ taskid æŸ¥è¯¢ä¸»è®¡åˆ’
     */
    ServiceTaskSchedule selectByTaskid(Long taskid);
    /**
     * é€šè¿‡ scheduleid æŸ¥è¯¢æ˜Žç»†
     */
    List<ServiceTaskScheduleDetail> selectDetailByScheduleid(Long scheduleid);
    /**
     * æŒ‰ taskid è½¯åˆ ä¸»è®¡åˆ’ + æ˜Žç»†
     */
    int removeByTaskid(Long taskid);
    /**
     * å¾ªçŽ¯ä»»åŠ¡æ»šåŠ¨è°ƒåº¦ï¼š
     * <p>
     * æ‰«ææ‰€æœ‰ schedule_style=1 çš„æœ‰æ•ˆè®¡åˆ’,若 current_loop_count å·²è¾¾ä¸Šé™åˆ™è·³è¿‡ï¼›
     * å¦åˆ™åˆ¤å®šæœ¬è½®ï¼ˆcurrent_loop_count)是否已全部 finalize(sendstate âˆ‰ 1/2/3)。
     * æ˜¯ -> ç”Ÿæˆä¸‹ä¸€è½® subtask(克隆本轮的患者,visit_time = æœ¬è½® visit_time + N天/周/月/年),
     *      current_loop_count + 1,next_execute_time æŽ¨è¿›ã€‚
     * å¦ -> è·³è¿‡ç­‰å¾…下次扫描。
     * <p>
     * ç”± RyTask å®šæ—¶è°ƒç”¨ï¼Œå•次扫描限制 200 æ¡é¿å…é•¿äº‹åŠ¡ã€‚
     *
     * @return å®žé™…推进了多少个计划
     */
    int rollCycleTasks();
}
smartor/src/main/java/com/smartor/service/impl/ServiceSubtaskServiceImpl.java
@@ -116,6 +116,12 @@
    private IServiceSubtaskDetailTraceService traceService;
    @Autowired
    private IServiceTaskScheduleService serviceTaskScheduleService;
    @Autowired
    private com.smartor.common.ScheduleSubtaskBuilder scheduleSubtaskBuilder;
    @Autowired
    private FtpService ftpService;
    @Autowired
@@ -727,6 +733,24 @@
                        serviceTaskoperService.insertServiceTaskoper(serviceTaskoper);
                    }
                } else if (serviceTaskVO.getAppltype().equals("5")) {
                    // ä»»åŠ¡ç»„ï¼šå¿…é¡»ä¸ºé•¿æœŸä»»åŠ¡ï¼ˆå¦åˆ™ findMatchingTaskIds æ‰«ä¸åˆ°ï¼Œæ— æ³•自动纳入新出院患者)
                    if (serviceTaskVO.getLongTask() == null || serviceTaskVO.getLongTask() != 1) {
                        throw new BaseException("appltype=5 ä»»åŠ¡ç»„å¿…é¡»ä¸ºé•¿æœŸä»»åŠ¡ï¼ˆlongTask=1)");
                    }
                    // ä¿å­˜è®¡åˆ’ + æ˜Žç»†
                    ServiceTaskScheduleVO scheduleVO = serviceTaskVO.getServiceTaskScheduleVO();
                    if (scheduleVO == null) {
                        throw new BaseException("appltype=5 ä»»åŠ¡ç»„ï¼šserviceTaskScheduleVO ä¸èƒ½ä¸ºç©º");
                    }
                    scheduleVO.setTaskid(serviceTask.getTaskid());
                    scheduleVO.setOrgid(serviceTask.getOrgid());
                    scheduleVO.setCampusid(serviceTask.getCampusid());
                    scheduleVO.setCreateBy(serviceTask.getCreateBy());
                    log.info("----serviceTaskSchedule的值为:{}", scheduleVO);
                    serviceTaskScheduleService.saveScheduleWithDetail(scheduleVO);
                    // ç»‘定维度写条件表(供 HIS è‡ªåŠ¨çº³å…¥åŒ¹é…ï¼Œlongtask æ’=1)
                    bindDimensionCondition(serviceTask, serviceTaskVO);
                } else {
                    ServiceTaskdept serviceTaskdept = new ServiceTaskdept();
                    serviceTaskdept.setTaskId(serviceTask.getTaskid());
@@ -812,6 +836,20 @@
                        serviceSubtask.setLongSendTime(patTaskRelevance.getLongSendTime());
                        serviceSubtask.setVisitTime(patTaskRelevance.getVisittime());
                    }
                    // ===== appltype=5 ä»»åŠ¡ç»„ï¼šæŒ‰ schedule çš„ detailList æ´¾ç”Ÿå¤šæ¡ subtask =====
                    if ("5".equals(serviceTaskVO.getAppltype())) {
                        ServiceTaskSchedule schedule = serviceTaskScheduleService.selectByTaskid(serviceTask.getTaskid());
                        if (schedule == null) {
                            throw new BaseException("appltype=5 ä»»åŠ¡ç»„ï¼šæœªæ‰¾åˆ° taskid=" + serviceTask.getTaskid() + " çš„计划");
                        }
                        List<ServiceTaskScheduleDetail> detailList =
                                serviceTaskScheduleService.selectDetailByScheduleid(schedule.getId());
                        scheduleSubtaskBuilder.buildSubtasksForPatient(
                                serviceTask, serviceTaskVO, schedule, detailList, patTaskRelevance, serviceSubtask);
                        continue;
                    }
                    serviceSubtaskMapper.insertServiceSubtask(serviceSubtask);
                    serviceSubtask.getId().intValue();
@@ -1026,6 +1064,23 @@
                            }
                        }
                    }
                } else if (serviceTaskVO.getAppltype().equals("5")) {
                    // ä»»åŠ¡ç»„ä¿®æ”¹ï¼šåˆ æ—§ schedule + æ˜Žç»†ï¼Œé‡å»ºã€‚subtask ä¸Šé¢å·²æ‰¹é‡æ¸…理。
                    if (serviceTaskVO.getLongTask() == null || serviceTaskVO.getLongTask() != 1) {
                        throw new BaseException("appltype=5 ä»»åŠ¡ç»„å¿…é¡»ä¸ºé•¿æœŸä»»åŠ¡ï¼ˆlongTask=1)");
                    }
                    ServiceTaskScheduleVO scheduleVO = serviceTaskVO.getServiceTaskScheduleVO();
                    if (scheduleVO == null) {
                        throw new BaseException("appltype=5 ä»»åŠ¡ç»„ï¼šserviceTaskScheduleVO ä¸èƒ½ä¸ºç©º");
                    }
                    scheduleVO.setTaskid(serviceTask.getTaskid());
                    scheduleVO.setOrgid(serviceTask.getOrgid());
                    scheduleVO.setCampusid(serviceTask.getCampusid());
                    scheduleVO.setCreateBy(serviceTask.getUpdateBy());
                    serviceTaskScheduleService.saveScheduleWithDetail(scheduleVO);
                    // å…ˆæ¸…旧维度条件表,再按最新入参重写(防过期维度残留导致误纳入)
                    clearDimensionCondition(serviceTask.getTaskid());
                    bindDimensionCondition(serviceTask, serviceTaskVO);
                }
            }
            if (ObjectUtils.isNotEmpty(serviceTaskVO.getIvrTaskTemplateVO())) {
@@ -1079,6 +1134,20 @@
                    serviceSubtask.setVisitTime(patTaskRelevance.getVisittime());
                    serviceSubtask.setTextParam(new Gson().toJson(serviceTaskVO.getTextParam()));
                    if (patTaskRelevance.getIsoperation() != null) {
                        // appltype=5 ä»»åŠ¡ç»„ï¼šè¿½åŠ æ‚£è€…ï¼ˆisoperation=1)走计划派生;修改(2)/删除(3)仍走原逻辑
                        if ("5".equals(serviceTaskVO.getAppltype()) && patTaskRelevance.getIsoperation() == 1) {
                            ServiceTaskSchedule schedule = serviceTaskScheduleService.selectByTaskid(serviceTask.getTaskid());
                            if (schedule == null) {
                                throw new BaseException("appltype=5 ä»»åŠ¡ç»„ï¼šæœªæ‰¾åˆ° taskid=" + serviceTask.getTaskid() + " çš„计划");
                            }
                            List<ServiceTaskScheduleDetail> detailList =
                                    serviceTaskScheduleService.selectDetailByScheduleid(schedule.getId());
                            serviceSubtask.setUpdateBy(serviceTask.getUpdateBy());
                            serviceSubtask.setCreateBy(serviceTask.getUpdateBy());
                            scheduleSubtaskBuilder.buildSubtasksForPatient(
                                    serviceTask, serviceTaskVO, schedule, detailList, patTaskRelevance, serviceSubtask);
                            continue;
                        }
                        if (patTaskRelevance.getIsoperation() == 2) {
                            serviceSubtask.setUpdateBy(serviceTask.getUpdateBy());
                            serviceSubtaskMapper.updateServiceSubtaskByCondition(serviceSubtask);
@@ -1132,6 +1201,85 @@
        return map;
    }
    /**
     * ä»»åŠ¡ç»„ï¼ˆappltype=5)绑定维度写条件表,供 HIS è‡ªåŠ¨çº³å…¥åŒ¹é…ã€‚
     * longtask æ’=1(否则独立扫描/主扫描按 longtask=1 æŸ¥ä¸åˆ°ï¼‰ã€‚
     * å¤ç”¨çŽ°æœ‰ service_taskdept(科室deptType=1/病区deptType=2)、service_taskdiag(疾病)。
     * æ‰‹æœ¯ç»´åº¦æœ¬æœŸä¸å†™ï¼ˆæš‚不支持自动纳入)。
     */
    private void bindDimensionCondition(ServiceTask serviceTask, ServiceTaskVO vo) {
        // ç§‘室
        if (StringUtils.isNotEmpty(vo.getDeptcode())) {
            for (String deptCode : vo.getDeptcode().split(",")) {
                if (StringUtils.isEmpty(deptCode)) continue;
                ServiceTaskdept d = new ServiceTaskdept();
                d.setTaskId(serviceTask.getTaskid());
                d.setTaskName(serviceTask.getTaskName());
                d.setOrgid(serviceTask.getOrgid());
                d.setLongtask(1L);
                d.setDeptCode(deptCode);
                d.setDeptType("1");
                d.setServiceType(vo.getServiceType());
                serviceTaskdeptService.insertServiceTaskdept(d);
            }
        }
        // ç—…区
        if (StringUtils.isNotEmpty(vo.getLeavehospitaldistrictcode())) {
            for (String wardCode : vo.getLeavehospitaldistrictcode().split(",")) {
                if (StringUtils.isEmpty(wardCode)) continue;
                ServiceTaskdept d = new ServiceTaskdept();
                d.setTaskId(serviceTask.getTaskid());
                d.setTaskName(serviceTask.getTaskName());
                d.setOrgid(serviceTask.getOrgid());
                d.setLongtask(1L);
                d.setDeptCode(wardCode);
                d.setDeptType("2");
                d.setServiceType(vo.getServiceType());
                serviceTaskdeptService.insertServiceTaskdept(d);
            }
        }
        // ç–¾ç—…(用 split(","),勿照抄 appltype=3 çŽ°ç½‘çš„ split("$") bug)
        if (StringUtils.isNotEmpty(vo.getIcd10code())) {
            String[] codes = vo.getIcd10code().split(",");
            String[] names = StringUtils.isNotEmpty(vo.getIcd10name()) ? vo.getIcd10name().split(",") : new String[0];
            for (int i = 0; i < codes.length; i++) {
                if (StringUtils.isEmpty(codes[i])) continue;
                ServiceTaskdiag dg = new ServiceTaskdiag();
                dg.setTaskId(serviceTask.getTaskid());
                dg.setTaskName(serviceTask.getTaskName());
                dg.setOrgid(serviceTask.getOrgid());
                dg.setLongtask(1L);
                dg.setServiceType(vo.getServiceType());
                dg.setIcd10code(codes[i]);
                dg.setIcd10name(i < names.length ? names[i] : null);
                dg.setGuid(serviceTask.getGuid());
                dg.setCreateTime(new Date());
                serviceTaskdiagService.insertServiceTaskdiag(dg);
            }
        }
    }
    /**
     * ä»»åŠ¡ç»„ä¿®æ”¹æ—¶ï¼Œå…ˆè½¯åˆ è¯¥ task æ—§çš„维度条件表记录(防过期维度残留导致误纳入)。
     */
    private void clearDimensionCondition(Long taskId) {
        ServiceTaskdept dq = new ServiceTaskdept();
        dq.setTaskId(taskId);
        List<ServiceTaskdept> depts = serviceTaskdeptService.selectServiceTaskdeptList(dq);
        if (CollectionUtils.isNotEmpty(depts)) {
            for (ServiceTaskdept d : depts) {
                serviceTaskdeptService.deleteServiceTaskdeptById(d.getId());
            }
        }
        ServiceTaskdiag gq = new ServiceTaskdiag();
        gq.setTaskId(taskId);
        List<ServiceTaskdiag> diags = serviceTaskdiagService.selectServiceTaskdiagList(gq);
        if (CollectionUtils.isNotEmpty(diags)) {
            for (ServiceTaskdiag g : diags) {
                serviceTaskdiagService.deleteServiceTaskdiagById(g.getId());
            }
        }
    }
    @Override
    public void phoneCallBack(PhoneCallBackVO phoneCallBackVO) {
smartor/src/main/java/com/smartor/service/impl/ServiceTaskScheduleServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,356 @@
package com.smartor.service.impl;
import com.alibaba.fastjson2.JSON;
import com.ruoyi.common.exception.base.BaseException;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.spring.SpringUtils;
import com.smartor.domain.ServiceSubtask;
import com.smartor.domain.ServiceSubtaskPreachform;
import com.smartor.domain.ServiceTaskSchedule;
import com.smartor.domain.ServiceTaskScheduleDetail;
import com.smartor.domain.ServiceTaskScheduleVO;
import com.smartor.mapper.ServiceSubtaskMapper;
import com.smartor.mapper.ServiceSubtaskPreachformMapper;
import com.smartor.mapper.ServiceTaskScheduleDetailMapper;
import com.smartor.mapper.ServiceTaskScheduleMapper;
import com.smartor.service.IServiceTaskScheduleService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
/**
 * éšè®¿ä»»åŠ¡è®¡åˆ’ Service å®žçް
 *
 * @author smartor
 * @date 2026-06-30
 */
@Slf4j
@Service
public class ServiceTaskScheduleServiceImpl implements IServiceTaskScheduleService {
    @Autowired
    private ServiceTaskScheduleMapper serviceTaskScheduleMapper;
    @Autowired
    private ServiceTaskScheduleDetailMapper serviceTaskScheduleDetailMapper;
    @Autowired
    private ServiceSubtaskMapper serviceSubtaskMapper;
    @Autowired
    private ServiceSubtaskPreachformMapper serviceSubtaskPreachformMapper;
    /** å•次扫描的最大计划数,避免长事务。 */
    private static final int ROLL_BATCH_LIMIT = 200;
    @Override
    @Transactional(rollbackFor = Exception.class)
    public ServiceTaskSchedule saveScheduleWithDetail(ServiceTaskScheduleVO vo) {
        if (vo == null || vo.getTaskid() == null) {
            throw new BaseException("任务计划入参非法:taskid ä¸èƒ½ä¸ºç©º");
        }
        if (CollectionUtils.isEmpty(vo.getDetailList())) {
            throw new BaseException("任务计划明细不能为空(至少一条 detail)");
        }
        // 1. æ—§è®¡åˆ’软删
        serviceTaskScheduleMapper.deleteByTaskid(vo.getTaskid());
        serviceTaskScheduleDetailMapper.deleteByTaskid(vo.getTaskid());
        // 2. ä¸»è®¡åˆ’落库
        Date now = DateUtils.getNowDate();
        ServiceTaskSchedule schedule = new ServiceTaskSchedule();
        // æ‹·è´çˆ¶ç±»ï¼ˆServiceTaskSchedule)字段
        schedule.setTaskid(vo.getTaskid());
        schedule.setMaxLoopCount(vo.getMaxLoopCount());
        schedule.setCurrentLoopCount(vo.getCurrentLoopCount() == null ? 0L : vo.getCurrentLoopCount());
        schedule.setCurrentSeq(vo.getCurrentSeq() == null ? 0 : vo.getCurrentSeq());
        schedule.setNextSeq(vo.getNextSeq() == null ? 1 : vo.getNextSeq());
        schedule.setScheduleStyle(vo.getScheduleStyle() == null ? 0 : vo.getScheduleStyle());
        schedule.setScheduleTimeNum(vo.getScheduleTimeNum() == null ? 0 : vo.getScheduleTimeNum());
        schedule.setScheduleTimeUnit(vo.getScheduleTimeUnit() == null ? 0 : vo.getScheduleTimeUnit());
        schedule.setNextExecuteTime(vo.getNextExecuteTime());
        schedule.setScheduleBenchmark(vo.getScheduleBenchmark() == null ? 0 : vo.getScheduleBenchmark());
        schedule.setScheduleBenchmarkCustomTime(vo.getScheduleBenchmarkCustomTime());
        schedule.setRemark(vo.getRemark());
        schedule.setDelFlag("0");
        schedule.setCreateBy(vo.getCreateBy());
        schedule.setUpdateBy(vo.getCreateBy());
        schedule.setCreateTime(now);
        schedule.setUpdateTime(now);
        schedule.setOrgid(vo.getOrgid());
        schedule.setCampusid(vo.getCampusid());
        serviceTaskScheduleMapper.insertServiceTaskSchedule(schedule);
        // 3. æ˜Žç»†è½åº“
        List<ServiceTaskScheduleDetail> detailList = vo.getDetailList();
        int seq = 1;
        for (ServiceTaskScheduleDetail detail : detailList) {
            detail.setTaskid(vo.getTaskid());
            detail.setScheduleid(schedule.getId());
            if (detail.getSeq() == null) {
                detail.setSeq(seq);
            }
            if (detail.getDayOffset() == null) {
                throw new BaseException("计划明细 seq=" + detail.getSeq() + " çš„ dayOffset ä¸èƒ½ä¸ºç©º");
            }
            detail.setDelFlag("0");
            detail.setCreateBy(vo.getCreateBy());
            detail.setUpdateBy(vo.getCreateBy());
            detail.setCreateTime(now);
            detail.setUpdateTime(now);
            detail.setOrgid(vo.getOrgid());
            detail.setCampusid(vo.getCampusid());
            seq++;
        }
        serviceTaskScheduleDetailMapper.batchInsertServiceTaskScheduleDetail(detailList);
        log.info("[任务组] taskid={} è®¡åˆ’保存完成, scheduleId={}, detail æ¡æ•°={}",
                vo.getTaskid(), schedule.getId(), detailList.size());
        return schedule;
    }
    @Override
    public ServiceTaskScheduleVO getScheduleByTaskid(Long taskid) {
        ServiceTaskSchedule schedule = serviceTaskScheduleMapper.selectByTaskid(taskid);
        if (schedule == null) {
            return null;
        }
        ServiceTaskScheduleVO vo = new ServiceTaskScheduleVO();
        vo.setId(schedule.getId());
        vo.setTaskid(schedule.getTaskid());
        vo.setMaxLoopCount(schedule.getMaxLoopCount());
        vo.setCurrentLoopCount(schedule.getCurrentLoopCount());
        vo.setCurrentSeq(schedule.getCurrentSeq());
        vo.setNextSeq(schedule.getNextSeq());
        vo.setScheduleStyle(schedule.getScheduleStyle());
        vo.setScheduleTimeNum(schedule.getScheduleTimeNum());
        vo.setScheduleTimeUnit(schedule.getScheduleTimeUnit());
        vo.setNextExecuteTime(schedule.getNextExecuteTime());
        vo.setScheduleBenchmark(schedule.getScheduleBenchmark());
        vo.setScheduleBenchmarkCustomTime(schedule.getScheduleBenchmarkCustomTime());
        vo.setRemark(schedule.getRemark());
        vo.setOrgid(schedule.getOrgid());
        vo.setCampusid(schedule.getCampusid());
        vo.setDetailList(serviceTaskScheduleDetailMapper.selectByScheduleid(schedule.getId()));
        return vo;
    }
    @Override
    public ServiceTaskSchedule selectByTaskid(Long taskid) {
        return serviceTaskScheduleMapper.selectByTaskid(taskid);
    }
    @Override
    public List<ServiceTaskScheduleDetail> selectDetailByScheduleid(Long scheduleid) {
        return serviceTaskScheduleDetailMapper.selectByScheduleid(scheduleid);
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public int removeByTaskid(Long taskid) {
        int n = serviceTaskScheduleMapper.deleteByTaskid(taskid);
        serviceTaskScheduleDetailMapper.deleteByTaskid(taskid);
        return n;
    }
    @Override
    public int rollCycleTasks() {
        List<ServiceTaskSchedule> schedules = serviceTaskScheduleMapper.selectActiveCycleSchedules(ROLL_BATCH_LIMIT);
        if (CollectionUtils.isEmpty(schedules)) {
            return 0;
        }
        log.info("[任务组-滚动] æ‰«æåˆ° {} ä¸ªå¾…评估的循环计划", schedules.size());
        int rolled = 0;
        for (ServiceTaskSchedule schedule : schedules) {
            try {
                // é€šè¿‡ AOP ä»£ç†è°ƒç”¨ï¼Œç¡®ä¿ @Transactional ç”Ÿæ•ˆ
                if (SpringUtils.getAopProxy(this).rollOneSchedule(schedule)) {
                    rolled++;
                }
            } catch (Exception e) {
                // å•条失败不影响其它计划,记日志继续
                log.error("[任务组-滚动] scheduleId={} æ»šåŠ¨å¤±è´¥ï¼š{}", schedule.getId(), e.getMessage(), e);
            }
        }
        log.info("[任务组-滚动] æœ¬æ¬¡æŽ¨è¿› {} ä¸ªè®¡åˆ’", rolled);
        return rolled;
    }
    /**
     * å•个计划的滚动:本轮全部 finalize æ‰æŽ¨è¿›ï¼Œæ¯æ¬¡ä»…推进 1 è½®ã€‚
     */
    @Transactional(rollbackFor = Exception.class)
    public boolean rollOneSchedule(ServiceTaskSchedule schedule) {
        Long scheduleId = schedule.getId();
        Long currentLoop = schedule.getCurrentLoopCount() == null ? 0L : schedule.getCurrentLoopCount();
        // ä¸Šé™æ ¡éªŒ
        Long max = schedule.getMaxLoopCount();
        if (max != null && max > 0 && currentLoop + 1 >= max) {
            // æ³¨æ„ï¼šå…ˆåˆ¤æ–­"再开一轮是否会超限"——currentLoop æ˜¯å·²å®Œæˆè½®æ¬¡ï¼Œä¸‹ä¸€è½®å· = currentLoop+1
            // å½“ currentLoop+1 == max æ—¶æ­£å¥½å¤„于最后一轮,不再产生新一轮
            if (currentLoop >= max) {
                log.info("[任务组-滚动] scheduleId={} å·²è¾¾ maxLoopCount={}, è·³è¿‡", scheduleId, max);
                return false;
            }
        }
        // æœ¬è½®æ˜¯å¦å®Œæˆ
        int active = serviceSubtaskMapper.countActiveByScheduleIdAndLoop(scheduleId, currentLoop);
        if (active > 0) {
            log.debug("[任务组-滚动] scheduleId={} loop={} ä»æœ‰ {} æ¡ subtask æœªç»“束, è·³è¿‡", scheduleId, currentLoop, active);
            return false;
        }
        // æ‹‰æœ¬è½® subtask åšå…‹éš†æ¨¡æ¿ï¼ˆå–去重后的患者集合)
        List<ServiceSubtask> currentLoopSubtasks =
                serviceSubtaskMapper.selectByScheduleIdAndLoop(scheduleId, currentLoop);
        if (CollectionUtils.isEmpty(currentLoopSubtasks)) {
            log.warn("[任务组-滚动] scheduleId={} loop={} æ‰¾ä¸åˆ°æœ¬è½® subtask, è·³è¿‡", scheduleId, currentLoop);
            return false;
        }
        // æ‹‰æœ¬è®¡åˆ’明细:seq -> detail,用来定位下一轮每条 subtask ç”¨å“ªä¸ªæ¨¡æ¿
        List<ServiceTaskScheduleDetail> detailList =
                serviceTaskScheduleDetailMapper.selectByScheduleid(scheduleId);
        if (CollectionUtils.isEmpty(detailList)) {
            log.warn("[任务组-滚动] scheduleId={} æ˜Žç»†ä¸ºç©º, è·³è¿‡", scheduleId);
            return false;
        }
        // æŽ¨è¿›æ­¥é•¿ï¼ˆè½®é—´éš”)
        int stepNum = schedule.getScheduleTimeNum() == null ? 0 : schedule.getScheduleTimeNum();
        int stepUnit = schedule.getScheduleTimeUnit() == null ? 1 : schedule.getScheduleTimeUnit();
        if (stepNum <= 0) {
            log.warn("[任务组-滚动] scheduleId={} scheduleTimeNum={} éžæ³•, è·³è¿‡", scheduleId, stepNum);
            return false;
        }
        long nextLoop = currentLoop + 1;
        // æŒ‰ patid+inhospid åŽ»é‡ï¼ˆåŒæ‚£è€…æœ¬è½®å¯èƒ½æœ‰ N æ¡ detail,模板任选其一即可——克隆时按 seq å¤ç”¨æ˜Žç»†ï¼‰
        // ä¸ºç®€åŒ–:直接按 schedule_seq å¤åˆ¶ N æ¡ï¼Œæ¯æ¡ç”¨å¯¹åº” detail。
        Date now = DateUtils.getNowDate();
        // æŠŠ detailList æŒ‰ seq ç´¢å¼•
        java.util.Map<Integer, ServiceTaskScheduleDetail> detailBySeq = new java.util.HashMap<>(detailList.size());
        for (ServiceTaskScheduleDetail d : detailList) {
            detailBySeq.put(d.getSeq(), d);
        }
        int produced = 0;
        for (ServiceSubtask src : currentLoopSubtasks) {
            ServiceTaskScheduleDetail detail = detailBySeq.get(src.getScheduleSeq());
            if (detail == null) {
                log.warn("[任务组-滚动] scheduleId={} æ‰¾ä¸åˆ° seq={} å¯¹åº”的明细, è·³è¿‡ subtask id={}",
                        scheduleId, src.getScheduleSeq(), src.getId());
                continue;
            }
            ServiceSubtask copy = cloneForNextLoop(src, detail, nextLoop, stepNum, stepUnit, now);
            serviceSubtaskMapper.insertServiceSubtask(copy);
            // å¤åˆ¶ preachform
            copyPreachform(src.getId(), copy);
            produced++;
        }
        // æŽ¨è¿› schedule çš„ current_loop_count / next_seq / next_execute_time
        ServiceTaskSchedule upd = new ServiceTaskSchedule();
        upd.setId(scheduleId);
        upd.setCurrentLoopCount(nextLoop);
        upd.setCurrentSeq(1);
        upd.setNextSeq(detailList.size() > 1 ? 2 : 1);
        upd.setNextExecuteTime(advance(now, stepNum, stepUnit));
        upd.setUpdateTime(now);
        serviceTaskScheduleMapper.updateServiceTaskSchedule(upd);
        log.info("[任务组-滚动] scheduleId={} æŽ¨è¿›åˆ°ç¬¬ {} è½®, æ–°å»º {} æ¡ subtask", scheduleId, nextLoop, produced);
        return true;
    }
    /**
     * å…‹éš† subtask åˆ°ä¸‹ä¸€è½®ï¼šæ¸…主键、清回签字段、推进 visit_time、设置 loop_count。
     */
    private ServiceSubtask cloneForNextLoop(ServiceSubtask src, ServiceTaskScheduleDetail detail,
                                            long nextLoop, int stepNum, int stepUnit, Date now) {
        ServiceSubtask copy = JSON.parseObject(JSON.toJSONString(src), ServiceSubtask.class);
        copy.setId(null);
        copy.setSenduuid(null);
        copy.setResult(null);
        copy.setFinishtime(null);
        copy.setExrecallcount(null);
        copy.setCurrentPreachform(null);
        copy.setSendstate(2L);
        copy.setLoopCount(nextLoop);
        copy.setScheduleDetailId(detail.getId());
        // æ¨¡æ¿ï¼šdetail ä¼˜å…ˆ
        if (detail.getTemplateid() != null) {
            copy.setTemplateid(detail.getTemplateid());
            copy.setTemplatename(detail.getTemplatename());
        }
        if (detail.getLibtemplateid() != null) {
            copy.setLibtemplateid(detail.getLibtemplateid());
        }
        Date base = src.getVisitTime() != null ? src.getVisitTime() : now;
        Date nextVisit = advance(base, stepNum, stepUnit);
        copy.setVisitTime(nextVisit);
        copy.setSenddate(nextVisit);
        copy.setLongSendTime(nextVisit);
        copy.setCreateTime(now);
        copy.setUpdateTime(now);
        return copy;
    }
    /**
     * å¤åˆ¶æº subtask çš„发送方式行到新 subtask。
     */
    private void copyPreachform(Long srcSubId, ServiceSubtask newSubtask) {
        ServiceSubtaskPreachform q = new ServiceSubtaskPreachform();
        q.setSubid(srcSubId);
        q.setTaskid(newSubtask.getTaskid());
        List<ServiceSubtaskPreachform> list = serviceSubtaskPreachformMapper.selectServiceSubtaskPreachformList(q);
        if (CollectionUtils.isEmpty(list)) {
            return;
        }
        for (ServiceSubtaskPreachform src : list) {
            ServiceSubtaskPreachform pf = new ServiceSubtaskPreachform();
            pf.setSort(src.getSort());
            pf.setPreachform(src.getPreachform());
            pf.setCompensateTime(src.getCompensateTime());
            pf.setTaskid(newSubtask.getTaskid());
            pf.setSubid(newSubtask.getId());
            pf.setSendstate("1");
            pf.setOrgid(newSubtask.getOrgid());
            pf.setCreateTime(new Date());
            serviceSubtaskPreachformMapper.insertServiceSubtaskPreachform(pf);
        }
    }
    /**
     * æŒ‰ schedule_time_unit æŽ¨è¿›æ—¶é—´ï¼š1-天 2-周 3-月 4-å¹´
     */
    private Date advance(Date base, int num, int unit) {
        Calendar c = Calendar.getInstance();
        c.setTime(base);
        switch (unit) {
            case 2:
                c.add(Calendar.WEEK_OF_YEAR, num);
                break;
            case 3:
                c.add(Calendar.MONTH, num);
                break;
            case 4:
                c.add(Calendar.YEAR, num);
                break;
            case 1:
            default:
                c.add(Calendar.DAY_OF_MONTH, num);
        }
        return c.getTime();
    }
}
smartor/src/main/resources/mapper/smartor/PatMedInhospMapper.xml
@@ -52,6 +52,7 @@
        <result property="inDeptcheckFlag" column="in_deptcheck_flag"/>
        <result property="wardcheckFlag" column="wardcheck_flag"/>
        <result property="diagcheckFlag" column="diagcheck_flag"/>
        <result property="scheduleCheckFlag" column="schedulecheck_flag"/>
        <result property="age" column="age"/>
        <result property="sex" column="sex"/>
        <result property="telcode" column="telcode"/>
@@ -103,6 +104,7 @@
               in_deptcheck_flag,
               in_wardcheck_flag,
               diagcheck_flag,
               schedulecheck_flag,
               inhospstate,
               patno,
               long_task_reason,
@@ -164,6 +166,7 @@
        b.in_deptcheck_flag,
        b.in_wardcheck_flag,
        b.diagcheck_flag,
        b.schedulecheck_flag,
        b.inhospstate,
        b.patno,
        b.inhospno,
@@ -253,6 +256,7 @@
        <if test="nurseId != null ">and b.nurse_id = #{nurseId}</if>
        <if test="nurseName != null and nurseName != ''">and b.nurse_name = #{nurseName}</if>
        <if test="deptcheckFlag != null ">and b.deptcheck_flag = #{deptcheckFlag}</if>
        <if test="scheduleCheckFlag != null ">and b.schedulecheck_flag = #{scheduleCheckFlag}</if>
        <if test="wardcheckFlag != null ">and b.wardcheck_flag = #{wardcheckFlag}</if>
        <if test="inDeptcheckFlag != null ">and b.in_deptcheck_flag = #{inDeptcheckFlag}</if>
        <if test="inWardcheckFlag != null ">and b.in_wardcheck_flag = #{inWardcheckFlag}</if>
@@ -342,6 +346,7 @@
        b.in_deptcheck_flag,
        b.in_wardcheck_flag,
        b.diagcheck_flag,
        b.schedulecheck_flag,
        b.inhospstate,
        b.patno,
        b.inhospno,
@@ -499,6 +504,7 @@
        <if test="campusid != null ">and b.campusid = #{campusid}</if>
        <if test="diagcheckFlag != null ">and b.diagcheck_flag = #{diagcheckFlag}</if>
        <if test="deptcheckFlag != null ">and b.deptcheck_flag = #{deptcheckFlag}</if>
        <if test="scheduleCheckFlag != null ">and b.schedulecheck_flag = #{scheduleCheckFlag}</if>
        <if test="wardcheckFlag != null ">and b.wardcheck_flag = #{wardcheckFlag}</if>
        <if test="inDeptcheckFlag != null ">and b.in_deptcheck_flag = #{inDeptcheckFlag}</if>
        <if test="inWardcheckFlag != null ">and b.in_wardcheck_flag = #{inWardcheckFlag}</if>
@@ -838,6 +844,7 @@
            <if test="patno != null">patno = #{patno},</if>
            <if test="inhospstate != null">inhospstate = #{inhospstate},</if>
            <if test="deptcheckFlag != null">deptcheck_flag = #{deptcheckFlag},</if>
            <if test="scheduleCheckFlag != null">schedulecheck_flag = #{scheduleCheckFlag},</if>
            <if test="wardcheckFlag != null">wardcheck_flag = #{wardcheckFlag},</if>
            <if test="inDeptcheckFlag != null">in_deptcheck_flag = #{inDeptcheckFlag},</if>
            <if test="inWardcheckFlag != null">in_wardcheck_flag = #{inWardcheckFlag},</if>
smartor/src/main/resources/mapper/smartor/ServiceOutPathMapper.xml
@@ -166,7 +166,7 @@
        FROM INFORMATION_SCHEMA.TABLES
        WHERE TABLE_NAME = 'service_out_path'
          AND table_schema = DATABASE()
          and onorgid
--           and onorgid
    </select>
</mapper>
smartor/src/main/resources/mapper/smartor/ServiceSubtaskMapper.xml
@@ -94,6 +94,10 @@
        <result property="continueCount" column="continue_count"/>
        <result property="continueTimeNext" column="continue_time_next"/>
        <result property="operationItemId" column="operation_item_id"/>
        <result property="scheduleId" column="schedule_id"/>
        <result property="scheduleDetailId" column="schedule_detail_id"/>
        <result property="scheduleSeq" column="schedule_seq"/>
        <result property="loopCount" column="loop_count"/>
    </resultMap>
    <resultMap type="com.smartor.domain.ServiceSubtaskCount" id="ServiceSubtaskResult2">
@@ -238,6 +242,10 @@
               continue_count,
               continue_time_next,
               operation_item_id,
               schedule_id,
               schedule_detail_id,
               schedule_seq,
               loop_count,
               task_situation
        from service_subtask
    </sql>
@@ -963,6 +971,10 @@
            <if test="continueTimeNext != null ">continue_time_next,</if>
            <if test="continueContent != null ">continue_content,</if>
            <if test="operationItemId != null ">operation_item_id,</if>
            <if test="scheduleId != null ">schedule_id,</if>
            <if test="scheduleDetailId != null ">schedule_detail_id,</if>
            <if test="scheduleSeq != null ">schedule_seq,</if>
            <if test="loopCount != null ">loop_count,</if>
        </trim>
        <trim prefix="values (" suffix=")" suffixOverrides=",">
            <if test="sendname != null">#{sendname},</if>
@@ -1055,6 +1067,10 @@
            <if test="continueTimeNext != null ">#{continueTimeNext,jdbcType=TIMESTAMP},</if>
            <if test="continueContent != null ">#{continueContent},</if>
            <if test="operationItemId != null ">#{operationItemId},</if>
            <if test="scheduleId != null ">#{scheduleId},</if>
            <if test="scheduleDetailId != null ">#{scheduleDetailId},</if>
            <if test="scheduleSeq != null ">#{scheduleSeq},</if>
            <if test="loopCount != null ">#{loopCount},</if>
        </trim>
    </insert>
@@ -1152,6 +1168,10 @@
            <if test="continueTimeNext != null ">continue_time_next = #{continueTimeNext,jdbcType=TIMESTAMP},</if>
            <if test="continueContent != null ">continue_content = #{continueContent},</if>
            <if test="operationItemId != null ">operation_item_id = #{operationItemId},</if>
            <if test="scheduleId != null ">schedule_id = #{scheduleId},</if>
            <if test="scheduleDetailId != null ">schedule_detail_id = #{scheduleDetailId},</if>
            <if test="scheduleSeq != null ">schedule_seq = #{scheduleSeq},</if>
            <if test="loopCount != null ">loop_count = #{loopCount},</if>
        </trim>
        where id = #{id}
    </update>
@@ -2896,5 +2916,24 @@
        </choose>
    </select>
    <!-- ä»»åŠ¡ç»„ï¼ˆappltype=5)滚动调度用:本轮仍在跑的数量 -->
    <select id="countActiveByScheduleIdAndLoop" resultType="int">
        select count(1)
        from service_subtask
        where schedule_id = #{scheduleId}
          and loop_count = #{loopCount}
          and (del_flag is null or del_flag = '0')
          and sendstate in (1, 2, 3)
    </select>
    <!-- ä»»åŠ¡ç»„ï¼ˆappltype=5)滚动调度用:拉本轮全量 subtask,作为下一轮的克隆模板 -->
    <select id="selectByScheduleIdAndLoop" resultMap="ServiceSubtaskResult">
        <include refid="selectServiceSubtaskVo"/>
        where schedule_id = #{scheduleId}
          and loop_count = #{loopCount}
          and (del_flag is null or del_flag = '0')
        order by schedule_seq asc, id asc
    </select>
</mapper>
smartor/src/main/resources/mapper/smartor/ServiceTaskScheduleDetailMapper.xml
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,186 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.smartor.mapper.ServiceTaskScheduleDetailMapper">
    <resultMap type="com.smartor.domain.ServiceTaskScheduleDetail" id="ServiceTaskScheduleDetailResult">
        <id property="id" column="id"/>
        <result property="taskid" column="taskid"/>
        <result property="scheduleid" column="scheduleid"/>
        <result property="seq" column="seq"/>
        <result property="dayOffset" column="day_offset"/>
        <result property="templateid" column="templateid"/>
        <result property="libtemplateid" column="libtemplateid"/>
        <result property="templatename" column="templatename"/>
        <result property="content" column="content"/>
        <result property="nextExecuteTime" column="next_execute_time"/>
        <result property="remark" column="remark"/>
        <result property="delFlag" column="del_flag"/>
        <result property="createBy" column="create_by"/>
        <result property="createTime" column="create_time"/>
        <result property="updateBy" column="update_by"/>
        <result property="updateTime" column="update_time"/>
        <result property="orgid" column="orgid"/>
        <result property="campusid" column="campusid"/>
    </resultMap>
    <sql id="selectServiceTaskScheduleDetailVo">
        select id,
               taskid,
               scheduleid,
               seq,
               day_offset,
               templateid,
               libtemplateid,
               templatename,
               content,
               next_execute_time,
               remark,
               del_flag,
               create_by,
               create_time,
               update_by,
               update_time,
               orgid,
               campusid
        from service_task_schedule_detail
    </sql>
    <select id="selectServiceTaskScheduleDetailById" parameterType="Long"
            resultMap="ServiceTaskScheduleDetailResult">
        <include refid="selectServiceTaskScheduleDetailVo"/>
        where id = #{id} and del_flag = '0'
    </select>
    <select id="selectServiceTaskScheduleDetailList"
            parameterType="com.smartor.domain.ServiceTaskScheduleDetail"
            resultMap="ServiceTaskScheduleDetailResult">
        <include refid="selectServiceTaskScheduleDetailVo"/>
        where del_flag = '0'
        <if test="taskid != null">and taskid = #{taskid}</if>
        <if test="scheduleid != null">and scheduleid = #{scheduleid}</if>
        <if test="seq != null">and seq = #{seq}</if>
        <if test="templateid != null">and templateid = #{templateid}</if>
        order by seq asc
    </select>
    <select id="selectByScheduleid" resultMap="ServiceTaskScheduleDetailResult">
        <include refid="selectServiceTaskScheduleDetailVo"/>
        where scheduleid = #{scheduleid} and del_flag = '0'
        order by seq asc
    </select>
    <select id="selectByTaskid" resultMap="ServiceTaskScheduleDetailResult">
        <include refid="selectServiceTaskScheduleDetailVo"/>
        where taskid = #{taskid} and del_flag = '0'
        order by seq asc
    </select>
    <insert id="insertServiceTaskScheduleDetail" parameterType="com.smartor.domain.ServiceTaskScheduleDetail"
            useGeneratedKeys="true" keyProperty="id">
        insert into service_task_schedule_detail
        <trim prefix="(" suffix=")" suffixOverrides=",">
            <if test="taskid != null">taskid,</if>
            <if test="scheduleid != null">scheduleid,</if>
            <if test="seq != null">seq,</if>
            <if test="dayOffset != null">day_offset,</if>
            <if test="templateid != null">templateid,</if>
            <if test="libtemplateid != null">libtemplateid,</if>
            <if test="templatename != null">templatename,</if>
            <if test="content != null">content,</if>
            <if test="nextExecuteTime != null">next_execute_time,</if>
            <if test="remark != null">remark,</if>
            <if test="delFlag != null">del_flag,</if>
            <if test="createBy != null">create_by,</if>
            <if test="createTime != null">create_time,</if>
            <if test="updateBy != null">update_by,</if>
            <if test="updateTime != null">update_time,</if>
            <if test="orgid != null">orgid,</if>
            <if test="campusid != null">campusid,</if>
        </trim>
        <trim prefix="values (" suffix=")" suffixOverrides=",">
            <if test="taskid != null">#{taskid},</if>
            <if test="scheduleid != null">#{scheduleid},</if>
            <if test="seq != null">#{seq},</if>
            <if test="dayOffset != null">#{dayOffset},</if>
            <if test="templateid != null">#{templateid},</if>
            <if test="libtemplateid != null">#{libtemplateid},</if>
            <if test="templatename != null">#{templatename},</if>
            <if test="content != null">#{content},</if>
            <if test="nextExecuteTime != null">#{nextExecuteTime},</if>
            <if test="remark != null">#{remark},</if>
            <if test="delFlag != null">#{delFlag},</if>
            <if test="createBy != null">#{createBy},</if>
            <if test="createTime != null">#{createTime},</if>
            <if test="updateBy != null">#{updateBy},</if>
            <if test="updateTime != null">#{updateTime},</if>
            <if test="orgid != null">#{orgid},</if>
            <if test="campusid != null">#{campusid},</if>
        </trim>
    </insert>
    <insert id="batchInsertServiceTaskScheduleDetail" useGeneratedKeys="true" keyProperty="id">
        insert into service_task_schedule_detail
        (taskid, scheduleid, seq, day_offset, templateid, libtemplateid, templatename, content,
         next_execute_time, remark, del_flag, create_by, create_time, update_by, update_time, orgid, campusid)
        values
        <foreach collection="list" item="item" separator=",">
            (#{item.taskid}, #{item.scheduleid}, #{item.seq}, #{item.dayOffset},
             #{item.templateid}, #{item.libtemplateid}, #{item.templatename}, #{item.content},
             #{item.nextExecuteTime}, #{item.remark},
             <choose>
                 <when test="item.delFlag != null">#{item.delFlag}</when>
                 <otherwise>'0'</otherwise>
             </choose>,
             #{item.createBy}, #{item.createTime}, #{item.updateBy}, #{item.updateTime},
             #{item.orgid}, #{item.campusid})
        </foreach>
    </insert>
    <update id="updateServiceTaskScheduleDetail" parameterType="com.smartor.domain.ServiceTaskScheduleDetail">
        update service_task_schedule_detail
        <trim prefix="SET" suffixOverrides=",">
            <if test="taskid != null">taskid = #{taskid},</if>
            <if test="scheduleid != null">scheduleid = #{scheduleid},</if>
            <if test="seq != null">seq = #{seq},</if>
            <if test="dayOffset != null">day_offset = #{dayOffset},</if>
            <if test="templateid != null">templateid = #{templateid},</if>
            <if test="libtemplateid != null">libtemplateid = #{libtemplateid},</if>
            <if test="templatename != null">templatename = #{templatename},</if>
            <if test="content != null">content = #{content},</if>
            <if test="nextExecuteTime != null">next_execute_time = #{nextExecuteTime},</if>
            <if test="remark != null">remark = #{remark},</if>
            <if test="delFlag != null">del_flag = #{delFlag},</if>
            <if test="updateBy != null">update_by = #{updateBy},</if>
            <if test="updateTime != null">update_time = #{updateTime},</if>
            <if test="orgid != null">orgid = #{orgid},</if>
            <if test="campusid != null">campusid = #{campusid},</if>
        </trim>
        where id = #{id}
    </update>
    <update id="deleteServiceTaskScheduleDetailById" parameterType="Long">
        update service_task_schedule_detail
        set del_flag = '1',
            update_time = now()
        where id = #{id}
    </update>
    <update id="deleteByScheduleid">
        update service_task_schedule_detail
        set del_flag = '1',
            update_time = now()
        where scheduleid = #{scheduleid}
          and del_flag = '0'
    </update>
    <update id="deleteByTaskid">
        update service_task_schedule_detail
        set del_flag = '1',
            update_time = now()
        where taskid = #{taskid}
          and del_flag = '0'
    </update>
</mapper>
smartor/src/main/resources/mapper/smartor/ServiceTaskScheduleMapper.xml
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,176 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.smartor.mapper.ServiceTaskScheduleMapper">
    <resultMap type="com.smartor.domain.ServiceTaskSchedule" id="ServiceTaskScheduleResult">
        <id property="id" column="id"/>
        <result property="taskid" column="taskid"/>
        <result property="maxLoopCount" column="max_loop_count"/>
        <result property="currentLoopCount" column="current_loop_count"/>
        <result property="currentSeq" column="current_seq"/>
        <result property="nextSeq" column="next_seq"/>
        <result property="scheduleStyle" column="schedule_style"/>
        <result property="scheduleTimeNum" column="schedule_time_num"/>
        <result property="scheduleTimeUnit" column="schedule_time_unit"/>
        <result property="nextExecuteTime" column="next_execute_time"/>
        <result property="scheduleBenchmark" column="schedule_benchmark"/>
        <result property="scheduleBenchmarkCustomTime" column="schedule_benchmark_custom_time"/>
        <result property="remark" column="remark"/>
        <result property="delFlag" column="del_flag"/>
        <result property="createBy" column="create_by"/>
        <result property="createTime" column="create_time"/>
        <result property="updateBy" column="update_by"/>
        <result property="updateTime" column="update_time"/>
        <result property="orgid" column="orgid"/>
        <result property="campusid" column="campusid"/>
    </resultMap>
    <sql id="selectServiceTaskScheduleVo">
        select id,
               taskid,
               max_loop_count,
               current_loop_count,
               current_seq,
               next_seq,
               schedule_style,
               schedule_time_num,
               schedule_time_unit,
               next_execute_time,
               schedule_benchmark,
               schedule_benchmark_custom_time,
               remark,
               del_flag,
               create_by,
               create_time,
               update_by,
               update_time,
               orgid,
               campusid
        from service_task_schedule
    </sql>
    <select id="selectServiceTaskScheduleById" parameterType="Long" resultMap="ServiceTaskScheduleResult">
        <include refid="selectServiceTaskScheduleVo"/>
        where id = #{id} and del_flag = '0'
    </select>
    <select id="selectByTaskid" resultMap="ServiceTaskScheduleResult">
        <include refid="selectServiceTaskScheduleVo"/>
        where taskid = #{taskid} and del_flag = '0'
        order by id desc
        limit 1
    </select>
    <select id="selectServiceTaskScheduleList" parameterType="com.smartor.domain.ServiceTaskSchedule"
            resultMap="ServiceTaskScheduleResult">
        <include refid="selectServiceTaskScheduleVo"/>
        where del_flag = '0'
        <if test="taskid != null">and taskid = #{taskid}</if>
        <if test="scheduleStyle != null">and schedule_style = #{scheduleStyle}</if>
        <if test="orgid != null and orgid != ''">and orgid = #{orgid}</if>
        <if test="campusid != null and campusid != ''">and campusid = #{campusid}</if>
        order by id desc
    </select>
    <insert id="insertServiceTaskSchedule" parameterType="com.smartor.domain.ServiceTaskSchedule"
            useGeneratedKeys="true" keyProperty="id">
        insert into service_task_schedule
        <trim prefix="(" suffix=")" suffixOverrides=",">
            <if test="taskid != null">taskid,</if>
            <if test="maxLoopCount != null">max_loop_count,</if>
            <if test="currentLoopCount != null">current_loop_count,</if>
            <if test="currentSeq != null">current_seq,</if>
            <if test="nextSeq != null">next_seq,</if>
            <if test="scheduleStyle != null">schedule_style,</if>
            <if test="scheduleTimeNum != null">schedule_time_num,</if>
            <if test="scheduleTimeUnit != null">schedule_time_unit,</if>
            <if test="nextExecuteTime != null">next_execute_time,</if>
            <if test="scheduleBenchmark != null">schedule_benchmark,</if>
            <if test="scheduleBenchmarkCustomTime != null">schedule_benchmark_custom_time,</if>
            <if test="remark != null">remark,</if>
            <if test="delFlag != null">del_flag,</if>
            <if test="createBy != null">create_by,</if>
            <if test="createTime != null">create_time,</if>
            <if test="updateBy != null">update_by,</if>
            <if test="updateTime != null">update_time,</if>
            <if test="orgid != null">orgid,</if>
            <if test="campusid != null">campusid,</if>
        </trim>
        <trim prefix="values (" suffix=")" suffixOverrides=",">
            <if test="taskid != null">#{taskid},</if>
            <if test="maxLoopCount != null">#{maxLoopCount},</if>
            <if test="currentLoopCount != null">#{currentLoopCount},</if>
            <if test="currentSeq != null">#{currentSeq},</if>
            <if test="nextSeq != null">#{nextSeq},</if>
            <if test="scheduleStyle != null">#{scheduleStyle},</if>
            <if test="scheduleTimeNum != null">#{scheduleTimeNum},</if>
            <if test="scheduleTimeUnit != null">#{scheduleTimeUnit},</if>
            <if test="nextExecuteTime != null">#{nextExecuteTime},</if>
            <if test="scheduleBenchmark != null">#{scheduleBenchmark},</if>
            <if test="scheduleBenchmarkCustomTime != null">#{scheduleBenchmarkCustomTime},</if>
            <if test="remark != null">#{remark},</if>
            <if test="delFlag != null">#{delFlag},</if>
            <if test="createBy != null">#{createBy},</if>
            <if test="createTime != null">#{createTime},</if>
            <if test="updateBy != null">#{updateBy},</if>
            <if test="updateTime != null">#{updateTime},</if>
            <if test="orgid != null">#{orgid},</if>
            <if test="campusid != null">#{campusid},</if>
        </trim>
    </insert>
    <update id="updateServiceTaskSchedule" parameterType="com.smartor.domain.ServiceTaskSchedule">
        update service_task_schedule
        <trim prefix="SET" suffixOverrides=",">
            <if test="taskid != null">taskid = #{taskid},</if>
            <if test="maxLoopCount != null">max_loop_count = #{maxLoopCount},</if>
            <if test="currentLoopCount != null">current_loop_count = #{currentLoopCount},</if>
            <if test="currentSeq != null">current_seq = #{currentSeq},</if>
            <if test="nextSeq != null">next_seq = #{nextSeq},</if>
            <if test="scheduleStyle != null">schedule_style = #{scheduleStyle},</if>
            <if test="scheduleTimeNum != null">schedule_time_num = #{scheduleTimeNum},</if>
            <if test="scheduleTimeUnit != null">schedule_time_unit = #{scheduleTimeUnit},</if>
            <if test="nextExecuteTime != null">next_execute_time = #{nextExecuteTime},</if>
            <if test="scheduleBenchmark != null">schedule_benchmark = #{scheduleBenchmark},</if>
            <if test="scheduleBenchmarkCustomTime != null">
                schedule_benchmark_custom_time = #{scheduleBenchmarkCustomTime},
            </if>
            <if test="remark != null">remark = #{remark},</if>
            <if test="delFlag != null">del_flag = #{delFlag},</if>
            <if test="updateBy != null">update_by = #{updateBy},</if>
            <if test="updateTime != null">update_time = #{updateTime},</if>
            <if test="orgid != null">orgid = #{orgid},</if>
            <if test="campusid != null">campusid = #{campusid},</if>
        </trim>
        where id = #{id}
    </update>
    <update id="deleteServiceTaskScheduleById" parameterType="Long">
        update service_task_schedule
        set del_flag = '1',
            update_time = now()
        where id = #{id}
    </update>
    <update id="deleteByTaskid">
        update service_task_schedule
        set del_flag = '1',
            update_time = now()
        where taskid = #{taskid}
          and del_flag = '0'
    </update>
    <select id="selectActiveCycleSchedules" resultMap="ServiceTaskScheduleResult">
        <include refid="selectServiceTaskScheduleVo"/>
        where del_flag = '0'
          and schedule_style = 1
          and (max_loop_count is null
                or max_loop_count = 0
                or current_loop_count &lt; max_loop_count)
        order by id asc
        limit #{limit}
    </select>
</mapper>