yxh
7 天以前 68ff33beff11310e830b0615b5a81f83fad0f683
jh-module-ecg/jh-module-ecg-biz/src/main/java/cn/lihu/jh/module/ecg/controller/admin/doctor/DoctorController.java
@@ -7,9 +7,12 @@
import cn.lihu.jh.module.ecg.controller.admin.call.vo.CallSaveReqVO;
import cn.lihu.jh.module.ecg.controller.admin.queue.vo.PatientStatisticVO;
import cn.lihu.jh.module.ecg.controller.admin.queue.vo.QueueRespVO;
import cn.lihu.jh.module.ecg.controller.admin.queue.vo.QueueSaveReqVO;
import cn.lihu.jh.module.ecg.controller.admin.room.vo.RoomRespVO;
import cn.lihu.jh.module.ecg.dal.dataobject.queue.QueueDO;
import cn.lihu.jh.module.ecg.dal.dataobject.room.RoomDO;
import cn.lihu.jh.module.ecg.enums.QueueStatusEnum;
import cn.lihu.jh.module.ecg.annotation.RequireBedOwner;
import cn.lihu.jh.module.ecg.service.call.CallService;
import cn.lihu.jh.module.ecg.service.queue.QueueService;
import io.swagger.v3.oas.annotations.Operation;
@@ -23,12 +26,13 @@
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import static cn.lihu.jh.framework.common.exception.enums.GlobalErrorCodeConstants.SUCCESS;
import static cn.lihu.jh.framework.common.pojo.CommonResult.error;
import static cn.lihu.jh.framework.common.pojo.CommonResult.success;
import static cn.lihu.jh.module.ecg.enums.ErrorCodeConstants.ECG_INNER_ERROR;
import static cn.lihu.jh.module.ecg.enums.ErrorCodeConstants.*;
@Tag(name = "管理后台 - 医生叫号")
@RestController
@@ -40,9 +44,14 @@
    @Resource
    private QueueService queueService;
    /** 【本次修复】撤销"检查完成"时回滚检查单状态并取消确费 */
    @Resource
    private cn.lihu.jh.module.ecg.service.devrent.DevRentService devRentService;
    @Resource
    private CallService callService;
    @RequireBedOwner("暂停接诊")
    @GetMapping("/bed-doctor-pause")
    @Operation(summary = "医生暂停")
    @PreAuthorize("@ss.hasPermission('ecg:doctor:state')")
@@ -54,12 +63,15 @@
        String userNickname = SecurityFrameworkUtils.getLoginUserNickname();
        ErrorCode result = queueService.startBedDoctorPause(roomId, bedNo, userId, userNickname);
        if (result.equals(SUCCESS))
        if (result.equals(SUCCESS)) {
            queueService.updateStop(roomId, bedNo);
            return success(SUCCESS.getCode());
        }
        return error(result);
    }
    @RequireBedOwner("恢复接诊")
    @GetMapping("/bed-doctor-resume")
    @Operation(summary = "医生恢复")
    @PreAuthorize("@ss.hasPermission('ecg:doctor:state')")
