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, session: Session) -> Result { 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::>()); ctx.insert("stuck_flows", &flows.iter().filter(|f| f.status == FlowStatus::Stuck).collect::>()); render_template(&tmpl, "flows/index.html", &ctx) } /// Renders the flows list page pub async fn list_flows(tmpl: web::Data, session: Session) -> Result { 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, tmpl: web::Data, session: Session ) -> Result { 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, session: Session) -> Result { 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, _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, session: Session) -> Result { 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::>(); 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, _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, _form: web::Form, _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, _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 { if let Ok(Some(user)) = session.get::("user") { Some(user) } else { None } } /// Creates mock flow data for testing fn get_mock_flows() -> Vec { 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, }