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; } }