X-Git-Url: http://wagnertech.de/git?a=blobdiff_plain;f=WEB-INF%2Flib%2FttReportHelper.class.php;h=966865fbb673934fe8b4aafc0e07efe730d85b7c;hb=9b5a1834db24f25803fc0d461394e4a2bfcc44b7;hp=d3c7b7ce3f77a70d52b5d5d41b93c940709b88f6;hpb=bacdf3405307cfc27832db00b09bbf52d4b9f72c;p=timetracker.git diff --git a/WEB-INF/lib/ttReportHelper.class.php b/WEB-INF/lib/ttReportHelper.class.php index d3c7b7ce..966865fb 100644 --- a/WEB-INF/lib/ttReportHelper.class.php +++ b/WEB-INF/lib/ttReportHelper.class.php @@ -30,9 +30,18 @@ import('ttClientHelper'); import('DateAndTime'); import('Period'); import('ttTimeHelper'); +import('ttConfigHelper'); require_once(dirname(__FILE__).'/../../plugins/CustomFields.class.php'); +// Definitions of types for timesheet dropdown. +define('TIMESHEET_ALL', 0); // Include all records. +define('TIMESHEET_NOT_ASSIGNED', 1); // Include records not assigned to timesheets. +define('TIMESHEET_ASSIGNED', 2); // Include records assigned to timesheets. +define('TIMESHEET_PENDING', 3); // Include records in submitted timesheets that are pending manager approval. +define('TIMESHEET_APPROVED', 4); // Include records in approved timesheets. +define('TIMESHEET_NOT_APPROVED', 5); // Include records in disapproved timesheets. + // Class ttReportHelper is used for help with reports. class ttReportHelper { @@ -43,6 +52,12 @@ class ttReportHelper { $group_id = $user->getGroup(); $org_id = $user->org_id; + // A shortcut for timesheets. + if ($options['timesheet_id']) { + $where = " where l.timesheet_id = ".$options['timesheet_id']." and l.group_id = $group_id and l.org_id = $org_id"; + return $where; + } + // Prepare dropdown parts. $dropdown_parts = ''; if ($options['client_id']) @@ -54,8 +69,12 @@ class ttReportHelper { if ($options['task_id']) $dropdown_parts .= ' and l.task_id = '.$options['task_id']; if ($options['billable']=='1') $dropdown_parts .= ' and l.billable = 1'; if ($options['billable']=='2') $dropdown_parts .= ' and l.billable = 0'; - if ($options['invoice']=='1') $dropdown_parts .= ' and l.invoice_id is not NULL'; - if ($options['invoice']=='2') $dropdown_parts .= ' and l.invoice_id is NULL'; + if ($options['invoice']=='1') $dropdown_parts .= ' and l.invoice_id is not null'; + if ($options['invoice']=='2') $dropdown_parts .= ' and l.invoice_id is null'; + if ($options['timesheet']==TIMESHEET_NOT_ASSIGNED) $dropdown_parts .= ' and l.timesheet_id is null'; + if ($options['timesheet']==TIMESHEET_ASSIGNED) $dropdown_parts .= ' and l.timesheet_id is not null'; + if ($options['approved']=='1') $dropdown_parts .= ' and l.approved = 1'; + if ($options['approved']=='2') $dropdown_parts .= ' and l.approved = 0'; if ($options['paid_status']=='1') $dropdown_parts .= ' and l.paid = 1'; if ($options['paid_status']=='2') $dropdown_parts .= ' and l.paid = 0'; @@ -96,8 +115,13 @@ class ttReportHelper { elseif ($user->isClient() && $user->client_id) $dropdown_parts .= ' and ei.client_id = '.$user->client_id; if ($options['project_id']) $dropdown_parts .= ' and ei.project_id = '.$options['project_id']; - if ($options['invoice']=='1') $dropdown_parts .= ' and ei.invoice_id is not NULL'; - if ($options['invoice']=='2') $dropdown_parts .= ' and ei.invoice_id is NULL'; + if ($options['invoice']=='1') $dropdown_parts .= ' and ei.invoice_id is not null'; + if ($options['invoice']=='2') $dropdown_parts .= ' and ei.invoice_id is null'; + if (isset($options['timesheet']) && ($options['timesheet']!=TIMESHEET_ALL && $options['timesheet']!=TIMESHEET_NOT_ASSIGNED)) { + $dropdown_parts .= ' and 0 = 1'; // Expense items do not have a timesheet_id. + } + if ($options['approved']=='1') $dropdown_parts .= ' and ei.approved = 1'; + if ($options['approved']=='2') $dropdown_parts .= ' and ei.approved = 0'; if ($options['paid_status']=='1') $dropdown_parts .= ' and ei.paid = 1'; if ($options['paid_status']=='2') $dropdown_parts .= ' and ei.paid = 0'; @@ -131,6 +155,9 @@ class ttReportHelper { global $user; $mdb2 = getConnection(); + $group_id = $user->getGroup(); + $org_id = $user->org_id; + // Determine these once as they are used in multiple places in this function. $canViewReports = $user->can('view_reports') || $user->can('view_all_reports'); $isClient = $user->isClient(); @@ -150,9 +177,10 @@ class ttReportHelper { // Prepare a query for time items in tt_log table. $fields = array(); // An array of fields for database query. - array_push($fields, 'l.id as id'); + array_push($fields, 'l.id'); + array_push($fields, 'l.user_id'); array_push($fields, '1 as type'); // Type 1 is for tt_log entries. - array_push($fields, 'l.date as date'); + array_push($fields, 'l.date'); if($canViewReports || $isClient) array_push($fields, 'u.name as user'); // Add client name if it is selected. @@ -190,8 +218,11 @@ class ttReportHelper { if ($options['show_work_units']) { if ($user->getConfigOption('unit_totals_only')) array_push($fields, "null as units"); - else - array_push($fields, "if(l.billable = 0 or time_to_sec(l.duration)/60 < $user->first_unit_threshold, 0, ceil(time_to_sec(l.duration)/60/$user->minutes_in_unit)) as units"); + else { + $firstUnitThreshold = $user->getConfigInt('1st_unit_threshold', 0); + $minutesInUnit = $user->getConfigInt('minutes_in_unit', 15); + array_push($fields, "if(l.billable = 0 or time_to_sec(l.duration)/60 < $firstUnitThreshold, 0, ceil(time_to_sec(l.duration)/60/$minutesInUnit)) as units"); + } } // Add note. if ($options['show_note']) @@ -205,19 +236,28 @@ class ttReportHelper { array_push($fields, "cast(l.billable * coalesce(upb.rate, 0) * time_to_sec(l.duration)/3600 as decimal(10,2)) as cost"); // Use project rate for user. array_push($fields, "null as expense"); } + // Add approved. + if ($options['show_approved']) + array_push($fields, 'l.approved'); // Add paid status. if ($canViewReports && $options['show_paid']) - array_push($fields, 'l.paid as paid'); + array_push($fields, 'l.paid'); // Add IP address. if ($canViewReports && $options['show_ip']) { - array_push($fields, 'l.created as created'); - array_push($fields, 'l.created_ip as created_ip'); - array_push($fields, 'l.modified as modified'); - array_push($fields, 'l.modified_ip as modified_ip'); + array_push($fields, 'l.created'); + array_push($fields, 'l.created_ip'); + array_push($fields, 'l.modified'); + array_push($fields, 'l.modified_ip'); } // Add invoice name if it is selected. if (($canViewReports || $isClient) && $options['show_invoice']) array_push($fields, 'i.name as invoice'); + // Add timesheet name if it is selected. + if ($options['show_timesheet']) + array_push($fields, 'ts.name as timesheet_name'); + // Add has_files. + if ($options['show_files']) + array_push($fields, 'if(Sub1.entity_id is null, 0, 1) as has_files'); // Prepare sql query part for left joins. $left_joins = null; @@ -241,11 +281,30 @@ class ttReportHelper { } if ($includeCost && MODE_TIME != $trackingMode) $left_joins .= " left join tt_user_project_binds upb on (l.user_id = upb.user_id and l.project_id = upb.project_id)"; + if ($options['show_files']) { + $left_joins .= " left join (select distinct entity_id from tt_files". + " where entity_type = 'time' and group_id = $group_id and org_id = $org_id and status = 1) Sub1". + " on (l.id = Sub1.entity_id)"; + } + + // Prepare sql query part for inner joins. + $inner_joins = null; + if ($user->isPluginEnabled('ts')) { + $timesheet_option = $options['timesheet']; + if ($timesheet_option == TIMESHEET_PENDING) + $inner_joins .= " inner join tt_timesheets ts on (l.timesheet_id = ts.id and ts.submit_status = 1 and ts.approve_status is null)"; + else if ($timesheet_option == TIMESHEET_APPROVED) + $inner_joins .= " inner join tt_timesheets ts on (l.timesheet_id = ts.id and ts.approve_status = 1)"; + else if ($timesheet_option == TIMESHEET_NOT_APPROVED) + $inner_joins .= " inner join tt_timesheets ts on (l.timesheet_id = ts.id and ts.approve_status = 0)"; + else if ($options['show_timesheet']) + $inner_joins .= " left join tt_timesheets ts on (l.timesheet_id = ts.id)"; // Left join for timesheet nme. + } $where = ttReportHelper::getWhere($options); // Construct sql query for tt_log items. - $sql = "select ".join(', ', $fields)." from tt_log l $left_joins $where"; + $sql = "select ".join(', ', $fields)." from tt_log l $left_joins $inner_joins $where"; // If we don't have expense items (such as when the Expenses plugin is disabled), the above is all sql we need, // with an exception of sorting part, that is added in the end. @@ -254,6 +313,7 @@ class ttReportHelper { $fields = array(); // An array of fields for database query. array_push($fields, 'ei.id'); + array_push($fields, 'ei.user_id'); array_push($fields, '2 as type'); // Type 2 is for tt_expense_items entries. array_push($fields, 'ei.date'); if($canViewReports || $isClient) @@ -283,19 +343,27 @@ class ttReportHelper { array_push($fields, 'ei.name as note'); array_push($fields, 'ei.cost as cost'); array_push($fields, 'ei.cost as expense'); + // Add approved. + if ($options['show_approved']) + array_push($fields, 'ei.approved'); // Add paid status. if ($canViewReports && $options['show_paid']) - array_push($fields, 'ei.paid as paid'); + array_push($fields, 'ei.paid'); // Add IP address. if ($canViewReports && $options['show_ip']) { - array_push($fields, 'ei.created as created'); - array_push($fields, 'ei.created_ip as created_ip'); - array_push($fields, 'ei.modified as modified'); - array_push($fields, 'ei.modified_ip as modified_ip'); + array_push($fields, 'ei.created'); + array_push($fields, 'ei.created_ip'); + array_push($fields, 'ei.modified'); + array_push($fields, 'ei.modified_ip'); } // Add invoice name if it is selected. if (($canViewReports || $isClient) && $options['show_invoice']) array_push($fields, 'i.name as invoice'); + if ($options['show_timesheet']) + array_push($fields, 'null as timesheet_name'); + // Add has_files. + if ($options['show_files']) + array_push($fields, 'if(Sub1.entity_id is null, 0, 1) as has_files'); // Prepare sql query part for left joins. $left_joins = null; @@ -307,6 +375,11 @@ class ttReportHelper { $left_joins .= " left join tt_projects p on (p.id = ei.project_id)"; if (($canViewReports || $isClient) && $options['show_invoice']) $left_joins .= " left join tt_invoices i on (i.id = ei.invoice_id and i.status = 1)"; + if ($options['show_files']) { + $left_joins .= " left join (select distinct entity_id from tt_files". + " where entity_type = 'expense' and group_id = $group_id and org_id = $org_id and status = 1) Sub1". + " on (ei.id = Sub1.entity_id)"; + } $where = ttReportHelper::getExpenseWhere($options); @@ -404,53 +477,17 @@ class ttReportHelper { // With expenses, it becomes a select with group by from a combined set of records obtained with "union all". static function getSubtotals($options) { global $user; - $mdb2 = getConnection(); $concat_part = ttReportHelper::makeConcatPart($options); + $work_unit_part = ttReportHelper::makeWorkUnitPart($options); $join_part = ttReportHelper::makeJoinPart($options); + $cost_part = ttReportHelper::makeCostPart($options); $where = ttReportHelper::getWhere($options); $group_by_part = ttReportHelper::makeGroupByPart($options); - if ($options['show_cost']) { - if (MODE_TIME == $user->tracking_mode) { - if (!ttReportHelper::groupingBy('user', $options)) - $left_join = 'left join tt_users u on (l.user_id = u.id)'; - $sql = "select $concat_part, sum(time_to_sec(l.duration)) as time"; - if ($options['show_work_units']) { - if ($user->unit_totals_only) - $sql .= ", if (sum(l.billable * time_to_sec(l.duration)/60) < $user->first_unit_threshold, 0, ceil(sum(l.billable * time_to_sec(l.duration)/60/$user->minutes_in_unit))) as units"; - else - $sql .= ", sum(if(l.billable = 0 or time_to_sec(l.duration)/60 < $user->first_unit_threshold, 0, ceil(time_to_sec(l.duration)/60/$user->minutes_in_unit))) as units"; - } - $sql .= ", sum(cast(l.billable * coalesce(u.rate, 0) * time_to_sec(l.duration)/3600 as decimal(10, 2))) as cost, - null as expenses from tt_log l - $join_part $left_join $where $group_by_part"; - } else { - // If we are including cost and tracking projects, our query (the same as above) needs to join the tt_user_project_binds table. - $sql = "select $concat_part, sum(time_to_sec(l.duration)) as time"; - if ($options['show_work_units']) { - if ($user->unit_totals_only) - $sql .= ", if (sum(l.billable * time_to_sec(l.duration)/60) < $user->first_unit_threshold, 0, ceil(sum(l.billable * time_to_sec(l.duration)/60/$user->minutes_in_unit))) as units"; - else - $sql .= ", sum(if(l.billable = 0 or time_to_sec(l.duration)/60 < $user->first_unit_threshold, 0, ceil(time_to_sec(l.duration)/60/$user->minutes_in_unit))) as units"; - } - $sql .= ", sum(cast(l.billable * coalesce(upb.rate, 0) * time_to_sec(l.duration)/3600 as decimal(10,2))) as cost, - null as expenses from tt_log l - $join_part - left join tt_user_project_binds upb on (l.user_id = upb.user_id and l.project_id = upb.project_id) $where $group_by_part"; - } - } else { - // $sql = "select $group_field as group_field, sum(time_to_sec(l.duration)) as time"; - $sql = "select $concat_part, sum(time_to_sec(l.duration)) as time"; - if ($options['show_work_units']) { - if ($user->unit_totals_only) - $sql .= ", if (sum(l.billable * time_to_sec(l.duration)/60) < $user->first_unit_threshold, 0, ceil(sum(l.billable * time_to_sec(l.duration)/60/$user->minutes_in_unit))) as units"; - else - $sql .= ", sum(if(l.billable = 0 or time_to_sec(l.duration)/60 < $user->first_unit_threshold, 0, ceil(time_to_sec(l.duration)/60/$user->minutes_in_unit))) as units"; - } - $sql .= ", null as expenses from tt_log l - $join_part $where $group_by_part"; - } + + $parts = "$concat_part, sum(time_to_sec(l.duration)) as time, null as expenses".$work_unit_part.$cost_part; + $sql = "select $parts from tt_log l $join_part $where $group_by_part"; // By now we have sql for time items. // However, when we have expenses, we need to do a union with a separate query for expense items from tt_expense_items table. @@ -476,12 +513,13 @@ class ttReportHelper { $res = $mdb2->query($sql); if (is_a($res, 'PEAR_Error')) die($res->getMessage()); while ($val = $res->fetchRow()) { - $time = $val['time'] ? sec_to_time_fmt_hm($val['time']) : null; + $time = ttTimeHelper::minutesToDuration($val['time'] / 60); $rowLabel = ttReportHelper::makeGroupByLabel($val['group_field'], $options); if ($options['show_cost']) { - if ('.' != $user->decimal_mark) { - $val['cost'] = str_replace('.', $user->decimal_mark, $val['cost']); - $val['expenses'] = str_replace('.', $user->decimal_mark, $val['expenses']); + $decimalMark = $user->getDecimalMark(); + if ('.' != $decimalMark) { + $val['cost'] = str_replace('.', $decimalMark, $val['cost']); + $val['expenses'] = str_replace('.', $decimalMark, $val['expenses']); } $subtotals[$val['group_field']] = array('name'=>$rowLabel,'user'=>$val['user'],'project'=>$val['project'],'task'=>$val['task'],'client'=>$val['client'],'cf_1'=>$val['cf_1'],'time'=>$time,'units'=> $val['units'],'cost'=>$val['cost'],'expenses'=>$val['expenses']); } else @@ -495,18 +533,22 @@ class ttReportHelper { static function getTotals($options) { global $user; - $mdb2 = getConnection(); + $trackingMode = $user->getTrackingMode(); + $decimalMark = $user->getDecimalMark(); $where = ttReportHelper::getWhere($options); // Prepare parts. $time_part = "sum(time_to_sec(l.duration)) as time"; if ($options['show_work_units']) { - $units_part = $user->unit_totals_only ? ", null as units" : ", sum(if(l.billable = 0 or time_to_sec(l.duration)/60 < $user->first_unit_threshold, 0, ceil(time_to_sec(l.duration)/60/$user->minutes_in_unit))) as units"; + $unitTotalsOnly = $user->getConfigOption('unit_totals_only'); + $firstUnitThreshold = $user->getConfigInt('1st_unit_threshold', 0); + $minutesInUnit = $user->getConfigInt('minutes_in_unit', 15); + $units_part = $unitTotalsOnly ? ", null as units" : ", sum(if(l.billable = 0 or time_to_sec(l.duration)/60 < $firstUnitThreshold, 0, ceil(time_to_sec(l.duration)/60/$minutesInUnit))) as units"; } if ($options['show_cost']) { - if (MODE_TIME == $user->tracking_mode) + if (MODE_TIME == $trackingMode) $cost_part = ", sum(cast(l.billable * coalesce(u.rate, 0) * time_to_sec(l.duration)/3600 as decimal(10,2))) as cost, null as expenses"; else $cost_part = ", sum(cast(l.billable * coalesce(upb.rate, 0) * time_to_sec(l.duration)/3600 as decimal(10,2))) as cost, null as expenses"; @@ -514,14 +556,25 @@ class ttReportHelper { $cost_part = ", null as cost, null as expenses"; } if ($options['show_cost']) { - if (MODE_TIME == $user->tracking_mode) { + if (MODE_TIME == $trackingMode) { $left_joins = "left join tt_users u on (l.user_id = u.id)"; } else { $left_joins = "left join tt_user_project_binds upb on (l.user_id = upb.user_id and l.project_id = upb.project_id)"; } } + // Prepare sql query part for inner joins. + $inner_joins = null; + if ($user->isPluginEnabled('ts') && $options['timesheet']) { + $timesheet_option = $options['timesheet']; + if ($timesheet_option == TIMESHEET_PENDING) + $inner_joins .= " inner join tt_timesheets ts on (l.timesheet_id = ts.id and ts.submit_status = 1 and ts.approve_status is null)"; + else if ($timesheet_option == TIMESHEET_APPROVED) + $inner_joins .= " inner join tt_timesheets ts on (l.timesheet_id = ts.id and ts.approve_status = 1)"; + else if ($timesheet_option == TIMESHEET_NOT_APPROVED) + $inner_joins .= " inner join tt_timesheets ts on (l.timesheet_id = ts.id and ts.approve_status = 0)"; + } // Prepare a query for time items. - $sql = "select $time_part $units_part $cost_part from tt_log l $left_joins $where"; + $sql = "select $time_part $units_part $cost_part from tt_log l $left_joins $inner_joins $where"; // If we have expenses, query becomes a bit more complex. if ($options['show_cost'] && $user->isPluginEnabled('ex')) { @@ -542,25 +595,26 @@ class ttReportHelper { if (is_a($res, 'PEAR_Error')) die($res->getMessage()); $val = $res->fetchRow(); - $total_time = $val['time'] ? sec_to_time_fmt_hm($val['time']) : null; + $total_time = ttTimeHelper::minutesToDuration($val['time'] / 60); if ($options['show_cost']) { $total_cost = $val['cost']; if (!$total_cost) $total_cost = '0.00'; - if ('.' != $user->decimal_mark) - $total_cost = str_replace('.', $user->decimal_mark, $total_cost); + if ('.' != $decimalMark) + $total_cost = str_replace('.', $decimalMark, $total_cost); $total_expenses = $val['expenses']; if (!$total_expenses) $total_expenses = '0.00'; - if ('.' != $user->decimal_mark) - $total_expenses = str_replace('.', $user->decimal_mark, $total_expenses); + if ('.' != $decimalMark) + $total_expenses = str_replace('.', $decimalMark, $total_expenses); } + $dateFormat = $user->getDateFormat(); if ($options['period']) - $period = new Period($options['period'], new DateAndTime($user->date_format)); + $period = new Period($options['period'], new DateAndTime($dateFormat)); else { $period = new Period(); $period->setPeriod( - new DateAndTime($user->date_format, $options['period_start']), - new DateAndTime($user->date_format, $options['period_end'])); + new DateAndTime($dateFormat, $options['period_start']), + new DateAndTime($dateFormat, $options['period_end'])); } $totals['start_date'] = $period->getStartDate(); @@ -595,6 +649,60 @@ class ttReportHelper { } } + // The assignToTimesheet assigns a set of tt_log records to a specific timesheet. + static function assignToTimesheet($timesheet_id, $time_log_ids) { + global $user; + $mdb2 = getConnection(); + + $user_id = $user->getUser(); + $group_id = $user->getGroup(); + $org_id = $user->org_id; + + if ($time_log_ids) { + // Use inner join as a protection mechanism not to do anything with "acted upon" timesheets. + // Allow oprations only with pending timesheets. + if ($timesheet_id) { + // Assigning a timesheet to records. + $inner_join = " inner join tt_timesheets ts on (ts.id = $timesheet_id". + " and ts.user_id = $user_id and ts.approve_status is null". // Timesheet to assign to is pending. + // Part below: existing timesheet either not exists or is also pending. + " and (l.timesheet_id is null or (l.timesheet_id = ts.id and ts.approve_status is null)))"; + } else { + $inner_join = " inner join tt_timesheets ts on (ts.id = l.timesheet_id". + " and ts.user_id = $user_id and ts.approve_status is null)"; // Do not deassign from acted-upon timesheets. + } + + $sql = "update tt_log l $inner_join". + " set l.timesheet_id = ".$mdb2->quote($timesheet_id). + " where l.id in(".join(', ', $time_log_ids).") and l.user_id = $user_id and l.group_id = $group_id and l.org_id = $org_id"; + $affected = $mdb2->exec($sql); + if (is_a($affected, 'PEAR_Error')) die($affected->getMessage()); + } + } + + // The markApproved marks a set of records as either approved or unapproved. + static function markApproved($time_log_ids, $expense_item_ids, $approved = true) { + global $user; + $mdb2 = getConnection(); + + $group_id = $user->getGroup(); + $org_id = $user->org_id; + + $approved_val = (int) $approved; + if ($time_log_ids) { + $sql = "update tt_log set approved = $approved_val". + " where id in(".join(', ', $time_log_ids).") and group_id = $group_id and org_id = $org_id"; + $affected = $mdb2->exec($sql); + if (is_a($affected, 'PEAR_Error')) die($affected->getMessage()); + } + if ($expense_item_ids) { + $sql = "update tt_expense_items set approved = $approved_val". + " where id in(".join(', ', $expense_item_ids).") and group_id = $group_id and org_id = $org_id"; + $affected = $mdb2->exec($sql); + if (is_a($affected, 'PEAR_Error')) die($affected->getMessage()); + } + } + // The markPaid marks a set of records as either paid or unpaid. static function markPaid($time_log_ids, $expense_item_ids, $paid = true) { global $user; @@ -628,6 +736,10 @@ class ttReportHelper { $canViewReports = $user->can('view_reports') || $user->can('view_all_reports'); $isClient = $user->isClient(); + $config = new ttConfigHelper($user->getConfig()); + $show_note_column = $options['show_note'] && !$config->getDefinedValue('report_note_on_separate_row'); + $show_note_row = $options['show_note'] && $config->getDefinedValue('report_note_on_separate_row'); + $items = ttReportHelper::getItems($options); $grouping = ttReportHelper::grouping($options); if ($grouping) @@ -650,6 +762,24 @@ class ttReportHelper { $cellLeftAlignedSubtotal = 'font-weight: bold; text-align: left; vertical-align: top;'; $cellRightAlignedSubtotal = 'font-weight: bold; text-align: right; vertical-align: top;'; + // Determine column span for note field. + $colspan = 1; + if ($user->can('view_reports') || $user->can('view_all_reports') || $user->isClient()) $colspan++; + if ($options['show_client']) $colspan++; + if ($options['show_project']) $colspan++; + if ($options['show_task']) $colspan++; + if ($options['show_custom_field_1']) $colspan++; + if ($options['show_start']) $colspan++; + if ($options['show_end']) $colspan++; + if ($options['show_duration']) $colspan++; + if ($options['show_work_units']) $colspan++; + if ($options['show_cost']) $colspan++; + if ($options['show_approved']) $colspan++; + if ($options['show_paid']) $colspan++; + if ($options['show_ip']) $colspan++; + if ($options['show_invoice']) $colspan++; + if ($options['show_timesheet']) $colspan++; + // Start creating email body. $body = ''; $body .= ''; @@ -743,16 +873,20 @@ class ttReportHelper { $body .= ''.$i18n->get('label.duration').''; if ($options['show_work_units']) $body .= ''.$i18n->get('label.work_units_short').''; - if ($options['show_note']) + if ($show_note_column) $body .= ''.$i18n->get('label.note').''; if ($options['show_cost']) $body .= ''.$i18n->get('label.cost').''; + if ($options['show_approved']) + $body .= ''.$i18n->get('label.approved').''; if ($options['show_paid']) $body .= ''.$i18n->get('label.paid').''; if ($options['show_ip']) $body .= ''.$i18n->get('label.ip').''; if ($options['show_invoice']) $body .= ''.$i18n->get('label.invoice').''; + if ($options['show_timesheet']) + $body .= ''.$i18n->get('label.timesheet').''; $body .= ''; // Initialize variables to print subtotals. @@ -787,15 +921,17 @@ class ttReportHelper { if ($options['show_end']) $body .= ''; if ($options['show_duration']) $body .= ''.$subtotals[$prev_grouped_by]['time'].''; if ($options['show_work_units']) $body .= ''.$subtotals[$prev_grouped_by]['units'].''; - if ($options['show_note']) $body .= ''; + if ($show_note_column) $body .= ''; if ($options['show_cost']) { $body .= ''; $body .= ($canViewReports || $isClient) ? $subtotals[$prev_grouped_by]['cost'] : $subtotals[$prev_grouped_by]['expenses']; $body .= ''; } + if ($options['show_approved']) $body .= ''; if ($options['show_paid']) $body .= ''; if ($options['show_ip']) $body .= ''; if ($options['show_invoice']) $body .= ''; + if ($options['show_timesheet']) $body .= ''; $body .= ''; $body .= ' '; } @@ -825,10 +961,15 @@ class ttReportHelper { $body .= ''.$record['duration'].''; if ($options['show_work_units']) $body .= ''.$record['units'].''; - if ($options['show_note']) + if ($show_note_column) $body .= ''.htmlspecialchars($record['note']).''; if ($options['show_cost']) $body .= ''.$record['cost'].''; + if ($options['show_approved']) { + $body .= ''; + $body .= $record['approved'] == 1 ? $i18n->get('label.yes') : $i18n->get('label.no'); + $body .= ''; + } if ($options['show_paid']) { $body .= ''; $body .= $record['paid'] == 1 ? $i18n->get('label.yes') : $i18n->get('label.no'); @@ -841,8 +982,15 @@ class ttReportHelper { } if ($options['show_invoice']) $body .= ''.htmlspecialchars($record['invoice']).''; + if ($options['show_timesheet']) + $body .= ''.htmlspecialchars($record['timesheet']).''; $body .= ''; - + if ($show_note_row && $record['note']) { + $body .= ''; + $body .= ''.$i18n->get('label.note').':'; + $body .= ''.$record['note'].''; + $body .= ''; + } $prev_date = $record['date']; if ($print_subtotals) $prev_grouped_by = $record['grouped_by']; @@ -863,15 +1011,17 @@ class ttReportHelper { if ($options['show_end']) $body .= ''; if ($options['show_duration']) $body .= ''.$subtotals[$cur_grouped_by]['time'].''; if ($options['show_work_units']) $body .= ''.$subtotals[$cur_grouped_by]['units'].''; - if ($options['show_note']) $body .= ''; + if ($show_note_column) $body .= ''; if ($options['show_cost']) { $body .= ''; $body .= ($canViewReports || $isClient) ? $subtotals[$cur_grouped_by]['cost'] : $subtotals[$cur_grouped_by]['expenses']; $body .= ''; } + if ($options['show_approved']) $body .= ''; if ($options['show_paid']) $body .= ''; if ($options['show_ip']) $body .= ''; if ($options['show_invoice']) $body .= ''; + if ($options['show_timesheet']) $body .= ''; $body .= ''; } @@ -888,15 +1038,17 @@ class ttReportHelper { if ($options['show_end']) $body .= ''; if ($options['show_duration']) $body .= ''.$totals['time'].''; if ($options['show_work_units']) $body .= ''.$totals['units'].''; - if ($options['show_note']) $body .= ''; + if ($show_note_column) $body .= ''; if ($options['show_cost']) { $body .= ''.htmlspecialchars($user->currency).' '; $body .= ($canViewReports || $isClient) ? $totals['cost'] : $totals['expenses']; $body .= ''; } + if ($options['show_approved']) $body .= ''; if ($options['show_paid']) $body .= ''; if ($options['show_ip']) $body .= ''; if ($options['show_invoice']) $body .= ''; + if ($options['show_timesheet']) $body .= ''; $body .= ''; $body .= ''; @@ -999,12 +1151,28 @@ class ttReportHelper { $options['billable'] = $bean->getAttribute('include_records'); $options['invoice'] = $bean->getAttribute('invoice'); $options['paid_status'] = $bean->getAttribute('paid_status'); - if (is_array($bean->getAttribute('users'))) $options['users'] = join(',', $bean->getAttribute('users')); + $options['approved'] = $bean->getAttribute('approved'); + if ($user->isPluginEnabled('ap') && $user->isClient() && !$user->can('view_client_unapproved')) + $options['approved'] = 1; // Restrict clients to approved records only. + $options['timesheet'] = $bean->getAttribute('timesheet'); + + $active_users_in_bean = $bean->getAttribute('users_active'); + if ($active_users_in_bean && is_array($active_users_in_bean)) { + $users = join(',', $active_users_in_bean); + } + $inactive_users_in_bean = $bean->getAttribute('users_inactive'); + if ($inactive_users_in_bean && is_array($inactive_users_in_bean)) { + if ($users) $users .= ','; + $users .= join(',', $inactive_users_in_bean); + } + if ($users) $options['users'] = $users; + $options['period'] = $bean->getAttribute('period'); $options['period_start'] = $bean->getAttribute('start_date'); $options['period_end'] = $bean->getAttribute('end_date'); $options['show_client'] = $bean->getAttribute('chclient'); $options['show_invoice'] = $bean->getAttribute('chinvoice'); + $options['show_approved'] = $bean->getAttribute('chapproved'); $options['show_paid'] = $bean->getAttribute('chpaid'); $options['show_ip'] = $bean->getAttribute('chip'); $options['show_project'] = $bean->getAttribute('chproject'); @@ -1016,6 +1184,8 @@ class ttReportHelper { $options['show_note'] = $bean->getAttribute('chnote'); $options['show_custom_field_1'] = $bean->getAttribute('chcf_1'); $options['show_work_units'] = $bean->getAttribute('chunits'); + $options['show_timesheet'] = $bean->getAttribute('chtimesheet'); + $options['show_files'] = $bean->getAttribute('chfiles'); $options['show_totals_only'] = $bean->getAttribute('chtotalsonly'); $options['group_by1'] = $bean->getAttribute('group_by1'); $options['group_by2'] = $bean->getAttribute('group_by2'); @@ -1028,13 +1198,19 @@ class ttReportHelper { global $user; // Check users. - $users_in_bean = $bean->getAttribute('users'); - if (is_array($users_in_bean)) { + $active_users_in_bean = $bean->getAttribute('users_active'); + $inactive_users_in_bean = $bean->getAttribute('users_inactive'); + if (is_array($active_users_in_bean) || is_array($inactive_users_in_bean)) { $users_in_group = ttGroupHelper::getUsers(); foreach ($users_in_group as $user_in_group) { $valid_ids[] = $user_in_group['id']; } - foreach ($users_in_bean as $user_in_bean) { + foreach ($active_users_in_bean as $user_in_bean) { + if (!in_array($user_in_bean, $valid_ids)) { + return false; + } + } + foreach ($inactive_users_in_bean as $user_in_bean) { if (!in_array($user_in_bean, $valid_ids)) { return false; } @@ -1467,7 +1643,8 @@ class ttReportHelper { static function makeJoinPart($options) { global $user; - if (ttReportHelper::groupingBy('user', $options)) { + $trackingMode = $user->getTrackingMode(); + if (ttReportHelper::groupingBy('user', $options) || MODE_TIME == $trackingMode) { $join .= ' left join tt_users u on (l.user_id = u.id)'; } if (ttReportHelper::groupingBy('client', $options)) { @@ -1486,11 +1663,58 @@ class ttReportHelper { elseif ($custom_fields->fields[0]['type'] == CustomFields::TYPE_DROPDOWN) $join .= ' left join tt_custom_field_log cfl on (l.id = cfl.log_id and cfl.status = 1) left join tt_custom_field_options cfo on (cfl.option_id = cfo.id)'; } + if ($options['show_cost'] && $trackingMode != MODE_TIME) { + $join .= ' left join tt_user_project_binds upb on (l.user_id = upb.user_id and l.project_id = upb.project_id)'; + } + // Prepare inner joins. + $inner_joins = null; + if ($user->isPluginEnabled('ts') && $options['timesheet']) { + $timesheet_option = $options['timesheet']; + if ($timesheet_option == TIMESHEET_PENDING) + $inner_joins .= " inner join tt_timesheets ts on (l.timesheet_id = ts.id and ts.submit_status = 1 and ts.approve_status is null)"; + else if ($timesheet_option == TIMESHEET_APPROVED) + $inner_joins .= " inner join tt_timesheets ts on (l.timesheet_id = ts.id and ts.approve_status = 1)"; + else if ($timesheet_option == TIMESHEET_NOT_APPROVED) + $inner_joins .= " inner join tt_timesheets ts on (l.timesheet_id = ts.id and ts.approve_status = 0)"; + } + $join .= $inner_joins; return $join; } + // makeWorkUnitPart builds an sql part for work units for time items. + static function makeWorkUnitPart($options) { + global $user; + + $workUnits = $options['show_work_units']; + if ($workUnits) { + $unitTotalsOnly = $user->getConfigOption('unit_totals_only'); + $firstUnitThreshold = $user->getConfigInt('1st_unit_threshold', 0); + $minutesInUnit = $user->getConfigInt('minutes_in_unit', 15); + if ($unitTotalsOnly) + $work_unit_part = ", if (sum(l.billable * time_to_sec(l.duration)/60) < $firstUnitThreshold, 0, ceil(sum(l.billable * time_to_sec(l.duration)/60/$minutesInUnit))) as units"; + else + $work_unit_part = ", sum(if(l.billable = 0 or time_to_sec(l.duration)/60 < $firstUnitThreshold, 0, ceil(time_to_sec(l.duration)/60/$minutesInUnit))) as units"; + } + return $work_unit_part; + } + + // makeCostPart builds a cost part for time items. + static function makeCostPart($options) { + global $user; + + if ($options['show_cost']) { + if (MODE_TIME == $user->getTrackingMode()) + $cost_part = ", sum(cast(l.billable * coalesce(u.rate, 0) * time_to_sec(l.duration)/3600 as decimal(10, 2))) as cost"; + else + $cost_part .= ", sum(cast(l.billable * coalesce(upb.rate, 0) * time_to_sec(l.duration)/3600 as decimal(10,2))) as cost"; + } + return $cost_part; + } + // makeJoinExpensesPart builds a left join part for getSubtotals query for expense items. static function makeJoinExpensesPart($options) { + global $user; + if (ttReportHelper::groupingBy('user', $options)) { $join .= ' left join tt_users u on (ei.user_id = u.id)'; } @@ -1591,7 +1815,7 @@ class ttReportHelper { return $key; // No need to format. global $user; - if ($user->date_format == DB_DATEFORMAT) + if ($user->getDateFormat() == DB_DATEFORMAT) return $key; // No need to format. $label = $key;