diff --git a/apps/web/src/pages/health/AlertList.tsx b/apps/web/src/pages/health/AlertList.tsx index 5882c1a..bb35d10 100644 --- a/apps/web/src/pages/health/AlertList.tsx +++ b/apps/web/src/pages/health/AlertList.tsx @@ -200,7 +200,7 @@ export default function AlertList() { render: (_: unknown, record: Alert) => ( - {record.status === 'pending' && ( + {(record.status === 'pending' || record.status === 'active') && ( handleAcknowledge(record)} @@ -218,6 +218,7 @@ export default function AlertList() { )} {(record.status === 'pending' || + record.status === 'active' || record.status === 'acknowledged') && ( ( + State(state): State, + Extension(ctx): Extension, + Path(id): Path, +) -> Result +where + HealthState: FromRef, + S: Clone + Send + Sync + 'static, +{ + require_permission(&ctx, "health.alerts.list")?; + let alert = alert_service::get_alert(&state, ctx.tenant_id, id).await?; + Ok(axum::Json(ApiResponse::ok(alert))) +} + pub async fn acknowledge( State(state): State, Extension(ctx): Extension, diff --git a/crates/erp-health/src/module.rs b/crates/erp-health/src/module.rs index 981a927..a240307 100644 --- a/crates/erp-health/src/module.rs +++ b/crates/erp-health/src/module.rs @@ -760,6 +760,10 @@ impl HealthModule { "/health/alerts", axum::routing::get(alert_handler::list_alerts), ) + .route( + "/health/alerts/{id}", + axum::routing::get(alert_handler::get_alert), + ) .route( "/health/alerts/{id}/acknowledge", axum::routing::put(alert_handler::acknowledge), diff --git a/crates/erp-health/src/service/alert_service.rs b/crates/erp-health/src/service/alert_service.rs index 4cb0e8f..47ca393 100644 --- a/crates/erp-health/src/service/alert_service.rs +++ b/crates/erp-health/src/service/alert_service.rs @@ -13,6 +13,21 @@ use crate::error::{HealthError, HealthResult}; use crate::service::validation; use crate::state::HealthState; +pub async fn get_alert( + state: &HealthState, + tenant_id: Uuid, + alert_id: Uuid, +) -> HealthResult { + let model = alerts::Entity::find_by_id(alert_id) + .filter(alerts::Column::TenantId.eq(tenant_id)) + .filter(alerts::Column::DeletedAt.is_null()) + .one(&state.db) + .await? + .ok_or(HealthError::AlertNotFound)?; + + enrich_alert_response(&state.db, tenant_id, model).await +} + pub async fn list_alerts( state: &HealthState, tenant_id: Uuid, diff --git a/crates/erp-server/migration/src/lib.rs b/crates/erp-server/migration/src/lib.rs index f3f600b..2602d87 100644 --- a/crates/erp-server/migration/src/lib.rs +++ b/crates/erp-server/migration/src/lib.rs @@ -126,6 +126,7 @@ mod m20260505_000123_update_ai_prompts_system_instruction; mod m20260505_000124_freeze_deferred_menus; mod m20260506_000125_restructure_menus_and_roles; mod m20260506_000126_fix_role_permissions_cleanup; +mod m20260507_000127_fix_doctor_extra_permissions; pub struct Migrator; @@ -259,6 +260,7 @@ impl MigratorTrait for Migrator { Box::new(m20260505_000124_freeze_deferred_menus::Migration), Box::new(m20260506_000125_restructure_menus_and_roles::Migration), Box::new(m20260506_000126_fix_role_permissions_cleanup::Migration), + Box::new(m20260507_000127_fix_doctor_extra_permissions::Migration), ] } } diff --git a/crates/erp-server/migration/src/m20260507_000127_fix_doctor_extra_permissions.rs b/crates/erp-server/migration/src/m20260507_000127_fix_doctor_extra_permissions.rs new file mode 100644 index 0000000..35b906c --- /dev/null +++ b/crates/erp-server/migration/src/m20260507_000127_fix_doctor_extra_permissions.rs @@ -0,0 +1,36 @@ +use sea_orm_migration::prelude::*; + +#[derive(DeriveMigrationName)] +pub struct Migration; + +#[async_trait::async_trait] +impl MigrationTrait for Migration { + async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { + let db = manager.get_connection(); + + // Doctor 移除 health.health-data.manage 和 ai.analysis.manage + // 000125 正确分配了 health.health-data.list 和 ai.analysis.list, + // 但早期迁移分配了 .manage 权限且未被 000126 清理 + let doctor_remove = vec![ + "health.health-data.manage", + "ai.analysis.manage", + ]; + for code in &doctor_remove { + db.execute(sea_orm::Statement::from_string( + sea_orm::DatabaseBackend::Postgres, + format!( + "UPDATE role_permissions SET deleted_at = NOW() \ + FROM roles r, permissions p \ + WHERE role_permissions.role_id = r.id AND role_permissions.permission_id = p.id \ + AND r.code = 'doctor' AND p.code = '{code}' AND role_permissions.deleted_at IS NULL", + ), + )).await?; + } + + Ok(()) + } + + async fn down(&self, _manager: &SchemaManager) -> Result<(), DbErr> { + Ok(()) + } +}