rust-viewflow 0.1.0

Rust workflow library inspired by Viewflow, compatible with Axum and Actix-web
Documentation
use async_trait::async_trait;
use chrono;
use serde_json;
use std::sync::Arc;

use crate::core::engine::generate_id;
use crate::core::{
    TaskState, TaskStatus, WorkflowDefinition, WorkflowResult, WorkflowState, WorkflowStatus,
};
use crate::db::WorkflowDatabase;

/// Leave request workflow definition
pub struct LeaveRequestWorkflow {
    database: Arc<dyn WorkflowDatabase>,
}

impl LeaveRequestWorkflow {
    pub fn new(database: Arc<dyn WorkflowDatabase>) -> Self {
        Self { database }
    }

    fn task_name(locale: &str, stage: &str) -> &'static str {
        match (locale, stage) {
            ("zh-CN", "manager") => "直属经理审批",
            ("zh-CN", "hr") => "HR 备案",
            ("zh-TW", "manager") => "直屬主管審批",
            ("zh-TW", "hr") => "HR 備案",
            (_, "manager") => "Manager Approval",
            (_, "hr") => "HR Approval",
            _ => "Task",
        }
    }
}

#[async_trait]
impl WorkflowDefinition for LeaveRequestWorkflow {
    fn name(&self) -> &str {
        "leave_request"
    }

    async fn start(&self, data: serde_json::Value) -> WorkflowResult<WorkflowState> {
        let now = chrono::Utc::now();
        let workflow_id = generate_id();
        let locale = data.get("locale").and_then(|v| v.as_str()).unwrap_or("en");

        let workflow = WorkflowState {
            id: workflow_id.clone(),
            name: self.name().to_string(),
            status: WorkflowStatus::Running,
            data: data.clone(),
            created_at: now,
            updated_at: now,
        };

        self.database.create_workflow(&workflow).await?;

        let manager_task = TaskState {
            id: generate_id(),
            workflow_id: workflow_id.clone(),
            name: Self::task_name(locale, "manager").to_string(),
            status: TaskStatus::Pending,
            assignee: data
                .get("manager_id")
                .and_then(|v| v.as_str())
                .map(|s| s.to_string()),
            data: data.clone(),
            created_at: now,
            updated_at: now,
            completed_at: None,
        };

        self.database.create_task(&manager_task).await?;

        Ok(workflow)
    }

    async fn execute_task(
        &self,
        task_id: &str,
        data: serde_json::Value,
    ) -> WorkflowResult<TaskState> {
        let mut task = self.database.get_task(task_id).await?;
        let locale = data
            .get("locale")
            .and_then(|v| v.as_str())
            .or_else(|| task.data.get("locale").and_then(|v| v.as_str()))
            .unwrap_or("en");

        if data
            .get("approved")
            .and_then(|v| v.as_bool())
            .unwrap_or(false)
        {
            task.status = TaskStatus::Completed;
            task.completed_at = Some(chrono::Utc::now());

            if task.name.contains("Manager")
                || task.name.contains("经理")
                || task.name.contains("主管")
            {
                let hr_task = TaskState {
                    id: generate_id(),
                    workflow_id: task.workflow_id.clone(),
                    name: Self::task_name(locale, "hr").to_string(),
                    status: TaskStatus::Pending,
                    assignee: data
                        .get("hr_id")
                        .and_then(|v| v.as_str())
                        .map(|s| s.to_string())
                        .or_else(|| Some("hr@example.com".to_string())),
                    data: data.clone(),
                    created_at: chrono::Utc::now(),
                    updated_at: chrono::Utc::now(),
                    completed_at: None,
                };
                self.database.create_task(&hr_task).await?;
            } else {
                let mut workflow = self.database.get_workflow(&task.workflow_id).await?;
                workflow.status = WorkflowStatus::Completed;
                workflow.updated_at = chrono::Utc::now();
                self.database.update_workflow(&workflow).await?;
            }
        } else {
            task.status = TaskStatus::Cancelled;
            task.completed_at = Some(chrono::Utc::now());

            let mut workflow = self.database.get_workflow(&task.workflow_id).await?;
            workflow.status = WorkflowStatus::Cancelled;
            workflow.updated_at = chrono::Utc::now();
            self.database.update_workflow(&workflow).await?;
        }

        task.data = data;
        task.updated_at = chrono::Utc::now();

        self.database.update_task(&task).await?;

        Ok(task)
    }

    async fn get_tasks(&self, workflow_id: &str) -> WorkflowResult<Vec<TaskState>> {
        self.database.get_tasks_by_workflow(workflow_id).await
    }
}