// getDefinedValue determines if a value identified by name is defined.
function getDefinedValue($name) {
- return in_array($name, $this->$config_array);
+ return in_array($name, $this->config_array);
}
// The getIntValue parses an integer value from the source config string.
if (($canViewReports || $isClient) && $bean->getAttribute('chinvoice'))
array_push($fields, 'i.name as invoice');
+ // Add work units.
+ if ($bean->getAttribute('chunits'))
+ array_push($fields, "if(time_to_sec(duration)/60 < $user->first_unit_threshold, 0, ceil(time_to_sec(duration)/60/$user->minutes_in_unit)) as units");
+
// Prepare sql query part for left joins.
$left_joins = null;
if ($bean->getAttribute('chclient') || 'client' == $group_by_option)
if (($canViewReports || $isClient) && $bean->getAttribute('chinvoice'))
array_push($fields, 'i.name as invoice');
+ // Add work units.
+ if ($bean->getAttribute('chunits'))
+ array_push($fields, 'null'); // null for work units.
+
// Prepare sql query part for left joins.
$left_joins = null;
if ($canViewReports || $isClient)
if (MODE_TIME == $user->tracking_mode) {
if ($group_by_option != 'user')
$left_join = 'left join tt_users u on (l.user_id = u.id)';
- $sql = "select $group_field as group_field, sum(time_to_sec(l.duration)) as time,
+ $sql = "select $group_field as group_field, sum(time_to_sec(l.duration)) as time,
+ sum(if(time_to_sec(duration)/60 < $user->first_unit_threshold, 0, ceil(time_to_sec(duration)/60/$user->minutes_in_unit))) as units,
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
$group_join $left_join $where group by $group_field";
} 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 $group_field as group_field, sum(time_to_sec(l.duration)) as time,
+ $sql = "select $group_field as group_field, sum(time_to_sec(l.duration)) as time,
+ sum(if(time_to_sec(duration)/60 < $user->first_unit_threshold, 0, ceil(time_to_sec(duration)/60/$user->minutes_in_unit))) as units,
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
$group_join
left join tt_user_project_binds upb on (l.user_id = upb.user_id and l.project_id = upb.project_id) $where group by $group_field";
}
} else {
- $sql = "select $group_field as group_field, sum(time_to_sec(l.duration)) as time, null as expenses from tt_log l
- $group_join $where group by $group_field";
+ $sql = "select $group_field as group_field, sum(time_to_sec(l.duration)) as time,
+ sum(if(time_to_sec(duration)/60 < $user->first_unit_threshold, 0, ceil(time_to_sec(duration)/60/$user->minutes_in_unit))) as units,
+ null as expenses from tt_log l
+ $group_join $where group by $group_field";
}
// By now we have sql for time items.
}
$where = ttReportHelper::getExpenseWhere($bean);
- $sql_for_expenses = "select $group_field as group_field, null as time, sum(ei.cost) as cost, sum(ei.cost) as expenses from tt_expense_items ei
+ $sql_for_expenses = "select $group_field as group_field, null as time, null as units, sum(ei.cost) as cost, sum(ei.cost) as expenses from tt_expense_items ei
$group_join $where";
// Add a "group by" clause if we are grouping.
if ('null' != $group_field) $sql_for_expenses .= " group by $group_field";
// Create a combined query.
- $sql = "select group_field, sum(time) as time, sum(cost) as cost, sum(expenses) as expenses from (($sql) union all ($sql_for_expenses)) t group by group_field";
+ $sql = "select group_field, sum(time) as time, sum(units) as units, sum(cost) as cost, sum(expenses) as expenses from (($sql) union all ($sql_for_expenses)) t group by group_field";
}
// Execute query.
$val['cost'] = str_replace('.', $user->decimal_mark, $val['cost']);
$val['expenses'] = str_replace('.', $user->decimal_mark, $val['expenses']);
}
- $subtotals[$val['group_field']] = array('name'=>$val['group_field'],'time'=>$time,'cost'=>$val['cost'],'expenses'=>$val['expenses']);
+ $subtotals[$val['group_field']] = array('name'=>$val['group_field'],'time'=>$time, 'units'=> $val['units'],'cost'=>$val['cost'],'expenses'=>$val['expenses']);
} else
- $subtotals[$val['group_field']] = array('name'=>$val['group_field'],'time'=>$time);
+ $subtotals[$val['group_field']] = array('name'=>$val['group_field'],'time'=>$time, 'units'=> $val['units']);
}
return $subtotals;
$where = ttReportHelper::getWhere($bean);
+ // TODO: build query in parts so the work units inclusion is conditional.
+
// Start with a query for time items.
if ($bean->getAttribute('chcost')) {
if (MODE_TIME == $user->tracking_mode) {
$sql = "select sum(time_to_sec(l.duration)) as time,
+ sum(if(time_to_sec(duration)/60 < $user->first_unit_threshold, 0, ceil(time_to_sec(duration)/60/$user->minutes_in_unit))) as units,
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
left join tt_users u on (l.user_id = u.id) $where";
} else {
$sql = "select sum(time_to_sec(l.duration)) as time,
+ sum(if(time_to_sec(duration)/60 < $user->first_unit_threshold, 0, ceil(time_to_sec(duration)/60/$user->minutes_in_unit))) as units,
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
left join tt_user_project_binds upb on (l.user_id = upb.user_id and l.project_id = upb.project_id) $where";
}
} else
- $sql = "select sum(time_to_sec(l.duration)) as time, null as cost, null as expenses from tt_log l $where";
+ $sql = "select sum(time_to_sec(l.duration)) as time,"
+ ." sum(if(time_to_sec(duration)/60 < $user->first_unit_threshold, 0, ceil(time_to_sec(duration)/60/$user->minutes_in_unit))) as units,"
+ ." null as cost, null as expenses from tt_log l $where";
// If we have expenses, query becomes a bit more complex.
if ($bean->getAttribute('chcost') && $user->isPluginEnabled('ex')) {
$where = ttReportHelper::getExpenseWhere($bean);
- $sql_for_expenses = "select null as time, sum(cost) as cost, sum(cost) as expenses from tt_expense_items ei $where";
+ $sql_for_expenses = "select null as time, null as units, sum(cost) as cost, sum(cost) as expenses from tt_expense_items ei $where";
+
// Create a combined query.
- $sql = "select sum(time) as time, sum(cost) as cost, sum(expenses) as expenses from (($sql) union all ($sql_for_expenses)) t";
+ $sql = "select sum(time) as time, sum(units) as units, sum(cost) as cost, sum(expenses) as expenses from (($sql) union all ($sql_for_expenses)) t";
}
// Execute query.
$totals['start_date'] = $period->getStartDate();
$totals['end_date'] = $period->getEndDate();
$totals['time'] = $total_time;
+ $totals['units'] = $val['units'];
$totals['cost'] = $total_cost;
$totals['expenses'] = $total_expenses;
// | https://www.anuko.com/time_tracker/credits.htm
// +----------------------------------------------------------------------+
+import('ttConfigHelper');
+
class ttUser {
var $login = null; // User login.
var $name = null; // User name.
var $workday_minutes = 480; // Number of work minutes in a regular day.
var $rights = array(); // An array of user rights such as 'track_own_time', etc.
var $is_client = false; // Whether user is a client as determined by missing 'track_own_time' right.
+ var $minutes_in_unit = 15; // Number of minutes in unit for Work units plugin.
+ var $first_unit_threshold = 0;// Threshold for 1st unit for Work units plugin.
// Constructor.
function __construct($login, $id = null) {
$this->custom_logo = $val['custom_logo'];
$this->config = $val['config'];
- $config_array = explode(',', $this->config);
-
+ $config = new ttConfigHelper($this->config);
// Set user config options.
- $this->show_holidays = in_array('show_holidays', $config_array);
- $this->punch_mode = in_array('punch_mode', $config_array);
- $this->allow_overlap = in_array('allow_overlap', $config_array);
- $this->future_entries = in_array('future_entries', $config_array);
- $this->uncompleted_indicators = in_array('uncompleted_indicators', $config_array);
-
+ $this->show_holidays = $config->getDefinedValue('show_holidays');
+ $this->punch_mode = $config->getDefinedValue('punch_mode');
+ $this->allow_overlap = $config->getDefinedValue('allow_overlap');
+ $this->future_entries = $config->getDefinedValue('future_entries');
+ $this->uncompleted_indicators = $config->getDefinedValue('uncompleted_indicators');
+ if ($this->isPluginEnabled('wu')) {
+ $minutes_in_unit = $config->getIntValue('minutes_in_unit');
+ if ($minutes_in_unit) $this->minutes_in_unit = $minutes_in_unit;
+ $first_unit_threshold = $config->getIntValue('1st_unit_threshold');
+ if ($first_unit_threshold) $this->first_unit_threshold = $first_unit_threshold;
+ }
+
// Set "on behalf" id and name.
if (isset($_SESSION['behalf_id'])) {
$this->behalf_id = $_SESSION['behalf_id'];
// 'label.week_note' => 'Week note',
// 'label.week_list' => 'Week list',
// 'label.work_units' => 'Work units',
+// 'label.work_units_short' => 'Units',
// Form titles.
// TODO: Improve titles for consistency, so that each title explains correctly what each
// 'label.week_note' => 'Week note',
// 'label.week_list' => 'Week list',
// 'label.work_units' => 'Work units',
+// 'label.work_units_short' => 'Units',
// Form titles.
// TODO: Improve titles for consistency, so that each title explains correctly what each
// 'label.week_note' => 'Week note',
// 'label.week_list' => 'Week list',
// 'label.work_units' => 'Work units',
+// 'label.work_units_short' => 'Units',
// Form titles.
'title.login' => 'Login',
// 'label.week_note' => 'Week note',
// 'label.week_list' => 'Week list',
// 'label.work_units' => 'Work units',
+// 'label.work_units_short' => 'Units',
// Form titles.
'title.login' => 'Anmelden',
'label.week_note' => 'Week note',
'label.week_list' => 'Week list',
'label.work_units' => 'Work units',
+'label.work_units_short' => 'Units',
// Form titles.
'title.login' => 'Login',
// 'label.week_note' => 'Week note',
// 'label.week_list' => 'Week list',
// 'label.work_units' => 'Work units',
+// 'label.work_units_short' => 'Units',
// Form titles.
'title.login' => 'Sesión iniciada',
// 'label.week_note' => 'Week note',
// 'label.week_list' => 'Week list',
// 'label.work_units' => 'Work units',
+// 'label.work_units_short' => 'Units',
// Form titles.
// TODO: Improve titles for consistency, so that each title explains correctly what each
// 'label.week_note' => 'Week note',
// 'label.week_list' => 'Week list',
// 'label.work_units' => 'Work units',
+// 'label.work_units_short' => 'Units',
// Form titles.
'title.login' => 'ورود',
// 'label.week_note' => 'Week note',
// 'label.week_list' => 'Week list',
// 'label.work_units' => 'Work units',
+// 'label.work_units_short' => 'Units',
// Form titles.
'title.login' => 'Kirjautuminen',
// 'label.week_note' => 'Week note',
// 'label.week_list' => 'Week list',
// 'label.work_units' => 'Work units',
+// 'label.work_units_short' => 'Units',
// Form titles.
'title.login' => 'Connexion',
'label.week_list' => 'Λίστα εβδομάδων',
// TODO: translate the following.
// 'label.work_units' => 'Work units',
+// 'label.work_units_short' => 'Units',
// Form titles.
'title.login' => 'Σύνδεση',
// 'label.week_note' => 'Week note',
// 'label.week_list' => 'Week list',
// 'label.work_units' => 'Work units',
+// 'label.work_units_short' => 'Units',
// Form titles.
'title.login' => 'כניסה',
// 'label.week_note' => 'Week note',
// 'label.week_list' => 'Week list',
// 'label.work_units' => 'Work units',
+// 'label.work_units_short' => 'Units',
// Form titles.
'title.login' => 'Bejelentkezés',
'label.week_list' => 'Lista settimanale',
// TODO: translate the following.
// 'label.work_units' => 'Work units',
+// 'label.work_units_short' => 'Units',
// Form titles.
// TODO: Improve titles for consistency, so that each title explains correctly what each
// 'label.week_note' => 'Week note',
// 'label.week_list' => 'Week list',
// 'label.work_units' => 'Work units',
+// 'label.work_units_short' => 'Units',
// Form titles.
'title.login' => 'ログイン',
// 'label.week_note' => 'Week note',
// 'label.week_list' => 'Week list',
// 'label.work_units' => 'Work units',
+// 'label.work_units_short' => 'Units',
// Form titles.
'title.login' => '로그인',
'label.week_list' => 'Week overzicht',
// TODO: translate the following.
// 'label.work_units' => 'Work units',
+// 'label.work_units_short' => 'Units',
// Form titles.
'title.login' => 'Aanmelden',
// 'label.week_note' => 'Week note',
// 'label.week_list' => 'Week list',
// 'label.work_units' => 'Work units',
+// 'label.work_units_short' => 'Units',
// Form titles.
'title.login' => 'Innlogging',
// 'label.week_note' => 'Week note',
// 'label.week_list' => 'Week list',
// 'label.work_units' => 'Work units',
+// 'label.work_units_short' => 'Units',
// Form titles.
'title.login' => 'Logowanie',
// 'label.week_note' => 'Week note',
// 'label.week_list' => 'Week list',
// 'label.work_units' => 'Work units',
+// 'label.work_units_short' => 'Units',
// Form titles.
'title.login' => 'Login',
// 'label.week_note' => 'Week note',
// 'label.week_list' => 'Week list',
// 'label.work_units' => 'Work units',
+// 'label.work_units_short' => 'Units',
// Form titles.
'title.login' => 'Login',
// 'label.week_note' => 'Week note',
// 'label.week_list' => 'Week list',
// 'label.work_units' => 'Work units',
+// 'label.work_units_short' => 'Units',
// Form titles.
// TODO: Improve titles for consistency, so that each title explains correctly what each
'label.week_note' => 'Комментарий недели',
'label.week_list' => 'Список недели',
'label.work_units' => 'Единицы работы',
+'label.work_units_short' => 'Единицы',
// Form titles.
'title.login' => 'Вход в систему',
// 'label.week_note' => 'Week note',
// 'label.week_list' => 'Week list',
// 'label.work_units' => 'Work units',
+// 'label.work_units_short' => 'Units',
// Form titles.
'title.login' => 'Prihlásenie',
// 'label.week_note' => 'Week note',
// 'label.week_list' => 'Week list',
// 'label.work_units' => 'Work units',
+// 'label.work_units_short' => 'Units',
// Form titles.
'title.login' => 'Prijava',
// 'label.week_note' => 'Week note',
// 'label.week_list' => 'Week list',
// 'label.work_units' => 'Work units',
+// 'label.work_units_short' => 'Units',
// Rubriker för formulär
'title.login' => 'Logga in',
// 'label.week_note' => 'Week note',
// 'label.week_list' => 'Week list',
// 'label.work_units' => 'Work units',
+// 'label.work_units_short' => 'Units',
// Form titles.
// Form titles.
// 'label.week_note' => 'Week note',
// 'label.week_list' => 'Week list',
// 'label.work_units' => 'Work units',
+// 'label.work_units_short' => 'Units',
// Form titles.
'title.login' => '登录',
// 'label.week_note' => 'Week note',
// 'label.week_list' => 'Week list',
// 'label.work_units' => 'Work units',
+// 'label.work_units_short' => 'Units',
// Form titles.
'title.login' => '登錄',
<br>
<table cellspacing="0" cellpadding="4" width="100%" border="0">
<tr>
- <td align="center"> Anuko Time Tracker 1.17.91.4275 | Copyright © <a href="https://www.anuko.com/lp/tt_3.htm" target="_blank">Anuko</a> |
+ <td align="center"> Anuko Time Tracker 1.17.92.4276 | Copyright © <a href="https://www.anuko.com/lp/tt_3.htm" target="_blank">Anuko</a> |
<a href="https://www.anuko.com/lp/tt_4.htm" target="_blank">{$i18n.footer.credits}</a> |
<a href="https://www.anuko.com/lp/tt_5.htm" target="_blank">{$i18n.footer.license}</a> |
<a href="https://www.anuko.com/lp/tt_7.htm" target="_blank">{$i18n.footer.improve}</a>
<tr>
<td class="tableHeader">{$group_by_header|escape}</td>
{if $bean->getAttribute('chduration')}<td class="tableHeaderCentered" width="5%">{$i18n.label.duration}</td>{/if}
+ {if $bean->getAttribute('chunits')}<td class="tableHeaderCentered" width="5%">{$i18n.label.work_units_short}</td>{/if}
{if $bean->getAttribute('chcost')}<td class="tableHeaderCentered" width="5%">{$i18n.label.cost}</td>{/if}
</tr>
{foreach $subtotals as $subtotal}
<tr class="rowReportSubtotal">
<td class="cellLeftAlignedSubtotal">{if $subtotal['name']}{$subtotal['name']|escape}{else} {/if}</td>
{if $bean->getAttribute('chduration')}<td class="cellRightAlignedSubtotal">{$subtotal['time']}</td>{/if}
+ {if $bean->getAttribute('chunits')}<td class="cellRightAlignedSubtotal">{$subtotal['units']}</td>{/if}
{if $bean->getAttribute('chcost')}<td class="cellRightAlignedSubtotal">{if $user->can('manage_invoices') || $user->isClient()}{$subtotal['cost']}{else}{$subtotal['expenses']}{/if}</td>{/if}
</tr>
{/foreach}
<tr class="rowReportSubtotal">
<td class="cellLeftAlignedSubtotal">{$i18n.label.total}</td>
{if $bean->getAttribute('chduration')}<td nowrap class="cellRightAlignedSubtotal">{$totals['time']}</td>{/if}
+ {if $bean->getAttribute('chunits')}<td nowrap class="cellRightAlignedSubtotal">{$totals['units']}</td>{/if}
{if $bean->getAttribute('chcost')}<td nowrap class="cellRightAlignedSubtotal">{$user->currency|escape} {if $user->can('manage_invoices') || $user->isClient()}{$totals['cost']}{else}{$totals['expenses']}{/if}</td>{/if}
</tr>
{else}
{if $bean->getAttribute('chstart')}<td class="tableHeaderCentered" width="5%">{$i18n.label.start}</td>{/if}
{if $bean->getAttribute('chfinish')}<td class="tableHeaderCentered" width="5%">{$i18n.label.finish}</td>{/if}
{if $bean->getAttribute('chduration')}<td class="tableHeaderCentered" width="5%">{$i18n.label.duration}</td>{/if}
+ {if $bean->getAttribute('chunits')}<td class="tableHeaderCentered" width="5%">{$i18n.label.work_units_short}</td>{/if}
{if $bean->getAttribute('chnote')}<td class="tableHeader">{$i18n.label.note}</td>{/if}
{if $bean->getAttribute('chcost')}<td class="tableHeaderCentered" width="5%">{$i18n.label.cost}</td>{/if}
{if $bean->getAttribute('chpaid')}<td class="tableHeader">{$i18n.label.paid}</td>{/if}
{if $bean->getAttribute('chstart')}<td></td>{/if}
{if $bean->getAttribute('chfinish')}<td></td>{/if}
{if $bean->getAttribute('chduration')}<td class="cellRightAlignedSubtotal">{$subtotals[$prev_grouped_by]['time']}</td>{/if}
+ {if $bean->getAttribute('chunits')}<td class="cellRightAlignedSubtotal">{$subtotals[$prev_grouped_by]['units']}</td>{/if}
{if $bean->getAttribute('chnote')}<td></td>{/if}
{if $bean->getAttribute('chcost')}<td class="cellRightAlignedSubtotal">{if $user->can('manage_invoices') || $user->isClient()}{$subtotals[$prev_grouped_by]['cost']}{else}{$subtotals[$prev_grouped_by]['expenses']}{/if}</td>{/if}
{if $bean->getAttribute('chpaid')}<td></td>{/if}
{if $bean->getAttribute('chstart')}<td nowrap class="cellRightAligned">{$item.start}</td>{/if}
{if $bean->getAttribute('chfinish')}<td nowrap class="cellRightAligned">{$item.finish}</td>{/if}
{if $bean->getAttribute('chduration')}<td class="cellRightAligned">{$item.duration}</td>{/if}
+ {if $bean->getAttribute('chunits')}<td class="cellRightAligned">{$item.units}</td>{/if}
{if $bean->getAttribute('chnote')}<td class="cellLeftAligned">{$item.note|escape}</td>{/if}
{if $bean->getAttribute('chcost')}<td class="cellRightAligned">{if $user->can('manage_invoices') || $user->isClient()}{$item.cost}{else}{$item.expense}{/if}</td>{/if}
{if $bean->getAttribute('chpaid')}<td class="cellRightAligned">{if $item.paid == 1}{$i18n.label.yes}{else}{$i18n.label.no}{/if}{/if}
{if $bean->getAttribute('chstart')}<td></td>{/if}
{if $bean->getAttribute('chfinish')}<td></td>{/if}
{if $bean->getAttribute('chduration')}<td class="cellRightAlignedSubtotal">{$subtotals[$cur_grouped_by]['time']}</td>{/if}
+ {if $bean->getAttribute('chunits')}<td class="cellRightAlignedSubtotal">{$subtotals[$cur_grouped_by]['units']}</td>{/if}
{if $bean->getAttribute('chnote')}<td></td>{/if}
{if $bean->getAttribute('chcost')}<td class="cellRightAlignedSubtotal">{if $user->can('manage_invoices') || $user->isClient()}{$subtotals[$cur_grouped_by]['cost']}{else}{$subtotals[$cur_grouped_by]['expenses']}{/if}</td>{/if}
{if $bean->getAttribute('chpaid')}<td></td>{/if}
{if $bean->getAttribute('chstart')}<td></td>{/if}
{if $bean->getAttribute('chfinish')}<td></td>{/if}
{if $bean->getAttribute('chduration')}<td class="cellRightAlignedSubtotal">{$totals['time']}</td>{/if}
+ {if $bean->getAttribute('chunits')}<td class="cellRightAlignedSubtotal">{$totals['units']}</td>{/if}
{if $bean->getAttribute('chnote')}<td></td>{/if}
{if $bean->getAttribute('chcost')}<td nowrap class="cellRightAlignedSubtotal">{$user->currency|escape} {if $user->can('manage_invoices') || $user->isClient()}{$totals['cost']}{else}{$totals['expenses']}{/if}</td>{/if}
{if $bean->getAttribute('chpaid')}<td></td>{/if}
<td></td>
{/if}
</tr>
+{if $user->isPluginEnabled('wu')}
+ <tr>
+ <td></td>
+ <td></td>
+ <td width="25%"><label>{$forms.reportForm.chunits.control} {$i18n.label.work_units}</label></td>
+ <td></td>
+ </tr>
+{/if}
</table>
</td>
</tr>
// If we have a custom field - add a checkbox for it.
if ($custom_fields && $custom_fields->fields[0])
$form->addInput(array('type'=>'checkbox','name'=>'chcf_1'));
+if ($user->isPluginEnabled('wu'))
+ $form->addInput(array('type'=>'checkbox','name'=>'chunits'));
+
// Add group by control.
$group_by_options['no_grouping'] = $i18n->get('form.reports.group_by_no');
$group_by_options['date'] = $i18n->get('form.reports.group_by_date');
$form->setValueByElement('chfinish', '1');
$form->setValueByElement('chnote', '1');
$form->setValueByElement('chcf_1', '0');
+ $form->setValueByElement('chunits', '0');
$form->setValueByElement('chtotalsonly', '0');
}
$cl_minutes_in_unit = $request->getParameter('minutes_in_unit');
$cl_1st_unit_threshold = $request->getParameter('1st_unit_threshold');
} else {
- $cl_minutes_in_unit = $config->getIntValue('minutes_in_unit');
- $cl_1st_unit_threshold = $config->getIntValue('1st_unit_threshold');
+ $cl_minutes_in_unit = $user->minutes_in_unit;
+ $cl_1st_unit_threshold = $user->first_unit_threshold;
}
$form = new Form('workUnitsForm');