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 erp_core::types::PaginatedResponse;
|
||||||
use sea_orm::{DatabaseBackend, DatabaseConnection, FromQueryResult, Statement};
|
use sea_orm::{DatabaseBackend, DatabaseConnection, FromQueryResult, Statement};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use tracing;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use crate::error::HealthError;
|
use crate::error::HealthError;
|
||||||
@@ -212,6 +213,7 @@ pub async fn list_action_items(
|
|||||||
tenant_id: Uuid,
|
tenant_id: Uuid,
|
||||||
query: &ActionInboxQuery,
|
query: &ActionInboxQuery,
|
||||||
) -> Result<PaginatedResponse<ActionItem>, HealthError> {
|
) -> 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 = query.page.unwrap_or(1).max(1);
|
||||||
let page_size = query.page_size.unwrap_or(20).min(100);
|
let page_size = query.page_size.unwrap_or(20).min(100);
|
||||||
let offset = (page - 1) * page_size;
|
let offset = (page - 1) * page_size;
|
||||||
@@ -324,16 +326,23 @@ pub async fn list_action_items(
|
|||||||
)
|
)
|
||||||
.all(db)
|
.all(db)
|
||||||
.await
|
.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(
|
let count_row: Option<CountRow> = FromQueryResult::find_by_statement(
|
||||||
Statement::from_sql_and_values(DatabaseBackend::Postgres, count_sql, [tenant_id.into()]),
|
Statement::from_sql_and_values(DatabaseBackend::Postgres, count_sql, [tenant_id.into()]),
|
||||||
)
|
)
|
||||||
.one(db)
|
.one(db)
|
||||||
.await
|
.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;
|
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
|
let items: Vec<ActionItem> = rows
|
||||||
.into_iter()
|
.into_iter()
|
||||||
@@ -378,13 +387,19 @@ pub async fn get_action_thread(
|
|||||||
tenant_id: Uuid,
|
tenant_id: Uuid,
|
||||||
source_ref: &str,
|
source_ref: &str,
|
||||||
) -> Result<Option<ThreadResponse>, HealthError> {
|
) -> Result<Option<ThreadResponse>, HealthError> {
|
||||||
|
tracing::info!(tenant_id = %tenant_id, source_ref = %source_ref, "获取待办事项详情");
|
||||||
// 解析 "action_type:uuid" 格式
|
// 解析 "action_type:uuid" 格式
|
||||||
let (action_type_str, uuid_str) = source_ref
|
let (action_type_str, uuid_str) = source_ref
|
||||||
.find(':')
|
.find(':')
|
||||||
.map(|pos| (&source_ref[..pos], &source_ref[pos + 1..]))
|
.map(|pos| (&source_ref[..pos], &source_ref[pos + 1..]))
|
||||||
.ok_or_else(|| HealthError::Validation("无效的 source_ref 格式".into()))?;
|
.ok_or_else(|| {
|
||||||
let uuid = Uuid::parse_str(uuid_str)
|
tracing::error!(source_ref = %source_ref, "无效的 source_ref 格式");
|
||||||
.map_err(|e| HealthError::Validation(format!("无效的 UUID: {e}")))?;
|
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 {
|
match action_type_str {
|
||||||
"ai_suggestion" => get_ai_suggestion_thread(db, tenant_id, uuid).await,
|
"ai_suggestion" => get_ai_suggestion_thread(db, tenant_id, uuid).await,
|
||||||
@@ -775,6 +790,7 @@ pub async fn get_workbench_stats(
|
|||||||
db: &DatabaseConnection,
|
db: &DatabaseConnection,
|
||||||
tenant_id: Uuid,
|
tenant_id: Uuid,
|
||||||
) -> Result<WorkbenchStats, HealthError> {
|
) -> Result<WorkbenchStats, HealthError> {
|
||||||
|
tracing::info!(tenant_id = %tenant_id, "获取工作台统计");
|
||||||
let ai_pending: i64 = FromQueryResult::find_by_statement(
|
let ai_pending: i64 = FromQueryResult::find_by_statement(
|
||||||
Statement::from_sql_and_values(
|
Statement::from_sql_and_values(
|
||||||
DatabaseBackend::Postgres,
|
DatabaseBackend::Postgres,
|
||||||
@@ -784,7 +800,10 @@ pub async fn get_workbench_stats(
|
|||||||
)
|
)
|
||||||
.one(db)
|
.one(db)
|
||||||
.await
|
.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)
|
.map(|r: CountRow| r.cnt)
|
||||||
.unwrap_or(0);
|
.unwrap_or(0);
|
||||||
|
|
||||||
@@ -797,7 +816,10 @@ pub async fn get_workbench_stats(
|
|||||||
)
|
)
|
||||||
.one(db)
|
.one(db)
|
||||||
.await
|
.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)
|
.map(|r: CountRow| r.cnt)
|
||||||
.unwrap_or(0);
|
.unwrap_or(0);
|
||||||
|
|
||||||
@@ -810,11 +832,15 @@ pub async fn get_workbench_stats(
|
|||||||
)
|
)
|
||||||
.one(db)
|
.one(db)
|
||||||
.await
|
.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)
|
.map(|r: CountRow| r.cnt)
|
||||||
.unwrap_or(0);
|
.unwrap_or(0);
|
||||||
|
|
||||||
let total_pending = (ai_pending + urgent_alerts + followup_due) as u64;
|
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 {
|
Ok(WorkbenchStats {
|
||||||
total_pending,
|
total_pending,
|
||||||
@@ -829,6 +855,7 @@ pub async fn get_team_overview(
|
|||||||
db: &DatabaseConnection,
|
db: &DatabaseConnection,
|
||||||
tenant_id: Uuid,
|
tenant_id: Uuid,
|
||||||
) -> Result<TeamOverview, HealthError> {
|
) -> Result<TeamOverview, HealthError> {
|
||||||
|
tracing::info!(tenant_id = %tenant_id, "获取团队概览");
|
||||||
// 成员统计
|
// 成员统计
|
||||||
#[derive(Debug, FromQueryResult)]
|
#[derive(Debug, FromQueryResult)]
|
||||||
struct MemberRow {
|
struct MemberRow {
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ pub async fn list_vital_signs(
|
|||||||
page: u64,
|
page: u64,
|
||||||
page_size: u64,
|
page_size: u64,
|
||||||
) -> HealthResult<PaginatedResponse<VitalSignsResp>> {
|
) -> HealthResult<PaginatedResponse<VitalSignsResp>> {
|
||||||
|
tracing::info!(tenant_id = %tenant_id, patient_id = %patient_id, page, page_size, "查询体征记录列表");
|
||||||
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;
|
||||||
|
|
||||||
@@ -46,6 +47,7 @@ pub async fn list_vital_signs(
|
|||||||
.all(&state.db)
|
.all(&state.db)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
tracing::debug!(total, "体征记录查询结果数量");
|
||||||
let total_pages = total.div_ceil(limit.max(1));
|
let total_pages = total.div_ceil(limit.max(1));
|
||||||
let data: Vec<VitalSignsResp> = models.into_iter().map(|m| VitalSignsResp {
|
let data: Vec<VitalSignsResp> = models.into_iter().map(|m| VitalSignsResp {
|
||||||
id: m.id,
|
id: m.id,
|
||||||
@@ -80,6 +82,7 @@ pub async fn create_vital_signs(
|
|||||||
operator_id: Option<Uuid>,
|
operator_id: Option<Uuid>,
|
||||||
req: CreateVitalSignsReq,
|
req: CreateVitalSignsReq,
|
||||||
) -> HealthResult<VitalSignsResp> {
|
) -> HealthResult<VitalSignsResp> {
|
||||||
|
tracing::info!(tenant_id = %tenant_id, patient_id = %patient_id, "创建体征记录");
|
||||||
// 校验患者存在
|
// 校验患者存在
|
||||||
patient::Entity::find()
|
patient::Entity::find()
|
||||||
.filter(patient::Column::Id.eq(patient_id))
|
.filter(patient::Column::Id.eq(patient_id))
|
||||||
@@ -87,7 +90,10 @@ pub async fn create_vital_signs(
|
|||||||
.filter(patient::Column::DeletedAt.is_null())
|
.filter(patient::Column::DeletedAt.is_null())
|
||||||
.one(&state.db)
|
.one(&state.db)
|
||||||
.await?
|
.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 now = Utc::now();
|
||||||
let alert_req = req.clone();
|
let alert_req = req.clone();
|
||||||
@@ -118,6 +124,7 @@ pub async fn create_vital_signs(
|
|||||||
version: Set(1),
|
version: Set(1),
|
||||||
};
|
};
|
||||||
let m = active.insert(&state.db).await?;
|
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;
|
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,
|
req: UpdateVitalSignsReq,
|
||||||
expected_version: i32,
|
expected_version: i32,
|
||||||
) -> HealthResult<VitalSignsResp> {
|
) -> 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()
|
let model = vital_signs::Entity::find()
|
||||||
.filter(vital_signs::Column::Id.eq(vital_signs_id))
|
.filter(vital_signs::Column::Id.eq(vital_signs_id))
|
||||||
.filter(vital_signs::Column::PatientId.eq(patient_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())
|
.filter(vital_signs::Column::DeletedAt.is_null())
|
||||||
.one(&state.db)
|
.one(&state.db)
|
||||||
.await?
|
.await?
|
||||||
.ok_or(HealthError::VitalSignsNotFound)?;
|
.ok_or_else(|| {
|
||||||
let next_ver = check_version(expected_version, model.version)
|
tracing::error!(vital_signs_id = %vital_signs_id, tenant_id = %tenant_id, "更新体征记录失败:记录不存在");
|
||||||
.map_err(|_| HealthError::VersionMismatch)?;
|
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!({
|
let old_values = serde_json::json!({
|
||||||
@@ -198,6 +211,7 @@ pub async fn update_vital_signs(
|
|||||||
active.version = Set(next_ver);
|
active.version = Set(next_ver);
|
||||||
|
|
||||||
let m = active.update(&state.db).await?;
|
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!({
|
let new_values = serde_json::json!({
|
||||||
@@ -262,16 +276,22 @@ pub async fn delete_vital_signs(
|
|||||||
operator_id: Option<Uuid>,
|
operator_id: Option<Uuid>,
|
||||||
expected_version: i32,
|
expected_version: i32,
|
||||||
) -> HealthResult<()> {
|
) -> HealthResult<()> {
|
||||||
|
tracing::info!(tenant_id = %tenant_id, vital_signs_id = %vital_signs_id, expected_version, "删除体征记录");
|
||||||
let model = vital_signs::Entity::find()
|
let model = vital_signs::Entity::find()
|
||||||
.filter(vital_signs::Column::Id.eq(vital_signs_id))
|
.filter(vital_signs::Column::Id.eq(vital_signs_id))
|
||||||
.filter(vital_signs::Column::TenantId.eq(tenant_id))
|
.filter(vital_signs::Column::TenantId.eq(tenant_id))
|
||||||
.filter(vital_signs::Column::DeletedAt.is_null())
|
.filter(vital_signs::Column::DeletedAt.is_null())
|
||||||
.one(&state.db)
|
.one(&state.db)
|
||||||
.await?
|
.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)
|
let next_ver = check_version(expected_version, model.version).map_err(|e| {
|
||||||
.map_err(|_| HealthError::VersionMismatch)?;
|
tracing::error!(vital_signs_id = %vital_signs_id, expected_version, db_version = model.version, "删除体征记录失败:版本冲突");
|
||||||
|
e
|
||||||
|
})?;
|
||||||
|
|
||||||
let mut active: vital_signs::ActiveModel = model.into();
|
let mut active: vital_signs::ActiveModel = model.into();
|
||||||
active.deleted_at = Set(Some(Utc::now()));
|
active.deleted_at = Set(Some(Utc::now()));
|
||||||
@@ -279,6 +299,7 @@ pub async fn delete_vital_signs(
|
|||||||
active.updated_by = Set(operator_id);
|
active.updated_by = Set(operator_id);
|
||||||
active.version = Set(next_ver);
|
active.version = Set(next_ver);
|
||||||
active.update(&state.db).await?;
|
active.update(&state.db).await?;
|
||||||
|
tracing::info!(vital_signs_id = %vital_signs_id, tenant_id = %tenant_id, "体征记录删除成功");
|
||||||
|
|
||||||
audit_service::record(
|
audit_service::record(
|
||||||
AuditLog::new(tenant_id, operator_id, "vital_signs.deleted", "vital_signs")
|
AuditLog::new(tenant_id, operator_id, "vital_signs.deleted", "vital_signs")
|
||||||
@@ -300,6 +321,7 @@ pub async fn list_lab_reports(
|
|||||||
page: u64,
|
page: u64,
|
||||||
page_size: u64,
|
page_size: u64,
|
||||||
) -> HealthResult<PaginatedResponse<LabReportResp>> {
|
) -> HealthResult<PaginatedResponse<LabReportResp>> {
|
||||||
|
tracing::info!(tenant_id = %tenant_id, patient_id = %patient_id, page, page_size, "查询化验报告列表");
|
||||||
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;
|
||||||
|
|
||||||
@@ -309,6 +331,7 @@ pub async fn list_lab_reports(
|
|||||||
.filter(lab_report::Column::DeletedAt.is_null());
|
.filter(lab_report::Column::DeletedAt.is_null());
|
||||||
|
|
||||||
let total = query.clone().count(&state.db).await?;
|
let total = query.clone().count(&state.db).await?;
|
||||||
|
tracing::debug!(total, "化验报告查询结果数量");
|
||||||
let models = query
|
let models = query
|
||||||
.order_by_desc(lab_report::Column::ReportDate)
|
.order_by_desc(lab_report::Column::ReportDate)
|
||||||
.offset(offset)
|
.offset(offset)
|
||||||
@@ -350,6 +373,7 @@ pub async fn create_lab_report(
|
|||||||
operator_id: Option<Uuid>,
|
operator_id: Option<Uuid>,
|
||||||
req: CreateLabReportReq,
|
req: CreateLabReportReq,
|
||||||
) -> HealthResult<LabReportResp> {
|
) -> HealthResult<LabReportResp> {
|
||||||
|
tracing::info!(tenant_id = %tenant_id, patient_id = %patient_id, "创建化验报告");
|
||||||
// 校验患者存在
|
// 校验患者存在
|
||||||
patient::Entity::find()
|
patient::Entity::find()
|
||||||
.filter(patient::Column::Id.eq(patient_id))
|
.filter(patient::Column::Id.eq(patient_id))
|
||||||
@@ -357,7 +381,10 @@ pub async fn create_lab_report(
|
|||||||
.filter(patient::Column::DeletedAt.is_null())
|
.filter(patient::Column::DeletedAt.is_null())
|
||||||
.one(&state.db)
|
.one(&state.db)
|
||||||
.await?
|
.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();
|
let kek = state.crypto.kek();
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user