feat(分诊队列): 实现分诊队列核心功能与日志记录
新增分诊队列相关服务接口与实现,包括队列管理、叫号操作和日志记录 添加DivLogService和CallRecordService用于记录分诊操作和叫号历史 在CurrentDayEncounterDto和TriageQueueItem中增加seqNo字段用于显示预约序号 实现分诊操作日志记录功能,包括添加队列、移除队列、叫号、完成等操作 新增CallType枚举定义叫号类型,并实现叫号记录功能 优化队列状态映射逻辑,支持更多状态类型显示
This commit is contained in:
@@ -40,7 +40,9 @@ import com.openhis.web.chargemanage.dto.PatientMetadata;
|
||||
import com.openhis.web.chargemanage.dto.PractitionerMetadata;
|
||||
import com.openhis.web.chargemanage.dto.ReprintRegistrationDto;
|
||||
import com.openhis.web.chargemanage.mapper.OutpatientRegistrationAppMapper;
|
||||
import com.openhis.triageandqueuemanage.domain.DivLog;
|
||||
import com.openhis.triageandqueuemanage.domain.TriageCandidateExclusion;
|
||||
import com.openhis.triageandqueuemanage.service.DivLogService;
|
||||
import com.openhis.triageandqueuemanage.service.TriageCandidateExclusionService;
|
||||
import com.openhis.web.paymentmanage.appservice.IPaymentRecService;
|
||||
import com.openhis.web.paymentmanage.dto.CancelPaymentDto;
|
||||
@@ -111,6 +113,9 @@ public class OutpatientRegistrationAppServiceImpl implements IOutpatientRegistra
|
||||
@Resource
|
||||
com.openhis.triageandqueuemanage.service.TriageQueueItemService triageQueueItemService;
|
||||
|
||||
@Resource
|
||||
DivLogService divLogService;
|
||||
|
||||
@Resource
|
||||
ScheduleSlotMapper scheduleSlotMapper;
|
||||
|
||||
@@ -807,6 +812,23 @@ public class OutpatientRegistrationAppServiceImpl implements IOutpatientRegistra
|
||||
queueItem.setDeleteFlag("1");
|
||||
queueItem.setUpdateTime(LocalDateTime.now());
|
||||
triageQueueItemService.updateById(queueItem);
|
||||
|
||||
// 写入分诊操作日志:诊前退号
|
||||
try {
|
||||
LoginUser loginUser = SecurityUtils.getLoginUser();
|
||||
DivLog divLog = new DivLog()
|
||||
.setPoolId(queueItem.getPoolId())
|
||||
.setSlotId(queueItem.getSlotId())
|
||||
.setOpUserId(loginUser != null ? loginUser.getUserId() : null)
|
||||
.setAction("REFUND")
|
||||
.setCreateTime(LocalDateTime.now())
|
||||
.setUpdateAt(LocalDateTime.now())
|
||||
.setCreatedAt(LocalDateTime.now());
|
||||
divLogService.save(divLog);
|
||||
} catch (Exception e) {
|
||||
log.error("写入分诊退号日志失败,encounterId={}", encounterId, e);
|
||||
}
|
||||
|
||||
log.info("退号成功,已移除分诊队列记录,encounterId={}, queueItemId={}", encounterId, queueItem.getId());
|
||||
}
|
||||
|
||||
|
||||
@@ -170,4 +170,9 @@ public class CurrentDayEncounterDto {
|
||||
*/
|
||||
private String clinicRoom;
|
||||
|
||||
/**
|
||||
* 预约序号(来自 adm_schedule_slot.seq_no)
|
||||
*/
|
||||
private Integer seqNo;
|
||||
|
||||
}
|
||||
|
||||
@@ -5,22 +5,32 @@ import cn.hutool.core.util.ObjectUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.core.common.core.domain.R;
|
||||
import com.core.common.utils.SecurityUtils;
|
||||
import com.openhis.triageandqueuemanage.domain.CallRecord;
|
||||
import com.openhis.triageandqueuemanage.domain.DivLog;
|
||||
import com.openhis.triageandqueuemanage.domain.TriageCandidateExclusion;
|
||||
import com.openhis.triageandqueuemanage.domain.TriageQueueItem;
|
||||
import com.openhis.triageandqueuemanage.service.CallRecordService;
|
||||
import com.openhis.triageandqueuemanage.service.DivLogService;
|
||||
import com.openhis.triageandqueuemanage.service.TriageCandidateExclusionService;
|
||||
import com.openhis.triageandqueuemanage.service.TriageQueueItemService;
|
||||
import com.openhis.appointmentmanage.domain.ScheduleSlot;
|
||||
import com.openhis.appointmentmanage.mapper.ScheduleSlotMapper;
|
||||
import com.openhis.common.enums.CallType;
|
||||
import com.openhis.web.triageandqueuemanage.appservice.TriageQueueAppService;
|
||||
import com.openhis.web.triageandqueuemanage.dto.*;
|
||||
import com.openhis.web.triageandqueuemanage.sse.CallNumberSseManager;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import com.core.common.core.domain.model.LoginUser;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
public class TriageQueueAppServiceImpl implements TriageQueueAppService {
|
||||
|
||||
@@ -39,13 +49,22 @@ public class TriageQueueAppServiceImpl implements TriageQueueAppService {
|
||||
|
||||
@Resource
|
||||
private TriageQueueItemService triageQueueItemService;
|
||||
|
||||
|
||||
@Resource
|
||||
private CallNumberSseManager callNumberSseManager;
|
||||
|
||||
|
||||
@Resource
|
||||
private TriageCandidateExclusionService triageCandidateExclusionService;
|
||||
|
||||
@Resource
|
||||
private DivLogService divLogService;
|
||||
|
||||
@Resource
|
||||
private CallRecordService callRecordService;
|
||||
|
||||
@Resource
|
||||
private ScheduleSlotMapper scheduleSlotMapper;
|
||||
|
||||
@Override
|
||||
public R<?> list(Long organizationId, LocalDate date) {
|
||||
Integer tenantId = SecurityUtils.getLoginUser().getTenantId();
|
||||
@@ -65,31 +84,33 @@ public class TriageQueueAppServiceImpl implements TriageQueueAppService {
|
||||
}
|
||||
|
||||
List<TriageQueueItem> list = triageQueueItemService.list(wrapper);
|
||||
|
||||
// 通过 slotId 查询 seqNo 并填充到返回结果中(用于选呼显示预约号)
|
||||
if (list != null && !list.isEmpty()) {
|
||||
Map<Long, Integer> seqNoMap = buildSlotSeqNoMap(list);
|
||||
list.forEach(item -> {
|
||||
if (item.getSlotId() != null && seqNoMap.containsKey(item.getSlotId())) {
|
||||
item.setSeqNo(seqNoMap.get(item.getSlotId()));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 双重保险:再次过滤掉 COMPLETED 状态的患者(防止数据库中有异常数据)
|
||||
if (list != null && !list.isEmpty()) {
|
||||
int beforeSize = list.size();
|
||||
list = list.stream()
|
||||
.filter(item -> !STATUS_COMPLETED.equals(item.getStatus()))
|
||||
.collect(java.util.stream.Collectors.toList());
|
||||
if (beforeSize != list.size()) {
|
||||
System.out.println(">>> [TriageQueue] list() 警告:过滤掉了 " + (beforeSize - list.size()) + " 条 COMPLETED 状态的记录");
|
||||
}
|
||||
}
|
||||
|
||||
// 调试日志:检查状态值
|
||||
if (list != null && !list.isEmpty()) {
|
||||
System.out.println(">>> [TriageQueue] list() 返回 " + list.size() + " 条记录(已排除 COMPLETED)");
|
||||
for (int i = 0; i < Math.min(3, list.size()); i++) {
|
||||
TriageQueueItem item = list.get(i);
|
||||
System.out.println(" [" + i + "] patientName=" + item.getPatientName()
|
||||
+ ", status=" + item.getStatus()
|
||||
+ ", organizationId=" + item.getOrganizationId());
|
||||
}
|
||||
}
|
||||
return R.ok(list);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将候选池患者的挂号移入队列操作
|
||||
* 并同时写入移入队列操作日志
|
||||
*
|
||||
* @param req
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public R<?> add(TriageQueueAddReq req) {
|
||||
@@ -133,6 +154,7 @@ public class TriageQueueAppServiceImpl implements TriageQueueAppService {
|
||||
.setRoomNo(it.getRoomNo()) // ✅ 新增字段(可选)
|
||||
.setPoolId(it.getPoolId()) // ✅ 号源池ID(用于div_log审计)
|
||||
.setSlotId(it.getSlotId()) // ✅ 号源槽位ID(用于div_log审计)
|
||||
.setSeqNo(it.getSeqNo()) // ✅ 预约序号(用于叫号显示)
|
||||
.setStatus(STATUS_WAITING)
|
||||
.setQueueOrder(++maxOrder)
|
||||
.setDeleteFlag("0")
|
||||
@@ -140,7 +162,8 @@ public class TriageQueueAppServiceImpl implements TriageQueueAppService {
|
||||
.setUpdateTime(LocalDateTime.now());
|
||||
|
||||
triageQueueItemService.save(qi);
|
||||
|
||||
// 写入分诊日志
|
||||
writeDivLog(it.getPoolId(), it.getSlotId(), "ADD_QUEUE");
|
||||
// 记录到候选池排除列表(避免刷新后重新出现在候选池)
|
||||
TriageCandidateExclusion exclusion = triageCandidateExclusionService.getOne(
|
||||
new LambdaQueryWrapper<TriageCandidateExclusion>()
|
||||
@@ -171,17 +194,26 @@ public class TriageQueueAppServiceImpl implements TriageQueueAppService {
|
||||
return R.ok(added);
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除队列操作并同时写入移除操作日志
|
||||
*
|
||||
* @param id
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public R<?> remove(Long id) {
|
||||
if (id == null) return R.fail("id 不能为空");
|
||||
TriageQueueItem item = triageQueueItemService.getById(id);
|
||||
if (item == null) return R.fail("队列项不存在");
|
||||
|
||||
if (item.getStatus() != null && item.getStatus() != 0) {
|
||||
return R.fail("仅等待状态的患者可移出队列,当前状态码:" + item.getStatus());
|
||||
}
|
||||
// 逻辑删除队列项
|
||||
item.setDeleteFlag("1").setUpdateTime(LocalDateTime.now());
|
||||
triageQueueItemService.updateById(item);
|
||||
|
||||
// 写入分诊日志
|
||||
writeDivLog(item.getPoolId(), item.getSlotId(), "REMOVE_QUEUE");
|
||||
// 从排除列表中删除记录,使患者重新出现在候选池中
|
||||
Integer tenantId = item.getTenantId();
|
||||
LocalDate exclusionDate = item.getQueueDate();
|
||||
@@ -239,6 +271,12 @@ public class TriageQueueAppServiceImpl implements TriageQueueAppService {
|
||||
return R.ok(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 智能分诊选呼功能,在进行选呼操作时同时写入叫号记录和选呼操作日志
|
||||
*
|
||||
* @param req
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public R<?> call(TriageQueueActionReq req) {
|
||||
@@ -253,7 +291,9 @@ public class TriageQueueAppServiceImpl implements TriageQueueAppService {
|
||||
|
||||
// 叫号后推送 SSE 消息(实时通知显示屏刷新)
|
||||
pushDisplayUpdate(selected.getOrganizationId(), selected.getQueueDate(), selected.getTenantId());
|
||||
|
||||
// 写入分诊日志和叫号记录
|
||||
writeDivLog(selected.getPoolId(), selected.getSlotId(), "CALL");
|
||||
writeCallRecord(selected.getId(), selected.getPractitionerId(), CallType.CALL, selected.getRoomNo());
|
||||
return R.ok(true);
|
||||
} else if (STATUS_CALLING.equals(selected.getStatus())) {
|
||||
// 如果已经是"叫号中"状态,直接返回成功(不做任何操作)
|
||||
@@ -264,6 +304,13 @@ public class TriageQueueAppServiceImpl implements TriageQueueAppService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 智能分诊完成操作
|
||||
* 在进行完成操作后同时写入叫号记录和完成操作日志
|
||||
*
|
||||
* @param req
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public R<?> complete(TriageQueueActionReq req) {
|
||||
@@ -283,7 +330,6 @@ public class TriageQueueAppServiceImpl implements TriageQueueAppService {
|
||||
} else {
|
||||
// 如果没有提供 id,通过查询条件查找(兼容旧逻辑)
|
||||
Long orgId = req != null && req.getOrganizationId() != null ? req.getOrganizationId() : null;
|
||||
System.out.println(">>> [TriageQueue] complete() 开始执行(不限制日期,通过查询条件), tenantId=" + tenantId + ", orgId=" + orgId);
|
||||
|
||||
LambdaQueryWrapper<TriageQueueItem> callingWrapper = new LambdaQueryWrapper<TriageQueueItem>()
|
||||
.eq(TriageQueueItem::getTenantId, tenantId)
|
||||
@@ -297,8 +343,6 @@ public class TriageQueueAppServiceImpl implements TriageQueueAppService {
|
||||
}
|
||||
|
||||
calling = triageQueueItemService.getOne(callingWrapper, false);
|
||||
|
||||
System.out.println(">>> [TriageQueue] complete() 查询叫号中患者(不限制日期): orgId=" + orgId + ", 结果=" + (calling != null ? calling.getPatientName() + "(status=" + calling.getStatus() + ", queueDate=" + calling.getQueueDate() + ")" : "null"));
|
||||
|
||||
if (calling == null) {
|
||||
return R.fail("当前没有叫号中的患者");
|
||||
@@ -328,8 +372,6 @@ public class TriageQueueAppServiceImpl implements TriageQueueAppService {
|
||||
}
|
||||
|
||||
TriageQueueItem next = triageQueueItemService.getOne(nextWrapper, false);
|
||||
|
||||
System.out.println(">>> [TriageQueue] complete() 查询等待患者(不限制日期): actualOrgId=" + actualOrgId + ", 结果=" + (next != null ? next.getPatientName() + "(status=" + next.getStatus() + ")" : "null"));
|
||||
|
||||
if (next != null) {
|
||||
next.setStatus(STATUS_CALLING).setUpdateTime(LocalDateTime.now());
|
||||
@@ -340,16 +382,45 @@ public class TriageQueueAppServiceImpl implements TriageQueueAppService {
|
||||
|
||||
// 完成后推送 SSE 消息(实时通知显示屏刷新)
|
||||
pushDisplayUpdate(actualOrgId, calling.getQueueDate(), tenantId);
|
||||
|
||||
// 写入分诊日志和叫号记录
|
||||
writeDivLog(calling.getPoolId(), calling.getSlotId(), "COMPLETE");
|
||||
writeCallRecord(calling.getId(), calling.getPractitionerId(), CallType.COMPLETE, calling.getRoomNo());
|
||||
return R.ok(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 智能队列重排序功能操作单元
|
||||
* 在进行队列重排序时同时在div_log表中写入重排序日志和叫号记录
|
||||
* @param req
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public R<?> requeue(TriageQueueActionReq req) {
|
||||
return doRequeue(req, "REQUEUE");
|
||||
}
|
||||
|
||||
/**
|
||||
* 智能分诊跳过功能操作单元内部包含将队列正在叫号状态的号进行跳过
|
||||
* 同时将跳过操作日志写入div_log表中,以及写入叫号记录表。
|
||||
*
|
||||
* @param req
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public R<?> skip(TriageQueueActionReq req) {
|
||||
// 当前业务"跳过"按"过号重排"处理:叫号中 -> 跳过并移到末尾,自动推进下一等待
|
||||
return doRequeue(req, "SKIP");
|
||||
}
|
||||
|
||||
/**
|
||||
* 过号重排/跳过的核心逻辑
|
||||
* @param action 日志动作:REQUEUE 或 SKIP
|
||||
*/
|
||||
private R<?> doRequeue(TriageQueueActionReq req, String action) {
|
||||
Integer tenantId = SecurityUtils.getLoginUser().getTenantId();
|
||||
TriageQueueItem calling = null;
|
||||
|
||||
|
||||
if (req != null && req.getId() != null) {
|
||||
calling = triageQueueItemService.getById(req.getId());
|
||||
@@ -358,7 +429,7 @@ public class TriageQueueAppServiceImpl implements TriageQueueAppService {
|
||||
}
|
||||
// 验证状态
|
||||
if (!STATUS_CALLING.equals(calling.getStatus())) {
|
||||
return R.fail("只能对\"叫号中\"状态的患者进行过号重排,当前患者状态为:" + calling.getStatus());
|
||||
return R.fail("只能对\"叫号中\"状态的患者进行" + ("SKIP".equals(action) ? "跳过" : "过号重排") + ",当前患者状态为:" + calling.getStatus());
|
||||
}
|
||||
} else {
|
||||
// 如果没有提供 id,通过查询条件查找(兼容旧逻辑)
|
||||
@@ -379,12 +450,11 @@ public class TriageQueueAppServiceImpl implements TriageQueueAppService {
|
||||
|
||||
if (calling == null) return R.fail("当前没有叫号中的患者");
|
||||
}
|
||||
|
||||
|
||||
// 使用实际找到的科室ID
|
||||
Long actualOrgId = calling.getOrganizationId();
|
||||
|
||||
// 关键改进:在执行"跳过"操作之前,先检查是否有等待中的患者(判断队列状态)
|
||||
// 如果没有等待中的患者,就不应该执行"过号重排"操作
|
||||
// 关键改进:在执行跳过/重排操作之前,先检查是否有等待中的患者(判断队列状态)
|
||||
LambdaQueryWrapper<TriageQueueItem> nextWrapper = new LambdaQueryWrapper<TriageQueueItem>()
|
||||
.eq(TriageQueueItem::getTenantId, tenantId)
|
||||
.eq(TriageQueueItem::getDeleteFlag, "0")
|
||||
@@ -393,22 +463,14 @@ public class TriageQueueAppServiceImpl implements TriageQueueAppService {
|
||||
.eq(TriageQueueItem::getStatus, STATUS_SKIPPED))
|
||||
.orderByAsc(TriageQueueItem::getQueueOrder)
|
||||
.last("LIMIT 1");
|
||||
|
||||
// 如果指定了科室ID,则按科室过滤;否则查询所有科室(全科模式)
|
||||
|
||||
if (actualOrgId != null) {
|
||||
nextWrapper.eq(TriageQueueItem::getOrganizationId, actualOrgId);
|
||||
}
|
||||
|
||||
|
||||
TriageQueueItem next = triageQueueItemService.getOne(nextWrapper, false);
|
||||
|
||||
// 调试日志:检查查询结果
|
||||
System.out.println(">>> [TriageQueue] requeue() 查询等待中的患者:");
|
||||
System.out.println(">>> - 科室ID: " + actualOrgId);
|
||||
System.out.println(">>> - 找到的等待患者: " + (next != null ? next.getPatientName() + " (状态: " + next.getStatus() + ")" : "null"));
|
||||
|
||||
// 如果找不到等待中的患者,直接返回失败(不执行跳过操作)
|
||||
|
||||
if (next == null) {
|
||||
System.out.println(">>> [TriageQueue] requeue() 失败:没有等待中的患者");
|
||||
return R.fail("当前没有等待中的患者");
|
||||
}
|
||||
|
||||
@@ -433,27 +495,20 @@ public class TriageQueueAppServiceImpl implements TriageQueueAppService {
|
||||
triageQueueItemService.updateById(next);
|
||||
|
||||
recalcOrders(actualOrgId, null);
|
||||
|
||||
// ✅ 过号重排后推送 SSE 消息(实时通知显示屏刷新)
|
||||
// 推送 SSE 消息
|
||||
pushDisplayUpdate(actualOrgId, calling.getQueueDate(), tenantId);
|
||||
|
||||
// 写入分诊日志和叫号记录
|
||||
writeDivLog(calling.getPoolId(), calling.getSlotId(), action);
|
||||
writeCallRecord(calling.getId(), calling.getPractitionerId(),
|
||||
"SKIP".equals(action) ? CallType.SKIP : CallType.REQUEUE, calling.getRoomNo());
|
||||
return R.ok(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public R<?> skip(TriageQueueActionReq req) {
|
||||
// 当前业务“跳过”按“过号重排”处理:叫号中 -> 跳过并移到末尾,自动推进下一等待
|
||||
return requeue(req);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public R<?> next(TriageQueueActionReq req) {
|
||||
Integer tenantId = SecurityUtils.getLoginUser().getTenantId();
|
||||
TriageQueueItem calling = null;
|
||||
|
||||
System.out.println(">>> [TriageQueue] next() 开始执行(不限制日期), tenantId=" + tenantId);
|
||||
|
||||
// 关键改进:如果提供了 id,优先使用 id 直接查找(像 call 方法一样)
|
||||
if (req != null && req.getId() != null) {
|
||||
@@ -513,14 +568,6 @@ public class TriageQueueAppServiceImpl implements TriageQueueAppService {
|
||||
}
|
||||
|
||||
TriageQueueItem next = triageQueueItemService.getOne(nextWrapper, false);
|
||||
|
||||
// 调试日志:打印查询条件和结果
|
||||
System.out.println(">>> [TriageQueue] next() 查询条件(不限制日期): tenantId=" + tenantId
|
||||
+ ", actualOrgId=" + actualOrgId
|
||||
+ ", deleteFlag=0"
|
||||
+ ", status IN (WAITING, SKIPPED)");
|
||||
System.out.println(">>> [TriageQueue] next() 查询结果: calling=" + (calling != null ? calling.getPatientName() + "(status=" + calling.getStatus() + ", queueDate=" + calling.getQueueDate() + ")" : "null")
|
||||
+ ", next=" + (next != null ? next.getPatientName() + "(status=" + next.getStatus() + ", queueDate=" + next.getQueueDate() + ")" : "null"));
|
||||
|
||||
if (next == null) {
|
||||
if (actualOrgId != null) {
|
||||
@@ -535,6 +582,13 @@ public class TriageQueueAppServiceImpl implements TriageQueueAppService {
|
||||
if (next.getOrganizationId() != null) {
|
||||
recalcOrders(next.getOrganizationId(), null);
|
||||
}
|
||||
|
||||
// 写入叫号记录
|
||||
if (calling != null) {
|
||||
writeCallRecord(calling.getId(), calling.getPractitionerId(), CallType.NEXT, calling.getRoomNo());
|
||||
}
|
||||
writeCallRecord(next.getId(), next.getPractitionerId(), CallType.NEXT, next.getRoomNo());
|
||||
|
||||
return R.ok(true);
|
||||
}
|
||||
|
||||
@@ -608,7 +662,10 @@ public class TriageQueueAppServiceImpl implements TriageQueueAppService {
|
||||
.eq(TriageQueueItem::getDeleteFlag, "0")
|
||||
.orderByAsc(TriageQueueItem::getQueueOrder)
|
||||
);
|
||||
|
||||
|
||||
// 通过 slotId 批量查询 seqNo(方案 B:不修改 triage_queue_item 表结构)
|
||||
Map<Long, Integer> slotSeqNoMap = buildSlotSeqNoMap(allItems);
|
||||
|
||||
CallNumberDisplayResp resp = new CallNumberDisplayResp();
|
||||
|
||||
// 1. 获取科室名称(从第一条数据中取)
|
||||
@@ -626,7 +683,8 @@ public class TriageQueueAppServiceImpl implements TriageQueueAppService {
|
||||
|
||||
if (callingItem != null) {
|
||||
CallNumberDisplayResp.CurrentCallInfo currentCall = new CallNumberDisplayResp.CurrentCallInfo();
|
||||
currentCall.setNumber(callingItem.getQueueOrder());
|
||||
Integer displayNo = resolveDisplayNumber(callingItem, slotSeqNoMap);
|
||||
currentCall.setNumber(displayNo);
|
||||
currentCall.setName(maskPatientName(callingItem.getPatientName()));
|
||||
currentCall.setRoom(callingItem.getRoomNo() != null ? callingItem.getRoomNo() : "1号");
|
||||
currentCall.setDoctor(callingItem.getPractitionerName());
|
||||
@@ -680,7 +738,7 @@ public class TriageQueueAppServiceImpl implements TriageQueueAppService {
|
||||
patient.setId(item.getId());
|
||||
patient.setName(maskPatientName(item.getPatientName()));
|
||||
patient.setStatus(item.getStatus());
|
||||
patient.setQueueOrder(item.getQueueOrder());
|
||||
patient.setQueueOrder(resolveDisplayNumber(item, slotSeqNoMap));
|
||||
patients.add(patient);
|
||||
|
||||
// 统计等待人数(不包括 CALLING 状态)
|
||||
@@ -748,6 +806,82 @@ public class TriageQueueAppServiceImpl implements TriageQueueAppService {
|
||||
System.err.println("推送显示屏更新失败:" + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 写入分诊操作日志
|
||||
*
|
||||
* @param poolId 号源池ID
|
||||
* @param slotId 号源槽位ID
|
||||
* @param action 操作动作:ADD_QUEUE/REMOVE_QUEUE/CALL/COMPLETE/SKIP/REQUEUE
|
||||
*/
|
||||
private void writeDivLog(Long poolId, Long slotId, String action) {
|
||||
try {
|
||||
LoginUser loginUser = SecurityUtils.getLoginUser();
|
||||
DivLog log = new DivLog()
|
||||
.setPoolId(poolId)
|
||||
.setSlotId(slotId)
|
||||
.setOpUserId(loginUser != null ? loginUser.getUserId() : null)
|
||||
.setAction(action)
|
||||
.setCreateTime(LocalDateTime.now())
|
||||
.setUpdateAt(LocalDateTime.now())
|
||||
.setCreatedAt(LocalDateTime.now());
|
||||
divLogService.save(log);
|
||||
} catch (Exception e) {
|
||||
log.error("写入分诊日志失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 写入叫号记录
|
||||
*
|
||||
* @param queueId 队列ID
|
||||
* @param doctorId 医生ID
|
||||
* @param callType 叫号类型枚举
|
||||
* @param room 诊室号
|
||||
*/
|
||||
private void writeCallRecord(Long queueId, Long doctorId, CallType callType, String room) {
|
||||
try {
|
||||
CallRecord record = new CallRecord()
|
||||
.setQueueId(queueId)
|
||||
.setDoctorId(doctorId)
|
||||
.setCallTime(LocalDateTime.now())
|
||||
.setCallType(callType.getValue().toString())
|
||||
.setRoom(room)
|
||||
.setCreateAt(LocalDateTime.now());
|
||||
callRecordService.save(record);
|
||||
} catch (Exception e) {
|
||||
log.error("写入叫号记录失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过 slotId 批量查询 seqNo,返回 slotId -> seqNo 映射
|
||||
*/
|
||||
private Map<Long, Integer> buildSlotSeqNoMap(List<TriageQueueItem> items) {
|
||||
List<Long> slotIds = items.stream()
|
||||
.map(TriageQueueItem::getSlotId)
|
||||
.filter(Objects::nonNull)
|
||||
.distinct()
|
||||
.collect(Collectors.toList());
|
||||
if (slotIds.isEmpty()) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
List<ScheduleSlot> slots = scheduleSlotMapper.selectSeqNoBySlotIds(slotIds);
|
||||
return slots.stream()
|
||||
.filter(s -> s.getId() != null && s.getSeqNo() != null)
|
||||
.collect(Collectors.toMap(ScheduleSlot::getId, ScheduleSlot::getSeqNo, (a, b) -> a));
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算患者在叫号显示屏上应显示的号码:优先 seqNo(预约序号),否则 queueOrder(排队号)
|
||||
*/
|
||||
private Integer resolveDisplayNumber(TriageQueueItem item, Map<Long, Integer> slotSeqNoMap) {
|
||||
if (item == null) return null;
|
||||
if (item.getSlotId() != null && slotSeqNoMap.containsKey(item.getSlotId())) {
|
||||
return slotSeqNoMap.get(item.getSlotId());
|
||||
}
|
||||
return item.getQueueOrder();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -19,6 +19,8 @@ public class TriageQueueEncounterItem {
|
||||
private Long poolId;
|
||||
/** 号源槽位ID(关联 adm_schedule_slot.id,用于 div_log 审计日志) */
|
||||
private Long slotId;
|
||||
/** 预约序号(来自 adm_schedule_slot.seq_no,用于叫号显示) */
|
||||
private Integer seqNo;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -71,7 +71,8 @@
|
||||
COALESCE(T9.identifier_no, T9.patient_bus_no, '') AS identifierNo,
|
||||
COALESCE(T9.order_id IS NOT NULL, false) AS isFromAppointment,
|
||||
T9.slot_id AS slotId,
|
||||
T9.pool_id AS poolId
|
||||
T9.pool_id AS poolId,
|
||||
T9.seq_no AS seqNo
|
||||
from (
|
||||
SELECT T1.tenant_id AS tenant_id,
|
||||
T1.id AS encounter_id,
|
||||
@@ -100,6 +101,7 @@
|
||||
T18.identifier_no AS identifier_no,
|
||||
T1.order_id AS order_id,
|
||||
om.slot_id AS slot_id,
|
||||
ss.seq_no AS seq_no,
|
||||
ss.pool_id AS pool_id,
|
||||
sp.clinic_room AS clinic_room -- Bug #410:从号源池获取诊室
|
||||
FROM adm_encounter AS T1
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
package com.openhis.common.enums;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 叫号类型
|
||||
*
|
||||
* @author wangjian963
|
||||
* @date 2026-04-29
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum CallType implements HisEnumInterface {
|
||||
|
||||
/** 手动叫号(选呼) */
|
||||
CALL(10, "CALL", "手动叫号(选呼)"),
|
||||
|
||||
/** 手动叫号(下一患者) */
|
||||
NEXT(20, "NEXT", "手动叫号(下一患者)"),
|
||||
|
||||
/** 自动叫号 */
|
||||
AUTO_CALL(30, "AUTO_CALL", "自动叫号"),
|
||||
|
||||
/** 跳过 */
|
||||
SKIP(40, "SKIP", "跳过"),
|
||||
|
||||
/** 完成 */
|
||||
COMPLETE(50, "COMPLETE", "完成"),
|
||||
|
||||
/** 重排 */
|
||||
REQUEUE(60, "REQUEUE", "重排");
|
||||
|
||||
/** 状态码 */
|
||||
private Integer value;
|
||||
/** 英文标识 */
|
||||
private String code;
|
||||
/** 中文描述 */
|
||||
private String info;
|
||||
|
||||
/**
|
||||
* 根据状态码获取对应的叫号类型枚举
|
||||
*
|
||||
* @param value 状态码
|
||||
* @return 对应的枚举值,未匹配时返回 null
|
||||
*/
|
||||
public static CallType getByValue(Integer value) {
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
for (CallType val : values()) {
|
||||
if (val.getValue().equals(value)) {
|
||||
return val;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -61,4 +61,9 @@ public interface ScheduleSlotMapper extends BaseMapper<ScheduleSlot> {
|
||||
*/
|
||||
List<DoctorAvailabilityDTO> selectDoctorAvailabilitySummary(@Param("query") TicketQueryDTO query);
|
||||
|
||||
/**
|
||||
* 批量查询槽位序号(用于分诊叫号显示)
|
||||
*/
|
||||
List<ScheduleSlot> selectSeqNoBySlotIds(@Param("slotIds") List<Long> slotIds);
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
package com.openhis.triageandqueuemanage.domain;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
@TableName(value = "call_record")
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
public class CallRecord {
|
||||
|
||||
@TableId(type = IdType.AUTO)
|
||||
private Long recordId;
|
||||
|
||||
/** 队列ID (FK triage_queue_item.id) */
|
||||
private Long queueId;
|
||||
|
||||
/** 医生ID */
|
||||
private Long doctorId;
|
||||
|
||||
/** 叫号时间 */
|
||||
private LocalDateTime callTime;
|
||||
|
||||
/**
|
||||
* 叫号类型,使用 {@link com.openhis.common.enums.CallType} 枚举值
|
||||
* 10-CALL(选呼), 20-NEXT(下一患者), 30-AUTO_CALL(自动叫号),
|
||||
* 40-SKIP(跳过), 50-COMPLETE(完成), 60-REQUEUE(重排)
|
||||
*/
|
||||
private String callType;
|
||||
|
||||
/** 诊室(冗余) */
|
||||
private String room;
|
||||
|
||||
/** 创建时间 */
|
||||
private LocalDateTime createAt;
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package com.openhis.triageandqueuemanage.domain;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
@TableName(value = "div_log")
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
public class DivLog {
|
||||
|
||||
@TableId(type = IdType.AUTO)
|
||||
private Long logId;
|
||||
|
||||
/** 号源池ID */
|
||||
private Long poolId;
|
||||
|
||||
/** 号源槽位ID */
|
||||
private Long slotId;
|
||||
|
||||
/** 操作人ID */
|
||||
private Long opUserId;
|
||||
|
||||
/** 操作动作:ADD_QUEUE/REMOVE_QUEUE/CALL/REFUND/COMPLETE/SKIP/REQUEUE */
|
||||
private String action;
|
||||
|
||||
/** 操作时间 */
|
||||
private LocalDateTime createTime;
|
||||
|
||||
/** 更新时间 */
|
||||
private LocalDateTime updateAt;
|
||||
|
||||
/** 创建时间 */
|
||||
private LocalDateTime createdAt;
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.openhis.triageandqueuemanage.domain;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
@@ -40,7 +41,10 @@ public class TriageQueueItem {
|
||||
* 30=COMPLETED(已完成), 40=SKIPPED(已跳过), 50=REFUNDED(已退费), 60=FOLLOW(已随访)
|
||||
*/
|
||||
private Integer status;
|
||||
private Integer queueOrder; //“排队序号”,也就是患者在当前科室、当天队列里的 顺序号(从 1 开始递增)。
|
||||
private Integer queueOrder; //”排队序号”,也就是患者在当前科室、当天队列里的 顺序号(从 1 开始递增)。
|
||||
|
||||
@TableField(exist = false)
|
||||
private Integer seqNo; // 预约序号(来自 adm_schedule_slot.seq_no,非数据库字段,通过 JOIN 查询)
|
||||
|
||||
private LocalDateTime createTime;
|
||||
private LocalDateTime updateTime;
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.openhis.triageandqueuemanage.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.openhis.triageandqueuemanage.domain.CallRecord;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
@Repository
|
||||
public interface CallRecordMapper extends BaseMapper<CallRecord> {
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.openhis.triageandqueuemanage.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.openhis.triageandqueuemanage.domain.DivLog;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
@Repository
|
||||
public interface DivLogMapper extends BaseMapper<DivLog> {
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.openhis.triageandqueuemanage.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.openhis.triageandqueuemanage.domain.CallRecord;
|
||||
|
||||
public interface CallRecordService extends IService<CallRecord> {
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.openhis.triageandqueuemanage.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.openhis.triageandqueuemanage.domain.DivLog;
|
||||
|
||||
public interface DivLogService extends IService<DivLog> {
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.openhis.triageandqueuemanage.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.openhis.triageandqueuemanage.domain.CallRecord;
|
||||
import com.openhis.triageandqueuemanage.mapper.CallRecordMapper;
|
||||
import com.openhis.triageandqueuemanage.service.CallRecordService;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class CallRecordServiceImpl extends ServiceImpl<CallRecordMapper, CallRecord> implements CallRecordService {
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.openhis.triageandqueuemanage.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.openhis.triageandqueuemanage.domain.DivLog;
|
||||
import com.openhis.triageandqueuemanage.mapper.DivLogMapper;
|
||||
import com.openhis.triageandqueuemanage.service.DivLogService;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class DivLogServiceImpl extends ServiceImpl<DivLogMapper, DivLog> implements DivLogService {
|
||||
}
|
||||
@@ -437,5 +437,15 @@
|
||||
p.doctor_name ASC
|
||||
</select>
|
||||
|
||||
<select id="selectSeqNoBySlotIds" resultType="com.openhis.appointmentmanage.domain.ScheduleSlot">
|
||||
SELECT id, seq_no
|
||||
FROM adm_schedule_slot
|
||||
WHERE id IN
|
||||
<foreach collection="slotIds" item="slotId" open="(" separator="," close=")">
|
||||
#{slotId}
|
||||
</foreach>
|
||||
AND delete_flag = '0'
|
||||
</select>
|
||||
|
||||
|
||||
</mapper>
|
||||
|
||||
Reference in New Issue
Block a user