From d4d05267ada4b9aa4ee957a4a7755c181637afc0 Mon Sep 17 00:00:00 2001 From: wangjian963 <15215920+aprilry@user.noreply.gitee.com> Date: Wed, 29 Apr 2026 17:05:17 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E5=88=86=E8=AF=8A=E9=98=9F=E5=88=97):=20?= =?UTF-8?q?=E5=AE=9E=E7=8E=B0=E5=88=86=E8=AF=8A=E9=98=9F=E5=88=97=E6=A0=B8?= =?UTF-8?q?=E5=BF=83=E5=8A=9F=E8=83=BD=E4=B8=8E=E6=97=A5=E5=BF=97=E8=AE=B0?= =?UTF-8?q?=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新增分诊队列相关服务接口与实现,包括队列管理、叫号操作和日志记录 添加DivLogService和CallRecordService用于记录分诊操作和叫号历史 在CurrentDayEncounterDto和TriageQueueItem中增加seqNo字段用于显示预约序号 实现分诊操作日志记录功能,包括添加队列、移除队列、叫号、完成等操作 新增CallType枚举定义叫号类型,并实现叫号记录功能 优化队列状态映射逻辑,支持更多状态类型显示 --- .../OutpatientRegistrationAppServiceImpl.java | 22 + .../dto/CurrentDayEncounterDto.java | 5 + .../impl/TriageQueueAppServiceImpl.java | 266 +++++-- .../dto/TriageQueueEncounterItem.java | 2 + .../OutpatientRegistrationAppMapper.xml | 4 +- .../com/openhis/common/enums/CallType.java | 58 ++ .../mapper/ScheduleSlotMapper.java | 5 + .../domain/CallRecord.java | 42 + .../triageandqueuemanage/domain/DivLog.java | 41 + .../domain/TriageQueueItem.java | 6 +- .../mapper/CallRecordMapper.java | 9 + .../mapper/DivLogMapper.java | 9 + .../service/CallRecordService.java | 7 + .../service/DivLogService.java | 7 + .../service/impl/CallRecordServiceImpl.java | 11 + .../service/impl/DivLogServiceImpl.java | 11 + .../administration/ScheduleSlotMapper.xml | 10 + .../src/views/triageandqueuemanage/api.js | 4 +- .../triageandqueuemanage/cardiology/index.vue | 717 ++++++++++++------ 19 files changed, 933 insertions(+), 303 deletions(-) create mode 100644 openhis-server-new/openhis-common/src/main/java/com/openhis/common/enums/CallType.java create mode 100644 openhis-server-new/openhis-domain/src/main/java/com/openhis/triageandqueuemanage/domain/CallRecord.java create mode 100644 openhis-server-new/openhis-domain/src/main/java/com/openhis/triageandqueuemanage/domain/DivLog.java create mode 100644 openhis-server-new/openhis-domain/src/main/java/com/openhis/triageandqueuemanage/mapper/CallRecordMapper.java create mode 100644 openhis-server-new/openhis-domain/src/main/java/com/openhis/triageandqueuemanage/mapper/DivLogMapper.java create mode 100644 openhis-server-new/openhis-domain/src/main/java/com/openhis/triageandqueuemanage/service/CallRecordService.java create mode 100644 openhis-server-new/openhis-domain/src/main/java/com/openhis/triageandqueuemanage/service/DivLogService.java create mode 100644 openhis-server-new/openhis-domain/src/main/java/com/openhis/triageandqueuemanage/service/impl/CallRecordServiceImpl.java create mode 100644 openhis-server-new/openhis-domain/src/main/java/com/openhis/triageandqueuemanage/service/impl/DivLogServiceImpl.java diff --git a/openhis-server-new/openhis-application/src/main/java/com/openhis/web/chargemanage/appservice/impl/OutpatientRegistrationAppServiceImpl.java b/openhis-server-new/openhis-application/src/main/java/com/openhis/web/chargemanage/appservice/impl/OutpatientRegistrationAppServiceImpl.java index 0852a29b..d98c31f3 100644 --- a/openhis-server-new/openhis-application/src/main/java/com/openhis/web/chargemanage/appservice/impl/OutpatientRegistrationAppServiceImpl.java +++ b/openhis-server-new/openhis-application/src/main/java/com/openhis/web/chargemanage/appservice/impl/OutpatientRegistrationAppServiceImpl.java @@ -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()); } diff --git a/openhis-server-new/openhis-application/src/main/java/com/openhis/web/chargemanage/dto/CurrentDayEncounterDto.java b/openhis-server-new/openhis-application/src/main/java/com/openhis/web/chargemanage/dto/CurrentDayEncounterDto.java index df6b257c..6756cc09 100644 --- a/openhis-server-new/openhis-application/src/main/java/com/openhis/web/chargemanage/dto/CurrentDayEncounterDto.java +++ b/openhis-server-new/openhis-application/src/main/java/com/openhis/web/chargemanage/dto/CurrentDayEncounterDto.java @@ -170,4 +170,9 @@ public class CurrentDayEncounterDto { */ private String clinicRoom; + /** + * 预约序号(来自 adm_schedule_slot.seq_no) + */ + private Integer seqNo; + } diff --git a/openhis-server-new/openhis-application/src/main/java/com/openhis/web/triageandqueuemanage/appservice/impl/TriageQueueAppServiceImpl.java b/openhis-server-new/openhis-application/src/main/java/com/openhis/web/triageandqueuemanage/appservice/impl/TriageQueueAppServiceImpl.java index 30cbfed6..65794da2 100644 --- a/openhis-server-new/openhis-application/src/main/java/com/openhis/web/triageandqueuemanage/appservice/impl/TriageQueueAppServiceImpl.java +++ b/openhis-server-new/openhis-application/src/main/java/com/openhis/web/triageandqueuemanage/appservice/impl/TriageQueueAppServiceImpl.java @@ -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 list = triageQueueItemService.list(wrapper); - + // 通过 slotId 查询 seqNo 并填充到返回结果中(用于选呼显示预约号) + if (list != null && !list.isEmpty()) { + Map 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() @@ -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 callingWrapper = new LambdaQueryWrapper() .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 nextWrapper = new LambdaQueryWrapper() .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 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 buildSlotSeqNoMap(List items) { + List slotIds = items.stream() + .map(TriageQueueItem::getSlotId) + .filter(Objects::nonNull) + .distinct() + .collect(Collectors.toList()); + if (slotIds.isEmpty()) { + return Collections.emptyMap(); + } + List 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 slotSeqNoMap) { + if (item == null) return null; + if (item.getSlotId() != null && slotSeqNoMap.containsKey(item.getSlotId())) { + return slotSeqNoMap.get(item.getSlotId()); + } + return item.getQueueOrder(); + } } diff --git a/openhis-server-new/openhis-application/src/main/java/com/openhis/web/triageandqueuemanage/dto/TriageQueueEncounterItem.java b/openhis-server-new/openhis-application/src/main/java/com/openhis/web/triageandqueuemanage/dto/TriageQueueEncounterItem.java index 2902662d..c308f8c6 100644 --- a/openhis-server-new/openhis-application/src/main/java/com/openhis/web/triageandqueuemanage/dto/TriageQueueEncounterItem.java +++ b/openhis-server-new/openhis-application/src/main/java/com/openhis/web/triageandqueuemanage/dto/TriageQueueEncounterItem.java @@ -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; } diff --git a/openhis-server-new/openhis-application/src/main/resources/mapper/chargemanage/OutpatientRegistrationAppMapper.xml b/openhis-server-new/openhis-application/src/main/resources/mapper/chargemanage/OutpatientRegistrationAppMapper.xml index 3d86e881..383d78c0 100644 --- a/openhis-server-new/openhis-application/src/main/resources/mapper/chargemanage/OutpatientRegistrationAppMapper.xml +++ b/openhis-server-new/openhis-application/src/main/resources/mapper/chargemanage/OutpatientRegistrationAppMapper.xml @@ -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 diff --git a/openhis-server-new/openhis-common/src/main/java/com/openhis/common/enums/CallType.java b/openhis-server-new/openhis-common/src/main/java/com/openhis/common/enums/CallType.java new file mode 100644 index 00000000..6de73d39 --- /dev/null +++ b/openhis-server-new/openhis-common/src/main/java/com/openhis/common/enums/CallType.java @@ -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; + } +} diff --git a/openhis-server-new/openhis-domain/src/main/java/com/openhis/appointmentmanage/mapper/ScheduleSlotMapper.java b/openhis-server-new/openhis-domain/src/main/java/com/openhis/appointmentmanage/mapper/ScheduleSlotMapper.java index 8d2e4966..f5910cf3 100644 --- a/openhis-server-new/openhis-domain/src/main/java/com/openhis/appointmentmanage/mapper/ScheduleSlotMapper.java +++ b/openhis-server-new/openhis-domain/src/main/java/com/openhis/appointmentmanage/mapper/ScheduleSlotMapper.java @@ -61,4 +61,9 @@ public interface ScheduleSlotMapper extends BaseMapper { */ List selectDoctorAvailabilitySummary(@Param("query") TicketQueryDTO query); + /** + * 批量查询槽位序号(用于分诊叫号显示) + */ + List selectSeqNoBySlotIds(@Param("slotIds") List slotIds); + } diff --git a/openhis-server-new/openhis-domain/src/main/java/com/openhis/triageandqueuemanage/domain/CallRecord.java b/openhis-server-new/openhis-domain/src/main/java/com/openhis/triageandqueuemanage/domain/CallRecord.java new file mode 100644 index 00000000..c669a68f --- /dev/null +++ b/openhis-server-new/openhis-domain/src/main/java/com/openhis/triageandqueuemanage/domain/CallRecord.java @@ -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; +} diff --git a/openhis-server-new/openhis-domain/src/main/java/com/openhis/triageandqueuemanage/domain/DivLog.java b/openhis-server-new/openhis-domain/src/main/java/com/openhis/triageandqueuemanage/domain/DivLog.java new file mode 100644 index 00000000..b0111487 --- /dev/null +++ b/openhis-server-new/openhis-domain/src/main/java/com/openhis/triageandqueuemanage/domain/DivLog.java @@ -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; +} diff --git a/openhis-server-new/openhis-domain/src/main/java/com/openhis/triageandqueuemanage/domain/TriageQueueItem.java b/openhis-server-new/openhis-domain/src/main/java/com/openhis/triageandqueuemanage/domain/TriageQueueItem.java index c5d27414..38d3a6d3 100644 --- a/openhis-server-new/openhis-domain/src/main/java/com/openhis/triageandqueuemanage/domain/TriageQueueItem.java +++ b/openhis-server-new/openhis-domain/src/main/java/com/openhis/triageandqueuemanage/domain/TriageQueueItem.java @@ -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; diff --git a/openhis-server-new/openhis-domain/src/main/java/com/openhis/triageandqueuemanage/mapper/CallRecordMapper.java b/openhis-server-new/openhis-domain/src/main/java/com/openhis/triageandqueuemanage/mapper/CallRecordMapper.java new file mode 100644 index 00000000..421756ae --- /dev/null +++ b/openhis-server-new/openhis-domain/src/main/java/com/openhis/triageandqueuemanage/mapper/CallRecordMapper.java @@ -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 { +} diff --git a/openhis-server-new/openhis-domain/src/main/java/com/openhis/triageandqueuemanage/mapper/DivLogMapper.java b/openhis-server-new/openhis-domain/src/main/java/com/openhis/triageandqueuemanage/mapper/DivLogMapper.java new file mode 100644 index 00000000..a99e34ce --- /dev/null +++ b/openhis-server-new/openhis-domain/src/main/java/com/openhis/triageandqueuemanage/mapper/DivLogMapper.java @@ -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 { +} diff --git a/openhis-server-new/openhis-domain/src/main/java/com/openhis/triageandqueuemanage/service/CallRecordService.java b/openhis-server-new/openhis-domain/src/main/java/com/openhis/triageandqueuemanage/service/CallRecordService.java new file mode 100644 index 00000000..69d8eab6 --- /dev/null +++ b/openhis-server-new/openhis-domain/src/main/java/com/openhis/triageandqueuemanage/service/CallRecordService.java @@ -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 { +} diff --git a/openhis-server-new/openhis-domain/src/main/java/com/openhis/triageandqueuemanage/service/DivLogService.java b/openhis-server-new/openhis-domain/src/main/java/com/openhis/triageandqueuemanage/service/DivLogService.java new file mode 100644 index 00000000..52828a66 --- /dev/null +++ b/openhis-server-new/openhis-domain/src/main/java/com/openhis/triageandqueuemanage/service/DivLogService.java @@ -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 { +} diff --git a/openhis-server-new/openhis-domain/src/main/java/com/openhis/triageandqueuemanage/service/impl/CallRecordServiceImpl.java b/openhis-server-new/openhis-domain/src/main/java/com/openhis/triageandqueuemanage/service/impl/CallRecordServiceImpl.java new file mode 100644 index 00000000..d31a3c17 --- /dev/null +++ b/openhis-server-new/openhis-domain/src/main/java/com/openhis/triageandqueuemanage/service/impl/CallRecordServiceImpl.java @@ -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 implements CallRecordService { +} diff --git a/openhis-server-new/openhis-domain/src/main/java/com/openhis/triageandqueuemanage/service/impl/DivLogServiceImpl.java b/openhis-server-new/openhis-domain/src/main/java/com/openhis/triageandqueuemanage/service/impl/DivLogServiceImpl.java new file mode 100644 index 00000000..8cb0ed45 --- /dev/null +++ b/openhis-server-new/openhis-domain/src/main/java/com/openhis/triageandqueuemanage/service/impl/DivLogServiceImpl.java @@ -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 implements DivLogService { +} diff --git a/openhis-server-new/openhis-domain/src/main/resources/mapper/administration/ScheduleSlotMapper.xml b/openhis-server-new/openhis-domain/src/main/resources/mapper/administration/ScheduleSlotMapper.xml index 9a2e73ec..d92abec8 100644 --- a/openhis-server-new/openhis-domain/src/main/resources/mapper/administration/ScheduleSlotMapper.xml +++ b/openhis-server-new/openhis-domain/src/main/resources/mapper/administration/ScheduleSlotMapper.xml @@ -437,5 +437,15 @@ p.doctor_name ASC + + diff --git a/openhis-ui-vue3/src/views/triageandqueuemanage/api.js b/openhis-ui-vue3/src/views/triageandqueuemanage/api.js index b7e7d176..117f4022 100644 --- a/openhis-ui-vue3/src/views/triageandqueuemanage/api.js +++ b/openhis-ui-vue3/src/views/triageandqueuemanage/api.js @@ -37,7 +37,7 @@ export function getCandidatePool(params) { pageNo: params?.pageNo || 1, pageSize: params?.pageSize || 10000, searchKey: params?.searchKey || '', - statusEnum: params?.statusEnum || -1 // -1表示排除退号记录(正常挂号) + statusEnum: params?.statusEnum ?? 1 // 1=PLANNED(待诊),已挂号未接诊的患者;不传或传-1会返回已接诊的患者 }, skipErrorMsg: true // 跳过错误提示,由组件处理 }) @@ -169,4 +169,4 @@ export function getLocationTree(query) { method: 'get', params: query }) -} \ No newline at end of file +} diff --git a/openhis-ui-vue3/src/views/triageandqueuemanage/cardiology/index.vue b/openhis-ui-vue3/src/views/triageandqueuemanage/cardiology/index.vue index e6a6bf1a..0a19dfce 100644 --- a/openhis-ui-vue3/src/views/triageandqueuemanage/cardiology/index.vue +++ b/openhis-ui-vue3/src/views/triageandqueuemanage/cardiology/index.vue @@ -3,15 +3,22 @@
- 智能分诊排队管理 - {{ currentDeptName }} + 智能分诊排队管理 - {{ currentDeptName }}
- + 刷新 - 退出 - 后台配置 + + 退出 + + + 后台配置 +
@@ -31,28 +38,67 @@ style="width: 100%" @selection-change="handleCandidateSelectionChange" > - - - - - - - - + + + + + + + +
加入队列 >> 一键加入队列 @@ -75,13 +121,48 @@ highlight-current-row @row-click="handleQueueRowClick" > - - - - - - - + + + + + + + + -
- - - - - - - - + + + + +
+ + + + + + - - - - - - - - - - - - - - {{ w.label }} - - - - - - - - -
- 快速生成器 - 语法检查 + + 清除 +
- -
-
+ - - - - - - - -
- - - - - - + + + + + + - + + + + + + + + + + + + + + + + 清除 + + + + + + + + +
@@ -627,29 +867,40 @@ const parseAge = (ageStr) => { } // 后端队列状态 -> 前端展示状态 +// 后端状态码:0=WAITING, 10=CALLING, 20=IN_CLINIC, 30=COMPLETED, 40=SKIPPED, 50=REFUNDED, 60=FOLLOW const mapBackendStatusToFrontend = (status) => { - if (!status) { + if (status === null || status === undefined) { console.warn('【心内科】状态映射:收到空状态值') return '等待' } - // 转换为大写并去除空格,确保匹配 - const normalizedStatus = String(status).trim().toUpperCase() - if (normalizedStatus === 'CALLING') return '叫号中' - if (normalizedStatus === 'WAITING') return '等待' - if (normalizedStatus === 'SKIPPED') return '跳过' - if (normalizedStatus === 'COMPLETED') return '已完成' - console.warn('【心内科】状态映射:未知状态值', status, '-> 默认返回"等待"') - return '等待' + const numStatus = Number(status) + switch (numStatus) { + case 0: return '等待' + case 10: return '叫号中' + case 20: return '诊中' + case 30: return '已完成' + case 40: return '跳过' + case 50: return '已退费' + case 60: return '已随访' + default: + console.warn('【心内科】状态映射:未知状态值', status, '-> 默认返回"等待"') + return '等待' + } } -// 前端状态 -> 后端状态(目前仅展示用) +// 前端状态 -> 后端状态码 const mapFrontendStatusToBackend = (status) => { - if (!status) return 'WAITING' - if (status === '叫号中') return 'CALLING' - if (status === '等待') return 'WAITING' - if (status === '跳过') return 'SKIPPED' - if (status === '已完成') return 'COMPLETED' - return 'WAITING' + if (!status) return 0 + switch (status) { + case '叫号中': return 10 + case '等待': return 0 + case '诊中': return 20 + case '已完成': return 30 + case '跳过': return 40 + case '已退费': return 50 + case '已随访': return 60 + default: return 0 + } } // 从数据库加载队列