use heromodels_core::BaseModelData; use heromodels_derive::model; use serde::{Deserialize, Serialize}; use std::fmt; use std::time::{SystemTime, UNIX_EPOCH}; // --- Helper Functions --- /// Helper function to get current timestamp in seconds fn current_timestamp_secs() -> u64 { SystemTime::now() .duration_since(UNIX_EPOCH) .unwrap_or_default() .as_secs() } // --- Enums --- /// Defines the possible statuses of a contract #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub enum ContractStatus { Draft, PendingSignatures, Signed, Active, Expired, Cancelled, } impl Default for ContractStatus { fn default() -> Self { ContractStatus::Draft } } impl fmt::Display for ContractStatus { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{:?}", self) // Outputs the variant name, e.g., "Draft" } } /// Defines the status of a contract signer #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub enum SignerStatus { Pending, Signed, Rejected, } impl Default for SignerStatus { fn default() -> Self { SignerStatus::Pending } } impl fmt::Display for SignerStatus { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{:?}", self) // Outputs the variant name, e.g., "Pending" } } // --- Structs for nested data --- /// ContractRevision represents a version of the contract content #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)] pub struct ContractRevision { pub version: u32, pub content: String, pub created_at: u64, // Timestamp pub created_by: String, pub comments: Option, } impl ContractRevision { pub fn new(version: u32, content: String, created_at: u64, created_by: String) -> Self { Self { version, content, created_at, created_by, comments: None, } } pub fn comments(mut self, comments: impl ToString) -> Self { self.comments = Some(comments.to_string()); self } } /// ContractSigner represents a party involved in signing a contract #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)] pub struct ContractSigner { pub id: String, // Unique ID for the signer (UUID string) pub name: String, pub email: String, pub status: SignerStatus, pub signed_at: Option, // Timestamp pub comments: Option, pub last_reminder_mail_sent_at: Option, // Unix timestamp of last reminder sent pub signature_data: Option, // Base64 encoded signature image data } impl ContractSigner { pub fn new(id: String, name: String, email: String) -> Self { Self { id, name, email, status: SignerStatus::default(), signed_at: None, comments: None, last_reminder_mail_sent_at: None, signature_data: None, } } pub fn status(mut self, status: SignerStatus) -> Self { self.status = status; self } pub fn signed_at(mut self, signed_at: u64) -> Self { self.signed_at = Some(signed_at); self } pub fn clear_signed_at(mut self) -> Self { self.signed_at = None; self } pub fn comments(mut self, comments: impl ToString) -> Self { self.comments = Some(comments.to_string()); self } pub fn clear_comments(mut self) -> Self { self.comments = None; self } pub fn last_reminder_mail_sent_at(mut self, timestamp: u64) -> Self { self.last_reminder_mail_sent_at = Some(timestamp); self } pub fn clear_last_reminder_mail_sent_at(mut self) -> Self { self.last_reminder_mail_sent_at = None; self } pub fn signature_data(mut self, signature_data: impl ToString) -> Self { self.signature_data = Some(signature_data.to_string()); self } pub fn clear_signature_data(mut self) -> Self { self.signature_data = None; self } /// Helper method to check if a reminder can be sent (30-minute rate limiting) pub fn can_send_reminder(&self, current_timestamp: u64) -> bool { match self.last_reminder_mail_sent_at { None => true, // No reminder sent yet Some(last_sent) => { let thirty_minutes_in_seconds = 30 * 60; // 30 minutes = 1800 seconds current_timestamp >= last_sent + thirty_minutes_in_seconds } } } /// Helper method to get remaining cooldown time in seconds pub fn reminder_cooldown_remaining(&self, current_timestamp: u64) -> Option { match self.last_reminder_mail_sent_at { None => None, // No cooldown if no reminder sent yet Some(last_sent) => { let thirty_minutes_in_seconds = 30 * 60; // 30 minutes = 1800 seconds let cooldown_end = last_sent + thirty_minutes_in_seconds; if current_timestamp < cooldown_end { Some(cooldown_end - current_timestamp) } else { None // Cooldown has expired } } } } /// Helper method to update the reminder timestamp to current time pub fn mark_reminder_sent(&mut self, current_timestamp: u64) { self.last_reminder_mail_sent_at = Some(current_timestamp); } /// Signs the contract with optional signature data and comments pub fn sign(&mut self, signature_data: Option, comments: Option) { self.status = SignerStatus::Signed; self.signed_at = Some(current_timestamp_secs()); self.signature_data = signature_data; if let Some(comment) = comments { self.comments = Some(comment); } } } // --- Main Contract Model --- /// Represents a legal agreement #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)] #[model] pub struct Contract { pub base_data: BaseModelData, // Provides id (u32), created_at (u64), updated_at (u64) #[index] pub contract_id: String, // Unique ID for the contract (UUID string) pub title: String, pub description: String, #[index] pub contract_type: String, #[index] pub status: crate::models::ContractStatus, // Use re-exported path for #[model] macro pub created_by: String, pub terms_and_conditions: String, pub start_date: Option, pub end_date: Option, pub renewal_period_days: Option, pub next_renewal_date: Option, pub signers: Vec, pub revisions: Vec, pub current_version: u32, pub last_signed_date: Option, } impl Contract { pub fn new(_base_id: u32, contract_id: String) -> Self { Self { base_data: BaseModelData::new(), contract_id, title: String::new(), description: String::new(), contract_type: String::new(), status: ContractStatus::default(), created_by: String::new(), terms_and_conditions: String::new(), start_date: None, end_date: None, renewal_period_days: None, next_renewal_date: None, signers: Vec::new(), revisions: Vec::new(), current_version: 0, last_signed_date: None, } } // Builder methods pub fn title(mut self, title: impl ToString) -> Self { self.title = title.to_string(); self } pub fn description(mut self, description: impl ToString) -> Self { self.description = description.to_string(); self } pub fn contract_type(mut self, contract_type: impl ToString) -> Self { self.contract_type = contract_type.to_string(); self } pub fn status(mut self, status: ContractStatus) -> Self { self.status = status; self } pub fn created_by(mut self, created_by: impl ToString) -> Self { self.created_by = created_by.to_string(); self } pub fn terms_and_conditions(mut self, terms: impl ToString) -> Self { self.terms_and_conditions = terms.to_string(); self } pub fn start_date(mut self, start_date: u64) -> Self { self.start_date = Some(start_date); self } pub fn clear_start_date(mut self) -> Self { self.start_date = None; self } pub fn end_date(mut self, end_date: u64) -> Self { self.end_date = Some(end_date); self } pub fn clear_end_date(mut self) -> Self { self.end_date = None; self } pub fn renewal_period_days(mut self, days: i32) -> Self { self.renewal_period_days = Some(days); self } pub fn clear_renewal_period_days(mut self) -> Self { self.renewal_period_days = None; self } pub fn next_renewal_date(mut self, date: u64) -> Self { self.next_renewal_date = Some(date); self } pub fn clear_next_renewal_date(mut self) -> Self { self.next_renewal_date = None; self } pub fn add_signer(mut self, signer: ContractSigner) -> Self { self.signers.push(signer); self } pub fn signers(mut self, signers: Vec) -> Self { self.signers = signers; self } pub fn add_revision(mut self, revision: ContractRevision) -> Self { self.revisions.push(revision); self } pub fn revisions(mut self, revisions: Vec) -> Self { self.revisions = revisions; self } pub fn current_version(mut self, version: u32) -> Self { self.current_version = version; self } pub fn last_signed_date(mut self, date: u64) -> Self { self.last_signed_date = Some(date); self } pub fn clear_last_signed_date(mut self) -> Self { self.last_signed_date = None; self } // Example methods for state changes pub fn set_status(&mut self, status: crate::models::ContractStatus) { self.status = status; // self.base_data.touch(); // Assume #[model] handles timestamp updates } }