use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; use uuid::Uuid; /// Contract status enum #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub enum ContractStatus { Draft, PendingSignatures, Signed, Active, Expired, Cancelled } impl ContractStatus { pub fn as_str(&self) -> &str { match self { ContractStatus::Draft => "Draft", ContractStatus::PendingSignatures => "Pending Signatures", ContractStatus::Signed => "Signed", ContractStatus::Active => "Active", ContractStatus::Expired => "Expired", ContractStatus::Cancelled => "Cancelled", } } } /// Contract type enum #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub enum ContractType { Service, Employment, NDA, SLA, Partnership, Distribution, License, Membership, Other } impl ContractType { pub fn as_str(&self) -> &str { match self { ContractType::Service => "Service Agreement", ContractType::Employment => "Employment Contract", ContractType::NDA => "Non-Disclosure Agreement", ContractType::SLA => "Service Level Agreement", ContractType::Partnership => "Partnership Agreement", ContractType::Distribution => "Distribution Agreement", ContractType::License => "License Agreement", ContractType::Membership => "Membership Agreement", ContractType::Other => "Other", } } } /// Contract signer status #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub enum SignerStatus { Pending, Signed, Rejected } impl SignerStatus { pub fn as_str(&self) -> &str { match self { SignerStatus::Pending => "Pending", SignerStatus::Signed => "Signed", SignerStatus::Rejected => "Rejected", } } } /// Contract signer #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ContractSigner { pub id: String, pub name: String, pub email: String, pub status: SignerStatus, pub signed_at: Option>, pub comments: Option, } impl ContractSigner { /// Creates a new contract signer pub fn new(name: String, email: String) -> Self { Self { id: Uuid::new_v4().to_string(), name, email, status: SignerStatus::Pending, signed_at: None, comments: None, } } /// Signs the contract pub fn sign(&mut self, comments: Option) { self.status = SignerStatus::Signed; self.signed_at = Some(Utc::now()); self.comments = comments; } /// Rejects the contract pub fn reject(&mut self, comments: Option) { self.status = SignerStatus::Rejected; self.signed_at = Some(Utc::now()); self.comments = comments; } } /// Contract revision #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ContractRevision { pub version: u32, pub content: String, pub created_at: DateTime, pub created_by: String, pub comments: Option, } impl ContractRevision { /// Creates a new contract revision pub fn new(version: u32, content: String, created_by: String, comments: Option) -> Self { Self { version, content, created_at: Utc::now(), created_by, comments, } } } /// Table of Contents item for multi-page contracts #[derive(Debug, Clone, Serialize, Deserialize)] pub struct TocItem { pub title: String, pub file: String, pub children: Vec, } /// Contract model #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Contract { pub id: String, pub title: String, pub description: String, pub contract_type: ContractType, pub status: ContractStatus, pub created_at: DateTime, pub updated_at: DateTime, pub created_by: String, pub effective_date: Option>, pub expiration_date: Option>, pub signers: Vec, pub revisions: Vec, pub current_version: u32, pub organization_id: Option, // Multi-page markdown support pub content_dir: Option, pub toc: Option>, } impl Contract { /// Creates a new contract pub fn new(title: String, description: String, contract_type: ContractType, created_by: String, organization_id: Option) -> Self { Self { id: Uuid::new_v4().to_string(), title, description, contract_type, status: ContractStatus::Draft, created_at: Utc::now(), updated_at: Utc::now(), created_by, effective_date: None, expiration_date: None, signers: Vec::new(), revisions: Vec::new(), current_version: 1, organization_id, content_dir: None, toc: None, } } /// Adds a signer to the contract pub fn add_signer(&mut self, name: String, email: String) { let signer = ContractSigner::new(name, email); self.signers.push(signer); self.updated_at = Utc::now(); } /// Adds a revision to the contract pub fn add_revision(&mut self, content: String, created_by: String, comments: Option) { let new_version = self.current_version + 1; let revision = ContractRevision::new(new_version, content, created_by, comments); self.revisions.push(revision); self.current_version = new_version; self.updated_at = Utc::now(); } /// Sends the contract for signatures pub fn send_for_signatures(&mut self) -> Result<(), String> { if self.revisions.is_empty() { return Err("Cannot send contract without content".to_string()); } if self.signers.is_empty() { return Err("Cannot send contract without signers".to_string()); } self.status = ContractStatus::PendingSignatures; self.updated_at = Utc::now(); Ok(()) } /// Checks if all signers have signed pub fn is_fully_signed(&self) -> bool { if self.signers.is_empty() { return false; } self.signers.iter().all(|signer| signer.status == SignerStatus::Signed) } /// Marks the contract as signed if all signers have signed pub fn finalize_if_signed(&mut self) -> bool { if self.is_fully_signed() { self.status = ContractStatus::Signed; self.updated_at = Utc::now(); true } else { false } } /// Cancels the contract pub fn cancel(&mut self) { self.status = ContractStatus::Cancelled; self.updated_at = Utc::now(); } /// Gets the latest revision pub fn latest_revision(&self) -> Option<&ContractRevision> { self.revisions.last() } /// Gets a specific revision pub fn get_revision(&self, version: u32) -> Option<&ContractRevision> { self.revisions.iter().find(|r| r.version == version) } /// Gets the number of pending signers pub fn pending_signers_count(&self) -> usize { self.signers.iter().filter(|s| s.status == SignerStatus::Pending).count() } /// Gets the number of signed signers pub fn signed_signers_count(&self) -> usize { self.signers.iter().filter(|s| s.status == SignerStatus::Signed).count() } /// Gets the number of rejected signers pub fn rejected_signers_count(&self) -> usize { self.signers.iter().filter(|s| s.status == SignerStatus::Rejected).count() } } /// Contract filter for listing contracts #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ContractFilter { pub status: Option, pub contract_type: Option, pub created_by: Option, pub organization_id: Option, } /// Contract statistics #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ContractStatistics { pub total_contracts: usize, pub draft_contracts: usize, pub pending_signature_contracts: usize, pub signed_contracts: usize, pub expired_contracts: usize, pub cancelled_contracts: usize, } impl ContractStatistics { /// Creates new contract statistics from a list of contracts pub fn new(contracts: &[Contract]) -> Self { let total_contracts = contracts.len(); let draft_contracts = contracts.iter().filter(|c| c.status == ContractStatus::Draft).count(); let pending_signature_contracts = contracts.iter().filter(|c| c.status == ContractStatus::PendingSignatures).count(); let signed_contracts = contracts.iter().filter(|c| c.status == ContractStatus::Signed).count(); let expired_contracts = contracts.iter().filter(|c| c.status == ContractStatus::Expired).count(); let cancelled_contracts = contracts.iter().filter(|c| c.status == ContractStatus::Cancelled).count(); Self { total_contracts, draft_contracts, pending_signature_contracts, signed_contracts, expired_contracts, cancelled_contracts, } } }