package cn.lihu.jh.module.ecg.service.queue;
|
|
import java.util.*;
|
import java.util.concurrent.*;
|
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.appointment.AppointmentDO;
|
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.appointment.AppointmentMapper;
|
import cn.lihu.jh.module.ecg.dal.mysql.checktype.CheckTypeMapper;
|
import cn.lihu.jh.module.ecg.dal.mysql.devrent.DevRentMapper;
|
import cn.lihu.jh.framework.mybatis.core.query.LambdaQueryWrapperX;
|
import cn.lihu.jh.module.ecg.enums.DevRentStateEnum;
|
import cn.lihu.jh.module.system.api.oauth2.OAuth2TokenApi;
|
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
|
import lombok.extern.slf4j.Slf4j;
|
import org.apache.commons.lang3.ObjectUtils;
|
import org.apache.cxf.common.util.CollectionUtils;
|
import org.springframework.stereotype.Service;
|
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.validation.annotation.Validated;
|
|
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.mysql.room.RoomMapper;
|
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 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;
|
import static cn.lihu.jh.framework.common.pojo.CommonResult.success;
|
|
/**
|
* 排队 Service 实现类
|
*
|
* @author 芋道源码
|
*/
|
@Service
|
@Validated
|
@Slf4j
|
public class QueueServiceImpl implements QueueService {
|
|
@Resource
|
QueueServiceTxFunctions queueServiceTxFunctions;
|
|
@Resource
|
private OAuth2TokenApi oAuth2TokenApi;
|
|
@Resource
|
private QueueMapper queueMapper;
|
|
@Resource
|
private RoomMapper roomMapper;
|
|
@Resource
|
private AppointmentMapper appointmentMapper;
|
|
@Resource
|
private DevRentMapper devRentMapper;
|
|
/**
|
* 用于「设备领用/装机」取数时过滤出 {@code need_dev_ready = 1} 的检查项目
|
* (见 {@link #filterNeedDevReady})。
|
*/
|
@Resource
|
private CheckTypeMapper checkTypeMapper;
|
|
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
|
|
ConcurrentHashMap<String, RoomDO> mapRoomBed = new ConcurrentHashMap<>();
|
|
@Override
|
public Integer createqueue(QueueSaveReqVO createReqVO) {
|
// 插入
|
QueueDO queue = BeanUtils.toBean(createReqVO, QueueDO.class);
|
queueMapper.insert(queue);
|
// 返回
|
return queue.getId();
|
}
|
|
@Override
|
public void updatequeue(QueueSaveReqVO updateReqVO) {
|
// 校验存在
|
validatequeueExists(updateReqVO.getId());
|
// 更新
|
QueueDO updateObj = BeanUtils.toBean(updateReqVO, QueueDO.class);
|
queueMapper.updateById(updateObj);
|
}
|
|
public void updateStop(long roomId,String bedNo) {
|
// 校验存在
|
if(ObjectUtils.isNotEmpty(roomId)) {
|
QueueDO updateObj=new QueueDO();
|
updateObj.setStatus(40);
|
UpdateWrapper<QueueDO> wrapper=new UpdateWrapper<>();
|
wrapper.eq("room_id",roomId).eq("bed_no",bedNo).eq("status","30");
|
queueMapper.update(updateObj,wrapper);
|
|
QueueDO updateObj1=new QueueDO();
|
updateObj1.setRoomId(0l);
|
updateObj1.setBedNo(" ");
|
UpdateWrapper<QueueDO> wrapper1=new UpdateWrapper<>();
|
wrapper1.eq("room_id",roomId).eq("bed_no",bedNo).eq("status","20");
|
queueMapper.update(updateObj1,wrapper1);
|
}
|
}
|
|
@Override
|
public void deletequeue(Integer id) {
|
// 校验存在
|
validatequeueExists(id);
|
// 删除
|
queueMapper.deleteById(id);
|
}
|
|
@Override
|
public ErrorCode startBedOpen(Long roomId, String roomName, String bedNo) {
|
Future<ErrorCode> future = singleThreadExecutor.submit(new BedOpenCallable(queueServiceTxFunctions, roomId, roomName, bedNo));
|
|
try {
|
ErrorCode ret = future.get();
|
return ret;
|
} catch (InterruptedException e) {
|
e.printStackTrace();
|
} catch (ExecutionException e) {
|
e.printStackTrace();
|
}
|
|
System.out.println("startBedOpen ========");
|
return ECG_INNER_ERROR;
|
}
|
|
@Override
|
public ErrorCode startBedClose(Long roomId, String bedNo) {
|
Future<ErrorCode> future = singleThreadExecutor.submit(new BedCloseCallable(queueServiceTxFunctions, roomId, bedNo));
|
|
try {
|
ErrorCode ret = future.get();
|
return ret;
|
} catch (InterruptedException e) {
|
e.printStackTrace();
|
} catch (ExecutionException e) {
|
e.printStackTrace();
|
}
|
|
System.out.println("startBedClose ========");
|
return ECG_INNER_ERROR;
|
}
|
|
@Override
|
public ErrorCode startBedDoctorPause(Long roomId, String bedNo, Long docId, String docName) {
|
Future<ErrorCode> future = singleThreadExecutor.submit(new BedDoctorPauseCallable(queueServiceTxFunctions, roomId, bedNo, docId, docName));
|
|
try {
|
ErrorCode ret = future.get();
|
return ret;
|
} catch (InterruptedException e) {
|
e.printStackTrace();
|
} catch (ExecutionException e) {
|
e.printStackTrace();
|
}
|
|
System.out.println("startBedDoctorPause ========");
|
return ECG_INNER_ERROR;
|
}
|
|
@Override
|
public ErrorCode startBedDoctorResume(Long roomId, String bedNo, Long docId, String docName) {
|
Future<ErrorCode> future = singleThreadExecutor.submit(new BedDoctorResumeCallable(queueServiceTxFunctions, roomId, bedNo, docId, docName));
|
|
try {
|
ErrorCode ret = future.get();
|
return ret;
|
} catch (InterruptedException e) {
|
e.printStackTrace();
|
} catch (ExecutionException e) {
|
e.printStackTrace();
|
}
|
|
System.out.println("startBedDoctorResume ========");
|
return ECG_INNER_ERROR;
|
}
|
|
@Override
|
public ErrorCode startBedDoctorOn(Long roomId, String bedNo, Long docId, String docName) {
|
Future<ErrorCode> future = singleThreadExecutor.submit(new BedDoctorOnCallable(queueServiceTxFunctions, roomId, bedNo, docId, docName));
|
|
try {
|
ErrorCode ret = future.get();
|
return ret;
|
} catch (InterruptedException e) {
|
e.printStackTrace();
|
} catch (ExecutionException e) {
|
e.printStackTrace();
|
}
|
|
System.out.println("startBedDoctorOn ========");
|
return ECG_INNER_ERROR;
|
}
|
|
@Override
|
public ErrorCode startBedDoctorOff(Long roomId, String bedNo, Long docId, String docName) {
|
Future<ErrorCode> future = singleThreadExecutor.submit(new BedDoctorOffCallable(queueServiceTxFunctions, roomId, bedNo, docId, docName));
|
|
try {
|
ErrorCode ret = future.get();
|
return ret;
|
} catch (InterruptedException e) {
|
e.printStackTrace();
|
} catch (ExecutionException e) {
|
e.printStackTrace();
|
}
|
|
//System.out.println("startBedDoctorOff ========");
|
return ECG_INNER_ERROR;
|
}
|
|
@Override
|
public ErrorCode startNextPatient(Long roomId, String bedNo) {
|
Future<ErrorCode> future = singleThreadExecutor.submit(new BedDoctorNextPatientCallable(queueServiceTxFunctions, roomId, bedNo));
|
|
try {
|
ErrorCode ret = future.get();
|
return ret;
|
} catch (InterruptedException e) {
|
e.printStackTrace();
|
} catch (ExecutionException e) {
|
e.printStackTrace();
|
}
|
|
//System.out.println("startNextPatient ========");
|
return ECG_INNER_ERROR;
|
}
|
|
@Override
|
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(() -> {
|
queueServiceTxFunctions.hurryupOneBed(roomId, bedNo);
|
});
|
}
|
|
@Override
|
public void startHurryUpOneCheckType(Integer checkType) {
|
singleThreadExecutor.execute(() -> {
|
queueServiceTxFunctions.hurryupOneCheckType(checkType);
|
});
|
}
|
|
@Override
|
public void startBedReload() {
|
singleThreadExecutor.execute(() -> {
|
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() {
|
return queueServiceTxFunctions.getMonitorInfo();
|
}
|
|
@Override
|
public CommonResult<RoomRespVO> getRoom(Long roomId, String bedNo, Long docId) {
|
RoomDO roomDO = roomMapper.getRoomByRoomBedDoc(roomId, bedNo, docId);
|
if (null == roomDO) {
|
return error(ROOM_NOT_SIT);
|
}
|
|
BedBO bedBO = queueServiceTxFunctions.getBedQueueBO(roomId, bedNo);
|
if (null == bedBO) {
|
log.error("getRoom mapBedVsQueue DONOT existed. " + roomId + " " + bedNo);
|
return error(QUEUE_BED_NOT_EXIST);
|
}
|
|
RoomRespVO roomRespVO = BeanUtils.toBean(roomDO, RoomRespVO.class);
|
return success(roomRespVO);
|
}
|
|
private void validatequeueExists(Integer id) {
|
if (queueMapper.selectById(id) == null) {
|
throw exception(QUEUE_NOT_EXISTS);
|
}
|
}
|
|
@Override
|
public QueueDO getqueue(Integer id) {
|
return queueMapper.selectById(id);
|
}
|
|
@Override
|
public PageResult<QueueDO> getqueuePage(QueuePageReqVO pageReqVO) {
|
PageResult<QueueDO> queueDOPageResult = queueMapper.selectPage(pageReqVO);
|
fillAppointmentInfo(queueDOPageResult.getList());
|
return queueDOPageResult;
|
}
|
|
|
/**
|
* 预约确认后的排队
|
*
|
* @param queueSaveReqVO
|
*/
|
@Override
|
@Transactional
|
public void queue(QueueSaveReqVO queueSaveReqVO) {
|
// 默认以 [排队中] 状态 进入 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);
|
|
DevRentDO devRent = BeanUtils.toBean(queueSaveReqVO, DevRentDO.class);
|
devRent.setApplyNo(queueSaveReqVO.getApplyNo());
|
devRent.setEpisodeId(queueSaveReqVO.getEpisodeId());
|
devRent.setState(DevRentStateEnum.FREE.getState());
|
devRent.setPatDetails(queueSaveReqVO.getPatDetails());
|
devRent.setCheckType(queueSaveReqVO.getBookCheckType());
|
devRentMapper.insert(devRent);
|
|
startHurryUpOneCheckType(queue.getBookCheckType());
|
}
|
|
@Override
|
public List<QueueDO> selectBookSeqNumByAppointIdList(List<Long> appointIdList) {
|
return queueMapper.selectBookSeqNumByAppointIdList(appointIdList);
|
}
|
|
// 常规检查 叫号
|
@Override
|
public void finishNextPatient(Long roomId, String bedNo) {
|
// 从 DB 把 就诊中的人 设置为就诊完成
|
//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);
|
}
|
|
@Override
|
public void passWaitingPatient(String patId, Integer bookCheckType) {
|
// 从 DB 把 排队中的人 设置为 过号-排队中
|
Integer ret = queueMapper.updatePatientQueueStatus(patId, bookCheckType, QueueStatusEnum.WAITING.getStatus(), QueueStatusEnum.PASSED_WAITING.getStatus());
|
}
|
|
public void passNextPatient(Long roomId, String bedNo) {
|
// 从 DB 把 就诊中的人 设置为过号
|
Integer ret = queueMapper.updateBedQueueStatus(roomId, bedNo, QueueStatusEnum.ONSTAGE.getStatus(), QueueStatusEnum.PASSED.getStatus());
|
|
startNextPatient(roomId, bedNo);
|
}
|
|
@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);
|
fillAppointmentInfo(queueDOList);
|
return queueDOList;
|
}
|
|
/**
|
* 从预约单补齐 queue 表上没有的患者信息。
|
*
|
* queue 表不存这些字段,但医生端列表接口需要展示,所以查询后按 applyNo 回查预约单填充:
|
* 多耐药、性别、出生日期、登记时间、开单医师、开单日期。
|
*/
|
@Override
|
public void fillAppointmentInfo(List<QueueDO> queueDOList) {
|
if (CollectionUtils.isEmpty(queueDOList)) {
|
return;
|
}
|
queueDOList.forEach(queueDO -> {
|
if (null == queueDO.getApplyNo() || queueDO.getApplyNo().isEmpty()) {
|
return;
|
}
|
AppointmentDO appointment = appointmentMapper.getByApplyNo(queueDO.getApplyNo());
|
if (null == appointment) {
|
return;
|
}
|
queueDO.setTolerance(appointment.getTolerance());
|
// 需求2:床边标记(HIS zdy.ISCB)。queue 表已有该列,
|
// 但对「22_* 脚本执行前就已入队」的行,靠这里回查补填,保证屏幕/页面能看到。
|
if (queueDO.getIsBedside() == null) {
|
queueDO.setIsBedside(appointment.getIsBedside());
|
}
|
queueDO.setPatGender(appointment.getPatGender());
|
queueDO.setPatBirthday(appointment.getPatBirthday());
|
queueDO.setRegisterDate(appointment.getRegisterDate());
|
queueDO.setDoctor(appointment.getDoctor());
|
queueDO.setDoctorTime(appointment.getDoctorTime());
|
// 需求1:补平铺的患者信息来源字段(病历区/床号/电话/来源/就诊卡号等)
|
queueDO.setPatSrc(appointment.getPatSrc());
|
queueDO.setPatMobile(appointment.getPatMobile());
|
queueDO.setPatPhone(appointment.getPatPhone());
|
queueDO.setPatWardDesc(appointment.getPatWardDesc());
|
queueDO.setPatBedNo(appointment.getPatBedNo());
|
queueDO.setMedicalCard(appointment.getMedicalCard());
|
queueDO.setDoctorAdviceNo(appointment.getDoctorAdviceNo());
|
queueDO.setOutpatientNo(appointment.getOutpatientNo());
|
queueDO.setPatIdentityId(appointment.getPatIdentityId());
|
});
|
}
|
|
@Override
|
|
public List<QueueDO> getRoomQueueByStatus(Long roomId, List<Integer> statusList) {
|
List<QueueDO> queueDOList = queueMapper.getRoomQueueByStatus(roomId, statusList);
|
fillAppointmentInfo(queueDOList);
|
return queueDOList;
|
}
|
|
// 常规检查 医生界面的统计信息
|
public PatientStatisticVO getPatientStatistic(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.FINISH.getStatus() == item.getStatus()) {
|
patientStatisticVO.setFinishedNum(item.getTotalInStatus());
|
} else if (QueueStatusEnum.PASSED.getStatus() == item.getStatus()
|
|| QueueStatusEnum.PASSED_WAITING.getStatus() == item.getStatus()) {
|
// 【需求4 口径统一】过号 = 已过号(5) + 已过号-排队(3)。
|
// 原先只算 5,而列表与屏幕都含 3,导致"过号"数字比实际少。
|
int cur = patientStatisticVO.getPassedNum() == null ? 0 : patientStatisticVO.getPassedNum();
|
patientStatisticVO.setPassedNum(cur + item.getTotalInStatus());
|
}
|
});
|
|
// 统计 该工位类型的【排队中】人员数量
|
// 【Q7 确认】此处刻意统计**全项目**(该工位所含检查类型)的"排队中"总数,
|
// 语义为"全院还有多少人等着做这类检查",与"本工位一眼可见的候诊人数"互补。
|
// 且已补 deleted=0 / book_date=今日(见 QueueMapper.checkTypeAndStatusStatistic)。
|
RoomDO roomDO = getRoomDO(roomId, bedNo);
|
Integer[] checkTypes = roomDO.getCheckTypes();
|
int queuingNum = 0;
|
// 【防御】checkTypes 为空时不能拼 `book_check_type in ()`(MySQL 语法错误)。
|
// 实测该场景会让统计接口 500,进而导致医生端顶部统计整块报错。
|
if (checkTypes != null && checkTypes.length > 0) {
|
List<Integer> statusList = new ArrayList<>();
|
statusList.add(QueueStatusEnum.WAITING.getStatus());
|
Integer num = queueMapper.checkTypeAndStatusStatistic(checkTypes, statusList);
|
queuingNum = num == null ? 0 : num;
|
} else {
|
log.warn("[getPatientStatistic][工位未配置检查类型,排队中按 0 计] roomId={} bedNo={}", roomId, bedNo);
|
}
|
patientStatisticVO.setQueuingNum(queuingNum);
|
|
return patientStatisticVO;
|
}
|
|
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;
|
}
|
|
@Override
|
public Integer recallPassWaitingPatient(String patId, Integer bookCheckType) {
|
// 从 DB 把 过号-排队中的人 设置为 排队中
|
Integer ret = queueMapper.updatePatientQueueStatus(patId, bookCheckType, QueueStatusEnum.PASSED_WAITING.getStatus(), QueueStatusEnum.WAITING.getStatus());
|
|
return ret;
|
}
|
|
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()
|
|| QueueStatusEnum.PASSED_WAITING.getStatus() == item.getStatus()) {
|
// 【需求4 口径统一】过号 = 已过号(5) + 已过号-排队(3)。
|
//
|
// 与 getPatientStatistic 保持同一口径。原实现只判 PASSED(5),
|
// 而领用界面的列表/屏幕都含 status=3,导致「过号」数字比实际少
|
// —— 这正是用户反馈的「统计与实际不符」。
|
//
|
// 另:原实现用 setPassedNum(item.getTotalInStatus())(**覆盖**而非累加),
|
// 因 SQL 是 group by status,status=3 与 status=5 是两行,
|
// 覆盖会只保留最后一行 → 改为累加。
|
int cur = patientStatisticVO.getPassedNum() == null ? 0 : patientStatisticVO.getPassedNum();
|
patientStatisticVO.setPassedNum(cur + 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();
|
Integer num = 0;
|
// 【防御】checkTypes 为空时不能拼 `book_check_type in ()`(MySQL 语法错误),
|
// 实测该场景会让统计接口 500、医生端顶部统计整块报错。与 getPatientStatistic 保持一致。
|
if (checkTypes != null && checkTypes.length > 0) {
|
List<Integer> statusList = new ArrayList<>();
|
statusList.add(QueueStatusEnum.WAITING.getStatus());
|
num = queueMapper.checkTypeAndStatusStatistic(checkTypes, statusList);
|
} else {
|
log.warn("[getBedDevReadyStatistic][工位未配置检查类型,排队中按 0 计] roomId={} bedNo={}", roomId, bedNo);
|
}
|
patientStatisticVO.setQueuingNum(num == null ? 0 : num);
|
|
return patientStatisticVO;
|
}
|
|
public void initCheckType() {
|
queueServiceTxFunctions.initCheckType();
|
}
|
|
public void startBiz() {
|
if (1 == queueServiceTxFunctions.getOpeningFlag()) return;
|
|
queueServiceTxFunctions.setOpeningFlag(1);
|
startBedReload();
|
}
|
|
public void closeBiz() {
|
queueServiceTxFunctions.setOpeningFlag(0);
|
startBedReload();
|
}
|
|
@Override
|
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());
|
|
// 检查项目.亲和性 不需要在这里处理
|
// 会在 HurryUpOnBed 里面处理 检查项目.亲和性
|
|
startHurryUpOneBed(roomId, bedNo);
|
return updateNum;
|
}
|
|
// ==================== 需求2:撤销「叫号 / 检查完成」 ====================
|
|
/**
|
* 撤销「检查完成」:把本工位今日的指定患者从 已就诊(40) 退回 **候诊中(20)**。
|
* <p>
|
* <b>Q9 确认</b>:撤销后回到**候诊区**(而非"就诊中"),便于医生重新叫号。
|
* <p>
|
* 收尾必须调 {@link #startHurryUpOneBed}:它会重新计算该工位的
|
* {@code readyPatNum} 并刷新分流队列优先级。若不同步内存计数,
|
* {@code hurryupOneCheckType} 会抛出 {@code exceed max queue number}。
|
*
|
* @return 受影响行数(0 表示没找到该患者今日的已完成记录)
|
*/
|
@Override
|
public Integer undoFinishPatient(Long roomId, String bedNo, String patId, Integer checkType) {
|
Integer updateNum = queueMapper.undoFinishPatient(roomId, bedNo, patId, checkType,
|
QueueStatusEnum.FINISH.getStatus(), QueueStatusEnum.READY.getStatus());
|
if (null != updateNum && updateNum > 0) {
|
startHurryUpOneBed(roomId, bedNo);
|
}
|
return updateNum;
|
}
|
|
/**
|
* 撤销「误点叫号」:把本工位当前 就诊中(30) 的患者退回 **候诊中(20)**。
|
*
|
* @return 受影响行数(0 表示当前没有就诊中的患者)
|
*/
|
@Override
|
public Integer undoCallPatient(Long roomId, String bedNo) {
|
Integer updateNum = queueMapper.undoCallPatient(roomId, bedNo,
|
QueueStatusEnum.ONSTAGE.getStatus(), QueueStatusEnum.READY.getStatus());
|
if (null != updateNum && updateNum > 0) {
|
startHurryUpOneBed(roomId, bedNo);
|
}
|
return updateNum;
|
}
|
|
@Override
|
public QueueDO getBedLastFinishedQueueItem(Long roomId, String bedNo) {
|
return queueMapper.getBedLastFinishedQueueItem(roomId, bedNo, QueueStatusEnum.FINISH.getStatus());
|
}
|
|
// ==================== 需求3:停止候诊开关 ====================
|
|
@Override
|
public ErrorCode setStopReady(Long roomId, String bedNo, boolean stop) {
|
// 走单线程执行器,与 bedDoctorPause/Resume 保持一致的串行化处理,
|
// 避免与分流队列的并发修改冲突。
|
Future<ErrorCode> future = singleThreadExecutor.submit(
|
() -> queueServiceTxFunctions.setStopReady(roomId, bedNo, stop));
|
try {
|
return future.get();
|
} catch (InterruptedException e) {
|
Thread.currentThread().interrupt();
|
log.error("[setStopReady] 被中断 roomId={} bedNo={}", roomId, bedNo, e);
|
} catch (ExecutionException e) {
|
log.error("[setStopReady] 执行异常 roomId={} bedNo={}", roomId, bedNo, e);
|
}
|
return ECG_INNER_ERROR;
|
}
|
|
@Override
|
public Boolean getStopReady(Long roomId, String bedNo) {
|
return queueServiceTxFunctions.getStopReady(roomId, bedNo);
|
}
|
|
@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, Integer bookCheckType, Byte jumped) {
|
Integer updateNum = queueMapper.queueJump(patId, bookCheckType, QueueStatusEnum.WAITING.getStatus(), jumped);
|
|
QueueDO queueDO = queueMapper.getQueueByPatId(patId, bookCheckType);
|
startHurryUpOneCheckType(Integer.valueOf(queueDO.getBookCheckType()));
|
return updateNum;
|
}
|
|
@Override
|
public List<RoomDO> getDocRoomInfo(Long docId) {
|
return roomMapper.getRoomByDocId(docId);
|
}
|
|
/**
|
* 获取 指定工位 【检查|领用】相关的患者
|
*
|
* @param roomId
|
* @param bedNo
|
* @return
|
*/
|
@Override
|
public List<QueueDO> getCheckRelatedPatient(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());
|
// 【需求4 口径统一】补上 PASSED_WAITING(3)「已过号-排队」。
|
// 原先列表只含 5,而大屏/诊间屏查询含 3,导致同一患者在不同界面出现与否不一致;
|
// 统计侧也将 3 计入"过号"(见 getPatientStatistic / getBedDevReadyStatistic)。
|
queueStatusList.add(QueueStatusEnum.PASSED_WAITING.getStatus());
|
queueStatusList.add(QueueStatusEnum.AFFINITY.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.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;
|
}
|
|
/**
|
* 获取 指定工位 【安装】相关的患者
|
*
|
* @param roomId
|
* @param bedNo
|
* @return
|
*/
|
@Override
|
public List<QueueDO> getInstallRelatedPatient(Long roomId, String bedNo) {
|
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.RECALLED_INSTALL.getStatus());
|
List<QueueDO> queueDOList = getRoomQueueByStatus(roomId, queueStatusList); // 可以看到本诊室 待安装的患者
|
|
queueStatusList.clear();
|
queueStatusList.add(QueueStatusEnum.INSTALLING.getStatus());
|
queueDOList.addAll(0, getBedQueueByStatus(roomId, bedNo, queueStatusList)); // 当前工位的
|
|
// ======================================================================
|
// 【修复】只保留「需要设备领用」的检查项目。
|
//
|
// 背景:上面两处查询的条件是 `room_id = ? AND status IN (...)`
|
// (getRoomQueueByStatus 甚至**不含 bed_no**),**完全不看 book_check_type**。
|
// 而自动分流(preemptWaitingPatientWithBedCheckTypes)是**按工位 checkTypes 过滤**的,
|
// 所以只有"人工把患者拉进来"这条路径会漏 —— 表现为
|
// 「设备领用」页错误出现常规心电图(100)、平板运动心电(400)等**不需领用设备**的患者。
|
//
|
// 判定依据用配置 `check_type.need_dev_ready`:
|
// 1 = 需要设备领用(如 动态心电 200、动态血压 300)
|
// 0 = 不需要(常规心电图、平板运动心电 …)
|
//
|
// 放在服务层而不是改 SQL:一次查询得到集合后在内存过滤,逻辑集中、易读,
|
// 且不需要为多数据源/动态表名额外调整 SQL。本诊室待安装患者量级很小(几十行)。
|
// ======================================================================
|
queueDOList = filterNeedDevReady(queueDOList);
|
|
// 注意:下面用 lambda,捕获的变量必须是 effectively final;
|
// 故这里另起一个 final 变量,而不是复用上面的 queueDOList(它已被重新赋值)。
|
final List<QueueDO> installQueueList = queueDOList;
|
|
//把 [亲和的] 挪到主项后面
|
List<QueueDO> affinityReceivedList = new ArrayList<>();
|
ListIterator<QueueDO> iterator = installQueueList.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 = installQueueList.size() - 1; i >= 0; i--) {
|
if (affinityReceivedItem.getSeqNum() == installQueueList.get(i).getSeqNum()) {
|
installQueueList.add(i + 1, affinityReceivedItem);
|
break;
|
}
|
}
|
});
|
|
return installQueueList;
|
}
|
|
/**
|
* 只保留「需要设备领用」({@code check_type.need_dev_ready = 1})的检查项目的患者。
|
*
|
* <p>用于「设备领用 / 装机」取数:这些工位只应处理需要领用设备的检查项目,
|
* 否则会出现常规心电图等患者混入设备流程。
|
*
|
* <p>配置异常(查不到检查项目)时**返回原列表**,宁可多显示也不要把患者"藏起来"。
|
*/
|
private List<QueueDO> filterNeedDevReady(List<QueueDO> queueDOList) {
|
if (null == queueDOList || queueDOList.isEmpty()) {
|
return queueDOList;
|
}
|
List<CheckTypeDO> needDevList = checkTypeMapper.selectList(
|
new LambdaQueryWrapperX<CheckTypeDO>().eq(CheckTypeDO::getNeedDevReady, 1));
|
if (null == needDevList || needDevList.isEmpty()) {
|
log.warn("[filterNeedDevReady] 未配置任何[需要设备领用]的检查项目,跳过过滤以免患者全部不可见");
|
return queueDOList;
|
}
|
Set<Integer> needDevCheckTypes = new HashSet<>();
|
needDevList.forEach(ct -> {
|
if (null != ct.getValue()) {
|
needDevCheckTypes.add(ct.getValue());
|
}
|
});
|
|
List<QueueDO> filtered = new ArrayList<>();
|
for (QueueDO item : queueDOList) {
|
if (needDevCheckTypes.contains(item.getBookCheckType())) {
|
filtered.add(item);
|
} else {
|
log.info("[filterNeedDevReady] 过滤掉不需要设备领用的患者. patId={} 检查项目={} 状态={}",
|
item.getPatId(), item.getBookCheckType(), item.getStatus());
|
}
|
}
|
return filtered;
|
}
|
|
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_INSTALL.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(), QueueStatusEnum.PASSED.getStatus(), QueueStatusEnum.RECALLED.getStatus()));
|
if (!affinityItems.isEmpty()) {
|
QueueDO queueItem = affinityItems.get(0);
|
queueSaveReqVO.setStatus(QueueStatusEnum.AFFINITY.getStatus()); // [亲和-排队] 状态
|
queueSaveReqVO.setAffinityItem(1);
|
queueSaveReqVO.setRoomId(queueItem.getRoomId());
|
queueSaveReqVO.setRoomName(queueItem.getRoomName());
|
queueSaveReqVO.setBedNo(queueItem.getBedNo());
|
queueSaveReqVO.setSeqNum(queueItem.getSeqNum());
|
}
|
}
|
|
}
|