use heromodels::{ db::{Collection, Db}, models::legal::{Contract, ContractRevision, ContractSigner, ContractStatus, SignerStatus}, }; use super::db::get_db; /// Creates a new contract and saves it to the database. Returns the saved contract and its ID. pub fn create_new_contract( base_id: u32, contract_id: &str, title: &str, description: &str, contract_type: &str, status: ContractStatus, created_by: &str, terms_and_conditions: Option<&str>, start_date: Option, end_date: Option, renewal_period_days: Option, ) -> Result<(u32, Contract), String> { let db = get_db().expect("Can get DB"); // Create a new contract using the heromodels Contract::new constructor let mut contract = Contract::new(base_id, contract_id.to_string()) .title(title) .description(description) .contract_type(contract_type.to_string()) .status(status) .created_by(created_by.to_string()); if let Some(terms) = terms_and_conditions { contract = contract.terms_and_conditions(terms); } if let Some(start) = start_date { contract = contract.start_date(start); } if let Some(end) = end_date { contract = contract.end_date(end); } if let Some(renewal) = renewal_period_days { contract = contract.renewal_period_days(renewal as i32); } // Save the contract to the database let collection = db .collection::() .expect("can open contract collection"); let (contract_id, saved_contract) = collection.set(&contract).expect("can save contract"); Ok((contract_id, saved_contract)) } /// Loads all contracts from the database and returns them as a Vec. pub fn get_contracts() -> Result, String> { let db = get_db().map_err(|e| format!("DB error: {}", e))?; let collection = db .collection::() .expect("can open contract collection"); // Try to load all contracts, but handle deserialization errors gracefully let contracts = match collection.get_all() { Ok(contracts) => contracts, Err(e) => { log::error!("Failed to load contracts from database: {:?}", e); vec![] // Return empty vector if there's an error } }; Ok(contracts) } /// Fetches a single contract by its ID from the database. pub fn get_contract_by_id(contract_id: u32) -> Result, String> { let db = get_db().map_err(|e| format!("DB error: {}", e))?; let collection = db .collection::() .map_err(|e| format!("Collection error: {:?}", e))?; match collection.get_by_id(contract_id) { Ok(contract) => Ok(contract), Err(e) => { log::error!("Error fetching contract by id {}: {:?}", contract_id, e); Err(format!("Failed to fetch contract: {:?}", e)) } } } /// Updates a contract's basic information in the database and returns the updated contract. pub fn update_contract( contract_id: u32, title: &str, description: &str, contract_type: &str, terms_and_conditions: Option<&str>, start_date: Option, end_date: Option, ) -> Result { let db = get_db().map_err(|e| format!("DB error: {}", e))?; let collection = db .collection::() .map_err(|e| format!("Collection error: {:?}", e))?; if let Some(mut contract) = collection .get_by_id(contract_id) .map_err(|e| format!("Failed to fetch contract: {:?}", e))? { // Update the contract fields contract = contract .title(title) .description(description) .contract_type(contract_type.to_string()); if let Some(terms) = terms_and_conditions { contract = contract.terms_and_conditions(terms); } if let Some(start) = start_date { contract = contract.start_date(start); } if let Some(end) = end_date { contract = contract.end_date(end); } let (_, updated_contract) = collection .set(&contract) .map_err(|e| format!("Failed to update contract: {:?}", e))?; Ok(updated_contract) } else { Err("Contract not found".to_string()) } } /// Updates a contract's status in the database and returns the updated contract. pub fn update_contract_status( contract_id: u32, status: ContractStatus, ) -> Result { let db = get_db().map_err(|e| format!("DB error: {}", e))?; let collection = db .collection::() .map_err(|e| format!("Collection error: {:?}", e))?; if let Some(mut contract) = collection .get_by_id(contract_id) .map_err(|e| format!("Failed to fetch contract: {:?}", e))? { contract = contract.status(status); let (_, updated_contract) = collection .set(&contract) .map_err(|e| format!("Failed to update contract: {:?}", e))?; Ok(updated_contract) } else { Err("Contract not found".to_string()) } } /// Adds a signer to a contract and returns the updated contract. pub fn add_signer_to_contract( contract_id: u32, signer_id: &str, name: &str, email: &str, ) -> Result { let db = get_db().map_err(|e| format!("DB error: {}", e))?; let collection = db .collection::() .map_err(|e| format!("Collection error: {:?}", e))?; if let Some(mut contract) = collection .get_by_id(contract_id) .map_err(|e| format!("Failed to fetch contract: {:?}", e))? { let signer = ContractSigner::new(signer_id.to_string(), name.to_string(), email.to_string()); contract = contract.add_signer(signer); let (_, updated_contract) = collection .set(&contract) .map_err(|e| format!("Failed to update contract: {:?}", e))?; Ok(updated_contract) } else { Err("Contract not found".to_string()) } } /// Adds a revision to a contract and returns the updated contract. pub fn add_revision_to_contract( contract_id: u32, version: u32, content: &str, created_by: &str, comments: Option<&str>, ) -> Result { let db = get_db().map_err(|e| format!("DB error: {}", e))?; let collection = db .collection::() .map_err(|e| format!("Collection error: {:?}", e))?; if let Some(mut contract) = collection .get_by_id(contract_id) .map_err(|e| format!("Failed to fetch contract: {:?}", e))? { let revision = ContractRevision::new( version, content.to_string(), current_timestamp_secs(), created_by.to_string(), ) .comments(comments.unwrap_or("").to_string()); contract = contract.add_revision(revision); let (_, updated_contract) = collection .set(&contract) .map_err(|e| format!("Failed to update contract: {:?}", e))?; Ok(updated_contract) } else { Err("Contract not found".to_string()) } } /// Updates a signer's status for a contract and returns the updated contract. pub fn update_signer_status( contract_id: u32, signer_id: &str, status: SignerStatus, comments: Option<&str>, ) -> Result { update_signer_status_with_signature(contract_id, signer_id, status, comments, None) } /// Updates a signer's status with signature data for a contract and returns the updated contract. pub fn update_signer_status_with_signature( contract_id: u32, signer_id: &str, status: SignerStatus, comments: Option<&str>, signature_data: Option<&str>, ) -> Result { let db = get_db().map_err(|e| format!("DB error: {}", e))?; let collection = db .collection::() .map_err(|e| format!("Collection error: {:?}", e))?; if let Some(mut contract) = collection .get_by_id(contract_id) .map_err(|e| format!("Failed to fetch contract: {:?}", e))? { // Find and update the signer let mut signer_found = false; for signer in &mut contract.signers { if signer.id == signer_id { signer.status = status.clone(); if status == SignerStatus::Signed { signer.signed_at = Some(current_timestamp_secs()); } if let Some(comment) = comments { signer.comments = Some(comment.to_string()); } if let Some(sig_data) = signature_data { signer.signature_data = Some(sig_data.to_string()); } signer_found = true; break; } } if !signer_found { return Err(format!("Signer with ID {} not found", signer_id)); } let (_, updated_contract) = collection .set(&contract) .map_err(|e| format!("Failed to update contract: {:?}", e))?; Ok(updated_contract) } else { Err("Contract not found".to_string()) } } /// Deletes a contract from the database. pub fn delete_contract(contract_id: u32) -> Result<(), String> { let db = get_db().map_err(|e| format!("DB error: {}", e))?; let collection = db .collection::() .map_err(|e| format!("Collection error: {:?}", e))?; collection .delete_by_id(contract_id) .map_err(|e| format!("Failed to delete contract: {:?}", e))?; Ok(()) } /// Gets contracts by status pub fn get_contracts_by_status(status: ContractStatus) -> Result, String> { let contracts = get_contracts()?; let filtered_contracts = contracts .into_iter() .filter(|contract| contract.status == status) .collect(); Ok(filtered_contracts) } /// Gets contracts by creator pub fn get_contracts_by_creator(created_by: &str) -> Result, String> { let contracts = get_contracts()?; let filtered_contracts = contracts .into_iter() .filter(|contract| contract.created_by == created_by) .collect(); Ok(filtered_contracts) } /// Gets contracts that need renewal (approaching end date) pub fn get_contracts_needing_renewal(days_ahead: u64) -> Result, String> { let contracts = get_contracts()?; let threshold_timestamp = current_timestamp_secs() + (days_ahead * 24 * 60 * 60); let filtered_contracts = contracts .into_iter() .filter(|contract| { if let Some(end_date) = contract.end_date { end_date <= threshold_timestamp && contract.status == ContractStatus::Active } else { false } }) .collect(); Ok(filtered_contracts) } /// Gets expired contracts pub fn get_expired_contracts() -> Result, String> { let contracts = get_contracts()?; let current_time = current_timestamp_secs(); let filtered_contracts = contracts .into_iter() .filter(|contract| { if let Some(end_date) = contract.end_date { end_date < current_time && contract.status != ContractStatus::Expired } else { false } }) .collect(); Ok(filtered_contracts) } /// Updates multiple contracts to expired status pub fn mark_contracts_as_expired(contract_ids: Vec) -> Result, String> { let mut updated_contracts = Vec::new(); for contract_id in contract_ids { match update_contract_status(contract_id, ContractStatus::Expired) { Ok(contract) => updated_contracts.push(contract), Err(e) => log::error!("Failed to update contract {}: {}", contract_id, e), } } Ok(updated_contracts) } /// Updates a signer's reminder timestamp for a contract and returns the updated contract. pub fn update_signer_reminder_timestamp( contract_id: u32, signer_id: &str, _timestamp: u64, ) -> Result { let db = get_db().map_err(|e| format!("DB error: {}", e))?; let collection = db .collection::() .expect("can open contract collection"); if let Some(mut contract) = collection .get_by_id(contract_id) .map_err(|e| format!("Failed to fetch contract: {:?}", e))? { let mut signer_found = false; for signer in &mut contract.signers { if signer.id == signer_id { // TODO: Update reminder timestamp when field is available in heromodels // signer.last_reminder_mail_sent_at = Some(timestamp); signer_found = true; break; } } if !signer_found { return Err(format!("Signer with ID {} not found", signer_id)); } let (_, updated_contract) = collection .set(&contract) .map_err(|e| format!("Failed to update contract: {:?}", e))?; Ok(updated_contract) } else { Err("Contract not found".to_string()) } } /// Gets contract statistics pub fn get_contract_statistics() -> Result { let contracts = get_contracts()?; let total = contracts.len(); let draft = contracts .iter() .filter(|c| c.status == ContractStatus::Draft) .count(); let pending = contracts .iter() .filter(|c| c.status == ContractStatus::PendingSignatures) .count(); let signed = contracts .iter() .filter(|c| c.status == ContractStatus::Signed) .count(); let active = contracts .iter() .filter(|c| c.status == ContractStatus::Active) .count(); let expired = contracts .iter() .filter(|c| c.status == ContractStatus::Expired) .count(); let cancelled = contracts .iter() .filter(|c| c.status == ContractStatus::Cancelled) .count(); Ok(ContractStatistics { total_contracts: total, draft_contracts: draft, pending_signature_contracts: pending, signed_contracts: signed, active_contracts: active, expired_contracts: expired, cancelled_contracts: cancelled, }) } /// A helper for current timestamp (seconds since epoch) fn current_timestamp_secs() -> u64 { std::time::SystemTime::now() .duration_since(std::time::UNIX_EPOCH) .unwrap_or_default() .as_secs() } /// Contract statistics structure #[derive(Debug, Clone)] pub struct ContractStatistics { pub total_contracts: usize, pub draft_contracts: usize, pub pending_signature_contracts: usize, pub signed_contracts: usize, pub active_contracts: usize, pub expired_contracts: usize, pub cancelled_contracts: usize, }