# easy-invoice/2.1.2/includes/Controllers/InvoiceController.php

Easy Invoice – Invoice Generator, PDF Quotes &amp; Payments, version 2.1.2. 1,167 lines.

- Page: https://pluginprobe.com/plugins/easy-invoice/2.1.2/code/includes/Controllers/InvoiceController.php
- Raw: https://pluginprobe.com/plugins/easy-invoice/2.1.2/raw/includes/Controllers/InvoiceController.php
- Modified: 2025-10-13T12:23:02+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.1.2/code/includes/Controllers/InvoiceController.php#L10-L20`.

```php
<?php
/**
 * Invoice Controller Class
 *
 * @package Easy_Invoice
 * @subpackage Controllers
 */

namespace EasyInvoice\Controllers;

use EasyInvoice\Models\Invoice;
use EasyInvoice\Providers\InvoiceServiceProvider;
use EasyInvoice\Constants\PagesSlugs;
use WP_Query;

/**
 * InvoiceController handles all invoice-related functionality
 */
class InvoiceController extends BaseController {
    /**
     * Initialize the controller
     */
    public function init() {
        // Allow plugins to extend the controller initialization
        do_action('easy_invoice_invoice_controller_before_init', $this);
        
        // Register AJAX endpoints for invoice management
        add_action('wp_ajax_easy_invoice_trash_invoice', array($this, 'trashInvoice'));
        add_action('wp_ajax_easy_invoice_restore_invoice', array($this, 'restoreInvoice'));
        add_action('wp_ajax_easy_invoice_delete_invoice_permanently', array($this, 'deleteInvoicePermanently'));
        add_action('wp_ajax_easy_invoice_delete_invoice', array($this, 'deleteInvoice')); // Legacy support
        add_action('wp_ajax_easy_invoice_publish_invoice', array($this, 'publishInvoice'));
        add_action('wp_ajax_easy_invoice_draft_invoice', array($this, 'draftInvoice'));
        
        // Handle bulk actions
        add_action('admin_init', array($this, 'handleBulkActions'));

        // Register additional AJAX handlers
        $this->registerAjaxHandlers();

        // Add meta box for manual payment verification
        add_action('add_meta_boxes_easy-invoice', array($this, 'add_manual_payment_meta_box'));

        // AJAX handler for creating a sample invoice
        add_action('wp_ajax_easy_invoice_create_sample_invoice', array($this, 'ajax_create_sample_invoice'));
        
        // AJAX handler for creating a new invoice with title
        add_action('wp_ajax_easy_invoice_create_new_invoice', array($this, 'ajax_create_new_invoice'));
        
        // Allow plugins to extend the controller initialization
        do_action('easy_invoice_invoice_controller_after_init', $this);
    }
    
    /**
     * Display method implementation
     * 
     * @param array $args Display arguments
     */
    public function display(array $args = []) {
        // Allow plugins to modify display arguments
        $args = apply_filters('easy_invoice_invoice_controller_display_args', $args);
        
        $page = isset($args['page']) ? $args['page'] : '';
        
        // Allow plugins to modify the page before processing
        $page = apply_filters('easy_invoice_invoice_controller_display_page', $page, $args);
        
        switch ($page) {
            case PagesSlugs::ALL_INVOICES:
                $this->displayInvoicesPage();
                break;
                
            case PagesSlugs::INVOICE_NEW:
                // For a new invoice, ensure no ID is passed
                $_GET['id'] = isset($_GET['id']) ? $_GET['id'] : 0;
                $this->displayInvoiceBuilderPage();
                break;
                
            case PagesSlugs::INVOICE_PREVIEW:
                $this->displayPreviewPage();
                break;
                
            default:
                $this->displayInvoicesPage();
                break;
        }
        
        // Allow plugins to perform actions after display
        do_action('easy_invoice_invoice_controller_after_display', $page, $args);
    }
    
    /**
     * Display all invoices page
     * 
     * Uses WordPress's built-in WP_Query and paginate_links() for optimal performance
     * with large datasets (10,000+ invoices). The pagination is handled efficiently
     * by WordPress core functions which are optimized for scalability.
     */
    protected function displayInvoicesPage() {
        // Allow plugins to perform actions before displaying invoices page
        do_action('easy_invoice_invoice_controller_before_display_invoices_page');
        
        // Get filter parameters
        $status_filter = isset($_GET['status']) ? sanitize_text_field($_GET['status']) : '';
        $recurring_filter = isset($_GET['recurring']) ? sanitize_text_field($_GET['recurring']) : '';
        $subscription_filter = isset($_GET['subscription']) ? sanitize_text_field($_GET['subscription']) : '';
        $search_query = isset($_GET['search']) ? sanitize_text_field(wp_unslash($_GET['search'])) : '';
        $current_view = isset($_GET['view']) ? sanitize_text_field($_GET['view']) : 'all';
        $current_page = isset($_GET['paged']) ? max(1, intval($_GET['paged'])) : 1;
        $per_page = 20;
        $offset = ($current_page - 1) * $per_page;
        
        // Build repository query arguments
        $args = [];
        
        // Set post status based on view
        if ($current_view === 'trash') {
            $args['post_status'] = 'trash';
        } elseif ($current_view === 'draft') {
            $args['post_status'] = 'draft';
        }
        
        // Build meta query array
        $meta_query = [];
        
        // Add status filter if provided
        if (!empty($status_filter)) {
            $meta_query[] = [
                'key' => '_easy_invoice_status',
                'value' => $status_filter,
                'compare' => '='
            ];
        }
        
        // Add recurring filter if provided
        if (!empty($recurring_filter)) {
            if ($recurring_filter === 'recurring') {
                // Show only recurring invoices
                $meta_query[] = [
                    'key' => '_easy_invoice_recurring_enabled',
                    'value' => '1',
                    'compare' => '='
                ];
            } elseif ($recurring_filter === 'non-recurring') {
                // Show only non-recurring invoices
                $meta_query[] = [
                    'relation' => 'OR',
                    [
                        'key' => '_easy_invoice_recurring_enabled',
                        'compare' => 'NOT EXISTS'
                    ],
                    [
                        'key' => '_easy_invoice_recurring_enabled',
                        'value' => '0',
                        'compare' => '='
                    ]
                ];
            }
        }
        
        // Add meta query to args if we have any filters
        if (!empty($meta_query)) {
            if (count($meta_query) === 1) {
                $args['meta_query'] = $meta_query[0];
            } else {
                $args['meta_query'] = [
                    'relation' => 'AND',
                    ...$meta_query
                ];
            }
        }
        
        // Add pagination parameters to args
        $args['posts_per_page'] = $per_page;
        $args['offset'] = $offset;
        $args['orderby'] = 'date';
        $args['order'] = 'DESC';
        
        // Allow plugins to modify query arguments
        $args = apply_filters('easy_invoice_invoice_controller_query_args', $args, $current_view, $status_filter);
        
        // Get paginated invoices using WordPress query
        $repository = InvoiceServiceProvider::getInvoiceRepository();
        
        // Use WordPress WP_Query directly for better pagination handling
        $query_args = array_merge([
            'post_type' => \EasyInvoice\Constants\PostTypes::EASY_INVOICE_POST_TYPE,
            'post_status' => $args['post_status'] ?? 'publish',
            'posts_per_page' => $per_page,
            'paged' => $current_page,
            'orderby' => 'date',
            'order' => 'DESC',
            'no_found_rows' => false, // We need this for pagination
            'update_post_term_cache' => false, // Disable term cache for better performance
            'update_post_meta_cache' => false, // Disable meta cache for better performance
        ], $args);
        
        // Add search functionality
        if (!empty($search_query)) {
            // For search, we'll use a simpler approach that works better with WordPress
            // First, get all invoices that match the search criteria
            $search_ids = [];
            
            // Search in post title and content
            $title_search = new WP_Query([
                'post_type' => \EasyInvoice\Constants\PostTypes::EASY_INVOICE_POST_TYPE,
                'post_status' => $args['post_status'] ?? 'publish',
                'posts_per_page' => -1,
                's' => $search_query
            ]);
            
            if ($title_search->have_posts()) {
                $search_ids = array_merge($search_ids, wp_list_pluck($title_search->posts, 'ID'));
            }
            
            // Search in meta fields
            $meta_search = new WP_Query([
                'post_type' => \EasyInvoice\Constants\PostTypes::EASY_INVOICE_POST_TYPE,
                'post_status' => $args['post_status'] ?? 'publish',
                'posts_per_page' => -1,
                'meta_query' => [
                    'relation' => 'OR',
                    [
                        'key' => '_easy_invoice_number',
                        'value' => $search_query,
                        'compare' => 'LIKE'
                    ],
                    [
                        'key' => '_easy_invoice_customer_name',
                        'value' => $search_query,
                        'compare' => 'LIKE'
                    ],
                    [
                        'key' => '_easy_invoice_customer_email',
                        'value' => $search_query,
                        'compare' => 'LIKE'
                    ]
                ]
            ]);
            
            if ($meta_search->have_posts()) {
                $search_ids = array_merge($search_ids, wp_list_pluck($meta_search->posts, 'ID'));
            }
            
            // Remove duplicates
            $search_ids = array_unique($search_ids);
            
            if (!empty($search_ids)) {
                // Use post__in to filter by the found IDs
                $query_args['post__in'] = $search_ids;
            } else {
                // If no results found, set post__in to empty array to show no results
                $query_args['post__in'] = [0];
            }
        }
        
        // Remove offset as we're using paged
        unset($query_args['offset']);
        
        // Allow plugins to modify the final query arguments
        $query_args = apply_filters('easy_invoice_invoice_controller_final_query_args', $query_args);
        
        $wp_query = new WP_Query($query_args);
        $invoices = [];
        
        if ($wp_query->have_posts()) {
            foreach ($wp_query->posts as $post) {
                $invoice = $repository->find($post->ID);
                if ($invoice) {
                    $invoices[] = $invoice;
                }
            }
        }
        
        // Allow plugins to modify the invoices array
        $invoices = apply_filters('easy_invoice_invoice_controller_invoices_list', $invoices, $wp_query);
        
        // Get pagination info from WordPress query
        $total_invoices = $wp_query->found_posts;
        $total_pages = $wp_query->max_num_pages;
        
        // Get trash count for tab display (without pagination)
        $trash_args = ['post_status' => 'trash'];
        $trash_invoices = $repository->all($trash_args);
        $trash_count = count($trash_invoices);
        
        // Get draft count for tab display (without pagination)
        $draft_args = ['post_status' => 'draft'];
        $draft_invoices = $repository->all($draft_args);
        $draft_count = count($draft_invoices);
        
        // Prepare template data
        $template_data = [
            'invoices' => $invoices,
            'current_view' => $current_view,
            'status_filter' => $status_filter,
            'recurring_filter' => $recurring_filter,
            'search_query' => $search_query,
            'trash_count' => $trash_count,
            'draft_count' => $draft_count,
            'repository' => $repository,
            'current_page' => $current_page,
            'per_page' => $per_page,
            'total_invoices' => $total_invoices,
            'total_pages' => $total_pages,
            'wp_query' => $wp_query
        ];
        
        // Allow plugins to modify template data
        $template_data = apply_filters('easy_invoice_invoice_controller_template_data', $template_data);
        
        // Display the template
        $this->displayTemplate(
            EASY_INVOICE_PLUGIN_DIR . 'templates/invoices/listing.php',
            $template_data
        );
        
        // Allow plugins to perform actions after displaying invoices page
        do_action('easy_invoice_invoice_controller_after_display_invoices_page', $template_data);
    }
    
    /**
     * Display invoice builder page
     */
    protected function displayInvoiceBuilderPage() {
        $invoice_id = isset($_GET['id']) ? intval($_GET['id']) : 0;
        $repository = InvoiceServiceProvider::getInvoiceRepository();
        
        // Display the template
        $this->displayTemplate(
            EASY_INVOICE_PLUGIN_DIR . 'templates/invoices/builder.php',
            ['invoice_id' => $invoice_id, 'repository' => $repository]
        );
    }
    
    /**
     * Display invoice preview page
     */
    protected function displayPreviewPage() {
        $this->renderInvoicePreview();
    }
    
    /**
     * Common helper method to render an invoice preview
     * Used by both preview methods to ensure consistency
     */
    private function renderInvoicePreview() {
        $check = $this->checkCapability();
        if (is_wp_error($check)) {
            wp_die($check->get_error_message());
        }
        
        $invoice_id = isset($_GET['invoice_id']) ? intval($_GET['invoice_id']) : 0;
        
        if ($invoice_id <= 0) {
            wp_die(__('Invalid invoice ID', 'easy-invoice'));
        }
        
        // Get invoice from repository
        $repository = InvoiceServiceProvider::getInvoiceRepository();
        $invoice = $repository->find($invoice_id);
        
        if (!$invoice) {
            wp_die(__('Invalid invoice ID', 'easy-invoice'));
        }
        
        // Get common template variables
        $template_vars = $this->getCommonTemplateVars();
        $currency_symbol = $template_vars['currency_symbol'];
        
        // Enqueue preview styles
        wp_enqueue_style(
            'easy-invoice-preview',
            EASY_INVOICE_PLUGIN_URL . 'assets/css/preview.css',
            array(),
            EASY_INVOICE_VERSION
        );
        
        // Display the template
        $this->displayTemplate(
            EASY_INVOICE_PLUGIN_DIR . 'templates/invoices/preview.php',
            [
                'invoice' => $invoice,
                'currency_symbol' => $currency_symbol
            ]
        );
    }
    
    /**
     * Trash an invoice (move to trash)
     */
    public function trashInvoice() {
        if (!$this->handleAjaxSecurity($_POST['nonce'])) {
            return;
        }
        
        // Check invoice ID
        if (!isset($_POST['invoice_id']) || empty($_POST['invoice_id'])) {
            wp_send_json_error(array('message' => 'Invalid invoice ID'));
        }
        
        $invoice_id = intval($_POST['invoice_id']);
        
        // Get the invoice object to update status
        $invoice_repository = InvoiceServiceProvider::getInvoiceRepository();
        $invoice = $invoice_repository->find($invoice_id);
        if ($invoice) {
            // Set status to cancelled before moving to trash
            $invoice->setStatus('cancelled');
            $invoice->save();
        }
        
        // Move to trash
        $result = wp_trash_post($invoice_id);
        
        if ($result) {
            wp_send_json_success(array('message' => 'Invoice moved to trash'));
        } else {
            wp_send_json_error(array('message' => 'Error moving invoice to trash'));
        }
    }
    
    /**
     * Restore an invoice from trash
     */
    public function restoreInvoice() {
        if (!$this->handleAjaxSecurity($_POST['nonce'])) {
            return;
        }
        
        // Check invoice ID
        if (!isset($_POST['invoice_id']) || empty($_POST['invoice_id'])) {
            wp_send_json_error(array('message' => 'Invalid invoice ID'));
        }
        
        $invoice_id = intval($_POST['invoice_id']);
        
        // Restore from trash
        $result = wp_untrash_post($invoice_id);
        
        if ($result) {
            // WordPress defaults restored posts to 'draft', so we need to explicitly set it to 'publish'
            wp_update_post(array(
                'ID' => $invoice_id,
                'post_status' => 'publish'
            ));
            
            // Get the invoice object and set status to available
            $invoice_repository = InvoiceServiceProvider::getInvoiceRepository();
            $invoice = $invoice_repository->find($invoice_id);
            if ($invoice) {
                $invoice->setStatus('available');
                $invoice->save();
            }
            
            wp_send_json_success(array('message' => 'Invoice restored from trash'));
        } else {
            wp_send_json_error(array('message' => 'Error restoring invoice from trash'));
        }
    }
    
    /**
     * Delete an invoice permanently
     */
    public function deleteInvoicePermanently() {
        if (!$this->handleAjaxSecurity($_POST['nonce'])) {
            return;
        }
        
        // Check invoice ID
        if (!isset($_POST['invoice_id']) || empty($_POST['invoice_id'])) {
            wp_send_json_error(array('message' => 'Invalid invoice ID'));
        }
        
        $invoice_id = intval($_POST['invoice_id']);
        
        // Delete permanently
        $result = wp_delete_post($invoice_id, true);
        
        if ($result) {
            wp_send_json_success(array('message' => 'Invoice deleted permanently'));
        } else {
            wp_send_json_error(array('message' => 'Error deleting invoice'));
        }
    }
    
    /**
     * Legacy delete invoice handler (now redirects to trash)
     */
    public function deleteInvoice() {
        // Redirect to trash function for backward compatibility
        $this->trashInvoice();
    }
    
    /**
     * Publish an invoice (change status from draft to publish)
     */
    public function publishInvoice() {
        if (!$this->handleAjaxSecurity($_POST['nonce'])) {
            return;
        }
        
        // Check invoice ID
        if (!isset($_POST['invoice_id']) || empty($_POST['invoice_id'])) {
            wp_send_json_error(array('message' => 'Invalid invoice ID'));
        }
        
        $invoice_id = intval($_POST['invoice_id']);
        
        // Update post status to published
        $result = wp_update_post(array(
            'ID' => $invoice_id,
            'post_status' => 'publish'
        ));
        
        if ($result) {
            wp_send_json_success(array('message' => 'Invoice published successfully'));
        } else {
            wp_send_json_error(array('message' => 'Error publishing invoice'));
        }
    }
    
    /**
     * Set an invoice to draft status
     */
    public function draftInvoice() {
        if (!$this->handleAjaxSecurity($_POST['nonce'])) {
            return;
        }
        
        // Check invoice ID
        if (!isset($_POST['invoice_id']) || empty($_POST['invoice_id'])) {
            wp_send_json_error(array('message' => 'Invalid invoice ID'));
        }
        
        $invoice_id = intval($_POST['invoice_id']);
        
        // Update post status to draft
        $result = wp_update_post(array(
            'ID' => $invoice_id,
            'post_status' => 'draft'
        ));
        
        if ($result) {
            wp_send_json_success(array('message' => 'Invoice set to draft successfully'));
        } else {
            wp_send_json_error(array('message' => 'Error setting invoice to draft'));
        }
    }
    
    /**
     * Handle bulk actions
     */
    public function handleBulkActions() {
        // Check if we're processing a bulk action
        if (!isset($_POST['action']) || $_POST['action'] !== 'easy_invoice_bulk_action') {
            return;
        }
        
        // Check nonce and capability
        $security_check = $this->securityCheck($_POST['easy_invoice_bulk_nonce'], 'easy_invoice_bulk_action');
        if (is_wp_error($security_check)) {
            wp_die($security_check->get_error_message());
        }
        
        // Check if we have invoice IDs
        if (!isset($_POST['invoice_ids']) || !is_array($_POST['invoice_ids']) || empty($_POST['invoice_ids'])) {
            wp_redirect(admin_url('admin.php?page=easy-invoice-all&bulk_error=no_selection'));
            exit;
        }
        
        // Get bulk action and invoice IDs
        $bulk_action = isset($_POST['bulk_action']) ? sanitize_text_field($_POST['bulk_action']) : '';
        $invoice_ids = array_map('intval', $_POST['invoice_ids']);
        
        // Process based on action
        $processed = 0;
        
        switch ($bulk_action) {
            case 'trash':
                foreach ($invoice_ids as $id) {
                    if (wp_trash_post($id)) {
                        $processed++;
                    }
                }
                wp_redirect(admin_url('admin.php?page=easy-invoice-all&bulk_trashed=' . $processed));
                break;
                
            case 'restore':
                foreach ($invoice_ids as $id) {
                    if (wp_untrash_post($id)) {
                        // Also set status to publish (since WordPress sets it to draft by default)
                        wp_update_post(array(
                            'ID' => $id,
                            'post_status' => 'publish'
                        ));
                        $processed++;
                    }
                }
                wp_redirect(admin_url('admin.php?page=easy-invoice-all&bulk_restored=' . $processed));
                break;
                
            case 'delete':
                foreach ($invoice_ids as $id) {
                    if (wp_delete_post($id, true)) {
                        $processed++;
                    }
                }
                wp_redirect(admin_url('admin.php?page=easy-invoice-all&view=trash&bulk_deleted=' . $processed));
                break;
                
            case 'draft':
                foreach ($invoice_ids as $id) {
                    // Update post status to draft
                    if (wp_update_post(array(
                        'ID' => $id,
                        'post_status' => 'draft'
                    ))) {
                        $processed++;
                    }
                }
                wp_redirect(admin_url('admin.php?page=easy-invoice-all&bulk_drafted=' . $processed));
                break;
                
            case 'publish':
                foreach ($invoice_ids as $id) {
                    // Update post status to publish
                    if (wp_update_post(array(
                        'ID' => $id,
                        'post_status' => 'publish'
                    ))) {
                        $processed++;
                    }
                }
                wp_redirect(admin_url('admin.php?page=easy-invoice-all&bulk_published=' . $processed));
                break;
                
            default:
                wp_redirect(admin_url('admin.php?page=easy-invoice-all&bulk_error=invalid_action'));
        }
        
        exit;
    }
    
    /**
     * Get stats for dashboard 
     */
    public function getInvoiceStats() {
        $repository = InvoiceServiceProvider::getInvoiceRepository();
        $total_invoices = count($repository->all());
        $pending_invoices = count($repository->findByStatus('pending'));
        $paid_invoices = count($repository->findByStatus('paid'));
        
        // Get total revenue by currency from paid invoices
        $revenue_by_currency = [];
        $paid_invoices_list = $repository->findByStatus('paid');
        
        // First, get all currencies that exist in the system
        $all_invoices = $repository->all();
        $all_currencies = [];
        
        foreach ($all_invoices as $invoice) {
            $currency_code = $invoice->getCurrencyCode();
            
            // If currency is empty or "global", get the actual currency that was used
            if (empty($currency_code) || $currency_code === 'global') {
                // Get the actual currency from invoice meta
                $actual_currency = get_post_meta($invoice->getId(), '_easy_invoice_currency_code', true);
                $currency_code = !empty($actual_currency) ? $actual_currency : get_option('easy_invoice_currency_code', 'USD');
            }
            
            // If currency is still "global", use the global setting
            if ($currency_code === 'global') {
                $currency_code = get_option('easy_invoice_currency_code', 'USD');
            }
            
            // Normalize currency code to uppercase for consistent grouping
            $currency_code = strtoupper($currency_code);
            
            if (!empty($currency_code)) {
                $all_currencies[$currency_code] = \EasyInvoice\Helpers\CurrencyHelper::getCurrencySymbol($currency_code);
            }
        }
        
        // Initialize revenue for all currencies found
        foreach ($all_currencies as $currency_code => $currency_symbol) {
            $revenue_by_currency[$currency_code] = [
                'amount' => 0,
                'symbol' => $currency_symbol
            ];
        }
        
        // Now calculate revenue for paid invoices
        foreach ($paid_invoices_list as $invoice) {
            $invoice_total = $invoice->getTotal();
            if (!is_numeric($invoice_total)) {
                continue;
            }
            
            // Get the actual currency from the invoice
            $currency_code = $invoice->getCurrencyCode();
            
            // If currency is empty or "global", get the actual currency that was used
            if (empty($currency_code) || $currency_code === 'global') {
                // Get the actual currency from invoice meta
                $actual_currency = get_post_meta($invoice->getId(), '_easy_invoice_currency_code', true);
                $currency_code = !empty($actual_currency) ? $actual_currency : get_option('easy_invoice_currency_code', 'USD');
            }
            
            // If currency is still "global", use the global setting
            if ($currency_code === 'global') {
                $currency_code = get_option('easy_invoice_currency_code', 'USD');
            }
            
            // Normalize currency code to uppercase for consistent grouping
            $currency_code = strtoupper($currency_code);
            
            if (isset($revenue_by_currency[$currency_code])) {
                $revenue_by_currency[$currency_code]['amount'] += $invoice_total;
            }
        }
        
        // Calculate total value from ALL invoices (not just paid ones)
        $total_value_by_currency = [];
        
        // Initialize total value for all currencies found
        foreach ($all_currencies as $currency_code => $currency_symbol) {
            $total_value_by_currency[$currency_code] = [
                'amount' => 0,
                'invoices' => 0,
                'invoice_object' => null // Keep reference for formatting
            ];
        }
        
        // Calculate total value from all invoices
        foreach ($all_invoices as $invoice) {
            $invoice_total = $invoice->getTotal();
            if (!is_numeric($invoice_total)) {
                continue;
            }
            
            // Get the actual currency from the invoice
            $currency_code = $invoice->getCurrencyCode();
            
            // If currency is empty or "global", get the actual currency that was used
            if (empty($currency_code) || $currency_code === 'global') {
                // Get the actual currency from invoice meta
                $actual_currency = get_post_meta($invoice->getId(), '_easy_invoice_currency_code', true);
                $currency_code = !empty($actual_currency) ? $actual_currency : get_option('easy_invoice_currency_code', 'USD');
            }
            
            // If currency is still "global", use the global setting
            if ($currency_code === 'global') {
                $currency_code = get_option('easy_invoice_currency_code', 'USD');
            }
            
            // Normalize currency code to uppercase for consistent grouping
            $currency_code = strtoupper($currency_code);
            
            if (isset($total_value_by_currency[$currency_code])) {
                $total_value_by_currency[$currency_code]['amount'] += $invoice_total;
                $total_value_by_currency[$currency_code]['invoices']++;
                // Keep reference to first invoice for formatting
                if ($total_value_by_currency[$currency_code]['invoice_object'] === null) {
                    $total_value_by_currency[$currency_code]['invoice_object'] = $invoice;
                }
            }
        }
        
        return [
            'total_invoices' => $total_invoices,
            'pending_invoices' => $pending_invoices,
            'paid_invoices' => $paid_invoices,
            'total_revenue' => $revenue_by_currency,
            'total_value' => $total_value_by_currency
        ];
    }

    /**
     * Register additional AJAX handlers
     */
    public function registerAjaxHandlers() {
        add_action('wp_ajax_easy_invoice_load_template', array($this, 'handleLoadTemplate'));
        add_action('wp_ajax_easy_invoice_create_new_invoice', array($this, 'ajax_create_new_invoice'));
        add_action('wp_ajax_easy_invoice_search_clients', array($this, 'handleSearchClients'));
    }

    /**
     * Handle AJAX request to load invoice template
     */
    public function handleLoadTemplate() {
        // Verify nonce
        if (!isset($_POST['nonce']) || !wp_verify_nonce($_POST['nonce'], 'easy_invoice_nonce')) {
            wp_send_json_error(array('message' => __('Security check failed', 'easy-invoice')));
        }

        // Get template name
        $template = isset($_POST['template']) ? sanitize_text_field($_POST['template']) : 'standard';
        $invoice_id = isset($_POST['invoice_id']) ? intval($_POST['invoice_id']) : 0;

        // For new invoices (no ID), just return the template without invoice data
        if ($invoice_id === 0) {
            // Get template file path
            $template_file = EASY_INVOICE_PLUGIN_DIR . 'templates/invoice-templates/' . $template . '.php';
            
            if (!file_exists($template_file)) {
                $template_file = EASY_INVOICE_PLUGIN_DIR . 'templates/invoice-templates/standard.php';
            }

            // Start output buffering
            ob_start();
            
            // Set up empty variables for new invoices
            $invoice = null;
            $formatter = null;
            
            
            include_once $template_file;
            $html = ob_get_clean();

            // Send response
            wp_send_json_success(array('html' => $html));
            return;
        }

        // Get invoice data for existing invoices
        $repository = InvoiceServiceProvider::getInvoiceRepository();
        $invoice = $repository->find($invoice_id);

        if (!$invoice) {
            wp_send_json_error(array('message' => __('Invoice not found', 'easy-invoice')));
        }

        // Initialize formatter for currency formatting
        $formatter = new \EasyInvoice\Helpers\InvoiceFormatter($invoice);

        // Get template file path
        $template_file = EASY_INVOICE_PLUGIN_DIR . 'templates/invoice-templates/' . $template . '.php';
        
        if (!file_exists($template_file)) {
            $template_file = EASY_INVOICE_PLUGIN_DIR . 'templates/invoice-templates/standard.php';
        }

        // Start output buffering
        ob_start();
        include_once $template_file;
        $html = ob_get_clean();

        // Send response
        wp_send_json_success(array('html' => $html));
    }

    /**
     * Add meta box for manual payment verification to the invoice edit screen.
     */
    public function add_manual_payment_meta_box() {
        add_meta_box(
            'easy_invoice_manual_payment_verification',
            __('Manual Payment Verification', 'easy-invoice'),
            array($this, 'render_manual_payment_meta_box'),
            'easy-invoice', // Post type
            'side',         // Context
            'high'          // Priority
        );
    }

    /**
     * Render the manual payment verification meta box.
     *
     * @param \WP_Post $post The current post object.
     */
    public function render_manual_payment_meta_box(\WP_Post $post) {
        $payment_status = get_post_meta($post->ID, '_payment_status', true);
        $payment_method = get_post_meta($post->ID, '_payment_method', true);

        if (!in_array($payment_status, ['pending-bank', 'pending-cheque'])) {
            echo '<p>' . __('This invoice is not pending manual payment verification.', 'easy-invoice') . '</p>';
            return;
        }

        wp_nonce_field('easy_invoice_mark_paid_' . $post->ID, 'easy_invoice_mark_paid_nonce');

        echo '<h4>' . __('Submitted Payment Proof', 'easy-invoice') . '</h4>';

        if ($payment_method === 'bank') {
            $transaction_id = get_post_meta($post->ID, '_bank_transaction_id', true);
            $notes = get_post_meta($post->ID, '_bank_payment_notes', true);
            $proof_url = get_post_meta($post->ID, '_bank_payment_proof', true);

            echo '<p><strong>' . __('Transaction ID:', 'easy-invoice') . '</strong> ' . esc_html($transaction_id) . '</p>';
            if ($notes) {
                echo '<p><strong>' . __('Notes:', 'easy-invoice') . '</strong></p>';
                echo '<div style="white-space: pre-wrap; background: #f9f9f9; padding: 5px; border: 1px solid #eee;">' . esc_html($notes) . '</div>';
            }
            if ($proof_url) {
                echo '<p><strong>' . __('Proof Document:', 'easy-invoice') . '</strong> <a href="' . esc_url($proof_url) . '" target="_blank" rel="noopener noreferrer">' . __('View Proof', 'easy-invoice') . '</a></p>';
            }
        } elseif ($payment_method === 'cheque') {
            $cheque_number = get_post_meta($post->ID, '_cheque_number', true);
            $bank_name = get_post_meta($post->ID, '_cheque_bank_name', true);
            $cheque_date = get_post_meta($post->ID, '_cheque_date', true);
            $notes = get_post_meta($post->ID, '_cheque_notes', true);
            $image_url = get_post_meta($post->ID, '_cheque_image', true);

            echo '<p><strong>' . __('Cheque Number:', 'easy-invoice') . '</strong> ' . esc_html($cheque_number) . '</p>';
            if ($bank_name) echo '<p><strong>' . __('Bank Name:', 'easy-invoice') . '</strong> ' . esc_html($bank_name) . '</p>';
            if ($cheque_date) echo '<p><strong>' . __('Cheque Date:', 'easy-invoice') . '</strong> ' . esc_html($cheque_date) . '</p>';
            if ($notes) {
                echo '<p><strong>' . __('Notes:', 'easy-invoice') . '</strong></p>';
                echo '<div style="white-space: pre-wrap; background: #f9f9f9; padding: 5px; border: 1px solid #eee;">' . esc_html($notes) . '</div>';
            }
            if ($image_url) {
                echo '<p><strong>' . __('Cheque Image:', 'easy-invoice') . '</strong> <a href="' . esc_url($image_url) . '" target="_blank" rel="noopener noreferrer">' . __('View Image', 'easy-invoice') . '</a></p>';
            }
        }

        echo '<p style="margin-top: 15px;">';
        echo '<button type="button" id="easy-invoice-mark-paid-btn" class="button button-primary" data-invoice-id="' . esc_attr($post->ID) . '">' . __('Mark as Paid', 'easy-invoice') . '</button>';
        echo '</p>';
        echo '<div id="easy-invoice-mark-paid-message" style="margin-top:10px;"></div>';
        
        // Add a script for the AJAX call
        ?>
        <script type="text/javascript">
            jQuery(document).ready(function($) {
                $('#easy-invoice-mark-paid-btn').on('click', function() {
                    var invoiceId = $(this).data('invoice-id');
                    var nonce = $('#easy_invoice_mark_paid_nonce').val();
                    var button = $(this);
                    var messageDiv = $('#easy-invoice-mark-paid-message');

                    button.prop('disabled', true);
                    messageDiv.html('Processing...');

                    $.ajax({
                        url: ajaxurl, // WordPress AJAX URL
                        type: 'POST',
                        data: {
                            action: 'easy_invoice_mark_paid',
                            invoice_id: invoiceId,
                            nonce: nonce
                        },
                        success: function(response) {
                            if (response.success) {
                                messageDiv.css('color', 'green').html(response.data.message);
                                button.hide(); 
                                // Optionally, reload the page or update UI elements to reflect paid status
                                // window.location.reload(); 
                            } else {
                                messageDiv.css('color', 'red').html(response.data.message);
                                button.prop('disabled', false);
                            }
                        },
                        error: function() {
                            messageDiv.css('color', 'red').html('<?php echo esc_js(__("An error occurred. Please try again.", "easy-invoice")); ?>');
                            button.prop('disabled', false);
                        }
                    });
                });
            });
        </script>
        <?php
    }

    /**
     * AJAX handler to create a sample invoice.
     */
    public function ajax_create_sample_invoice() {
        // Security check: verify nonce
        check_ajax_referer('easy_invoice_admin_nonce', 'nonce');

        // Security check: verify user capabilities
        if (!current_user_can('edit_posts')) { // Or a more specific capability for your CPT
            wp_send_json_error([
                'message' => __('You do not have permission to create invoices.', 'easy-invoice')
            ], 403);
            return;
        }

        try {
            $invoice_repository = InvoiceServiceProvider::getInvoiceRepository();

            // Sample Invoice Data
            $sample_invoice_data = [
                'post_title' => 'Sample Invoice - ' . date('Y-m-d H:i'),
                'post_status' => 'draft', // Or 'publish' if you want it live immediately
                // Add other WP_Post fields as needed (e.g., post_author)
            ];

            // Sample Meta Data
            $sample_meta_data = [
                '_easy_invoice_number' => 'SAMPLE-' . time(),
                '_easy_invoice_issue_date' => date('Y-m-d'),
                '_easy_invoice_due_date' => date('Y-m-d', strtotime('+15 days')),
                '_easy_invoice_status' => 'draft',
                '_easy_invoice_customer_name' => 'John Doe (Sample Client)',
                '_easy_invoice_customer_email' => 'customer@example.com',
                '_easy_invoice_customer_address' => "123 Sample Street\nSampleville, ST 12345",
                'currency_code' => 'USD',
                'currency_position' => 'before',
                // Add other meta keys as needed
            ];

            // Sample Line Items
            $sample_items = [];
            for ($i = 1; $i <= 3; $i++) {
                $sample_items[] = [
                    'name' => 'Sample Service ' . $i,
                    'description' => 'Detailed description of sample service ' . $i . '.',
                    'quantity' => rand(1, 5),
                    'price' => rand(50, 200) * 1.00,
                    // 'taxable' => true/false (optional)
                ];
            }
            $sample_meta_data['_easy_invoice_items'] = $sample_items;

            // Create the invoice post
            $invoice_id = wp_insert_post($sample_invoice_data, true); // true for WP_Error on failure

            if (is_wp_error($invoice_id)) {
                throw new \Exception('Failed to create invoice post: ' . $invoice_id->get_error_message());
            }

            // Set invoice meta data
            foreach ($sample_meta_data as $key => $value) {
                update_post_meta($invoice_id, $key, $value);
            }
            
            // Recalculate totals if your Invoice model or repository has a method for it
            // For example, if you have $invoice->calculateTotals()->save(); or similar.
            // This step is crucial if subtotal, tax, total are not directly set but calculated.
            // For now, we assume they might be calculated on load or save by other parts of your plugin.
            // If not, you'd need to calculate and save them here.
            // Example (conceptual):
            // $invoice_object = $invoice_repository->find($invoice_id);
            // if ($invoice_object) {
            //     $invoice_object->setItems($sample_items); // This might trigger calculations if model is designed so
            //     // Or call a specific method: $invoice_object->recalculateAndSaveTotals();
            // }

            wp_send_json_success([
                'message' => __('Sample invoice created successfully!', 'easy-invoice'),
                'invoice_id' => $invoice_id,
                'edit_link' => admin_url('admin.php?page=easy-invoice-builder&id=' . $invoice_id)
            ]);

        } catch (\Exception $e) {
            wp_send_json_error([
                'message' => __('Error creating sample invoice:', 'easy-invoice') . ' ' . $e->getMessage()
            ], 500);
        }
    }

    /**
     * AJAX handler for creating a new invoice with title
     */
    public function ajax_create_new_invoice() {
        // Security check: verify nonce
        check_ajax_referer('easy_invoice_nonce', 'nonce');

        // Security check: verify user capabilities
        if (!current_user_can('edit_posts')) {
            wp_send_json_error([
                'message' => __('You do not have permission to create invoices.', 'easy-invoice')
            ], 403);
            return;
        }

        // Get the invoice title
        $title = isset($_POST['title']) ? sanitize_text_field($_POST['title']) : '';
        
        if (empty($title)) {
            wp_send_json_error([
                'message' => __('Invoice title is required.', 'easy-invoice')
            ], 400);
            return;
        }

        try {
            $invoice_repository = InvoiceServiceProvider::getInvoiceRepository();

            // Prepare invoice data for repository
            $invoice_data = [
                'title' => $title,
                'post_status' => 'draft',
                'issue_date' => date('Y-m-d'),
                'due_date' => date('Y-m-d', strtotime('+30 days')),
                'status' => 'draft',
                'invoice_template' => 'standard'
            ];

            // Create the invoice using repository (this will auto-generate invoice number)
            $invoice = $invoice_repository->create($invoice_data);

            if (!$invoice) {
                throw new \Exception('Failed to create invoice');
            }

            // Debug: Check if invoice number was set
            $invoice_number = $invoice->getNumber();
            if (empty($invoice_number)) {
                // Force set the invoice number if it's empty
                $invoice_number_service = easy_invoice_get_invoice_number_service();
                $generated_number = $invoice_number_service->generateUniqueNumber();
                $invoice->setNumber($generated_number);
                $invoice->save();
            }

            wp_send_json_success([
                'message' => __('Invoice created successfully!', 'easy-invoice'),
                'invoice_id' => $invoice->getId(),
                'invoice_number' => $invoice->getNumber(),
                'redirect_url' => admin_url('admin.php?page=easy-invoice-builder&invoice_id=' . $invoice->getId())
            ]);

        } catch (\Exception $e) {
            wp_send_json_error([
                'message' => __('Error creating invoice:', 'easy-invoice') . ' ' . $e->getMessage()
            ], 500);
        }
    }

    /**
     * Handle search clients AJAX request
     *
     * @since 1.0.0
     */
    public function handleSearchClients(): void {
        // Verify nonce
        if (!wp_verify_nonce($_POST['nonce'] ?? '', 'easy_invoice_nonce')) {
            wp_send_json_error(['message' => __('Security check failed.', 'easy-invoice')]);
        }
        
        // Check permissions
        if (!current_user_can('manage_options')) {
            wp_send_json_error(['message' => __('Insufficient permissions.', 'easy-invoice')]);
        }
        
        $query = sanitize_text_field($_POST['query'] ?? '');
        
        // Get client repository
        $client_repository = \EasyInvoice\Providers\ClientServiceProvider::getClientRepository();
        
        // If query is empty, get all clients
        if (empty($query)) {
            $clients = $client_repository->all();
        } else {
            // Search clients by name, email, or company
            $clients = $client_repository->search($query);
        }
        
        $results = [];
        foreach ($clients as $client) {
            $results[] = [
                'id' => $client->getId(),
                'name' => $client->getBusinessClientName() ?: ($client->getFirstName() . ' ' . $client->getLastName()),
                'email' => $client->getEmail(),
                'company' => $client->getBusinessClientName(),
                'phone' => $client->getExtraInfo(),
                'website' => $client->getWebsite(),
                'address' => $client->getAddress()
            ];
        }
        
        wp_send_json_success($results);
    }
} 
```
