挂号补单功能的完善

This commit is contained in:
2026-01-20 09:31:37 +08:00
parent 649f7bcf5b
commit d1223aec07
27 changed files with 4875 additions and 307 deletions

View File

@@ -29,6 +29,8 @@ 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.TriageCandidateExclusion;
import com.openhis.triageandqueuemanage.service.TriageCandidateExclusionService;
import com.openhis.web.paymentmanage.appservice.IPaymentRecService;
import com.openhis.web.paymentmanage.dto.CancelPaymentDto;
import com.openhis.web.paymentmanage.dto.CancelRegPaymentDto;
@@ -38,12 +40,15 @@ import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.time.LocalDate;
import java.util.*;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
/**
* 门诊挂号 应用实现类
*/
@Slf4j
@Service
public class OutpatientRegistrationAppServiceImpl implements IOutpatientRegistrationAppService {
@@ -76,6 +81,9 @@ public class OutpatientRegistrationAppServiceImpl implements IOutpatientRegistra
@Resource
IPatientIdentifierService patientIdentifierService;
@Resource
TriageCandidateExclusionService triageCandidateExclusionService;
/**
* 门诊挂号 - 查询患者信息
@@ -308,6 +316,47 @@ public class OutpatientRegistrationAppServiceImpl implements IOutpatientRegistra
new Page<>(pageNo, pageSize), EncounterClass.AMB.getValue(), EncounterStatus.IN_PROGRESS.getValue(),
ParticipantType.ADMITTER.getCode(), ParticipantType.REGISTRATION_DOCTOR.getCode(), queryWrapper,
ChargeItemContext.REGISTER.getValue(), PaymentStatus.SUCCESS.getValue());
// 过滤候选池排除列表(如果是从智能候选池查询,排除已加入队列的患者)
// 检查请求参数 excludeFromCandidatePool如果为 true 或未设置,则过滤排除列表
String excludeParam = request.getParameter("excludeFromCandidatePool");
boolean shouldExclude = excludeParam == null || "true".equalsIgnoreCase(excludeParam);
if (shouldExclude && currentDayEncounter != null && !currentDayEncounter.getRecords().isEmpty()) {
try {
// 获取当前租户和日期
Integer tenantId = SecurityUtils.getLoginUser().getTenantId();
LocalDate today = LocalDate.now();
// 查询排除列表
List<TriageCandidateExclusion> exclusions = triageCandidateExclusionService.list(
new LambdaQueryWrapper<TriageCandidateExclusion>()
.eq(TriageCandidateExclusion::getTenantId, tenantId)
.eq(TriageCandidateExclusion::getExclusionDate, today)
.eq(TriageCandidateExclusion::getDeleteFlag, "0")
);
if (exclusions != null && !exclusions.isEmpty()) {
// 构建排除的 encounterId 集合
Set<Long> excludedEncounterIds = exclusions.stream()
.map(TriageCandidateExclusion::getEncounterId)
.filter(Objects::nonNull)
.collect(Collectors.toSet());
// 过滤结果
List<CurrentDayEncounterDto> filteredRecords = currentDayEncounter.getRecords().stream()
.filter(e -> e.getEncounterId() == null || !excludedEncounterIds.contains(e.getEncounterId()))
.collect(Collectors.toList());
// 更新分页结果
currentDayEncounter.setRecords(filteredRecords);
currentDayEncounter.setTotal(filteredRecords.size());
}
} catch (Exception e) {
// 如果过滤失败,记录日志但不影响正常查询
log.warn("过滤候选池排除列表失败", e);
}
}
currentDayEncounter.getRecords().forEach(e -> {
// 性别
e.setGenderEnum_enumText(EnumUtils.getInfoByValue(AdministrativeGender.class, e.getGenderEnum()));

View File

@@ -0,0 +1,27 @@
package com.openhis.web.triageandqueuemanage.appservice;
import com.core.common.core.domain.R;
import com.openhis.web.triageandqueuemanage.dto.TriageQueueActionReq;
import com.openhis.web.triageandqueuemanage.dto.TriageQueueAddReq;
import com.openhis.web.triageandqueuemanage.dto.TriageQueueAdjustReq;
import java.time.LocalDate;
public interface TriageQueueAppService {
R<?> list(Long organizationId, LocalDate date);
R<?> add(TriageQueueAddReq req);
R<?> remove(Long id);
R<?> adjust(TriageQueueAdjustReq req);
/** 选呼:将之前叫号中置为完成,选中的置为叫号中 */
R<?> call(TriageQueueActionReq req);
/** 完成:叫号中 -> 完成(移出列表),并自动推进下一个等待为叫号中 */
R<?> complete(TriageQueueActionReq req);
/** 过号重排:叫号中 -> 跳过并移到末尾,并自动推进下一个等待为叫号中 */
R<?> requeue(TriageQueueActionReq req);
/** 跳过:兼容前端按钮(当前实现等同于过号重排) */
R<?> skip(TriageQueueActionReq req);
/** 下一患者:当前叫号中 -> 完成,下一位等待 -> 叫号中 */
R<?> next(TriageQueueActionReq req);
}

