feat(core): implement optimistic locking across all entities

Add VersionMismatch error variant and check_version() helper to erp-core.
All 13 mutable entities now enforce version checking on update/delete:
- erp-auth: user, role, organization, department, position
- erp-config: dictionary, dictionary_item, menu, setting, numbering_rule
- erp-workflow: process_definition, process_instance, task
- erp-message: message, message_subscription

Update DTOs to expose version in responses and require version in update
requests. HTTP 409 Conflict returned on version mismatch.
This commit is contained in:
iven
2026-04-11 23:25:43 +08:00
parent 1fec5e2cf2
commit 5d6e1dc394
32 changed files with 549 additions and 184 deletions

View File

@@ -10,6 +10,7 @@ use crate::dto::{
use crate::engine::parser;
use crate::entity::process_definition;
use crate::error::{WorkflowError, WorkflowResult};
use erp_core::error::check_version;
use erp_core::events::EventBus;
use erp_core::types::Pagination;
@@ -118,6 +119,7 @@ impl DefinitionService {
status: "draft".to_string(),
created_at: now,
updated_at: now,
lock_version: 1,
})
}
@@ -142,6 +144,7 @@ impl DefinitionService {
));
}
let current_version = model.version_field;
let mut active: process_definition::ActiveModel = model.into();
if let Some(name) = &req.name {
@@ -168,6 +171,9 @@ impl DefinitionService {
active.edges = Set(edges_json);
}
let next_ver = check_version(req.version, current_version)
.map_err(|_| WorkflowError::VersionMismatch)?;
active.version_field = Set(next_ver);
active.updated_at = Set(Utc::now());
active.updated_by = Set(operator_id);
@@ -207,8 +213,10 @@ impl DefinitionService {
.map_err(|e| WorkflowError::InvalidDiagram(format!("连线数据无效: {e}")))?;
parser::parse_and_validate(&nodes, &edges)?;
let current_version = model.version_field;
let mut active: process_definition::ActiveModel = model.into();
active.status = Set("published".to_string());
active.version_field = Set(current_version + 1);
active.updated_at = Set(Utc::now());
active.updated_by = Set(operator_id);
@@ -240,7 +248,9 @@ impl DefinitionService {
.filter(|m| m.tenant_id == tenant_id && m.deleted_at.is_none())
.ok_or_else(|| WorkflowError::NotFound(format!("流程定义不存在: {id}")))?;
let current_version = model.version_field;
let mut active: process_definition::ActiveModel = model.into();
active.version_field = Set(current_version + 1);
active.deleted_at = Set(Some(Utc::now()));
active.updated_at = Set(Utc::now());
active.updated_by = Set(operator_id);
@@ -265,6 +275,7 @@ impl DefinitionService {
status: m.status.clone(),
created_at: m.created_at,
updated_at: m.updated_at,
lock_version: m.version_field,
}
}
}