# easy-invoice/2.3.2/includes/Models/Invoice.php

Easy Invoice – Invoice Generator, PDF Quotes &amp; Payments, version 2.3.2. 933 lines.

- Page: https://pluginprobe.com/plugins/easy-invoice/2.3.2/code/includes/Models/Invoice.php
- Raw: https://pluginprobe.com/plugins/easy-invoice/2.3.2/raw/includes/Models/Invoice.php
- Modified: 2026-05-21T08:29:24+00:00

Line numbers below start at 1. Link to a line or a range by appending a fragment to the
page URL, for example `https://pluginprobe.com/plugins/easy-invoice/2.3.2/code/includes/Models/Invoice.php#L10-L20`.

```php
<?php
/**
 * Invoice Model
 *
 * @package     EasyInvoice
 * @author      Your Name
 * @copyright   Copyright (c) 2023, Your Company
 * @license     http://opensource.org/licenses/gpl-2.0.php GNU Public License
 * @since       1.0.0
 */

namespace EasyInvoice\Models;

use EasyInvoice\Constants\PostTypes;
use EasyInvoice\Models\InvoiceItem;

/**
 * Invoice Model
 *
 * Represents an invoice in the system with extensible architecture for pro features.
 *
 * @since 1.0.0
 */
class Invoice {
    /**
     * Invoice ID
     *
     * @var int
     */
    private $id;

    /**
     * Dynamic data storage for all fields
     *
     * @var array
     */
    private $data = [];

    /**
     * Items
     *
     * @var array
     */
    private $items = [];

    /**
     * Modified flag
     *
     * @var bool
     */
    private $is_modified = false;

    /**
     * Magic method to get dynamic properties
     *
     * @since 1.0.0
     * @param string $name Property name
     * @return mixed Property value
     */
    public function __get($name) {
        // Handle special properties
        if ($name === 'id') {
            return $this->id;
        }
        
        // Special handling for total field
        if ($name === 'total') {
            return $this->getTotal();
        }
        
        // Return from dynamic data array
        return $this->data[$name] ?? null;
    }

    /**
     * Magic method to set dynamic properties
     *
     * @since 1.0.0
     * @param string $name Property name
     * @param mixed $value Property value
     */
    public function __set($name, $value) {
        // Handle special properties
        if ($name === 'id') {
            $this->id = $value;
            return;
        }
        
        // Store in dynamic data array
        $this->data[$name] = $value;
        $this->is_modified = true;
    }

    /**
     * Magic method to check if property exists
     *
     * @since 1.0.0
     * @param string $name Property name
     * @return bool
     */
    public function __isset($name) {
        if ($name === 'id') {
            return isset($this->id);
        }
        
        return isset($this->data[$name]);
    }

    /**
     * Constructor
     *
     * @since 1.0.0
     * @param \WP_Post|int $invoice Invoice post object or ID
     */
    public function __construct($invoice = null) {
        // Initialize data array dynamically from field configuration
        $this->data = $this->getDefaultValuesFromConfiguration();

        if ($invoice instanceof \WP_Post) {
            $this->loadFromPost($invoice);
        } elseif (is_numeric($invoice)) {
            $post = get_post($invoice);
            if ($post && $post->post_type === PostTypes::EASY_INVOICE_POST_TYPE) {
                $this->loadFromPost($post);
            }
        }

        do_action('easy_invoice_invoice_model_constructed', $this);
    }

    /**
     * Get default values from field configuration
     *
     * @since 1.0.0
     * @return array
     */
    private function getDefaultValuesFromConfiguration(): array {
        $default_values = [];
        
        // Get field definitions to determine default values
        $field_registration = new \EasyInvoice\Forms\Invoice\InvoiceFieldRegistration();
        
        // Initialize the field registration to ensure fields are registered
        $field_registration->registerDefaultTabs();
        $field_registration->registerDefaultFields();
        
        $field_definitions = [];
        $tabs = $field_registration->getTabs();
        foreach ($tabs as $tab_id => $tab) {
            $tab_fields = $field_registration->getFields($tab_id);
            foreach ($tab_fields as $field) {
                $field_definitions[] = $field;
            }
        }

        // Extract default values from field configuration
        foreach ($field_definitions as $field) {
            $field_name = $field['name'] ?? '';
            if (empty($field_name)) {
                continue;
            }
            
            $default_value = $field['default_value'] ?? null;
            
            // Handle callable default values
            if (is_callable($default_value)) {
                $default_value = $default_value();
            }
            
            // Set appropriate default based on field type
            if ($default_value !== null) {
                $default_values[$field_name] = $default_value;
            } else {
                // Set type-appropriate defaults
                $field_type = $field['type'] ?? 'text';
                switch ($field_type) {
                    case 'number':
                        $default_values[$field_name] = 0.0;
                        break;
                    case 'checkbox':
                        $default_values[$field_name] = false;
                        break;
                    case 'select':
                        $default_values[$field_name] = '';
                        break;
                    case 'array':
                        $default_values[$field_name] = [];
                        break;
                    default:
                        $default_values[$field_name] = '';
                        break;
                }
            }
        }

        return $default_values;
    }

    /**
     * Load invoice data from WP_Post
     *
     * @since 1.0.0
     * @param \WP_Post $post
     */
    private function loadFromPost(\WP_Post $post): void {
        $this->id = $post->ID;
        $this->data['title'] = $post->post_title ?: '';
        $this->data['created_date'] = $post->post_date ?: '';
        $this->data['modified_date'] = $post->post_modified ?: '';

        // Load meta data using configuration-driven approach
        $this->loadMetaData();

        // Load items
        $this->loadItems();

        // Auto-calculate totals if they're 0 or if we have items but no totals
        if ((($this->data['total'] ?? 0) == 0 && !empty($this->items)) || 
            (($this->data['subtotal'] ?? 0) == 0 && !empty($this->items))) {
            $this->calculateTotals();
        }

        // Populate client information if we have a client_id but no customer data
        if (($this->data['client_id'] ?? 0) > 0 && (empty($this->data['customer_name'] ?? '') || empty($this->data['customer_email'] ?? ''))) {
            $this->populateClientInfo();
        }
    }

    /**
     * Load meta data from database
     *
     * @since 1.0.0
     */
    private function loadMetaData(): void {
        if (!$this->id) {
            return;
        }

        // Get field definitions to determine which meta keys to load
        $field_registration = new \EasyInvoice\Forms\Invoice\InvoiceFieldRegistration();
        
        // Initialize the field registration to ensure fields are registered
        $field_registration->registerDefaultTabs();
        $field_registration->registerDefaultFields();
        
        $field_definitions = [];
        $tabs = $field_registration->getTabs();
        foreach ($tabs as $tab_id => $tab) {
            $tab_fields = $field_registration->getFields($tab_id);
            foreach ($tab_fields as $field) {
                $field_definitions[] = $field;
            }
        }

        // Load meta data for each field definition
        foreach ($field_definitions as $field) {
            $field_name = $field['name'] ?? '';
            if (empty($field_name)) {
                continue;
            }
            
            $meta_key = '_easy_invoice_' . $field_name;
            $value = get_post_meta($this->id, $meta_key, true);
            
            if ($value !== '') {
                // Store in dynamic data array
                $this->data[$field_name] = $value;
            }
        }




    }

    /**
     * Load invoice items
     *
     * @since 1.0.0
     */
    private function loadItems(): void {
        if (!$this->id) {
            return;
        }

        $items_data = get_post_meta($this->id, '_easy_invoice_items', true) ?: [];
        $this->items = [];

        foreach ($items_data as $item_data) {
            $this->items[] = new \EasyInvoice\Models\InvoiceItem($item_data);
        }
    }

    /**
     * Ensure the invoice has a proper slug for pretty permalinks
     *
     * @since 1.0.0
     * @return bool True if successful, false otherwise
     */
    public function ensureProperSlug(): bool {
        if (!$this->id) {
            return false;
        }
        
        $post = get_post($this->id);
        if (!$post || empty($post->post_name)) {
            // Generate a slug from the title
            $post_title = $this->data['title'] ?: $this->data['number'] ?: 'Untitled Invoice';
            $post_name = sanitize_title($post_title);
            
            // Ensure uniqueness
            $original_slug = $post_name;
            $counter = 1;
            while (get_page_by_path($post_name, OBJECT, PostTypes::EASY_INVOICE_POST_TYPE)) {
                $post_name = $original_slug . '-' . $counter;
                $counter++;
            }
            
            // Update the post with the new slug
            $result = wp_update_post([
                'ID' => $this->id,
                'post_name' => $post_name,
                'post_status' => 'publish' // Ensure it's published for proper permalinks
            ]);
            
            return !is_wp_error($result);
        }
        
        return true;
    }

    /**
     * Save the invoice
     * Always publish invoices to ensure proper permalinks
     * Invoice status will be stored in post meta (_easy_invoice_status)
     *
     * @since 1.0.0
     * @return bool True if successful, false otherwise
     */
    public function save(): bool {
        // Allow plugins to modify the invoice before saving
        do_action('easy_invoice_model_before_save', $this);
        
        // Always publish invoices to ensure proper permalinks
        // Invoice status will be stored in post meta (_easy_invoice_status)
        $post_status = 'publish';
        
        // Generate a proper slug for pretty permalinks
        $post_title = $this->data['title'] ?: $this->data['number'] ?: 'Untitled Invoice';
        $post_name = '';
        
        if ($this->id) {
            // For existing posts, get the current post to preserve slug if it exists
            $existing_post = get_post($this->id);
            if ($existing_post && !empty($existing_post->post_name)) {
                $post_name = $existing_post->post_name;
            }
        }
        
        // If no slug exists, generate one from the title
        if (empty($post_name)) {
            $post_name = sanitize_title($post_title);
            
            // Ensure uniqueness by appending number if needed
            $original_slug = $post_name;
            $counter = 1;
            while (get_page_by_path($post_name, OBJECT, PostTypes::EASY_INVOICE_POST_TYPE)) {
                $post_name = $original_slug . '-' . $counter;
                $counter++;
            }
        }
        
        // Prepare post data
        $post_data = [
            'post_title' => $post_title,
            'post_content' => $this->data['notes'] ?: '', // Ensure post_content is never null
            'post_status' => $post_status, // Always publish for proper permalinks
            'post_type' => PostTypes::EASY_INVOICE_POST_TYPE,
            'post_name' => $post_name, // Add the generated slug
        ];
        
        // Update existing post or create new one
        if ($this->id) {
            $post_data['ID'] = $this->id;
            $post_id = wp_update_post($post_data);
        } else {
            $post_id = wp_insert_post($post_data);
        }
        
        if (is_wp_error($post_id)) {
            return false;
        }
        
        // Update the ID if this was a new post
        if (!$this->id) {
            $this->id = $post_id;
        }
        
        // Calculate totals from items before saving
        $this->calculateTotals();
        
        // Save meta data (including invoice status)
        $this->saveMetaData();
        
        // Allow plugins to perform actions after saving
        do_action('easy_invoice_model_after_save', $this);
        
        $this->is_modified = false;
        return true;
    }

    /**
     * Save meta data to database
     *
     * @since 1.0.0
     */
    private function saveMetaData(): void {
        if (!$this->id) {
            return;
        }

        // Get field definitions to determine which meta keys to save
        $field_registration = new \EasyInvoice\Forms\Invoice\InvoiceFieldRegistration();
        
        // Initialize the field registration to ensure fields are registered
        $field_registration->registerDefaultTabs();
        $field_registration->registerDefaultFields();
        
        $field_definitions = [];
        $tabs = $field_registration->getTabs();
        foreach ($tabs as $tab_id => $tab) {
            $tab_fields = $field_registration->getFields($tab_id);
            foreach ($tab_fields as $field) {
                $field_definitions[] = $field;
            }
        }

        // Save meta data for each field definition
        foreach ($field_definitions as $field) {
            $field_name = $field['name'] ?? '';
            if (empty($field_name)) {
                continue;
            }
            
            $meta_key = '_easy_invoice_' . $field_name;
            
            // Get the value from dynamic data array
            // Save all fields that exist in the data array (including empty strings to allow clearing fields)
            // If a field exists in $this->data, it means it was explicitly set, so we should save it
            if (array_key_exists($field_name, $this->data)) {
                $value = $this->data[$field_name];
                update_post_meta($this->id, $meta_key, $value);
            }
        }





        // Save items
        $this->saveItems();
        
        // Allow plugins to save additional meta data
        do_action('easy_invoice_model_save_meta_data', $this);
    }

    /**
     * Save invoice items
     *
     * @since 1.0.0
     */
    private function saveItems(): void {
        if (!$this->id) {
            return;
        }

        $items_data = [];
        foreach ($this->items as $item) {
            if (is_object($item) && method_exists($item, 'toArray')) {
                $items_data[] = $item->toArray();
            } else {
                $items_data[] = $item;
            }
        }
        update_post_meta($this->id, '_easy_invoice_items', $items_data);
    }

    /**
     * Calculate totals from items
     *
     * @since 1.0.0
     */
    public function calculateTotals(): void {
        // Per-invoice tax_enabled override. Falls back to the global
        // Settings → Tax → Enable Tax option when the per-invoice
        // value is unset (eg. invoices created before the per-invoice
        // override was introduced). Set tax_rate to 0 when disabled
        // so the rest of the math runs unchanged — keeps tax_rate as
        // an authored value the user can flip back on without
        // re-entering it.
        $tax_enabled_meta = $this->data['tax_enabled'] ?? null;
        if ($tax_enabled_meta === null || $tax_enabled_meta === '') {
            $tax_enabled = get_option('easy_invoice_tax_enabled', 'no') === 'yes';
        } else {
            $tax_enabled = ($tax_enabled_meta === 'yes' || $tax_enabled_meta === '1'
                            || $tax_enabled_meta === 1   || $tax_enabled_meta === true);
        }

        $prices_include_tax = ($this->data['prices_include_tax'] ?? 'no') === 'yes';
        $tax_rate = $tax_enabled ? floatval($this->data['tax_rate'] ?? 0) : 0;
        
        // Initialize totals
        $subtotal = 0;
        $taxable_subtotal = 0;
        $this->data['tax_amount'] = 0;
        $this->data['discount_amount'] = 0;
        
        // First pass: Calculate raw totals
        foreach ($this->items as $index => $item) {
            $quantity = 0;
            $price = 0;
            $adjust_percentage = 0;
            $is_taxable = true;
            
            if (is_object($item) && method_exists($item, 'getAmount')) {
                $quantity = $item->getQuantity();
                $price = $item->getPrice();
                $adjust_percentage = $item->getAdjustPercentage();
                $is_taxable = $item->isTaxable();
            } elseif (is_array($item)) {
                $quantity = isset($item['quantity']) ? (float) $item['quantity'] : 0;
                $price = isset($item['price']) ? (float) $item['price'] : 0;
                $adjust_percentage = isset($item['adjust_percentage']) ? (float) $item['adjust_percentage'] : 0;
                $is_taxable = isset($item['taxable']) ? (bool) $item['taxable'] : true;
            }
            
            // If prices include tax and item is taxable, remove tax from price
            if ($prices_include_tax && $is_taxable && $tax_rate > 0) {
                $price = $price / (1 + ($tax_rate / 100));
            }
            
            // Calculate item total
            $item_total = $quantity * $price;
            // Only apply adjust percentage if the adjust field is enabled
            if ($adjust_percentage != 0 && \EasyInvoice\Controllers\SettingsController::shouldShowInvoiceAdjustField()) {
                $item_total = $item_total * (1 + $adjust_percentage / 100);
            }
            
            $subtotal += $item_total;
            if ($is_taxable) {
                $taxable_subtotal += $item_total;
            }
        }
        
        $this->data['subtotal'] = $subtotal;
        
        // Get discount calculation method (default to before_tax)
        $discount_calculation_method = $this->data['discount_calculation_method'] ?? 'before_tax';
        
        // Calculate initial discount amount
        if (($this->data['discount_type'] ?? '') === 'percentage' && ($this->data['discount_value'] ?? 0) > 0 && $subtotal > 0) {
            $this->data['discount_amount'] = ($subtotal * $this->data['discount_value']) / 100;
        } elseif (($this->data['discount_type'] ?? '') === 'fixed' && ($this->data['discount_value'] ?? 0) > 0) {
            $this->data['discount_amount'] = $this->data['discount_value'];
        } else {
            $this->data['discount_amount'] = 0;
        }
        
        // Calculate tax and total based on discount calculation method
        if ($discount_calculation_method === 'before_tax') {
            // For before_tax: Apply discount first, then calculate tax on remaining taxable amount
            $discount_ratio = ($subtotal > 0) ? ($this->data['discount_amount'] / $subtotal) : 0;
            $taxable_amount = $taxable_subtotal * (1 - $discount_ratio);
            
            if ($tax_rate > 0) {
                $this->data['tax_amount'] = ($taxable_amount * $tax_rate) / 100;
            }
            
            $this->data['total'] = $subtotal - $this->data['discount_amount'] + $this->data['tax_amount'];
        } else {
            // For after_tax: Calculate tax first, then apply discount
            if ($tax_rate > 0) {
                $this->data['tax_amount'] = ($taxable_subtotal * $tax_rate) / 100;
            }
            
            $total_before_discount = $subtotal + $this->data['tax_amount'];
            
            // Recalculate percentage discount based on total including tax
            if (($this->data['discount_type'] ?? '') === 'percentage' && ($this->data['discount_value'] ?? 0) > 0 && $total_before_discount > 0) {
                $this->data['discount_amount'] = ($total_before_discount * $this->data['discount_value']) / 100;
            }
            
            $this->data['total'] = $total_before_discount - $this->data['discount_amount'];
        }
        
        // Allow plugins to modify calculations
        do_action('easy_invoice_model_calculate_totals', $this);
    }

    /**
     * Populate client information from client repository
     *
     * @since 1.0.0
     */
    public function populateClientInfo(): void {
        if (($this->data['client_id'] ?? 0) > 0) {
            $client_repository = new \EasyInvoice\Repositories\ClientRepository();
            $client = $client_repository->find($this->data['client_id']);
            
            if ($client) {
                $this->data['customer_name'] = $client->getBusinessClientName() ?: '';
                $this->data['customer_email'] = $client->getEmail() ?: '';
                $this->data['customer_address'] = $client->getAddress() ?: '';
            }
        }
    }

    /**
     * Force populate client info and save (for existing invoices that need client data)
     *
     * @since 1.0.0
     * @return bool
     */
    public function forcePopulateClientInfo(): bool {
        $this->populateClientInfo();
        return $this->save();
    }

    /**
     * Recalculate totals and save (for existing invoices)
     *
     * @since 1.0.0
     * @return bool
     */
    public function recalculateAndSave(): bool {
        // Populate client information if we have a client_id
        $this->populateClientInfo();
        
        // Calculate totals from items
        $this->calculateTotals();
        
        // Save the updated invoice
        return $this->save();
    }

    /**
     * Convert to array
     *
     * @since 1.0.0
     * @return array
     */
    public function toArray(): array {
        $items_array = [];
        foreach ($this->items as $item) {
            if (is_object($item) && method_exists($item, 'toArray')) {
                $items_array[] = $item->toArray();
            } else {
                $items_array[] = $item;
            }
        }

        // Start with dynamic data
        $data = $this->data;
        
        // Add special properties
        $data['id'] = $this->id;
        $data['items'] = $items_array;

        // Allow plugins to modify the array data
        return apply_filters('easy_invoice_model_to_array', $data, $this);
    }

    /**
     * Dynamic getter method
     *
     * @since 1.0.0
     * @param string $name Method name
     * @param array $arguments Method arguments
     * @return mixed
     */
    public function __call($name, $arguments) {
        // Handle getter methods (getFieldName)
        if (strpos($name, 'get') === 0) {
            $field_name = $this->camelCaseToSnakeCase(substr($name, 3)); // Remove 'get' prefix
            return $this->__get($field_name);
        }
        
        // Handle setter methods (setFieldName)
        if (strpos($name, 'set') === 0) {
            $field_name = $this->camelCaseToSnakeCase(substr($name, 3)); // Remove 'set' prefix
            $value = $arguments[0] ?? null;
            $this->__set($field_name, $value);
            return null;
        }
        
        // Handle isset methods (isFieldName)
        if (strpos($name, 'is') === 0) {
            $field_name = $this->camelCaseToSnakeCase(substr($name, 2)); // Remove 'is' prefix
            return (bool) $this->__get($field_name);
        }
        
        // Handle has methods (hasFieldName)
        if (strpos($name, 'has') === 0) {
            $field_name = $this->camelCaseToSnakeCase(substr($name, 3)); // Remove 'has' prefix
            return !empty($this->__get($field_name));
        }
        
        throw new \BadMethodCallException("Method $name does not exist");
    }

    /**
     * Convert camelCase to snake_case
     *
     * @since 1.0.0
     * @param string $camelCase
     * @return string
     */
    private function camelCaseToSnakeCase($camelCase) {
        return strtolower(preg_replace('/(?<!^)[A-Z]/', '_$0', $camelCase));
    }

    // Essential methods only
    public function getId(): int { return $this->id ?? 0; }
    public function setId(int $id): void { $this->id = $id; }
    public function getItems(): array { return $this->items; }
    
    /**
     * Check if invoice exists
     *
     * @return bool
     */
    public function exists(): bool {
        return $this->id > 0 && get_post($this->id) !== null;
    }
    public function setItems(array $items): void { 
        $this->items = [];
        foreach ($items as $item) {
            if (is_array($item)) {
                $this->items[] = new InvoiceItem($item);
            } elseif (is_object($item) && $item instanceof InvoiceItem) {
                $this->items[] = $item;
            }
        }
        $this->is_modified = true;
    }
    public function isModified(): bool { return $this->is_modified; }
    
    /**
     * Set meta data for the invoice
     *
     * @since 1.0.0
     * @param string $key Meta key
     * @param mixed $value Meta value
     */
    public function setMetaData(string $key, $value): void {
        // Store in dynamic data array without the _easy_invoice_ prefix
        $field_name = easy_invoice_str_replace('_easy_invoice_', '', $key);
        $this->data[$field_name] = $value;
        $this->is_modified = true;
    }
    
    /**
     * Get currency code
     *
     * @since 1.0.0
     * @return string Currency code
     */
    public function getCurrencyCode(): string {
        $currency_code = $this->data['currency_code'] ?? 'USD';
        
        // If currency is set to 'global', resolve to actual global setting
        if ($currency_code === 'global') {
            $currency_code = get_option('easy_invoice_currency_code', 'USD');
        }
        
        // Ensure currency code is uppercase for consistency
        return strtoupper($currency_code);
    }
    
    /**
     * Set currency code
     *
     * @since 1.0.0
     * @param string $currency_code Currency code
     */
    public function setCurrencyCode(string $currency_code): void {
        $this->data['currency_code'] = $currency_code;
        $this->is_modified = true;
    }
    
    /**
     * Get currency position
     *
     * @since 1.0.0
     * @return string Currency position
     */
    public function getCurrencyPosition(): string {
        $currency_position = $this->data['currency_position'] ?? 'left';
        
        // If currency position is set to 'global', resolve to actual global setting
        if ($currency_position === 'global') {
            $currency_position = get_option('easy_invoice_currency_position', 'left');
        }
        
        // Map form format to global settings format
        if ($currency_position === 'before') {
            $currency_position = 'left';
        } elseif ($currency_position === 'after') {
            $currency_position = 'right';
        }
        
        return $currency_position;
    }
    
    /**
     * Set currency position
     *
     * @since 1.0.0
     * @param string $currency_position Currency position
     */
    public function setCurrencyPosition(string $currency_position): void {
        $this->data['currency_position'] = $currency_position;
        $this->is_modified = true;
    }

    /**
     * Get raw currency code (without resolving global)
     *
     * @since 1.0.0
     * @return string Raw currency code
     */
    public function getRawCurrencyCode(): string {
        return $this->data['currency_code'] ?? 'global';
    }

    /**
     * Get raw currency position (without resolving global)
     *
     * @since 1.0.0
     * @return string Raw currency position
     */
    public function getRawCurrencyPosition(): string {
        return $this->data['currency_position'] ?? 'global';
    }

    public function getDescription(): string {
        return $this->data['description'] ?? '';
    }

    public function setDescription(string $description): void {
        $this->data['description'] = $description;
        $this->is_modified = true;
    }

    public function getTemplate(): string {
        return $this->data['invoice_template'] ?? 'standard';
    }

    public function setTemplate(string $template): void {
        $this->data['invoice_template'] = $template;
        $this->is_modified = true;
    }

    /**
     * Get discount type (percentage, fixed, none)
     *
     * @since 1.0.0
     * @return string
     */
    public function getDiscountType(): string {
        return $this->data['discount_type'] ?? 'none';
    }

    /**
     * Get discount value (percentage or fixed amount)
     *
     * @since 1.0.0
     * @return float
     */
    public function getDiscountValue(): float {
        return floatval($this->data['discount_value'] ?? 0);
    }

    /**
     * Get discount amount (calculated value)
     *
     * @since 1.0.0
     * @return float
     */
    public function getDiscountAmount(): float {
        $this->calculateTotals();
        return floatval($this->data['discount_amount'] ?? 0);
    }

    /**
     * Get tax rate percentage
     *
     * @since 1.0.0
     * @return float
     */
    public function getTaxRate(): float {
        return floatval($this->data['tax_rate'] ?? 0);
    }

    /**
     * Get tax amount (calculated value)
     *
     * @since 1.0.0
     * @return float
     */
    public function getTaxAmount(): float {
        $this->calculateTotals();
        return floatval($this->data['tax_amount'] ?? 0);
    }

    /**
     * Get the total amount for this invoice
     * Calculate from items
     *
     * @since 1.0.0
     * @return float
     */
    public function getTotal(): float {
        // Calculate from items
        $this->calculateTotals();
        $total = (float) ($this->data['total'] ?? 0);
        
        // Allow plugins to modify the total
        return apply_filters('easy_invoice_invoice_total', $total, $this);
    }


} 
```
