yxh
昨天 fce96ef468291fb9a0e6a4d34ab371315e9485d4
jh-module-ecg/jh-module-ecg-biz/src/main/java/cn/lihu/jh/module/ecg/service/queue/QueueServiceTxFunctions.java
@@ -7,7 +7,6 @@
import cn.lihu.jh.module.ecg.controller.admin.room.vo.MonitorInfoVO;
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.queue.QueueStatisticDO;
import cn.lihu.jh.module.ecg.dal.dataobject.room.RoomDO;
import cn.lihu.jh.module.ecg.dal.mysql.call.CallMapper;
import cn.lihu.jh.module.ecg.dal.mysql.checktype.CheckTypeMapper;
@@ -69,10 +68,10 @@
    AtomicInteger curSeqNum = new AtomicInteger(0);
    // 维护 开放的工位
    ConcurrentHashMap<String, RoutingBedBO> mapOpeningBed = new ConcurrentHashMap<>();
    ConcurrentHashMap<String, BedBO> mapOpeningBed = new ConcurrentHashMap<>();
    // 装机工位..不进入分流队列
    Map<Integer, PriorityBlockingQueue<RoutingBedBO>> mapCheckTypeRoutingQueue = new HashMap();
    ConcurrentHashMap<Integer, PriorityBlockingQueue<BedBO>> mapCheckTypeRoutingQueue = new ConcurrentHashMap<>();
    
    Map<Integer, CheckTypeDO> mapCheckTypeVsReadyMax = null;