@@ -82,12 +94,15 @@
    @PreAuthorize("@ss.hasPermission('ecg:doctor:state')")
    public CommonResult<Integer> bedDoctorOn(
            @RequestParam("roomId") Long roomId,
            @RequestParam("bedNo") String bedNo)
            @RequestParam("bedNo") String bedNo,
            // 工位主键(前端请求里本来就带):传入时优先按它定位,避免 bed_no 与请求值
            // 存在肉眼难辨的差异(结尾空格/大小写/全角)时"明明空闲却更新 0 行"
            @RequestParam(value = "id", required = false) Integer bedId)
    {
        Long userId = SecurityFrameworkUtils.getLoginUserId();
        String userNickname = SecurityFrameworkUtils.getLoginUserNickname();
        ErrorCode result = queueService.startBedDoctorOn(roomId, bedNo, userId, userNickname);
        ErrorCode result = queueService.startBedDoctorOn(roomId, bedNo, userId, userNickname, bedId);
        if (result.equals(SUCCESS))
            return success(0);
@@ -111,6 +126,45 @@
        return error(result);
    }
    /**
     * 释放**当前登录用户**占用的全部诊疗床位(退出系统时自动离座)。
     *
     * <p>背景:医生在「诊室选择」页选好床位后即占用该床位({@code clinic_room.doc_id}),
     * 若一直不点「离座」就退出系统,床位会一直显示被占用,其他医生无法使用。
     *
     * <p>与 {@link #bedDoctorOff} 的区别:
     * <ul>
     *   <li>{@code /bed-doctor-off} 需要前端传 roomId + bedNo(离开某个**指定**床位);</li>
     *   <li>本接口**不需要任何参数**,按当前登录用户反查其占用的**所有**床位并逐一释放。</li>
     * </ul>
     * 之所以做成"无参":退出登录 / 空闲超时 / 令牌失效等场景下,前端可能已经拿不到
     * 床位的 roomId/bedNo(本地缓存已被清理),此时只能由服务端按 userId 反查。
     * 服务端反查也更可靠 —— 前端缓存的床位若被管理员改动过,就会出现"释放不掉"。
     *
     * <p>幂等:未占用任何床位时直接返回成功。
     */
    @PostMapping("/release-my-beds")
    @Operation(summary = "释放当前登录用户占用的全部床位(退出系统时自动离座)")
    public CommonResult<Boolean> releaseMyBeds() {
        Long userId = SecurityFrameworkUtils.getLoginUserId();
        if (userId == null) {
            // 未登录(或令牌已失效)时无床位可释放,按成功返回,避免前端报错
            return success(true);
        }
        String userNickname = SecurityFrameworkUtils.getLoginUserNickname();
        List<RoomDO> occupiedBeds = queueService.getDocRoomInfo(userId);
        if (occupiedBeds == null || occupiedBeds.isEmpty()) {
            return success(true);   // 未占用床位,无需处理
        }
        log.info("[releaseMyBeds] 退出系统自动离座. userId={} nickname={} 占用床位数={}",
                userId, userNickname, occupiedBeds.size());
        for (RoomDO bed : occupiedBeds) {
            // 逐个释放;单个失败不影响其它床位
            queueService.startBedDoctorOff(bed.getRoomId(), bed.getBedNo(), userId, userNickname);
        }
        return success(true);
    }
    @GetMapping("/bed-doctor-get")
    @Operation(summary = "医生工位获取")
    @PreAuthorize("@ss.hasPermission('ecg:doctor:state')")
@@ -124,8 +178,9 @@
        return result;
    }
    @RequireBedOwner("叫号:取下一位患者")
    @GetMapping("/finish-next-patient")
    @Operation(summary = "完成、下一位患者")
    @Operation(summary = "常规叫号、下一位患者")
    @Parameter(name = "roomId", description = "诊室编号", required = true, example = "116")
    @Parameter(name = "bedNo", description = "工位编号", required = true, example = "B2")
    @PreAuthorize("@ss.hasPermission('ecg:doctor:task')")
