...
This commit is contained in:
parent
a8ef07bb3f
commit
25983f701a
@ -365,7 +365,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
|
|
||||||
// Get the calendar for an event
|
// Get the calendar for an event
|
||||||
println!("\nGetting calendar for Family Dinner event (ID: {}):", family_dinner.id);
|
println!("\nGetting calendar for Family Dinner event (ID: {}):", family_dinner.id);
|
||||||
let event_calendar = db.get::<Calendar>(&family_dinner.calendar_id.to_string())?;
|
let event_calendar = db.get::<Calendar>(family_dinner.calendar_id)?;
|
||||||
println!(" - Calendar: {} ({})", event_calendar.title, event_calendar.description);
|
println!(" - Calendar: {} ({})", event_calendar.title, event_calendar.description);
|
||||||
|
|
||||||
// Get events for a contact
|
// Get events for a contact
|
||||||
|
@ -14,8 +14,5 @@ pub use store::{DbOperations, OurDbStore};
|
|||||||
pub mod db;
|
pub mod db;
|
||||||
pub use db::{DB, DBBuilder, ModelRegistration, ModelRegistrar};
|
pub use db::{DB, DBBuilder, ModelRegistration, ModelRegistrar};
|
||||||
|
|
||||||
// Export the base module (compatibility layer for migration)
|
|
||||||
pub mod base;
|
|
||||||
|
|
||||||
// Export macros for model methods
|
// Export macros for model methods
|
||||||
pub mod macros;
|
pub mod macros;
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use crate::db::base::{SledModel, Storable}; // Import Sled traits from db module
|
use crate::db::{Model, Storable}; // Import Model trait from db module
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
@ -235,13 +235,10 @@ impl ContractBuilder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Implement Storable trait (provides default dump/load)
|
// Implement Model trait
|
||||||
impl Storable for Contract {}
|
impl Model for Contract {
|
||||||
|
fn get_id(&self) -> u32 {
|
||||||
// Implement SledModel trait
|
self.id
|
||||||
impl SledModel for Contract {
|
|
||||||
fn get_id(&self) -> String {
|
|
||||||
self.id.to_string()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn db_prefix() -> &'static str {
|
fn db_prefix() -> &'static str {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use crate::db::base::{SledModel, Storable}; // Import Sled traits from db module
|
use crate::db::{Model, Storable}; // Import Model trait from db module
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
@ -133,13 +133,10 @@ impl CustomerBuilder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Implement Storable trait (provides default dump/load)
|
// Implement Model trait
|
||||||
impl Storable for Customer {}
|
impl Model for Customer {
|
||||||
|
fn get_id(&self) -> u32 {
|
||||||
// Implement SledModel trait
|
self.id
|
||||||
impl SledModel for Customer {
|
|
||||||
fn get_id(&self) -> String {
|
|
||||||
self.id.to_string()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn db_prefix() -> &'static str {
|
fn db_prefix() -> &'static str {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
use crate::models::biz::Currency; // Use crate:: for importing from the module
|
use crate::models::biz::Currency; // Use crate:: for importing from the module
|
||||||
use crate::db::base::{SledModel, Storable}; // Import Sled traits from db module
|
use crate::db::{Model, Storable}; // Import Model trait from db module
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
@ -185,10 +185,11 @@ impl Invoice {
|
|||||||
due_date: DateTime<Utc>,
|
due_date: DateTime<Utc>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let now = Utc::now();
|
let now = Utc::now();
|
||||||
let zero_amount = Currency {
|
let zero_amount = Currency::new(
|
||||||
amount: 0.0,
|
0, // Use 0 as a temporary ID for zero amounts
|
||||||
currency_code: currency_code.clone(),
|
0.0,
|
||||||
};
|
currency_code.clone()
|
||||||
|
);
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
id,
|
id,
|
||||||
@ -214,14 +215,16 @@ impl Invoice {
|
|||||||
// Update the total amount
|
// Update the total amount
|
||||||
if self.items.is_empty() {
|
if self.items.is_empty() {
|
||||||
// First item, initialize the total amount with the same currency
|
// First item, initialize the total amount with the same currency
|
||||||
self.total_amount = Currency {
|
self.total_amount = Currency::new(
|
||||||
amount: item.amount.amount,
|
0, // Use 0 as a temporary ID
|
||||||
currency_code: item.amount.currency_code.clone(),
|
item.amount.amount,
|
||||||
};
|
item.amount.currency_code.clone()
|
||||||
self.balance_due = Currency {
|
);
|
||||||
amount: item.amount.amount,
|
self.balance_due = Currency::new(
|
||||||
currency_code: item.amount.currency_code.clone(),
|
0, // Use 0 as a temporary ID
|
||||||
};
|
item.amount.amount,
|
||||||
|
item.amount.currency_code.clone()
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
// Add to the existing total
|
// Add to the existing total
|
||||||
// (Assumes all items have the same currency)
|
// (Assumes all items have the same currency)
|
||||||
@ -252,10 +255,11 @@ impl Invoice {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Update the total amount
|
// Update the total amount
|
||||||
self.total_amount = Currency {
|
self.total_amount = Currency::new(
|
||||||
amount: total,
|
0, // Use 0 as a temporary ID
|
||||||
currency_code: currency_code.clone(),
|
total,
|
||||||
};
|
currency_code.clone()
|
||||||
|
);
|
||||||
|
|
||||||
// Recalculate the balance due
|
// Recalculate the balance due
|
||||||
self.calculate_balance();
|
self.calculate_balance();
|
||||||
@ -290,10 +294,11 @@ impl Invoice {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Update the balance due
|
// Update the balance due
|
||||||
self.balance_due = Currency {
|
self.balance_due = Currency::new(
|
||||||
amount: balance,
|
0, // Use 0 as a temporary ID
|
||||||
currency_code: self.total_amount.currency_code.clone(),
|
balance,
|
||||||
};
|
self.total_amount.currency_code.clone()
|
||||||
|
);
|
||||||
|
|
||||||
// Update the payment status
|
// Update the payment status
|
||||||
self.update_payment_status();
|
self.update_payment_status();
|
||||||
@ -438,10 +443,11 @@ impl InvoiceBuilder {
|
|||||||
let currency_code = self.currency_code.ok_or("currency_code is required")?;
|
let currency_code = self.currency_code.ok_or("currency_code is required")?;
|
||||||
|
|
||||||
// Initialize with empty total amount and balance due
|
// Initialize with empty total amount and balance due
|
||||||
let mut total_amount = Currency {
|
let mut total_amount = Currency::new(
|
||||||
amount: 0.0,
|
0, // Use 0 as a temporary ID
|
||||||
currency_code: currency_code.clone(),
|
0.0,
|
||||||
};
|
currency_code.clone()
|
||||||
|
);
|
||||||
|
|
||||||
// Calculate total amount from items
|
// Calculate total amount from items
|
||||||
for item in &self.items {
|
for item in &self.items {
|
||||||
@ -494,13 +500,10 @@ impl InvoiceBuilder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Implement Storable trait (provides default dump/load)
|
// Implement Model trait
|
||||||
impl Storable for Invoice {}
|
impl Model for Invoice {
|
||||||
|
fn get_id(&self) -> u32 {
|
||||||
// Implement SledModel trait
|
self.id
|
||||||
impl SledModel for Invoice {
|
|
||||||
fn get_id(&self) -> String {
|
|
||||||
self.id.to_string()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn db_prefix() -> &'static str {
|
fn db_prefix() -> &'static str {
|
||||||
|
@ -26,5 +26,5 @@ pub use product::Currency;
|
|||||||
pub use currency::CurrencyBuilder;
|
pub use currency::CurrencyBuilder;
|
||||||
|
|
||||||
// Re-export database components
|
// Re-export database components
|
||||||
// Re-export from core module
|
// Re-export database components from db module
|
||||||
pub use crate::core::{SledDB, SledDBError, SledDBResult, Storable, SledModel, DB};
|
pub use crate::db::{DB, DBBuilder, Model, Storable, DbError, DbResult, ModelRegistration, ModelRegistrar};
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
use crate::db::base::{SledModel, Storable};
|
use crate::db::{Model, Storable};
|
||||||
use crate::models::biz::Currency; // Use crate:: for importing from the module // Import Sled traits from db module
|
use crate::models::biz::Currency; // Use crate:: for importing from the module
|
||||||
// use super::db::Model; // Removed old Model trait import
|
// use super::db::Model; // Removed old Model trait import
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use rhai::{CustomType, TypeBuilder};
|
use rhai::{CustomType, TypeBuilder};
|
||||||
@ -47,17 +47,19 @@ impl SaleItem {
|
|||||||
) -> Self {
|
) -> Self {
|
||||||
// Calculate subtotal (before tax)
|
// Calculate subtotal (before tax)
|
||||||
let amount = unit_price.amount * quantity as f64;
|
let amount = unit_price.amount * quantity as f64;
|
||||||
let subtotal = Currency {
|
let subtotal = Currency::new(
|
||||||
|
0, // Use 0 as a temporary ID
|
||||||
amount,
|
amount,
|
||||||
currency_code: unit_price.currency_code.clone(),
|
unit_price.currency_code.clone()
|
||||||
};
|
);
|
||||||
|
|
||||||
// Calculate tax amount
|
// Calculate tax amount
|
||||||
let tax_amount_value = subtotal.amount * (tax_rate / 100.0);
|
let tax_amount_value = subtotal.amount * (tax_rate / 100.0);
|
||||||
let tax_amount = Currency {
|
let tax_amount = Currency::new(
|
||||||
amount: tax_amount_value,
|
0, // Use 0 as a temporary ID
|
||||||
currency_code: unit_price.currency_code.clone(),
|
tax_amount_value,
|
||||||
};
|
unit_price.currency_code.clone()
|
||||||
|
);
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
id,
|
id,
|
||||||
@ -77,10 +79,11 @@ impl SaleItem {
|
|||||||
|
|
||||||
/// Get the total amount including tax
|
/// Get the total amount including tax
|
||||||
pub fn total_with_tax(&self) -> Currency {
|
pub fn total_with_tax(&self) -> Currency {
|
||||||
Currency {
|
Currency::new(
|
||||||
amount: self.subtotal.amount + self.tax_amount.amount,
|
0, // Use 0 as a temporary ID
|
||||||
currency_code: self.subtotal.currency_code.clone(),
|
self.subtotal.amount + self.tax_amount.amount,
|
||||||
}
|
self.subtotal.currency_code.clone()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -188,17 +191,19 @@ impl SaleItemBuilder {
|
|||||||
|
|
||||||
// Calculate subtotal
|
// Calculate subtotal
|
||||||
let amount = unit_price.amount * quantity as f64;
|
let amount = unit_price.amount * quantity as f64;
|
||||||
let subtotal = Currency {
|
let subtotal = Currency::new(
|
||||||
|
0, // Use 0 as a temporary ID
|
||||||
amount,
|
amount,
|
||||||
currency_code: unit_price.currency_code.clone(),
|
unit_price.currency_code.clone()
|
||||||
};
|
);
|
||||||
|
|
||||||
// Calculate tax amount
|
// Calculate tax amount
|
||||||
let tax_amount_value = subtotal.amount * (tax_rate / 100.0);
|
let tax_amount_value = subtotal.amount * (tax_rate / 100.0);
|
||||||
let tax_amount = Currency {
|
let tax_amount = Currency::new(
|
||||||
amount: tax_amount_value,
|
0, // Use 0 as a temporary ID
|
||||||
currency_code: unit_price.currency_code.clone(),
|
tax_amount_value,
|
||||||
};
|
unit_price.currency_code.clone()
|
||||||
|
);
|
||||||
|
|
||||||
Ok(SaleItem {
|
Ok(SaleItem {
|
||||||
id: self.id.ok_or("id is required")?,
|
id: self.id.ok_or("id is required")?,
|
||||||
@ -250,10 +255,11 @@ impl Sale {
|
|||||||
status: SaleStatus,
|
status: SaleStatus,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let now = Utc::now();
|
let now = Utc::now();
|
||||||
let zero_currency = Currency {
|
let zero_currency = Currency::new(
|
||||||
amount: 0.0,
|
0, // Use 0 as a temporary ID
|
||||||
currency_code: currency_code.clone(),
|
0.0,
|
||||||
};
|
currency_code.clone()
|
||||||
|
);
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
id,
|
id,
|
||||||
@ -281,18 +287,21 @@ impl Sale {
|
|||||||
// Update the amounts
|
// Update the amounts
|
||||||
if self.items.is_empty() {
|
if self.items.is_empty() {
|
||||||
// First item, initialize the amounts with the same currency
|
// First item, initialize the amounts with the same currency
|
||||||
self.subtotal_amount = Currency {
|
self.subtotal_amount = Currency::new(
|
||||||
amount: item.subtotal.amount,
|
0, // Use 0 as a temporary ID
|
||||||
currency_code: item.subtotal.currency_code.clone(),
|
item.subtotal.amount,
|
||||||
};
|
item.subtotal.currency_code.clone()
|
||||||
self.tax_amount = Currency {
|
);
|
||||||
amount: item.tax_amount.amount,
|
self.tax_amount = Currency::new(
|
||||||
currency_code: item.tax_amount.currency_code.clone(),
|
0, // Use 0 as a temporary ID
|
||||||
};
|
item.tax_amount.amount,
|
||||||
self.total_amount = Currency {
|
item.tax_amount.currency_code.clone()
|
||||||
amount: item.subtotal.amount + item.tax_amount.amount,
|
);
|
||||||
currency_code: item.subtotal.currency_code.clone(),
|
self.total_amount = Currency::new(
|
||||||
};
|
0, // Use 0 as a temporary ID
|
||||||
|
item.subtotal.amount + item.tax_amount.amount,
|
||||||
|
item.subtotal.currency_code.clone()
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
// Add to the existing totals
|
// Add to the existing totals
|
||||||
// (Assumes all items have the same currency)
|
// (Assumes all items have the same currency)
|
||||||
@ -327,18 +336,21 @@ impl Sale {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Update the amounts
|
// Update the amounts
|
||||||
self.subtotal_amount = Currency {
|
self.subtotal_amount = Currency::new(
|
||||||
amount: subtotal,
|
0, // Use 0 as a temporary ID
|
||||||
currency_code: currency_code.clone(),
|
subtotal,
|
||||||
};
|
currency_code.clone()
|
||||||
self.tax_amount = Currency {
|
);
|
||||||
amount: tax_total,
|
self.tax_amount = Currency::new(
|
||||||
currency_code: currency_code.clone(),
|
0, // Use 0 as a temporary ID
|
||||||
};
|
tax_total,
|
||||||
self.total_amount = Currency {
|
currency_code.clone()
|
||||||
amount: subtotal + tax_total,
|
);
|
||||||
currency_code,
|
self.total_amount = Currency::new(
|
||||||
};
|
0, // Use 0 as a temporary ID
|
||||||
|
subtotal + tax_total,
|
||||||
|
currency_code
|
||||||
|
);
|
||||||
|
|
||||||
// Update the timestamp
|
// Update the timestamp
|
||||||
self.updated_at = Utc::now();
|
self.updated_at = Utc::now();
|
||||||
@ -505,18 +517,21 @@ impl SaleBuilder {
|
|||||||
let currency_code = self.currency_code.ok_or("currency_code is required")?;
|
let currency_code = self.currency_code.ok_or("currency_code is required")?;
|
||||||
|
|
||||||
// Initialize with empty amounts
|
// Initialize with empty amounts
|
||||||
let mut subtotal_amount = Currency {
|
let mut subtotal_amount = Currency::new(
|
||||||
amount: 0.0,
|
0, // Use 0 as a temporary ID
|
||||||
currency_code: currency_code.clone(),
|
0.0,
|
||||||
};
|
currency_code.clone()
|
||||||
let mut tax_amount = Currency {
|
);
|
||||||
amount: 0.0,
|
let mut tax_amount = Currency::new(
|
||||||
currency_code: currency_code.clone(),
|
0, // Use 0 as a temporary ID
|
||||||
};
|
0.0,
|
||||||
let mut total_amount = Currency {
|
currency_code.clone()
|
||||||
amount: 0.0,
|
);
|
||||||
currency_code: currency_code.clone(),
|
let mut total_amount = Currency::new(
|
||||||
};
|
0, // Use 0 as a temporary ID
|
||||||
|
0.0,
|
||||||
|
currency_code.clone()
|
||||||
|
);
|
||||||
|
|
||||||
// Calculate amounts from items
|
// Calculate amounts from items
|
||||||
for item in &self.items {
|
for item in &self.items {
|
||||||
@ -551,16 +566,14 @@ impl SaleBuilder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Implement Storable trait (provides default dump/load)
|
// Implement Model trait
|
||||||
impl Storable for Sale {}
|
impl Model for Sale {
|
||||||
|
fn get_id(&self) -> u32 {
|
||||||
// Implement SledModel trait
|
self.id
|
||||||
impl SledModel for Sale {
|
|
||||||
fn get_id(&self) -> String {
|
|
||||||
self.id.to_string()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn db_prefix() -> &'static str {
|
fn db_prefix() -> &'static str {
|
||||||
"sale"
|
"sale"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
use crate::models::biz::Currency; // Use crate:: for importing from the module
|
use crate::models::biz::Currency; // Use crate:: for importing from the module
|
||||||
use crate::db::base::{SledModel, Storable}; // Import Sled traits from db module
|
use crate::db::{Model, Storable}; // Import Model trait from db module
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
@ -57,22 +57,25 @@ impl ServiceItem {
|
|||||||
) -> Self {
|
) -> Self {
|
||||||
// Calculate subtotal
|
// Calculate subtotal
|
||||||
let amount = unit_price.amount * quantity as f64;
|
let amount = unit_price.amount * quantity as f64;
|
||||||
let subtotal = Currency {
|
let subtotal = Currency::new(
|
||||||
|
0, // Use 0 as a temporary ID
|
||||||
amount,
|
amount,
|
||||||
currency_code: unit_price.currency_code.clone(),
|
unit_price.currency_code.clone()
|
||||||
};
|
);
|
||||||
|
|
||||||
// Calculate tax amount if taxable
|
// Calculate tax amount if taxable
|
||||||
let tax_amount = if is_taxable {
|
let tax_amount = if is_taxable {
|
||||||
Currency {
|
Currency::new(
|
||||||
amount: subtotal.amount * tax_rate,
|
0, // Use 0 as a temporary ID
|
||||||
currency_code: unit_price.currency_code.clone(),
|
subtotal.amount * tax_rate,
|
||||||
}
|
unit_price.currency_code.clone()
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
Currency {
|
Currency::new(
|
||||||
amount: 0.0,
|
0, // Use 0 as a temporary ID
|
||||||
currency_code: unit_price.currency_code.clone(),
|
0.0,
|
||||||
}
|
unit_price.currency_code.clone()
|
||||||
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
@ -95,24 +98,27 @@ impl ServiceItem {
|
|||||||
/// Calculate the subtotal based on quantity and unit price
|
/// Calculate the subtotal based on quantity and unit price
|
||||||
pub fn calculate_subtotal(&mut self) {
|
pub fn calculate_subtotal(&mut self) {
|
||||||
let amount = self.unit_price.amount * self.quantity as f64;
|
let amount = self.unit_price.amount * self.quantity as f64;
|
||||||
self.subtotal = Currency {
|
self.subtotal = Currency::new(
|
||||||
|
0, // Use 0 as a temporary ID
|
||||||
amount,
|
amount,
|
||||||
currency_code: self.unit_price.currency_code.clone(),
|
self.unit_price.currency_code.clone()
|
||||||
};
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Calculate the tax amount based on subtotal and tax rate
|
/// Calculate the tax amount based on subtotal and tax rate
|
||||||
pub fn calculate_tax(&mut self) {
|
pub fn calculate_tax(&mut self) {
|
||||||
if self.is_taxable {
|
if self.is_taxable {
|
||||||
self.tax_amount = Currency {
|
self.tax_amount = Currency::new(
|
||||||
amount: self.subtotal.amount * self.tax_rate,
|
0, // Use 0 as a temporary ID
|
||||||
currency_code: self.subtotal.currency_code.clone(),
|
self.subtotal.amount * self.tax_rate,
|
||||||
};
|
self.subtotal.currency_code.clone()
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
self.tax_amount = Currency {
|
self.tax_amount = Currency::new(
|
||||||
amount: 0.0,
|
0, // Use 0 as a temporary ID
|
||||||
currency_code: self.subtotal.currency_code.clone(),
|
0.0,
|
||||||
};
|
self.subtotal.currency_code.clone()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -229,22 +235,25 @@ impl ServiceItemBuilder {
|
|||||||
|
|
||||||
// Calculate subtotal
|
// Calculate subtotal
|
||||||
let amount = unit_price.amount * quantity as f64;
|
let amount = unit_price.amount * quantity as f64;
|
||||||
let subtotal = Currency {
|
let subtotal = Currency::new(
|
||||||
|
0, // Use 0 as a temporary ID
|
||||||
amount,
|
amount,
|
||||||
currency_code: unit_price.currency_code.clone(),
|
unit_price.currency_code.clone()
|
||||||
};
|
);
|
||||||
|
|
||||||
// Calculate tax amount if taxable
|
// Calculate tax amount if taxable
|
||||||
let tax_amount = if is_taxable {
|
let tax_amount = if is_taxable {
|
||||||
Currency {
|
Currency::new(
|
||||||
amount: subtotal.amount * tax_rate,
|
0, // Use 0 as a temporary ID
|
||||||
currency_code: unit_price.currency_code.clone(),
|
subtotal.amount * tax_rate,
|
||||||
}
|
unit_price.currency_code.clone()
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
Currency {
|
Currency::new(
|
||||||
amount: 0.0,
|
0, // Use 0 as a temporary ID
|
||||||
currency_code: unit_price.currency_code.clone(),
|
0.0,
|
||||||
}
|
unit_price.currency_code.clone()
|
||||||
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(ServiceItem {
|
Ok(ServiceItem {
|
||||||
@ -292,7 +301,7 @@ impl Service {
|
|||||||
Self {
|
Self {
|
||||||
id,
|
id,
|
||||||
customer_id,
|
customer_id,
|
||||||
total_amount: Currency { amount: 0.0, currency_code },
|
total_amount: Currency::new(0, 0.0, currency_code),
|
||||||
status,
|
status,
|
||||||
billing_frequency,
|
billing_frequency,
|
||||||
service_date: now,
|
service_date: now,
|
||||||
@ -310,10 +319,11 @@ impl Service {
|
|||||||
// Update the total amount
|
// Update the total amount
|
||||||
if self.items.is_empty() {
|
if self.items.is_empty() {
|
||||||
// First item, initialize the total amount with the same currency
|
// First item, initialize the total amount with the same currency
|
||||||
self.total_amount = Currency {
|
self.total_amount = Currency::new(
|
||||||
amount: item.subtotal.amount + item.tax_amount.amount,
|
0, // Use 0 as a temporary ID
|
||||||
currency_code: item.subtotal.currency_code.clone(),
|
item.subtotal.amount + item.tax_amount.amount,
|
||||||
};
|
item.subtotal.currency_code.clone()
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
// Add to the existing total
|
// Add to the existing total
|
||||||
// (Assumes all items have the same currency)
|
// (Assumes all items have the same currency)
|
||||||
@ -343,10 +353,11 @@ impl Service {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Update the total amount
|
// Update the total amount
|
||||||
self.total_amount = Currency {
|
self.total_amount = Currency::new(
|
||||||
amount: total,
|
0, // Use 0 as a temporary ID
|
||||||
currency_code,
|
total,
|
||||||
};
|
currency_code
|
||||||
|
);
|
||||||
|
|
||||||
// Update the service timestamp
|
// Update the service timestamp
|
||||||
self.updated_at = Utc::now();
|
self.updated_at = Utc::now();
|
||||||
@ -439,10 +450,11 @@ impl ServiceBuilder {
|
|||||||
let currency_code = self.currency_code.ok_or("currency_code is required")?;
|
let currency_code = self.currency_code.ok_or("currency_code is required")?;
|
||||||
|
|
||||||
// Initialize with empty total amount
|
// Initialize with empty total amount
|
||||||
let mut total_amount = Currency {
|
let mut total_amount = Currency::new(
|
||||||
amount: 0.0,
|
0, // Use 0 as a temporary ID
|
||||||
currency_code: currency_code.clone(),
|
0.0,
|
||||||
};
|
currency_code.clone()
|
||||||
|
);
|
||||||
|
|
||||||
// Calculate total amount from items
|
// Calculate total amount from items
|
||||||
for item in &self.items {
|
for item in &self.items {
|
||||||
@ -453,10 +465,11 @@ impl ServiceBuilder {
|
|||||||
|
|
||||||
if total_amount.amount == 0.0 {
|
if total_amount.amount == 0.0 {
|
||||||
// First item, initialize the total amount with the same currency
|
// First item, initialize the total amount with the same currency
|
||||||
total_amount = Currency {
|
total_amount = Currency::new(
|
||||||
amount: item.subtotal.amount + item.tax_amount.amount,
|
0, // Use 0 as a temporary ID
|
||||||
currency_code: item.subtotal.currency_code.clone(),
|
item.subtotal.amount + item.tax_amount.amount,
|
||||||
};
|
item.subtotal.currency_code.clone()
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
// Add to the existing total
|
// Add to the existing total
|
||||||
// (Assumes all items have the same currency)
|
// (Assumes all items have the same currency)
|
||||||
@ -478,13 +491,10 @@ impl ServiceBuilder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Implement Storable trait (provides default dump/load)
|
// Implement Model trait
|
||||||
impl Storable for Service {}
|
impl Model for Service {
|
||||||
|
fn get_id(&self) -> u32 {
|
||||||
// Implement SledModel trait
|
self.id
|
||||||
impl SledModel for Service {
|
|
||||||
fn get_id(&self) -> String {
|
|
||||||
self.id.to_string()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn db_prefix() -> &'static str {
|
fn db_prefix() -> &'static str {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use crate::db::{SledModel, Storable};
|
use crate::db::{Model, Storable};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
/// Circle represents a collection of members (users or other circles)
|
/// Circle represents a collection of members (users or other circles)
|
||||||
@ -28,13 +28,10 @@ impl Circle {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Implement Storable trait (provides default dump/load)
|
// Implement Model trait
|
||||||
impl Storable for Circle {}
|
impl Model for Circle {
|
||||||
|
fn get_id(&self) -> u32 {
|
||||||
// Implement SledModel trait
|
self.id
|
||||||
impl SledModel for Circle {
|
|
||||||
fn get_id(&self) -> String {
|
|
||||||
self.id.to_string()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn db_prefix() -> &'static str {
|
fn db_prefix() -> &'static str {
|
||||||
|
@ -6,4 +6,4 @@ pub use circle::{Circle, Member, Role};
|
|||||||
pub use name::{Name, Record, RecordType};
|
pub use name::{Name, Record, RecordType};
|
||||||
|
|
||||||
// Re-export database components
|
// Re-export database components
|
||||||
pub use crate::db::{SledDB, SledDBError, SledDBResult, Storable, SledModel, DB};
|
pub use crate::db::{DB, DBBuilder, Model, Storable, DbError, DbResult, ModelRegistration, ModelRegistrar};
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use crate::db::{SledModel, Storable};
|
use crate::db::{Model, Storable};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
/// Role represents the role of a member in a circle
|
/// Role represents the role of a member in a circle
|
||||||
@ -67,13 +67,10 @@ impl Member {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Implement Storable trait (provides default dump/load)
|
// Implement Model trait
|
||||||
impl Storable for Member {}
|
impl Model for Member {
|
||||||
|
fn get_id(&self) -> u32 {
|
||||||
// Implement SledModel trait
|
self.id
|
||||||
impl SledModel for Member {
|
|
||||||
fn get_id(&self) -> String {
|
|
||||||
self.id.to_string()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn db_prefix() -> &'static str {
|
fn db_prefix() -> &'static str {
|
||||||
|
@ -10,4 +10,4 @@ pub use name::{Name, Record, RecordType};
|
|||||||
pub use wallet::{Wallet, Asset};
|
pub use wallet::{Wallet, Asset};
|
||||||
|
|
||||||
// Re-export database components from db module
|
// Re-export database components from db module
|
||||||
pub use crate::db::{SledDB, SledDBError, SledDBResult, Storable, SledModel, DB};
|
pub use crate::db::{DB, DBBuilder, Model, Storable, DbError, DbResult, ModelRegistration, ModelRegistrar};
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use crate::db::{SledModel, Storable};
|
use crate::db::{Model, Storable};
|
||||||
|
|
||||||
/// Record types for a DNS record
|
/// Record types for a DNS record
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
@ -57,13 +57,10 @@ impl Name {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Implement Storable trait (provides default dump/load)
|
// Implement Model trait
|
||||||
impl Storable for Name {}
|
impl Model for Name {
|
||||||
|
fn get_id(&self) -> u32 {
|
||||||
// Implement SledModel trait
|
self.id
|
||||||
impl SledModel for Name {
|
|
||||||
fn get_id(&self) -> String {
|
|
||||||
self.id.to_string()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn db_prefix() -> &'static str {
|
fn db_prefix() -> &'static str {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use crate::db::{SledModel, Storable};
|
use crate::db::{Model, Storable};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
/// Asset represents a cryptocurrency asset in a wallet
|
/// Asset represents a cryptocurrency asset in a wallet
|
||||||
@ -69,13 +69,10 @@ impl Wallet {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Implement Storable trait (provides default dump/load)
|
// Implement Model trait
|
||||||
impl Storable for Wallet {}
|
impl Model for Wallet {
|
||||||
|
fn get_id(&self) -> u32 {
|
||||||
// Implement SledModel trait
|
self.id
|
||||||
impl SledModel for Wallet {
|
|
||||||
fn get_id(&self) -> String {
|
|
||||||
self.id.to_string()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn db_prefix() -> &'static str {
|
fn db_prefix() -> &'static str {
|
||||||
|
@ -2,7 +2,10 @@ in @src/models/circle/circle.rs
|
|||||||
|
|
||||||
- member us now new rootobject, check implementation
|
- member us now new rootobject, check implementation
|
||||||
- a member is linked to one or more contacts id's (from src/models/mcc/contacts.rs)
|
- a member is linked to one or more contacts id's (from src/models/mcc/contacts.rs)
|
||||||
- a member has one or more wallets
|
- create a new rootobject called wallet
|
||||||
|
- has a name, description, blockchainname (string), pubkey
|
||||||
|
- a wallet has embedded struct for asset which is name e.g. USDC and float which is the amount of money in the asset
|
||||||
|
- a member has one or more wallets, in member link to the id's of the wallet
|
||||||
|
|
||||||
|
|
||||||
in@src/models/biz add a ticket module
|
in@src/models/biz add a ticket module
|
||||||
|
126
instructions.md
Normal file
126
instructions.md
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
# HeroDB: ACL Layer Implementation
|
||||||
|
|
||||||
|
## Project Overview
|
||||||
|
|
||||||
|
Create a new module that implements an Access Control List (ACL) layer on top of the existing `ourdb` and `tst` databases. This module will manage permissions and access control for data stored in the database system.
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
- The module will sit as a layer between client applications and the underlying `ourdb` & `tst` databases
|
||||||
|
- ACLs are defined at the circle level and stored in a special topic called "acl"
|
||||||
|
- Data in `ourdb` is stored at path: `~/hero/var/ourdb/$circleid/$topicid`
|
||||||
|
- `tst` is used to create mappings between keys and IDs in `ourdb`
|
||||||
|
|
||||||
|
## ACL Structure
|
||||||
|
|
||||||
|
Each ACL contains:
|
||||||
|
- A unique name (per circle)
|
||||||
|
- A list of public keys with associated permissions
|
||||||
|
- Rights are hierarchical: read → write → delete → execute → admin (each right includes all rights to its left)
|
||||||
|
|
||||||
|
## Core Methods
|
||||||
|
|
||||||
|
### ACL Management
|
||||||
|
|
||||||
|
#### aclupdate
|
||||||
|
Updates or creates an ACL with specified permissions.
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
- `callerpubkey`: Public key of the requesting user
|
||||||
|
- `circleid`: ID of the circle where the ACL exists
|
||||||
|
- `name`: Unique name for the ACL within the circle
|
||||||
|
- `pubkeys`: Array of public keys to grant permissions to
|
||||||
|
- `right`: Permission level (enum: read/write/delete/execute/admin)
|
||||||
|
#### aclremove
|
||||||
|
Removes specific public keys from an existing ACL.
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
- `callerpubkey`: Public key of the requesting user
|
||||||
|
- `circleid`: ID of the circle where the ACL exists
|
||||||
|
- `name`: Name of the ACL to modify
|
||||||
|
- `pubkeys`: Array of public keys to remove from the ACL
|
||||||
|
|
||||||
|
#### acldel
|
||||||
|
Deletes an entire ACL.
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
- `callerpubkey`: Public key of the requesting user
|
||||||
|
- `circleid`: ID of the circle where the ACL exists
|
||||||
|
- `name`: Name of the ACL to delete
|
||||||
|
|
||||||
|
### Data Operations
|
||||||
|
|
||||||
|
#### set
|
||||||
|
Stores or updates data in the database with optional ACL protection.
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
- `callerpubkey`: Public key of the requesting user
|
||||||
|
- `circleid`: ID of the circle where the data belongs
|
||||||
|
- `topic`: String identifier for the database category (e.g., "customer", "product")
|
||||||
|
- `key`: Optional string key for the record
|
||||||
|
- `id`: Optional numeric ID for direct access
|
||||||
|
- `value`: Binary blob of data to store
|
||||||
|
- `aclid`: ID of the ACL to protect this record (0 for public access)
|
||||||
|
|
||||||
|
**Behavior:**
|
||||||
|
- If only `key` is provided, use `tst` to map the key to a new or existing ID
|
||||||
|
- If `id` is specified or derived from an existing key, update the corresponding record
|
||||||
|
- Returns the ID of the created/updated record
|
||||||
|
|
||||||
|
#### del
|
||||||
|
Marks a record as deleted.
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
- `callerpubkey`: Public key of the requesting user
|
||||||
|
- `circleid`: ID of the circle where the data belongs
|
||||||
|
- `topic`: String identifier for the database category
|
||||||
|
- `id` or `key`: Identifier for the record to delete
|
||||||
|
|
||||||
|
**Behavior:**
|
||||||
|
- Deletes the mapping in `tst` if a key was used
|
||||||
|
- Marks the record as deleted in `ourdb` (not physically removed)
|
||||||
|
|
||||||
|
#### get
|
||||||
|
Retrieves data from the database.
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
- `callerpubkey`: Public key of the requesting user
|
||||||
|
- `circleid`: ID of the circle where the data belongs
|
||||||
|
- `topic`: String identifier for the database category
|
||||||
|
- `id` or `key`: Identifier for the record to retrieve
|
||||||
|
|
||||||
|
**Returns:**
|
||||||
|
- The binary data stored in the record if the caller has access
|
||||||
|
|
||||||
|
## Implementation Details
|
||||||
|
|
||||||
|
### ACL Storage Format
|
||||||
|
- ACLs are stored in a special topic named "acl" within each circle
|
||||||
|
- Each ACL has a unique numeric ID within the circle
|
||||||
|
|
||||||
|
### Record ACL Protection
|
||||||
|
- When a record uses ACL protection, the first 4 bytes of the stored data contain the ACL ID
|
||||||
|
- A new constructor in `ourdb` should be created to handle ACL-protected records
|
||||||
|
- Records with ACL ID of 0 are accessible to everyone
|
||||||
|
|
||||||
|
## RPC Interface
|
||||||
|
|
||||||
|
The module should expose its functionality through an RPC interface:
|
||||||
|
|
||||||
|
1. Client sends:
|
||||||
|
- Method name (e.g., "del", "set", "get")
|
||||||
|
- JSON-encoded arguments
|
||||||
|
- Cryptographic signature of the JSON data
|
||||||
|
|
||||||
|
2. Server:
|
||||||
|
- Verifies the signature is valid
|
||||||
|
- Extracts the caller's public key from the signature
|
||||||
|
- Checks permissions against applicable ACLs
|
||||||
|
- Executes the requested operation if authorized
|
||||||
|
- Returns appropriate response
|
||||||
|
|
||||||
|
## Security Considerations
|
||||||
|
|
||||||
|
- All operations must validate the caller has appropriate permissions
|
||||||
|
- ACL changes should be logged for audit purposes
|
||||||
|
- Consider implementing rate limiting to prevent abuse
|
Loading…
Reference in New Issue
Block a user