@@ -83,8 +82,8 @@
     * @return
     */
    public ErrorCode bedOpen(Long roomId, String roomName, String bedNo) {
        RoutingBedBO routingBedBO2 = mapOpeningBed.get( Utils.formatRoomBed(roomId, bedNo) );
        if (null != routingBedBO2) {
        BedBO bedBO2 = mapOpeningBed.get( Utils.formatRoomBed(roomId, bedNo) );
        if (null != bedBO2) {
            log.error("bedOpen mapBedVsQueue has existed. " + roomId + " " + bedNo);
            return QUEUE_BED_EXIST;
        }
@@ -98,30 +97,24 @@
            return ROOM_INVALID_STATUS;
        }
        List<Integer> queueStatusList = new ArrayList<>();
        queueStatusList.add(QueueStatusEnum.READY.getStatus());
        List<QueueDO> queueDOList = queueMapper.getBedQueueByStatus(roomId, bedNo, queueStatusList);
        RoomDO roomDO = roomMapper.getRoom(roomId, bedNo);
        // 新增..工位队列
        RoutingBedBO routingBedBO = new RoutingBedBO();
        routingBedBO.setRoomId(roomId);
        routingBedBO.setRoomName(roomName);
        routingBedBO.setBedNo(bedNo);
        routingBedBO.setMaxReadyPatNum(getBedReadyMax(roomId, bedNo));
        routingBedBO.setReadyPatNum(new AtomicInteger(queueDOList.size()));
        routingBedBO.setStatus(BedStatusEnum.OPENING.getStatus());
        routingBedBO.setCheckTypes( roomDO.getCheckTypes() );
        routingBedBO.setOpType( roomDO.getOpType() );
        mapOpeningBed.put( Utils.formatRoomBed(roomId, bedNo), routingBedBO);
        BedBO bedBO = new BedBO();
        bedBO.setRoomId(roomId);
        bedBO.setRoomName(roomName);
        bedBO.setBedNo(bedNo);
        bedBO.setStatus(BedStatusEnum.OPENING.getStatus());
        bedBO.setCheckTypes( roomDO.getCheckTypes() );
        bedBO.setOpType( roomDO.getOpType() );
        mapOpeningBed.put( Utils.formatRoomBed(roomId, bedNo), bedBO);
        return GlobalErrorCodeConstants.SUCCESS;
    }
    public ErrorCode bedClose(Long roomId, String bedNo) {
        RoutingBedBO routingBedBO = mapOpeningBed.get( Utils.formatRoomBed(roomId, bedNo) );
        if (null == routingBedBO) {
        BedBO bedBO = mapOpeningBed.get( Utils.formatRoomBed(roomId, bedNo) );
        if (null == bedBO) {
            log.error("bedClose mapBedVsQueue DONOT existed. " + roomId + " " + bedNo);
            return QUEUE_BED_NOT_EXIST;
        }
@@ -154,13 +147,13 @@
        }
        mapOpeningBed.remove( Utils.formatRoomBed(roomId, bedNo) );
        removeRoutingQueue(routingBedBO);
        removeBedFromRoutingQueue(bedBO);
        return GlobalErrorCodeConstants.SUCCESS;
    }
    public ErrorCode bedDoctorPause(Long roomId, String bedNo, Long docId, String docName) {
        RoutingBedBO routingBedBO = mapOpeningBed.get( Utils.formatRoomBed(roomId, bedNo) );
        if (null == routingBedBO) {
        BedBO bedBO = mapOpeningBed.get( Utils.formatRoomBed(roomId, bedNo) );
        if (null == bedBO) {
            log.error("bedDoctorPause mapBedVsQueue DONOT existed. " + roomId + " " + bedNo);
            return QUEUE_BED_NOT_EXIST;
        }
@@ -174,14 +167,14 @@
            return ROOM_INVALID_STATUS;
        }
        routingBedBO.setStatus(BedStatusEnum.DOCTOR_PAUSE.getStatus());
        removeRoutingQueue(routingBedBO);
        bedBO.setStatus(BedStatusEnum.DOCTOR_PAUSE.getStatus());
        removeBedFromRoutingQueue(bedBO);
        return GlobalErrorCodeConstants.SUCCESS;
    }
    public ErrorCode bedDoctorResume(Long roomId, String bedNo, Long docId, String docName) {
        RoutingBedBO routingBedBO = mapOpeningBed.get( Utils.formatRoomBed(roomId, bedNo) );
        if (null == routingBedBO || !routingBedBO.getStatus().equals(BedStatusEnum.DOCTOR_PAUSE.getStatus())) {
        BedBO bedBO = mapOpeningBed.get( Utils.formatRoomBed(roomId, bedNo) );
        if (null == bedBO || !bedBO.getStatus().equals(BedStatusEnum.DOCTOR_PAUSE.getStatus())) {
            log.error("bedDoctorResume mapBedVsQueue DONOT existed OR NOT Paused. " + roomId + " " + bedNo);
            return QUEUE_BED_NOT_EXIST;
        }
@@ -196,14 +189,75 @@
            return ROOM_INVALID_STATUS;
        }
        routingBedBO.setStatus(BedStatusEnum.DOCTOR_ON.getStatus());
        addRoutingQueue(routingBedBO);
        bedBO.setStatus(BedStatusEnum.DOCTOR_ON.getStatus());
        addBedToRoutingQueue(bedBO);
        return GlobalErrorCodeConstants.SUCCESS;
    }
    // ==================== 需求3:停止候诊开关 ====================
    /**
     * 设置「停止候诊」开关。
     * <p>
     * {@code stop=true}:该工位**不再新增**候诊患者(移出分流队列),
     * 但**已有候诊患者仍可正常叫号**({@code nextPatient} 不检查此开关)。
     * <p>
     * {@code stop=false}:恢复正常,重新加入分流队列开始接收患者
     * (前提是工位状态为"医生在座",与 {@code bedReload} 的判定一致)。
     * <p>
     * 状态写入 {@code clinic_room.stop_ready},以在 {@code bedReload} 后保持。
     *
     * @return 成功返回 SUCCESS;工位不存在返回 {@link #QUEUE_BED_NOT_EXIST}
     */
    public ErrorCode setStopReady(Long roomId, String bedNo, boolean stop) {
        BedBO bedBO = mapOpeningBed.get( Utils.formatRoomBed(roomId, bedNo) );
        if (null == bedBO) {
            log.error("[setStopReady] 工位不存在. roomId={} bedNo={}", roomId, bedNo);
            return QUEUE_BED_NOT_EXIST;
        }
        // 先落库(失败则不改内存,避免状态不一致)
        //
        // 【部署保护】若数据库尚未执行升级脚本(缺 clinic_room.stop_ready 列),
        // roomMapper.setBedStopReady(...) 会在 Mapper 层直接抛 SQL 异常
        // (@Update 注解方法没有返回值可判断,异常直接冒泡)。
        // 这里显式捕获并转成明确的业务错误码,避免把原始 SQL 错误暴露给前端,
        // 便于现场一眼看出是"部署遗漏"而不是"功能坏了"。
        Integer updateNum;
        try {
            updateNum = roomMapper.setBedStopReady(roomId, bedNo, stop ? 1 : 0);
        } catch (Exception e) {
            log.error("[setStopReady] 写库失败,请确认已执行 _prep/11_需求升级_汇总SQL.sql"
                    + "(clinic_room.stop_ready). roomId={} bedNo={}", roomId, bedNo, e);
            return QUEUE_STOP_READY_NOT_CONFIGURED;
        }
        if (null == updateNum || 0 == updateNum) {
            // 工位可能尚未在 clinic_room 登记,仅告警但仍允许改内存,
            // 以免因 DB 记录缺失导致医生完全无法使用该功能。
            log.warn("[setStopReady] DB 未更新到行,仅更新内存. roomId={} bedNo={} stop={}", roomId, bedNo, stop);
        }
        bedBO.setStopReady(stop);
        // 同步分流队列:停止 → 移出;恢复 → 加入(仅在医生在座时)
        if (stop) {
            removeBedFromRoutingQueue(bedBO);
        } else if (BedStatusEnum.DOCTOR_ON.getStatus().equals(bedBO.getStatus())) {
            addBedToRoutingQueue(bedBO);
        }
        log.info("[setStopReady] roomId={} bedNo={} stopReady={}", roomId, bedNo, stop);
        return GlobalErrorCodeConstants.SUCCESS;
    }
    /** 查询当前「停止候诊」状态(工位不存在返回 null) */
    public Boolean getStopReady(Long roomId, String bedNo) {
        BedBO bedBO = mapOpeningBed.get( Utils.formatRoomBed(roomId, bedNo) );
        return null == bedBO ? null : Boolean.TRUE.equals(bedBO.getStopReady());
    }
    public ErrorCode bedDoctorOn(Long roomId, String bedNo, Long docId, String docName) {
        RoutingBedBO routingBedBO = mapOpeningBed.get( Utils.formatRoomBed(roomId, bedNo) );
        if (null == routingBedBO) {
        BedBO bedBO = mapOpeningBed.get( Utils.formatRoomBed(roomId, bedNo) );
        if (null == bedBO) {
            log.error("bedDoctorOn mapBedVsQueue DONOT existed. " + roomId + " " + bedNo);
            return QUEUE_BED_NOT_EXIST;
        }
@@ -217,14 +271,14 @@
            return ROOM_INVALID_STATUS;
        }
        routingBedBO.setStatus(BedStatusEnum.DOCTOR_ON.getStatus());
        addRoutingQueue(routingBedBO);
        bedBO.setStatus(BedStatusEnum.DOCTOR_ON.getStatus());
        addBedToRoutingQueue(bedBO);
        return GlobalErrorCodeConstants.SUCCESS;
    }
    public ErrorCode bedDoctorOff(Long roomId, String bedNo, Long docId, String docName) {
        RoutingBedBO routingBedBO = mapOpeningBed.get( Utils.formatRoomBed( roomId, bedNo ) );
        if (null == routingBedBO) {
        BedBO bedBO = mapOpeningBed.get( Utils.formatRoomBed( roomId, bedNo ) );
        if (null == bedBO) {
            log.error("bedDoctorOff mapBedVsQueue DONOT existed. " + roomId + " " + bedNo);
            return QUEUE_BED_NOT_EXIST;
        }
@@ -239,9 +293,9 @@
            return ROOM_INVALID_STATUS;
        }
        removeRoutingQueue(routingBedBO);
        removeBedFromRoutingQueue(bedBO);
        routingBedBO.setStatus(BedStatusEnum.OPENING.getStatus());
        bedBO.setStatus(BedStatusEnum.OPENING.getStatus());
        return GlobalErrorCodeConstants.SUCCESS;
    }
