package com.ruoyi.task.api.contract;
|
|
import com.ruoyi.common.core.service.VisitTaskCreateApi;
|
import com.ruoyi.common.core.service.dto.VisitLongTaskDto;
|
import com.ruoyi.common.core.service.dto.VisitPendingSubtaskDto;
|
import com.ruoyi.common.core.service.dto.VisitSubtaskDraft;
|
import lombok.extern.slf4j.Slf4j;
|
import org.apache.commons.collections4.CollectionUtils;
|
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.stereotype.Component;
|
|
import java.util.ArrayList;
|
import java.util.Collections;
|
import java.util.List;
|
|
import com.ruoyi.task.domain.entity.ServiceSubtaskEntity;
|
import com.ruoyi.task.domain.ServiceSubtask;
|
import com.ruoyi.task.domain.ServiceTask;
|
import com.ruoyi.task.mapper.ServiceSubtaskMapper;
|
import com.ruoyi.task.mapper.ServiceTaskMapper;
|
|
/**
|
* 【过渡实现·临时】{@link VisitTaskCreateApi} 在 smartor 侧的桥接。
|
*
|
* <h3>为什么需要它</h3>
|
* 出入院随访要"建随访任务",但按你的版图,<b>任务创建将来是独立模块</b>。
|
* 在它落地之前,先把现有的写库能力({@link ServiceSubtaskMapper} /
|
* {@code IPatMedInhospService#persistSubtask})原样包一层,
|
* 让 {@code ruoyi-inhospvisit} 现在就只依赖 {@link VisitTaskCreateApi} 这个接口。
|
* <p>
|
* <b>将来动作</b>:新模块建好后,把它的实现注册进容器,
|
* 本类整体删除即可,{@code ruoyi-inhospvisit} <b>一行都不用改</b>。
|
*
|
* <h3>⚠️ 本类必须保持"纯搬运"</h3>
|
* 这里<b>不得</b>加入去重、状态机、默认值补齐、合并更新等任何新逻辑 ——
|
* 那些语义在 {@code ruoyi-inhospvisit} 的 {@code writeInSubTask} 里。
|
* 本类只做:<b>字段搬运</b> + <b>转发现有 Mapper 调用</b>。
|
* 逐字段对应关系由 {@code sql/_probe/verify_inhospvisit_equivalence.py}(A 档)与
|
* {@code verify_inhospvisit_semantics.py}(B 档口径断言)把关。
|
*
|
* <h3>逐方法来源(迁移前 {@code PatMedInhospServiceImpl},@ b25c3b51)</h3>
|
* <table border="1">
|
* <tr><th>本类方法</th><th>原实现位置</th></tr>
|
* <tr><td>{@link #findPublishedLongTask(Long)}</td><td>L686-728(查 ServiceTask,sendState=2)</td></tr>
|
* <tr><td>{@link #insertSubtask(VisitSubtaskDraft)}</td><td>L823 / L838 / L854 / L862 / L873</td></tr>
|
* <tr><td>{@link #findPendingSubtasks(Long, Long)}</td><td>L760-764、L1325-1328</td></tr>
|
* <tr><td>{@link #updatePendingSubtask(VisitPendingSubtaskDto)}</td><td>L782 / L791 / L812 / L836 / L1349</td></tr>
|
* </table>
|
*
|
* @see com.ruoyi.common.core.service.VisitTaskCreateApi
|
*/
|
@Slf4j
|
@Component
|
public class VisitTaskCreateApiImpl implements VisitTaskCreateApi {
|
|
@Autowired
|
private ServiceTaskMapper serviceTaskMapper;
|
|
@Autowired
|
private ServiceSubtaskMapper serviceSubtaskMapper;
|
|
|
/**
|
* 按 taskid 取「已发布的长期任务」。
|
* <p>
|
* 原实现({@code PatMedInhospServiceImpl} L686-690):
|
* <pre>
|
* ServiceTask st = new ServiceTask();
|
* st.setTaskid(taskid);
|
* st.setSendState(2L);
|
* List<ServiceTask> serviceTasks = serviceTaskMapper.selectServiceTaskList(st);
|
* ServiceTask serviceTask = null;
|
* if (CollectionUtils.isEmpty(serviceTasks)) { ...按 type 写不同 flag 后 return... }
|
* else { serviceTask = serviceTasks.get(0); }
|
* </pre>
|
* 这里只负责"查 + 取第一条";"查不到时按 type 写哪个 flag"属于随访域,
|
* 留在 {@code writeInSubTask} 里。
|
*/
|
@Override
|
public VisitLongTaskDto findPublishedLongTask(Long taskid) {
|
ServiceTask query = new ServiceTask();
|
query.setTaskid(taskid);
|
query.setSendState(2L);
|
List<ServiceTask> tasks = serviceTaskMapper.selectServiceTaskList(query);
|
if (CollectionUtils.isEmpty(tasks)) {
|
return null;
|
}
|
return toLongTaskDto(tasks.get(0));
|
}
|
|
@Override
|
public boolean insertSubtask(VisitSubtaskDraft draft) {
|
if (draft == null) {
|
log.warn("【出入院随访·过渡桥】insertSubtask 收到 null 草稿,跳过");
|
return false;
|
}
|
ServiceSubtask entity = toSubtaskEntity(draft);
|
// 转发现有过渡入口(等价于 serviceSubtaskMapper.insertServiceSubtask(entity) == 1)
|
boolean ok = serviceSubtaskMapper.insertServiceSubtask(entity) == 1;
|
// useGeneratedKeys 会把自增主键回填到 entity 上,回传给出参草稿,
|
// 供"先 insert 再补字段回写"的那条链路使用(原实现也是复用同一个对象)
|
draft.setId(entity.getId());
|
return ok;
|
}
|
|
@Override
|
public List<VisitPendingSubtaskDto> findPendingSubtasks(Long patid, Long taskid) {
|
ServiceSubtaskEntity query = new ServiceSubtaskEntity();
|
query.setPatid(patid);
|
query.setSendstate(2L);
|
// 原实现在 dealInHospInfo(L1325-1328)里【不设 taskid】(按患者全量查),
|
// 在 writeInSubTask(L760-764)里【设 taskid】。这里保持同样的可选语义。
|
query.setTaskid(taskid);
|
List<ServiceSubtask> rows = serviceSubtaskMapper.selectServiceSubtaskList(query);
|
if (CollectionUtils.isEmpty(rows)) {
|
return Collections.emptyList();
|
}
|
List<VisitPendingSubtaskDto> out = new ArrayList<>(rows.size());
|
for (ServiceSubtask row : rows) {
|
out.add(toPendingDto(row));
|
}
|
return out;
|
}
|
|
@Override
|
public boolean updatePendingSubtask(VisitPendingSubtaskDto pending) {
|
if (pending == null || pending.getId() == null) {
|
log.warn("【出入院随访·过渡桥】updatePendingSubtask 缺少 id,跳过。pending={}", pending);
|
return false;
|
}
|
ServiceSubtask entity = new ServiceSubtask();
|
// 只映射"原实现会带过去"的字段:
|
// 原实现传的是【查询出来的完整对象】+ 在内存里改的那几个字段。
|
// 查询出来的完整对象里,与本方法语义相关的只有 id/taskid/endtime/longSendTime/serviceType/orgid/sendstate/patid,
|
// 其余字段值不变、写回也等于不写,故此处只带这些 + 调用方改过的 result/remark/updateBy/updateTime。
|
entity.setId(pending.getId());
|
entity.setTaskid(pending.getTaskid());
|
entity.setSendstate(pending.getSendstate());
|
entity.setResult(pending.getResult());
|
entity.setRemark(pending.getRemark());
|
entity.setUpdateBy(pending.getUpdateBy());
|
entity.setUpdateTime(pending.getUpdateTime());
|
entity.setIsVisitAgain(pending.getIsVisitAgain());
|
// finishtime:出入院侧从不设置(原样保留"不写"),门诊侧会设置
|
entity.setFinishtime(pending.getFinishtime());
|
return Boolean.TRUE.equals(serviceSubtaskMapper.updateServiceSubtask(entity));
|
}
|
|
/**
|
* 取任务的 {@code preachformDesc}。
|
* <p>
|
* 原实现({@code PatMedInhospServiceImpl#addServiceSubtaskPreachform} L1063)用的是
|
* {@code serviceTaskMapper.selectServiceTaskByTaskid(taskid)} ——
|
* <b>不带 sendState 条件</b>,且查不到时返回 null。
|
* 这里逐字照搬,不复用 {@link #findPublishedLongTask}(那个带 {@code sendState=2})。
|
*/
|
@Override
|
public String findPreachformDesc(Long taskid) {
|
if (taskid == null) {
|
return null;
|
}
|
ServiceTask task = serviceTaskMapper.selectServiceTaskByTaskid(taskid);
|
return task == null ? null : task.getPreachformDesc();
|
}
|
|
/**
|
* 是否存在「指定状态 + 指定服务类型」的子任务。
|
* <p>
|
* 原实现(门诊 {@code selectPatMedOuthospList} L108-117):调
|
* {@code serviceSubtaskMapper.selectServiceSubtaskBySendstate(v)},
|
* 参数是 {@code ServiceSubtaskEntity},设了 {@code patid} / {@code sendstates} / {@code serviceType}。
|
* 这里逐字照搬,只把"非空"折叠成 {@code boolean}(调用方原本只用它做 if 判断)。
|
*
|
* <p>⚠️ 不属于 {@link #findPendingSubtasks(Long, Long)}:那个固定 {@code sendstate=2}
|
* 且按 taskid 过滤、查 {@code selectServiceSubtaskList},口径不同、不可互替。
|
*/
|
@Override
|
public boolean existsSubtaskWithStates(Long patid, java.util.List<Long> sendstates, String serviceType) {
|
if (patid == null) {
|
return false;
|
}
|
ServiceSubtaskEntity v = new ServiceSubtaskEntity();
|
v.setPatid(patid);
|
v.setSendstates(sendstates);
|
v.setServiceType(serviceType);
|
List<ServiceSubtask> list = serviceSubtaskMapper.selectServiceSubtaskBySendstate(v);
|
return !CollectionUtils.isEmpty(list);
|
}
|
|
/**
|
* 是否存在「同一患者 + 同一次出院记录 + 同一任务」的子任务({@code del_flag = 0})。
|
* <p>
|
* 直接用现成的 {@code selectServiceSubtaskList}(本身就带
|
* {@code del_flag = 0} + {@code patid} + {@code inhospid} + {@code taskid} 三个条件),
|
* 不新增 XML —— 避免为了一个"有没有"再写一条等价 SQL。
|
* <p>
|
* ⚠️ 不按 {@code sendstate} 过滤:已停用(sendstate=4)的历史行也算"生成过"。
|
* 否则"停旧一条 + 再生成一条"会无限累积 —— 这正是 2026-10 同一患者几十条子任务的成因。
|
*/
|
@Override
|
public boolean existsSubtask(Long patid, Long inhospid, Long taskid) {
|
if (patid == null || taskid == null) {
|
return false;
|
}
|
ServiceSubtaskEntity q = new ServiceSubtaskEntity();
|
q.setPatid(patid);
|
q.setInhospid(inhospid);
|
q.setTaskid(taskid);
|
return !CollectionUtils.isEmpty(serviceSubtaskMapper.selectServiceSubtaskList(q));
|
}
|
|
// ==========================================================================
|
// 字段搬运(逐字段对照,勿"顺手精简")
|
// ==========================================================================
|
|
private VisitLongTaskDto toLongTaskDto(ServiceTask t) {
|
VisitLongTaskDto dto = new VisitLongTaskDto();
|
dto.setTaskid(t.getTaskid());
|
dto.setTaskName(t.getTaskName());
|
dto.setTaskDesc(t.getTaskDesc());
|
dto.setType(t.getType());
|
dto.setServiceType(t.getServiceType());
|
dto.setTemplateid(t.getTemplateid());
|
dto.setTemplatename(t.getTemplatename());
|
dto.setLibtemplateid(t.getLibtemplateid());
|
dto.setLibtemplatename(t.getLibtemplatename());
|
dto.setSendDay(t.getSendDay());
|
dto.setPreachform(t.getPreachform());
|
dto.setPreachformDesc(t.getPreachformDesc());
|
dto.setOrgid(t.getOrgid());
|
dto.setCampusid(t.getCampusid());
|
dto.setHosType(t.getHospType());
|
// 原 boxedServiceSubtask 第一行的 DtoConversionUtils.sourceToTarget 会把这 6 个
|
// 拷进子任务并随 insertServiceSubtask 落库;不搬就是静默丢列(见 VisitLongTaskDto 注释)
|
dto.setGuid(t.getGuid());
|
dto.setPid(t.getPid());
|
dto.setSendType(t.getSendType());
|
dto.setIsupload(t.getIsupload());
|
dto.setTextParam(t.getTextParam());
|
dto.setUploadTime(t.getUploadTime());
|
dto.setUpdateBy(t.getUpdateBy());
|
dto.setCreateBy(t.getCreateBy());
|
return dto;
|
}
|
|
/**
|
* 草稿 → 实体。
|
* <p>
|
* 逐个 setter 对照迁移前的 {@code boxedServiceSubtask} 与 {@code writeInSubTask},
|
* <b>少一个字段就会让对应列静默不更新</b>(XML 里是 {@code <if test="xxx != null">}),
|
* 所以这里刻意写全,不做精简。
|
*/
|
private ServiceSubtask toSubtaskEntity(VisitSubtaskDraft d) {
|
ServiceSubtask e = new ServiceSubtask();
|
e.setId(d.getId());
|
e.setTaskid(d.getTaskid());
|
e.setOrgid(d.getOrgid());
|
e.setCampusid(d.getCampusid());
|
e.setPatid(d.getPatid());
|
e.setSendname(d.getSendname());
|
e.setSfzh(d.getSfzh());
|
e.setPhone(d.getPhone());
|
e.setSex(d.getSex());
|
e.setAge(d.getAge());
|
e.setInhospid(d.getInhospid());
|
e.setHospno(d.getHospno());
|
e.setNurseId(d.getNurseId());
|
e.setNurseName(d.getNurseName());
|
e.setDrcode(d.getDrcode());
|
e.setDrname(d.getDrname());
|
e.setManagementDoctor(d.getManagementDoctor());
|
e.setManagementDoctorCode(d.getManagementDoctorCode());
|
e.setDeptcode(d.getDeptcode());
|
e.setDeptname(d.getDeptname());
|
e.setLeavehospitaldistrictcode(d.getLeavehospitaldistrictcode());
|
e.setLeavehospitaldistrictname(d.getLeavehospitaldistrictname());
|
e.setLeaveicd10code(d.getLeaveicd10code());
|
e.setLeavediagname(d.getLeavediagname());
|
e.setDiagname(d.getDiagname());
|
e.setTemplateid(d.getTemplateid());
|
e.setTemplatename(d.getTemplatename());
|
e.setLibtemplateid(d.getLibtemplateid());
|
e.setLibtemplatename(d.getLibtemplatename());
|
e.setStarttime(d.getStarttime());
|
e.setEndtime(d.getEndtime());
|
e.setLongSendTime(d.getLongSendTime());
|
e.setVisitTime(d.getVisitTime());
|
e.setSendstate(d.getSendstate());
|
e.setSubmit(d.getSubmit());
|
e.setServiceType(d.getServiceType());
|
e.setDiagType(d.getDiagType());
|
e.setHospType(d.getHospType());
|
e.setPreachform(d.getPreachform());
|
e.setVisitCount(d.getVisitCount());
|
e.setIsVisitAgain(d.getIsVisitAgain());
|
e.setVisitDeptCode(d.getVisitDeptCode());
|
e.setVisitDeptName(d.getVisitDeptName());
|
e.setTaskSituation(d.getTaskSituation());
|
e.setResult(d.getResult());
|
e.setRemark(d.getRemark());
|
e.setOperationItemId(d.getOperationItemId());
|
e.setCreateBy(d.getCreateBy());
|
e.setUpdateBy(d.getUpdateBy());
|
// ⚠️ create_time / update_time 之前也是漏的:
|
// 组装器(boxedServiceSubtask 原 L1157-1177)、writeInSubTask 的每个 insert 分支
|
// 都在 setCreateTime/setUpdateTime,XML 也有对应条件列,但这里没映射 →
|
// 两条时间列静默落 NULL(2026-10 实机发现)。
|
e.setCreateTime(d.getCreateTime());
|
e.setUpdateTime(d.getUpdateTime());
|
// ------------------------------------------------------------------
|
// 任务来源字段(原 boxedServiceSubtask 第一行 DtoConversionUtils 整批拷入)
|
//
|
// ⚠️ 这 8 个是"任务 → 子任务"的快照列。原先只搬了 taskid/serviceType 等,
|
// taskName/taskDesc/type/libtemplatename 以及 guid/pid/sendType/isupload/
|
// textParam/uploadTime 全部漏映射 → XML 的 <if test="xxx != null"> 直接跳过,
|
// service_subtask 上这些列静默为 NULL(2026-10 由 addSubTaskByRule 实机发现)。
|
// ------------------------------------------------------------------
|
e.setTaskName(d.getTaskName());
|
e.setTaskDesc(d.getTaskDesc());
|
e.setType(d.getType());
|
e.setGuid(d.getGuid());
|
e.setPid(d.getPid());
|
e.setSendType(d.getSendType());
|
e.setIsupload(d.getIsupload());
|
e.setTextParam(d.getTextParam());
|
e.setUploadTime(d.getUploadTime());
|
|
// ------------------------------------------------------------------
|
// 门急诊随访专用字段(OuthospSubtaskDraft)。
|
//
|
// ⚠️ 用 instanceof 分流,而不是给 VisitSubtaskDraft 加字段:
|
// 加了就必须在这里补映射,而补映射会让【出入院那条已验收的路径】多写列
|
// (insertServiceSubtask 是 39 个条件列,本来传 null 的列从此会被写进去)。
|
// 分流后:出入院传的是 VisitSubtaskDraft,下面整块不执行 —— 行为完全不变。
|
// 见 OuthospSubtaskDraft 的类注释。
|
// ------------------------------------------------------------------
|
if (d instanceof com.ruoyi.common.core.service.dto.OuthospSubtaskDraft) {
|
com.ruoyi.common.core.service.dto.OuthospSubtaskDraft o = (com.ruoyi.common.core.service.dto.OuthospSubtaskDraft) d;
|
// hospno 已上提到父类 VisitSubtaskDraft(上面统一映射过了),这里不再重复
|
// 只有父类没有的字段才在这里补映射(其余如 sendname/sfzh/phone/sex/age 父类已有)
|
e.setAdmindate(o.getAdmitdate());
|
e.setFinishtime(o.getFinishtime());
|
// ⚠️ 原实现硬编码 "1"(不读 serviceTask.getHospType()),此处保持一致
|
e.setHospType(o.getHospType());
|
}
|
return e;
|
}
|
|
private VisitPendingSubtaskDto toPendingDto(ServiceSubtask s) {
|
VisitPendingSubtaskDto dto = new VisitPendingSubtaskDto();
|
dto.setId(s.getId());
|
dto.setTaskid(s.getTaskid());
|
dto.setEndtime(s.getEndtime());
|
dto.setLongSendTime(s.getLongSendTime());
|
dto.setServiceType(s.getServiceType());
|
dto.setOrgid(s.getOrgid());
|
dto.setSendstate(s.getSendstate());
|
// finishtime:门诊侧停旧子任务时要读到它吗?原实现是"在查询出来的完整对象上改",
|
// 而它要改的就是 finishtime 本身(设为 new Date()),所以这里带上查询值可保证
|
// "未改动的字段回写等于不写"这一语义成立。
|
dto.setFinishtime(s.getFinishtime());
|
// result / remark / updateBy / updateTime 刻意【不带过来】:
|
// 它们原本就是 null(查询结果为 sendstate=2 的待执行记录),
|
// 由调用方在"移出"时按原实现设置。
|
return dto;
|
}
|
}
|