Example #1
0
 private static function save_job_tasks($job_id, $data)
 {
     $result = array('status' => false);
     $check_completed = false;
     $job_data = false;
     $job_task_creation_permissions = self::get_job_task_creation_permissions();
     // check for new tasks or changed tasks.
     $tasks = self::get_tasks($job_id);
     if (isset($data['job_task']) && is_array($data['job_task'])) {
         foreach ($data['job_task'] as $task_id => $task_data) {
             if (isset($task_data['manual_percent']) && strlen($task_data['manual_percent']) == 0) {
                 unset($task_data['manual_percent']);
             }
             $original_task_id = $task_id;
             $task_id = (int) $task_id;
             if (!is_array($task_data)) {
                 continue;
             }
             if ($task_id > 0 && !isset($tasks[$task_id])) {
                 $task_id = 0;
                 // creating a new task on this job.
             }
             if (!isset($task_data['description']) || $task_data['description'] == '' || $task_data['description'] == _TASK_DELETE_KEY) {
                 if ($task_id > 0 && $task_data['description'] == _TASK_DELETE_KEY) {
                     // remove task.
                     // but onyl remove it if it hasn't been invoiced.
                     if (isset($tasks[$task_id]) && $tasks[$task_id]['invoiced']) {
                         // it has been invoiced! dont remove it.
                         set_error('Unable to remove an invoiced task');
                         $result['status'] = 'error';
                         break;
                         // break out of loop saving tasks.
                     } else {
                         $sql = "DELETE FROM `" . _DB_PREFIX . "task` WHERE task_id = '{$task_id}' AND job_id = {$job_id} LIMIT 1";
                         query($sql);
                         $sql = "DELETE FROM `" . _DB_PREFIX . "task_log` WHERE task_id = '{$task_id}'";
                         query($sql);
                         $result['status'] = 'deleted';
                         $result['task_id'] = $task_id;
                     }
                 }
                 continue;
             }
             // add / save this task.
             $task_data['job_id'] = $job_id;
             if (module_job::job_task_only_show_split_hours($job_id, $job_data, $task_id, $task_data)) {
                 if (isset($task_data['hours']) && !isset($task_data['staff_hours'])) {
                     $task_data['staff_hours'] = $task_data['hours'];
                     $task_data['staff_amount'] = $task_data['amount'];
                 }
                 if (isset($task_data['hours'])) {
                     unset($task_data['hours']);
                     unset($task_data['amount']);
                 }
             }
             if (isset($task_data['hours'])) {
                 $task_data['hours'] = function_exists('decimal_time_in') ? decimal_time_in($task_data['hours']) : $task_data['hours'];
             }
             if (isset($task_data['staff_hours'])) {
                 $task_data['staff_hours'] = function_exists('decimal_time_in') ? decimal_time_in($task_data['staff_hours']) : $task_data['staff_hours'];
             }
             if (isset($task_data['log_hours'])) {
                 $task_data['log_hours'] = function_exists('decimal_time_in') ? decimal_time_in($task_data['log_hours']) : $task_data['log_hours'];
             }
             // remove the amount of it equals the hourly rate.
             if (isset($task_data['amount']) && $task_data['amount'] != 0 && isset($task_data['hours']) && $task_data['hours'] > 0) {
                 if (isset($data['hourly_rate']) && $task_data['amount'] - $task_data['hours'] * $data['hourly_rate'] == 0) {
                     unset($task_data['amount']);
                 }
             }
             if (isset($task_data['staff_amount']) && $task_data['staff_amount'] != 0 && isset($task_data['staff_hours']) && $task_data['staff_hours'] > 0) {
                 if (isset($data['staff_hourly_rate']) && $task_data['staff_amount'] - $task_data['staff_hours'] * $data['staff_hourly_rate'] == 0) {
                     unset($task_data['staff_amount']);
                 }
             }
             // check if we haven't unticked a non-hourly task
             if (isset($task_data['fully_completed_t']) && $task_data['fully_completed_t']) {
                 if (!isset($task_data['fully_completed']) || !$task_data['fully_completed']) {
                     // we have unchecked that tickbox
                     $task_data['fully_completed'] = 0;
                 } else {
                     if (isset($tasks[$task_id]) && !$tasks[$task_id]['fully_completed']) {
                         // we completed a preveiously incomplete task.
                         // chekc if this task has a custom percentage filled in, we remove this custom percentage.
                         if (isset($task_data['manual_percent']) && $task_data['manual_percent'] >= 0) {
                             $task_data['manual_percent'] = -1;
                         }
                         // hack: if we haven't logged any hours for this, we log the number of hours.
                         // if we have logged some hours already then we don't log anything extra.
                         // this is so they can log 0.5hours for a 1 hour completed task etc..
                         if (isset($task_data['hours']) && $task_data['hours'] > 0 && (!isset($task_data['log_hours']) || !$task_data['log_hours'])) {
                             $logged_hours = 0;
                             foreach (get_multiple('task_log', array('job_id' => $job_id, 'task_id' => $task_id)) as $task_log) {
                                 $logged_hours += $task_log['hours'];
                             }
                             if ($logged_hours == 0) {
                                 $task_data['log_hours'] = $task_data['hours'];
                             }
                         }
                     }
                 }
                 $check_completed = true;
             }
             // check if we haven't unticked a billable task
             if (isset($task_data['billable_t']) && $task_data['billable_t'] && !isset($task_data['billable'])) {
                 $task_data['billable'] = 0;
             }
             // set default taxable status
             if (!$task_id && !isset($task_data['taxable_t'])) {
                 // we're creating a new task.
                 $task_data['taxable'] = module_config::c('task_taxable_default', 1);
             }
             if (isset($task_data['taxable_t']) && $task_data['taxable_t'] && !isset($task_data['taxable'])) {
                 $task_data['taxable'] = 0;
             }
             if (isset($task_data['completed']) && $task_data['completed'] > 0) {
                 // check the completed date of all our tasks.
                 $check_completed = true;
             }
             if (!$task_id && isset($task_data['new_fully_completed']) && $task_data['new_fully_completed']) {
                 $task_data['fully_completed'] = 1;
                 // is this bad for set amount tasks?
                 if (!isset($task_data['date_done']) || !$task_data['date_done']) {
                     $task_data['date_done'] = print_date(time());
                 }
                 if (isset($task_data['hours'])) {
                     $task_data['log_hours'] = $task_data['hours'];
                 }
                 $check_completed = true;
             }
             // todo: move the task creation code into a public method so that the public user can add tasks to their jobs.
             if (!$task_id && module_security::is_logged_in() && !module_job::can_i('create', 'Job Tasks')) {
                 continue;
                 // dont allow new tasks.
             }
             // check if the user is allowed to create new tasks.
             // check the approval status of jobs
             switch ($job_task_creation_permissions) {
                 case _JOB_TASK_CREATION_NOT_ALLOWED:
                     if (!$task_id) {
                         continue;
                         // dont allow new tasks.
                     }
                     break;
                 case _JOB_TASK_CREATION_REQUIRES_APPROVAL:
                     $task_data['approval_required'] = 1;
                     break;
                 case _JOB_TASK_CREATION_WITHOUT_APPROVAL:
                     // no action required .
                     break;
             }
             if (isset($tasks[$task_id]) && $tasks[$task_id]['approval_required'] == 2) {
                 // task has been rejected, saving it again for approval.
                 $task_data['approval_required'] = 1;
             }
             $task_id = update_insert('task_id', $task_id, 'task', $task_data);
             // todo - fix cross task job boundary issue. meh.
             $result['task_id'] = $task_id;
             if ($task_id != $original_task_id) {
                 $result['status'] = 'created';
             } else {
                 $result['status'] = 'edited';
             }
             if ($task_id && isset($task_data['log_hours']) && (double) $task_data['log_hours'] > 0) {
                 // we are increasing the task complete hours by the amount specified in log hours.
                 // log a new task record, and incrase the "completed" column.
                 //$original_task_data = $tasks[$task_id];
                 //$task_data['completed'] = $task_data['completed'] + $task_data['log_hours'];
                 // only log hours if it's an hourly task.
                 if (!isset($task_data['manual_task_type']) || $task_data['manual_task_type'] < 0) {
                     if (!$job_data) {
                         $job_data = self::get_job($job_id);
                     }
                     $task_data['manual_task_type'] = $job_data['default_task_type'];
                 }
                 if ($task_data['manual_task_type'] == _TASK_TYPE_HOURS_AMOUNT) {
                     update_insert('task_log_id', 'new', 'task_log', array('task_id' => $task_id, 'job_id' => $job_id, 'hours' => (double) $task_data['log_hours'], 'log_time' => time()));
                     $result['log_hours'] = $task_data['log_hours'];
                 }
             }
         }
     }
     if ($check_completed) {
         self::update_job_completion_status($job_id);
     }
     module_cache::clear('job');
     return $result;
 }