@@ -267,7 +321,7 @@
    // 常规 或者 领用 时 医生 取下一位患者
    public ErrorCode nextPatient(Long roomId, String bedNo) {
        QueueDO firstReadyQueueItem = queueMapper.getFirstItemByBedAndStatus(roomId, bedNo, QueueStatusEnum.READY.getStatus());
        QueueDO firstReadyQueueItem = queueMapper.getBedFirstQueueItemByStatus(roomId, bedNo, QueueStatusEnum.READY.getStatus());
        // 该工位 没有 [候诊中中] 人员
        if (null == firstReadyQueueItem) {
            return QUEUE_NOT_READY_PATIENT;
@@ -277,38 +331,34 @@
        firstReadyQueueItem.setStatus(QueueStatusEnum.ONSTAGE.getStatus());
        queueMapper.updateById( firstReadyQueueItem );
        // 分流队列中 该工位 候诊中中人的数量 减一
        RoutingBedBO bo = mapOpeningBed.get( Utils.formatRoomBed( roomId, bedNo ) );
        bo.readyPatNum.getAndDecrement(); // 可能已经【并发的】在 hurry-up 中改变了值
        // 分流队列中 该工位 【候诊中】患者数量 减一
        BedBO bo = mapOpeningBed.get( Utils.formatRoomBed( roomId, bedNo ) );
        bo.readyPatNum--; // 可能已经在 hurry-up 中改变了值
        refreshRoutingQueue(bo);
        refreshBedPriorityLevelInRoutingQueue(bo);
        return GlobalErrorCodeConstants.SUCCESS;
    }
    // 装机时 医生 取下一位患者
    public ErrorCode nextInstallPatient(Long roomId, String bedNo) {
        // 从 DB 把 序号最小的 [已领用] [已召回-安装] 的人 设置为 [安装中]
        QueueDO firstItem = queueMapper.getFirstItemByRoomAndStatus(roomId, List.of(QueueStatusEnum.RECEIVED.getStatus(), QueueStatusEnum.RECALLED_INSTALL.getStatus()));
        QueueDO firstItem = queueMapper.getRoomFirstQueueItemByStatus(roomId, List.of(QueueStatusEnum.RECEIVED.getStatus(), QueueStatusEnum.RECALLED_INSTALL.getStatus()));
        // 该工位 没有 [已领用] | [已召回-安装] 人员
        if (null == firstItem) {
            return QUEUE_NOT_READY_PATIENT;
        }
        firstItem.setStatus( QueueStatusEnum.INSTALLING.getStatus() );
        firstItem.setBedNo( bedNo );
        firstItem.setBedNo( bedNo );  // 转到 安装工位
        queueMapper.updateById( firstItem );
        // 检查项目.亲和性 处理逻辑
        // 检查项目.亲和性 处理逻辑:  变为【安装中】后 拉 其他检查项来亲和
        CheckTypeDO checkTypeDO = getCheckTypeItem( firstItem.getBookCheckType() );
        if (checkTypeDO.getAffinityCheckTypes().length > 0) {
            procAffinityWhenSomeOneEntryInstalling(firstItem.getPatId(), roomId, bedNo, checkTypeDO.getAffinityCheckTypes() );
        }
        // 装机工位 不进入 分流队列
        // 分流队列中 该工位 [已领用]人的数量 减一
        //BedQueueBO bo = mapBedVsQueue.get( Utils.formatRoomBed( roomId, bedNo ) );
        //bo.queueNum.getAndDecrement(); // 可能已经【并发的】在 hurry-up 中改变了值
        //refreshRoutingQueue(bo);
        // 装机工位 不参与分流
        return GlobalErrorCodeConstants.SUCCESS;
    }
@@ -332,24 +382,17 @@
        bedStatusEnumList.add(BedStatusEnum.DOCTOR_ON);
        bedStatusEnumList.add(BedStatusEnum.DOCTOR_PAUSE);
        List<RoomDO> roomDOList = roomMapper.simpleRoomList(bedStatusEnumList);
        List<RoutingBedBO> routingBedBOList = roomDOList.stream().map(item -> BeanUtils.toBean(item, RoutingBedBO.class)).toList();
        List<BedBO> bedBOList = roomDOList.stream().map(item -> BeanUtils.toBean(item, BedBO.class)).toList();
        // 从DB 获取 队列中 【候诊中】人员统计 列表
        List<Integer> queueStatusList = new ArrayList<>();
        queueStatusList.add(QueueStatusEnum.READY.getStatus());
        List<QueueStatisticDO> queueStatisticDOList = queueMapper.queueStatistic(queueStatusList);
        routingBedBOList.forEach(item -> {
            item.setMaxReadyPatNum( getBedReadyMax(item.roomId, item.bedNo) );
            Optional<QueueStatisticDO> queueStatisticDOOptional = queueStatisticDOList.stream().filter(it->it.getRoomId()==item.roomId && it.getBedNo().equals(item.getBedNo())).findFirst();
            int queueNum = queueStatisticDOOptional.isPresent() ? queueStatisticDOOptional.get().getTotalInStatus() : 0;
            if ( item.getMaxReadyPatNum() < queueNum )
                throw new RuntimeException("init: exceed max queue number!");
            item.setReadyPatNum( new AtomicInteger(queueNum) );
        bedBOList.forEach(item -> {
            mapOpeningBed.put( Utils.formatRoomBed(item.roomId, item.bedNo), item);
            if (BedStatusEnum.DOCTOR_ON.getStatus() == item.getStatus()) {
                addRoutingQueue(item);
            // 【需求3】「停止候诊」的工位不加入分流队列 —— 与医生在座(status=DOCTOR_ON)同样判定,
            // 否则每次 bedReload(开诊/重启/诊室重置)后开关会失效、又开始收病人。
            // 注意:stopReady 仅阻止**新增**候诊患者,已有候诊患者仍可正常叫号
            // (nextPatient 不检查该开关)。
            if (BedStatusEnum.DOCTOR_ON.getStatus() == item.getStatus()
                    && !Boolean.TRUE.equals(item.getStopReady())) {
                addBedToRoutingQueue(item);
            }
        });
@@ -388,15 +431,20 @@
        if (0 == openingFlag.get())
            return;
        RoutingBedBO routingBedBO = getBedQueueBO(roomId, bedNo);
        BedBO bedBO = getBedQueueBO(roomId, bedNo);
        // 【需求3】「停止候诊」时不再把【已召回】患者放入候诊区。
        // 注意:这**不影响**已有候诊患者被正常叫号(nextPatient 不检查本开关),
        // 也不影响医生对具体患者的"召回"操作本身(只影响其自动进入候诊的补位)。
        boolean stopReady = Boolean.TRUE.equals(bedBO.getStopReady());
        // 处理 过号-回来 的人
        while (routingBedBO.readyPatNum.get() < routingBedBO.maxReadyPatNum) {
        while (!stopReady && bedBO.getReadyPatNum() < bedBO.getMaxReadyPatNum()) {
            // 查看 当前工位 是否有过号-回来的患者
            Integer updateNum = queueMapper.queueRecalledPatient(
                    routingBedBO.getRoomId(),
                    routingBedBO.getRoomName(),
                    routingBedBO.getBedNo(),
                    bedBO.getRoomId(),
                    bedBO.getRoomName(),
                    bedBO.getBedNo(),
                    curSeqNum.get() + 1,  // 过号回来,排到当前的下一位
                    QueueStatusEnum.RECALLED.getStatus(), // 不需要处理 [安装召回], 原因: 安装工位 不进入分流队列
                    QueueStatusEnum.READY.getStatus());
@@ -413,15 +461,15 @@
            curSeqNum.getAndIncrement();
            // 可能已经【并发的】在 nextPatient 中改变了值
            routingBedBO.readyPatNum.incrementAndGet();
            // 可能已经在 nextPatient 中改变了值
            bedBO.readyPatNum++;
            // 可能已经【并发的】在 nextPatient 中改变了分流队列顺序
            refreshRoutingQueue(routingBedBO);
            refreshBedPriorityLevelInRoutingQueue(bedBO);
        }
        // 处理 排队中 患者
        hurryupOneBedCheckTypeList(routingBedBO);
        hurryupOneBedCheckTypeList(bedBO);
    }
    /**
@@ -433,31 +481,49 @@
     */
    public void hurryupOneCheckType(Integer checkType) {
        PriorityBlockingQueue<RoutingBedBO> routingQueue = mapCheckTypeRoutingQueue.get(checkType);
        // 【修复既有缺陷】补上开诊门闸。原实现**唯独这里漏了** openingFlag 检查
        // (hurryupAllBed 与 hurryupOneBed 都有),导致手动"闭诊"后,
        // 只要有人预约确认 / 加急,患者仍会被推进候诊区。
        if (0 == openingFlag.get())
            return;
        PriorityBlockingQueue<BedBO> routingQueue = mapCheckTypeRoutingQueue.get(checkType);
        if (null == routingQueue)
            return;
        while (true) {
            RoutingBedBO routingBedBO = routingQueue.peek();
            if (null == routingBedBO)
            BedBO bedBO = routingQueue.peek();
            if (null == bedBO)
                return;
            int curQueueNum = routingBedBO.readyPatNum.get();
            if (curQueueNum > routingBedBO.maxReadyPatNum)
            // 【需求3】跳过处于「停止候诊」的工位。
            // 不能直接 return —— 该队列按 readyPatNum 升序 peek,
            // 直接 return 会让其余工位也收不到患者(饿死)。
            // 这里 poll 出来后若命中停止态,仅将其从队列移除(不再 offer 回去),
            // 然后 continue 处理下一个工位。
            if (Boolean.TRUE.equals(bedBO.getStopReady())) {
                routingQueue.poll();
                log.info("[hurryupOneCheckType] 工位[停止候诊],移出分流队列. roomId={} bedNo={}",
                        bedBO.getRoomId(), bedBO.getBedNo());
                continue;
            }
            int curQueueNum = bedBO.getReadyPatNum();
            if (curQueueNum > bedBO.getMaxReadyPatNum())
                throw new RuntimeException("hurryupOneCheckType: exceed max queue number!");
            if (curQueueNum == routingBedBO.maxReadyPatNum)
            if (curQueueNum == bedBO.getMaxReadyPatNum())
                return;
            // 查看 是否有排队中的患者
            Integer updateNum = queueMapper.preemptWaitingPatientWithBedCheckTypes(
                    routingBedBO.getRoomId(),
                    routingBedBO.getRoomName(),
                    routingBedBO.getBedNo(),
                    bedBO.getRoomId(),
                    bedBO.getRoomName(),
                    bedBO.getBedNo(),
                    curSeqNum.get() + 1,
                    QueueStatusEnum.WAITING.getStatus(),
                    QueueStatusEnum.READY.getStatus(),
                    routingBedBO.checkTypes);  //关键点 关键点 关键点 checkType
                    bedBO.checkTypes);  //关键点 关键点 关键点 checkType
            // 没有抢到排队患者
            if (null == updateNum || 0 == updateNum) {
@@ -474,11 +540,11 @@
            curSeqNum.getAndIncrement();
            // 可能已经【并发的】在 nextPatient 中改变了值
            routingBedBO.readyPatNum.incrementAndGet();
            // 可能已经在 nextPatient 中改变了值
            bedBO.readyPatNum++;
            // 可能已经【并发的】在 nextPatient 中改变了分流队列顺序
            refreshRoutingQueue(routingBedBO);
            refreshBedPriorityLevelInRoutingQueue(bedBO);
        }
    }
@@ -499,7 +565,7 @@
        return mapCheckTypeVsReadyMax.get( checkType );
    }
    public RoutingBedBO getBedQueueBO(Long roomId, String bedNo) {
    public BedBO getBedQueueBO(Long roomId, String bedNo) {
        return mapOpeningBed.get( Utils.formatRoomBed(roomId, bedNo) );
    }
@@ -517,17 +583,22 @@
        log.info(" opening " + openingFlag.get() + " " + monitorInfoVO.getOpeningBedNum() + " " + monitorInfoVO.getDocBedNum() + " " + monitorInfoVO.getCheckTypeBedInfo().toString() );
    }
    private void hurryupOneBedCheckTypeList(RoutingBedBO routingBedBO) {
        while (routingBedBO.readyPatNum.get() < routingBedBO.maxReadyPatNum) {
    private void hurryupOneBedCheckTypeList(BedBO bedBO) {
        // 【需求3】「停止候诊」的工位不再新增候诊患者。
        // 已有候诊患者仍可正常叫号(本方法只负责"补齐候诊",不参与叫号)。
        if (Boolean.TRUE.equals(bedBO.getStopReady())) {
            return;
        }
        while (bedBO.getReadyPatNum() < bedBO.getMaxReadyPatNum()) {
            // 抢占 是否有排队中的患者
            Integer updateNum = queueMapper.preemptWaitingPatientWithBedCheckTypes(
                    routingBedBO.getRoomId(),
                    routingBedBO.getRoomName(),
                    routingBedBO.getBedNo(),
                    bedBO.getRoomId(),
                    bedBO.getRoomName(),
                    bedBO.getBedNo(),
                    curSeqNum.get() + 1,
                    QueueStatusEnum.WAITING.getStatus(),
                    QueueStatusEnum.READY.getStatus(),
                    routingBedBO.checkTypes);
                    bedBO.checkTypes);
            // 没有抢到排队患者
            if (null == updateNum || 0 == updateNum) {
@@ -544,11 +615,11 @@
            curSeqNum.getAndIncrement();
            routingBedBO.readyPatNum.incrementAndGet();
            bedBO.readyPatNum++;
        }
        // 改变了分流队列顺序
        refreshRoutingQueue(routingBedBO);
        refreshBedPriorityLevelInRoutingQueue(bedBO);
    }
    /**
@@ -582,63 +653,83 @@
        }
    }
    private Integer getBedReadyMax(Long roomId, String bedNo) {
        RoomDO roomDO = roomMapper.getRoom(roomId, bedNo);
        Integer[] checkTypes = roomDO.getCheckTypes();
        Integer ret = 0;
        for (int i=0; i<checkTypes.length; i++) {
            CheckTypeDO checkTypeDO = mapCheckTypeVsReadyMax.get(checkTypes[i]);
    private Integer getBedReadyMax(BedBO bedBO) {
        Integer[] checkTypes = bedBO.getCheckTypes();
        int ret = 0;
        for (Integer checkType : checkTypes) {
            CheckTypeDO checkTypeDO = mapCheckTypeVsReadyMax.get(checkType);
            Integer tmp = checkTypeDO.getReadyNum();
            ret = ret < tmp ? tmp : ret;  //【候诊中】数量 取大值
        }
        return ret;
    }
    private void addRoutingQueue(RoutingBedBO routingBedBO) {
    private void addBedToRoutingQueue(BedBO bedBO) {
        // 装机工位 不进 分流队列,不接收 【排队中】患者分流
        if (BedOpTypeEnum.OP_INSTALL.getOpType() == routingBedBO.getOpType())
        if (Objects.equals(BedOpTypeEnum.OP_INSTALL.getOpType(), bedBO.getOpType()))
            return;
        Integer[] checkTypes = routingBedBO.getCheckTypes();
        // 【需求3】「停止候诊」的工位不进分流队列。
        // 这是**唯一入口**的统一门闸:只要工位被标记为停止候诊,
        // 无论从哪条路径(开诊 addBedToRoutingQueue、医生恢复、bedReload、
        // 或手动恢复开关)试图加入,都会被拒绝,从而保证:
        //   · 已有候诊患者仍可正常叫号(nextPatient 不检查该开关)
        //   · 但不会再被分配【排队中】的新患者
        if (Boolean.TRUE.equals(bedBO.getStopReady())) {
            log.info("[addBedToRoutingQueue] 工位处于[停止候诊],跳过加入分流队列. roomId={} bedNo={}",
                    bedBO.getRoomId(), bedBO.getBedNo());
            return;
        }
        // 同步DB, 设置工位的分流控制信息
        List<Integer> queueStatusList = new ArrayList<>();
        queueStatusList.add(QueueStatusEnum.READY.getStatus()); //[候诊中]
        List<QueueDO> queueDOList = queueMapper.getBedQueueByStatus(bedBO.getRoomId(), bedBO.getBedNo(), queueStatusList);
        bedBO.setMaxReadyPatNum( getBedReadyMax(bedBO) );
        bedBO.setReadyPatNum( queueDOList.size() );
        Integer[] checkTypes = bedBO.getCheckTypes();
        Arrays.stream(checkTypes).forEach(checkType -> {
            CheckTypeDO checkTypeDO = mapCheckTypeVsReadyMax.get(checkType);
            PriorityBlockingQueue<RoutingBedBO> routingQueue = mapCheckTypeRoutingQueue.get(checkType);
            PriorityBlockingQueue<BedBO> routingQueue = mapCheckTypeRoutingQueue.get(checkType);
            if (null == routingQueue) {
                routingQueue = new PriorityBlockingQueue<RoutingBedBO>();
                routingQueue = new PriorityBlockingQueue<BedBO>();
                mapCheckTypeRoutingQueue.put( checkType, routingQueue );
            }
            if (!routingQueue.contains(routingBedBO)) {
                routingQueue.offer(routingBedBO);
            if (!routingQueue.contains(bedBO)) {
                routingQueue.offer(bedBO);
            } else {
                log.error("bedDoctorResume routingQueue still exist!. " + routingBedBO.getRoomId() + " " + routingBedBO.getBedNo());
                log.error("bedDoctorResume routingQueue still exist!. " + bedBO.getRoomId() + " " + bedBO.getBedNo());
            }
        });
    }
    private void removeRoutingQueue(RoutingBedBO routingBedBO) {
    private void removeBedFromRoutingQueue(BedBO bedBO) {
        // 装机工位 不进 分流队列,不接收 【排队中】患者分流
        if (BedOpTypeEnum.OP_INSTALL.getOpType() == routingBedBO.getOpType())
        if (Objects.equals(BedOpTypeEnum.OP_INSTALL.getOpType(), bedBO.getOpType()))
            return;
        Integer[] checkTypes = routingBedBO.getCheckTypes();
        Integer[] checkTypes = bedBO.getCheckTypes();
        Arrays.stream(checkTypes).forEach(checkType -> {
            PriorityBlockingQueue<RoutingBedBO> routingQueue = mapCheckTypeRoutingQueue.get(checkType);
            PriorityBlockingQueue<BedBO> routingQueue = mapCheckTypeRoutingQueue.get(checkType);
            if (null != routingQueue)
                routingQueue.remove(routingBedBO);
                routingQueue.remove(bedBO);
        });
    }
    private void refreshRoutingQueue(RoutingBedBO routingBedBO) {
    private void refreshBedPriorityLevelInRoutingQueue(BedBO bedBO) {
        // 装机工位 不进 分流队列,不接收 【排队中】患者分流
        if (BedOpTypeEnum.OP_INSTALL.getOpType() == routingBedBO.getOpType())
        if (Objects.equals(BedOpTypeEnum.OP_INSTALL.getOpType(), bedBO.getOpType()))
            return;
        Integer[] checkTypes = routingBedBO.getCheckTypes();
        Integer[] checkTypes = bedBO.getCheckTypes();
        Arrays.stream(checkTypes).forEach(checkType -> {
            PriorityBlockingQueue<RoutingBedBO> routingQueue = mapCheckTypeRoutingQueue.get(checkType);
            routingQueue.remove(routingBedBO);
            routingQueue.offer(routingBedBO);
            PriorityBlockingQueue<BedBO> routingQueue = mapCheckTypeRoutingQueue.get(checkType);
            if (null != routingQueue) {
                routingQueue.remove(bedBO);
                routingQueue.offer(bedBO);
            }
        });
    }
@@ -651,6 +742,7 @@
        for (int i = 0; i < affinityItems.size(); i++) {
            QueueDO queueItem = affinityItems.get(i);
            queueItem.setStatus(QueueStatusEnum.AFFINITY_INSTALL.getStatus()); //改变 排队状态
            queueItem.setAffinityItem(1);
            queueItem.setRoomId(roomId_operator);
            queueItem.setBedNo(bedNo_operator);  // 跟随到 装机 工位
            queueMapper.updateById(queueItem);
@@ -689,7 +781,7 @@
        });
        // 接收..排队中患者..的分流队列
        HashSet<RoutingBedBO> hashSetOperatingBedQueue = new HashSet<>();
        HashSet<BedBO> hashSetOperatingBedQueue = new HashSet<>();
        mapCheckTypeRoutingQueue.values().forEach(routingQueue->{
            routingQueue.stream().forEach(bedQueueBO -> hashSetOperatingBedQueue.add(bedQueueBO));
        });