diff --git a/md/test.html b/md/test.html deleted file mode 100644 index c7218294..00000000 --- a/md/test.html +++ /dev/null @@ -1,10 +0,0 @@ - - - - - 测试合并11111 - - - - - \ No newline at end of file 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 b6dc20ba..78623cba 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 @@ -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 exclusions = triageCandidateExclusionService.list( + new LambdaQueryWrapper() + .eq(TriageCandidateExclusion::getTenantId, tenantId) + .eq(TriageCandidateExclusion::getExclusionDate, today) + .eq(TriageCandidateExclusion::getDeleteFlag, "0") + ); + + if (exclusions != null && !exclusions.isEmpty()) { + // 构建排除的 encounterId 集合 + Set excludedEncounterIds = exclusions.stream() + .map(TriageCandidateExclusion::getEncounterId) + .filter(Objects::nonNull) + .collect(Collectors.toSet()); + + // 过滤结果 + List 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())); diff --git a/openhis-server-new/openhis-application/src/main/java/com/openhis/web/doctorstation/appservice/impl/DoctorPhraesAppServiceImpl.java b/openhis-server-new/openhis-application/src/main/java/com/openhis/web/doctorstation/appservice/impl/DoctorPhraseAppServiceImpl.java similarity index 97% rename from openhis-server-new/openhis-application/src/main/java/com/openhis/web/doctorstation/appservice/impl/DoctorPhraesAppServiceImpl.java rename to openhis-server-new/openhis-application/src/main/java/com/openhis/web/doctorstation/appservice/impl/DoctorPhraseAppServiceImpl.java index 0f7e7dd6..a3113190 100644 --- a/openhis-server-new/openhis-application/src/main/java/com/openhis/web/doctorstation/appservice/impl/DoctorPhraesAppServiceImpl.java +++ b/openhis-server-new/openhis-application/src/main/java/com/openhis/web/doctorstation/appservice/impl/DoctorPhraseAppServiceImpl.java @@ -13,7 +13,7 @@ import javax.annotation.Resource; import java.util.List; @Service -public class DoctorPhraesAppServiceImpl implements IDoctorPhraseAppService { +public class DoctorPhraseAppServiceImpl implements IDoctorPhraseAppService { @Resource private IDoctorPhraseService doctorPhraseService; diff --git a/openhis-server-new/openhis-application/src/main/java/com/openhis/web/inhospitalnursestation/appservice/impl/ATDManageAppServiceImpl.java b/openhis-server-new/openhis-application/src/main/java/com/openhis/web/inhospitalnursestation/appservice/impl/ATDManageAppServiceImpl.java index c99a1604..5a5e4509 100644 --- a/openhis-server-new/openhis-application/src/main/java/com/openhis/web/inhospitalnursestation/appservice/impl/ATDManageAppServiceImpl.java +++ b/openhis-server-new/openhis-application/src/main/java/com/openhis/web/inhospitalnursestation/appservice/impl/ATDManageAppServiceImpl.java @@ -293,11 +293,8 @@ public class ATDManageAppServiceImpl implements IATDManageAppService { if (admissionPatientInfoDto.getPriorityEnum() != null) { // 更新患者病情 encounterService.updatePriorityEnumById(encounterId, admissionPatientInfoDto.getPriorityEnum()); - // 将之前的住院参与者更新为已完成 - Integer result = encounterParticipantService.updateEncounterParticipantsStatus(encounterId); - if (result == 0) { - return R.fail("患者信息更新失败,请联系管理员"); - } + // 将之前的住院参与者更新为已完成(如果存在的话) + encounterParticipantService.updateEncounterParticipantsStatus(encounterId); // 更新住院参与者 // 住院医生 encounterParticipantService.creatEncounterParticipants(encounterId, startTime, diff --git a/openhis-server-new/openhis-application/src/main/java/com/openhis/web/patientmanage/appservice/impl/PatientInformationServiceImpl.java b/openhis-server-new/openhis-application/src/main/java/com/openhis/web/patientmanage/appservice/impl/PatientInformationServiceImpl.java index fd72e0ca..8018fd8d 100644 --- a/openhis-server-new/openhis-application/src/main/java/com/openhis/web/patientmanage/appservice/impl/PatientInformationServiceImpl.java +++ b/openhis-server-new/openhis-application/src/main/java/com/openhis/web/patientmanage/appservice/impl/PatientInformationServiceImpl.java @@ -308,12 +308,20 @@ public class PatientInformationServiceImpl implements IPatientInformationService * @return 患者信息 */ private Patient handlePatientInfo(PatientBaseInfoDto patientInfoDto) { - Patient patient = new Patient(); - patient.setId(patientInfoDto.getId()); - if (patientInfoDto.getId() == null) { + Patient patient; + if (patientInfoDto.getId() != null) { + // 更新现有患者信息 + patient = patientService.getById(patientInfoDto.getId()); + if (patient == null) { + throw new ServiceException("患者信息不存在,无法更新"); + } + } else { + // 新增患者信息 + patient = new Patient(); patient.setBusNo(assignSeqUtil.getSeq(AssignSeqEnum.PATIENT_NUM.getPrefix(), 10)); - patientInfoDto.setActiveFlag(PublicationStatus.ACTIVE.getValue()); // 默认启用 + patient.setActiveFlag(PublicationStatus.ACTIVE.getValue()); // 默认启用 } + patient.setName(patientInfoDto.getName()); // 患者姓名 patient.setPyStr(ChineseConvertUtils.toPinyinFirstLetter(patientInfoDto.getName())); // 拼音首拼 patient.setWbStr(ChineseConvertUtils.toWBFirstLetter(patientInfoDto.getName())); // 五笔首拼 @@ -337,7 +345,14 @@ public class PatientInformationServiceImpl implements IPatientInformationService patient.setDeceasedDate(patientInfoDto.getDeceasedDate()); // 死亡时间 patient.setNationalityCode(patientInfoDto.getNationalityCode());// 民族 patient.setActiveFlag(patientInfoDto.getActiveFlag());// 活动标识 - patientService.saveOrUpdate(patient); + + if (patientInfoDto.getId() != null) { + // 更新操作 + patientService.updateById(patient); + } else { + // 新增操作 + patientService.save(patient); + } return patient; } diff --git a/openhis-server-new/openhis-application/src/main/java/com/openhis/web/triageandqueuemanage/appservice/TriageQueueAppService.java b/openhis-server-new/openhis-application/src/main/java/com/openhis/web/triageandqueuemanage/appservice/TriageQueueAppService.java new file mode 100644 index 00000000..27231a19 --- /dev/null +++ b/openhis-server-new/openhis-application/src/main/java/com/openhis/web/triageandqueuemanage/appservice/TriageQueueAppService.java @@ -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); +} + + 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 new file mode 100644 index 00000000..e1606e63 --- /dev/null +++ b/openhis-server-new/openhis-application/src/main/java/com/openhis/web/triageandqueuemanage/appservice/impl/TriageQueueAppServiceImpl.java @@ -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 wrapper = new LambdaQueryWrapper() + .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 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 existing = triageQueueItemService.list(new LambdaQueryWrapper() + .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() + .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() + .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 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 callingWrapper = new LambdaQueryWrapper() + .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 nextWrapper = new LambdaQueryWrapper() + .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 callingWrapper = new LambdaQueryWrapper() + .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 nextWrapper = new LambdaQueryWrapper() + .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() + .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 callingWrapper = new LambdaQueryWrapper() + .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 nextWrapper = new LambdaQueryWrapper() + .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 listInternal(Long orgId, LocalDate qd) { + Integer tenantId = SecurityUtils.getLoginUser().getTenantId(); + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .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() + .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 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++; + } + } +} + + diff --git a/openhis-server-new/openhis-application/src/main/java/com/openhis/web/triageandqueuemanage/controller/TriageQueueController.java b/openhis-server-new/openhis-application/src/main/java/com/openhis/web/triageandqueuemanage/controller/TriageQueueController.java new file mode 100644 index 00000000..5697a499 --- /dev/null +++ b/openhis-server-new/openhis-application/src/main/java/com/openhis/web/triageandqueuemanage/controller/TriageQueueController.java @@ -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); + } +} + + diff --git a/openhis-server-new/openhis-application/src/main/java/com/openhis/web/triageandqueuemanage/dto/TriageQueueActionReq.java b/openhis-server-new/openhis-application/src/main/java/com/openhis/web/triageandqueuemanage/dto/TriageQueueActionReq.java new file mode 100644 index 00000000..1ba6e187 --- /dev/null +++ b/openhis-server-new/openhis-application/src/main/java/com/openhis/web/triageandqueuemanage/dto/TriageQueueActionReq.java @@ -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; +} + + + diff --git a/openhis-server-new/openhis-application/src/main/java/com/openhis/web/triageandqueuemanage/dto/TriageQueueAddReq.java b/openhis-server-new/openhis-application/src/main/java/com/openhis/web/triageandqueuemanage/dto/TriageQueueAddReq.java new file mode 100644 index 00000000..44f681f7 --- /dev/null +++ b/openhis-server-new/openhis-application/src/main/java/com/openhis/web/triageandqueuemanage/dto/TriageQueueAddReq.java @@ -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 items; +} + + + diff --git a/openhis-server-new/openhis-application/src/main/java/com/openhis/web/triageandqueuemanage/dto/TriageQueueAdjustReq.java b/openhis-server-new/openhis-application/src/main/java/com/openhis/web/triageandqueuemanage/dto/TriageQueueAdjustReq.java new file mode 100644 index 00000000..d800c1dc --- /dev/null +++ b/openhis-server-new/openhis-application/src/main/java/com/openhis/web/triageandqueuemanage/dto/TriageQueueAdjustReq.java @@ -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; +} + + + 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 new file mode 100644 index 00000000..09d6ea87 --- /dev/null +++ b/openhis-server-new/openhis-application/src/main/java/com/openhis/web/triageandqueuemanage/dto/TriageQueueEncounterItem.java @@ -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; +} + + + diff --git a/openhis-server-new/openhis-application/src/main/resources/mapper/inhospitalnursestation/ATDManageAppMapper.xml b/openhis-server-new/openhis-application/src/main/resources/mapper/inhospitalnursestation/ATDManageAppMapper.xml index d14e1454..63956bc9 100644 --- a/openhis-server-new/openhis-application/src/main/resources/mapper/inhospitalnursestation/ATDManageAppMapper.xml +++ b/openhis-server-new/openhis-application/src/main/resources/mapper/inhospitalnursestation/ATDManageAppMapper.xml @@ -224,28 +224,7 @@ SELECT - T1.id, - T1.active_flag, - T1.temp_flag, - T1."name" AS patientName, -- 患者姓名 - T1.name_json, - T1.bus_no AS patientNo, -- 病历号 - T1.gender_enum, -- 患者性别 - T1.birth_date, - T1.deceased_date, - T1.marital_status_enum, - T1.prfs_enum, - T1.phone, -- 患者手机号 - COALESCE(NULLIF(T1.address, ''), '') AS address, -- 处理空地址 - T1.address_province, - T1.address_city, - T1.address_district, - T1.address_street, - T1.address_json, - T1.nationality_code, - T1.id_card, - T1.py_str, - T1.wb_str, - T1.blood_abo, - T1.blood_rh, - T1.work_company, - T1.native_place, - T1.country_code, - T1.link_name, - T1.link_relation_code, - T1.link_telcom, - T1.link_jsons, - T1.tenant_id, - T2.bus_no AS hospitalNo, -- 住院号 - T2.id AS encounterId, -- 就诊ID - T2.priority_enum, -- 护理级别 - T2.status_enum, -- 患者状态 - T2.organization_id,-- 入院科室 - T2.start_time AS admissionDate, -- 入院日期 - T2.end_time AS dischargeDate, -- 出院日期 - T2.class_enum, -- 就诊类别 - Doctor.name AS responsibleDoctor, -- 责任医生 - Nurse.name AS responsibleNurse, -- 责任护士 - T6.type_code, -- 费别 - T7.status_enum AS surgeryStatusEnum, -- 手术状态 - T8.start_time AS surgeryStartTime, -- 手术开始时间 - T8.end_time AS surgeryEndTime, -- 手术结束时间 - T9.id AS encounterLocationId, -- 就诊位置ID - T9.location_id, -- 床位号 - T11."name" AS caty, -- 入院科室 - T12."name" AS mainDiagnosis -- 主要诊断 - FROM adm_patient AS T1 - LEFT JOIN adm_encounter T2 - ON T1.id = T2.patient_id -- 患者ID - AND T2.delete_flag = '0' - AND T2.class_enum = '1' -- 仅选择住院患者 - -- 获取责任医生 - LEFT JOIN ( - SELECT - encounter_id, - practitioner_id, - "name", - ROW_NUMBER() OVER (PARTITION BY encounter_id ORDER BY practitioner_id) as rn - FROM ( - SELECT - T3.encounter_id, - T4."name", - T3.practitioner_id - FROM adm_encounter_participant T3 - LEFT JOIN adm_practitioner T4 - ON T3.practitioner_id = T4.id - AND T4.delete_flag = '0' - WHERE T3.type_code = '9' -- 责任医生类型 - AND T3.delete_flag = '0' - ) sub - ) Doctor ON T2.id = Doctor.encounter_id AND Doctor.rn = 1 - -- 获取责任护士 - LEFT JOIN ( - SELECT - encounter_id, - practitioner_id, - "name", - ROW_NUMBER() OVER (PARTITION BY encounter_id ORDER BY practitioner_id) as rn - FROM ( - SELECT - T3.encounter_id, - T4."name", - T3.practitioner_id - FROM adm_encounter_participant T3 - LEFT JOIN adm_practitioner T4 - ON T3.practitioner_id = T4.id - AND T4.delete_flag = '0' - WHERE T3.type_code = '2' -- 责任护士类型 - AND T3.delete_flag = '0' - ) sub - ) Nurse ON T2.id = Nurse.encounter_id AND Nurse.rn = 1 - LEFT JOIN adm_encounter_diagnosis T5 - ON T2.id = T5.encounter_id -- 就诊ID - AND T5.maindise_flag = 1 - AND T5.delete_flag = '0' - LEFT JOIN adm_account T6 - ON T2.id = T6.encounter_id -- 就诊ID - AND T6.delete_flag = '0' - LEFT JOIN cli_procedure T7 - ON T1.id = T7.patient_id -- 患者ID - AND T7.delete_flag = '0' - LEFT JOIN cli_procedure_performer T8 - ON T7.id = T8.procedure_id -- 手术ID - AND T8.delete_flag = '0' - LEFT JOIN adm_encounter_location T9 - ON T2.id = T9.encounter_id -- 就诊ID - AND T9.form_enum = 8 - AND T9.delete_flag = '0' - LEFT JOIN adm_organization T11 - ON T2.organization_id = T11.id - AND T11.type_enum = 2 - AND T11.delete_flag = '0' - LEFT JOIN cli_condition_definition T12 - ON T5.condition_id = T12.id - AND T12.delete_flag = '0' - LEFT JOIN wor_service_request T13 - ON T2.id = T13.encounter_id - AND T7.base_service_req_id = T13.id - AND T13.delete_flag = '0' - LEFT JOIN wor_service_request_detail T14 - ON T13.id = T14.service_req_id - AND T14.type_code = '3' - AND T14.delete_flag = '0' - - T1.delete_flag = '0' - -- 患者ID - - AND T1.id = #{patientId} - + id, + active_flag, + temp_flag, + patientName, + name_json, + patientNo, + gender_enum, + birth_date, + deceased_date, + marital_status_enum, + prfs_enum, + phone, + address, + address_province, + address_city, + address_district, + address_street, + address_json, + nationality_code, + id_card, + py_str, + wb_str, + blood_abo, + blood_rh, + work_company, + native_place, + country_code, + link_name, + link_relation_code, + link_telcom, + link_jsons, + tenant_id, + hospitalNo, + encounterId, + priority_enum, + status_enum, + organization_id, + admissionDate, + dischargeDate, + class_enum, + responsibleDoctor, + responsibleNurse, + type_code, + surgeryStatusEnum, + surgeryStartTime, + surgeryEndTime, + encounterLocationId, + location_id, + caty, + mainDiagnosis + FROM ( + SELECT + patient_base.*, + ROW_NUMBER() OVER (PARTITION BY patient_base.id ORDER BY patient_base.encounterId) as rn + FROM ( + SELECT DISTINCT + T1.id, + T1.active_flag, + T1.temp_flag, + T1."name" AS patientName, -- 患者姓名 + T1.name_json, + T1.bus_no AS patientNo, -- 病历号 + T1.gender_enum, -- 患者性别 + T1.birth_date, + T1.deceased_date, + T1.marital_status_enum, + T1.prfs_enum, + T1.phone, -- 患者手机号 + COALESCE(NULLIF(T1.address, ''), '') AS address, -- 处理空地址 + T1.address_province, + T1.address_city, + T1.address_district, + T1.address_street, + T1.address_json, + T1.nationality_code, + T1.id_card, + T1.py_str, + T1.wb_str, + T1.blood_abo, + T1.blood_rh, + T1.work_company, + T1.native_place, + T1.country_code, + T1.link_name, + T1.link_relation_code, + T1.link_telcom, + T1.link_jsons, + T1.tenant_id, + T2.bus_no AS hospitalNo, -- 住院号 + T2.id AS encounterId, -- 就诊ID + T2.priority_enum, -- 护理级别 + T2.status_enum, -- 患者状态 + T2.organization_id,-- 入院科室 + T2.start_time AS admissionDate, -- 入院日期 + T2.end_time AS dischargeDate, -- 出院日期 + T2.class_enum, -- 就诊类别 + -- 获取责任医生(使用子查询确保只返回一个值) + (SELECT p."name" + FROM adm_encounter_participant ep + LEFT JOIN adm_practitioner p ON ep.practitioner_id = p.id AND p.delete_flag = '0' + WHERE ep.encounter_id = T2.id + AND ep.type_code = '9' + AND ep.delete_flag = '0' + ORDER BY ep.practitioner_id + LIMIT 1) AS responsibleDoctor, + -- 获取责任护士(使用子查询确保只返回一个值) + (SELECT p."name" + FROM adm_encounter_participant ep + LEFT JOIN adm_practitioner p ON ep.practitioner_id = p.id AND p.delete_flag = '0' + WHERE ep.encounter_id = T2.id + AND ep.type_code = '2' + AND ep.delete_flag = '0' + ORDER BY ep.practitioner_id + LIMIT 1) AS responsibleNurse, + T6.type_code, -- 费别 + T7.status_enum AS surgeryStatusEnum, -- 手术状态 + T8.start_time AS surgeryStartTime, -- 手术开始时间 + T8.end_time AS surgeryEndTime, -- 手术结束时间 + T9.id AS encounterLocationId, -- 就诊位置ID + T9.location_id, -- 床位号 + T11."name" AS caty, -- 入院科室 + T12."name" AS mainDiagnosis -- 主要诊断 + FROM adm_patient AS T1 + INNER JOIN adm_encounter T2 -- 改为INNER JOIN确保患者有就诊记录 + ON T1.id = T2.patient_id -- 患者ID + AND T2.delete_flag = '0' + AND T2.class_enum = '1' -- 仅选择住院患者 + LEFT JOIN adm_encounter_diagnosis T5 + ON T2.id = T5.encounter_id -- 就诊ID + AND T5.maindise_flag = 1 + AND T5.delete_flag = '0' + LEFT JOIN adm_account T6 + ON T2.id = T6.encounter_id -- 就诊ID + AND T6.delete_flag = '0' + LEFT JOIN ( + SELECT + patient_id, + status_enum, + id, + request_id, + ROW_NUMBER() OVER (PARTITION BY patient_id ORDER BY id DESC) as rn + FROM cli_procedure + WHERE delete_flag = '0' + ) T7 ON T1.id = T7.patient_id AND T7.rn = 1 + LEFT JOIN ( + SELECT + procedure_id, + start_time, + end_time, + ROW_NUMBER() OVER (PARTITION BY procedure_id ORDER BY id DESC) as rn + FROM cli_procedure_performer + WHERE delete_flag = '0' + ) T8 ON T7.id = T8.procedure_id AND T8.rn = 1 + LEFT JOIN adm_encounter_location T9 + ON T2.id = T9.encounter_id -- 就诊ID + AND T9.form_enum = 8 + AND T9.delete_flag = '0' + LEFT JOIN adm_organization T11 + ON T2.organization_id = T11.id + AND T11.type_enum = 2 + AND T11.delete_flag = '0' + LEFT JOIN cli_condition_definition T12 + ON T5.condition_id = T12.id + AND T12.delete_flag = '0' + WHERE T1.delete_flag = '0' + -- 患者ID + + AND T1.id = #{patientId} + - -- 在科 - - AND T9.status_enum = 2 - + -- 在科 + + AND T9.status_enum = 2 + - -- 待出院 - - AND T2.status_enum = 4 - + -- 待出院 + + AND T2.status_enum = 4 + - -- 危重 - - AND T2.priority_enum = 1 - + -- 危重 + + AND T2.priority_enum = 1 + - -- 手术 - - AND T14.type_code = '3' - - - -- 欠费 - - AND T6.balance_amount < 0 - - - -- 已出院 - - AND T2.status_enum = 5 - - - - AND ( - -- 住院号 - T2.bus_no = #{searchKey} - -- 患者姓名 - OR T1.name like concat('%', #{searchKey}, '%') - -- 责任医生 - OR Doctor.name like concat('%', #{searchKey}, '%') - -- 责任护士 - OR Nurse.name like concat('%', #{searchKey}, '%') + -- 手术 + + AND EXISTS ( + SELECT 1 + FROM wor_service_request_detail wsrd + INNER JOIN wor_service_request wsr ON wsrd.service_req_id = wsr.id + WHERE wsr.encounter_id = T2.id + AND wsrd.type_code = '3' + AND wsr.delete_flag = '0' + AND wsrd.delete_flag = '0' ) - - + - ORDER BY T9.location_id ASC + -- 欠费 + + AND T6.balance_amount < 0 + + + -- 已出院 + + AND T2.status_enum = 5 + + + + AND ( + -- 住院号 + T2.bus_no = #{searchKey} + -- 患者姓名 + OR T1.name like concat('%', #{searchKey}, '%') + -- 责任医生(在子查询中处理) + OR EXISTS ( + SELECT 1 + FROM adm_encounter_participant ep + LEFT JOIN adm_practitioner p ON ep.practitioner_id = p.id AND p.delete_flag = '0' + WHERE ep.encounter_id = T2.id + AND ep.type_code = '9' + AND ep.delete_flag = '0' + AND p."name" like concat('%', #{searchKey}, '%') + ) + -- 责任护士(在子查询中处理) + OR EXISTS ( + SELECT 1 + FROM adm_encounter_participant ep + LEFT JOIN adm_practitioner p ON ep.practitioner_id = p.id AND p.delete_flag = '0' + WHERE ep.encounter_id = T2.id + AND ep.type_code = '2' + AND ep.delete_flag = '0' + AND p."name" like concat('%', #{searchKey}, '%') + ) + ) + + ) patient_base + ) ranked_result + WHERE rn = 1 + ORDER BY location_id ASC diff --git a/openhis-server-new/openhis-application/src/main/resources/mapper/inpatientmanage/VitalSignsAppMapper.xml b/openhis-server-new/openhis-application/src/main/resources/mapper/inpatientmanage/VitalSignsAppMapper.xml index 9aed0bb4..225d6d1f 100644 --- a/openhis-server-new/openhis-application/src/main/resources/mapper/inpatientmanage/VitalSignsAppMapper.xml +++ b/openhis-server-new/openhis-application/src/main/resources/mapper/inpatientmanage/VitalSignsAppMapper.xml @@ -46,7 +46,7 @@ AND T6.delete_flag = '0' LEFT JOIN wor_service_request T7 ON T1.encounter_id = T7.encounter_id - AND T5.base_service_req_id = T7.id + AND T5.request_id = T7.id AND T7.delete_flag = '0' LEFT JOIN wor_service_request_detail T8 ON T7.id = T8.service_req_id diff --git a/openhis-server-new/openhis-common/src/main/java/com/openhis/common/aspectj/DictAspect.java b/openhis-server-new/openhis-common/src/main/java/com/openhis/common/aspectj/DictAspect.java index 82d75965..c35d38a6 100644 --- a/openhis-server-new/openhis-common/src/main/java/com/openhis/common/aspectj/DictAspect.java +++ b/openhis-server-new/openhis-common/src/main/java/com/openhis/common/aspectj/DictAspect.java @@ -2,6 +2,7 @@ package com.openhis.common.aspectj; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.core.common.core.domain.R; +import com.core.common.utils.DictUtils; import com.openhis.common.annotation.Dict; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; @@ -115,19 +116,12 @@ public class DictAspect { } private String queryDictLabel(String dictTable, String dictCode, String dictText, String dictValue) { - String sql; - if (StringUtils.isEmpty(dictTable)) { - // 场景 1:默认查询 sys_dict_data 表 - sql = "SELECT dict_label FROM sys_dict_data WHERE dict_type = ? AND dict_value::varchar = ? LIMIT 1"; - try { - return jdbcTemplate.queryForObject(sql, String.class, dictCode, dictValue); - } catch (DataAccessException e) { - // 如果查询结果为空,返回 空字符串 - return ""; - } + if (StringUtils.hasText(dictTable)) { + // 场景 1:默认字典走DictUtils缓存 + return DictUtils.getDictLabel(dictCode, dictValue); } else { // 场景 2:查询指定表 - sql = String.format("SELECT %s FROM %s WHERE %s::varchar = ? LIMIT 1", dictText, dictTable, dictCode); + String sql = String.format("SELECT %s FROM %s WHERE %s::varchar = ? LIMIT 1", dictText, dictTable, dictCode); try { return jdbcTemplate.queryForObject(sql, String.class, dictValue); } catch (DataAccessException e) { diff --git a/openhis-server-new/openhis-domain/src/main/java/com/openhis/administration/service/impl/PatientServiceImpl.java b/openhis-server-new/openhis-domain/src/main/java/com/openhis/administration/service/impl/PatientServiceImpl.java index 449d1b6e..0aa2eb59 100644 --- a/openhis-server-new/openhis-domain/src/main/java/com/openhis/administration/service/impl/PatientServiceImpl.java +++ b/openhis-server-new/openhis-domain/src/main/java/com/openhis/administration/service/impl/PatientServiceImpl.java @@ -32,18 +32,11 @@ public class PatientServiceImpl extends ServiceImpl impl */ @Override public boolean saveOrUpdatePatient(Patient patient) { - - // 身份证ID,患者ID,确定唯一患者 - LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); - queryWrapper.eq(Patient::getId, patient.getId()).eq(Patient::getIdCard, patient.getIdCard()); - - Patient existingPatient = baseMapper.selectOne(queryWrapper); - if (existingPatient != null) { - // 如果记录存在,更新记录 - patient.setId(existingPatient.getId()); + if (patient.getId() != null) { + // 如果提供了ID,则直接更新该ID的记录 return baseMapper.updateById(patient) > 0; } else { - // 如果记录不存在,插入新记录 + // 如果没有提供ID,则插入新记录 return baseMapper.insert(patient) > 0; } } diff --git a/openhis-server-new/openhis-domain/src/main/java/com/openhis/template/domain/DoctorPhrase.java b/openhis-server-new/openhis-domain/src/main/java/com/openhis/template/domain/DoctorPhrase.java index df260baa..0a15e4fe 100644 --- a/openhis-server-new/openhis-domain/src/main/java/com/openhis/template/domain/DoctorPhrase.java +++ b/openhis-server-new/openhis-domain/src/main/java/com/openhis/template/domain/DoctorPhrase.java @@ -1,12 +1,14 @@ package com.openhis.template.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; import lombok.EqualsAndHashCode; import lombok.experimental.Accessors; +import javax.validation.constraints.NotBlank; import java.time.LocalDateTime; @Data @@ -28,8 +30,13 @@ public class DoctorPhrase { private Integer phraseType; /** 业务分类(主诉/现病史等) */ + @NotBlank(message = "业务分类不能为空") private String phraseCategory; + // 非数据库字段,用于前端展示名称 + @TableField(exist = false) + private String businessTypeName; + /** 模板内容 */ private String phraseContent; diff --git a/openhis-server-new/openhis-domain/src/main/java/com/openhis/template/enums/DoctorPhraseBizTypeEnum.java b/openhis-server-new/openhis-domain/src/main/java/com/openhis/template/enums/DoctorPhraseBizTypeEnum.java new file mode 100644 index 00000000..71808efa --- /dev/null +++ b/openhis-server-new/openhis-domain/src/main/java/com/openhis/template/enums/DoctorPhraseBizTypeEnum.java @@ -0,0 +1,40 @@ +package com.openhis.template.enums; + +// 枚举类不需要任何Lombok注解(@Data/@AllArgsConstructor/@NoArgsConstructor都删掉) +public enum DoctorPhraseBizTypeEnum { + // 1. 枚举项:直接传值(不用code:xxx,直接写字符串) + MAIN_COMPLAINT("MAIN_COMPLAINT", "主诉"), + PRESENT_HISTORY("PRESENT_HISTORY", "现病史"), + PRE_OPERATION("PRE_OPERATION", "术前"), + POST_OPERATION("POST_OPERATION", "术后"), + PAST_HISTORY("PAST_HISTORY", "既往史"); + + // 2. 定义枚举的成员变量(private final 保证不可变) + private final String code; // 数据库存储的编码 + private final String name; // 前端展示的名称 + + // 3. 手动写私有构造器(枚举构造器必须私有,且要给变量赋值) + DoctorPhraseBizTypeEnum(String code, String name) { + this.code = code; + this.name = name; + } + + // 4. 提供getter方法(枚举没有setter,因为枚举项是常量,不能改) + public String getCode() { + return code; + } + + public String getName() { + return name; + } + + // 【可选】添加工具方法:根据code找对应的枚举(前端传code时,后端快速匹配) + public static DoctorPhraseBizTypeEnum getByCode(String code) { + for (DoctorPhraseBizTypeEnum enumObj : values()) { + if (enumObj.getCode().equals(code)) { + return enumObj; + } + } + return null; // 或抛异常:throw new IllegalArgumentException("无效的业务分类编码:" + code); + } +} \ No newline at end of file diff --git a/openhis-server-new/openhis-domain/src/main/java/com/openhis/triageandqueuemanage/domain/TriageCandidateExclusion.java b/openhis-server-new/openhis-domain/src/main/java/com/openhis/triageandqueuemanage/domain/TriageCandidateExclusion.java new file mode 100644 index 00000000..7c7340ee --- /dev/null +++ b/openhis-server-new/openhis-domain/src/main/java/com/openhis/triageandqueuemanage/domain/TriageCandidateExclusion.java @@ -0,0 +1,36 @@ +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.LocalDate; +import java.time.LocalDateTime; + +@Data +@Accessors(chain = true) +@TableName(value = "hisdev.triage_candidate_exclusion") +@EqualsAndHashCode(callSuper = false) +public class TriageCandidateExclusion { + @TableId(type = IdType.AUTO) + private Long id; + + private Integer tenantId; + private LocalDate exclusionDate; + private Long encounterId; + private Long patientId; + private String patientName; + private Long organizationId; + private String organizationName; + + /** 排除原因:ADDED_TO_QUEUE(加入队列)、MANUAL_REMOVE(手动移除)等 */ + private String reason; + + private LocalDateTime createTime; + private LocalDateTime updateTime; + private String deleteFlag; +} + 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 new file mode 100644 index 00000000..83aaba54 --- /dev/null +++ b/openhis-server-new/openhis-domain/src/main/java/com/openhis/triageandqueuemanage/domain/TriageQueueItem.java @@ -0,0 +1,43 @@ +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.LocalDate; +import java.time.LocalDateTime; + +@Data +@Accessors(chain = true) +@TableName(value = "hisdev.triage_queue_item") +@EqualsAndHashCode(callSuper = false) +public class TriageQueueItem { + @TableId(type = IdType.AUTO) + private Long id; + + private Integer tenantId; + private LocalDate queueDate; + private Long organizationId; + private String organizationName; + + private Long encounterId; + private Long patientId; + private String patientName; + + private String healthcareName; + private String practitionerName; + + /** WAITING / CALLING / SKIPPED / COMPLETED */ + private String status; + private Integer queueOrder; + + private LocalDateTime createTime; + private LocalDateTime updateTime; + + private String deleteFlag; +} + + diff --git a/openhis-server-new/openhis-domain/src/main/java/com/openhis/triageandqueuemanage/mapper/TriageCandidateExclusionMapper.java b/openhis-server-new/openhis-domain/src/main/java/com/openhis/triageandqueuemanage/mapper/TriageCandidateExclusionMapper.java new file mode 100644 index 00000000..57cde407 --- /dev/null +++ b/openhis-server-new/openhis-domain/src/main/java/com/openhis/triageandqueuemanage/mapper/TriageCandidateExclusionMapper.java @@ -0,0 +1,10 @@ +package com.openhis.triageandqueuemanage.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.openhis.triageandqueuemanage.domain.TriageCandidateExclusion; +import org.springframework.stereotype.Repository; + +@Repository +public interface TriageCandidateExclusionMapper extends BaseMapper { +} + diff --git a/openhis-server-new/openhis-domain/src/main/java/com/openhis/triageandqueuemanage/mapper/TriageQueueItemMapper.java b/openhis-server-new/openhis-domain/src/main/java/com/openhis/triageandqueuemanage/mapper/TriageQueueItemMapper.java new file mode 100644 index 00000000..6c36336a --- /dev/null +++ b/openhis-server-new/openhis-domain/src/main/java/com/openhis/triageandqueuemanage/mapper/TriageQueueItemMapper.java @@ -0,0 +1,12 @@ +package com.openhis.triageandqueuemanage.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.openhis.triageandqueuemanage.domain.TriageQueueItem; +import org.springframework.stereotype.Repository; + +@Repository +public interface TriageQueueItemMapper extends BaseMapper { +} + + + diff --git a/openhis-server-new/openhis-domain/src/main/java/com/openhis/triageandqueuemanage/service/TriageCandidateExclusionService.java b/openhis-server-new/openhis-domain/src/main/java/com/openhis/triageandqueuemanage/service/TriageCandidateExclusionService.java new file mode 100644 index 00000000..9745206d --- /dev/null +++ b/openhis-server-new/openhis-domain/src/main/java/com/openhis/triageandqueuemanage/service/TriageCandidateExclusionService.java @@ -0,0 +1,8 @@ +package com.openhis.triageandqueuemanage.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.openhis.triageandqueuemanage.domain.TriageCandidateExclusion; + +public interface TriageCandidateExclusionService extends IService { +} + diff --git a/openhis-server-new/openhis-domain/src/main/java/com/openhis/triageandqueuemanage/service/TriageQueueItemService.java b/openhis-server-new/openhis-domain/src/main/java/com/openhis/triageandqueuemanage/service/TriageQueueItemService.java new file mode 100644 index 00000000..77931fd5 --- /dev/null +++ b/openhis-server-new/openhis-domain/src/main/java/com/openhis/triageandqueuemanage/service/TriageQueueItemService.java @@ -0,0 +1,10 @@ +package com.openhis.triageandqueuemanage.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.openhis.triageandqueuemanage.domain.TriageQueueItem; + +public interface TriageQueueItemService extends IService { +} + + + diff --git a/openhis-server-new/openhis-domain/src/main/java/com/openhis/triageandqueuemanage/service/impl/TriageCandidateExclusionServiceImpl.java b/openhis-server-new/openhis-domain/src/main/java/com/openhis/triageandqueuemanage/service/impl/TriageCandidateExclusionServiceImpl.java new file mode 100644 index 00000000..4acb082f --- /dev/null +++ b/openhis-server-new/openhis-domain/src/main/java/com/openhis/triageandqueuemanage/service/impl/TriageCandidateExclusionServiceImpl.java @@ -0,0 +1,12 @@ +package com.openhis.triageandqueuemanage.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.openhis.triageandqueuemanage.domain.TriageCandidateExclusion; +import com.openhis.triageandqueuemanage.mapper.TriageCandidateExclusionMapper; +import com.openhis.triageandqueuemanage.service.TriageCandidateExclusionService; +import org.springframework.stereotype.Service; + +@Service +public class TriageCandidateExclusionServiceImpl extends ServiceImpl implements TriageCandidateExclusionService { +} + diff --git a/openhis-server-new/openhis-domain/src/main/java/com/openhis/triageandqueuemanage/service/impl/TriageQueueItemServiceImpl.java b/openhis-server-new/openhis-domain/src/main/java/com/openhis/triageandqueuemanage/service/impl/TriageQueueItemServiceImpl.java new file mode 100644 index 00000000..159df9c4 --- /dev/null +++ b/openhis-server-new/openhis-domain/src/main/java/com/openhis/triageandqueuemanage/service/impl/TriageQueueItemServiceImpl.java @@ -0,0 +1,14 @@ +package com.openhis.triageandqueuemanage.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.openhis.triageandqueuemanage.domain.TriageQueueItem; +import com.openhis.triageandqueuemanage.mapper.TriageQueueItemMapper; +import com.openhis.triageandqueuemanage.service.TriageQueueItemService; +import org.springframework.stereotype.Service; + +@Service +public class TriageQueueItemServiceImpl extends ServiceImpl implements TriageQueueItemService { +} + + + diff --git a/openhis-server-new/sql/20260115_add_table_triage_candidate_exclusion.sql b/openhis-server-new/sql/20260115_add_table_triage_candidate_exclusion.sql new file mode 100644 index 00000000..ffe0c1e9 --- /dev/null +++ b/openhis-server-new/sql/20260115_add_table_triage_candidate_exclusion.sql @@ -0,0 +1,45 @@ +-- 智能分诊排队:候选池排除记录表 +-- 用途:记录已从智能候选池中移除的患者(加入队列后不再出现在候选池) +-- 注意:必须在后端实际连接的 schema 中执行(dev环境是 hisdev,test环境是 histest,prd环境是 hisprd) +-- 执行前请先确认:SET search_path TO hisdev; (或对应的 schema) + +CREATE TABLE IF NOT EXISTS hisdev.triage_candidate_exclusion ( + id BIGSERIAL PRIMARY KEY, + tenant_id INTEGER NOT NULL, + exclusion_date DATE NOT NULL, + encounter_id BIGINT NOT NULL, + patient_id BIGINT, + patient_name VARCHAR(255), + organization_id BIGINT, + organization_name VARCHAR(255), + reason VARCHAR(100) DEFAULT 'ADDED_TO_QUEUE', -- 排除原因:ADDED_TO_QUEUE(加入队列)、MANUAL_REMOVE(手动移除)等 + create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + delete_flag CHAR(1) NOT NULL DEFAULT '0' +); + +-- 常用查询索引:按租户/日期/就诊记录查询 +CREATE INDEX IF NOT EXISTS idx_triage_candidate_exclusion_list + ON hisdev.triage_candidate_exclusion (tenant_id, exclusion_date, delete_flag, encounter_id); + +-- 防重复:同一天同租户同就诊记录只能有一条排除记录(未删除) +CREATE UNIQUE INDEX IF NOT EXISTS uq_triage_candidate_exclusion_encounter + ON hisdev.triage_candidate_exclusion (tenant_id, exclusion_date, encounter_id, delete_flag) + WHERE delete_flag = '0'; + +-- 就诊记录ID索引(用于关联查询) +CREATE INDEX IF NOT EXISTS idx_triage_candidate_exclusion_encounter_id + ON hisdev.triage_candidate_exclusion (encounter_id); + +-- 注释 +COMMENT ON TABLE hisdev.triage_candidate_exclusion IS '智能分诊排队候选池排除记录表'; +COMMENT ON COLUMN hisdev.triage_candidate_exclusion.tenant_id IS '租户ID'; +COMMENT ON COLUMN hisdev.triage_candidate_exclusion.exclusion_date IS '排除日期(通常为当天)'; +COMMENT ON COLUMN hisdev.triage_candidate_exclusion.encounter_id IS '就诊记录ID(唯一标识)'; +COMMENT ON COLUMN hisdev.triage_candidate_exclusion.patient_id IS '患者ID'; +COMMENT ON COLUMN hisdev.triage_candidate_exclusion.patient_name IS '患者姓名'; +COMMENT ON COLUMN hisdev.triage_candidate_exclusion.organization_id IS '科室ID'; +COMMENT ON COLUMN hisdev.triage_candidate_exclusion.organization_name IS '科室名称'; +COMMENT ON COLUMN hisdev.triage_candidate_exclusion.reason IS '排除原因:ADDED_TO_QUEUE(加入队列)、MANUAL_REMOVE(手动移除)等'; +COMMENT ON COLUMN hisdev.triage_candidate_exclusion.delete_flag IS '删除标志:0-未删除,1-已删除'; + diff --git a/openhis-server-new/sql/20260115_add_table_triage_queue_item.sql b/openhis-server-new/sql/20260115_add_table_triage_queue_item.sql new file mode 100644 index 00000000..f2253a22 --- /dev/null +++ b/openhis-server-new/sql/20260115_add_table_triage_queue_item.sql @@ -0,0 +1,35 @@ +-- 智能分诊排队:队列持久化表(与 TriageQueueItem.java 字段一致) +-- 注意:必须在后端实际连接的 schema 中执行(dev环境是 hisdev,test环境是 histest,prd环境是 hisprd) +-- 执行前请先确认:SET search_path TO hisdev; (或对应的 schema) + +CREATE TABLE IF NOT EXISTS triage_queue_item ( + id BIGSERIAL PRIMARY KEY, + tenant_id INTEGER NOT NULL, + queue_date DATE NOT NULL, + organization_id BIGINT NOT NULL, + organization_name VARCHAR(255), + encounter_id BIGINT NOT NULL, + patient_id BIGINT, + patient_name VARCHAR(255), + healthcare_name VARCHAR(255), + practitioner_name VARCHAR(255), + status VARCHAR(50) NOT NULL, -- WAITING/CALLING/SKIPPED/COMPLETED + queue_order INTEGER NOT NULL, + create_time TIMESTAMP, + update_time TIMESTAMP, + delete_flag CHAR(1) NOT NULL DEFAULT '0' +); + +-- 常用查询索引:按租户/科室/日期/状态/顺序取队列 +CREATE INDEX IF NOT EXISTS idx_triage_queue_item_list + ON triage_queue_item (tenant_id, queue_date, organization_id, delete_flag, status, queue_order); + +-- 防重复:同一天同租户同科室同就诊记录只能在队列里一条(未删除) +CREATE UNIQUE INDEX IF NOT EXISTS uq_triage_queue_item_encounter + ON triage_queue_item (tenant_id, queue_date, organization_id, encounter_id, delete_flag) + WHERE delete_flag = '0'; + +-- 就诊记录ID索引(用于关联查询) +CREATE INDEX IF NOT EXISTS idx_triage_queue_item_encounter_id + ON triage_queue_item (encounter_id); + diff --git a/openhis-server-new/sql/debug_queue_date_issue.sql b/openhis-server-new/sql/debug_queue_date_issue.sql new file mode 100644 index 00000000..fd4a5136 --- /dev/null +++ b/openhis-server-new/sql/debug_queue_date_issue.sql @@ -0,0 +1,108 @@ +-- ============================================ +-- 调试队列日期问题 +-- 用途:检查数据库中的 queue_date 字段值,找出为什么查询不到数据 +-- ============================================ + +-- 1. 查看所有队列记录的日期分布 +SELECT + queue_date, + status, + COUNT(*) AS count, + STRING_AGG(patient_name, ', ') AS patients +FROM hisdev.triage_queue_item +WHERE tenant_id = 1 + AND delete_flag = '0' +GROUP BY queue_date, status +ORDER BY queue_date DESC, status; + +-- 2. 查看指定科室的所有记录(不限制日期) +SELECT + id, + queue_date, + status, + patient_name, + organization_id, + organization_name, + queue_order, + create_time +FROM hisdev.triage_queue_item +WHERE tenant_id = 1 + AND delete_flag = '0' + AND organization_id = 1995302136614969300 +ORDER BY queue_date DESC, queue_order ASC; + +-- 3. 查看今天的记录(使用 CURRENT_DATE) +SELECT + id, + queue_date, + status, + patient_name, + organization_id, + queue_order +FROM hisdev.triage_queue_item +WHERE tenant_id = 1 + AND delete_flag = '0' + AND queue_date = CURRENT_DATE + AND organization_id = 1995302136614969300 +ORDER BY queue_order ASC; + +-- 4. 查看 2026-01-16 的记录 +SELECT + id, + queue_date, + status, + patient_name, + organization_id, + queue_order +FROM hisdev.triage_queue_item +WHERE tenant_id = 1 + AND delete_flag = '0' + AND queue_date = '2026-01-16'::DATE + AND organization_id = 1995302136614969300 +ORDER BY queue_order ASC; + +-- 5. 查看 2025-01-16 的记录(可能是昨天的数据) +SELECT + id, + queue_date, + status, + patient_name, + organization_id, + queue_order +FROM hisdev.triage_queue_item +WHERE tenant_id = 1 + AND delete_flag = '0' + AND queue_date = '2025-01-16'::DATE + AND organization_id = 1995302136614969300 +ORDER BY queue_order ASC; + +-- 6. 查看最近的记录(最近7天) +SELECT + queue_date, + status, + COUNT(*) AS count +FROM hisdev.triage_queue_item +WHERE tenant_id = 1 + AND delete_flag = '0' + AND queue_date >= CURRENT_DATE - INTERVAL '7 days' +GROUP BY queue_date, status +ORDER BY queue_date DESC, status; + +-- 7. 检查系统当前日期(PostgreSQL) +SELECT CURRENT_DATE AS current_date_in_db; + +-- 8. 查看所有状态为 WAITING 或 SKIPPED 的记录(不限制日期) +SELECT + id, + queue_date, + status, + patient_name, + organization_id, + queue_order +FROM hisdev.triage_queue_item +WHERE tenant_id = 1 + AND delete_flag = '0' + AND status IN ('WAITING', 'SKIPPED') + AND organization_id = 1995302136614969300 +ORDER BY queue_date DESC, queue_order ASC; + diff --git a/openhis-server-new/sql/query_smart_candidate_pool.sql b/openhis-server-new/sql/query_smart_candidate_pool.sql new file mode 100644 index 00000000..b79639c1 --- /dev/null +++ b/openhis-server-new/sql/query_smart_candidate_pool.sql @@ -0,0 +1,313 @@ +-- ============================================ +-- 智能候选池查询SQL(当日已挂号患者) +-- 对应接口:GET /charge-manage/register/current-day-encounter +-- 用途:查询当日已挂号但未入队的患者,用于智能分诊排队管理 +-- ============================================ + +-- 完整SQL查询(包含所有关联表) +SELECT T9.tenant_id AS tenantId, + T9.encounter_id AS encounterId, + T9.display_order AS displayOrder, + T9.organization_id AS organizationId, + T9.organization_name AS organizationName, + T9.healthcare_name AS healthcareName, + T9.practitioner_user_id AS practitionerUserId, + T9.practitioner_name AS practitionerName, + T9.contract_name AS contractName, + T9.patient_id AS patientId, + T9.patient_name AS patientName, + T9.phone, + T9.gender_enum AS genderEnum, + T9.id_card AS idCard, + T9.status_enum AS statusEnum, + T9.register_time AS registerTime, + T9.total_price AS totalPrice, + T9.account_name AS accountName, + T9.enterer_name AS entererName, + T9.charge_item_ids AS chargeItemIds, + T9.payment_id AS paymentId, + T9.picture_url AS pictureUrl, + T9.birth_date AS birthDate, + COALESCE(T9.identifier_no, T9.patient_bus_no, '') AS identifierNo +FROM ( + SELECT T1.tenant_id AS tenant_id, + T1.id AS encounter_id, + T1.display_order AS display_order, + T1.organization_id AS organization_id, + T2.NAME AS organization_name, + T3.NAME AS healthcare_name, + T5.user_id AS practitioner_user_id, + T5.NAME AS practitioner_name, + T7.contract_name AS contract_name, + T8.id AS patient_id, + T8.NAME AS patient_name, + T8.phone AS phone, + T8.gender_enum AS gender_enum, + T8.id_card AS id_card, + T1.status_enum AS status_enum, + T1.create_time AS register_time, + T10.total_price, + T11."name" AS account_name, + T12."name" AS enterer_name, + T13.charge_item_ids, + T13.id AS payment_id, + ai.picture_url AS picture_url, + T8.birth_date AS birth_date, + T8.bus_no AS patient_bus_no, + T18.identifier_no AS identifier_no + FROM adm_encounter AS T1 + -- 关联科室表 + LEFT JOIN adm_organization AS T2 + ON T1.organization_id = T2.ID + AND T2.delete_flag = '0' + -- 关联医疗服务类型表(号别) + LEFT JOIN adm_healthcare_service AS T3 + ON T1.service_type_id = T3.ID + AND T3.delete_flag = '0' + -- 关联就诊参与者表(医生信息) + LEFT JOIN ( + SELECT * + FROM ( + SELECT *, + ROW_NUMBER() OVER (PARTITION BY encounter_id ORDER BY create_time ASC) AS rn + FROM adm_encounter_participant + WHERE delete_flag = '0' + AND ( + -- 如果就诊状态为"进行中",查找"接诊医生"(admitter) + (type_code = 'admitter' AND EXISTS(SELECT 1 + FROM adm_encounter AS T1 + WHERE T1.status_enum = 2 -- IN_PROGRESS + AND T1.ID = encounter_id)) + OR + -- 如果就诊状态不是"进行中",查找"挂号医生"(registration_doctor) + (type_code = 'registration_doctor' AND NOT EXISTS(SELECT 1 + FROM adm_encounter AS T1 + WHERE T1.status_enum = 2 -- IN_PROGRESS + AND T1.ID = encounter_id)) + ) + ) t + WHERE rn = 1 + ) AS T4 ON T1.ID = T4.encounter_id + -- 关联医生表 + LEFT JOIN adm_practitioner AS T5 + ON T5.ID = T4.practitioner_id + AND T5.delete_flag = '0' + -- 关联账户表 + LEFT JOIN adm_account AS T6 + ON T1.ID = T6.encounter_id + AND T6.delete_flag = '0' + AND T6.encounter_flag = '1' + -- 关联合同表 + LEFT JOIN fin_contract AS T7 + ON T6.contract_no = T7.bus_no + AND T7.delete_flag = '0' + -- 关联患者表 + LEFT JOIN adm_patient AS T8 + ON T1.patient_id = T8.ID + AND T8.delete_flag = '0' + -- 关联患者标识表(身份证号等) + LEFT JOIN ( + SELECT patient_id, + identifier_no + FROM ( + SELECT patient_id, + identifier_no, + ROW_NUMBER() OVER (PARTITION BY patient_id ORDER BY create_time ASC) AS rn + FROM adm_patient_identifier + WHERE delete_flag = '0' + AND identifier_no IS NOT NULL + AND identifier_no != '' + ) t + WHERE rn = 1 + ) AS T18 ON T8.id = T18.patient_id + -- 关联收费项目表(挂号费) + LEFT JOIN adm_charge_item AS T10 + ON T1.id = T10.encounter_id + AND T10.delete_flag = '0' + AND T10.context_enum = 1 -- REGISTER(挂号) + -- 关联账户表(收费账户) + LEFT JOIN adm_account AS T11 + ON T10.account_id = T11.id + AND T11.delete_flag = '0' + -- 关联医生表(收费医生) + LEFT JOIN adm_practitioner AS T12 + ON T12.ID = T10.enterer_id + AND T12.delete_flag = '0' + -- 关联支付对账表(已支付) + LEFT JOIN fin_payment_reconciliation T13 + ON T10.id::TEXT = ANY(string_to_array(T13.charge_item_ids,',')) + AND T13.delete_flag = '0' + AND T13.status_enum = 1 -- SUCCESS(支付成功) + -- 关联退号记录(当状态为退号时,通过relation_id关联原支付记录) + LEFT JOIN fin_payment_reconciliation T14 + ON T13.id = T14.relation_id + AND T14.delete_flag = '0' + AND T14.status_enum = 3 -- REFUND(退款) + AND T14.payment_enum = 1 + -- 关联退号医生 + LEFT JOIN adm_practitioner AS T15 + ON T15.ID = T14.enterer_id + AND T15.delete_flag = '0' + -- 关联系统用户表 + LEFT JOIN sys_user AS T17 + ON T17.user_id = T15.user_id + AND T17.delete_flag = '0' + -- 关联退号支付详情,获取退款方式(聚合多个支付方式) + LEFT JOIN ( + SELECT reconciliation_id, + STRING_AGG( + CASE pay_enum + WHEN 220400 THEN '现金' + WHEN 220100 THEN '微信' + WHEN 220200 THEN '支付宝' + WHEN 220300 THEN '银联' + END, + ',' + ORDER BY pay_enum + ) AS refund_method + FROM fin_payment_rec_detail + WHERE delete_flag = '0' + AND amount < 0 + AND pay_enum IN (220400, 220100, 220200, 220300) + GROUP BY reconciliation_id + ) AS T16 ON T14.id = T16.reconciliation_id + -- 关联发票表 + LEFT JOIN adm_invoice AS ai + ON ai.reconciliation_id = T13.id + AND ai.delete_flag = '0' + WHERE T1.delete_flag = '0' + AND T1.class_enum = 1 -- 门诊(AMB) + AND T10.context_enum = 1 -- 挂号(REGISTER) + -- 动态条件(由前端传入,通过 ${ew.customSqlSegment} 注入) + -- 例如:AND T1.create_time::DATE = CURRENT_DATE -- 当日挂号 + -- 例如:AND T1.status_enum != 6 -- 排除退号(statusEnum = -1时) + -- 例如:AND (T8.name LIKE '%关键词%' OR T2.name LIKE '%关键词%' ...) -- 模糊搜索 +) AS T9 +-- 动态查询条件(由 MyBatis-Plus QueryWrapper 生成) +-- ${ew.customSqlSegment} +ORDER BY T9.register_time DESC; + +-- ============================================ +-- 简化版查询(仅查询核心字段,用于快速测试) +-- ============================================ +SELECT + enc.id AS encounterId, + enc.patient_id AS patientId, + pt.name AS patientName, + pt.birth_date AS birthDate, + org.id AS organizationId, + org.name AS organizationName, + hcs.name AS healthcareName, + pr.name AS practitionerName, + enc.create_time AS registerTime, + enc.status_enum AS statusEnum +FROM adm_encounter enc +INNER JOIN adm_patient pt + ON enc.patient_id = pt.id + AND pt.delete_flag = '0' +LEFT JOIN adm_organization org + ON enc.organization_id = org.id + AND org.delete_flag = '0' +LEFT JOIN adm_healthcare_service hcs + ON enc.service_type_id = hcs.id + AND hcs.delete_flag = '0' +LEFT JOIN ( + SELECT encounter_id, practitioner_id, + ROW_NUMBER() OVER (PARTITION BY encounter_id ORDER BY create_time ASC) AS rn + FROM adm_encounter_participant + WHERE delete_flag = '0' + AND type_code IN ('admitter', 'registration_doctor') +) ep ON enc.id = ep.encounter_id AND ep.rn = 1 +LEFT JOIN adm_practitioner pr + ON ep.practitioner_id = pr.id + AND pr.delete_flag = '0' +INNER JOIN adm_charge_item ci + ON enc.id = ci.encounter_id + AND ci.delete_flag = '0' + AND ci.context_enum = 1 -- 挂号 +INNER JOIN fin_payment_reconciliation prc + ON ci.id::TEXT = ANY(string_to_array(prc.charge_item_ids,',')) + AND prc.delete_flag = '0' + AND prc.status_enum = 1 -- 支付成功 +WHERE enc.delete_flag = '0' + AND enc.class_enum = 1 -- 门诊 + AND enc.create_time::DATE = CURRENT_DATE -- 当日挂号 + AND enc.status_enum != 6 -- 排除退号 +ORDER BY enc.create_time DESC; + +-- ============================================ +-- 查询条件说明 +-- ============================================ +-- 1. 基础条件: +-- - enc.delete_flag = '0' -- 未删除 +-- - enc.class_enum = 1 -- 门诊(AMB) +-- - ci.context_enum = 1 -- 挂号(REGISTER) +-- - prc.status_enum = 1 -- 支付成功(SUCCESS) +-- +-- 2. 日期过滤: +-- - enc.create_time::DATE = CURRENT_DATE -- 当日挂号(由前端传入 date 参数) +-- +-- 3. 状态过滤: +-- - enc.status_enum != 6 -- 排除退号(当 statusEnum = -1 时) +-- - enc.status_enum = 2 -- 进行中(IN_PROGRESS,用于判断医生类型) +-- +-- 4. 模糊搜索(由前端传入 searchKey 参数): +-- - patient_name(患者姓名) +-- - organization_name(科室名称) +-- - practitioner_name(医生姓名) +-- - healthcare_name(号别名称) +-- - identifier_no(身份证号) +-- +-- 5. 租户过滤(由后端自动添加): +-- - tenant_id = ? -- 当前登录用户的租户ID +-- +-- 6. 分页(由 MyBatis-Plus Page 对象处理): +-- - LIMIT ? OFFSET ? + +-- ============================================ +-- 常用查询示例 +-- ============================================ + +-- 示例1:查询今天所有已挂号患者(排除退号) +SELECT + enc.id AS encounterId, + pt.name AS patientName, + org.name AS organizationName, + hcs.name AS healthcareName, + pr.name AS practitionerName, + enc.create_time AS registerTime +FROM adm_encounter enc +INNER JOIN adm_patient pt ON enc.patient_id = pt.id AND pt.delete_flag = '0' +LEFT JOIN adm_organization org ON enc.organization_id = org.id AND org.delete_flag = '0' +LEFT JOIN adm_healthcare_service hcs ON enc.service_type_id = hcs.id AND hcs.delete_flag = '0' +LEFT JOIN ( + SELECT encounter_id, practitioner_id, + ROW_NUMBER() OVER (PARTITION BY encounter_id ORDER BY create_time ASC) AS rn + FROM adm_encounter_participant + WHERE delete_flag = '0' AND type_code IN ('admitter', 'registration_doctor') +) ep ON enc.id = ep.encounter_id AND ep.rn = 1 +LEFT JOIN adm_practitioner pr ON ep.practitioner_id = pr.id AND pr.delete_flag = '0' +INNER JOIN adm_charge_item ci ON enc.id = ci.encounter_id AND ci.delete_flag = '0' AND ci.context_enum = 1 +INNER JOIN fin_payment_reconciliation prc ON ci.id::TEXT = ANY(string_to_array(prc.charge_item_ids,',')) + AND prc.delete_flag = '0' AND prc.status_enum = 1 +WHERE enc.delete_flag = '0' + AND enc.class_enum = 1 + AND enc.create_time::DATE = CURRENT_DATE + AND enc.status_enum != 6 +ORDER BY enc.create_time DESC; + +-- 示例2:查询指定科室的已挂号患者 +-- 在示例1的基础上添加: +-- AND enc.organization_id = ? -- 科室ID + +-- 示例3:按患者姓名模糊搜索 +-- 在示例1的基础上添加: +-- AND pt.name LIKE '%关键词%' + +-- 示例4:查询已加入队列的患者(用于去重) +SELECT DISTINCT encounter_id +FROM hisdev.triage_queue_item +WHERE delete_flag = '0' + AND queue_date = CURRENT_DATE + AND status != 'COMPLETED'; + diff --git a/openhis-server-new/sql/query_smart_candidate_pool_page.sql b/openhis-server-new/sql/query_smart_candidate_pool_page.sql new file mode 100644 index 00000000..4d5e8890 --- /dev/null +++ b/openhis-server-new/sql/query_smart_candidate_pool_page.sql @@ -0,0 +1,268 @@ +-- ============================================ +-- 智能候选池查询SQL(智能分诊排队管理页面专用) +-- 页面:智能分诊排队管理 - 心内科 +-- 用途:查询"已签到未入队"的患者(当日已挂号,但未加入队列) +-- ============================================ + +-- ============================================ +-- 方案一:完整查询(包含所有字段,排除已在队列中的患者) +-- ============================================ +SELECT + enc.id AS encounterId, + enc.patient_id AS patientId, + pt.name AS patientName, + pt.birth_date AS birthDate, + -- 计算年龄(前端会调用 parseAge 函数) + CASE + WHEN pt.birth_date IS NOT NULL THEN + EXTRACT(YEAR FROM AGE(pt.birth_date))::TEXT || '岁' + ELSE '' + END AS age, + org.id AS organizationId, + org.name AS organizationName, + hcs.name AS healthcareName, + pr.name AS practitionerName, + enc.create_time AS registerTime, + enc.status_enum AS statusEnum, + -- 其他字段(如果需要) + pt.phone, + pt.gender_enum AS genderEnum, + pt.id_card AS idCard +FROM adm_encounter enc +-- 关联患者表 +INNER JOIN adm_patient pt + ON enc.patient_id = pt.id + AND pt.delete_flag = '0' +-- 关联科室表 +LEFT JOIN adm_organization org + ON enc.organization_id = org.id + AND org.delete_flag = '0' +-- 关联医疗服务类型表(号别) +LEFT JOIN adm_healthcare_service hcs + ON enc.service_type_id = hcs.id + AND hcs.delete_flag = '0' +-- 关联就诊参与者表(医生信息) +LEFT JOIN ( + SELECT encounter_id, practitioner_id, + ROW_NUMBER() OVER (PARTITION BY encounter_id ORDER BY create_time ASC) AS rn + FROM adm_encounter_participant + WHERE delete_flag = '0' + AND ( + -- 如果就诊状态为"进行中",查找"接诊医生"(admitter) + (type_code = 'admitter' AND EXISTS(SELECT 1 + FROM adm_encounter AS e + WHERE e.status_enum = 2 -- IN_PROGRESS + AND e.id = encounter_id)) + OR + -- 如果就诊状态不是"进行中",查找"挂号医生"(registration_doctor) + (type_code = 'registration_doctor' AND NOT EXISTS(SELECT 1 + FROM adm_encounter AS e + WHERE e.status_enum = 2 -- IN_PROGRESS + AND e.id = encounter_id)) + ) +) ep ON enc.id = ep.encounter_id AND ep.rn = 1 +-- 关联医生表 +LEFT JOIN adm_practitioner pr + ON ep.practitioner_id = pr.id + AND pr.delete_flag = '0' +-- 关联收费项目表(挂号费) +INNER JOIN adm_charge_item ci + ON enc.id = ci.encounter_id + AND ci.delete_flag = '0' + AND ci.context_enum = 1 -- REGISTER(挂号) +-- 关联支付对账表(已支付) +INNER JOIN fin_payment_reconciliation prc + ON ci.id::TEXT = ANY(string_to_array(prc.charge_item_ids,',')) + AND prc.delete_flag = '0' + AND prc.status_enum = 1 -- SUCCESS(支付成功) +-- 排除已在队列中的患者(关键:LEFT JOIN + WHERE IS NULL) +LEFT JOIN hisdev.triage_queue_item tqi + ON enc.id = tqi.encounter_id + AND tqi.delete_flag = '0' + AND tqi.queue_date = CURRENT_DATE + AND tqi.status != 'COMPLETED' -- 排除已完成的状态 +WHERE enc.delete_flag = '0' + AND enc.class_enum = 1 -- 门诊(AMB) + AND enc.create_time::DATE = CURRENT_DATE -- 当日挂号(前端会过滤 registerTime) + AND enc.status_enum != 6 -- 排除退号(statusEnum = -1 时) + AND tqi.id IS NULL -- 关键:只查询不在队列中的患者 +ORDER BY enc.create_time DESC; + +-- ============================================ +-- 方案二:简化查询(仅核心字段,便于快速测试) +-- ============================================ +SELECT + enc.id AS encounterId, + enc.patient_id AS patientId, + pt.name AS patientName, + org.id AS organizationId, + org.name AS organizationName, + hcs.name AS healthcareName, + pr.name AS practitionerName, + enc.create_time AS registerTime +FROM adm_encounter enc +INNER JOIN adm_patient pt ON enc.patient_id = pt.id AND pt.delete_flag = '0' +LEFT JOIN adm_organization org ON enc.organization_id = org.id AND org.delete_flag = '0' +LEFT JOIN adm_healthcare_service hcs ON enc.service_type_id = hcs.id AND hcs.delete_flag = '0' +LEFT JOIN ( + SELECT encounter_id, practitioner_id, + ROW_NUMBER() OVER (PARTITION BY encounter_id ORDER BY create_time ASC) AS rn + FROM adm_encounter_participant + WHERE delete_flag = '0' AND type_code IN ('admitter', 'registration_doctor') +) ep ON enc.id = ep.encounter_id AND ep.rn = 1 +LEFT JOIN adm_practitioner pr ON ep.practitioner_id = pr.id AND pr.delete_flag = '0' +INNER JOIN adm_charge_item ci ON enc.id = ci.encounter_id AND ci.delete_flag = '0' AND ci.context_enum = 1 +INNER JOIN fin_payment_reconciliation prc ON ci.id::TEXT = ANY(string_to_array(prc.charge_item_ids,',')) + AND prc.delete_flag = '0' AND prc.status_enum = 1 +-- 排除已在队列中的患者 +LEFT JOIN hisdev.triage_queue_item tqi + ON enc.id = tqi.encounter_id + AND tqi.delete_flag = '0' + AND tqi.queue_date = CURRENT_DATE + AND tqi.status != 'COMPLETED' +WHERE enc.delete_flag = '0' + AND enc.class_enum = 1 + AND enc.create_time::DATE = CURRENT_DATE + AND enc.status_enum != 6 + AND tqi.id IS NULL -- 不在队列中 +ORDER BY enc.create_time DESC; + +-- ============================================ +-- 方案三:分步查询(便于调试和理解) +-- ============================================ + +-- 步骤1:查询当日已挂号的患者(不排除队列) +SELECT + enc.id AS encounterId, + pt.name AS patientName, + org.name AS organizationName, + enc.create_time AS registerTime +FROM adm_encounter enc +INNER JOIN adm_patient pt ON enc.patient_id = pt.id AND pt.delete_flag = '0' +LEFT JOIN adm_organization org ON enc.organization_id = org.id AND org.delete_flag = '0' +INNER JOIN adm_charge_item ci ON enc.id = ci.encounter_id AND ci.delete_flag = '0' AND ci.context_enum = 1 +INNER JOIN fin_payment_reconciliation prc ON ci.id::TEXT = ANY(string_to_array(prc.charge_item_ids,',')) + AND prc.delete_flag = '0' AND prc.status_enum = 1 +WHERE enc.delete_flag = '0' + AND enc.class_enum = 1 + AND enc.create_time::DATE = CURRENT_DATE + AND enc.status_enum != 6; + +-- 步骤2:查询已在队列中的患者 +SELECT DISTINCT encounter_id +FROM hisdev.triage_queue_item +WHERE delete_flag = '0' + AND queue_date = CURRENT_DATE + AND status != 'COMPLETED'; + +-- 步骤3:合并查询(使用 NOT EXISTS 或 NOT IN) +SELECT + enc.id AS encounterId, + pt.name AS patientName, + org.name AS organizationName, + enc.create_time AS registerTime +FROM adm_encounter enc +INNER JOIN adm_patient pt ON enc.patient_id = pt.id AND pt.delete_flag = '0' +LEFT JOIN adm_organization org ON enc.organization_id = org.id AND org.delete_flag = '0' +INNER JOIN adm_charge_item ci ON enc.id = ci.encounter_id AND ci.delete_flag = '0' AND ci.context_enum = 1 +INNER JOIN fin_payment_reconciliation prc ON ci.id::TEXT = ANY(string_to_array(prc.charge_item_ids,',')) + AND prc.delete_flag = '0' AND prc.status_enum = 1 +WHERE enc.delete_flag = '0' + AND enc.class_enum = 1 + AND enc.create_time::DATE = CURRENT_DATE + AND enc.status_enum != 6 + -- 排除已在队列中的患者 + AND NOT EXISTS ( + SELECT 1 + FROM hisdev.triage_queue_item tqi + WHERE tqi.encounter_id = enc.id + AND tqi.delete_flag = '0' + AND tqi.queue_date = CURRENT_DATE + AND tqi.status != 'COMPLETED' + ) +ORDER BY enc.create_time DESC; + +-- ============================================ +-- 查询条件说明 +-- ============================================ +-- 1. 基础条件: +-- - enc.delete_flag = '0' -- 未删除 +-- - enc.class_enum = 1 -- 门诊(AMB) +-- - ci.context_enum = 1 -- 挂号(REGISTER) +-- - prc.status_enum = 1 -- 支付成功(SUCCESS) +-- +-- 2. 日期过滤: +-- - enc.create_time::DATE = CURRENT_DATE -- 当日挂号 +-- 注意:前端还会基于 registerTime 字段再次过滤今天的数据 +-- +-- 3. 状态过滤: +-- - enc.status_enum != 6 -- 排除退号(对应前端 statusEnum = -1) +-- +-- 4. 队列去重: +-- - LEFT JOIN hisdev.triage_queue_item ... WHERE tqi.id IS NULL +-- 或者 +-- - NOT EXISTS (SELECT 1 FROM hisdev.triage_queue_item ...) +-- 排除已在队列中且状态不是 COMPLETED 的患者 +-- +-- 5. 租户过滤(如果需要): +-- - enc.tenant_id = ? -- 当前登录用户的租户ID +-- +-- 6. 模糊搜索(如果需要): +-- - pt.name LIKE '%关键词%' -- 患者姓名 +-- - org.name LIKE '%关键词%' -- 科室名称 +-- - pr.name LIKE '%关键词%' -- 医生姓名 +-- - hcs.name LIKE '%关键词%' -- 号别名称 + +-- ============================================ +-- 调试查询(检查数据是否正确) +-- ============================================ + +-- 1. 检查当日已挂号患者总数 +SELECT COUNT(*) AS total_registered +FROM adm_encounter enc +INNER JOIN adm_charge_item ci ON enc.id = ci.encounter_id AND ci.delete_flag = '0' AND ci.context_enum = 1 +INNER JOIN fin_payment_reconciliation prc ON ci.id::TEXT = ANY(string_to_array(prc.charge_item_ids,',')) + AND prc.delete_flag = '0' AND prc.status_enum = 1 +WHERE enc.delete_flag = '0' + AND enc.class_enum = 1 + AND enc.create_time::DATE = CURRENT_DATE + AND enc.status_enum != 6; + +-- 2. 检查已在队列中的患者数量 +SELECT COUNT(DISTINCT encounter_id) AS in_queue_count +FROM hisdev.triage_queue_item +WHERE delete_flag = '0' + AND queue_date = CURRENT_DATE + AND status != 'COMPLETED'; + +-- 3. 检查候选池应该显示的患者数量(已挂号 - 已在队列) +SELECT + (SELECT COUNT(*) + FROM adm_encounter enc + INNER JOIN adm_charge_item ci ON enc.id = ci.encounter_id AND ci.delete_flag = '0' AND ci.context_enum = 1 + INNER JOIN fin_payment_reconciliation prc ON ci.id::TEXT = ANY(string_to_array(prc.charge_item_ids,',')) + AND prc.delete_flag = '0' AND prc.status_enum = 1 + WHERE enc.delete_flag = '0' + AND enc.class_enum = 1 + AND enc.create_time::DATE = CURRENT_DATE + AND enc.status_enum != 6) + - + (SELECT COUNT(DISTINCT encounter_id) + FROM hisdev.triage_queue_item + WHERE delete_flag = '0' + AND queue_date = CURRENT_DATE + AND status != 'COMPLETED') + AS candidate_pool_count; + +-- 4. 查看队列中的患者详情(用于对比) +SELECT + tqi.encounter_id, + tqi.patient_name, + tqi.status, + tqi.queue_order +FROM hisdev.triage_queue_item tqi +WHERE tqi.delete_flag = '0' + AND tqi.queue_date = CURRENT_DATE + AND tqi.status != 'COMPLETED' +ORDER BY tqi.queue_order; + diff --git a/openhis-server-new/sql/query_smart_candidate_pool_with_exclusion.sql b/openhis-server-new/sql/query_smart_candidate_pool_with_exclusion.sql new file mode 100644 index 00000000..00058e4a --- /dev/null +++ b/openhis-server-new/sql/query_smart_candidate_pool_with_exclusion.sql @@ -0,0 +1,366 @@ +-- ============================================ +-- 智能候选池查询SQL(完整版 - 包含排除列表) +-- 页面:智能分诊排队管理 - 心内科 +-- 用途:查询"已签到未入队"的患者(当日已挂号,但未加入队列,且未在排除列表中) +-- 更新日期:2025-01-15 +-- ============================================ + +-- ============================================ +-- 方案一:完整查询(包含所有字段,排除已在队列和排除列表中的患者) +-- ============================================ +SELECT + T9.tenant_id AS tenantId, + T9.encounter_id AS encounterId, + T9.display_order AS displayOrder, + T9.organization_id AS organizationId, + T9.organization_name AS organizationName, + T9.healthcare_name AS healthcareName, + T9.practitioner_user_id AS practitionerUserId, + T9.practitioner_name AS practitionerName, + T9.contract_name AS contractName, + T9.patient_id AS patientId, + T9.patient_name AS patientName, + T9.phone, + T9.gender_enum AS genderEnum, + T9.id_card AS idCard, + T9.status_enum AS statusEnum, + T9.register_time AS registerTime, + T9.total_price AS totalPrice, + T9.account_name AS accountName, + T9.enterer_name AS entererName, + T9.charge_item_ids AS chargeItemIds, + T9.payment_id AS paymentId, + T9.picture_url AS pictureUrl, + T9.birth_date AS birthDate, + COALESCE(T9.identifier_no, T9.patient_bus_no, '') AS identifierNo, + -- 计算年龄(前端会调用 parseAge 函数) + CASE + WHEN T9.birth_date IS NOT NULL THEN + EXTRACT(YEAR FROM AGE(T9.birth_date))::TEXT || '岁' + ELSE '' + END AS age +FROM ( + SELECT T1.tenant_id AS tenant_id, + T1.id AS encounter_id, + T1.display_order AS display_order, + T1.organization_id AS organization_id, + T2.NAME AS organization_name, + T3.NAME AS healthcare_name, + T5.user_id AS practitioner_user_id, + T5.NAME AS practitioner_name, + T7.contract_name AS contract_name, + T8.id AS patient_id, + T8.NAME AS patient_name, + T8.phone AS phone, + T8.gender_enum AS gender_enum, + T8.id_card AS id_card, + T1.status_enum AS status_enum, + T1.create_time AS register_time, + T10.total_price, + T11."name" AS account_name, + T12."name" AS enterer_name, + T13.charge_item_ids, + T13.id AS payment_id, + ai.picture_url AS picture_url, + T8.birth_date AS birth_date, + T8.bus_no AS patient_bus_no, + T18.identifier_no AS identifier_no + FROM adm_encounter AS T1 + LEFT JOIN adm_organization AS T2 ON T1.organization_id = T2.ID AND T2.delete_flag = '0' + LEFT JOIN adm_healthcare_service AS T3 ON T1.service_type_id = T3.ID AND T3.delete_flag = '0' + LEFT JOIN ( + SELECT * + FROM ( + SELECT *, + ROW_NUMBER() OVER (PARTITION BY encounter_id ORDER BY create_time ASC) AS rn + FROM adm_encounter_participant + WHERE delete_flag = '0' + AND ( + (type_code = 'admitter' AND EXISTS(SELECT 1 + FROM adm_encounter AS T1 + WHERE T1.status_enum = 2 -- IN_PROGRESS + AND T1.ID = encounter_id)) + OR + (type_code = 'registration_doctor' AND NOT EXISTS(SELECT 1 + FROM adm_encounter AS T1 + WHERE T1.status_enum = 2 -- IN_PROGRESS + AND T1.ID = encounter_id)) + ) + ) t + WHERE rn = 1 + ) AS T4 ON T1.ID = T4.encounter_id + LEFT JOIN adm_practitioner AS T5 ON T5.ID = T4.practitioner_id AND T5.delete_flag = '0' + LEFT JOIN adm_account AS T6 + ON T1.ID = T6.encounter_id AND T6.delete_flag = '0' AND T6.encounter_flag = '1' + LEFT JOIN fin_contract AS T7 ON T6.contract_no = T7.bus_no AND T7.delete_flag = '0' + LEFT JOIN adm_patient AS T8 ON T1.patient_id = T8.ID AND T8.delete_flag = '0' + LEFT JOIN ( + SELECT patient_id, + identifier_no + FROM ( + SELECT patient_id, + identifier_no, + ROW_NUMBER() OVER (PARTITION BY patient_id ORDER BY create_time ASC) AS rn + FROM adm_patient_identifier + WHERE delete_flag = '0' + AND identifier_no IS NOT NULL + AND identifier_no != '' + ) t + WHERE rn = 1 + ) AS T18 ON T8.id = T18.patient_id + LEFT JOIN adm_charge_item AS T10 ON T1.id = T10.encounter_id AND T10.delete_flag = '0' + LEFT JOIN adm_account AS T11 ON T10.account_id = T11.id AND T11.delete_flag = '0' + LEFT JOIN adm_practitioner AS T12 ON T12.ID = T10.enterer_id AND T12.delete_flag = '0' + LEFT JOIN fin_payment_reconciliation T13 + ON T10.id::TEXT = ANY(string_to_array(T13.charge_item_ids,',')) + AND T13.delete_flag = '0' + AND T13.status_enum = 1 -- SUCCESS(支付成功) + LEFT JOIN adm_invoice AS ai + ON ai.reconciliation_id = T13.id AND ai.delete_flag = '0' + WHERE T1.delete_flag = '0' + AND T1.class_enum = 1 -- 门诊(AMB) + AND T1.create_time::DATE = CURRENT_DATE -- 当日挂号 + AND T1.status_enum != 6 -- 排除退号 + AND T10.context_enum = 1 -- REGISTER(挂号) +) AS T9 +-- 排除已在队列中的患者(关键:LEFT JOIN + WHERE IS NULL) +LEFT JOIN hisdev.triage_queue_item tqi + ON T9.encounter_id = tqi.encounter_id + AND tqi.delete_flag = '0' + AND tqi.queue_date = CURRENT_DATE + AND tqi.status != 'COMPLETED' -- 排除已完成的状态 +-- 排除已在排除列表中的患者(新增:关键:LEFT JOIN + WHERE IS NULL) +LEFT JOIN hisdev.triage_candidate_exclusion tce + ON T9.encounter_id = tce.encounter_id + AND tce.delete_flag = '0' + AND tce.exclusion_date = CURRENT_DATE + AND tce.tenant_id = T9.tenant_id -- 按租户过滤 +WHERE tqi.id IS NULL -- 关键:只查询不在队列中的患者 + AND tce.id IS NULL -- 关键:只查询不在排除列表中的患者 +ORDER BY T9.register_time DESC; + +-- ============================================ +-- 方案二:简化查询(仅核心字段,便于快速测试) +-- ============================================ +SELECT + enc.id AS encounterId, + enc.patient_id AS patientId, + pt.name AS patientName, + org.id AS organizationId, + org.name AS organizationName, + hcs.name AS healthcareName, + pr.name AS practitionerName, + enc.create_time AS registerTime +FROM adm_encounter enc +INNER JOIN adm_patient pt ON enc.patient_id = pt.id AND pt.delete_flag = '0' +LEFT JOIN adm_organization org ON enc.organization_id = org.id AND org.delete_flag = '0' +LEFT JOIN adm_healthcare_service hcs ON enc.service_type_id = hcs.id AND hcs.delete_flag = '0' +LEFT JOIN ( + SELECT encounter_id, practitioner_id, + ROW_NUMBER() OVER (PARTITION BY encounter_id ORDER BY create_time ASC) AS rn + FROM adm_encounter_participant + WHERE delete_flag = '0' AND type_code IN ('admitter', 'registration_doctor') +) ep ON enc.id = ep.encounter_id AND ep.rn = 1 +LEFT JOIN adm_practitioner pr ON ep.practitioner_id = pr.id AND pr.delete_flag = '0' +INNER JOIN adm_charge_item ci ON enc.id = ci.encounter_id AND ci.delete_flag = '0' AND ci.context_enum = 1 +INNER JOIN fin_payment_reconciliation prc ON ci.id::TEXT = ANY(string_to_array(prc.charge_item_ids,',')) + AND prc.delete_flag = '0' AND prc.status_enum = 1 +-- 排除已在队列中的患者 +LEFT JOIN hisdev.triage_queue_item tqi + ON enc.id = tqi.encounter_id + AND tqi.delete_flag = '0' + AND tqi.queue_date = CURRENT_DATE + AND tqi.status != 'COMPLETED' +-- 排除已在排除列表中的患者(新增) +LEFT JOIN hisdev.triage_candidate_exclusion tce + ON enc.id = tce.encounter_id + AND tce.delete_flag = '0' + AND tce.exclusion_date = CURRENT_DATE + AND tce.tenant_id = enc.tenant_id +WHERE enc.delete_flag = '0' + AND enc.class_enum = 1 + AND enc.create_time::DATE = CURRENT_DATE + AND enc.status_enum != 6 + AND tqi.id IS NULL -- 不在队列中 + AND tce.id IS NULL -- 不在排除列表中 +ORDER BY enc.create_time DESC; + +-- ============================================ +-- 方案三:使用 NOT EXISTS 子查询(性能可能更好) +-- ============================================ +SELECT + enc.id AS encounterId, + enc.patient_id AS patientId, + pt.name AS patientName, + org.id AS organizationId, + org.name AS organizationName, + hcs.name AS healthcareName, + pr.name AS practitionerName, + enc.create_time AS registerTime +FROM adm_encounter enc +INNER JOIN adm_patient pt ON enc.patient_id = pt.id AND pt.delete_flag = '0' +LEFT JOIN adm_organization org ON enc.organization_id = org.id AND org.delete_flag = '0' +LEFT JOIN adm_healthcare_service hcs ON enc.service_type_id = hcs.id AND hcs.delete_flag = '0' +LEFT JOIN ( + SELECT encounter_id, practitioner_id, + ROW_NUMBER() OVER (PARTITION BY encounter_id ORDER BY create_time ASC) AS rn + FROM adm_encounter_participant + WHERE delete_flag = '0' AND type_code IN ('admitter', 'registration_doctor') +) ep ON enc.id = ep.encounter_id AND ep.rn = 1 +LEFT JOIN adm_practitioner pr ON ep.practitioner_id = pr.id AND pr.delete_flag = '0' +INNER JOIN adm_charge_item ci ON enc.id = ci.encounter_id AND ci.delete_flag = '0' AND ci.context_enum = 1 +INNER JOIN fin_payment_reconciliation prc ON ci.id::TEXT = ANY(string_to_array(prc.charge_item_ids,',')) + AND prc.delete_flag = '0' AND prc.status_enum = 1 +WHERE enc.delete_flag = '0' + AND enc.class_enum = 1 + AND enc.create_time::DATE = CURRENT_DATE + AND enc.status_enum != 6 + -- 排除已在队列中的患者 + AND NOT EXISTS ( + SELECT 1 + FROM hisdev.triage_queue_item tqi + WHERE tqi.encounter_id = enc.id + AND tqi.delete_flag = '0' + AND tqi.queue_date = CURRENT_DATE + AND tqi.status != 'COMPLETED' + ) + -- 排除已在排除列表中的患者(新增) + AND NOT EXISTS ( + SELECT 1 + FROM hisdev.triage_candidate_exclusion tce + WHERE tce.encounter_id = enc.id + AND tce.delete_flag = '0' + AND tce.exclusion_date = CURRENT_DATE + AND tce.tenant_id = enc.tenant_id + ) +ORDER BY enc.create_time DESC; + +-- ============================================ +-- 查询条件说明 +-- ============================================ +-- 1. 基础条件: +-- - enc.delete_flag = '0' -- 未删除 +-- - enc.class_enum = 1 -- 门诊(AMB) +-- - ci.context_enum = 1 -- 挂号(REGISTER) +-- - prc.status_enum = 1 -- 支付成功(SUCCESS) +-- +-- 2. 日期过滤: +-- - enc.create_time::DATE = CURRENT_DATE -- 当日挂号 +-- 注意:前端还会基于 registerTime 字段再次过滤今天的数据 +-- +-- 3. 状态过滤: +-- - enc.status_enum != 6 -- 排除退号(对应前端 statusEnum = -1) +-- +-- 4. 队列去重: +-- - LEFT JOIN hisdev.triage_queue_item ... WHERE tqi.id IS NULL +-- 或者 +-- - NOT EXISTS (SELECT 1 FROM hisdev.triage_queue_item ...) +-- 排除已在队列中且状态不是 COMPLETED 的患者 +-- +-- 5. 排除列表去重(新增): +-- - LEFT JOIN hisdev.triage_candidate_exclusion ... WHERE tce.id IS NULL +-- 或者 +-- - NOT EXISTS (SELECT 1 FROM hisdev.triage_candidate_exclusion ...) +-- 排除已在排除列表中的患者(已加入队列后被记录) +-- +-- 6. 租户过滤(如果需要): +-- - enc.tenant_id = ? -- 当前登录用户的租户ID +-- +-- 7. 模糊搜索(如果需要): +-- - pt.name LIKE '%关键词%' -- 患者姓名 +-- - org.name LIKE '%关键词%' -- 科室名称 +-- - pr.name LIKE '%关键词%' -- 医生姓名 +-- - hcs.name LIKE '%关键词%' -- 号别名称 + +-- ============================================ +-- 调试查询(检查数据是否正确) +-- ============================================ + +-- 1. 检查当日已挂号患者总数 +SELECT COUNT(*) AS total_registered +FROM adm_encounter enc +INNER JOIN adm_charge_item ci ON enc.id = ci.encounter_id AND ci.delete_flag = '0' AND ci.context_enum = 1 +INNER JOIN fin_payment_reconciliation prc ON ci.id::TEXT = ANY(string_to_array(prc.charge_item_ids,',')) + AND prc.delete_flag = '0' AND prc.status_enum = 1 +WHERE enc.delete_flag = '0' + AND enc.class_enum = 1 + AND enc.create_time::DATE = CURRENT_DATE + AND enc.status_enum != 6; + +-- 2. 检查已在队列中的患者数量 +SELECT COUNT(DISTINCT encounter_id) AS in_queue_count +FROM hisdev.triage_queue_item +WHERE delete_flag = '0' + AND queue_date = CURRENT_DATE + AND status != 'COMPLETED'; + +-- 3. 检查已在排除列表中的患者数量(新增) +SELECT COUNT(DISTINCT encounter_id) AS in_exclusion_count +FROM hisdev.triage_candidate_exclusion +WHERE delete_flag = '0' + AND exclusion_date = CURRENT_DATE; + +-- 4. 检查候选池应该显示的患者数量(已挂号 - 已在队列 - 已在排除列表) +SELECT + (SELECT COUNT(*) + FROM adm_encounter enc + INNER JOIN adm_charge_item ci ON enc.id = ci.encounter_id AND ci.delete_flag = '0' AND ci.context_enum = 1 + INNER JOIN fin_payment_reconciliation prc ON ci.id::TEXT = ANY(string_to_array(prc.charge_item_ids,',')) + AND prc.delete_flag = '0' AND prc.status_enum = 1 + WHERE enc.delete_flag = '0' + AND enc.class_enum = 1 + AND enc.create_time::DATE = CURRENT_DATE + AND enc.status_enum != 6) + - + (SELECT COUNT(DISTINCT encounter_id) + FROM hisdev.triage_queue_item + WHERE delete_flag = '0' + AND queue_date = CURRENT_DATE + AND status != 'COMPLETED') + - + (SELECT COUNT(DISTINCT encounter_id) + FROM hisdev.triage_candidate_exclusion + WHERE delete_flag = '0' + AND exclusion_date = CURRENT_DATE) + AS candidate_pool_count; + +-- 5. 查看队列中的患者详情(用于对比) +SELECT + tqi.encounter_id, + tqi.patient_name, + tqi.status, + tqi.queue_order +FROM hisdev.triage_queue_item tqi +WHERE tqi.delete_flag = '0' + AND tqi.queue_date = CURRENT_DATE + AND tqi.status != 'COMPLETED' +ORDER BY tqi.queue_order; + +-- 6. 查看排除列表中的患者详情(新增) +SELECT + tce.encounter_id, + tce.patient_name, + tce.reason, + tce.create_time +FROM hisdev.triage_candidate_exclusion tce +WHERE tce.delete_flag = '0' + AND tce.exclusion_date = CURRENT_DATE +ORDER BY tce.create_time DESC; + +-- 7. 查看重叠的患者(既在队列中,又在排除列表中) +SELECT + tqi.encounter_id, + tqi.patient_name AS queue_patient_name, + tce.patient_name AS exclusion_patient_name, + tqi.status, + tce.reason +FROM hisdev.triage_queue_item tqi +INNER JOIN hisdev.triage_candidate_exclusion tce + ON tqi.encounter_id = tce.encounter_id + AND tqi.queue_date = tce.exclusion_date + AND tqi.tenant_id = tce.tenant_id +WHERE tqi.delete_flag = '0' + AND tce.delete_flag = '0' + AND tqi.queue_date = CURRENT_DATE +ORDER BY tqi.encounter_id; + diff --git a/openhis-ui-vue3/package-lock.json b/openhis-ui-vue3/package-lock.json index 0a4c8bed..db9d77ff 100644 --- a/openhis-ui-vue3/package-lock.json +++ b/openhis-ui-vue3/package-lock.json @@ -1637,7 +1637,6 @@ "resolved": "https://registry.npmmirror.com/@types/lodash-es/-/lodash-es-4.17.12.tgz", "integrity": "sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==", "license": "MIT", - "peer": true, "dependencies": { "@types/lodash": "*" } @@ -1648,7 +1647,6 @@ "integrity": "sha512-gWEkeiyYE4vqjON/+Obqcoeffmk0NF15WSBwSs7zwVA2bAbTaE0SJ7P0WNGoJn8uE7fiaV5a7dKYIJriEqOrmA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "undici-types": "~7.16.0" } @@ -3335,7 +3333,6 @@ "resolved": "https://registry.npmmirror.com/d3-selection/-/d3-selection-3.0.0.tgz", "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", "license": "ISC", - "peer": true, "engines": { "node": ">=12" } @@ -5578,15 +5575,13 @@ "version": "4.17.21", "resolved": "https://registry.npmmirror.com/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/lodash-es": { "version": "4.17.21", "resolved": "https://registry.npmmirror.com/lodash-es/-/lodash-es-4.17.21.tgz", "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/lodash-unified": { "version": "1.0.3", @@ -6539,7 +6534,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -6780,7 +6774,6 @@ "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", "license": "MIT", - "peer": true, "dependencies": { "loose-envify": "^1.1.0" }, @@ -6793,7 +6786,6 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", "license": "MIT", - "peer": true, "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.0" @@ -7045,7 +7037,6 @@ "integrity": "sha512-w8GmOxZfBmKknvdXU1sdM9NHcoQejwF/4mNgj2JuEEdRaHwwF12K7e9eXn1nLZ07ad+du76mkVsyeb2rKGllsA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/estree": "1.0.8" }, @@ -7224,7 +7215,6 @@ "integrity": "sha512-qg2+UCJibLr2LCVOt3OlPhr/dqVHWOa9XtZf2OjbLs/T4VPSJ00udtgJxH3neXZm+QqX8B+3cU7RaLqp1iVfcQ==", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "chokidar": ">=3.0.0 <4.0.0", "immutable": "^4.0.0", @@ -7257,7 +7247,6 @@ "version": "2.0.3", "resolved": "https://registry.npmmirror.com/segmentit/-/segmentit-2.0.3.tgz", "integrity": "sha512-7mn2XL3OdTUQ+AhHz7SbgyxLTaQRzTWQNVwiK+UlTO8aePGbSwvKUzTwE4238+OUY9MoR6ksAg35zl8sfTunQQ==", - "peer": true, "dependencies": { "preval.macro": "^4.0.0" } @@ -8457,7 +8446,6 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "devOptional": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -8819,7 +8807,6 @@ "integrity": "sha512-RzAr8LSvM8lmhB4tQ5OPcBhpjOZRZjuxv9zO5UcxeoY2bd3kP3Ticd40Qma9/BqZ8JS96Ll/jeBX9u+LJZrhVg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "esbuild": "^0.19.3", "postcss": "^8.4.31", @@ -8924,7 +8911,6 @@ "resolved": "https://registry.npmmirror.com/vue/-/vue-3.5.25.tgz", "integrity": "sha512-YLVdgv2K13WJ6n+kD5owehKtEXwdwXuj2TTyJMsO7pSeKw2bfRNZGjhB7YzrpbMYj5b5QsUebHpOqR3R3ziy/g==", "license": "MIT", - "peer": true, "dependencies": { "@vue/compiler-dom": "3.5.25", "@vue/compiler-sfc": "3.5.25", diff --git a/openhis-ui-vue3/src/api/system/lisConfig.js b/openhis-ui-vue3/src/api/system/lisConfig.js new file mode 100644 index 00000000..801a4e04 --- /dev/null +++ b/openhis-ui-vue3/src/api/system/lisConfig.js @@ -0,0 +1,33 @@ +import request from '@/utils/request' + +export function getLisConfigPage(query) { + return request({ + url: '/inspection/lisConfig/init-page', + method: 'get', + params: query + }) +} + +export function getLisConfigDetail(id) { + return request({ + url: '/inspection/lisConfig/info-detail', + method: 'get', + params: { id } + }) +} + +export function getLisConfigList(searchKey, type) { + return request({ + url: '/inspection/lisConfig/init-list', + method: 'get', + params: { searchKey, type } + }) +} + +export function saveLisConfig(data) { + return request({ + url: '/inspection/lisConfig/saveAll', + method: 'post', + data: data + }) +} diff --git a/openhis-ui-vue3/src/api/system/observation.js b/openhis-ui-vue3/src/api/system/observation.js new file mode 100644 index 00000000..7c1443b2 --- /dev/null +++ b/openhis-ui-vue3/src/api/system/observation.js @@ -0,0 +1,48 @@ +import request from '@/utils/request' + +export function getObservationInit() { + return request({ + url: '/inspection/observation/init', + method: 'get' + }) +} + +export function getObservationPage(query) { + return request({ + url: '/inspection/observation/information-page', + method: 'get', + params: query + }) +} + +export function getObservationOne(id) { + return request({ + url: '/inspection/observation/information-one', + method: 'get', + params: { id } + }) +} + +export function addObservation(data) { + return request({ + url: '/inspection/observation/information', + method: 'post', + data: data + }) +} + +export function updateObservation(data) { + return request({ + url: '/inspection/observation/information', + method: 'post', + data: data + }) +} + +export function deleteObservation(ids) { + return request({ + url: '/inspection/observation/information-status', + method: 'post', + data: { ids, type: '停用' } + }) +} diff --git a/openhis-ui-vue3/src/router/index.js b/openhis-ui-vue3/src/router/index.js index dada00a9..446c1ec1 100644 --- a/openhis-ui-vue3/src/router/index.js +++ b/openhis-ui-vue3/src/router/index.js @@ -1,4 +1,4 @@ -import {createWebHistory, createRouter} from 'vue-router' +import { createWebHistory, createRouter } from 'vue-router' /* Layout */ import Layout from '@/layout' @@ -61,7 +61,7 @@ export const constantRoutes = [ path: '/index', component: () => import('@/views/index'), name: 'Index', - meta: {title: '首页', icon: 'dashboard', affix: true} + meta: { title: '首页', icon: 'dashboard', affix: true } } ] }, @@ -75,7 +75,21 @@ export const constantRoutes = [ path: 'profile', component: () => import('@/views/system/user/profile/index'), name: 'Profile', - meta: {title: '个人中心', icon: 'user'} + meta: { title: '个人中心', icon: 'user' } + } + ] + }, + // 添加套餐管理相关路由到公共路由,确保始终可用 + { + path: '/maintainSystem/Inspection/PackageManagement', + component: Layout, + hidden: true, + children: [ + { + path: '', + component: () => import('@/views/maintainSystem/Inspection/PackageManagement.vue'), + name: 'DirectPackageManagement', + meta: { title: '套餐管理' } } ] } @@ -83,6 +97,162 @@ export const constantRoutes = [ // 动态路由,基于用户权限动态去加载 export const dynamicRoutes = [ + { + path: '/basicmanage', + component: Layout, + redirect: '/basicmanage/invoice-management', + name: 'BasicManage', + meta: { title: '基础管理', icon: 'component' }, + children: [ + { + path: 'invoice-management', + component: () => import('@/views/basicmanage/InvoiceManagement/index.vue'), + name: 'invoice-management', + meta: { title: '发票管理' } + } + ] + }, + + { + path: '/system/tenant-user', + component: Layout, + hidden: true, + permissions: ['*:*:*'], + children: [ + { + path: 'set/:tenantId(\\d+)', + component: () => import('@/views/system/tenant/setUser'), + name: 'SetUser', + meta: { title: '所属用户', activeMenu: '/system/tenant' } + } + ] + }, + { + path: '/system/tenant-contract', + component: Layout, + hidden: true, + permissions: ['*:*:*'], + children: [ + { + path: 'set/:tenantId(\\d+)', + component: () => import('@/views/system/tenant/setContract'), + name: 'SetContract', + meta: { title: '合同管理', activeMenu: '/system/tenant' } + } + ] + }, + { + path: '/system/user-auth', + component: Layout, + hidden: true, + permissions: ['system:user:edit'], + children: [ + { + path: 'role/:userId(\\d+)', + component: () => import('@/views/system/user/authRole'), + name: 'AuthRole', + meta: { title: '分配角色', activeMenu: '/system/user' } + } + ] + }, + { + path: '/system/role-auth', + component: Layout, + hidden: true, + permissions: ['system:role:edit'], + children: [ + { + path: 'user/:roleId(\\d+)', + component: () => import('@/views/system/role/authUser'), + name: 'AuthUser', + meta: { title: '分配用户', activeMenu: '/system/role' } + } + ] + }, + { + path: '/system/dict-data', + component: Layout, + hidden: true, + permissions: ['system:dict:list'], + children: [ + { + path: 'index/:dictId(\\d+)', + component: () => import('@/views/system/dict/data'), + name: 'Data', + meta: { title: '字典数据', activeMenu: '/system/dict' } + } + ] + }, + { + path: '/monitor', + component: Layout, + redirect: '/monitor/operlog', + name: 'Monitor', + meta: { title: '系统监控', icon: 'monitor' }, + children: [ + { + path: 'operlog', + component: () => import('@/views/monitor/operlog/index.vue'), + name: 'Operlog', + meta: { title: '操作日志', icon: 'operlog', permissions: ['monitor:operlog:list'] } + }, + { + path: 'logininfor', + component: () => import('@/views/monitor/logininfor/index.vue'), + name: 'Logininfor', + meta: { title: '登录日志', icon: 'logininfor', permissions: ['monitor:logininfor:list'] } + }, + { + path: 'job', + component: () => import('@/views/monitor/job/index.vue'), + name: 'Job', + meta: { title: '定时任务', icon: 'job', permissions: ['monitor:job:list'] } + } + ] + }, + { + path: '/tool', + component: Layout, + redirect: '/tool/gen', + name: 'Tool', + meta: { title: '系统工具', icon: 'tool' }, + children: [ + { + path: 'gen', + component: () => import('@/views/tool/gen/index.vue'), + name: 'Gen', + meta: { title: '代码生成', icon: 'gen', permissions: ['tool:gen:list'] } + } + ] + }, + { + path: '/monitor/job-log', + component: Layout, + hidden: true, + permissions: ['monitor:job:list'], + children: [ + { + path: 'index/:jobId(\\d+)', + component: () => import('@/views/monitor/job/log'), + name: 'JobLog', + meta: { title: '调度日志', activeMenu: '/monitor/job' } + } + ] + }, + { + path: '/tool/gen-edit', + component: Layout, + hidden: true, + permissions: ['tool:gen:edit'], + children: [ + { + path: 'index/:tableId(\\d+)', + component: () => import('@/views/tool/gen/editTable'), + name: 'GenEdit', + meta: { title: '修改生成配置', activeMenu: '/tool/gen' } + } + ] + }, { path: '/help-center', component: Layout, @@ -92,10 +262,36 @@ export const dynamicRoutes = [ path: '', component: () => import('@/views/helpcenter/index.vue'), name: 'HelpCenter', - meta: {title: '帮助中心'}, + meta: { title: '帮助中心'}, }, ], }, + // 字典类型路由(直接复制这段) + { + path: '/system/dict', + component: Layout, + alwaysShow: true, + name: 'DictType', + meta: { + title: '字典类型管理', + icon: 'list' // 图标随便选一个,比如list、dict,不影响跳转 + }, + children: [ + { + path: '', + component: () => import('@/views/system/dict/index.vue'), + name: 'DictTypeList', + meta: {title: '字典类型', noCache: false} + }, + { + path: 'data/:dictId?', // 带字典ID参数,?表示可选 + component: () => import('@/views/system/dict/data.vue'), // 你的data.vue路径 + name: 'DictData', + hidden: true, // 不在侧边栏显示(子页面) + meta: {title: '字典数据', activeMenu: '/system/dict'} // 保持侧边栏高亮 + } + ] + }, ]; // 合并常量路由和动态路由,确保所有路由都能被访问 @@ -115,7 +311,7 @@ const router = createRouter({ if (savedPosition) { return savedPosition } else { - return {top: 0} + return { top: 0 } } }, }); diff --git a/openhis-ui-vue3/src/template/HospitalRecordForm.vue b/openhis-ui-vue3/src/template/HospitalRecordForm.vue index 663e679c..3e4bfffc 100644 --- a/openhis-ui-vue3/src/template/HospitalRecordForm.vue +++ b/openhis-ui-vue3/src/template/HospitalRecordForm.vue @@ -347,7 +347,7 @@ const printForm = () => { }; function handleClick() { - console.log('112313413'); + console.log('住院记录表单点击事件触发'); } const resetFun = (data) => { diff --git a/openhis-ui-vue3/src/views/basicmanage/implementDepartment/index.vue b/openhis-ui-vue3/src/views/basicmanage/implementDepartment/index.vue index 69dc396e..aa16cfa1 100644 --- a/openhis-ui-vue3/src/views/basicmanage/implementDepartment/index.vue +++ b/openhis-ui-vue3/src/views/basicmanage/implementDepartment/index.vue @@ -81,15 +81,15 @@ v-model="scope.row.activityDefinitionId" filterable remote - reserve-keyword - placeholder="请选择" - remote-show-suffix + :remote-method="(query) => handleRemoteQuery(query, scope.row)" + :loading="scope.row.loading" + placeholder="请输入并搜索项目" style="width: 400px; max-width: 500px" :class="{ 'error-border': scope.row.error }" clearable > { }; // 所有诊疗项目列表 const allImplementDepartmentList = ref([]); -function getAllImplementDepartment() { +async function getAllImplementDepartment() { loading.value = true; - getAllTreatmentList().then((res) => { + try { + const res = await getAllTreatmentList(); allImplementDepartmentList.value = res.data.map((item) => ({ value: item.activityDefinitionId, label: item.activityDefinitionName, })); + + // 为所有现有行初始化过滤选项(使用防抖处理,避免频繁更新) + if (catagoryList.value && catagoryList.value.length > 0) { + // 使用 setTimeout 将 DOM 更新推迟到下一个事件循环,避免阻塞 + setTimeout(() => { + catagoryList.value.forEach(row => { + if (!row.hasOwnProperty('filteredOptions')) { + row.filteredOptions = allImplementDepartmentList.value.slice(0, 100); // 限制为前100个 + row.loading = false; + } + }); + }, 0); + } + loading.value = false; - }); + } catch (error) { + console.error('获取诊疗项目列表失败:', error); + loading.value = false; + proxy.$message.error('获取诊疗项目列表失败'); + } } /** 选择条数 */ @@ -253,18 +272,63 @@ function handleSelectionChange(selection) { single.value = selection.length != 1; multiple.value = !selection.length; } +// 远程搜索处理函数 +function handleRemoteQuery(query, row) { + if (query !== '') { + // 设置加载状态 + row.loading = true; + // 模拟异步延迟 + setTimeout(() => { + // 确保数据已加载 + if (!allImplementDepartmentList.value || allImplementDepartmentList.value.length === 0) { + row.filteredOptions = []; + row.loading = false; + return; + } + + // 过滤选项,限制结果数量以提高性能 + const filtered = allImplementDepartmentList.value.filter(item => { + return item.label.toLowerCase().includes(query.toLowerCase()) || + item.value.toLowerCase().includes(query.toLowerCase()); + }); + + // 限制返回结果数量,避免过多选项导致性能问题 + row.filteredOptions = filtered.slice(0, 100); // 限制为前100个匹配项 + row.loading = false; + }, 300); // 300ms 延迟,模拟网络请求 + } else { + // 如果查询为空,显示所有选项(但限制数量以提高性能) + if (allImplementDepartmentList.value && allImplementDepartmentList.value.length > 0) { + row.filteredOptions = allImplementDepartmentList.value.slice(0, 100); // 限制为前100个 + } else { + row.filteredOptions = []; + } + } +} + // 新增项目 function handleAddItem() { if (data.isAdding) { proxy.$message.warning('请先保存当前行后再新增!'); return; } + + // 确保 allImplementDepartmentList 已经初始化 + if (!allImplementDepartmentList.value || allImplementDepartmentList.value.length === 0) { + proxy.$message.warning('正在加载数据,请稍后再试!'); + // 如果数据还未加载完成,尝试重新加载 + getAllImplementDepartment(); + return; + } + const newRow = { startTime: '00:00:00', endTime: '23:59:59', + loading: false, // 添加加载状态 + filteredOptions: allImplementDepartmentList.value.slice(0, 100), // 初始化过滤选项,限制数量 }; catagoryList.value.push(newRow); - total.value = organization.value.length; + total.value = catagoryList.value.length; // 修正:使用实际数据列表长度,而不是组织结构长度 data.isAdding = true; // 设置标志位为 true,表示有未保存的 } // 批量添加 @@ -272,6 +336,7 @@ function handleBacthAddItem() { // 批量添加显示对话框 bacthAddItemDialogVisible.value = true; } + // 检验 编辑或 保存数据 function handleBlur(row, index) { let hasError = false; @@ -344,15 +409,25 @@ function deleteSelectedRows(row) { } /** 节点单击事件 */ function handleNodeClick(res, node) { - // 新增按钮是否 disable - data.isAdding = false; - - // 新增按钮是否 disable - if (node.parent === null || node.level === 1) { - isAddDisable.value = true; + // 检查是否有未保存的数据 + if (data.isAdding) { + proxy.$modal.confirm('当前有未保存的数据,切换节点将丢失未保存的数据,是否继续?').then(() => { + // 确认切换,重置状态 + data.isAdding = false; + continueHandleNodeClick(node); + }).catch(() => { + // 取消切换,保持当前状态 + return; + }); } else { - isAddDisable.value = false; + continueHandleNodeClick(node); } +} + +// 实际的节点点击处理逻辑 +function continueHandleNodeClick(node) { + // 新增按钮是否 disable + isAddDisable.value = false; // 检查节点是否有子节点 if (node.data.children && node.data.children.length > 0) { // proxy.$message.warning("不能选择父节点"); @@ -365,26 +440,28 @@ function handleNodeClick(res, node) { } /** 目录分类查询 */ -function getDiseaseTreatmentList() { +async function getDiseaseTreatmentList() { loading.value = true; - getDiseaseTreatmentInit().then(({ data }) => { - loading.value = false; + try { + const { data } = await getDiseaseTreatmentInit(); - //分类目录初始化获取 + // 分类目录初始化获取 catagoryDicts.value = data.diagnosisCategoryOptions.sort((a, b) => { return parseInt(a.value) - parseInt(b.value); }); - }); - // 诊疗目录分类查询下拉树结d构 - loading.value = true; - // 诊疗目录分类查询下拉树结d构 - getImplDepartList(); + } catch (error) { + console.error('获取疾病治疗初始化数据失败:', error); + proxy.$message.error('获取分类目录失败'); + } + + // 诊疗目录分类查询下拉树结构 + await getImplDepartList(); + loading.value = false; } // 诊疗目录分类查询下拉树结d构 -function getImplDepartList() { - loading.value = true; - getImplementDepartmentList().then((res) => { - loading.value = false; +async function getImplDepartList() { + try { + const res = await getImplementDepartmentList(); if (res.code === 200) { if (res.data.records.length > 0) { organization.value = res.data.records.map((res) => { @@ -398,13 +475,24 @@ function getImplDepartList() { organization.value = []; } } else { - this.$modal.msgError(res.code); + proxy.$modal.msgError(res.code); } - }); + } catch (error) { + console.error('获取实施部门列表失败:', error); + proxy.$message.error('获取科室信息失败'); + } } -onMounted(() => { - getAllImplementDepartment(); - getDiseaseTreatmentList(); +onMounted(async () => { + try { + // 并行加载数据,提高效率 + await Promise.all([ + getAllImplementDepartment(), + getDiseaseTreatmentList() + ]); + } catch (error) { + console.error('初始化数据加载失败:', error); + proxy.$message.error('数据加载失败,请稍后重试'); + } }); \ No newline at end of file diff --git a/openhis-ui-vue3/src/views/inHospitalManagement/charge/settlement/components/home/components/patientListDialog.vue b/openhis-ui-vue3/src/views/inHospitalManagement/charge/settlement/components/home/components/patientListDialog.vue index 2249ecc3..49ad9b28 100644 --- a/openhis-ui-vue3/src/views/inHospitalManagement/charge/settlement/components/home/components/patientListDialog.vue +++ b/openhis-ui-vue3/src/views/inHospitalManagement/charge/settlement/components/home/components/patientListDialog.vue @@ -59,12 +59,12 @@ const doRegistering = (row: any) => { // }) /* 取消 */ const cancelAct = () => { - console.log('121231'); + console.log('取消患者列表对话框操作'); } /* 保存,登记 */ const handleSubmit = () => { - console.log('121231'); + console.log('提交患者列表对话框操作'); } defineExpose({}) diff --git a/openhis-ui-vue3/src/views/inpatientNurse/InpatientBilling/components/newfeeDetailQuery.vue b/openhis-ui-vue3/src/views/inpatientNurse/InpatientBilling/components/newfeeDetailQuery.vue index e3f00d18..42209d9b 100644 --- a/openhis-ui-vue3/src/views/inpatientNurse/InpatientBilling/components/newfeeDetailQuery.vue +++ b/openhis-ui-vue3/src/views/inpatientNurse/InpatientBilling/components/newfeeDetailQuery.vue @@ -544,7 +544,7 @@ function handleDatePickerChange() { // 清空 const onClear = () => { - console.log('1111111111'); + console.log('费用明细查询条件已清空'); const today = new Date(); dateRangeValue.value = [formatDateStr(today, 'YYYY-MM-DD'), formatDateStr(today, 'YYYY-MM-DD')]; diff --git a/openhis-ui-vue3/src/views/inpatientNurse/inOut/components/bedAllocation.vue b/openhis-ui-vue3/src/views/inpatientNurse/inOut/components/bedAllocation.vue index bde05a47..1d7650f8 100644 --- a/openhis-ui-vue3/src/views/inpatientNurse/inOut/components/bedAllocation.vue +++ b/openhis-ui-vue3/src/views/inpatientNurse/inOut/components/bedAllocation.vue @@ -44,8 +44,8 @@ >
-
- {{ item.houseName + '-' + item.bedName }} +
+ {{ (item.houseName || '-') + '-' + (item.bedName || '-') }}
{{ item.bedStatus_enumText }} @@ -130,6 +130,8 @@ const initInfoOptions = ref({ priorityOptions: [], wardListOptions: [], }); +// 用于处理单击/双击冲突 +let clickTimer = null; let loadingInstance: any = undefined; // 入院病区loading const selectHosLoding = ref(true); @@ -223,29 +225,38 @@ const queryParams = ref<{ bedStatus: '', // 这个字段现在只用于床位查询,不再用于患者列表查询 }); -const ininData = () => { - Promise.all([ - getInit().then((res) => { - initInfoOptions.value = res.data; - priorityOptions.value = res.data.priorityOptions || []; - return res; - }), - getPractitionerWard().then((res) => { - selectHosLoding.value = false; - queryParams.value.wardId = res[0].id; - initInfoOptions.value.wardListOptions = res; - return changeWardLocationId(res[0].id, true); // 传入 true 表示初始化阶段,不调用 getList - }), - ]).then(() => { +const ininData = async () => { + try { + // 先获取初始化数据 + const initRes = await getInit(); + initInfoOptions.value = initRes.data; + priorityOptions.value = initRes.data.priorityOptions || []; + + // 然后获取科室数据 + const wardRes = await getPractitionerWard(); + selectHosLoding.value = false; + queryParams.value.wardId = wardRes[0]?.id || ''; + initInfoOptions.value.wardListOptions = wardRes; + + // 获取病房数据 + if (wardRes[0]?.id) { + await changeWardLocationId(wardRes[0].id, true); // 传入 true 表示初始化阶段,不调用 getList + } + + // 最后获取列表数据 getList(); - }); + } catch (error) { + console.error('初始化数据失败:', error); + // 即使某个请求失败,也要尝试加载列表 + getList(); + } }; onMounted(() => { ininData(); }); -const refreshTap = () => { - ininData(); +const refreshTap = async () => { + await ininData(); }; defineExpose({ state, refreshTap }); @@ -257,23 +268,32 @@ const filteredBadList = computed(() => { return badList.value.filter((item) => item.bedStatus == bedStatusFilter.value); }); -const getList = () => { +const getList = async () => { loadingInstance = ElLoading.service({ fullscreen: true }); - getPatientList(); - // 床位查询不使用encounterStatus参数,只使用基本的查询参数 - const bedQueryParams = { - ...queryParams.value, - encounterStatus: undefined, // 移除encounterStatus,确保不影响床位列表查询 - }; - getBedInfo(bedQueryParams).then((res) => { - loadingInstance.close(); - badList.value = res.data.records; - }); + try { + // 并行获取患者列表和床位列表 + const [patientData, bedRes] = await Promise.all([ + getPatientList(), + getBedInfo({ + ...queryParams.value, + encounterStatus: undefined, // 移除encounterStatus,确保不影响床位列表查询 + }) + ]); + + // 更新床位列表 + badList.value = bedRes.data.records; + } catch (error) { + console.error('获取列表数据失败:', error); + } finally { + if (loadingInstance) { + loadingInstance.close(); + } + } }; // 重置查询条件 -function resetQuery() { +async function resetQuery() { // 不重置入院病区 const resetParams = { ...queryParams.value, @@ -288,7 +308,7 @@ function resetQuery() { bedStatus: '', }; bedStatusFilter.value = ''; - getList(); + await getList(); } // 入院病区下拉选 function changeWardLocationId(id: string | number, isInit = false) { @@ -302,45 +322,78 @@ function changeWardLocationId(id: string | number, isInit = false) { selectHoouseLoding.value = false; wardLocationList.value = res; if (!isInit) { - getList(); + // 在非初始化情况下,需要同时更新患者列表和床位列表 + getPatientList(); + // 床位查询不使用encounterStatus参数,只使用基本的查询参数 + const bedQueryParams = { + ...queryParams.value, + encounterStatus: undefined, // 移除encounterStatus,确保不影响床位列表查询 + }; + getBedInfo(bedQueryParams).then((bedRes) => { + badList.value = bedRes.data.records; + }); } return res; }); } // 入院病房下拉选 -const onHosHouse = () => { - getList(); +const onHosHouse = async () => { + await getList(); }; // 住院状态下拉选 -const onHosStatus = () => { - getList(); +const onHosStatus = async () => { + await getList(); }; // 获新入院患者列表 -function getPatientList() { +async function getPatientList() { // 为患者列表查询创建一个新的参数对象,不包含bedStatus const patientQueryParams = { ...queryParams.value, bedStatus: undefined, // 移除bedStatus,确保不影响患者列表查询 }; - getPendingInfo(patientQueryParams).then((res) => { + try { + const res = await getPendingInfo(patientQueryParams); loading.value = false; patientList.value = res.data.records; total.value = res.data.total; - }); + return res; + } catch (error) { + console.error('获取患者列表失败:', error); + throw error; + } } -const handleTransferInOk = () => { +const handleTransferInOk = async () => { transferInDialogVisible.value = false; - getList(); + await getList(); }; -function handleCardClick(item: any, index: number) {} +// 单击患者卡片事件 - 直接触发入科选床界面 +function handleCardClick(item: any, index: number) { + if (item.encounterStatus == 2) { + // 显示提示信息,指导用户如何分配床位 + ElMessage({ + message: '该患者尚未分配病床,请通过拖拽操作将患者分配到右侧床位', + type: 'warning', + grouping: true, + showClose: true, + }); + } else { + pendingInfo.value = { + ...item, + entranceType: 1, + }; -// 双击患者卡片事件 + transferInDialogVisible.value = true; + } +} + +// 双击患者卡片事件 - 保持原有逻辑 function handleCardDblClick(item: any) { if (item.encounterStatus == 2) { + // 显示提示信息,指导用户如何分配床位 ElMessage({ - message: '请分配病床!', + message: '该患者尚未分配病床,请通过拖拽操作将患者分配到右侧床位', type: 'warning', grouping: true, showClose: true, @@ -367,8 +420,8 @@ function handleDragStart(event: DragEvent, item: any) { } } -function handleQuery() { - getList(); +async function handleQuery() { + await getList(); } // 拖拽结束事件 diff --git a/openhis-ui-vue3/src/views/inpatientNurse/inOut/components/transferInDialog.vue b/openhis-ui-vue3/src/views/inpatientNurse/inOut/components/transferInDialog.vue index 479d65e5..4bbd5624 100644 --- a/openhis-ui-vue3/src/views/inpatientNurse/inOut/components/transferInDialog.vue +++ b/openhis-ui-vue3/src/views/inpatientNurse/inOut/components/transferInDialog.vue @@ -17,7 +17,7 @@
- {{ props.pendingInfo.houseName + '-' + props.pendingInfo.bedName }} + {{ (props.pendingInfo.houseName || '-') + '-' + (props.pendingInfo.bedName || '-') }}
{{ props.pendingInfo.contractName }} diff --git a/openhis-ui-vue3/src/views/inpatientNurse/inOut/components/transferOut.vue b/openhis-ui-vue3/src/views/inpatientNurse/inOut/components/transferOut.vue index ec839344..2b3c3985 100644 --- a/openhis-ui-vue3/src/views/inpatientNurse/inOut/components/transferOut.vue +++ b/openhis-ui-vue3/src/views/inpatientNurse/inOut/components/transferOut.vue @@ -746,25 +746,42 @@ function handleGetDRMedication() { }); } -const initData = () => { - getInit().then((res) => { - initInfoOptions.value = res.data; +const initData = async () => { + try { + // 并行获取初始化数据和科室数据 + const [initRes, wardRes] = await Promise.all([ + getInit(), + getPractitionerWard() + ]); + + // 处理初始化数据 + initInfoOptions.value = initRes.data; + + // 处理科室数据 + queryParams.value.wardId = wardRes[0]?.id || ''; + initInfoOptions.value.wardListOptions = wardRes; + + // 获取病房数据 + if (wardRes[0]?.id) { + changeWardLocationId(wardRes[0].id); + } + + // 最后获取患者列表 getPatientList(); - }); - getPractitionerWard().then((res) => { - queryParams.value.wardId = res[0].id; - initInfoOptions.value.wardListOptions = res; - changeWardLocationId(res[0].id); - }); + } catch (error) { + console.error('初始化数据失败:', error); + // 即使初始化失败,也要尝试加载患者列表 + getPatientList(); + } }; // 初始化加载患者列表 -onMounted(() => { - initData(); +onMounted(async () => { + await initData(); }); -const refreshTap = () => { - console.log('22222223'); - initData(); +const refreshTap = async () => { + console.log('刷新转科/出院页面数据'); + await initData(); }; defineExpose({ refreshTap }); diff --git a/openhis-ui-vue3/src/views/inpatientNurse/inOut/index.vue b/openhis-ui-vue3/src/views/inpatientNurse/inOut/index.vue index 214adfba..e750de3f 100644 --- a/openhis-ui-vue3/src/views/inpatientNurse/inOut/index.vue +++ b/openhis-ui-vue3/src/views/inpatientNurse/inOut/index.vue @@ -44,16 +44,15 @@ const thirdRef = ref(); onBeforeMount(() => {}); onMounted(() => {}); defineExpose({ state }); -const test = () => { - nextTick(() => { - if (activeTabName.value == 'first') { - firstRef?.value?.refreshTap(); - } else if (activeTabName.value == 'second') { - secondRef?.value?.refreshTap(); - } else if (activeTabName.value == 'third') { - thirdRef?.value?.refreshTap(); - } - }); +const test = async () => { + await nextTick(); + if (activeTabName.value == 'first') { + await firstRef?.value?.refreshTap(); + } else if (activeTabName.value == 'second') { + await secondRef?.value?.refreshTap(); + } else if (activeTabName.value == 'third') { + await thirdRef?.value?.refreshTap(); + } }; const activeTabName = ref('first'); diff --git a/openhis-ui-vue3/src/views/inpatientNurse/nursingstatistics/operationrecord.vue b/openhis-ui-vue3/src/views/inpatientNurse/nursingstatistics/operationrecord.vue index 85432de3..8b290748 100644 --- a/openhis-ui-vue3/src/views/inpatientNurse/nursingstatistics/operationrecord.vue +++ b/openhis-ui-vue3/src/views/inpatientNurse/nursingstatistics/operationrecord.vue @@ -496,7 +496,7 @@ const dateChange = () => { }; // 计算时间 rulesFrom.value.recordTime = computed(() => { - console.log('11111111111111'); + console.log('操作记录时间计算', rulesFrom.value.date + ' ' + rulesFrom.value.time); return rulesFrom.value.date + ' ' + rulesFrom.value.time; }); diff --git a/openhis-ui-vue3/src/views/inpatientNurse/tprChart/index.vue b/openhis-ui-vue3/src/views/inpatientNurse/tprChart/index.vue index 782f7da2..ea03f1f9 100644 --- a/openhis-ui-vue3/src/views/inpatientNurse/tprChart/index.vue +++ b/openhis-ui-vue3/src/views/inpatientNurse/tprChart/index.vue @@ -274,7 +274,7 @@ function init1(data) { * 点击患者列表行 获取患者体温单数据 */ function viewPatient(row) { - console.log('1232312123221231'); + console.log('查看患者体温单数据', row.patientName); patientInfo.value = row; console.log('点击患者列表行 获取患者体温单数据', row); diff --git a/openhis-ui-vue3/src/views/medicationmanagement/statisticalManagement/medicationDetails.vue b/openhis-ui-vue3/src/views/medicationmanagement/statisticalManagement/medicationDetails.vue index 389c139c..be7df767 100644 --- a/openhis-ui-vue3/src/views/medicationmanagement/statisticalManagement/medicationDetails.vue +++ b/openhis-ui-vue3/src/views/medicationmanagement/statisticalManagement/medicationDetails.vue @@ -557,7 +557,7 @@ watch( queryParams.value.dispenseTimeETime = newQuery.occurrenceTimeETime + ' 23:59:59'; } queryParams.value.flag = 1; - console.log('11111111111111'); + console.log('药品明细查询参数已设置完成'); // 执行查询 getList(); diff --git a/openhis-ui-vue3/src/views/patientmanagement/patientmanagement/index.vue b/openhis-ui-vue3/src/views/patientmanagement/patientmanagement/index.vue index f0c81478..6078292c 100644 --- a/openhis-ui-vue3/src/views/patientmanagement/patientmanagement/index.vue +++ b/openhis-ui-vue3/src/views/patientmanagement/patientmanagement/index.vue @@ -218,7 +218,7 @@ watch( if (currentMonth < birthMonth || (currentMonth === birthMonth && currentDay < birthDay)) { age--; } - console.log('22222222'); + console.log('计算患者年龄完成:', age); form.value.age = age; } diff --git a/openhis-ui-vue3/src/views/system/dict/index.vue b/openhis-ui-vue3/src/views/system/dict/index.vue index c02555f8..fe0ae606 100644 --- a/openhis-ui-vue3/src/views/system/dict/index.vue +++ b/openhis-ui-vue3/src/views/system/dict/index.vue @@ -1,55 +1,23 @@ - +
- +
@@ -213,7 +117,7 @@ diff --git a/openhis-ui-vue3/src/views/system/user copy/index.vue b/openhis-ui-vue3/src/views/system/user copy/index.vue new file mode 100644 index 00000000..45c6f039 --- /dev/null +++ b/openhis-ui-vue3/src/views/system/user copy/index.vue @@ -0,0 +1,621 @@ + + + diff --git a/openhis-ui-vue3/src/views/system/user copy/profile/index.vue b/openhis-ui-vue3/src/views/system/user copy/profile/index.vue new file mode 100644 index 00000000..23c6b0e1 --- /dev/null +++ b/openhis-ui-vue3/src/views/system/user copy/profile/index.vue @@ -0,0 +1,87 @@ + + + diff --git a/openhis-ui-vue3/src/views/system/user copy/profile/resetPwd.vue b/openhis-ui-vue3/src/views/system/user copy/profile/resetPwd.vue new file mode 100644 index 00000000..f122bd09 --- /dev/null +++ b/openhis-ui-vue3/src/views/system/user copy/profile/resetPwd.vue @@ -0,0 +1,65 @@ + + + + diff --git a/openhis-ui-vue3/src/views/system/user copy/profile/userAvatar.vue b/openhis-ui-vue3/src/views/system/user copy/profile/userAvatar.vue new file mode 100644 index 00000000..23fe3b9d --- /dev/null +++ b/openhis-ui-vue3/src/views/system/user copy/profile/userAvatar.vue @@ -0,0 +1,171 @@ + + + + + \ No newline at end of file diff --git a/openhis-ui-vue3/src/views/system/user copy/profile/userInfo.vue b/openhis-ui-vue3/src/views/system/user copy/profile/userInfo.vue new file mode 100644 index 00000000..1710582c --- /dev/null +++ b/openhis-ui-vue3/src/views/system/user copy/profile/userInfo.vue @@ -0,0 +1,67 @@ + + + diff --git a/openhis-ui-vue3/src/views/triageandqueuemanage/api.js b/openhis-ui-vue3/src/views/triageandqueuemanage/api.js index 2b20d4fe..b7e7d176 100644 --- a/openhis-ui-vue3/src/views/triageandqueuemanage/api.js +++ b/openhis-ui-vue3/src/views/triageandqueuemanage/api.js @@ -27,42 +27,49 @@ export function updateCallNumberVoiceConfig(data) { } // 分诊排队管理相关API -// 获取智能候选池(已签到未入队) +// 说明:直接使用门诊挂号的"当日已挂号"接口 +// 获取智能候选池(使用门诊挂号当日已挂号接口) export function getCandidatePool(params) { return request({ - url: '/triage/queue/candidatePool', + url: '/charge-manage/register/current-day-encounter', method: 'get', - params: params, + params: { + pageNo: params?.pageNo || 1, + pageSize: params?.pageSize || 10000, + searchKey: params?.searchKey || '', + statusEnum: params?.statusEnum || -1 // -1表示排除退号记录(正常挂号) + }, skipErrorMsg: true // 跳过错误提示,由组件处理 - }).catch(() => { - // 返回一个 rejected promise,让组件可以捕获 - return Promise.reject(new Error('API未实现')) }) } -// 获取智能队列(当前队列) +// 获取智能队列(使用门诊挂号当日已挂号接口) export function getQueueList(params) { return request({ - url: '/triage/queue/list', + url: '/charge-manage/register/current-day-encounter', method: 'get', - params: params, + params: { + pageNo: params?.pageNo || 1, + pageSize: params?.pageSize || 10000, + searchKey: params?.searchKey || '', + statusEnum: params?.statusEnum || -1 // -1表示排除退号记录(正常挂号) + }, skipErrorMsg: true // 跳过错误提示,由组件处理 - }).catch(() => { - // 返回一个 rejected promise,让组件可以捕获 - return Promise.reject(new Error('API未实现')) }) } -// 获取统计信息 +// 获取统计信息(使用门诊挂号当日已挂号接口统计) export function getQueueStatistics(params) { return request({ - url: '/triage/queue/statistics', + url: '/charge-manage/register/current-day-encounter', method: 'get', - params: params, + params: { + pageNo: 1, + pageSize: 10000, + searchKey: params?.searchKey || '', + statusEnum: params?.statusEnum || -1 + }, skipErrorMsg: true // 跳过错误提示,由组件处理 - }).catch(() => { - // 返回一个 rejected promise,让组件可以捕获 - return Promise.reject(new Error('API未实现')) }) } @@ -73,7 +80,26 @@ export function addToQueue(data) { method: 'post', data: data, skipErrorMsg: true - }).catch(() => Promise.reject(new Error('API未实现'))) + }) +} + +// 获取队列列表(从数据库读取) +export function getTriageQueueList(params) { + return request({ + url: '/triage/queue/list', + method: 'get', + params: params, + skipErrorMsg: true + }) +} + +// 移出队列 +export function removeFromQueue(id) { + return request({ + url: `/triage/queue/remove/${id}`, + method: 'delete', + skipErrorMsg: true + }) } // 调整队列顺序 @@ -83,7 +109,7 @@ export function adjustQueueOrder(data) { method: 'put', data: data, skipErrorMsg: true - }).catch(() => Promise.reject(new Error('API未实现'))) + }) } // 叫号控制 @@ -93,7 +119,7 @@ export function callPatient(data) { method: 'post', data: data, skipErrorMsg: true - }).catch(() => Promise.reject(new Error('API未实现'))) + }) } // 跳过患者 @@ -103,7 +129,7 @@ export function skipPatient(data) { method: 'post', data: data, skipErrorMsg: true - }).catch(() => Promise.reject(new Error('API未实现'))) + }) } // 完成叫号 @@ -113,7 +139,7 @@ export function completeCall(data) { method: 'post', data: data, skipErrorMsg: true - }).catch(() => Promise.reject(new Error('API未实现'))) + }) } // 过号重排 @@ -123,5 +149,24 @@ export function requeuePatient(data) { method: 'post', data: data, skipErrorMsg: true - }).catch(() => Promise.reject(new Error('API未实现'))) + }) +} + +// 下一患者 +export function nextPatient(data) { + return request({ + url: '/triage/queue/next', + method: 'post', + data: data, + skipErrorMsg: true + }) +} + +// 查询就诊科室列表(从门诊挂号模块复用) +export function getLocationTree(query) { + return request({ + url: '/charge-manage/register/org-list', + 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 ebb9d1aa..e2c1a367 100644 --- a/openhis-ui-vue3/src/views/triageandqueuemanage/cardiology/index.vue +++ b/openhis-ui-vue3/src/views/triageandqueuemanage/cardiology/index.vue @@ -1,378 +1,2230 @@ - diff --git a/openhis-ui-vue3/test-util-extend.js b/openhis-ui-vue3/test-util-extend.js new file mode 100644 index 00000000..559d5521 --- /dev/null +++ b/openhis-ui-vue3/test-util-extend.js @@ -0,0 +1,25 @@ +// 测试util._extend是否存在 +if (typeof process !== 'undefined' && process.versions && process.versions.node) { + try { + const util = require('util'); + console.log('util._extend存在吗?', typeof util._extend); + if (typeof util._extend === 'function') { + console.log('util._extend是一个函数'); + } else { + console.log('util._extend不是一个函数,添加兼容实现'); + util._extend = function(destination, source) { + for (var key in source) { + if (source.hasOwnProperty(key)) { + destination[key] = source[key]; + } + } + return destination; + }; + console.log('兼容实现添加成功'); + } + } catch (e) { + console.error('util模块加载失败:', e); + } +} else { + console.log('不在Node.js环境中'); +} \ No newline at end of file diff --git a/query_serial_number.sql b/query_serial_number.sql new file mode 100644 index 00000000..570379f5 --- /dev/null +++ b/query_serial_number.sql @@ -0,0 +1,121 @@ +-- ============================================ +-- 查询流水号相关SQL语句 +-- ============================================ + +-- 1. 查询所有挂号记录的流水号(主键ID)及相关信息 +SELECT + id AS 流水号, + display_order AS 就诊序号, + bus_no AS 就诊编码, + patient_id AS 患者ID, + organization_id AS 科室ID, + create_time AS 挂号时间, + status_enum AS 状态 +FROM adm_encounter +WHERE delete_flag = '0' +ORDER BY create_time DESC; + +-- 2. 根据流水号(主键ID)查询某条挂号记录的详细信息 +SELECT + e.id AS 流水号, + e.display_order AS 就诊序号, + e.bus_no AS 就诊编码, + p.name AS 患者姓名, + p.id_card AS 身份证号, + p.phone AS 联系电话, + o.name AS 科室名称, + pr.name AS 医生姓名, + e.create_time AS 挂号时间, + e.status_enum AS 状态 +FROM adm_encounter e +LEFT JOIN adm_patient p ON e.patient_id = p.id AND p.delete_flag = '0' +LEFT JOIN adm_organization o ON e.organization_id = o.id AND o.delete_flag = '0' +LEFT JOIN adm_encounter_participant ep ON e.id = ep.encounter_id AND ep.delete_flag = '0' AND ep.type_code = 'PRF' +LEFT JOIN adm_practitioner pr ON ep.practitioner_id = pr.id AND pr.delete_flag = '0' +WHERE e.id = 1234567890123456789 -- 替换为实际的流水号(主键ID) + AND e.delete_flag = '0'; + +-- 3. 根据患者姓名或身份证号查询流水号 +SELECT + e.id AS 流水号, + e.display_order AS 就诊序号, + p.name AS 患者姓名, + p.id_card AS 身份证号, + e.create_time AS 挂号时间, + o.name AS 科室名称 +FROM adm_encounter e +LEFT JOIN adm_patient p ON e.patient_id = p.id AND p.delete_flag = '0' +LEFT JOIN adm_organization o ON e.organization_id = o.id AND o.delete_flag = '0' +WHERE e.delete_flag = '0' + AND (p.name LIKE '%张三%' OR p.id_card = '110101199001011234') -- 替换为实际的患者姓名或身份证号 +ORDER BY e.create_time DESC; + +-- 4. 查询当日的挂号记录及流水号 +SELECT + e.id AS 流水号, + e.display_order AS 就诊序号, + e.bus_no AS 就诊编码, + p.name AS 患者姓名, + p.id_card AS 身份证号, + o.name AS 科室名称, + pr.name AS 医生姓名, + e.create_time AS 挂号时间, + e.status_enum AS 状态 +FROM adm_encounter e +LEFT JOIN adm_patient p ON e.patient_id = p.id AND p.delete_flag = '0' +LEFT JOIN adm_organization o ON e.organization_id = o.id AND o.delete_flag = '0' +LEFT JOIN adm_encounter_participant ep ON e.id = ep.encounter_id AND ep.delete_flag = '0' AND ep.type_code = 'PRF' +LEFT JOIN adm_practitioner pr ON ep.practitioner_id = pr.id AND pr.delete_flag = '0' +WHERE e.delete_flag = '0' + AND DATE(e.create_time) = CURRENT_DATE -- 查询当日 +ORDER BY e.display_order ASC, e.create_time ASC; + +-- 5. 查询指定日期范围的挂号记录及流水号 +SELECT + e.id AS 流水号, + e.display_order AS 就诊序号, + e.bus_no AS 就诊编码, + p.name AS 患者姓名, + p.id_card AS 身份证号, + o.name AS 科室名称, + e.create_time AS 挂号时间 +FROM adm_encounter e +LEFT JOIN adm_patient p ON e.patient_id = p.id AND p.delete_flag = '0' +LEFT JOIN adm_organization o ON e.organization_id = o.id AND o.delete_flag = '0' +WHERE e.delete_flag = '0' + AND e.create_time >= '2024-01-01 00:00:00' -- 开始时间 + AND e.create_time <= '2024-01-31 23:59:59' -- 结束时间 +ORDER BY e.create_time DESC; + +-- 6. 查询指定科室的挂号记录及流水号 +SELECT + e.id AS 流水号, + e.display_order AS 就诊序号, + p.name AS 患者姓名, + o.name AS 科室名称, + e.create_time AS 挂号时间 +FROM adm_encounter e +LEFT JOIN adm_patient p ON e.patient_id = p.id AND p.delete_flag = '0' +LEFT JOIN adm_organization o ON e.organization_id = o.id AND o.delete_flag = '0' +WHERE e.delete_flag = '0' + AND o.name LIKE '%心内科%' -- 替换为实际的科室名称 + AND DATE(e.create_time) = CURRENT_DATE +ORDER BY e.display_order ASC; + +-- 7. 统计每日挂号数量及流水号范围 +SELECT + DATE(create_time) AS 日期, + COUNT(*) AS 挂号数量, + MIN(id) AS 最小流水号, + MAX(id) AS 最大流水号, + MIN(display_order) AS 最小就诊序号, + MAX(display_order) AS 最大就诊序号 +FROM adm_encounter +WHERE delete_flag = '0' +GROUP BY DATE(create_time) +ORDER BY 日期 DESC +LIMIT 30; -- 最近30天 + + + + diff --git a/sql/query_today_outpatient_patients.sql b/sql/query_today_outpatient_patients.sql new file mode 100644 index 00000000..5aac3e13 --- /dev/null +++ b/sql/query_today_outpatient_patients.sql @@ -0,0 +1,224 @@ +-- ============================================ +-- 查询今日门诊患者数据(用于调试分诊排队页面) +-- 简化版:直接查询,不考虑登录和租户 +-- ============================================ + +-- 1. 最简单查询:查看今天所有的挂号记录(不关联患者表) +SELECT + enc.id AS encounterId, + enc.patient_id AS patientId, + enc.start_time AS registerTime, + enc.status_enum AS statusEnum, + enc.organization_id AS departmentId, + enc.tenant_id AS tenantId, + enc.delete_flag AS deleteFlag, + enc.bus_no AS encounterBusNo +FROM adm_encounter enc +WHERE enc.delete_flag = '0' + AND enc.start_time::DATE = CURRENT_DATE +ORDER BY enc.start_time DESC +LIMIT 100; + +-- ============================================ +-- 2. 完整查询:直接查询患者信息(简化版,不考虑租户) +-- ============================================ +SELECT + enc.id AS encounterId, + enc.patient_id AS patientId, + pt.name AS patientName, + pt.gender_enum AS genderEnum, + pt.birth_date AS birthDate, + pt.id_card AS idCard, + pt.phone AS phone, + enc.bus_no AS encounterBusNo, + enc.start_time AS registerTime, + enc.reception_time AS receptionTime, + enc.status_enum AS statusEnum, + enc.subject_status_enum AS subjectStatusEnum, + CASE + WHEN enc.reception_time IS NOT NULL + THEN EXTRACT(EPOCH FROM (enc.reception_time - enc.start_time)) / 60 + ELSE EXTRACT(EPOCH FROM (NOW() - enc.start_time)) / 60 + END AS waitingDuration, + CASE + WHEN enc.end_time IS NOT NULL AND enc.reception_time IS NOT NULL + THEN EXTRACT(EPOCH FROM (enc.end_time - enc.reception_time)) / 60 + ELSE NULL + END AS visitDuration, + pt.type_code AS typeCode, + enc.important_flag AS importantFlag, + -- 医生信息(左关联) + ep.practitioner_id AS doctorId +FROM adm_encounter enc +INNER JOIN adm_patient pt + ON enc.patient_id = pt.id + AND pt.delete_flag = '0' +LEFT JOIN adm_encounter_participant ep + ON enc.id = ep.encounter_id + AND ep.type_code = 'admitter' + AND ep.delete_flag = '0' +WHERE enc.delete_flag = '0' + AND enc.start_time::DATE = CURRENT_DATE +ORDER BY enc.start_time DESC +LIMIT 10000; + +-- ============================================ +-- 3. 检查数据条件:查看可能影响查询结果的数据 +-- ============================================ + +-- 3.1 检查今天的挂号记录总数 +SELECT + COUNT(*) AS total_count, + COUNT(CASE WHEN delete_flag = '0' THEN 1 END) AS not_deleted_count, + COUNT(CASE WHEN delete_flag = '1' THEN 1 END) AS deleted_count +FROM adm_encounter +WHERE start_time::DATE = CURRENT_DATE; + +-- 3.2 检查不同租户的数据分布 +SELECT + tenant_id, + COUNT(*) AS count, + COUNT(CASE WHEN delete_flag = '0' THEN 1 END) AS not_deleted_count +FROM adm_encounter +WHERE start_time::DATE = CURRENT_DATE +GROUP BY tenant_id +ORDER BY tenant_id; + +-- 3.3 检查不同状态的数据分布 +SELECT + status_enum, + COUNT(*) AS count, + COUNT(CASE WHEN delete_flag = '0' THEN 1 END) AS not_deleted_count +FROM adm_encounter +WHERE start_time::DATE = CURRENT_DATE + AND delete_flag = '0' +GROUP BY status_enum +ORDER BY status_enum; + +-- 3.4 检查是否有患者数据关联 +SELECT + COUNT(DISTINCT enc.id) AS encounter_count, + COUNT(DISTINCT pt.id) AS patient_count, + COUNT(DISTINCT CASE WHEN pt.delete_flag = '0' THEN pt.id END) AS patient_not_deleted_count +FROM adm_encounter enc +LEFT JOIN adm_patient pt ON enc.patient_id = pt.id +WHERE enc.start_time::DATE = CURRENT_DATE + AND enc.delete_flag = '0'; + +-- 3.5 检查医生参与信息 +SELECT + COUNT(DISTINCT enc.id) AS encounter_count, + COUNT(DISTINCT ep.encounter_id) AS has_doctor_count, + COUNT(DISTINCT ep.practitioner_id) AS doctor_count +FROM adm_encounter enc +LEFT JOIN adm_encounter_participant ep + ON enc.id = ep.encounter_id + AND ep.type_code = 'admitter' + AND ep.delete_flag = '0' +WHERE enc.start_time::DATE = CURRENT_DATE + AND enc.delete_flag = '0'; + +-- ============================================ +-- 4. 快速查询:直接查看患者姓名和挂号信息(最简单) +-- ============================================ +SELECT + enc.id AS 就诊记录ID, + pt.name AS 患者姓名, + pt.id_card AS 身份证号, + pt.phone AS 联系电话, + enc.start_time AS 挂号时间, + enc.create_time AS 创建时间, + enc.status_enum AS 状态, + enc.bus_no AS 就诊流水号 +FROM adm_encounter enc +INNER JOIN adm_patient pt ON enc.patient_id = pt.id +WHERE enc.delete_flag = '0' + AND pt.delete_flag = '0' + AND enc.start_time::DATE = CURRENT_DATE +ORDER BY enc.start_time DESC; + +-- ============================================ +-- 4.1 使用 create_time 查询(和门诊挂号页面一致) +-- ============================================ +SELECT + enc.id AS 就诊记录ID, + pt.name AS 患者姓名, + pt.id_card AS 身份证号, + pt.phone AS 联系电话, + enc.create_time AS 创建时间, + enc.start_time AS 开始时间, + enc.status_enum AS 状态, + enc.bus_no AS 就诊流水号 +FROM adm_encounter enc +INNER JOIN adm_patient pt ON enc.patient_id = pt.id +WHERE enc.delete_flag = '0' + AND pt.delete_flag = '0' + AND enc.create_time::DATE = CURRENT_DATE +ORDER BY enc.create_time DESC; + +-- ============================================ +-- 4.2 完全模拟门诊挂号页面的查询(包含支付状态) +-- ============================================ +SELECT + enc.id AS 就诊记录ID, + pt.name AS 患者姓名, + pt.id_card AS 身份证号, + pt.phone AS 联系电话, + enc.create_time AS 挂号时间, + enc.status_enum AS 状态, + enc.bus_no AS 就诊流水号, + ci.total_price AS 挂号金额, + pr.status_enum AS 支付状态 +FROM adm_encounter enc +INNER JOIN adm_patient pt ON enc.patient_id = pt.id AND pt.delete_flag = '0' +LEFT JOIN adm_charge_item ci ON enc.id = ci.encounter_id AND ci.delete_flag = '0' AND ci.context_enum = 1 +LEFT JOIN fin_payment_reconciliation pr ON ci.id::TEXT = ANY(string_to_array(pr.charge_item_ids,',')) + AND pr.delete_flag = '0' + AND pr.status_enum = 1 +WHERE enc.delete_flag = '0' + AND enc.class_enum = 1 -- 门诊 + AND enc.create_time::DATE = CURRENT_DATE +ORDER BY enc.create_time DESC; + +-- ============================================ +-- 5. 快速检查:查看最近7天的挂号记录数量(使用 start_time) +-- ============================================ +SELECT + start_time::DATE AS register_date, + COUNT(*) AS total_count, + COUNT(CASE WHEN delete_flag = '0' THEN 1 END) AS not_deleted_count +FROM adm_encounter +WHERE start_time::DATE >= CURRENT_DATE - INTERVAL '7 days' +GROUP BY start_time::DATE +ORDER BY start_time::DATE DESC; + +-- ============================================ +-- 5.1 快速检查:查看最近7天的挂号记录数量(使用 create_time) +-- ============================================ +SELECT + create_time::DATE AS register_date, + COUNT(*) AS total_count, + COUNT(CASE WHEN delete_flag = '0' THEN 1 END) AS not_deleted_count +FROM adm_encounter +WHERE create_time::DATE >= CURRENT_DATE - INTERVAL '7 days' + AND class_enum = 1 -- 门诊 +GROUP BY create_time::DATE +ORDER BY create_time::DATE DESC; + +-- ============================================ +-- 6. 对比 start_time 和 create_time 的差异 +-- ============================================ +SELECT + id AS 就诊记录ID, + start_time AS 开始时间, + create_time AS 创建时间, + (start_time::DATE) AS 开始日期, + (create_time::DATE) AS 创建日期, + status_enum AS 状态, + delete_flag AS 是否删除 +FROM adm_encounter +WHERE (start_time::DATE = CURRENT_DATE OR create_time::DATE = CURRENT_DATE) + AND delete_flag = '0' +ORDER BY create_time DESC +LIMIT 20; + diff --git a/排查指南-字段查询问题.md b/排查指南-字段查询问题.md new file mode 100644 index 00000000..ddebfb9e --- /dev/null +++ b/排查指南-字段查询问题.md @@ -0,0 +1,284 @@ +# 排查指南:字段查询不到数据的问题 + +## 问题类型 +**症状**:SQL 查询条件使用了某个字段,但查询结果为空,而数据库中明明有数据。 + +**根本原因**:查询条件使用的字段在数据插入/更新时没有被设置(为 NULL 或默认值)。 + +--- + +## 排查步骤 + +### 第一步:确认字段是否存在 +```sql +-- 检查表结构,确认字段是否存在 +SELECT column_name, data_type, is_nullable, column_default +FROM information_schema.columns +WHERE table_name = 'adm_encounter' + AND column_name = 'start_time'; +``` + +**检查点**: +- ✅ 字段确实存在 +- ❌ 字段不存在 → 检查字段名拼写、大小写 + +--- + +### 第二步:检查字段是否有值 +```sql +-- 检查字段的实际值 +SELECT + id, + start_time, -- 检查这个字段 + create_time, -- 对比其他时间字段 + status_enum +FROM adm_encounter +WHERE delete_flag = '0' + AND create_time::DATE = CURRENT_DATE -- 用能查到数据的条件 +LIMIT 10; +``` + +**检查点**: +- ✅ `start_time` 有值 → 继续排查查询条件 +- ❌ `start_time` 为 NULL → **问题确认**:字段没有被设置 + +--- + +### 第三步:检查插入/更新代码 +找到创建/更新数据的 Service 方法,检查是否设置了该字段。 + +**检查位置**: +1. **Service 实现类**:查找 `save`、`insert`、`update` 相关方法 +2. **Mapper XML**:检查 INSERT/UPDATE 语句 +3. **Entity 类**:检查字段定义和默认值 + +**示例检查**: +```java +// ❌ 错误示例:没有设置 start_time +Encounter encounter = new Encounter(); +encounter.setBusNo("xxx"); +encounter.setPatientId(123L); +// 没有 encounter.setStartTime(...) +iEncounterService.save(encounter); + +// ✅ 正确示例:设置了 start_time +Encounter encounter = new Encounter(); +encounter.setBusNo("xxx"); +encounter.setPatientId(123L); +encounter.setStartTime(new Date()); // 设置了字段 +iEncounterService.save(encounter); +``` + +--- + +### 第四步:对比能查到数据的方法 +找到系统中**能查到数据**的类似查询,对比差异。 + +**对比维度**: +1. **使用的字段**:`start_time` vs `create_time` +2. **查询条件**:WHERE 子句的差异 +3. **关联表**:是否 JOIN 了其他表 +4. **业务逻辑**:是否过滤了特定状态 + +**示例对比**: +```sql +-- ❌ 查不到数据的方法 +SELECT * FROM adm_encounter +WHERE start_time::DATE = CURRENT_DATE; -- start_time 为 NULL + +-- ✅ 能查到数据的方法 +SELECT * FROM adm_encounter +WHERE create_time::DATE = CURRENT_DATE; -- create_time 有值 +``` + +--- + +### 第五步:检查字段的业务含义 +确认字段的**业务含义**和**使用场景**。 + +**常见情况**: +- `create_time`:记录创建时间(自动设置) +- `update_time`:记录更新时间(自动设置) +- `start_time`:业务开始时间(需要手动设置) +- `end_time`:业务结束时间(需要手动设置) + +**判断规则**: +- 如果字段是**业务字段**(如 `start_time`),需要手动设置 +- 如果字段是**系统字段**(如 `create_time`),通常自动设置 + +--- + +## 调试技巧 + +### 技巧1:打印 SQL 和参数 +在 Service 方法中添加日志,查看实际执行的 SQL: + +```java +@Override +public IPage getTodayOutpatientPatients(...) { + // 打印查询条件 + log.info("查询条件: queryDate={}, doctorId={}, departmentId={}", + queryDate, doctorId, departmentId); + + // 执行查询 + IPage result = mapper.getTodayOutpatientPatients(...); + + // 打印结果 + log.info("查询结果: 总数={}, 记录数={}", + result.getTotal(), result.getRecords().size()); + + return result; +} +``` + +### 技巧2:直接执行 SQL 验证 +在数据库客户端直接执行 SQL,验证查询条件是否正确: + +```sql +-- 1. 先查看数据 +SELECT id, start_time, create_time, status_enum +FROM adm_encounter +WHERE delete_flag = '0' +LIMIT 10; + +-- 2. 测试查询条件 +SELECT COUNT(*) +FROM adm_encounter +WHERE delete_flag = '0' + AND start_time::DATE = CURRENT_DATE; -- 测试这个条件 + +-- 3. 对比其他条件 +SELECT COUNT(*) +FROM adm_encounter +WHERE delete_flag = '0' + AND create_time::DATE = CURRENT_DATE; -- 对比这个条件 +``` + +### 技巧3:使用数据库工具检查 +使用数据库管理工具(如 pgAdmin、DBeaver): +1. 查看表结构 +2. 查看数据样本 +3. 执行测试查询 +4. 检查字段的 NULL 值比例 + +--- + +## 预防措施 + +### 1. 代码审查检查清单 +在代码审查时,检查以下内容: + +- [ ] **插入数据时**:是否设置了所有必要的业务字段? +- [ ] **查询条件时**:使用的字段是否在插入时被设置? +- [ ] **字段命名**:是否遵循命名规范(`create_time` vs `start_time`)? +- [ ] **文档注释**:字段的业务含义是否清晰? + +### 2. 单元测试 +编写单元测试,验证字段设置: + +```java +@Test +public void testSaveEncounter() { + Encounter encounter = new Encounter(); + encounter.setPatientId(123L); + encounter.setBusNo("TEST001"); + // 检查是否设置了 start_time + assertNotNull(encounter.getStartTime(), "start_time 应该被设置"); + + Long id = encounterService.saveEncounterByRegister(encounter); + + // 验证数据库中的值 + Encounter saved = encounterService.getById(id); + assertNotNull(saved.getStartTime(), "数据库中的 start_time 不应该为 NULL"); +} +``` + +### 3. 数据库约束 +在数据库层面添加约束,防止 NULL 值: + +```sql +-- 如果 start_time 是必填字段,添加 NOT NULL 约束 +ALTER TABLE adm_encounter +ALTER COLUMN start_time SET NOT NULL; + +-- 或者添加默认值 +ALTER TABLE adm_encounter +ALTER COLUMN start_time SET DEFAULT CURRENT_TIMESTAMP; +``` + +### 4. 代码规范 +建立代码规范,明确字段使用规则: + +```java +/** + * 保存就诊信息(门诊挂号) + * + * 注意: + * - create_time: 自动设置(系统字段) + * - start_time: 需要手动设置(业务字段,表示就诊开始时间) + * - 如果 start_time 为 NULL,查询时使用 create_time 作为挂号时间 + */ +public Long saveEncounterByRegister(Encounter encounter) { + // 如果没有设置 start_time,使用当前时间 + if (encounter.getStartTime() == null) { + encounter.setStartTime(new Date()); + } + // ... +} +``` + +--- + +## 常见问题模式 + +### 模式1:时间字段混淆 +- **问题**:使用 `start_time` 查询,但插入时没有设置 +- **解决**:使用 `create_time` 或确保插入时设置 `start_time` + +### 模式2:状态字段未设置 +- **问题**:使用 `status_enum` 查询,但插入时使用默认值 +- **解决**:明确设置状态值 + +### 模式3:关联字段未设置 +- **问题**:使用 `organization_id` 查询,但插入时为 NULL +- **解决**:确保插入时设置关联字段 + +--- + +## 快速排查清单 + +遇到"查询不到数据"问题时,按以下顺序检查: + +1. ✅ **数据库中有数据吗?** + ```sql + SELECT COUNT(*) FROM adm_encounter WHERE delete_flag = '0'; + ``` + +2. ✅ **查询条件使用的字段有值吗?** + ```sql + SELECT start_time FROM adm_encounter WHERE delete_flag = '0' LIMIT 10; + ``` + +3. ✅ **插入代码设置了该字段吗?** + - 查看 Service 的 save/insert 方法 + - 检查是否调用了 setter 方法 + +4. ✅ **字段的业务含义是什么?** + - 是系统字段(自动设置)还是业务字段(手动设置)? + +5. ✅ **有类似功能能查到数据吗?** + - 对比能查到数据的方法,找出差异 + +--- + +## 总结 + +**核心原则**: +1. **先看数据**:检查字段是否有值 +2. **再看代码**:检查插入时是否设置 +3. **对比差异**:找出能查到数据的方法 +4. **验证修复**:修改后验证数据 + +**记住**:查询条件使用的字段,必须在数据插入/更新时被设置! + +