陈昶聿
9 天以前 af3ec57db40f80fba5903aaedae5e66bf46a7ed9
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
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;
    }
}