hostbasket/actix_mvc_app/src/controllers/flow.rs

634 lines
28 KiB
Rust

use actix_web::{web, HttpResponse, Responder, Result};
use actix_session::Session;
use chrono::{Utc, Duration};
use serde::Deserialize;
use tera::Tera;
use crate::models::flow::{Flow, FlowStatus, FlowType, FlowStatistics, FlowStep, StepStatus, FlowLog};
use crate::controllers::auth::Claims;
use crate::utils::render_template;
pub struct FlowController;
impl FlowController {
/// Renders the flows dashboard
pub async fn index(tmpl: web::Data<Tera>, session: Session) -> Result<impl Responder> {
let user = Self::get_user_from_session(&session);
let flows = Self::get_mock_flows();
let stats = FlowStatistics::new(&flows);
let mut ctx = tera::Context::new();
ctx.insert("active_page", "flows");
ctx.insert("user", &user);
ctx.insert("flows", &flows);
ctx.insert("stats", &stats);
ctx.insert("active_flows", &flows.iter().filter(|f| f.status == FlowStatus::InProgress).collect::<Vec<_>>());
ctx.insert("stuck_flows", &flows.iter().filter(|f| f.status == FlowStatus::Stuck).collect::<Vec<_>>());
render_template(&tmpl, "flows/index.html", &ctx)
}
/// Renders the flows list page
pub async fn list_flows(tmpl: web::Data<Tera>, session: Session) -> Result<impl Responder> {
let user = Self::get_user_from_session(&session);
let flows = Self::get_mock_flows();
let mut ctx = tera::Context::new();
ctx.insert("active_page", "flows");
ctx.insert("user", &user);
ctx.insert("flows", &flows);
render_template(&tmpl, "flows/flows.html", &ctx)
}
/// Renders the flow detail page
pub async fn flow_detail(
path: web::Path<String>,
tmpl: web::Data<Tera>,
session: Session
) -> Result<impl Responder> {
let flow_id = path.into_inner();
let user = Self::get_user_from_session(&session);
// Find the flow with the given ID
let flows = Self::get_mock_flows();
let flow = flows.iter().find(|f| f.id == flow_id);
if let Some(flow) = flow {
let mut ctx = tera::Context::new();
ctx.insert("active_page", "flows");
ctx.insert("user", &user);
ctx.insert("flow", flow);
render_template(&tmpl, "flows/flow_detail.html", &ctx)
} else {
let mut ctx = tera::Context::new();
ctx.insert("active_page", "flows");
ctx.insert("error", "Flow not found");
// For the error page, we'll use a special case to set the status code to 404
match tmpl.render("error.html", &ctx) {
Ok(content) => Ok(HttpResponse::NotFound().content_type("text/html").body(content)),
Err(e) => {
log::error!("Error rendering error template: {}", e);
Err(actix_web::error::ErrorInternalServerError(format!("Error: {}", e)))
}
}
}
}
/// Renders the create flow page
pub async fn create_flow_form(tmpl: web::Data<Tera>, session: Session) -> Result<impl Responder> {
let user = Self::get_user_from_session(&session);
let mut ctx = tera::Context::new();
ctx.insert("active_page", "flows");
ctx.insert("user", &user);
render_template(&tmpl, "flows/create_flow.html", &ctx)
}
/// Handles the create flow form submission
pub async fn create_flow(
_form: web::Form<FlowForm>,
_session: Session
) -> impl Responder {
// In a real application, we would create a new flow here
// For now, just redirect to the flows list
HttpResponse::Found()
.append_header(("Location", "/flows"))
.finish()
}
/// Renders the my flows page
pub async fn my_flows(tmpl: web::Data<Tera>, session: Session) -> Result<impl Responder> {
let user = Self::get_user_from_session(&session);
if let Some(user) = &user {
let flows = Self::get_mock_flows();
let my_flows = flows.iter()
.filter(|f| f.owner_name == user.sub)
.collect::<Vec<_>>();
let mut ctx = tera::Context::new();
ctx.insert("active_page", "flows");
ctx.insert("user", &user);
ctx.insert("flows", &my_flows);
render_template(&tmpl, "flows/my_flows.html", &ctx)
} else {
Ok(HttpResponse::Found()
.append_header(("Location", "/login"))
.finish())
}
}
/// Handles the advance flow step action
pub async fn advance_flow_step(
path: web::Path<String>,
_session: Session
) -> impl Responder {
let flow_id = path.into_inner();
// In a real application, we would advance the flow step here
// For now, just redirect to the flow detail page
HttpResponse::Found()
.append_header(("Location", format!("/flows/{}", flow_id)))
.finish()
}
/// Handles the mark flow step as stuck action
pub async fn mark_flow_step_stuck(
path: web::Path<String>,
_form: web::Form<StuckForm>,
_session: Session
) -> impl Responder {
let flow_id = path.into_inner();
// In a real application, we would mark the flow step as stuck here
// For now, just redirect to the flow detail page
HttpResponse::Found()
.append_header(("Location", format!("/flows/{}", flow_id)))
.finish()
}
/// Handles the add log to flow step action
pub async fn add_log_to_flow_step(
path: web::Path<(String, String)>,
_form: web::Form<LogForm>,
_session: Session
) -> impl Responder {
let (flow_id, _step_id) = path.into_inner();
// In a real application, we would add a log to the flow step here
// For now, just redirect to the flow detail page
HttpResponse::Found()
.append_header(("Location", format!("/flows/{}", flow_id)))
.finish()
}
/// Gets the user from the session
fn get_user_from_session(session: &Session) -> Option<Claims> {
if let Ok(Some(user)) = session.get::<Claims>("user") {
Some(user)
} else {
None
}
}
/// Creates mock flow data for testing
fn get_mock_flows() -> Vec<Flow> {
let mut flows = Vec::new();
// Create a few mock flows
let mut flow1 = Flow {
id: "flow-1".to_string(),
name: "ZAZ Business Entity Registration".to_string(),
description: "Register a new business entity within the Zanzibar Autonomous Zone legal framework".to_string(),
flow_type: FlowType::CompanyRegistration,
status: FlowStatus::InProgress,
owner_id: "user-1".to_string(),
owner_name: "Ibrahim Faraji".to_string(),
steps: vec![
FlowStep {
id: "step-1-1".to_string(),
name: "Document Submission".to_string(),
description: "Submit required business registration documents including business plan, ownership structure, and KYC information".to_string(),
order: 1,
status: StepStatus::Completed,
started_at: Some(Utc::now() - Duration::days(5)),
completed_at: Some(Utc::now() - Duration::days(4)),
logs: vec![
FlowLog {
id: "log-1-1-1".to_string(),
message: "Initial document package submitted".to_string(),
timestamp: Utc::now() - Duration::days(5),
},
FlowLog {
id: "log-1-1-2".to_string(),
message: "Additional ownership verification documents requested".to_string(),
timestamp: Utc::now() - Duration::days(4) - Duration::hours(12),
},
FlowLog {
id: "log-1-1-3".to_string(),
message: "Additional documents submitted and verified".to_string(),
timestamp: Utc::now() - Duration::days(4),
},
],
},
FlowStep {
id: "step-1-2".to_string(),
name: "Regulatory Review".to_string(),
description: "ZAZ Business Registry review of submitted documents and compliance with regulatory requirements".to_string(),
order: 2,
status: StepStatus::InProgress,
started_at: Some(Utc::now() - Duration::days(3)),
completed_at: None,
logs: vec![
FlowLog {
id: "log-1-2-1".to_string(),
message: "Regulatory review initiated by ZAZ Business Registry".to_string(),
timestamp: Utc::now() - Duration::days(3),
},
FlowLog {
id: "log-1-2-2".to_string(),
message: "Preliminary compliance assessment completed".to_string(),
timestamp: Utc::now() - Duration::days(2),
},
FlowLog {
id: "log-1-2-3".to_string(),
message: "Awaiting final approval from regulatory committee".to_string(),
timestamp: Utc::now() - Duration::days(1),
},
],
},
FlowStep {
id: "step-1-3".to_string(),
name: "Digital Identity Creation".to_string(),
description: "Creation of the entity's digital identity and blockchain credentials within the ZAZ ecosystem".to_string(),
order: 3,
status: StepStatus::Pending,
started_at: None,
completed_at: None,
logs: vec![],
},
FlowStep {
id: "step-1-4".to_string(),
name: "License and Certificate Issuance".to_string(),
description: "Issuance of business licenses, certificates, and digital credentials".to_string(),
order: 4,
status: StepStatus::Pending,
started_at: None,
completed_at: None,
logs: vec![],
},
],
created_at: Utc::now() - Duration::days(5),
updated_at: Utc::now() - Duration::days(1),
completed_at: None,
progress_percentage: 40,
current_step: None,
};
// Update the current step
flow1.current_step = flow1.steps.iter().find(|s| s.status == StepStatus::InProgress).cloned();
let mut flow2 = Flow {
id: "flow-2".to_string(),
name: "Digital Asset Tokenization Approval".to_string(),
description: "Process for approving the tokenization of a real estate asset within the ZAZ regulatory framework".to_string(),
flow_type: FlowType::AssetTokenization,
status: FlowStatus::Completed,
owner_id: "user-2".to_string(),
owner_name: "Amina Salim".to_string(),
steps: vec![
FlowStep {
id: "step-2-1".to_string(),
name: "Asset Verification".to_string(),
description: "Verification of the underlying asset ownership and valuation".to_string(),
order: 1,
status: StepStatus::Completed,
started_at: Some(Utc::now() - Duration::days(30)),
completed_at: Some(Utc::now() - Duration::days(25)),
logs: vec![
FlowLog {
id: "log-2-1-1".to_string(),
message: "Asset documentation submitted for verification".to_string(),
timestamp: Utc::now() - Duration::days(30),
},
FlowLog {
id: "log-2-1-2".to_string(),
message: "Independent valuation completed by ZAZ Property Registry".to_string(),
timestamp: Utc::now() - Duration::days(27),
},
FlowLog {
id: "log-2-1-3".to_string(),
message: "Asset ownership and valuation verified".to_string(),
timestamp: Utc::now() - Duration::days(25),
},
],
},
FlowStep {
id: "step-2-2".to_string(),
name: "Tokenization Structure Review".to_string(),
description: "Review of the proposed token structure, distribution model, and compliance with ZAZ tokenization standards".to_string(),
order: 2,
status: StepStatus::Completed,
started_at: Some(Utc::now() - Duration::days(24)),
completed_at: Some(Utc::now() - Duration::days(20)),
logs: vec![
FlowLog {
id: "log-2-2-1".to_string(),
message: "Tokenization proposal submitted for review".to_string(),
timestamp: Utc::now() - Duration::days(24),
},
FlowLog {
id: "log-2-2-2".to_string(),
message: "Technical review completed by ZAZ Digital Assets Committee".to_string(),
timestamp: Utc::now() - Duration::days(22),
},
FlowLog {
id: "log-2-2-3".to_string(),
message: "Tokenization structure approved with minor modifications".to_string(),
timestamp: Utc::now() - Duration::days(20),
},
],
},
FlowStep {
id: "step-2-3".to_string(),
name: "Smart Contract Deployment".to_string(),
description: "Deployment and verification of the asset tokenization smart contracts".to_string(),
order: 3,
status: StepStatus::Completed,
started_at: Some(Utc::now() - Duration::days(19)),
completed_at: Some(Utc::now() - Duration::days(15)),
logs: vec![
FlowLog {
id: "log-2-3-1".to_string(),
message: "Smart contract code submitted for audit".to_string(),
timestamp: Utc::now() - Duration::days(19),
},
FlowLog {
id: "log-2-3-2".to_string(),
message: "Security audit completed with no critical issues".to_string(),
timestamp: Utc::now() - Duration::days(17),
},
FlowLog {
id: "log-2-3-3".to_string(),
message: "Smart contracts deployed to ZAZ-approved blockchain".to_string(),
timestamp: Utc::now() - Duration::days(15),
},
],
},
FlowStep {
id: "step-2-4".to_string(),
name: "Final Approval and Listing".to_string(),
description: "Final regulatory approval and listing on the ZAZ Digital Asset Exchange".to_string(),
order: 4,
status: StepStatus::Completed,
started_at: Some(Utc::now() - Duration::days(14)),
completed_at: Some(Utc::now() - Duration::days(10)),
logs: vec![
FlowLog {
id: "log-2-4-1".to_string(),
message: "Final documentation package submitted for approval".to_string(),
timestamp: Utc::now() - Duration::days(14),
},
FlowLog {
id: "log-2-4-2".to_string(),
message: "Regulatory approval granted by ZAZ Financial Authority".to_string(),
timestamp: Utc::now() - Duration::days(12),
},
FlowLog {
id: "log-2-4-3".to_string(),
message: "Asset tokens listed on ZAZ Digital Asset Exchange".to_string(),
timestamp: Utc::now() - Duration::days(10),
},
],
},
],
created_at: Utc::now() - Duration::days(30),
updated_at: Utc::now() - Duration::days(10),
completed_at: Some(Utc::now() - Duration::days(10)),
progress_percentage: 100,
current_step: None,
};
flow2.current_step = flow2.steps.last().cloned();
let mut flow3 = Flow {
id: "flow-3".to_string(),
name: "Sustainable Tourism Certification".to_string(),
description: "Application process for ZAZ Sustainable Tourism Certification for eco-tourism businesses".to_string(),
flow_type: FlowType::Certification,
status: FlowStatus::Stuck,
owner_id: "user-3".to_string(),
owner_name: "Hassan Mwinyi".to_string(),
steps: vec![
FlowStep {
id: "step-3-1".to_string(),
name: "Initial Application".to_string(),
description: "Submission of initial application and supporting documentation".to_string(),
order: 1,
status: StepStatus::Completed,
started_at: Some(Utc::now() - Duration::days(15)),
completed_at: Some(Utc::now() - Duration::days(12)),
logs: vec![
FlowLog {
id: "log-3-1-1".to_string(),
message: "Application submitted for Coral Reef Eco Tours".to_string(),
timestamp: Utc::now() - Duration::days(15),
},
FlowLog {
id: "log-3-1-2".to_string(),
message: "Application fee payment confirmed".to_string(),
timestamp: Utc::now() - Duration::days(14),
},
FlowLog {
id: "log-3-1-3".to_string(),
message: "Initial documentation review completed".to_string(),
timestamp: Utc::now() - Duration::days(12),
},
],
},
FlowStep {
id: "step-3-2".to_string(),
name: "Environmental Impact Assessment".to_string(),
description: "Assessment of the business's environmental impact and sustainability practices".to_string(),
order: 2,
status: StepStatus::Stuck,
started_at: Some(Utc::now() - Duration::days(11)),
completed_at: None,
logs: vec![
FlowLog {
id: "log-3-2-1".to_string(),
message: "Environmental assessment initiated".to_string(),
timestamp: Utc::now() - Duration::days(11),
},
FlowLog {
id: "log-3-2-2".to_string(),
message: "Site visit scheduled with environmental officer".to_string(),
timestamp: Utc::now() - Duration::days(9),
},
FlowLog {
id: "log-3-2-3".to_string(),
message: "STUCK: Missing required marine conservation plan documentation".to_string(),
timestamp: Utc::now() - Duration::days(7),
},
],
},
FlowStep {
id: "step-3-3".to_string(),
name: "Community Engagement Verification".to_string(),
description: "Verification of community engagement and benefit-sharing mechanisms".to_string(),
order: 3,
status: StepStatus::Pending,
started_at: None,
completed_at: None,
logs: vec![],
},
FlowStep {
id: "step-3-4".to_string(),
name: "Certification Issuance".to_string(),
description: "Final review and issuance of ZAZ Sustainable Tourism Certification".to_string(),
order: 4,
status: StepStatus::Pending,
started_at: None,
completed_at: None,
logs: vec![],
},
],
created_at: Utc::now() - Duration::days(15),
updated_at: Utc::now() - Duration::days(7),
completed_at: None,
progress_percentage: 35,
current_step: None,
};
flow3.current_step = flow3.steps.iter().find(|s| s.status == StepStatus::Stuck).cloned();
let mut flow4 = Flow {
id: "flow-4".to_string(),
name: "Digital Payment Provider License".to_string(),
description: "Application for a license to operate as a digital payment provider within the ZAZ financial system".to_string(),
flow_type: FlowType::LicenseApplication,
status: FlowStatus::InProgress,
owner_id: "user-4".to_string(),
owner_name: "Fatma Busaidy".to_string(),
steps: vec![
FlowStep {
id: "step-4-1".to_string(),
name: "Initial Application".to_string(),
description: "Submission of license application and company information".to_string(),
order: 1,
status: StepStatus::Completed,
started_at: Some(Utc::now() - Duration::days(20)),
completed_at: Some(Utc::now() - Duration::days(18)),
logs: vec![
FlowLog {
id: "log-4-1-1".to_string(),
message: "Application submitted for ZanziPay digital payment services".to_string(),
timestamp: Utc::now() - Duration::days(20),
},
FlowLog {
id: "log-4-1-2".to_string(),
message: "Application fee payment confirmed".to_string(),
timestamp: Utc::now() - Duration::days(19),
},
FlowLog {
id: "log-4-1-3".to_string(),
message: "Initial documentation review completed".to_string(),
timestamp: Utc::now() - Duration::days(18),
},
],
},
FlowStep {
id: "step-4-2".to_string(),
name: "Technical Infrastructure Review".to_string(),
description: "Review of the technical infrastructure, security measures, and compliance with ZAZ financial standards".to_string(),
order: 2,
status: StepStatus::Completed,
started_at: Some(Utc::now() - Duration::days(17)),
completed_at: Some(Utc::now() - Duration::days(10)),
logs: vec![
FlowLog {
id: "log-4-2-1".to_string(),
message: "Technical documentation submitted for review".to_string(),
timestamp: Utc::now() - Duration::days(17),
},
FlowLog {
id: "log-4-2-2".to_string(),
message: "Security audit initiated by ZAZ Financial Technology Office".to_string(),
timestamp: Utc::now() - Duration::days(15),
},
FlowLog {
id: "log-4-2-3".to_string(),
message: "Technical infrastructure approved with recommendations".to_string(),
timestamp: Utc::now() - Duration::days(10),
},
],
},
FlowStep {
id: "step-4-3".to_string(),
name: "AML/KYC Compliance Review".to_string(),
description: "Review of anti-money laundering and know-your-customer procedures".to_string(),
order: 3,
status: StepStatus::InProgress,
started_at: Some(Utc::now() - Duration::days(9)),
completed_at: None,
logs: vec![
FlowLog {
id: "log-4-3-1".to_string(),
message: "AML/KYC documentation submitted for review".to_string(),
timestamp: Utc::now() - Duration::days(9),
},
FlowLog {
id: "log-4-3-2".to_string(),
message: "Initial compliance assessment completed".to_string(),
timestamp: Utc::now() - Duration::days(5),
},
FlowLog {
id: "log-4-3-3".to_string(),
message: "Additional KYC procedure documentation requested".to_string(),
timestamp: Utc::now() - Duration::days(3),
},
],
},
FlowStep {
id: "step-4-4".to_string(),
name: "License Issuance".to_string(),
description: "Final review and issuance of Digital Payment Provider License".to_string(),
order: 4,
status: StepStatus::Pending,
started_at: None,
completed_at: None,
logs: vec![],
},
],
created_at: Utc::now() - Duration::days(20),
updated_at: Utc::now() - Duration::days(3),
completed_at: None,
progress_percentage: 65,
current_step: None,
};
flow4.current_step = flow4.steps.iter().find(|s| s.status == StepStatus::InProgress).cloned();
flows.push(flow1);
flows.push(flow2);
flows.push(flow3);
flows.push(flow4);
flows
}
}
/// Form for creating a new flow
#[derive(Debug, Deserialize)]
pub struct FlowForm {
/// Flow name
pub name: String,
/// Flow description
pub description: String,
/// Flow type
pub flow_type: String,
}
/// Form for marking a step as stuck
#[derive(Debug, Deserialize)]
pub struct StuckForm {
/// Reason for being stuck
pub reason: String,
}
/// Form for adding a log to a step
#[derive(Debug, Deserialize)]
pub struct LogForm {
/// Log message
pub message: String,
}