More progress on timesheet approval workflow.
authorNik Okuntseff <support@anuko.com>
Fri, 22 Feb 2019 21:38:50 +0000 (21:38 +0000)
committerNik Okuntseff <support@anuko.com>
Fri, 22 Feb 2019 21:38:50 +0000 (21:38 +0000)
34 files changed:
WEB-INF/lib/ttTimesheetHelper.class.php
WEB-INF/resources/ca.lang.php
WEB-INF/resources/cs.lang.php
WEB-INF/resources/da.lang.php
WEB-INF/resources/de.lang.php
WEB-INF/resources/en.lang.php
WEB-INF/resources/es.lang.php
WEB-INF/resources/et.lang.php
WEB-INF/resources/fa.lang.php
WEB-INF/resources/fi.lang.php
WEB-INF/resources/fr.lang.php
WEB-INF/resources/gr.lang.php
WEB-INF/resources/he.lang.php
WEB-INF/resources/hu.lang.php
WEB-INF/resources/it.lang.php
WEB-INF/resources/ja.lang.php
WEB-INF/resources/ko.lang.php
WEB-INF/resources/nl.lang.php
WEB-INF/resources/no.lang.php
WEB-INF/resources/pl.lang.php
WEB-INF/resources/pt-br.lang.php
WEB-INF/resources/pt.lang.php
WEB-INF/resources/ro.lang.php
WEB-INF/resources/ru.lang.php
WEB-INF/resources/sk.lang.php
WEB-INF/resources/sl.lang.php
WEB-INF/resources/sr.lang.php
WEB-INF/resources/sv.lang.php
WEB-INF/resources/tr.lang.php
WEB-INF/resources/zh-cn.lang.php
WEB-INF/resources/zh-tw.lang.php
WEB-INF/templates/footer.tpl
WEB-INF/templates/timesheet_view.tpl
timesheet_view.php

index 0d4556b..df1e4e4 100644 (file)
@@ -215,7 +215,7 @@ class ttTimesheetHelper {
     if ($user->isClient()) $client_part = "and ts.client_id = $user->client_id";
 
     $sql = "select ts.id, ts.user_id, u.name as user_name, ts.client_id, c.name as client_name,".
-      " ts.name, ts.submitter_comment, ts.submit_status from tt_timesheets ts".
+      " ts.name, ts.submitter_comment, ts.submit_status, ts.approval_status, ts.manager_comment from tt_timesheets ts".
       " left join tt_users u on (u.id = ts.user_id)".
       " left join tt_clients c on (c.id = ts.client_id)".
       " 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";
@@ -346,4 +346,27 @@ class ttTimesheetHelper {
     }
     return $approvers;
   }
+
+  // submitTimesheet marks a timesheet as submitted and sends an email to an approver.
+  static function submitTimesheet($fields) {
+    global $user;
+    $mdb2 = getConnection();
+
+    $group_id = $user->getGroup();
+    $org_id = $user->org_id;
+
+    // First, mark a timesheet as submitted.
+    // Even if mail part below does not work, this will get us a functioning workflow
+    // (without email notifications).
+    $timesheet_id = $fields['timesheet_id'];
+    $sql = "update tt_timesheets set submit_status = 1".
+      " where id = $timesheet_id and group_id = $group_id and org_id = $org_id";
+    $affected = $mdb2->exec($sql);
+    if (is_a($affected, 'PEAR_Error')) return false;
+
+    // TODO: send email to approver here...
+    // $approver_id = $fields['approver_id'];
+
+    return true;
+  }
 }
