...
This commit is contained in:
parent
6757a62fec
commit
537cf58b6f
@ -9,26 +9,26 @@ The business models are implemented as Rust structs and enums with serialization
|
||||
## Model Relationships
|
||||
|
||||
```
|
||||
┌─────────────┐
|
||||
│ Customer │
|
||||
└──────┬──────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────┐
|
||||
│ Customer │
|
||||
└──────┬──────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
|
||||
│ Currency │◄────┤ Product │◄────┤ SaleItem │◄────┤ Sale │
|
||||
└─────────────┘ └─────────────┘ └─────────────┘ └──────┬──────┘
|
||||
▲ │
|
||||
│ │
|
||||
┌─────┴──────────┐ │
|
||||
│ProductComponent│ │
|
||||
└────────────────┘ │
|
||||
│
|
||||
┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
|
||||
│ Currency │◄────┤ Service │◄────┤ ServiceItem │◄───────────┘
|
||||
└─────────────┘ └─────────────┘ └─────────────┘
|
||||
│
|
||||
│
|
||||
▼
|
||||
│ Currency │◄────┤ Product │◄────┤ │ │ │
|
||||
└─────────────┘ └─────────────┘ │ │ │ │
|
||||
▲ │ SaleItem │◄────┤ Sale │
|
||||
│ │ │ │ │
|
||||
┌─────┴──────────┐ │ │ │ │
|
||||
│ProductComponent│ └─────────────┘ └──────┬──────┘
|
||||
└────────────────┘ ▲ │
|
||||
/ │
|
||||
┌─────────────┐ ┌─────────────┐ / │
|
||||
│ Currency │◄────┤ Service │◄────────/ │
|
||||
└─────────────┘ └─────────────┘ │
|
||||
│
|
||||
│
|
||||
▼
|
||||
┌─────────────┐ ┌─────────────┐
|
||||
│ InvoiceItem │◄────┤ Invoice │
|
||||
└─────────────┘ └─────────────┘
|
||||
@ -40,7 +40,9 @@ The business models are implemented as Rust structs and enums with serialization
|
||||
- **Product/Service**: Defines what is being sold, including its base price
|
||||
- Can be marked as a template (`is_template=true`) to create copies for actual sales
|
||||
- **Sale**: Represents the transaction of selling products/services to customers, including tax calculations
|
||||
- Can be linked to a Service when the sale creates an ongoing service
|
||||
- Contains SaleItems that can be linked to either Products or Services
|
||||
- **SaleItem**: Represents an item within a sale
|
||||
- Can be linked to either a Product or a Service (via product_id or service_id)
|
||||
- **Service**: Represents an ongoing service provided to a customer
|
||||
- Created from a Product template when the product type is Service
|
||||
- **Invoice**: Represents the billing document for a sale, with payment tracking
|
||||
@ -217,8 +219,9 @@ Represents an item within a sale.
|
||||
**Properties:**
|
||||
- `id`: u32 - Unique identifier
|
||||
- `sale_id`: u32 - Parent sale ID
|
||||
- `product_id`: u32 - ID of the product sold
|
||||
- `name`: String - Product name at time of sale
|
||||
- `product_id`: Option<u32> - ID of the product sold (if this is a product sale)
|
||||
- `service_id`: Option<u32> - ID of the service sold (if this is a service sale)
|
||||
- `name`: String - Product/service name at time of sale
|
||||
- `description`: String - Detailed description of the item
|
||||
- `comments`: String - Additional notes or comments about the item
|
||||
- `quantity`: i32 - Number of items purchased
|
||||
@ -481,15 +484,22 @@ Products and Services can be marked as templates (`is_template=true`). When a cu
|
||||
|
||||
#### Sale to Service Relationship
|
||||
|
||||
When a product of type `Service` is sold, a Service instance can be created from the Sale:
|
||||
A SaleItem can be directly linked to a Service via the `service_id` field. This allows for selling existing services or creating new services as part of a sale:
|
||||
|
||||
```rust
|
||||
// Create a service from a sale
|
||||
let service = sale.create_service(
|
||||
service_id,
|
||||
ServiceStatus::Active,
|
||||
BillingFrequency::Monthly
|
||||
);
|
||||
// Create a SaleItem linked to a service
|
||||
let sale_item = SaleItemBuilder::new()
|
||||
.id(1)
|
||||
.sale_id(1)
|
||||
.service_id(Some(42)) // Link to service with ID 42
|
||||
.product_id(None) // No product link since this is a service
|
||||
.name("Premium Support")
|
||||
.quantity(1)
|
||||
.unit_price(unit_price)
|
||||
.tax_rate(20.0)
|
||||
.active_till(now + Duration::days(30))
|
||||
.build()
|
||||
.expect("Failed to build sale item");
|
||||
```
|
||||
|
||||
#### Sale to Invoice Relationship
|
||||
|
@ -1,4 +1,4 @@
|
||||
use crate::db::{Model, Storable}; // Import Model trait from db module
|
||||
use crate::db::{Model, Storable, IndexKey}; // Import Model trait and IndexKey from db module
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
@ -248,4 +248,46 @@ impl Model for Contract {
|
||||
fn db_prefix() -> &'static str {
|
||||
"contract"
|
||||
}
|
||||
|
||||
fn db_keys(&self) -> Vec<IndexKey> {
|
||||
let mut keys = Vec::new();
|
||||
|
||||
// Add an index for customer_id
|
||||
keys.push(IndexKey {
|
||||
name: "customer_id",
|
||||
value: self.customer_id.to_string(),
|
||||
});
|
||||
|
||||
// Add an index for service_id if present
|
||||
if let Some(service_id) = self.service_id {
|
||||
keys.push(IndexKey {
|
||||
name: "service_id",
|
||||
value: service_id.to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
// Add an index for sale_id if present
|
||||
if let Some(sale_id) = self.sale_id {
|
||||
keys.push(IndexKey {
|
||||
name: "sale_id",
|
||||
value: sale_id.to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
// Add an index for status
|
||||
keys.push(IndexKey {
|
||||
name: "status",
|
||||
value: format!("{:?}", self.status),
|
||||
});
|
||||
|
||||
// Add an index for active contracts
|
||||
if self.is_active() {
|
||||
keys.push(IndexKey {
|
||||
name: "active",
|
||||
value: "true".to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
keys
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
use crate::db::model::Model;
|
||||
use crate::db::model::{Model, IndexKey};
|
||||
use crate::db::{Storable, DbError, DbResult};
|
||||
use chrono::{DateTime, Duration, Utc};
|
||||
use rhai::{CustomType, EvalAltResult, TypeBuilder};
|
||||
@ -85,4 +85,30 @@ impl Model for Currency {
|
||||
fn db_prefix() -> &'static str {
|
||||
"currency"
|
||||
}
|
||||
|
||||
fn db_keys(&self) -> Vec<IndexKey> {
|
||||
let mut keys = Vec::new();
|
||||
|
||||
// Add an index for currency_code
|
||||
keys.push(IndexKey {
|
||||
name: "currency_code",
|
||||
value: self.currency_code.clone(),
|
||||
});
|
||||
|
||||
// Add an index for amount range
|
||||
// This allows finding currencies within specific ranges
|
||||
let amount_range = match self.amount {
|
||||
a if a < 100.0 => "0-100",
|
||||
a if a < 1000.0 => "100-1000",
|
||||
a if a < 10000.0 => "1000-10000",
|
||||
_ => "10000+",
|
||||
};
|
||||
|
||||
keys.push(IndexKey {
|
||||
name: "amount_range",
|
||||
value: amount_range.to_string(),
|
||||
});
|
||||
|
||||
keys
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
use crate::models::biz::Currency; // Use crate:: for importing from the module
|
||||
use crate::db::{Model, Storable}; // Import Model trait from db module
|
||||
use crate::db::{Model, Storable, IndexKey}; // Import Model trait and IndexKey from db module
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
@ -511,4 +511,67 @@ impl Model for Invoice {
|
||||
fn db_prefix() -> &'static str {
|
||||
"invoice"
|
||||
}
|
||||
|
||||
fn db_keys(&self) -> Vec<IndexKey> {
|
||||
let mut keys = Vec::new();
|
||||
|
||||
// Add an index for customer_id
|
||||
keys.push(IndexKey {
|
||||
name: "customer_id",
|
||||
value: self.customer_id.to_string(),
|
||||
});
|
||||
|
||||
// Add an index for status
|
||||
keys.push(IndexKey {
|
||||
name: "status",
|
||||
value: format!("{:?}", self.status),
|
||||
});
|
||||
|
||||
// Add an index for payment_status
|
||||
keys.push(IndexKey {
|
||||
name: "payment_status",
|
||||
value: format!("{:?}", self.payment_status),
|
||||
});
|
||||
|
||||
// Add an index for currency code
|
||||
keys.push(IndexKey {
|
||||
name: "currency",
|
||||
value: self.total_amount.currency_code.clone(),
|
||||
});
|
||||
|
||||
// Add an index for amount range
|
||||
let amount_range = match self.total_amount.amount {
|
||||
a if a < 100.0 => "0-100",
|
||||
a if a < 1000.0 => "100-1000",
|
||||
a if a < 10000.0 => "1000-10000",
|
||||
_ => "10000+",
|
||||
};
|
||||
|
||||
keys.push(IndexKey {
|
||||
name: "amount_range",
|
||||
value: amount_range.to_string(),
|
||||
});
|
||||
|
||||
// Add an index for issue date (year-month)
|
||||
keys.push(IndexKey {
|
||||
name: "issue_date",
|
||||
value: format!("{}-{:02}", self.issue_date.year(), self.issue_date.month()),
|
||||
});
|
||||
|
||||
// Add an index for due date (year-month)
|
||||
keys.push(IndexKey {
|
||||
name: "due_date",
|
||||
value: format!("{}-{:02}", self.due_date.year(), self.due_date.month()),
|
||||
});
|
||||
|
||||
// Add an index for overdue invoices
|
||||
if self.is_overdue() {
|
||||
keys.push(IndexKey {
|
||||
name: "overdue",
|
||||
value: "true".to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
keys
|
||||
}
|
||||
}
|
@ -1,8 +1,8 @@
|
||||
use crate::db::model::Model;
|
||||
use crate::db::model::{Model, IndexKey};
|
||||
use crate::db::Storable;
|
||||
use chrono::{DateTime, Duration, Utc};
|
||||
use rhai::{CustomType, EvalAltResult, TypeBuilder, export_module};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// ProductType represents the type of a product
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
@ -355,6 +355,70 @@ impl Model for Product {
|
||||
fn db_prefix() -> &'static str {
|
||||
"product"
|
||||
}
|
||||
|
||||
fn db_keys(&self) -> Vec<IndexKey> {
|
||||
let mut keys = Vec::new();
|
||||
|
||||
// Add an index for name
|
||||
keys.push(IndexKey {
|
||||
name: "name",
|
||||
value: self.name.clone(),
|
||||
});
|
||||
|
||||
// Add an index for category
|
||||
keys.push(IndexKey {
|
||||
name: "category",
|
||||
value: self.category.clone(),
|
||||
});
|
||||
|
||||
// Add an index for product type
|
||||
keys.push(IndexKey {
|
||||
name: "type",
|
||||
value: format!("{:?}", self.type_),
|
||||
});
|
||||
|
||||
// Add an index for status
|
||||
keys.push(IndexKey {
|
||||
name: "status",
|
||||
value: format!("{:?}", self.status),
|
||||
});
|
||||
|
||||
// Add an index for price range
|
||||
let price_range = match self.price.amount {
|
||||
a if a < 100.0 => "0-100",
|
||||
a if a < 1000.0 => "100-1000",
|
||||
a if a < 10000.0 => "1000-10000",
|
||||
_ => "10000+",
|
||||
};
|
||||
|
||||
keys.push(IndexKey {
|
||||
name: "price_range",
|
||||
value: price_range.to_string(),
|
||||
});
|
||||
|
||||
// Add an index for currency code
|
||||
keys.push(IndexKey {
|
||||
name: "currency",
|
||||
value: self.price.currency_code.clone(),
|
||||
});
|
||||
|
||||
// Add indexes for purchasable and active products
|
||||
if self.is_purchasable() {
|
||||
keys.push(IndexKey {
|
||||
name: "purchasable",
|
||||
value: "true".to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
if self.is_active() {
|
||||
keys.push(IndexKey {
|
||||
name: "active",
|
||||
value: "true".to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
keys
|
||||
}
|
||||
}
|
||||
|
||||
// Import Currency from the currency module
|
||||
|
@ -1,4 +1,4 @@
|
||||
use crate::db::{Model, Storable, DbError, DbResult};
|
||||
use crate::db::{Model, Storable, DbError, DbResult, IndexKey};
|
||||
use crate::models::biz::Currency; // Use crate:: for importing from the module
|
||||
// use super::db::Model; // Removed old Model trait import
|
||||
use chrono::{DateTime, Utc};
|
||||
@ -19,7 +19,8 @@ pub enum SaleStatus {
|
||||
pub struct SaleItem {
|
||||
pub id: u32,
|
||||
pub sale_id: u32,
|
||||
pub product_id: u32,
|
||||
pub product_id: Option<u32>, // ID of the product sold (if this is a product sale)
|
||||
pub service_id: Option<u32>, // ID of the service sold (if this is a service sale)
|
||||
pub name: String,
|
||||
pub description: String, // Description of the item
|
||||
pub comments: String, // Additional comments about the item
|
||||
@ -36,7 +37,8 @@ impl SaleItem {
|
||||
pub fn new(
|
||||
id: u32,
|
||||
sale_id: u32,
|
||||
product_id: u32,
|
||||
product_id: Option<u32>,
|
||||
service_id: Option<u32>,
|
||||
name: String,
|
||||
description: String,
|
||||
comments: String,
|
||||
@ -45,6 +47,12 @@ impl SaleItem {
|
||||
tax_rate: f64,
|
||||
active_till: DateTime<Utc>,
|
||||
) -> Self {
|
||||
// Validate that either product_id or service_id is provided, but not both
|
||||
assert!(
|
||||
(product_id.is_some() && service_id.is_none()) ||
|
||||
(product_id.is_none() && service_id.is_some()),
|
||||
"Either product_id or service_id must be provided, but not both"
|
||||
);
|
||||
// Calculate subtotal (before tax)
|
||||
let amount = unit_price.amount * quantity as f64;
|
||||
let subtotal = Currency::new(
|
||||
@ -65,6 +73,7 @@ impl SaleItem {
|
||||
id,
|
||||
sale_id,
|
||||
product_id,
|
||||
service_id,
|
||||
name,
|
||||
description,
|
||||
comments,
|
||||
@ -93,6 +102,7 @@ pub struct SaleItemBuilder {
|
||||
id: Option<u32>,
|
||||
sale_id: Option<u32>,
|
||||
product_id: Option<u32>,
|
||||
service_id: Option<u32>,
|
||||
name: Option<String>,
|
||||
description: Option<String>,
|
||||
comments: Option<String>,
|
||||
@ -111,6 +121,7 @@ impl SaleItemBuilder {
|
||||
id: None,
|
||||
sale_id: None,
|
||||
product_id: None,
|
||||
service_id: None,
|
||||
name: None,
|
||||
description: None,
|
||||
comments: None,
|
||||
@ -136,8 +147,22 @@ impl SaleItemBuilder {
|
||||
}
|
||||
|
||||
/// Set the product_id
|
||||
pub fn product_id(mut self, product_id: u32) -> Self {
|
||||
self.product_id = Some(product_id);
|
||||
pub fn product_id(mut self, product_id: Option<u32>) -> Self {
|
||||
// If setting product_id, ensure service_id is None
|
||||
if product_id.is_some() {
|
||||
self.service_id = None;
|
||||
}
|
||||
self.product_id = product_id;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the service_id
|
||||
pub fn service_id(mut self, service_id: Option<u32>) -> Self {
|
||||
// If setting service_id, ensure product_id is None
|
||||
if service_id.is_some() {
|
||||
self.product_id = None;
|
||||
}
|
||||
self.service_id = service_id;
|
||||
self
|
||||
}
|
||||
|
||||
@ -189,6 +214,14 @@ impl SaleItemBuilder {
|
||||
let quantity = self.quantity.ok_or("quantity is required")?;
|
||||
let tax_rate = self.tax_rate.unwrap_or(0.0); // Default to 0% tax if not specified
|
||||
|
||||
// Validate that either product_id or service_id is provided, but not both
|
||||
if self.product_id.is_none() && self.service_id.is_none() {
|
||||
return Err("Either product_id or service_id must be provided");
|
||||
}
|
||||
if self.product_id.is_some() && self.service_id.is_some() {
|
||||
return Err("Only one of product_id or service_id can be provided");
|
||||
}
|
||||
|
||||
// Calculate subtotal
|
||||
let amount = unit_price.amount * quantity as f64;
|
||||
let subtotal = Currency::new(
|
||||
@ -208,7 +241,8 @@ impl SaleItemBuilder {
|
||||
Ok(SaleItem {
|
||||
id: self.id.ok_or("id is required")?,
|
||||
sale_id: self.sale_id.ok_or("sale_id is required")?,
|
||||
product_id: self.product_id.ok_or("product_id is required")?,
|
||||
product_id: self.product_id,
|
||||
service_id: self.service_id,
|
||||
name: self.name.ok_or("name is required")?,
|
||||
description: self.description.unwrap_or_default(),
|
||||
comments: self.comments.unwrap_or_default(),
|
||||
@ -226,10 +260,7 @@ impl SaleItemBuilder {
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, CustomType)]
|
||||
pub struct Sale {
|
||||
pub id: u32,
|
||||
pub company_id: u32,
|
||||
pub customer_id: u32, // ID of the customer making the purchase
|
||||
pub buyer_name: String,
|
||||
pub buyer_email: String,
|
||||
pub subtotal_amount: Currency, // Total before tax
|
||||
pub tax_amount: Currency, // Total tax
|
||||
pub total_amount: Currency, // Total including tax
|
||||
@ -247,10 +278,7 @@ impl Sale {
|
||||
/// Create a new sale with default timestamps
|
||||
pub fn new(
|
||||
id: u32,
|
||||
company_id: u32,
|
||||
customer_id: u32,
|
||||
buyer_name: String,
|
||||
buyer_email: String,
|
||||
currency_code: String,
|
||||
status: SaleStatus,
|
||||
) -> Self {
|
||||
@ -263,10 +291,7 @@ impl Sale {
|
||||
|
||||
Self {
|
||||
id,
|
||||
company_id,
|
||||
customer_id,
|
||||
buyer_name,
|
||||
buyer_email,
|
||||
subtotal_amount: zero_currency.clone(),
|
||||
tax_amount: zero_currency.clone(),
|
||||
total_amount: zero_currency,
|
||||
@ -362,49 +387,10 @@ impl Sale {
|
||||
self.updated_at = Utc::now();
|
||||
}
|
||||
|
||||
/// Create a service from this sale
|
||||
/// This method should be called when a product of type Service is sold
|
||||
pub fn create_service(&mut self, service_id: u32, status: crate::models::biz::ServiceStatus, billing_frequency: crate::models::biz::BillingFrequency) -> Result<crate::models::biz::Service, &'static str> {
|
||||
use crate::models::biz::{Service, ServiceItem, ServiceStatus, BillingFrequency};
|
||||
|
||||
// Create a new service
|
||||
let mut service = Service::new(
|
||||
service_id,
|
||||
self.customer_id,
|
||||
self.total_amount.currency_code.clone(),
|
||||
status,
|
||||
billing_frequency,
|
||||
);
|
||||
|
||||
// Convert sale items to service items
|
||||
for sale_item in &self.items {
|
||||
// Check if the product is a service type
|
||||
// In a real implementation, you would check the product type from the database
|
||||
|
||||
// Create a service item from the sale item
|
||||
let service_item = ServiceItem::new(
|
||||
sale_item.id,
|
||||
service_id,
|
||||
sale_item.product_id,
|
||||
sale_item.name.clone(),
|
||||
sale_item.description.clone(), // Copy description from sale item
|
||||
sale_item.comments.clone(), // Copy comments from sale item
|
||||
sale_item.quantity,
|
||||
sale_item.unit_price.clone(),
|
||||
sale_item.tax_rate,
|
||||
true, // is_taxable
|
||||
sale_item.active_till,
|
||||
);
|
||||
|
||||
// Add the service item to the service
|
||||
service.add_item(service_item);
|
||||
}
|
||||
|
||||
// Link this sale to the service
|
||||
/// Link this sale to an existing service
|
||||
pub fn link_to_service(&mut self, service_id: u32) {
|
||||
self.service_id = Some(service_id);
|
||||
self.updated_at = Utc::now();
|
||||
|
||||
Ok(service)
|
||||
}
|
||||
}
|
||||
|
||||
@ -549,10 +535,7 @@ impl SaleBuilder {
|
||||
|
||||
Ok(Sale {
|
||||
id,
|
||||
company_id: self.company_id.ok_or("company_id is required")?,
|
||||
customer_id: self.customer_id.ok_or("customer_id is required")?,
|
||||
buyer_name: self.buyer_name.ok_or("buyer_name is required")?,
|
||||
buyer_email: self.buyer_email.ok_or("buyer_email is required")?,
|
||||
subtotal_amount: self.subtotal_amount.unwrap_or(subtotal_amount),
|
||||
tax_amount: self.tax_amount.unwrap_or(tax_amount),
|
||||
total_amount: self.total_amount.unwrap_or(total_amount),
|
||||
@ -578,5 +561,19 @@ impl Model for Sale {
|
||||
fn db_prefix() -> &'static str {
|
||||
"sale"
|
||||
}
|
||||
|
||||
fn db_keys(&self) -> Vec<IndexKey> {
|
||||
let mut keys = Vec::new();
|
||||
|
||||
// Add an index for customer_id
|
||||
keys.push(IndexKey {
|
||||
name: "customer_id",
|
||||
value: self.customer_id.to_string(),
|
||||
});
|
||||
|
||||
|
||||
|
||||
keys
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
use crate::models::biz::Currency; // Use crate:: for importing from the module
|
||||
use crate::db::{Model, Storable, DbError, DbResult}; // Import Model trait from db module
|
||||
use crate::db::{Model, Storable, DbError, DbResult, IndexKey}; // Import Model trait and IndexKey from db module
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
@ -34,9 +34,6 @@ pub struct ServiceItem {
|
||||
pub quantity: i32,
|
||||
pub unit_price: Currency,
|
||||
pub subtotal: Currency,
|
||||
pub tax_rate: f64,
|
||||
pub tax_amount: Currency,
|
||||
pub is_taxable: bool,
|
||||
pub active_till: DateTime<Utc>,
|
||||
}
|
||||
|
||||
@ -51,8 +48,6 @@ impl ServiceItem {
|
||||
comments: String,
|
||||
quantity: i32,
|
||||
unit_price: Currency,
|
||||
tax_rate: f64,
|
||||
is_taxable: bool,
|
||||
active_till: DateTime<Utc>,
|
||||
) -> Self {
|
||||
// Calculate subtotal
|
||||
@ -62,22 +57,7 @@ impl ServiceItem {
|
||||
amount,
|
||||
unit_price.currency_code.clone()
|
||||
);
|
||||
|
||||
// Calculate tax amount if taxable
|
||||
let tax_amount = if is_taxable {
|
||||
Currency::new(
|
||||
0, // Use 0 as a temporary ID
|
||||
subtotal.amount * tax_rate,
|
||||
unit_price.currency_code.clone()
|
||||
)
|
||||
} else {
|
||||
Currency::new(
|
||||
0, // Use 0 as a temporary ID
|
||||
0.0,
|
||||
unit_price.currency_code.clone()
|
||||
)
|
||||
};
|
||||
|
||||
|
||||
Self {
|
||||
id,
|
||||
service_id,
|
||||
@ -88,9 +68,6 @@ impl ServiceItem {
|
||||
quantity,
|
||||
unit_price,
|
||||
subtotal,
|
||||
tax_rate,
|
||||
tax_amount,
|
||||
is_taxable,
|
||||
active_till,
|
||||
}
|
||||
}
|
||||
@ -105,22 +82,6 @@ impl ServiceItem {
|
||||
);
|
||||
}
|
||||
|
||||
/// Calculate the tax amount based on subtotal and tax rate
|
||||
pub fn calculate_tax(&mut self) {
|
||||
if self.is_taxable {
|
||||
self.tax_amount = Currency::new(
|
||||
0, // Use 0 as a temporary ID
|
||||
self.subtotal.amount * self.tax_rate,
|
||||
self.subtotal.currency_code.clone()
|
||||
);
|
||||
} else {
|
||||
self.tax_amount = Currency::new(
|
||||
0, // Use 0 as a temporary ID
|
||||
0.0,
|
||||
self.subtotal.currency_code.clone()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Builder for ServiceItem
|
||||
@ -266,9 +227,6 @@ impl ServiceItemBuilder {
|
||||
quantity,
|
||||
unit_price,
|
||||
subtotal,
|
||||
tax_rate,
|
||||
tax_amount,
|
||||
is_taxable,
|
||||
active_till: self.active_till.ok_or("active_till is required")?,
|
||||
})
|
||||
}
|
||||
@ -321,13 +279,13 @@ impl Service {
|
||||
// First item, initialize the total amount with the same currency
|
||||
self.total_amount = Currency::new(
|
||||
0, // Use 0 as a temporary ID
|
||||
item.subtotal.amount + item.tax_amount.amount,
|
||||
item.subtotal.amount ,
|
||||
item.subtotal.currency_code.clone()
|
||||
);
|
||||
} else {
|
||||
// Add to the existing total
|
||||
// (Assumes all items have the same currency)
|
||||
self.total_amount.amount += item.subtotal.amount + item.tax_amount.amount;
|
||||
self.total_amount.amount += item.subtotal.amount;
|
||||
}
|
||||
|
||||
// Add the item to the list
|
||||
@ -349,7 +307,7 @@ impl Service {
|
||||
// Calculate the total amount
|
||||
let mut total = 0.0;
|
||||
for item in &self.items {
|
||||
total += item.subtotal.amount + item.tax_amount.amount;
|
||||
total += item.subtotal.amount;
|
||||
}
|
||||
|
||||
// Update the total amount
|
||||
@ -467,13 +425,13 @@ impl ServiceBuilder {
|
||||
// First item, initialize the total amount with the same currency
|
||||
total_amount = Currency::new(
|
||||
0, // Use 0 as a temporary ID
|
||||
item.subtotal.amount + item.tax_amount.amount,
|
||||
item.subtotal.amount,
|
||||
item.subtotal.currency_code.clone()
|
||||
);
|
||||
} else {
|
||||
// Add to the existing total
|
||||
// (Assumes all items have the same currency)
|
||||
total_amount.amount += item.subtotal.amount + item.tax_amount.amount;
|
||||
total_amount.amount += item.subtotal.amount ;
|
||||
}
|
||||
}
|
||||
|
||||
@ -504,4 +462,17 @@ impl Model for Service {
|
||||
fn db_prefix() -> &'static str {
|
||||
"service"
|
||||
}
|
||||
|
||||
fn db_keys(&self) -> Vec<IndexKey> {
|
||||
let mut keys = Vec::new();
|
||||
|
||||
// Add an index for customer_id
|
||||
keys.push(IndexKey {
|
||||
name: "customer_id",
|
||||
value: self.customer_id.to_string(),
|
||||
});
|
||||
|
||||
|
||||
keys
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user