Files
erp/crates/erp-workflow/src/dto.rs
iven 5d6e1dc394 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.
2026-04-11 23:25:43 +08:00

214 lines
6.2 KiB
Rust

use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use utoipa::ToSchema;
use uuid::Uuid;
use validator::Validate;
// --- 流程图节点/边定义 ---
/// BPMN 节点类型
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema, PartialEq)]
pub enum NodeType {
StartEvent,
EndEvent,
UserTask,
ServiceTask,
ExclusiveGateway,
ParallelGateway,
}
/// 流程图节点定义
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub struct NodeDef {
pub id: String,
#[serde(rename = "type")]
pub node_type: NodeType,
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub assignee_id: Option<Uuid>,
#[serde(skip_serializing_if = "Option::is_none")]
pub candidate_groups: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub service_type: Option<String>,
/// 前端渲染位置
#[serde(skip_serializing_if = "Option::is_none")]
pub position: Option<NodePosition>,
}
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub struct NodePosition {
pub x: f64,
pub y: f64,
}
/// 流程图连线定义
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub struct EdgeDef {
pub id: String,
pub source: String,
pub target: String,
/// 条件表达式(排他网关分支)
#[serde(skip_serializing_if = "Option::is_none")]
pub condition: Option<String>,
/// 前端渲染标签
#[serde(skip_serializing_if = "Option::is_none")]
pub label: Option<String>,
}
/// 完整流程图
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub struct FlowDiagram {
pub nodes: Vec<NodeDef>,
pub edges: Vec<EdgeDef>,
}
// --- 流程定义 DTOs ---
#[derive(Debug, Serialize, ToSchema)]
pub struct ProcessDefinitionResp {
pub id: Uuid,
pub name: String,
pub key: String,
pub version: i32,
#[serde(skip_serializing_if = "Option::is_none")]
pub category: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
pub nodes: serde_json::Value,
pub edges: serde_json::Value,
pub status: String,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
pub lock_version: i32,
}
#[derive(Debug, Deserialize, Validate, ToSchema)]
pub struct CreateProcessDefinitionReq {
#[validate(length(min = 1, max = 200, message = "流程名称不能为空"))]
pub name: String,
#[validate(length(min = 1, max = 100, message = "流程编码不能为空"))]
pub key: String,
pub category: Option<String>,
pub description: Option<String>,
pub nodes: Vec<NodeDef>,
pub edges: Vec<EdgeDef>,
}
#[derive(Debug, Deserialize, Validate, ToSchema)]
pub struct UpdateProcessDefinitionReq {
#[validate(length(max = 200, message = "流程名称过长"))]
pub name: Option<String>,
pub category: Option<String>,
pub description: Option<String>,
pub nodes: Option<Vec<NodeDef>>,
pub edges: Option<Vec<EdgeDef>>,
pub version: i32,
}
// --- 流程实例 DTOs ---
#[derive(Debug, Serialize, ToSchema)]
pub struct ProcessInstanceResp {
pub id: Uuid,
pub definition_id: Uuid,
pub definition_name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub business_key: Option<String>,
pub status: String,
pub started_by: Uuid,
pub started_at: DateTime<Utc>,
#[serde(skip_serializing_if = "Option::is_none")]
pub completed_at: Option<DateTime<Utc>>,
pub created_at: DateTime<Utc>,
/// 当前活跃的 token 位置
pub active_tokens: Vec<TokenResp>,
pub version: i32,
}
#[derive(Debug, Deserialize, Validate, ToSchema)]
pub struct StartInstanceReq {
pub definition_id: Uuid,
pub business_key: Option<String>,
/// 初始流程变量
pub variables: Option<Vec<SetVariableReq>>,
}
// --- Token DTOs ---
#[derive(Debug, Serialize, ToSchema)]
pub struct TokenResp {
pub id: Uuid,
pub node_id: String,
pub status: String,
pub created_at: DateTime<Utc>,
}
// --- 任务 DTOs ---
#[derive(Debug, Serialize, ToSchema)]
pub struct TaskResp {
pub id: Uuid,
pub instance_id: Uuid,
pub token_id: Uuid,
pub node_id: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub node_name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub assignee_id: Option<Uuid>,
#[serde(skip_serializing_if = "Option::is_none")]
pub candidate_groups: Option<serde_json::Value>,
pub status: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub outcome: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub form_data: Option<serde_json::Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub due_date: Option<DateTime<Utc>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub completed_at: Option<DateTime<Utc>>,
pub created_at: DateTime<Utc>,
/// 流程定义名称(用于列表展示)
#[serde(skip_serializing_if = "Option::is_none")]
pub definition_name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub business_key: Option<String>,
pub version: i32,
}
#[derive(Debug, Deserialize, Validate, ToSchema)]
pub struct CompleteTaskReq {
#[validate(length(min = 1, max = 50, message = "审批结果不能为空"))]
pub outcome: String,
pub form_data: Option<serde_json::Value>,
}
#[derive(Debug, Deserialize, Validate, ToSchema)]
pub struct DelegateTaskReq {
pub delegate_to: Uuid,
}
// --- 流程变量 DTOs ---
#[derive(Debug, Serialize, ToSchema)]
pub struct ProcessVariableResp {
pub id: Uuid,
pub name: String,
pub var_type: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub value_string: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub value_number: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub value_boolean: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub value_date: Option<DateTime<Utc>>,
}
#[derive(Debug, Clone, Deserialize, Validate, ToSchema)]
pub struct SetVariableReq {
#[validate(length(min = 1, max = 100, message = "变量名不能为空"))]
pub name: String,
pub var_type: Option<String>,
pub value: serde_json::Value,
}