yxh
昨天 fce96ef468291fb9a0e6a4d34ab371315e9485d4
jh-module-ecg/jh-module-ecg-biz/src/main/java/cn/lihu/jh/module/ecg/service/callingscreen/CallingScreenServiceImpl.java
@@ -34,6 +34,15 @@
    @Resource
    private RoomMapper roomMapper;
    /**
     * 需求1:用于回填患者信息(性别/出生日期/病区/床号/电话/来源/开单医师等)。
     * <p>
     * 下列屏幕查询是**直接查 queueMapper** 的,不经过 QueueService 的查询方法,
     * 因此必须显式调用 fillAppointmentInfo 补齐;否则大屏/诊间屏只有姓名。
     */
    @Resource
    private cn.lihu.jh.module.ecg.service.queue.QueueService queueService;
    @Resource
    private CheckTypeMapper checkTypeMapper;
@@ -58,7 +67,16 @@
        PageResult<CheckTypeDO> pageResult = checkTypeMapper.selectPageForCallingScreen(checkTypePageReqVO);
        // 显示列 包含的检查类型列表
        Map<Integer, List<CheckTypeDO>> mapDisplayColName = pageResult.getList().stream().collect(Collectors.groupingBy(CheckTypeDO::getCallingColumn));
        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()));
@@ -66,7 +84,8 @@
        // 显示列 对应的检查类型名列表
        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;
    }
@@ -76,20 +95,40 @@
     * 不会有安装相关的患者信息
     */
    @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());
@@ -97,101 +136,82 @@
        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.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 );
        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() {
        List<Integer> queueStatusList = new ArrayList<>();
        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;
    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();
        }
        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);
        }
        if (listPassed.size() < bigScreenConfig.passedSize) {
            passedFrom = 0;
        } else {
            passedFrom = listPassed.getLast().getId();
        }
        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;
    }
    @Override
    public List<QueueDO> getRoomCheckRelatedPatient(String ip) {
        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<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<>();
@@ -215,15 +235,21 @@
        return queueDOList;
    }
    /**
     * 诊间屏 安装患者列表
     */
    @Override
    public List<QueueDO> getRoomInstallRelatedPatient(String ip) {
        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>();
        }
    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>();
            }
        Long roomId = optionalQueueDO.get().getRoomId();
            roomId = optionalQueueDO.get().getRoomId();
        }
        List<Integer> queueStatusList = new ArrayList<>();
        queueStatusList.add(QueueStatusEnum.RECEIVED.getStatus());
        queueStatusList.add(QueueStatusEnum.PASSED_INSTALL.getStatus());
@@ -231,6 +257,8 @@
        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<>();
@@ -252,21 +280,6 @@
        });
        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;
    }
}