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

@@ -7,6 +7,7 @@ use uuid::Uuid;
use crate::dto::{CreateOrganizationReq, OrganizationResp, UpdateOrganizationReq};
use crate::entity::organization;
use crate::error::{AuthError, AuthResult};
use erp_core::error::check_version;
use erp_core::events::EventBus;
/// Organization CRUD service -- create, read, update, soft-delete organizations
@@ -118,6 +119,7 @@ impl OrgService {
level,
sort_order: req.sort_order.unwrap_or(0),
children: vec![],
version: 1,
})
}
@@ -152,6 +154,8 @@ impl OrgService {
}
}
let next_ver = check_version(req.version, model.version)?;
let mut active: organization::ActiveModel = model.into();
if let Some(ref name) = req.name {
@@ -166,6 +170,7 @@ impl OrgService {
active.updated_at = Set(Utc::now());
active.updated_by = Set(operator_id);
active.version = Set(next_ver);
let updated = active
.update(db)
@@ -181,6 +186,7 @@ impl OrgService {
level: updated.level,
sort_order: updated.sort_order,
children: vec![],
version: updated.version,
})
}
@@ -213,10 +219,12 @@ impl OrgService {
));
}
let current_version = model.version;
let mut active: organization::ActiveModel = model.into();
active.deleted_at = Set(Some(Utc::now()));
active.updated_at = Set(Utc::now());
active.updated_by = Set(operator_id);
active.version = Set(current_version + 1);
active
.update(db)
.await
@@ -258,6 +266,7 @@ fn build_org_tree(items: &[organization::Model]) -> Vec<OrganizationResp> {
level: item.level,
sort_order: item.sort_order,
children,
version: item.version,
}
}