| | |
| | | @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, List<CheckTypeDO>> mapDisplayColName = null; |
| | | |
| | | Map<Integer, Integer> mapCheckTypeVsDisplayColumn = null; |
| | | |
| | |
| | | |
| | | @Override |
| | | public Map<Integer, List<String>> getDisplayColInfo() { |
| | | if (null == mapDisplayColName) { |
| | | CheckTypePageReqVO checkTypePageReqVO = new CheckTypePageReqVO(); |
| | | checkTypePageReqVO.setPageSize(-1); |
| | | PageResult<CheckTypeDO> pageResult = checkTypeMapper.selectPage(checkTypePageReqVO); |
| | | mapDisplayColName = pageResult.getList().stream().collect(Collectors.groupingBy(CheckTypeDO::getCallingColumn)); |
| | | mapCheckTypeVsDisplayColumn = pageResult.getList().stream().collect(Collectors.toMap(CheckTypeDO::getValue, item -> item.getCallingColumn())); |
| | | } |
| | | 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 -> { |
| | | _map.put(key, mapDisplayColName.get(key).stream().map(item->item.getName()).toList()); |
| | | List<String> displayColNameList = mapDisplayColName.get(key).stream().map(item->item.getName()+item.getSeqPrefix()).toList(); |
| | | _map.put(key, displayColNameList); |
| | | }); |
| | | return _map; |
| | | } |
| | | |
| | | /** |
| | | * 大屏显示的患者信息 |
| | | * 不会有安装相关的患者信息 |
| | | */ |
| | | @Override |
| | | public Map<Integer, Map<Integer, List<QueueDO>>> callingData() { |
| | | Map<Integer, Map<Integer, List<QueueDO>>> mapDisplayColQueue = new HashMap<>(); |
| | | int displayColCnt = (int) mapCheckTypeVsDisplayColumn.values().stream().distinct().count(); |
| | | for (int dispCol=0; dispCol<displayColCnt; dispCol++) { |
| | | Map<Integer, List<QueueDO>> mapSoltQueueData = new HashMap<>(); |
| | | mapSoltQueueData.put(0, new ArrayList<>()); |
| | | mapSoltQueueData.put(1, new ArrayList<>()); |
| | | mapSoltQueueData.put(2, new ArrayList<>()); |
| | | mapDisplayColQueue.put(dispCol, mapSoltQueueData); |
| | | } |
| | | public Map<Integer, List<QueueDO>> getBigScreenPatient() { |
| | | Map<Integer, List<QueueDO>> mapDisplayColQueue = new HashMap<>(); |
| | | |
| | | mapDisplayColQueue.values().stream().forEach(mapSlotQueue -> { |
| | | mapSlotQueue.values().stream().forEach(queueList -> queueList.clear()); |
| | | }); |
| | | // 【修复 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.WAITING.getStatus()); |
| | | queueStatusList.add(QueueStatusEnum.RECALLED.getStatus()); |
| | | queueStatusList.add(QueueStatusEnum.PASSED.getStatus()); |
| | | List<QueueDO> queueDOList = queueMapper.getQueueByStatus( queueStatusList ); |
| | | queueDOList.stream().forEach(queueDO -> { |
| | | Map<Integer, List<QueueDO>> mapSlotQueue = mapDisplayColQueue.get(mapCheckTypeVsDisplayColumn.get(queueDO.getBookCheckType())); |
| | | List<QueueDO> soltList = mapSlotQueue.get(getDisplatSlotByQueueStatus(queueDO.getStatus())); |
| | | soltList.add( queueDO ); |
| | | 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().stream().forEach(mapSoltQueue -> mapSoltQueue.values().stream().forEach( |
| | | slotQueueList -> slotQueueList.sort((o1, o2) -> { |
| | | if (null == o1.getSeqNum() && null == o2.getSeqNum()) { |
| | | return o1.getBookTimeslot() - o2.getBookTimeslot(); |
| | | } else if (null != o1.getSeqNum() && null != o2.getSeqNum()) { |
| | | return o1.getSeqNum() - o2.getSeqNum(); |
| | | } else if (null == o1.getSeqNum()) { |
| | | return 1; |
| | | } else { //这里 必定 null == o2.getSeqNum() |
| | | return -1; |
| | | 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 Map<Integer, List<QueueDO>> callingData2() { |
| | | 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()); |
| | | List<QueueDO> listReady = queueMapper.getQueueByStatus( queueStatusList ); |
| | | |
| | | queueStatusList.clear(); |
| | | queueStatusList.add(QueueStatusEnum.WAITING.getStatus()); |
| | | queueStatusList.add(QueueStatusEnum.RECALLED.getStatus()); |
| | | List<QueueDO> listWaiting = queueMapper.getPartialQueueByStatus( queueStatusList, waitingFrom, bigScreenConfig.waitingSize ); |
| | | if (listWaiting.size() < bigScreenConfig.waitingSize && waitingFrom != 0) { |
| | | List<QueueDO> listWaiting_next = queueMapper.getPartialQueueByStatus( queueStatusList, 0, bigScreenConfig.waitingSize - listWaiting.size()); |
| | | listWaiting.addAll(listWaiting_next); |
| | | } |
| | | if (listWaiting.size() < bigScreenConfig.waitingSize) { |
| | | waitingFrom = 0; |
| | | } else { |
| | | waitingFrom = listWaiting.getLast().getId() + 1; |
| | | } |
| | | |
| | | queueStatusList.clear(); |
| | | queueStatusList.add(QueueStatusEnum.PASSED.getStatus()); |
| | | List<QueueDO> listPassed = queueMapper.getPartialQueueByStatus( queueStatusList, passedFrom, bigScreenConfig.passedSize ); |
| | | if (listPassed.size() < bigScreenConfig.passedSize && passedFrom != 0) { |
| | | List<QueueDO> listPassed_next = queueMapper.getPartialQueueByStatus( queueStatusList, 0, bigScreenConfig.passedSize - listPassed.size()); |
| | | listPassed.addAll(listPassed_next); |
| | | // 【需求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); |
| | | } |
| | | } |
| | | if (listPassed.size() < bigScreenConfig.passedSize) { |
| | | passedFrom = 0; |
| | | } else { |
| | | passedFrom = listPassed.getLast().getId(); |
| | | } |
| | | 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; |
| | | } |
| | | } |
| | | }); |
| | | |
| | | log.info( " waitingFrom: {} passedFrom: {} ", waitingFrom, passedFrom); |
| | | |
| | | Map<Integer, List<QueueDO>> map = new HashMap<>(); |
| | | map.put(1, listReady); |
| | | map.put(2, listWaiting); |
| | | map.put(3, listPassed); |
| | | return map; |
| | | } |
| | | |
| | | @Override |
| | | public List<QueueDO> getRoomQueue(String ip, List<Integer> statusList) { |
| | | 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>(); |
| | | } |
| | | |
| | | Long roomId = optionalQueueDO.get().getRoomId(); |
| | | List<QueueDO> queueDOList = queueMapper.getRoomQueueByStatus(roomId, statusList); |
| | | return queueDOList; |
| | | } |
| | | |
| | | private Integer getDisplatSlotByQueueStatus( Integer queueStatus ) { |
| | | if (QueueStatusEnum.READY.getStatus() == queueStatus) |
| | | return 0; |
| | | else if (QueueStatusEnum.ONSTAGE.getStatus() == queueStatus) |
| | | return 0; |
| | | else if (QueueStatusEnum.WAITING.getStatus() == queueStatus) |
| | | return 1; |
| | | else if (QueueStatusEnum.RECALLED.getStatus() == queueStatus) |
| | | return 1; |
| | | else if (QueueStatusEnum.PASSED.getStatus() == queueStatus) |
| | | return 2; |
| | | else |
| | | return 0; |
| | | /** |
| | | * 诊间屏 安装患者列表 |
| | | */ |
| | | @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; |
| | | } |
| | | |
| | | } |