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.
214 lines
6.2 KiB
Rust
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,
|
|
}
|