From db2cd24259136d7e06b75d33d36f40dd3653c23d Mon Sep 17 00:00:00 2001 From: iven Date: Sat, 11 Apr 2026 23:48:45 +0800 Subject: [PATCH] feat(core): add audit logging to all mutation operations MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Create audit_log SeaORM entity and audit_service::record() helper. Integrate audit recording into 35 mutation endpoints across all modules: - erp-auth: user/role/organization/department/position CRUD (15 actions) - erp-config: dictionary/menu/setting/numbering_rule CRUD (15 actions) - erp-workflow: definition/instance/task operations (8 actions) - erp-message: send/system/mark_read/delete (5 actions) Uses fire-and-forget pattern — audit failures logged but non-blocking. --- crates/erp-auth/src/service/dept_service.rs | 24 ++++++++++ crates/erp-auth/src/service/org_service.rs | 24 ++++++++++ .../erp-auth/src/service/position_service.rs | 24 ++++++++++ crates/erp-auth/src/service/role_service.rs | 24 ++++++++++ crates/erp-auth/src/service/user_service.rs | 24 ++++++++++ .../src/service/dictionary_service.rs | 44 +++++++++++++++++++ crates/erp-config/src/service/menu_service.rs | 23 ++++++++++ .../src/service/numbering_service.rs | 23 ++++++++++ .../erp-config/src/service/setting_service.rs | 24 ++++++++++ crates/erp-core/src/audit_service.rs | 27 ++++++++++++ crates/erp-core/src/entity/audit_log.rs | 25 +++++++++++ crates/erp-core/src/entity/mod.rs | 1 + crates/erp-core/src/lib.rs | 2 + .../src/service/message_service.rs | 36 +++++++++++++++ .../src/service/definition_service.rs | 30 +++++++++++++ .../src/service/instance_service.rs | 17 +++++++ .../erp-workflow/src/service/task_service.rs | 16 +++++++ 17 files changed, 388 insertions(+) create mode 100644 crates/erp-core/src/audit_service.rs create mode 100644 crates/erp-core/src/entity/audit_log.rs create mode 100644 crates/erp-core/src/entity/mod.rs diff --git a/crates/erp-auth/src/service/dept_service.rs b/crates/erp-auth/src/service/dept_service.rs index 50e8f8a..48d048f 100644 --- a/crates/erp-auth/src/service/dept_service.rs +++ b/crates/erp-auth/src/service/dept_service.rs @@ -8,6 +8,8 @@ use crate::dto::{CreateDepartmentReq, DepartmentResp, UpdateDepartmentReq}; use crate::entity::department; use crate::entity::organization; use crate::error::{AuthError, AuthResult}; +use erp_core::audit::AuditLog; +use erp_core::audit_service; use erp_core::error::check_version; use erp_core::events::EventBus; @@ -124,6 +126,13 @@ impl DeptService { serde_json::json!({ "dept_id": id, "org_id": org_id, "name": req.name }), )); + audit_service::record( + AuditLog::new(tenant_id, Some(operator_id), "department.create", "department") + .with_resource_id(id), + db, + ) + .await; + Ok(DepartmentResp { id, org_id, @@ -195,6 +204,13 @@ impl DeptService { .await .map_err(|e| AuthError::Validation(e.to_string()))?; + audit_service::record( + AuditLog::new(tenant_id, Some(operator_id), "department.update", "department") + .with_resource_id(id), + db, + ) + .await; + Ok(DepartmentResp { id: updated.id, org_id: updated.org_id, @@ -256,6 +272,14 @@ impl DeptService { tenant_id, serde_json::json!({ "dept_id": id }), )); + + audit_service::record( + AuditLog::new(tenant_id, Some(operator_id), "department.delete", "department") + .with_resource_id(id), + db, + ) + .await; + Ok(()) } } diff --git a/crates/erp-auth/src/service/org_service.rs b/crates/erp-auth/src/service/org_service.rs index 2097a3e..4686056 100644 --- a/crates/erp-auth/src/service/org_service.rs +++ b/crates/erp-auth/src/service/org_service.rs @@ -7,6 +7,8 @@ use uuid::Uuid; use crate::dto::{CreateOrganizationReq, OrganizationResp, UpdateOrganizationReq}; use crate::entity::organization; use crate::error::{AuthError, AuthResult}; +use erp_core::audit::AuditLog; +use erp_core::audit_service; use erp_core::error::check_version; use erp_core::events::EventBus; @@ -110,6 +112,13 @@ impl OrgService { serde_json::json!({ "org_id": id, "name": req.name }), )); + audit_service::record( + AuditLog::new(tenant_id, Some(operator_id), "organization.create", "organization") + .with_resource_id(id), + db, + ) + .await; + Ok(OrganizationResp { id, name: req.name.clone(), @@ -177,6 +186,13 @@ impl OrgService { .await .map_err(|e| AuthError::Validation(e.to_string()))?; + audit_service::record( + AuditLog::new(tenant_id, Some(operator_id), "organization.update", "organization") + .with_resource_id(id), + db, + ) + .await; + Ok(OrganizationResp { id: updated.id, name: updated.name.clone(), @@ -235,6 +251,14 @@ impl OrgService { tenant_id, serde_json::json!({ "org_id": id }), )); + + audit_service::record( + AuditLog::new(tenant_id, Some(operator_id), "organization.delete", "organization") + .with_resource_id(id), + db, + ) + .await; + Ok(()) } } diff --git a/crates/erp-auth/src/service/position_service.rs b/crates/erp-auth/src/service/position_service.rs index 3f1da85..8d5aa0b 100644 --- a/crates/erp-auth/src/service/position_service.rs +++ b/crates/erp-auth/src/service/position_service.rs @@ -6,6 +6,8 @@ use crate::dto::{CreatePositionReq, PositionResp, UpdatePositionReq}; use crate::entity::department; use crate::entity::position; use crate::error::{AuthError, AuthResult}; +use erp_core::audit::AuditLog; +use erp_core::audit_service; use erp_core::error::check_version; use erp_core::events::EventBus; @@ -109,6 +111,13 @@ impl PositionService { serde_json::json!({ "position_id": id, "dept_id": dept_id, "name": req.name }), )); + audit_service::record( + AuditLog::new(tenant_id, Some(operator_id), "position.create", "position") + .with_resource_id(id), + db, + ) + .await; + Ok(PositionResp { id, dept_id, @@ -177,6 +186,13 @@ impl PositionService { .await .map_err(|e| AuthError::Validation(e.to_string()))?; + audit_service::record( + AuditLog::new(tenant_id, Some(operator_id), "position.update", "position") + .with_resource_id(id), + db, + ) + .await; + Ok(PositionResp { id: updated.id, dept_id: updated.dept_id, @@ -219,6 +235,14 @@ impl PositionService { tenant_id, serde_json::json!({ "position_id": id }), )); + + audit_service::record( + AuditLog::new(tenant_id, Some(operator_id), "position.delete", "position") + .with_resource_id(id), + db, + ) + .await; + Ok(()) } } diff --git a/crates/erp-auth/src/service/role_service.rs b/crates/erp-auth/src/service/role_service.rs index 4cbe072..f444cfe 100644 --- a/crates/erp-auth/src/service/role_service.rs +++ b/crates/erp-auth/src/service/role_service.rs @@ -8,6 +8,8 @@ use crate::dto::{PermissionResp, RoleResp}; use crate::entity::{permission, role, role_permission}; use crate::error::AuthError; use crate::error::AuthResult; +use erp_core::audit::AuditLog; +use erp_core::audit_service; use erp_core::error::check_version; use erp_core::events::EventBus; use erp_core::types::Pagination; @@ -131,6 +133,13 @@ impl RoleService { serde_json::json!({ "role_id": id, "code": code }), )); + audit_service::record( + AuditLog::new(tenant_id, Some(operator_id), "role.create", "role") + .with_resource_id(id), + db, + ) + .await; + Ok(RoleResp { id, name: name.to_string(), @@ -180,6 +189,13 @@ impl RoleService { .await .map_err(|e| AuthError::Validation(e.to_string()))?; + audit_service::record( + AuditLog::new(tenant_id, Some(operator_id), "role.update", "role") + .with_resource_id(id), + db, + ) + .await; + Ok(RoleResp { id: updated.id, name: updated.name.clone(), @@ -227,6 +243,14 @@ impl RoleService { tenant_id, serde_json::json!({ "role_id": id }), )); + + audit_service::record( + AuditLog::new(tenant_id, Some(operator_id), "role.delete", "role") + .with_resource_id(id), + db, + ) + .await; + Ok(()) } diff --git a/crates/erp-auth/src/service/user_service.rs b/crates/erp-auth/src/service/user_service.rs index 528b597..a10c9c9 100644 --- a/crates/erp-auth/src/service/user_service.rs +++ b/crates/erp-auth/src/service/user_service.rs @@ -5,6 +5,8 @@ use uuid::Uuid; use crate::dto::{CreateUserReq, RoleResp, UpdateUserReq, UserResp}; use crate::entity::{role, user, user_credential, user_role}; use crate::error::AuthError; +use erp_core::audit::AuditLog; +use erp_core::audit_service; use erp_core::error::check_version; use erp_core::events::EventBus; use erp_core::types::Pagination; @@ -94,6 +96,13 @@ impl UserService { serde_json::json!({ "user_id": user_id, "username": req.username }), )); + audit_service::record( + AuditLog::new(tenant_id, Some(operator_id), "user.create", "user") + .with_resource_id(user_id), + db, + ) + .await; + Ok(UserResp { id: user_id, username: req.username.clone(), @@ -215,6 +224,13 @@ impl UserService { .await .map_err(|e| AuthError::Validation(e.to_string()))?; + audit_service::record( + AuditLog::new(tenant_id, Some(operator_id), "user.update", "user") + .with_resource_id(id), + db, + ) + .await; + let roles = Self::fetch_user_role_resps(id, tenant_id, db).await?; Ok(model_to_resp(&updated, roles)) } @@ -250,6 +266,14 @@ impl UserService { tenant_id, serde_json::json!({ "user_id": id }), )); + + audit_service::record( + AuditLog::new(tenant_id, Some(operator_id), "user.delete", "user") + .with_resource_id(id), + db, + ) + .await; + Ok(()) } diff --git a/crates/erp-config/src/service/dictionary_service.rs b/crates/erp-config/src/service/dictionary_service.rs index e856d1d..9359162 100644 --- a/crates/erp-config/src/service/dictionary_service.rs +++ b/crates/erp-config/src/service/dictionary_service.rs @@ -7,6 +7,8 @@ use uuid::Uuid; use crate::dto::{DictionaryItemResp, DictionaryResp}; use crate::entity::{dictionary, dictionary_item}; use crate::error::{ConfigError, ConfigResult}; +use erp_core::audit::AuditLog; +use erp_core::audit_service; use erp_core::error::check_version; use erp_core::events::EventBus; use erp_core::types::Pagination; @@ -137,6 +139,13 @@ impl DictionaryService { serde_json::json!({ "dictionary_id": id, "code": code }), )); + audit_service::record( + AuditLog::new(tenant_id, Some(operator_id), "dictionary.create", "dictionary") + .with_resource_id(id), + db, + ) + .await; + Ok(DictionaryResp { id, name: name.to_string(), @@ -188,6 +197,13 @@ impl DictionaryService { let items = Self::fetch_items(updated.id, tenant_id, db).await?; + audit_service::record( + AuditLog::new(tenant_id, Some(operator_id), "dictionary.update", "dictionary") + .with_resource_id(id), + db, + ) + .await; + Ok(DictionaryResp { id: updated.id, name: updated.name.clone(), @@ -234,6 +250,13 @@ impl DictionaryService { serde_json::json!({ "dictionary_id": id }), )); + audit_service::record( + AuditLog::new(tenant_id, Some(operator_id), "dictionary.delete", "dictionary") + .with_resource_id(id), + db, + ) + .await; + Ok(()) } @@ -291,6 +314,13 @@ impl DictionaryService { .await .map_err(|e| ConfigError::Validation(e.to_string()))?; + audit_service::record( + AuditLog::new(tenant_id, Some(operator_id), "dictionary_item.create", "dictionary_item") + .with_resource_id(id), + db, + ) + .await; + Ok(DictionaryItemResp { id, dictionary_id, @@ -345,6 +375,13 @@ impl DictionaryService { .await .map_err(|e| ConfigError::Validation(e.to_string()))?; + audit_service::record( + AuditLog::new(tenant_id, Some(operator_id), "dictionary_item.update", "dictionary_item") + .with_resource_id(item_id), + db, + ) + .await; + Ok(DictionaryItemResp { id: updated.id, dictionary_id: updated.dictionary_id, @@ -385,6 +422,13 @@ impl DictionaryService { .await .map_err(|e| ConfigError::Validation(e.to_string()))?; + audit_service::record( + AuditLog::new(tenant_id, Some(operator_id), "dictionary_item.delete", "dictionary_item") + .with_resource_id(item_id), + db, + ) + .await; + Ok(()) } diff --git a/crates/erp-config/src/service/menu_service.rs b/crates/erp-config/src/service/menu_service.rs index b6a6db0..0da88c7 100644 --- a/crates/erp-config/src/service/menu_service.rs +++ b/crates/erp-config/src/service/menu_service.rs @@ -9,6 +9,8 @@ use uuid::Uuid; use crate::dto::{CreateMenuReq, MenuResp}; use crate::entity::{menu, menu_role}; use crate::error::{ConfigError, ConfigResult}; +use erp_core::audit::AuditLog; +use erp_core::audit_service; use erp_core::error::check_version; use erp_core::events::EventBus; @@ -156,6 +158,13 @@ impl MenuService { serde_json::json!({ "menu_id": id, "title": req.title }), )); + audit_service::record( + AuditLog::new(tenant_id, Some(operator_id), "menu.create", "menu") + .with_resource_id(id), + db, + ) + .await; + Ok(MenuResp { id, parent_id: req.parent_id, @@ -225,6 +234,13 @@ impl MenuService { Self::assign_roles(id, role_ids, tenant_id, operator_id, db).await?; } + audit_service::record( + AuditLog::new(tenant_id, Some(operator_id), "menu.update", "menu") + .with_resource_id(id), + db, + ) + .await; + Ok(MenuResp { id: updated.id, parent_id: updated.parent_id, @@ -275,6 +291,13 @@ impl MenuService { serde_json::json!({ "menu_id": id }), )); + audit_service::record( + AuditLog::new(tenant_id, Some(operator_id), "menu.delete", "menu") + .with_resource_id(id), + db, + ) + .await; + Ok(()) } diff --git a/crates/erp-config/src/service/numbering_service.rs b/crates/erp-config/src/service/numbering_service.rs index 881cf95..4d285ca 100644 --- a/crates/erp-config/src/service/numbering_service.rs +++ b/crates/erp-config/src/service/numbering_service.rs @@ -8,6 +8,8 @@ use uuid::Uuid; use crate::dto::{CreateNumberingRuleReq, GenerateNumberResp, NumberingRuleResp}; use crate::entity::numbering_rule; use crate::error::{ConfigError, ConfigResult}; +use erp_core::audit::AuditLog; +use erp_core::audit_service; use erp_core::error::check_version; use erp_core::events::EventBus; use erp_core::types::Pagination; @@ -107,6 +109,13 @@ impl NumberingService { serde_json::json!({ "rule_id": id, "code": req.code }), )); + audit_service::record( + AuditLog::new(tenant_id, Some(operator_id), "numbering_rule.create", "numbering_rule") + .with_resource_id(id), + db, + ) + .await; + Ok(NumberingRuleResp { id, name: req.name.clone(), @@ -171,6 +180,13 @@ impl NumberingService { .await .map_err(|e| ConfigError::Validation(e.to_string()))?; + audit_service::record( + AuditLog::new(tenant_id, Some(operator_id), "numbering_rule.update", "numbering_rule") + .with_resource_id(id), + db, + ) + .await; + Ok(Self::model_to_resp(&updated)) } @@ -209,6 +225,13 @@ impl NumberingService { serde_json::json!({ "rule_id": id }), )); + audit_service::record( + AuditLog::new(tenant_id, Some(operator_id), "numbering_rule.delete", "numbering_rule") + .with_resource_id(id), + db, + ) + .await; + Ok(()) } diff --git a/crates/erp-config/src/service/setting_service.rs b/crates/erp-config/src/service/setting_service.rs index 8f7e045..1621bca 100644 --- a/crates/erp-config/src/service/setting_service.rs +++ b/crates/erp-config/src/service/setting_service.rs @@ -7,6 +7,8 @@ use uuid::Uuid; use crate::dto::SettingResp; use crate::entity::setting; use crate::error::{ConfigError, ConfigResult}; +use erp_core::audit::AuditLog; +use erp_core::audit_service; use erp_core::error::check_version; use erp_core::events::EventBus; use erp_core::types::Pagination; @@ -117,6 +119,13 @@ impl SettingService { }), )); + audit_service::record( + AuditLog::new(tenant_id, Some(operator_id), "setting.upsert", "setting") + .with_resource_id(updated.id), + db, + ) + .await; + Ok(Self::model_to_resp(&updated)) } else { // Insert new record @@ -151,6 +160,13 @@ impl SettingService { }), )); + audit_service::record( + AuditLog::new(tenant_id, Some(operator_id), "setting.upsert", "setting") + .with_resource_id(id), + db, + ) + .await; + Ok(Self::model_to_resp(&inserted)) } } @@ -217,6 +233,7 @@ impl SettingService { let next_version = check_version(version, model.version).map_err(|_| ConfigError::VersionMismatch)?; + let setting_id = model.id; let mut active: setting::ActiveModel = model.into(); active.deleted_at = Set(Some(Utc::now())); active.updated_at = Set(Utc::now()); @@ -227,6 +244,13 @@ impl SettingService { .await .map_err(|e| ConfigError::Validation(e.to_string()))?; + audit_service::record( + AuditLog::new(tenant_id, Some(operator_id), "setting.delete", "setting") + .with_resource_id(setting_id), + db, + ) + .await; + Ok(()) } diff --git a/crates/erp-core/src/audit_service.rs b/crates/erp-core/src/audit_service.rs new file mode 100644 index 0000000..36d97db --- /dev/null +++ b/crates/erp-core/src/audit_service.rs @@ -0,0 +1,27 @@ +use crate::audit::AuditLog; +use crate::entity::audit_log; +use sea_orm::{ActiveModelTrait, Set}; +use tracing; + +/// 持久化审计日志到 audit_logs 表。 +/// +/// 使用 fire-and-forget 模式:失败仅记录 warning 日志,不影响业务操作。 +pub async fn record(log: AuditLog, db: &sea_orm::DatabaseConnection) { + let model = audit_log::ActiveModel { + id: Set(log.id), + tenant_id: Set(log.tenant_id), + user_id: Set(log.user_id), + action: Set(log.action), + resource_type: Set(log.resource_type), + resource_id: Set(log.resource_id), + old_value: Set(log.old_value), + new_value: Set(log.new_value), + ip_address: Set(log.ip_address), + user_agent: Set(log.user_agent), + created_at: Set(log.created_at), + }; + + if let Err(e) = model.insert(db).await { + tracing::warn!(error = %e, "审计日志写入失败"); + } +} diff --git a/crates/erp-core/src/entity/audit_log.rs b/crates/erp-core/src/entity/audit_log.rs new file mode 100644 index 0000000..0bc0557 --- /dev/null +++ b/crates/erp-core/src/entity/audit_log.rs @@ -0,0 +1,25 @@ +use sea_orm::entity::prelude::*; +use serde::{Deserialize, Serialize}; + +/// 审计日志实体 — 映射 audit_logs 表。 +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Serialize, Deserialize)] +#[sea_orm(table_name = "audit_logs")] +pub struct Model { + #[sea_orm(primary_key, auto_increment = false)] + pub id: Uuid, + pub tenant_id: Uuid, + pub user_id: Option, + pub action: String, + pub resource_type: String, + pub resource_id: Option, + pub old_value: Option, + pub new_value: Option, + pub ip_address: Option, + pub user_agent: Option, + pub created_at: DateTimeUtc, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation {} + +impl ActiveModelBehavior for ActiveModel {} diff --git a/crates/erp-core/src/entity/mod.rs b/crates/erp-core/src/entity/mod.rs new file mode 100644 index 0000000..9c4a543 --- /dev/null +++ b/crates/erp-core/src/entity/mod.rs @@ -0,0 +1 @@ +pub mod audit_log; diff --git a/crates/erp-core/src/lib.rs b/crates/erp-core/src/lib.rs index 3fcf792..8177c84 100644 --- a/crates/erp-core/src/lib.rs +++ b/crates/erp-core/src/lib.rs @@ -1,4 +1,6 @@ pub mod audit; +pub mod audit_service; +pub mod entity; pub mod error; pub mod events; pub mod module; diff --git a/crates/erp-message/src/service/message_service.rs b/crates/erp-message/src/service/message_service.rs index c8d290d..8ad346b 100644 --- a/crates/erp-message/src/service/message_service.rs +++ b/crates/erp-message/src/service/message_service.rs @@ -8,6 +8,8 @@ use uuid::Uuid; use crate::dto::{MessageQuery, MessageResp, SendMessageReq, UnreadCountResp}; use crate::entity::message; use crate::error::{MessageError, MessageResult}; +use erp_core::audit::AuditLog; +use erp_core::audit_service; use erp_core::events::EventBus; /// 消息服务。 @@ -130,6 +132,13 @@ impl MessageService { }), )); + audit_service::record( + AuditLog::new(tenant_id, Some(sender_id), "message.send", "message") + .with_resource_id(id), + db, + ) + .await; + Ok(Self::model_to_resp(&inserted)) } @@ -191,6 +200,13 @@ impl MessageService { }), )); + audit_service::record( + AuditLog::new(tenant_id, Some(system_user), "message.send_system", "message") + .with_resource_id(id), + db, + ) + .await; + Ok(Self::model_to_resp(&inserted)) } @@ -230,6 +246,13 @@ impl MessageService { .await .map_err(|e| MessageError::Validation(e.to_string()))?; + audit_service::record( + AuditLog::new(tenant_id, Some(user_id), "message.mark_read", "message") + .with_resource_id(id), + db, + ) + .await; + Ok(()) } @@ -254,6 +277,12 @@ impl MessageService { .await .map_err(|e| MessageError::Validation(e.to_string()))?; + audit_service::record( + AuditLog::new(tenant_id, Some(user_id), "message.mark_all_read", "message"), + db, + ) + .await; + Ok(()) } @@ -288,6 +317,13 @@ impl MessageService { .await .map_err(|e| MessageError::Validation(e.to_string()))?; + audit_service::record( + AuditLog::new(tenant_id, Some(user_id), "message.delete", "message") + .with_resource_id(id), + db, + ) + .await; + Ok(()) } diff --git a/crates/erp-workflow/src/service/definition_service.rs b/crates/erp-workflow/src/service/definition_service.rs index 2f71d40..955bdd2 100644 --- a/crates/erp-workflow/src/service/definition_service.rs +++ b/crates/erp-workflow/src/service/definition_service.rs @@ -10,6 +10,8 @@ use crate::dto::{ use crate::engine::parser; use crate::entity::process_definition; use crate::error::{WorkflowError, WorkflowResult}; +use erp_core::audit::AuditLog; +use erp_core::audit_service; use erp_core::error::check_version; use erp_core::events::EventBus; use erp_core::types::Pagination; @@ -107,6 +109,13 @@ impl DefinitionService { serde_json::json!({ "definition_id": id, "key": req.key }), )); + audit_service::record( + AuditLog::new(tenant_id, Some(operator_id), "process_definition.create", "process_definition") + .with_resource_id(id), + db, + ) + .await; + Ok(ProcessDefinitionResp { id, name: req.name.clone(), @@ -182,6 +191,13 @@ impl DefinitionService { .await .map_err(|e| WorkflowError::Validation(e.to_string()))?; + audit_service::record( + AuditLog::new(tenant_id, Some(operator_id), "process_definition.update", "process_definition") + .with_resource_id(id), + db, + ) + .await; + Ok(Self::model_to_resp(&updated)) } @@ -231,6 +247,13 @@ impl DefinitionService { serde_json::json!({ "definition_id": id }), )); + audit_service::record( + AuditLog::new(tenant_id, Some(operator_id), "process_definition.publish", "process_definition") + .with_resource_id(id), + db, + ) + .await; + Ok(Self::model_to_resp(&updated)) } @@ -259,6 +282,13 @@ impl DefinitionService { .await .map_err(|e| WorkflowError::Validation(e.to_string()))?; + audit_service::record( + AuditLog::new(tenant_id, Some(operator_id), "process_definition.delete", "process_definition") + .with_resource_id(id), + db, + ) + .await; + Ok(()) } diff --git a/crates/erp-workflow/src/service/instance_service.rs b/crates/erp-workflow/src/service/instance_service.rs index ebfa8be..4f79de2 100644 --- a/crates/erp-workflow/src/service/instance_service.rs +++ b/crates/erp-workflow/src/service/instance_service.rs @@ -12,6 +12,8 @@ use crate::engine::executor::FlowExecutor; use crate::engine::parser; use crate::entity::{process_definition, process_instance, process_variable, token}; use crate::error::{WorkflowError, WorkflowResult}; +use erp_core::audit::AuditLog; +use erp_core::audit_service; use erp_core::events::EventBus; use erp_core::types::Pagination; @@ -130,6 +132,13 @@ impl InstanceService { serde_json::json!({ "instance_id": instance_id, "definition_id": definition.id, "started_by": operator_id }), )); + audit_service::record( + AuditLog::new(tenant_id, Some(operator_id), "process_instance.start", "process_instance") + .with_resource_id(instance_id), + db, + ) + .await; + // 查询创建后的实例(包含 token) let instance = process_instance::Entity::find_by_id(instance_id) .one(db) @@ -303,6 +312,14 @@ impl InstanceService { .await .map_err(|e| WorkflowError::Validation(e.to_string()))?; + let action = format!("process_instance.{}", to_status); + audit_service::record( + AuditLog::new(tenant_id, Some(operator_id), action, "process_instance") + .with_resource_id(id), + db, + ) + .await; + Ok(()) } diff --git a/crates/erp-workflow/src/service/task_service.rs b/crates/erp-workflow/src/service/task_service.rs index c9f17ef..88f2009 100644 --- a/crates/erp-workflow/src/service/task_service.rs +++ b/crates/erp-workflow/src/service/task_service.rs @@ -12,6 +12,8 @@ use crate::engine::executor::FlowExecutor; use crate::engine::parser; use crate::entity::{process_definition, process_instance, task}; use crate::error::{WorkflowError, WorkflowResult}; +use erp_core::audit::AuditLog; +use erp_core::audit_service; use erp_core::events::EventBus; use erp_core::types::Pagination; @@ -242,6 +244,13 @@ impl TaskService { serde_json::json!({ "task_id": id, "outcome": req.outcome }), )); + audit_service::record( + AuditLog::new(tenant_id, Some(operator_id), "task.complete", "task") + .with_resource_id(id), + db, + ) + .await; + // 重新查询任务 let updated = task::Entity::find_by_id(id) .one(db) @@ -311,6 +320,13 @@ impl TaskService { .await .map_err(|e| WorkflowError::Validation(e.to_string()))?; + audit_service::record( + AuditLog::new(tenant_id, Some(operator_id), "task.delegate", "task") + .with_resource_id(id), + db, + ) + .await; + Ok(Self::model_to_resp(&updated)) }