1: <?php
2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12:
13: class Hermes_Api extends Horde_Registry_Api
14: {
15: 16: 17: 18: 19: 20: 21: 22:
23: public static function getTableMetaData($name, $params)
24: {
25: switch ($name) {
26: case 'hours':
27: $emptype = Hermes::getEmployeesType('enum');
28: $clients = Hermes::listClients();
29: $hours = $GLOBALS['injector']->getInstance('Hermes_Driver')->getHours($params);
30: $yesno = array(1 => _("Yes"),
31: 0 => _("No"));
32:
33: $columns = array(
34: array('name' => 'icons',
35: 'title' => '',
36: 'type' => '%html',
37: 'nobr' => true),
38: array('name' => 'checkbox',
39: 'title' => '',
40: 'type' => '%html',
41: 'nobr' => true),
42: array('name' => 'date',
43: 'title' => _("Date"),
44: 'type' => 'date',
45: 'params' => array($GLOBALS['prefs']->getValue('date_format')),
46: 'nobr' => true),
47: array('name' => 'employee',
48: 'title' => _("Employee"),
49: 'type' => $emptype[0],
50: 'params' => $emptype[1]),
51: array('name' => '_client_name',
52: 'title' => _("Client"),
53: 'type' => 'enum',
54: 'params' => array($clients)),
55: array('name' => '_type_name',
56: 'title' => _("Job Type")),
57: array('name' => '_costobject_name',
58: 'title' => _("Cost Object")),
59: array('name' => 'hours',
60: 'title' => _("Hours"),
61: 'type' => 'number',
62: 'align' => 'right'));
63: if ($GLOBALS['conf']['time']['choose_ifbillable']) {
64: $columns[] =
65: array('name' => 'billable',
66: 'title' => _("Bill?"),
67: 'type' => 'enum',
68: 'params' => array($yesno));
69: }
70: $columns = array_merge($columns, array(
71: array('name' => 'description',
72: 'title' => _("Description")),
73: array('name' => 'note',
74: 'title' => _("Notes"))));
75:
76: $colspan = 6;
77: if ($GLOBALS['conf']['time']['choose_ifbillable']) {
78: $colspan++;
79: }
80: $fColumns = array(
81: array('name' => 'approval',
82: 'colspan' => $colspan,
83: 'type' => '%html',
84: 'align' => 'right'),
85: array('name' => 'hours',
86: 'type' => 'number',
87: 'align' => 'right'));
88: if ($GLOBALS['conf']['time']['choose_ifbillable']) {
89: $fColumns[] =
90: array('name' => 'billable',
91: 'type' => 'enum',
92: 'params' => array($yesno));
93: }
94: $fColumns = array_merge($fColumns, array(
95: array('name' => 'description'),
96: array('name' => 'blank2')));
97:
98: return array('title' => _("Search Results"),
99: 'sections' => array(
100: 'data' => array(
101: 'rows' => count($hours),
102: 'columns' => $columns),
103: 'footer' => array(
104: 'rows' => 3,
105: 'strong' => true,
106: 'columns' => $fColumns)));
107:
108: default:
109: throw new Hermes_Exception(sprintf(_("\"%s\" is not a defined table."), $name));
110: }
111: }
112:
113: 114: 115: 116: 117: 118: 119:
120: public static function getTableData($name, $params)
121: {
122: switch ($name) {
123: case 'hours':
124: $time_data = $GLOBALS['injector']->getInstance('Hermes_Driver')->getHours($params);
125: $subtotal_column = null;
126: if ($search_mode = $GLOBALS['session']->get('hermes', 'search_mode')) {
127: switch ($search_mode) {
128: case 'date':
129: $subtotal_column = 'date';
130: break;
131:
132: case 'employee':
133: $subtotal_column = 'employee';
134: break;
135:
136: case 'client':
137: $subtotal_column = '_client_name';
138: break;
139:
140: case 'jobtype':
141: $subtotal_column = '_type_name';
142: break;
143:
144: case 'costobject':
145: $subtotal_column = '_costobject_name';
146: break;
147: }
148:
149: $clients = Hermes::listClients();
150: $column = array();
151: foreach ($time_data as $key => $row) {
152: if (empty($row['client'])) {
153: $time_data[$key]['_client_name'] = _("no client");
154: } elseif (isset($clients[$row['client']])) {
155: $time_data[$key]['_client_name'] = $clients[$row['client']];
156: } else {
157: $time_data[$key]['_client_name'] = $row['client'];
158: }
159: if (!is_null($subtotal_column)) {
160: $column[$key] = $time_data[$key][$subtotal_column];
161: }
162: }
163: if (!is_null($subtotal_column)) {
164: array_multisort($column, SORT_ASC, $time_data);
165: }
166: }
167:
168: $total_hours = 0.0;
169: $total_billable_hours = 0.0;
170: $subtotal_hours = 0.0;
171: $subtotal_billable_hours = 0.0;
172: $subtotal_control = null;
173:
174: $result['data'] = array();
175: foreach ($time_data as $k => $vals) {
176:
177: if (is_null($subtotal_control) && isset($vals[$subtotal_column])) {
178: $subtotal_control = $vals[$subtotal_column];
179: }
180:
181: if (!empty($subtotal_column) &&
182: $vals[$subtotal_column] != $subtotal_control) {
183: Hermes_Api::renderSubtotals($result['data'], $subtotal_hours, $subtotal_billable_hours,
184: $subtotal_column == 'date' ? strftime("%m/%d/%Y", $subtotal_control) :
185: $subtotal_control);
186: $subtotal_hours = 0.0;
187: $subtotal_billable_hours = 0.0;
188: $subtotal_control = $vals[$subtotal_column];
189: }
190:
191:
192: if (Hermes::canEditTimeslice($vals['id'])) {
193: $edit_link = Horde::url('entry.php', true)
194: ->add(array(
195: 'id' => $vals['id'],
196: 'url' => Horde::selfUrl(true, true, true)));
197: $vals['icons'] =
198: Horde::link($edit_link, _("Edit Entry")) .
199: Horde::img('edit.png', _("Edit Entry"), '') . '</a>';
200:
201: if (empty($vals['submitted'])) {
202: $vals['checkbox'] =
203: '<input type="checkbox" name="item[' .
204: htmlspecialchars($vals['id']) .
205: ']" checked="checked" />';
206: } else {
207: $vals['checkbox'] = '';
208: }
209: }
210:
211:
212: $subtotal_hours += (double)$vals['hours'];
213: $total_hours += (double)$vals['hours'];
214: if ($vals['billable']) {
215: $subtotal_billable_hours += (double)$vals['hours'];
216: $total_billable_hours += (double)$vals['hours'];
217: }
218:
219:
220: $vals['hours'] = sprintf('%.02f', $vals['hours']);
221:
222: $result['data'][] = $vals;
223: }
224:
225: if (!empty($subtotal_column)) {
226: Hermes_Api::renderSubtotals($result['data'], $subtotal_hours, $subtotal_billable_hours,
227: $subtotal_column == 'date' ? strftime("%m/%d/%Y", $subtotal_control) :
228: $subtotal_control);
229: }
230:
231:
232: if ($total_hours == 0.0) {
233: $billable_pct = 0.0;
234: } else {
235: $billable_pct = round($total_billable_hours / $total_hours * 100.0);
236: }
237:
238: $descr = _("Billable Hours") . ' (' . $billable_pct . '%)';
239: $result['footer'] = array();
240: $result['footer'][] = array(
241: 'hours' => sprintf('%.02f', $total_billable_hours),
242: 'description' => $descr);
243:
244: $descr = _("Non-billable Hours") . ' (' . (100.0 - $billable_pct) . '%)';
245: $result['footer'][] = array(
246: 'hours' => sprintf('%.02f', $total_hours - $total_billable_hours),
247: 'description' => $descr);
248: $result['footer'][] = array(
249: 'hours' => sprintf('%.02f', $total_hours),
250: 'description' => _("Total Hours"),
251: 'approval' => '<div id="approval">' . _("Approved By:") .
252: ' ________________________________________ ' .
253: ' </div>');
254: break;
255: }
256:
257: return $result;
258: }
259:
260: 261: 262: 263: 264: 265: 266: 267: 268:
269: public static function renderSubtotals(&$table_data, $hours, $billable_hours, $value)
270: {
271: $billable_pct = ($hours == 0.0) ? 0.0 :
272: round($billable_hours / $hours * 100.0);
273: $descr = _("Billable Hours") . ' (' . $billable_pct . '%)';
274: $table_data[] = array(
275: 'date' => '',
276: 'employee' => '',
277: 'client' => '',
278: 'billable' => '',
279: 'note' => '',
280: '_type_name' => '',
281: '_costobject_name' => '',
282: 'hours' => sprintf('%.02f', $billable_hours),
283: 'description' => $descr);
284: $descr = _("Non-billable Hours") . ' (' . (100.0 - $billable_pct) . '%)';
285: $table_data[] = array(
286: 'hours' => sprintf('%.02f', $hours - $billable_hours),
287: 'description' => $descr);
288: $table_data[] = array(
289: 'hours' => sprintf('%.02f', $hours),
290: 'description' => sprintf(_("Total Hours for %s"), $value),
291: );
292:
293: return;
294: }
295:
296: 297: 298: 299: 300: 301: 302:
303: public function listCostObjects($criteria)
304: {
305: if (!$GLOBALS['conf']['time']['deliverables']) {
306: return array();
307: }
308:
309: $deliverables = $GLOBALS['injector']->getInstance('Hermes_Driver')->listDeliverables($criteria);
310: if (empty($criteria['id'])) {
311:
312: $levels = array();
313: $hash = array();
314: foreach ($deliverables as $deliverable) {
315: if (empty($deliverable['parent'])) {
316: $parent = -1;
317: } else {
318: $parent = $deliverable['parent'];
319: }
320: $levels[$parent][$deliverable['id']] = $deliverable['name'];
321: $hash[$deliverable['id']] = $deliverable;
322: }
323:
324:
325: foreach ($levels as $key => $level) {
326: asort($levels[$key]);
327: }
328:
329: 330:
331: $elts = array();
332: $stack = empty($levels[-1]) ? array() : array(-1);
333: while (count($stack)) {
334: if (!(list($key, $val) = each($levels[$stack[count($stack) - 1]]))) {
335: array_pop($stack);
336: continue;
337: }
338: $elts[$key] = str_repeat(' + ', count($stack)-1) . $val;
339: if (!empty($levels[$key])) {
340: $stack[] = $key;
341: }
342: }
343:
344: $results = array();
345: foreach ($elts as $key => $value) {
346: $results[] = array('id' => $key,
347: 'active' => $hash[$key]['active'],
348: 'estimate' => $hash[$key]['estimate'],
349: 'name' => $value);
350: }
351: } else {
352: $results = $deliverables;
353: }
354:
355: if (!$results) {
356: return array();
357: }
358:
359: return array(array('category' => _("Deliverables"),
360: 'objects' => $results));
361: }
362:
363: 364: 365: 366: 367: 368: 369: 370: 371: 372:
373: public function listJobTypes($criteria = array())
374: {
375: return $GLOBALS['injector']->getInstance('Hermes_Driver')->listJobTypes($criteria);
376: }
377:
378: 379: 380: 381:
382: public function listClients()
383: {
384: return Hermes::listClients();
385: }
386:
387: 388: 389: 390: 391: 392: 393: 394: 395: 396: 397: 398: 399: 400: 401: 402: 403:
404: public function recordTime(array $data)
405: {
406: $data = new Horde_Support_Array($data);
407:
408: if (!$data->date || !$data->client || !$data->type || !$data->hours || !$data->description) {
409: throw new Hermes_Exception(_("Missing required values: check data and retry"));
410: }
411:
412:
413: $dateobj = new Horde_Date($data->date);
414: $date['year'] = $dateobj->year;
415: $date['month'] = $dateobj->month;
416: $date['day'] = $dateobj->mday;
417: $data->date = $date;
418:
419: if (!$data->billable) {
420: $data->billable = true;
421: }
422:
423: return $GLOBALS['injector']->getInstance('Hermes_Driver')->enterTime($GLOBALS['registry']->getAuth(), $data);
424: }
425:
426: 427: 428: 429: 430: 431: 432: 433: 434: 435: 436: 437: 438:
439: public function getCostObjectInfo($costobject)
440: {
441: $filter = array('costobject' => $costobject);
442: $slices = $GLOBALS['injector']->getInstance('Hermes_Driver')->getHours($filter);
443: $billable = $time = 0;
444: $employees = array();
445: foreach ($slices as $slice) {
446: $time += $slice['hours'];
447: if ($slice['billable']) {
448: $billable += $slice['hours'];
449: }
450:
451: $employees[$slice['employee']] += $slice['hours'];
452: }
453:
454: return array('employees' => $employees,
455: 'total' => $time,
456: 'billable' => $billable);
457:
458: }
459:
460: }
461: