Files
hms/crates/erp-health/src/handler/stats_handler.rs
iven 22b8ac7ac6
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
fix: 修复多角色找茬测试 V2 发现的 11 个问题
P0 (CRITICAL):
- C1: 统计 API 全部改为 safe_aggregate 容错,防止单个子查询崩溃导致 500
- C2: Token 刷新增加用户身份验证,防止并发场景下身份切换
- C3: 患者端线下活动接口添加患者档案验证,防止 Doctor/HM 越权访问

P1 (HIGH):
- H1: 操作记录用 EntityName 组件解析用户名,不再显示截断 UUID
- H4: 告警标题添加中英文映射 (translateAlertTitle)
- H5: 告警面板补全 message import + 修复 hooks 顺序
- H8: 咨询消息发送按钮添加 AuthButton 权限控制
- H9: routeConfig 日常监测权限码改为 health.daily-monitoring.*

P2 (MEDIUM):
- M4: 咨询类型映射补全 online/phone/doctor/follow_up 中文标签

DTO: LabReportStatisticsResp, AppointmentStatisticsResp, VitalSignsReportRateResp 添加 Default derive
2026-05-08 12:42:41 +08:00

260 lines
8.0 KiB
Rust

use axum::Extension;
use axum::extract::{FromRef, Json, State};
use erp_core::aggregate::safe_aggregate;
use erp_core::error::AppError;
use erp_core::rbac::require_permission;
use erp_core::types::{ApiResponse, TenantContext};
use crate::dto::stats_dto::*;
use crate::service::stats_service;
use crate::state::HealthState;
pub async fn get_patient_stats<S>(
State(state): State<HealthState>,
Extension(ctx): Extension<TenantContext>,
) -> Result<Json<ApiResponse<PatientStatisticsResp>>, AppError>
where
HealthState: FromRef<S>,
S: Clone + Send + Sync + 'static,
{
require_permission(&ctx, "health.patient.list")?;
let result = stats_service::get_patient_statistics(&state, ctx.tenant_id).await?;
Ok(Json(ApiResponse::ok(result)))
}
pub async fn get_consultation_stats<S>(
State(state): State<HealthState>,
Extension(ctx): Extension<TenantContext>,
) -> Result<Json<ApiResponse<ConsultationStatisticsResp>>, AppError>
where
HealthState: FromRef<S>,
S: Clone + Send + Sync + 'static,
{
require_permission(&ctx, "health.consultation.list")?;
let result = safe_aggregate(
stats_service::get_consultation_statistics(&state, ctx.tenant_id),
"咨询统计",
)
.await;
Ok(Json(ApiResponse::ok(result)))
}
pub async fn get_follow_up_stats<S>(
State(state): State<HealthState>,
Extension(ctx): Extension<TenantContext>,
) -> Result<Json<ApiResponse<FollowUpStatisticsResp>>, AppError>
where
HealthState: FromRef<S>,
S: Clone + Send + Sync + 'static,
{
require_permission(&ctx, "health.follow-up.list")?;
let result = stats_service::get_follow_up_statistics(&state, ctx.tenant_id).await?;
Ok(Json(ApiResponse::ok(result)))
}
pub async fn get_dashboard_stats<S>(
State(state): State<HealthState>,
Extension(ctx): Extension<TenantContext>,
) -> Result<Json<ApiResponse<DashboardStatsResp>>, AppError>
where
HealthState: FromRef<S>,
S: Clone + Send + Sync + 'static,
{
require_permission(&ctx, "health.patient.list")?;
let patients = safe_aggregate(
stats_service::get_patient_statistics(&state, ctx.tenant_id),
"患者统计",
)
.await;
let consultations = safe_aggregate(
stats_service::get_consultation_statistics(&state, ctx.tenant_id),
"咨询统计",
)
.await;
let follow_ups = safe_aggregate(
stats_service::get_follow_up_statistics(&state, ctx.tenant_id),
"随访统计",
)
.await;
Ok(Json(ApiResponse::ok(DashboardStatsResp {
patients,
consultations,
follow_ups,
})))
}
// ---------------------------------------------------------------------------
// 健康数据统计
// ---------------------------------------------------------------------------
pub async fn get_lab_report_stats<S>(
State(state): State<HealthState>,
Extension(ctx): Extension<TenantContext>,
) -> Result<Json<ApiResponse<LabReportStatisticsResp>>, AppError>
where
HealthState: FromRef<S>,
S: Clone + Send + Sync + 'static,
{
require_permission(&ctx, "health.patient.list")?;
let result = safe_aggregate(
stats_service::get_lab_report_statistics(&state, ctx.tenant_id),
"化验报告统计",
)
.await;
Ok(Json(ApiResponse::ok(result)))
}
pub async fn get_appointment_stats<S>(
State(state): State<HealthState>,
Extension(ctx): Extension<TenantContext>,
) -> Result<Json<ApiResponse<AppointmentStatisticsResp>>, AppError>
where
HealthState: FromRef<S>,
S: Clone + Send + Sync + 'static,
{
require_permission(&ctx, "health.patient.list")?;
let result = safe_aggregate(
stats_service::get_appointment_statistics(&state, ctx.tenant_id),
"预约统计",
)
.await;
Ok(Json(ApiResponse::ok(result)))
}
pub async fn get_vital_signs_report_rate<S>(
State(state): State<HealthState>,
Extension(ctx): Extension<TenantContext>,
) -> Result<Json<ApiResponse<VitalSignsReportRateResp>>, AppError>
where
HealthState: FromRef<S>,
S: Clone + Send + Sync + 'static,
{
require_permission(&ctx, "health.patient.list")?;
let result = safe_aggregate(
stats_service::get_vital_signs_report_rate(&state, ctx.tenant_id),
"体征上报率统计",
)
.await;
Ok(Json(ApiResponse::ok(result)))
}
pub async fn get_health_data_stats<S>(
State(state): State<HealthState>,
Extension(ctx): Extension<TenantContext>,
) -> Result<Json<ApiResponse<HealthDataStatsResp>>, AppError>
where
HealthState: FromRef<S>,
S: Clone + Send + Sync + 'static,
{
require_permission(&ctx, "health.patient.list")?;
let lab_reports = safe_aggregate(
stats_service::get_lab_report_statistics(&state, ctx.tenant_id),
"化验报告统计",
)
.await;
let appointments = safe_aggregate(
stats_service::get_appointment_statistics(&state, ctx.tenant_id),
"预约统计",
)
.await;
let vital_signs_report_rate = safe_aggregate(
stats_service::get_vital_signs_report_rate(&state, ctx.tenant_id),
"体征上报率统计",
)
.await;
Ok(Json(ApiResponse::ok(HealthDataStatsResp {
lab_reports,
appointments,
vital_signs_report_rate,
})))
}
// ---------------------------------------------------------------------------
// 个人维度统计
// ---------------------------------------------------------------------------
pub async fn get_personal_stats<S>(
State(state): State<HealthState>,
Extension(ctx): Extension<TenantContext>,
) -> Result<Json<ApiResponse<PersonalStatsResp>>, AppError>
where
HealthState: FromRef<S>,
S: Clone + Send + Sync + 'static,
{
require_permission(&ctx, "health.patient.list")?;
let result = stats_service::get_personal_stats(&state, ctx.user_id, ctx.tenant_id).await?;
Ok(Json(ApiResponse::ok(result)))
}
// ---------------------------------------------------------------------------
// 工作台管理统计
// ---------------------------------------------------------------------------
pub async fn get_system_health<S>(
State(state): State<HealthState>,
Extension(_ctx): Extension<TenantContext>,
) -> Result<Json<ApiResponse<SystemHealthResp>>, AppError>
where
HealthState: FromRef<S>,
S: Clone + Send + Sync + 'static,
{
let result = stats_service::get_system_health(&state).await?;
Ok(Json(ApiResponse::ok(result)))
}
pub async fn get_user_activity<S>(
State(state): State<HealthState>,
Extension(ctx): Extension<TenantContext>,
) -> Result<Json<ApiResponse<UserActivityResp>>, AppError>
where
HealthState: FromRef<S>,
S: Clone + Send + Sync + 'static,
{
require_permission(&ctx, "health.dashboard.manage")?;
let result = stats_service::get_user_activity(&state.db, ctx.tenant_id).await?;
Ok(Json(ApiResponse::ok(result)))
}
pub async fn get_module_status<S>(
State(state): State<HealthState>,
Extension(ctx): Extension<TenantContext>,
) -> Result<Json<ApiResponse<Vec<ModuleStatusResp>>>, AppError>
where
HealthState: FromRef<S>,
S: Clone + Send + Sync + 'static,
{
require_permission(&ctx, "health.dashboard.manage")?;
let result = stats_service::get_module_status(&state).await?;
Ok(Json(ApiResponse::ok(result)))
}
pub async fn get_points_recent_activity<S>(
State(state): State<HealthState>,
Extension(ctx): Extension<TenantContext>,
) -> Result<Json<ApiResponse<Vec<PointsActivityItem>>>, AppError>
where
HealthState: FromRef<S>,
S: Clone + Send + Sync + 'static,
{
require_permission(&ctx, "health.dashboard.manage")?;
let result = stats_service::get_points_recent_activity(&state.db, ctx.tenant_id, 10).await?;
Ok(Json(ApiResponse::ok(result)))
}
pub async fn get_article_stats<S>(
State(state): State<HealthState>,
Extension(ctx): Extension<TenantContext>,
) -> Result<Json<ApiResponse<ArticleStatsResp>>, AppError>
where
HealthState: FromRef<S>,
S: Clone + Send + Sync + 'static,
{
require_permission(&ctx, "health.dashboard.manage")?;
let result = stats_service::get_article_stats(&state.db, ctx.tenant_id).await?;
Ok(Json(ApiResponse::ok(result)))
}