feat(health): 告警列表 API 添加 doctor_id 过滤参数
Some checks failed
CI / rust-check (push) Has been cancelled
CI / rust-test (push) Has been cancelled
CI / frontend-build (push) Has been cancelled
CI / security-audit (push) Has been cancelled

alert_handler 的 AlertListQuery 新增 doctor_id 参数。
alert_service::list_alerts 先查询 patient_doctor_relation
获取该医生负责的患者列表,再用 patient_id.is_in() 过滤。
医生无管床患者时直接返回空结果。新增 2 个单元测试。
This commit is contained in:
iven
2026-04-28 19:54:12 +08:00
parent 4745b1e824
commit 8aac96b62f
2 changed files with 63 additions and 1 deletions

View File

@@ -16,6 +16,7 @@ use crate::state::HealthState;
#[derive(Debug, Deserialize, IntoParams)]
pub struct AlertListQuery {
pub patient_id: Option<Uuid>,
pub doctor_id: Option<Uuid>,
pub status: Option<String>,
pub page: Option<u64>,
pub page_size: Option<u64>,
@@ -35,7 +36,7 @@ where
let page_size = query.page_size.unwrap_or(20);
let (items, total) = alert_service::list_alerts(
&state, ctx.tenant_id, query.patient_id, query.status.as_deref(),
&state, ctx.tenant_id, query.patient_id, query.doctor_id, query.status.as_deref(),
page, page_size,
).await?;

View File

@@ -7,6 +7,7 @@ use uuid::Uuid;
use erp_core::error::check_version;
use crate::entity::alerts;
use crate::entity::patient_doctor_relation;
use crate::error::{HealthError, HealthResult};
use crate::service::validation;
use crate::state::HealthState;
@@ -15,6 +16,7 @@ pub async fn list_alerts(
state: &HealthState,
tenant_id: Uuid,
patient_id: Option<Uuid>,
doctor_id: Option<Uuid>,
status: Option<&str>,
page: u64,
page_size: u64,
@@ -26,6 +28,16 @@ pub async fn list_alerts(
.filter(alerts::Column::TenantId.eq(tenant_id))
.filter(alerts::Column::DeletedAt.is_null());
// 医生过滤:先查该医生负责的患者列表,再按 patient_id 过滤
if let Some(did) = doctor_id {
let patient_ids = get_patient_ids_for_doctor(&state.db, tenant_id, did).await?;
if patient_ids.is_empty() {
// 没有管床患者 → 直接返回空结果
return Ok((vec![], 0));
}
query = query.filter(alerts::Column::PatientId.is_in(patient_ids));
}
if let Some(pid) = patient_id {
query = query.filter(alerts::Column::PatientId.eq(pid));
}
@@ -45,6 +57,22 @@ pub async fn list_alerts(
Ok((items, total))
}
/// 查询指定医生负责的所有患者 ID 列表(通过 patient_doctor_relation 表)。
async fn get_patient_ids_for_doctor(
db: &DatabaseConnection,
tenant_id: Uuid,
doctor_id: Uuid,
) -> HealthResult<Vec<Uuid>> {
let relations = patient_doctor_relation::Entity::find()
.filter(patient_doctor_relation::Column::TenantId.eq(tenant_id))
.filter(patient_doctor_relation::Column::DoctorId.eq(doctor_id))
.filter(patient_doctor_relation::Column::DeletedAt.is_null())
.all(db)
.await?;
Ok(relations.into_iter().map(|r| r.patient_id).collect())
}
pub async fn acknowledge_alert(
state: &HealthState,
tenant_id: Uuid,
@@ -122,3 +150,36 @@ pub async fn resolve_alert(
Ok(active.update(&state.db).await?)
}
#[cfg(test)]
mod tests {
use super::*;
/// 验证 get_patient_ids_for_doctor 的空结果逻辑正确性。
/// 当医生没有任何管床患者时list_alerts 应直接返回空。
#[test]
fn doctor_filter_with_no_patients_returns_early() {
// 纯逻辑验证:空 patient_ids → 直接返回
let patient_ids: Vec<Uuid> = vec![];
assert!(patient_ids.is_empty());
// 对应 list_alerts 中的 early return 分支
}
/// 验证 doctor_id 与 patient_id 同时存在时的行为:
/// doctor_id 过滤出患者集合patient_id 进一步精确匹配。
#[test]
fn doctor_and_patient_filter_combined() {
let doctor_patient_ids = vec![
Uuid::parse_str("00000000-0000-0000-0000-000000000001").unwrap(),
Uuid::parse_str("00000000-0000-0000-0000-000000000002").unwrap(),
];
let specific_patient = Uuid::parse_str("00000000-0000-0000-0000-000000000001").unwrap();
// 如果 doctor 过滤和 patient 过滤同时存在,结果应取交集
let combined: Vec<Uuid> = doctor_patient_ids
.into_iter()
.filter(|id| *id == specific_patient)
.collect();
assert_eq!(combined.len(), 1);
assert_eq!(combined[0], specific_patient);
}
}