* Deploy: 9809 f200f46c2a19bb98d112f2d32a8de0c4 * Envato: 4ffca17e-861e-4921-86c3-8931978c40ca * Package Date: 2015-11-25 02:55:20 * IP Address: 67.79.165.254 */ if (!$quote_safe) { die('failed'); } if (!module_quote::can_i('edit', 'Quotes')) { die('no perms'); } $quote_id = (int) $_REQUEST['quote_id']; $staff_id = (int) $_REQUEST['staff_id']; $staff = module_user::get_user($staff_id); $quote = module_quote::get_quote($quote_id); $quote_tasks = module_quote::get_tasks($quote_id); // template for sending emails. // are we sending the paid one? or the dueone. $template_name = isset($_REQUEST['template_name']) ? $_REQUEST['template_name'] : 'quote_staff_email'; $template = module_template::get_template_by_key($template_name); $quote['quote_name'] = $quote['name']; $quote['staff_name'] = $staff['name']; $quote['quote_url'] = module_quote::link_open($quote_id); $quote['quote_tasks'] = '<ul>'; $quote['task_count'] = 0; foreach ($quote_tasks as $quote_task) { if ($quote_task['user_id'] != $staff_id) { continue; } $quote['quote_tasks'] .= '<li><strong>' . $quote_task['description'] . '</strong>'; $quote['quote_tasks'] .= ' <br/>';
public static function get_quote($quote_id, $full = true, $skip_permissions = false) { $quote_id = (int) $quote_id; if ($quote_id <= 0) { $quote = array(); } else { $cache_key = self::_quote_cache_key($quote_id, array($quote_id, $full, $skip_permissions)); if ($cached_item = module_cache::get('quote', $cache_key)) { if (function_exists('hook_filter_var')) { $cached_item = hook_filter_var('get_quote', $cached_item, $quote_id); } return $cached_item; } $cache_key_full = self::_quote_cache_key($quote_id, array($quote_id, true, $skip_permissions)); if ($cache_key_full != $cache_key && ($cached_item = module_cache::get('quote', $cache_key_full))) { if (function_exists('hook_filter_var')) { $cached_item = hook_filter_var('get_quote', $cached_item, $quote_id); } return $cached_item; } $cache_timeout = module_config::c('cache_objects', 60); $quote = get_single("quote", "quote_id", $quote_id); } // check permissions if ($quote && isset($quote['quote_id']) && $quote['quote_id'] == $quote_id) { switch (self::get_quote_access_permissions()) { case _QUOTE_ACCESS_ALL: break; case _QUOTE_ACCESS_ASSIGNED: // only assigned quotes! $has_quote_access = false; if ($quote['user_id'] == module_security::get_loggedin_id()) { $has_quote_access = true; break; } $tasks = module_quote::get_tasks($quote['quote_id']); foreach ($tasks as $task) { if ($task['user_id'] == module_security::get_loggedin_id()) { $has_quote_access = true; break; } } unset($tasks); if (!$has_quote_access) { if ($skip_permissions) { $quote['_no_access'] = true; // set a flag for custom processing. we check for this when calling get_customer with the skip permissions argument. (eg: in the ticket file listing link) } else { $quote = false; } } break; case _QUOTE_ACCESS_CUSTOMER: // tie in with customer permissions to only get quotes from customers we can access. $customers = module_customer::get_customers(); $has_quote_access = false; if (isset($customers[$quote['customer_id']])) { $has_quote_access = true; } /*foreach($customers as $customer){ // todo, if($quote['customer_id'] == 0) // ignore this permission if($customer['customer_id']==$quote['customer_id']){ $has_quote_access = true; break; } }*/ unset($customers); if (!$has_quote_access) { if ($skip_permissions) { $quote['_no_access'] = true; // set a flag for custom processing. we check for this when calling get_customer with the skip permissions argument. (eg: in the ticket file listing link) } else { $quote = false; } } break; } if (!$quote) { $quote = array(); if (function_exists('hook_filter_var')) { $quote = hook_filter_var('get_quote', $quote, $quote_id); } return $quote; } $quote['taxes'] = get_multiple('quote_tax', array('quote_id' => $quote_id), 'quote_tax_id', 'exact', 'order'); } if (!$full) { if (isset($cache_key)) { module_cache::put('quote', $cache_key, $quote, $cache_timeout); } if (function_exists('hook_filter_var')) { $quote = hook_filter_var('get_quote', $quote, $quote_id); } return $quote; } if (!$quote) { $customer_id = 0; if (isset($_REQUEST['customer_id']) && $_REQUEST['customer_id']) { // $customer_id = (int) $_REQUEST['customer_id']; // find default website id to use. if (isset($_REQUEST['website_id'])) { $website_id = (int) $_REQUEST['website_id']; } else { } } $default_quote_name = module_config::c('quote_default_new_name', ''); if (module_config::c('quote_name_incrementing', 0)) { $quote_number = module_config::c('quote_name_incrementing_next', 1); // see if there is an quote number matching this one. $this_quote_number = $quote_number; do { $quotes = get_multiple('quote', array('name' => $this_quote_number)); //'customer_id'=>$customer_id, if (!count($quotes)) { $quote_number = $this_quote_number; } else { $this_quote_number++; } } while (count($quotes)); module_config::save_config('quote_name_incrementing_next', $quote_number); $default_quote_name = $quote_number . $default_quote_name; } $quote = array('quote_id' => 'new', 'customer_id' => $customer_id, 'website_id' => isset($_REQUEST['website_id']) ? $_REQUEST['website_id'] : 0, 'hourly_rate' => module_config::c('hourly_rate', 60), 'name' => $default_quote_name, 'date_create' => date('Y-m-d'), 'date_approved' => '0000-00-00', 'approved_by' => '', 'user_id' => module_security::get_loggedin_id(), 'contact_user_id' => -1, 'status' => module_config::s('quote_status_default', 'New'), 'tax_type' => module_config::c('invoice_tax_type', 0), 'type' => module_config::s('quote_type_default', 'Website Design'), 'currency_id' => module_config::c('default_currency_id', 1), 'auto_task_numbers' => '0', 'default_task_type' => module_config::c('default_task_type', _TASK_TYPE_HOURS_AMOUNT), 'description' => '', 'discount_description' => _l('Discount:'), 'discount_amount' => 0, 'discount_type' => module_config::c('invoice_discount_type', _DISCOUNT_TYPE_BEFORE_TAX)); // some defaults from the db. $quote['total_tax_rate'] = module_config::c('tax_percent', 10); $quote['total_tax_name'] = module_config::c('tax_name', 'TAX'); if ($customer_id > 0) { $customer_data = module_customer::get_customer($customer_id, false, true); if ($customer_data && isset($customer_data['default_tax']) && $customer_data['default_tax'] >= 0) { $quote['total_tax_rate'] = $customer_data['default_tax']; $quote['total_tax_name'] = $customer_data['default_tax_name']; } } } // new support for multiple taxes if (!isset($quote['taxes']) || !count($quote['taxes']) && $quote['total_tax_rate'] > 0) { $quote['taxes'] = array(); $tax_rates = explode(',', $quote['total_tax_rate']); $tax_names = explode(',', $quote['total_tax_name']); foreach ($tax_rates as $tax_rate_id => $tax_rate_amount) { if ($tax_rate_amount > 0) { $quote['taxes'][] = array('order' => 0, 'percent' => $tax_rate_amount, 'name' => isset($tax_names[$tax_rate_id]) ? $tax_names[$tax_rate_id] : $quote['total_tax_name'], 'total' => 0, 'amount' => 0, 'discount' => 0, 'increment' => module_config::c('tax_multiple_increment', 0)); } } } if ($quote) { // work out total hours etc.. $quote['total_hours'] = 0; $quote['total_hours_completed'] = 0; $quote['total_hours_overworked'] = 0; $quote['total_sub_amount'] = 0; $quote['total_sub_amount_taxable'] = 0; $quote['total_sub_amount_unbillable'] = 0; $quote['total_sub_amount_invoicable'] = 0; $quote['total_sub_amount_invoicable_taxable'] = 0; $quote['total_amount_invoicable'] = 0; $quote['total_tasks_remain'] = 0; $quote['total_amount'] = 0; $quote['total_amount_paid'] = 0; $quote['total_amount_invoiced'] = 0; $quote['total_amount_invoiced_deposit'] = 0; $quote['total_amount_todo'] = 0; $quote['total_amount_outstanding'] = 0; $quote['total_amount_due'] = 0; $quote['total_hours_remain'] = 0; $quote['total_percent_complete'] = 0; $quote['total_tax'] = 0; $quote['total_tax_invoicable'] = 0; // $quote['invoice_discount_amount'] = 0; // $quote['invoice_discount_amount_on_tax'] = 0; // $quote['total_amount_discounted'] = 0; // new feature to invoice incompleted tasks $quote['uninvoiced_quote_task_ids'] = array(); $quote_items = self::get_quote_items((int) $quote['quote_id'], $quote); foreach ($quote_items as $quote_item) { if ($quote_item['quote_item_amount'] != 0) { // we have a custom amount for this quote_item if ($quote_item['billable']) { $quote['total_sub_amount'] += $quote_item['quote_item_amount']; if ($quote_item['taxable']) { $quote['total_sub_amount_taxable'] += $quote_item['quote_item_amount']; if (module_config::c('tax_calculate_mode', _TAX_CALCULATE_AT_END) == _TAX_CALCULATE_INCREMENTAL) { // tax calculated along the way (this isn't the recommended way, but was included as a feature request) // we add tax to each of the tax array items //$quote['total_tax'] += round(($quote_item['quote_item_amount'] * ($quote['total_tax_rate'] / 100)),module_config::c('currency_decimal_places',2)); foreach ($quote['taxes'] as $quote_tax_id => $quote_tax) { if (!isset($quote['taxes'][$quote_tax_id]['total'])) { $quote['taxes'][$quote_tax_id]['total'] = 0; } $quote['taxes'][$quote_tax_id]['total'] += $quote_item['quote_item_amount']; $quote['taxes'][$quote_tax_id]['amount'] += round($quote_item['quote_item_amount'] * ($quote_tax['percent'] / 100), module_config::c('currency_decimal_places', 2)); } } } } else { $quote['total_sub_amount_unbillable'] += $quote_item['quote_item_amount']; } } } // add any discounts. if ($quote['discount_amount'] != 0) { if ($quote['discount_type'] == _DISCOUNT_TYPE_AFTER_TAX) { // after tax discount :::::::::: // handled below. //$quote['final_modification'] = -$quote['discount_amount']; } else { if ($quote['discount_type'] == _DISCOUNT_TYPE_BEFORE_TAX) { // before tax discount::::: //$quote['final_modification'] = -$quote['discount_amount']; // problem : this 'discount_amount_on_tax' calculation may not match the correct final discount calculation as per below if (module_config::c('tax_calculate_mode', _TAX_CALCULATE_AT_END) == _TAX_CALCULATE_INCREMENTAL) { // tax calculated along the way. // we have discounted the 'total amount taxable' so that means we need to reduce the tax amount by that much as well. foreach ($quote['taxes'] as $quote_tax_id => $quote_tax) { $this_tax_discount = round($quote['discount_amount'] * ($quote['taxes'][$quote_tax_id]['percent'] / 100), module_config::c('currency_decimal_places', 2)); $quote['discount_amount_on_tax'] += $this_tax_discount; if (!isset($quote['taxes'][$quote_tax_id]['total'])) { $quote['taxes'][$quote_tax_id]['total'] = 0; } $quote['taxes'][$quote_tax_id]['total'] -= $quote['discount_amount']; $quote['taxes'][$quote_tax_id]['amount'] -= $this_tax_discount; $quote['taxes'][$quote_tax_id]['discount'] = $this_tax_discount; } } else { // we work out what the tax would have been if there was no applied discount // this is used in job.php $quote['taxes_backup'] = $quote['taxes']; $quote['total_sub_amount_taxable_backup'] = $quote['total_sub_amount_taxable']; $total_tax_before_discount = 0; foreach ($quote['taxes'] as $quote_tax_id => $quote_tax) { $quote['taxes'][$quote_tax_id]['total'] = $quote['total_sub_amount_taxable']; $quote['taxes'][$quote_tax_id]['amount'] = round($quote['total_sub_amount_taxable'] * ($quote_tax['percent'] / 100), module_config::c('currency_decimal_places', 2)); // here we adjust the 'total_sub_amount_taxable' to include the value from the previous calculation. // this is for multiple taxes that addup as they go (eg: Canada) if (isset($quote_tax['increment']) && $quote_tax['increment']) { $quote['total_sub_amount_taxable'] += $quote['taxes'][$quote_tax_id]['amount']; } $total_tax_before_discount += $quote['taxes'][$quote_tax_id]['amount']; } $quote['taxes'] = $quote['taxes_backup']; $quote['total_sub_amount_taxable'] = $quote['total_sub_amount_taxable_backup']; } $quote['total_sub_amount'] -= $quote['discount_amount']; $quote['total_sub_amount_taxable'] -= $quote['discount_amount']; } } } if (module_config::c('tax_calculate_mode', _TAX_CALCULATE_AT_END) == _TAX_CALCULATE_AT_END) { // tax needs to be calculated based on the total_sub_amount_taxable $previous_quote_tax_id = false; foreach ($quote['taxes'] as $quote_tax_id => $quote_tax) { $quote['taxes'][$quote_tax_id]['total'] = $quote['total_sub_amount_taxable']; if (isset($quote_tax['increment']) && $quote_tax['increment'] && $previous_quote_tax_id) { $quote['taxes'][$quote_tax_id]['total'] += $quote['taxes'][$previous_quote_tax_id]['amount']; } $quote['taxes'][$quote_tax_id]['amount'] = round($quote['taxes'][$quote_tax_id]['total'] * ($quote_tax['percent'] / 100), module_config::c('currency_decimal_places', 2)); // here we adjust the 'total_sub_amount_taxable' to include the value from the previous calculation. // this is for multiple taxes that addup as they go (eg: Canada) $previous_quote_tax_id = $quote_tax_id; } //$quote['total_tax'] = round(($quote['total_sub_amount_taxable'] * ($quote['total_tax_rate'] / 100)),module_config::c('currency_decimal_places',2)); } else { //$quote['total_tax'] = 0; } if (isset($quote['tax_type']) && $quote['tax_type'] == 1) { // hack! not completely correct, oh well. // todo - make this work with more than 1 tax rate. // $amount / 1.05 ( this is 1 + tax %) // this will only work if a single tax has been included. if (is_array($quote['taxes']) && count($quote['taxes']) > 1) { set_error('Included tax calculation only works with 1 tax rate'); } else { if (is_array($quote['taxes']) && count($quote['taxes'])) { reset($quote['taxes']); $quote_tax_id = key($quote['taxes']); if (isset($quote['taxes'][$quote_tax_id])) { $taxable_amount = $quote['total_sub_amount_taxable'] / (1 + $quote['taxes'][$quote_tax_id]['percent'] / 100); $quote['taxes'][$quote_tax_id]['amount'] = $quote['total_sub_amount_taxable'] - $taxable_amount; $quote['total_sub_amount'] = $quote['total_sub_amount'] - $quote['taxes'][$quote_tax_id]['amount']; } } } } $quote['total_tax'] = 0; foreach ($quote['taxes'] as $quote_tax_id => $quote_tax) { $quote['total_tax'] += $quote_tax['amount']; } $quote['total_amount'] = $quote['total_sub_amount'] + $quote['total_tax']; if ($quote['discount_type'] == _DISCOUNT_TYPE_AFTER_TAX) { $quote['total_amount'] -= $quote['discount_amount']; } $quote['total_amount'] = round($quote['total_amount'], module_config::c('currency_decimal_places', 2)); } if (isset($cache_key)) { module_cache::put('quote', $cache_key, $quote, $cache_timeout); } if (function_exists('hook_filter_var')) { $quote = hook_filter_var('get_quote', $quote, $quote_id); } return $quote; }