package com.ruoyi.outpatientvisit.service; import com.ruoyi.common.core.service.ArchiveReadApi; import com.ruoyi.common.core.service.SubtaskStatApi; import com.ruoyi.common.core.service.VisitTaskCreateApi; import com.ruoyi.common.core.service.dto.PatMedReq; import com.ruoyi.common.core.service.dto.PatMedRes; import com.ruoyi.common.core.service.dto.VisitArchiveRow; import com.ruoyi.common.utils.DateUtils; import com.ruoyi.common.utils.DtoConversionUtils; import com.ruoyi.common.utils.StringUtils; import com.ruoyi.outpatientvisit.domain.PatMedOuthosp; import com.ruoyi.outpatientvisit.domain.PatMedOuthospQueryReq; import com.ruoyi.outpatientvisit.mapper.PatMedOuthospMapper; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.ObjectUtils; import org.springframework.beans.factory.ObjectProvider; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.util.CollectionUtils; import java.time.LocalDate; import java.time.Period; import java.time.ZoneId; import java.time.temporal.ChronoUnit; import java.util.*; /** * 门诊记录基础数据服务:查询(含展示富化)/ 统计 / 增删改 / 分表路由。 * *
| 本类方法 | 原方法 |
|---|---|
| {@link #selectList(PatMedOuthosp)} | {@code selectPatMedOuthospList} L83-133 |
| {@link #calculateAge} | {@code calculateAge} L135-185 |
| {@link #insert(PatMedOuthosp)} | {@code insertPatMedOuthosp} L187-204 |
| {@link #selectExistingSerialnums} | 同名方法 L210-259 |
| {@link #update(PatMedOuthosp)} | {@code updatePatMedOuthosp} L268-291 |
| {@link #resolveTargetTable} | 同名私有方法 L305-341 |
| {@link #selectCount(PatMedReq)} | {@code selectPatMedOuthospCount} L335-348 |
| {@link #getDeptCodeByPatId} | 同名方法 L351-357 |
| {@link #selectById} | {@code selectPatMedOuthospById} L68-71(空壳) |
| {@link #deleteByIds} / {@link #deleteById} | *L319-332(均为空壳) |
* 与 {@link #selectList} 的区别:{@code selectList} 会在 {@code pageNum != null} 时 * 做年龄/性别/电话富化 + 子任务 {@code serverState} 查询; * 本方法是"原样把存储过程结果给你",供不需要富化的内部调用方使用 * (如 smartor 的随访答复明细,它只需要原始记录)。 *
* ⚠️ 入参请按最小必要字段构造,不要用 {@code DtoConversionUtils} 全量带字段 ——
* 多带的字段可能改变存储过程的 WHERE 分支。
*/
public List
* ⚠️ 原实现是空壳(L68-71):`// return patMedOuthospMapper.selectPatMedOuthospById(id); return null;`
* —— 本方法原样保留空壳语义,不要"顺手实现"它(那会改变线上行为)。
* 需要按主键取记录请用 {@link #selectList} 或存储过程。
*/
public PatMedOuthosp selectById(Long id) {
return null;
}
/**
* 按条件走存储过程查门诊记录条数(纯转发,原 {@code callSpQueryOuthospCount})。
*
* 供不需要富化、只需要计数的跨模块调用方使用(如 smartor 的随访统计)。
*/
public Long callSpQueryOuthospCount(PatMedOuthospQueryReq req) {
return mapper.callSpQueryOuthospCount(req);
}
/**
* 门诊记录列表(含展示富化)。
*
* 逐行对应原 {@code selectPatMedOuthospList}(L83-133):
*
* 逐行对应原 {@code selectPatMedOuthospCount}(L335-348):
*
* ⚠️ 原实现的空列表越界缺陷原样保留(L351-357):
* 逐行对应原 {@code insertPatMedOuthosp}(L187-204)。
*/
public int insert(PatMedOuthosp row) {
if (row.getAdmitdate() == null) {
log.error("门急诊就诊时间不能为空");
return 0;
}
String targetTable = resolveTargetTable(row.getAdmitdate());
if (targetTable == null) {
return 0;
}
// 插入前按 serialnum 查重,防止重复采集导致重复入库
if (StringUtils.isNotBlank(row.getSerialnum())) {
int exists = mapper.countBySerialnum(targetTable, row.getSerialnum());
if (exists > 0) {
log.info("[insert] serialnum={} 已存在于表 {},跳过插入", row.getSerialnum(), targetTable);
return 0;
}
}
row.setCreateTime(DateUtils.getNowDate());
row.setUpdateTime(DateUtils.getNowDate());
row.setGuid(UUID.randomUUID().toString());
log.info("[insert] 路由到分表: {}", targetTable);
try {
return mapper.insertIntoTable(targetTable, row);
} catch (org.springframework.dao.DuplicateKeyException e) {
// 加了 serialnum 唯一索引后,极端并发或重复采集会被数据库直接拦下;
// 这属于"记录已存在",按正常跳过处理(返回 0),不要当成入库失败写进错误表
log.info("[insert] serialnum={} 已存在于表 {}(唯一索引拦截),跳过插入", row.getSerialnum(), targetTable);
return 0;
}
}
/**
* 批量查已入库的 serialnum(一次 IN 查询,避免逐条 count)。
* 逐行对应原 {@code selectExistingSerialnums}(L210-259)。
*/
public Set 逐行对应原 {@code updatePatMedOuthosp}(L268-291)。
* ⚠️ 查不到时转调 {@link #insert} 但丢弃其返回值,最终仍 {@code return 0} —— 原样保留。
*/
public int update(PatMedOuthosp row) {
if (row.getSerialnum() == null) {
log.error("流水号不能为空");
return 0;
}
PatMedOuthospQueryReq req = new PatMedOuthospQueryReq();
req.setSerialnum(row.getSerialnum());
req.setGuid(row.getGuid());
req.setOuthospno(row.getOuthospno());
List
* ⚠️ 原实现是空壳(L319-322):`// return patMedOuthospMapper.deletePatMedOuthospByIds(ids); return 0;`
* —— 原样保留。
*/
public int deleteByIds(Long[] ids) {
return 0;
}
/**
* 按主键删除。
*
* ⚠️ 原实现是空壳(L328-332):`// return patMedOuthospMapper.deletePatMedOuthospById(id); return 0;`
* —— 原样保留。
*/
public int deleteById(Long id) {
return 0;
}
// ==========================================================================
// 分表路由
// ==========================================================================
/**
* 根据就诊时间推算目标分表名,与建表策略保持一致。
* 逐行对应原私有方法 {@code resolveTargetTable}(L305-341):
*
*
*
* ⚠️ 富化里有一处原实现的 NPE 风险(原样保留)
* {@code patArchive.getAge() + getAgeUnit() + getAge2() + getAgeUnit2()} 在
* {@code calculateAge} 返回 null(生日在未来)时会 NPE;原实现没有防护。
*/
public List
*
*
* @return 含 {@code age/ageUnit/age2/ageUnit2} 的 map;
* {@code birthdate} 为 null 或晚于今天时返回 {@code null}
*/
public Map
* 本域 callSpQueryOuthospCount(req) -> count
* 随访域 countVisitForStat(patMedReq) -> PatMedRes
* patMedRes.setRc(count)
*
* ⚠️ 原实现不判 {@code patMedRes} 是否 null 就直接 setRc,此处保持原样。
*/
public PatMedRes selectCount(PatMedReq patMedReq) {
PatMedOuthospQueryReq req = new PatMedOuthospQueryReq();
String deptcodes = CollectionUtils.isEmpty(patMedReq.getDeptcodeList())
? null : String.join(",", patMedReq.getDeptcodeList());
req.setBeginAdmitdate(patMedReq.getStartDate());
req.setEndAdmitdate(DateUtils.addDays(patMedReq.getEndDate(), 1));
req.setDeptcode(deptcodes);
req.setOrgid(patMedReq.getOrgid());
// 原实现把下面这行注释掉了,原样保留(不设 campusid)
// req.setCampusid(patMedReq.getCampusid());
Long count = mapper.callSpQueryOuthospCount(req);
SubtaskStatApi statApi = statApi();
if (statApi == null) {
log.warn("【门急诊随访】SubtaskStatApi 不可用,selectCount 返回 null。");
return null;
}
PatMedRes patMedRes = statApi.countVisitForStat(patMedReq);
patMedRes.setRc(count == null ? 0 : count.intValue());
return patMedRes;
}
/**
* 按 patid 取最新一条门诊记录(按就诊日期倒序)。
*
* if (!CollectionUtils.isEmpty(list)) { Collections.sort(...); }
* return list.get(0); // ← 列表为空时在这里越界
*
*/
public PatMedOuthosp getDeptCodeByPatId(PatMedOuthosp patMedOuthosp) {
List
*
* 推算出表名后校验是否存在,不存在则回落主表(返回 {@code "pat_med_outhosp"},
* 不是 null)—— 注意与"参数为 null 时返回 null"的区别。
*/
private String resolveTargetTable(Date createTime) {
if (createTime == null) {
return null;
}
List