feat(health): action_inbox + health_data_service tracing 补全
action_inbox_service: 从 0 → 8 处 tracing(4 个公开函数全覆盖) health_data_service: 从 3 → 12 处 tracing(13 个公开函数全覆盖)
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user