feat(分诊队列): 实现分诊队列核心功能与日志记录

新增分诊队列相关服务接口与实现,包括队列管理、叫号操作和日志记录
添加DivLogService和CallRecordService用于记录分诊操作和叫号历史
在CurrentDayEncounterDto和TriageQueueItem中增加seqNo字段用于显示预约序号
实现分诊操作日志记录功能,包括添加队列、移除队列、叫号、完成等操作
新增CallType枚举定义叫号类型,并实现叫号记录功能
优化队列状态映射逻辑,支持更多状态类型显示
This commit is contained in:
wangjian963
2026-04-29 17:05:17 +08:00
parent 2b0acce1db
commit d4d05267ad
19 changed files with 933 additions and 303 deletions

View File

@@ -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());
}

View File

@@ -170,4 +170,9 @@ public class CurrentDayEncounterDto {
*/
private String clinicRoom;
/**
* 预约序号(来自 adm_schedule_slot.seq_no
*/
private Integer seqNo;
}

View File

@@ -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();
}
}

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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;
}
}

View File

@@ -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);
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;

View File

@@ -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> {
}

View File

@@ -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> {
}

View File

@@ -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> {
}

View File

@@ -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> {
}

View File

@@ -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 {
}

View File

@@ -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 {
}

View File

@@ -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>