Example #2
0
 public static function save_invoice($invoice_id, $data)
 {
     if (!(int) $invoice_id && isset($data['job_id']) && $data['job_id']) {
         $linkedjob = module_job::get_job($data['job_id']);
         $data['currency_id'] = $linkedjob['currency_id'];
         $data['customer_id'] = $linkedjob['customer_id'];
     }
     if ($invoice_id) {
         // used when working out the hourly rate fix below
         $original_invoice_data = self::get_invoice($invoice_id);
     } else {
         $original_invoice_data = 0;
     }
     $invoice_id = update_insert("invoice_id", $invoice_id, "invoice", $data);
     if ($invoice_id) {
         module_cache::clear('invoice');
         // save the invoice tax rates (copied to finance.php)
         if (isset($data['tax_ids']) && isset($data['tax_names']) && $data['tax_percents']) {
             $existing_taxes = get_multiple('invoice_tax', array('invoice_id' => $invoice_id), 'invoice_tax_id', 'exact', 'order');
             $order = 1;
             foreach ($data['tax_ids'] as $key => $val) {
                 if (isset($data['tax_percents'][$key]) && $data['tax_percents'][$key] == 0) {
                     // we are not saving this particular tax item because it has a 0% tax rate
                 } else {
                     if ((int) $val > 0 && isset($existing_taxes[$val])) {
                         // this means we are trying to update an existing record on the invoice_tax table, we confirm this id matches this invoice.
                         $invoice_tax_id = $val;
                         unset($existing_taxes[$invoice_tax_id]);
                         // so we know which ones to remove from the end.
                     } else {
                         $invoice_tax_id = false;
                         // create new record
                     }
                     $invoice_tax_data = array('invoice_id' => $invoice_id, 'percent' => isset($data['tax_percents'][$key]) ? $data['tax_percents'][$key] : 0, 'amount' => 0, 'name' => isset($data['tax_names'][$key]) ? $data['tax_names'][$key] : 'TAX', 'order' => $order++, 'increment' => isset($data['tax_increment_checkbox']) && $data['tax_increment_checkbox'] ? 1 : 0);
                     $invoice_tax_id = update_insert('invoice_tax_id', $invoice_tax_id, 'invoice_tax', $invoice_tax_data);
                 }
             }
             foreach ($existing_taxes as $existing_tax) {
                 delete_from_db('invoice_tax', array('invoice_id', 'invoice_tax_id'), array($invoice_id, $existing_tax['invoice_tax_id']));
             }
         }
         $invoice_data = self::get_invoice($invoice_id);
         if (!$invoice_data) {
             set_error('No permissions to access invoice.');
             return $invoice_id;
         }
         // check for new invoice_items or changed invoice_items.
         $invoice_items = self::get_invoice_items($invoice_id, $invoice_data);
         if (isset($data['invoice_invoice_item']) && is_array($data['invoice_invoice_item'])) {
             foreach ($data['invoice_invoice_item'] as $invoice_item_id => $invoice_item_data) {
                 $invoice_item_id = (int) $invoice_item_id;
                 if (!is_array($invoice_item_data)) {
                     continue;
                 }
                 if ($invoice_item_id > 0 && !isset($invoice_items[$invoice_item_id])) {
                     continue;
                 }
                 // wrong invoice_item save - will never happen.
                 if (!isset($invoice_item_data['description']) || $invoice_item_data['description'] == '') {
                     if ($invoice_item_id > 0) {
                         // remove invoice_item.
                         $sql = "DELETE FROM `" . _DB_PREFIX . "invoice_item` WHERE invoice_item_id = '{$invoice_item_id}' AND invoice_id = {$invoice_id} LIMIT 1";
                         query($sql);
                     }
                     continue;
                 }
                 // add / save this invoice_item.
                 $invoice_item_data['invoice_id'] = $invoice_id;
                 // what type of task is this?
                 $invoice_task_type = isset($invoice_item_data['manual_task_type']) && $invoice_item_data['manual_task_type'] >= 0 ? $invoice_item_data['manual_task_type'] : $invoice_data['default_task_type'];
                 $invoice_item_data['hours_mins'] = 0;
                 if (isset($invoice_item_data['hours']) && $invoice_task_type == _TASK_TYPE_HOURS_AMOUNT) {
                 }
                 if (isset($invoice_item_data['hours']) && $invoice_task_type == _TASK_TYPE_HOURS_AMOUNT && function_exists('decimal_time_in')) {
                     $invoice_item_data['hours'] = decimal_time_in($invoice_item_data['hours']);
                     if (strpos($invoice_item_data['hours'], ':') !== false) {
                         $invoice_item_data['hours_mins'] = str_replace(":", ".", $invoice_item_data['hours']);
                     }
                 } else {
                     if (isset($invoice_item_data['hours']) && strlen($invoice_item_data['hours'])) {
                         $invoice_item_data['hours'] = number_in($invoice_item_data['hours']);
                     } else {
                         $invoice_item_data['hours'] = 0;
                     }
                 }
                 // number formatting
                 //print_r($invoice_item_data);
                 if (isset($invoice_item_data['hourly_rate']) && strlen($invoice_item_data['hourly_rate'])) {
                     $invoice_item_data['hourly_rate'] = number_in($invoice_item_data['hourly_rate'], module_config::c('task_amount_decimal_places', -1));
                 }
                 //print_r($invoice_item_data);exit;
                 // somenew hacks here to support out new method of creating an item.
                 // the 'amount' column is never edited any more
                 // this column is now always automatically calculated based on
                 // 'hours' and 'hourly_rate'
                 if (!isset($invoice_item_data['amount'])) {
                     if ($invoice_task_type == _TASK_TYPE_AMOUNT_ONLY) {
                         // ignore the quantity field all together.
                         $invoice_item_data['amount'] = $invoice_item_data['hourly_rate'];
                         $invoice_item_data['hourly_rate'] = 0;
                     } else {
                         if (isset($invoice_item_data['hourly_rate']) && strlen($invoice_item_data['hourly_rate']) > 0) {
                             // if we have inputted an hourly rate (ie: not left empty)
                             if (isset($invoice_item_data['hours']) && strlen($invoice_item_data['hours']) == 0) {
                                 // no hours entered (eg: empty) so we treat whatever was in 'hourly_rate' as the amount
                                 $invoice_item_data['amount'] = $invoice_item_data['hourly_rate'];
                             } else {
                                 if (isset($invoice_item_data['hours']) && strlen($invoice_item_data['hours']) > 0) {
                                     // hours inputted, along with hourly rate. work out the new amount.
                                     $invoice_item_data['amount'] = round($invoice_item_data['hours'] * $invoice_item_data['hourly_rate'], module_config::c('currency_decimal_places', 2));
                                 }
                             }
                         }
                     }
                 }
                 if ($invoice_task_type == _TASK_TYPE_HOURS_AMOUNT) {
                     if ($invoice_item_data['hourly_rate'] == $invoice_data['hourly_rate'] || isset($original_invoice_data['hourly_rate']) && $invoice_item_data['hourly_rate'] == $original_invoice_data['hourly_rate']) {
                         $invoice_item_data['hourly_rate'] = -1;
                     }
                 }
                 // remove the amount of it equals the hourly rate.
                 /*if(isset($invoice_item_data['amount']) && isset($invoice_item_data['hours']) && $invoice_item_data['amount'] > 0 && $invoice_item_data['hours'] > 0){
                       if($invoice_item_data['amount'] - ($invoice_item_data['hours'] * $data['hourly_rate']) == 0){
                           unset($invoice_item_data['amount']);
                       }
                   }*/
                 // check if we haven't unticked a non-hourly invoice_item
                 /*if(isset($invoice_item_data['completed_t']) && $invoice_item_data['completed_t'] && !isset($invoice_item_data['completed'])){
                       $invoice_item_data['completed'] = 0;
                   }*/
                 if (!isset($invoice_item_data['taxable_t'])) {
                     $invoice_item_data['taxable'] = module_config::c('task_taxable_default', 1);
                 } else {
                     if (isset($invoice_item_data['taxable_t']) && $invoice_item_data['taxable_t'] && !isset($invoice_item_data['taxable'])) {
                         $invoice_item_data['taxable'] = 0;
                     }
                 }
                 if (!strlen($invoice_item_data['hours'])) {
                     $invoice_item_data['hours'] = 0;
                 }
                 $invoice_item_data['hourly_rate'] = number_out($invoice_item_data['hourly_rate'], false, module_config::c('task_amount_decimal_places', -1));
                 $invoice_item_data['hours'] = number_out($invoice_item_data['hours']);
                 $invoice_item_data['amount'] = number_out($invoice_item_data['amount']);
                 update_insert('invoice_item_id', $invoice_item_id, 'invoice_item', $invoice_item_data);
             }
         }
         $last_payment_time = 0;
         if (isset($data['invoice_invoice_payment']) && is_array($data['invoice_invoice_payment'])) {
             foreach ($data['invoice_invoice_payment'] as $invoice_payment_id => $invoice_payment_data) {
                 $invoice_payment_id = (int) $invoice_payment_id;
                 if (!is_array($invoice_payment_data)) {
                     continue;
                 }
                 if (isset($invoice_payment_data['amount'])) {
                     $invoice_payment_data['amount'] = number_in($invoice_payment_data['amount']);
                     // toggle between 'normal' and 'refund' payment types
                     if (isset($invoice_payment_data['payment_type'])) {
                         if ($invoice_payment_data['amount'] < 0 && $invoice_payment_data['payment_type'] == _INVOICE_PAYMENT_TYPE_NORMAL) {
                             // this is a refund.
                             $invoice_payment_data['payment_type'] = _INVOICE_PAYMENT_TYPE_REFUND;
                         } else {
                             if ($invoice_payment_data['payment_type'] == _INVOICE_PAYMENT_TYPE_REFUND) {
                                 $invoice_payment_data['payment_type'] = _INVOICE_PAYMENT_TYPE_NORMAL;
                             }
                         }
                     }
                 }
                 // check this invoice payment actually matches this invoice.
                 $invoice_payment_data_existing = false;
                 if ($invoice_payment_id > 0) {
                     $invoice_payment_data_existing = get_single('invoice_payment', array('invoice_payment_id', 'invoice_id'), array($invoice_payment_id, $invoice_id));
                     if (!$invoice_payment_data_existing || $invoice_payment_data_existing['invoice_payment_id'] != $invoice_payment_id || $invoice_payment_data_existing['invoice_id'] != $invoice_id) {
                         $invoice_payment_id = 0;
                         $invoice_payment_data_existing = false;
                     }
                 }
                 if (!isset($invoice_payment_data['amount']) || $invoice_payment_data['amount'] == '' || $invoice_payment_data['amount'] == 0) {
                     // || $invoice_payment_data['amount'] <= 0
                     if ($invoice_payment_id > 0) {
                         // if this is a customer credit payment, return that back to the customer account.
                         if ($invoice_payment_data_existing && $invoice_data['customer_id']) {
                             switch ($invoice_payment_data_existing['payment_type']) {
                                 case _INVOICE_PAYMENT_TYPE_CREDIT:
                                     module_customer::add_credit($invoice_data['customer_id'], $invoice_payment_data_existing['amount'], 'Refunded credit from invoice payment');
                                     break;
                             }
                         }
                         // remove invoice_payment.
                         $sql = "DELETE FROM `" . _DB_PREFIX . "invoice_payment` WHERE invoice_payment_id = '{$invoice_payment_id}' AND invoice_id = {$invoice_id} LIMIT 1";
                         query($sql);
                         // delete any existing transactions from the system as well.
                         hook_handle_callback('invoice_payment_deleted', $invoice_payment_id, $invoice_id);
                     }
                     continue;
                 }
                 if (!$invoice_payment_id && (!isset($_REQUEST['add_payment']) || $_REQUEST['add_payment'] != 'go')) {
                     continue;
                     // not saving a new one.
                 }
                 // add / save this invoice_payment.
                 $invoice_payment_data['invoice_id'] = $invoice_id;
                 // $invoice_payment_data['currency_id'] = $invoice_data['currency_id'];
                 $last_payment_time = max($last_payment_time, strtotime(input_date($invoice_payment_data['date_paid'])));
                 if (isset($invoice_payment_data['custom_notes'])) {
                     $details = @unserialize($invoice_payment_data['data']);
                     if (!is_array($details)) {
                         $details = array();
                     }
                     $details['custom_notes'] = $invoice_payment_data['custom_notes'];
                     $invoice_payment_data['data'] = serialize($details);
                 }
                 $invoice_payment_data['amount'] = number_out($invoice_payment_data['amount']);
                 update_insert('invoice_payment_id', $invoice_payment_id, 'invoice_payment', $invoice_payment_data);
             }
         }
         if (!$last_payment_time) {
             $last_payment_time = strtotime(date('Y-m-d'));
         }
         // check if the invoice has been paid
         module_cache::clear('invoice');
         //module_cache::clear_cache(); // this helps fix the bug where part payments are not caulcated a correct paid date.
         $invoice_data = self::get_invoice($invoice_id);
         if (!$invoice_data) {
             set_error('No permissions to access invoice.');
             return $invoice_id;
         }
         if ((!$invoice_data['date_paid'] || $invoice_data['date_paid'] == '0000-00-00') && $invoice_data['total_amount_due'] <= 0 && ($invoice_data['total_amount_paid'] > 0 || $invoice_data['discount_amount'] > 0) && (!$invoice_data['date_cancel'] || $invoice_data['date_cancel'] == '0000-00-00')) {
             // find the date of the last payment history.
             // if the sent date is null also update that.
             $date_sent = $invoice_data['date_sent'];
             if (!$date_sent || $date_sent == '0000-00-00') {
                 $date_sent = date('Y-m-d', $last_payment_time);
             }
             update_insert("invoice_id", $invoice_id, "invoice", array('date_paid' => date('Y-m-d', $last_payment_time), 'date_sent' => $date_sent, 'status' => _l('Paid')));
             // hook for our ticketing plugin to mark a priority support ticket as paid.
             // or anything else down the track.
             module_cache::clear('invoice');
             handle_hook('invoice_paid', $invoice_id);
             if (module_config::c('invoice_automatic_receipt', 1)) {
                 // send receipt to customer.
                 self::email_invoice_to_customer($invoice_id);
             }
         }
         if ($invoice_data['total_amount_due'] > 0) {
             // update the status to unpaid.
             update_insert("invoice_id", $invoice_id, "invoice", array('date_paid' => '', 'status' => $invoice_data['status'] == _l('Paid') ? module_config::s('invoice_status_default', 'New') : $invoice_data['status']));
         }
         if (class_exists('module_extra', false) && module_extra::is_plugin_enabled()) {
             module_extra::save_extras('invoice', 'invoice_id', $invoice_id);
         }
         if ($invoice_data['customer_id']) {
             //module_cache::clear_cache();
             module_cache::clear('invoice');
             module_customer::update_customer_status($invoice_data['customer_id']);
         }
         hook_handle_callback('invoice_saved', $invoice_id, $invoice_data);
     }
     module_cache::clear('invoice');
     module_cache::clear('job');
     return $invoice_id;
 }
