...
This commit is contained in:
		@@ -7,7 +7,7 @@
 | 
			
		||||
pub mod db;
 | 
			
		||||
pub mod error;
 | 
			
		||||
pub mod models;
 | 
			
		||||
// pub mod rhaiengine;
 | 
			
		||||
pub mod rhaiengine;
 | 
			
		||||
 | 
			
		||||
// Re-exports
 | 
			
		||||
pub use error::Error;
 | 
			
		||||
 
 | 
			
		||||
@@ -9,27 +9,53 @@ The business models are implemented as Rust structs and enums with serialization
 | 
			
		||||
## Model Relationships
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
┌─────────────┐     ┌─────────────┐     ┌─────────────┐
 | 
			
		||||
│   Currency  │◄────┤   Product   │◄────┤  SaleItem   │
 | 
			
		||||
└─────────────┘     └─────────────┘     └──────┬──────┘
 | 
			
		||||
                          ▲                    │
 | 
			
		||||
                          │                    │
 | 
			
		||||
                    ┌─────┴──────────┐         │
 | 
			
		||||
                    │ProductComponent│         │
 | 
			
		||||
                    └────────────────┘         │
 | 
			
		||||
                                               ▼
 | 
			
		||||
                                        ┌─────────────┐
 | 
			
		||||
                                        │    Sale     │
 | 
			
		||||
                                        └─────────────┘
 | 
			
		||||
                                        │  Customer   │
 | 
			
		||||
                                        └──────┬──────┘
 | 
			
		||||
                                               │
 | 
			
		||||
                                               ▼
 | 
			
		||||
┌─────────────┐     ┌─────────────┐     ┌─────────────┐     ┌─────────────┐
 | 
			
		||||
│   Currency  │◄────┤   Product   │◄────┤  SaleItem   │◄────┤    Sale     │
 | 
			
		||||
└─────────────┘     └─────────────┘     └─────────────┘     └──────┬──────┘
 | 
			
		||||
                          ▲                                        │
 | 
			
		||||
                          │                                        │
 | 
			
		||||
                    ┌─────┴──────────┐                             │
 | 
			
		||||
                    │ProductComponent│                             │
 | 
			
		||||
                    └────────────────┘                             │
 | 
			
		||||
                                                                   │
 | 
			
		||||
┌─────────────┐     ┌─────────────┐     ┌─────────────┐            │
 | 
			
		||||
│   Currency  │◄────┤   Service   │◄────┤ ServiceItem │◄───────────┘
 | 
			
		||||
└─────────────┘     └─────────────┘     └─────────────┘
 | 
			
		||||
                                                                   │
 | 
			
		||||
                                                                   │
 | 
			
		||||
                                                                   ▼
 | 
			
		||||
                                        ┌─────────────┐     ┌─────────────┐
 | 
			
		||||
                                        │ InvoiceItem │◄────┤   Invoice   │
 | 
			
		||||
                                        └─────────────┘     └─────────────┘
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## Business Logic Relationships
 | 
			
		||||
 | 
			
		||||
- **Customer**: The entity purchasing products or services
 | 
			
		||||
- **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
 | 
			
		||||
- **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
 | 
			
		||||
  - Created from a Sale object to handle billing and payment tracking
 | 
			
		||||
 | 
			
		||||
## Root Objects
 | 
			
		||||
 | 
			
		||||
- root objects are the one who are stored in the DB
 | 
			
		||||
- Root Objects are
 | 
			
		||||
  - currency
 | 
			
		||||
  - product
 | 
			
		||||
- Root objects are the ones stored directly in the DB
 | 
			
		||||
- Root Objects are:
 | 
			
		||||
  - Customer
 | 
			
		||||
  - Currency
 | 
			
		||||
  - Product
 | 
			
		||||
  - Sale
 | 
			
		||||
  - Service
 | 
			
		||||
  - Invoice
 | 
			
		||||
 | 
			
		||||
## Models
 | 
			
		||||
 | 
			
		||||
@@ -44,6 +70,23 @@ Represents a monetary value with an amount and currency code.
 | 
			
		||||
**Builder:**
 | 
			
		||||
- `CurrencyBuilder` - Provides a fluent interface for creating Currency instances
 | 
			
		||||
 | 
			
		||||
### Customer (Root Object)
 | 
			
		||||
 | 
			
		||||
Represents a customer who can purchase products or services.
 | 
			
		||||
 | 
			
		||||
**Properties:**
 | 
			
		||||
- `id`: u32 - Unique identifier
 | 
			
		||||
- `name`: String - Customer name
 | 
			
		||||
- `description`: String - Customer description
 | 
			
		||||
- `pubkey`: String - Customer's public key
 | 
			
		||||
- `contact_ids`: Vec<u32> - List of contact IDs
 | 
			
		||||
- `created_at`: DateTime<Utc> - Creation timestamp
 | 
			
		||||
- `updated_at`: DateTime<Utc> - Last update timestamp
 | 
			
		||||
 | 
			
		||||
**Methods:**
 | 
			
		||||
- `add_contact()` - Adds a contact ID to the customer
 | 
			
		||||
- `remove_contact()` - Removes a contact ID from the customer
 | 
			
		||||
 | 
			
		||||
### Product
 | 
			
		||||
 | 
			
		||||
#### ProductType Enum
 | 
			
		||||
@@ -70,11 +113,11 @@ Represents a component part of a product.
 | 
			
		||||
**Builder:**
 | 
			
		||||
- `ProductComponentBuilder` - Provides a fluent interface for creating ProductComponent instances
 | 
			
		||||
 | 
			
		||||
#### Product  (Root Object)
 | 
			
		||||
#### Product (Root Object)
 | 
			
		||||
Represents a product or service offered.
 | 
			
		||||
 | 
			
		||||
**Properties:**
 | 
			
		||||
- `id`: u32 - Unique identifier
 | 
			
		||||
- `id`: i64 - Unique identifier
 | 
			
		||||
- `name`: String - Product name
 | 
			
		||||
- `description`: String - Product description
 | 
			
		||||
- `price`: Currency - Product price
 | 
			
		||||
@@ -83,10 +126,11 @@ Represents a product or service offered.
 | 
			
		||||
- `status`: ProductStatus - Available or Unavailable
 | 
			
		||||
- `created_at`: DateTime<Utc> - Creation timestamp
 | 
			
		||||
- `updated_at`: DateTime<Utc> - Last update timestamp
 | 
			
		||||
- `max_amount`: u16 - Maximum quantity available
 | 
			
		||||
- `max_amount`: i64 - Maximum quantity available
 | 
			
		||||
- `purchase_till`: DateTime<Utc> - Deadline for purchasing
 | 
			
		||||
- `active_till`: DateTime<Utc> - When product/service expires
 | 
			
		||||
- `components`: Vec<ProductComponent> - List of product components
 | 
			
		||||
- `is_template`: bool - Whether this is a template product (to be added)
 | 
			
		||||
 | 
			
		||||
**Methods:**
 | 
			
		||||
- `add_component()` - Adds a component to this product
 | 
			
		||||
@@ -104,7 +148,62 @@ Represents a product or service offered.
 | 
			
		||||
  - `get_id()` - Returns the ID as a string
 | 
			
		||||
  - `db_prefix()` - Returns "product" as the database prefix
 | 
			
		||||
 | 
			
		||||
### Sale  
 | 
			
		||||
### Service (Root Object)
 | 
			
		||||
 | 
			
		||||
#### BillingFrequency Enum
 | 
			
		||||
Defines how often a service is billed:
 | 
			
		||||
- `Hourly` - Billed by the hour
 | 
			
		||||
- `Daily` - Billed daily
 | 
			
		||||
- `Weekly` - Billed weekly
 | 
			
		||||
- `Monthly` - Billed monthly
 | 
			
		||||
- `Yearly` - Billed yearly
 | 
			
		||||
 | 
			
		||||
#### ServiceStatus Enum
 | 
			
		||||
Tracks the status of a service:
 | 
			
		||||
- `Active` - Service is currently active
 | 
			
		||||
