Files
hms/crates/erp-health/src/handler/health_data_handler.rs
iven fa9278590d
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
refactor(dialysis): 透析模块拆分为独立 erp-dialysis crate
- 创建 erp-dialysis crate(DialysisState + DialysisError + DialysisModule)
- 迁移 2 Entity + 2 Service + 2 Handler + 2 DTO 共 8 个文件
- Entity 移除跨 crate patient Relation(FK 列保留)
- Service 内联 validation 逻辑,移除 patient 存在性检查(FK 约束保证)
- erp-health 的 stats/consultation 中 dialysis 查询改为 raw SQL
- ReviewLabReportReq 从 dialysis_dto 移至 health_data_dto(正确归属)
- workspace 全量编译通过
2026-04-28 12:37:23 +08:00

442 lines
14 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
use axum::Extension;
use axum::extract::{FromRef, Json, Path, Query, State};
use serde::Deserialize;
use utoipa::IntoParams;
use uuid::Uuid;
use erp_core::error::AppError;
use erp_core::rbac::require_permission;
use erp_core::types::{ApiResponse, PaginatedResponse, TenantContext};
use crate::dto::health_data_dto::*;
use crate::dto::DeleteWithVersion;
use crate::service::health_data_service;
use crate::service::trend_service;
use crate::state::HealthState;
// ---------------------------------------------------------------------------
// 查询参数
// ---------------------------------------------------------------------------
#[derive(Debug, Deserialize, IntoParams)]
pub struct PaginationParams {
pub page: Option<u64>,
pub page_size: Option<u64>,
}
#[derive(Debug, Deserialize, IntoParams)]
pub struct IndicatorTimeseriesParams {
pub start_date: Option<chrono::NaiveDate>,
pub end_date: Option<chrono::NaiveDate>,
}
#[derive(Debug, serde::Deserialize, utoipa::ToSchema)]
pub struct GenerateTrendReq {
pub period_start: chrono::NaiveDate,
pub period_end: chrono::NaiveDate,
}
#[derive(Debug, serde::Deserialize, utoipa::ToSchema)]
pub struct UpdateWithVersion<T> {
pub data: T,
pub version: i32,
}
// ---------------------------------------------------------------------------
// 生命体征
// ---------------------------------------------------------------------------
pub async fn list_vital_signs<S>(
State(state): State<HealthState>,
Extension(ctx): Extension<TenantContext>,
Path(patient_id): Path<Uuid>,
Query(params): Query<PaginationParams>,
) -> Result<Json<ApiResponse<PaginatedResponse<VitalSignsResp>>>, AppError>
where
HealthState: FromRef<S>,
S: Clone + Send + Sync + 'static,
{
require_permission(&ctx, "health.health-data.list")?;
let page = params.page.unwrap_or(1);
let page_size = params.page_size.unwrap_or(20);
let result = health_data_service::list_vital_signs(
&state, ctx.tenant_id, patient_id, page, page_size,
)
.await?;
Ok(Json(ApiResponse::ok(result)))
}
pub async fn create_vital_signs<S>(
State(state): State<HealthState>,
Extension(ctx): Extension<TenantContext>,
Path(patient_id): Path<Uuid>,
Json(req): Json<CreateVitalSignsReq>,
) -> Result<Json<ApiResponse<VitalSignsResp>>, AppError>
where
HealthState: FromRef<S>,
S: Clone + Send + Sync + 'static,
{
require_permission(&ctx, "health.health-data.manage")?;
let mut req = req;
req.sanitize();
let result = health_data_service::create_vital_signs(
&state, ctx.tenant_id, patient_id, Some(ctx.user_id), req,
)
.await?;
Ok(Json(ApiResponse::ok(result)))
}
pub async fn update_vital_signs<S>(
State(state): State<HealthState>,
Extension(ctx): Extension<TenantContext>,
Path((patient_id, vid)): Path<(Uuid, Uuid)>,
Json(req): Json<UpdateVitalSignsWithVersion>,
) -> Result<Json<ApiResponse<VitalSignsResp>>, AppError>
where
HealthState: FromRef<S>,
S: Clone + Send + Sync + 'static,
{
require_permission(&ctx, "health.health-data.manage")?;
let mut data = req.data;
data.sanitize();
let result = health_data_service::update_vital_signs(
&state, ctx.tenant_id, patient_id, vid, Some(ctx.user_id), data, req.version,
)
.await?;
Ok(Json(ApiResponse::ok(result)))
}
pub async fn delete_vital_signs<S>(
State(state): State<HealthState>,
Extension(ctx): Extension<TenantContext>,
Path((_patient_id, vid)): Path<(Uuid, Uuid)>,
Json(req): Json<DeleteWithVersion>,
) -> Result<Json<ApiResponse<()>>, AppError>
where
HealthState: FromRef<S>,
S: Clone + Send + Sync + 'static,
{
require_permission(&ctx, "health.health-data.manage")?;
health_data_service::delete_vital_signs(&state, ctx.tenant_id, vid, Some(ctx.user_id), req.version).await?;
Ok(Json(ApiResponse::ok(())))
}
// ---------------------------------------------------------------------------
// 化验报告
// ---------------------------------------------------------------------------
pub async fn list_lab_reports<S>(
State(state): State<HealthState>,
Extension(ctx): Extension<TenantContext>,
Path(patient_id): Path<Uuid>,
Query(params): Query<PaginationParams>,
) -> Result<Json<ApiResponse<PaginatedResponse<LabReportResp>>>, AppError>
where
HealthState: FromRef<S>,
S: Clone + Send + Sync + 'static,
{
require_permission(&ctx, "health.health-data.list")?;
let page = params.page.unwrap_or(1);
let page_size = params.page_size.unwrap_or(20);
let result = health_data_service::list_lab_reports(
&state, ctx.tenant_id, patient_id, page, page_size,
)
.await?;
Ok(Json(ApiResponse::ok(result)))
}
pub async fn create_lab_report<S>(
State(state): State<HealthState>,
Extension(ctx): Extension<TenantContext>,
Path(patient_id): Path<Uuid>,
Json(req): Json<CreateLabReportReq>,
) -> Result<Json<ApiResponse<LabReportResp>>, AppError>
where
HealthState: FromRef<S>,
S: Clone + Send + Sync + 'static,
{
require_permission(&ctx, "health.health-data.manage")?;
let mut req = req;
req.sanitize();
let result = health_data_service::create_lab_report(
&state, ctx.tenant_id, patient_id, Some(ctx.user_id), req,
)
.await?;
Ok(Json(ApiResponse::ok(result)))
}
pub async fn update_lab_report<S>(
State(state): State<HealthState>,
Extension(ctx): Extension<TenantContext>,
Path((_patient_id, rid)): Path<(Uuid, Uuid)>,
Json(req): Json<UpdateLabReportWithVersion>,
) -> Result<Json<ApiResponse<LabReportResp>>, AppError>
where
HealthState: FromRef<S>,
S: Clone + Send + Sync + 'static,
{
require_permission(&ctx, "health.health-data.manage")?;
let mut data = req.data;
data.sanitize();
let result = health_data_service::update_lab_report(
&state, ctx.tenant_id, _patient_id, rid, Some(ctx.user_id), data, req.version,
)
.await?;
Ok(Json(ApiResponse::ok(result)))
}
pub async fn delete_lab_report<S>(
State(state): State<HealthState>,
Extension(ctx): Extension<TenantContext>,
Path((_patient_id, rid)): Path<(Uuid, Uuid)>,
Json(req): Json<DeleteWithVersion>,
) -> Result<Json<ApiResponse<()>>, AppError>
where
HealthState: FromRef<S>,
S: Clone + Send + Sync + 'static,
{
require_permission(&ctx, "health.health-data.manage")?;
health_data_service::delete_lab_report(&state, ctx.tenant_id, rid, Some(ctx.user_id), req.version).await?;
Ok(Json(ApiResponse::ok(())))
}
pub async fn review_lab_report<S>(
State(state): State<HealthState>,
Extension(ctx): Extension<TenantContext>,
Path((_patient_id, rid)): Path<(Uuid, Uuid)>,
Json(req): Json<ReviewLabReportWithVersion>,
) -> Result<Json<ApiResponse<LabReportResp>>, AppError>
where
HealthState: FromRef<S>,
S: Clone + Send + Sync + 'static,
{
require_permission(&ctx, "health.health-data.manage")?;
let mut data = req.data;
data.sanitize();
let result = health_data_service::review_lab_report(
&state, ctx.tenant_id, _patient_id, rid, ctx.user_id, data, req.version,
)
.await?;
Ok(Json(ApiResponse::ok(result)))
}
// ---------------------------------------------------------------------------
// 健康档案
// ---------------------------------------------------------------------------
pub async fn list_health_records<S>(
State(state): State<HealthState>,
Extension(ctx): Extension<TenantContext>,
Path(patient_id): Path<Uuid>,
Query(params): Query<PaginationParams>,
) -> Result<Json<ApiResponse<PaginatedResponse<HealthRecordResp>>>, AppError>
where
HealthState: FromRef<S>,
S: Clone + Send + Sync + 'static,
{
require_permission(&ctx, "health.health-data.list")?;
let page = params.page.unwrap_or(1);
let page_size = params.page_size.unwrap_or(20);
let result = health_data_service::list_health_records(
&state, ctx.tenant_id, patient_id, page, page_size,
)
.await?;
Ok(Json(ApiResponse::ok(result)))
}
pub async fn create_health_record<S>(
State(state): State<HealthState>,
Extension(ctx): Extension<TenantContext>,
Path(patient_id): Path<Uuid>,
Json(req): Json<CreateHealthRecordReq>,
) -> Result<Json<ApiResponse<HealthRecordResp>>, AppError>
where
HealthState: FromRef<S>,
S: Clone + Send + Sync + 'static,
{
require_permission(&ctx, "health.health-data.manage")?;
let mut req = req;
req.sanitize();
let result = health_data_service::create_health_record(
&state, ctx.tenant_id, patient_id, Some(ctx.user_id), req,
)
.await?;
Ok(Json(ApiResponse::ok(result)))
}
pub async fn update_health_record<S>(
State(state): State<HealthState>,
Extension(ctx): Extension<TenantContext>,
Path((patient_id, rid)): Path<(Uuid, Uuid)>,
Json(req): Json<UpdateHealthRecordWithVersion>,
) -> Result<Json<ApiResponse<HealthRecordResp>>, AppError>
where
HealthState: FromRef<S>,
S: Clone + Send + Sync + 'static,
{
require_permission(&ctx, "health.health-data.manage")?;
let mut data = req.data;
data.sanitize();
let result = health_data_service::update_health_record(
&state, ctx.tenant_id, patient_id, rid, Some(ctx.user_id), data, req.version,
)
.await?;
Ok(Json(ApiResponse::ok(result)))
}
pub async fn delete_health_record<S>(
State(state): State<HealthState>,
Extension(ctx): Extension<TenantContext>,
Path((_patient_id, rid)): Path<(Uuid, Uuid)>,
Json(req): Json<DeleteWithVersion>,
) -> Result<Json<ApiResponse<()>>, AppError>
where
HealthState: FromRef<S>,
S: Clone + Send + Sync + 'static,
{
require_permission(&ctx, "health.health-data.manage")?;
health_data_service::delete_health_record(&state, ctx.tenant_id, rid, Some(ctx.user_id), req.version).await?;
Ok(Json(ApiResponse::ok(())))
}
// ---------------------------------------------------------------------------
// 趋势分析
// ---------------------------------------------------------------------------
pub async fn list_trends<S>(
State(state): State<HealthState>,
Extension(ctx): Extension<TenantContext>,
Path(patient_id): Path<Uuid>,
Query(params): Query<PaginationParams>,
) -> Result<Json<ApiResponse<PaginatedResponse<TrendResp>>>, AppError>
where
HealthState: FromRef<S>,
S: Clone + Send + Sync + 'static,
{
require_permission(&ctx, "health.health-data.list")?;
let page = params.page.unwrap_or(1);
let page_size = params.page_size.unwrap_or(20);
let result = trend_service::list_trends(
&state, ctx.tenant_id, patient_id, page, page_size,
)
.await?;
Ok(Json(ApiResponse::ok(result)))
}
pub async fn generate_trend<S>(
State(state): State<HealthState>,
Extension(ctx): Extension<TenantContext>,
Path(patient_id): Path<Uuid>,
Json(req): Json<GenerateTrendReq>,
) -> Result<Json<ApiResponse<TrendResp>>, AppError>
where
HealthState: FromRef<S>,
S: Clone + Send + Sync + 'static,
{
require_permission(&ctx, "health.health-data.manage")?;
let result = trend_service::generate_trend(
&state, ctx.tenant_id, patient_id, Some(ctx.user_id), req.period_start, req.period_end,
)
.await?;
Ok(Json(ApiResponse::ok(result)))
}
pub async fn get_indicator_timeseries<S>(
State(state): State<HealthState>,
Extension(ctx): Extension<TenantContext>,
Path((patient_id, indicator)): Path<(Uuid, String)>,
Query(params): Query<IndicatorTimeseriesParams>,
) -> Result<Json<ApiResponse<IndicatorTimeseriesResp>>, AppError>
where
HealthState: FromRef<S>,
S: Clone + Send + Sync + 'static,
{
require_permission(&ctx, "health.health-data.list")?;
let result = trend_service::get_indicator_timeseries(
&state, ctx.tenant_id, patient_id, indicator, params.start_date, params.end_date,
)
.await?;
Ok(Json(ApiResponse::ok(result)))
}
// ---------------------------------------------------------------------------
// 小程序趋势查询(通过当前用户关联 patient无需传 patient_id
// ---------------------------------------------------------------------------
pub async fn get_mini_trend<S>(
State(state): State<HealthState>,
Extension(ctx): Extension<TenantContext>,
Query(params): Query<MiniTrendQueryParams>,
) -> Result<Json<ApiResponse<MiniTrendResp>>, AppError>
where
HealthState: FromRef<S>,
S: Clone + Send + Sync + 'static,
{
require_permission(&ctx, "health.health-data.list")?;
let result = trend_service::get_mini_trend(
&state, ctx.tenant_id, ctx.user_id, params.indicator, params.range,
)
.await?;
Ok(Json(ApiResponse::ok(result)))
}
// ---------------------------------------------------------------------------
// 小程序今日体征摘要(通过当前用户关联 patient无需传 patient_id
// ---------------------------------------------------------------------------
pub async fn get_mini_today<S>(
State(state): State<HealthState>,
Extension(ctx): Extension<TenantContext>,
Query(params): Query<MiniTodayParams>,
) -> Result<Json<ApiResponse<MiniTodayResp>>, AppError>
where
HealthState: FromRef<S>,
S: Clone + Send + Sync + 'static,
{
require_permission(&ctx, "health.health-data.list")?;
let result = trend_service::get_mini_today(
&state, ctx.tenant_id, ctx.user_id, params.patient_id,
)
.await?;
Ok(Json(ApiResponse::ok(result)))
}
/// 小程序今日体征请求参数
#[derive(Debug, serde::Deserialize, utoipa::IntoParams)]
pub struct MiniTodayParams {
/// 可选:直接指定患者 ID小程序传入当前选中患者
pub patient_id: Option<Uuid>,
}
// ---------------------------------------------------------------------------
// 带版本号的更新请求包装
// ---------------------------------------------------------------------------
#[derive(Debug, serde::Deserialize, utoipa::ToSchema)]
pub struct UpdateVitalSignsWithVersion {
#[serde(flatten)]
pub data: UpdateVitalSignsReq,
pub version: i32,
}
#[derive(Debug, serde::Deserialize, utoipa::ToSchema)]
pub struct UpdateLabReportWithVersion {
#[serde(flatten)]
pub data: UpdateLabReportReq,
pub version: i32,
}
#[derive(Debug, serde::Deserialize, utoipa::ToSchema)]
pub struct UpdateHealthRecordWithVersion {
#[serde(flatten)]
pub data: UpdateHealthRecordReq,
pub version: i32,
}
#[derive(Debug, serde::Deserialize, utoipa::ToSchema)]
pub struct ReviewLabReportWithVersion {
#[serde(flatten)]
pub data: ReviewLabReportReq,
pub version: i32,
}