liusheng
6 天以前 cc479e6a056785103996cc7ea67dbd5faa3e800e
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
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
package com.ruoyi.othervisit.service;
 
import com.ruoyi.common.core.service.ArchiveReadApi;
import com.ruoyi.common.core.service.ServiceTaskMatchApi;
import com.ruoyi.common.core.service.VisitTaskCreateApi;
import com.ruoyi.common.core.service.dto.ServiceTaskMatchDto;
import com.ruoyi.common.core.service.dto.VisitArchiveRow;
import com.ruoyi.common.core.service.dto.VisitLongTaskDto;
import com.ruoyi.common.core.service.dto.VisitSubtaskDraft;
import com.ruoyi.othervisit.domain.PatMedOperation;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
 
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;
import com.ruoyi.common.core.domain.entity.PatArchive;
 
/**
 * 手术随访编排:扫描未处理的手术记录 → 按<b>手术编码</b>匹配长期任务 → 写子任务。
 *
 * <h3>迁移对应(@ b25c3b51)</h3>
 * <table border="1">
 *   <tr><th>本类方法</th><th>原实现</th></tr>
 *   <tr><td>{@link #dealOperationInfo()}</td>
 *       <td>{@code PatMedOperationServiceImpl#dealOperationInfo} L107-149(42 行)</td></tr>
 *   <tr><td>{@link #writeInSubTask}</td>
 *       <td>同文件 L152-187(36 行)</td></tr>
 *   <tr><td>{@link #boxedServiceSubtask}</td>
 *       <td>同文件 L191-238(48 行,29 个 setter)</td></tr>
 *   <tr><td>{@link #addDays}</td><td>同文件 L240-248</td></tr>
 * </table>
 *
 * <h3>跨模块能力怎么拿(本模块只依赖 ruoyi-common)</h3>
 * <ul>
 *   <li>{@link ServiceTaskMatchApi#findLongTasksByOpcode(String)} ——
 *       按手术编码匹配长期任务(原 {@code serviceTaskoperMapper.selectServiceTaskoperList},
 *       {@code longtask=1} + {@code opcode=?});</li>
 *   <li>{@link VisitTaskCreateApi#findPublishedLongTask(Long)} —— 查已发布长期任务
 *       (原 {@code serviceTaskMapper.selectServiceTaskList} 配 {@code sendState=2});</li>
 *   <li>{@link VisitTaskCreateApi#insertSubtask(VisitSubtaskDraft)} —— 把组装好的草稿落库
 *       (原 {@code serviceSubtaskMapper.insertServiceSubtask});</li>
 *   <li>{@link ArchiveReadApi#findByPatid(Long)} —— 患者档案只读
 *       (原 {@code patArchiveMapper.selectPatArchiveByPatid})。</li>
 * </ul>
 * 任务创建本身<b>本次不迁</b>:你要求它将来单独成一个模块,本模块只调上面这些接口,
 * 将来换实现方时本类<b>一行都不用改</b>。
 *
 * <h3>⚠️ 保留的原实现行为(勿"顺手优化")</h3>
 * <ol>
 *   <li>{@code dealOperationInfo} 返回<b>固定 {@code 1}</b>(不是处理条数);</li>
 *   <li>匹配不到任务 → {@code checkFlag="2"} + 原因逐字照抄
 *       「该手术编码,未配置长期任务;」(<b>注意末尾是分号,且与出入院/门诊的文案不同</b>);</li>
 *   <li>匹配到<b>多个</b>任务 → 原因写「该手术编码,有多个随访长期任务:」+ <b>taskIds 列表的 toString</b>,
 *       先 update 一次,然后<b>只用第一个</b>继续;</li>
 *   <li>⚠️ <b>记录本身不参与"多任务"分支的 checkFlag 赋值</b>:那一支只写 {@code longTaskReason},
 *       不写 {@code checkFlag},随后由 {@code writeInSubTask} 成功时置 {@code "1"} —— 原样保留;</li>
 *   <li>⚠️ 每条记录的整段处理被 {@code try/catch(Exception)} 包住,异常只
 *       {@code printStackTrace()} + {@code log.error} 后<b>继续下一条</b>
 *       (原实现会吞掉异常);本实现把入参从 {@code PatArchive} 换成了 {@link VisitArchiveRow},
 *       但 {@code getNotrequiredFlag()} 在 {@link VisitArchiveRow} 里仍是 {@code String},
 *       所以"档案查不到 → NPE → 被吞 → 该条不处理"这一行为逐字保持;</li>
 *   <li>{@code check=true} 是<b>硬编码</b>传入的({@link #dealOperationInfo()} 里写死),
 *       而 {@code writeInSubTask} 的 {@code check} 分支实际上没有任何判断
 *       —— 这是原实现的写法,不要为它补逻辑;</li>
 *   <li>{@code setHospType("2")} 是<b>硬编码</b>,不读 {@code serviceTask.getHospType()}。</li>
 * </ol>
 *
 * <h3>⚠️ 与门诊版的三处不同(别照抄门诊)</h3>
 * <ol>
 *   <li><b>没有"先停旧再新增"</b>:{@code check} 参数被接收但完全不使用,
 *       手术路径从不查/停待执行子任务;</li>
 *   <li><b>不使用 {@code OuthospSubtaskDraft}</b>:手术的 {@code admitdate}/{@code fudate}/
 *       {@code finishtime} 三个字段在原实现里就没有被 set,故直接用 {@link VisitSubtaskDraft};</li>
 *   <li>入参是<b>整个 {@link PatMedOperation}</b>(不是明细),
 *       因为原实现对 {@code pat_med_operation} 逐条处理。</li>
 * </ol>
 *
 * <h3>开关</h3>
 * 本类不含开关判断 —— 由 {@link com.ruoyi.othervisit.api.OtherVisitFacade} 统一决定是否调用。
 */
