/home/edulekha/crm.edulekha.com/modules/appointly/models/Appointlyinvoices_model.php
<?php

defined('BASEPATH') or exit('No direct script access allowed');

class Appointlyinvoices_model extends App_Model
{
    public function __construct()
    {
        parent::__construct();
    }

    /**
     * Create invoice from appointment
     * @param int $appointment_id
     * @return int|false Invoice ID or false on failure
     */
    public function create_invoice_from_appointment($appointment_id)
    {
        // Get appointment details
        $this->db->select('a.*, s.name as service_name, s.price as service_price');
        $this->db->from(db_prefix() . 'appointly_appointments a');
        $this->db->join(db_prefix() . 'appointly_services s', 's.id = a.service_id', 'left');
        $this->db->where('a.id', $appointment_id);
        $appointment = $this->db->get()->row_array();

        if (!$appointment) {
            return false;
        }

        // Only create invoice for appointments with contact_id
        if (empty($appointment['contact_id'])) {
            return false;
        }

        // Check if invoice already exists
        if (!empty($appointment['invoice_id'])) {
            return $appointment['invoice_id'];
        }

        // Skip invoice creation for free services (price = 0)
        if (empty($appointment['service_price']) || $appointment['service_price'] <= 0) {
            return false;
        }

        // Get contact details
        $this->db->select('c.*, cl.company');
        $this->db->from(db_prefix() . 'contacts c');
        $this->db->join(db_prefix() . 'clients cl', 'cl.userid = c.userid', 'left');
        $this->db->where('c.id', $appointment['contact_id']);
        $contact = $this->db->get()->row_array();

        if (!$contact) {
            return false;
        }

        // Prepare invoice data
        $invoice_data = [
            'clientid' => $contact['userid'],
            'date' => date('Y-m-d'),
            'duedate' => date('Y-m-d', strtotime('+' . get_option('invoice_due_after', 30) . ' DAY')),
            'currency' => get_base_currency()->id,
            'number' => get_option('next_invoice_number'),
            'subtotal' => 0,
            'total' => 0,
            'adminnote' => 'Invoice created from appointment #' . $appointment_id,
            'status' => 1, // Unpaid
            'addedfrom' => get_staff_user_id() ?: 1,
            'show_quantity_as' => 1,
            'billing_street' => $contact['address'] ?? '',
            'billing_city' => '',
            'billing_state' => '',
            'billing_zip' => '',
            'billing_country' => 0,
            'shipping_street' => '',
            'shipping_city' => '',
            'shipping_state' => '',
            'shipping_zip' => '',
            'shipping_country' => 0,
            'terms' => get_option('predefined_terms_invoice'),
            'clientnote' => get_option('predefined_clientnote_invoice'),
        ];

        // Determine service price
        $service_price = 0;
        if (!empty($appointment['service_price']) && $appointment['service_price'] > 0) {
            $service_price = $appointment['service_price'];
        }

        // Determine tax to apply based on settings
        $tax_names = [];
        $tax_rate_total = 0;
        $tax_type = get_option('appointly_invoice_tax_type', 'none');

        if ($tax_type === 'custom') {
            // Custom percentage tax
            $custom_vat = get_option('appointly_invoice_default_vat', 0);
            if ($custom_vat > 0) {
                $tax_names[] = 'VAT|' . $custom_vat;
                $tax_rate_total += $custom_vat;
            }
        } elseif ($tax_type === 'system') {
            // Use CRM tax rate
            $system_tax_id = get_option('appointly_invoice_system_tax');
            if ($system_tax_id) {
                $this->db->where('id', $system_tax_id);
                $tax = $this->db->get(db_prefix() . 'taxes')->row();
                if ($tax) {
                    $tax_names[] = $tax->name . '|' . $tax->taxrate;
                    $tax_rate_total += $tax->taxrate;
                }
            }
        }

        // Calculate totals manually (Perfex expects these to be pre-calculated)
        $subtotal = $service_price * 1; // qty * rate
        $tax_amount = ($subtotal * $tax_rate_total) / 100;
        $total = $subtotal + $tax_amount;

        // Update invoice data with calculated totals
        $invoice_data['subtotal'] = $subtotal;
        $invoice_data['total'] = $total;

        // Add items to invoice data using Perfex's newitems pattern
        $invoice_data['newitems'] = [
            [
                'description' => $appointment['service_name'] ?: 'Appointment Service',
                'long_description' => $appointment['subject'] . (!empty($appointment['description']) ? "\n" . $appointment['description'] : ''),
                'qty' => 1,
                'unit' => '',
                'rate' => $service_price,
                'taxname' => $tax_names,
                'order' => 1,
            ]
        ];

        // Create invoice
        $this->load->model('invoices_model');

        // Clear any lingering $_POST data that might contaminate invoice creation
        // Perfex's invoices_model->add() checks $_POST for additional items
        $original_post = $_POST;
        $_POST = [];

        $invoice_id = $this->invoices_model->add($invoice_data);

        // Restore original $_POST data
        $_POST = $original_post;

        if ($invoice_id) {
            // Force status to unpaid (Perfex auto-marks $0 invoices as paid)
            $this->db->where('id', $invoice_id);
            $this->db->update(db_prefix() . 'invoices', [
                'status' => 1, // Force unpaid status
            ]);

            // Update appointment with invoice ID
            $this->db->where('id', $appointment_id);
            $this->db->update(db_prefix() . 'appointly_appointments', [
                'invoice_id' => $invoice_id,
                'invoice_date' => date('Y-m-d H:i:s'),
            ]);

            return $invoice_id;
        }

        return false;
    }
}