Example #3
0
 private static function save_quote_tasks($quote_id, $data)
 {
     $result = array('status' => false);
     $check_completed = false;
     $quote_data = false;
     // check for new tasks or changed tasks.
     $tasks = self::get_tasks($quote_id);
     if (isset($data['quote_task']) && is_array($data['quote_task'])) {
         foreach ($data['quote_task'] as $quote_task_id => $task_data) {
             if (isset($task_data['manual_percent']) && strlen($task_data['manual_percent']) == 0) {
                 unset($task_data['manual_percent']);
             }
             $original_quote_task_id = $quote_task_id;
             $quote_task_id = (int) $quote_task_id;
             if (!is_array($task_data)) {
                 continue;
             }
             if ($quote_task_id > 0 && !isset($tasks[$quote_task_id])) {
                 $quote_task_id = 0;
                 // creating a new task on this quote.
             }
             if (!isset($task_data['description']) || $task_data['description'] == '' || $task_data['description'] == _TASK_DELETE_KEY) {
                 if ($quote_task_id > 0 && $task_data['description'] == _TASK_DELETE_KEY) {
                     // remove task.
                     // but onyl remove it if it hasn't been invoiced.
                     $sql = "DELETE FROM `" . _DB_PREFIX . "quote_task` WHERE quote_task_id = '{$quote_task_id}' AND quote_id = {$quote_id} LIMIT 1";
                     query($sql);
                     $result['status'] = 'deleted';
                     $result['quote_task_id'] = $quote_task_id;
                 }
                 continue;
             }
             // add / save this task.
             $task_data['quote_id'] = $quote_id;
             $task_data['hours'] = function_exists('decimal_time_in') ? decimal_time_in($task_data['hours']) : $task_data['hours'];
             // remove the amount of it equals the hourly rate.
             if (isset($task_data['amount']) && $task_data['amount'] != 0 && isset($task_data['hours']) && $task_data['hours'] > 0) {
                 if (isset($data['hourly_rate']) && $task_data['amount'] - $task_data['hours'] * $data['hourly_rate'] == 0) {
                     unset($task_data['amount']);
                 }
             }
             // check if we haven't unticked a non-hourly task
             // check if we haven't unticked a billable task
             if (isset($task_data['billable_t']) && $task_data['billable_t'] && !isset($task_data['billable'])) {
                 $task_data['billable'] = 0;
             }
             // set default taxable status
             if (!isset($task_data['taxable_t'])) {
                 // we're creating a new task.
                 $task_data['taxable'] = module_config::c('task_taxable_default', 1);
             }
             if (isset($task_data['taxable_t']) && $task_data['taxable_t'] && !isset($task_data['taxable'])) {
                 $task_data['taxable'] = 0;
             }
             // todo: move the task creation code into a public method so that the public user can add tasks to their quotes.
             if (!$quote_task_id && module_security::is_logged_in() && !module_quote::can_i('create', 'Quote Tasks')) {
                 continue;
                 // dont allow new tasks.
             }
             // check if the user is allowed to create new tasks.
             $quote_task_id = update_insert('quote_task_id', $quote_task_id, 'quote_task', $task_data);
             // todo - fix cross task quote boundary issue. meh.
             $result['quote_task_id'] = $quote_task_id;
             if ($quote_task_id != $original_quote_task_id) {
                 $result['status'] = 'created';
             } else {
                 $result['status'] = 'edited';
             }
         }
     }
     if ($check_completed) {
         self::update_quote_completion_status($quote_id);
     }
     module_cache::clear('quote');
     return $result;
 }