- erp-health: article/banner/consultation/media 服务层优化 - erp-ai: analysis/insight/prompt 服务增强 - erp-auth: auth/role/token 服务改进 - erp-workflow: executor 执行引擎修复 - erp-plugin: 服务层改进 - 新增媒体上传文件样例
211 lines
6.4 KiB
Rust
211 lines
6.4 KiB
Rust
use sea_orm::ActiveValue::Set;
|
||
use sea_orm::entity::prelude::*;
|
||
use uuid::Uuid;
|
||
|
||
use crate::entity::critical_value_threshold;
|
||
use crate::error::{HealthError, HealthResult};
|
||
|
||
/// 阈值查询条件:指标名 + 方向
|
||
pub struct ThresholdQuery {
|
||
pub indicator: String,
|
||
pub direction: String,
|
||
}
|
||
|
||
/// 查询指定租户的活跃危急值阈值。
|
||
///
|
||
/// 如果指定了 department 和 age,优先匹配精确条件,回退到通用阈值。
|
||
pub async fn find_thresholds(
|
||
db: &DatabaseConnection,
|
||
tenant_id: Uuid,
|
||
) -> HealthResult<Vec<critical_value_threshold::Model>> {
|
||
let list = critical_value_threshold::Entity::find()
|
||
.filter(critical_value_threshold::Column::TenantId.eq(tenant_id))
|
||
.filter(critical_value_threshold::Column::IsActive.eq(true))
|
||
.filter(critical_value_threshold::Column::DeletedAt.is_null())
|
||
.all(db)
|
||
.await?;
|
||
Ok(list)
|
||
}
|
||
|
||
/// 查找匹配的阈值配置(按 indicator + direction 匹配)。
|
||
///
|
||
/// 优先匹配有科室/年龄限制的精确规则,否则返回通用规则。
|
||
pub async fn find_threshold(
|
||
db: &DatabaseConnection,
|
||
tenant_id: Uuid,
|
||
indicator: &str,
|
||
direction: &str,
|
||
department: Option<&str>,
|
||
age: Option<i32>,
|
||
) -> HealthResult<Option<critical_value_threshold::Model>> {
|
||
let all = find_thresholds(db, tenant_id).await?;
|
||
|
||
let mut exact_match: Option<&critical_value_threshold::Model> = None;
|
||
let mut generic_match: Option<&critical_value_threshold::Model> = None;
|
||
|
||
for t in &all {
|
||
if t.indicator != indicator || t.direction != direction {
|
||
continue;
|
||
}
|
||
let dept_match = match (t.department.as_deref(), department) {
|
||
(Some(td), Some(d)) => td == d,
|
||
(None, _) => true, // 通用规则匹配任意科室
|
||
(Some(_), None) => false, // 有科室限制但没传科室
|
||
};
|
||
let age_match = match (t.age_min, t.age_max, age) {
|
||
(Some(min), Some(max), Some(a)) => a >= min && a <= max,
|
||
(None, None, _) => true, // 通用规则匹配任意年龄
|
||
_ => false, // 有年龄限制但没传年龄
|
||
};
|
||
if dept_match && age_match {
|
||
if t.department.is_some() || t.age_min.is_some() {
|
||
// 精确规则
|
||
if exact_match.is_none() {
|
||
exact_match = Some(t);
|
||
}
|
||
} else {
|
||
// 通用规则
|
||
if generic_match.is_none() {
|
||
generic_match = Some(t);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
Ok(exact_match.or(generic_match).cloned())
|
||
}
|
||
|
||
/// 创建新的危急值阈值。
|
||
#[allow(clippy::too_many_arguments)]
|
||
pub async fn create_threshold(
|
||
db: &DatabaseConnection,
|
||
tenant_id: Uuid,
|
||
operator_id: Option<Uuid>,
|
||
indicator: String,
|
||
direction: String,
|
||
threshold_value: f64,
|
||
level: String,
|
||
department: Option<String>,
|
||
age_min: Option<i32>,
|
||
age_max: Option<i32>,
|
||
) -> HealthResult<critical_value_threshold::Model> {
|
||
validate_direction(&direction)?;
|
||
validate_indicator(&indicator)?;
|
||
|
||
let now = chrono::Utc::now();
|
||
let model = critical_value_threshold::ActiveModel {
|
||
id: Set(Uuid::now_v7()),
|
||
tenant_id: Set(tenant_id),
|
||
indicator: Set(indicator),
|
||
direction: Set(direction),
|
||
threshold_value: Set(threshold_value),
|
||
level: Set(level),
|
||
department: Set(department),
|
||
age_min: Set(age_min),
|
||
age_max: Set(age_max),
|
||
is_active: Set(true),
|
||
created_at: Set(now),
|
||
updated_at: Set(now),
|
||
created_by: Set(operator_id),
|
||
updated_by: Set(operator_id),
|
||
deleted_at: Set(None),
|
||
version: Set(1),
|
||
};
|
||
let result = model.insert(db).await?;
|
||
Ok(result)
|
||
}
|
||
|
||
/// 更新危急值阈值。
|
||
#[allow(clippy::too_many_arguments)]
|
||
pub async fn update_threshold(
|
||
db: &DatabaseConnection,
|
||
tenant_id: Uuid,
|
||
threshold_id: Uuid,
|
||
operator_id: Option<Uuid>,
|
||
threshold_value: f64,
|
||
level: String,
|
||
department: Option<String>,
|
||
age_min: Option<i32>,
|
||
age_max: Option<i32>,
|
||
expected_version: i32,
|
||
) -> HealthResult<critical_value_threshold::Model> {
|
||
let existing = critical_value_threshold::Entity::find_by_id(threshold_id)
|
||
.one(db)
|
||
.await?
|
||
.ok_or(HealthError::ThresholdNotFound)?;
|
||
|
||
if existing.tenant_id != tenant_id {
|
||
return Err(HealthError::ThresholdNotFound);
|
||
}
|
||
|
||
erp_core::error::check_version(existing.version, expected_version)?;
|
||
|
||
let now = chrono::Utc::now();
|
||
let mut active: critical_value_threshold::ActiveModel = existing.into();
|
||
active.threshold_value = Set(threshold_value);
|
||
active.level = Set(level);
|
||
active.department = Set(department);
|
||
active.age_min = Set(age_min);
|
||
active.age_max = Set(age_max);
|
||
active.updated_at = Set(now);
|
||
active.updated_by = Set(operator_id);
|
||
active.version = Set(expected_version + 1);
|
||
let result = active.update(db).await?;
|
||
Ok(result)
|
||
}
|
||
|
||
/// 软删除危急值阈值。
|
||
pub async fn delete_threshold(
|
||
db: &DatabaseConnection,
|
||
tenant_id: Uuid,
|
||
threshold_id: Uuid,
|
||
operator_id: Option<Uuid>,
|
||
) -> HealthResult<()> {
|
||
let existing = critical_value_threshold::Entity::find_by_id(threshold_id)
|
||
.one(db)
|
||
.await?
|
||
.ok_or(HealthError::ThresholdNotFound)?;
|
||
|
||
if existing.tenant_id != tenant_id {
|
||
return Err(HealthError::ThresholdNotFound);
|
||
}
|
||
|
||
let mut active: critical_value_threshold::ActiveModel = existing.into();
|
||
active.deleted_at = Set(Some(chrono::Utc::now()));
|
||
active.updated_by = Set(operator_id);
|
||
active.version = Set(active.version.unwrap() + 1);
|
||
active.update(db).await?;
|
||
Ok(())
|
||
}
|
||
|
||
fn validate_direction(direction: &str) -> HealthResult<()> {
|
||
if matches!(direction, "high" | "low") {
|
||
Ok(())
|
||
} else {
|
||
Err(HealthError::Validation(
|
||
"direction 必须为 'high' 或 'low'".to_string(),
|
||
))
|
||
}
|
||
}
|
||
|
||
fn validate_indicator(indicator: &str) -> HealthResult<()> {
|
||
let valid = [
|
||
"systolic_bp",
|
||
"diastolic_bp",
|
||
"heart_rate",
|
||
"blood_sugar",
|
||
"blood_sugar_fasting",
|
||
"blood_sugar_postprandial",
|
||
"blood_oxygen",
|
||
"temperature",
|
||
];
|
||
if valid.contains(&indicator) {
|
||
Ok(())
|
||
} else {
|
||
Err(HealthError::Validation(format!(
|
||
"indicator 必须为以下之一: {}",
|
||
valid.join(", ")
|
||
)))
|
||
}
|
||
}
|