374 lines
10 KiB
Rust
374 lines
10 KiB
Rust
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<String>,
|
|
}
|
|
|
|
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<u64>, // Timestamp
|
|
pub comments: Option<String>,
|
|
pub last_reminder_mail_sent_at: Option<u64>, // Unix timestamp of last reminder sent
|
|
pub signature_data: Option<String>, // 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<u64> {
|
|
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<String>, comments: Option<String>) {
|
|
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<u64>,
|
|
pub end_date: Option<u64>,
|
|
pub renewal_period_days: Option<i32>,
|
|
pub next_renewal_date: Option<u64>,
|
|
|
|
pub signers: Vec<ContractSigner>,
|
|
pub revisions: Vec<ContractRevision>,
|
|
pub current_version: u32,
|
|
pub last_signed_date: Option<u64>,
|
|
}
|
|
|
|
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<ContractSigner>) -> 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<ContractRevision>) -> 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
|
|
}
|
|
}
|