@Slf4j
@Service
public class OperationVisitTaskService {
 
    /** 待执行状态(原实现硬编码 2L) */
    private static final Long SENDSTATE_PENDING = 2L;
 
    /** 不执行状态(原实现硬编码 4L:"患者不需要随访"时用) */
    private static final Long SENDSTATE_SKIP = 4L;
 
    /** 标记"不需要随访"的值(原 {@code patArchive.notrequiredFlag == "1"}) */
    private static final String NOT_REQUIRED_YES = "1";
 
    /** 手术侧 {@code hospType} 的固定值(原实现硬编码) */
    private static final String HOSP_TYPE_OPERATION = "2";
 
    @Autowired
    private OperationService operationService;
 
    @Autowired
    private ObjectProvider<ArchiveReadApi> archiveApiProvider;
 
    @Autowired
    private ObjectProvider<ServiceTaskMatchApi> matchApiProvider;
 
    @Autowired
    private ObjectProvider<VisitTaskCreateApi> taskApiProvider;
 
    /**
     * 手术随访主扫描。
     * <p>
     * 逐行对应原 {@code dealOperationInfo}(L107-149):
     * <pre>
     * PatMedOperation q = new PatMedOperation();
     * q.setCheckFlag("0");                       // 未处理
     * List&lt;PatMedOperation&gt; list = selectPatMedOperationList(q);
     * log.error("dealOperationInfo需要处理的手术信息:{}", list);   // ⚠️ 原实现是 error 级
     * for (每条记录) {
     *     查档案;
     *     try {
     *         按 opcode 查 longtask=1 的任务;
     *         为空  -> checkFlag="2" + "该手术编码,未配置长期任务;" + update + continue
     *         多个  -> longTaskReason="该手术编码,有多个随访长期任务:"+taskIds + update,只留第一个
     *         writeInSubTask(第一个任务ID, true, 记录, 档案);
     *     } catch (Exception e) { e.printStackTrace(); log.error("人员处理出异常了:{}", e.getMessage()); }
     * }
     * return 1;   // 固定值
     * </pre>
     *
     * @return 固定 {@code 1}(与原实现一致,<b>不是</b>处理条数)
     */
    public Integer dealOperationInfo() {
        ServiceTaskMatchApi matchApi = matchApi();
        VisitTaskCreateApi taskApi = taskApi();
        ArchiveReadApi archiveApi = archiveApi();
        if (matchApi == null || taskApi == null) {
            log.warn("【手术随访】跨模块契约不可用(ServiceTaskMatchApi={}, VisitTaskCreateApi={}),跳过本次手术随访。",
                    matchApi != null, taskApi != null);
            return 1;
        }
 
        PatMedOperation query = new PatMedOperation();
        // 获取需要出院随访,未处理的数据
        query.setCheckFlag("0");
        List<PatMedOperation> patMedOperations = operationService.selectList(query);
        // ⚠️ 原实现用的是 log.error(不是 info/warn),级别照抄
        log.error("dealOperationInfo需要处理的手术信息:{}", patMedOperations);
        if (CollectionUtils.isEmpty(patMedOperations)) {
            return 1;
        }
 
        for (PatMedOperation patMedOperation1 : patMedOperations) {
            // 获取患者信息
            VisitArchiveRow patArchive = archiveApi == null ? null : archiveApi.findByPatid(patMedOperation1.getPatid());
            try {
                // 根据患者所在科室,获取该手术编码对应的长期任务
                List<ServiceTaskMatchDto> serviceTaskopers = matchApi.findLongTasksByOpcode(patMedOperation1.getOpcode());
                // 如果部门模板为空(暂定,将出入院表的check_flag设置为2  ,long_task_reason 记录原因)
                if (CollectionUtils.isEmpty(serviceTaskopers) || serviceTaskopers.size() == 0) {
                    patMedOperation1.setCheckFlag("2");
                    patMedOperation1.setLongTaskReason("该手术编码,未配置长期任务;");
                    operationService.update(patMedOperation1);
                    continue;
                } else if (serviceTaskopers.size() > 1) {
                    // 如果查询出多个任务,那就取第一个,并在患者备注一下
                    List<Long> taskIds = serviceTaskopers.stream().map(ServiceTaskMatchDto::getTaskId).collect(Collectors.toList());
                    patMedOperation1.setLongTaskReason("该手术编码,有多个随访长期任务:" + taskIds);
                    operationService.update(patMedOperation1);
                    ServiceTaskMatchDto serviceTaskoper1 = serviceTaskopers.get(0);
                    serviceTaskopers = new ArrayList<>();
                    serviceTaskopers.add(serviceTaskoper1);
                }
 
                writeInSubTask(taskApi, serviceTaskopers.get(0).getTaskId(), Boolean.TRUE, patMedOperation1, patArchive);
 
            } catch (Exception e) {
                e.printStackTrace();
                log.error("人员处理出异常了:{}", e.getMessage());
            }
        }
 
        return 1;
    }
 
