package cn.lihu.jh.module.ecg.service.callingscreen;
|
|
import cn.lihu.jh.framework.common.pojo.PageResult;
|
import cn.lihu.jh.module.ecg.controller.admin.checktype.vo.CheckTypePageReqVO;
|
import cn.lihu.jh.module.ecg.dal.dataobject.checktype.CheckTypeDO;
|
import cn.lihu.jh.module.ecg.dal.dataobject.queue.QueueDO;
|
import cn.lihu.jh.module.ecg.dal.dataobject.room.RoomDO;
|
import cn.lihu.jh.module.ecg.dal.mysql.checktype.CheckTypeMapper;
|
import cn.lihu.jh.module.ecg.dal.mysql.queue.QueueMapper;
|
import cn.lihu.jh.module.ecg.dal.mysql.room.RoomMapper;
|
import cn.lihu.jh.module.ecg.enums.QueueStatusEnum;
|
import lombok.extern.slf4j.Slf4j;
|
import org.springframework.stereotype.Service;
|
import org.springframework.util.StringUtils;
|
import org.springframework.validation.annotation.Validated;
|
|
import javax.annotation.Resource;
|
import java.util.*;
|
import java.util.stream.Collectors;
|
|
/**
|
* 排队 Service 实现类
|
*
|
* @author 芋道源码
|
*/
|
@Service
|
@Validated
|
@Slf4j
|
public class CallingScreenServiceImpl implements CallingScreenService {
|
|
@Resource
|
private QueueMapper queueMapper;
|
|
@Resource
|
private RoomMapper roomMapper;
|
|
/**
|
* 需求1:用于回填患者信息(性别/出生日期/病区/床号/电话/来源/开单医师等)。
|
* <p>
|
* 下列屏幕查询是**直接查 queueMapper** 的,不经过 QueueService 的查询方法,
|
* 因此必须显式调用 fillAppointmentInfo 补齐;否则大屏/诊间屏只有姓名。
|
*/
|
@Resource
|
private cn.lihu.jh.module.ecg.service.queue.QueueService queueService;
|
|
@Resource
|
private CheckTypeMapper checkTypeMapper;
|
|
BigScreenConfig bigScreenConfig;
|
|
Map<Integer, Integer> mapCheckTypeVsDisplayColumn = null;
|
|
Integer waitingFrom = 0;
|
Integer passedFrom = 0;
|
|
@Override
|
public void setBigScreenConfig(BigScreenConfig bigScreenConfig) {
|
this.bigScreenConfig = bigScreenConfig;
|
}
|
|
|
@Override
|
public Map<Integer, List<String>> getDisplayColInfo() {
|
CheckTypePageReqVO checkTypePageReqVO = new CheckTypePageReqVO();
|
checkTypePageReqVO.setPageSize(-1);
|
checkTypePageReqVO.setCallingColumn(-1); // 列号 -1 的不显示
|
PageResult<CheckTypeDO> pageResult = checkTypeMapper.selectPageForCallingScreen(checkTypePageReqVO);
|
|
// 显示列 包含的检查类型列表
|
Map<Integer, List<CheckTypeDO>> mapDisplayColName = pageResult.getList().stream().
|
collect(
|
Collectors.groupingBy(
|
CheckTypeDO::getCallingColumn,
|
Collectors.collectingAndThen(
|
Collectors.toList(),
|
list -> list.stream().sorted(Comparator.comparing(CheckTypeDO::getId)).toList()
|
)
|
)
|
);
|
|
// 检查类型 对应的显示列
|
mapCheckTypeVsDisplayColumn = pageResult.getList().stream().collect(Collectors.toMap(CheckTypeDO::getValue, item -> item.getCallingColumn()));
|
|
// 显示列 对应的检查类型名列表
|
Map<Integer, List<String>> _map = new HashMap<>();
|
mapDisplayColName.keySet().forEach(key -> {
|
List<String> displayColNameList = mapDisplayColName.get(key).stream().map(item->item.getName()+item.getSeqPrefix()).toList();
|
_map.put(key, displayColNameList);
|
});
|
return _map;
|
}
|
|
/**
|
* 大屏显示的患者信息
|
* 不会有安装相关的患者信息
|
*/
|
@Override
|
public Map<Integer, List<QueueDO>> getBigScreenPatient() {
|
Map<Integer, List<QueueDO>> mapDisplayColQueue = new HashMap<>();
|
|
// 【修复 NPE】按**真实 calling_column 值**建键,而不是 0..count-1。
|
//
|
// 原实现:
|
// int displayColCnt = distinct().count(); // 例如 = 5
|
// for (i = 0; i < displayColCnt; i++) put(i, ...); // 建键 0,1,2,3,4
|
// 但下面取值用的是**真实 calling_column**(如 0,1,3,4,-1),
|
// 其中 -1(表示"不在大屏显示")永远不会被建键 → get(-1) 返回 null → NPE,
|
// 导致**整个大屏接口 500、整屏空白**。实测 check_type 中有 7 个类型的
|
// calling_column = -1(700/800/900/1200/1300/1400/1500),只要这些类型有患者
|
// 在排队,就会触发(大屏 SQL 无 check_type 过滤,必然被捞出)。
|
//
|
// 修复后:只为**有效的显示列**(>=0)建键;-1 的类型在大屏不显示,
|
// 由下方 col 判定跳过,不再产生 NPE。
|
//
|
// 【健壮性】mapCheckTypeVsDisplayColumn 仅在 getDisplayColInfo() 中赋值
|
// (声明处为 null)。正常启动流程由 MySpringEventListener 调用该方法初始化,
|
// 但若启动阶段该调用未成功(例如 CheckTypeMapper 查询异常),
|
// 这里会直接 NPE → 大屏再次 500。故先判空并惰性初始化。
|
if (null == mapCheckTypeVsDisplayColumn) {
|
log.warn("[getBigScreenPatient] 显示列映射尚未初始化,尝试惰性初始化");
|
try {
|
getDisplayColInfo();
|
} catch (Exception e) {
|
log.error("[getBigScreenPatient] 显示列映射初始化失败,返回空结果", e);
|
return mapDisplayColQueue; // 返回空 map,前端展示空屏而非 500
|
}
|
}
|
mapCheckTypeVsDisplayColumn.values().stream()
|
.filter(col -> col != null && col >= 0)
|
.distinct()
|
.forEach(col -> mapDisplayColQueue.put(col, new ArrayList<>()));
|
|
List<Integer> queueStatusList = new ArrayList<>();
|
queueStatusList.add(QueueStatusEnum.READY.getStatus());
|
queueStatusList.add(QueueStatusEnum.ONSTAGE.getStatus());
|
queueStatusList.add(QueueStatusEnum.WAITING.getStatus());
|
queueStatusList.add(QueueStatusEnum.RECALLED.getStatus());
|
queueStatusList.add(QueueStatusEnum.PASSED.getStatus());
|
queueStatusList.add(QueueStatusEnum.PASSED_WAITING.getStatus());
|
queueStatusList.add(QueueStatusEnum.AFFINITY.getStatus());
|
List<QueueDO> queueDOList = queueMapper.getBigScreenQueueByStatus( queueStatusList );
|
// 需求1:回填患者信息(性别/出生日期/病区/床号/电话/来源/开单医师等)
|
queueService.fillAppointmentInfo(queueDOList);
|
queueDOList.forEach(queueDO -> {
|
Integer col = mapCheckTypeVsDisplayColumn.get(queueDO.getBookCheckType());
|
// 双重防御:映射缺失或列为 -1(不显示)时跳过,避免 NPE
|
if (col == null || col < 0) {
|
return;
|
}
|
List<QueueDO> columnQueueData = mapDisplayColQueue.get(col);
|
if (columnQueueData == null) {
|
log.warn("[getBigScreenPatient][显示列未初始化,已跳过] checkType={} callingColumn={}",
|
queueDO.getBookCheckType(), col);
|
return;
|
}
|
columnQueueData.add( queueDO );
|
});
|
|
mapDisplayColQueue.values().forEach( _queueDOList -> {
|
//把 [亲和的] 挪到主项后面
|
List<QueueDO> affinityWaitingList = new ArrayList<>();
|
ListIterator<QueueDO> iterator = _queueDOList.listIterator();
|
while (iterator.hasNext()) {
|
QueueDO item = iterator.next();
|
if (item.getStatus() == QueueStatusEnum.AFFINITY.getStatus()) {
|
iterator.remove();
|
affinityWaitingList.add(item);
|
}
|
}
|
affinityWaitingList.forEach( affinityWaitingItem -> {
|
for (int i = _queueDOList.size()-1; i >= 0; i--) {
|
if ( affinityWaitingItem.getSeqNum() == _queueDOList.get(i).getSeqNum() ) {
|
_queueDOList.add(i + 1, affinityWaitingItem);
|
break;
|
}
|
}
|
});
|
});
|
|
return mapDisplayColQueue;
|
}
|
|
@Override
|
public List<QueueDO> getRoomCheckRelatedPatient(String ip, Long roomId) {
|
if (null == roomId) {
|
List<RoomDO> roomDOList = roomMapper.queueByIp(ip);
|
Optional<RoomDO> optionalQueueDO = roomDOList.stream().filter(item -> StringUtils.hasLength(item.getIp())).findFirst();
|
if (!optionalQueueDO.isPresent()) {
|
return new ArrayList<QueueDO>();
|
}
|
roomId = optionalQueueDO.get().getRoomId();
|
}
|
|
List<Integer> queueStatusList = new ArrayList<>();
|
queueStatusList.add(QueueStatusEnum.RECALLED.getStatus());
|
queueStatusList.add(QueueStatusEnum.READY.getStatus());
|
queueStatusList.add(QueueStatusEnum.ONSTAGE.getStatus());
|
queueStatusList.add(QueueStatusEnum.PASSED.getStatus());
|
// 【需求8 修复】补上「已过号-排队」(3)。
|
//
|
// 需求原文:「过号患者应出现在**大屏和诊间屏**」,且「过号与等待中排在一起、按序号升序」。
|
// 大屏的 getBigScreenPatient 已含 PASSED_WAITING(3),但本方法(诊间屏)**漏了**,
|
// 导致 status=3 的患者在大屏可见、在诊间屏不可见。
|
//
|
// 实测复现(构造 seq 1~5 共 5 位患者,含 status=3 与 5):
|
// 大屏返回 5 位 ✅ ;诊间屏返回 4 位(缺 status=3 那位)🔴
|
//
|
// 排序无需额外处理:getRoomQueueByStatus 的 order by 已把 status in (3,5) 归为同一组
|
// (排在就诊中之后、候诊之前),组内再按 seq_num、book_timeslot 升序 —— 正合需求。
|
queueStatusList.add(QueueStatusEnum.PASSED_WAITING.getStatus());
|
queueStatusList.add(QueueStatusEnum.AFFINITY.getStatus());
|
List<QueueDO> queueDOList = queueMapper.getRoomQueueByStatus(roomId, queueStatusList);
|
// 需求1:回填患者信息(诊间屏-检查队列)
|
queueService.fillAppointmentInfo(queueDOList);
|
|
//把 [亲和的] 挪到主项后面
|
List<QueueDO> affinityWaitingList = new ArrayList<>();
|
ListIterator<QueueDO> iterator = queueDOList.listIterator();
|
while (iterator.hasNext()) {
|
QueueDO item = iterator.next();
|
if (item.getStatus() == QueueStatusEnum.AFFINITY.getStatus()) {
|
iterator.remove();
|
affinityWaitingList.add(item);
|
}
|
}
|
affinityWaitingList.forEach( affinityWaitingItem -> {
|
for (int i = queueDOList.size()-1; i >= 0; i--) {
|
if ( affinityWaitingItem.getSeqNum() == queueDOList.get(i).getSeqNum() ) {
|
queueDOList.add(i + 1, affinityWaitingItem);
|
break;
|
}
|
}
|
});
|
|
return queueDOList;
|
}
|
|
/**
|
* 诊间屏 安装患者列表
|
*/
|
@Override
|
public List<QueueDO> getRoomInstallRelatedPatient(String ip, Long roomId) {
|
if (null == roomId) {
|
List<RoomDO> roomDOList = roomMapper.queueByIp(ip);
|
Optional<RoomDO> optionalQueueDO = roomDOList.stream().filter(item -> StringUtils.hasLength(item.getIp())).findFirst();
|
if (!optionalQueueDO.isPresent()) {
|
return new ArrayList<QueueDO>();
|
}
|
|
roomId = optionalQueueDO.get().getRoomId();
|
}
|
|
List<Integer> queueStatusList = new ArrayList<>();
|
queueStatusList.add(QueueStatusEnum.RECEIVED.getStatus());
|
queueStatusList.add(QueueStatusEnum.PASSED_INSTALL.getStatus());
|
queueStatusList.add(QueueStatusEnum.AFFINITY_INSTALL.getStatus());
|
queueStatusList.add(QueueStatusEnum.INSTALLING.getStatus());
|
queueStatusList.add(QueueStatusEnum.RECALLED_INSTALL.getStatus());
|
List<QueueDO> queueDOList = queueMapper.getRoomQueueByStatus(roomId, queueStatusList);
|
// 需求1:回填患者信息(诊间屏-装机队列)
|
queueService.fillAppointmentInfo(queueDOList);
|
|
//把 [亲和的] 挪到主项后面
|
List<QueueDO> affinityReceivedList = new ArrayList<>();
|
ListIterator<QueueDO> iterator = queueDOList.listIterator();
|
while (iterator.hasNext()) {
|
QueueDO item = iterator.next();
|
if (item.getStatus() == QueueStatusEnum.AFFINITY_INSTALL.getStatus()) {
|
iterator.remove();
|
affinityReceivedList.add(item);
|
}
|
}
|
affinityReceivedList.forEach( affinityReceivedItem -> {
|
for (int i = queueDOList.size()-1; i >= 0; i--) {
|
if ( affinityReceivedItem.getSeqNum() == queueDOList.get(i).getSeqNum() ) {
|
queueDOList.add(i + 1, affinityReceivedItem);
|
break;
|
}
|
}
|
});
|
|
return queueDOList;
|
}
|
|
}
|