package cn.lihu.jh.module.ecg.controller.admin.room;
|
|
import java.util.*;
|
import java.io.IOException;
|
import java.util.stream.Collectors;
|
import javax.annotation.Resource;
|
import javax.annotation.security.PermitAll;
|
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletResponse;
|
import javax.validation.Valid;
|
|
import cn.hutool.extra.servlet.ServletUtil;
|
import cn.lihu.jh.module.ecg.utils.IpUtils;
|
import cn.lihu.jh.module.ecg.controller.admin.room.vo.*;
|
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.dal.dataobject.room.RoomProfile;
|
import cn.lihu.jh.module.ecg.service.queue.QueueService;
|
import org.springframework.util.StringUtils;
|
import org.springframework.web.bind.annotation.*;
|
import org.springframework.validation.annotation.Validated;
|
import org.springframework.security.access.prepost.PreAuthorize;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.Parameter;
|
import io.swagger.v3.oas.annotations.Operation;
|
|
import cn.lihu.jh.module.ecg.service.room.RoomService;
|
import cn.lihu.jh.module.system.api.dept.DeptApi;
|
import cn.lihu.jh.module.system.api.dept.dto.DeptRespDTO;
|
import cn.lihu.jh.framework.common.pojo.PageParam;
|
import cn.lihu.jh.framework.common.pojo.PageResult;
|
import cn.lihu.jh.framework.common.pojo.CommonResult;
|
import cn.lihu.jh.framework.common.exception.ErrorCode;
|
import cn.lihu.jh.framework.common.exception.enums.GlobalErrorCodeConstants;
|
import cn.lihu.jh.framework.common.util.object.BeanUtils;
|
import cn.lihu.jh.framework.excel.core.util.ExcelUtils;
|
import cn.lihu.jh.framework.apilog.core.annotation.ApiAccessLog;
|
import cn.lihu.jh.framework.security.core.util.SecurityFrameworkUtils;
|
import lombok.extern.slf4j.Slf4j;
|
|
import static cn.lihu.jh.framework.common.pojo.CommonResult.success;
|
import static cn.lihu.jh.framework.common.exception.util.ServiceExceptionUtil.exception;
|
import static cn.lihu.jh.framework.apilog.core.enums.OperateTypeEnum.*;
|
|
|
@Tag(name = "管理后台 - 诊室和诊疗床")
|
@RestController
|
@RequestMapping("/clinic/room")
|
@Validated
|
@Slf4j
|
public class RoomController {
|
|
@Resource
|
private RoomService roomService;
|
|
@Resource
|
private QueueService queueService;
|
|
@Resource
|
private DeptApi deptApi;
|
|
@PostMapping("/create")
|
@Operation(summary = "创建诊室和诊疗床")
|
@PreAuthorize("@ss.hasPermission('clinic:room:create')")
|
public CommonResult<Integer> createRoom(@Valid @RequestBody RoomSaveReqVO createReqVO) {
|
return success(roomService.createRoom(createReqVO));
|
}
|
|
@PutMapping("/update")
|
@Operation(summary = "更新诊室和诊疗床")
|
@PreAuthorize("@ss.hasPermission('clinic:room:update')")
|
public CommonResult<Boolean> updateRoom(@Valid @RequestBody RoomSaveReqVO updateReqVO) {
|
roomService.updateRoom(updateReqVO);
|
return success(true);
|
}
|
|
@DeleteMapping("/delete")
|
@Operation(summary = "删除诊室和诊疗床")
|
@Parameter(name = "id", description = "编号", required = true)
|
@PreAuthorize("@ss.hasPermission('clinic:room:delete')")
|
public CommonResult<Boolean> deleteRoom(@RequestParam("id") Integer id) {
|
roomService.deleteRoom(id);
|
return success(true);
|
}
|
|
@GetMapping("/get")
|
@Operation(summary = "获得诊室和诊疗床")
|
@Parameter(name = "id", description = "编号", required = true, example = "1024")
|
@PreAuthorize("@ss.hasPermission('clinic:room:query')")
|
public CommonResult<RoomRespVO> getRoom(@RequestParam("id") Integer id) {
|
RoomDO room = roomService.getRoom(id);
|
return success(BeanUtils.toBean(room, RoomRespVO.class));
|
}
|
|
@GetMapping("/get-room-by-ip")
|
@Operation(summary = "获得诊室和诊疗床")
|
@PermitAll
|
public CommonResult<RoomProfileRespVO> getRoomByIP(HttpServletRequest request, @RequestParam(value = "roomId", required = false) Long roomId) {
|
String reqIp = ServletUtil.getClientIP(request);
|
RoomProfile roomProfile = roomService.getRoomByIP(reqIp, roomId);
|
return success(BeanUtils.toBean(roomProfile, RoomProfileRespVO.class));
|
}
|
|
/* ==================== 选择工位模式(需求:诊室工位选择模式 0/1/2/3) ==================== */
|
|
/**
|
* 按本机IP匹配工位(模式 1/3 用)。
|
*
|
* <p>只读接口:
|
* <ul>
|
* <li>把后端实际识别到的来访IP一起返回,便于排查"前置代理未透传 X-Forwarded-For";</li>
|
* <li>候选工位已按「本人已占用 → 空闲 → 被他人占用」排好序,前端取第一个可用的自动入座;</li>
|
* <li>本接口不改任何状态,入座仍走原有的 bedDoctorOn 流程。</li>
|
* </ul>
|
* 需要登录(未登录时返回空结果),但不做菜单权限校验:能登录就能查自己的工位。</p>
|
*/
|
@GetMapping("/get-ip-bed")
|
@Operation(summary = "按本机IP匹配工位(选择工位模式 1/3)")
|
public CommonResult<RoomSelectBedRespVO> getIpBed(HttpServletRequest request) {
|
// 规范化:去括号/区域号、统一小写、IPv4-mapped IPv6 还原成 IPv4(支持 IPv4 与 IPv6)
|
String clientIp = IpUtils.normalize(ServletUtil.getClientIP(request));
|
Long docId = SecurityFrameworkUtils.getLoginUserId();
|
RoomSelectBedRespVO resp = new RoomSelectBedRespVO();
|
resp.setClientIp(clientIp);
|
resp.setIpValid(IpUtils.isBindableIp(clientIp));
|
resp.setIpLoopback(IpUtils.isLoopback(clientIp));
|
resp.setIpv6(IpUtils.isIpv6Address(clientIp));
|
List<RoomSelectBedRespVO.RoomSelectBedItem> beds = roomService.matchBedsByIp(clientIp, docId);
|
resp.setBeds(beds);
|
resp.setIpBound(!beds.isEmpty());
|
// 有"本人已占用"或"空闲"的工位,就能自动入座
|
resp.setMatched(beds.stream()
|
.anyMatch(b -> Boolean.TRUE.equals(b.getMine()) || !Boolean.TRUE.equals(b.getOccupied())));
|
log.info("[getIpBed][按IP匹配工位] clientIp={} docId={} ipValid={} loopback={} 候选数={} 可自动入座={}",
|
clientIp, docId, resp.getIpValid(), resp.getIpLoopback(), beds.size(), resp.getMatched());
|
return success(resp);
|
}
|
|
/**
|
* 把本机IP写入指定工位(模式 2/3 用)。
|
*
|
* <p>只更新 {@code clinic_room.ip} 一个字段,不走 {@code clinic:room:update}(否则为了选工位还得给医生开诊室管理权限),
|
* 也不会碰 status/doc_id/check_types 等其它字段。
|
* 若该IP已绑在别的工位上,默认返回冲突工位让前端确认;确认后带 {@code force=true} 再调一次即覆盖。</p>
|
*/
|
@PostMapping("/bind-ip")
|
@Operation(summary = "把本机IP绑定到工位(选择工位模式 2/3)")
|
public CommonResult<Map<String, Object>> bindIp(HttpServletRequest request,
|
@RequestParam("id") Integer id,
|
@RequestParam(value = "force", required = false, defaultValue = "false") Boolean force) {
|
Map<String, Object> data = new HashMap<>();
|
String rawIp = ServletUtil.getClientIP(request);
|
String clientIp = IpUtils.normalize(rawIp);
|
data.put("clientIp", clientIp);
|
if (!IpUtils.isBindableIp(clientIp)) {
|
data.put("code", 400);
|
if (IpUtils.isLoopback(clientIp)) {
|
// 浏览器和服务在同一台机器上访问时必然出现:IPv4 是 127.0.0.1,IPv6 是 ::1
|
data.put("msg", "无法绑定工位:检测到当前访问来自本机回环地址(" + clientIp
|
+ ",即本机访问),请在医生实际使用的电脑上访问后再绑定");
|
} else if (IpUtils.isUnspecified(clientIp)) {
|
data.put("msg", "无法绑定工位:当前识别到的地址是未指定地址(" + clientIp + ")");
|
} else if (IpUtils.isLocalAddress(clientIp)) {
|
// 本次新增:现场最常见的"IP 不对"就是这种 —— 在服务器上(或远程桌面里)访问系统,
|
// 客户端本来就是服务器自己;若把它写进工位,该工位以后永远匹配不到医生电脑。
|
data.put("msg", "无法绑定工位:识别到的是**服务器自身地址**(" + clientIp
|
+ ")—— 说明当前是在服务器上访问,或经远程桌面/本机代理访问,这不是医生电脑的 IP。"
|
+ "请在医生实际使用的电脑上、用服务器的 IPv4 地址访问后再绑定"
|
+ "(可用「IP 诊断」查看后端到底把谁当成了客户端)");
|
log.warn("[bindIp][识别到服务器自身地址,拒绝绑定] clientIp={} bedId={} 服务器网卡IPv4={}",
|
clientIp, id, IpUtils.localIpv4Addresses());
|
} else if (IpUtils.isIpv6Address(clientIp)) {
|
// 本系统工位绑定统一采用 IPv4;客户端走 IPv6 时无法从代码"退化成"IPv4
|
data.put("msg", "无法绑定工位:当前访问使用的是 IPv6 地址(" + clientIp
|
+ "),本系统工位绑定仅采用 IPv4,请改用服务器的 IPv4 地址访问"
|
+ "(或让服务只监听 IPv4:JVM 参数 -Djava.net.preferIPv4Stack=true)");
|
} else {
|
data.put("msg", "无法绑定工位:未能识别到合法的 IPv4 来访地址(" + clientIp
|
+ "),请检查是否通过代理访问、以及代理是否透传客户端地址(X-Forwarded-For)");
|
}
|
log.warn("[bindIp][IP不可用,拒绝绑定] raw={} normalized={} ipv6={} bedId={}",
|
rawIp, clientIp, IpUtils.isIpv6Address(clientIp), id);
|
return success(data);
|
}
|
// 冲突检测:该IP已绑在别的工位 → 先让前端确认
|
List<RoomDO> conflicts = roomService.listIpBindings(clientIp, id);
|
if (!conflicts.isEmpty() && !Boolean.TRUE.equals(force)) {
|
RoomDO c = conflicts.get(0);
|
data.put("code", 409);
|
data.put("conflict", true);
|
data.put("conflictBedId", c.getId());
|
data.put("conflictRoomName", c.getRoomName());
|
data.put("conflictBedNo", c.getBedNo());
|
data.put("msg", "该IP已绑定「" + c.getRoomName() + " / " + c.getBedNo() + "」");
|
return success(data);
|
}
|
Integer rows = roomService.bindIpToBed(id, clientIp);
|
data.put("code", rows != null && rows > 0 ? 200 : 500);
|
data.put("msg", rows != null && rows > 0 ? "已绑定本机IP到该工位" : "绑定失败(工位不存在?)");
|
data.put("rows", rows);
|
log.info("[bindIp][绑定结果] raw={} clientIp={} bedId={} force={} rows={}", rawIp, clientIp, id, force, rows);
|
return success(data);
|
}
|
|
/* ==================== 自动入座 / 我的工位(本次需求) ==================== */
|
|
/**
|
* 一键自动入座(选择工位模式 1/3 用)。
|
*
|
* <p>为什么合并成一个接口:前端原来把「匹配 → 绑 IP → 入座」拆成三个请求自己拼,
|
* 任一步失败都只能回落"手选工位",现场表现就是"登录后没有自动入座"且看不出原因。
|
* 现在一次调用完成,并返回**识别到的 IP** 与**未入座原因**
|
* ({@code ip_invalid / nomatch / multi / occupied / error}),排查一眼可辨。
|
*
|
* <p>需要登录,但不做菜单权限校验:能登录就能坐自己的工位。
|
*/
|
@PostMapping("/auto-seat")
|
@Operation(summary = "按本机IP自动匹配工位并自动入座(选择工位模式 1/3)")
|
public CommonResult<RoomSeatRespVO> autoSeat(HttpServletRequest request) {
|
String clientIp = IpUtils.normalize(ServletUtil.getClientIP(request));
|
Long docId = SecurityFrameworkUtils.getLoginUserId();
|
String docName = SecurityFrameworkUtils.getLoginUserNickname();
|
RoomSeatRespVO resp = roomService.autoSeatByIp(clientIp, docId, docName);
|
log.info("[autoSeat][自动入座] clientIp={} docId={} seated={} reason={} 工位={}/{}",
|
clientIp, docId, resp.getSeated(), resp.getReason(), resp.getRoomName(), resp.getBedNo());
|
return success(resp);
|
}
|
|
/**
|
* 查询当前登录人**已入座**的工位(系统顶栏"当前工位"标签用)。
|
*
|
* <p>未入座时返回 {@code seated=false},前端据此显示灰色「未入座」并可点击去选工位。
|
*/
|
@GetMapping("/my-seat")
|
@Operation(summary = "查询当前登录人已入座的工位(系统顶栏展示)")
|
public CommonResult<RoomSeatRespVO> mySeat() {
|
Long docId = SecurityFrameworkUtils.getLoginUserId();
|
RoomSeatRespVO resp = roomService.getMySeat(docId);
|
log.info("[mySeat][查询我的工位] docId={} seated={} 工位={}/{}",
|
docId, resp.getSeated(), resp.getRoomName(), resp.getBedNo());
|
return success(resp);
|
}
|
|
/**
|
* 【本需求·一人多工位】查询当前登录人占据的**全部**工位(节假日值班多岗位)。
|
*
|
* <p>返回「医生在座 / 医生暂停」的工位列表,按 roomId + bedNo 升序;未占工位返回空数组。
|
* 前端用它渲染顶栏「我的工位(N 个)」并在工位之间切换。
|
*/
|
@GetMapping("/my-seats")
|
@Operation(summary = "查询当前登录人已入座的全部工位(一人多岗位)")
|
public CommonResult<List<RoomSeatRespVO>> mySeats() {
|
Long docId = SecurityFrameworkUtils.getLoginUserId();
|
List<RoomSeatRespVO> seats = roomService.getMySeats(docId);
|
log.info("[mySeats][查询我的全部工位] docId={} 数量={}", docId, null == seats ? 0 : seats.size());
|
return success(seats);
|
}
|
|
/**
|
* 【本需求·接班自助接管】把工位从"别人占用"改为"当前登录人占用"。
|
*
|
* <p>场景:上一班医生下班未点「离座」,下一班医生在同一台电脑/同一工位接班。
|
* 原占用人会被强制离座(其页面 5s 轮询会提示「请重新就坐」并转只读)。
|
* 权限沿用医生工作台已有的 {@code ecg:doctor:task},无需新增权限点。
|
*/
|
@PostMapping("/take-over-bed")
|
@Operation(summary = "接管工位(接班:把他人占用的工位转给我)")
|
@PreAuthorize("@ss.hasPermission('ecg:doctor:task')")
|
public CommonResult<Boolean> takeOverBed(@RequestParam("roomId") Long roomId,
|
@RequestParam("bedNo") String bedNo) {
|
roomService.takeOverBed(roomId, bedNo);
|
return success(true);
|
}
|
|
/**
|
* 客户端 IP 识别诊断(现场排查"工位绑定的 IP 不对"用)。
|
*
|
* <p>一次把所有相关线索摊开:各个代理头、TCP 直连地址、规范化结果、是否回环/IPv6、
|
* **是否是服务器自身地址**、以及服务器所有 IPv4 网卡地址。界面上的「IP 诊断」按钮就调它。
|
*
|
* <p>典型结论:
|
* <ul>
|
* <li>取到的是服务器自身地址 → 当前是在服务器上/远程桌面里访问,或代理把客户端地址覆盖成了服务器地址;</li>
|
* <li>取到 127.x / ::1 → 浏览器与服务在同一台机器;</li>
|
* <li>取到 IPv6 → 客户端走 IPv6(本系统工位绑定只用 IPv4);</li>
|
* <li>{@code X-Forwarded-For} 为空且直连地址=网关 → 前面的代理没透传客户端地址。</li>
|
* </ul>
|
*/
|
@GetMapping("/ip-debug")
|
@Operation(summary = "客户端IP识别诊断(排查工位绑定IP不对)")
|
public CommonResult<Map<String, Object>> ipDebug(HttpServletRequest request) {
|
Map<String, Object> data = new LinkedHashMap<>();
|
String xff = request.getHeader("X-Forwarded-For");
|
String realIp = request.getHeader("X-Real-IP");
|
String remoteAddr = request.getRemoteAddr();
|
String raw = ServletUtil.getClientIP(request);
|
String clientIp = IpUtils.normalize(raw);
|
|
data.put("访问Host", request.getHeader("Host"));
|
data.put("X-Forwarded-For", xff);
|
data.put("X-Real-IP", realIp);
|
data.put("Proxy-Client-IP", request.getHeader("Proxy-Client-IP"));
|
data.put("WL-Proxy-Client-IP", request.getHeader("WL-Proxy-Client-IP"));
|
data.put("HTTP_CLIENT_IP", request.getHeader("HTTP_CLIENT_IP"));
|
data.put("直连地址(remoteAddr)", remoteAddr);
|
data.put("取到的原始值", raw);
|
data.put("规范化后(工位按此匹配)", clientIp);
|
data.put("可用于工位绑定", IpUtils.isBindableIp(clientIp));
|
data.put("是回环(浏览器与服务同机)", IpUtils.isLoopback(clientIp));
|
data.put("是IPv6", IpUtils.isIpv6Address(clientIp));
|
data.put("是服务器自身地址", IpUtils.isLocalAddress(clientIp));
|
data.put("服务器网卡IPv4", IpUtils.localIpv4Addresses());
|
|
String verdict;
|
if (IpUtils.isLoopback(clientIp)) {
|
verdict = "客户端就是服务器本机(回环 " + clientIp + "):属【浏览器与服务在同一台机器】的正常现象,"
|
+ "不能作为工位标识,请在医生电脑上访问。";
|
} else if (IpUtils.isLocalAddress(clientIp)) {
|
verdict = "⚠ 识别到的是【服务器自身地址】(" + clientIp + "):当前是在服务器上/远程桌面里访问,"
|
+ "或前置代理把客户端地址覆盖成了服务器地址 → 不能作为工位标识。"
|
+ "请在医生电脑上、用服务器的 IPv4 地址访问;若确认是在医生电脑上访问,请检查代理是否透传 "
|
+ "X-Forwarded-For(nginx: proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;)。";
|
} else if (IpUtils.isIpv6Address(clientIp)) {
|
verdict = "客户端走的是 IPv6(" + clientIp + "):本系统工位绑定只采用 IPv4,"
|
+ "请让服务只监听 IPv4(JVM: -Djava.net.preferIPv4Stack=true)或改用 IPv4 地址访问。";
|
} else if (IpUtils.isBindableIp(clientIp)) {
|
verdict = "识别正常,可用于工位绑定:" + clientIp;
|
} else {
|
verdict = "未能识别到可用的 IPv4 客户端地址(原始值:" + raw + ",直连地址:" + remoteAddr
|
+ "):请检查代理是否透传客户端地址(X-Forwarded-For)。";
|
}
|
data.put("诊断结论", verdict);
|
|
log.info("[ipDebug][客户端IP诊断] Host={} XFF={} X-Real-IP={} remoteAddr={} → clientIp={} 服务器地址={}",
|
request.getHeader("Host"), xff, realIp, remoteAddr, clientIp, IpUtils.isLocalAddress(clientIp));
|
return success(data);
|
}
|
|
/**
|
* 工位状态诊断(排查"入座报状态异常 / 明明空闲却坐不上")。
|
*
|
* <p>把库里**原始行**摊开:`id / room_id / room_name / bed_no(含长度与 HEX,用于发现结尾空格、全角) /
|
* ip / status / doc_id / doc_name / op_type / deleted / update_time`,并给出一句结论。
|
*
|
* <p>用法:`GET /clinic/room/bed-state?id=3` 或 `?roomId=118&bedNo=C1`。
|
*/
|
@GetMapping("/bed-state")
|
@Operation(summary = "工位状态诊断(排查入座报状态异常)")
|
public CommonResult<Map<String, Object>> bedState(
|
@RequestParam(value = "id", required = false) Integer id,
|
@RequestParam(value = "roomId", required = false) Long roomId,
|
@RequestParam(value = "bedNo", required = false) String bedNo) {
|
return success(roomService.diagnoseBed(id, roomId, bedNo));
|
}
|
|
@GetMapping("/page")
|
@Operation(summary = "获得诊室和诊疗床分页")
|
@PreAuthorize("@ss.hasPermission('clinic:room:query')")
|
public CommonResult<PageResult<RoomRespVO>> getRoomPage(@Valid RoomPageReqVO pageReqVO) {
|
PageResult<RoomDO> pageResult = roomService.getRoomPage(pageReqVO);
|
return success(BeanUtils.toBean(pageResult, RoomRespVO.class));
|
}
|
|
@GetMapping("/export-excel")
|
@Operation(summary = "导出诊室和诊疗床 Excel")
|
@PreAuthorize("@ss.hasPermission('clinic:room:export')")
|
@ApiAccessLog(operateType = EXPORT)
|
public void exportRoomExcel(@Valid RoomPageReqVO pageReqVO,
|
HttpServletResponse response) throws IOException {
|
pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
|
List<RoomDO> list = roomService.getRoomPage(pageReqVO).getList();
|
// 导出 Excel
|
ExcelUtils.write(response, "诊室和诊疗床.xls", "数据", RoomRespVO.class,
|
BeanUtils.toBean(list, RoomRespVO.class));
|
}
|
|
@GetMapping(value = {"/list-all-simple", "/simple-list"})
|
@Operation(summary = "获取部门精简信息列表", description = "只包含被开启的部门,主要用于前端的下拉选项")
|
public CommonResult<List<RoomRespVO>> getSimpleDeptList() {
|
|
List<DeptRespDTO> deptRespDTOList = deptApi.getSimpleDeptList();
|
Optional<DeptRespDTO> ecgDept = deptRespDTOList.stream()
|
.filter(a -> a.getName().equals("ECG"))
|
.findAny();
|
|
List<DeptRespDTO> list2 = new ArrayList<DeptRespDTO>();
|
if (ecgDept.isPresent()){
|
System.out.println(ecgDept.get().getName());
|
|
list2 = deptRespDTOList.stream()
|
.filter(a -> Objects.equals(a.getParentId(), ecgDept.get().getId()))
|
.toList();
|
list2.forEach(System.out::println);
|
list2.size();
|
}
|
|
List<RoomRespVO> list3 = list2.stream().map(item -> {
|
RoomRespVO roomRespVO = new RoomRespVO();
|
roomRespVO.setRoomId( item.getId() );
|
roomRespVO.setRoomName( item.getName() );
|
return roomRespVO;
|
}).toList();
|
|
//return success(BeanUtils.toBean(list2, RoomRespVO.class));
|
return success(list3);
|
}
|
|
@GetMapping(value = {"/list-all-bed"})
|
@Operation(summary = "获取诊床列表", description = "主要用于前端的开诊管理")
|
public CommonResult<Map<String, List<RoomRespVO>>> getAllBedMap() {
|
|
RoomPageReqVO pageReqVO = new RoomPageReqVO();
|
pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
|
List<RoomDO> list = roomService.getRoomPage(pageReqVO).getList();
|
|
List<RoomRespVO> list2 = BeanUtils.toBean(list, RoomRespVO.class);
|
|
Map<String, List<RoomRespVO>> map = list2.stream().collect(Collectors.groupingBy(RoomRespVO::getRoomName));
|
return success(map);
|
}
|
|
@GetMapping(value = {"/list-simple-room"})
|
@Operation(summary = "获取门诊室列表", description = "主要用于前端的诊室选择")
|
public CommonResult<Map<Long, List<RoomRespVO>>> getSimpleRoomList() {
|
List<RoomDO> list = roomService.simpleRoomList();
|
List<RoomRespVO> list2 = BeanUtils.toBean(list, RoomRespVO.class);
|
|
// 显式标注「是否已被医生占用」:选工位页据此显示「已入座:张三」并**禁用入座按钮**,
|
// 不依赖 BeanUtils 是否把 docId 复制过来(双保险;本次现场就有"看不出来谁占了工位"的反馈)。
|
list2.forEach(roomRespVO -> roomRespVO.setOccupied(null != roomRespVO.getDocId()));
|
|
Map<Long, List<RoomRespVO>> map = list2.stream().collect(Collectors.groupingBy(RoomRespVO::getRoomId));
|
return success(map);
|
}
|
|
@GetMapping(value = {"/reset-room"})
|
@Operation(summary = "重置门诊室", description = "主要用于每天关闭工位")
|
public CommonResult<String> resetRoom() {
|
roomService.resetRoom(true);
|
return success("reset room ok");
|
}
|
|
@GetMapping(value = {"/monitor"})
|
@Operation(summary = "监控状态获取", description = "监控状态获取 监控用")
|
public CommonResult<MonitorInfoVO> getMonitorInfo() {
|
MonitorInfoVO monitorInfoVO = roomService.getMonitorInfo();
|
return success( monitorInfoVO );
|
}
|
|
@GetMapping("/start-biz")
|
@Operation(summary = "手动开诊")
|
@PreAuthorize("@ss.hasPermission('ecg:room:setting')")
|
public CommonResult<Integer> startBiz() {
|
queueService.startBiz();
|
return success(0);
|
}
|
|
@GetMapping("/close-biz")
|
@Operation(summary = "手动闭诊")
|
@PreAuthorize("@ss.hasPermission('ecg:room:setting')")
|
public CommonResult<Integer> stopBiz() {
|
queueService.closeBiz();
|
return success(0);
|
}
|
|
/**
|
* 【本次需求】管理员强制清空某个工位(不要求是占用者本人)。
|
* <p>
|
* 使用场景:医生下班忘记「离座」,或换人使用同一工位。
|
* <p>
|
* 与医生自己的「离座」接口({@code /ecg/doctor/bed-doctor-off})的区别:
|
* · 该接口只能清**自己**占用的工位(SQL 带 {@code doc_id = 当前用户});
|
* · 本接口面向管理端,可以清**任何人**占用的工位(仅要求该工位当前是「医生在岗 / 暂停」)。
|
* <p>
|
* 工位空闲时会返回「当前工位状态不允许该操作」——这是预期行为。
|
*/
|
@PostMapping("/force-clear-bed")
|
@Operation(summary = "管理员强制清空工位", description = "医生下班忘离座 / 换人使用同一工位时使用")
|
@PreAuthorize("@ss.hasPermission('clinic:room:update')")
|
public CommonResult<Boolean> forceClearBed(@RequestParam("roomId") Long roomId,
|
@RequestParam("bedNo") String bedNo) {
|
ErrorCode ec = queueService.startBedDoctorOffByAdmin(roomId, bedNo);
|
if (!GlobalErrorCodeConstants.SUCCESS.getCode().equals(ec.getCode())) {
|
log.warn("[forceClearBed] 清空工位失败 roomId={} bedNo={} code={} msg={}",
|
roomId, bedNo, ec.getCode(), ec.getMsg());
|
throw exception(ec);
|
}
|
return success(true);
|
}
|
|
}
|