- `Paused` - Service is temporarily paused
 | 
			
		||||
- `Cancelled` - Service has been cancelled
 | 
			
		||||
- `Completed` - Service has been completed
 | 
			
		||||
 | 
			
		||||
#### ServiceItem
 | 
			
		||||
Represents an item within a service.
 | 
			
		||||
 | 
			
		||||
**Properties:**
 | 
			
		||||
- `id`: u32 - Unique identifier
 | 
			
		||||
- `service_id`: u32 - Parent service ID
 | 
			
		||||
- `product_id`: u32 - ID of the product this service is based on
 | 
			
		||||
- `name`: String - Service name
 | 
			
		||||
- `description`: String - Detailed description of the service item
 | 
			
		||||
- `comments`: String - Additional notes or comments about the service item
 | 
			
		||||
- `quantity`: i32 - Number of units
 | 
			
		||||
- `unit_price`: Currency - Price per unit
 | 
			
		||||
- `subtotal`: Currency - Total price before tax
 | 
			
		||||
- `tax_rate`: f64 - Tax rate as a percentage
 | 
			
		||||
- `tax_amount`: Currency - Calculated tax amount
 | 
			
		||||
- `is_taxable`: bool - Whether this item is taxable
 | 
			
		||||
- `active_till`: DateTime<Utc> - When service expires
 | 
			
		||||
 | 
			
		||||
#### Service
 | 
			
		||||
Represents an ongoing service provided to a customer.
 | 
			
		||||
 | 
			
		||||
**Properties:**
 | 
			
		||||
- `id`: u32 - Unique identifier
 | 
			
		||||
- `customer_id`: u32 - ID of the customer receiving the service
 | 
			
		||||
- `total_amount`: Currency - Total service amount including tax
 | 
			
		||||
- `status`: ServiceStatus - Current service status
 | 
			
		||||
- `billing_frequency`: BillingFrequency - How often the service is billed
 | 
			
		||||
- `service_date`: DateTime<Utc> - When service started
 | 
			
		||||
- `created_at`: DateTime<Utc> - Creation timestamp
 | 
			
		||||
- `updated_at`: DateTime<Utc> - Last update timestamp
 | 
			
		||||
- `items`: Vec<ServiceItem> - List of items in the service
 | 
			
		||||
- `is_template`: bool - Whether this is a template service (to be added)
 | 
			
		||||
 | 
			
		||||
**Methods:**
 | 
			
		||||
- `add_item()` - Adds an item to the service and updates total
 | 
			
		||||
- `calculate_total()` - Recalculates the total amount
 | 
			
		||||
- `update_status()` - Updates the status of the service
 | 
			
		||||
 | 
			
		||||
### Sale
 | 
			
		||||
 | 
			
		||||
#### SaleStatus Enum
 | 
			
		||||
Tracks the status of a sale:
 | 
			
		||||
@@ -120,11 +219,18 @@ Represents an item within a sale.
 | 
			
		||||
- `sale_id`: u32 - Parent sale ID
 | 
			
		||||
- `product_id`: u32 - ID of the product sold
 | 
			
		||||
- `name`: String - Product 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
 | 
			
		||||
- `unit_price`: Currency - Price per unit
 | 
			
		||||
- `subtotal`: Currency - Total price for this item (calculated)
 | 
			
		||||
- `subtotal`: Currency - Total price for this item before tax (calculated)
 | 
			
		||||
- `tax_rate`: f64 - Tax rate as a percentage (e.g., 20.0 for 20%)
 | 
			
		||||
- `tax_amount`: Currency - Calculated tax amount for this item
 | 
			
		||||
- `active_till`: DateTime<Utc> - When item/service expires
 | 
			
		||||
 | 
			
		||||
**Methods:**
 | 
			
		||||
- `total_with_tax()` - Returns the total amount including tax
 | 
			
		||||
 | 
			
		||||
**Builder:**
 | 
			
		||||
- `SaleItemBuilder` - Provides a fluent interface for creating SaleItem instances
 | 
			
		||||
 | 
			
		||||
@@ -134,18 +240,24 @@ Represents a complete sale transaction.
 | 
			
		||||
**Properties:**
 | 
			
		||||
- `id`: u32 - Unique identifier
 | 
			
		||||
- `company_id`: u32 - ID of the company making the sale
 | 
			
		||||
- `customer_id`: u32 - ID of the customer making the purchase (to be added)
 | 
			
		||||
- `buyer_name`: String - Name of the buyer
 | 
			
		||||
- `buyer_email`: String - Email of the buyer
 | 
			
		||||
- `total_amount`: Currency - Total sale amount
 | 
			
		||||
- `subtotal_amount`: Currency - Total sale amount before tax
 | 
			
		||||
- `tax_amount`: Currency - Total tax amount for the sale
 | 
			
		||||
- `total_amount`: Currency - Total sale amount including tax
 | 
			
		||||
- `status`: SaleStatus - Current sale status
 | 
			
		||||
- `service_id`: Option<u32> - ID of the service created from this sale (to be added)
 | 
			
		||||
- `sale_date`: DateTime<Utc> - When sale occurred
 | 
			
		||||
- `created_at`: DateTime<Utc> - Creation timestamp
 | 
			
		||||
- `updated_at`: DateTime<Utc> - Last update timestamp
 | 
			
		||||
- `items`: Vec<SaleItem> - List of items in the sale
 | 
			
		||||
 | 
			
		||||
**Methods:**
 | 
			
		||||
- `add_item()` - Adds an item to the sale and updates total
 | 
			
		||||
- `add_item()` - Adds an item to the sale and updates totals
 | 
			
		||||
- `update_status()` - Updates the status of the sale
 | 
			
		||||
- `recalculate_totals()` - Recalculates all totals based on items
 | 
			
		||||
- `create_service()` - Creates a service from this sale (to be added)
 | 
			
		||||
 | 
			
		||||
**Builder:**
 | 
			
		||||
- `SaleBuilder` - Provides a fluent interface for creating Sale instances
 | 
			
		||||
@@ -223,6 +335,7 @@ let item = SaleItemBuilder::new()
 | 
			
		||||
    .name("Premium Service")
 | 
			
		||||
    .quantity(1)
 | 
			
		||||
    .unit_price(unit_price)
 | 
			
		||||
    .tax_rate(20.0) // 20% tax rate
 | 
			
		||||
    .active_till(now + Duration::days(30))
 | 
			
		||||
    .build()
 | 
			
		||||
    .expect("Failed to build sale item");
 | 
			
		||||
@@ -241,6 +354,29 @@ let mut sale = SaleBuilder::new()
 | 
			
		||||
 | 
			
		||||
// Update the sale status
 | 
			
		||||
sale.update_status(SaleStatus::Completed);
 | 
			
		||||
 | 
			
		||||
// The sale now contains:
 | 
			
		||||
// - subtotal_amount: The sum of all items before tax
 | 
			
		||||
// - tax_amount: The sum of all tax amounts
 | 
			
		||||
// - total_amount: The total including tax
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### Relationship Between Sale and Invoice
 | 
			
		||||
 | 
			
		||||
The Sale model represents what is sold to a customer (products or services), including tax calculations. The Invoice model represents the billing document for that sale.
 | 
			
		||||
 | 
			
		||||
An InvoiceItem can be linked to a Sale via the `sale_id` field, establishing a connection between what was sold and how it's billed.
 | 
			
		||||
 | 
			
		||||