    /**
     * 把一条手术记录写入随访子任务。
     * <p>
     * 逐行对应原 {@code PatMedOperationServiceImpl#writeInSubTask}(L152-187)。
     *
     * <h3>⚠️ {@code check} 参数在原实现里<b>没有被使用</b></h3>
     * 原方法签名带 {@code Boolean check},但方法体里<b>一次都没读它</b>
     * (不像门诊版会用它决定"先停旧再新增")。这里刻意保留同样的签名与"不读"的写法,
     * 以免将来对照代码时误以为漏了一段逻辑。
     *
     * @param check 原实现传入但未使用;保留签名只为逐行可比
     */
    private void writeInSubTask(VisitTaskCreateApi taskApi, Long taskid, Boolean check,
                                PatMedOperation patMedOperation1, VisitArchiveRow patArchive) {
        String longTaskReason = patMedOperation1.getLongTaskReason();
        if (StringUtils.isEmpty(longTaskReason)) longTaskReason = "";
        // 原 L156-159:查「已发布的长期任务」(taskid + sendState=2)
        VisitLongTaskDto serviceTask = taskApi.findPublishedLongTask(taskid);
        if (serviceTask == null) {
            log.error("该手术编码长期任务不存在,任务ID为:{}", taskid);
            patMedOperation1.setCheckFlag("2");
            patMedOperation1.setLongTaskReason(longTaskReason + " & 该手术编码长期任务不存在,任务ID为:" + taskid);
            operationService.update(patMedOperation1);
            return;
        }
 
        // 封装serviceSubtask
        VisitSubtaskDraft serviceSubtask = boxedServiceSubtask(serviceTask, patMedOperation1, patArchive);
        boolean i = taskApi.insertSubtask(serviceSubtask);
 
        if (i) {
            // 将check_flag改成1(已处理)
            PatMedOperation patMedOperation = new PatMedOperation();
            patMedOperation.setId(patMedOperation1.getId());
            patMedOperation.setCheckFlag("1");
            operationService.update(patMedOperation);
        } else {
            // 生成子任务失败,
            log.error("生成子任务失败serviceSubtask为:{}", serviceSubtask);
            PatMedOperation patMedOperation = new PatMedOperation();
            patMedOperation.setId(patMedOperation1.getId());
            patMedOperation.setRemark("生成子任务失败");
            operationService.update(patMedOperation);
        }
    }
 
