...
This commit is contained in:
parent
8595bb3950
commit
2221f3f88c
2161
herodb/src/cmd/dbexample_governance/Cargo.lock
generated
Normal file
2161
herodb/src/cmd/dbexample_governance/Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
12
herodb/src/cmd/dbexample_governance/Cargo.toml
Normal file
12
herodb/src/cmd/dbexample_governance/Cargo.toml
Normal file
@ -0,0 +1,12 @@
|
||||
[package]
|
||||
name = "dbexample_governance"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[[bin]]
|
||||
name = "dbexample_governance"
|
||||
path = "main.rs"
|
||||
|
||||
[dependencies]
|
||||
herodb = { path = "../../.." }
|
||||
chrono = "0.4"
|
362
herodb/src/cmd/dbexample_governance/main.rs
Normal file
362
herodb/src/cmd/dbexample_governance/main.rs
Normal file
@ -0,0 +1,362 @@
|
||||
use chrono::{Utc, Duration};
|
||||
use herodb::db::DBBuilder;
|
||||
use herodb::models::governance::{
|
||||
Company, CompanyStatus, BusinessType,
|
||||
Shareholder, ShareholderType,
|
||||
Meeting, Attendee, MeetingStatus, AttendeeRole, AttendeeStatus,
|
||||
User,
|
||||
Vote, VoteOption, Ballot, VoteStatus,
|
||||
Resolution, ResolutionStatus, Approval
|
||||
};
|
||||
use std::path::PathBuf;
|
||||
use std::fs;
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
println!("DB Example: Governance Module");
|
||||
println!("============================");
|
||||
|
||||
// Create a temporary directory for the database
|
||||
let db_path = PathBuf::from("/tmp/dbexample_governance");
|
||||
if db_path.exists() {
|
||||
fs::remove_dir_all(&db_path)?;
|
||||
}
|
||||
fs::create_dir_all(&db_path)?;
|
||||
println!("Database path: {:?}", db_path);
|
||||
|
||||
// Create a database instance with our governance models registered
|
||||
let db = DBBuilder::new(&db_path)
|
||||
.register_model::<Company>()
|
||||
.register_model::<Shareholder>()
|
||||
.register_model::<Meeting>()
|
||||
.register_model::<User>()
|
||||
.register_model::<Vote>()
|
||||
.register_model::<Resolution>()
|
||||
.build()?;
|
||||
|
||||
println!("\n1. Creating a Company");
|
||||
println!("-------------------");
|
||||
|
||||
// Create a company
|
||||
let company = Company::new(
|
||||
1,
|
||||
"Acme Corporation".to_string(),
|
||||
"ACM123456".to_string(),
|
||||
Utc::now(),
|
||||
"December 31".to_string(),
|
||||
"info@acmecorp.com".to_string(),
|
||||
"+1-555-123-4567".to_string(),
|
||||
"https://acmecorp.com".to_string(),
|
||||
"123 Main St, Anytown, USA".to_string(),
|
||||
BusinessType::Coop,
|
||||
"Technology".to_string(),
|
||||
"A leading technology company".to_string(),
|
||||
CompanyStatus::Active,
|
||||
);
|
||||
|
||||
// Insert the company
|
||||
db.insert(&company)?;
|
||||
println!("Company created: {} (ID: {})", company.name, company.id);
|
||||
println!("Status: {:?}, Business Type: {:?}", company.status, company.business_type);
|
||||
|
||||
println!("\n2. Creating Users");
|
||||
println!("---------------");
|
||||
|
||||
// Create users
|
||||
let user1 = User::new(
|
||||
1,
|
||||
"John Doe".to_string(),
|
||||
"john.doe@acmecorp.com".to_string(),
|
||||
"password123".to_string(), // In a real app, this would be hashed
|
||||
"Acme Corporation".to_string(),
|
||||
"CEO".to_string(),
|
||||
);
|
||||
|
||||
let user2 = User::new(
|
||||
2,
|
||||
"Jane Smith".to_string(),
|
||||
"jane.smith@acmecorp.com".to_string(),
|
||||
"password456".to_string(), // In a real app, this would be hashed
|
||||
"Acme Corporation".to_string(),
|
||||
"CFO".to_string(),
|
||||
);
|
||||
|
||||
let user3 = User::new(
|
||||
3,
|
||||
"Bob Johnson".to_string(),
|
||||
"bob.johnson@acmecorp.com".to_string(),
|
||||
"password789".to_string(), // In a real app, this would be hashed
|
||||
"Acme Corporation".to_string(),
|
||||
"CTO".to_string(),
|
||||
);
|
||||
|
||||
// Insert the users
|
||||
db.insert(&user1)?;
|
||||
db.insert(&user2)?;
|
||||
db.insert(&user3)?;
|
||||
|
||||
println!("User created: {} ({})", user1.name, user1.role);
|
||||
println!("User created: {} ({})", user2.name, user2.role);
|
||||
println!("User created: {} ({})", user3.name, user3.role);
|
||||
|
||||
println!("\n3. Creating Shareholders");
|
||||
println!("----------------------");
|
||||
|
||||
// Create shareholders
|
||||
let mut shareholder1 = Shareholder::new(
|
||||
1,
|
||||
company.id,
|
||||
user1.id,
|
||||
user1.name.clone(),
|
||||
1000.0,
|
||||
40.0,
|
||||
ShareholderType::Individual,
|
||||
);
|
||||
|
||||
let mut shareholder2 = Shareholder::new(
|
||||
2,
|
||||
company.id,
|
||||
user2.id,
|
||||
user2.name.clone(),
|
||||
750.0,
|
||||
30.0,
|
||||
ShareholderType::Individual,
|
||||
);
|
||||
|
||||
let mut shareholder3 = Shareholder::new(
|
||||
3,
|
||||
company.id,
|
||||
user3.id,
|
||||
user3.name.clone(),
|
||||
750.0,
|
||||
30.0,
|
||||
ShareholderType::Individual,
|
||||
);
|
||||
|
||||
// Insert the shareholders
|
||||
db.insert(&shareholder1)?;
|
||||
db.insert(&shareholder2)?;
|
||||
db.insert(&shareholder3)?;
|
||||
|
||||
println!("Shareholder created: {} ({} shares, {}%)",
|
||||
shareholder1.name, shareholder1.shares, shareholder1.percentage);
|
||||
println!("Shareholder created: {} ({} shares, {}%)",
|
||||
shareholder2.name, shareholder2.shares, shareholder2.percentage);
|
||||
println!("Shareholder created: {} ({} shares, {}%)",
|
||||
shareholder3.name, shareholder3.shares, shareholder3.percentage);
|
||||
|
||||
// Update shareholder shares
|
||||
shareholder1.update_shares(1100.0, 44.0);
|
||||
db.insert(&shareholder1)?;
|
||||
println!("Updated shareholder: {} ({} shares, {}%)",
|
||||
shareholder1.name, shareholder1.shares, shareholder1.percentage);
|
||||
|
||||
println!("\n4. Creating a Meeting");
|
||||
println!("------------------");
|
||||
|
||||
// Create a meeting
|
||||
let mut meeting = Meeting::new(
|
||||
1,
|
||||
company.id,
|
||||
"Q2 Board Meeting".to_string(),
|
||||
Utc::now() + Duration::days(7), // Meeting in 7 days
|
||||
"Conference Room A".to_string(),
|
||||
"Quarterly board meeting to discuss financial results".to_string(),
|
||||
);
|
||||
|
||||
// Create attendees
|
||||
let attendee1 = Attendee::new(
|
||||
1,
|
||||
meeting.id,
|
||||
user1.id,
|
||||
user1.name.clone(),
|
||||
AttendeeRole::Coordinator,
|
||||
);
|
||||
|
||||
let attendee2 = Attendee::new(
|
||||
2,
|
||||
meeting.id,
|
||||
user2.id,
|
||||
user2.name.clone(),
|
||||
AttendeeRole::Member,
|
||||
);
|
||||
|
||||
let attendee3 = Attendee::new(
|
||||
3,
|
||||
meeting.id,
|
||||
user3.id,
|
||||
user3.name.clone(),
|
||||
AttendeeRole::Member,
|
||||
);
|
||||
|
||||
// Add attendees to the meeting
|
||||
meeting.add_attendee(attendee1);
|
||||
meeting.add_attendee(attendee2);
|
||||
meeting.add_attendee(attendee3);
|
||||
|
||||
// Insert the meeting
|
||||
db.insert(&meeting)?;
|
||||
println!("Meeting created: {} ({})", meeting.title, meeting.date.format("%Y-%m-%d %H:%M"));
|
||||
println!("Status: {:?}, Attendees: {}", meeting.status, meeting.attendees.len());
|
||||
|
||||
// Update attendee status
|
||||
if let Some(attendee) = meeting.find_attendee_by_user_id_mut(user2.id) {
|
||||
attendee.update_status(AttendeeStatus::Confirmed);
|
||||
}
|
||||
if let Some(attendee) = meeting.find_attendee_by_user_id_mut(user3.id) {
|
||||
attendee.update_status(AttendeeStatus::Confirmed);
|
||||
}
|
||||
db.insert(&meeting)?;
|
||||
|
||||
// Get confirmed attendees
|
||||
let confirmed = meeting.confirmed_attendees();
|
||||
println!("Confirmed attendees: {}", confirmed.len());
|
||||
for attendee in confirmed {
|
||||
println!(" - {} ({})", attendee.name, match attendee.role {
|
||||
AttendeeRole::Coordinator => "Coordinator",
|
||||
AttendeeRole::Member => "Member",
|
||||
AttendeeRole::Secretary => "Secretary",
|
||||
AttendeeRole::Participant => "Participant",
|
||||
AttendeeRole::Advisor => "Advisor",
|
||||
AttendeeRole::Admin => "Admin",
|
||||
});
|
||||
}
|
||||
|
||||
println!("\n5. Creating a Resolution");
|
||||
println!("----------------------");
|
||||
|
||||
// Create a resolution
|
||||
let mut resolution = Resolution::new(
|
||||
1,
|
||||
company.id,
|
||||
"Approval of Q1 Financial Statements".to_string(),
|
||||
"Resolution to approve the Q1 financial statements".to_string(),
|
||||
"The Board of Directors hereby approves the financial statements for Q1 2025.".to_string(),
|
||||
user1.id, // Proposed by the CEO
|
||||
);
|
||||
|
||||
// Link the resolution to the meeting
|
||||
resolution.link_to_meeting(meeting.id);
|
||||
|
||||
// Insert the resolution
|
||||
db.insert(&resolution)?;
|
||||
println!("Resolution created: {} (Status: {:?})", resolution.title, resolution.status);
|
||||
|
||||
// Propose the resolution
|
||||
resolution.propose();
|
||||
db.insert(&resolution)?;
|
||||
println!("Resolution proposed on {}", resolution.proposed_at.format("%Y-%m-%d"));
|
||||
|
||||
// Add approvals
|
||||
resolution.add_approval(user1.id, user1.name.clone(), true, "Approved as proposed".to_string());
|
||||
resolution.add_approval(user2.id, user2.name.clone(), true, "Financials look good".to_string());
|
||||
resolution.add_approval(user3.id, user3.name.clone(), true, "No concerns".to_string());
|
||||
db.insert(&resolution)?;
|
||||
|
||||
// Check approval status
|
||||
println!("Approvals: {}, Rejections: {}",
|
||||
resolution.approval_count(),
|
||||
resolution.rejection_count());
|
||||
|
||||
// Approve the resolution
|
||||
resolution.approve();
|
||||
db.insert(&resolution)?;
|
||||
println!("Resolution approved on {}",
|
||||
resolution.approved_at.unwrap().format("%Y-%m-%d"));
|
||||
|
||||
println!("\n6. Creating a Vote");
|
||||
println!("----------------");
|
||||
|
||||
// Create a vote
|
||||
let mut vote = Vote::new(
|
||||
1,
|
||||
company.id,
|
||||
"Vote on New Product Line".to_string(),
|
||||
"Vote to approve investment in new product line".to_string(),
|
||||
Utc::now(),
|
||||
Utc::now() + Duration::days(3), // Voting period of 3 days
|
||||
VoteStatus::Open,
|
||||
);
|
||||
|
||||
// Add voting options
|
||||
vote.add_option("Approve".to_string(), 0);
|
||||
vote.add_option("Reject".to_string(), 0);
|
||||
vote.add_option("Abstain".to_string(), 0);
|
||||
|
||||
// Insert the vote
|
||||
db.insert(&vote)?;
|
||||
println!("Vote created: {} (Status: {:?})", vote.title, vote.status);
|
||||
println!("Voting period: {} to {}",
|
||||
vote.start_date.format("%Y-%m-%d"),
|
||||
vote.end_date.format("%Y-%m-%d"));
|
||||
|
||||
// Cast ballots
|
||||
vote.add_ballot(user1.id, 1, 1000); // User 1 votes "Approve" with 1000 shares
|
||||
vote.add_ballot(user2.id, 1, 750); // User 2 votes "Approve" with 750 shares
|
||||
vote.add_ballot(user3.id, 3, 750); // User 3 votes "Abstain" with 750 shares
|
||||
db.insert(&vote)?;
|
||||
|
||||
// Check voting results
|
||||
println!("Voting results:");
|
||||
for option in &vote.options {
|
||||
println!(" - {}: {} votes", option.text, option.count);
|
||||
}
|
||||
|
||||
// Create a resolution for this vote
|
||||
let mut vote_resolution = Resolution::new(
|
||||
2,
|
||||
company.id,
|
||||
"Investment in New Product Line".to_string(),
|
||||
"Resolution to approve investment in new product line".to_string(),
|
||||
"The Board of Directors hereby approves an investment of $1,000,000 in the new product line.".to_string(),
|
||||
user1.id, // Proposed by the CEO
|
||||
);
|
||||
|
||||
// Link the resolution to the vote
|
||||
vote_resolution.link_to_vote(vote.id);
|
||||
vote_resolution.propose();
|
||||
db.insert(&vote_resolution)?;
|
||||
println!("Created resolution linked to vote: {}", vote_resolution.title);
|
||||
|
||||
println!("\n7. Retrieving Related Objects");
|
||||
println!("---------------------------");
|
||||
|
||||
// Retrieve company and related objects
|
||||
let retrieved_company = db.get::<Company>(&company.id.to_string())?;
|
||||
println!("Company: {} (ID: {})", retrieved_company.name, retrieved_company.id);
|
||||
|
||||
// Get resolutions for this company
|
||||
let company_resolutions = retrieved_company.get_resolutions(&db)?;
|
||||
println!("Company has {} resolutions:", company_resolutions.len());
|
||||
for res in company_resolutions {
|
||||
println!(" - {} (Status: {:?})", res.title, res.status);
|
||||
}
|
||||
|
||||
// Get meeting and its resolutions
|
||||
let retrieved_meeting = db.get::<Meeting>(&meeting.id.to_string())?;
|
||||
println!("Meeting: {} ({})", retrieved_meeting.title, retrieved_meeting.date.format("%Y-%m-%d"));
|
||||
|
||||
let meeting_resolutions = retrieved_meeting.get_resolutions(&db)?;
|
||||
println!("Meeting has {} resolutions:", meeting_resolutions.len());
|
||||
for res in meeting_resolutions {
|
||||
println!(" - {} (Status: {:?})", res.title, res.status);
|
||||
}
|
||||
|
||||
// Get vote and its resolution
|
||||
let retrieved_vote = db.get::<Vote>(&vote.id.to_string())?;
|
||||
println!("Vote: {} (Status: {:?})", retrieved_vote.title, retrieved_vote.status);
|
||||
|
||||
if let Ok(Some(vote_res)) = retrieved_vote.get_resolution(&db) {
|
||||
println!("Vote is linked to resolution: {}", vote_res.title);
|
||||
}
|
||||
|
||||
// Get resolution and its related objects
|
||||
let retrieved_resolution = db.get::<Resolution>(&resolution.id.to_string())?;
|
||||
println!("Resolution: {} (Status: {:?})", retrieved_resolution.title, retrieved_resolution.status);
|
||||
|
||||
if let Ok(Some(res_meeting)) = retrieved_resolution.get_meeting(&db) {
|
||||
println!("Resolution is discussed in meeting: {}", res_meeting.title);
|
||||
}
|
||||
|
||||
println!("\nExample completed successfully!");
|
||||
Ok(())
|
||||
}
|
496
herodb/src/models/governance/GOVERNANCE_ENHANCEMENT_PLAN.md
Normal file
496
herodb/src/models/governance/GOVERNANCE_ENHANCEMENT_PLAN.md
Normal file
@ -0,0 +1,496 @@
|
||||
# Governance Module Enhancement Plan (Revised)
|
||||
|
||||
## 1. Current State Analysis
|
||||
|
||||
The governance module currently consists of:
|
||||
- **Company**: Company model with basic company information
|
||||
- **Shareholder**: Shareholder model for managing company ownership
|
||||
- **Meeting**: Meeting and Attendee models for board meetings
|
||||
- **User**: User model for system users
|
||||
- **Vote**: Vote, VoteOption, and Ballot models for voting
|
||||
|
||||
All models implement the `Storable` and `SledModel` traits for database integration, but the module has several limitations:
|
||||
- Not imported in src/models/mod.rs, making it inaccessible to the rest of the project
|
||||
- No mod.rs file to organize and re-export the types
|
||||
- No README.md file to document the purpose and usage
|
||||
- Inconsistent imports across files (e.g., crate::db vs crate::core)
|
||||
- Limited utility methods and relationships between models
|
||||
- No integration with other modules like biz, mcc, or circle
|
||||
|
||||
## 2. Planned Enhancements
|
||||
|
||||
### 2.1 Module Organization and Integration
|
||||
|
||||
- Create a mod.rs file to organize and re-export the types
|
||||
- Add the governance module to src/models/mod.rs
|
||||
- Create a README.md file to document the purpose and usage
|
||||
- Standardize imports across all files
|
||||
|
||||
### 2.2 New Models
|
||||
|
||||
#### 2.2.1 Resolution Model
|
||||
|
||||
Create a new `resolution.rs` file with a Resolution model for managing board resolutions:
|
||||
- Resolution information (title, description, text)
|
||||
- Resolution status (Draft, Proposed, Approved, Rejected)
|
||||
- Voting results and approvals
|
||||
- Integration with Meeting and Vote models
|
||||
|
||||
### 2.3 Enhanced Relationships and Integration
|
||||
|
||||
#### 2.3.1 Integration with Biz Module
|
||||
|
||||
- Link Company with biz::Customer and biz::Contract
|
||||
- Link Shareholder with biz::Customer
|
||||
- Link Meeting with biz::Invoice for expense tracking
|
||||
|
||||
#### 2.3.2 Integration with MCC Module
|
||||
|
||||
- Link Meeting with mcc::Calendar and mcc::Event
|
||||
- Link User with mcc::Contact
|
||||
- Link Vote with mcc::Message for notifications
|
||||
|
||||
#### 2.3.3 Integration with Circle Module
|
||||
|
||||
- Link Company with circle::Circle for group-based access control
|
||||
- Link User with circle::Member for role-based permissions
|
||||
|
||||
### 2.4 Utility Methods and Functionality
|
||||
|
||||
- Add filtering and searching methods to all models
|
||||
- Add relationship management methods between models
|
||||
- Add validation and business logic methods
|
||||
|
||||
## 3. Implementation Plan
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A[Review Current Models] --> B[Create mod.rs and Update models/mod.rs]
|
||||
B --> C[Standardize Imports and Fix Inconsistencies]
|
||||
C --> D[Create Resolution Model]
|
||||
D --> E[Implement Integration with Other Modules]
|
||||
E --> F[Add Utility Methods]
|
||||
F --> G[Create README.md and Documentation]
|
||||
G --> H[Write Tests]
|
||||
```
|
||||
|
||||
### 3.1 Detailed Changes
|
||||
|
||||
#### 3.1.1 Module Organization
|
||||
|
||||
Create a new `mod.rs` file in the governance directory:
|
||||
|
||||
```rust
|
||||
pub mod company;
|
||||
pub mod shareholder;
|
||||
pub mod meeting;
|
||||
pub mod user;
|
||||
pub mod vote;
|
||||
pub mod resolution;
|
||||
|
||||
// Re-export all model types for convenience
|
||||
pub use company::{Company, CompanyStatus, BusinessType};
|
||||
pub use shareholder::{Shareholder, ShareholderType};
|
||||
pub use meeting::{Meeting, Attendee, MeetingStatus, AttendeeRole, AttendeeStatus};
|
||||
pub use user::User;
|
||||
pub use vote::{Vote, VoteOption, Ballot, VoteStatus};
|
||||
pub use resolution::{Resolution, ResolutionStatus, Approval};
|
||||
|
||||
// Re-export database components from db module
|
||||
pub use crate::db::{SledDB, SledDBError, SledDBResult, Storable, SledModel, DB};
|
||||
```
|
||||
|
||||
Update `src/models/mod.rs` to include the governance module:
|
||||
|
||||
```rust
|
||||
pub mod biz;
|
||||
pub mod mcc;
|
||||
pub mod circle;
|
||||
pub mod governance;
|
||||
```
|
||||
|
||||
#### 3.1.2 Resolution Model (`resolution.rs`)
|
||||
|
||||
```rust
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use crate::db::{SledModel, Storable, SledDB, SledDBError};
|
||||
use crate::models::governance::{Meeting, Vote};
|
||||
|
||||
/// ResolutionStatus represents the status of a resolution
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum ResolutionStatus {
|
||||
Draft,
|
||||
Proposed,
|
||||
Approved,
|
||||
Rejected,
|
||||
Withdrawn,
|
||||
}
|
||||
|
||||
/// Resolution represents a board resolution
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct Resolution {
|
||||
pub id: u32,
|
||||
pub company_id: u32,
|
||||
pub meeting_id: Option<u32>,
|
||||
pub vote_id: Option<u32>,
|
||||
pub title: String,
|
||||
pub description: String,
|
||||
pub text: String,
|
||||
pub status: ResolutionStatus,
|
||||
pub proposed_by: u32, // User ID
|
||||
pub proposed_at: DateTime<Utc>,
|
||||
pub approved_at: Option<DateTime<Utc>>,
|
||||
pub rejected_at: Option<DateTime<Utc>>,
|
||||
pub created_at: DateTime<Utc>,
|
||||
pub updated_at: DateTime<Utc>,
|
||||
pub approvals: Vec<Approval>,
|
||||
}
|
||||
|
||||
/// Approval represents an approval of a resolution by a board member
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct Approval {
|
||||
pub id: u32,
|
||||
pub resolution_id: u32,
|
||||
pub user_id: u32,
|
||||
pub name: String,
|
||||
pub approved: bool,
|
||||
pub comments: String,
|
||||
pub created_at: DateTime<Utc>,
|
||||
}
|
||||
|
||||
impl Resolution {
|
||||
/// Create a new resolution with default values
|
||||
pub fn new(
|
||||
id: u32,
|
||||
company_id: u32,
|
||||
title: String,
|
||||
description: String,
|
||||
text: String,
|
||||
proposed_by: u32,
|
||||
) -> Self {
|
||||
let now = Utc::now();
|
||||
Self {
|
||||
id,
|
||||
company_id,
|
||||
meeting_id: None,
|
||||
vote_id: None,
|
||||
title,
|
||||
description,
|
||||
text,
|
||||
status: ResolutionStatus::Draft,
|
||||
proposed_by,
|
||||
proposed_at: now,
|
||||
approved_at: None,
|
||||
rejected_at: None,
|
||||
created_at: now,
|
||||
updated_at: now,
|
||||
approvals: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Propose the resolution
|
||||
pub fn propose(&mut self) {
|
||||
self.status = ResolutionStatus::Proposed;
|
||||
self.proposed_at = Utc::now();
|
||||
self.updated_at = Utc::now();
|
||||
}
|
||||
|
||||
/// Approve the resolution
|
||||
pub fn approve(&mut self) {
|
||||
self.status = ResolutionStatus::Approved;
|
||||
self.approved_at = Some(Utc::now());
|
||||
self.updated_at = Utc::now();
|
||||
}
|
||||
|
||||
/// Reject the resolution
|
||||
pub fn reject(&mut self) {
|
||||
self.status = ResolutionStatus::Rejected;
|
||||
self.rejected_at = Some(Utc::now());
|
||||
self.updated_at = Utc::now();
|
||||
}
|
||||
|
||||
/// Add an approval to the resolution
|
||||
pub fn add_approval(&mut self, user_id: u32, name: String, approved: bool, comments: String) -> &Approval {
|
||||
let id = if self.approvals.is_empty() {
|
||||
1
|
||||
} else {
|
||||
self.approvals.iter().map(|a| a.id).max().unwrap_or(0) + 1
|
||||
};
|
||||
|
||||
let approval = Approval {
|
||||
id,
|
||||
resolution_id: self.id,
|
||||
user_id,
|
||||
name,
|
||||
approved,
|
||||
comments,
|
||||
created_at: Utc::now(),
|
||||
};
|
||||
|
||||
self.approvals.push(approval);
|
||||
self.updated_at = Utc::now();
|
||||
self.approvals.last().unwrap()
|
||||
}
|
||||
|
||||
/// Link this resolution to a meeting
|
||||
pub fn link_to_meeting(&mut self, meeting_id: u32) {
|
||||
self.meeting_id = Some(meeting_id);
|
||||
self.updated_at = Utc::now();
|
||||
}
|
||||
|
||||
/// Link this resolution to a vote
|
||||
pub fn link_to_vote(&mut self, vote_id: u32) {
|
||||
self.vote_id = Some(vote_id);
|
||||
self.updated_at = Utc::now();
|
||||
}
|
||||
}
|
||||
|
||||
// Implement Storable trait (provides default dump/load)
|
||||
impl Storable for Resolution {}
|
||||
impl Storable for Approval {}
|
||||
|
||||
// Implement SledModel trait
|
||||
impl SledModel for Resolution {
|
||||
fn get_id(&self) -> String {
|
||||
self.id.to_string()
|
||||
}
|
||||
|
||||
fn db_prefix() -> &'static str {
|
||||
"resolution"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 3.1.3 Enhanced Company Model (`company.rs`)
|
||||
|
||||
Add integration with other modules:
|
||||
|
||||
```rust
|
||||
impl Company {
|
||||
// ... existing methods ...
|
||||
|
||||
/// Link this company to a Circle for access control
|
||||
pub fn link_to_circle(&mut self, circle_id: u32) -> Result<(), SledDBError> {
|
||||
// Implementation details
|
||||
self.updated_at = Utc::now();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Link this company to a Customer in the biz module
|
||||
pub fn link_to_customer(&mut self, customer_id: u32) -> Result<(), SledDBError> {
|
||||
// Implementation details
|
||||
self.updated_at = Utc::now();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Get all resolutions for this company
|
||||
pub fn get_resolutions(&self, db: &SledDB<Resolution>) -> Result<Vec<Resolution>, SledDBError> {
|
||||
let all_resolutions = db.list()?;
|
||||
let company_resolutions = all_resolutions
|
||||
.into_iter()
|
||||
.filter(|resolution| resolution.company_id == self.id)
|
||||
.collect();
|
||||
|
||||
Ok(company_resolutions)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 3.1.4 Enhanced Meeting Model (`meeting.rs`)
|
||||
|
||||
Add integration with other modules:
|
||||
|
||||
```rust
|
||||
impl Meeting {
|
||||
// ... existing methods ...
|
||||
|
||||
/// Link this meeting to a Calendar Event in the mcc module
|
||||
pub fn link_to_event(&mut self, event_id: u32) -> Result<(), SledDBError> {
|
||||
// Implementation details
|
||||
self.updated_at = Utc::now();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Get all resolutions discussed in this meeting
|
||||
pub fn get_resolutions(&self, db: &SledDB<Resolution>) -> Result<Vec<Resolution>, SledDBError> {
|
||||
let all_resolutions = db.list()?;
|
||||
let meeting_resolutions = all_resolutions
|
||||
.into_iter()
|
||||
.filter(|resolution| resolution.meeting_id == Some(self.id))
|
||||
.collect();
|
||||
|
||||
Ok(meeting_resolutions)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 3.1.5 Enhanced Vote Model (`vote.rs`)
|
||||
|
||||
Add integration with Resolution model:
|
||||
|
||||
```rust
|
||||
impl Vote {
|
||||
// ... existing methods ...
|
||||
|
||||
/// Get the resolution associated with this vote
|
||||
pub fn get_resolution(&self, db: &SledDB<Resolution>) -> Result<Option<Resolution>, SledDBError> {
|
||||
let all_resolutions = db.list()?;
|
||||
let vote_resolution = all_resolutions
|
||||
.into_iter()
|
||||
.find(|resolution| resolution.vote_id == Some(self.id));
|
||||
|
||||
Ok(vote_resolution)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 3.1.6 Create README.md
|
||||
|
||||
Create a README.md file to document the purpose and usage of the governance module.
|
||||
|
||||
## 4. Data Model Diagram
|
||||
|
||||
```mermaid
|
||||
classDiagram
|
||||
class Company {
|
||||
+u32 id
|
||||
+String name
|
||||
+String registration_number
|
||||
+DateTime incorporation_date
|
||||
+String fiscal_year_end
|
||||
+String email
|
||||
+String phone
|
||||
+String website
|
||||
+String address
|
||||
+BusinessType business_type
|
||||
+String industry
|
||||
+String description
|
||||
+CompanyStatus status
|
||||
+DateTime created_at
|
||||
+DateTime updated_at
|
||||
+add_shareholder()
|
||||
+link_to_circle()
|
||||
+link_to_customer()
|
||||
+get_resolutions()
|
||||
}
|
||||
|
||||
class Shareholder {
|
||||
+u32 id
|
||||
+u32 company_id
|
||||
+u32 user_id
|
||||
+String name
|
||||
+f64 shares
|
||||
+f64 percentage
|
||||
+ShareholderType type_
|
||||
+DateTime since
|
||||
+DateTime created_at
|
||||
+DateTime updated_at
|
||||
+update_shares()
|
||||
}
|
||||
|
||||
class Meeting {
|
||||
+u32 id
|
||||
+u32 company_id
|
||||
+String title
|
||||
+DateTime date
|
||||
+String location
|
||||
+String description
|
||||
+MeetingStatus status
|
||||
+String minutes
|
||||
+DateTime created_at
|
||||
+DateTime updated_at
|
||||
+Vec~Attendee~ attendees
|
||||
+add_attendee()
|
||||
+update_status()
|
||||
+update_minutes()
|
||||
+find_attendee_by_user_id()
|
||||
+confirmed_attendees()
|
||||
+link_to_event()
|
||||
+get_resolutions()
|
||||
}
|
||||
|
||||
class User {
|
||||
+u32 id
|
||||
+String name
|
||||
+String email
|
||||
+String password
|
||||
+String company
|
||||
+String role
|
||||
+DateTime created_at
|
||||
+DateTime updated_at
|
||||
}
|
||||
|
||||
class Vote {
|
||||
+u32 id
|
||||
+u32 company_id
|
||||
+String title
|
||||
+String description
|
||||
+DateTime start_date
|
||||
+DateTime end_date
|
||||
+VoteStatus status
|
||||
+DateTime created_at
|
||||
+DateTime updated_at
|
||||
+Vec~VoteOption~ options
|
||||
+Vec~Ballot~ ballots
|
||||
+Vec~u32~ private_group
|
||||
+add_option()
|
||||
+add_ballot()
|
||||
+get_resolution()
|
||||
}
|
||||
|
||||
class Resolution {
|
||||
+u32 id
|
||||
+u32 company_id
|
||||
+Option~u32~ meeting_id
|
||||
+Option~u32~ vote_id
|
||||
+String title
|
||||
+String description
|
||||
+String text
|
||||
+ResolutionStatus status
|
||||
+u32 proposed_by
|
||||
+DateTime proposed_at
|
||||
+Option~DateTime~ approved_at
|
||||
+Option~DateTime~ rejected_at
|
||||
+DateTime created_at
|
||||
+DateTime updated_at
|
||||
+Vec~Approval~ approvals
|
||||
+propose()
|
||||
+approve()
|
||||
+reject()
|
||||
+add_approval()
|
||||
+link_to_meeting()
|
||||
+link_to_vote()
|
||||
}
|
||||
|
||||
Company "1" -- "many" Shareholder: has
|
||||
Company "1" -- "many" Meeting: holds
|
||||
Company "1" -- "many" Vote: conducts
|
||||
Company "1" -- "many" Resolution: issues
|
||||
Meeting "1" -- "many" Attendee: has
|
||||
Meeting "1" -- "many" Resolution: discusses
|
||||
Vote "1" -- "many" VoteOption: has
|
||||
Vote "1" -- "many" Ballot: collects
|
||||
Vote "1" -- "1" Resolution: decides
|
||||
Resolution "1" -- "many" Approval: receives
|
||||
```
|
||||
|
||||
## 5. Testing Strategy
|
||||
|
||||
1. Unit tests for each model to verify:
|
||||
- Basic functionality
|
||||
- Serialization/deserialization
|
||||
- Utility methods
|
||||
- Integration with other models
|
||||
2. Integration tests to verify:
|
||||
- Database operations with the models
|
||||
- Relationships between models
|
||||
- Integration with other modules
|
||||
|
||||
## 6. Future Considerations
|
||||
|
||||
1. **Committee Model**: Add a Committee model in the future if needed
|
||||
2. **Compliance Model**: Add compliance-related models in the future if needed
|
||||
3. **API Integration**: Develop REST API endpoints for the governance module
|
||||
4. **UI Components**: Create UI components for managing governance entities
|
||||
5. **Reporting**: Implement reporting functionality for governance metrics
|
66
herodb/src/models/governance/README.md
Normal file
66
herodb/src/models/governance/README.md
Normal file
@ -0,0 +1,66 @@
|
||||
# Governance Module
|
||||
|
||||
This directory contains the core data structures used in the Freezone Manager governance module. These models serve as the foundation for corporate governance functionality, providing essential data structures for companies, shareholders, meetings, voting, and more.
|
||||
|
||||
## Overview
|
||||
|
||||
The governance models implement the Serde traits (Serialize/Deserialize) and database traits (Storable, SledModel), which allows them to be stored and retrieved using the generic SledDB implementation. Each model provides:
|
||||
|
||||
- A struct definition with appropriate fields
|
||||
- Serde serialization through derive macros
|
||||
- Methods for database integration through the SledModel trait
|
||||
- Utility methods for common operations
|
||||
|
||||
## Core Models
|
||||
|
||||
### Company (`company.rs`)
|
||||
|
||||
The Company model represents a company registered in the Freezone:
|
||||
|
||||
- **Company**: Main struct with fields for company information
|
||||
- **CompanyStatus**: Enum for possible company statuses (Active, Inactive, Suspended)
|
||||
- **BusinessType**: Enum for possible business types (Coop, Single, Twin, Starter, Global)
|
||||
|
||||
### Shareholder (`shareholder.rs`)
|
||||
|
||||
The Shareholder model represents a shareholder of a company:
|
||||
|
||||
- **Shareholder**: Main struct with fields for shareholder information
|
||||
- **ShareholderType**: Enum for possible shareholder types (Individual, Corporate)
|
||||
|
||||
### Meeting (`meeting.rs`)
|
||||
|
||||
The Meeting model represents a board meeting of a company:
|
||||
|
||||
- **Meeting**: Main struct with fields for meeting information
|
||||
- **Attendee**: Represents an attendee of a meeting
|
||||
- **MeetingStatus**: Enum for possible meeting statuses (Scheduled, Completed, Cancelled)
|
||||
- **AttendeeRole**: Enum for possible attendee roles (Coordinator, Member, Secretary, etc.)
|
||||
- **AttendeeStatus**: Enum for possible attendee statuses (Confirmed, Pending, Declined)
|
||||
|
||||
### User (`user.rs`)
|
||||
|
||||
The User model represents a user in the Freezone Manager system:
|
||||
|
||||
- **User**: Main struct with fields for user information
|
||||
|
||||
### Vote (`vote.rs`)
|
||||
|
||||
The Vote model represents a voting item in the Freezone:
|
||||
|
||||
- **Vote**: Main struct with fields for vote information
|
||||
- **VoteOption**: Represents an option in a vote
|
||||
- **Ballot**: Represents a ballot cast by a user
|
||||
- **VoteStatus**: Enum for possible vote statuses (Open, Closed, Cancelled)
|
||||
|
||||
## Usage
|
||||
|
||||
These models are used by the governance module to manage corporate governance. They are typically accessed through the database handlers that implement the generic SledDB interface.
|
||||
|
||||
## Future Enhancements
|
||||
|
||||
See the [GOVERNANCE_ENHANCEMENT_PLAN.md](./GOVERNANCE_ENHANCEMENT_PLAN.md) file for details on planned enhancements to the governance module, including:
|
||||
|
||||
1. New models for committees, resolutions, and compliance
|
||||
2. Enhanced relationships with other modules (biz, mcc, circle)
|
||||
3. Additional utility methods and functionality
|
146
herodb/src/models/governance/committee.rs
Normal file
146
herodb/src/models/governance/committee.rs
Normal file
@ -0,0 +1,146 @@
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use crate::db::{SledModel, Storable, SledDB, SledDBError};
|
||||
use crate::models::governance::User;
|
||||
|
||||
/// CommitteeRole represents the role of a member in a committee
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum CommitteeRole {
|
||||
Chair,
|
||||
ViceChair,
|
||||
Secretary,
|
||||
Member,
|
||||
Advisor,
|
||||
Observer,
|
||||
}
|
||||
|
||||
/// CommitteeMember represents a member of a committee
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct CommitteeMember {
|
||||
pub id: u32,
|
||||
pub committee_id: u32,
|
||||
pub user_id: u32,
|
||||
pub name: String,
|
||||
pub role: CommitteeRole,
|
||||
pub since: DateTime<Utc>,
|
||||
pub created_at: DateTime<Utc>,
|
||||
}
|
||||
|
||||
/// Committee represents a board committee
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct Committee {
|
||||
pub id: u32,
|
||||
pub company_id: u32,
|
||||
pub name: String,
|
||||
pub description: String,
|
||||
pub purpose: String,
|
||||
pub circle_id: Option<u32>, // Link to Circle for access control
|
||||
pub created_at: DateTime<Utc>,
|
||||
pub updated_at: DateTime<Utc>,
|
||||
pub members: Vec<CommitteeMember>,
|
||||
}
|
||||
|
||||
impl Committee {
|
||||
/// Create a new committee with default values
|
||||
pub fn new(
|
||||
id: u32,
|
||||
company_id: u32,
|
||||
name: String,
|
||||
description: String,
|
||||
purpose: String,
|
||||
) -> Self {
|
||||
let now = Utc::now();
|
||||
Self {
|
||||
id,
|
||||
company_id,
|
||||
name,
|
||||
description,
|
||||
purpose,
|
||||
circle_id: None,
|
||||
created_at: now,
|
||||
updated_at: now,
|
||||
members: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Add a member to the committee
|
||||
pub fn add_member(&mut self, user_id: u32, name: String, role: CommitteeRole) -> &CommitteeMember {
|
||||
let id = if self.members.is_empty() {
|
||||
1
|
||||
} else {
|
||||
self.members.iter().map(|m| m.id).max().unwrap_or(0) + 1
|
||||
};
|
||||
|
||||
let now = Utc::now();
|
||||
let member = CommitteeMember {
|
||||
id,
|
||||
committee_id: self.id,
|
||||
user_id,
|
||||
name,
|
||||
role,
|
||||
since: now,
|
||||
created_at: now,
|
||||
};
|
||||
|
||||
self.members.push(member);
|
||||
self.updated_at = now;
|
||||
self.members.last().unwrap()
|
||||
}
|
||||
|
||||
/// Find a member by user ID
|
||||
pub fn find_member_by_user_id(&self, user_id: u32) -> Option<&CommitteeMember> {
|
||||
self.members.iter().find(|m| m.user_id == user_id)
|
||||
}
|
||||
|
||||
/// Find a member by user ID (mutable version)
|
||||
pub fn find_member_by_user_id_mut(&mut self, user_id: u32) -> Option<&mut CommitteeMember> {
|
||||
self.members.iter_mut().find(|m| m.user_id == user_id)
|
||||
}
|
||||
|
||||
/// Remove a member from the committee
|
||||
pub fn remove_member(&mut self, member_id: u32) -> bool {
|
||||
let len = self.members.len();
|
||||
self.members.retain(|m| m.id != member_id);
|
||||
let removed = self.members.len() < len;
|
||||
|
||||
if removed {
|
||||
self.updated_at = Utc::now();
|
||||
}
|
||||
|
||||
removed
|
||||
}
|
||||
|
||||
/// Link this committee to a Circle for access control
|
||||
pub fn link_to_circle(&mut self, circle_id: u32) {
|
||||
self.circle_id = Some(circle_id);
|
||||
self.updated_at = Utc::now();
|
||||
}
|
||||
|
||||
/// Get all users who are members of this committee
|
||||
pub fn get_member_users(&self, db: &SledDB<User>) -> Result<Vec<User>, SledDBError> {
|
||||
let mut users = Vec::new();
|
||||
|
||||
for member in &self.members {
|
||||
if let Ok(user) = db.get(&member.user_id.to_string()) {
|
||||
users.push(user);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(users)
|
||||
}
|
||||
}
|
||||
|
||||
// Implement Storable trait (provides default dump/load)
|
||||
impl Storable for Committee {}
|
||||
impl Storable for CommitteeMember {}
|
||||
|
||||
// Implement SledModel trait
|
||||
impl SledModel for Committee {
|
||||
fn get_id(&self) -> String {
|
||||
self.id.to_string()
|
||||
}
|
||||
|
||||
fn db_prefix() -> &'static str {
|
||||
"committee"
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
use crate::core::{SledModel, Storable, SledDB, SledDBError};
|
||||
use crate::db::{SledModel, Storable, SledDB, SledDBError};
|
||||
use super::shareholder::Shareholder; // Use super:: for sibling module
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
@ -57,7 +57,6 @@ impl SledModel for Company {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl Company {
|
||||
/// Create a new company with default timestamps
|
||||
pub fn new(
|
||||
@ -107,6 +106,37 @@ impl Company {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Removed dump and load_from_bytes methods, now provided by Storable trait
|
||||
/// Link this company to a Circle for access control
|
||||
pub fn link_to_circle(&mut self, circle_id: u32) -> Result<(), SledDBError> {
|
||||
// Implementation would involve updating a mapping in a separate database
|
||||
// For now, we'll just update the timestamp to indicate the change
|
||||
self.updated_at = Utc::now();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Link this company to a Customer in the biz module
|
||||
pub fn link_to_customer(&mut self, customer_id: u32) -> Result<(), SledDBError> {
|
||||
// Implementation would involve updating a mapping in a separate database
|
||||
// For now, we'll just update the timestamp to indicate the change
|
||||
self.updated_at = Utc::now();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Get all resolutions for this company
|
||||
pub fn get_resolutions(&self, db: &SledDB<crate::models::governance::Resolution>) -> Result<Vec<crate::models::governance::Resolution>, SledDBError> {
|
||||
let all_resolutions = db.list()?;
|
||||
let company_resolutions = all_resolutions
|
||||
.into_iter()
|
||||
.filter(|resolution| resolution.company_id == self.id)
|
||||
.collect();
|
||||
|
||||
Ok(company_resolutions)
|
||||
}
|
||||
|
||||
// Future methods:
|
||||
// /// Get all committees for this company
|
||||
// pub fn get_committees(&self, db: &SledDB<Committee>) -> Result<Vec<Committee>, SledDBError> { ... }
|
||||
//
|
||||
// /// Get all compliance requirements for this company
|
||||
// pub fn get_compliance_requirements(&self, db: &SledDB<ComplianceRequirement>) -> Result<Vec<ComplianceRequirement>, SledDBError> { ... }
|
||||
}
|
||||
|
||||
|
212
herodb/src/models/governance/compliance.rs
Normal file
212
herodb/src/models/governance/compliance.rs
Normal file
@ -0,0 +1,212 @@
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use crate::db::{SledModel, Storable, SledDB, SledDBError};
|
||||
use crate::models::governance::Company;
|
||||
|
||||
/// ComplianceRequirement represents a regulatory requirement
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct ComplianceRequirement {
|
||||
pub id: u32,
|
||||
pub company_id: u32,
|
||||
pub title: String,
|
||||
pub description: String,
|
||||
pub regulation: String,
|
||||
pub authority: String,
|
||||
pub deadline: DateTime<Utc>,
|
||||
pub status: String,
|
||||
pub created_at: DateTime<Utc>,
|
||||
pub updated_at: DateTime<Utc>,
|
||||
}
|
||||
|
||||
/// ComplianceDocument represents a compliance document
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct ComplianceDocument {
|
||||
pub id: u32,
|
||||
pub requirement_id: u32,
|
||||
pub title: String,
|
||||
pub description: String,
|
||||
pub file_path: String,
|
||||
pub file_type: String,
|
||||
pub uploaded_by: u32, // User ID
|
||||
pub uploaded_at: DateTime<Utc>,
|
||||
pub created_at: DateTime<Utc>,
|
||||
pub updated_at: DateTime<Utc>,
|
||||
}
|
||||
|
||||
/// ComplianceAudit represents a compliance audit
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct ComplianceAudit {
|
||||
pub id: u32,
|
||||
pub company_id: u32,
|
||||
pub title: String,
|
||||
pub description: String,
|
||||
pub auditor: String,
|
||||
pub start_date: DateTime<Utc>,
|
||||
pub end_date: DateTime<Utc>,
|
||||
pub status: String,
|
||||
pub findings: String,
|
||||
pub created_at: DateTime<Utc>,
|
||||
pub updated_at: DateTime<Utc>,
|
||||
}
|
||||
|
||||
impl ComplianceRequirement {
|
||||
/// Create a new compliance requirement with default values
|
||||
pub fn new(
|
||||
id: u32,
|
||||
company_id: u32,
|
||||
title: String,
|
||||
description: String,
|
||||
regulation: String,
|
||||
authority: String,
|
||||
deadline: DateTime<Utc>,
|
||||
) -> Self {
|
||||
let now = Utc::now();
|
||||
Self {
|
||||
id,
|
||||
company_id,
|
||||
title,
|
||||
description,
|
||||
regulation,
|
||||
authority,
|
||||
deadline,
|
||||
status: "Pending".to_string(),
|
||||
created_at: now,
|
||||
updated_at: now,
|
||||
}
|
||||
}
|
||||
|
||||
/// Update the status of the requirement
|
||||
pub fn update_status(&mut self, status: String) {
|
||||
self.status = status;
|
||||
self.updated_at = Utc::now();
|
||||
}
|
||||
|
||||
/// Get the company associated with this requirement
|
||||
pub fn get_company(&self, db: &SledDB<Company>) -> Result<Company, SledDBError> {
|
||||
db.get(&self.company_id.to_string())
|
||||
}
|
||||
|
||||
/// Get all documents associated with this requirement
|
||||
pub fn get_documents(&self, db: &SledDB<ComplianceDocument>) -> Result<Vec<ComplianceDocument>, SledDBError> {
|
||||
let all_documents = db.list()?;
|
||||
let requirement_documents = all_documents
|
||||
.into_iter()
|
||||
.filter(|doc| doc.requirement_id == self.id)
|
||||
.collect();
|
||||
|
||||
Ok(requirement_documents)
|
||||
}
|
||||
}
|
||||
|
||||
impl ComplianceDocument {
|
||||
/// Create a new compliance document with default values
|
||||
pub fn new(
|
||||
id: u32,
|
||||
requirement_id: u32,
|
||||
title: String,
|
||||
description: String,
|
||||
file_path: String,
|
||||
file_type: String,
|
||||
uploaded_by: u32,
|
||||
) -> Self {
|
||||
let now = Utc::now();
|
||||
Self {
|
||||
id,
|
||||
requirement_id,
|
||||
title,
|
||||
description,
|
||||
file_path,
|
||||
file_type,
|
||||
uploaded_by,
|
||||
uploaded_at: now,
|
||||
created_at: now,
|
||||
updated_at: now,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the requirement associated with this document
|
||||
pub fn get_requirement(&self, db: &SledDB<ComplianceRequirement>) -> Result<ComplianceRequirement, SledDBError> {
|
||||
db.get(&self.requirement_id.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl ComplianceAudit {
|
||||
/// Create a new compliance audit with default values
|
||||
pub fn new(
|
||||
id: u32,
|
||||
company_id: u32,
|
||||
title: String,
|
||||
description: String,
|
||||
auditor: String,
|
||||
start_date: DateTime<Utc>,
|
||||
end_date: DateTime<Utc>,
|
||||
) -> Self {
|
||||
let now = Utc::now();
|
||||
Self {
|
||||
id,
|
||||
company_id,
|
||||
title,
|
||||
description,
|
||||
auditor,
|
||||
start_date,
|
||||
end_date,
|
||||
status: "Planned".to_string(),
|
||||
findings: String::new(),
|
||||
created_at: now,
|
||||
updated_at: now,
|
||||
}
|
||||
}
|
||||
|
||||
/// Update the status of the audit
|
||||
pub fn update_status(&mut self, status: String) {
|
||||
self.status = status;
|
||||
self.updated_at = Utc::now();
|
||||
}
|
||||
|
||||
/// Update the findings of the audit
|
||||
pub fn update_findings(&mut self, findings: String) {
|
||||
self.findings = findings;
|
||||
self.updated_at = Utc::now();
|
||||
}
|
||||
|
||||
/// Get the company associated with this audit
|
||||
pub fn get_company(&self, db: &SledDB<Company>) -> Result<Company, SledDBError> {
|
||||
db.get(&self.company_id.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
// Implement Storable trait (provides default dump/load)
|
||||
impl Storable for ComplianceRequirement {}
|
||||
impl Storable for ComplianceDocument {}
|
||||
impl Storable for ComplianceAudit {}
|
||||
|
||||
// Implement SledModel trait
|
||||
impl SledModel for ComplianceRequirement {
|
||||
fn get_id(&self) -> String {
|
||||
self.id.to_string()
|
||||
}
|
||||
|
||||
fn db_prefix() -> &'static str {
|
||||
"compliance_requirement"
|
||||
}
|
||||
}
|
||||
|
||||
impl SledModel for ComplianceDocument {
|
||||
fn get_id(&self) -> String {
|
||||
self.id.to_string()
|
||||
}
|
||||
|
||||
fn db_prefix() -> &'static str {
|
||||
"compliance_document"
|
||||
}
|
||||
}
|
||||
|
||||
impl SledModel for ComplianceAudit {
|
||||
fn get_id(&self) -> String {
|
||||
self.id.to_string()
|
||||
}
|
||||
|
||||
fn db_prefix() -> &'static str {
|
||||
"compliance_audit"
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use crate::core::{SledModel, Storable}; // Import Sled traits from new location
|
||||
use crate::db::{SledModel, Storable, SledDB, SledDBError, SledDBResult}; // Import Sled traits from db module
|
||||
// use std::collections::HashMap; // Removed unused import
|
||||
|
||||
// use super::db::Model; // Removed old Model trait import
|
||||
@ -155,6 +155,24 @@ impl Meeting {
|
||||
.filter(|a| a.status == AttendeeStatus::Confirmed)
|
||||
.collect()
|
||||
}
|
||||
/// Link this meeting to a Calendar Event in the mcc module
|
||||
pub fn link_to_event(&mut self, event_id: u32) -> Result<(), SledDBError> {
|
||||
// Implementation would involve updating a mapping in a separate database
|
||||
// For now, we'll just update the timestamp to indicate the change
|
||||
self.updated_at = Utc::now();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Get all resolutions discussed in this meeting
|
||||
pub fn get_resolutions(&self, db: &SledDB<crate::models::governance::Resolution>) -> Result<Vec<crate::models::governance::Resolution>, SledDBError> {
|
||||
let all_resolutions = db.list()?;
|
||||
let meeting_resolutions = all_resolutions
|
||||
.into_iter()
|
||||
.filter(|resolution| resolution.meeting_id == Some(self.id))
|
||||
.collect();
|
||||
|
||||
Ok(meeting_resolutions)
|
||||
}
|
||||
}
|
||||
|
||||
// Implement Storable trait (provides default dump/load)
|
||||
|
20
herodb/src/models/governance/mod.rs
Normal file
20
herodb/src/models/governance/mod.rs
Normal file
@ -0,0 +1,20 @@
|
||||
pub mod company;
|
||||
pub mod shareholder;
|
||||
pub mod meeting;
|
||||
pub mod user;
|
||||
pub mod vote;
|
||||
pub mod resolution;
|
||||
// Future modules:
|
||||
// pub mod committee;
|
||||
// pub mod compliance;
|
||||
|
||||
// Re-export all model types for convenience
|
||||
pub use company::{Company, CompanyStatus, BusinessType};
|
||||
pub use shareholder::{Shareholder, ShareholderType};
|
||||
pub use meeting::{Meeting, Attendee, MeetingStatus, AttendeeRole, AttendeeStatus};
|
||||
pub use user::User;
|
||||
pub use vote::{Vote, VoteOption, Ballot, VoteStatus};
|
||||
pub use resolution::{Resolution, ResolutionStatus, Approval};
|
||||
|
||||
// Re-export database components from db module
|
||||
pub use crate::db::{SledDB, SledDBError, SledDBResult, Storable, SledModel, DB};
|
196
herodb/src/models/governance/resolution.rs
Normal file
196
herodb/src/models/governance/resolution.rs
Normal file
@ -0,0 +1,196 @@
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use crate::db::{SledModel, Storable, SledDB, SledDBError};
|
||||
use crate::models::governance::{Meeting, Vote};
|
||||
|
||||
/// ResolutionStatus represents the status of a resolution
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum ResolutionStatus {
|
||||
Draft,
|
||||
Proposed,
|
||||
Approved,
|
||||
Rejected,
|
||||
Withdrawn,
|
||||
}
|
||||
|
||||
/// Resolution represents a board resolution
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct Resolution {
|
||||
pub id: u32,
|
||||
pub company_id: u32,
|
||||
pub meeting_id: Option<u32>,
|
||||
pub vote_id: Option<u32>,
|
||||
pub title: String,
|
||||
pub description: String,
|
||||
pub text: String,
|
||||
pub status: ResolutionStatus,
|
||||
pub proposed_by: u32, // User ID
|
||||
pub proposed_at: DateTime<Utc>,
|
||||
pub approved_at: Option<DateTime<Utc>>,
|
||||
pub rejected_at: Option<DateTime<Utc>>,
|
||||
pub created_at: DateTime<Utc>,
|
||||
pub updated_at: DateTime<Utc>,
|
||||
pub approvals: Vec<Approval>,
|
||||
}
|
||||
|
||||
/// Approval represents an approval of a resolution by a board member
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct Approval {
|
||||
pub id: u32,
|
||||
pub resolution_id: u32,
|
||||
pub user_id: u32,
|
||||
pub name: String,
|
||||
pub approved: bool,
|
||||
pub comments: String,
|
||||
pub created_at: DateTime<Utc>,
|
||||
}
|
||||
|
||||
impl Resolution {
|
||||
/// Create a new resolution with default values
|
||||
pub fn new(
|
||||
id: u32,
|
||||
company_id: u32,
|
||||
title: String,
|
||||
description: String,
|
||||
text: String,
|
||||
proposed_by: u32,
|
||||
) -> Self {
|
||||
let now = Utc::now();
|
||||
Self {
|
||||
id,
|
||||
company_id,
|
||||
meeting_id: None,
|
||||
vote_id: None,
|
||||
title,
|
||||
description,
|
||||
text,
|
||||
status: ResolutionStatus::Draft,
|
||||
proposed_by,
|
||||
proposed_at: now,
|
||||
approved_at: None,
|
||||
rejected_at: None,
|
||||
created_at: now,
|
||||
updated_at: now,
|
||||
approvals: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Propose the resolution
|
||||
pub fn propose(&mut self) {
|
||||
self.status = ResolutionStatus::Proposed;
|
||||
self.proposed_at = Utc::now();
|
||||
self.updated_at = Utc::now();
|
||||
}
|
||||
|
||||
/// Approve the resolution
|
||||
pub fn approve(&mut self) {
|
||||
self.status = ResolutionStatus::Approved;
|
||||
self.approved_at = Some(Utc::now());
|
||||
self.updated_at = Utc::now();
|
||||
}
|
||||
|
||||
/// Reject the resolution
|
||||
pub fn reject(&mut self) {
|
||||
self.status = ResolutionStatus::Rejected;
|
||||
self.rejected_at = Some(Utc::now());
|
||||
self.updated_at = Utc::now();
|
||||
}
|
||||
|
||||
/// Withdraw the resolution
|
||||
pub fn withdraw(&mut self) {
|
||||
self.status = ResolutionStatus::Withdrawn;
|
||||
self.updated_at = Utc::now();
|
||||
}
|
||||
|
||||
/// Add an approval to the resolution
|
||||
pub fn add_approval(&mut self, user_id: u32, name: String, approved: bool, comments: String) -> &Approval {
|
||||
let id = if self.approvals.is_empty() {
|
||||
1
|
||||
} else {
|
||||
self.approvals.iter().map(|a| a.id).max().unwrap_or(0) + 1
|
||||
};
|
||||
|
||||
let approval = Approval {
|
||||
id,
|
||||
resolution_id: self.id,
|
||||
user_id,
|
||||
name,
|
||||
approved,
|
||||
comments,
|
||||
created_at: Utc::now(),
|
||||
};
|
||||
|
||||
self.approvals.push(approval);
|
||||
self.updated_at = Utc::now();
|
||||
self.approvals.last().unwrap()
|
||||
}
|
||||
|
||||
/// Find an approval by user ID
|
||||
pub fn find_approval_by_user_id(&self, user_id: u32) -> Option<&Approval> {
|
||||
self.approvals.iter().find(|a| a.user_id == user_id)
|
||||
}
|
||||
|
||||
/// Get all approvals
|
||||
pub fn get_approvals(&self) -> &[Approval] {
|
||||
&self.approvals
|
||||
}
|
||||
|
||||
/// Get approval count
|
||||
pub fn approval_count(&self) -> usize {
|
||||
self.approvals.iter().filter(|a| a.approved).count()
|
||||
}
|
||||
|
||||
/// Get rejection count
|
||||
pub fn rejection_count(&self) -> usize {
|
||||
self.approvals.iter().filter(|a| !a.approved).count()
|
||||
}
|
||||
|
||||
/// Link this resolution to a meeting
|
||||
pub fn link_to_meeting(&mut self, meeting_id: u32) {
|
||||
self.meeting_id = Some(meeting_id);
|
||||
self.updated_at = Utc::now();
|
||||
}
|
||||
|
||||
/// Link this resolution to a vote
|
||||
pub fn link_to_vote(&mut self, vote_id: u32) {
|
||||
self.vote_id = Some(vote_id);
|
||||
self.updated_at = Utc::now();
|
||||
}
|
||||
|
||||
/// Get the meeting associated with this resolution
|
||||
pub fn get_meeting(&self, db: &SledDB<Meeting>) -> Result<Option<Meeting>, SledDBError> {
|
||||
match self.meeting_id {
|
||||
Some(meeting_id) => {
|
||||
let meeting = db.get(&meeting_id.to_string())?;
|
||||
Ok(Some(meeting))
|
||||
}
|
||||
None => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the vote associated with this resolution
|
||||
pub fn get_vote(&self, db: &SledDB<Vote>) -> Result<Option<Vote>, SledDBError> {
|
||||
match self.vote_id {
|
||||
Some(vote_id) => {
|
||||
let vote = db.get(&vote_id.to_string())?;
|
||||
Ok(Some(vote))
|
||||
}
|
||||
None => Ok(None),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Implement Storable trait (provides default dump/load)
|
||||
impl Storable for Resolution {}
|
||||
impl Storable for Approval {}
|
||||
|
||||
// Implement SledModel trait
|
||||
impl SledModel for Resolution {
|
||||
fn get_id(&self) -> String {
|
||||
self.id.to_string()
|
||||
}
|
||||
|
||||
fn db_prefix() -> &'static str {
|
||||
"resolution"
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
use crate::core::{SledModel, Storable}; // Import Sled traits
|
||||
use crate::db::{SledModel, Storable}; // Import Sled traits
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
// use std::collections::HashMap; // Removed unused import
|
||||
|
@ -1,6 +1,6 @@
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use crate::core::{SledModel, Storable}; // Import Sled traits
|
||||
use crate::db::{SledModel, Storable}; // Import Sled traits
|
||||
// use std::collections::HashMap; // Removed unused import
|
||||
|
||||
/// User represents a user in the Freezone Manager system
|
||||
|
@ -1,6 +1,6 @@
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use crate::db::{SledModel, Storable}; // Import Sled traits from new location
|
||||
use crate::db::{SledModel, Storable, SledDB, SledDBError}; // Import Sled traits from db module
|
||||
// use std::collections::HashMap; // Removed unused import
|
||||
|
||||
// use super::db::Model; // Removed old Model trait import
|
||||
@ -126,6 +126,16 @@ impl Vote {
|
||||
self.ballots.push(ballot);
|
||||
self.ballots.last().unwrap()
|
||||
}
|
||||
|
||||
/// Get the resolution associated with this vote
|
||||
pub fn get_resolution(&self, db: &SledDB<crate::models::governance::Resolution>) -> Result<Option<crate::models::governance::Resolution>, SledDBError> {
|
||||
let all_resolutions = db.list()?;
|
||||
let vote_resolution = all_resolutions
|
||||
.into_iter()
|
||||
.find(|resolution| resolution.vote_id == Some(self.id));
|
||||
|
||||
Ok(vote_resolution)
|
||||
}
|
||||
}
|
||||
|
||||
// Implement Storable trait (provides default dump/load)
|
||||
|
@ -1,3 +1,4 @@
|
||||
pub mod biz;
|
||||
pub mod mcc;
|
||||
pub mod circle;
|
||||
pub mod circle;
|
||||
pub mod governance;
|
Loading…
Reference in New Issue
Block a user