eight
2024-11-28 d1708b4b2bc6596f9c62354274f1af97be45401c
jh-module-ecg/jh-module-ecg-biz/src/main/java/cn/lihu/jh/module/ecg/service/queue/QueueServiceImpl.java
@@ -2,31 +2,33 @@
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
import javax.annotation.Resource;
import cn.lihu.jh.module.ecg.Utils;
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.devrent.DevRentDO;
import cn.lihu.jh.module.ecg.dal.mysql.call.CallMapper;
import cn.lihu.jh.module.ecg.dal.mysql.devrent.DevRentMapper;
import cn.lihu.jh.module.ecg.enums.DevRentStateEnum;
import cn.lihu.jh.module.system.api.oauth2.OAuth2TokenApi;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import cn.lihu.jh.framework.common.exception.enums.GlobalErrorCodeConstants;
import cn.lihu.jh.module.ecg.dal.dataobject.room.RoomDO;
import cn.lihu.jh.framework.common.exception.ErrorCode;
import cn.lihu.jh.framework.common.pojo.CommonResult;
import cn.lihu.jh.module.ecg.controller.admin.room.vo.RoomRespVO;
import cn.lihu.jh.module.ecg.dal.dataobject.queue.BedQueueStatisticDO;
import cn.lihu.jh.module.ecg.dal.dataobject.queue.QueueStatisticDO;
import cn.lihu.jh.module.ecg.dal.mysql.room.RoomMapper;
import cn.lihu.jh.module.ecg.enums.BedStatusEnum;
import cn.lihu.jh.module.ecg.enums.QueueStatusEnum;
import cn.lihu.jh.module.ecg.controller.admin.queue.vo.*;
import cn.lihu.jh.module.ecg.dal.dataobject.queue.QueueDO;
import cn.lihu.jh.framework.common.pojo.PageResult;
import cn.lihu.jh.framework.common.util.object.BeanUtils;
import cn.lihu.jh.module.ecg.dal.mysql.queue.queueMapper;
import cn.lihu.jh.module.ecg.dal.mysql.queue.QueueMapper;
import static cn.lihu.jh.module.ecg.enums.ErrorCodeConstants.*;
import static cn.lihu.jh.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.lihu.jh.framework.common.pojo.CommonResult.error;
@@ -43,7 +45,13 @@
public class QueueServiceImpl implements QueueService {
    @Resource
    private queueMapper queueMapper;
    QueueServiceTxFunctions queueServiceTxFunctions;
    @Resource
    private OAuth2TokenApi oAuth2TokenApi;
    @Resource
    private QueueMapper queueMapper;
    @Resource
    private RoomMapper roomMapper;
@@ -51,20 +59,12 @@
    @Resource
    private CallMapper callMapper;
    AtomicInteger openingFlag = new AtomicInteger(0);
    AtomicInteger curSeqNum = new AtomicInteger(0);
    PriorityBlockingQueue<BedQueueBO> priorityQueue = new PriorityBlockingQueue<>();
    ConcurrentHashMap<String, BedQueueBO > mapBedVsQueue = new ConcurrentHashMap<>();
    @Resource
    private DevRentMapper devRentMapper;
    ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
    Integer queueReadyMax = 0;
    Integer bigScrenWaitingSize = 5;
    Integer bigScrenPassedSize = 5;
    Integer bigScrenWaitingFrom = 0;
    Integer bigScrenPassedFrom = 0;
    ConcurrentHashMap<String, RoomDO> mapRoomBed = new ConcurrentHashMap<>();
    @Override
    public Integer createqueue(QueueSaveReqVO createReqVO) {
@@ -94,7 +94,7 @@
    @Override
    public ErrorCode startBedOpen(Long roomId, String roomName, String bedNo) {
        Future<ErrorCode> future = singleThreadExecutor.submit( new BedOpenCallable(this, roomId, roomName, bedNo));
        Future<ErrorCode> future = singleThreadExecutor.submit( new BedOpenCallable(queueServiceTxFunctions, roomId, roomName, bedNo));
        try {
            ErrorCode ret = future.get();
@@ -111,7 +111,7 @@
    @Override
    public ErrorCode startBedClose(Long roomId, String bedNo) {
        Future<ErrorCode> future = singleThreadExecutor.submit( new BedCloseCallable(this, roomId, bedNo));
        Future<ErrorCode> future = singleThreadExecutor.submit( new BedCloseCallable(queueServiceTxFunctions, roomId, bedNo));
        try {
            ErrorCode ret = future.get();
@@ -129,7 +129,7 @@
    @Override
    public ErrorCode startBedDoctorPause(Long roomId, String bedNo, Long docId, String docName) {
        Future<ErrorCode> future = singleThreadExecutor.submit(
            new BedDoctorPauseCallable(this, roomId, bedNo, docId, docName)
            new BedDoctorPauseCallable(queueServiceTxFunctions, roomId, bedNo, docId, docName)
        );
        try {
@@ -148,7 +148,7 @@
    @Override
    public ErrorCode startBedDoctorResume(Long roomId, String bedNo, Long docId, String docName) {
        Future<ErrorCode> future = singleThreadExecutor.submit(
            new BedDoctorResumeCallable(this, roomId, bedNo, docId, docName)
            new BedDoctorResumeCallable(queueServiceTxFunctions, roomId, bedNo, docId, docName)
        );
        try {
@@ -167,7 +167,7 @@
    @Override
    public ErrorCode startBedDoctorOn(Long roomId, String bedNo, Long docId, String docName) {
        Future<ErrorCode> future = singleThreadExecutor.submit(
            new BedDoctorOnCallable(this, roomId, bedNo, docId, docName)
            new BedDoctorOnCallable(queueServiceTxFunctions, roomId, bedNo, docId, docName)
        );
        try {
@@ -186,7 +186,7 @@
    @Override
    public ErrorCode startBedDoctorOff(Long roomId, String bedNo, Long docId, String docName) {
        Future<ErrorCode> future = singleThreadExecutor.submit(
            new BedDoctorOffCallable(this, roomId, bedNo, docId, docName)
            new BedDoctorOffCallable(queueServiceTxFunctions, roomId, bedNo, docId, docName)
        );
        try {
@@ -198,14 +198,14 @@
            e.printStackTrace();
        }
        System.out.println("startBedDoctorOff ========");
        //System.out.println("startBedDoctorOff ========");
        return ECG_INNER_ERROR;
    }
    @Override
    public ErrorCode startNextPatient(Long roomId, String bedNo) {
        Future<ErrorCode> future = singleThreadExecutor.submit(
                new BedDoctorNextPatientCallable(this, roomId, bedNo)
                new BedDoctorNextPatientCallable(queueServiceTxFunctions, roomId, bedNo)
        );
        try {
@@ -217,120 +217,74 @@
            e.printStackTrace();
        }
        System.out.println("startNextPatient ========");
        //System.out.println("startNextPatient ========");
        return ECG_INNER_ERROR;
    }
    @Override
    public void startHurryUp() {
    public ErrorCode startNextInstallPatient(Long roomId, String bedNo) {
        Future<ErrorCode> future = singleThreadExecutor.submit(
                new BedDoctorNextInstallPatientCallable(queueServiceTxFunctions, roomId, bedNo)
        );
        try {
            ErrorCode ret = future.get();
            return ret;
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
        System.out.println("startNextInstallPatient ========");
        return ECG_INNER_ERROR;
    }
    @Override
    public void startHurryUpOneBed(Long roomId, String bedNo) {
        singleThreadExecutor.execute( () -> {
            hurryup();
            queueServiceTxFunctions.hurryupOneBed(roomId, bedNo);
        });
    }
    @Override
    public void startHurryUpOneCheckType(Integer checkType) {
        singleThreadExecutor.execute( () -> {
            queueServiceTxFunctions.hurryupOneCheckType( checkType );
        });
    }
    @Override
    public void startBedReload() {
        singleThreadExecutor.execute( () -> {
            bedReload();
            hurryup();
            monitorInfo();
            queueServiceTxFunctions.bedReload();
            queueServiceTxFunctions.hurryupAllBed();
            queueServiceTxFunctions.monitorInfo();
        });
    }
    @Override
    public void startResetRoom(Boolean needCloseBed) {
        singleThreadExecutor.execute( () -> {
            queueServiceTxFunctions.resetRoom(needCloseBed);
            queueServiceTxFunctions.bedReload();
            queueServiceTxFunctions.monitorInfo();
        });
    }
    @Override
    public MonitorInfoVO getMonitorInfo() {
        MonitorInfoVO monitorInfoVO = new MonitorInfoVO();
        monitorInfoVO.setOpeningFlag( openingFlag.get() );
        monitorInfoVO.setQueueNum( mapBedVsQueue.size() );
        monitorInfoVO.setActiveQueueNum( priorityQueue.size() );
        return monitorInfoVO;
    }
    /**
     * 已关闭 或者 关闭中,可以开通工位
     * @param roomId
     * @param bedNo
     * @return
     */
    @Override
    public ErrorCode bedOpen(Long roomId, String roomName, String bedNo) {
        BedQueueBO bedQueueBO2 = mapBedVsQueue.get(String.format("%09d%s", roomId, bedNo));
        if (null != bedQueueBO2) {
            log.error("bedOpen mapBedVsQueue has existed. " + roomId + " " + bedNo);
            return QUEUE_BED_EXIST;
        }
        // DB update
        List statusList = new ArrayList<BedStatusEnum>();
        statusList.add(BedStatusEnum.CLOSED);
        Integer updateNum = roomMapper.setBedOpeningOpening(roomId, bedNo, BedStatusEnum.OPENING, statusList);
        if ( null==updateNum || 0 == updateNum ) {
            log.error("bedOpen DB invalid status. " + roomId + " " + bedNo);
            return ROOM_INVALID_STATUS;
        }
        List<Byte> queueStatusList = new ArrayList<>();
        queueStatusList.add(QueueStatusEnum.READY.getStatus());
        List<QueueDO> queueDOList = queueMapper.getDoctorQueueByStatus(roomId, bedNo, queueStatusList);
        // 新增工位 优先队列
        BedQueueBO bedQueueBO = new BedQueueBO();
        bedQueueBO.setRoomId(roomId);
        bedQueueBO.setRoomName(roomName);
        bedQueueBO.setBedNo(bedNo);
        bedQueueBO.setMaxQueueNum(queueReadyMax);
        bedQueueBO.setQueueNum(new AtomicInteger(queueDOList.size()));
        bedQueueBO.setStatus(BedStatusEnum.OPENING.getStatus());
        priorityQueue.offer(bedQueueBO);
        mapBedVsQueue.put(String.format("%09d%s", roomId, bedNo), bedQueueBO);
        return GlobalErrorCodeConstants.SUCCESS;
    }
    @Override
    public ErrorCode bedClose(Long roomId, String bedNo) {
        BedQueueBO bedQueueBO = mapBedVsQueue.get(String.format("%09d%s", roomId, bedNo));
        if (null == bedQueueBO) {
            log.error("bedClose mapBedVsQueue DONOT existed. " + roomId + " " + bedNo);
            return QUEUE_BED_NOT_EXIST;
        }
        // 除了判断 准备候诊中 的人数,还需要 判断 过号的人数
        //if (bedQueueBO.getQueueNum().get() >0)
        //    return QUEUE_HAVE_PATIENT;
        List<Byte> queueStatusList = new ArrayList<>();
        queueStatusList.add(QueueStatusEnum.READY.getStatus());
        queueStatusList.add(QueueStatusEnum.PASSED.getStatus());
        queueStatusList.add(QueueStatusEnum.RECALLED.getStatus());
        List<QueueDO>  queueDOList = queueMapper.getDoctorQueueByStatus(roomId, bedNo, queueStatusList);
        if (queueDOList.size() > 0)
            return QUEUE_HAVE_PATIENT;
        // DB update
        List statusList = new ArrayList<BedStatusEnum>();
        statusList.add(BedStatusEnum.OPENING);
        statusList.add(BedStatusEnum.DOCTOR_ON);
        statusList.add(BedStatusEnum.PAUSE);
        Integer updateNum = roomMapper.setBedOpeningClosed(roomId, bedNo,
                BedStatusEnum.CLOSED, statusList);
        if ( null==updateNum || 0 == updateNum ) {
            log.error("bedClose DB invalid status. " + roomId + " " + bedNo);
            return ROOM_INVALID_STATUS;
        }
        mapBedVsQueue.remove(String.format("%09d%s", roomId, bedNo));
        priorityQueue.remove(bedQueueBO);
        return GlobalErrorCodeConstants.SUCCESS;
        return queueServiceTxFunctions.getMonitorInfo();
    }
    @Override
    public CommonResult<RoomRespVO> getRoom(Long roomId, String bedNo, Long docId) {
        RoomDO roomDO = roomMapper.getRoom(roomId, bedNo, docId);
        RoomDO roomDO = roomMapper.getRoomByRoomBedDoc(roomId, bedNo, docId);
        if (null == roomDO) {
            return error(ROOM_NOT_SIT);
        }
        BedQueueBO bedQueueBO = mapBedVsQueue.get(String.format("%09d%s", roomId, bedNo));
        BedQueueBO bedQueueBO = queueServiceTxFunctions.getBedQueueBO(roomId, bedNo);
        if (null == bedQueueBO) {
            log.error("getRoom mapBedVsQueue DONOT existed. " + roomId + " " + bedNo);
            return error(QUEUE_BED_NOT_EXIST);
@@ -338,110 +292,6 @@
        RoomRespVO roomRespVO = BeanUtils.toBean(roomDO, RoomRespVO.class);
        return success(roomRespVO);
    }
    @Override
    public ErrorCode bedDoctorPause(Long roomId, String bedNo, Long docId, String docName) {
        BedQueueBO bedQueueBO = mapBedVsQueue.get(String.format("%09d%s", roomId, bedNo));
        if (null == bedQueueBO) {
            log.error("bedDoctorPause mapBedVsQueue DONOT existed. " + roomId + " " + bedNo);
            return QUEUE_BED_NOT_EXIST;
        }
        // DB update
        List statusList = new ArrayList<BedStatusEnum>();
        statusList.add(BedStatusEnum.DOCTOR_ON);
        Integer updateNum = roomMapper.setBedDoctorStatus(roomId, bedNo, docId, BedStatusEnum.PAUSE, statusList);
        if ( null==updateNum || 0 == updateNum ) {
            log.error("bedDoctorPause DB invalid status. " + roomId + " " + bedNo);
            return ROOM_INVALID_STATUS;
        }
        bedQueueBO.setStatus(BedStatusEnum.PAUSE.getStatus());
        priorityQueue.remove(bedQueueBO);
        return GlobalErrorCodeConstants.SUCCESS;
    }
    @Override
    public ErrorCode bedDoctorResume(Long roomId, String bedNo, Long docId, String docName) {
        BedQueueBO bedQueueBO = mapBedVsQueue.get(String.format("%09d%s", roomId, bedNo));
        if (null == bedQueueBO || !bedQueueBO.getStatus().equals(BedStatusEnum.PAUSE.getStatus())) {
            log.error("bedDoctorResume mapBedVsQueue DONOT existed OR NOT Paused. " + roomId + " " + bedNo);
            return QUEUE_BED_NOT_EXIST;
        }
        // DB update
        List statusList = new ArrayList<BedStatusEnum>();
        statusList.add(BedStatusEnum.PAUSE);
        Integer updateNum = roomMapper.setBedDoctorStatus(roomId, bedNo, docId,
                BedStatusEnum.DOCTOR_ON, statusList);
        if ( null==updateNum || 0 == updateNum ) {
            log.error("bedDoctorResume DB invalid status. " + roomId + " " + bedNo);
            return ROOM_INVALID_STATUS;
        }
        bedQueueBO.setStatus(BedStatusEnum.DOCTOR_ON.getStatus());
        if (!priorityQueue.contains(bedQueueBO)) {
            priorityQueue.offer(bedQueueBO);
        } else {
            log.error("bedDoctorResume priorityQueue still exist!. " + roomId + " " + bedNo);
            return ECG_INNER_ERROR;
        }
        return GlobalErrorCodeConstants.SUCCESS;
    }
    @Override
    public ErrorCode bedDoctorOn(Long roomId, String bedNo, Long docId, String docName) {
        BedQueueBO bedQueueBO = mapBedVsQueue.get(String.format("%09d%s", roomId, bedNo));
        if (null == bedQueueBO) {
            log.error("bedDoctorOn mapBedVsQueue DONOT existed. " + roomId + " " + bedNo);
            return QUEUE_BED_NOT_EXIST;
        }
        // DB update
        List statusList = new ArrayList<BedStatusEnum>();
        statusList.add(BedStatusEnum.OPENING);
        Integer updateNum = roomMapper.setBedDoctorOn(roomId, bedNo, docId, docName, BedStatusEnum.DOCTOR_ON, statusList);
        if ( null==updateNum || 0 == updateNum ) {
            log.error("bedDoctorOn DB invalid status. " + roomId + " " + bedNo);
            return ROOM_INVALID_STATUS;
        }
        bedQueueBO.setStatus(BedStatusEnum.DOCTOR_ON.getStatus());
        return GlobalErrorCodeConstants.SUCCESS;
    }
    @Override
    public ErrorCode bedDoctorOff(Long roomId, String bedNo, Long docId, String docName) {
        BedQueueBO bedQueueBO = mapBedVsQueue.get(String.format("%09d%s", roomId, bedNo));
        if (null == bedQueueBO) {
            log.error("bedDoctorOff mapBedVsQueue DONOT existed. " + roomId + " " + bedNo);
            return QUEUE_BED_NOT_EXIST;
        }
        // DB update
        List statusList = new ArrayList<BedStatusEnum>();
        statusList.add(BedStatusEnum.DOCTOR_ON);
        statusList.add(BedStatusEnum.PAUSE);
        Integer updateNum = roomMapper.setBedDoctorOff(roomId, bedNo, docId, BedStatusEnum.OPENING, statusList);
        if ( null==updateNum || 0 == updateNum ) {
            log.error("bedDoctorOff DB invalid status. " + roomId + " " + bedNo);
            return ROOM_INVALID_STATUS;
        }
        // 如果是在 PAUSE 状态离座的,需要重新加回 优先队列
        if (bedQueueBO.getStatus().equals(BedStatusEnum.PAUSE.getStatus())) {
            if (!priorityQueue.contains(bedQueueBO)) {
                priorityQueue.offer(bedQueueBO);
            } else {
                log.error("bedDoctorResume priorityQueue still exist!. " + roomId + " " + bedNo);
                return ECG_INNER_ERROR;
            }
        }
        bedQueueBO.setStatus(BedStatusEnum.OPENING.getStatus());
        return GlobalErrorCodeConstants.SUCCESS;
    }
    private void validatequeueExists(Integer id) {
@@ -460,177 +310,112 @@
        return queueMapper.selectPage(pageReqVO);
    }
    /**
     * !!开诊期间,不能执行这个方法,否则会有 P0 问题
     * 1. 每天开诊前 从DB同步工位的患者队列数据到 工位优先队列
     * 2. 服务运维重启时
     */
    @Override
    public ErrorCode bedReload() {
        priorityQueue.clear();
        mapBedVsQueue.clear();
        // 清除非当天的排队人员
        queueMapper.clearQueue();
        // 清除非当天的语音叫号记录
        callMapper.clearCall();
        // 从DB 获取 工位列表
        List<BedStatusEnum> bedStatusEnumList = new ArrayList<BedStatusEnum>();
        bedStatusEnumList.add(BedStatusEnum.OPENING);
        bedStatusEnumList.add(BedStatusEnum.DOCTOR_ON);
        bedStatusEnumList.add(BedStatusEnum.PAUSE);
        List<RoomDO> roomDOList = roomMapper.simpleRoomList(bedStatusEnumList);
        List<BedQueueBO> bedQueueBOList = roomDOList.stream().map(item -> BeanUtils.toBean(item, BedQueueBO.class)).toList();
        // 从DB 获取 队列中 就诊准备中人员统计 列表
        List<Byte> queueStatusList = new ArrayList<>();
        queueStatusList.add(QueueStatusEnum.READY.getStatus());
        List<QueueStatisticDO> queueStatisticDOList = queueMapper.queueStatistic(queueStatusList);
        bedQueueBOList.forEach(item -> {
            item.maxQueueNum = queueReadyMax;
            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 ( queueReadyMax < queueNum )
                throw new RuntimeException("init: exceed max queue number!");
            item.queueNum.set( queueNum );
            mapBedVsQueue.put(String.format("%09d%s", item.roomId, item.bedNo), item);
            if (BedStatusEnum.PAUSE.getStatus() != item.getStatus()) {
                priorityQueue.offer(item);
            }
        });
        Integer num = queueMapper.getMaxSeqNum();
        curSeqNum = new AtomicInteger(null == num ? 0 : num);
        return GlobalErrorCodeConstants.SUCCESS;
    }
    /**
     *
     */
    public void hurryup() {
        if (0 == openingFlag.get())
            return;
        // 处理 过号-回来 的人
        for (BedQueueBO bedQueueBO : mapBedVsQueue.values()) {
            while (bedQueueBO.queueNum.get() < bedQueueBO.maxQueueNum) {
                // 查看 当前工位 是否有过号-回来的患者
                Integer updateNum = queueMapper.queueRecalledPatient(
                        bedQueueBO.getRoomId(),
                        bedQueueBO.getRoomName(),
                        bedQueueBO.getBedNo(),
                        curSeqNum.get() + 1,
                        QueueStatusEnum.RECALLED.getStatus(),
                        QueueStatusEnum.READY.getStatus());
                if (null == updateNum || 0 == updateNum)
                    break;
                curSeqNum.getAndIncrement();
                // 可能已经【并发的】在 nextPatient 中改变了值
                bedQueueBO.queueNum.incrementAndGet();
                // 可能已经【并发的】在 nextPatient 中改变了优先队列顺序
                priorityQueue.remove(bedQueueBO);
                priorityQueue.offer(bedQueueBO);
            }
        }
        // 处理 排队中 患者
        while (true) {
            BedQueueBO bedQueueBO = priorityQueue.peek();
            if (null == bedQueueBO)
                return;
            int curQueueNum = bedQueueBO.queueNum.get();
            if (curQueueNum > bedQueueBO.maxQueueNum)
                throw new RuntimeException("hurryup: exceed max queue number!");
            if (curQueueNum == bedQueueBO.maxQueueNum)
                return;
            // 查看 是否有排队中的患者
            Integer updateNum = queueMapper.preemptPatient(
                                bedQueueBO.getRoomId(),
                                bedQueueBO.getRoomName(),
                                bedQueueBO.getBedNo(),
                                curSeqNum.get() + 1,
                                QueueStatusEnum.WAITING.getStatus(),
                                QueueStatusEnum.READY.getStatus());
            // 没有抢到排队患者
            if (null == updateNum || 0 == updateNum) {
                return;
            }
            curSeqNum.getAndIncrement();
            // 可能已经【并发的】在 nextPatient 中改变了值
            bedQueueBO.queueNum.incrementAndGet();
            // 可能已经【并发的】在 nextPatient 中改变了优先队列顺序
            priorityQueue.remove(bedQueueBO);
            priorityQueue.offer(bedQueueBO);
        }
    }
    /**
     * 预约确认后的排队
     * @param queueSaveReqVO
     */
    @Override
    @Transactional
    public void queue(QueueSaveReqVO queueSaveReqVO) {
        queueSaveReqVO.setStatus(QueueStatusEnum.WAITING.getStatus()); //排队中
        // 默认以 [排队中] 状态 进入 queue 表
        queueSaveReqVO.setStatus(QueueStatusEnum.WAITING.getStatus());
        // 处理 检查项目.亲和性 逻辑
        CheckTypeDO checkTypeDO = queueServiceTxFunctions.getCheckTypeItem( queueSaveReqVO.getBookCheckType() );
        if ( checkTypeDO.getAffinityCheckTypes().length > 0) {
            procAffinityWhenQueue(/*IN,OUT*/queueSaveReqVO, checkTypeDO.getAffinityCheckTypes());
        }
        QueueDO queue = BeanUtils.toBean(queueSaveReqVO, QueueDO.class);
        queueMapper.insert(queue);
        startHurryUp();
        DevRentDO devRent = BeanUtils.toBean(queueSaveReqVO, DevRentDO.class);
        devRent.setState( DevRentStateEnum.FREE.getState() );
        devRent.setPatDetails( queueSaveReqVO.getPatDetails() );
        devRent.setCheckType( queueSaveReqVO.getBookCheckType() );
        devRentMapper.insert(devRent);
        //startHurryUpOneCheckType( queue.getBookCheckType() );
    }
    @Override
    public ErrorCode nextPatient(Long roomId, String bedNo) {
        // 从 DB 把 序号最小的 就诊准备中的人 设置为就诊中
        Integer updateNum = queueMapper.updateQueueStatus(roomId, bedNo,
                QueueStatusEnum.READY.getStatus(), QueueStatusEnum.ONSTAGE.getStatus());
        // 该工位 没有 就诊准备中 人员
        if (null == updateNum || 0 == updateNum) {
            return QUEUE_NOT_READY_PATIENT;
        }
        // 优先队列中 该工位 就诊准备中人的数量 减一
        BedQueueBO bo = mapBedVsQueue.get(String.format("%09d%s", roomId, bedNo));
        bo.queueNum.getAndDecrement(); // 可能已经【并发的】在 hurry-up 中改变了值
        priorityQueue.remove(bo);
        priorityQueue.offer(bo);
        return GlobalErrorCodeConstants.SUCCESS;
    public List<QueueDO> selectBookSeqNumByAppointIdList(List<Long> appointIdList) {
        return queueMapper.selectBookSeqNumByAppointIdList( appointIdList );
    }
    // 常规检查 叫号
    @Override
    public void finishNextPatient(Long roomId, String bedNo) {
        // 从 DB 把 就诊中的人 设置为就诊完成
        Integer ret =  queueMapper.updateQueueStatus(roomId, bedNo,
                QueueStatusEnum.ONSTAGE.getStatus(), QueueStatusEnum.FINISH.getStatus());
        //Integer ret =  queueMapper.updateBedQueueStatus(roomId, bedNo,
        //        QueueStatusEnum.ONSTAGE.getStatus(), QueueStatusEnum.FINISH.getStatus());
        // 从 DB 检查 该工位 是否有 [就诊中] 人员,若有就.放弃取下一位
        List<Integer> queueStatusList = new ArrayList<>();
        queueStatusList.add(QueueStatusEnum.ONSTAGE.getStatus());
        Integer num = queueMapper.bedQueueStatisticByStatus(roomId, bedNo, queueStatusList);
        if ( num != null && num > 0)
            return;
        startNextPatient(roomId, bedNo);
    }
    // 领用 叫号
    @Override
    public void finishReceiveNextPatient(Long roomId, String bedNo) {
        // 从 DB 检查 该工位 是否有 [就诊中] 人员,若有就.放弃取下一位
        List<Integer> queueStatusList = new ArrayList<>();
        queueStatusList.add(QueueStatusEnum.ONSTAGE.getStatus());
        Integer num = queueMapper.bedQueueStatisticByStatus(roomId, bedNo, queueStatusList);
        if ( num != null && num > 0)
            return;
        startNextPatient(roomId, bedNo);
    }
    // 装机 叫号
    @Override
    public void finishInstallNextPatient(Long roomId, String bedNo) {
        // 从 DB 检查 该工位 是否有 [安装中] 人员,若有就.放弃取下一位
        List<Integer> queueStatusList = new ArrayList<>();
        queueStatusList.add(QueueStatusEnum.INSTALLING.getStatus());
        Integer num = queueMapper.bedQueueStatisticByStatus(roomId, bedNo, queueStatusList);
        if ( num != null && num > 0)
            return;
        startNextInstallPatient(roomId, bedNo);
    }
    public void passNextPatient(Long roomId, String bedNo) {
        // 从 DB 把 就诊中的人 设置为过号
        Integer ret =  queueMapper.updateQueueStatus(roomId, bedNo,
        Integer ret =  queueMapper.updateBedQueueStatus(roomId, bedNo,
                QueueStatusEnum.ONSTAGE.getStatus(), QueueStatusEnum.PASSED.getStatus());
        startNextPatient(roomId, bedNo);
    }
    public List<QueueDO> getDoctorQueueByStatus(Long roomId, String bedNo, List<Byte> statusList) {
        List<QueueDO> queueDOList = queueMapper.getDoctorQueueByStatus(roomId, bedNo, statusList);
    @Override
    public void passInstallNextPatient(Long roomId, String bedNo) {
        // 从 DB 把 [安装中]的人 设置为 [过号-安装]
        Integer ret =  queueMapper.updateBedQueueStatus(roomId, bedNo,
                QueueStatusEnum.INSTALLING.getStatus(), QueueStatusEnum.PASSED_INSTALL.getStatus());
        startNextPatient(roomId, bedNo);
    }
    public List<QueueDO> getBedQueueByStatus(Long roomId, String bedNo, List<Integer> statusList) {
        List<QueueDO> queueDOList = queueMapper.getBedQueueByStatus(roomId, bedNo, statusList);
        return queueDOList;
    }
    @Override
    public List<QueueDO> getRoomQueueByStatus(Long roomId, List<Integer> statusList) {
        List<QueueDO> queueDOList = queueMapper.getRoomQueueByStatus(roomId, statusList);
        return queueDOList;
    }
    // 常规检查 医生界面的统计信息
    public PatientStatisticVO getPatientStatistic(Long roomId, String bedNo) {
        PatientStatisticVO patientStatisticVO = new PatientStatisticVO();
        List<BedQueueStatisticDO> bedQueueStatisticDOList = queueMapper.bedQueueStatistic(roomId, bedNo);
@@ -644,51 +429,249 @@
            }
        });
        List<Byte> statusList = new ArrayList<>();
        // 统计 该工位类型的【排队中】人员数量
        RoomDO roomDO = getRoomDO(roomId, bedNo);
        Integer[] checkTypes = roomDO.getCheckTypes();
        List<Integer> statusList = new ArrayList<>();
        statusList.add(QueueStatusEnum.WAITING.getStatus());
        Integer num = queueMapper.statusStatistic(statusList);
        Integer num = queueMapper.checkTypeAndStatusStatistic(checkTypes, statusList);
        patientStatisticVO.setQueuingNum(num);
        return patientStatisticVO;
    }
    @Override
    public void setQueueReadyMax(Integer max) {
        queueReadyMax = max;
    public PatientStatisticVO getBedDevInstallStatistic(Long roomId, String bedNo) {
        PatientStatisticVO patientStatisticVO = new PatientStatisticVO();
        List<BedQueueStatisticDO> bedQueueStatisticDOList = queueMapper.bedQueueStatistic(roomId, bedNo);
        bedQueueStatisticDOList.forEach(item -> {
//            } else if (QueueStatusEnum.INSTALLING.getStatus() == item.getStatus()) {
//                patientStatisticVO.setReceivedNum( patientStatisticVO.getReceivedNum() + item.getTotalInStatus() );
            if (QueueStatusEnum.FINISH.getStatus() == item.getStatus()) {
                patientStatisticVO.setFinishedNum(item.getTotalInStatus());
            }
        });
        // 装机界面:统计 该诊室 [已领用] 的数量, 因为安装工位看不到 [已领用] 患者,所以无法根据装机工位来统计,只能按诊室统计
        patientStatisticVO.setReceivedNum( 0 );
        List<BedQueueStatisticDO> roomQueueStatisticDOList = queueMapper.roomQueueStatistic(roomId);
        roomQueueStatisticDOList.forEach(item -> {
            if (QueueStatusEnum.RECEIVED.getStatus() == item.getStatus()) {
                patientStatisticVO.setReceivedNum( patientStatisticVO.getReceivedNum() + item.getTotalInStatus() );
            }
        });
        return patientStatisticVO;
    }
    public PatientStatisticVO getBedDevReadyStatistic(Long roomId, String bedNo) {
        PatientStatisticVO patientStatisticVO = new PatientStatisticVO();
        List<BedQueueStatisticDO> bedQueueStatisticDOList = queueMapper.bedQueueStatistic(roomId, bedNo);
        bedQueueStatisticDOList.forEach(item -> {
            if (QueueStatusEnum.READY.getStatus() == item.getStatus()) {
                patientStatisticVO.setReadyNum(item.getTotalInStatus());
            } else if (QueueStatusEnum.PASSED.getStatus() == item.getStatus()) {
                patientStatisticVO.setPassedNum(item.getTotalInStatus());
            }
        });
        // 领用界面:统计 该诊室 [已领用] 的数量, 因为安装时患者归属工位从领用工位变更到安装工位,所以无法根据领用工位来统计,只能按诊室统计
        patientStatisticVO.setReceivedNum( 0 );
        List<BedQueueStatisticDO> roomQueueStatisticDOList = queueMapper.roomQueueStatistic(roomId);
        roomQueueStatisticDOList.forEach(item -> {
            if (QueueStatusEnum.RECEIVED.getStatus() == item.getStatus()) {
                patientStatisticVO.setReceivedNum( patientStatisticVO.getReceivedNum() + item.getTotalInStatus() );
            } else if (QueueStatusEnum.INSTALLING.getStatus() == item.getStatus()) {
                patientStatisticVO.setReceivedNum( patientStatisticVO.getReceivedNum() + item.getTotalInStatus() );
            } else if (QueueStatusEnum.FINISH.getStatus() == item.getStatus()) {
                patientStatisticVO.setReceivedNum(patientStatisticVO.getReceivedNum() + item.getTotalInStatus() );
            }
        });
        // 统计 该工位类型的【排队中】人员数量
        RoomDO roomDO = getRoomDO(roomId, bedNo);
        Integer[] checkTypes = roomDO.getCheckTypes();
        List<Integer> statusList = new ArrayList<>();
        statusList.add(QueueStatusEnum.WAITING.getStatus());
        Integer num = queueMapper.checkTypeAndStatusStatistic(checkTypes, statusList);
        patientStatisticVO.setQueuingNum(num);
        return patientStatisticVO;
    }
    public void initCheckType() {
        queueServiceTxFunctions.initCheckType( );
    }
    public void startBiz() {
        if (1 == openingFlag.get())
        if (1 == queueServiceTxFunctions.getOpeningFlag())
            return;
        openingFlag.set(1);
        queueServiceTxFunctions.setOpeningFlag(1);
        startBedReload();
    }
    public void closeBiz() {
        openingFlag.set(0);
        queueServiceTxFunctions.setOpeningFlag(0);
        startBedReload();
    }
    @Override
    public Integer recallPatient(Long roomId, String bedNo, String patId) {
        Integer updateNum = queueMapper.recallPassedPatient(roomId, bedNo, patId,
    public Integer recallPatient(Long roomId, String bedNo, String patId, Integer checkType) {
        Integer updateNum = queueMapper.recallPassedPatient(roomId, bedNo, patId, checkType,
                QueueStatusEnum.PASSED.getStatus(), QueueStatusEnum.RECALLED.getStatus());
        startHurryUp();
        // 检查项目.亲和性 不需要在这里处理
        // 会在 HurryUpOnBed 里面处理 检查项目.亲和性
        startHurryUpOneBed(roomId, bedNo);
        return updateNum;
    }
    @Override
    public Integer recallInstallPatient(Long roomId, String bedNo, String patId, Integer checkType, Long roomId_operator, String bedNo_operator) {
        Integer updateNum = queueMapper.recallPassedInstallPatient(roomId, bedNo_operator, patId, checkType,
                QueueStatusEnum.PASSED_INSTALL.getStatus(), QueueStatusEnum.RECALLED_INSTALL.getStatus());
        // 检查项目.亲和性 处理
        if (null != updateNum && 1 == updateNum) {
            CheckTypeDO checkTypeDO = queueServiceTxFunctions.getCheckTypeItem( checkType );
            if ( checkTypeDO.getAffinityCheckTypes().length > 0) {
                procAffinityWhenInstallRecall(patId, checkTypeDO.getAffinityCheckTypes(), roomId_operator, bedNo_operator);
            }
        }
        // 安装工位 不涉及 优先队列
        //startHurryUpOneBed(roomId, bedNo);
        return updateNum;
    }
    /**
     * 加急
     * @param patId
     * @param jumped
     * @return
     */
    @Override
    public Integer patientJump(String patId, Byte jumped) {
        Integer updateNum = queueMapper.queueJump(patId, QueueStatusEnum.WAITING.getStatus(), jumped);
        startHurryUp();
        QueueDO queueDO = queueMapper.getQueueByPatId(patId);
        //startHurryUpOneCheckType(Integer.valueOf(queueDO.getBookCheckType()));
        return updateNum;
    }
    @Override
    public void monitorInfo() {
        log.info("map " + mapBedVsQueue.size() + " priority " + priorityQueue.size()
                 + " opening " + openingFlag.get());
    public RoomDO getDocRoomInfo(Long docId) {
        return roomMapper.getRoomByDocId(docId);
    }
    /**
     * 获取 指定工位 待检查|待领用的人
     * @param roomId
     * @param bedNo
     * @return
     */
    @Override
    public List<QueueDO> getToBeCheckedPatient(Long roomId, String bedNo) {
        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());
        queueStatusList.add(QueueStatusEnum.AFFINITY_WAITING.getStatus());
        List<QueueDO> queueDOList = getBedQueueByStatus(roomId, bedNo, queueStatusList);
        //把 [亲和的] 挪到主项后面
        List<QueueDO> affinityWaitingList = new ArrayList<>();
        ListIterator<QueueDO> iterator = queueDOList.listIterator();
        while (iterator.hasNext()) {
            QueueDO item = iterator.next();
            if (item.getStatus() == QueueStatusEnum.AFFINITY_WAITING.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<QueueDO> getToBeInstalledPatient(Long roomId, String bedNo) {
        List<Integer> queueStatusList = new ArrayList<>();
        queueStatusList.add(QueueStatusEnum.RECEIVED.getStatus());
        queueStatusList.add(QueueStatusEnum.PASSED_INSTALL.getStatus());
        queueStatusList.add(QueueStatusEnum.AFFINITY_RECEIVED.getStatus());
        List<QueueDO> queueDOList = getRoomQueueByStatus(roomId, queueStatusList);
        queueStatusList.clear();
        queueStatusList.add(QueueStatusEnum.INSTALLING.getStatus());
        queueStatusList.add(QueueStatusEnum.RECALLED_INSTALL.getStatus());
        queueDOList.addAll(0, getBedQueueByStatus(roomId, bedNo, queueStatusList) );
        //把 [亲和的] 挪到主项后面
        List<QueueDO> affinityReceivedList = new ArrayList<>();
        ListIterator<QueueDO> iterator = queueDOList.listIterator();
        while (iterator.hasNext()) {
            QueueDO item = iterator.next();
            if (item.getStatus() == QueueStatusEnum.AFFINITY_RECEIVED.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;
    }
    private RoomDO getRoomDO(Long roomId, String bedNo) {
        RoomDO roomDO = mapRoomBed.get( Utils.formatRoomBed(roomId, bedNo) );
        if ( null == roomDO)
            roomDO = roomMapper.getRoom(roomId, bedNo);
        return roomDO;
    }
    private void procAffinityWhenInstallRecall(String patId, Integer[] affinityCheckTypes, Long roomId_operator, String bedNo_operator) {
        List<QueueDO> affinityItems = queueMapper.getCurPatGivenCheckTypesAndStatus(
                        patId, affinityCheckTypes, QueueStatusEnum.AFFINITY_RECEIVED.getStatus());
        for (int i = 0; i < affinityItems.size(); i++) {
            QueueDO queueItem = affinityItems.get(i);
            queueItem.setRoomId( roomId_operator );
            queueItem.setBedNo( bedNo_operator );
            //queueItem.setSeqNum( preemptQueueItem.getSeqNum() );  // 装机时 内部序号不变 故不需更新
            queueMapper.updateById(queueItem);
        }
    }
    private void procAffinityWhenQueue(/*IN, OUT*/QueueSaveReqVO queueSaveReqVO, Integer[] affinityCheckTypes) {
        // 如果存在 处于 [就诊准备中] | [就诊中] 的亲和项,则跟随其中一个亲和项
        List<QueueDO> affinityItems = queueMapper.getCurPatGivenCheckTypesAndStatusList(
                queueSaveReqVO.getPatId(), affinityCheckTypes,
                List.of(QueueStatusEnum.READY.getStatus(), QueueStatusEnum.ONSTAGE.getStatus()));
        if (!affinityItems.isEmpty()) {
            QueueDO queueItem = affinityItems.get(0);
            queueSaveReqVO.setStatus(QueueStatusEnum.AFFINITY_WAITING.getStatus()); // [亲和-排队] 状态
            queueSaveReqVO.setAffinityItem( 1 );
            queueSaveReqVO.setRoomId(queueItem.getRoomId());
            queueSaveReqVO.setRoomName(queueItem.getRoomName());
            queueSaveReqVO.setBedNo(queueItem.getBedNo());
            queueSaveReqVO.setSeqNum(queueItem.getSeqNum());
        }
    }
}