    /**
     * 手术版子任务组装。
     * <p>
     * 逐字段对应原 {@code boxedServiceSubtask}(L191-238,29 个 setter)。
     *
     * <h3>原实现第一行是 {@code DtoConversionUtils.sourceToTarget(serviceTask, ServiceSubtask.class)}</h3>
     * 它整批拷了 {@code ServiceTask} 与 {@code ServiceSubtask} 的 33 个同名字段,
     * 其中 18 个之后再没被显式覆盖。本次换了数据来源——
     * {@code serviceTask} 现在是 {@link VisitLongTaskDto}(15 个字段的任务快照),
     * <b>不是</b> {@code ServiceTask} 实体。
     * <p>
     * ⚠️ <b>这是一个已登记的、与门诊版完全相同的口径限制</b>:
     * 那 18 个"靠拷贝带进子任务"的字段里,{@code VisitLongTaskDto} 只承载
     * {@code taskName}/{@code type}/{@code preachformDesc}/{@code createBy} 等已列出的几个,
     * 其余({@code guid}/{@code pid}/{@code sendType}/{@code isupload}/…)
     * 需要由将来的「随访任务创建模块」在实现 {@code insertSubtask} 时补齐。
     * 本模块<b>不</b>自己补,也不把它们加到 {@code VisitSubtaskDraft} 上 ——
     * 那会让出入院/门诊两条已验收的链路多写列。
     * 落地方式与门诊侧({@code OuthospVisitTaskService#boxedServiceSubtask})一致。
     *
     * <h3>⚠️ 刻意保留的原行为</h3>
     * <ul>
     *   <li>{@code setStarttime} 与 {@code setEndtime} <b>同值</b>(都取 {@code opreqtime}),原样保留;</li>
     *   <li>{@code serviceTask.getSendDay() == null} 时按 1 天处理,且<b>写回 serviceTask</b>;</li>
     *   <li>{@code addDays} 的基准是 {@code opreqtime}(可能为 null,
     *       原实现同样会 NPE —— 不额外加保护);</li>
     *   <li>不需要随访时置 {@code sendstate=4} + {@code result="error"} 并把原因写进 remark,
     *       但<b>仍然继续 insert</b>(原实现没有提前 return);</li>
     *   <li>{@code setPhone} 只在 {@code telcode} 为空时才回落到 {@code relativetelcode}
     *       (与门诊版"三元表达式"写法等价,此处照抄原 if 写法)。</li>
     * </ul>
     */
    public VisitSubtaskDraft boxedServiceSubtask(VisitLongTaskDto serviceTask,
                                                 PatMedOperation patMedOperation1,
                                                 VisitArchiveRow patArchive) {
        VisitSubtaskDraft serviceSubtask = new VisitSubtaskDraft();
        serviceSubtask.setTaskid(serviceTask.getTaskid());
        serviceSubtask.setOrgid(serviceTask.getOrgid());
        serviceSubtask.setCampusid(serviceTask.getCampusid());
        // 原 L194-195:libtemplateid 在 ServiceTask 里是 String,落库列/草稿里是 Long,
        // 原实现显式 Long.valueOf 转换,且【仅在非空时】设值(为空则保持 null -> 列不写)。原样保留。
        if (StringUtils.isNotEmpty(serviceTask.getLibtemplateid()))
            serviceSubtask.setLibtemplateid(Long.valueOf(serviceTask.getLibtemplateid()));
        serviceSubtask.setNurseId(patMedOperation1.getNurseId());
        serviceSubtask.setNurseName(patMedOperation1.getNurseName());
//        serviceSubtask.setInhospid(patMedOperation1.getInhospid());
        serviceSubtask.setDrcode(patMedOperation1.getDrcode());
        serviceSubtask.setDrname(patMedOperation1.getDrname());
        serviceSubtask.setDeptcode(patMedOperation1.getDeptcode());
        serviceSubtask.setDeptname(patMedOperation1.getDeptname());
        serviceSubtask.setTemplateid(serviceTask.getTemplateid());
        serviceSubtask.setTemplatename(serviceTask.getTemplatename());
        serviceSubtask.setPatid(patArchive.getId());
        serviceSubtask.setSendname(patArchive.getName());
        serviceSubtask.setSfzh(patArchive.getIdcardno());
        serviceSubtask.setPhone(patArchive.getTelcode());
        if (StringUtils.isBlank(patArchive.getTelcode())) serviceSubtask.setPhone(patArchive.getRelativetelcode());
        serviceSubtask.setSex(patArchive.getSex());
        serviceSubtask.setAge(patArchive.getAge());
        serviceSubtask.setSendstate(SENDSTATE_PENDING);
        serviceSubtask.setStarttime(patMedOperation1.getOpreqtime());
        serviceSubtask.setEndtime(patMedOperation1.getOpreqtime());
        serviceSubtask.setServiceType("2");
        serviceSubtask.setPreachform(serviceTask.getPreachform());
        serviceSubtask.setHospType(HOSP_TYPE_OPERATION);
        serviceSubtask.setCreateTime(new Date());
        serviceSubtask.setUpdateTime(new Date());
//        serviceSubtask.setCreateBy(patMedInhosp1.getNurseName());
//        serviceSubtask.setLeavehospitaldistrictcode(patMedInhosp1.getLeavehospitaldistrictcode());
//        serviceSubtask.setLeavehospitaldistrictname(patMedInhosp1.getLeavehospitaldistrictname());
        // 设置发送时间
        if (serviceTask.getSendDay() == null) serviceTask.setSendDay(1L);
        Date newDate = addDays(patMedOperation1.getOpreqtime(), serviceTask.getSendDay().intValue());
        serviceSubtask.setLongSendTime(newDate);
        serviceSubtask.setVisitTime(newDate);
        // 患者发送时间
        if (StringUtils.isNotEmpty(patArchive.getNotrequiredFlag()) && patArchive.getNotrequiredFlag().equals("1")) {
            String remark = patArchive.getNotrequiredreason();
            serviceSubtask.setRemark(remark);
            serviceSubtask.setResult("error");
//            serviceSubtask.setFinishtime(new Date());
            // 不执行
            serviceSubtask.setSendstate(SENDSTATE_SKIP);
        }
        return serviceSubtask;
    }
 