View File

@@ -0,0 +1,556 @@
package com.openhis.web.triageandqueuemanage.appservice.impl;
import cn.hutool.core.collection.CollUtil;
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.TriageQueueItem;
import com.openhis.triageandqueuemanage.domain.TriageCandidateExclusion;
import com.openhis.triageandqueuemanage.service.TriageQueueItemService;
import com.openhis.triageandqueuemanage.service.TriageCandidateExclusionService;
import com.openhis.web.triageandqueuemanage.appservice.TriageQueueAppService;
import com.openhis.web.triageandqueuemanage.dto.TriageQueueActionReq;
import com.openhis.web.triageandqueuemanage.dto.TriageQueueAddReq;
import com.openhis.web.triageandqueuemanage.dto.TriageQueueAdjustReq;
import com.openhis.web.triageandqueuemanage.dto.TriageQueueEncounterItem;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;
@Service
public class TriageQueueAppServiceImpl implements TriageQueueAppService {
private static final String STATUS_WAITING = "WAITING";
private static final String STATUS_CALLING = "CALLING";
private static final String STATUS_SKIPPED = "SKIPPED";
private static final String STATUS_COMPLETED = "COMPLETED";
@Resource
private TriageQueueItemService triageQueueItemService;
@Resource
private TriageCandidateExclusionService triageCandidateExclusionService;
@Override
public R<?> list(Long organizationId, LocalDate date) {
Integer tenantId = SecurityUtils.getLoginUser().getTenantId();
// 只查询今天的患者
LocalDate qd = date != null ? date : LocalDate.now();
LambdaQueryWrapper<TriageQueueItem> wrapper = new LambdaQueryWrapper<TriageQueueItem>()
.eq(TriageQueueItem::getTenantId, tenantId)
.eq(TriageQueueItem::getQueueDate, qd)
.eq(TriageQueueItem::getDeleteFlag, "0")
.ne(TriageQueueItem::getStatus, STATUS_COMPLETED)
.orderByAsc(TriageQueueItem::getQueueOrder);
// 如果指定了科室,按科室过滤;否则查询所有科室(全科模式)
if (organizationId != null) {
wrapper.eq(TriageQueueItem::getOrganizationId, organizationId);
}
List<TriageQueueItem> list = triageQueueItemService.list(wrapper);
// 双重保险:再次过滤掉 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);
}
@Override
@Transactional(rollbackFor = Exception.class)
public R<?> add(TriageQueueAddReq req) {
if (req == null || ObjectUtil.isNull(req.getOrganizationId())) {
return R.fail("organizationId 不能为空");
}
if (CollUtil.isEmpty(req.getItems())) {
return R.fail("items 不能为空");
}
Integer tenantId = SecurityUtils.getLoginUser().getTenantId();
LocalDate qd = LocalDate.now();
Long orgId = req.getOrganizationId();
List<TriageQueueItem> existing = triageQueueItemService.list(new LambdaQueryWrapper<TriageQueueItem>()
.eq(TriageQueueItem::getTenantId, tenantId)
.eq(TriageQueueItem::getOrganizationId, orgId)
.eq(TriageQueueItem::getQueueDate, qd)
.eq(TriageQueueItem::getDeleteFlag, "0")
.ne(TriageQueueItem::getStatus, STATUS_COMPLETED));
int maxOrder = existing.stream().map(TriageQueueItem::getQueueOrder).filter(Objects::nonNull).max(Integer::compareTo).orElse(0);
int added = 0;
for (TriageQueueEncounterItem it : req.getItems()) {
if (it == null || it.getEncounterId() == null) continue;
boolean exists = existing.stream().anyMatch(e -> Objects.equals(e.getEncounterId(), it.getEncounterId()));
if (exists) continue;
TriageQueueItem qi = new TriageQueueItem()
.setTenantId(tenantId)
.setQueueDate(qd)
.setOrganizationId(orgId)
.setOrganizationName(req.getOrganizationName())
.setEncounterId(it.getEncounterId())
.setPatientId(it.getPatientId())
.setPatientName(it.getPatientName())
.setHealthcareName(it.getHealthcareName())
.setPractitionerName(it.getPractitionerName())
.setStatus(STATUS_WAITING)
.setQueueOrder(++maxOrder)
.setDeleteFlag("0")
.setCreateTime(LocalDateTime.now())
.setUpdateTime(LocalDateTime.now());
triageQueueItemService.save(qi);
// 记录到候选池排除列表(避免刷新后重新出现在候选池)
TriageCandidateExclusion exclusion = triageCandidateExclusionService.getOne(
new LambdaQueryWrapper<TriageCandidateExclusion>()
.eq(TriageCandidateExclusion::getTenantId, tenantId)
.eq(TriageCandidateExclusion::getExclusionDate, qd)
.eq(TriageCandidateExclusion::getEncounterId, it.getEncounterId())
.eq(TriageCandidateExclusion::getDeleteFlag, "0")
);
if (exclusion == null) {
exclusion = new TriageCandidateExclusion()
.setTenantId(tenantId)
.setExclusionDate(qd)
.setEncounterId(it.getEncounterId())
.setPatientId(it.getPatientId())
.setPatientName(it.getPatientName())
.setOrganizationId(orgId)
.setOrganizationName(req.getOrganizationName())
.setReason("ADDED_TO_QUEUE")
.setDeleteFlag("0")
.setCreateTime(LocalDateTime.now())
.setUpdateTime(LocalDateTime.now());
triageCandidateExclusionService.save(exclusion);
}
added++;
}
return R.ok(added);
}
@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("队列项不存在");
// 逻辑删除队列项
item.setDeleteFlag("1").setUpdateTime(LocalDateTime.now());
triageQueueItemService.updateById(item);
// 从排除列表中删除记录,使患者重新出现在候选池中
Integer tenantId = item.getTenantId();
LocalDate exclusionDate = item.getQueueDate();
Long encounterId = item.getEncounterId();
if (tenantId != null && exclusionDate != null && encounterId != null) {
TriageCandidateExclusion exclusion = triageCandidateExclusionService.getOne(
new LambdaQueryWrapper<TriageCandidateExclusion>()
.eq(TriageCandidateExclusion::getTenantId, tenantId)
.eq(TriageCandidateExclusion::getExclusionDate, exclusionDate)
.eq(TriageCandidateExclusion::getEncounterId, encounterId)
.eq(TriageCandidateExclusion::getDeleteFlag, "0")
);
if (exclusion != null) {
// 逻辑删除排除记录
exclusion.setDeleteFlag("1").setUpdateTime(LocalDateTime.now());
triageCandidateExclusionService.updateById(exclusion);
}
}
recalcOrders(item.getOrganizationId(), item.getQueueDate());
return R.ok(true);
}
@Override
@Transactional(rollbackFor = Exception.class)
public R<?> adjust(TriageQueueAdjustReq req) {
if (req == null || req.getId() == null) return R.fail("id 不能为空");
if (!"up".equalsIgnoreCase(req.getDirection()) && !"down".equalsIgnoreCase(req.getDirection())) {
return R.fail("direction 只能是 up/down");
}
TriageQueueItem cur = triageQueueItemService.getById(req.getId());
if (cur == null) return R.fail("队列项不存在");
List<TriageQueueItem> list = listInternal(cur.getOrganizationId(), cur.getQueueDate());
list.sort(Comparator.comparing(TriageQueueItem::getQueueOrder).thenComparing(TriageQueueItem::getId));
int idx = -1;
for (int i = 0; i < list.size(); i++) {
if (Objects.equals(list.get(i).getId(), cur.getId())) { idx = i; break; }
}
if (idx == -1) return R.fail("队列项不在当前队列");
int targetIdx = "up".equalsIgnoreCase(req.getDirection()) ? idx - 1 : idx + 1;
if (targetIdx < 0 || targetIdx >= list.size()) return R.ok(false);
TriageQueueItem other = list.get(targetIdx);
Integer tmp = cur.getQueueOrder();
cur.setQueueOrder(other.getQueueOrder()).setUpdateTime(LocalDateTime.now());
other.setQueueOrder(tmp).setUpdateTime(LocalDateTime.now());
triageQueueItemService.updateById(cur);
triageQueueItemService.updateById(other);
recalcOrders(cur.getOrganizationId(), cur.getQueueDate());
return R.ok(true);
}
@Override
@Transactional(rollbackFor = Exception.class)
public R<?> call(TriageQueueActionReq req) {
if (req == null || req.getId() == null) return R.fail("id 不能为空");
TriageQueueItem selected = triageQueueItemService.getById(req.getId());
if (selected == null) return R.fail("队列项不存在");
// 只将"等待"状态的患者转为"叫号中",允许有多个"叫号中"的患者
if (STATUS_WAITING.equals(selected.getStatus())) {
selected.setStatus(STATUS_CALLING).setUpdateTime(LocalDateTime.now());
triageQueueItemService.updateById(selected);
return R.ok(true);
} else if (STATUS_CALLING.equals(selected.getStatus())) {
// 如果已经是"叫号中"状态,直接返回成功(不做任何操作)
return R.ok(true);
} else {
// 其他状态(如 SKIPPED、COMPLETED不能选呼
return R.fail("只能选呼\"等待\"状态的患者,当前患者状态为:" + selected.getStatus());
}
}
@Override
@Transactional(rollbackFor = Exception.class)
public R<?> complete(TriageQueueActionReq req) {
Integer tenantId = SecurityUtils.getLoginUser().getTenantId();
TriageQueueItem calling = null;
// 关键改进:如果提供了 id直接通过ID获取像 call 方法一样),不依赖查询条件
if (req != null && req.getId() != null) {
calling = triageQueueItemService.getById(req.getId());
if (calling == null) {
return R.fail("队列项不存在");
}
// 验证状态
if (!STATUS_CALLING.equals(calling.getStatus())) {
return R.fail("只能完成\"叫号中\"状态的患者,当前患者状态为:" + calling.getStatus());
}
} 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)
.eq(TriageQueueItem::getDeleteFlag, "0")
.eq(TriageQueueItem::getStatus, STATUS_CALLING)
.orderByAsc(TriageQueueItem::getQueueOrder)
.last("LIMIT 1");
if (orgId != null) {
callingWrapper.eq(TriageQueueItem::getOrganizationId, orgId);
}
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("当前没有叫号中的患者");
}
}
// 使用实际找到的科室ID
Long actualOrgId = calling.getOrganizationId();
// 1) 叫号中 -> 完成(移出列表)
calling.setStatus(STATUS_COMPLETED).setUpdateTime(LocalDateTime.now());
triageQueueItemService.updateById(calling);
// 2) 自动推进下一个等待为叫号中(同一科室,包含跳过状态,不限制日期)
LambdaQueryWrapper<TriageQueueItem> nextWrapper = new LambdaQueryWrapper<TriageQueueItem>()
.eq(TriageQueueItem::getTenantId, tenantId)
.eq(TriageQueueItem::getDeleteFlag, "0")
.and(w -> w.eq(TriageQueueItem::getStatus, STATUS_WAITING)
.or()
.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] complete() 查询等待患者(不限制日期): actualOrgId=" + actualOrgId + ", 结果=" + (next != null ? next.getPatientName() + "(status=" + next.getStatus() + ")" : "null"));
if (next != null) {
next.setStatus(STATUS_CALLING).setUpdateTime(LocalDateTime.now());
triageQueueItemService.updateById(next);
}
recalcOrders(actualOrgId, null);
return R.ok(true);
}
@Override
@Transactional(rollbackFor = Exception.class)
public R<?> requeue(TriageQueueActionReq req) {
Integer tenantId = SecurityUtils.getLoginUser().getTenantId();
TriageQueueItem calling = null;
if (req != null && req.getId() != null) {
calling = triageQueueItemService.getById(req.getId());
if (calling == null) {
return R.fail("队列项不存在");
}
// 验证状态
if (!STATUS_CALLING.equals(calling.getStatus())) {
return R.fail("只能对\"叫号中\"状态的患者进行过号重排,当前患者状态为:" + calling.getStatus());
}
} else {
// 如果没有提供 id通过查询条件查找兼容旧逻辑
Long orgId = req != null && req.getOrganizationId() != null ? req.getOrganizationId() : null;
LambdaQueryWrapper<TriageQueueItem> callingWrapper = new LambdaQueryWrapper<TriageQueueItem>()
.eq(TriageQueueItem::getTenantId, tenantId)
.eq(TriageQueueItem::getDeleteFlag, "0")
.eq(TriageQueueItem::getStatus, STATUS_CALLING)
.orderByAsc(TriageQueueItem::getQueueOrder)
.last("LIMIT 1");
if (orgId != null) {
callingWrapper.eq(TriageQueueItem::getOrganizationId, orgId);
}
calling = triageQueueItemService.getOne(callingWrapper, false);
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")
.and(w -> w.eq(TriageQueueItem::getStatus, STATUS_WAITING)
.or()
.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("当前没有等待中的患者");
}
// 找末尾序号(同一科室,不限制日期)
Integer maxOrder = triageQueueItemService.list(new LambdaQueryWrapper<TriageQueueItem>()
.eq(TriageQueueItem::getTenantId, tenantId)
.eq(TriageQueueItem::getOrganizationId, actualOrgId)
.eq(TriageQueueItem::getDeleteFlag, "0")
.ne(TriageQueueItem::getStatus, STATUS_COMPLETED))
.stream()
.map(TriageQueueItem::getQueueOrder)
.filter(Objects::nonNull)
.max(Integer::compareTo)
.orElse(0);
// 1) 叫号中 -> 跳过,并移到末尾
calling.setStatus(STATUS_SKIPPED).setQueueOrder(maxOrder + 1).setUpdateTime(LocalDateTime.now());
triageQueueItemService.updateById(calling);
// 2) 自动推进下一个等待为叫号中
next.setStatus(STATUS_CALLING).setUpdateTime(LocalDateTime.now());
triageQueueItemService.updateById(next);
recalcOrders(actualOrgId, null);
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) {
calling = triageQueueItemService.getById(req.getId());
if (calling == null) {
return R.fail("队列项不存在");
}
// 验证状态:必须是"叫号中"状态
if (!STATUS_CALLING.equals(calling.getStatus())) {
return R.fail("只能对\"叫号中\"状态的患者执行\"下一患者\"操作,当前患者状态为:" + calling.getStatus());
}
} else {
// 如果没有提供 id通过查询条件查找兼容旧逻辑
Long orgId = req != null && req.getOrganizationId() != null ? req.getOrganizationId() : null;
LambdaQueryWrapper<TriageQueueItem> callingWrapper = new LambdaQueryWrapper<TriageQueueItem>()
.eq(TriageQueueItem::getTenantId, tenantId)
.eq(TriageQueueItem::getDeleteFlag, "0")
.eq(TriageQueueItem::getStatus, STATUS_CALLING)
.orderByAsc(TriageQueueItem::getQueueOrder)
.last("LIMIT 1");
if (orgId != null) {
callingWrapper.eq(TriageQueueItem::getOrganizationId, orgId);
}
calling = triageQueueItemService.getOne(callingWrapper, false);
}
Long actualOrgId = null;
// 当前叫号中 -> 完成(如果不存在,就当作从头找第一位等待)
if (calling != null) {
calling.setStatus(STATUS_COMPLETED).setUpdateTime(LocalDateTime.now());
triageQueueItemService.updateById(calling);
actualOrgId = calling.getOrganizationId(); // 使用叫号中患者所在的科室
} else {
// 如果没有叫号中的患者,使用请求中的 organizationId如果有
if (req != null && req.getOrganizationId() != null) {
actualOrgId = req.getOrganizationId();
}
}
// 下一位等待 -> 叫号中(如果之前有叫号中的,就在同一科室找;否则在全科找)
// 注意:也包含"跳过"状态的患者,因为跳过后的患者也可以重新叫号(不限制日期)
LambdaQueryWrapper<TriageQueueItem> nextWrapper = new LambdaQueryWrapper<TriageQueueItem>()
.eq(TriageQueueItem::getTenantId, tenantId)
.eq(TriageQueueItem::getDeleteFlag, "0")
.and(w -> w.eq(TriageQueueItem::getStatus, STATUS_WAITING)
.or()
.eq(TriageQueueItem::getStatus, STATUS_SKIPPED))
.orderByAsc(TriageQueueItem::getQueueOrder)
.last("LIMIT 1");
if (actualOrgId != null) {
nextWrapper.eq(TriageQueueItem::getOrganizationId, actualOrgId);
}
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) {
recalcOrders(actualOrgId, null);
}
return R.fail("当前没有等待的患者");
}
next.setStatus(STATUS_CALLING).setUpdateTime(LocalDateTime.now());
triageQueueItemService.updateById(next);
if (next.getOrganizationId() != null) {
recalcOrders(next.getOrganizationId(), null);
}
return R.ok(true);
}
private List<TriageQueueItem> listInternal(Long orgId, LocalDate qd) {
Integer tenantId = SecurityUtils.getLoginUser().getTenantId();
LambdaQueryWrapper<TriageQueueItem> wrapper = new LambdaQueryWrapper<TriageQueueItem>()
.eq(TriageQueueItem::getTenantId, tenantId)
.eq(TriageQueueItem::getOrganizationId, orgId)
.eq(TriageQueueItem::getDeleteFlag, "0")
.ne(TriageQueueItem::getStatus, STATUS_COMPLETED);
// 如果 qd 不为 null才添加日期限制
if (qd != null) {
wrapper.eq(TriageQueueItem::getQueueDate, qd);
}
return triageQueueItemService.list(wrapper);
}
private TriageQueueItem findCalling(Long orgId, LocalDate qd) {
Integer tenantId = SecurityUtils.getLoginUser().getTenantId();
return triageQueueItemService.getOne(new LambdaQueryWrapper<TriageQueueItem>()
.eq(TriageQueueItem::getTenantId, tenantId)
.eq(TriageQueueItem::getOrganizationId, orgId)
.eq(TriageQueueItem::getQueueDate, qd)
.eq(TriageQueueItem::getDeleteFlag, "0")
.eq(TriageQueueItem::getStatus, STATUS_CALLING)
.last("LIMIT 1"), false);
}
private void recalcOrders(Long orgId, LocalDate qd) {
List<TriageQueueItem> list = listInternal(orgId, qd);
list.sort(Comparator.comparing(TriageQueueItem::getQueueOrder).thenComparing(TriageQueueItem::getId));
int i = 1;
for (TriageQueueItem it : list) {
if (!Objects.equals(it.getQueueOrder(), i)) {
it.setQueueOrder(i).setUpdateTime(LocalDateTime.now());
triageQueueItemService.updateById(it);
}
i++;
}
}
}

