Started redesign of timesheets feature.
[timetracker.git] / WEB-INF / lib / ttTimesheetHelper.class.php
1 <?php
2 // +----------------------------------------------------------------------+
3 // | Anuko Time Tracker
4 // +----------------------------------------------------------------------+
5 // | Copyright (c) Anuko International Ltd. (https://www.anuko.com)
6 // +----------------------------------------------------------------------+
7 // | LIBERAL FREEWARE LICENSE: This source code document may be used
8 // | by anyone for any purpose, and freely redistributed alone or in
9 // | combination with other software, provided that the license is obeyed.
10 // |
11 // | There are only two ways to violate the license:
12 // |
13 // | 1. To redistribute this code in source form, with the copyright
14 // |    notice or license removed or altered. (Distributing in compiled
15 // |    forms without embedded copyright notices is permitted).
16 // |
17 // | 2. To redistribute modified versions of this code in *any* form
18 // |    that bears insufficient indications that the modifications are
19 // |    not the work of the original author(s).
20 // |
21 // | This license applies to this document only, not any other software
22 // | that it may be combined with.
23 // |
24 // +----------------------------------------------------------------------+
25 // | Contributors:
26 // | https://www.anuko.com/time_tracker/credits.htm
27 // +----------------------------------------------------------------------+
28
29 import('ttUserHelper');
30 import('ttGroupHelper');
31 import('form.ActionForm');
32 import('ttReportHelper');
33
34 // Class ttTimesheetHelper is used to help with project related tasks.
35 class ttTimesheetHelper {
36
37   // The getTimesheetByName looks up a project by name.
38   static function getTimesheetByName($name, $user_id) {
39     global $user;
40     $mdb2 = getConnection();
41
42     $group_id = $user->getGroup();
43     $org_id = $user->org_id;
44
45     $sql = "select id from tt_timesheets".
46       " where group_id = $group_id and org_id = $org_id and user_id = $user_id and name = ".$mdb2->quote($name).
47       " and (status = 1 or status = 0)";
48     $res = $mdb2->query($sql);
49     if (!is_a($res, 'PEAR_Error')) {
50       $val = $res->fetchRow();
51       if ($val && $val['id'])
52         return $val;
53     }
54     return false;
55   }
56
57   // insert function inserts a new timesheet into database.
58   static function insert($fields)
59   {
60     // First, we obtain report items.
61
62     // Obtain session bean with report attributes.
63     $bean = new ActionForm('reportBean', new Form('reportForm'));
64     $options = ttReportHelper::getReportOptions($bean);
65     $report_items = ttReportHelper::getItems($options);
66
67     // Prepare ids for time and expense items, at the same time checking
68     // if we can proceed with creating a timesheet.
69     $canCreateTimesheet = true;
70     $first_user_id = null;
71
72     foreach ($report_items as $report_item) {
73       // Check user id.
74       if (!$first_user_id)
75         $first_user_id = $report_item['user_id'];
76       else {
77         if ($report_item['user_id'] != $first_user_id) {
78           // We have items for multiple users.
79           $canCreateTimesheet = false;
80           break;
81         }
82       }
83       // Check timesheet id.
84       if ($report_item['timesheet_id']) {
85         // We have an item already assigned to a timesheet.
86         $canCreateTimesheet = false;
87         break;
88       }
89       if ($report_item['type'] == 1)
90         $time_ids[] = $report_item['id'];
91       elseif ($report_item['type'] == 2)
92         $expense_ids[] = $report_item['id'];
93     }
94     if (!$canCreateTimesheet) return false;
95
96     // Make comma-seperated lists of ids for sql.
97     if ($time_ids)
98       $comma_separated_time_ids = implode(',', $time_ids);
99     if ($expense_ids)
100       $comma_separated_expense_ids = implode(',', $expense_ids);
101
102     // Create a new timesheet entry.
103     global $user;
104     $mdb2 = getConnection();
105
106     $group_id = $user->getGroup();
107     $org_id = $user->org_id;
108
109     $user_id = $fields['user_id'];
110     $client_id = $fields['client_id'];
111     $name = $fields['name'];
112     $submitter_comment = $fields['comment'];
113
114     $sql = "insert into tt_timesheets (user_id, group_id, org_id, client_id, name, submitter_comment)".
115       " values ($user_id, $group_id, $org_id, ".$mdb2->quote($client_id).", ".$mdb2->quote($name).", ".$mdb2->quote($submitter_comment).")";
116     $affected = $mdb2->exec($sql);
117     if (is_a($affected, 'PEAR_Error'))
118       return false;
119
120     $last_id = $mdb2->lastInsertID('tt_timesheets', 'id');
121
122     // Associate time items with timesheet.
123     if ($comma_separated_time_ids) {
124       $sql = "update tt_log set timesheet_id = $last_id".
125         " where id in ($comma_separated_time_ids) and group_id = $group_id and org_id = $org_id";
126       $affected = $mdb2->exec($sql);
127       if (is_a($affected, 'PEAR_Error'))
128         return false;
129     }
130
131     // Associate expense items with timesheet.
132     if ($comma_separated_expense_ids) {
133       $sql = "update tt_expense_items set timesheet_id = $last_id".
134         " where id in ($comma_separated_expense_ids) and group_id = $group_id and org_id = $org_id";
135       $affected = $mdb2->exec($sql);
136       if (is_a($affected, 'PEAR_Error'))
137         return false;
138     }
139
140     return $last_id;
141   }
142
143   // The getActiveTimesheets obtains active timesheets for a user.
144   static function getActiveTimesheets($user_id)
145   {
146     global $user;
147     $mdb2 = getConnection();
148
149     $group_id = $user->getGroup();
150     $org_id = $user->org_id;
151
152     // $addPaidStatus = $user->isPluginEnabled('ps');
153     $result = array();
154
155     if ($user->isClient())
156       $client_part = "and ts.client_id = $user->client_id";
157
158     $sql = "select ts.id, ts.name, ts.client_id, c.name as client_name, ts.submit_status, ts.approval_status from tt_timesheets ts".
159       " left join tt_clients c on (c.id = ts.client_id)".
160       " where ts.status = 1 and ts.group_id = $group_id and ts.org_id = $org_id and ts.user_id = $user_id".
161       " $client_part order by ts.name";
162     $res = $mdb2->query($sql);
163     $result = array();
164     if (!is_a($res, 'PEAR_Error')) {
165       $dt = new DateAndTime(DB_DATEFORMAT);
166       while ($val = $res->fetchRow()) {
167         //if ($addPaidStatus)
168         //  $val['paid'] = ttTimesheetHelper::isPaid($val['id']);
169         $result[] = $val;
170       }
171     }
172     return $result;
173   }
174
175   // The getInactiveTimesheets obtains inactive timesheets for a user.
176   static function getInactiveTimesheets($user_id)
177   {
178     global $user;
179     $mdb2 = getConnection();
180
181     $group_id = $user->getGroup();
182     $org_id = $user->org_id;
183
184     // $addPaidStatus = $user->isPluginEnabled('ps');
185     $result = array();
186
187     if ($user->isClient())
188       $client_part = "and ts.client_id = $user->client_id";
189
190     $sql = "select ts.id, ts.name, ts.client_id, c.name as client_name, ts.submit_status, ts.approval_status from tt_timesheets ts".
191       " left join tt_clients c on (c.id = ts.client_id)".
192       " where ts.status = 0 and ts.group_id = $group_id and ts.org_id = $org_id and ts.user_id = $user_id".
193       " $client_part order by ts.name";
194     $res = $mdb2->query($sql);
195     $result = array();
196     if (!is_a($res, 'PEAR_Error')) {
197       $dt = new DateAndTime(DB_DATEFORMAT);
198       while ($val = $res->fetchRow()) {
199         //if ($addPaidStatus)
200         //  $val['paid'] = ttTimesheetHelper::isPaid($val['id']);
201         $result[] = $val;
202       }
203     }
204     return $result;
205   }
206
207   // getTimesheet - obtains timesheet data from the database.
208   static function getTimesheet($timesheet_id) {
209     global $user;
210     $mdb2 = getConnection();
211
212     $group_id = $user->getGroup();
213     $org_id = $user->org_id;
214
215     if ($user->isClient()) $client_part = "and ts.client_id = $user->client_id";
216
217     $sql = "select ts.id, ts.user_id, u.name as user_name, ts.client_id, c.name as client_name,".
218       " ts.name, ts.submitter_comment, ts.submit_status, ts.approval_status, ts.manager_comment from tt_timesheets ts".
219       " left join tt_users u on (u.id = ts.user_id)".
220       " left join tt_clients c on (c.id = ts.client_id)".
221       " where ts.id = $timesheet_id and ts.group_id = $group_id and ts.org_id = $org_id $client_part and ts.status is not null";
222     $res = $mdb2->query($sql);
223     if (!is_a($res, 'PEAR_Error')) {
224       if ($val = $res->fetchRow())
225         return $val;
226     }
227     return false;
228   }
229
230   // delete - deletes timesheet from the database.
231   static function delete($timesheet_id) {
232     global $user;
233     $mdb2 = getConnection();
234
235     $group_id = $user->getGroup();
236     $org_id = $user->org_id;
237
238     // Handle time records.
239     $sql = "update tt_log set timesheet_id = null".
240       " where timesheet_id = $timesheet_id and group_id = $group_id and org_id = $org_id";
241     $affected = $mdb2->exec($sql);
242     if (is_a($affected, 'PEAR_Error')) return false;
243
244     // Handle expense items.
245     $sql = "update tt_expense_items set timesheet_id = null".
246       " where timesheet_id = $timesheet_id and group_id = $group_id and org_id = $org_id";
247     $affected = $mdb2->exec($sql);
248     if (is_a($affected, 'PEAR_Error')) return false;
249
250     // Delete timesheet.
251     $sql = "update tt_timesheets set status = null".
252       " where id = $timesheet_id and group_id = $group_id and org_id = $org_id";
253     $affected = $mdb2->exec($sql);
254     return (!is_a($affected, 'PEAR_Error'));
255   }
256
257   // update function - updates the timesheet in database.
258   static function update($fields) {
259     global $user;
260     $mdb2 = getConnection();
261
262     $group_id = $user->getGroup();
263     $org_id = $user->org_id;
264
265     $timesheet_id = $fields['id']; // Timesheet we are updating.
266     $name = $fields['name']; // Timesheet name.
267     $submitter_comment = $fields['submitter_comment'];
268     $status = $fields['status']; // Project status.
269
270     $sql = "update tt_timesheets set name = ".$mdb2->quote($name).", submitter_comment = ".$mdb2->quote($submitter_comment).
271       ", status = ".$mdb2->quote($status).
272       " where id = $timesheet_id and group_id = $group_id and org_id = $org_id";
273     $affected = $mdb2->exec($sql);
274     return (!is_a($affected, 'PEAR_Error'));
275   }
276
277   // isUserValid function is used during access checks and determines whether user id, passed in post, is valid
278   // in current context.
279   static function isUserValid($user_id) {
280     // We have to cover several situations.
281     //
282     // 1) User is a client.
283     // 2) User with view_all_timesheets rights.
284     // 3) User with view_timesheets rights.
285
286     global $user;
287
288     // TODO: we are currently re-designing timesheets.
289     // Clients are not supposed to view them at all.
290     // And the post will change on_behalf user, to keep things consistent.
291     return false;
292   }
293
294   // getReportOptions prepares $options array to be used with ttReportHelper
295   // to obtain items for timesheet view.
296   static function getReportOptions($timesheet) {
297     global $user;
298     $group_by_client = $user->isPluginEnabled('cl') && !$timesheet['client_id'];
299     $trackingMode = $user->getTrackingMode();
300     $group_by_project = MODE_PROJECTS == $trackingMode || MODE_PROJECTS_AND_TASKS == $trackingMode;
301
302     $options['timesheet_id'] = $timesheet['id'];
303     $options['client_id'] = $timesheet['client_id'];
304     $options['users'] = $timesheet['user_id'];
305     $options['show_durarion'] = 1;
306     $options['show_cost'] = 1; // To include expenses.
307     $options['show_totals_only'] = 1;
308     $options['group_by1'] = 'date';
309     if ($group_by_client || $group_by_project) {
310       $options['group_by2'] = $group_by_client ? 'client' : 'project';
311     }
312     if ($options['group_by2'] && $options['group_by2'] != 'project' && $group_by_project) {
313       $options['group_by3'] = 'project';
314     }
315     return $options;
316   }
317
318   // getApprovers obtains a list of users who can approve a timesheet for a given user
319   // and also have an email to receive a notification about it.
320   static function getApprovers($user_id) {
321     global $user;
322     $mdb2 = getConnection();
323
324     $group_id = $user->getGroup();
325     $org_id = $user->org_id;
326
327     $approvers = array();
328     $rank = ttUserHelper::getUserRank($user_id);
329     $sql = "select u.id, u.name, u.email".
330       " from tt_users u".
331       " left join tt_roles r on (r.id = u.role_id)".
332       " where u.status = 1 and u.email is not null and u.group_id = $group_id and u.org_id = $org_id".
333       " and (r.rights like '%approve_all_timesheets%' or (r.rank > $rank and r.rights like '%approve_timesheets%'))";
334     $res = $mdb2->query($sql);
335     if (!is_a($res, 'PEAR_Error')) {
336       while ($val = $res->fetchRow()) {
337         $approvers[] = $val;
338       }
339     }
340     return $approvers;
341   }
342
343   // submitTimesheet marks a timesheet as submitted and sends an email to an approver.
344   static function submitTimesheet($fields) {
345     global $user;
346     $mdb2 = getConnection();
347
348     $group_id = $user->getGroup();
349     $org_id = $user->org_id;
350
351     // First, mark a timesheet as submitted.
352     // Even if mail part below does not work, this will get us a functioning workflow
353     // (without email notifications).
354     $timesheet_id = $fields['timesheet_id'];
355     $sql = "update tt_timesheets set submit_status = 1".
356       " where id = $timesheet_id and group_id = $group_id and org_id = $org_id";
357     $affected = $mdb2->exec($sql);
358     if (is_a($affected, 'PEAR_Error')) return false;
359
360     // TODO: send email to approver here...
361     // $approver_id = $fields['approver_id'];
362
363     return true;
364   }
365
366   // approveTimesheet marks a timesheet as approved and sends an email to submitter.
367   static function approveTimesheet($fields) {
368     global $user;
369     $mdb2 = getConnection();
370
371     $group_id = $user->getGroup();
372     $org_id = $user->org_id;
373
374     // First, mark a timesheet as approved.
375     // Even if mail part below does not work, this will get us a functioning workflow
376     // (without email notifications).
377     $timesheet_id = $fields['timesheet_id'];
378     $manager_comment = $fields['comment'];
379
380     $sql = "update tt_timesheets set approval_status = 1, manager_comment = ".$mdb2->quote($manager_comment).
381       " where id = $timesheet_id and submit_status = 1 and group_id = $group_id and org_id = $org_id";
382     $affected = $mdb2->exec($sql);
383     if (is_a($affected, 'PEAR_Error')) return false;
384
385     // TODO: send email to submitter here...
386     return true;
387   }
388
389   // disapproveTimesheet marks a timesheet as approved and sends an email to submitter.
390   static function disapproveTimesheet($fields) {
391     global $user;
392     $mdb2 = getConnection();
393
394     $group_id = $user->getGroup();
395     $org_id = $user->org_id;
396
397     // First, mark a timesheet as disapproved.
398     // Even if mail part below does not work, this will get us a functioning workflow
399     // (without email notifications).
400     $timesheet_id = $fields['timesheet_id'];
401     $manager_comment = $fields['comment'];
402
403     $sql = "update tt_timesheets set approval_status = 0, manager_comment = ".$mdb2->quote($manager_comment).
404       " where id = $timesheet_id and submit_status = 1 and group_id = $group_id and org_id = $org_id";
405     $affected = $mdb2->exec($sql);
406     if (is_a($affected, 'PEAR_Error')) return false;
407
408     // TODO: send email to submitter here...
409     return true;
410   }
411 }