db/heromodels/examples/legal_contract_example.rs
Mahmoud-Emad f0a0dd6d73 feat: Add signature functionality to ContractSigner
- Add `signature_data` field to `ContractSigner` to store base64
  encoded signature image data.  Allows for storing visual
  signatures alongside electronic ones.
- Implement `sign` method for `ContractSigner` to handle signing
  with optional signature data and comments. Improves the
  flexibility and expressiveness of the signing process.
- Add Rhai functions for signature management, including signing
  with/without data and clearing signature data.  Extends the
  Rhai scripting capabilities for contract management.
- Add comprehensive unit tests to cover the new signature
  functionality. Ensures correctness and robustness of the
  implementation.
- Update examples to demonstrate the new signature functionality.
  Provides clear usage examples for developers.
2025-06-12 14:30:58 +03:00

225 lines
9.3 KiB
Rust

use heromodels::models::legal::{
Contract, ContractRevision, ContractSigner, ContractStatus, SignerStatus,
};
// If BaseModelData's touch method or new method isn't directly part of the public API
// of heromodels crate root, we might need to import heromodels_core.
// For now, assuming `contract.base_data.touch()` would work if BaseModelData has a public `touch` method.
// Or, if `heromodels::models::BaseModelData` is the path.
// 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()
}
fn main() {
println!("Demonstrating Legal Contract Model Usage");
// Create contract signers
let signer1 = ContractSigner::new(
"signer-uuid-alice-001".to_string(),
"Alice Wonderland".to_string(),
"alice@example.com".to_string(),
)
.status(SignerStatus::Pending)
.comments("Awaiting Alice's review and signature.");
let signer2 = ContractSigner::new(
"signer-uuid-bob-002".to_string(),
"Bob The Builder".to_string(),
"bob@example.com".to_string(),
)
.status(SignerStatus::Signed)
.signed_at(current_timestamp_secs() - 86400) // Signed yesterday
.comments("Bob has signed the agreement.");
// Create contract revisions
let revision1 = ContractRevision::new(
1,
"Initial draft: This Service Agreement outlines the terms...".to_string(),
current_timestamp_secs() - (86400 * 2), // 2 days ago
"user-uuid-creator-charlie".to_string(),
)
.comments("Version 1.0 - Initial draft for review.");
let revision2 = ContractRevision::new(
2,
"Updated draft: Added clause 5.b regarding data privacy...".to_string(),
current_timestamp_secs() - 86400, // 1 day ago
"user-uuid-editor-diana".to_string(),
)
.comments("Version 2.0 - Incorporated feedback from legal team.");
// Create a new contract
// base_id (u32 for BaseModelData) and contract_id (String for UUID)
let mut contract = Contract::new(101, "contract-uuid-main-789".to_string())
.title("Master Service Agreement (MSA) - Q3 2025")
.description("Agreement for ongoing IT support services between TechSolutions Inc. and ClientCorp LLC.")
.contract_type("MSA".to_string())
.status(ContractStatus::PendingSignatures)
.created_by("user-uuid-admin-eve".to_string())
.terms_and_conditions("The full terms are detailed in the attached PDF, referenced as 'MSA_Q3_2025_Full.pdf'. This string can hold markdown or JSON summary.")
.start_date(current_timestamp_secs() + (86400 * 7)) // Starts in 7 days
.end_date(current_timestamp_secs() + (86400 * (365 + 7))) // Ends in 1 year + 7 days
.renewal_period_days(30)
.next_renewal_date(current_timestamp_secs() + (86400 * (365 + 7 - 30))) // Approx. 30 days before end_date
.current_version(2)
.add_signer(signer1.clone())
.add_signer(signer2.clone())
.add_revision(revision1.clone())
.add_revision(revision2.clone());
// The `#[model]` derive handles `created_at` and `updated_at` in `base_data`.
// `base_data.touch()` might be called internally by setters or needs explicit call if fields are set directly.
// For builder pattern, the final state of `base_data.updated_at` reflects the time of the last builder call if `touch()` is implicit.
// If not, one might call `contract.base_data.touch()` after building.
println!("\n--- Initial Contract Details ---");
println!("{:#?}", contract);
// Simulate a status change and signing
contract.set_status(ContractStatus::Signed); // This should call base_data.touch()
contract = contract.last_signed_date(current_timestamp_secs());
// If set_status doesn't touch, and last_signed_date is not a builder method that touches:
// contract.base_data.touch(); // Manually update timestamp if needed after direct field manipulation
println!("\n--- Contract Details After Signing ---");
println!("{:#?}", contract);
println!("\n--- Accessing Specific Fields ---");
println!("Contract Title: {}", contract.title);
println!("Contract Status: {:?}", contract.status);
println!("Contract ID (UUID): {}", contract.contract_id);
println!("Base Model ID (u32): {}", contract.base_data.id); // From BaseModelData
println!("Created At (timestamp): {}", contract.base_data.created_at); // From BaseModelData
println!("Updated At (timestamp): {}", contract.base_data.modified_at); // From BaseModelData
if let Some(first_signer_details) = contract.signers.first() {
println!(
"\nFirst Signer: {} ({})",
first_signer_details.name, first_signer_details.email
);
println!(" Status: {:?}", first_signer_details.status);
if let Some(signed_time) = first_signer_details.signed_at {
println!(" Signed At: {}", signed_time);
}
}
if let Some(latest_rev) = contract.revisions.iter().max_by_key(|r| r.version) {
println!("\nLatest Revision (v{}):", latest_rev.version);
println!(" Content Snippet: {:.60}...", latest_rev.content);
println!(" Created By: {}", latest_rev.created_by);
println!(" Revision Created At: {}", latest_rev.created_at);
}
// Demonstrate reminder functionality
println!("\n--- Reminder Functionality Demo ---");
let current_time = current_timestamp_secs();
// Check if we can send reminders to signers
for (i, signer) in contract.signers.iter().enumerate() {
println!("\nSigner {}: {} ({})", i + 1, signer.name, signer.email);
println!(" Status: {:?}", signer.status);
if signer.last_reminder_mail_sent_at.is_none() {
println!(" Last reminder: Never sent");
} else {
println!(
" Last reminder: {}",
signer.last_reminder_mail_sent_at.unwrap()
);
}
let can_send = signer.can_send_reminder(current_time);
println!(" Can send reminder now: {}", can_send);
if let Some(remaining) = signer.reminder_cooldown_remaining(current_time) {
println!(" Cooldown remaining: {} seconds", remaining);
} else {
println!(" No cooldown active");
}
}
// Simulate sending a reminder to the first signer
if let Some(first_signer) = contract.signers.get_mut(0) {
if first_signer.can_send_reminder(current_time) {
println!("\nSimulating reminder sent to: {}", first_signer.name);
first_signer.mark_reminder_sent(current_time);
println!(
" Reminder timestamp updated to: {}",
first_signer.last_reminder_mail_sent_at.unwrap()
);
// Check cooldown after sending
if let Some(remaining) = first_signer.reminder_cooldown_remaining(current_time) {
println!(" New cooldown: {} seconds (30 minutes)", remaining);
}
}
}
// Demonstrate signature functionality
println!("\n--- Signature Functionality Demo ---");
// Simulate signing with signature data
if let Some(signer_to_sign) = contract.signers.get_mut(1) {
println!("\nBefore signing:");
println!(
" Signer: {} ({})",
signer_to_sign.name, signer_to_sign.email
);
println!(" Status: {:?}", signer_to_sign.status);
println!(" Signed at: {:?}", signer_to_sign.signed_at);
println!(" Signature data: {:?}", signer_to_sign.signature_data);
// Example base64 signature data (1x1 transparent PNG)
let signature_data = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==".to_string();
// Sign the contract with signature data
signer_to_sign.sign(
Some(signature_data.clone()),
Some("I agree to all terms and conditions.".to_string()),
);
println!("\nAfter signing:");
println!(" Status: {:?}", signer_to_sign.status);
println!(" Signed at: {:?}", signer_to_sign.signed_at);
println!(" Comments: {:?}", signer_to_sign.comments);
println!(
" Signature data length: {} characters",
signer_to_sign
.signature_data
.as_ref()
.map_or(0, |s| s.len())
);
println!(
" Signature data preview: {}...",
signer_to_sign
.signature_data
.as_ref()
.map_or("None".to_string(), |s| s
.chars()
.take(50)
.collect::<String>())
);
}
// Demonstrate signing without signature data
if let Some(first_signer) = contract.signers.get_mut(0) {
println!("\nSigning without signature data:");
println!(" Signer: {}", first_signer.name);
first_signer.sign(
None,
Some("Signed electronically without visual signature.".to_string()),
);
println!(" Status after signing: {:?}", first_signer.status);
println!(" Signature data: {:?}", first_signer.signature_data);
println!(" Comments: {:?}", first_signer.comments);
}
println!("\nLegal Contract Model demonstration complete.");
}