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;
|
|
/**
|
* 手术随访编排:扫描未处理的手术记录 → 按<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<PatMedOperation> 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;
|
}
|
}
|
}
|