public function pre_menu() { if ($this->can_i('view', 'Members')) { // how many members are there? $link_name = _l('Members'); if (module_config::c('member_show_summary', 1)) { $member_count = module_cache::get('member', 'member_menu_count'); if ($member_count === false) { $sql = "SELECT COUNT(member_id) AS c FROM `" . _DB_PREFIX . "member` m"; $res = qa1($sql); $member_count = $res['c']; module_cache::put('member', 'member_menu_count', $member_count); } if ($member_count > 0) { $link_name .= " <span class='menu_label'>" . $member_count . "</span> "; } } $this->links['members'] = array("name" => $link_name, "p" => "member_admin", "args" => array('member_id' => false)); if (class_exists('module_newsletter', false) && module_config::c('member_menu_under_newsletter', 1)) { $this->links['members']['holder_module'] = 'newsletter'; $this->links['members']['holder_module_page'] = 'newsletter_admin'; $this->links['members']['menu_include_parent'] = 0; $this->links['members']['allow_nesting'] = 1; } } if (class_exists('module_template', false)) { module_template::init_template('member_subscription_form', '<h2>Subscribe</h2> <form action="" method="post"> <p>Please Enter Your Email Address: <input type="text" name="member[email]" value="{EMAIL}"> </p> <p>Please Enter Your First Name: <input type="text" name="member[first_name]" value="{FIRST_NAME}"> </p> <p>Please Enter Your Last Name: <input type="text" name="member[last_name]" value="{LAST_NAME}"> </p> <p>Please Enter Your Business Name: <input type="text" name="member[business]" value="{BUSINESS}"> </p> <p>Please Enter Your Phone Number: <input type="text" name="member[phone]" value="{PHONE}"> </p> <p> Please choose your newsletter subscription options: <br/> {NEWSLETTER_OPTIONS} </p> <p><input type="submit" name="confirm" value="Subscribe"></p> </form> ', 'Used when a user wishes to subscribe.', 'code', array()); module_template::init_template('member_subscription_error', '<h2>Subscription Error</h2> <p>Sorry there was an error when processing your request:</p> <p>{MESSAGE}</p> ', 'Displayed when subscription fails (eg: missing email address).', 'code', array('MESSAGE' => 'Message to the user')); module_template::init_template('member_subscription_success', '<h2>Subscription Success</h2> <p>Thank you, subscription successful.</p> <p>A message has been sent to your email address ({EMAIL}) to confirm your newsletter subscription.</p> ', 'Displayed when subscription is successful.', 'code', array('EMAIL' => 'Users email address')); module_template::init_template('member_update_details_success', '<h2>Subscription Success</h2> <p>Thank you, subscription details updated.</p> <p>Your email address: ({EMAIL})</p> ', 'Displayed when updating details is successful.', 'code', array('EMAIL' => 'Users email address')); } }
public static function get_finance_summary($week_start, $week_end, $multiplyer = 1, $row_limit = 7) { $cache_key = 'finance_sum_' . md5(module_security::get_loggedin_id() . '_' . serialize(func_get_args())); $cache_timeout = module_config::c('cache_objects', 60); if ($cached_item = module_cache::get('finance', $cache_key)) { return $cached_item; } $base_href = module_finance::link_generate(false, array('full' => false, 'page' => 'dashboard_popup', 'arguments' => array('display_mode' => 'ajax')), array('foo')); $base_href .= '&'; /*$base_href .= (strpos($base_href,'?')!==false) ? '&' : '?'; $base_href .= 'display_mode=ajax&'; $base_href .= 'home_page_stats=true&';*/ // init structure: if ($multiplyer > 1) { $row_limit++; } for ($x = 0; $x < $row_limit; $x++) { //$time = strtotime("+$x days",strtotime($week_start)); $time = strtotime("+" . $x * $multiplyer . " days", strtotime($week_start)); $data[date("Ymd", $time)] = array("day" => $time, "hours" => 0, "amount" => 0, "amount_invoiced" => 0, "amount_paid" => 0, "amount_spent" => 0); if (class_exists('module_envato', false)) { $data[date("Ymd", $time)]['envato_earnings'] = 0; } } $data['total'] = array('day' => _l('Totals:'), 'week' => _l('Totals:'), 'hours' => 0, 'amount' => 0, 'amount_invoiced' => 0, 'amount_paid' => 0, 'amount_spent' => 0); if (class_exists('module_envato', false)) { $data['total']['envato_earnings'] = 0; } if (class_exists('module_job', false)) { module_debug::log(array('title' => 'Finance Dashboard Job', 'data' => '')); // find all task LOGS completed within these dayes $sql = "SELECT t.task_id, tl.date_created, t.hours AS task_hours, t.amount, tl.hours AS hours_logged, p.job_id, p.hourly_rate, t.date_done "; // $sql .= " FROM `"._DB_PREFIX."task_log` tl "; // $sql .= " LEFT JOIN `"._DB_PREFIX."task` t ON tl.task_id = t.task_id "; $sql .= " FROM `" . _DB_PREFIX . "task` t"; $sql .= " LEFT JOIN `" . _DB_PREFIX . "task_log` tl ON t.task_id = tl.task_id "; $sql .= " LEFT JOIN `" . _DB_PREFIX . "job` p ON t.job_id = p.job_id"; $sql .= " WHERE ( (tl.date_created >= '{$week_start}' AND tl.date_created < '{$week_end}') OR (t.fully_completed = 1 AND t.date_done >= '{$week_start}' AND t.date_done < '{$week_end}') )"; $sql .= " AND t.job_id IN ( "; $valid_job_ids = module_job::get_valid_job_ids(); if (count($valid_job_ids)) { foreach ($valid_job_ids as $valid_job_id) { $sql .= (int) $valid_job_id['job_id'] . ", "; } $sql = rtrim($sql, ', '); } else { $sql .= ' NULL '; } $sql .= " ) "; // echo $sql; $tasks = query($sql); $logged_tasks = array(); while ($r = mysql_fetch_assoc($tasks)) { if (!$r['date_created']) { $r['date_created'] = $r['date_done']; } if ($multiplyer > 1) { $week_day = date('w', strtotime($r['date_created'])) - 1; $r['date_created'] = date('Y-m-d', strtotime('-' . $week_day . ' days', strtotime($r['date_created']))); } $key = date("Ymd", strtotime($r['date_created'])); if (!isset($data[$key])) { // for some reason we're getting results here that shouldn't be in the list // for now we just skip these results until I figure out why (only had 1 guy report this error, maybe misconfig) continue; } // copied from dashboard_popup_hours_logged.php // needed get_tasks call to do the _JOB_TASK_ACCESS_ASSIGNED_ONLY permission check $jobtasks = module_job::get_tasks($r['job_id']); $task = isset($jobtasks[$r['task_id']]) ? $jobtasks[$r['task_id']] : false; if (!$task) { continue; } if (!isset($task['manual_task_type']) || $task['manual_task_type'] < 0) { $task['manual_task_type'] = $task['default_task_type']; } if (isset($r['hours_logged']) && $r['hours_logged'] > 0) { if ($r['hours_logged'] == $task['completed']) { // this listing is the only logged hours for this task. if ($task['fully_completed']) { // task complete, we show the final amount and hours. if ($task['amount'] > 0) { if ($task['manual_task_type'] == _TASK_TYPE_QTY_AMOUNT) { $display_amount = $task['amount'] * $task['hours']; } else { $display_amount = $task['amount']; } } else { $display_amount = $r['task_hours'] * $r['hourly_rate']; } } else { // task isn't fully completed yet, just use hourly rate for now. $display_amount = $r['hours_logged'] * $r['hourly_rate']; } } else { // this is part of a bigger log of hours for this single task. $display_amount = $r['hours_logged'] * $r['hourly_rate']; } $hours_logged = $r['task_hours'] > 0 ? $r['hours_logged'] : 0; } else { // there are no logged hours for this particular task, but it is set to completed. // we just assume it is completed on this day. if ($task['amount'] > 0) { if ($task['manual_task_type'] == _TASK_TYPE_QTY_AMOUNT) { $display_amount = $task['amount'] * $task['hours']; } else { $display_amount = $task['amount']; } } else { $display_amount = $r['task_hours'] * $r['hourly_rate']; } $hours_logged = $task['hours']; } $data[$key]['amount'] += $display_amount; $data['total']['amount'] += $display_amount; $data[$key]['hours'] += $hours_logged; $data['total']['hours'] += $hours_logged; /*$hourly_rate = $r['hourly_rate']; if($hours_logged > 0 && $r['amount'] > 0 && $hourly_rate > 0){ // there is a custom amount assigned to thsi task. // only calculate this amount if the full hours is complete. $hourly_rate = $r['amount'] / $r['task_hours']; } if($hours_logged > 0 && $hourly_rate > 0){ $data[$key]['amount'] += ($hours_logged * $hourly_rate); $data['total']['amount'] += ($hours_logged * $hourly_rate); }*/ } } module_debug::log(array('title' => 'Finance Dashboard Invoices', 'data' => '')); // find invoices sent this week. $sql = "SELECT i.* "; $sql .= " FROM `" . _DB_PREFIX . "invoice` i "; $sql .= " LEFT JOIN `" . _DB_PREFIX . "invoice_item` ii ON i.invoice_id = ii.invoice_id "; if (class_exists('module_job', false)) { $sql .= " LEFT JOIN `" . _DB_PREFIX . "task` t ON ii.task_id = t.task_id "; $sql .= " LEFT JOIN `" . _DB_PREFIX . "job` p ON t.job_id = p.job_id "; } $sql .= " WHERE (i.date_create >= '{$week_start}' AND i.date_create <= '{$week_end}')"; $sql .= " GROUP BY i.invoice_id"; // todo - sql in here to limit what they can see. $invoices = query($sql); // group invoices into days of the week. while ($invoice_data = mysql_fetch_assoc($invoices)) { //$invoice_data = module_invoice::get_invoice($i['invoice_id']); if ($invoice_data) { if ($multiplyer > 1) { $week_day = date('w', strtotime($invoice_data['date_create'])) - 1; $invoice_data['date_create'] = date('Y-m-d', strtotime('-' . $week_day . ' days', strtotime($invoice_data['date_create']))); } $key = date("Ymd", strtotime($invoice_data['date_create'])); if (!isset($data[$key])) { // for some reason we're getting results here that shouldn't be in the list // for now we just skip these results until I figure out why (only had 1 guy report this error, maybe misconfig) continue; } if (isset($data[$key])) { $data[$key]['amount_invoiced'] += $invoice_data['c_total_amount']; $data['total']['amount_invoiced'] += $invoice_data['c_total_amount']; } } } module_debug::log(array('title' => 'Finance Dashboard Finances', 'data' => '')); // find all payments made this week. // we also have to search for entries in the new "finance" table and make sure we dont double up here. $finance_records = module_finance::get_finances(array('date_from' => $week_start, 'date_to' => $week_end)); foreach ($finance_records as $finance_record) { if (isset($finance_record['payment_type']) && ($finance_record['payment_type'] == _INVOICE_PAYMENT_TYPE_OVERPAYMENT_CREDIT || $finance_record['payment_type'] == _INVOICE_PAYMENT_TYPE_CREDIT)) { // CODE COPIED FROM FINANCE_LIST.PHP // dont add these ones to the totals on the dashboard continue; } if ($finance_record['credit'] > 0) { if ($multiplyer > 1) { $week_day = date('w', strtotime($finance_record['transaction_date'])) - 1; $finance_record['transaction_date'] = date('Y-m-d', strtotime('-' . $week_day . ' days', strtotime($finance_record['transaction_date']))); } $key = date("Ymd", strtotime($finance_record['transaction_date'])); if (isset($data[$key])) { $data[$key]['amount_paid'] += $finance_record['amount']; $data['total']['amount_paid'] += $finance_record['amount']; } } if ($finance_record['debit'] > 0) { if ($multiplyer > 1) { $week_day = date('w', strtotime($finance_record['transaction_date'])) - 1; $finance_record['transaction_date'] = date('Y-m-d', strtotime('-' . $week_day . ' days', strtotime($finance_record['transaction_date']))); } $key = date("Ymd", strtotime($finance_record['transaction_date'])); if (isset($data[$key])) { $data[$key]['amount_spent'] += $finance_record['amount']; $data['total']['amount_spent'] += $finance_record['amount']; } } } module_debug::log(array('title' => 'Finance Dashboard DONE!', 'data' => '')); /*$sql = "SELECT p.* "; $sql .= " FROM `"._DB_PREFIX."invoice_payment` p "; $sql .= " WHERE (p.date_paid >= '$week_start' AND p.date_paid <= '$week_end')"; // todo - sql in here to limit what they can see. $payments = query($sql); // group invoices into days of the week. while($payment = mysql_fetch_assoc($payments)){ //$invoice_data = module_invoice::get_invoice($i['invoice_id']); if($multiplyer > 1){ $week_day = date('w',strtotime($payment['date_paid'])) - 1; $payment['date_paid'] = date('Y-m-d',strtotime('-'.$week_day.' days',strtotime($payment['date_paid']))); } $key = date("Ymd",strtotime($payment['date_paid'])); if(isset($data[$key])){ $data[$key]['amount_paid'] += $payment['amount']; $data['total']['amount_paid'] += $payment['amount']; } }*/ if (class_exists('module_envato', false)) { $envato_currency = "USD"; $envato = new envato_api(); $local_currency = $envato->read_setting("local_currency", "AUD"); $currency_convert_multiplier = $envato->currency_convert($envato_currency, $local_currency); // find summary of earnings between these dates in the envato statement. $week_start_time = strtotime($week_start); $week_end_time = strtotime($week_end); $sql = "SELECT * FROM `" . _DB_PREFIX . "envato_statement` s WHERE `time` >= '{$week_start_time}' AND `time` <= {$week_end_time}"; $sql .= " AND ( `type` = 'sale' OR `type` = 'referral_cut' )"; foreach (qa($sql) as $sale) { $sale_time = $sale['time']; if ($multiplyer > 1) { $week_day = date('w', $sale_time) - 1; $sale_time = strtotime('-' . $week_day . ' days', $sale_time); } $key = date("Ymd", $sale_time); if (!isset($data[$key])) { continue; } $data[$key]['envato_earnings'] += round($currency_convert_multiplier * $sale['earnt'], 2); $data['total']['envato_earnings'] += round($currency_convert_multiplier * $sale['earnt'], 2); /*if($sale['type']=='sale'){ $sales_count++; } $sales_amount+= $sale['earnt'];*/ } } if ($multiplyer > 1) { // dont want totals on previous weeks listing unset($data['total']); } foreach ($data as $data_id => $row) { //$row['amount'] = dollar($row['amount']); $row['chart_amount'] = $row['amount']; $row['amount'] = currency((int) $row['amount']); $row['chart_amount_invoiced'] = $row['amount_invoiced']; $row['amount_invoiced'] = currency((int) $row['amount_invoiced']); $row['chart_amount_paid'] = $row['amount_paid']; $row['amount_paid'] = currency((int) $row['amount_paid']); $row['chart_amount_spent'] = $row['amount_spent']; $row['amount_spent'] = currency((int) $row['amount_spent']); if (class_exists('module_envato', false)) { $row['chart_envato_earnings'] = $row['envato_earnings']; $row['envato_earnings'] = currency((int) $row['envato_earnings']); } // combine together $row['chart_hours'] = $row['hours']; $row['hours'] = sprintf('%s (%s)', $row['hours'], $row['amount']); if (is_numeric($row['day'])) { $time = $row['day']; $date = date('Y-m-d', $time); $row['date'] = $date; if ($multiplyer > 1) { $date .= '|' . date('Y-m-d', strtotime('+' . $multiplyer . ' days', $time)); } //$row['hours'] = '<a href="'.$base_href.'w=hours&date='.$date.'" class="summary_popup">'. _l('%s hours',$row['hours']) . '</a>'; $row['hours_link'] = '<a href="' . $base_href . 'w=hours&date=' . $date . '" class="summary_popup">' . $row['hours'] . '</a>'; $row['amount_link'] = '<a href="' . $base_href . 'w=hours&date=' . $date . '" class="summary_popup">' . $row['amount'] . '</a>'; $row['amount_invoiced_link'] = '<a href="' . $base_href . 'w=amount_invoiced&date=' . $date . '" class="summary_popup">' . $row['amount_invoiced'] . '</a>'; $row['amount_paid_link'] = '<a href="' . $base_href . 'w=amount_paid&date=' . $date . '" class="summary_popup">' . $row['amount_paid'] . '</a>'; $row['amount_spent_link'] = '<a href="' . $base_href . 'w=amount_spent&date=' . $date . '" class="summary_popup">' . $row['amount_spent'] . '</a>'; $row['day'] = _l(date('D', $time)) . ' ' . date('j', $time) . _l(date('S', $time)); $row['week'] = _l(date('M', $time)) . ' ' . date('j', $time) . _l(date('S', $time)); // if it's today. if ($time == strtotime(date("Y-m-d"))) { $row['highlight'] = true; } } else { } $data[$data_id] = $row; } module_cache::put('finance', $cache_key, $data, $cache_timeout); return $data; }
public static function get_invoice($invoice_id, $basic = false, $skip_permissions = false) { $invoice = array(); $invoice_id = (int) $invoice_id; if ((int) $invoice_id > 0) { // we check the cache to see if the 'full' copy of this invoice exists anywhere yet. // if it does $cache_key = self::_invoice_cache_key($invoice_id, array($invoice_id, $basic, $skip_permissions, isset($_REQUEST['customer_id']) ? $_REQUEST['customer_id'] : 0, isset($_REQUEST['job_id']) ? $_REQUEST['job_id'] : 0)); if ($cached_item = module_cache::get('invoice', $cache_key)) { return $cached_item; } $cache_key_full = self::_invoice_cache_key($invoice_id, array($invoice_id, false, $skip_permissions, isset($_REQUEST['customer_id']) ? $_REQUEST['customer_id'] : 0, isset($_REQUEST['job_id']) ? $_REQUEST['job_id'] : 0)); if ($cache_key_full != $cache_key && ($cached_item = module_cache::get('invoice', $cache_key_full))) { return $cached_item; } $cache_timeout = module_config::c('cache_objects', 60); if ($basic === 2) { // used in links. just want the invoice name really. // todo - cache. meh return get_single('invoice', 'invoice_id', $invoice_id); } else { $sql = "SELECT i.*"; $sql .= ", c.primary_user_id "; // AS user_id // DONE - change this to the invoice table. drop down to select invoice contact. auto select based on contacts role? $sql .= ", c.customer_name AS customer_name "; $sql .= ", GROUP_CONCAT(DISTINCT j.`website_id` SEPARATOR ',') AS website_ids"; // the website id(s) $sql .= ", GROUP_CONCAT(DISTINCT j.`job_id` SEPARATOR ',') AS job_ids"; // the job id(s) $sql .= ", j.customer_id AS new_customer_id "; $sql .= " FROM `" . _DB_PREFIX . "invoice` i "; $sql .= " LEFT JOIN `" . _DB_PREFIX . "invoice_item` ii USING (invoice_id) "; $sql .= " LEFT JOIN `" . _DB_PREFIX . "task` t ON ii.task_id = t.task_id"; $sql .= " LEFT JOIN `" . _DB_PREFIX . "job` j ON t.job_id = j.job_id"; $sql .= " LEFT JOIN `" . _DB_PREFIX . "customer` c ON i.customer_id = c.customer_id "; //$sql .= " LEFT JOIN `"._DB_PREFIX."user` u ON c.primary_user_id = u.user_id "; $sql .= " WHERE i.invoice_id = " . (int) $invoice_id; $sql .= " GROUP BY i.invoice_id"; $invoice = qa1($sql); if (isset($invoice['website_id']) && $invoice['website_id']) { $website_ids = explode(',', $invoice['website_ids']); if (!in_array($invoice['website_id'], $website_ids)) { $website_ids[] = $invoice['website_id']; $invoice['website_ids'] = implode(',', $website_ids); } } } if (isset($invoice['job_ids']) && strlen(trim($invoice['job_ids'])) > 0) { $invoice['job_ids'] = explode(',', $invoice['job_ids']); } else { $invoice['job_ids'] = array(); } // check permissions if ($invoice && isset($invoice['invoice_id']) && $invoice['invoice_id'] == $invoice_id) { switch (self::get_invoice_access_permissions()) { case _INVOICE_ACCESS_ALL: break; case _INVOICE_ACCESS_STAFF: if ($invoice['vendor_user_id'] != module_security::get_loggedin_id()) { if ($skip_permissions) { $invoice['_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 { $invoice = false; } } break; case _INVOICE_ACCESS_JOB: // only invoices from jobs! $has_invoice_access = false; $jobs = module_job::get_jobs(); foreach ($invoice['job_ids'] as $invoice_job_id) { if (isset($jobs[$invoice_job_id])) { $has_invoice_access = true; } } unset($jobs); if (!$has_invoice_access) { if ($skip_permissions) { $invoice['_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 { $invoice = false; } } break; case _INVOICE_ACCESS_CUSTOMER: // tie in with customer permissions to only get invoices from customers we can access. $customers = module_customer::get_customers(); $has_invoice_access = false; if (isset($customers[$invoice['customer_id']])) { $has_invoice_access = true; } unset($customers); /*foreach($customers as $customer){ // todo, if($invoice['customer_id'] == 0) // ignore this permission if($customer['customer_id']==$invoice['customer_id']){ $has_invoice_access = true; break; } }*/ if (!$has_invoice_access) { if ($skip_permissions) { $invoice['_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 { $invoice = false; } } break; } // print_r($invoice);exit; if (!$invoice) { return array(); } $original_invoice = $invoice; $invoice['taxes'] = get_multiple('invoice_tax', array('invoice_id' => $invoice_id), 'invoice_tax_id', 'exact', 'order'); // set the job id of the first job just for kicks if (isset($invoice['deposit_job_id']) && (int) $invoice['deposit_job_id'] > 0) { $invoice['job_ids'][] = $invoice['deposit_job_id']; } if (isset($invoice['website_ids'])) { $invoice['website_ids'] = explode(',', $invoice['website_ids']); } else { $invoice['website_ids'] = array(); } // incase teh customer id on this invoice changes: if (isset($invoice['new_customer_id']) && $invoice['new_customer_id'] > 0 && $invoice['new_customer_id'] != $invoice['customer_id']) { $invoice['customer_id'] = $invoice['new_customer_id']; update_insert('invoice_id', $invoice_id, 'invoice', array('customer_id' => $invoice['new_customer_id'])); } if ($invoice['customer_id'] > 0) { $customer_data = module_customer::get_customer($invoice['customer_id']); if ($customer_data && class_exists('module_company', false) && isset($invoice['company_id']) && !$invoice['company_id'] && isset($customer_data['company_ids']) && count($customer_data['company_ids']) == 1) { // check if this customer has a company. $invoice['company_id'] = key($customer_data['company_ids']); } } if ($basic === true) { module_cache::put('invoice', $cache_key, $invoice, $cache_timeout); return $invoice; } } } // not sure why this code was here, commenting it out for now until we need it. /*if(isset($invoice['customer_id']) && isset($invoice['job_id']) && $invoice['customer_id'] <= 0 && $invoice['job_id'] > 0){ $job_data = module_job::get_job($invoice['job_id'],false); $invoice['customer_id'] = $job_data['customer_id']; }*/ if (!$invoice || !is_array($invoice) || !isset($invoice['invoice_id']) || !$invoice['invoice_id']) { $customer_id = isset($_REQUEST['customer_id']) ? $_REQUEST['customer_id'] : 0; $job_id = isset($_REQUEST['job_id']) ? $_REQUEST['job_id'] : 0; $currency_id = module_config::c('default_currency_id', 1); if ($customer_id > 0) { // find a default website to use ? } else { if ($job_id > 0) { // only a job, no customer. set the customer id. $job_data = module_job::get_job($job_id, false); $customer_id = $job_data['customer_id']; $currency_id = $job_data['currency_id']; } } // work out an invoice number $invoice_number = self::new_invoice_number($customer_id); $invoice = array('invoice_id' => 'new', 'customer_id' => $customer_id, 'job_id' => $job_id, 'job_ids' => $job_id > 0 ? array($job_id) : array(), 'currency_id' => $currency_id, 'name' => $invoice_number, 'cached_total' => 0, 'discount_description' => $job_id > 0 && isset($job_data['discount_description']) ? $job_data['discount_description'] : _l('Discount:'), 'discount_amount' => $job_id > 0 && isset($job_data['discount_amount']) ? $job_data['discount_amount'] : 0, 'discount_type' => $job_id > 0 && isset($job_data['discount_type']) ? $job_data['discount_type'] : module_config::c('invoice_discount_type', _DISCOUNT_TYPE_BEFORE_TAX), 'tax_type' => module_config::c('invoice_tax_type', 0), 'date_create' => date('Y-m-d'), 'date_sent' => '', 'date_due' => date('Y-m-d', strtotime('+' . module_config::c('invoice_due_days', 30) . ' days')), 'date_paid' => '', 'hourly_rate' => module_config::c('hourly_rate', 60), 'status' => module_config::s('invoice_status_default', 'New'), 'user_id' => '', 'date_renew' => '', 'renew_invoice_id' => '', 'deposit_job_id' => 0, 'date_cancel' => '0000-00-00', 'total_amount_deposits' => 0, 'total_amount_deposits_tax' => 0, 'default_task_type' => module_config::c('default_task_type', _TASK_TYPE_HOURS_AMOUNT), 'overdue_email_auto' => module_config::c('overdue_email_auto', 0), 'renew_auto' => 0, 'renew_email' => 0, 'overdue' => false, 'invoice_template_print' => '', 'website_id' => '0', 'website_ids' => ''); $invoice['total_tax_rate'] = module_config::c('tax_percent', 10); $invoice['total_tax_name'] = module_config::c('tax_name', 'TAX'); $customer_data = false; if ($customer_id > 0) { $customer_data = module_customer::get_customer($customer_id); } if ($customer_data && $customer_data['customer_id'] && $customer_data['customer_id'] == $customer_id) { // is there a default invoice template for this customer? if (class_exists('module_extra', false)) { $extras = module_extra::get_extras(array('owner_table' => 'customer', 'owner_id' => $customer_id)); foreach ($extras as $e) { if ($e['extra_key'] == 'invoice_template_print') { $invoice['invoice_template_print'] = $e['extra']; } } } if ($customer_data['primary_user_id']) { $invoice['primary_user_id'] = $customer_data['primary_user_id']; } if (isset($customer_data['default_tax']) && $customer_data['default_tax'] >= 0) { $invoice['total_tax_rate'] = $customer_data['default_tax']; $invoice['total_tax_name'] = $customer_data['default_tax_name']; } } } // drag some details from the related job $first_job_id = 0; if (!(int) $invoice_id) { if (isset($invoice['job_ids']) && $invoice['job_ids']) { $first_job_id = current($invoice['job_ids']); } else { if (isset($invoice['job_id']) && $invoice['job_id']) { $first_job_id = $invoice['job_id']; // abckwards compatibility } else { $first_job_id = 0; } } if ($first_job_id > 0) { $job_data = module_job::get_job($first_job_id, false); $invoice['hourly_rate'] = $job_data['hourly_rate']; $invoice['taxes'] = $job_data['taxes']; //$invoice['total_tax_rate'] = $job_data['total_tax_rate']; //$invoice['total_tax_name'] = $job_data['total_tax_name']; } } // new support for multiple taxes if (!isset($invoice['taxes']) || !count($invoice['taxes']) && $invoice['total_tax_rate'] > 0) { $invoice['taxes'] = array(); if ($first_job_id > 0 && !(int) $invoice_id) { // taxes set above from job } else { $tax_rates = explode(',', $invoice['total_tax_rate']); $tax_names = explode(',', $invoice['total_tax_name']); foreach ($tax_rates as $tax_rate_id => $tax_rate_amount) { if ($tax_rate_amount > 0) { $invoice['taxes'][] = array('order' => 0, 'percent' => $tax_rate_amount, 'name' => isset($tax_names[$tax_rate_id]) ? $tax_names[$tax_rate_id] : $invoice['total_tax_name'], 'total' => 0, 'amount' => 0, 'discount' => 0, 'increment' => module_config::c('tax_multiple_increment', 0)); } } } } // work out total hours etc.. //$invoice['total_hours'] = 0; //$invoice['total_hours_completed'] = 0; //$invoice['total_hours_overworked'] = 0; $invoice['discount_amount_on_tax'] = 0; // used in job.php $invoice['total_sub_amount'] = 0; $invoice['total_sub_amount_taxable'] = 0; $invoice_items = self::get_invoice_items((int) $invoice['invoice_id'], $invoice); foreach ($invoice_items as $invoice_item) { if ($invoice_item['invoice_item_amount'] != 0) { // we have a custom amount for this invoice_item $invoice['total_sub_amount'] += $invoice_item['invoice_item_amount']; if ($invoice_item['taxable']) { $invoice['total_sub_amount_taxable'] += $invoice_item['invoice_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 //$invoice['total_tax'] += round(($invoice_item['invoice_item_amount'] * ($invoice['total_tax_rate'] / 100)),module_config::c('currency_decimal_places',2)); foreach ($invoice['taxes'] as $invoice_tax_id => $invoice_tax) { if (!isset($invoice['taxes'][$invoice_tax_id]['total'])) { $invoice['taxes'][$invoice_tax_id]['total'] = 0; } $invoice['taxes'][$invoice_tax_id]['total'] += $invoice_item['invoice_item_amount']; $invoice['taxes'][$invoice_tax_id]['amount'] += round($invoice_item['invoice_item_amount'] * ($invoice_tax['percent'] / 100), module_config::c('currency_decimal_places', 2)); } } } } } //$invoice['final_modification'] = 0; // hack for discount modes - change this to just 'discount_amount' cos that is all that uses this variable. HERE // add any discounts. if ($invoice['discount_amount'] != 0) { if ($invoice['discount_type'] == _DISCOUNT_TYPE_AFTER_TAX) { // after tax discount :::::::::: // handled below. //$invoice['final_modification'] = -$invoice['discount_amount']; } else { if ($invoice['discount_type'] == _DISCOUNT_TYPE_BEFORE_TAX) { // before tax discount::::: //$invoice['final_modification'] = -$invoice['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 ($invoice['taxes'] as $invoice_tax_id => $invoice_tax) { $this_tax_discount = round($invoice['discount_amount'] * ($invoice['taxes'][$invoice_tax_id]['percent'] / 100), module_config::c('currency_decimal_places', 2)); $invoice['discount_amount_on_tax'] += $this_tax_discount; if (!isset($invoice['taxes'][$invoice_tax_id]['total'])) { $invoice['taxes'][$invoice_tax_id]['total'] = 0; } $invoice['taxes'][$invoice_tax_id]['total'] -= $invoice['discount_amount']; $invoice['taxes'][$invoice_tax_id]['amount'] -= $this_tax_discount; $invoice['taxes'][$invoice_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 $invoice['taxes_backup'] = $invoice['taxes']; $invoice['total_sub_amount_taxable_backup'] = $invoice['total_sub_amount_taxable']; $total_tax_before_discount = 0; foreach ($invoice['taxes'] as $invoice_tax_id => $invoice_tax) { $invoice['taxes'][$invoice_tax_id]['total'] = $invoice['total_sub_amount_taxable']; $invoice['taxes'][$invoice_tax_id]['amount'] = round($invoice['total_sub_amount_taxable'] * ($invoice_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($invoice_tax['increment']) && $invoice_tax['increment']) { $invoice['total_sub_amount_taxable'] += $invoice['taxes'][$invoice_tax_id]['amount']; } $total_tax_before_discount += $invoice['taxes'][$invoice_tax_id]['amount']; } $invoice['taxes'] = $invoice['taxes_backup']; $invoice['total_sub_amount_taxable'] = $invoice['total_sub_amount_taxable_backup']; } // remove the discount amount from the 'sub total' and the 'taxable total' but don't go negative on it. // remove the discount from any non-taxable portion first. $non_taxable_amount = $invoice['total_sub_amount'] - $invoice['total_sub_amount_taxable']; $non_taxable_discount = min($invoice['discount_amount'], $non_taxable_amount); $taxable_discount = $invoice['discount_amount'] - $non_taxable_discount; //echo "non tax $non_taxable_amount \n nontax discount: $non_taxable_discount \n tax discount: $taxable_discount \n";print_r($invoice);exit; $invoice['total_sub_amount'] -= $invoice['discount_amount']; $invoice['total_sub_amount_taxable'] -= $taxable_discount; // $invoice['total_sub_amount']-=$invoice['discount_amount']; // $invoice['total_sub_amount_taxable']-=$invoice['discount_amount']; } } } //$invoice['total_hours_remain'] = $invoice['total_hours'] - $invoice['total_hours_completed']; //$invoice['total_percent_complete'] = $invoice['total_hours'] > 0 ? round($invoice['total_hours_remain'] / $invoice['total_hours'],2) : 0; //if(isset($invoice['total_tax_rate'])){ if (module_config::c('tax_calculate_mode', _TAX_CALCULATE_AT_END) == _TAX_CALCULATE_INCREMENTAL && isset($invoice['total_tax']) && $invoice['total_tax'] > 0) { // tax already calculated above. } else { 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_invoice_tax_id = false; foreach ($invoice['taxes'] as $invoice_tax_id => $invoice_tax) { $invoice['taxes'][$invoice_tax_id]['total'] = $invoice['total_sub_amount_taxable']; if (isset($invoice_tax['increment']) && $invoice_tax['increment'] && $previous_invoice_tax_id) { $invoice['taxes'][$invoice_tax_id]['total'] += $invoice['taxes'][$previous_invoice_tax_id]['amount']; } $invoice['taxes'][$invoice_tax_id]['amount'] = round($invoice['taxes'][$invoice_tax_id]['total'] * ($invoice_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_invoice_tax_id = $invoice_tax_id; } //$invoice['total_tax'] = round(($invoice['total_sub_amount_taxable'] * ($invoice['total_tax_rate'] / 100)),module_config::c('currency_decimal_places',2)); } else { //$invoice['total_tax'] = 0; } } if (isset($invoice['tax_type']) && $invoice['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($invoice['taxes']) && count($invoice['taxes']) > 1) { set_error('Included tax calculation only works with 1 tax rate'); } else { if (is_array($invoice['taxes']) && count($invoice['taxes'])) { reset($invoice['taxes']); $invoice_tax_id = key($invoice['taxes']); if (isset($invoice['taxes'][$invoice_tax_id])) { $taxable_amount = $invoice['total_sub_amount_taxable'] / (1 + $invoice['taxes'][$invoice_tax_id]['percent'] / 100); $invoice['taxes'][$invoice_tax_id]['amount'] = $invoice['total_sub_amount_taxable'] - $taxable_amount; $invoice['total_sub_amount'] = $invoice['total_sub_amount'] - $invoice['taxes'][$invoice_tax_id]['amount']; } } } } $invoice['total_tax'] = 0; foreach ($invoice['taxes'] as $invoice_tax_id => $invoice_tax) { $invoice['total_tax'] += $invoice_tax['amount']; } if (isset($total_tax_before_discount)) { $invoice['discount_amount_on_tax'] += $total_tax_before_discount - $invoice['total_tax']; } $invoice['total_amount'] = $invoice['total_sub_amount'] + $invoice['total_tax']; if ($invoice['discount_type'] == _DISCOUNT_TYPE_AFTER_TAX) { $invoice['total_amount'] -= $invoice['discount_amount']; } $invoice['total_amount'] = round($invoice['total_amount'], module_config::c('currency_decimal_places', 2)); $invoice['overdue'] = $invoice['date_due'] && $invoice['date_due'] != '0000-00-00' && (!$invoice['date_paid'] || $invoice['date_paid'] == '0000-00-00') && strtotime($invoice['date_due']) < strtotime(date('Y-m-d')); if ($basic === 1) { // so we don't go clearning cache and working out how much has been paid. // used in the finance module while displaying dashboard summary. return $invoice; } // find the user id if none exists. /*if($invoice['customer_id'] && !$invoice['user_id']){ $customer_data = module_customer::get_customer($invoice['customer_id']); if($customer_data && $customer_data['customer_id'] == $invoice['customer_id']){ if($customer_data['primary_user_id']){ $invoice['user_id'] = $customer_data['primary_user_id']; }else{ $customer_contacts = module_user::get_contacts(array('customer_id'=>$invoice['customer_id'])); foreach($customer_contacts as $contact){ // todo - search roles or something to find the accountant. $invoice['user_id'] = $contact['user_id']; break; } } } }*/ $paid = 0; /* START DEPOSITS */ $invoice['total_amount_deposits'] = 0; // calculate deposits separately. $invoice['total_amount_deposits_tax'] = 0; // calculate deposits separately. //module_cache::clear_cache(); // no longer clearnig cache, it does it in get_invoice_payments. //module_cache::clear('invoice'); foreach (self::get_invoice_payments($invoice_id) as $payment) { if ($payment['date_paid'] && $payment['date_paid'] != '0000-00-00') { if ($payment['payment_type'] == _INVOICE_PAYMENT_TYPE_DEPOSIT) { // what invoice did this payment come from? $deposit_invoice = module_invoice::get_invoice($payment['other_id']); if ($deposit_invoice && $deposit_invoice['invoice_id'] == $payment['other_id']) { $invoice['total_amount_deposits'] += min($deposit_invoice['total_amount'] - $deposit_invoice['total_tax'], $payment['amount'] - $deposit_invoice['total_tax']); $invoice['total_amount_deposits_tax'] += $deposit_invoice['total_tax']; } } else { $paid += $payment['amount']; } } } if ($invoice['total_amount_deposits'] > 0) { // we need to reduce the 'total_amount' of this invoice so it doesn't double up with the other paid deposit invoice $invoice['total_amount'] -= $invoice['total_amount_deposits']; } if ($invoice['total_amount_deposits_tax'] > 0) { //$invoice['total_tax'] -= $invoice['total_amount_deposits_tax']; // we need to reduce the 'total_amount' of this invoice so it doesn't double up with the other paid deposit invoice $invoice['total_amount'] -= $invoice['total_amount_deposits_tax']; } /* END DEPOSITS */ // any extra fees (eG: paypap fee?) $invoice['fees'] = self::get_fees($invoice_id, $invoice); foreach ($invoice['fees'] as $fee) { $invoice['total_amount'] += $fee['total']; } // dont go negative on payments: $invoice['total_amount_paid'] = max(0, min($invoice['total_amount'], $paid)); $invoice['total_amount_credit'] = 0; if ($invoice['total_amount'] > 0 && $paid > $invoice['total_amount']) { // raise a credit against this customer for the difference. $invoice['total_amount_credit'] = round($paid - $invoice['total_amount'], 2); //echo $invoice['total_amount_overpaid'];exit; } if ($invoice['total_amount'] != $invoice['cached_total']) { if ((int) $invoice_id > 0) { update_insert('invoice_id', $invoice_id, 'invoice', array('cached_total' => $invoice['total_amount'])); } $invoice['cached_total'] = $invoice['total_amount']; } $invoice['total_amount_due'] = round($invoice['total_amount'] - $invoice['total_amount_paid'], module_config::c('currency_decimal_places', 2)); if ($invoice['date_cancel'] != '0000-00-00') { $invoice['total_amount_due'] = 0; } // a special addition for deposit invoices. if (isset($invoice['deposit_job_id']) && $invoice['deposit_job_id']) { // we find out how much deposit has actually been paid // and how much is remaining that hasn't been allocated to any other invoices $invoice['deposit_remaining'] = 0; if ($invoice['total_amount_paid'] > 0) { $invoice['deposit_remaining'] = $invoice['total_amount_paid']; $payments = get_multiple('invoice_payment', array('payment_type' => _INVOICE_PAYMENT_TYPE_DEPOSIT, 'other_id' => $invoice['invoice_id'])); foreach ($payments as $payment) { $invoice['deposit_remaining'] = $invoice['deposit_remaining'] - $payment['amount']; } } } // save our database cache values: if ((int) $invoice_id > 0) { foreach (array('total_amount', 'total_amount_due') as $cacheable_item) { if (isset($invoice[$cacheable_item]) && (!isset($original_invoice) || !isset($original_invoice['c_' . $cacheable_item]) || $original_invoice['c_' . $cacheable_item] != $invoice[$cacheable_item])) { // cacheable items can be the same name or prefixed with c_ update_insert('invoice_id', $invoice_id, 'invoice', array("c_{$cacheable_item}" => $invoice[$cacheable_item])); $invoice["c_{$cacheable_item}"] = $invoice[$cacheable_item]; } } } if (isset($cache_key)) { module_cache::put('invoice', $cache_key, $invoice, $cache_timeout); } return $invoice; }
public static function get_fields($table, $ignore = array(), $hidden = array(), $from_cache = false) { if (is_array($table) || !trim($table)) { return array(); } if (isset(self::$fieldscache[$table])) { return self::$fieldscache[$table]; } $res = $db_cache = array(); if ($from_cache) { $db_cache = module_cache::get('db', 'db_fields_' . $table); if (!is_array($db_cache)) { $db_cache = array(); } if (isset($db_cache[$table])) { $res = $db_cache[$table]; } } if (!count($res)) { $sql = "SHOW FIELDS FROM `" . _DB_PREFIX . "{$table}`"; $res = qa($sql); if (!is_array($db_cache)) { $db_cache = array(); } $db_cache[$table] = $res; module_cache::put('db', 'db_fields_' . $table, $db_cache, 172800); } $fields = array(); foreach ($res as $r) { $format = ""; $type = 'text'; if (count($ignore) && in_array($r['Field'], $ignore)) { continue; } if (count($hidden) && in_array($r['Field'], $hidden)) { $type = "hidden"; // new field for file. } else { if (preg_match("/^file_/", $r['Field']) && preg_match("/varchar\\((\\d+)\\)/", $r['Type'], $matches)) { $type = "file"; $size = 50; $maxlength = 255; } else { if (preg_match("/varchar\\((\\d+)\\)/", $r['Type'], $matches)) { $type = "text"; $size = max("10", min("30", $matches[1])); $maxlength = $matches[1]; } else { if (preg_match("/int/i", $r['Type']) || preg_match("/float/i", $r['Type'])) { $format = array("/^\\d+\$/", "Integer"); $type = "number"; $maxlength = $size = 20; } else { if ($r['Type'] == "text") { $type = "textarea"; $size = 0; } else { if ($r['Type'] == "date" || $r['Type'] == "datetime") { $format = array("/^\\d\\d\\d\\d-\\d\\d-\\d\\d\$/", "YYYY-MM-DD"); $type = "date"; $maxlength = $size = 20; } else { if (preg_match("/decimal/", $r['Type']) || preg_match("/double/", $r['Type'])) { $format = array("/^\\d+\\.?[\\d+]?\$/", "Decimal"); $type = "decimal"; $maxlength = $size = 20; } } } } } } } $required = false; if ($r['Null'] == "NO") { $required = true; } $fields[$r['Field']] = array("name" => $r['Field'], "type" => $type, "dbtype" => $r['Type'], "size" => $size, "maxlength" => $maxlength, "required" => $required, "format" => $format); } self::$fieldscache[$table] = $fields; return $fields; }
public static function get_job($job_id, $full = true, $skip_permissions = false) { $job_id = (int) $job_id; if ($job_id <= 0) { $job = array(); } else { $cache_key = self::_job_cache_key($job_id, array($job_id, $full, $skip_permissions)); if ($cached_item = module_cache::get('job', $cache_key)) { return $cached_item; } $cache_key_full = self::_job_cache_key($job_id, array($job_id, true, $skip_permissions)); if ($cache_key_full != $cache_key && ($cached_item = module_cache::get('job', $cache_key_full))) { return $cached_item; } $cache_timeout = module_config::c('cache_objects', 60); $job = get_single("job", "job_id", $job_id); } // check permissions if ($job && isset($job['job_id']) && $job['job_id'] == $job_id) { switch (self::get_job_access_permissions()) { case _JOB_ACCESS_ALL: break; case _JOB_ACCESS_ASSIGNED: // only assigned jobs! $has_job_access = false; if ($job['user_id'] == module_security::get_loggedin_id()) { $has_job_access = true; break; } $tasks = module_job::get_tasks($job['job_id']); foreach ($tasks as $task) { if ($task['user_id'] == module_security::get_loggedin_id()) { $has_job_access = true; break; } } unset($tasks); if (!$has_job_access) { if ($skip_permissions) { $job['_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 { $job = false; } } break; case _JOB_ACCESS_CUSTOMER: // tie in with customer permissions to only get jobs from customers we can access. $customers = module_customer::get_customers(); $has_job_access = false; if (isset($customers[$job['customer_id']])) { $has_job_access = true; } /*foreach($customers as $customer){ // todo, if($job['customer_id'] == 0) // ignore this permission if($customer['customer_id']==$job['customer_id']){ $has_job_access = true; break; } }*/ unset($customers); if (!$has_job_access) { if ($skip_permissions) { $job['_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 { $job = false; } } break; } if ($job) { $job['taxes'] = get_multiple('job_tax', array('job_id' => $job_id), 'job_tax_id', 'exact', 'order'); } } if (!$full) { // unserialize our cached staff_total_grouped key (and other cache keys?) // this is used in finance.php line 1053 $job['staff_total_grouped'] = array(); if (isset($job['c_staff_total_grouped']) && strlen($job['c_staff_total_grouped'])) { $job['staff_total_grouped'] = @unserialize($job['c_staff_total_grouped']); } if (isset($cache_key)) { module_cache::put('job', $cache_key, $job, $cache_timeout); } return $job; } if (!$job) { $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_job_name = module_config::c('job_default_new_name', ''); if (module_config::c('job_name_incrementing', 0)) { $job_number = module_config::c('job_name_incrementing_next', 1); // see if there is an job number matching this one. $this_job_number = $job_number; do { $jobs = get_multiple('job', array('name' => $this_job_number)); //'customer_id'=>$customer_id, if (!count($jobs)) { $job_number = $this_job_number; } else { $this_job_number++; } } while (count($jobs)); module_config::save_config('job_name_incrementing_next', $job_number); $default_job_name = $job_number . $default_job_name; } $job = array('job_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_job_name, 'date_quote' => date('Y-m-d'), 'date_start' => module_config::c('job_allow_quotes', 0) ? '' : date('Y-m-d'), 'date_due' => '', 'date_completed' => '', 'date_renew' => '', 'user_id' => module_security::get_loggedin_id(), 'renew_job_id' => '', 'status' => module_config::s('job_status_default', 'New'), 'type' => module_config::s('job_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' => '', 'quote_id' => 0, 'discount_description' => _l('Discount:'), 'discount_amount' => 0, 'discount_type' => module_config::c('invoice_discount_type', _DISCOUNT_TYPE_BEFORE_TAX)); if (isset($_REQUEST['from_quote_id']) && (int) $_REQUEST['from_quote_id']) { $quote = module_quote::get_quote($_REQUEST['from_quote_id']); $job = array_merge($job, $quote); $job['date_quote'] = $quote['date_create']; $job['date_start'] = date('Y-m-d'); $job['quote_id'] = (int) $_REQUEST['from_quote_id']; } // some defaults from the db. $job['total_tax_rate'] = module_config::c('tax_percent', 10); $job['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) { $job['total_tax_rate'] = $customer_data['default_tax']; $job['total_tax_name'] = $customer_data['default_tax_name']; } } } // new support for multiple taxes if (!isset($job['taxes']) || !count($job['taxes']) && $job['total_tax_rate'] > 0) { $job['taxes'] = array(); $tax_rates = explode(',', $job['total_tax_rate']); $tax_names = explode(',', $job['total_tax_name']); foreach ($tax_rates as $tax_rate_id => $tax_rate_amount) { if ($tax_rate_amount > 0) { $job['taxes'][] = array('order' => 0, 'percent' => $tax_rate_amount, 'name' => isset($tax_names[$tax_rate_id]) ? $tax_names[$tax_rate_id] : $job['total_tax_name'], 'total' => 0, 'amount' => 0, 'discount' => 0, 'increment' => module_config::c('tax_multiple_increment', 0)); } } } if ($job) { // work out total hours etc.. $job['total_hours'] = 0; $job['total_hours_completed'] = 0; $job['total_hours_overworked'] = 0; $job['total_sub_amount'] = 0; $job['total_sub_amount_taxable'] = 0; $job['total_sub_amount_unbillable'] = 0; $job['total_sub_amount_invoicable'] = 0; $job['total_sub_amount_invoicable_taxable'] = 0; $job['total_amount_invoicable'] = 0; $job['total_tasks_remain'] = 0; $job['total_amount'] = 0; $job['total_amount_paid'] = 0; $job['total_amount_invoiced'] = 0; $job['total_amount_invoiced_deposit'] = 0; $job['total_amount_todo'] = 0; $job['total_amount_outstanding'] = 0; $job['total_amount_due'] = 0; $job['total_hours_remain'] = 0; $job['total_percent_complete'] = isset($job['total_percent_complete']) ? $job['total_percent_complete'] : 0; $job['total_tax'] = 0; $job['total_tax_invoicable'] = 0; $job['invoice_discount_amount'] = 0; $job['invoice_discount_amount_on_tax'] = 0; $job['total_amount_discounted'] = 0; // new feature to invoice incompleted tasks $job['uninvoiced_task_ids'] = array(); // new staff expenses/totals $job['staff_hourly_rate'] = $job['hourly_rate']; $job['staff_total_hours'] = 0; $job['staff_total_hours_completed'] = 0; $job['staff_total_hours_overworked'] = 0; $job['staff_total_sub_amount'] = 0; $job['staff_total_sub_amount_unbillable'] = 0; $job['staff_total_amount'] = 0; $job['staff_total_grouped'] = array(); // total staff expenses grouped by individual staff members. $job['total_net_amount'] = 0; // after the staff expense is taken away. if ($job_id > 0) { $non_hourly_job_count = $non_hourly_job_completed = 0; $tasks = self::get_tasks($job['job_id']); $job_percentage_complete_averages = array(); foreach ($tasks as $task_id => $task) { // new support for different task types if (!isset($task['manual_task_type']) || $task['manual_task_type'] < 0) { $task['manual_task_type'] = $job['default_task_type']; } if (module_config::c('job_task_log_all_hours', 1)) { // jobs have to be marked fully_completd. if (!$task['fully_completed']) { $job['total_tasks_remain']++; } } else { if ($task['amount'] != 0 && $task['completed'] <= 0) { $job['total_tasks_remain']++; } else { if ($task['hours'] > 0 && $task['completed'] < $task['hours']) { $job['total_tasks_remain']++; } } } $tasks[$task_id]['sum_amount'] = 0; if ($task['amount'] != 0) { // we have a custom amount for this task. // do we multiply it by qty (stored in hours?) if ($task['manual_task_type'] == _TASK_TYPE_QTY_AMOUNT) { $tasks[$task_id]['sum_amount'] = $task['amount'] * $task['hours']; } else { $tasks[$task_id]['sum_amount'] = $task['amount']; } } if ($task['manual_task_type'] == _TASK_TYPE_QTY_AMOUNT && $task['hours'] > 0 && $task['amount'] == 0) { $tasks[$task_id]['sum_amount'] = $task['hours'] * $job['hourly_rate']; } if ($task['manual_task_type'] == _TASK_TYPE_HOURS_AMOUNT && $task['hours'] > 0) { $job['total_hours'] += $task['hours']; $task_completed_hours = min($task['hours'], $task['completed']); if ($task['fully_completed']) { // hack to record that we have worked 100% of this task. $task_completed_hours = $task['hours']; } $job['total_hours_completed'] += $task_completed_hours; if ($task['completed'] > $task['hours']) { $job['total_hours_overworked'] += $task['completed'] - $task['hours']; } else { if ($task['completed'] > 0) { // underworked hours $job['total_hours_overworked'] += $task['completed'] - $task['hours']; } } if ($task['amount'] <= 0) { $tasks[$task_id]['sum_amount'] = $task['hours'] * $job['hourly_rate']; } } else { // it's a non-hourly task. // work out if it's completed or not. $non_hourly_job_count++; if ($task['fully_completed']) { $non_hourly_job_completed++; } } if (!$task['invoiced'] && $task['billable']) { $job['uninvoiced_task_ids'][] = $task_id; } if (!$task['invoiced'] && $task['billable'] && (module_config::c('job_task_log_all_hours', 1) || $task['hours'] > 0 && $task['completed'] > 0 && $task['completed'] >= $task['hours'] || $task['hours'] <= 0 && $task['fully_completed'])) { /*if(module_config::c('job_task_log_all_hours',1)){*/ // a task has to be marked "fully_completeD" before it will be invoiced. if ($task['fully_completed']) { $job['total_sub_amount_invoicable'] += $tasks[$task_id]['sum_amount']; if ($task['taxable']) { if (module_config::c('tax_calculate_mode', _TAX_CALCULATE_AT_END) == _TAX_CALCULATE_INCREMENTAL) { foreach ($job['taxes'] as $job_tax_id => $job_tax) { $job['total_tax_invoicable'] += round($tasks[$task_id]['sum_amount'] * ($job_tax['percent'] / 100), module_config::c('currency_decimal_places', 2)); } } else { $job['total_sub_amount_invoicable_taxable'] += $tasks[$task_id]['sum_amount']; } } } /*}else{ $job['total_sub_amount_invoicable'] += $tasks[$task_id]['sum_amount']; if($task['taxable']){ if(module_config::c('tax_calculate_mode',_TAX_CALCULATE_AT_END)==_TAX_CALCULATE_INCREMENTAL){ $job['total_tax_invoicable'] += round(($tasks[$task_id]['sum_amount'] * ($job['total_tax_rate'] / 100)),module_config::c('currency_decimal_places',2)); }else{ $job['total_sub_amount_invoicable_taxable'] += $tasks[$task_id]['sum_amount']; } } //(min($task['hours'],$task['completed']) * $job['hourly_rate']); }*/ } if ($task['taxable'] && $task['billable']) { $job['total_sub_amount_taxable'] += $tasks[$task_id]['sum_amount']; if (module_config::c('tax_calculate_mode', _TAX_CALCULATE_AT_END) == _TAX_CALCULATE_INCREMENTAL) { //$job['total_tax'] += round(($tasks[$task_id]['sum_amount'] * ($job['total_tax_rate'] / 100)),module_config::c('currency_decimal_places',2)); // todo - incremental multi-tax calculation foreach ($job['taxes'] as $job_tax_id => $job_tax) { if (!isset($job['taxes'][$job_tax_id]['total'])) { $job['taxes'][$job_tax_id]['total'] = 0; } $job['taxes'][$job_tax_id]['total'] += $tasks[$task_id]['sum_amount']; $job['taxes'][$job_tax_id]['amount'] += round($tasks[$task_id]['sum_amount'] * ($job_tax['percent'] / 100), module_config::c('currency_decimal_places', 2)); } } } if ($task['billable']) { $job['total_sub_amount'] += $tasks[$task_id]['sum_amount']; } else { $job['total_sub_amount_unbillable'] += $tasks[$task_id]['sum_amount']; } $job_percentage_complete_averages[] = self::get_percentage($tasks[$task_id]); // new staff expenses calculations if (self::job_task_has_split_hours($job_id, $job, $task_id, $task)) { $tasks[$task_id]['staff_sum_amount'] = 0; switch ($task['manual_task_type']) { case _TASK_TYPE_QTY_AMOUNT: $tasks[$task_id]['staff_sum_amount'] = $task['staff_amount'] * $task['staff_hours']; break; case _TASK_TYPE_AMOUNT_ONLY: $tasks[$task_id]['staff_sum_amount'] = $task['staff_amount']; break; case _TASK_TYPE_HOURS_AMOUNT: $tasks[$task_id]['staff_sum_amount'] = $task['staff_amount'] == 0 ? $task['staff_hours'] * $job['staff_hourly_rate'] : $task['staff_amount'] * $task['staff_hours']; break; } if ($task['billable']) { $job['staff_total_sub_amount'] += $tasks[$task_id]['staff_sum_amount']; if (!isset($job['staff_total_grouped'][$task['user_id']])) { $job['staff_total_grouped'][$task['user_id']] = 0; } $job['staff_total_grouped'][$task['user_id']] += $tasks[$task_id]['staff_sum_amount']; } else { $job['staff_total_sub_amount_unbillable'] += $tasks[$task_id]['staff_sum_amount']; } } } // end task loop $job['total_hours_remain'] = $job['total_hours'] - $job['total_hours_completed']; // add any discounts. if ($job['discount_amount'] != 0) { if ($job['discount_type'] == _DISCOUNT_TYPE_AFTER_TAX) { // after tax discount :::::::::: // handled below. //$job['final_modification'] = -$job['discount_amount']; } else { if ($job['discount_type'] == _DISCOUNT_TYPE_BEFORE_TAX) { // before tax discount::::: //$job['final_modification'] = -$job['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 ($job['taxes'] as $job_tax_id => $job_tax) { $this_tax_discount = round($job['discount_amount'] * ($job['taxes'][$job_tax_id]['percent'] / 100), module_config::c('currency_decimal_places', 2)); $job['discount_amount_on_tax'] += $this_tax_discount; if (!isset($job['taxes'][$job_tax_id]['total'])) { $job['taxes'][$job_tax_id]['total'] = 0; } $job['taxes'][$job_tax_id]['total'] -= $job['discount_amount']; $job['taxes'][$job_tax_id]['amount'] -= $this_tax_discount; $job['taxes'][$job_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 $job['taxes_backup'] = $job['taxes']; $job['total_sub_amount_taxable_backup'] = $job['total_sub_amount_taxable']; $total_tax_before_discount = 0; foreach ($job['taxes'] as $job_tax_id => $job_tax) { $job['taxes'][$job_tax_id]['total'] = $job['total_sub_amount_taxable']; $job['taxes'][$job_tax_id]['amount'] = round($job['total_sub_amount_taxable'] * ($job_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($job_tax['increment']) && $job_tax['increment']) { $job['total_sub_amount_taxable'] += $job['taxes'][$job_tax_id]['amount']; } $total_tax_before_discount += $job['taxes'][$job_tax_id]['amount']; } $job['taxes'] = $job['taxes_backup']; $job['total_sub_amount_taxable'] = $job['total_sub_amount_taxable_backup']; } // remove the discount amount from the 'sub total' and the 'taxable total' but don't go negative on it. // remove the discount from any non-taxable portion first. $non_taxable_amount = $job['total_sub_amount'] - $job['total_sub_amount_taxable']; $non_taxable_discount = min($job['discount_amount'], $non_taxable_amount); $taxable_discount = $job['discount_amount'] - $non_taxable_discount; //echo "non tax $non_taxable_amount \n nontax discount: $non_taxable_discount \n tax discount: $taxable_discount \n";print_r($job);exit; $job['total_sub_amount'] -= $job['discount_amount']; $job['total_sub_amount_taxable'] -= $taxable_discount; } } } if (count($job_percentage_complete_averages) > 0) { if (!isset($job['total_percent_complete_manual']) || !$job['total_percent_complete_manual']) { $job['total_percent_complete'] = round(array_sum($job_percentage_complete_averages) / count($job_percentage_complete_averages), 2); } else { $job['total_percent_complete_calculated'] = round(array_sum($job_percentage_complete_averages) / count($job_percentage_complete_averages), 2); } } /*if($job['total_hours'] > 0){ // total hours completed. work out job task based on hours completed. $job['total_percent_complete'] = round($job['total_hours_completed'] / $job['total_hours'],2); }else if($non_hourly_job_count>0){ // work out job completed rate based on $non_hourly_job_completed and $non_hourly_job_count $job['total_percent_complete'] = round($non_hourly_job_completed/$non_hourly_job_count,2); }*/ // find any invoices $invoices = module_invoice::get_invoices(array('job_id' => $job_id)); foreach ($invoices as $invoice) { $invoice = module_invoice::get_invoice($invoice['invoice_id']); if (!$invoice) { continue; } //print_r($invoice); // we only ad up the invoiced tasks that are from this job // an invoice could have added manually more items to it, so this would throw the price out. $this_invoice = 0; $this_invoice_taxable = 0; $invoice_items = module_invoice::get_invoice_items($invoice['invoice_id']); // first loop will find out of this is a merged invoice or not. $merged_invoice = false; foreach ($invoice_items as $invoice_item) { if ($invoice_item['task_id'] && !isset($tasks[$invoice_item['task_id']])) { $merged_invoice = true; } } // if it's a merged invoice we don't add non-task-id items to the total. // if its a normal non-merged invoice then we can add the non-task linked items to the total. if (!$merged_invoice) { $this_invoice = $invoice['total_amount']; } else { foreach ($invoice_items as $invoice_item) { if ($invoice_item['task_id'] && isset($tasks[$invoice_item['task_id']]) && $tasks[$invoice_item['task_id']]['billable']) { $this_invoice += $tasks[$invoice_item['task_id']]['sum_amount']; if ($invoice_item['taxable']) { $this_invoice_taxable += $tasks[$invoice_item['task_id']]['sum_amount']; if (module_config::c('tax_calculate_mode', _TAX_CALCULATE_AT_END) == _TAX_CALCULATE_INCREMENTAL) { foreach ($invoice_item['taxes'] as $invoice_item_tax) { $this_invoice += round($tasks[$invoice_item['task_id']]['sum_amount'] * ($invoice_item_tax['percent'] / 100), module_config::c('currency_decimal_places', 2)); } } } } } } // any discounts ? $job['invoice_discount_amount'] += $invoice['discount_amount']; $job['invoice_discount_amount_on_tax'] += $invoice['discount_amount_on_tax']; // todo - move all this tax calculation back to if ($merged_invoice && module_config::c('tax_calculate_mode', _TAX_CALCULATE_AT_END) == _TAX_CALCULATE_AT_END && $this_invoice_taxable > 0) { $this_invoice_tax = 0; foreach ($invoice['taxes'] as $invoice_tax) { // todo - incremental or what not in here. $this_invoice_tax = $this_invoice_tax + $this_invoice_taxable * ($invoice_tax['percent'] / 100); } $this_invoice += $this_invoice_tax; //$this_invoice = ($this_invoice + ($this_invoice_taxable * ($invoice['total_tax_rate'] / 100))); } //print_r($invoice); if ($invoice['deposit_job_id'] == $job_id) { $job['total_amount_invoiced_deposit'] += $this_invoice; } else { } $job['total_amount_invoiced'] += $this_invoice; $job['total_amount_paid'] += min($invoice['total_amount_paid'], $this_invoice); $job['total_amount_outstanding'] += min($invoice['total_amount_due'], $this_invoice); } // todo: save these two values in the database so that future changes do not affect them. if (module_config::c('tax_calculate_mode', _TAX_CALCULATE_AT_END) == _TAX_CALCULATE_AT_END) { $job['total_tax'] = 0; $job['total_tax_invoicable'] = 0; $previous_tax_id = false; foreach ($job['taxes'] as $job_tax_id => $job_tax) { if (!isset($job['taxes'][$job_tax_id]['total'])) { $job['taxes'][$job_tax_id]['total'] = 0; } if (!isset($job['taxes'][$job_tax_id]['total_invoicable'])) { $job['taxes'][$job_tax_id]['total_invoicable'] = 0; } if (!isset($job['taxes'][$job_tax_id]['amount_invoicable'])) { $job['taxes'][$job_tax_id]['amount_invoicable'] = 0; } $job['taxes'][$job_tax_id]['total'] += $job['total_sub_amount_taxable']; $job['taxes'][$job_tax_id]['total_invoicable'] += $job['total_sub_amount_invoicable_taxable']; if (isset($job_tax['increment']) && $job_tax['increment'] && $previous_tax_id) { $job['taxes'][$job_tax_id]['total'] += $job['taxes'][$previous_tax_id]['amount']; $job['taxes'][$job_tax_id]['total_invoicable'] += $job['taxes'][$previous_tax_id]['amount_invoicable']; } $t = round($job['taxes'][$job_tax_id]['total'] * ($job_tax['percent'] / 100), module_config::c('currency_decimal_places', 2)); $job['taxes'][$job_tax_id]['amount'] += $t; $job['total_tax'] += $t; $t = round($job['taxes'][$job_tax_id]['total_invoicable'] * ($job_tax['percent'] / 100), module_config::c('currency_decimal_places', 2)); $job['taxes'][$job_tax_id]['amount_invoicable'] += $t; $job['total_tax_invoicable'] += $t; $previous_tax_id = $job_tax_id; } //$job['total_tax'] = ( ($job['total_sub_amount_taxable']) * ($job['total_tax_rate'] / 100)); //$job['total_tax_invoicable'] =$job['total_sub_amount_invoicable_taxable'] > 0 ? ($job['total_sub_amount_invoicable_taxable'] * ($job['total_tax_rate'] / 100)) : 0; } $job['total_amount'] = round($job['total_sub_amount'] + $job['total_tax'], module_config::c('currency_decimal_places', 2)); if ($job['discount_type'] == _DISCOUNT_TYPE_AFTER_TAX) { $job['total_amount'] -= $job['discount_amount']; $job['total_sub_amount_invoicable'] -= $job['discount_amount']; } $job['total_amount_invoicable'] = $job['total_sub_amount_invoicable'] + $job['total_tax_invoicable']; // + ($job['total_sub_amount_invoicable'] * ($job['total_tax_rate'] / 100)); $job['total_amount_due'] = $job['total_amount'] - $job['total_amount_paid']; //todo: chekc if this is wrong with non-invoicable tasks. //$job['total_amount_outstanding'] = $job['total_amount_invoiced'] - $job['total_amount_paid']; $job['total_amount_discounted'] = $job['total_amount'] - $job['invoice_discount_amount'] - $job['invoice_discount_amount_on_tax']; //$job['total_amount_invoicable'] = $job['total_amount_invoicable'] - $job['invoice_discounts']-$job['invoice_discounts_tax']; $job['total_amount_todo'] = $job['total_amount_discounted'] - $job['total_amount_invoiced'] - $job['total_amount_invoicable']; //$job['total_amount_paid'] - // staff calculations if ($job['staff_total_sub_amount'] > 0) { // tax for staff?? $job['staff_total_amount'] = $job['staff_total_sub_amount']; } $job['total_net_amount'] = $job['total_amount'] - $job['staff_total_amount']; } } if (isset($cache_key)) { module_cache::put('job', $cache_key, $job, $cache_timeout); } self::save_job_cache($job_id, $job); return $job; }
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; }
public function handle_hook($hook, &$calling_module = false) { switch ($hook) { case "home_alerts": $alerts = array(); if (module_config::c('change_request_alerts', 1) && class_exists('module_website', false)) { $cache_key = "home_alerts_" . module_security::get_loggedin_id(); $cache_timeout = module_config::c('cache_objects', 60); if ($alerts = module_cache::get('change_request', $cache_key)) { return $alerts; } // find any open change requests for all customers. $websites = module_website::get_websites(array(), array('columns' => 'u.website_id')); // this gets websites we have permission to view. if (count($websites) > 0) { $website_ids = array(); foreach ($websites as $website) { $website_ids[] = $website['website_id']; } // build a query to find all new change requests for websitse we have access to $sql = "SELECT * FROM `" . _DB_PREFIX . "change_request` cr WHERE `website_id` IN (" . implode(', ', $website_ids) . ") AND `status` = " . _CHANGE_REQUEST_STATUS_NEW; $website_requests = qa($sql); foreach ($website_requests as $website_request) { $alert_res = process_alert($website_request['date_created'], _l('Change Request')); if ($alert_res) { $alert_res['link'] = module_website::link_open($website_request['website_id'], false); $alert_res['name'] = $website_request['url']; $alerts[] = $alert_res; } } /*$website_requests = self::get_change_requests(array( 'website_id'=>$website['website_id'], 'status'=>_CHANGE_REQUEST_STATUS_NEW, )); foreach($website_requests as $website_request){ $alert_res = process_alert($website_request['date_created'], _l('Change Request')); if($alert_res){ $alert_res['link'] = module_website::link_open($website['website_id'],false); $alert_res['name'] = $website_request['url']; $alerts[] = $alert_res; } }*/ } module_cache::put('change_request', $cache_key, $alerts, $cache_timeout); } return $alerts; } }
} $item_ticket_count[$item_id]['count']++; $envato_count += $item['cost']; } } else { $item_id = '-1'; if (!isset($item_ticket_count[$item_id])) { $item_ticket_count[$item_id] = array('envato_id' => $item_id, 'name' => 'No product', 'count' => 0, 'cost' => 0); } $item_ticket_count[$item_id]['count']++; } } if (mysql_num_rows($tickets) > 0) { mysql_data_seek($tickets, 0); } module_cache::put('ticket', 'envato_ticket_earning', $envato_count); //} function sort_envato_ticket_count($a, $b) { //return ($a['count']*$a['cost'])<=($b['count']*$b['cost']); return $a['count'] <= $b['count']; } uasort($item_ticket_count, 'sort_envato_ticket_count'); foreach ($item_ticket_count as $i) { ?> <a href="?search[envato_item_id][]=<?php echo $i['envato_id']; ?> "><?php echo htmlspecialchars($i['name']); ?>
<li class="footer"> <a href="<?php echo module_job::link_open(false); ?> "><?php _e('View All Jobs'); ?> </a> </li> </ul> </li> <?php } $job_todo_cache = ob_get_clean(); echo $job_todo_cache; module_cache::put('job', 'job_todo_header_cache', $job_todo_cache); } } ?> <!-- User Account: style can be found in dropdown.less --> <li class="dropdown user user-menu"> <?php $user = module_user::get_user(module_security::get_loggedin_id()); ?> <a href="#" class="dropdown-toggle" data-toggle="dropdown"> <i class="glyphicon glyphicon-user"></i> <span><?php echo htmlspecialchars($user['name']);
public static function get_customer($customer_id, $skip_permissions = false, $basic_for_link = false) { $customer_id = (int) $customer_id; $customer = false; if ($customer_id > 0) { $cache_key_args = func_get_args(); $cache_key = self::_customer_cache_key($customer_id, $cache_key_args); $cache_timeout = module_config::c('cache_objects', 60); if ($cached_item = module_cache::get('customer', $cache_key)) { return $cached_item; } $customer = get_single("customer", "customer_id", $customer_id); // get their address. if ($customer && isset($customer['customer_id']) && $customer['customer_id'] == $customer_id) { if (!$basic_for_link) { $customer['staff_ids'] = array(); foreach (get_multiple('customer_user_rel', array('customer_id' => $customer_id), 'user_id') as $val) { $customer['staff_ids'][] = $val['user_id']; } $customer['customer_address'] = module_address::get_address($customer_id, 'customer', 'physical', true); } switch (self::get_customer_data_access()) { case _CUSTOMER_ACCESS_ALL: break; case _CUSTOMER_ACCESS_ALL_COMPANY: case _CUSTOMER_ACCESS_CONTACTS: case _CUSTOMER_ACCESS_TASKS: case _CUSTOMER_ACCESS_STAFF: $valid_customer_ids = module_security::get_customer_restrictions(); $is_valid_customer = isset($valid_customer_ids[$customer['customer_id']]); if (!$is_valid_customer) { if ($skip_permissions) { $customer['_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 { $customer = false; } } break; } } } if (!$customer) { $customer = array('customer_id' => 'new', 'customer_name' => '', 'customer_status' => _CUSTOMER_STATUS_PAID, 'primary_user_id' => '', 'credit' => '0', 'customer_address' => array(), 'staff_ids' => array(), 'customer_type_id' => self::get_current_customer_type_id()); } if (class_exists('module_company', false) && module_company::is_enabled() && !$basic_for_link) { $customer['company_ids'] = array(); if (isset($customer['customer_id']) && (int) $customer['customer_id'] > 0) { foreach (module_company::get_companys_by_customer($customer['customer_id']) as $company) { $customer['company_ids'][$company['company_id']] = $company['name']; } } } //$customer['customer_industry_id'] = get_multiple('customer_industry_rel',array('customer_id'=>$customer_id),'customer_industry_id'); //echo $customer_id;print_r($customer);exit; if (isset($cache_key) && isset($cache_timeout)) { module_cache::put('customer', $cache_key, $customer, $cache_timeout); } return $customer; }
public static function get_user($user_id, $perms = true, $do_link = true, $basic_for_link = false) { //,$basic=false $cache_key_args = func_get_args(); $cache_key = self::_user_cache_key($user_id, $cache_key_args); $cache_timeout = module_config::c('cache_objects', 60); if ($cached_item = module_cache::get('user', $cache_key)) { return $cached_item; } $user = get_single("user", "user_id", $user_id); if ($do_link && $user && isset($user['linked_parent_user_id']) && $user['linked_parent_user_id'] && $user['linked_parent_user_id'] != $user['user_id']) { $user = self::get_user($user['linked_parent_user_id']); module_cache::put('user', $cache_key, $user, $cache_timeout); return $user; } if ($user) { if ($basic_for_link) { module_cache::put('user', $cache_key, $user, $cache_timeout); return $user; } // if this user is a linked contact to the current contact then we allow access. if (isset($user['linked_parent_user_id']) && $user['linked_parent_user_id'] == module_security::get_loggedin_id()) { // allow all access. } else { if (class_exists('module_customer', false)) { if ($user) { switch (module_user::get_user_data_access()) { case _USER_ACCESS_ME: if ($user['user_id'] != module_security::get_loggedin_id()) { if ($perms) { $user = false; } else { // eg for linking. $user['_perms'] = false; } } break; case _USER_ACCESS_CONTACTS: if (!$user['customer_id'] && !$user['vendor_id'] && $user['user_id'] != module_security::get_loggedin_id()) { // this user is not a customer contact, don't let them access it. if ($perms) { $user = false; } else { // eg for linking. $user['_perms'] = false; } } break; case _USER_ACCESS_ALL: default: // all user accounts. break; } } if ($user && $user['customer_id'] > 0) { switch (module_customer::get_customer_data_access()) { case _CUSTOMER_ACCESS_ALL: // all customers! so this means all jobs! break; case _CUSTOMER_ACCESS_ALL_COMPANY: case _CUSTOMER_ACCESS_CONTACTS: case _CUSTOMER_ACCESS_TASKS: case _CUSTOMER_ACCESS_STAFF: $valid_customer_ids = module_security::get_customer_restrictions(); $is_valid_user = isset($valid_customer_ids[$user['customer_id']]); if (!$is_valid_user) { if ($perms) { $user = false; } else { // eg for linking. $user['_perms'] = false; } } } } } if ($user && $user['vendor_id'] > 0) { switch (module_vendor::get_vendor_data_access()) { case _VENDOR_ACCESS_ALL: // all vendors! so this means all jobs! break; case _VENDOR_ACCESS_ALL_COMPANY: case _VENDOR_ACCESS_CONTACTS: $valid_vendor_check = module_vendor::get_vendor($user['vendor_id']); $is_valid_user = $valid_vendor_check && isset($valid_vendor_check['vendor_id']) && $valid_vendor_check['vendor_id'] == $user['vendor_id']; if (!$is_valid_user) { if ($perms) { $user = false; } else { // eg for linking. $user['_perms'] = false; } } } } } } if (!$user) { $user = array('user_id' => 'new', 'customer_id' => 0, 'vendor_id' => 0, 'name' => '', 'last_name' => '', 'email' => '', 'password' => '', 'phone' => '', 'mobile' => '', 'fax' => '', 'roles' => array(), 'language' => module_config::c('default_language', 'en'), 'company_ids' => array()); $use_master_key = self::get_contact_master_key(); if (isset($_REQUEST[$use_master_key])) { $user[$use_master_key] = $_REQUEST[$use_master_key]; } } else { $user['roles'] = get_multiple('user_role', array('user_id' => $user_id)); if (class_exists('module_company', false) && module_company::is_enabled()) { $user['company_ids'] = array(); foreach (module_company::get_companys_by_user($user['user_id']) as $company) { $user['company_ids'][$company['company_id']] = $company['name']; } } module_cache::put('user', $cache_key, $user, $cache_timeout); } return $user; }
public static function get_reply_rate() { // cached? $rate = module_cache::get('ticket', 'ticket_count_rate'); if ($rate === false) { $rate = array('daily' => 0, 'weekly' => 0); // how many messages were replied to by the admin in the last week? $admins_rel = module_ticket::get_ticket_staff_rel(); if (count($admins_rel)) { $sql = "SELECT COUNT(*) AS c FROM `" . _DB_PREFIX . "ticket_message` WHERE from_user_id IN (" . implode(', ', array_keys($admins_rel)) . ") AND message_time >= " . (int) strtotime('-7 days'); $res = qa1($sql); $rate['weekly'] = $res['c']; $rate['daily'] = ceil($res['c'] / 7); module_cache::put('ticket', 'ticket_count_rate', $rate); } } return $rate; }