diff --git a/crates/erp-health/src/handler/alert_handler.rs b/crates/erp-health/src/handler/alert_handler.rs index 73a8864..56fe5af 100644 --- a/crates/erp-health/src/handler/alert_handler.rs +++ b/crates/erp-health/src/handler/alert_handler.rs @@ -10,6 +10,7 @@ use erp_core::rbac::require_permission; use erp_core::types::{ApiResponse, PaginatedResponse, TenantContext}; use crate::dto::alert_dto::AcknowledgeAlertRequest; +use crate::handler::consent_check::check_consent_active; use crate::service::alert_service; use crate::state::HealthState; @@ -32,6 +33,9 @@ where S: Clone + Send + Sync + 'static, { require_permission(&ctx, "health.alerts.list")?; + if let Some(pid) = query.patient_id { + check_consent_active(&state.db, ctx.tenant_id, pid, &ctx).await?; + } let page = query.page.unwrap_or(1); let page_size = query.page_size.unwrap_or(20).min(100); diff --git a/crates/erp-health/src/handler/consent_check.rs b/crates/erp-health/src/handler/consent_check.rs new file mode 100644 index 0000000..963188c --- /dev/null +++ b/crates/erp-health/src/handler/consent_check.rs @@ -0,0 +1,40 @@ +use erp_core::error::AppError; +use erp_core::types::TenantContext; +use sea_orm::{ColumnTrait, EntityTrait, QueryFilter}; + +use crate::entity::consent::{Column, Entity as ConsentEntity}; +use uuid::Uuid; + +/// 医护角色跳过 consent 检查 +const BYPASS_ROLES: &[&str] = &["admin", "doctor", "nurse", "health_manager"]; + +/// 检查患者是否有有效的知情同意记录(status = granted) +/// 在 handler 层调用,对患者数据的读取进行 consent 门控 +pub async fn check_consent_active( + db: &sea_orm::DatabaseConnection, + tenant_id: Uuid, + patient_id: Uuid, + ctx: &TenantContext, +) -> Result<(), AppError> { + // 医护和管理角色不需要 consent 检查 + if ctx.roles.iter().any(|r| BYPASS_ROLES.contains(&r.as_str())) { + return Ok(()); + } + + let has_active = ConsentEntity::find() + .filter(Column::TenantId.eq(tenant_id)) + .filter(Column::PatientId.eq(patient_id)) + .filter(Column::Status.eq("granted")) + .filter(Column::DeletedAt.is_null()) + .one(db) + .await + .map_err(|e| AppError::Internal(e.to_string()))?; + + if has_active.is_none() { + return Err(AppError::Forbidden( + "患者未签署知情同意书,无法访问数据".to_string(), + )); + } + + Ok(()) +} diff --git a/crates/erp-health/src/handler/daily_monitoring_handler.rs b/crates/erp-health/src/handler/daily_monitoring_handler.rs index efe697c..7535926 100644 --- a/crates/erp-health/src/handler/daily_monitoring_handler.rs +++ b/crates/erp-health/src/handler/daily_monitoring_handler.rs @@ -11,6 +11,7 @@ use erp_core::types::{ApiResponse, PaginatedResponse, TenantContext}; use crate::dto::DeleteWithVersion; use crate::dto::daily_monitoring_dto::*; +use crate::handler::consent_check::check_consent_active; use crate::service::daily_monitoring_service; use crate::state::HealthState; @@ -38,6 +39,7 @@ where S: Clone + Send + Sync + 'static, { require_permission(&ctx, "health.daily-monitoring.list")?; + check_consent_active(&state.db, ctx.tenant_id, patient_id, &ctx).await?; let page = params.page.unwrap_or(1); let page_size = params.page_size.unwrap_or(20).min(100); let result = daily_monitoring_service::list_daily_monitoring( diff --git a/crates/erp-health/src/handler/device_reading_handler.rs b/crates/erp-health/src/handler/device_reading_handler.rs index c9019fa..84e3dcb 100644 --- a/crates/erp-health/src/handler/device_reading_handler.rs +++ b/crates/erp-health/src/handler/device_reading_handler.rs @@ -9,6 +9,7 @@ use erp_core::error::AppError; use erp_core::rbac::require_permission; use erp_core::types::{ApiResponse, TenantContext}; +use crate::handler::consent_check::check_consent_active; use crate::service::device_reading_service; use crate::state::HealthState; @@ -75,6 +76,7 @@ where S: Clone + Send + Sync + 'static, { require_permission(&ctx, "health.device-readings.list")?; + check_consent_active(&state.db, ctx.tenant_id, path.patient_id, &ctx).await?; let page = query.page.unwrap_or(1); let page_size = query.page_size.unwrap_or(20).min(100); let result = device_reading_service::query_device_readings( @@ -108,6 +110,7 @@ where S: Clone + Send + Sync + 'static, { require_permission(&ctx, "health.device-readings.list")?; + check_consent_active(&state.db, ctx.tenant_id, path.patient_id, &ctx).await?; let page = query.page.unwrap_or(1); let page_size = query.page_size.unwrap_or(20).min(100); let days = query.days.unwrap_or(7); diff --git a/crates/erp-health/src/handler/health_data_handler.rs b/crates/erp-health/src/handler/health_data_handler.rs index b1616b7..325b324 100644 --- a/crates/erp-health/src/handler/health_data_handler.rs +++ b/crates/erp-health/src/handler/health_data_handler.rs @@ -12,6 +12,7 @@ use validator::Validate; use crate::dto::DeleteWithVersion; use crate::dto::health_data_dto::*; +use crate::handler::consent_check::check_consent_active; use crate::service::health_data_service; use crate::service::trend_service; use crate::state::HealthState; @@ -59,6 +60,7 @@ where S: Clone + Send + Sync + 'static, { require_permission(&ctx, "health.health-data.list")?; + check_consent_active(&state.db, ctx.tenant_id, patient_id, &ctx).await?; let page = params.page.unwrap_or(1); let page_size = params.page_size.unwrap_or(20).min(100); let result = @@ -158,6 +160,7 @@ where S: Clone + Send + Sync + 'static, { require_permission(&ctx, "health.health-data.list")?; + check_consent_active(&state.db, ctx.tenant_id, patient_id, &ctx).await?; let page = params.page.unwrap_or(1); let page_size = params.page_size.unwrap_or(20).min(100); let result = @@ -285,6 +288,7 @@ where S: Clone + Send + Sync + 'static, { require_permission(&ctx, "health.health-data.list")?; + check_consent_active(&state.db, ctx.tenant_id, patient_id, &ctx).await?; let page = params.page.unwrap_or(1); let page_size = params.page_size.unwrap_or(20).min(100); let result = health_data_service::list_health_records( @@ -389,6 +393,7 @@ where S: Clone + Send + Sync + 'static, { require_permission(&ctx, "health.health-data.list")?; + check_consent_active(&state.db, ctx.tenant_id, patient_id, &ctx).await?; let page = params.page.unwrap_or(1); let page_size = params.page_size.unwrap_or(20).min(100); let result = @@ -430,6 +435,7 @@ where S: Clone + Send + Sync + 'static, { require_permission(&ctx, "health.health-data.list")?; + check_consent_active(&state.db, ctx.tenant_id, patient_id, &ctx).await?; let result = trend_service::get_indicator_timeseries( &state, ctx.tenant_id, diff --git a/crates/erp-health/src/handler/mod.rs b/crates/erp-health/src/handler/mod.rs index 45eb4f0..a760c5e 100644 --- a/crates/erp-health/src/handler/mod.rs +++ b/crates/erp-health/src/handler/mod.rs @@ -8,6 +8,7 @@ pub mod article_tag_handler; pub mod banner_handler; pub mod ble_gateway_handler; pub mod care_plan_handler; +pub mod consent_check; pub mod consent_handler; pub mod consultation_handler; pub mod critical_alert_handler; diff --git a/crates/erp-health/src/handler/vital_signs_daily_handler.rs b/crates/erp-health/src/handler/vital_signs_daily_handler.rs index 7e10b3e..f86d4da 100644 --- a/crates/erp-health/src/handler/vital_signs_daily_handler.rs +++ b/crates/erp-health/src/handler/vital_signs_daily_handler.rs @@ -8,6 +8,7 @@ use erp_core::error::AppError; use erp_core::rbac::require_permission; use erp_core::types::{ApiResponse, TenantContext}; +use crate::handler::consent_check::check_consent_active; use crate::service::vital_signs_daily_service; use crate::state::HealthState; @@ -37,6 +38,10 @@ where { require_permission(&ctx, "health.device-readings.list")?; + if let Some(pid) = query.patient_id { + check_consent_active(&state.db, ctx.tenant_id, pid, &ctx).await?; + } + let start = query.start_date.parse::().map_err(|_| { AppError::Validation("Invalid start_date format, expected YYYY-MM-DD".into()) })?;