View File

@@ -0,0 +1,70 @@
package com.openhis.web.triageandqueuemanage.controller;
import com.core.common.core.domain.R;
import com.openhis.web.triageandqueuemanage.appservice.TriageQueueAppService;
import com.openhis.web.triageandqueuemanage.dto.TriageQueueActionReq;
import com.openhis.web.triageandqueuemanage.dto.TriageQueueAddReq;
import com.openhis.web.triageandqueuemanage.dto.TriageQueueAdjustReq;
import lombok.extern.slf4j.Slf4j;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.time.LocalDate;
@RestController
@Slf4j
@RequestMapping("/triage/queue")
public class TriageQueueController {
@Resource
private TriageQueueAppService triageQueueAppService;
@GetMapping("/list")
public R<?> list(@RequestParam(value = "organizationId", required = false) Long organizationId,
@RequestParam(value = "date", required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate date) {
return triageQueueAppService.list(organizationId, date);
}
@PostMapping("/add")
public R<?> add(@RequestBody TriageQueueAddReq req) {
return triageQueueAppService.add(req);
}
@DeleteMapping("/remove/{id}")
public R<?> remove(@PathVariable("id") Long id) {
return triageQueueAppService.remove(id);
}
@PutMapping("/adjust")
public R<?> adjust(@RequestBody TriageQueueAdjustReq req) {
return triageQueueAppService.adjust(req);
}
@PostMapping("/call")
public R<?> call(@RequestBody TriageQueueActionReq req) {
return triageQueueAppService.call(req);
}
@PostMapping("/complete")
public R<?> complete(@RequestBody(required = false) TriageQueueActionReq req) {
return triageQueueAppService.complete(req);
}
@PostMapping("/requeue")
public R<?> requeue(@RequestBody(required = false) TriageQueueActionReq req) {
return triageQueueAppService.requeue(req);
}
@PostMapping("/skip")
public R<?> skip(@RequestBody(required = false) TriageQueueActionReq req) {
return triageQueueAppService.skip(req);
}
@PostMapping("/next")
public R<?> next(@RequestBody(required = false) TriageQueueActionReq req) {
return triageQueueAppService.next(req);
}
}

View File

@@ -0,0 +1,14 @@
package com.openhis.web.triageandqueuemanage.dto;
import lombok.Data;
@Data
public class TriageQueueActionReq {
/** 目标队列项ID例如选呼时选中的患者 */
private Long id;
/** 科室ID可选不传则用当前登录人orgId */
private Long organizationId;
}

View File

@@ -0,0 +1,18 @@
package com.openhis.web.triageandqueuemanage.dto;
import lombok.Data;
import java.util.List;
@Data
public class TriageQueueAddReq {
/** 科室ID就诊科室 */
private Long organizationId;
/** 科室名称(冗余存储,便于展示) */
private String organizationName;
/** 要加入队列的就诊记录 */
private List<TriageQueueEncounterItem> items;
}

View File

@@ -0,0 +1,13 @@
package com.openhis.web.triageandqueuemanage.dto;
import lombok.Data;
@Data
public class TriageQueueAdjustReq {
private Long id;
/** up / down */
private String direction;
}

View File

@@ -0,0 +1,15 @@
package com.openhis.web.triageandqueuemanage.dto;
import lombok.Data;
@Data
public class TriageQueueEncounterItem {
private Long encounterId;
private Long patientId;
private String patientName;
private String healthcareName;
private String practitionerName;
}