    /**
     * 在 {@code date} 上加 {@code days} 天;{@code days} 为 null 时按 1 天。
     * <p>
     * 逐行对应原 {@code PatMedOperationServiceImpl#addDays}(L240-248)。
     * ⚠️ 与出入院侧的 {@code minusDay} 不是同一个方法(那个硬编码减 2 天并忽略入参)。
     */
    private Date addDays(Date date, Integer days) {
        if (days == null) {
            days = 1;
        }
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(date);
        calendar.add(Calendar.DAY_OF_MONTH, days);
        return calendar.getTime();
    }
 
    // ==========================================================================
    // 契约取用(与门诊/出入院模块同一写法:ObjectProvider + stream,取不到就降级)
    // ==========================================================================
 
    private ServiceTaskMatchApi matchApi() {
        return providerGet(matchApiProvider, "ServiceTaskMatchApi");
    }
 
    private ArchiveReadApi archiveApi() {
        return providerGet(archiveApiProvider, "ArchiveReadApi");
    }
 
    private VisitTaskCreateApi taskApi() {
        return providerGet(taskApiProvider, "VisitTaskCreateApi");
    }
 
    private <T> T providerGet(ObjectProvider<T> p, String name) {
        if (p == null) {
            return null;
        }
        try {
            return p.stream().findFirst().orElse(null);
        } catch (Exception e) {
            log.warn("【手术随访】获取 {} 失败,按不可用处理:{}", name, e.toString());
            return null;
        }
    }
}