```rust
 | 
			
		||||
// Create an invoice item linked to a sale
 | 
			
		||||
let invoice_item = InvoiceItemBuilder::new()
 | 
			
		||||
    .id(1)
 | 
			
		||||
    .invoice_id(1)
 | 
			
		||||
    .description("Premium Service")
 | 
			
		||||
    .amount(sale.total_amount.clone()) // Use the total amount from the sale
 | 
			
		||||
    .sale_id(sale.id) // Link to the sale
 | 
			
		||||
    .build()
 | 
			
		||||
    .expect("Failed to build invoice item");
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## Database Operations
 | 
			
		||||
@@ -266,4 +402,125 @@ These methods are available for all root objects:
 | 
			
		||||
- `insert_product`, `get_product`, `delete_product`, `list_products` for Product
 | 
			
		||||
- `insert_currency`, `get_currency`, `delete_currency`, `list_currencies` for Currency
 | 
			
		||||
- `insert_sale`, `get_sale`, `delete_sale`, `list_sales` for Sale
 | 
			
		||||
- `insert_service`, `get_service`, `delete_service`, `list_services` for Service
 | 
			
		||||
- `insert_invoice`, `get_invoice`, `delete_invoice`, `list_invoices` for Invoice
 | 
			
		||||
- `insert_customer`, `get_customer`, `delete_customer`, `list_customers` for Customer
 | 
			
		||||
 | 
			
		||||
### Invoice (Root Object)
 | 
			
		||||
 | 
			
		||||
#### InvoiceStatus Enum
 | 
			
		||||
Tracks the status of an invoice:
 | 
			
		||||
- `Draft` - Invoice is in draft state
 | 
			
		||||
- `Sent` - Invoice has been sent to the customer
 | 
			
		||||
- `Paid` - Invoice has been paid
 | 
			
		||||
- `Overdue` - Invoice is past due date
 | 
			
		||||
- `Cancelled` - Invoice has been cancelled
 | 
			
		||||
 | 
			
		||||
#### PaymentStatus Enum
 | 
			
		||||
Tracks the payment status of an invoice:
 | 
			
		||||
- `Unpaid` - Invoice has not been paid
 | 
			
		||||
- `PartiallyPaid` - Invoice has been partially paid
 | 
			
		||||
- `Paid` - Invoice has been fully paid
 | 
			
		||||
 | 
			
		||||
#### Payment
 | 
			
		||||
Represents a payment made against an invoice.
 | 
			
		||||
 | 
			
		||||
**Properties:**
 | 
			
		||||
- `amount`: Currency - Payment amount
 | 
			
		||||
- `date`: DateTime<Utc> - Payment date
 | 
			
		||||
- `method`: String - Payment method
 | 
			
		||||
- `comment`: String - Payment comment
 | 
			
		||||
 | 
			
		||||
#### InvoiceItem
 | 
			
		||||
Represents an item in an invoice.
 | 
			
		||||
 | 
			
		||||
**Properties:**
 | 
			
		||||
- `id`: u32 - Unique identifier
 | 
			
		||||
- `invoice_id`: u32 - Parent invoice ID
 | 
			
		||||
- `description`: String - Item description
 | 
			
		||||
- `amount`: Currency - Item amount
 | 
			
		||||
- `service_id`: Option<u32> - ID of the service this item is for
 | 
			
		||||
- `sale_id`: Option<u32> - ID of the sale this item is for
 | 
			
		||||
 | 
			
		||||
**Methods:**
 | 
			
		||||
- `link_to_service()` - Links the invoice item to a service
 | 
			
		||||
- `link_to_sale()` - Links the invoice item to a sale
 | 
			
		||||
 | 
			
		||||
#### Invoice
 | 
			
		||||
Represents an invoice sent to a customer.
 | 
			
		||||
 | 
			
		||||
**Properties:**
 | 
			
		||||
- `id`: u32 - Unique identifier
 | 
			
		||||
- `customer_id`: u32 - ID of the customer being invoiced
 | 
			
		||||
- `total_amount`: Currency - Total invoice amount
 | 
			
		||||
- `balance_due`: Currency - Amount still due
 | 
			
		||||
- `status`: InvoiceStatus - Current invoice status
 | 
			
		||||
- `payment_status`: PaymentStatus - Current payment status
 | 
			
		||||
- `issue_date`: DateTime<Utc> - When invoice was issued
 | 
			
		||||
- `due_date`: DateTime<Utc> - When payment is due
 | 
			
		||||
- `created_at`: DateTime<Utc> - Creation timestamp
 | 
			
		||||
- `updated_at`: DateTime<Utc> - Last update timestamp
 | 
			
		||||
- `items`: Vec<InvoiceItem> - List of items in the invoice
 | 
			
		||||
- `payments`: Vec<Payment> - List of payments made
 | 
			
		||||
 | 
			
		||||
**Methods:**
 | 
			
		||||
- `add_item()` - Adds an item to the invoice
 | 
			
		||||
- `calculate_total()` - Calculates the total amount
 | 
			
		||||
- `add_payment()` - Adds a payment to the invoice
 | 
			
		||||
- `calculate_balance()` - Calculates the balance due
 | 
			
		||||
- `update_payment_status()` - Updates the payment status
 | 
			
		||||
- `update_status()` - Updates the status of the invoice
 | 
			
		||||
- `is_overdue()` - Checks if the invoice is overdue
 | 
			
		||||
- `check_if_overdue()` - Marks the invoice as overdue if past due date
 | 
			
		||||
 | 
			
		||||
### Relationships Between Models
 | 
			
		||||
 | 
			
		||||
#### Product/Service Templates and Instances
 | 
			
		||||
 | 
			
		||||
Products and Services can be marked as templates (`is_template=true`). When a customer purchases a product or service, a copy is created from the template with the specific details of what was sold.
 | 
			
		||||
 | 
			
		||||
#### Sale to Service Relationship
 | 
			
		||||
 | 
			
		||||
When a product of type `Service` is sold, a Service instance can be created from the Sale:
 | 
			
		||||
 | 
			
		||||
```rust
 | 
			
		||||
// Create a service from a sale
 | 
			
		||||
let service = sale.create_service(
 | 
			
		||||
    service_id,
 | 
			
		||||
    ServiceStatus::Active,
 | 
			
		||||
    BillingFrequency::Monthly
 | 
			
		||||
);
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### Sale to Invoice Relationship
 | 
			
		||||
 | 
			
		||||
An Invoice is created from a Sale to handle billing and payment tracking:
 | 
			
		||||
 | 
			
		||||
```rust
 | 
			
		||||
// Create an invoice from a sale
 | 
			
		||||
let invoice = Invoice::from_sale(
 | 
			
		||||
    invoice_id,
 | 
			
		||||
    sale,
 | 
			
		||||
    due_date
 | 
			
		||||
);
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### Customer-Centric View
 | 
			
		||||
 | 
			
		||||
The models allow tracking all customer interactions:
 | 
			
		||||
 | 
			
		||||
- What products/services they've purchased (via Sale records)
 | 
			
		||||
- What ongoing services they have (via Service records)
 | 
			
		||||
- What they've been invoiced for (via Invoice records)
 | 
			
		||||
- What they've paid (via Payment records in Invoices)
 | 
			
		||||
 | 
			
		||||
```rust
 | 
			
		||||
// Get all sales for a customer
 | 
			
		||||
let customer_sales = db.list_sales_by_customer(customer_id);
 | 
			
		||||
 | 
			
		||||
// Get all services for a customer
 | 
			
		||||
let customer_services = db.list_services_by_customer(customer_id);
 | 
			
		||||
 | 
			
		||||
// Get all invoices for a customer
 | 
			
		||||
let customer_invoices = db.list_invoices_by_customer(customer_id);
 | 
			
		||||
```
 | 
			
		||||
 
 | 
			
		||||
@@ -1,371 +0,0 @@
 | 
			
		||||
# Business Models Implementation Plan
 | 
			
		||||
 | 
			
		||||
## Overview
 | 
			
		||||
 | 
			
		||||
This document outlines the plan for implementing new business models in the codebase:
 | 
			
		||||
 | 
			
		||||
1. **Service**: For tracking recurring payments (similar to Sale)
 | 
			
		||||
2. **Customer**: For storing customer information
 | 
			
		||||
3. **Contract**: For linking services or sales to customers
 | 
			
		||||
4. **Invoice**: For invoicing customers
 | 
			
		||||
 | 
			
		||||
## Model Diagrams
 | 
			
		||||
 | 
			
		||||
### Core Models and Relationships
 | 
			
		||||
 | 
			
		||||
```mermaid
 | 
			
		||||
