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 全量编译通过
This commit is contained in:
20
Cargo.lock
generated
20
Cargo.lock
generated
@@ -1465,6 +1465,26 @@ dependencies = [
|
|||||||
"uuid",
|
"uuid",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "erp-dialysis"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"async-trait",
|
||||||
|
"axum",
|
||||||
|
"chrono",
|
||||||
|
"erp-core",
|
||||||
|
"num-traits",
|
||||||
|
"sea-orm",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"thiserror 2.0.18",
|
||||||
|
"tokio",
|
||||||
|
"tracing",
|
||||||
|
"utoipa",
|
||||||
|
"uuid",
|
||||||
|
"validator",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "erp-health"
|
name = "erp-health"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ members = [
|
|||||||
"crates/erp-health",
|
"crates/erp-health",
|
||||||
"crates/erp-ai",
|
"crates/erp-ai",
|
||||||
"crates/erp-plugin-assessment",
|
"crates/erp-plugin-assessment",
|
||||||
|
"crates/erp-dialysis",
|
||||||
]
|
]
|
||||||
|
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
|
|||||||
20
crates/erp-dialysis/Cargo.toml
Normal file
20
crates/erp-dialysis/Cargo.toml
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
[package]
|
||||||
|
name = "erp-dialysis"
|
||||||
|
version.workspace = true
|
||||||
|
edition.workspace = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
erp-core.workspace = true
|
||||||
|
sea-orm.workspace = true
|
||||||
|
tokio.workspace = true
|
||||||
|
serde = { workspace = true, features = ["derive"] }
|
||||||
|
serde_json.workspace = true
|
||||||
|
uuid.workspace = true
|
||||||
|
chrono.workspace = true
|
||||||
|
axum.workspace = true
|
||||||
|
utoipa.workspace = true
|
||||||
|
validator.workspace = true
|
||||||
|
async-trait.workspace = true
|
||||||
|
tracing.workspace = true
|
||||||
|
thiserror.workspace = true
|
||||||
|
num-traits = "0.2"
|
||||||
7
crates/erp-dialysis/src/dto/mod.rs
Normal file
7
crates/erp-dialysis/src/dto/mod.rs
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
pub mod dialysis_dto;
|
||||||
|
pub mod dialysis_prescription_dto;
|
||||||
|
|
||||||
|
#[derive(Debug, serde::Deserialize, utoipa::ToSchema)]
|
||||||
|
pub struct DeleteWithVersion {
|
||||||
|
pub version: i32,
|
||||||
|
}
|
||||||
@@ -59,19 +59,6 @@ pub struct Model {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||||
pub enum Relation {
|
pub enum Relation {}
|
||||||
#[sea_orm(
|
|
||||||
belongs_to = "super::patient::Entity",
|
|
||||||
from = "Column::PatientId",
|
|
||||||
to = "super::patient::Column::Id"
|
|
||||||
)]
|
|
||||||
Patient,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Related<super::patient::Entity> for Entity {
|
|
||||||
fn to() -> RelationDef {
|
|
||||||
Relation::Patient.def()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ActiveModelBehavior for ActiveModel {}
|
impl ActiveModelBehavior for ActiveModel {}
|
||||||
@@ -63,19 +63,6 @@ pub struct Model {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||||
pub enum Relation {
|
pub enum Relation {}
|
||||||
#[sea_orm(
|
|
||||||
belongs_to = "super::patient::Entity",
|
|
||||||
from = "Column::PatientId",
|
|
||||||
to = "super::patient::Column::Id"
|
|
||||||
)]
|
|
||||||
Patient,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Related<super::patient::Entity> for Entity {
|
|
||||||
fn to() -> RelationDef {
|
|
||||||
Relation::Patient.def()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ActiveModelBehavior for ActiveModel {}
|
impl ActiveModelBehavior for ActiveModel {}
|
||||||
2
crates/erp-dialysis/src/entity/mod.rs
Normal file
2
crates/erp-dialysis/src/entity/mod.rs
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
pub mod dialysis_prescription;
|
||||||
|
pub mod dialysis_record;
|
||||||
60
crates/erp-dialysis/src/error.rs
Normal file
60
crates/erp-dialysis/src/error.rs
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
use erp_core::error::AppError;
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum DialysisError {
|
||||||
|
#[error("{0}")]
|
||||||
|
Validation(String),
|
||||||
|
|
||||||
|
#[error("患者不存在")]
|
||||||
|
PatientNotFound,
|
||||||
|
|
||||||
|
#[error("透析记录不存在")]
|
||||||
|
DialysisRecordNotFound,
|
||||||
|
|
||||||
|
#[error("透析方案不存在")]
|
||||||
|
DialysisPrescriptionNotFound,
|
||||||
|
|
||||||
|
#[error("状态转换无效: {0}")]
|
||||||
|
InvalidStatusTransition(String),
|
||||||
|
|
||||||
|
#[error("版本冲突: 数据已被其他操作修改,请刷新后重试")]
|
||||||
|
VersionMismatch,
|
||||||
|
|
||||||
|
#[error("数据库操作失败: {0}")]
|
||||||
|
DbError(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<DialysisError> for AppError {
|
||||||
|
fn from(err: DialysisError) -> Self {
|
||||||
|
match err {
|
||||||
|
DialysisError::Validation(s) => AppError::Validation(s),
|
||||||
|
DialysisError::PatientNotFound
|
||||||
|
| DialysisError::DialysisRecordNotFound
|
||||||
|
| DialysisError::DialysisPrescriptionNotFound => AppError::NotFound(err.to_string()),
|
||||||
|
DialysisError::InvalidStatusTransition(s) => AppError::Validation(s),
|
||||||
|
DialysisError::VersionMismatch => AppError::VersionMismatch,
|
||||||
|
DialysisError::DbError(_) => AppError::Internal(err.to_string()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<sea_orm::DbErr> for DialysisError {
|
||||||
|
fn from(err: sea_orm::DbErr) -> Self {
|
||||||
|
DialysisError::DbError(err.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<AppError> for DialysisError {
|
||||||
|
fn from(err: AppError) -> Self {
|
||||||
|
DialysisError::Validation(err.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<String> for DialysisError {
|
||||||
|
fn from(err: String) -> Self {
|
||||||
|
DialysisError::Validation(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type DialysisResult<T> = Result<T, DialysisError>;
|
||||||
6
crates/erp-dialysis/src/event.rs
Normal file
6
crates/erp-dialysis/src/event.rs
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
use erp_core::events::EventBus;
|
||||||
|
|
||||||
|
/// 预留事件处理器注册
|
||||||
|
pub fn register_handlers_with_state(_state: crate::state::DialysisState) {
|
||||||
|
// 透析模块事件消费者待后续迭代
|
||||||
|
}
|
||||||
@@ -11,7 +11,7 @@ use erp_core::types::{ApiResponse, PaginatedResponse, TenantContext};
|
|||||||
use crate::dto::dialysis_dto::*;
|
use crate::dto::dialysis_dto::*;
|
||||||
use crate::dto::DeleteWithVersion;
|
use crate::dto::DeleteWithVersion;
|
||||||
use crate::service::dialysis_service;
|
use crate::service::dialysis_service;
|
||||||
use crate::state::HealthState;
|
use crate::state::DialysisState;
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, IntoParams)]
|
#[derive(Debug, Deserialize, IntoParams)]
|
||||||
pub struct PaginationParams {
|
pub struct PaginationParams {
|
||||||
@@ -32,13 +32,13 @@ pub struct ReviewDialysisWithVersion {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn list_dialysis_records<S>(
|
pub async fn list_dialysis_records<S>(
|
||||||
State(state): State<HealthState>,
|
State(state): State<DialysisState>,
|
||||||
Extension(ctx): Extension<TenantContext>,
|
Extension(ctx): Extension<TenantContext>,
|
||||||
Path(patient_id): Path<Uuid>,
|
Path(patient_id): Path<Uuid>,
|
||||||
Query(params): Query<PaginationParams>,
|
Query(params): Query<PaginationParams>,
|
||||||
) -> Result<Json<ApiResponse<PaginatedResponse<DialysisRecordResp>>>, AppError>
|
) -> Result<Json<ApiResponse<PaginatedResponse<DialysisRecordResp>>>, AppError>
|
||||||
where
|
where
|
||||||
HealthState: FromRef<S>,
|
DialysisState: FromRef<S>,
|
||||||
S: Clone + Send + Sync + 'static,
|
S: Clone + Send + Sync + 'static,
|
||||||
{
|
{
|
||||||
require_permission(&ctx, "health.health-data.list")?;
|
require_permission(&ctx, "health.health-data.list")?;
|
||||||
@@ -52,12 +52,12 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_dialysis_record<S>(
|
pub async fn get_dialysis_record<S>(
|
||||||
State(state): State<HealthState>,
|
State(state): State<DialysisState>,
|
||||||
Extension(ctx): Extension<TenantContext>,
|
Extension(ctx): Extension<TenantContext>,
|
||||||
Path(record_id): Path<Uuid>,
|
Path(record_id): Path<Uuid>,
|
||||||
) -> Result<Json<ApiResponse<DialysisRecordResp>>, AppError>
|
) -> Result<Json<ApiResponse<DialysisRecordResp>>, AppError>
|
||||||
where
|
where
|
||||||
HealthState: FromRef<S>,
|
DialysisState: FromRef<S>,
|
||||||
S: Clone + Send + Sync + 'static,
|
S: Clone + Send + Sync + 'static,
|
||||||
{
|
{
|
||||||
require_permission(&ctx, "health.health-data.list")?;
|
require_permission(&ctx, "health.health-data.list")?;
|
||||||
@@ -69,12 +69,12 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn create_dialysis_record<S>(
|
pub async fn create_dialysis_record<S>(
|
||||||
State(state): State<HealthState>,
|
State(state): State<DialysisState>,
|
||||||
Extension(ctx): Extension<TenantContext>,
|
Extension(ctx): Extension<TenantContext>,
|
||||||
Json(req): Json<CreateDialysisRecordReq>,
|
Json(req): Json<CreateDialysisRecordReq>,
|
||||||
) -> Result<Json<ApiResponse<DialysisRecordResp>>, AppError>
|
) -> Result<Json<ApiResponse<DialysisRecordResp>>, AppError>
|
||||||
where
|
where
|
||||||
HealthState: FromRef<S>,
|
DialysisState: FromRef<S>,
|
||||||
S: Clone + Send + Sync + 'static,
|
S: Clone + Send + Sync + 'static,
|
||||||
{
|
{
|
||||||
require_permission(&ctx, "health.health-data.manage")?;
|
require_permission(&ctx, "health.health-data.manage")?;
|
||||||
@@ -88,13 +88,13 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn update_dialysis_record<S>(
|
pub async fn update_dialysis_record<S>(
|
||||||
State(state): State<HealthState>,
|
State(state): State<DialysisState>,
|
||||||
Extension(ctx): Extension<TenantContext>,
|
Extension(ctx): Extension<TenantContext>,
|
||||||
Path(record_id): Path<Uuid>,
|
Path(record_id): Path<Uuid>,
|
||||||
Json(req): Json<UpdateDialysisWithVersion>,
|
Json(req): Json<UpdateDialysisWithVersion>,
|
||||||
) -> Result<Json<ApiResponse<DialysisRecordResp>>, AppError>
|
) -> Result<Json<ApiResponse<DialysisRecordResp>>, AppError>
|
||||||
where
|
where
|
||||||
HealthState: FromRef<S>,
|
DialysisState: FromRef<S>,
|
||||||
S: Clone + Send + Sync + 'static,
|
S: Clone + Send + Sync + 'static,
|
||||||
{
|
{
|
||||||
require_permission(&ctx, "health.health-data.manage")?;
|
require_permission(&ctx, "health.health-data.manage")?;
|
||||||
@@ -108,13 +108,13 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn review_dialysis_record<S>(
|
pub async fn review_dialysis_record<S>(
|
||||||
State(state): State<HealthState>,
|
State(state): State<DialysisState>,
|
||||||
Extension(ctx): Extension<TenantContext>,
|
Extension(ctx): Extension<TenantContext>,
|
||||||
Path(record_id): Path<Uuid>,
|
Path(record_id): Path<Uuid>,
|
||||||
Json(req): Json<ReviewDialysisWithVersion>,
|
Json(req): Json<ReviewDialysisWithVersion>,
|
||||||
) -> Result<Json<ApiResponse<DialysisRecordResp>>, AppError>
|
) -> Result<Json<ApiResponse<DialysisRecordResp>>, AppError>
|
||||||
where
|
where
|
||||||
HealthState: FromRef<S>,
|
DialysisState: FromRef<S>,
|
||||||
S: Clone + Send + Sync + 'static,
|
S: Clone + Send + Sync + 'static,
|
||||||
{
|
{
|
||||||
require_permission(&ctx, "health.health-data.manage")?;
|
require_permission(&ctx, "health.health-data.manage")?;
|
||||||
@@ -126,13 +126,13 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn delete_dialysis_record<S>(
|
pub async fn delete_dialysis_record<S>(
|
||||||
State(state): State<HealthState>,
|
State(state): State<DialysisState>,
|
||||||
Extension(ctx): Extension<TenantContext>,
|
Extension(ctx): Extension<TenantContext>,
|
||||||
Path(record_id): Path<Uuid>,
|
Path(record_id): Path<Uuid>,
|
||||||
Json(req): Json<DeleteWithVersion>,
|
Json(req): Json<DeleteWithVersion>,
|
||||||
) -> Result<Json<ApiResponse<()>>, AppError>
|
) -> Result<Json<ApiResponse<()>>, AppError>
|
||||||
where
|
where
|
||||||
HealthState: FromRef<S>,
|
DialysisState: FromRef<S>,
|
||||||
S: Clone + Send + Sync + 'static,
|
S: Clone + Send + Sync + 'static,
|
||||||
{
|
{
|
||||||
require_permission(&ctx, "health.health-data.manage")?;
|
require_permission(&ctx, "health.health-data.manage")?;
|
||||||
@@ -11,7 +11,7 @@ use erp_core::types::{ApiResponse, PaginatedResponse, TenantContext};
|
|||||||
use crate::dto::dialysis_prescription_dto::*;
|
use crate::dto::dialysis_prescription_dto::*;
|
||||||
use crate::dto::DeleteWithVersion;
|
use crate::dto::DeleteWithVersion;
|
||||||
use crate::service::dialysis_prescription_service;
|
use crate::service::dialysis_prescription_service;
|
||||||
use crate::state::HealthState;
|
use crate::state::DialysisState;
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, IntoParams)]
|
#[derive(Debug, Deserialize, IntoParams)]
|
||||||
pub struct DialysisPrescriptionListParams {
|
pub struct DialysisPrescriptionListParams {
|
||||||
@@ -29,12 +29,12 @@ pub struct UpdateDialysisPrescriptionWithVersion {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn list_prescriptions<S>(
|
pub async fn list_prescriptions<S>(
|
||||||
State(state): State<HealthState>,
|
State(state): State<DialysisState>,
|
||||||
Extension(ctx): Extension<TenantContext>,
|
Extension(ctx): Extension<TenantContext>,
|
||||||
Query(params): Query<DialysisPrescriptionListParams>,
|
Query(params): Query<DialysisPrescriptionListParams>,
|
||||||
) -> Result<Json<ApiResponse<PaginatedResponse<DialysisPrescriptionResp>>>, AppError>
|
) -> Result<Json<ApiResponse<PaginatedResponse<DialysisPrescriptionResp>>>, AppError>
|
||||||
where
|
where
|
||||||
HealthState: FromRef<S>,
|
DialysisState: FromRef<S>,
|
||||||
S: Clone + Send + Sync + 'static,
|
S: Clone + Send + Sync + 'static,
|
||||||
{
|
{
|
||||||
require_permission(&ctx, "health.dialysis-prescription.list")?;
|
require_permission(&ctx, "health.dialysis-prescription.list")?;
|
||||||
@@ -48,12 +48,12 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_prescription<S>(
|
pub async fn get_prescription<S>(
|
||||||
State(state): State<HealthState>,
|
State(state): State<DialysisState>,
|
||||||
Extension(ctx): Extension<TenantContext>,
|
Extension(ctx): Extension<TenantContext>,
|
||||||
Path(id): Path<Uuid>,
|
Path(id): Path<Uuid>,
|
||||||
) -> Result<Json<ApiResponse<DialysisPrescriptionResp>>, AppError>
|
) -> Result<Json<ApiResponse<DialysisPrescriptionResp>>, AppError>
|
||||||
where
|
where
|
||||||
HealthState: FromRef<S>,
|
DialysisState: FromRef<S>,
|
||||||
S: Clone + Send + Sync + 'static,
|
S: Clone + Send + Sync + 'static,
|
||||||
{
|
{
|
||||||
require_permission(&ctx, "health.dialysis-prescription.list")?;
|
require_permission(&ctx, "health.dialysis-prescription.list")?;
|
||||||
@@ -62,12 +62,12 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn create_prescription<S>(
|
pub async fn create_prescription<S>(
|
||||||
State(state): State<HealthState>,
|
State(state): State<DialysisState>,
|
||||||
Extension(ctx): Extension<TenantContext>,
|
Extension(ctx): Extension<TenantContext>,
|
||||||
Json(req): Json<CreateDialysisPrescriptionReq>,
|
Json(req): Json<CreateDialysisPrescriptionReq>,
|
||||||
) -> Result<Json<ApiResponse<DialysisPrescriptionResp>>, AppError>
|
) -> Result<Json<ApiResponse<DialysisPrescriptionResp>>, AppError>
|
||||||
where
|
where
|
||||||
HealthState: FromRef<S>,
|
DialysisState: FromRef<S>,
|
||||||
S: Clone + Send + Sync + 'static,
|
S: Clone + Send + Sync + 'static,
|
||||||
{
|
{
|
||||||
require_permission(&ctx, "health.dialysis-prescription.manage")?;
|
require_permission(&ctx, "health.dialysis-prescription.manage")?;
|
||||||
@@ -81,13 +81,13 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn update_prescription<S>(
|
pub async fn update_prescription<S>(
|
||||||
State(state): State<HealthState>,
|
State(state): State<DialysisState>,
|
||||||
Extension(ctx): Extension<TenantContext>,
|
Extension(ctx): Extension<TenantContext>,
|
||||||
Path(id): Path<Uuid>,
|
Path(id): Path<Uuid>,
|
||||||
Json(req): Json<UpdateDialysisPrescriptionWithVersion>,
|
Json(req): Json<UpdateDialysisPrescriptionWithVersion>,
|
||||||
) -> Result<Json<ApiResponse<DialysisPrescriptionResp>>, AppError>
|
) -> Result<Json<ApiResponse<DialysisPrescriptionResp>>, AppError>
|
||||||
where
|
where
|
||||||
HealthState: FromRef<S>,
|
DialysisState: FromRef<S>,
|
||||||
S: Clone + Send + Sync + 'static,
|
S: Clone + Send + Sync + 'static,
|
||||||
{
|
{
|
||||||
require_permission(&ctx, "health.dialysis-prescription.manage")?;
|
require_permission(&ctx, "health.dialysis-prescription.manage")?;
|
||||||
@@ -101,13 +101,13 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn delete_prescription<S>(
|
pub async fn delete_prescription<S>(
|
||||||
State(state): State<HealthState>,
|
State(state): State<DialysisState>,
|
||||||
Extension(ctx): Extension<TenantContext>,
|
Extension(ctx): Extension<TenantContext>,
|
||||||
Path(id): Path<Uuid>,
|
Path(id): Path<Uuid>,
|
||||||
Json(req): Json<DeleteWithVersion>,
|
Json(req): Json<DeleteWithVersion>,
|
||||||
) -> Result<Json<ApiResponse<()>>, AppError>
|
) -> Result<Json<ApiResponse<()>>, AppError>
|
||||||
where
|
where
|
||||||
HealthState: FromRef<S>,
|
DialysisState: FromRef<S>,
|
||||||
S: Clone + Send + Sync + 'static,
|
S: Clone + Send + Sync + 'static,
|
||||||
{
|
{
|
||||||
require_permission(&ctx, "health.dialysis-prescription.manage")?;
|
require_permission(&ctx, "health.dialysis-prescription.manage")?;
|
||||||
2
crates/erp-dialysis/src/handler/mod.rs
Normal file
2
crates/erp-dialysis/src/handler/mod.rs
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
pub mod dialysis_handler;
|
||||||
|
pub mod dialysis_prescription_handler;
|
||||||
11
crates/erp-dialysis/src/lib.rs
Normal file
11
crates/erp-dialysis/src/lib.rs
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
pub mod dto;
|
||||||
|
pub mod entity;
|
||||||
|
pub mod error;
|
||||||
|
pub mod event;
|
||||||
|
pub mod handler;
|
||||||
|
pub mod module;
|
||||||
|
pub mod service;
|
||||||
|
pub mod state;
|
||||||
|
|
||||||
|
pub use module::DialysisModule;
|
||||||
|
pub use state::DialysisState;
|
||||||
114
crates/erp-dialysis/src/module.rs
Normal file
114
crates/erp-dialysis/src/module.rs
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
use async_trait::async_trait;
|
||||||
|
use axum::Router;
|
||||||
|
use erp_core::error::AppResult;
|
||||||
|
use erp_core::module::{ErpModule, ModuleContext, ModuleType, PermissionDescriptor};
|
||||||
|
|
||||||
|
use crate::handler::{dialysis_handler, dialysis_prescription_handler};
|
||||||
|
use crate::state::DialysisState;
|
||||||
|
|
||||||
|
pub struct DialysisModule;
|
||||||
|
|
||||||
|
impl DialysisModule {
|
||||||
|
pub fn public_routes<S>() -> Router<S>
|
||||||
|
where
|
||||||
|
DialysisState: axum::extract::FromRef<S>,
|
||||||
|
S: Clone + Send + Sync + 'static,
|
||||||
|
{
|
||||||
|
Router::new()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn protected_routes<S>() -> Router<S>
|
||||||
|
where
|
||||||
|
DialysisState: axum::extract::FromRef<S>,
|
||||||
|
S: Clone + Send + Sync + 'static,
|
||||||
|
{
|
||||||
|
Router::new()
|
||||||
|
// 透析记录
|
||||||
|
.route(
|
||||||
|
"/health/patients/{id}/dialysis-records",
|
||||||
|
axum::routing::get(dialysis_handler::list_dialysis_records),
|
||||||
|
)
|
||||||
|
.route(
|
||||||
|
"/health/dialysis-records",
|
||||||
|
axum::routing::post(dialysis_handler::create_dialysis_record),
|
||||||
|
)
|
||||||
|
.route(
|
||||||
|
"/health/dialysis-records/{id}",
|
||||||
|
axum::routing::get(dialysis_handler::get_dialysis_record)
|
||||||
|
.put(dialysis_handler::update_dialysis_record)
|
||||||
|
.delete(dialysis_handler::delete_dialysis_record),
|
||||||
|
)
|
||||||
|
.route(
|
||||||
|
"/health/dialysis-records/{id}/review",
|
||||||
|
axum::routing::put(dialysis_handler::review_dialysis_record),
|
||||||
|
)
|
||||||
|
// 透析方案
|
||||||
|
.route(
|
||||||
|
"/health/dialysis-prescriptions",
|
||||||
|
axum::routing::get(dialysis_prescription_handler::list_prescriptions)
|
||||||
|
.post(dialysis_prescription_handler::create_prescription),
|
||||||
|
)
|
||||||
|
.route(
|
||||||
|
"/health/dialysis-prescriptions/{id}",
|
||||||
|
axum::routing::get(dialysis_prescription_handler::get_prescription)
|
||||||
|
.put(dialysis_prescription_handler::update_prescription)
|
||||||
|
.delete(dialysis_prescription_handler::delete_prescription),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl ErpModule for DialysisModule {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"透析管理"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn id(&self) -> &str {
|
||||||
|
"erp-dialysis"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn version(&self) -> &str {
|
||||||
|
"0.1.0"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn module_type(&self) -> ModuleType {
|
||||||
|
ModuleType::Builtin
|
||||||
|
}
|
||||||
|
|
||||||
|
fn permissions(&self) -> Vec<PermissionDescriptor> {
|
||||||
|
vec![
|
||||||
|
PermissionDescriptor {
|
||||||
|
code: "health.health-data.list".into(),
|
||||||
|
name: "查看透析记录".into(),
|
||||||
|
description: "查看透析记录列表和详情".into(),
|
||||||
|
module: "erp-dialysis".into(),
|
||||||
|
},
|
||||||
|
PermissionDescriptor {
|
||||||
|
code: "health.health-data.manage".into(),
|
||||||
|
name: "管理透析记录".into(),
|
||||||
|
description: "创建、编辑、审阅、删除透析记录".into(),
|
||||||
|
module: "erp-dialysis".into(),
|
||||||
|
},
|
||||||
|
PermissionDescriptor {
|
||||||
|
code: "health.dialysis-prescription.list".into(),
|
||||||
|
name: "查看透析处方".into(),
|
||||||
|
description: "查看透析处方列表和详情".into(),
|
||||||
|
module: "erp-dialysis".into(),
|
||||||
|
},
|
||||||
|
PermissionDescriptor {
|
||||||
|
code: "health.dialysis-prescription.manage".into(),
|
||||||
|
name: "管理透析处方".into(),
|
||||||
|
description: "创建、编辑、删除透析处方".into(),
|
||||||
|
module: "erp-dialysis".into(),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn on_startup(&self, _ctx: &ModuleContext) -> AppResult<()> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn as_any(&self) -> &dyn std::any::Any {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
//! 透析方案 Service — 透析处方 CRUD
|
||||||
|
|
||||||
use chrono::Utc;
|
use chrono::Utc;
|
||||||
use num_traits::ToPrimitive;
|
use num_traits::ToPrimitive;
|
||||||
use sea_orm::entity::prelude::*;
|
use sea_orm::entity::prelude::*;
|
||||||
@@ -10,18 +12,18 @@ use erp_core::error::check_version;
|
|||||||
use erp_core::types::PaginatedResponse;
|
use erp_core::types::PaginatedResponse;
|
||||||
|
|
||||||
use crate::dto::dialysis_prescription_dto::*;
|
use crate::dto::dialysis_prescription_dto::*;
|
||||||
use crate::entity::{dialysis_prescription, patient};
|
use crate::entity::dialysis_prescription;
|
||||||
use crate::error::{HealthError, HealthResult};
|
use crate::error::{DialysisError, DialysisResult};
|
||||||
use crate::state::HealthState;
|
use crate::state::DialysisState;
|
||||||
|
|
||||||
pub async fn list_prescriptions(
|
pub async fn list_prescriptions(
|
||||||
state: &HealthState,
|
state: &DialysisState,
|
||||||
tenant_id: Uuid,
|
tenant_id: Uuid,
|
||||||
page: u64,
|
page: u64,
|
||||||
page_size: u64,
|
page_size: u64,
|
||||||
patient_id: Option<Uuid>,
|
patient_id: Option<Uuid>,
|
||||||
status: Option<String>,
|
status: Option<String>,
|
||||||
) -> HealthResult<PaginatedResponse<DialysisPrescriptionResp>> {
|
) -> DialysisResult<PaginatedResponse<DialysisPrescriptionResp>> {
|
||||||
let limit = page_size.min(100);
|
let limit = page_size.min(100);
|
||||||
let offset = page.saturating_sub(1) * limit;
|
let offset = page.saturating_sub(1) * limit;
|
||||||
|
|
||||||
@@ -51,35 +53,28 @@ pub async fn list_prescriptions(
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_prescription(
|
pub async fn get_prescription(
|
||||||
state: &HealthState,
|
state: &DialysisState,
|
||||||
tenant_id: Uuid,
|
tenant_id: Uuid,
|
||||||
id: Uuid,
|
id: Uuid,
|
||||||
) -> HealthResult<DialysisPrescriptionResp> {
|
) -> DialysisResult<DialysisPrescriptionResp> {
|
||||||
let m = dialysis_prescription::Entity::find()
|
let m = dialysis_prescription::Entity::find()
|
||||||
.filter(dialysis_prescription::Column::Id.eq(id))
|
.filter(dialysis_prescription::Column::Id.eq(id))
|
||||||
.filter(dialysis_prescription::Column::TenantId.eq(tenant_id))
|
.filter(dialysis_prescription::Column::TenantId.eq(tenant_id))
|
||||||
.filter(dialysis_prescription::Column::DeletedAt.is_null())
|
.filter(dialysis_prescription::Column::DeletedAt.is_null())
|
||||||
.one(&state.db)
|
.one(&state.db)
|
||||||
.await?
|
.await?
|
||||||
.ok_or(HealthError::DialysisPrescriptionNotFound)?;
|
.ok_or(DialysisError::DialysisPrescriptionNotFound)?;
|
||||||
|
|
||||||
Ok(model_to_resp(m))
|
Ok(model_to_resp(m))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn create_prescription(
|
pub async fn create_prescription(
|
||||||
state: &HealthState,
|
state: &DialysisState,
|
||||||
tenant_id: Uuid,
|
tenant_id: Uuid,
|
||||||
operator_id: Option<Uuid>,
|
operator_id: Option<Uuid>,
|
||||||
req: CreateDialysisPrescriptionReq,
|
req: CreateDialysisPrescriptionReq,
|
||||||
) -> HealthResult<DialysisPrescriptionResp> {
|
) -> DialysisResult<DialysisPrescriptionResp> {
|
||||||
// 校验患者存在
|
// 患者存在性由数据库 FK 约束保证,不再显式查询 patient 表
|
||||||
patient::Entity::find()
|
|
||||||
.filter(patient::Column::Id.eq(req.patient_id))
|
|
||||||
.filter(patient::Column::TenantId.eq(tenant_id))
|
|
||||||
.filter(patient::Column::DeletedAt.is_null())
|
|
||||||
.one(&state.db)
|
|
||||||
.await?
|
|
||||||
.ok_or(HealthError::PatientNotFound)?;
|
|
||||||
|
|
||||||
validate_anticoagulation_type(req.anticoagulation_type.as_deref())?;
|
validate_anticoagulation_type(req.anticoagulation_type.as_deref())?;
|
||||||
validate_vascular_access_type(req.vascular_access_type.as_deref())?;
|
validate_vascular_access_type(req.vascular_access_type.as_deref())?;
|
||||||
@@ -128,23 +123,23 @@ pub async fn create_prescription(
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn update_prescription(
|
pub async fn update_prescription(
|
||||||
state: &HealthState,
|
state: &DialysisState,
|
||||||
tenant_id: Uuid,
|
tenant_id: Uuid,
|
||||||
id: Uuid,
|
id: Uuid,
|
||||||
operator_id: Option<Uuid>,
|
operator_id: Option<Uuid>,
|
||||||
req: UpdateDialysisPrescriptionReq,
|
req: UpdateDialysisPrescriptionReq,
|
||||||
expected_version: i32,
|
expected_version: i32,
|
||||||
) -> HealthResult<DialysisPrescriptionResp> {
|
) -> DialysisResult<DialysisPrescriptionResp> {
|
||||||
let model = dialysis_prescription::Entity::find()
|
let model = dialysis_prescription::Entity::find()
|
||||||
.filter(dialysis_prescription::Column::Id.eq(id))
|
.filter(dialysis_prescription::Column::Id.eq(id))
|
||||||
.filter(dialysis_prescription::Column::TenantId.eq(tenant_id))
|
.filter(dialysis_prescription::Column::TenantId.eq(tenant_id))
|
||||||
.filter(dialysis_prescription::Column::DeletedAt.is_null())
|
.filter(dialysis_prescription::Column::DeletedAt.is_null())
|
||||||
.one(&state.db)
|
.one(&state.db)
|
||||||
.await?
|
.await?
|
||||||
.ok_or(HealthError::DialysisPrescriptionNotFound)?;
|
.ok_or(DialysisError::DialysisPrescriptionNotFound)?;
|
||||||
|
|
||||||
let next_ver = check_version(expected_version, model.version)
|
let next_ver = check_version(expected_version, model.version)
|
||||||
.map_err(|_| HealthError::VersionMismatch)?;
|
.map_err(|_| DialysisError::VersionMismatch)?;
|
||||||
|
|
||||||
if let Some(ref t) = req.anticoagulation_type { validate_anticoagulation_type(Some(t))?; }
|
if let Some(ref t) = req.anticoagulation_type { validate_anticoagulation_type(Some(t))?; }
|
||||||
if let Some(ref t) = req.vascular_access_type { validate_vascular_access_type(Some(t))?; }
|
if let Some(ref t) = req.vascular_access_type { validate_vascular_access_type(Some(t))?; }
|
||||||
@@ -185,22 +180,22 @@ pub async fn update_prescription(
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn delete_prescription(
|
pub async fn delete_prescription(
|
||||||
state: &HealthState,
|
state: &DialysisState,
|
||||||
tenant_id: Uuid,
|
tenant_id: Uuid,
|
||||||
id: Uuid,
|
id: Uuid,
|
||||||
operator_id: Option<Uuid>,
|
operator_id: Option<Uuid>,
|
||||||
expected_version: i32,
|
expected_version: i32,
|
||||||
) -> HealthResult<()> {
|
) -> DialysisResult<()> {
|
||||||
let model = dialysis_prescription::Entity::find()
|
let model = dialysis_prescription::Entity::find()
|
||||||
.filter(dialysis_prescription::Column::Id.eq(id))
|
.filter(dialysis_prescription::Column::Id.eq(id))
|
||||||
.filter(dialysis_prescription::Column::TenantId.eq(tenant_id))
|
.filter(dialysis_prescription::Column::TenantId.eq(tenant_id))
|
||||||
.filter(dialysis_prescription::Column::DeletedAt.is_null())
|
.filter(dialysis_prescription::Column::DeletedAt.is_null())
|
||||||
.one(&state.db)
|
.one(&state.db)
|
||||||
.await?
|
.await?
|
||||||
.ok_or(HealthError::DialysisPrescriptionNotFound)?;
|
.ok_or(DialysisError::DialysisPrescriptionNotFound)?;
|
||||||
|
|
||||||
let next_ver = check_version(expected_version, model.version)
|
let next_ver = check_version(expected_version, model.version)
|
||||||
.map_err(|_| HealthError::VersionMismatch)?;
|
.map_err(|_| DialysisError::VersionMismatch)?;
|
||||||
|
|
||||||
let mut active: dialysis_prescription::ActiveModel = model.into();
|
let mut active: dialysis_prescription::ActiveModel = model.into();
|
||||||
active.deleted_at = Set(Some(Utc::now()));
|
active.deleted_at = Set(Some(Utc::now()));
|
||||||
@@ -218,6 +213,10 @@ pub async fn delete_prescription(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// 私有辅助函数
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
fn model_to_resp(m: dialysis_prescription::Model) -> DialysisPrescriptionResp {
|
fn model_to_resp(m: dialysis_prescription::Model) -> DialysisPrescriptionResp {
|
||||||
DialysisPrescriptionResp {
|
DialysisPrescriptionResp {
|
||||||
id: m.id,
|
id: m.id,
|
||||||
@@ -248,11 +247,11 @@ fn model_to_resp(m: dialysis_prescription::Model) -> DialysisPrescriptionResp {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn validate_anticoagulation_type(val: Option<&str>) -> HealthResult<()> {
|
fn validate_anticoagulation_type(val: Option<&str>) -> DialysisResult<()> {
|
||||||
if let Some(t) = val {
|
if let Some(t) = val {
|
||||||
let valid = ["heparin", "lmwh", "heparin_free"];
|
let valid = ["heparin", "lmwh", "heparin_free"];
|
||||||
if !valid.contains(&t) {
|
if !valid.contains(&t) {
|
||||||
return Err(HealthError::Validation(format!(
|
return Err(DialysisError::Validation(format!(
|
||||||
"anticoagulation_type 必须为: {}", valid.join(", ")
|
"anticoagulation_type 必须为: {}", valid.join(", ")
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
@@ -260,11 +259,11 @@ fn validate_anticoagulation_type(val: Option<&str>) -> HealthResult<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn validate_vascular_access_type(val: Option<&str>) -> HealthResult<()> {
|
fn validate_vascular_access_type(val: Option<&str>) -> DialysisResult<()> {
|
||||||
if let Some(t) = val {
|
if let Some(t) = val {
|
||||||
let valid = ["avf", "avg", "cvc"];
|
let valid = ["avf", "avg", "cvc"];
|
||||||
if !valid.contains(&t) {
|
if !valid.contains(&t) {
|
||||||
return Err(HealthError::Validation(format!(
|
return Err(DialysisError::Validation(format!(
|
||||||
"vascular_access_type 必须为: {}", valid.join(", ")
|
"vascular_access_type 必须为: {}", valid.join(", ")
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
@@ -13,18 +13,17 @@ use erp_core::error::check_version;
|
|||||||
use erp_core::types::PaginatedResponse;
|
use erp_core::types::PaginatedResponse;
|
||||||
|
|
||||||
use crate::dto::dialysis_dto::*;
|
use crate::dto::dialysis_dto::*;
|
||||||
use crate::entity::{dialysis_record, patient};
|
use crate::entity::dialysis_record;
|
||||||
use crate::error::{HealthError, HealthResult};
|
use crate::error::{DialysisError, DialysisResult};
|
||||||
use crate::service::validation;
|
use crate::state::DialysisState;
|
||||||
use crate::state::HealthState;
|
|
||||||
|
|
||||||
pub async fn list_dialysis_records(
|
pub async fn list_dialysis_records(
|
||||||
state: &HealthState,
|
state: &DialysisState,
|
||||||
tenant_id: Uuid,
|
tenant_id: Uuid,
|
||||||
patient_id: Uuid,
|
patient_id: Uuid,
|
||||||
page: u64,
|
page: u64,
|
||||||
page_size: u64,
|
page_size: u64,
|
||||||
) -> HealthResult<PaginatedResponse<DialysisRecordResp>> {
|
) -> DialysisResult<PaginatedResponse<DialysisRecordResp>> {
|
||||||
let limit = page_size.min(100);
|
let limit = page_size.min(100);
|
||||||
let offset = page.saturating_sub(1) * limit;
|
let offset = page.saturating_sub(1) * limit;
|
||||||
|
|
||||||
@@ -49,34 +48,28 @@ pub async fn list_dialysis_records(
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_dialysis_record(
|
pub async fn get_dialysis_record(
|
||||||
state: &HealthState,
|
state: &DialysisState,
|
||||||
tenant_id: Uuid,
|
tenant_id: Uuid,
|
||||||
record_id: Uuid,
|
record_id: Uuid,
|
||||||
) -> HealthResult<DialysisRecordResp> {
|
) -> DialysisResult<DialysisRecordResp> {
|
||||||
let m = dialysis_record::Entity::find()
|
let m = dialysis_record::Entity::find()
|
||||||
.filter(dialysis_record::Column::Id.eq(record_id))
|
.filter(dialysis_record::Column::Id.eq(record_id))
|
||||||
.filter(dialysis_record::Column::TenantId.eq(tenant_id))
|
.filter(dialysis_record::Column::TenantId.eq(tenant_id))
|
||||||
.filter(dialysis_record::Column::DeletedAt.is_null())
|
.filter(dialysis_record::Column::DeletedAt.is_null())
|
||||||
.one(&state.db)
|
.one(&state.db)
|
||||||
.await?
|
.await?
|
||||||
.ok_or(HealthError::DialysisRecordNotFound)?;
|
.ok_or(DialysisError::DialysisRecordNotFound)?;
|
||||||
|
|
||||||
Ok(to_resp(&state.crypto, m))
|
Ok(to_resp(&state.crypto, m))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn create_dialysis_record(
|
pub async fn create_dialysis_record(
|
||||||
state: &HealthState,
|
state: &DialysisState,
|
||||||
tenant_id: Uuid,
|
tenant_id: Uuid,
|
||||||
operator_id: Option<Uuid>,
|
operator_id: Option<Uuid>,
|
||||||
req: CreateDialysisRecordReq,
|
req: CreateDialysisRecordReq,
|
||||||
) -> HealthResult<DialysisRecordResp> {
|
) -> DialysisResult<DialysisRecordResp> {
|
||||||
patient::Entity::find()
|
// 患者存在性由数据库 FK 约束保证,不再显式查询 patient 表
|
||||||
.filter(patient::Column::Id.eq(req.patient_id))
|
|
||||||
.filter(patient::Column::TenantId.eq(tenant_id))
|
|
||||||
.filter(patient::Column::DeletedAt.is_null())
|
|
||||||
.one(&state.db)
|
|
||||||
.await?
|
|
||||||
.ok_or(HealthError::PatientNotFound)?;
|
|
||||||
|
|
||||||
validate_dialysis_type(&req.dialysis_type)?;
|
validate_dialysis_type(&req.dialysis_type)?;
|
||||||
|
|
||||||
@@ -84,9 +77,9 @@ pub async fn create_dialysis_record(
|
|||||||
|
|
||||||
// PII 加密
|
// PII 加密
|
||||||
let encrypted_symptoms = req.symptoms.as_ref()
|
let encrypted_symptoms = req.symptoms.as_ref()
|
||||||
.map(|v| -> HealthResult<serde_json::Value> {
|
.map(|v| -> DialysisResult<serde_json::Value> {
|
||||||
let json_str = serde_json::to_string(v)
|
let json_str = serde_json::to_string(v)
|
||||||
.map_err(|e| HealthError::Validation(e.to_string()))?;
|
.map_err(|e| DialysisError::Validation(e.to_string()))?;
|
||||||
Ok(serde_json::Value::String(pii::encrypt(kek, &json_str)?))
|
Ok(serde_json::Value::String(pii::encrypt(kek, &json_str)?))
|
||||||
})
|
})
|
||||||
.transpose()?;
|
.transpose()?;
|
||||||
@@ -141,23 +134,23 @@ pub async fn create_dialysis_record(
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn update_dialysis_record(
|
pub async fn update_dialysis_record(
|
||||||
state: &HealthState,
|
state: &DialysisState,
|
||||||
tenant_id: Uuid,
|
tenant_id: Uuid,
|
||||||
record_id: Uuid,
|
record_id: Uuid,
|
||||||
operator_id: Option<Uuid>,
|
operator_id: Option<Uuid>,
|
||||||
req: UpdateDialysisRecordReq,
|
req: UpdateDialysisRecordReq,
|
||||||
expected_version: i32,
|
expected_version: i32,
|
||||||
) -> HealthResult<DialysisRecordResp> {
|
) -> DialysisResult<DialysisRecordResp> {
|
||||||
let model = dialysis_record::Entity::find()
|
let model = dialysis_record::Entity::find()
|
||||||
.filter(dialysis_record::Column::Id.eq(record_id))
|
.filter(dialysis_record::Column::Id.eq(record_id))
|
||||||
.filter(dialysis_record::Column::TenantId.eq(tenant_id))
|
.filter(dialysis_record::Column::TenantId.eq(tenant_id))
|
||||||
.filter(dialysis_record::Column::DeletedAt.is_null())
|
.filter(dialysis_record::Column::DeletedAt.is_null())
|
||||||
.one(&state.db)
|
.one(&state.db)
|
||||||
.await?
|
.await?
|
||||||
.ok_or(HealthError::DialysisRecordNotFound)?;
|
.ok_or(DialysisError::DialysisRecordNotFound)?;
|
||||||
|
|
||||||
let next_ver = check_version(expected_version, model.version)
|
let next_ver = check_version(expected_version, model.version)
|
||||||
.map_err(|_| HealthError::VersionMismatch)?;
|
.map_err(|_| DialysisError::VersionMismatch)?;
|
||||||
|
|
||||||
let mut active: dialysis_record::ActiveModel = model.into();
|
let mut active: dialysis_record::ActiveModel = model.into();
|
||||||
if let Some(v) = req.dialysis_date { active.dialysis_date = Set(v); }
|
if let Some(v) = req.dialysis_date { active.dialysis_date = Set(v); }
|
||||||
@@ -205,24 +198,24 @@ pub async fn update_dialysis_record(
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn review_dialysis_record(
|
pub async fn review_dialysis_record(
|
||||||
state: &HealthState,
|
state: &DialysisState,
|
||||||
tenant_id: Uuid,
|
tenant_id: Uuid,
|
||||||
record_id: Uuid,
|
record_id: Uuid,
|
||||||
reviewer_id: Uuid,
|
reviewer_id: Uuid,
|
||||||
expected_version: i32,
|
expected_version: i32,
|
||||||
) -> HealthResult<DialysisRecordResp> {
|
) -> DialysisResult<DialysisRecordResp> {
|
||||||
let model = dialysis_record::Entity::find()
|
let model = dialysis_record::Entity::find()
|
||||||
.filter(dialysis_record::Column::Id.eq(record_id))
|
.filter(dialysis_record::Column::Id.eq(record_id))
|
||||||
.filter(dialysis_record::Column::TenantId.eq(tenant_id))
|
.filter(dialysis_record::Column::TenantId.eq(tenant_id))
|
||||||
.filter(dialysis_record::Column::DeletedAt.is_null())
|
.filter(dialysis_record::Column::DeletedAt.is_null())
|
||||||
.one(&state.db)
|
.one(&state.db)
|
||||||
.await?
|
.await?
|
||||||
.ok_or(HealthError::DialysisRecordNotFound)?;
|
.ok_or(DialysisError::DialysisRecordNotFound)?;
|
||||||
|
|
||||||
let next_ver = check_version(expected_version, model.version)
|
let next_ver = check_version(expected_version, model.version)
|
||||||
.map_err(|_| HealthError::VersionMismatch)?;
|
.map_err(|_| DialysisError::VersionMismatch)?;
|
||||||
|
|
||||||
validation::validate_dialysis_status_transition(&model.status, "reviewed")?;
|
validate_dialysis_status_transition(&model.status, "reviewed")?;
|
||||||
|
|
||||||
let mut active: dialysis_record::ActiveModel = model.into();
|
let mut active: dialysis_record::ActiveModel = model.into();
|
||||||
active.status = Set("reviewed".to_string());
|
active.status = Set("reviewed".to_string());
|
||||||
@@ -244,22 +237,22 @@ pub async fn review_dialysis_record(
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn delete_dialysis_record(
|
pub async fn delete_dialysis_record(
|
||||||
state: &HealthState,
|
state: &DialysisState,
|
||||||
tenant_id: Uuid,
|
tenant_id: Uuid,
|
||||||
record_id: Uuid,
|
record_id: Uuid,
|
||||||
operator_id: Option<Uuid>,
|
operator_id: Option<Uuid>,
|
||||||
expected_version: i32,
|
expected_version: i32,
|
||||||
) -> HealthResult<()> {
|
) -> DialysisResult<()> {
|
||||||
let model = dialysis_record::Entity::find()
|
let model = dialysis_record::Entity::find()
|
||||||
.filter(dialysis_record::Column::Id.eq(record_id))
|
.filter(dialysis_record::Column::Id.eq(record_id))
|
||||||
.filter(dialysis_record::Column::TenantId.eq(tenant_id))
|
.filter(dialysis_record::Column::TenantId.eq(tenant_id))
|
||||||
.filter(dialysis_record::Column::DeletedAt.is_null())
|
.filter(dialysis_record::Column::DeletedAt.is_null())
|
||||||
.one(&state.db)
|
.one(&state.db)
|
||||||
.await?
|
.await?
|
||||||
.ok_or(HealthError::DialysisRecordNotFound)?;
|
.ok_or(DialysisError::DialysisRecordNotFound)?;
|
||||||
|
|
||||||
let next_ver = check_version(expected_version, model.version)
|
let next_ver = check_version(expected_version, model.version)
|
||||||
.map_err(|_| HealthError::VersionMismatch)?;
|
.map_err(|_| DialysisError::VersionMismatch)?;
|
||||||
|
|
||||||
let mut active: dialysis_record::ActiveModel = model.into();
|
let mut active: dialysis_record::ActiveModel = model.into();
|
||||||
active.deleted_at = Set(Some(Utc::now()));
|
active.deleted_at = Set(Some(Utc::now()));
|
||||||
@@ -277,15 +270,40 @@ pub async fn delete_dialysis_record(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn validate_dialysis_type(dialysis_type: &str) -> HealthResult<()> {
|
// ---------------------------------------------------------------------------
|
||||||
|
// 私有辅助函数
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/// 校验透析类型枚举
|
||||||
|
fn validate_dialysis_type(dialysis_type: &str) -> DialysisResult<()> {
|
||||||
match dialysis_type {
|
match dialysis_type {
|
||||||
"HD" | "HDF" | "HF" => Ok(()),
|
"HD" | "HDF" | "HF" => Ok(()),
|
||||||
_ => Err(HealthError::Validation(format!(
|
_ => Err(DialysisError::Validation(format!(
|
||||||
"无效的透析类型: {},允许值: HD, HDF, HF", dialysis_type
|
"无效的透析类型: {},允许值: HD, HDF, HF", dialysis_type
|
||||||
))),
|
))),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 校验透析记录状态转换
|
||||||
|
/// draft -> completed -> reviewed
|
||||||
|
fn validate_dialysis_status_transition(current: &str, new: &str) -> DialysisResult<()> {
|
||||||
|
if current == new {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
let allowed = match current {
|
||||||
|
"draft" => matches!(new, "completed"),
|
||||||
|
"completed" => matches!(new, "reviewed"),
|
||||||
|
_ => false,
|
||||||
|
};
|
||||||
|
if allowed {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(DialysisError::InvalidStatusTransition(format!(
|
||||||
|
"dialysis_record.status: 不允许从 '{}' 转换到 '{}'", current, new
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn to_resp(crypto: &erp_core::crypto::PiiCrypto, m: dialysis_record::Model) -> DialysisRecordResp {
|
fn to_resp(crypto: &erp_core::crypto::PiiCrypto, m: dialysis_record::Model) -> DialysisRecordResp {
|
||||||
let kek = crypto.kek();
|
let kek = crypto.kek();
|
||||||
|
|
||||||
@@ -330,3 +348,32 @@ fn to_resp(crypto: &erp_core::crypto::PiiCrypto, m: dialysis_record::Model) -> D
|
|||||||
version: m.version,
|
version: m.version,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
// --- validate_dialysis_type ---
|
||||||
|
#[test]
|
||||||
|
fn dialysis_type_hd() { assert!(validate_dialysis_type("HD").is_ok()); }
|
||||||
|
#[test]
|
||||||
|
fn dialysis_type_hdf() { assert!(validate_dialysis_type("HDF").is_ok()); }
|
||||||
|
#[test]
|
||||||
|
fn dialysis_type_hf() { assert!(validate_dialysis_type("HF").is_ok()); }
|
||||||
|
#[test]
|
||||||
|
fn dialysis_type_invalid() { assert!(validate_dialysis_type("PD").is_err()); }
|
||||||
|
|
||||||
|
// --- validate_dialysis_status_transition ---
|
||||||
|
#[test]
|
||||||
|
fn dial_draft_to_completed() { assert!(validate_dialysis_status_transition("draft", "completed").is_ok()); }
|
||||||
|
#[test]
|
||||||
|
fn dial_draft_to_reviewed_fails() { assert!(validate_dialysis_status_transition("draft", "reviewed").is_err()); }
|
||||||
|
#[test]
|
||||||
|
fn dial_completed_to_reviewed() { assert!(validate_dialysis_status_transition("completed", "reviewed").is_ok()); }
|
||||||
|
#[test]
|
||||||
|
fn dial_completed_to_draft_fails() { assert!(validate_dialysis_status_transition("completed", "draft").is_err()); }
|
||||||
|
#[test]
|
||||||
|
fn dial_reviewed_to_any_fails() { assert!(validate_dialysis_status_transition("reviewed", "draft").is_err()); }
|
||||||
|
#[test]
|
||||||
|
fn dial_same_status_ok() { assert!(validate_dialysis_status_transition("draft", "draft").is_ok()); }
|
||||||
|
}
|
||||||
2
crates/erp-dialysis/src/service/mod.rs
Normal file
2
crates/erp-dialysis/src/service/mod.rs
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
pub mod dialysis_service;
|
||||||
|
pub mod dialysis_prescription_service;
|
||||||
10
crates/erp-dialysis/src/state.rs
Normal file
10
crates/erp-dialysis/src/state.rs
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
use erp_core::crypto::PiiCrypto;
|
||||||
|
use erp_core::events::EventBus;
|
||||||
|
use sea_orm::DatabaseConnection;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct DialysisState {
|
||||||
|
pub db: DatabaseConnection,
|
||||||
|
pub event_bus: EventBus,
|
||||||
|
pub crypto: PiiCrypto,
|
||||||
|
}
|
||||||
@@ -263,3 +263,15 @@ pub struct MiniTodayResp {
|
|||||||
pub blood_sugar: Option<IndicatorSummary>,
|
pub blood_sugar: Option<IndicatorSummary>,
|
||||||
pub weight: Option<IndicatorSummary>,
|
pub weight: Option<IndicatorSummary>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, serde::Deserialize, ToSchema)]
|
||||||
|
pub struct ReviewLabReportReq {
|
||||||
|
pub doctor_notes: Option<String>,
|
||||||
|
pub items: Option<serde_json::Value>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ReviewLabReportReq {
|
||||||
|
pub fn sanitize(&mut self) {
|
||||||
|
self.doctor_notes = sanitize_option(self.doctor_notes.take());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -6,8 +6,6 @@ pub mod consultation_dto;
|
|||||||
pub mod daily_monitoring_dto;
|
pub mod daily_monitoring_dto;
|
||||||
pub mod diagnosis_dto;
|
pub mod diagnosis_dto;
|
||||||
pub mod medication_record_dto;
|
pub mod medication_record_dto;
|
||||||
pub mod dialysis_dto;
|
|
||||||
pub mod dialysis_prescription_dto;
|
|
||||||
pub mod doctor_dto;
|
pub mod doctor_dto;
|
||||||
pub mod follow_up_dto;
|
pub mod follow_up_dto;
|
||||||
pub mod follow_up_template_dto;
|
pub mod follow_up_template_dto;
|
||||||
|
|||||||
@@ -16,8 +16,6 @@ pub mod critical_alert_response;
|
|||||||
pub mod daily_monitoring;
|
pub mod daily_monitoring;
|
||||||
pub mod device_readings;
|
pub mod device_readings;
|
||||||
pub mod diagnosis;
|
pub mod diagnosis;
|
||||||
pub mod dialysis_prescription;
|
|
||||||
pub mod dialysis_record;
|
|
||||||
pub mod doctor_profile;
|
pub mod doctor_profile;
|
||||||
pub mod doctor_schedule;
|
pub mod doctor_schedule;
|
||||||
pub mod follow_up_record;
|
pub mod follow_up_record;
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ use erp_core::rbac::require_permission;
|
|||||||
use erp_core::types::{ApiResponse, PaginatedResponse, TenantContext};
|
use erp_core::types::{ApiResponse, PaginatedResponse, TenantContext};
|
||||||
|
|
||||||
use crate::dto::health_data_dto::*;
|
use crate::dto::health_data_dto::*;
|
||||||
use crate::dto::dialysis_dto::ReviewLabReportReq;
|
|
||||||
use crate::dto::DeleteWithVersion;
|
use crate::dto::DeleteWithVersion;
|
||||||
use crate::service::health_data_service;
|
use crate::service::health_data_service;
|
||||||
use crate::service::trend_service;
|
use crate::service::trend_service;
|
||||||
|
|||||||
@@ -12,8 +12,6 @@ pub mod daily_monitoring_handler;
|
|||||||
pub mod device_reading_handler;
|
pub mod device_reading_handler;
|
||||||
pub mod diagnosis_handler;
|
pub mod diagnosis_handler;
|
||||||
pub mod medication_record_handler;
|
pub mod medication_record_handler;
|
||||||
pub mod dialysis_handler;
|
|
||||||
pub mod dialysis_prescription_handler;
|
|
||||||
pub mod doctor_handler;
|
pub mod doctor_handler;
|
||||||
pub mod follow_up_handler;
|
pub mod follow_up_handler;
|
||||||
pub mod follow_up_template_handler;
|
pub mod follow_up_template_handler;
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ use erp_core::module::{ErpModule, PermissionDescriptor};
|
|||||||
|
|
||||||
use crate::handler::{
|
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_reading_handler, diagnosis_handler, dialysis_handler, dialysis_prescription_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_reading_handler, diagnosis_handler, doctor_handler, follow_up_handler, follow_up_template_handler,
|
||||||
health_data_handler, medication_record_handler, patient_handler, points_handler, stats_handler,
|
health_data_handler, medication_record_handler, patient_handler, points_handler, stats_handler,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -225,37 +225,6 @@ impl HealthModule {
|
|||||||
"/health/vital-signs/today",
|
"/health/vital-signs/today",
|
||||||
axum::routing::get(health_data_handler::get_mini_today),
|
axum::routing::get(health_data_handler::get_mini_today),
|
||||||
)
|
)
|
||||||
// 透析记录(血透专科)
|
|
||||||
.route(
|
|
||||||
"/health/patients/{id}/dialysis-records",
|
|
||||||
axum::routing::get(dialysis_handler::list_dialysis_records),
|
|
||||||
)
|
|
||||||
.route(
|
|
||||||
"/health/dialysis-records",
|
|
||||||
axum::routing::post(dialysis_handler::create_dialysis_record),
|
|
||||||
)
|
|
||||||
.route(
|
|
||||||
"/health/dialysis-records/{id}",
|
|
||||||
axum::routing::get(dialysis_handler::get_dialysis_record)
|
|
||||||
.put(dialysis_handler::update_dialysis_record)
|
|
||||||
.delete(dialysis_handler::delete_dialysis_record),
|
|
||||||
)
|
|
||||||
.route(
|
|
||||||
"/health/dialysis-records/{id}/review",
|
|
||||||
axum::routing::put(dialysis_handler::review_dialysis_record),
|
|
||||||
)
|
|
||||||
// 透析方案
|
|
||||||
.route(
|
|
||||||
"/health/dialysis-prescriptions",
|
|
||||||
axum::routing::get(dialysis_prescription_handler::list_prescriptions)
|
|
||||||
.post(dialysis_prescription_handler::create_prescription),
|
|
||||||
)
|
|
||||||
.route(
|
|
||||||
"/health/dialysis-prescriptions/{id}",
|
|
||||||
axum::routing::get(dialysis_prescription_handler::get_prescription)
|
|
||||||
.put(dialysis_prescription_handler::update_prescription)
|
|
||||||
.delete(dialysis_prescription_handler::delete_prescription),
|
|
||||||
)
|
|
||||||
// 随访模板
|
// 随访模板
|
||||||
.route(
|
.route(
|
||||||
"/health/follow-up-templates",
|
"/health/follow-up-templates",
|
||||||
|
|||||||
@@ -581,16 +581,18 @@ pub async fn enrich_doctor_dashboard_health(
|
|||||||
doctor_user_id: Uuid,
|
doctor_user_id: Uuid,
|
||||||
dashboard: &mut DoctorDashboard,
|
dashboard: &mut DoctorDashboard,
|
||||||
) -> HealthResult<()> {
|
) -> HealthResult<()> {
|
||||||
use crate::entity::{dialysis_record, lab_report, appointment};
|
use crate::entity::{lab_report, appointment};
|
||||||
|
use sea_orm::{FromQueryResult, Statement, DatabaseBackend};
|
||||||
|
|
||||||
// 待审核透析记录(doctor_id 通过患者关联,这里取全租户待审核)
|
// 待审核透析记录(raw SQL — entity 已拆分到 erp-dialysis crate)
|
||||||
let pending_dialysis = dialysis_record::Entity::find()
|
#[derive(FromQueryResult)]
|
||||||
.filter(dialysis_record::Column::TenantId.eq(tenant_id))
|
struct DialysisCount { count: i64 }
|
||||||
.filter(dialysis_record::Column::DeletedAt.is_null())
|
let pending_dialysis = DialysisCount::find_by_statement(Statement::from_sql_and_values(
|
||||||
.filter(dialysis_record::Column::Status.eq("draft"))
|
DatabaseBackend::Postgres,
|
||||||
.count(&state.db)
|
"SELECT COUNT(*)::int8 AS count FROM dialysis_record WHERE tenant_id = $1 AND deleted_at IS NULL AND status = 'draft'",
|
||||||
.await?;
|
[tenant_id.into()],
|
||||||
dashboard.pending_dialysis_review = pending_dialysis as i64;
|
)).one(&state.db).await?.map(|r| r.count).unwrap_or(0);
|
||||||
|
dashboard.pending_dialysis_review = pending_dialysis;
|
||||||
|
|
||||||
// 待审核化验报告
|
// 待审核化验报告
|
||||||
let pending_lab = lab_report::Entity::find()
|
let pending_lab = lab_report::Entity::find()
|
||||||
|
|||||||
@@ -563,7 +563,7 @@ pub async fn review_lab_report(
|
|||||||
patient_id: Uuid,
|
patient_id: Uuid,
|
||||||
report_id: Uuid,
|
report_id: Uuid,
|
||||||
reviewer_id: Uuid,
|
reviewer_id: Uuid,
|
||||||
req: crate::dto::dialysis_dto::ReviewLabReportReq,
|
req: crate::dto::health_data_dto::ReviewLabReportReq,
|
||||||
expected_version: i32,
|
expected_version: i32,
|
||||||
) -> HealthResult<LabReportResp> {
|
) -> HealthResult<LabReportResp> {
|
||||||
let model = lab_report::Entity::find()
|
let model = lab_report::Entity::find()
|
||||||
|
|||||||
@@ -13,8 +13,6 @@ pub mod daily_monitoring_service;
|
|||||||
pub mod device_reading_service;
|
pub mod device_reading_service;
|
||||||
pub mod diagnosis_service;
|
pub mod diagnosis_service;
|
||||||
pub mod medication_record_service;
|
pub mod medication_record_service;
|
||||||
pub mod dialysis_prescription_service;
|
|
||||||
pub mod dialysis_service;
|
|
||||||
pub mod doctor_service;
|
pub mod doctor_service;
|
||||||
pub mod follow_up_service;
|
pub mod follow_up_service;
|
||||||
pub mod follow_up_template_service;
|
pub mod follow_up_template_service;
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
use sea_orm::{ColumnTrait, EntityTrait, PaginatorTrait, QueryFilter, sea_query::Expr, FromQueryResult};
|
use sea_orm::{ColumnTrait, EntityTrait, PaginatorTrait, QueryFilter, sea_query::Expr, FromQueryResult, Statement, DatabaseBackend};
|
||||||
|
|
||||||
use erp_core::error::AppResult;
|
use erp_core::error::AppResult;
|
||||||
|
|
||||||
use crate::dto::stats_dto::*;
|
use crate::dto::stats_dto::*;
|
||||||
use crate::entity::{
|
use crate::entity::{
|
||||||
patient, consultation_session, follow_up_task,
|
patient, consultation_session, follow_up_task,
|
||||||
points_transaction, dialysis_record, lab_report,
|
points_transaction, lab_report,
|
||||||
appointment, vital_signs, patient_doctor_relation, doctor_profile,
|
appointment, vital_signs, patient_doctor_relation, doctor_profile,
|
||||||
};
|
};
|
||||||
use crate::state::HealthState;
|
use crate::state::HealthState;
|
||||||
@@ -190,25 +190,27 @@ pub async fn get_dialysis_statistics(
|
|||||||
) -> AppResult<DialysisStatisticsResp> {
|
) -> AppResult<DialysisStatisticsResp> {
|
||||||
let db = &state.db;
|
let db = &state.db;
|
||||||
|
|
||||||
let total_records = dialysis_record::Entity::find()
|
// 使用 raw SQL 替代 dialysis_record entity(已拆分到 erp-dialysis crate)
|
||||||
.filter(dialysis_record::Column::TenantId.eq(tenant_id))
|
#[derive(FromQueryResult)]
|
||||||
.filter(dialysis_record::Column::DeletedAt.is_null())
|
struct CountRow { count: i64 }
|
||||||
.count(db)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
let this_month = dialysis_record::Entity::find()
|
let total_records = CountRow::find_by_statement(Statement::from_sql_and_values(
|
||||||
.filter(dialysis_record::Column::TenantId.eq(tenant_id))
|
DatabaseBackend::Postgres,
|
||||||
.filter(dialysis_record::Column::DeletedAt.is_null())
|
"SELECT COUNT(*)::int8 AS count FROM dialysis_record WHERE tenant_id = $1 AND deleted_at IS NULL",
|
||||||
.filter(Expr::col(dialysis_record::Column::CreatedAt).gte(Expr::cust("date_trunc('month', NOW())")))
|
[tenant_id.into()],
|
||||||
.count(db)
|
)).one(db).await?.map(|r| r.count).unwrap_or(0);
|
||||||
.await?;
|
|
||||||
|
|
||||||
let pending_review = dialysis_record::Entity::find()
|
let this_month = CountRow::find_by_statement(Statement::from_sql_and_values(
|
||||||
.filter(dialysis_record::Column::TenantId.eq(tenant_id))
|
DatabaseBackend::Postgres,
|
||||||
.filter(dialysis_record::Column::DeletedAt.is_null())
|
"SELECT COUNT(*)::int8 AS count FROM dialysis_record WHERE tenant_id = $1 AND deleted_at IS NULL AND created_at >= date_trunc('month', NOW())",
|
||||||
.filter(dialysis_record::Column::Status.eq("draft"))
|
[tenant_id.into()],
|
||||||
.count(db)
|
)).one(db).await?.map(|r| r.count).unwrap_or(0);
|
||||||
.await?;
|
|
||||||
|
let pending_review = CountRow::find_by_statement(Statement::from_sql_and_values(
|
||||||
|
DatabaseBackend::Postgres,
|
||||||
|
"SELECT COUNT(*)::int8 AS count FROM dialysis_record WHERE tenant_id = $1 AND deleted_at IS NULL AND status = 'draft'",
|
||||||
|
[tenant_id.into()],
|
||||||
|
)).one(db).await?.map(|r| r.count).unwrap_or(0);
|
||||||
|
|
||||||
let type_distribution = count_by_field(
|
let type_distribution = count_by_field(
|
||||||
db, tenant_id,
|
db, tenant_id,
|
||||||
@@ -223,13 +225,13 @@ pub async fn get_dialysis_statistics(
|
|||||||
let avg_duration = compute_avg_field(db, tenant_id, "dialysis_duration").await?;
|
let avg_duration = compute_avg_field(db, tenant_id, "dialysis_duration").await?;
|
||||||
|
|
||||||
Ok(DialysisStatisticsResp {
|
Ok(DialysisStatisticsResp {
|
||||||
total_records: total_records as i64,
|
total_records,
|
||||||
this_month: this_month as i64,
|
this_month,
|
||||||
type_distribution,
|
type_distribution,
|
||||||
complication_rate,
|
complication_rate,
|
||||||
avg_ultrafiltration,
|
avg_ultrafiltration,
|
||||||
avg_duration,
|
avg_duration,
|
||||||
pending_review: pending_review as i64,
|
pending_review,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user