| | |
| | | |
| | | import java.util.*; |
| | | import java.util.concurrent.*; |
| | | import java.util.stream.Collectors; |
| | | 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.call.CallMapper; |
| | | 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.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; |
| | |
| | | private RoomMapper roomMapper; |
| | | |
| | | @Resource |
| | | private CallMapper callMapper; |
| | | private AppointmentMapper appointmentMapper; |
| | | |
| | | @Resource |
| | | private DevRentMapper devRentMapper; |
| | | |
| | | /** |
| | | * 用于「设备领用/装机」取数时过滤出 {@code need_dev_ready = 1} 的检查项目 |
| | | * (见 {@link #filterNeedDevReady})。 |
| | | */ |
| | | @Resource |
| | | private CheckTypeMapper checkTypeMapper; |
| | | |
| | | ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor(); |
| | | |
| | |
| | | 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) { |
| | | // 校验存在 |
| | |
| | | |
| | | @Override |
| | | public ErrorCode startBedOpen(Long roomId, String roomName, String bedNo) { |
| | | Future<ErrorCode> future = singleThreadExecutor.submit( new BedOpenCallable(queueServiceTxFunctions, roomId, roomName, bedNo)); |
| | | Future<ErrorCode> future = singleThreadExecutor.submit(new BedOpenCallable(queueServiceTxFunctions, roomId, roomName, bedNo)); |
| | | |
| | | try { |
| | | ErrorCode ret = future.get(); |
| | |
| | | |
| | | @Override |
| | | public ErrorCode startBedClose(Long roomId, String bedNo) { |
| | | Future<ErrorCode> future = singleThreadExecutor.submit( new BedCloseCallable(queueServiceTxFunctions, roomId, bedNo)); |
| | | Future<ErrorCode> future = singleThreadExecutor.submit(new BedCloseCallable(queueServiceTxFunctions, roomId, bedNo)); |
| | | |
| | | try { |
| | | ErrorCode ret = future.get(); |
| | |
| | | |
| | | @Override |
| | | public ErrorCode startBedDoctorPause(Long roomId, String bedNo, Long docId, String docName) { |
| | | Future<ErrorCode> future = singleThreadExecutor.submit( |
| | | new BedDoctorPauseCallable(queueServiceTxFunctions, roomId, bedNo, docId, docName) |
| | | ); |
| | | Future<ErrorCode> future = singleThreadExecutor.submit(new BedDoctorPauseCallable(queueServiceTxFunctions, roomId, bedNo, docId, docName)); |
| | | |
| | | try { |
| | | ErrorCode ret = future.get(); |
| | |
| | | |
| | | @Override |
| | | public ErrorCode startBedDoctorResume(Long roomId, String bedNo, Long docId, String docName) { |
| | | Future<ErrorCode> future = singleThreadExecutor.submit( |
| | | new BedDoctorResumeCallable(queueServiceTxFunctions, roomId, bedNo, docId, docName) |
| | | ); |
| | | Future<ErrorCode> future = singleThreadExecutor.submit(new BedDoctorResumeCallable(queueServiceTxFunctions, roomId, bedNo, docId, docName)); |
| | | |
| | | try { |
| | | ErrorCode ret = future.get(); |
| | |
| | | return ECG_INNER_ERROR; |
| | | } |
| | | |
| | | /** |
| | | * 「本诊室排队中(未分配工位)」的患者列表。 |
| | | * |
| | | * <p>工作台候诊列表按 {@code room_id + bed_no} 查,只看得到**已分流到本工位**的患者; |
| | | * 而"排队中(10)"的患者 {@code bed_no} 为空(大屏会显示、工作台看不到), |
| | | * 这里按**本工位开展的检查项目**把他们单独查出来,供医生手工拉入。 |
| | | * |
| | | * <p>工位不存在或没配检查项目时返回空列表(记警告),不抛异常 —— 列表为空不影响主流程。 |
| | | */ |
| | | @Override |
| | | public ErrorCode startBedDoctorOn(Long roomId, String bedNo, Long docId, String docName) { |
| | | Future<ErrorCode> future = singleThreadExecutor.submit( |
| | | new BedDoctorOnCallable(queueServiceTxFunctions, roomId, bedNo, docId, docName) |
| | | ); |
| | | public List<QueueDO> getUnassignedWaitingPatient(Long roomId, String bedNo) { |
| | | RoomDO bed = (null == roomId || null == bedNo) ? null : roomMapper.getRoom(roomId, bedNo); |
| | | if (null == bed || null == bed.getCheckTypes() || 0 == bed.getCheckTypes().length) { |
| | | log.warn("[getUnassignedWaitingPatient] 工位不存在或未配置检查项目,返回空. roomId={} bedNo={}", |
| | | roomId, bedNo); |
| | | return new ArrayList<>(); |
| | | } |
| | | return queueMapper.selectUnassignedWaitingByCheckTypes( |
| | | QueueStatusEnum.WAITING.getStatus(), bed.getCheckTypes()); |
| | | } |
| | | |
| | | /** |
| | | * 手工把某位「排队中」患者拉入本工位(→ 准备中)。 |
| | | * |
| | | * <p>权限由 Controller 上的 {@code @RequireBedOwner} 保证:只有本工位主人能拉。 |
| | | * 这里只做业务校验与条件更新(并发安全:{@code and status = 10}),并复用自动分流的亲和项逻辑。 |
| | | */ |
| | | @Override |
| | | public ErrorCode pullWaitingPatient(Long roomId, String bedNo, Integer queueId) { |
| | | return queueServiceTxFunctions.pullWaitingPatient(roomId, bedNo, queueId); |
| | | } |
| | | |
| | | @Override |
| | | public ErrorCode startBedDoctorOn(Long roomId, String bedNo, Long docId, String docName, Integer bedId) { |
| | | Future<ErrorCode> future = singleThreadExecutor.submit(new BedDoctorOnCallable(queueServiceTxFunctions, roomId, bedNo, docId, docName, bedId)); |
| | | |
| | | try { |
| | | ErrorCode ret = future.get(); |
| | |
| | | |
| | | @Override |
| | | public ErrorCode startBedDoctorOff(Long roomId, String bedNo, Long docId, String docName) { |
| | | Future<ErrorCode> future = singleThreadExecutor.submit( |
| | | new BedDoctorOffCallable(queueServiceTxFunctions, roomId, bedNo, docId, docName) |
| | | ); |
| | | Future<ErrorCode> future = singleThreadExecutor.submit(new BedDoctorOffCallable(queueServiceTxFunctions, roomId, bedNo, docId, docName)); |
| | | |
| | | try { |
| | | ErrorCode ret = future.get(); |
| | |
| | | return ECG_INNER_ERROR; |
| | | } |
| | | |
| | | /** |
| | | * 【本次需求】管理员强制清空工位(不要求是本人)。 |
| | | * <p> |
| | | * 与 {@link #startBedDoctorOff} 共用同一个单线程执行器,保证同一时刻只处理一个工位操作, |
| | | * 避免与「叫号 / 入座 / 离座」并发改同一行。 |
| | | */ |
| | | @Override |
| | | public ErrorCode startBedDoctorOffByAdmin(Long roomId, String bedNo) { |
| | | Future<ErrorCode> future = singleThreadExecutor.submit( |
| | | () -> queueServiceTxFunctions.bedDoctorOffByAdmin(roomId, bedNo)); |
| | | try { |
| | | return future.get(); |
| | | } catch (InterruptedException e) { |
| | | Thread.currentThread().interrupt(); |
| | | log.error("[管理员清空工位] 被中断", e); |
| | | } catch (ExecutionException e) { |
| | | log.error("[管理员清空工位] 执行失败", e.getCause() != null ? e.getCause() : e); |
| | | } |
| | | return ECG_INNER_ERROR; |
| | | } |
| | | |
| | | @Override |
| | | public ErrorCode startNextPatient(Long roomId, String bedNo) { |
| | | Future<ErrorCode> future = singleThreadExecutor.submit( |
| | | new BedDoctorNextPatientCallable(queueServiceTxFunctions, roomId, bedNo) |
| | | ); |
| | | Future<ErrorCode> future = singleThreadExecutor.submit(new BedDoctorNextPatientCallable(queueServiceTxFunctions, roomId, bedNo)); |
| | | |
| | | try { |
| | | ErrorCode ret = future.get(); |
| | |
| | | |
| | | @Override |
| | | public ErrorCode startNextInstallPatient(Long roomId, String bedNo) { |
| | | Future<ErrorCode> future = singleThreadExecutor.submit( |
| | | new BedDoctorNextInstallPatientCallable(queueServiceTxFunctions, roomId, bedNo) |
| | | ); |
| | | Future<ErrorCode> future = singleThreadExecutor.submit(new BedDoctorNextInstallPatientCallable(queueServiceTxFunctions, roomId, bedNo)); |
| | | |
| | | try { |
| | | ErrorCode ret = future.get(); |
| | |
| | | |
| | | @Override |
| | | public void startHurryUpOneBed(Long roomId, String bedNo) { |
| | | singleThreadExecutor.execute( () -> { |
| | | singleThreadExecutor.execute(() -> { |
| | | queueServiceTxFunctions.hurryupOneBed(roomId, bedNo); |
| | | }); |
| | | } |
| | | |
| | | @Override |
| | | public Map<String, Object> hurryUpNow(Long roomId, String bedNo) { |
| | | // 同步执行:要立刻返回"补人前后人数 + 结论",异步就看不到结果了 |
| | | return queueServiceTxFunctions.hurryUpNow(roomId, bedNo); |
| | | } |
| | | |
| | | @Override |
| | | public void startHurryUpOneCheckType(Integer checkType) { |
| | | singleThreadExecutor.execute( () -> { |
| | | queueServiceTxFunctions.hurryupOneCheckType( checkType ); |
| | | singleThreadExecutor.execute(() -> { |
| | | queueServiceTxFunctions.hurryupOneCheckType(checkType); |
| | | }); |
| | | } |
| | | |
| | | @Override |
| | | public void startBedReload() { |
| | | singleThreadExecutor.execute( () -> { |
| | | singleThreadExecutor.execute(() -> { |
| | | queueServiceTxFunctions.bedReload(); |
| | | queueServiceTxFunctions.hurryupAllBed(); |
| | | queueServiceTxFunctions.monitorInfo(); |
| | |
| | | |
| | | @Override |
| | | public void startResetRoom(Boolean needCloseBed) { |
| | | singleThreadExecutor.execute( () -> { |
| | | singleThreadExecutor.execute(() -> { |
| | | queueServiceTxFunctions.resetRoom(needCloseBed); |
| | | queueServiceTxFunctions.bedReload(); |
| | | queueServiceTxFunctions.monitorInfo(); |
| | |
| | | return error(ROOM_NOT_SIT); |
| | | } |
| | | |
| | | BedQueueBO bedQueueBO = queueServiceTxFunctions.getBedQueueBO(roomId, bedNo); |
| | | if (null == bedQueueBO) { |
| | | BedBO bedBO = queueServiceTxFunctions.getBedQueueBO(roomId, bedNo); |
| | | if (null == bedBO) { |
| | | log.error("getRoom mapBedVsQueue DONOT existed. " + roomId + " " + bedNo); |
| | | return error(QUEUE_BED_NOT_EXIST); |
| | | } |
| | |
| | | |
| | | @Override |
| | | public PageResult<QueueDO> getqueuePage(QueuePageReqVO pageReqVO) { |
| | | return queueMapper.selectPage(pageReqVO); |
| | | PageResult<QueueDO> queueDOPageResult = queueMapper.selectPage(pageReqVO); |
| | | fillAppointmentInfo(queueDOPageResult.getList()); |
| | | return queueDOPageResult; |
| | | } |
| | | |
| | | |
| | | /** |
| | | * 预约确认后的排队 |
| | | * |
| | | * @param queueSaveReqVO |
| | | */ |
| | | @Override |
| | |
| | | queueSaveReqVO.setStatus(QueueStatusEnum.WAITING.getStatus()); |
| | | |
| | | // 处理 检查项目.亲和性 逻辑 |
| | | CheckTypeDO checkTypeDO = queueServiceTxFunctions.getCheckTypeItem( queueSaveReqVO.getBookCheckType() ); |
| | | if ( checkTypeDO.getAffinityCheckTypes().length > 0) { |
| | | CheckTypeDO checkTypeDO = queueServiceTxFunctions.getCheckTypeItem(queueSaveReqVO.getBookCheckType()); |
| | | if (checkTypeDO.getAffinityCheckTypes().length > 0) { |
| | | procAffinityWhenQueue(/*IN,OUT*/queueSaveReqVO, checkTypeDO.getAffinityCheckTypes()); |
| | | } |
| | | |
| | |
| | | queueMapper.insert(queue); |
| | | |
| | | DevRentDO devRent = BeanUtils.toBean(queueSaveReqVO, DevRentDO.class); |
| | | devRent.setState( DevRentStateEnum.FREE.getState() ); |
| | | devRent.setPatDetails( queueSaveReqVO.getPatDetails() ); |
| | | devRent.setCheckType( queueSaveReqVO.getBookCheckType() ); |
| | | 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() ); |
| | | startHurryUpOneCheckType(queue.getBookCheckType()); |
| | | } |
| | | |
| | | @Override |
| | | public List<QueueDO> selectQueueByAppointIdList(List<Long> appointIdList) { |
| | | return queueMapper.selectQueueByAppointIdList(appointIdList); |
| | | } |
| | | |
| | | // 常规检查 叫号 |
| | |
| | | List<Integer> queueStatusList = new ArrayList<>(); |
| | | queueStatusList.add(QueueStatusEnum.ONSTAGE.getStatus()); |
| | | Integer num = queueMapper.bedQueueStatisticByStatus(roomId, bedNo, queueStatusList); |
| | | if ( num != null && num > 0) |
| | | return; |
| | | if (num != null && num > 0) return; |
| | | |
| | | startNextPatient(roomId, bedNo); |
| | | } |
| | |
| | | List<Integer> queueStatusList = new ArrayList<>(); |
| | | queueStatusList.add(QueueStatusEnum.ONSTAGE.getStatus()); |
| | | Integer num = queueMapper.bedQueueStatisticByStatus(roomId, bedNo, queueStatusList); |
| | | if ( num != null && num > 0) |
| | | return; |
| | | if (num != null && num > 0) return; |
| | | |
| | | startNextPatient(roomId, bedNo); |
| | | } |
| | |
| | | List<Integer> queueStatusList = new ArrayList<>(); |
| | | queueStatusList.add(QueueStatusEnum.INSTALLING.getStatus()); |
| | | Integer num = queueMapper.bedQueueStatisticByStatus(roomId, bedNo, queueStatusList); |
| | | if ( num != null && num > 0) |
| | | return; |
| | | 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()); |
| | | 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()); |
| | | Integer ret = queueMapper.updateBedQueueStatus(roomId, bedNo, QueueStatusEnum.INSTALLING.getStatus(), QueueStatusEnum.PASSED_INSTALL.getStatus()); |
| | | |
| | | startNextPatient(roomId, bedNo); |
| | | // 【既有笔误修复】这里原本调的是 startNextPatient(常规检查取人:候诊中 → 就诊中), |
| | | // 而装机过号后应当继续取**安装队列**里的下一位(已领用/已召回-安装 → 安装中)。 |
| | | // 由于装机工位不参与分流、没有"候诊中"患者,原调用实际上什么也不会发生, |
| | | // 表现为"装机过号后不会自动叫下一位"。 |
| | | startNextInstallPatient(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 表上没有的患者信息。 |
| | | * |
| | | * <p>queue 表不存这些字段,但医生端列表接口需要展示,所以查询后按 applyNo 回查预约单填充: |
| | | * 多耐药、性别、出生日期、登记时间、开单医师、开单日期。</p> |
| | | * |
| | | * <h3>⚠️ 性能修复:由 N+1 改为一次批量查询</h3> |
| | | * <p><b>原实现</b>在 {@code forEach} 里对每一行调用 |
| | | * {@code appointmentMapper.getByApplyNo(applyNo)},即**每行一次 SQL**。 |
| | | * 列表每页 10 条 ⇒ 10 次查询;而该 SQL |
| | | * ({@code WHERE apply_no = ? AND deleted = 0})在修复前**用不上索引**, |
| | | * 每次都是全表扫描 appointment(实测 16.6 万行,约 549 ms/次), |
| | | * 一页就要 4.5~5.5 秒 —— 这是「队列列表打开很慢」的直接原因。</p> |
| | | * |
| | | * <p><b>现实现</b>改为:先收集本页所有 applyNo(去重)→ **一次** |
| | | * {@code getByApplyNoList} 批量取回 → 内存里按 applyNo 建映射再回填。 |
| | | * 一页 SQL 从 10 次降到 1 次。</p> |
| | | * |
| | | * <p>注:配套已于 {@code 26_add_index_appointment_apply_no.sql} |
| | | * 给 {@code appointment.apply_no} 加上索引; |
| | | * 但即便有索引,N+1 仍是 10 次往返,故两处一起改才是最优。</p> |
| | | */ |
| | | @Override |
| | | public void fillAppointmentInfo(List<QueueDO> queueDOList) { |
| | | if (CollectionUtils.isEmpty(queueDOList)) { |
| | | return; |
| | | } |
| | | // 1. 收集本次需要回查的申请单号(跳过空值)。 |
| | | // 用 LinkedHashSet 去重:同一页里理论上 apply_no 唯一,但去重后 IN 列表更短、更安全。 |
| | | Set<String> applyNoSet = new LinkedHashSet<>(); |
| | | for (QueueDO queueDO : queueDOList) { |
| | | if (queueDO != null && queueDO.getApplyNo() != null && !queueDO.getApplyNo().isEmpty()) { |
| | | applyNoSet.add(queueDO.getApplyNo()); |
| | | } |
| | | } |
| | | if (applyNoSet.isEmpty()) { |
| | | return; |
| | | } |
| | | |
| | | // 2. 一次批量取回(SQL 内部已带 deleted = 0)。 |
| | | // 注意:MySQL 的 IN () 会语法错误,故必须保证列表非空 —— 上面已判空。 |
| | | List<AppointmentDO> appointmentList = appointmentMapper.getByApplyNoList(new ArrayList<>(applyNoSet)); |
| | | |
| | | // 3. 按 applyNo 建映射;个别 applyNo 查不到预约单是正常情况(历史脏数据),跳过即可。 |
| | | Map<String, AppointmentDO> appointmentMap = appointmentList.stream() |
| | | .filter(item -> item != null && item.getApplyNo() != null) |
| | | .collect(Collectors.toMap(AppointmentDO::getApplyNo, item -> item, (a, b) -> a)); |
| | | |
| | | // 4. 内存回填,不再逐条查库 |
| | | queueDOList.forEach(queueDO -> { |
| | | if (queueDO == null || queueDO.getApplyNo() == null || queueDO.getApplyNo().isEmpty()) { |
| | | return; |
| | | } |
| | | AppointmentDO appointment = appointmentMap.get(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; |
| | | } |
| | | |
| | |
| | | patientStatisticVO.setReadyNum(item.getTotalInStatus()); |
| | | } else if (QueueStatusEnum.FINISH.getStatus() == item.getStatus()) { |
| | | patientStatisticVO.setFinishedNum(item.getTotalInStatus()); |
| | | } else if (QueueStatusEnum.PASSED.getStatus() == item.getStatus()) { |
| | | patientStatisticVO.setPassedNum(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(); |
| | | List<Integer> statusList = new ArrayList<>(); |
| | | statusList.add(QueueStatusEnum.WAITING.getStatus()); |
| | | Integer num = queueMapper.checkTypeAndStatusStatistic(checkTypes, statusList); |
| | | patientStatisticVO.setQueuingNum(num); |
| | | 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; |
| | | } |
| | |
| | | }); |
| | | |
| | | // 装机界面:统计 该诊室 [已领用] 的数量, 因为安装工位看不到 [已领用] 患者,所以无法根据装机工位来统计,只能按诊室统计 |
| | | patientStatisticVO.setReceivedNum( 0 ); |
| | | patientStatisticVO.setReceivedNum(0); |
| | | List<BedQueueStatisticDO> roomQueueStatisticDOList = queueMapper.roomQueueStatistic(roomId); |
| | | roomQueueStatisticDOList.forEach(item -> { |
| | | if (QueueStatusEnum.RECEIVED.getStatus() == item.getStatus()) { |
| | | patientStatisticVO.setReceivedNum( patientStatisticVO.getReceivedNum() + item.getTotalInStatus() ); |
| | | 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) { |
| | |
| | | bedQueueStatisticDOList.forEach(item -> { |
| | | if (QueueStatusEnum.READY.getStatus() == item.getStatus()) { |
| | | patientStatisticVO.setReadyNum(item.getTotalInStatus()); |
| | | } else if (QueueStatusEnum.PASSED.getStatus() == item.getStatus()) { |
| | | patientStatisticVO.setPassedNum(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 ); |
| | | patientStatisticVO.setReceivedNum(0); |
| | | List<BedQueueStatisticDO> roomQueueStatisticDOList = queueMapper.roomQueueStatistic(roomId); |
| | | roomQueueStatisticDOList.forEach(item -> { |
| | | if (QueueStatusEnum.RECEIVED.getStatus() == item.getStatus()) { |
| | | patientStatisticVO.setReceivedNum( patientStatisticVO.getReceivedNum() + item.getTotalInStatus() ); |
| | | patientStatisticVO.setReceivedNum(patientStatisticVO.getReceivedNum() + item.getTotalInStatus()); |
| | | } else if (QueueStatusEnum.INSTALLING.getStatus() == item.getStatus()) { |
| | | patientStatisticVO.setReceivedNum( patientStatisticVO.getReceivedNum() + item.getTotalInStatus() ); |
| | | patientStatisticVO.setReceivedNum(patientStatisticVO.getReceivedNum() + item.getTotalInStatus()); |
| | | } else if (QueueStatusEnum.FINISH.getStatus() == item.getStatus()) { |
| | | patientStatisticVO.setReceivedNum(patientStatisticVO.getReceivedNum() + item.getTotalInStatus() ); |
| | | 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); |
| | | 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( ); |
| | | queueServiceTxFunctions.initCheckType(); |
| | | } |
| | | |
| | | public void startBiz() { |
| | | if (1 == queueServiceTxFunctions.getOpeningFlag()) |
| | | return; |
| | | if (1 == queueServiceTxFunctions.getOpeningFlag()) return; |
| | | |
| | | queueServiceTxFunctions.setOpeningFlag(1); |
| | | startBedReload(); |
| | |
| | | |
| | | public void closeBiz() { |
| | | queueServiceTxFunctions.setOpeningFlag(0); |
| | | |
| | | // 【本次需求】闭诊时清空当班工位(doc_id / doc_name 置空,状态改回空闲)。 |
| | | // |
| | | // 背景:工位不再因「空闲超时 / 令牌失效 / 刷新页面」被释放(避免医生离开一会儿就丢工位), |
| | | // 因此需要一个兜底 —— 每天闭诊时统一清空,防止医生下班忘离座导致工位长期被占用。 |
| | | // 先清库,再由下面的 startBedReload() 按库重新装载内存,保证二者一致。 |
| | | try { |
| | | queueServiceTxFunctions.closeBizClearBeds(); |
| | | } catch (Exception e) { |
| | | // 清空失败不能阻断闭诊流程本身 |
| | | log.error("[闭诊] 清空当班工位失败(不影响闭诊)", e); |
| | | } |
| | | |
| | | 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()); |
| | | Integer updateNum = queueMapper.recallPassedPatient(roomId, bedNo, patId, checkType, QueueStatusEnum.PASSED.getStatus(), QueueStatusEnum.RECALLED.getStatus()); |
| | | |
| | | // 检查项目.亲和性 不需要在这里处理 |
| | | // 会在 HurryUpOnBed 里面处理 检查项目.亲和性 |
| | | |
| | | startHurryUpOneBed(roomId, bedNo); |
| | | |
| | | // 【诊断】补人循环有三道门闸(未开诊 / 已停止候诊 / 候诊区已满), |
| | | // 任一条成立时患者会**停在「已召回(15)」不进候诊区**。 |
| | | // 现在 nextPatient 支持直接从「已召回」叫号(2026-10-29 修复),这里只补一条日志方便现场排查。 |
| | | if (null != updateNum && updateNum > 0) { |
| | | try { |
| | | QueueDO stillRecalled = queueMapper.getBedQueueItemByPatAndStatus( |
| | | roomId, bedNo, patId, checkType, QueueStatusEnum.RECALLED.getStatus()); |
| | | if (null != stillRecalled) { |
| | | log.warn("[recallPatient] 已置为「已召回」,但未进入候诊区" |
| | | + "(可能原因:未开诊 / 已停止候诊 / 候诊区已满),叫号时将直接从「已召回」取人. " |
| | | + "工位={}/{} patId={} seqNum={}", |
| | | roomId, bedNo, patId, stillRecalled.getSeqNum()); |
| | | } |
| | | } catch (Exception e) { |
| | | log.warn("[recallPatient] 检查「已召回」是否进入候诊区时异常(不影响召回结果). 工位={}/{} patId={}", |
| | | roomId, bedNo, patId, e); |
| | | } |
| | | } |
| | | 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()); |
| | | 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) { |
| | | CheckTypeDO checkTypeDO = queueServiceTxFunctions.getCheckTypeItem(checkType); |
| | | if (checkTypeDO.getAffinityCheckTypes().length > 0) { |
| | | procAffinityWhenInstallRecall(patId, checkTypeDO.getAffinityCheckTypes(), roomId_operator, bedNo_operator); |
| | | } |
| | | } |
| | |
| | | return updateNum; |
| | | } |
| | | |
| | | /** |
| | | * 加急 |
| | | * |
| | | * @param patId |
| | | * @param jumped |
| | | * @return |
| | | */ |
| | | @Override |
| | | public Integer patientJump(String patId, Byte jumped) { |
| | | Integer updateNum = queueMapper.queueJump(patId, QueueStatusEnum.WAITING.getStatus(), jumped); |
| | | public Integer patientJump(String patId, Integer bookCheckType, Byte jumped) { |
| | | Integer updateNum = queueMapper.queueJump(patId, bookCheckType, QueueStatusEnum.WAITING.getStatus(), jumped); |
| | | |
| | | QueueDO queueDO = queueMapper.getQueueByPatId(patId); |
| | | QueueDO queueDO = queueMapper.getQueueByPatId(patId, bookCheckType); |
| | | startHurryUpOneCheckType(Integer.valueOf(queueDO.getBookCheckType())); |
| | | return updateNum; |
| | | } |
| | | |
| | | @Override |
| | | public RoomDO getDocRoomInfo(Long docId) { |
| | | public List<RoomDO> getDocRoomInfo(Long docId) { |
| | | return roomMapper.getRoomByDocId(docId); |
| | | } |
| | | |
| | | /** |
| | | * 获取 指定工位 待检查|待领用的人 |
| | | * 获取 指定工位 【检查|领用】相关的患者 |
| | | * |
| | | * @param roomId |
| | | * @param bedNo |
| | | * @return |
| | | */ |
| | | @Override |
| | | public List<QueueDO> getToBeCheckedPatient(Long roomId, String bedNo) { |
| | | 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()); |
| | | queueStatusList.add(QueueStatusEnum.AFFINITY_WAITING.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); |
| | | |
| | | //把 [亲和的] 挪到主项后面 |
| | | //把 [亲和的] 挪到主项后面(同排队号 + 同患者才算一组,见 Utils#insertAffinityItemsAfterMain) |
| | | List<QueueDO> affinityWaitingList = new ArrayList<>(); |
| | | ListIterator<QueueDO> iterator = queueDOList.listIterator(); |
| | | while (iterator.hasNext()) { |
| | | QueueDO item = iterator.next(); |
| | | if (item.getStatus() == QueueStatusEnum.AFFINITY_WAITING.getStatus()) { |
| | | 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; |
| | | } |
| | | } |
| | | }); |
| | | Utils.insertAffinityItemsAfterMain(queueDOList, affinityWaitingList); |
| | | |
| | | return queueDOList; |
| | | } |
| | | |
| | | /** |
| | | * 获取 指定工位 【安装】相关的患者 |
| | | * |
| | | * @param roomId |
| | | * @param bedNo |
| | | * @return |
| | | */ |
| | | @Override |
| | | public List<QueueDO> getToBeInstalledPatient(Long roomId, String bedNo) { |
| | | 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_RECEIVED.getStatus()); |
| | | List<QueueDO> queueDOList = getRoomQueueByStatus(roomId, queueStatusList); |
| | | 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()); |
| | | queueStatusList.add(QueueStatusEnum.RECALLED_INSTALL.getStatus()); |
| | | queueDOList.addAll(0, getBedQueueByStatus(roomId, bedNo, queueStatusList) ); |
| | | queueDOList.addAll(0, getBedQueueByStatus(roomId, bedNo, queueStatusList)); // 当前工位的 |
| | | |
| | | //把 [亲和的] 挪到主项后面 |
| | | // ====================================================================== |
| | | // 【修复】只保留「需要设备领用」的检查项目。 |
| | | // |
| | | // 背景:上面两处查询的条件是 `room_id = ? AND status IN (...)` |
| | | // (getRoomQueueByStatus 甚至**不含 bed_no**),**完全不看 book_check_type**。 |
| | | // 而自动分流(QueueMapper#getFirstWaitingByCheckTypes + preemptWaitingPatientById)是**按工位 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; |
| | | |
| | | //把 [亲和的] 挪到主项后面(同排队号 + 同患者才算一组,见 Utils#insertAffinityItemsAfterMain) |
| | | List<QueueDO> affinityReceivedList = new ArrayList<>(); |
| | | ListIterator<QueueDO> iterator = queueDOList.listIterator(); |
| | | ListIterator<QueueDO> iterator = installQueueList.listIterator(); |
| | | while (iterator.hasNext()) { |
| | | QueueDO item = iterator.next(); |
| | | if (item.getStatus() == QueueStatusEnum.AFFINITY_RECEIVED.getStatus()) { |
| | | if (item.getStatus() == QueueStatusEnum.AFFINITY_INSTALL.getStatus()) { |
| | | iterator.remove(); |
| | | affinityReceivedList.add(item); |
| | | } |
| | | } |
| | | affinityReceivedList.forEach( affinityReceivedItem -> { |
| | | for (int i = queueDOList.size()-1; i >= 0; i--) { |
| | | if ( affinityReceivedItem.getSeqNum() == queueDOList.get(i).getSeqNum() ) { |
| | | queueDOList.add(i + 1, affinityReceivedItem); |
| | | break; |
| | | } |
| | | Utils.insertAffinityItemsAfterMain(installQueueList, affinityReceivedList); |
| | | |
| | | 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()); |
| | | } |
| | | }); |
| | | |
| | | return queueDOList; |
| | | 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); |
| | | 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()); |
| | | 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.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())); |
| | | // 如果存在 [候诊中] [就诊中] [过号] [已召回] 能被依附的检查项,则依附到其中一个 |
| | | 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_WAITING.getStatus()); // [亲和-排队] 状态 |
| | | queueSaveReqVO.setStatus(QueueStatusEnum.AFFINITY.getStatus()); // [亲和-排队] 状态 |
| | | queueSaveReqVO.setAffinityItem(1); |
| | | queueSaveReqVO.setRoomId(queueItem.getRoomId()); |
| | | queueSaveReqVO.setRoomName(queueItem.getRoomName()); |
| | | queueSaveReqVO.setBedNo(queueItem.getBedNo()); |