Merge remote-tracking branch 'origin/develop' into develop
This commit is contained in:
@@ -69,6 +69,7 @@
|
||||
<groupId>org.apache.velocity</groupId>
|
||||
<artifactId>velocity-engine-core</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- rabbitMQ -->
|
||||
<!-- <dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
|
||||
@@ -1,16 +1,14 @@
|
||||
package com.openhis.web.doctorstation.appservice;
|
||||
|
||||
import com.core.common.core.domain.R;
|
||||
import com.openhis.template.domain.DoctorPhrase;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface IDoctorPhraseAppService {
|
||||
R<?> getDoctorPhraseList();
|
||||
List<DoctorPhrase> getDoctorPhraseList();
|
||||
List<DoctorPhrase> searchDoctorPhraseList(String phraseName, Integer phraseType);
|
||||
Boolean addDoctorPhrase(DoctorPhrase doctorPhrase);
|
||||
Boolean updateDoctorPhrase(DoctorPhrase doctorPhrase);
|
||||
Boolean deleteDoctorPhrase(Integer doctorPhraseId);
|
||||
|
||||
R<?> searchDoctorPhraseList(String phraseName ,Integer phraseType);
|
||||
|
||||
R<?> addDoctorPhrase(DoctorPhrase doctorPhrase);
|
||||
|
||||
R<?> updateDoctorPhrase(DoctorPhrase doctorPhrase);
|
||||
|
||||
R<?> deleteDoctorPhrase(Integer doctorPhraseId);
|
||||
}
|
||||
|
||||
@@ -2,30 +2,71 @@ package com.openhis.web.doctorstation.appservice.impl;
|
||||
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||
import com.core.common.core.domain.R;
|
||||
import com.core.common.utils.SecurityUtils;
|
||||
import com.openhis.common.enums.BindingType;
|
||||
import com.openhis.template.domain.DoctorPhrase;
|
||||
import com.openhis.template.service.IDoctorPhraseService;
|
||||
import com.openhis.web.doctorstation.appservice.IDoctorPhraseAppService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.List;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
public class DoctorPhraseAppServiceImpl implements IDoctorPhraseAppService {
|
||||
|
||||
@Resource
|
||||
private IDoctorPhraseService doctorPhraseService;
|
||||
|
||||
@Override
|
||||
public R<?> getDoctorPhraseList() {
|
||||
List<DoctorPhrase> list = doctorPhraseService.list();
|
||||
return R.ok(list);
|
||||
@Override
|
||||
public List<DoctorPhrase> getDoctorPhraseList() {
|
||||
Long orgId = SecurityUtils.getLoginUser().getOrgId();
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("orgId: {}", orgId);
|
||||
}
|
||||
LambdaQueryWrapper<DoctorPhrase> wrapper = new LambdaQueryWrapper<>();
|
||||
|
||||
// 1. 获取当前登录用户信息
|
||||
Long userId = SecurityUtils.getUserId();
|
||||
// 2. 权限判定:非管理员才需要过滤
|
||||
// 如果是超级管理员,默认可以看到所有
|
||||
if (!SecurityUtils.isAdmin(userId)) {
|
||||
// 3. 获取当前医生的科室编码
|
||||
String deptCode = "";
|
||||
if (orgId != null) {
|
||||
deptCode = String.valueOf(orgId);
|
||||
}
|
||||
// final 变量用于 Lambda 表达式
|
||||
String finalDeptCode = deptCode;
|
||||
// 4. 核心逻辑:三级数据共享
|
||||
wrapper.and(w -> w
|
||||
.eq(DoctorPhrase::getStaffId, userId.intValue()) // 1. 个人的:只看 staffId 是我的
|
||||
.or(o -> o.eq(DoctorPhrase::getPhraseType, BindingType.HOSPITAL.getValue())) // 2. 全院的:类型为 3
|
||||
.or(o -> o.eq(DoctorPhrase::getPhraseType, BindingType.ORGANIZATION.getValue()) // 3. 科室的:类型为 2 且 科室编码匹配
|
||||
.eq(DoctorPhrase::getDeptCode, finalDeptCode))
|
||||
);
|
||||
}
|
||||
// 5. 按排序号排序(可选优化,让常用语显示更整齐)
|
||||
List<DoctorPhrase> list = doctorPhraseService.list(wrapper);
|
||||
return list;
|
||||
}
|
||||
|
||||
@Override
|
||||
public R<?> searchDoctorPhraseList(String phraseName,Integer phraseType) {
|
||||
public List<DoctorPhrase> searchDoctorPhraseList(String phraseName,Integer phraseType) {
|
||||
// 1. 获取当前登录用户信息
|
||||
Long userId = SecurityUtils.getUserId();
|
||||
//2.获取到当前医生当前科室的id
|
||||
Long orgId = SecurityUtils.getLoginUser().getOrgId();
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Search phrase - orgId: {}, phraseName: {}, phraseType: {}", orgId, phraseName, phraseType);
|
||||
}
|
||||
String deptCode = "";
|
||||
if (orgId != null) {
|
||||
deptCode = String.valueOf(orgId);
|
||||
}
|
||||
String finalDeptCode = deptCode;
|
||||
LambdaQueryWrapper<DoctorPhrase> wrapper = new LambdaQueryWrapper<>();
|
||||
if (phraseName !=null && ObjectUtil.isNotEmpty(phraseName)) {
|
||||
wrapper.like(DoctorPhrase::getPhraseName, phraseName);
|
||||
@@ -33,50 +74,135 @@ public class DoctorPhraseAppServiceImpl implements IDoctorPhraseAppService {
|
||||
if (phraseType !=null && ObjectUtil.isNotEmpty(phraseType)) {
|
||||
wrapper.eq(DoctorPhrase::getPhraseType, phraseType);
|
||||
}
|
||||
Long currentUserId = SecurityUtils.getUserId();
|
||||
if (!SecurityUtils.isAdmin(currentUserId)) {
|
||||
// 建议统一使用 staffId 进行业务隔离
|
||||
wrapper.and(w -> w
|
||||
.eq(DoctorPhrase::getStaffId, userId.intValue()) // 1. 个人的:只看 staffId 是我的
|
||||
.or(o -> o.eq(DoctorPhrase::getPhraseType, BindingType.HOSPITAL.getValue())) // 2. 全院的:类型为 3
|
||||
.or(o -> o.eq(DoctorPhrase::getPhraseType, BindingType.ORGANIZATION.getValue()) // 3. 科室的:类型为 2 且 科室编码匹配
|
||||
.eq(DoctorPhrase::getDeptCode, finalDeptCode))
|
||||
);
|
||||
}
|
||||
//2.查询
|
||||
List<DoctorPhrase> list = doctorPhraseService.list(wrapper);
|
||||
return R.ok(list);
|
||||
return list;
|
||||
}
|
||||
|
||||
@Override
|
||||
public R<?> addDoctorPhrase(DoctorPhrase doctorPhrase) {
|
||||
//1.数据校验
|
||||
if(ObjectUtil.isEmpty(doctorPhrase)){
|
||||
return R.fail("医生常用语不能为空");
|
||||
public Boolean addDoctorPhrase(DoctorPhrase doctorPhrase) {
|
||||
// 1. 基础校验
|
||||
if (ObjectUtil.isEmpty(doctorPhrase) || ObjectUtil.isEmpty(doctorPhrase.getPhraseName())) {
|
||||
throw new IllegalArgumentException("新增失败:常用语名称不能为空");
|
||||
}
|
||||
//2.名称唯一性校验
|
||||
LambdaUpdateWrapper<DoctorPhrase> wrapper = new LambdaUpdateWrapper<>();
|
||||
wrapper.eq(DoctorPhrase::getPhraseName, doctorPhrase.getPhraseName());
|
||||
DoctorPhrase one = doctorPhraseService.getOne(wrapper);
|
||||
if(ObjectUtil.isNotEmpty(one)){
|
||||
return R.fail("该名称已经存在");
|
||||
|
||||
Long currentUserId = SecurityUtils.getUserId();
|
||||
|
||||
/*
|
||||
* 如果前端没传类型,必须给个默认值(比如 1-个人)
|
||||
* 否则存成 NULL,查询列表时会被过滤掉,导致"新增了却看不见"
|
||||
*/
|
||||
if (doctorPhrase.getPhraseType() == null) {
|
||||
doctorPhrase.setPhraseType(BindingType.PERSONAL.getValue());
|
||||
}
|
||||
//3.新增
|
||||
boolean save = doctorPhraseService.save(doctorPhrase);
|
||||
System.out.println(save);
|
||||
return R.ok(save);
|
||||
|
||||
// 2. 注入归属信息
|
||||
doctorPhrase.setStaffId(currentUserId.intValue());
|
||||
doctorPhrase.setCreatorId(currentUserId.intValue());
|
||||
|
||||
// 注入科室 (处理 null 情况)
|
||||
Long orgId = SecurityUtils.getLoginUser().getOrgId();
|
||||
if (orgId != null) {
|
||||
// 检查dept_code字段长度,避免数据库错误
|
||||
String deptCode = String.valueOf(orgId);
|
||||
if (deptCode.length() > 50) { // 假设字段长度为50,根据实际情况调整
|
||||
// 如果超过字段长度限制,可以考虑截断或抛出有意义的错误
|
||||
throw new IllegalArgumentException("科室ID过长,无法保存");
|
||||
}
|
||||
doctorPhrase.setDeptCode(deptCode);
|
||||
}
|
||||
|
||||
// =========== 【修复点 2】查重范围限制在"个人" ===========
|
||||
// 使用 QueryWrapper 而不是 UpdateWrapper
|
||||
LambdaQueryWrapper<DoctorPhrase> queryWrapper = new LambdaQueryWrapper<>();
|
||||
queryWrapper.eq(DoctorPhrase::getPhraseName, doctorPhrase.getPhraseName())
|
||||
.eq(DoctorPhrase::getStaffId, currentUserId.intValue()); // 重点:只查自己名下的!
|
||||
|
||||
if (doctorPhraseService.count(queryWrapper) > 0) {
|
||||
throw new IllegalArgumentException("新增失败:您已存在同名的常用语");
|
||||
}
|
||||
|
||||
return doctorPhraseService.save(doctorPhrase);
|
||||
}
|
||||
|
||||
@Override
|
||||
public R<?> updateDoctorPhrase(DoctorPhrase doctorPhrase) {
|
||||
//1.数据校验
|
||||
if(ObjectUtil.isEmpty(doctorPhrase)){
|
||||
return R.fail("医生常用语不能为空");
|
||||
public Boolean updateDoctorPhrase(DoctorPhrase doctorPhrase) {
|
||||
// 1. 基础校验
|
||||
if (ObjectUtil.isEmpty(doctorPhrase) || doctorPhrase.getId() == null) {
|
||||
throw new IllegalArgumentException("修改失败:ID不能为空");
|
||||
}
|
||||
//2.更新
|
||||
boolean updateById = doctorPhraseService.updateById(doctorPhrase);
|
||||
return R.ok(updateById);
|
||||
|
||||
// 2. 查旧数据
|
||||
DoctorPhrase original = doctorPhraseService.getById(doctorPhrase.getId());
|
||||
if (original == null) {
|
||||
throw new IllegalArgumentException("修改失败:该常用语不存在或已被删除");
|
||||
}
|
||||
|
||||
Long currentUserId = SecurityUtils.getUserId();
|
||||
|
||||
// 3. 【权限校验优化】
|
||||
if (!SecurityUtils.isAdmin(currentUserId)) {
|
||||
// 规则 A:严禁修改全院公共模板 (Type=3)
|
||||
// 这一步是关键!之前你的代码漏了这里,所以普通医生可能改动全院模板
|
||||
if (BindingType.HOSPITAL.getValue().equals(original.getPhraseType())) {
|
||||
throw new SecurityException("无权操作:全院公共常用语仅限管理员修改");
|
||||
}
|
||||
|
||||
// 规则 B:严禁修改他人的模板
|
||||
if (!original.getStaffId().equals(currentUserId.intValue())) {
|
||||
throw new SecurityException("无权操作:您只能修改自己创建的常用语");
|
||||
}
|
||||
}
|
||||
|
||||
// 4. 数据保护:防止篡改归属
|
||||
doctorPhrase.setStaffId(original.getStaffId());
|
||||
doctorPhrase.setCreatorId(original.getCreatorId());
|
||||
Long orgId = SecurityUtils.getLoginUser().getOrgId();
|
||||
if (orgId != null) {
|
||||
// 检查dept_code字段长度,避免数据库错误
|
||||
String deptCode = String.valueOf(orgId);
|
||||
if (deptCode.length() > 50) { // 假设字段长度为50,根据实际情况调整
|
||||
throw new IllegalArgumentException("科室ID过长,无法保存");
|
||||
}
|
||||
doctorPhrase.setDeptCode(deptCode);
|
||||
}
|
||||
|
||||
return doctorPhraseService.updateById(doctorPhrase);
|
||||
}
|
||||
|
||||
@Override
|
||||
public R<?> deleteDoctorPhrase(Integer doctorPhraseId) {
|
||||
//1.数据校验
|
||||
if(doctorPhraseId == null){
|
||||
return R.fail("ID不能为空");
|
||||
public Boolean deleteDoctorPhrase(Integer doctorPhraseId) {
|
||||
if (doctorPhraseId == null) {
|
||||
throw new IllegalArgumentException("删除失败:ID不能为空");
|
||||
}
|
||||
//2.删除
|
||||
boolean removeById = doctorPhraseService.removeById(doctorPhraseId);
|
||||
return R.ok(removeById);
|
||||
|
||||
DoctorPhrase original = doctorPhraseService.getById(doctorPhraseId);
|
||||
if (original == null) {
|
||||
throw new IllegalArgumentException("删除失败:数据不存在");
|
||||
}
|
||||
|
||||
Long currentUserId = SecurityUtils.getUserId();
|
||||
|
||||
// 权限校验
|
||||
if (!SecurityUtils.isAdmin(currentUserId)) {
|
||||
if (BindingType.HOSPITAL.getValue().equals(original.getPhraseType())) {
|
||||
throw new SecurityException("无权操作:全院公共常用语仅限管理员删除");
|
||||
}
|
||||
if (!original.getStaffId().equals(currentUserId.intValue())) {
|
||||
throw new SecurityException("无权操作:您只能删除自己创建的常用语");
|
||||
}
|
||||
}
|
||||
return doctorPhraseService.removeById(doctorPhraseId);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -111,6 +111,8 @@ public class DoctorStationMainAppServiceImpl implements IDoctorStationMainAppSer
|
||||
e.setAge(e.getBirthDate() != null ? AgeCalculatorUtil.getAge(e.getBirthDate()) : "");
|
||||
// 就诊状态
|
||||
e.setStatusEnum_enumText(EnumUtils.getInfoByValue(EncounterStatus.class, e.getStatusEnum()));
|
||||
// 初复诊
|
||||
e.setFirstEnum_enumText(EnumUtils.getInfoByValue(EncounterType.class, e.getFirstEnum()));
|
||||
});
|
||||
return patientInfo;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.openhis.web.doctorstation.controller;
|
||||
|
||||
import com.core.common.core.domain.R;
|
||||
import com.openhis.common.enums.BindingType;
|
||||
import com.openhis.template.domain.DoctorPhrase;
|
||||
import com.openhis.web.doctorstation.appservice.IDoctorPhraseAppService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@@ -23,7 +24,13 @@ public class DoctorPhraseController {
|
||||
*/
|
||||
@GetMapping("/list")
|
||||
public R<?> getDoctorPhraseList(){
|
||||
return R.ok(doctorPhraseAppService.getDoctorPhraseList());
|
||||
try {
|
||||
return R.ok(doctorPhraseAppService.getDoctorPhraseList());
|
||||
} catch (Exception e) {
|
||||
// 系统异常,使用error级别日志
|
||||
log.error("获取医生常用语列表系统异常", e);
|
||||
return R.fail("获取医生常用语列表失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -37,7 +44,13 @@ public class DoctorPhraseController {
|
||||
@RequestParam(required = false) Integer phraseType,
|
||||
@RequestParam(required = false) String phraseName
|
||||
){
|
||||
return R.ok(doctorPhraseAppService.searchDoctorPhraseList(phraseName,phraseType));
|
||||
try {
|
||||
return R.ok(doctorPhraseAppService.searchDoctorPhraseList(phraseName, phraseType));
|
||||
} catch (Exception e) {
|
||||
// 系统异常,使用error级别日志
|
||||
log.error("查询医生常用语系统异常", e);
|
||||
return R.fail("查询医生常用语失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -48,7 +61,22 @@ public class DoctorPhraseController {
|
||||
*/
|
||||
@PostMapping("/add")
|
||||
public R<?> addDoctorPhrase(@RequestBody DoctorPhrase doctorPhrase){
|
||||
return R.ok(doctorPhraseAppService.addDoctorPhrase(doctorPhrase));
|
||||
try {
|
||||
Boolean result = doctorPhraseAppService.addDoctorPhrase(doctorPhrase);
|
||||
if (result != null && result) {
|
||||
return R.ok("新增成功");
|
||||
} else {
|
||||
return R.fail("新增失败");
|
||||
}
|
||||
} catch (IllegalArgumentException e) {
|
||||
// 参数错误异常,使用warn级别日志
|
||||
log.warn("新增医生常用语参数错误: {}", e.getMessage());
|
||||
return R.fail(e.getMessage());
|
||||
} catch (Exception e) {
|
||||
// 系统异常,使用error级别日志
|
||||
log.error("新增医生常用语系统异常", e);
|
||||
return R.fail("新增失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -59,7 +87,26 @@ public class DoctorPhraseController {
|
||||
*/
|
||||
@PutMapping("/update")
|
||||
public R<?> updateDoctorPhrase(@RequestBody DoctorPhrase doctorPhrase){
|
||||
return R.ok(doctorPhraseAppService.updateDoctorPhrase(doctorPhrase));
|
||||
try {
|
||||
Boolean result = doctorPhraseAppService.updateDoctorPhrase(doctorPhrase);
|
||||
if (result != null && result) {
|
||||
return R.ok("更新成功");
|
||||
} else {
|
||||
return R.fail("更新失败");
|
||||
}
|
||||
} catch (IllegalArgumentException e) {
|
||||
// 参数错误异常,使用warn级别日志
|
||||
log.warn("更新医生常用语参数错误: {}", e.getMessage());
|
||||
return R.fail(e.getMessage());
|
||||
} catch (SecurityException e) {
|
||||
// 权限相关异常,使用warn级别日志
|
||||
log.warn("更新医生常用语权限异常: {}", e.getMessage());
|
||||
return R.fail(e.getMessage());
|
||||
} catch (Exception e) {
|
||||
// 系统异常,使用error级别日志
|
||||
log.error("更新医生常用语系统异常", e);
|
||||
return R.fail("更新失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -70,7 +117,26 @@ public class DoctorPhraseController {
|
||||
*/
|
||||
@DeleteMapping("/delete/{DoctorPhraseId}")
|
||||
public R<?> deleteDoctorPhrase(@PathVariable Integer DoctorPhraseId){
|
||||
return R.ok(doctorPhraseAppService.deleteDoctorPhrase(DoctorPhraseId));
|
||||
try {
|
||||
Boolean result = doctorPhraseAppService.deleteDoctorPhrase(DoctorPhraseId);
|
||||
if (result != null && result) {
|
||||
return R.ok("删除成功");
|
||||
} else {
|
||||
return R.fail("删除失败");
|
||||
}
|
||||
} catch (IllegalArgumentException e) {
|
||||
// 参数错误异常,使用warn级别日志
|
||||
log.warn("删除医生常用语参数错误: {}", e.getMessage());
|
||||
return R.fail(e.getMessage());
|
||||
} catch (SecurityException e) {
|
||||
// 权限相关异常,使用warn级别日志
|
||||
log.warn("删除医生常用语权限异常: {}", e.getMessage());
|
||||
return R.fail(e.getMessage());
|
||||
} catch (Exception e) {
|
||||
// 系统异常,使用error级别日志
|
||||
log.error("删除医生常用语系统异常", e);
|
||||
return R.fail("删除失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -133,4 +133,10 @@ public class PatientInfoDto {
|
||||
* 过号时间
|
||||
*/
|
||||
private Date missedTime;
|
||||
|
||||
/**
|
||||
* 初复诊标识
|
||||
*/
|
||||
private Integer firstEnum;
|
||||
private String firstEnum_enumText;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.openhis.web.triageandqueuemanage.appservice;
|
||||
|
||||
import com.core.common.core.domain.R;
|
||||
import com.openhis.web.triageandqueuemanage.dto.CallNumberDisplayResp;
|
||||
import com.openhis.web.triageandqueuemanage.dto.TriageQueueActionReq;
|
||||
import com.openhis.web.triageandqueuemanage.dto.TriageQueueAddReq;
|
||||
import com.openhis.web.triageandqueuemanage.dto.TriageQueueAdjustReq;
|
||||
@@ -22,6 +23,9 @@ public interface TriageQueueAppService {
|
||||
R<?> skip(TriageQueueActionReq req);
|
||||
/** 下一患者:当前叫号中 -> 完成,下一位等待 -> 叫号中 */
|
||||
R<?> next(TriageQueueActionReq req);
|
||||
|
||||
/** 叫号显示屏:获取当前叫号和等候队列信息 */
|
||||
CallNumberDisplayResp getDisplayData(Long organizationId, LocalDate date, Integer tenantId);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -10,19 +10,20 @@ import com.openhis.triageandqueuemanage.domain.TriageCandidateExclusion;
|
||||
import com.openhis.triageandqueuemanage.service.TriageQueueItemService;
|
||||
import com.openhis.triageandqueuemanage.service.TriageCandidateExclusionService;
|
||||
import com.openhis.web.triageandqueuemanage.appservice.TriageQueueAppService;
|
||||
import com.openhis.web.triageandqueuemanage.dto.CallNumberDisplayResp;
|
||||
import com.openhis.web.triageandqueuemanage.dto.TriageQueueActionReq;
|
||||
import com.openhis.web.triageandqueuemanage.dto.TriageQueueAddReq;
|
||||
import com.openhis.web.triageandqueuemanage.dto.TriageQueueAdjustReq;
|
||||
import com.openhis.web.triageandqueuemanage.dto.TriageQueueEncounterItem;
|
||||
import com.openhis.web.triageandqueuemanage.sse.CallNumberSseManager;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Service
|
||||
public class TriageQueueAppServiceImpl implements TriageQueueAppService {
|
||||
@@ -35,6 +36,9 @@ public class TriageQueueAppServiceImpl implements TriageQueueAppService {
|
||||
@Resource
|
||||
private TriageQueueItemService triageQueueItemService;
|
||||
|
||||
@Resource
|
||||
private CallNumberSseManager callNumberSseManager;
|
||||
|
||||
@Resource
|
||||
private TriageCandidateExclusionService triageCandidateExclusionService;
|
||||
|
||||
@@ -121,6 +125,8 @@ public class TriageQueueAppServiceImpl implements TriageQueueAppService {
|
||||
.setPatientName(it.getPatientName())
|
||||
.setHealthcareName(it.getHealthcareName())
|
||||
.setPractitionerName(it.getPractitionerName())
|
||||
.setPractitionerId(it.getPractitionerId()) // ✅ 新增字段(可选)
|
||||
.setRoomNo(it.getRoomNo()) // ✅ 新增字段(可选)
|
||||
.setStatus(STATUS_WAITING)
|
||||
.setQueueOrder(++maxOrder)
|
||||
.setDeleteFlag("0")
|
||||
@@ -238,6 +244,10 @@ public class TriageQueueAppServiceImpl implements TriageQueueAppService {
|
||||
if (STATUS_WAITING.equals(selected.getStatus())) {
|
||||
selected.setStatus(STATUS_CALLING).setUpdateTime(LocalDateTime.now());
|
||||
triageQueueItemService.updateById(selected);
|
||||
|
||||
// 叫号后推送 SSE 消息(实时通知显示屏刷新)
|
||||
pushDisplayUpdate(selected.getOrganizationId(), selected.getQueueDate(), selected.getTenantId());
|
||||
|
||||
return R.ok(true);
|
||||
} else if (STATUS_CALLING.equals(selected.getStatus())) {
|
||||
// 如果已经是"叫号中"状态,直接返回成功(不做任何操作)
|
||||
@@ -321,6 +331,10 @@ public class TriageQueueAppServiceImpl implements TriageQueueAppService {
|
||||
}
|
||||
|
||||
recalcOrders(actualOrgId, null);
|
||||
|
||||
// 完成后推送 SSE 消息(实时通知显示屏刷新)
|
||||
pushDisplayUpdate(actualOrgId, calling.getQueueDate(), tenantId);
|
||||
|
||||
return R.ok(true);
|
||||
}
|
||||
|
||||
@@ -413,6 +427,10 @@ public class TriageQueueAppServiceImpl implements TriageQueueAppService {
|
||||
triageQueueItemService.updateById(next);
|
||||
|
||||
recalcOrders(actualOrgId, null);
|
||||
|
||||
// ✅ 过号重排后推送 SSE 消息(实时通知显示屏刷新)
|
||||
pushDisplayUpdate(actualOrgId, calling.getQueueDate(), tenantId);
|
||||
|
||||
return R.ok(true);
|
||||
}
|
||||
|
||||
@@ -551,6 +569,179 @@ public class TriageQueueAppServiceImpl implements TriageQueueAppService {
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取叫号显示屏数据
|
||||
* @param organizationId 科室ID
|
||||
* @param date 日期
|
||||
* @param tenantId 租户ID
|
||||
* @return 显示屏数据
|
||||
*/
|
||||
@Override
|
||||
public CallNumberDisplayResp getDisplayData(Long organizationId, LocalDate date, Integer tenantId) {
|
||||
// 如果没有传入租户ID,尝试从登录用户获取,否则默认为1
|
||||
if (tenantId == null) {
|
||||
try {
|
||||
tenantId = SecurityUtils.getLoginUser().getTenantId();
|
||||
} catch (Exception e) {
|
||||
tenantId = 1; // 默认租户ID
|
||||
}
|
||||
}
|
||||
LocalDate qd = date != null ? date : LocalDate.now();
|
||||
|
||||
/**
|
||||
* 查询所有队列项(WAITING 和 CALLING 状态)某天的某个科室的某个状态
|
||||
*
|
||||
*/
|
||||
List<TriageQueueItem> allItems = triageQueueItemService.list(
|
||||
new LambdaQueryWrapper<TriageQueueItem>()
|
||||
.eq(TriageQueueItem::getQueueDate, qd)
|
||||
.eq(TriageQueueItem::getOrganizationId, organizationId)
|
||||
.eq(TriageQueueItem::getTenantId, tenantId)
|
||||
.in(TriageQueueItem::getStatus, STATUS_WAITING, STATUS_CALLING)
|
||||
.eq(TriageQueueItem::getDeleteFlag, "0")
|
||||
.orderByAsc(TriageQueueItem::getQueueOrder)
|
||||
);
|
||||
|
||||
CallNumberDisplayResp resp = new CallNumberDisplayResp();
|
||||
|
||||
// 1. 获取科室名称(从第一条数据中取)
|
||||
if (!allItems.isEmpty()) {
|
||||
resp.setDepartmentName(allItems.get(0).getOrganizationName() + " 叫号显示屏");
|
||||
} else {
|
||||
resp.setDepartmentName("叫号显示屏");
|
||||
}
|
||||
|
||||
// 2. 查找当前叫号中的患者(CALLING 状态)
|
||||
TriageQueueItem callingItem = allItems.stream()
|
||||
.filter(item -> STATUS_CALLING.equals(item.getStatus()))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
|
||||
if (callingItem != null) {
|
||||
CallNumberDisplayResp.CurrentCallInfo currentCall = new CallNumberDisplayResp.CurrentCallInfo();
|
||||
currentCall.setNumber(callingItem.getQueueOrder());
|
||||
currentCall.setName(maskPatientName(callingItem.getPatientName()));
|
||||
currentCall.setRoom(callingItem.getRoomNo() != null ? callingItem.getRoomNo() : "1号");
|
||||
currentCall.setDoctor(callingItem.getPractitionerName());
|
||||
resp.setCurrentCall(currentCall);
|
||||
} else {
|
||||
// 没有叫号中的患者,返回默认值
|
||||
CallNumberDisplayResp.CurrentCallInfo currentCall = new CallNumberDisplayResp.CurrentCallInfo();
|
||||
currentCall.setNumber(null);
|
||||
currentCall.setName("-");
|
||||
currentCall.setRoom("-");
|
||||
currentCall.setDoctor("-");
|
||||
resp.setCurrentCall(currentCall);
|
||||
}
|
||||
|
||||
// 3. 按医生分组(包括 CALLING 和 WAITING 状态)
|
||||
Map<Long, List<TriageQueueItem>> groupedByDoctor = allItems.stream()
|
||||
// 严格按医生分组:仅保留有 practitionerId 的记录
|
||||
.filter(item -> item.getPractitionerId() != null)
|
||||
.collect(Collectors.groupingBy(TriageQueueItem::getPractitionerId));
|
||||
|
||||
// 每个医生的等待队列
|
||||
List<CallNumberDisplayResp.DoctorGroup> waitingList = new ArrayList<>();
|
||||
int totalWaiting = 0;
|
||||
|
||||
for (Map.Entry<Long, List<TriageQueueItem>> entry : groupedByDoctor.entrySet()) {
|
||||
|
||||
List<TriageQueueItem> doctorItems = entry.getValue();
|
||||
String doctorName = doctorItems.get(0).getPractitionerName();
|
||||
if (doctorName == null || doctorName.isEmpty()) {
|
||||
doctorName = "未分配";
|
||||
}
|
||||
// 按排队顺序排序
|
||||
doctorItems.sort(Comparator.comparing(TriageQueueItem::getQueueOrder));
|
||||
|
||||
// 该医生 下边的患者列表 和 诊室号
|
||||
CallNumberDisplayResp.DoctorGroup doctorGroup = new CallNumberDisplayResp.DoctorGroup();
|
||||
doctorGroup.setDoctorName(doctorName);
|
||||
|
||||
// 获取诊室号(从该医生的任一患者中取)
|
||||
String roomNo = doctorItems.stream()
|
||||
.map(TriageQueueItem::getRoomNo)
|
||||
.filter(Objects::nonNull)
|
||||
.findFirst()
|
||||
.orElse("1号");
|
||||
doctorGroup.setRoomNo(roomNo);
|
||||
|
||||
// 转换患者列表
|
||||
List<CallNumberDisplayResp.PatientInfo> patients = new ArrayList<>();
|
||||
for (TriageQueueItem item : doctorItems) {
|
||||
CallNumberDisplayResp.PatientInfo patient = new CallNumberDisplayResp.PatientInfo();
|
||||
patient.setId(item.getId());
|
||||
patient.setName(maskPatientName(item.getPatientName()));
|
||||
patient.setStatus(item.getStatus());
|
||||
patient.setQueueOrder(item.getQueueOrder());
|
||||
patients.add(patient);
|
||||
|
||||
// 统计等待人数(不包括 CALLING 状态)
|
||||
if (STATUS_WAITING.equals(item.getStatus())) {
|
||||
totalWaiting++;
|
||||
}
|
||||
}
|
||||
|
||||
doctorGroup.setPatients(patients);
|
||||
waitingList.add(doctorGroup);
|
||||
}
|
||||
|
||||
|
||||
// 按医生名称排序
|
||||
waitingList.sort(Comparator.comparing(CallNumberDisplayResp.DoctorGroup::getDoctorName));
|
||||
|
||||
resp.setWaitingList(waitingList);
|
||||
resp.setWaitingCount(totalWaiting);
|
||||
|
||||
return resp;
|
||||
}
|
||||
|
||||
/**
|
||||
* 患者姓名脱敏处理
|
||||
* @param name 原始姓名
|
||||
* @return 脱敏后的姓名(如:张*三)
|
||||
*/
|
||||
private String maskPatientName(String name) {
|
||||
if (name == null || name.isEmpty()) {
|
||||
return "-";
|
||||
}
|
||||
if (name.length() == 1) {
|
||||
return name;
|
||||
}
|
||||
if (name.length() == 2) {
|
||||
return name.charAt(0) + "*";
|
||||
}
|
||||
// 3个字及以上:保留首尾,中间用*代替
|
||||
return name.charAt(0) + "*" + name.charAt(name.length() - 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* 推送显示屏更新消息到 SSE
|
||||
* @param organizationId 科室ID
|
||||
* @param queueDate 队列日期
|
||||
* @param tenantId 租户ID
|
||||
*/
|
||||
private void pushDisplayUpdate(Long organizationId, LocalDate queueDate, Integer tenantId) {
|
||||
try {
|
||||
// 获取最新的显示屏数据
|
||||
CallNumberDisplayResp displayData = getDisplayData(organizationId, queueDate, tenantId);
|
||||
|
||||
// 构造推送消息
|
||||
Map<String, Object> message = new HashMap<>();
|
||||
message.put("type", "update");
|
||||
message.put("action", "queue_changed");
|
||||
message.put("data", displayData);
|
||||
message.put("timestamp", System.currentTimeMillis());
|
||||
|
||||
// 推送到该科室的所有 SSE 连接
|
||||
callNumberSseManager.pushToOrganization(organizationId, message);
|
||||
|
||||
} catch (Exception e) {
|
||||
// SSE 推送失败不应该影响业务逻辑
|
||||
System.err.println("推送显示屏更新失败:" + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,16 +1,25 @@
|
||||
package com.openhis.web.triageandqueuemanage.controller;
|
||||
|
||||
import com.core.common.annotation.Anonymous;
|
||||
import com.core.common.core.domain.R;
|
||||
import com.core.common.utils.SecurityUtils;
|
||||
import com.openhis.web.triageandqueuemanage.appservice.TriageQueueAppService;
|
||||
import com.openhis.web.triageandqueuemanage.dto.CallNumberDisplayResp;
|
||||
import com.openhis.web.triageandqueuemanage.dto.TriageQueueActionReq;
|
||||
import com.openhis.web.triageandqueuemanage.dto.TriageQueueAddReq;
|
||||
import com.openhis.web.triageandqueuemanage.dto.TriageQueueAdjustReq;
|
||||
import com.openhis.web.triageandqueuemanage.sse.CallNumberSseManager;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.time.LocalDate;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@RestController
|
||||
@Slf4j
|
||||
@@ -19,6 +28,9 @@ public class TriageQueueController {
|
||||
|
||||
@Resource
|
||||
private TriageQueueAppService triageQueueAppService;
|
||||
|
||||
@Resource
|
||||
private CallNumberSseManager callNumberSseManager;
|
||||
|
||||
@GetMapping("/list")
|
||||
public R<?> list(@RequestParam(value = "organizationId", required = false) Long organizationId,
|
||||
@@ -65,6 +77,100 @@ public class TriageQueueController {
|
||||
public R<?> next(@RequestBody(required = false) TriageQueueActionReq req) {
|
||||
return triageQueueAppService.next(req);
|
||||
}
|
||||
|
||||
/**
|
||||
* 叫号显示屏:获取当前叫号和等候队列信息
|
||||
* @param organizationId 科室ID
|
||||
* @param date 日期(可选,默认今天)
|
||||
* @param tenantId 租户ID(可选,默认1)
|
||||
* @return 显示屏数据
|
||||
*/
|
||||
@Anonymous // 显示屏不需要登录
|
||||
@GetMapping("/display")
|
||||
public R<CallNumberDisplayResp> getDisplayData(
|
||||
@RequestParam(required = false) String organizationId,
|
||||
@RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate date,
|
||||
@RequestParam(required = false) Integer tenantId
|
||||
) {
|
||||
try {
|
||||
Long orgId = resolveOrganizationId(organizationId);
|
||||
if (orgId == null) {
|
||||
return R.fail("organizationId参数不合法或未获取到登录用户科室");
|
||||
}
|
||||
Integer actualTenantId = resolveTenantId(tenantId);
|
||||
CallNumberDisplayResp data = triageQueueAppService.getDisplayData(orgId, date, actualTenantId);
|
||||
return R.ok(data);
|
||||
} catch (Exception e) {
|
||||
log.error("获取显示屏数据失败", e);
|
||||
return R.fail("获取显示屏数据失败:" + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 叫号显示屏:SSE 实时推送
|
||||
*/
|
||||
@Anonymous
|
||||
@GetMapping(value = "/display/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
|
||||
public SseEmitter streamDisplayData(
|
||||
@RequestParam(required = false) String organizationId,
|
||||
@RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate date,
|
||||
@RequestParam(required = false) Integer tenantId
|
||||
) {
|
||||
// 1) 解析科室与租户(SSE 连接根据科室分组管理)
|
||||
Long orgId = resolveOrganizationId(organizationId);
|
||||
if (orgId == null) {
|
||||
SseEmitter emitter = new SseEmitter(0L);
|
||||
Map<String, Object> error = new HashMap<>();
|
||||
error.put("type", "error");
|
||||
error.put("message", "organizationId参数不合法或未获取到登录用户科室");
|
||||
callNumberSseManager.sendToEmitter(emitter, error);
|
||||
emitter.complete();
|
||||
return emitter;
|
||||
}
|
||||
Integer actualTenantId = resolveTenantId(tenantId);
|
||||
// 2) 创建并注册 SSE 连接
|
||||
SseEmitter emitter = callNumberSseManager.addEmitter(orgId);
|
||||
try {
|
||||
// 3) 连接建立后,先推送一次初始化数据
|
||||
CallNumberDisplayResp data = triageQueueAppService.getDisplayData(orgId, date, actualTenantId);
|
||||
Map<String, Object> init = new HashMap<>();
|
||||
init.put("type", "init");
|
||||
init.put("data", data);
|
||||
init.put("timestamp", System.currentTimeMillis());
|
||||
callNumberSseManager.sendToEmitter(emitter, init);
|
||||
} catch (Exception e) {
|
||||
log.error("SSE初始化数据发送失败", e);
|
||||
}
|
||||
return emitter;
|
||||
}
|
||||
|
||||
private Long resolveOrganizationId(String organizationId) {
|
||||
if (!StringUtils.hasText(organizationId)) {
|
||||
try {
|
||||
return SecurityUtils.getLoginUser().getOrgId();
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
try {
|
||||
return Long.parseLong(organizationId.trim());
|
||||
} catch (NumberFormatException e) {
|
||||
log.warn("非法organizationId: {}", organizationId);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private Integer resolveTenantId(Integer tenantId) {
|
||||
if (tenantId != null) {
|
||||
return tenantId;
|
||||
}
|
||||
try {
|
||||
Integer loginTenantId = SecurityUtils.getLoginUser().getTenantId();
|
||||
return loginTenantId != null ? loginTenantId : 1;
|
||||
} catch (Exception e) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
package com.openhis.web.triageandqueuemanage.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 叫号显示屏响应DTO
|
||||
*/
|
||||
@Data
|
||||
public class CallNumberDisplayResp {
|
||||
/** 科室名称 */
|
||||
private String departmentName;
|
||||
|
||||
/** 当前叫号信息 */
|
||||
private CurrentCallInfo currentCall;
|
||||
|
||||
/** 等候患者列表(按医生分组) */
|
||||
private List<DoctorGroup> waitingList;
|
||||
|
||||
/** 等待总人数 */
|
||||
private Integer waitingCount;
|
||||
|
||||
/**
|
||||
* 当前叫号信息
|
||||
*/
|
||||
@Data
|
||||
public static class CurrentCallInfo {
|
||||
/** 排队号 */
|
||||
private Integer number;
|
||||
/** 患者姓名(脱敏) */
|
||||
private String name;
|
||||
/** 诊室号 */
|
||||
private String room;
|
||||
/** 医生姓名 */
|
||||
private String doctor;
|
||||
}
|
||||
|
||||
/**
|
||||
* 医生分组信息
|
||||
*/
|
||||
@Data
|
||||
public static class DoctorGroup {
|
||||
/** 医生姓名 */
|
||||
private String doctorName;
|
||||
/** 诊室号 */
|
||||
private String roomNo;
|
||||
/** 该医生的患者列表 */
|
||||
private List<PatientInfo> patients;
|
||||
}
|
||||
|
||||
/**
|
||||
* 患者信息
|
||||
*/
|
||||
@Data
|
||||
public static class PatientInfo {
|
||||
/** 队列项ID */
|
||||
private Long id;
|
||||
/** 患者姓名(脱敏) */
|
||||
private String name;
|
||||
/** 状态:CALLING=就诊中,WAITING=等待 */
|
||||
private String status;
|
||||
/** 排队号 */
|
||||
private Integer queueOrder;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,12 @@ public class TriageQueueEncounterItem {
|
||||
private String patientName;
|
||||
private String healthcareName;
|
||||
private String practitionerName;
|
||||
|
||||
// ========== 新增字段(可选,用于叫号显示屏)==========
|
||||
/** 医生ID(可选) */
|
||||
private Long practitionerId;
|
||||
/** 诊室号(可选) */
|
||||
private String roomNo;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
package com.openhis.web.triageandqueuemanage.sse;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.CopyOnWriteArraySet;
|
||||
|
||||
/**
|
||||
* 叫号显示屏 SSE 管理器(服务端推送)
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class CallNumberSseManager {
|
||||
|
||||
private static final long NO_TIMEOUT = 0L; // 0 表示“永不超时”
|
||||
// 按科室分组保存连接(消化内科有3个屏、心内科有2个屏)
|
||||
// 很多屏幕同时连、同时断。故用 ConcurrentHashMap 存储,线程安全。内部分段锁,不阻塞其他科室的操作。
|
||||
private static final Map<Long, CopyOnWriteArraySet<SseEmitter>> emitterMap = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* 创建并注册一个 SSE 连接(按科室分组保存)
|
||||
*/
|
||||
public SseEmitter addEmitter(Long organizationId) {
|
||||
SseEmitter emitter = new SseEmitter(NO_TIMEOUT);
|
||||
emitterMap.computeIfAbsent(organizationId, k -> new CopyOnWriteArraySet<>()).add(emitter);
|
||||
|
||||
emitter.onCompletion(() -> removeEmitter(organizationId, emitter));
|
||||
emitter.onTimeout(() -> removeEmitter(organizationId, emitter));
|
||||
emitter.onError((ex) -> removeEmitter(organizationId, emitter));
|
||||
|
||||
log.info("SSE连接建立:科室ID={}, 当前该科室连接数={}",
|
||||
organizationId, emitterMap.get(organizationId).size());
|
||||
return emitter;
|
||||
}
|
||||
|
||||
/**
|
||||
* 向指定科室的所有 SSE 连接推送消息
|
||||
*/
|
||||
public void pushToOrganization(Long organizationId, Object message) {
|
||||
CopyOnWriteArraySet<SseEmitter> emitters = emitterMap.get(organizationId);
|
||||
if (emitters == null || emitters.isEmpty()) {
|
||||
log.debug("科室{}没有SSE连接,跳过推送", organizationId);
|
||||
return;
|
||||
}
|
||||
|
||||
for (SseEmitter emitter : emitters) {
|
||||
if (!sendToEmitter(emitter, message)) {
|
||||
removeEmitter(organizationId, emitter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 向单个 SSE 连接发送数据
|
||||
*/
|
||||
public boolean sendToEmitter(SseEmitter emitter, Object data) {
|
||||
try {
|
||||
emitter.send(SseEmitter.event().data(data));
|
||||
return true;
|
||||
} catch (IOException e) {
|
||||
log.warn("SSE推送失败:{}", e.getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 断开或异常时移除 SSE 连接
|
||||
*/
|
||||
private void removeEmitter(Long organizationId, SseEmitter emitter) {
|
||||
CopyOnWriteArraySet<SseEmitter> emitters = emitterMap.get(organizationId);
|
||||
if (emitters != null) {
|
||||
emitters.remove(emitter);
|
||||
if (emitters.isEmpty()) {
|
||||
emitterMap.remove(organizationId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,7 +25,9 @@
|
||||
T10.jz_practitioner_user_id,
|
||||
T10.bus_no,
|
||||
T10.identifier_no,
|
||||
T10.missed_time
|
||||
T10.missed_time,
|
||||
T10.first_enum,
|
||||
T10.first_enum_enumText
|
||||
from
|
||||
(
|
||||
SELECT T1.tenant_id AS tenant_id,
|
||||
@@ -52,7 +54,13 @@
|
||||
T1.organization_id AS org_id,
|
||||
T8.bus_no AS bus_no,
|
||||
T9.identifier_no AS identifier_no,
|
||||
T1.missed_time AS missed_time
|
||||
T1.missed_time AS missed_time,
|
||||
T1.first_enum AS first_enum,
|
||||
CASE
|
||||
WHEN T1.first_enum = 1 THEN '初诊'
|
||||
WHEN T1.first_enum = 2 THEN '复诊'
|
||||
ELSE NULL
|
||||
END AS first_enum_enumText
|
||||
FROM adm_encounter AS T1
|
||||
LEFT JOIN adm_organization AS T2 ON T1.organization_id = T2.ID AND T2.delete_flag = '0'
|
||||
LEFT JOIN adm_healthcare_service AS T3 ON T1.service_type_id = T3.ID AND T3.delete_flag = '0'
|
||||
|
||||
Reference in New Issue
Block a user