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:用于回填患者信息(性别/出生日期/病区/床号/电话/来源/开单医师等)。
*
* 下列屏幕查询是**直接查 queueMapper** 的,不经过 QueueService 的查询方法,
* 因此必须显式调用 fillAppointmentInfo 补齐;否则大屏/诊间屏只有姓名。
*/
@Resource
private cn.lihu.jh.module.ecg.service.queue.QueueService queueService;
@Resource
private CheckTypeMapper checkTypeMapper;
BigScreenConfig bigScreenConfig;
Map mapCheckTypeVsDisplayColumn = null;
Integer waitingFrom = 0;
Integer passedFrom = 0;
@Override
public void setBigScreenConfig(BigScreenConfig bigScreenConfig) {
this.bigScreenConfig = bigScreenConfig;
}
@Override
public Map> getDisplayColInfo() {
CheckTypePageReqVO checkTypePageReqVO = new CheckTypePageReqVO();
checkTypePageReqVO.setPageSize(-1);
checkTypePageReqVO.setCallingColumn(-1); // 列号 -1 的不显示
PageResult pageResult = checkTypeMapper.selectPageForCallingScreen(checkTypePageReqVO);
// 显示列 包含的检查类型列表
Map> 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> _map = new HashMap<>();
mapDisplayColName.keySet().forEach(key -> {
List displayColNameList = mapDisplayColName.get(key).stream().map(item->item.getName()+item.getSeqPrefix()).toList();
_map.put(key, displayColNameList);
});
return _map;
}
/**
* 大屏显示的患者信息
* 不会有安装相关的患者信息
*/
@Override
public Map> getBigScreenPatient() {
Map> 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 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 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 columnQueueData = mapDisplayColQueue.get(col);
if (columnQueueData == null) {
log.warn("[getBigScreenPatient][显示列未初始化,已跳过] checkType={} callingColumn={}",
queueDO.getBookCheckType(), col);
return;
}
columnQueueData.add( queueDO );
});
mapDisplayColQueue.values().forEach( _queueDOList -> {
//把 [亲和的] 挪到主项后面
List affinityWaitingList = new ArrayList<>();
ListIterator 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 getRoomCheckRelatedPatient(String ip, Long roomId) {
if (null == roomId) {
List roomDOList = roomMapper.queueByIp(ip);
Optional optionalQueueDO = roomDOList.stream().filter(item -> StringUtils.hasLength(item.getIp())).findFirst();
if (!optionalQueueDO.isPresent()) {
return new ArrayList();
}
roomId = optionalQueueDO.get().getRoomId();
}
List 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 queueDOList = queueMapper.getRoomQueueByStatus(roomId, queueStatusList);
// 需求1:回填患者信息(诊间屏-检查队列)
queueService.fillAppointmentInfo(queueDOList);
//把 [亲和的] 挪到主项后面
List affinityWaitingList = new ArrayList<>();
ListIterator 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 getRoomInstallRelatedPatient(String ip, Long roomId) {
if (null == roomId) {
List roomDOList = roomMapper.queueByIp(ip);
Optional optionalQueueDO = roomDOList.stream().filter(item -> StringUtils.hasLength(item.getIp())).findFirst();
if (!optionalQueueDO.isPresent()) {
return new ArrayList();
}
roomId = optionalQueueDO.get().getRoomId();
}
List 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 queueDOList = queueMapper.getRoomQueueByStatus(roomId, queueStatusList);
// 需求1:回填患者信息(诊间屏-装机队列)
queueService.fillAppointmentInfo(queueDOList);
//把 [亲和的] 挪到主项后面
List affinityReceivedList = new ArrayList<>();
ListIterator 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;
}
}