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 侧的桥接。
*
*
为什么需要它
* 出入院随访要"建随访任务",但按你的版图,任务创建将来是独立模块。
* 在它落地之前,先把现有的写库能力({@link ServiceSubtaskMapper} /
* {@code IPatMedInhospService#persistSubtask})原样包一层,
* 让 {@code ruoyi-inhospvisit} 现在就只依赖 {@link VisitTaskCreateApi} 这个接口。
*
* 将来动作:新模块建好后,把它的实现注册进容器,
* 本类整体删除即可,{@code ruoyi-inhospvisit} 一行都不用改。
*
*
⚠️ 本类必须保持"纯搬运"
* 这里不得加入去重、状态机、默认值补齐、合并更新等任何新逻辑 ——
* 那些语义在 {@code ruoyi-inhospvisit} 的 {@code writeInSubTask} 里。
* 本类只做:字段搬运 + 转发现有 Mapper 调用。
* 逐字段对应关系由 {@code sql/_probe/verify_inhospvisit_equivalence.py}(A 档)与
* {@code verify_inhospvisit_semantics.py}(B 档口径断言)把关。
*
* 逐方法来源(迁移前 {@code PatMedInhospServiceImpl},@ b25c3b51)
*
* | 本类方法 | 原实现位置 |
* | {@link #findPublishedLongTask(Long)} | L686-728(查 ServiceTask,sendState=2) |
* | {@link #insertSubtask(VisitSubtaskDraft)} | L823 / L838 / L854 / L862 / L873 |
* | {@link #findPendingSubtasks(Long, Long)} | L760-764、L1325-1328 |
* | {@link #updatePendingSubtask(VisitPendingSubtaskDto)} | L782 / L791 / L812 / L836 / L1349 |
*
*
* @see com.ruoyi.common.core.service.VisitTaskCreateApi
*/
@Slf4j
@Component
public class VisitTaskCreateApiImpl implements VisitTaskCreateApi {
@Autowired
private ServiceTaskMapper serviceTaskMapper;
@Autowired
private ServiceSubtaskMapper serviceSubtaskMapper;
/**
* 按 taskid 取「已发布的长期任务」。
*
* 原实现({@code PatMedInhospServiceImpl} L686-690):
*
* 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); }
*
* 这里只负责"查 + 取第一条";"查不到时按 type 写哪个 flag"属于随访域,
* 留在 {@code writeInSubTask} 里。
*/
@Override
public VisitLongTaskDto findPublishedLongTask(Long taskid) {
ServiceTask query = new ServiceTask();
query.setTaskid(taskid);
query.setSendState(2L);
List 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 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 rows = serviceSubtaskMapper.selectServiceSubtaskList(query);
if (CollectionUtils.isEmpty(rows)) {
return Collections.emptyList();
}
List 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}。
*
* 原实现({@code PatMedInhospServiceImpl#addServiceSubtaskPreachform} L1063)用的是
* {@code serviceTaskMapper.selectServiceTaskByTaskid(taskid)} ——
* 不带 sendState 条件,且查不到时返回 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();
}
/**
* 是否存在「指定状态 + 指定服务类型」的子任务。
*
* 原实现(门诊 {@code selectPatMedOuthospList} L108-117):调
* {@code serviceSubtaskMapper.selectServiceSubtaskBySendstate(v)},
* 参数是 {@code ServiceSubtaskEntity},设了 {@code patid} / {@code sendstates} / {@code serviceType}。
* 这里逐字照搬,只把"非空"折叠成 {@code boolean}(调用方原本只用它做 if 判断)。
*
*
⚠️ 不属于 {@link #findPendingSubtasks(Long, Long)}:那个固定 {@code sendstate=2}
* 且按 taskid 过滤、查 {@code selectServiceSubtaskList},口径不同、不可互替。
*/
@Override
public boolean existsSubtaskWithStates(Long patid, java.util.List sendstates, String serviceType) {
if (patid == null) {
return false;
}
ServiceSubtaskEntity v = new ServiceSubtaskEntity();
v.setPatid(patid);
v.setSendstates(sendstates);
v.setServiceType(serviceType);
List list = serviceSubtaskMapper.selectServiceSubtaskBySendstate(v);
return !CollectionUtils.isEmpty(list);
}
// ==========================================================================
// 字段搬运(逐字段对照,勿"顺手精简")
// ==========================================================================
private VisitLongTaskDto toLongTaskDto(ServiceTask t) {
VisitLongTaskDto dto = new VisitLongTaskDto();
dto.setTaskid(t.getTaskid());
dto.setTaskName(t.getTaskName());
dto.setType(t.getType());
dto.setServiceType(t.getServiceType());
dto.setTemplateid(t.getTemplateid());
dto.setTemplatename(t.getTemplatename());
dto.setLibtemplateid(t.getLibtemplateid());
dto.setSendDay(t.getSendDay());
dto.setPreachform(t.getPreachform());
dto.setPreachformDesc(t.getPreachformDesc());
dto.setOrgid(t.getOrgid());
dto.setCampusid(t.getCampusid());
dto.setHosType(t.getHospType());
dto.setUpdateBy(t.getUpdateBy());
dto.setCreateBy(t.getCreateBy());
return dto;
}
/**
* 草稿 → 实体。
*
* 逐个 setter 对照迁移前的 {@code boxedServiceSubtask} 与 {@code writeInSubTask},
* 少一个字段就会让对应列静默不更新(XML 里是 {@code }),
* 所以这里刻意写全,不做精简。
*/
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.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.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());
// ------------------------------------------------------------------
// 门急诊随访专用字段(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;
// 只有父类没有的字段才在这里补映射(其余如 sendname/sfzh/phone/sex/age 父类已有)
e.setHospno(o.getHospno());
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;
}
}