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.
This commit is contained in:
@@ -2,6 +2,17 @@ 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 ---
|
||||
|
||||
@@ -87,6 +98,7 @@ pub struct ContractSigner {
|
||||
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 {
|
||||
@@ -99,6 +111,7 @@ impl ContractSigner {
|
||||
signed_at: None,
|
||||
comments: None,
|
||||
last_reminder_mail_sent_at: None,
|
||||
signature_data: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -137,6 +150,16 @@ impl ContractSigner {
|
||||
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 {
|
||||
@@ -168,6 +191,16 @@ impl ContractSigner {
|
||||
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 ---
|
||||
|
@@ -211,6 +211,18 @@ pub fn register_legal_rhai_module(engine: &mut Engine, db: Arc<OurDB>) {
|
||||
|signer: ContractSigner| -> ContractSigner { signer.clear_last_reminder_mail_sent_at() },
|
||||
);
|
||||
|
||||
// Signature data functionality
|
||||
engine.register_fn(
|
||||
"signature_data",
|
||||
|signer: ContractSigner, signature_data: String| -> ContractSigner {
|
||||
signer.signature_data(signature_data)
|
||||
},
|
||||
);
|
||||
engine.register_fn(
|
||||
"clear_signature_data",
|
||||
|signer: ContractSigner| -> ContractSigner { signer.clear_signature_data() },
|
||||
);
|
||||
|
||||
// Helper methods for reminder logic
|
||||
engine.register_fn(
|
||||
"can_send_reminder",
|
||||
@@ -263,6 +275,23 @@ pub fn register_legal_rhai_module(engine: &mut Engine, db: Arc<OurDB>) {
|
||||
},
|
||||
);
|
||||
|
||||
// Sign methods
|
||||
engine.register_fn(
|
||||
"sign",
|
||||
|signer: &mut ContractSigner, signature_data: String, comments: String| {
|
||||
signer.sign(Some(signature_data), Some(comments));
|
||||
},
|
||||
);
|
||||
engine.register_fn(
|
||||
"sign_without_signature",
|
||||
|signer: &mut ContractSigner, comments: String| {
|
||||
signer.sign(None, Some(comments));
|
||||
},
|
||||
);
|
||||
engine.register_fn("sign_simple", |signer: &mut ContractSigner| {
|
||||
signer.sign(None, None);
|
||||
});
|
||||
|
||||
engine.register_get(
|
||||
"id",
|
||||
|signer: &mut ContractSigner| -> Result<String, Box<EvalAltResult>> {
|
||||
@@ -317,6 +346,15 @@ pub fn register_legal_rhai_module(engine: &mut Engine, db: Arc<OurDB>) {
|
||||
.map_or(Dynamic::UNIT, |ts| Dynamic::from(ts as i64)))
|
||||
},
|
||||
);
|
||||
engine.register_get(
|
||||
"signature_data",
|
||||
|signer: &mut ContractSigner| -> Result<Dynamic, Box<EvalAltResult>> {
|
||||
Ok(signer
|
||||
.signature_data
|
||||
.as_ref()
|
||||
.map_or(Dynamic::UNIT, |data| Dynamic::from(data.clone())))
|
||||
},
|
||||
);
|
||||
|
||||
// --- Contract ---
|
||||
engine.register_type_with_name::<Contract>("Contract");
|
||||
|
Reference in New Issue
Block a user