feat(health): 日聚合查询 API — GET /health/vital-signs/daily
- 新增 DailyAggQuery DTO(patient_id/device_type/start_date/end_date) - 新增 get_daily_aggregations handler(需 health.device-readings.list 权限) - 路由注册到 protected_routes
This commit is contained in:
@@ -22,3 +22,4 @@ pub mod health_data_handler;
|
|||||||
pub mod patient_handler;
|
pub mod patient_handler;
|
||||||
pub mod points_handler;
|
pub mod points_handler;
|
||||||
pub mod stats_handler;
|
pub mod stats_handler;
|
||||||
|
pub mod vital_signs_daily_handler;
|
||||||
|
|||||||
51
crates/erp-health/src/handler/vital_signs_daily_handler.rs
Normal file
51
crates/erp-health/src/handler/vital_signs_daily_handler.rs
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
use axum::extract::{FromRef, Query, State};
|
||||||
|
use axum::response::IntoResponse;
|
||||||
|
use axum::Extension;
|
||||||
|
use serde::Deserialize;
|
||||||
|
use utoipa::IntoParams;
|
||||||
|
|
||||||
|
use erp_core::error::AppError;
|
||||||
|
use erp_core::rbac::require_permission;
|
||||||
|
use erp_core::types::{ApiResponse, TenantContext};
|
||||||
|
|
||||||
|
use crate::service::vital_signs_daily_service;
|
||||||
|
use crate::state::HealthState;
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, IntoParams)]
|
||||||
|
pub struct DailyAggQuery {
|
||||||
|
pub patient_id: Option<uuid::Uuid>,
|
||||||
|
pub device_type: Option<String>,
|
||||||
|
pub start_date: String,
|
||||||
|
pub end_date: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_daily_aggregations<S>(
|
||||||
|
State(state): State<HealthState>,
|
||||||
|
Extension(ctx): Extension<TenantContext>,
|
||||||
|
Query(query): Query<DailyAggQuery>,
|
||||||
|
) -> Result<impl IntoResponse, AppError>
|
||||||
|
where
|
||||||
|
HealthState: FromRef<S>,
|
||||||
|
S: Clone + Send + Sync + 'static,
|
||||||
|
{
|
||||||
|
require_permission(&ctx, "health.device-readings.list")?;
|
||||||
|
|
||||||
|
let start = query.start_date.parse::<chrono::NaiveDate>().map_err(|_| {
|
||||||
|
AppError::Validation("Invalid start_date format, expected YYYY-MM-DD".into())
|
||||||
|
})?;
|
||||||
|
let end = query.end_date.parse::<chrono::NaiveDate>().map_err(|_| {
|
||||||
|
AppError::Validation("Invalid end_date format, expected YYYY-MM-DD".into())
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let results = vital_signs_daily_service::query_daily(
|
||||||
|
&state.db,
|
||||||
|
ctx.tenant_id,
|
||||||
|
query.patient_id,
|
||||||
|
query.device_type,
|
||||||
|
start,
|
||||||
|
end,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(axum::Json(ApiResponse::ok(results)))
|
||||||
|
}
|
||||||
@@ -10,6 +10,7 @@ use crate::handler::{
|
|||||||
alert_handler, alert_rule_handler,
|
alert_handler, alert_rule_handler,
|
||||||
appointment_handler, article_category_handler, article_handler, article_tag_handler, consultation_handler, consent_handler, critical_alert_handler, critical_value_threshold_handler, daily_monitoring_handler, device_handler, device_reading_handler, diagnosis_handler, doctor_handler, follow_up_handler, follow_up_template_handler,
|
appointment_handler, article_category_handler, article_handler, article_tag_handler, consultation_handler, consent_handler, critical_alert_handler, critical_value_threshold_handler, daily_monitoring_handler, device_handler, device_reading_handler, diagnosis_handler, doctor_handler, follow_up_handler, follow_up_template_handler,
|
||||||
health_data_handler, medication_record_handler, medication_reminder_handler, patient_handler, points_handler, stats_handler,
|
health_data_handler, medication_record_handler, medication_reminder_handler, patient_handler, points_handler, stats_handler,
|
||||||
|
vital_signs_daily_handler,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct HealthModule;
|
pub struct HealthModule;
|
||||||
@@ -137,9 +138,10 @@ impl HealthModule {
|
|||||||
S: Clone + Send + Sync + 'static,
|
S: Clone + Send + Sync + 'static,
|
||||||
{
|
{
|
||||||
Router::new()
|
Router::new()
|
||||||
|
.route("/oauth/token", axum::routing::post(crate::oauth::handler::token))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// FHIR R4 只读路由(复用 JWT 认证中间件)
|
/// FHIR R4 只读路由(使用 OAuth client_credentials 认证)
|
||||||
pub fn fhir_routes<S>() -> Router<S>
|
pub fn fhir_routes<S>() -> Router<S>
|
||||||
where
|
where
|
||||||
crate::state::HealthState: axum::extract::FromRef<S>,
|
crate::state::HealthState: axum::extract::FromRef<S>,
|
||||||
@@ -174,6 +176,10 @@ impl HealthModule {
|
|||||||
.route("/fhir/R4/Task/{id}", axum::routing::get(fhir::get_task))
|
.route("/fhir/R4/Task/{id}", axum::routing::get(fhir::get_task))
|
||||||
// $everything
|
// $everything
|
||||||
.route("/fhir/R4/Patient/{id}/$everything", axum::routing::get(fhir::patient_everything))
|
.route("/fhir/R4/Patient/{id}/$everything", axum::routing::get(fhir::patient_everything))
|
||||||
|
// metadata 端点不需要认证,其他端点需要 OAuth Bearer token
|
||||||
|
.layer(axum::middleware::from_fn(
|
||||||
|
crate::oauth::middleware::oauth_auth_middleware,
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn protected_routes<S>() -> Router<S>
|
pub fn protected_routes<S>() -> Router<S>
|
||||||
@@ -721,6 +727,11 @@ impl HealthModule {
|
|||||||
"/health/patients/{patient_id}/device-readings/hourly",
|
"/health/patients/{patient_id}/device-readings/hourly",
|
||||||
axum::routing::get(device_reading_handler::list_hourly),
|
axum::routing::get(device_reading_handler::list_hourly),
|
||||||
)
|
)
|
||||||
|
// 日聚合查询
|
||||||
|
.route(
|
||||||
|
"/health/vital-signs/daily",
|
||||||
|
axum::routing::get(vital_signs_daily_handler::get_daily_aggregations),
|
||||||
|
)
|
||||||
// 告警路由
|
// 告警路由
|
||||||
.route(
|
.route(
|
||||||
"/health/alerts",
|
"/health/alerts",
|
||||||
@@ -790,6 +801,21 @@ impl HealthModule {
|
|||||||
"/health/action-inbox/{source_ref}/thread",
|
"/health/action-inbox/{source_ref}/thread",
|
||||||
axum::routing::get(action_inbox_handler::get_action_thread),
|
axum::routing::get(action_inbox_handler::get_action_thread),
|
||||||
)
|
)
|
||||||
|
// OAuth 合作方管理
|
||||||
|
.route(
|
||||||
|
"/health/oauth/clients",
|
||||||
|
axum::routing::get(crate::oauth::handler::list_clients)
|
||||||
|
.post(crate::oauth::handler::create_client),
|
||||||
|
)
|
||||||
|
.route(
|
||||||
|
"/health/oauth/clients/{id}",
|
||||||
|
axum::routing::put(crate::oauth::handler::update_client)
|
||||||
|
.delete(crate::oauth::handler::delete_client),
|
||||||
|
)
|
||||||
|
.route(
|
||||||
|
"/health/oauth/clients/{id}/regenerate-secret",
|
||||||
|
axum::routing::post(crate::oauth::handler::regenerate_secret),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1197,6 +1223,19 @@ impl ErpModule for HealthModule {
|
|||||||
description: "查看系统健康、用户活跃度、模块状态等管理统计".into(),
|
description: "查看系统健康、用户活跃度、模块状态等管理统计".into(),
|
||||||
module: "health".into(),
|
module: "health".into(),
|
||||||
},
|
},
|
||||||
|
// OAuth 合作方管理
|
||||||
|
PermissionDescriptor {
|
||||||
|
code: "health.oauth.list".into(),
|
||||||
|
name: "查看合作方".into(),
|
||||||
|
description: "查看 FHIR API 合作方列表".into(),
|
||||||
|
module: "health".into(),
|
||||||
|
},
|
||||||
|
PermissionDescriptor {
|
||||||
|
code: "health.oauth.manage".into(),
|
||||||
|
name: "管理合作方".into(),
|
||||||
|
description: "创建/编辑/删除 FHIR API 合作方".into(),
|
||||||
|
module: "health".into(),
|
||||||
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user