index 6dd031c..4830b0e 100644 (file)
@@ -151,6 +151,8 @@ $i18n_key_words = array(
 // TODO: translate the following.
 // 'button.close' => 'Close',
 // 'button.stop' => 'Stop',
+// 'button.approve' => 'Approve',
+// 'button.disapprove' => 'Disapprove',
 
 // Labels for controls on forms. Labels in this section are used on multiple forms.
 // TODO: translate the following.
index 771a0f2..9cbdba5 100644 (file)
@@ -153,6 +153,8 @@ $i18n_key_words = array(
 // TODO: translate the following.
 // 'button.close' => 'Close',
 // 'button.stop' => 'Stop',
+// 'button.approve' => 'Approve',
+// 'button.disapprove' => 'Disapprove',
 
 // Labels for controls on forms. Labels in this section are used on multiple forms.
 // TODO: translate the following.
index 702958d..1d39ffb 100644 (file)
@@ -144,6 +144,9 @@ $i18n_key_words = array(
 'button.import' => 'Importer team', // TODO: replace "team" with "group".
 'button.close' => 'Luk',
 'button.stop' => 'Stop',
+// TODO: translate the following.
+// 'button.approve' => 'Approve',
+// 'button.disapprove' => 'Disapprove',
 
 // Labels for controls on forms. Labels in this section are used on multiple forms.
 'label.group_name' => 'Team navn', // TODO: replace "team" with "group".
index 257ab7e..6a967bd 100644 (file)
@@ -138,6 +138,9 @@ $i18n_key_words = array(
 'button.import' => 'Gruppe importieren',
 'button.close' => 'Schließen',
 'button.stop' => 'Stop',
+// TODO: translate the following.
+// 'button.approve' => 'Approve',
+// 'button.disapprove' => 'Disapprove',
 
 // Labels for controls on forms. Labels in this section are used on multiple forms.
 'label.group_name' => 'Gruppenname',
index 7621a83..0534e60 100644 (file)
@@ -131,6 +131,8 @@ $i18n_key_words = array(
 'button.import' => 'Import group',
 'button.close' => 'Close',
 'button.stop' => 'Stop',
+'button.approve' => 'Approve',
+'button.disapprove' => 'Disapprove',
 
 // Labels for controls on forms. Labels in this section are used on multiple forms.
 'label.group_name' => 'Group name',
index 19a8ed5..02d6215 100644 (file)
@@ -150,6 +150,8 @@ $i18n_key_words = array(
 // TODO: translate the following.
 // 'button.close' => 'Close',
 // 'button.stop' => 'Stop',
+// 'button.approve' => 'Approve',
+// 'button.disapprove' => 'Disapprove',
 
 // Labels for controls on forms. Labels in this section are used on multiple forms.
 // TODO: translate the following.
index 170c447..a5b4319 100644 (file)
@@ -152,6 +152,10 @@ $i18n_key_words = array(
 'button.import' => 'Impordi grupp',
 'button.close' => 'Sulge',
 'button.stop' => 'Stopp',
+// TODO: translate the following.
+// 'button.approve' => 'Approve',
+// 'button.disapprove' => 'Disapprove',
+
 
 // Labels for controls on forms. Labels in this section are used on multiple forms.
 'label.group_name' => 'Grupi nimi',
index 783a1b5..e4983f4 100644 (file)
@@ -151,6 +151,9 @@ $i18n_key_words = array(
 'button.import' => 'وارد کردن تیم', // TODO: replace "team" with "group".
 'button.close' => 'بستن',
 'button.stop' => 'توقف',
+// TODO: translate the following.
+// 'button.approve' => 'Approve',
+// 'button.disapprove' => 'Disapprove',
 
 // Labels for controls on forms. Labels in this section are used on multiple forms.
 'label.group_name' => 'نام تیم', // TODO: replace "team" with "group".
index 175c993..2dc50b6 100644 (file)
@@ -146,6 +146,9 @@ $i18n_key_words = array(
 'button.import' => 'Tuo tiimi', // TODO: replace "team" with "group".
 'button.close' => 'Sulje',
 'button.stop' => 'Lopeta',
+// TODO: translate the following.
+// 'button.approve' => 'Approve',
+// 'button.disapprove' => 'Disapprove',
 
 // Labels for controls on forms. Labels in this section are used on multiple forms.
 'label.group_name' => 'Tiimin nimi', // TODO: replace "team" with "group".
index 9d7a7c7..a26bba7 100644 (file)
@@ -144,6 +144,9 @@ $i18n_key_words = array(
 'button.import' => 'Importer une équipe', // TODO: replace "team" with "group".
 'button.close' => 'Fermer',
 'button.stop' => 'Arrêter',
+// TODO: translate the following.
+// 'button.approve' => 'Approve',
+// 'button.disapprove' => 'Disapprove',
 
 // Labels for controls on forms. Labels in this section are used on multiple forms.
 'label.group_name' => 'Nom équipe', // TODO: replace "team" with "group".
index 9e22079..0159ff2 100644 (file)
@@ -139,6 +139,9 @@ $i18n_key_words = array(
 'button.import' => 'Εισαγωγή ομάδας',
 'button.close' => 'Κλείσιμο',
 'button.stop' => 'Τέλος',
+// TODO: translate the following.
+// 'button.approve' => 'Approve',
+// 'button.disapprove' => 'Disapprove',
 
 // Labels for controls on forms. Labels in this section are used on multiple forms.
 'label.group_name' => 'Όνομα ομάδας',
index 3418d10..ab5d1b0 100644 (file)
@@ -161,6 +161,9 @@ $i18n_key_words = array(
 'button.import' => 'ייבא צוות', // TODO: replace "team" with "group".
 'button.close' => 'סגור',
 'button.stop' => 'עצור',
+// TODO: translate the following.
+// 'button.approve' => 'Approve',
+// 'button.disapprove' => 'Disapprove',
 
 // Labels for controls on forms. Labels in this section are used on multiple forms.
 'label.group_name' => 'שם הצוות', // TODO: replace "team" with "group".
index 9157dd6..521b42e 100644 (file)
@@ -152,6 +152,8 @@ $i18n_key_words = array(
 // TODO: translate the following.
 // 'button.close' => 'Close',
 // 'button.stop' => 'Stop',
+// 'button.approve' => 'Approve',
+// 'button.disapprove' => 'Disapprove',
 
 // Labels for controls on forms. Labels in this section are used on multiple forms.
 // TODO: translate the following.
index 51768b0..ca39ee6 100644 (file)
@@ -143,6 +143,9 @@ $i18n_key_words = array(
 'button.import' => 'Importa gruppo',
 'button.close' => 'Chiudi',
 'button.stop' => 'Stop',
+// TODO: translate the following.
+// 'button.approve' => 'Approve',
+// 'button.disapprove' => 'Disapprove',
 
 // Labels for controls on forms. Labels in this section are used on multiple forms.
 'label.group_name' => 'Nome del gruppo',
index e19c4a4..f85d3ef 100644 (file)
@@ -156,6 +156,8 @@ $i18n_key_words = array(
 // TODO: translate the following.
 // 'button.close' => 'Close',
 // 'button.stop' => 'Stop',
+// 'button.approve' => 'Approve',
+// 'button.disapprove' => 'Disapprove',
 
 // Labels for controls on forms. Labels in this section are used on multiple forms.
 // TODO: translate the following.
index 3999a09..ba9b4e0 100644 (file)
@@ -155,6 +155,8 @@ $i18n_key_words = array(
 // TODO: translate the following.
 // 'button.close' => 'Close',
 // 'button.stop' => 'Stop',
+// 'button.approve' => 'Approve',
+// 'button.disapprove' => 'Disapprove',
 
 // Labels for controls on forms. Labels in this section are used on multiple forms.
 // TODO: translate the following.
index 58fdd72..a416da5 100644 (file)
@@ -132,6 +132,9 @@ $i18n_key_words = array(
 'button.import' => 'Groep importeren',
 'button.close' => 'Sluiten',
 'button.stop' => 'Stop',
+// TODO: translate the following.
+// 'button.approve' => 'Approve',
+// 'button.disapprove' => 'Disapprove',
 
 // Labels for controls on forms. Labels in this section are used on multiple forms.
 'label.group_name' => 'Groepsnaam',
index 1a88ce3..3973be8 100644 (file)
@@ -156,6 +156,8 @@ $i18n_key_words = array(
 'button.close' => 'Lukk',
 // TODO: translate the following.
 // 'button.stop' => 'Stop',
+// 'button.approve' => 'Approve',
+// 'button.disapprove' => 'Disapprove',
 
 // Labels for controls on forms. Labels in this section are used on multiple forms.
 // TODO: translate the following.
index 8af61cf..0b867ff 100644 (file)
@@ -148,6 +148,9 @@ $i18n_key_words = array(
 'button.import' => 'Importuj zespół', // TODO: replace "team" with "group".
 'button.close' => 'Zamknij',
 'button.stop' => 'Zatrzymaj',
+// TODO: translate the following.
+// 'button.approve' => 'Approve',
+// 'button.disapprove' => 'Disapprove',
 
 // Labels for controls on forms. Labels in this section are used on multiple forms.
 'label.group_name' => 'Nazwa zespołu',  // TODO: replace "team" with "group".
index 018c771..3a48dec 100644 (file)
@@ -145,6 +145,9 @@ $i18n_key_words = array(
 'button.import' => 'Importar equipe', // TODO: replace "team" with "group".
 'button.close' => 'Fechar',
 'button.stop' => 'Parar',
+// TODO: translate the following.
+// 'button.approve' => 'Approve',
+// 'button.disapprove' => 'Disapprove',
 
 // Labels for controls on forms. Labels in this section are used on multiple forms.
 'label.group_name' => 'Nome da equipe',  // TODO: replace "team" with "group".
index d8f1611..6ee9363 100644 (file)
@@ -148,6 +148,8 @@ $i18n_key_words = array(
 // 'button.import' => 'Import group',
 // 'button.close' => 'Close',
 // 'button.stop' => 'Stop',
+// 'button.approve' => 'Approve',
+// 'button.disapprove' => 'Disapprove',
 
 // Labels for controls on forms. Labels in this section are used on multiple forms.
 // TODO: translate the following.
index 47ae568..f2bd2e9 100644 (file)
@@ -156,6 +156,8 @@ $i18n_key_words = array(
 // TODO: translate the following.
 // 'button.close' => 'Close',
 // 'button.stop' => 'Stop',
+// 'button.approve' => 'Approve',
+// 'button.disapprove' => 'Disapprove',
 
 // Labels for controls on forms. Labels in this section are used on multiple forms.
 // TODO: translate the following.
index 97cab56..b30765a 100644 (file)
@@ -130,6 +130,8 @@ $i18n_key_words = array(
 'button.import' => 'Импортировать группу',
 'button.close' => 'Закрыть',
 'button.stop' => 'Завершить',
+'button.approve' => 'Одобрить',
+'button.disapprove' => 'Не одобрить',
 
 // Labels for controls on forms. Labels in this section are used on multiple forms.
 'label.group_name' => 'Название группы',
index fa0df1d..f6a0a06 100644 (file)
@@ -152,6 +152,8 @@ $i18n_key_words = array(
 'button.close' => 'Zatvoriť',
 // TODO: translate the following.
 // 'button.stop' => 'Stop',
+// 'button.approve' => 'Approve',
+// 'button.disapprove' => 'Disapprove',
 
 // Labels for controls on forms. Labels in this section are used on multiple forms.
 'label.group_name' => 'Názov tímu', // TODO: replace "team" with "group".
index 620a8b8..0e602e4 100644 (file)
@@ -146,6 +146,8 @@ $i18n_key_words = array(
 // TODO: translate the following.
 // 'button.close' => 'Close',
 // 'button.stop' => 'Stop',
+// 'button.approve' => 'Approve',
+// 'button.disapprove' => 'Disapprove',
 
 // Labels for controls on forms. Labels in this section are used on multiple forms.
 // TODO: translate the following.
index 8163a80..cfc2dac 100644 (file)
@@ -145,6 +145,9 @@ $i18n_key_words = array(
 'button.import' => 'Uvezi tim', // TODO: replace "team" with "group".
 'button.close' => 'Zatvori',
 'button.stop' => 'Stani',
+// TODO: translate the following.
+// 'button.approve' => 'Approve',
+// 'button.disapprove' => 'Disapprove',
 
 // Labels for controls on forms. Labels in this section are used on multiple forms.
 'label.group_name' => 'Naziv tim-a', // TODO: replace "team" with "group".
index 8ddf01a..24072b0 100644 (file)
@@ -143,6 +143,9 @@ $i18n_key_words = array(
 'button.import' => 'Importera grupp',
 'button.close' => 'Stäng',
 'button.stop' => 'Avsluta',
+// TODO: translate the following.
+// 'button.approve' => 'Approve',
+// 'button.disapprove' => 'Disapprove',
 
 // Labels for controls on forms. Labels in this section are used on multiple forms.
 'label.group_name' => 'Namn på grupp',
index 4f82410..cccf336 100644 (file)
@@ -159,6 +159,8 @@ $i18n_key_words = array(
 // TODO: translate the following.
 // 'button.close' => 'Close',
 // 'button.stop' => 'Stop',
+// 'button.approve' => 'Approve',
+// 'button.disapprove' => 'Disapprove',
 
 // Labels for controls on forms. Labels in this section are used on multiple forms.
 // TODO: translate the following.
index 41e4acd..6f4f638 100644 (file)
@@ -147,6 +147,8 @@ $i18n_key_words = array(
 'button.close' => '关闭',
 // TODO: translate the following.
 // 'button.stop' => 'Stop',
+// 'button.approve' => 'Approve',
+// 'button.disapprove' => 'Disapprove',
 
 // Labels for controls on forms. Labels in this section are used on multiple forms.
 'label.group_name' => '团队名称', // TODO: replace "team" with "group".
index 5e58239..49bfb85 100644 (file)
@@ -152,6 +152,8 @@ $i18n_key_words = array(
 // TODO: translate the following.
 // 'button.close' => 'Close',
 // 'button.stop' => 'Stop',
+// 'button.approve' => 'Approve',
+// 'button.disapprove' => 'Disapprove',
 
 // Labels for controls on forms. Labels in this section are used on multiple forms.
 // TODO: translate the following.
index a84e324..71d49f2 100644 (file)
@@ -12,7 +12,7 @@
       <br>
       <table cellspacing="0" cellpadding="4" width="100%" border="0">
         <tr>
-          <td align="center">&nbsp;Anuko Time Tracker 1.18.37.4752 | Copyright &copy; <a href="https://www.anuko.com/lp/tt_3.htm" target="_blank">Anuko</a> |
+          <td align="center">&nbsp;Anuko Time Tracker 1.18.37.4753 | Copyright &copy; <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>
index c22df7a..1711443 100644 (file)
   {if $timesheet['submitter_comment']}
         <tr><td align="left"><b>{$i18n.label.comment}:</b> {$timesheet['submitter_comment']|escape}</td></tr>
   {/if}
+  {if $timesheet['submit_status']}
+        <tr><td align="left"><b>{$i18n.label.approved}:</b> {if $timesheet.approval_status != null}{if $timesheet.approval_status}{$i18n.label.yes}{else}{$i18n.label.no}{/if}</td></tr>{/if}
+  {/if}
+  {if $timesheet['manager_comment']}
+        <tr><td align="left"><b>{$i18n.label.note}:</b> {$timesheet['manager_comment']|escape}</td></tr>
+  {/if}
 {/if}
       </table>
     </td>
         <td nowrap class="cellRightAlignedSubtotal">{$user->currency|escape} {if $user->can('manage_invoices') || $user->isClient()}{$totals['cost']}{else}{$totals['expenses']}{/if}</td>
       </tr>
       </table>
+
+{$forms.timesheetForm.open}
+  {if $show_submit}
+  <table width="720" cellspacing="0" cellpadding="0" border="0">
+  <tr>
+    <td align="center">
+      <table>
+        <tr><td>{$i18n.form.mail.to}: {$forms.timesheetForm.approver.control} {$forms.timesheetForm.btn_submit.control}</td></tr>
+      </table>
+    </td>
+  </tr>
+  </table>
+  {/if}
+  {if $show_approve}
+  <table width="720" cellspacing="0" cellpadding="0" border="0">
+  <tr>
+    <td align="center">
+      <table>
+        <tr><td>{$forms.timesheetForm.btn_approve.control} {$forms.timesheetForm.btn_disapprove.control}</td></tr>
+      </table>
+    </td>
+  </tr>
+  </table>
+  {/if}
+{$forms.timesheetForm.close}
+
     </td>
   </tr>
 </table>
index bd97a52..0e9a7ea 100644 (file)
@@ -45,6 +45,7 @@ if (!$timesheet) {
   exit();
 }
 // TODO: add other checks here for timesheet being appropriate for user role.
+// TODO: if this is a timeheet submit, validate approver id, too.
 // End of access checks.
 
 $options = ttTimesheetHelper::getReportOptions($timesheet);
@@ -52,14 +53,54 @@ $subtotals = ttReportHelper::getSubtotals($options);
 $totals = ttReportHelper::getTotals($options);
 $notClient = !$user->isClient();
 
-// Determine managers we can submit this timesheet for approval to.
-$approvers = ttTimesheetHelper::getApprovers($timesheet['user_id']);
+// Determine which controls to show and obtain date for them.
+$showSubmit = $notClient && !$timesheet['submit_status'];
+if ($showSubmit) $approvers = ttTimesheetHelper::getApprovers($timesheet['user_id']);
+$canApprove = $user->can('approve_timesheets') || $user_>can('approve_all_timesheets');
+$showApprove = $notClient && $timesheet['submit_status'] && !$timesheet['approval_status'];
+
+// Add a form with controls.
+$form = new Form('timesheetForm');
+$form->addInput(array('type'=>'hidden','name'=>'id','value'=>$timesheet['id']));
+
+if ($showSubmit) {
+  if (count($approvers) >= 1) {
+    $form->addInput(array('type'=>'combobox',
+      'name'=>'approver',
+      'style'=>'width: 200px;',
+      'data'=>$approvers,
+      'datakeys'=>array('id','name')));
+  }
+  $form->addInput(array('type'=>'submit','name'=>'btn_submit','value'=>$i18n->get('button.submit')));
+}
+
+if ($showApprove) {
+  $form->addInput(array('type'=>'submit','name'=>'btn_approve','value'=>$i18n->get('button.approve')));
+  $form->addInput(array('type'=>'submit','name'=>'btn_disapprove','value'=>$i18n->get('button.disapprove')));
+}
+
+// Submit.
+if ($request->isPost()) {
+  if ($request->getParameter('btn_submit')) {
+    $fields = array('timesheet_id' => $timesheet['id'],
+      'approver_id' => $approver_id); // TODO: obtain (and check) approver id above during access checks.
+    if (ttTimesheetHelper::submitTimesheet($fields)) {
+      // Redirect to self.
+      header('Location: timesheet_view.php?id='.$timesheet['id']);
+      exit();
+    } else
+      $err->add($i18n->get('error.db'));
+  }
+}
 
 $smarty->assign('not_client', $notClient);
 $smarty->assign('group_by_header', ttReportHelper::makeGroupByHeader($options));
 $smarty->assign('timesheet', $timesheet);
 $smarty->assign('subtotals', $subtotals);
 $smarty->assign('totals', $totals);
+$smarty->assign('show_submit', $showSubmit);
+$smarty->assign('show_approve', $showApprove);
+$smarty->assign('forms', array($form->getName()=>$form->toArray()));
 $smarty->assign('title', $i18n->get('title.timesheet'));
 $smarty->assign('content_page_name', 'timesheet_view.tpl');
 $smarty->display('index.tpl');