implement contracts
This commit is contained in:
344
actix_mvc_app/src/controllers/contract.rs
Normal file
344
actix_mvc_app/src/controllers/contract.rs
Normal file
@@ -0,0 +1,344 @@
|
||||
use actix_web::{web, HttpResponse, Responder, Result};
|
||||
use tera::{Context, Tera};
|
||||
use chrono::{Utc, Duration};
|
||||
use uuid::Uuid;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::models::contract::{Contract, ContractStatus, ContractType, ContractStatistics, ContractSigner, ContractRevision, SignerStatus, ContractFilter};
|
||||
use crate::controllers::auth::Claims;
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct ContractForm {
|
||||
pub title: String,
|
||||
pub description: String,
|
||||
pub contract_type: String,
|
||||
pub content: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct SignerForm {
|
||||
pub name: String,
|
||||
pub email: String,
|
||||
}
|
||||
|
||||
pub struct ContractController;
|
||||
|
||||
impl ContractController {
|
||||
// Display the contracts dashboard
|
||||
pub async fn index(tmpl: web::Data<Tera>) -> Result<HttpResponse> {
|
||||
let mut context = Context::new();
|
||||
|
||||
let contracts = Self::get_mock_contracts();
|
||||
let stats = ContractStatistics::new(&contracts);
|
||||
|
||||
// Add active_page for navigation highlighting
|
||||
context.insert("active_page", &"contracts");
|
||||
|
||||
// Add stats
|
||||
context.insert("stats", &serde_json::to_value(stats).unwrap());
|
||||
|
||||
// Add recent contracts
|
||||
let recent_contracts: Vec<serde_json::Map<String, serde_json::Value>> = contracts
|
||||
.iter()
|
||||
.take(5)
|
||||
.map(|c| Self::contract_to_json(c))
|
||||
.collect();
|
||||
|
||||
context.insert("recent_contracts", &recent_contracts);
|
||||
|
||||
// Add pending signature contracts
|
||||
let pending_signature_contracts: Vec<serde_json::Map<String, serde_json::Value>> = contracts
|
||||
.iter()
|
||||
.filter(|c| c.status == ContractStatus::PendingSignatures)
|
||||
.map(|c| Self::contract_to_json(c))
|
||||
.collect();
|
||||
|
||||
context.insert("pending_signature_contracts", &pending_signature_contracts);
|
||||
|
||||
// Add draft contracts
|
||||
let draft_contracts: Vec<serde_json::Map<String, serde_json::Value>> = contracts
|
||||
.iter()
|
||||
.filter(|c| c.status == ContractStatus::Draft)
|
||||
.map(|c| Self::contract_to_json(c))
|
||||
.collect();
|
||||
|
||||
context.insert("draft_contracts", &draft_contracts);
|
||||
|
||||
Ok(HttpResponse::Ok().content_type("text/html").body(
|
||||
tmpl.render("contracts/index.html", &context).unwrap()
|
||||
))
|
||||
}
|
||||
|
||||
// Display the list of all contracts
|
||||
pub async fn list(tmpl: web::Data<Tera>) -> Result<HttpResponse> {
|
||||
let mut context = Context::new();
|
||||
|
||||
let contracts = Self::get_mock_contracts();
|
||||
let contracts_data: Vec<serde_json::Map<String, serde_json::Value>> = contracts
|
||||
.iter()
|
||||
.map(|c| Self::contract_to_json(c))
|
||||
.collect();
|
||||
|
||||
// Add active_page for navigation highlighting
|
||||
context.insert("active_page", &"contracts");
|
||||
|
||||
context.insert("contracts", &contracts_data);
|
||||
context.insert("filter", &"all");
|
||||
|
||||
Ok(HttpResponse::Ok().content_type("text/html").body(
|
||||
tmpl.render("contracts/contracts.html", &context).unwrap()
|
||||
))
|
||||
}
|
||||
|
||||
// Display the list of user's contracts
|
||||
pub async fn my_contracts(tmpl: web::Data<Tera>) -> Result<HttpResponse> {
|
||||
let mut context = Context::new();
|
||||
|
||||
let contracts = Self::get_mock_contracts();
|
||||
let contracts_data: Vec<serde_json::Map<String, serde_json::Value>> = contracts
|
||||
.iter()
|
||||
.map(|c| Self::contract_to_json(c))
|
||||
.collect();
|
||||
|
||||
// Add active_page for navigation highlighting
|
||||
context.insert("active_page", &"contracts");
|
||||
|
||||
context.insert("contracts", &contracts_data);
|
||||
|
||||
Ok(HttpResponse::Ok().content_type("text/html").body(
|
||||
tmpl.render("contracts/my_contracts.html", &context).unwrap()
|
||||
))
|
||||
}
|
||||
|
||||
// Display a specific contract
|
||||
pub async fn detail(tmpl: web::Data<Tera>, path: web::Path<String>) -> Result<HttpResponse> {
|
||||
let contract_id = path.into_inner();
|
||||
let mut context = Context::new();
|
||||
|
||||
// Add active_page for navigation highlighting
|
||||
context.insert("active_page", &"contracts");
|
||||
|
||||
// Find the contract by ID
|
||||
let contracts = Self::get_mock_contracts();
|
||||
let contract = contracts.iter().find(|c| c.id == contract_id);
|
||||
|
||||
match contract {
|
||||
Some(contract) => {
|
||||
// Convert contract to JSON
|
||||
let contract_json = Self::contract_to_json(contract);
|
||||
|
||||
context.insert("contract", &contract_json);
|
||||
context.insert("user_has_signed", &false); // Mock data
|
||||
|
||||
Ok(HttpResponse::Ok().content_type("text/html").body(
|
||||
tmpl.render("contracts/contract_detail.html", &context).unwrap()
|
||||
))
|
||||
},
|
||||
None => {
|
||||
Ok(HttpResponse::NotFound().finish())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Display the create contract form
|
||||
pub async fn create_form(tmpl: web::Data<Tera>) -> Result<HttpResponse> {
|
||||
let mut context = Context::new();
|
||||
|
||||
// Add active_page for navigation highlighting
|
||||
context.insert("active_page", &"contracts");
|
||||
|
||||
// Add contract types for dropdown
|
||||
let contract_types = vec![
|
||||
("Service", "Service Agreement"),
|
||||
("Employment", "Employment Contract"),
|
||||
("NDA", "Non-Disclosure Agreement"),
|
||||
("SLA", "Service Level Agreement"),
|
||||
("Other", "Other")
|
||||
];
|
||||
|
||||
context.insert("contract_types", &contract_types);
|
||||
|
||||
Ok(HttpResponse::Ok().content_type("text/html").body(
|
||||
tmpl.render("contracts/create_contract.html", &context).unwrap()
|
||||
))
|
||||
}
|
||||
|
||||
// Process the create contract form
|
||||
pub async fn create(
|
||||
tmpl: web::Data<Tera>,
|
||||
form: web::Form<ContractForm>,
|
||||
) -> Result<HttpResponse> {
|
||||
// In a real application, we would save the contract to the database
|
||||
// For now, we'll just redirect to the contracts list
|
||||
|
||||
Ok(HttpResponse::Found().header("Location", "/contracts").finish())
|
||||
}
|
||||
|
||||
// Helper method to convert Contract to a JSON object for templates
|
||||
fn contract_to_json(contract: &Contract) -> serde_json::Map<String, serde_json::Value> {
|
||||
let mut map = serde_json::Map::new();
|
||||
|
||||
map.insert("id".to_string(), serde_json::Value::String(contract.id.clone()));
|
||||
map.insert("title".to_string(), serde_json::Value::String(contract.title.clone()));
|
||||
map.insert("description".to_string(), serde_json::Value::String(contract.description.clone()));
|
||||
map.insert("status".to_string(), serde_json::Value::String(format!("{:?}", contract.status)));
|
||||
map.insert("contract_type".to_string(), serde_json::Value::String(format!("{:?}", contract.contract_type)));
|
||||
map.insert("created_at".to_string(), serde_json::Value::String(contract.created_at.format("%Y-%m-%d").to_string()));
|
||||
map.insert("updated_at".to_string(), serde_json::Value::String(contract.updated_at.format("%Y-%m-%d").to_string()));
|
||||
map.insert("created_by".to_string(), serde_json::Value::String("John Doe".to_string())); // Mock data
|
||||
|
||||
// Add signers
|
||||
let signers: Vec<serde_json::Value> = contract.signers.iter()
|
||||
.map(|s| {
|
||||
let mut signer_map = serde_json::Map::new();
|
||||
signer_map.insert("id".to_string(), serde_json::Value::String(s.id.clone()));
|
||||
signer_map.insert("name".to_string(), serde_json::Value::String(s.name.clone()));
|
||||
signer_map.insert("email".to_string(), serde_json::Value::String(s.email.clone()));
|
||||
signer_map.insert("status".to_string(), serde_json::Value::String(format!("{:?}", s.status)));
|
||||
|
||||
if let Some(signed_at) = s.signed_at {
|
||||
signer_map.insert("signed_at".to_string(), serde_json::Value::String(signed_at.format("%Y-%m-%d").to_string()));
|
||||
}
|
||||
|
||||
serde_json::Value::Object(signer_map)
|
||||
})
|
||||
.collect();
|
||||
|
||||
map.insert("signers".to_string(), serde_json::Value::Array(signers));
|
||||
|
||||
// Add signed_signers count for templates
|
||||
let signed_signers = contract.signers.iter().filter(|s| s.status == SignerStatus::Signed).count();
|
||||
map.insert("signed_signers".to_string(), serde_json::Value::Number(serde_json::Number::from(signed_signers)));
|
||||
|
||||
// Add pending_signers count for templates
|
||||
let pending_signers = contract.signers.iter().filter(|s| s.status == SignerStatus::Pending).count();
|
||||
map.insert("pending_signers".to_string(), serde_json::Value::Number(serde_json::Number::from(pending_signers)));
|
||||
|
||||
// Add revisions
|
||||
let revisions: Vec<serde_json::Value> = contract.revisions.iter()
|
||||
.map(|r| {
|
||||
let mut revision_map = serde_json::Map::new();
|
||||
revision_map.insert("version".to_string(), serde_json::Value::Number(serde_json::Number::from(r.version)));
|
||||
revision_map.insert("content".to_string(), serde_json::Value::String(r.content.clone()));
|
||||
revision_map.insert("created_at".to_string(), serde_json::Value::String(r.created_at.format("%Y-%m-%d").to_string()));
|
||||
revision_map.insert("created_by".to_string(), serde_json::Value::String(r.created_by.clone()));
|
||||
|
||||
if let Some(comments) = &r.comments {
|
||||
revision_map.insert("comments".to_string(), serde_json::Value::String(comments.clone()));
|
||||
// Add notes field using comments since ContractRevision doesn't have a notes field
|
||||
revision_map.insert("notes".to_string(), serde_json::Value::String(comments.clone()));
|
||||
}
|
||||
|
||||
serde_json::Value::Object(revision_map)
|
||||
})
|
||||
.collect();
|
||||
|
||||
map.insert("revisions".to_string(), serde_json::Value::Array(revisions));
|
||||
|
||||
// Add effective and expiration dates if present
|
||||
if let Some(effective_date) = &contract.effective_date {
|
||||
map.insert("effective_date".to_string(), serde_json::Value::String(effective_date.format("%Y-%m-%d").to_string()));
|
||||
}
|
||||
|
||||
if let Some(expiration_date) = &contract.expiration_date {
|
||||
map.insert("expiration_date".to_string(), serde_json::Value::String(expiration_date.format("%Y-%m-%d").to_string()));
|
||||
}
|
||||
|
||||
map
|
||||
}
|
||||
|
||||
// Generate mock contracts for testing
|
||||
fn get_mock_contracts() -> Vec<Contract> {
|
||||
let now = Utc::now();
|
||||
|
||||
let mut contracts = Vec::new();
|
||||
|
||||
// Contract 1 - Draft
|
||||
let mut contract1 = Contract::new(
|
||||
"Employment Agreement - Marketing Manager".to_string(),
|
||||
"Standard employment contract for the Marketing Manager position".to_string(),
|
||||
ContractType::Employment,
|
||||
"John Doe".to_string(),
|
||||
Some("Acme Corp".to_string())
|
||||
);
|
||||
|
||||
contract1.effective_date = Some(now + Duration::days(30));
|
||||
contract1.expiration_date = Some(now + Duration::days(395)); // ~1 year
|
||||
|
||||
contract1.add_revision(
|
||||
"<p>This is a draft employment contract for the Marketing Manager position.</p>".to_string(),
|
||||
"John Doe".to_string(),
|
||||
Some("Initial draft".to_string())
|
||||
);
|
||||
|
||||
// Contract 2 - Pending Signatures
|
||||
let mut contract2 = Contract::new(
|
||||
"Non-Disclosure Agreement - Vendor XYZ".to_string(),
|
||||
"NDA for our partnership with Vendor XYZ".to_string(),
|
||||
ContractType::NDA,
|
||||
"John Doe".to_string(),
|
||||
Some("Acme Corp".to_string())
|
||||
);
|
||||
|
||||
contract2.effective_date = Some(now);
|
||||
contract2.expiration_date = Some(now + Duration::days(730)); // 2 years
|
||||
|
||||
contract2.add_revision(
|
||||
"<p>This is the first version of the NDA with Vendor XYZ.</p>".to_string(),
|
||||
"John Doe".to_string(),
|
||||
Some("Initial draft".to_string())
|
||||
);
|
||||
|
||||
contract2.add_revision(
|
||||
"<p>This is the revised version of the NDA with Vendor XYZ.</p>".to_string(),
|
||||
"John Doe".to_string(),
|
||||
Some("Added confidentiality period".to_string())
|
||||
);
|
||||
|
||||
contract2.add_signer("Jane Smith".to_string(), "jane@example.com".to_string());
|
||||
contract2.add_signer("Bob Johnson".to_string(), "bob@vendorxyz.com".to_string());
|
||||
|
||||
// Mark Jane as signed
|
||||
if let Some(signer) = contract2.signers.iter_mut().next() {
|
||||
signer.sign(None);
|
||||
}
|
||||
|
||||
// Send for signatures
|
||||
let _ = contract2.send_for_signatures();
|
||||
|
||||
// Contract 3 - Signed
|
||||
let mut contract3 = Contract::new(
|
||||
"Service Agreement - Website Maintenance".to_string(),
|
||||
"Agreement for ongoing website maintenance services".to_string(),
|
||||
ContractType::Service,
|
||||
"John Doe".to_string(),
|
||||
Some("Acme Corp".to_string())
|
||||
);
|
||||
|
||||
contract3.effective_date = Some(now - Duration::days(7));
|
||||
contract3.expiration_date = Some(now + Duration::days(358)); // ~1 year from effective date
|
||||
|
||||
contract3.add_revision(
|
||||
"<p>This is a service agreement for website maintenance.</p>".to_string(),
|
||||
"John Doe".to_string(),
|
||||
Some("Initial version".to_string())
|
||||
);
|
||||
|
||||
contract3.add_signer("Jane Smith".to_string(), "jane@example.com".to_string());
|
||||
contract3.add_signer("Alice Brown".to_string(), "alice@webmaintenance.com".to_string());
|
||||
|
||||
// Mark both signers as signed
|
||||
for signer in contract3.signers.iter_mut() {
|
||||
signer.sign(None);
|
||||
}
|
||||
|
||||
// Mark as signed
|
||||
contract3.status = ContractStatus::Signed;
|
||||
|
||||
contracts.push(contract1);
|
||||
contracts.push(contract2);
|
||||
contracts.push(contract3);
|
||||
|
||||
contracts
|
||||
}
|
||||
}
|
@@ -5,6 +5,7 @@ pub mod ticket;
|
||||
pub mod calendar;
|
||||
pub mod governance;
|
||||
pub mod flow;
|
||||
pub mod contract;
|
||||
|
||||
// Re-export controllers for easier imports
|
||||
pub use home::HomeController;
|
||||
@@ -12,4 +13,5 @@ pub use auth::AuthController;
|
||||
pub use ticket::TicketController;
|
||||
pub use calendar::CalendarController;
|
||||
pub use governance::GovernanceController;
|
||||
pub use flow::FlowController;
|
||||
pub use flow::FlowController;
|
||||
pub use contract::ContractController;
|
Reference in New Issue
Block a user