@@ -135,14 +190,12 @@
    {
        queueService.finishNextPatient(roomId, bedNo);
        List<Byte> queueStatusList = new ArrayList<>();
        queueStatusList.add(QueueStatusEnum.READY.getStatus());
        queueStatusList.add(QueueStatusEnum.ONSTAGE.getStatus());
        queueStatusList.add(QueueStatusEnum.PASSED.getStatus());
        List<QueueDO> queueDOList = queueService.getDoctorQueueByStatus(roomId, bedNo, queueStatusList);
        List<QueueDO> queueDOList = queueService.getCheckRelatedPatient(roomId, bedNo);
        // 过滤出  就诊中的,准备叫号
        QueueDO onStageItem = queueDOList.stream().filter(item -> Objects.equals(item.getStatus(), QueueStatusEnum.ONSTAGE.getStatus())).findFirst().orElse(null);
        QueueDO onStageItem = queueDOList.stream().filter(item ->
                item.getStatus() == QueueStatusEnum.ONSTAGE.getStatus() && item.getAffinityItem() == 0
        ).findFirst().orElse(null);
        if (null != onStageItem) {
            CallSaveReqVO callSaveReqVO = BeanUtils.toBean(onStageItem, CallSaveReqVO.class);
            callSaveReqVO.setId(null);
@@ -152,6 +205,78 @@
        return success(BeanUtils.toBean(queueDOList, QueueRespVO.class));
    }
    @RequireBedOwner("领用:取下一位患者")
    @GetMapping("/finish-receive-next-patient")
    @Operation(summary = "领用叫号、下一位患者")
    @Parameter(name = "roomId", description = "诊室编号", required = true, example = "116")
    @Parameter(name = "bedNo", description = "工位编号", required = true, example = "B2")
    @PreAuthorize("@ss.hasPermission('ecg:doctor:task')")
    public CommonResult<List<QueueRespVO>> finishReceiveNextPatient(
            @RequestParam("roomId") Long roomId,
            @RequestParam("bedNo") String bedNo)
    {
        queueService.finishReceiveNextPatient(roomId, bedNo);
        List<QueueDO> queueDOList = queueService.getCheckRelatedPatient(roomId, bedNo);
        // 过滤出  就诊中的,准备叫号
        QueueDO onStageItem = queueDOList.stream().filter(item ->
                item.getStatus() == QueueStatusEnum.ONSTAGE.getStatus() && item.getAffinityItem() == 0
        ).findFirst().orElse(null);
        if (null != onStageItem) {
            CallSaveReqVO callSaveReqVO = BeanUtils.toBean(onStageItem, CallSaveReqVO.class);
            callSaveReqVO.setId(null);
            callService.createCall(callSaveReqVO);
        }
        return success(BeanUtils.toBean(queueDOList, QueueRespVO.class));
    }
    @RequireBedOwner("装机:取下一位患者")
    @GetMapping("/finish-install-next-patient")
    @Operation(summary = "装机叫号、下一位患者")
    @Parameter(name = "roomId", description = "诊室编号", required = true, example = "116")
    @Parameter(name = "bedNo", description = "工位编号", required = true, example = "B2")
    @PreAuthorize("@ss.hasPermission('ecg:doctor:task')")
    public CommonResult<List<QueueRespVO>> finishInstallNextPatient(
            @RequestParam("roomId") Long roomId,
            @RequestParam("bedNo") String bedNo)
    {
        queueService.finishInstallNextPatient(roomId, bedNo);
        List<QueueDO> queueDOList = queueService.getInstallRelatedPatient(roomId, bedNo); // 待安装的患者
        List<QueueDO> installingQueueDOList = queueDOList.stream().filter(queueDO ->
                queueDO.getStatus() == QueueStatusEnum.INSTALLING.getStatus() && queueDO.getAffinityItem() == 0
        ).toList();
        // 过滤出  [安装中] 的,准备叫号   该工位应该 最多只有一个 [安装中]
        if (installingQueueDOList.size() > 0) {
            QueueDO onStageItem = installingQueueDOList.get(0);
            CallSaveReqVO callSaveReqVO = BeanUtils.toBean(onStageItem, CallSaveReqVO.class);
            callSaveReqVO.setId(null);
            callSaveReqVO.setCallType(1); // 装机叫号
            callService.createCall(callSaveReqVO);
        }
        return success(BeanUtils.toBean(queueDOList, QueueRespVO.class));
    }
    // 常规检查过号、领用过号
    @GetMapping("/pass-waiting-patient")
    @Operation(summary = "过号排队中患者")
    @Parameter(name = "patId", description = "患者编号", required = true, example = "20247845")
    @Parameter(name = "checkType", description = "预约检查类型", required = true, example = "100")
    @PreAuthorize("@ss.hasPermission('ecg:doctor:task')")
    public CommonResult<Integer> passWaitingPatient(
            @RequestParam("patId") String patId,
            @RequestParam("checkType") Integer checkType)
    {
        queueService.passWaitingPatient( patId, checkType );
        return success(0);
    }
    // 常规检查过号、领用过号
    @RequireBedOwner("过号")
    @GetMapping("/pass-next-patient")
    @Operation(summary = "过号、下一位患者")
    @Parameter(name = "roomId", description = "诊室编号", required = true, example = "116")
@@ -163,11 +288,7 @@
    {
        queueService.passNextPatient(roomId, bedNo);
        List<Byte> queueStatusList = new ArrayList<>();
        queueStatusList.add(QueueStatusEnum.READY.getStatus());
        queueStatusList.add(QueueStatusEnum.ONSTAGE.getStatus());
        queueStatusList.add(QueueStatusEnum.PASSED.getStatus());
        List<QueueDO> queueDOList = queueService.getDoctorQueueByStatus(roomId, bedNo, queueStatusList);
        List<QueueDO> queueDOList = queueService.getCheckRelatedPatient(roomId, bedNo);
        // 过滤出  就诊中的,准备叫号
        QueueDO onStageItem = queueDOList.stream().filter(item -> Objects.equals(item.getStatus(), QueueStatusEnum.ONSTAGE.getStatus())).findFirst().orElse(null);
@@ -180,18 +301,46 @@
        return success(BeanUtils.toBean(queueDOList, QueueRespVO.class));
    }
    @GetMapping("/call-again")
    @Operation(summary = "重叫、患者")
    @RequireBedOwner("装机过号")
    @GetMapping("/pass-install-next-patient")
    @Operation(summary = "安装过号、下一位患者")
    @Parameter(name = "roomId", description = "诊室编号", required = true, example = "116")
    @Parameter(name = "bedNo", description = "工位编号", required = true, example = "B2")
    @PreAuthorize("@ss.hasPermission('ecg:doctor:task')")
    public CommonResult<String> callAgainPatient(
    public CommonResult<List<QueueRespVO>> passInstallNextPatient(
            @RequestParam("roomId") Long roomId,
            @RequestParam("bedNo") String bedNo)
    {
        List<Byte> queueStatusList = new ArrayList<>();
        queueService.passInstallNextPatient(roomId, bedNo);
        List<QueueDO> queueDOList = queueService.getInstallRelatedPatient(roomId, bedNo);
        List<QueueDO> installingQueueDOList = queueDOList.stream().filter(queueDO -> queueDO.getStatus() == QueueStatusEnum.INSTALLING.getStatus()).toList();
        // 过滤出  [安装中] 的,准备叫号   该工位应该 最多只有一个 [安装中]
        if (installingQueueDOList.size() > 0) {
            QueueDO onStageItem = installingQueueDOList.get(0);
            CallSaveReqVO callSaveReqVO = BeanUtils.toBean(onStageItem, CallSaveReqVO.class);
            callSaveReqVO.setId(null);
            callSaveReqVO.setCallType(1); // 装机叫号
            callService.createCall(callSaveReqVO);
        }
        return success(BeanUtils.toBean(queueDOList, QueueRespVO.class));
    }
    @RequireBedOwner("重叫")
    @GetMapping("/call-again")
    @Operation(summary = "[常规检查]或[领用] 重叫、患者")
    @Parameter(name = "roomId", description = "诊室编号", required = true, example = "116")
    @Parameter(name = "bedNo", description = "工位编号", required = true, example = "B2")
    @PreAuthorize("@ss.hasPermission('ecg:doctor:task')")
    public CommonResult<String> callPatientAgain(
            @RequestParam("roomId") Long roomId,
            @RequestParam("bedNo") String bedNo)
    {
        List<Integer> queueStatusList = new ArrayList<>();
        queueStatusList.add(QueueStatusEnum.ONSTAGE.getStatus());
        List<QueueDO> queueDOList = queueService.getDoctorQueueByStatus(roomId, bedNo, queueStatusList);
        List<QueueDO> queueDOList = queueService.getBedQueueByStatus(roomId, bedNo, queueStatusList);
        if (queueDOList.isEmpty())
            return success("无就诊中患者");
@@ -209,20 +358,118 @@
        return success("操作成功");
    }
    @GetMapping("/get-patient-list")
    @Operation(summary = "取患者列表")
    @RequireBedOwner("重叫(安装)")
    @GetMapping("/call-install-again")
    @Operation(summary = "重叫、患者")
    @Parameter(name = "roomId", description = "诊室编号", required = true, example = "116")
    @Parameter(name = "bedNo", description = "工位编号", required = true, example = "B2")
    @PreAuthorize("@ss.hasPermission('ecg:doctor:task')")
    public CommonResult<List<QueueRespVO>> getPatientList(
    public CommonResult<String> callInstallingPatientAgain(
            @RequestParam("roomId") Long roomId,
            @RequestParam("bedNo") String bedNo)
    {
        List<Byte> queueStatusList = new ArrayList<>();
        queueStatusList.add(QueueStatusEnum.READY.getStatus());
        queueStatusList.add(QueueStatusEnum.ONSTAGE.getStatus());
        queueStatusList.add(QueueStatusEnum.PASSED.getStatus());
        List<QueueDO> queueDOList = queueService.getDoctorQueueByStatus(roomId, bedNo, queueStatusList);
        List<Integer> queueStatusList = new ArrayList<>();
        queueStatusList.add(QueueStatusEnum.INSTALLING.getStatus());
        List<QueueDO> queueDOList = queueService.getBedQueueByStatus(roomId, bedNo, queueStatusList);
        if (queueDOList.isEmpty())
            return success("无安装中的患者");
        if (queueDOList.size() > 1) {
            log.error("room {} bed {} have {} 安装中的患者", roomId, bedNo, queueDOList.size());
            return error(ECG_INNER_ERROR);
        }
        QueueDO onStageItem = queueDOList.get(0);
        CallSaveReqVO callSaveReqVO = BeanUtils.toBean(onStageItem, CallSaveReqVO.class);
        callSaveReqVO.setId(null);
        callSaveReqVO.setCallType(1); // 装机叫号
        callService.callAgain(callSaveReqVO);
        return success("操作成功");
    }
    @GetMapping("/get-to-be-checked-list")
    @Operation(summary = "取 [常规检查] 或 [待领用] 患者列表")
    @Parameter(name = "roomId", description = "诊室编号", required = true, example = "116")
    @Parameter(name = "bedNo", description = "工位编号", required = true, example = "B2")
    @PreAuthorize("@ss.hasPermission('ecg:doctor:task')")
    public CommonResult<List<QueueRespVO>> getToBeCheckedPatientList(
            @RequestParam("roomId") Long roomId,
            @RequestParam("bedNo") String bedNo)
    {
        List<QueueDO> queueDOList = queueService.getCheckRelatedPatient(roomId, bedNo);
        // 【自助诊断】现场多次出现"列表是空的"却看不出原因:这里把**前端问的工位 + 后端返回几条**
        // 打出来,配合接口一起看就能立刻区分"前端问错工位 / 后端确实没数据 / 页面没刷新"。
        log.info("[getToBeCheckedPatientList] roomId={} bedNo=[{}] 登录人={} → 返回 {} 条",
                roomId, bedNo, SecurityFrameworkUtils.getLoginUserId(),
                null == queueDOList ? 0 : queueDOList.size());
        return success(BeanUtils.toBean(queueDOList, QueueRespVO.class));
    }
    @GetMapping("/get-room-waiting-list")
    @Operation(summary = "取本诊室「排队中(未分配工位)」患者列表")
    @Parameter(name = "roomId", description = "诊室编号", required = true, example = "121")
    @Parameter(name = "bedNo", description = "工位编号", required = true, example = "F1")
    @PreAuthorize("@ss.hasPermission('ecg:doctor:task')")
    public CommonResult<List<QueueRespVO>> getRoomWaitingList(
            @RequestParam("roomId") Long roomId,
            @RequestParam("bedNo") String bedNo)
    {
        // 【本诊室未分配】候诊列表只含"已分流到本工位"的患者;这里把"本工位检查类型、
        // 还没分配工位"的排队患者(大屏能看到、工作台原先看不到)单独返回,供医生手工拉入。
        List<QueueDO> queueDOList = queueService.getUnassignedWaitingPatient(roomId, bedNo);
        log.info("[getRoomWaitingList] roomId={} bedNo=[{}] 登录人={} → 未分配排队中 {} 条",
                roomId, bedNo, SecurityFrameworkUtils.getLoginUserId(),
                null == queueDOList ? 0 : queueDOList.size());
        return success(BeanUtils.toBean(queueDOList, QueueRespVO.class));
    }
    @RequireBedOwner("拉入排队患者")
    @GetMapping("/pull-waiting-patient")
    @Operation(summary = "把「排队中」的患者手工拉入本工位")
    @Parameter(name = "roomId", description = "诊室编号", required = true, example = "121")
    @Parameter(name = "bedNo", description = "工位编号", required = true, example = "F1")
    @Parameter(name = "id", description = "排队行id", required = true, example = "123")
    @PreAuthorize("@ss.hasPermission('ecg:doctor:task')")
    public CommonResult<String> pullWaitingPatient(
            @RequestParam("roomId") Long roomId,
            @RequestParam("bedNo") String bedNo,
            @RequestParam("id") Integer id)
    {
        ErrorCode result = queueService.pullWaitingPatient(roomId, bedNo, id);
        if (result.equals(SUCCESS))
            return success("操作成功");
        return error(result);
    }
    @RequireBedOwner("立即补人")
    @GetMapping("/hurry-up-now")
    @Operation(summary = "立即补人(手动触发本工位自动分流,并返回诊断信息)")
    @Parameter(name = "roomId", description = "诊室编号", required = true, example = "121")
    @Parameter(name = "bedNo", description = "工位编号", required = true, example = "F1")
    @PreAuthorize("@ss.hasPermission('ecg:doctor:task')")
    public CommonResult<Map<String, Object>> hurryUpNow(
            @RequestParam("roomId") Long roomId,
            @RequestParam("bedNo") String bedNo)
    {
        // 现场自救 + 自助诊断:候诊区为空时点一下,立刻得到
        // 「系统开诊状态 / 是否停止候诊 / 候诊上限 / 补人前后人数 / 结论」。
        return success(queueService.hurryUpNow(roomId, bedNo));
    }
    @GetMapping("/get-to-be-installed-list")
    @Operation(summary = "取 [待安装] 患者列表")
    @Parameter(name = "roomId", description = "诊室编号", required = true, example = "116")
    @Parameter(name = "bedNo", description = "工位编号", required = true, example = "B2")
    @PreAuthorize("@ss.hasPermission('ecg:doctor:task')")
    public CommonResult<List<QueueRespVO>> getToBeInstalledPatientList(
            @RequestParam("roomId") Long roomId,
            @RequestParam("bedNo") String bedNo)
    {
        List<QueueDO> queueDOList = queueService.getInstallRelatedPatient(roomId, bedNo);
        return success(BeanUtils.toBean(queueDOList, QueueRespVO.class));
    }
@@ -239,6 +486,53 @@
        return success(patientStatisticVO);
    }
    @GetMapping("/get-dev-ready-statistic")
    @Operation(summary = "设备领用统计")
    @Parameter(name = "roomId", description = "诊室编号", required = true, example = "116")
    @Parameter(name = "bedNo", description = "工位编号", required = true, example = "B2")
    @PreAuthorize("@ss.hasPermission('ecg:doctor:task')")
    public CommonResult<PatientStatisticVO> getDevReadyStatistic(
            @RequestParam("roomId") Long roomId,
            @RequestParam("bedNo") String bedNo)
    {
        PatientStatisticVO patientStatisticVO = queueService.getBedDevReadyStatistic(roomId, bedNo);
        return success(patientStatisticVO);
    }
    @GetMapping("/get-dev-install-statistic")
    @Operation(summary = "设备装机统计")
    @Parameter(name = "roomId", description = "诊室编号", required = true, example = "116")
    @Parameter(name = "bedNo", description = "工位编号", required = true, example = "B2")
    @PreAuthorize("@ss.hasPermission('ecg:doctor:task')")
    public CommonResult<PatientStatisticVO> getDevInstallStatistic(
            @RequestParam("roomId") Long roomId,
            @RequestParam("bedNo") String bedNo)
    {
        PatientStatisticVO patientStatisticVO = queueService.getBedDevInstallStatistic(roomId, bedNo);
        return success(patientStatisticVO);
    }
    // 【本需求·排队队列供服务台分诊调度使用】不再做「工位主人」校验:
    // 分诊台不占座(clinic_room.doc_id 是坐诊医生),否则必报 1010002003「围观模式,不能执行该操作」。
    // 仍保留 @PreAuthorize('ecg:doctor:task') 权限点。
    @GetMapping("/recall-pass-waiting-patient")
    @Operation(summary = "过号-排队中 患者召回")
    @Parameter(name = "patId", description = "患者编号", required = true, example = "B2")
    @Parameter(name = "checkType", description = "预约检查类型", required = true, example = "100")
    @PreAuthorize("@ss.hasPermission('ecg:doctor:task')")
    public CommonResult<String> recallPatient(
            @RequestParam("patId") String patId,
            @RequestParam("checkType") Integer checkType)
    {
        Integer result = queueService.recallPassWaitingPatient(patId, checkType);
        if (null == result || 0 == result)
            return error(PATIENT_NOT_EXISTS);
        return success("操作成功");
    }
    // 【本需求·排队队列供服务台分诊调度使用】不再做「工位主人」校验(原因同上)。
    @GetMapping("/recall-patient")
    @Operation(summary = "过号病人召回")
    @Parameter(name = "roomId", description = "诊室编号", required = true, example = "116")
@@ -248,13 +542,148 @@
    public CommonResult<String> recallPatient(
            @RequestParam("roomId") Long roomId,
            @RequestParam("bedNo") String bedNo,
            @RequestParam("patId") String patId )
            @RequestParam("patId") String patId,
            @RequestParam("checkType") Integer checkType,
            @RequestParam("roomId_operator") Long roomId_operator,
            @RequestParam("bedNo_operator") String bedNo_operator )
    {
        Integer result = queueService.recallPatient(roomId, bedNo, patId);
        Integer result = queueService.recallPatient(roomId, bedNo, patId, checkType);
        if (null == result || 0 == result)
            return error( new ErrorCode(201, "找不到患者") );
            return error(PATIENT_NOT_EXISTS);
        return success("success");
        return success("操作成功");
    }
    // 【本需求·排队队列供服务台分诊调度使用】不再做「工位主人」校验(原因同上)。
    @GetMapping("/recall-install-patient")
    @Operation(summary = "过号安装病人召回")
    @Parameter(name = "roomId", description = "诊室编号", required = true, example = "116")
    @Parameter(name = "bedNo", description = "工位编号", required = true, example = "B2")
    @Parameter(name = "patId", description = "患者编号", required = true, example = "B2")
    @Parameter(name = "checkType", description = "检查类型", required = true, example = "100")
    @PreAuthorize("@ss.hasPermission('ecg:doctor:task')")
    public CommonResult<String> recallInstallPatient(
            @RequestParam("roomId") Long roomId,
            @RequestParam("bedNo") String bedNo,
            @RequestParam("patId") String patId,
            @RequestParam("checkType") Integer checkType,
            @RequestParam("roomId_operator") Long roomId_operator,
            @RequestParam("bedNo_operator") String bedNo_operator )
    {
        // 【本需求·排队队列供服务台分诊调度使用】去掉「操作者工位必须等于患者工位」的校验:
        //   · 分诊台不占座,roomId_operator / bedNo_operator 可能是空或与服务台当前工位不一致;
        //   · 排队队列页传的是**队列行内的工位**,本来也只用于亲和项重新定位。
        // 原校验:if (roomId != roomId_operator) return error(QUEUE_RECALL_INSTALL_NOT_CUR_ROOM);
        // 现在:操作者工位缺失时,退化为用患者队列行上的工位(roomId/bedNo)。
        Long operatorRoomId = (null != roomId_operator) ? roomId_operator : roomId;
        String operatorBedNo = (null != bedNo_operator && !bedNo_operator.isEmpty()) ? bedNo_operator : bedNo;
        Integer result = queueService.recallInstallPatient(roomId, bedNo, patId, checkType, operatorRoomId, operatorBedNo);
        if (null == result || 0 == result)
            return error(PATIENT_NOT_EXISTS);
        return success("操作成功");
    }
    // ==================== 需求2:撤销「叫号 / 检查完成」 ====================
    @RequireBedOwner("撤销检查完成")
    @GetMapping("/undo-finish-patient")
    @Operation(summary = "撤销检查完成:已就诊(40) → 候诊中(20)")
    @Parameter(name = "roomId", description = "诊室编号", required = true, example = "116")
    @Parameter(name = "bedNo", description = "工位编号", required = true, example = "A1")
    @Parameter(name = "patId", description = "患者编号", required = true, example = "913639423")
    @Parameter(name = "checkType", description = "检查类型", required = true, example = "100")
    @PreAuthorize("@ss.hasPermission('ecg:doctor:task')")
    public CommonResult<String> undoFinishPatient(
            @RequestParam("roomId") Long roomId,
            @RequestParam("bedNo") String bedNo,
            @RequestParam("patId") String patId,
            @RequestParam("checkType") Integer checkType) {
        // 说明:必须按 patId + checkType 精确定位,避免误撤销其他患者(既有缺陷见 DevRentServiceImpl)
        Integer result = queueService.undoFinishPatient(roomId, bedNo, patId, checkType);
        if (null == result || 0 == result) {
            return error(APPOINTMENT_NOT_EXISTS);
        }
        // 【本次修复】只回滚队列状态是不够的:检查单(dev_rent)仍停在「常规检查完成(60)」,
        // 前端再次完成时取不到「待检查」单(rentId 为空)→ 后端报「请先预约确认」。
        // 这里把检查单回滚成「待检查」并取消确费(内部已 try/catch,失败不影响撤销结果)。
        devRentService.rollbackRoutineFinish(patId, checkType);
        return success("已撤销检查完成,患者已返回候诊区");
    }
    @RequireBedOwner("撤销叫号")
    @GetMapping("/undo-call-patient")
    @Operation(summary = "撤销叫号:就诊中(30) → 候诊中(20)")
    @Parameter(name = "roomId", description = "诊室编号", required = true, example = "116")
    @Parameter(name = "bedNo", description = "工位编号", required = true, example = "A1")
    @PreAuthorize("@ss.hasPermission('ecg:doctor:task')")
    public CommonResult<String> undoCallPatient(
            @RequestParam("roomId") Long roomId,
            @RequestParam("bedNo") String bedNo) {
        Integer result = queueService.undoCallPatient(roomId, bedNo);
        if (null == result || 0 == result) {
            return error(QUEUE_NOT_EXISTS);
        }
        return success("已撤销叫号,患者已返回候诊区");
    }
    @GetMapping("/last-finished-patient")
    @Operation(summary = "取本工位今日最近一位已完成患者(供撤销提示)")
    @Parameter(name = "roomId", description = "诊室编号", required = true, example = "116")
    @Parameter(name = "bedNo", description = "工位编号", required = true, example = "A1")
    @PreAuthorize("@ss.hasPermission('ecg:doctor:task')")
    public CommonResult<QueueRespVO> lastFinishedPatient(
            @RequestParam("roomId") Long roomId,
            @RequestParam("bedNo") String bedNo) {
        QueueDO queueDO = queueService.getBedLastFinishedQueueItem(roomId, bedNo);
        return success(BeanUtils.toBean(queueDO, QueueRespVO.class));
    }
    // ==================== 需求3:停止候诊开关 ====================
    @RequireBedOwner("停止候诊")
    @GetMapping("/stop-ready")
    @Operation(summary = "停止候诊:不再新增候诊患者(已有候诊仍可叫号)")
    @Parameter(name = "roomId", description = "诊室编号", required = true, example = "116")
    @Parameter(name = "bedNo", description = "工位编号", required = true, example = "A1")
    @PreAuthorize("@ss.hasPermission('ecg:doctor:task')")
    public CommonResult<Boolean> stopReady(
            @RequestParam("roomId") Long roomId,
            @RequestParam("bedNo") String bedNo) {
        ErrorCode ec = queueService.setStopReady(roomId, bedNo, true);
        if (!SUCCESS.getCode().equals(ec.getCode())) {
            return error(ec);
        }
        return success(true);
    }
    @RequireBedOwner("恢复候诊")
    @GetMapping("/resume-ready")
    @Operation(summary = "恢复候诊:重新开始接收候诊患者")
    @Parameter(name = "roomId", description = "诊室编号", required = true, example = "116")
    @Parameter(name = "bedNo", description = "工位编号", required = true, example = "A1")
    @PreAuthorize("@ss.hasPermission('ecg:doctor:task')")
    public CommonResult<Boolean> resumeReady(
            @RequestParam("roomId") Long roomId,
            @RequestParam("bedNo") String bedNo) {
        ErrorCode ec = queueService.setStopReady(roomId, bedNo, false);
        if (!SUCCESS.getCode().equals(ec.getCode())) {
            return error(ec);
        }
        return success(true);
    }
    @GetMapping("/get-stop-ready")
    @Operation(summary = "查询停止候诊状态")
    @Parameter(name = "roomId", description = "诊室编号", required = true, example = "116")
    @Parameter(name = "bedNo", description = "工位编号", required = true, example = "A1")
    @PreAuthorize("@ss.hasPermission('ecg:doctor:task')")
    public CommonResult<Boolean> getStopReady(
            @RequestParam("roomId") Long roomId,
            @RequestParam("bedNo") String bedNo) {
        Boolean stop = queueService.getStopReady(roomId, bedNo);
        return success(stop != null && stop);
    }
}