feat(health): action_inbox + health_data_service tracing 补全
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

action_inbox_service: 从 0 → 8 处 tracing(4 个公开函数全覆盖)
health_data_service: 从 3 → 12 处 tracing(13 个公开函数全覆盖)
This commit is contained in:
iven
2026-05-03 19:41:04 +08:00
parent 34504d4179
commit 80bc60f5e4
2 changed files with 70 additions and 16 deletions

View File

@@ -2,6 +2,7 @@ use chrono::{DateTime, Utc};
use erp_core::types::PaginatedResponse;
use sea_orm::{DatabaseBackend, DatabaseConnection, FromQueryResult, Statement};
use serde::{Deserialize, Serialize};
use tracing;
use uuid::Uuid;
use crate::error::HealthError;
@@ -212,6 +213,7 @@ pub async fn list_action_items(
tenant_id: Uuid,
query: &ActionInboxQuery,
) -> Result<PaginatedResponse<ActionItem>, HealthError> {
tracing::info!(tenant_id = %tenant_id, status = ?query.status, action_type = ?query.action_type, "列出待办事项");
let page = query.page.unwrap_or(1).max(1);
let page_size = query.page_size.unwrap_or(20).min(100);
let offset = (page - 1) * page_size;
@@ -324,16 +326,23 @@ pub async fn list_action_items(
)
.all(db)
.await
.map_err(|e| HealthError::DbError(e.to_string()))?;
.map_err(|e| {
tracing::error!(tenant_id = %tenant_id, error = %e, "查询待办事项数据失败");
HealthError::DbError(e.to_string())
})?;
let count_row: Option<CountRow> = FromQueryResult::find_by_statement(
Statement::from_sql_and_values(DatabaseBackend::Postgres, count_sql, [tenant_id.into()]),
)
.one(db)
.await
.map_err(|e| HealthError::DbError(e.to_string()))?;
.map_err(|e| {
tracing::error!(tenant_id = %tenant_id, error = %e, "查询待办事项总数失败");
HealthError::DbError(e.to_string())
})?;
let total = count_row.map(|r| r.cnt).unwrap_or(0) as u64;
tracing::debug!(tenant_id = %tenant_id, total = total, "待办事项查询结果数量");
let items: Vec<ActionItem> = rows
.into_iter()
@@ -378,13 +387,19 @@ pub async fn get_action_thread(
tenant_id: Uuid,
source_ref: &str,
) -> Result<Option<ThreadResponse>, HealthError> {
tracing::info!(tenant_id = %tenant_id, source_ref = %source_ref, "获取待办事项详情");
// 解析 "action_type:uuid" 格式
let (action_type_str, uuid_str) = source_ref
.find(':')
.map(|pos| (&source_ref[..pos], &source_ref[pos + 1..]))
.ok_or_else(|| HealthError::Validation("无效的 source_ref 格式".into()))?;
let uuid = Uuid::parse_str(uuid_str)
.map_err(|e| HealthError::Validation(format!("无效的 UUID: {e}")))?;
.ok_or_else(|| {
tracing::error!(source_ref = %source_ref, "无效的 source_ref 格式");
HealthError::Validation("无效的 source_ref 格式".into())
})?;
let uuid = Uuid::parse_str(uuid_str).map_err(|e| {
tracing::error!(uuid_str = %uuid_str, error = %e, "无效的 UUID");
HealthError::Validation(format!("无效的 UUID: {e}"))
})?;
match action_type_str {
"ai_suggestion" => get_ai_suggestion_thread(db, tenant_id, uuid).await,
@@ -775,6 +790,7 @@ pub async fn get_workbench_stats(
db: &DatabaseConnection,
tenant_id: Uuid,
) -> Result<WorkbenchStats, HealthError> {
tracing::info!(tenant_id = %tenant_id, "获取工作台统计");
let ai_pending: i64 = FromQueryResult::find_by_statement(
Statement::from_sql_and_values(
DatabaseBackend::Postgres,
@@ -784,7 +800,10 @@ pub async fn get_workbench_stats(
)
.one(db)
.await
.map_err(|e| HealthError::DbError(e.to_string()))?
.map_err(|e| {
tracing::error!(tenant_id = %tenant_id, error = %e, "查询AI建议待办数失败");
HealthError::DbError(e.to_string())
})?
.map(|r: CountRow| r.cnt)
.unwrap_or(0);
@@ -797,7 +816,10 @@ pub async fn get_workbench_stats(
)
.one(db)
.await
.map_err(|e| HealthError::DbError(e.to_string()))?
.map_err(|e| {
tracing::error!(tenant_id = %tenant_id, error = %e, "查询紧急告警数失败");
HealthError::DbError(e.to_string())
})?
.map(|r: CountRow| r.cnt)
.unwrap_or(0);
@@ -810,11 +832,15 @@ pub async fn get_workbench_stats(
)
.one(db)
.await
.map_err(|e| HealthError::DbError(e.to_string()))?
.map_err(|e| {
tracing::error!(tenant_id = %tenant_id, error = %e, "查询到期随访数失败");
HealthError::DbError(e.to_string())
})?
.map(|r: CountRow| r.cnt)
.unwrap_or(0);
let total_pending = (ai_pending + urgent_alerts + followup_due) as u64;
tracing::debug!(tenant_id = %tenant_id, total_pending = total_pending, ai_pending = ai_pending, urgent_alerts = urgent_alerts, followup_due = followup_due, "工作台统计数据");
Ok(WorkbenchStats {
total_pending,
@@ -829,6 +855,7 @@ pub async fn get_team_overview(
db: &DatabaseConnection,
tenant_id: Uuid,
) -> Result<TeamOverview, HealthError> {
tracing::info!(tenant_id = %tenant_id, "获取团队概览");
// 成员统计
#[derive(Debug, FromQueryResult)]
struct MemberRow {

View File

@@ -30,6 +30,7 @@ pub async fn list_vital_signs(
page: u64,
page_size: u64,
) -> HealthResult<PaginatedResponse<VitalSignsResp>> {
tracing::info!(tenant_id = %tenant_id, patient_id = %patient_id, page, page_size, "查询体征记录列表");
let limit = page_size.min(100);
let offset = page.saturating_sub(1) * limit;
@@ -46,6 +47,7 @@ pub async fn list_vital_signs(
.all(&state.db)
.await?;
tracing::debug!(total, "体征记录查询结果数量");
let total_pages = total.div_ceil(limit.max(1));
let data: Vec<VitalSignsResp> = models.into_iter().map(|m| VitalSignsResp {
id: m.id,
@@ -80,6 +82,7 @@ pub async fn create_vital_signs(
operator_id: Option<Uuid>,
req: CreateVitalSignsReq,
) -> HealthResult<VitalSignsResp> {
tracing::info!(tenant_id = %tenant_id, patient_id = %patient_id, "创建体征记录");
// 校验患者存在
patient::Entity::find()
.filter(patient::Column::Id.eq(patient_id))
@@ -87,7 +90,10 @@ pub async fn create_vital_signs(
.filter(patient::Column::DeletedAt.is_null())
.one(&state.db)
.await?
.ok_or(HealthError::PatientNotFound)?;
.ok_or_else(|| {
tracing::error!(patient_id = %patient_id, tenant_id = %tenant_id, "创建体征记录失败:患者不存在");
HealthError::PatientNotFound
})?;
let now = Utc::now();
let alert_req = req.clone();
@@ -118,6 +124,7 @@ pub async fn create_vital_signs(
version: Set(1),
};
let m = active.insert(&state.db).await?;
tracing::info!(id = %m.id, tenant_id = %tenant_id, patient_id = %patient_id, "体征记录创建成功");
// 数据持久化成功后再触发危急值检测
check_vital_signs_alert(state, tenant_id, patient_id, operator_id, alert_req).await;
@@ -154,6 +161,7 @@ pub async fn update_vital_signs(
req: UpdateVitalSignsReq,
expected_version: i32,
) -> HealthResult<VitalSignsResp> {
tracing::info!(tenant_id = %tenant_id, patient_id = %patient_id, vital_signs_id = %vital_signs_id, expected_version, "更新体征记录");
let model = vital_signs::Entity::find()
.filter(vital_signs::Column::Id.eq(vital_signs_id))
.filter(vital_signs::Column::PatientId.eq(patient_id))
@@ -161,9 +169,14 @@ pub async fn update_vital_signs(
.filter(vital_signs::Column::DeletedAt.is_null())
.one(&state.db)
.await?
.ok_or(HealthError::VitalSignsNotFound)?;
let next_ver = check_version(expected_version, model.version)
.map_err(|_| HealthError::VersionMismatch)?;
.ok_or_else(|| {
tracing::error!(vital_signs_id = %vital_signs_id, tenant_id = %tenant_id, "更新体征记录失败:记录不存在");
HealthError::VitalSignsNotFound
})?;
let next_ver = check_version(expected_version, model.version).map_err(|e| {
tracing::error!(vital_signs_id = %vital_signs_id, expected_version, db_version = model.version, "更新体征记录失败:版本冲突");
e
})?;
// 记录变更前的关键体征值
let old_values = serde_json::json!({
@@ -198,6 +211,7 @@ pub async fn update_vital_signs(
active.version = Set(next_ver);
let m = active.update(&state.db).await?;
tracing::info!(id = %m.id, tenant_id = %tenant_id, version = m.version, "体征记录更新成功");
// 变更后快照
let new_values = serde_json::json!({
@@ -262,16 +276,22 @@ pub async fn delete_vital_signs(
operator_id: Option<Uuid>,
expected_version: i32,
) -> HealthResult<()> {
tracing::info!(tenant_id = %tenant_id, vital_signs_id = %vital_signs_id, expected_version, "删除体征记录");
let model = vital_signs::Entity::find()
.filter(vital_signs::Column::Id.eq(vital_signs_id))
.filter(vital_signs::Column::TenantId.eq(tenant_id))
.filter(vital_signs::Column::DeletedAt.is_null())
.one(&state.db)
.await?
.ok_or(HealthError::VitalSignsNotFound)?;
.ok_or_else(|| {
tracing::error!(vital_signs_id = %vital_signs_id, tenant_id = %tenant_id, "删除体征记录失败:记录不存在");
HealthError::VitalSignsNotFound
})?;
let next_ver = check_version(expected_version, model.version)
.map_err(|_| HealthError::VersionMismatch)?;
let next_ver = check_version(expected_version, model.version).map_err(|e| {
tracing::error!(vital_signs_id = %vital_signs_id, expected_version, db_version = model.version, "删除体征记录失败:版本冲突");
e
})?;
let mut active: vital_signs::ActiveModel = model.into();
active.deleted_at = Set(Some(Utc::now()));
@@ -279,6 +299,7 @@ pub async fn delete_vital_signs(
active.updated_by = Set(operator_id);
active.version = Set(next_ver);
active.update(&state.db).await?;
tracing::info!(vital_signs_id = %vital_signs_id, tenant_id = %tenant_id, "体征记录删除成功");
audit_service::record(
AuditLog::new(tenant_id, operator_id, "vital_signs.deleted", "vital_signs")
@@ -300,6 +321,7 @@ pub async fn list_lab_reports(
page: u64,
page_size: u64,
) -> HealthResult<PaginatedResponse<LabReportResp>> {
tracing::info!(tenant_id = %tenant_id, patient_id = %patient_id, page, page_size, "查询化验报告列表");
let limit = page_size.min(100);
let offset = page.saturating_sub(1) * limit;
@@ -309,6 +331,7 @@ pub async fn list_lab_reports(
.filter(lab_report::Column::DeletedAt.is_null());
let total = query.clone().count(&state.db).await?;
tracing::debug!(total, "化验报告查询结果数量");
let models = query
.order_by_desc(lab_report::Column::ReportDate)
.offset(offset)
@@ -350,6 +373,7 @@ pub async fn create_lab_report(
operator_id: Option<Uuid>,
req: CreateLabReportReq,
) -> HealthResult<LabReportResp> {
tracing::info!(tenant_id = %tenant_id, patient_id = %patient_id, "创建化验报告");
// 校验患者存在
patient::Entity::find()
.filter(patient::Column::Id.eq(patient_id))
@@ -357,7 +381,10 @@ pub async fn create_lab_report(
.filter(patient::Column::DeletedAt.is_null())
.one(&state.db)
.await?
.ok_or(HealthError::PatientNotFound)?;
.ok_or_else(|| {
tracing::error!(patient_id = %patient_id, tenant_id = %tenant_id, "创建化验报告失败:患者不存在");
HealthError::PatientNotFound
})?;
let kek = state.crypto.kek();