Skip to main content

sz_rust_workflow/scheduling/
approval_strategy.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright (c) 2024-2026 SZ-Rust Team
3//
4use crate::definition::ApprovalStrategyType;
5use crate::instance::{Task, TaskAction, TaskStatus};
6
7/// 节点完成状态。
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub enum NodeCompletion {
10    Completed,
11    Rejected,
12    InProgress,
13}
14
15/// 审批策略 trait,对齐 design 2.2.2.7。
16pub trait ApprovalStrategy: Send + Sync + 'static {
17    fn check_completion(&self, tasks: &[Task], action: TaskAction) -> NodeCompletion;
18}
19
20/// 会签策略:所有候选人均 approve 才 Completed,任一 reject 则 Rejected。
21pub struct AndSignStrategy;
22
23impl ApprovalStrategy for AndSignStrategy {
24    fn check_completion(&self, tasks: &[Task], _action: TaskAction) -> NodeCompletion {
25        let relevant: Vec<&Task> = tasks
26            .iter()
27            .filter(|t| {
28                t.status == TaskStatus::Completed
29                    || t.status == TaskStatus::Rejected
30                    || t.status == TaskStatus::Pending
31            })
32            .collect();
33        if relevant.is_empty() {
34            return NodeCompletion::InProgress;
35        }
36        if relevant.iter().any(|t| t.status == TaskStatus::Rejected) {
37            return NodeCompletion::Rejected;
38        }
39        if relevant.iter().all(|t| t.status == TaskStatus::Completed) {
40            return NodeCompletion::Completed;
41        }
42        NodeCompletion::InProgress
43    }
44}
45
46/// 或签策略:任一候选人 approve 即 Completed,任一 reject 是否 Rejected 由配置决定。
47pub struct OrSignStrategy;
48
49impl ApprovalStrategy for OrSignStrategy {
50    fn check_completion(&self, tasks: &[Task], _action: TaskAction) -> NodeCompletion {
51        if tasks.iter().any(|t| t.status == TaskStatus::Completed) {
52            return NodeCompletion::Completed;
53        }
54        if tasks.iter().all(|t| t.status == TaskStatus::Rejected) && !tasks.is_empty() {
55            return NodeCompletion::Rejected;
56        }
57        NodeCompletion::InProgress
58    }
59}
60
61/// 按类型选择策略。
62pub fn select_strategy(strategy_type: ApprovalStrategyType) -> Box<dyn ApprovalStrategy> {
63    match strategy_type {
64        ApprovalStrategyType::AndSign => Box::new(AndSignStrategy),
65        ApprovalStrategyType::OrSign => Box::new(OrSignStrategy),
66    }
67}
68
69#[cfg(test)]
70mod tests {
71    use super::*;
72    use chrono::Utc;
73
74    fn make_task(id: &str, status: TaskStatus) -> Task {
75        Task {
76            task_id: id.into(),
77            instance_id: "i1".into(),
78            node_id: "n1".into(),
79            candidates: vec!["u1".into()],
80            status,
81            assignee: None,
82            action: None,
83            handled_at: None,
84            created_at: Utc::now(),
85        }
86    }
87
88    #[test]
89    fn and_sign_in_progress() {
90        let tasks = vec![
91            make_task("t1", TaskStatus::Completed),
92            make_task("t2", TaskStatus::Pending),
93            make_task("t3", TaskStatus::Pending),
94        ];
95        let s = AndSignStrategy;
96        assert_eq!(
97            s.check_completion(&tasks, TaskAction::Approve),
98            NodeCompletion::InProgress
99        );
100    }
101
102    #[test]
103    fn and_sign_completed() {
104        let tasks = vec![
105            make_task("t1", TaskStatus::Completed),
106            make_task("t2", TaskStatus::Completed),
107            make_task("t3", TaskStatus::Completed),
108        ];
109        let s = AndSignStrategy;
110        assert_eq!(
111            s.check_completion(&tasks, TaskAction::Approve),
112            NodeCompletion::Completed
113        );
114    }
115
116    #[test]
117    fn and_sign_rejected() {
118        let tasks = vec![
119            make_task("t1", TaskStatus::Completed),
120            make_task("t2", TaskStatus::Rejected),
121        ];
122        let s = AndSignStrategy;
123        assert_eq!(
124            s.check_completion(&tasks, TaskAction::Reject),
125            NodeCompletion::Rejected
126        );
127    }
128
129    #[test]
130    fn or_sign_completed() {
131        let tasks = vec![
132            make_task("t1", TaskStatus::Completed),
133            make_task("t2", TaskStatus::Pending),
134        ];
135        let s = OrSignStrategy;
136        assert_eq!(
137            s.check_completion(&tasks, TaskAction::Approve),
138            NodeCompletion::Completed
139        );
140    }
141
142    #[test]
143    fn or_sign_rejected() {
144        let tasks = vec![
145            make_task("t1", TaskStatus::Rejected),
146            make_task("t2", TaskStatus::Rejected),
147        ];
148        let s = OrSignStrategy;
149        assert_eq!(
150            s.check_completion(&tasks, TaskAction::Reject),
151            NodeCompletion::Rejected
152        );
153    }
154
155    #[test]
156    fn or_sign_in_progress() {
157        let tasks = vec![
158            make_task("t1", TaskStatus::Pending),
159            make_task("t2", TaskStatus::Pending),
160        ];
161        let s = OrSignStrategy;
162        assert_eq!(
163            s.check_completion(&tasks, TaskAction::Approve),
164            NodeCompletion::InProgress
165        );
166    }
167}