classDiagram
 | 
			
		||||
    class Service {
 | 
			
		||||
        +id: u32
 | 
			
		||||
        +customer_id: u32
 | 
			
		||||
        +total_amount: Currency
 | 
			
		||||
        +status: ServiceStatus
 | 
			
		||||
        +billing_frequency: BillingFrequency
 | 
			
		||||
        +service_date: DateTime~Utc~
 | 
			
		||||
        +created_at: DateTime~Utc~
 | 
			
		||||
        +updated_at: DateTime~Utc~
 | 
			
		||||
        +items: Vec~ServiceItem~
 | 
			
		||||
        +calculate_total()
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    class ServiceItem {
 | 
			
		||||
        +id: u32
 | 
			
		||||
        +service_id: u32
 | 
			
		||||
        +name: String
 | 
			
		||||
        +quantity: i32
 | 
			
		||||
        +unit_price: Currency
 | 
			
		||||
        +subtotal: Currency
 | 
			
		||||
        +tax_rate: f64
 | 
			
		||||
        +tax_amount: Currency
 | 
			
		||||
        +is_taxable: bool
 | 
			
		||||
        +active_till: DateTime~Utc~
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    class Customer {
 | 
			
		||||
        +id: u32
 | 
			
		||||
        +name: String
 | 
			
		||||
        +description: String
 | 
			
		||||
        +pubkey: String
 | 
			
		||||
        +contact_ids: Vec~u32~
 | 
			
		||||
        +created_at: DateTime~Utc~
 | 
			
		||||
        +updated_at: DateTime~Utc~
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    class Contract {
 | 
			
		||||
        +id: u32
 | 
			
		||||
        +customer_id: u32
 | 
			
		||||
        +service_id: Option~u32~
 | 
			
		||||
        +sale_id: Option~u32~
 | 
			
		||||
        +terms: String
 | 
			
		||||
        +start_date: DateTime~Utc~
 | 
			
		||||
        +end_date: DateTime~Utc~
 | 
			
		||||
        +auto_renewal: bool
 | 
			
		||||
        +renewal_terms: String
 | 
			
		||||
        +status: ContractStatus
 | 
			
		||||
        +created_at: DateTime~Utc~
 | 
			
		||||
        +updated_at: DateTime~Utc~
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    class Invoice {
 | 
			
		||||
        +id: u32
 | 
			
		||||
        +customer_id: u32
 | 
			
		||||
        +total_amount: Currency
 | 
			
		||||
        +balance_due: Currency
 | 
			
		||||
        +status: InvoiceStatus
 | 
			
		||||
        +payment_status: PaymentStatus
 | 
			
		||||
        +issue_date: DateTime~Utc~
 | 
			
		||||
        +due_date: DateTime~Utc~
 | 
			
		||||
        +created_at: DateTime~Utc~
 | 
			
		||||
        +updated_at: DateTime~Utc~
 | 
			
		||||
        +items: Vec~InvoiceItem~
 | 
			
		||||
        +payments: Vec~Payment~
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    class InvoiceItem {
 | 
			
		||||
        +id: u32
 | 
			
		||||
        +invoice_id: u32
 | 
			
		||||
        +description: String
 | 
			
		||||
        +amount: Currency
 | 
			
		||||
        +service_id: Option~u32~
 | 
			
		||||
        +sale_id: Option~u32~
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    class Payment {
 | 
			
		||||
        +amount: Currency
 | 
			
		||||
        +date: DateTime~Utc~
 | 
			
		||||
        +method: String
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    Service "1" -- "many" ServiceItem : contains
 | 
			
		||||
    Customer "1" -- "many" Service : has
 | 
			
		||||
    Customer "1" -- "many" Contract : has
 | 
			
		||||
    Contract "1" -- "0..1" Service : references
 | 
			
		||||
    Contract "1" -- "0..1" Sale : references
 | 
			
		||||
    Invoice "1" -- "many" InvoiceItem : contains
 | 
			
		||||
    Invoice "1" -- "many" Payment : contains
 | 
			
		||||
    Customer "1" -- "many" Invoice : has
 | 
			
		||||
    InvoiceItem "1" -- "0..1" Service : references
 | 
			
		||||
    InvoiceItem "1" -- "0..1" Sale : references
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### Enums and Supporting Types
 | 
			
		||||
 | 
			
		||||
```mermaid
 | 
			
		||||
classDiagram
 | 
			
		||||
    class BillingFrequency {
 | 
			
		||||
        <<enumeration>>
 | 
			
		||||
        Hourly
 | 
			
		||||
        Daily
 | 
			
		||||
        Weekly
 | 
			
		||||
        Monthly
 | 
			
		||||
        Yearly
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    class ServiceStatus {
 | 
			
		||||
        <<enumeration>>
 | 
			
		||||
        Active
 | 
			
		||||
        Paused
 | 
			
		||||
        Cancelled
 | 
			
		||||
        Completed
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    class ContractStatus {
 | 
			
		||||
        <<enumeration>>
 | 
			
		||||
        Active
 | 
			
		||||
        Expired
 | 
			
		||||
        Terminated
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    class InvoiceStatus {
 | 
			
		||||
        <<enumeration>>
 | 
			
		||||
        Draft
 | 
			
		||||
        Sent
 | 
			
		||||
        Paid
 | 
			
		||||
        Overdue
 | 
			
		||||
        Cancelled
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    class PaymentStatus {
 | 
			
		||||
        <<enumeration>>
 | 
			
		||||
        Unpaid
 | 
			
		||||
        PartiallyPaid
 | 
			
		||||
        Paid
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    Service -- ServiceStatus : has
 | 
			
		||||
    Service -- BillingFrequency : has
 | 
			
		||||
    Contract -- ContractStatus : has
 | 
			
		||||
    Invoice -- InvoiceStatus : has
 | 
			
		||||
    Invoice -- PaymentStatus : has
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## Detailed Implementation Plan
 | 
			
		||||
 | 
			
		||||
### 1. Service and ServiceItem (service.rs)
 | 
			
		||||
 | 
			
		||||
The Service model will be similar to Sale but designed for recurring payments:
 | 
			
		||||
 | 
			
		||||
- **Service**: Main struct for tracking recurring services
 | 
			
		||||
  - Fields: 
 | 
			
		||||
    - id: u32
 | 
			
		||||
    - customer_id: u32
 | 
			
		||||
    - total_amount: Currency
 | 
			
		||||
    - status: ServiceStatus
 | 
			
		||||
    - billing_frequency: BillingFrequency
 | 
			
		||||
    - service_date: DateTime<Utc>
 | 
			
		||||
    - created_at: DateTime<Utc>
 | 
			
		||||
    - updated_at: DateTime<Utc>
 | 
			
		||||
    - items: Vec<ServiceItem>
 | 
			
		||||
  - Methods: 
 | 
			
		||||
    - calculate_total(): Updates the total_amount based on all items
 | 
			
		||||
    - add_item(item: ServiceItem): Adds an item and updates the total
 | 
			
		||||
    - update_status(status: ServiceStatus): Updates the status and timestamp
 | 
			
		||||
  
 | 
			
		||||
- **ServiceItem**: Items within a service (similar to SaleItem)
 | 
			
		||||
  - Fields: 
 | 
			
		||||
    - id: u32
 | 
			
		||||
    - service_id: u32
 | 
			
		||||
    - name: String
 | 
			
		||||
    - quantity: i32
 | 
			
		||||
    - unit_price: Currency
 | 
			
		||||
    - subtotal: Currency
 | 
			
		||||
    - tax_rate: f64
 | 
			
		||||
    - tax_amount: Currency
 | 
			
		||||
    - is_taxable: bool
 | 
			
		||||
    - active_till: DateTime<Utc>
 | 
			
		||||
  - Methods: 
 | 
			
		||||
    - calculate_subtotal(): Calculates subtotal based on quantity and unit_price
 | 
			
		||||
    - calculate_tax(): Calculates tax amount based on subtotal and tax_rate
 | 
			
		||||
 | 
			
		||||
- **BillingFrequency**: Enum for different billing periods
 | 
			
		||||
  - Variants: Hourly, Daily, Weekly, Monthly, Yearly
 | 
			
		||||
 | 
			
		||||
- **ServiceStatus**: Enum for service status
 | 
			
		||||
  - Variants: Active, Paused, Cancelled, Completed
 | 
			
		||||
 | 
			
		||||
### 2. Customer (customer.rs)
 | 
			
		||||
 | 
			
		||||
The Customer model will store customer information:
 | 
			
		||||
 | 
			
		||||
- **Customer**: Main struct for customer data
 | 
			
		||||
  - Fields: 
 | 
			
		||||
    - id: u32
 | 
			
		||||
    - name: String
 | 
			
		||||
    - description: String
 | 
			
		||||
    - pubkey: String
 | 
			
		||||
    - contact_ids: Vec<u32>
 | 
			
		||||
    - created_at: DateTime<Utc>
 | 
			
		||||
    - updated_at: DateTime<Utc>
 | 
			
		||||
  - Methods: 
 | 
			
		||||
    - add_contact(contact_id: u32): Adds a contact ID to the list
 | 
			
		||||
    - remove_contact(contact_id: u32): Removes a contact ID from the list
 | 
			
		||||
 | 
			
		||||
### 3. Contract (contract.rs)
 | 
			
		||||
 | 
			
		||||
The Contract model will link services or sales to customers:
 | 
			
		||||
 | 
			
		||||
- **Contract**: Main struct for contract data
 | 
			
		||||
  - Fields: 
 | 
			
		||||
    - id: u32
 | 
			
		||||
    - customer_id: u32
 | 
			
		||||
    - service_id: Option<u32>
 | 
			
		||||
    - sale_id: Option<u32>
 | 
			
		||||
    - terms: String
 | 
			
		||||
    - start_date: DateTime<Utc>
 | 
			
		||||
    - end_date: DateTime<Utc>
 | 
			
		||||
    - auto_renewal: bool
 | 
			
		||||
    - renewal_terms: String
 | 
			
		||||
    - status: ContractStatus
 | 
			
		||||
    - created_at: DateTime<Utc>
 | 
			
		||||
    - updated_at: DateTime<Utc>
 | 
			
		||||
  - Methods: 
 | 
			
		||||
    - is_active(): bool - Checks if the contract is currently active
 | 
			
		||||
    - is_expired(): bool - Checks if the contract has expired
 | 
			
		||||
    - renew(): Updates the contract dates based on renewal terms
 | 
			
		||||
 | 
			
		||||
- **ContractStatus**: Enum for contract status
 | 
			
		||||
  - Variants: Active, Expired, Terminated
 | 
			
		||||
 | 
			
		||||
### 4. Invoice (invoice.rs)
 | 
			
		||||
 | 
			
		||||
The Invoice model will handle billing:
 | 
			
		||||
 | 
			
		||||
- **Invoice**: Main struct for invoice data
 | 
			
		||||
  - Fields: 
 | 
			
		||||
    - id: u32
 | 
			
		||||
    - customer_id: u32
 | 
			
		||||
    - total_amount: Currency
 | 
			
		||||
    - balance_due: Currency
 | 
			
		||||
    - status: InvoiceStatus
 | 
			
		||||
    - payment_status: PaymentStatus
 | 
			
		||||
    - issue_date: DateTime<Utc>
 | 
			
		||||
    - due_date: DateTime<Utc>
 | 
			
		||||
    - created_at: DateTime<Utc>
 | 
			
		||||
    - updated_at: DateTime<Utc>
 | 
			
		||||
    - items: Vec<InvoiceItem>
 | 
			
		||||
    - payments: Vec<Payment>
 | 
			
		||||
  - Methods: 
 | 
			
		||||
    - calculate_total(): Updates the total_amount based on all items
 | 
			
		||||
    - add_item(item: InvoiceItem): Adds an item and updates the total
 | 
			
		||||
    - add_payment(payment: Payment): Adds a payment and updates balance_due and payment_status
 | 
			
		||||
    - update_status(status: InvoiceStatus): Updates the status and timestamp
 | 
			
		||||
    - calculate_balance(): Updates the balance_due based on total_amount and payments
 | 
			
		||||
 | 
			
		||||
- **InvoiceItem**: Items within an invoice
 | 
			
		||||
  - Fields: 
 | 
			
		||||
    - id: u32
 | 
			
		||||
    - invoice_id: u32
 | 
			
		||||
    - description: String
 | 
			
		||||
    - amount: Currency
 | 
			
		||||
    - service_id: Option<u32>
 | 
			
		||||
    - sale_id: Option<u32>
 | 
			
		||||
 | 
			
		||||
- **Payment**: Struct for tracking payments
 | 
			
		||||
  - Fields:
 | 
			
		||||
    - amount: Currency
 | 
			
		||||
    - date: DateTime<Utc>
 | 
			
		||||
    - method: String
 | 
			
		||||
 | 
			
		||||
- **InvoiceStatus**: Enum for invoice status
 | 
			
		||||
  - Variants: Draft, Sent, Paid, Overdue, Cancelled
 | 
			
		||||
 | 
			
		||||
- **PaymentStatus**: Enum for payment status
 | 
			
		||||
  - Variants: Unpaid, PartiallyPaid, Paid
 | 
			
		||||
 | 
			
		||||
### 5. Updates to mod.rs
 | 
			
		||||
 | 
			
		||||
We'll need to update the mod.rs file to include the new modules and re-export the types:
 | 
			
		||||
 | 
			
		||||
```rust
 | 
			
		||||
pub mod currency;
 | 
			
		||||
pub mod product;
 | 
			
		||||
pub mod sale;
 | 
			
		||||
pub mod exchange_rate;
 | 
			
		||||
pub mod service;
 | 
			
		||||
pub mod customer;
 | 
			
		||||
pub mod contract;
 | 
			
		||||
pub mod invoice;
 | 
			
		||||
 | 
			
		||||
// Re-export all model types for convenience
 | 
			
		||||
pub use product::{Product, ProductComponent, ProductType, ProductStatus};
 | 
			
		||||
pub use sale::{Sale, SaleItem, SaleStatus};
 | 
			
		||||
pub use currency::Currency;
 | 
			
		||||
pub use exchange_rate::{ExchangeRate, ExchangeRateService, EXCHANGE_RATE_SERVICE};
 | 
			
		||||
pub use service::{Service, ServiceItem, ServiceStatus, BillingFrequency};
 | 
			
		||||
pub use customer::Customer;
 | 
			
		||||
pub use contract::{Contract, ContractStatus};
 | 
			
		||||
pub use invoice::{Invoice, InvoiceItem, InvoiceStatus, PaymentStatus, Payment};
 | 
			
		||||
 | 
			
		||||
// Re-export builder types
 | 
			
		||||
pub use product::{ProductBuilder, ProductComponentBuilder};
 | 
			
		||||
pub use sale::{SaleBuilder, SaleItemBuilder};
 | 
			
		||||
pub use currency::CurrencyBuilder;
 | 
			
		||||
pub use exchange_rate::ExchangeRateBuilder;
 | 
			
		||||
pub use service::{ServiceBuilder, ServiceItemBuilder};
 | 
			
		||||
pub use customer::CustomerBuilder;
 | 
			
		||||
pub use contract::ContractBuilder;
 | 
			
		||||
pub use invoice::{InvoiceBuilder, InvoiceItemBuilder};
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### 6. Updates to model_methods.rs
 | 
			
		||||
 | 
			
		||||
We'll need to update the model_methods.rs file to implement the model methods for the new models:
 | 
			
		||||
 | 
			
		||||
```rust
 | 
			
		||||
use crate::db::db::DB;
 | 
			
		||||
use crate::db::base::{SledDBResult, SledModel};
 | 
			
		||||
use crate::impl_model_methods;
 | 
			
		||||
use crate::models::biz::{Product, Sale, Currency, ExchangeRate, Service, Customer, Contract, Invoice};
 | 
			
		||||
 | 
			
		||||
// Implement model-specific methods for Product
 | 
			
		||||
impl_model_methods!(Product, product, products);
 | 
			
		||||
 | 
			
		||||
// Implement model-specific methods for Sale
 | 
			
		||||
impl_model_methods!(Sale, sale, sales);
 | 
			
		||||
 | 
			
		||||
// Implement model-specific methods for Currency
 | 
			
		||||
impl_model_methods!(Currency, currency, currencies);
 | 
			
		||||
 | 
			
		||||
// Implement model-specific methods for ExchangeRate
 | 
			
		||||
impl_model_methods!(ExchangeRate, exchange_rate, exchange_rates);
 | 
			
		||||
 | 
			
		||||
// Implement model-specific methods for Service
 | 
			
		||||
impl_model_methods!(Service, service, services);
 | 
			
		||||
 | 
			
		||||
// Implement model-specific methods for Customer
 | 
			
		||||
impl_model_methods!(Customer, customer, customers);
 | 
			
		||||
 | 
			
		||||
// Implement model-specific methods for Contract
 | 
			
		||||
impl_model_methods!(Contract, contract, contracts);
 | 
			
		||||
 | 
			
		||||
// Implement model-specific methods for Invoice
 | 
			
		||||
impl_model_methods!(Invoice, invoice, invoices);
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## Implementation Approach
 | 
			
		||||
 | 
			
		||||
1. Create the new model files (service.rs, customer.rs, contract.rs, invoice.rs)
 | 
			
		||||
2. Implement the structs, enums, and methods for each model
 | 
			
		||||
3. Update mod.rs to include the new modules and re-export the types
 | 
			
		||||
4. Update model_methods.rs to implement the model methods for the new models
 | 
			
		||||
5. Test the new models with example code
 | 
			
		||||
@@ -21,9 +21,13 @@ pub struct SaleItem {
 | 
			
		||||
    pub sale_id: u32,
 | 
			
		||||
    pub product_id: u32,
 | 
			
		||||
    pub name: String,
 | 
			
		||||
    pub description: String, // Description of the item
 | 
			
		||||
    pub comments: String, // Additional comments about the item
 | 
			
		||||
    pub quantity: i32,
 | 
			
		||||
    pub unit_price: Currency,
 | 
			
		||||
    pub subtotal: Currency,
 | 
			
		||||
    pub tax_rate: f64, // Tax rate as a percentage (e.g., 20.0 for 20%)
 | 
			
		||||
    pub tax_amount: Currency, // Calculated tax amount
 | 
			
		||||
    pub active_till: DateTime<Utc>, // after this product no longer active if e.g. a service
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -34,28 +38,50 @@ impl SaleItem {
 | 
			
		||||
        sale_id: u32,
 | 
			
		||||
        product_id: u32,
 | 
			
		||||
        name: String,
 | 
			
		||||
        description: String,
 | 
			
		||||
        comments: String,
 | 
			
		||||
        quantity: i32,
 | 
			
		||||
        unit_price: Currency,
 | 
			
		||||
        tax_rate: f64,
 | 
			
		||||
        active_till: DateTime<Utc>,
 | 
			
		||||
    ) -> Self {
 | 
			
		||||
        // Calculate subtotal
 | 
			
		||||
        // Calculate subtotal (before tax)
 | 
			
		||||
        let amount = unit_price.amount * quantity as f64;
 | 
			
		||||
        let subtotal = Currency {
 | 
			
		||||
            amount,
 | 
			
		||||
            currency_code: unit_price.currency_code.clone(),
 | 
			
		||||
        };
 | 
			
		||||
        
 | 
			
		||||
        // Calculate tax amount
 | 
			
		||||
        let tax_amount_value = subtotal.amount * (tax_rate / 100.0);
 | 
			
		||||
        let tax_amount = Currency {
 | 
			
		||||
            amount: tax_amount_value,
 | 
			
		||||
            currency_code: unit_price.currency_code.clone(),
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        Self {
 | 
			
		||||
            id,
 | 
			
		||||
            sale_id,
 | 
			
		||||
            product_id,
 | 
			
		||||
            name,
 | 
			
		||||
            description,
 | 
			
		||||
            comments,
 | 
			
		||||
            quantity,
 | 
			
		||||
            unit_price,
 | 
			
		||||
            subtotal,
 | 
			
		||||
            tax_rate,
 | 
			
		||||
            tax_amount,
 | 
			
		||||
            active_till,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /// Get the total amount including tax
 | 
			
		||||
    pub fn total_with_tax(&self) -> Currency {
 | 
			
		||||
        Currency {
 | 
			
		||||
            amount: self.subtotal.amount + self.tax_amount.amount,
 | 
			
		||||
            currency_code: self.subtotal.currency_code.clone(),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Builder for SaleItem
 | 
			
		||||
@@ -65,9 +91,13 @@ pub struct SaleItemBuilder {
 | 
			
		||||
    sale_id: Option<u32>,
 | 
			
		||||
    product_id: Option<u32>,
 | 
			
		||||
    name: Option<String>,
 | 
			
		||||
    description: Option<String>,
 | 
			
		||||
    comments: Option<String>,
 | 
			
		||||
    quantity: Option<i32>,
 | 
			
		||||
    unit_price: Option<Currency>,
 | 
			
		||||
    subtotal: Option<Currency>,
 | 
			
		||||
    tax_rate: Option<f64>,
 | 
			
		||||
    tax_amount: Option<Currency>,
 | 
			
		||||
    active_till: Option<DateTime<Utc>>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -79,9 +109,13 @@ impl SaleItemBuilder {
 | 
			
		||||
            sale_id: None,
 | 
			
		||||
            product_id: None,
 | 
			
		||||
            name: None,
 | 
			
		||||
            description: None,
 | 
			
		||||
            comments: None,
 | 
			
		||||
            quantity: None,
 | 
			
		||||
            unit_price: None,
 | 
			
		||||
            subtotal: None,
 | 
			
		||||
            tax_rate: None,
 | 
			
		||||
            tax_amount: None,
 | 
			
		||||
            active_till: None,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
@@ -109,6 +143,18 @@ impl SaleItemBuilder {
 | 
			
		||||
        self.name = Some(name.into());
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /// Set the description
 | 
			
		||||
    pub fn description<S: Into<String>>(mut self, description: S) -> Self {
 | 
			
		||||
        self.description = Some(description.into());
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /// Set the comments
 | 
			
		||||
    pub fn comments<S: Into<String>>(mut self, comments: S) -> Self {
 | 
			
		||||
        self.comments = Some(comments.into());
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Set the quantity
 | 
			
		||||
    pub fn quantity(mut self, quantity: i32) -> Self {
 | 
			
		||||
@@ -122,6 +168,12 @@ impl SaleItemBuilder {
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Set the tax_rate
 | 
			
		||||
    pub fn tax_rate(mut self, tax_rate: f64) -> Self {
 | 
			
		||||
        self.tax_rate = Some(tax_rate);
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /// Set the active_till
 | 
			
		||||
    pub fn active_till(mut self, active_till: DateTime<Utc>) -> Self {
 | 
			
		||||
        self.active_till = Some(active_till);
 | 
			
		||||
@@ -132,6 +184,7 @@ impl SaleItemBuilder {
 | 
			
		||||
    pub fn build(self) -> Result<SaleItem, &'static str> {
 | 
			
		||||
        let unit_price = self.unit_price.ok_or("unit_price is required")?;
 | 
			
		||||
        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
 | 
			
		||||
 | 
			
		||||
        // Calculate subtotal
 | 
			
		||||
        let amount = unit_price.amount * quantity as f64;
 | 
			
		||||
@@ -139,15 +192,26 @@ impl SaleItemBuilder {
 | 
			
		||||
            amount,
 | 
			
		||||
            currency_code: unit_price.currency_code.clone(),
 | 
			
		||||
        };
 | 
			
		||||
        
 | 
			
		||||
        // Calculate tax amount
 | 
			
		||||
        let tax_amount_value = subtotal.amount * (tax_rate / 100.0);
 | 
			
		||||
        let tax_amount = Currency {
 | 
			
		||||
            amount: tax_amount_value,
 | 
			
		||||
            currency_code: unit_price.currency_code.clone(),
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        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")?,
 | 
			
		||||
            name: self.name.ok_or("name is required")?,
 | 
			
		||||
            description: self.description.unwrap_or_default(),
 | 
			
		||||
            comments: self.comments.unwrap_or_default(),
 | 
			
		||||
            quantity,
 | 
			
		||||
            unit_price,
 | 
			
		||||
            subtotal,
 | 
			
		||||
            tax_rate,
 | 
			
		||||
            tax_amount,
 | 
			
		||||
            active_till: self.active_till.ok_or("active_till is required")?,
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
@@ -158,10 +222,14 @@ impl SaleItemBuilder {
 | 
			
		||||
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 total_amount: Currency,
 | 
			
		||||
    pub subtotal_amount: Currency, // Total before tax
 | 
			
		||||
    pub tax_amount: Currency, // Total tax
 | 
			
		||||
    pub total_amount: Currency, // Total including tax
 | 
			
		||||
    pub status: SaleStatus,
 | 
			
		||||
    pub service_id: Option<u32>, // ID of the service created from this sale (if applicable)
 | 
			
		||||
    pub sale_date: DateTime<Utc>,
 | 
			
		||||
    pub created_at: DateTime<Utc>,
 | 
			
		||||
    pub updated_at: DateTime<Utc>,
 | 
			
		||||
@@ -175,22 +243,29 @@ impl Sale {
 | 
			
		||||
    pub fn new(
 | 
			
		||||
        id: u32,
 | 
			
		||||
        company_id: u32,
 | 
			
		||||
        customer_id: u32,
 | 
			
		||||
        buyer_name: String,
 | 
			
		||||
        buyer_email: String,
 | 
			
		||||
        currency_code: String,
 | 
			
		||||
        status: SaleStatus,
 | 
			
		||||
    ) -> Self {
 | 
			
		||||
        let now = Utc::now();
 | 
			
		||||
        let zero_currency = Currency {
 | 
			
		||||
            amount: 0.0,
 | 
			
		||||
            currency_code: currency_code.clone(),
 | 
			
		||||
        };
 | 
			
		||||
        
 | 
			
		||||
        Self {
 | 
			
		||||
            id,
 | 
			
		||||
            company_id,
 | 
			
		||||
            customer_id,
 | 
			
		||||
            buyer_name,
 | 
			
		||||
            buyer_email,
 | 
			
		||||
            total_amount: Currency {
 | 
			
		||||
                amount: 0.0,
 | 
			
		||||
                currency_code,
 | 
			
		||||
            },
 | 
			
		||||
            subtotal_amount: zero_currency.clone(),
 | 
			
		||||
            tax_amount: zero_currency.clone(),
 | 
			
		||||
            total_amount: zero_currency,
 | 
			
		||||
            status,
 | 
			
		||||
            service_id: None,
 | 
			
		||||
            sale_date: now,
 | 
			
		||||
            created_at: now,
 | 
			
		||||
            updated_at: now,
 | 
			
		||||
@@ -203,17 +278,27 @@ impl Sale {
 | 
			
		||||
        // Make sure the item's sale_id matches this sale
 | 
			
		||||
        assert_eq!(self.id, item.sale_id, "Item sale_id must match sale id");
 | 
			
		||||
 | 
			
		||||
        // Update the total amount
 | 
			
		||||
        // Update the amounts
 | 
			
		||||
        if self.items.is_empty() {
 | 
			
		||||
            // First item, initialize the total amount with the same currency
 | 
			
		||||
            self.total_amount = Currency {
 | 
			
		||||
            // First item, initialize the amounts with the same currency
 | 
			
		||||
            self.subtotal_amount = Currency {
 | 
			
		||||
                amount: item.subtotal.amount,
 | 
			
		||||
                currency_code: item.subtotal.currency_code.clone(),
 | 
			
		||||
            };
 | 
			
		||||
            self.tax_amount = Currency {
 | 
			
		||||
                amount: item.tax_amount.amount,
 | 
			
		||||
                currency_code: item.tax_amount.currency_code.clone(),
 | 
			
		||||
            };
 | 
			
		||||
            self.total_amount = Currency {
 | 
			
		||||
                amount: item.subtotal.amount + item.tax_amount.amount,
 | 
			
		||||
                currency_code: item.subtotal.currency_code.clone(),
 | 
			
		||||
            };
 | 
			
		||||
        } else {
 | 
			
		||||
            // Add to the existing total
 | 
			
		||||
            // Add to the existing totals
 | 
			
		||||
            // (Assumes all items have the same currency)
 | 
			
		||||
            self.total_amount.amount += item.subtotal.amount;
 | 
			
		||||
            self.subtotal_amount.amount += item.subtotal.amount;
 | 
			
		||||
            self.tax_amount.amount += item.tax_amount.amount;
 | 
			
		||||
            self.total_amount.amount = self.subtotal_amount.amount + self.tax_amount.amount;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Add the item to the list
 | 
			
		||||
@@ -222,12 +307,93 @@ impl Sale {
 | 
			
		||||
        // Update the sale timestamp
 | 
			
		||||
        self.updated_at = Utc::now();
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /// Recalculate all totals based on items
 | 
			
		||||
    pub fn recalculate_totals(&mut self) {
 | 
			
		||||
        if self.items.is_empty() {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        // Get the currency code from the first item
 | 
			
		||||
        let currency_code = self.items[0].subtotal.currency_code.clone();
 | 
			
		||||
        
 | 
			
		||||
        // Calculate the totals
 | 
			
		||||
        let mut subtotal = 0.0;
 | 
			
		||||
        let mut tax_total = 0.0;
 | 
			
		||||
        
 | 
			
		||||
        for item in &self.items {
 | 
			
		||||
            subtotal += item.subtotal.amount;
 | 
			
		||||
            tax_total += item.tax_amount.amount;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        // Update the amounts
 | 
			
		||||
        self.subtotal_amount = Currency {
 | 
			
		||||
            amount: subtotal,
 | 
			
		||||
            currency_code: currency_code.clone(),
 | 
			
		||||
        };
 | 
			
		||||
        self.tax_amount = Currency {
 | 
			
		||||
            amount: tax_total,
 | 
			
		||||
            currency_code: currency_code.clone(),
 | 
			
		||||
        };
 | 
			
		||||
        self.total_amount = Currency {
 | 
			
		||||
            amount: subtotal + tax_total,
 | 
			
		||||
            currency_code,
 | 
			
		||||
        };
 | 
			
		||||
        
 | 
			
		||||
        // Update the timestamp
 | 
			
		||||
        self.updated_at = Utc::now();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Update the status of the sale
 | 
			
		||||
    pub fn update_status(&mut self, status: SaleStatus) {
 | 
			
		||||
        self.status = status;
 | 
			
		||||
        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
 | 
			
		||||
        self.service_id = Some(service_id);
 | 
			
		||||
        self.updated_at = Utc::now();
 | 
			
		||||
        
 | 
			
		||||
        Ok(service)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Builder for Sale
 | 
			
		||||
@@ -235,10 +401,14 @@ impl Sale {
 | 
			
		||||
pub struct SaleBuilder {
 | 
			
		||||
    id: Option<u32>,
 | 
			
		||||
    company_id: Option<u32>,
 | 
			
		||||
    customer_id: Option<u32>,
 | 
			
		||||
    buyer_name: Option<String>,
 | 
			
		||||
    buyer_email: Option<String>,
 | 
			
		||||
    subtotal_amount: Option<Currency>,
 | 
			
		||||
    tax_amount: Option<Currency>,
 | 
			
		||||
    total_amount: Option<Currency>,
 | 
			
		||||
    status: Option<SaleStatus>,
 | 
			
		||||
    service_id: Option<u32>,
 | 
			
		||||
    sale_date: Option<DateTime<Utc>>,
 | 
			
		||||
    created_at: Option<DateTime<Utc>>,
 | 
			
		||||
    updated_at: Option<DateTime<Utc>>,
 | 
			
		||||
@@ -252,10 +422,14 @@ impl SaleBuilder {
 | 
			
		||||
        Self {
 | 
			
		||||
            id: None,
 | 
			
		||||
            company_id: None,
 | 
			
		||||
            customer_id: None,
 | 
			
		||||
            buyer_name: None,
 | 
			
		||||
            buyer_email: None,
 | 
			
		||||
            subtotal_amount: None,
 | 
			
		||||
            tax_amount: None,
 | 
			
		||||
            total_amount: None,
 | 
			
		||||
            status: None,
 | 
			
		||||
            service_id: None,
 | 
			
		||||
            sale_date: None,
 | 
			
		||||
            created_at: None,
 | 
			
		||||
            updated_at: None,
 | 
			
		||||
@@ -275,6 +449,12 @@ impl SaleBuilder {
 | 
			
		||||
        self.company_id = Some(company_id);
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /// Set the customer_id
 | 
			
		||||
    pub fn customer_id(mut self, customer_id: u32) -> Self {
 | 
			
		||||
        self.customer_id = Some(customer_id);
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Set the buyer_name
 | 
			
		||||
    pub fn buyer_name<S: Into<String>>(mut self, buyer_name: S) -> Self {
 | 
			
		||||
@@ -299,6 +479,12 @@ impl SaleBuilder {
 | 
			
		||||
        self.status = Some(status);
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /// Set the service_id
 | 
			
		||||
    pub fn service_id(mut self, service_id: u32) -> Self {
 | 
			
		||||
        self.service_id = Some(service_id);
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Set the sale_date
 | 
			
		||||
    pub fn sale_date(mut self, sale_date: DateTime<Utc>) -> Self {
 | 
			
		||||
@@ -318,39 +504,45 @@ impl SaleBuilder {
 | 
			
		||||
        let id = self.id.ok_or("id is required")?;
 | 
			
		||||
        let currency_code = self.currency_code.ok_or("currency_code is required")?;
 | 
			
		||||
 | 
			
		||||
        // Initialize with empty total amount
 | 
			
		||||
        // Initialize with empty amounts
 | 
			
		||||
        let mut subtotal_amount = Currency {
 | 
			
		||||
            amount: 0.0,
 | 
			
		||||
            currency_code: currency_code.clone(),
 | 
			
		||||
        };
 | 
			
		||||
        let mut tax_amount = Currency {
 | 
			
		||||
            amount: 0.0,
 | 
			
		||||
            currency_code: currency_code.clone(),
 | 
			
		||||
        };
 | 
			
		||||
        let mut total_amount = Currency {
 | 
			
		||||
            amount: 0.0,
 | 
			
		||||
            currency_code: currency_code.clone(),
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        // Calculate total amount from items
 | 
			
		||||
        // Calculate amounts from items
 | 
			
		||||
        for item in &self.items {
 | 
			
		||||
            // Make sure the item's sale_id matches this sale
 | 
			
		||||
            if item.sale_id != id {
 | 
			
		||||
                return Err("Item sale_id must match sale id");
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if total_amount.amount == 0.0 {
 | 
			
		||||
                // First item, initialize the total amount with the same currency
 | 
			
		||||
                total_amount = Currency {
 | 
			
		||||
                    amount: item.subtotal.amount,
 | 
			
		||||
                    currency_code: item.subtotal.currency_code.clone(),
 | 
			
		||||
                };
 | 
			
		||||
            } else {
 | 
			
		||||
                // Add to the existing total
 | 
			
		||||
                // (Assumes all items have the same currency)
 | 
			
		||||
                total_amount.amount += item.subtotal.amount;
 | 
			
		||||
            }
 | 
			
		||||
            subtotal_amount.amount += item.subtotal.amount;
 | 
			
		||||
            tax_amount.amount += item.tax_amount.amount;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        // Calculate total amount
 | 
			
		||||
        total_amount.amount = subtotal_amount.amount + tax_amount.amount;
 | 
			
		||||
 | 
			
		||||
        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),
 | 
			
		||||
            status: self.status.ok_or("status is required")?,
 | 
			
		||||
            service_id: self.service_id,
 | 
			
		||||
            sale_date: self.sale_date.unwrap_or(now),
 | 
			
		||||
            created_at: self.created_at.unwrap_or(now),
 | 
			
		||||
            updated_at: self.updated_at.unwrap_or(now),
 | 
			
		||||
 
 | 
			
		||||
@@ -29,6 +29,8 @@ pub struct ServiceItem {
 | 
			
		||||
    pub service_id: u32,
 | 
			
		||||
    pub product_id: u32,
 | 
			
		||||
    pub name: String,
 | 
			
		||||
    pub description: String, // Description of the service item
 | 
			
		||||
    pub comments: String, // Additional comments about the service item
 | 
			
		||||
    pub quantity: i32,
 | 
			
		||||
    pub unit_price: Currency,
 | 
			
		||||
    pub subtotal: Currency,
 | 
			
		||||
@@ -45,6 +47,8 @@ impl ServiceItem {
 | 
			
		||||
        service_id: u32,
 | 
			
		||||
        product_id: u32,
 | 
			
		||||
        name: String,
 | 
			
		||||
        description: String,
 | 
			
		||||
        comments: String,
 | 
			
		||||
        quantity: i32,
 | 
			
		||||
        unit_price: Currency,
 | 
			
		||||
        tax_rate: f64,
 | 
			
		||||
@@ -76,6 +80,8 @@ impl ServiceItem {
 | 
			
		||||
            service_id,
 | 
			
		||||
            product_id,
 | 
			
		||||
            name,
 | 
			
		||||
            description,
 | 
			
		||||
            comments,
 | 
			
		||||
            quantity,
 | 
			
		||||
            unit_price,
 | 
			
		||||
            subtotal,
 | 
			
		||||
@@ -117,6 +123,8 @@ pub struct ServiceItemBuilder {
 | 
			
		||||
    service_id: Option<u32>,
 | 
			
		||||
    product_id: Option<u32>,
 | 
			
		||||
    name: Option<String>,
 | 
			
		||||
    description: Option<String>,
 | 
			
		||||
    comments: Option<String>,
 | 
			
		||||
    quantity: Option<i32>,
 | 
			
		||||
    unit_price: Option<Currency>,
 | 
			
		||||
    subtotal: Option<Currency>,
 | 
			
		||||
@@ -134,6 +142,8 @@ impl ServiceItemBuilder {
 | 
			
		||||
            service_id: None,
 | 
			
		||||
            product_id: None,
 | 
			
		||||
            name: None,
 | 
			
		||||
            description: None,
 | 
			
		||||
            comments: None,
 | 
			
		||||
            quantity: None,
 | 
			
		||||
            unit_price: None,
 | 
			
		||||
            subtotal: None,
 | 
			
		||||
@@ -167,6 +177,18 @@ impl ServiceItemBuilder {
 | 
			
		||||
        self.name = Some(name.into());
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /// Set the description
 | 
			
		||||
    pub fn description<S: Into<String>>(mut self, description: S) -> Self {
 | 
			
		||||
        self.description = Some(description.into());
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /// Set the comments
 | 
			
		||||
    pub fn comments<S: Into<String>>(mut self, comments: S) -> Self {
 | 
			
		||||
        self.comments = Some(comments.into());
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Set the quantity
 | 
			
		||||
    pub fn quantity(mut self, quantity: i32) -> Self {
 | 
			
		||||
@@ -230,6 +252,8 @@ impl ServiceItemBuilder {
 | 
			
		||||
            service_id: self.service_id.ok_or("service_id is required")?,
 | 
			
		||||
            product_id: self.product_id.ok_or("product_id is required")?,
 | 
			
		||||
            name: self.name.ok_or("name is required")?,
 | 
			
		||||
            description: self.description.unwrap_or_default(),
 | 
			
		||||
            comments: self.comments.unwrap_or_default(),
 | 
			
		||||
            quantity,
 | 
			
		||||
            unit_price,
 | 
			
		||||
            subtotal,
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user