feat(health): 注入审计日志覆盖所有写入操作

17 个方法全覆盖:patient(4)、appointment(2)、consultation(3)、
follow_up(2)、doctor(3)、health_data(3)。使用 fire-and-forget 模式。
This commit is contained in:
iven
2026-04-25 00:12:19 +08:00
parent 1d1f01df81
commit 479b5900c9
6 changed files with 124 additions and 0 deletions

View File

@@ -1,6 +1,8 @@
//! 预约排班 Service — 预约CRUD、排班管理、日历视图、原子CAS预约
use chrono::Utc;
use erp_core::audit::AuditLog;
use erp_core::audit_service;
use erp_core::events::DomainEvent;
use sea_orm::entity::prelude::*;
use sea_orm::{ActiveValue::Set, Condition, QueryOrder, QuerySelect, TransactionTrait};
@@ -175,6 +177,12 @@ pub async fn create_appointment(
);
state.event_bus.publish(event, &state.db).await;
audit_service::record(
AuditLog::new(tenant_id, operator_id, "appointment.created", "appointment")
.with_resource_id(m.id),
&state.db,
).await;
Ok(AppointmentResp {
id: m.id, patient_id: m.patient_id, doctor_id: m.doctor_id,
appointment_type: m.appointment_type, appointment_date: m.appointment_date,
@@ -206,6 +214,8 @@ pub async fn update_appointment_status(
let next_ver = check_version(expected_version, model.version)
.map_err(|_| HealthError::VersionMismatch)?;
let old_status = model.status.clone();
let txn = state.db.begin().await?;
// 取消时释放排班名额(带下限保护)
@@ -249,6 +259,16 @@ pub async fn update_appointment_status(
);
state.event_bus.publish(event, &state.db).await;
audit_service::record(
AuditLog::new(tenant_id, operator_id, "appointment.status_changed", "appointment")
.with_resource_id(m.id)
.with_changes(
Some(serde_json::json!({ "status": old_status })),
Some(serde_json::json!({ "status": m.status })),
),
&state.db,
).await;
Ok(AppointmentResp {
id: m.id, patient_id: m.patient_id, doctor_id: m.doctor_id,
appointment_type: m.appointment_type, appointment_date: m.appointment_date,

View File

@@ -1,6 +1,8 @@
//! 咨询管理 Service — 会话管理、消息收发、会话关闭、导出
use chrono::Utc;
use erp_core::audit::AuditLog;
use erp_core::audit_service;
use erp_core::events::DomainEvent;
use sea_orm::entity::prelude::*;
use sea_orm::{ActiveValue::Set, Condition, QueryOrder, QuerySelect};
@@ -65,6 +67,12 @@ pub async fn create_session(
);
state.event_bus.publish(event, &state.db).await;
audit_service::record(
AuditLog::new(tenant_id, operator_id, "consultation.opened", "consultation")
.with_resource_id(m.id),
&state.db,
).await;
Ok(SessionResp {
id: m.id, patient_id: m.patient_id, doctor_id: m.doctor_id,
consultation_type: m.consultation_type, status: m.status,
@@ -154,6 +162,12 @@ pub async fn close_session(
);
state.event_bus.publish(event, &state.db).await;
audit_service::record(
AuditLog::new(tenant_id, operator_id, "consultation.closed", "consultation")
.with_resource_id(m.id),
&state.db,
).await;
Ok(SessionResp {
id: m.id, patient_id: m.patient_id, doctor_id: m.doctor_id,
consultation_type: m.consultation_type, status: m.status,
@@ -308,6 +322,12 @@ pub async fn create_message(
return Err(HealthError::VersionMismatch);
}
audit_service::record(
AuditLog::new(tenant_id, operator_id, "consultation.message_sent", "consultation")
.with_resource_id(m.id),
&state.db,
).await;
Ok(MessageResp {
id: m.id, session_id: m.session_id, sender_id: m.sender_id,
sender_role: m.sender_role, content_type: m.content_type,

View File

@@ -1,6 +1,8 @@
//! 医护档案 Service — CRUD
use chrono::Utc;
use erp_core::audit::AuditLog;
use erp_core::audit_service;
use sea_orm::entity::prelude::*;
use sea_orm::{ActiveValue::Set, Condition, QueryOrder, QuerySelect};
use uuid::Uuid;
@@ -95,6 +97,13 @@ pub async fn create_doctor(
};
let model = active.insert(&state.db).await?;
audit_service::record(
AuditLog::new(tenant_id, operator_id, "doctor.created", "doctor")
.with_resource_id(model.id),
&state.db,
).await;
Ok(model_to_resp(model))
}
@@ -144,6 +153,13 @@ pub async fn update_doctor(
active.version = Set(next_ver);
let updated = active.update(&state.db).await?;
audit_service::record(
AuditLog::new(tenant_id, operator_id, "doctor.updated", "doctor")
.with_resource_id(updated.id),
&state.db,
).await;
Ok(model_to_resp(updated))
}
@@ -165,6 +181,12 @@ pub async fn delete_doctor(
active.version = Set(next_ver);
active.update(&state.db).await?;
audit_service::record(
AuditLog::new(tenant_id, operator_id, "doctor.deleted", "doctor")
.with_resource_id(id),
&state.db,
).await;
Ok(())
}

View File

@@ -1,6 +1,8 @@
//! 随访管理 Service — 随访任务CRUD、随访记录、状态流转
use chrono::Utc;
use erp_core::audit::AuditLog;
use erp_core::audit_service;
use erp_core::events::DomainEvent;
use sea_orm::entity::prelude::*;
use sea_orm::{ActiveValue::Set, QueryOrder, QuerySelect, TransactionTrait};
@@ -126,6 +128,12 @@ pub async fn create_task(
);
state.event_bus.publish(event, &state.db).await;
audit_service::record(
AuditLog::new(tenant_id, operator_id, "follow_up_task.created", "follow_up_task")
.with_resource_id(m.id),
&state.db,
).await;
Ok(FollowUpTaskResp {
id: m.id, patient_id: m.patient_id, assigned_to: m.assigned_to,
follow_up_type: m.follow_up_type, planned_date: m.planned_date,
@@ -295,6 +303,12 @@ pub async fn create_record(
);
state.event_bus.publish(event, &state.db).await;
audit_service::record(
AuditLog::new(tenant_id, operator_id, "follow_up_record.created", "follow_up_record")
.with_resource_id(record.id),
&state.db,
).await;
Ok(FollowUpRecordResp {
id: record.id, task_id: record.task_id, executed_by: record.executed_by,
executed_date: record.executed_date, result: record.result,

View File

@@ -1,6 +1,8 @@
//! 健康数据 Service — 体征记录、化验报告、体检记录、趋势分析
use chrono::Utc;
use erp_core::audit::AuditLog;
use erp_core::audit_service;
use erp_core::events::DomainEvent;
use num_traits::cast::ToPrimitive;
use sea_orm::entity::prelude::*;
@@ -106,6 +108,13 @@ pub async fn create_vital_signs(
version: Set(1),
};
let m = active.insert(&state.db).await?;
audit_service::record(
AuditLog::new(tenant_id, operator_id, "vital_signs.created", "vital_signs")
.with_resource_id(m.id),
&state.db,
).await;
Ok(VitalSignsResp {
id: m.id, patient_id: m.patient_id, record_date: m.record_date,
systolic_bp_morning: m.systolic_bp_morning, diastolic_bp_morning: m.diastolic_bp_morning,
@@ -274,6 +283,12 @@ pub async fn create_lab_report(
);
state.event_bus.publish(event, &state.db).await;
audit_service::record(
AuditLog::new(tenant_id, operator_id, "lab_report.created", "lab_report")
.with_resource_id(m.id),
&state.db,
).await;
Ok(LabReportResp {
id: m.id, patient_id: m.patient_id, report_date: m.report_date,
report_type: m.report_type, indicators: m.indicators,
@@ -424,6 +439,13 @@ pub async fn create_health_record(
version: Set(1),
};
let m = active.insert(&state.db).await?;
audit_service::record(
AuditLog::new(tenant_id, operator_id, "health_record.created", "health_record")
.with_resource_id(m.id),
&state.db,
).await;
Ok(HealthRecordResp {
id: m.id, patient_id: m.patient_id, record_type: m.record_type,
record_date: m.record_date, source: m.source,

View File

@@ -1,6 +1,8 @@
//! 患者管理 Service — CRUD、家庭成员、标签、医生关联、健康摘要
use chrono::Utc;
use erp_core::audit::AuditLog;
use erp_core::audit_service;
use erp_core::events::DomainEvent;
use sea_orm::entity::prelude::*;
use sea_orm::{ActiveValue::Set, Condition, QueryOrder, QuerySelect};
@@ -135,6 +137,12 @@ pub async fn create_patient(
);
state.event_bus.publish(event, &state.db).await;
audit_service::record(
AuditLog::new(tenant_id, operator_id, "patient.created", "patient")
.with_resource_id(model.id),
&state.db,
).await;
Ok(model_to_resp(model))
}
@@ -220,6 +228,12 @@ pub async fn update_patient(
);
state.event_bus.publish(event, &state.db).await;
audit_service::record(
AuditLog::new(tenant_id, operator_id, "patient.updated", "patient")
.with_resource_id(updated.id),
&state.db,
).await;
Ok(model_to_resp(updated))
}
@@ -242,6 +256,12 @@ pub async fn delete_patient(
active.version = Set(next_ver);
active.update(&state.db).await?;
audit_service::record(
AuditLog::new(tenant_id, operator_id, "patient.deleted", "patient")
.with_resource_id(id),
&state.db,
).await;
Ok(())
}
@@ -308,6 +328,12 @@ pub async fn manage_patient_tags(
rel.insert(&state.db).await?;
}
audit_service::record(
AuditLog::new(tenant_id, operator_id, "patient.tags_updated", "patient")
.with_resource_id(patient_id),
&state.db,
).await;
Ok(())
}