Merge branch 'taskChanges' of https://github.com/avidenic/timetracker into avidenic...
authorNik Okuntseff <support@anuko.com>
Mon, 18 Jul 2016 18:16:52 +0000 (18:16 +0000)
committerNik Okuntseff <support@anuko.com>
Mon, 18 Jul 2016 18:16:52 +0000 (18:16 +0000)
Conflicts:
.gitignore
WEB-INF/config.php.dist

29 files changed:
.gitignore
WEB-INF/config.php.dist
WEB-INF/lib/ttTeamHelper.class.php
WEB-INF/lib/ttTimeHelper.class.php
WEB-INF/resources/de.lang.php
WEB-INF/resources/en.lang.php
WEB-INF/resources/es.lang.php
WEB-INF/resources/fa.lang.php
WEB-INF/resources/fi.lang.php
WEB-INF/resources/fr.lang.php
WEB-INF/resources/he.lang.php
WEB-INF/resources/nl.lang.php
WEB-INF/resources/pl.lang.php
WEB-INF/resources/pt-br.lang.php
WEB-INF/resources/ru.lang.php
WEB-INF/resources/sk.lang.php
WEB-INF/resources/sr.lang.php
WEB-INF/templates/cf_monthly_quota.tpl [new file with mode: 0644]
WEB-INF/templates/footer.tpl
WEB-INF/templates/profile_edit.tpl
WEB-INF/templates/reports.tpl
WEB-INF/templates/time.tpl
cf_monthly_quota.php [new file with mode: 0644]
dbinstall.php [changed mode: 0644->0755]
default.css
mysql.sql
plugins/MonthlyQuota.class.php [new file with mode: 0644]
profile_edit.php
time.php

index c1b019f..8f4d269 100644 (file)
@@ -4,4 +4,4 @@ WEB-INF/templates_c/*.png
 WEB-INF/lib/tcpdf/
 nbproject/
 upload/
-.vscode/
\ No newline at end of file
+.vscode/
index 5f7cf25..cc3d0cd 100644 (file)
@@ -215,5 +215,9 @@ define('AUTH_MODULE', 'db');
 
 // define('AUTH_DEBUG', false); // Note: enabling AUTH_DEBUG breaks redirects as debug output is printed before setting redirect header. Do not enable on production systems.
 
+// team manager can set monthly quota for years between these values:
+define('MONTHLY_QUOTA_YEARS_START', 2010); // if nothing is specified, it falls back to 2010
+define('MONTHLY_QUOTA_YEARS_END', 2031); // if nothing is specified it falls back to 2030
+
 // note input height in time.php
-define('NOTE_INPUT_HEIGHT', '40');
\ No newline at end of file
+define('NOTE_INPUT_HEIGHT', '40');
index e76482d..e91a794 100644 (file)
@@ -703,7 +703,7 @@ class ttTeamHelper {
   static function update($team_id, $fields)
   {
     // We'll require team name to be always set.
-    if (!isset($fields['name'])) return false;
+    if (!isset($fields['name']) || $fields['name'] == "") return false;
 
     $mdb2 = getConnection();
     $name_part = 'name = '.$mdb2->quote($fields['name']);
@@ -718,6 +718,7 @@ class ttTeamHelper {
     $record_type_part = '';
     $plugins_part = '';
     $lock_spec_part = '';
+    $working_hours_part = '';
 
     if (isset($fields['address'])) $addr_part = ', address = '.$mdb2->quote($fields['address']);
     if (isset($fields['currency'])) $currency_part = ', currency = '.$mdb2->quote($fields['currency']);
@@ -730,10 +731,11 @@ class ttTeamHelper {
     if (isset($fields['record_type'])) $record_type_part = ', record_type = '.intval($fields['record_type']);
     if (isset($fields['plugins'])) $plugins_part = ', plugins = '.$mdb2->quote($fields['plugins']);
     if (isset($fields['lock_spec'])) $lock_spec_part = ', lock_spec = '.$mdb2->quote($fields['lock_spec']);
+    if (isset($fields['working_hours'])) $working_hours_part = ', daily_working_hours = '.$mdb2->quote($fields['working_hours']);
 
     $sql = "update tt_teams set $name_part $addr_part $currency_part $lang_part $decimal_mark_part
       $date_format_part $time_format_part $week_start_part $tracking_mode_part $record_type_part
-      $plugins_part $lock_spec_part where id = $team_id";
+      $plugins_part $lock_spec_part $working_hours_part where id = $team_id";
     $affected = $mdb2->exec($sql);
     if (is_a($affected, 'PEAR_Error')) return false;
 
index 29b7414..f378d0c 100644 (file)
@@ -146,13 +146,10 @@ class ttTimeHelper {
     return (int)@$time_a[1] + ((int)@$time_a[0]) * 60;
   }
   
-  // toDuration - calculates duration between start and finish times in 00:00 format.
-  static function toDuration($start, $finish) {
-    $duration_minutes = ttTimeHelper::toMinutes($finish) - ttTimeHelper::toMinutes($start);
-    if ($duration_minutes <= 0) return false;
-    
-    $hours = (string)((int)($duration_minutes / 60));
-    $mins = (string)($duration_minutes % 60);
+  // fromMinutes - converts a number of minutes to format 00:00
+  static function fromMinutes($minutes){
+    $hours = (string)((int)($minutes / 60));
+    $mins = (string)(abs($minutes % 60));
     if (strlen($hours) == 1)
       $hours = '0'.$hours;
     if (strlen($mins) == 1)
@@ -160,6 +157,14 @@ class ttTimeHelper {
     return $hours.':'.$mins;
   }
   
+  // toDuration - calculates duration between start and finish times in 00:00 format.
+  static function toDuration($start, $finish) {
+    $duration_minutes = ttTimeHelper::toMinutes($finish) - ttTimeHelper::toMinutes($start);
+    if ($duration_minutes <= 0) return false;
+    
+    return ttTimeHelper::fromMinutes($duration_minutes);
+  }
+  
   // The to12HourFormat function converts a 24-hour time value (such as 15:23) to 12 hour format (03:23 PM).
   static function to12HourFormat($value) {
        if ('24:00' == $value) return '12:00 AM';
@@ -488,6 +493,21 @@ class ttTimeHelper {
     return 0;
   }
   
+  // getTimeForMonth - gets total time for a user for a given month.
+  static function getTimeForMonth($user_id, $date){
+    import('Period');
+    $mdb2 = getConnection();
+    
+    $period = new Period(INTERVAL_THIS_MONTH, $date);
+    $sql = "select sum(time_to_sec(duration)) as sm from tt_log where user_id = $user_id and date >= '".$period->getBeginDate(DB_DATEFORMAT)."' and date <= '".$period->getEndDate(DB_DATEFORMAT)."' and status = 1";
+    $res = $mdb2->query($sql);
+    if (!is_a($res, 'PEAR_Error')) {
+      $val = $res->fetchRow();
+      return sec_to_time_fmt_hm($val['sm']);
+    }
+    return 0;
+  }
+  
   // getUncompleted - retrieves an uncompleted record for user, if one exists.
   static function getUncompleted($user_id) {
     $mdb2 = getConnection();
index b3b37fd..ae05e9f 100644 (file)
@@ -154,6 +154,9 @@ $i18n_key_words = array(
 'label.cost' => 'Kosten',
 'label.week_total' => 'Summe (Woche)',
 'label.day_total' => 'Summe (Tag)',
+// 'label.month_total' => 'Month total',
+// 'label.month_left' => 'Time until quota is met',
+// 'label.month_over' => 'Over monthly quota',
 'label.today' => 'Heute',
 'label.total_hours' => 'Gesamtstunden',
 'label.total_cost' => 'Totale Kosten',
@@ -197,6 +200,11 @@ $i18n_key_words = array(
 // TODO: translate the following strings.
 // 'label.cron_schedule' => 'Cron schedule',
 // 'label.what_is_it' => 'What is it?',
+// 'label.year' => 'Year',
+// 'label.month' => 'Month',
+// 'label.quota' => 'Quota',
+// 'label.dailyWorkingHours' => 'Daily working hours',
+// 'label.empty_values_explanation' => 'If values are empty, quotas are calculated automatically based on holidays in config',
 
 // Form titles.
 'title.login' => 'Anmelden',
@@ -243,6 +251,7 @@ $i18n_key_words = array(
 // 'title.add_notification' => 'Adding Notification',
 // 'title.edit_notification' => 'Editing Notification',
 // 'title.delete_notification' => 'Deleting Notification',
+// 'title.monthly_quota' => 'Monthly quota',
 'title.export' => 'Daten exportieren',
 'title.import' => 'Daten importieren',
 'title.options' => 'Optionen',
index 8a4fe50..f82e543 100644 (file)
@@ -153,6 +153,9 @@ $i18n_key_words = array(
 'label.cost' => 'Cost',
 'label.week_total' => 'Week total',
 'label.day_total' => 'Day total',
+'label.month_total' => 'Month total',
+'label.month_left' => 'Time until quota is met',
+'label.month_over' => 'Over monthly quota',
 'label.today' => 'Today',
 'label.total_hours' => 'Total hours',
 'label.total_cost' => 'Total cost',
@@ -193,6 +196,11 @@ $i18n_key_words = array(
 'label.fav_report' => 'Favorite report',
 'label.cron_schedule' => 'Cron schedule',
 'label.what_is_it' => 'What is it?',
+'label.year' => 'Year',
+'label.month' => 'Month',
+'label.quota' => 'Quota',
+'label.dailyWorkingHours' => 'Daily working hours',
+'label.empty_values_explanation' => 'If values are empty, quotas are calculated automatically based on holidays in config',
 
 // Form titles.
 'title.login' => 'Login',
@@ -238,6 +246,7 @@ $i18n_key_words = array(
 'title.add_notification' => 'Adding Notification',
 'title.edit_notification' => 'Editing Notification',
 'title.delete_notification' => 'Deleting Notification',
+'title.monthly_quota' => 'Monthly quota',
 'title.export' => 'Exporting Team Data',
 'title.import' => 'Importing Team Data',
 'title.options' => 'Options',
index 153df47..3f1696e 100644 (file)
@@ -178,6 +178,9 @@ $i18n_key_words = array(
 // 'label.cost' => 'Cost',
 // 'label.week_total' => 'Week total',
 // 'label.day_total' => 'Day total',
+// 'label.month_total' => 'Month total',
+// 'label.month_left' => 'Time until quota is met',
+// 'label.month_over' => 'Over monthly quota',
 'label.today' => 'Hoy',
 'label.total_hours' => 'Horas totales',
 // TODO: translate the following strings.
@@ -228,6 +231,11 @@ $i18n_key_words = array(
 // TODO: translate the following strings.
 // 'label.cron_schedule' => 'Cron schedule',
 // 'label.what_is_it' => 'What is it?',
+// 'label.year' => 'Year',
+// 'label.month' => 'Month',
+// 'label.quota' => 'Quota',
+// 'label.dailyWorkingHours' => 'Daily working hours',
+// 'label.empty_values_explanation' => 'If values are empty, quotas are calculated automatically based on holidays in config',
 
 // Form titles.
 'title.login' => 'SesiĆ³n iniciada',
@@ -281,6 +289,7 @@ $i18n_key_words = array(
 // 'title.add_notification' => 'Adding Notification',
 // 'title.edit_notification' => 'Editing Notification',
 // 'title.delete_notification' => 'Deleting Notification',
+// 'title.monthly_quota' => 'Monthly quota',
 'title.export' => 'Exportar datos',
 'title.import' => 'Importar datos',
 'title.options' => 'Opciones',
index 22b04cb..acdb8f0 100644 (file)
@@ -162,6 +162,9 @@ $i18n_key_words = array(
 'label.cost' => 'Ł‡Ų²ŪŒŁ†Ł‡',
 'label.week_total' => 'Ś©Ł„ Ł‡ŁŲŖŁ‡',
 'label.day_total' => 'Ś©Ł„ Ų±ŁˆŲ²',
+// 'label.month_total' => 'Month total',
+// 'label.month_left' => 'Time until quota is met',
+// 'label.month_over' => 'Over monthly quota',
 'label.today' => 'Ų§Ł…Ų±ŁˆŲ²',
 'label.total_hours' => 'Ł…Ų¬Ł…ŁˆŲ¹ Ų³Ų§Ų¹ŲŖ',
 'label.total_cost' => 'Ł…Ų¬Ł…ŁˆŲ¹ Ł‡Ų²ŪŒŁ†Ł‡ Ł‡Ų§',
@@ -205,6 +208,11 @@ $i18n_key_words = array(
 // TODO: translate the following strings.
 // 'label.cron_schedule' => 'Cron schedule',
 // 'label.what_is_it' => 'What is it?',
+// 'label.year' => 'Year',
+// 'label.month' => 'Month',
+// 'label.quota' => 'Quota',
+// 'label.dailyWorkingHours' => 'Daily working hours',
+// 'label.empty_values_explanation' => 'If values are empty, quotas are calculated automatically based on holidays in config',
 
 // Form titles.
 'title.login' => 'ŁˆŲ±ŁˆŲÆ',
@@ -252,6 +260,7 @@ $i18n_key_words = array(
 // 'title.add_notification' => 'Adding Notification',
 // 'title.edit_notification' => 'Editing Notification',
 // 'title.delete_notification' => 'Deleting Notification',
+// 'title.monthly_quota' => 'Monthly quota',
 'title.export' => 'Ł¾Ų“ŲŖŪŒŲ§Ł†ŪŒ ŚÆŲ±ŁŲŖŁ† Ų§Ų² Ų§Ų·Ł„Ų§Ų¹Ų§ŲŖ ŲŖŪŒŁ…',
 'title.import' => 'ŁˆŲ§Ų±ŲÆ Ś©Ų±ŲÆŁ† Ų§Ų·Ł„Ų§Ų¹Ų§ŲŖ ŲŖŪŒŁ…',
 'title.options' => 'ŚÆŲ²ŪŒŁ†Ł‡ Ł‡Ų§',
index b0a022d..ee94cad 100644 (file)
@@ -151,6 +151,9 @@ $i18n_key_words = array(
 'label.cost' => 'Hinta',
 'label.week_total' => 'Viikko yhteensƤ',
 'label.day_total' => 'PƤivƤ yhteensƤ',
+// 'label.month_total' => 'Month total',
+// 'label.month_left' => 'Time until quota is met',
+// 'label.month_over' => 'Over monthly quota',
 'label.today' => 'TƤnƤƤn',
 'label.total_hours' => 'Tunnit yhteensƤ',
 'label.total_cost' => 'Hinta yhteensƤ',
@@ -191,6 +194,11 @@ $i18n_key_words = array(
 'label.fav_report' => 'Raporttipohja',
 'label.cron_schedule' => 'Cron-ajoitus',
 'label.what_is_it' => 'MikƤ se on?',
+// 'label.year' => 'Year',
+// 'label.month' => 'Month',
+// 'label.quota' => 'Quota',
+// 'label.dailyWorkingHours' => 'Daily working hours',
+// 'label.empty_values_explanation' => 'If values are empty, quotas are calculated automatically based on holidays in config',
 
 // Form titles.
 'title.login' => 'Kirjautuminen',
@@ -236,6 +244,7 @@ $i18n_key_words = array(
 'title.add_notification' => 'Ilmoituksen lisƤys',
 'title.edit_notification' => 'Ilmoituksen muokkaus',
 'title.delete_notification' => 'Ilmoituksen poisto',
+// 'title.monthly_quota' => 'Monthly quota',
 'title.export' => 'Tiimitietojen vienti',
 'title.import' => 'Tiimitietojen tunti',
 'title.options' => 'Optiot',
index 52b17db..ee899fa 100644 (file)
@@ -166,6 +166,9 @@ $i18n_key_words = array(
 'label.week_total' => 'Total hebdomadaire',
 // TODO: translate the following string.
 // 'label.day_total' => 'Day total',
+// 'label.month_total' => 'Month total',
+// 'label.month_left' => 'Time until quota is met',
+// 'label.month_over' => 'Over monthly quota',
 'label.today' => 'Aujourd\\\'hui',
 'label.total_hours' => 'Total d\\\'heures',
 'label.total_cost' => 'CoĆ»t total',
@@ -210,6 +213,11 @@ $i18n_key_words = array(
 // TODO: translate the following strings.
 // 'label.cron_schedule' => 'Cron schedule',
 // 'label.what_is_it' => 'What is it?',
+// 'label.year' => 'Year',
+// 'label.month' => 'Month',
+// 'label.quota' => 'Quota',
+// 'label.dailyWorkingHours' => 'Daily working hours',
+// 'label.empty_values_explanation' => 'If values are empty, quotas are calculated automatically based on holidays in config',
 
 // Form titles.
 'title.login' => 'Connexion',
@@ -260,6 +268,7 @@ $i18n_key_words = array(
 // 'title.add_notification' => 'Adding Notification',
 // 'title.edit_notification' => 'Editing Notification',
 // 'title.delete_notification' => 'Deleting Notification',
+// 'title.monthly_quota' => 'Monthly quota',
 'title.export' => 'Exporter les donnĆ©es', // TODO: use a noun.
 'title.import' => 'Importer les donnĆ©es', // TODO: use a noun.
 'title.options' => 'Options',
index 698a92b..ce02dba 100644 (file)
@@ -167,6 +167,9 @@ $i18n_key_words = array(
 'label.cost' => 'עלו×Ŗ',
 'label.week_total' => 'היכום ×©×‘ועי',
 'label.day_total' => 'היכום ×™×•×ž×™',
+// 'label.month_total' => 'Month total',
+// 'label.month_left' => 'Time until quota is met',
+// 'label.month_over' => 'Over monthly quota',
 'label.today' => 'היום',
 'label.total_hours' => 'הך ×”כל ×©×¢×•×Ŗ',
 'label.total_cost' => 'הך ×”כל ×¢×œ×•×Ŗ',
@@ -210,6 +213,11 @@ $i18n_key_words = array(
 // TODO: translate the following strings.
 // 'label.cron_schedule' => 'Cron schedule',
 // 'label.what_is_it' => 'What is it?',
+// 'label.year' => 'Year',
+// 'label.month' => 'Month',
+// 'label.quota' => 'Quota',
+// 'label.dailyWorkingHours' => 'Daily working hours',
+// 'label.empty_values_explanation' => 'If values are empty, quotas are calculated automatically based on holidays in config',
 
 // Form titles.
 'title.login' => '×›× ×™×”×”',
@@ -258,6 +266,7 @@ $i18n_key_words = array(
 // 'title.add_notification' => 'Adding Notification',
 // 'title.edit_notification' => 'Editing Notification',
 // 'title.delete_notification' => 'Deleting Notification',
+// 'title.monthly_quota' => 'Monthly quota',
 'title.export' => 'ייצוא × ×Ŗוני ×¦×•×•×Ŗ',
 'title.import' => 'ייבוא × ×Ŗוני ×¦×•×•×Ŗ',
 'title.options' => 'אפש×Øויו×Ŗ',
index e2a9dd0..b2ad9bf 100644 (file)
@@ -152,6 +152,9 @@ $i18n_key_words = array(
 'label.cost' => 'Kosten',
 'label.week_total' => 'Week totaal',
 'label.day_total' => 'Dag totaal',
+// 'label.month_total' => 'Month total',
+// 'label.month_left' => 'Time until quota is met',
+// 'label.month_over' => 'Over monthly quota',
 'label.today' => 'Vandaag',
 'label.total_hours' => 'Uren totaal',
 'label.total_cost' => 'Totale kosten',
@@ -192,6 +195,11 @@ $i18n_key_words = array(
 'label.fav_report' => 'Standaard rapport',
 'label.cron_schedule' => 'Cron schema',
 'label.what_is_it' => 'Wat betekent dit?',
+// 'label.year' => 'Year',
+// 'label.month' => 'Month',
+// 'label.quota' => 'Quota',
+// 'label.dailyWorkingHours' => 'Daily working hours',
+// 'label.empty_values_explanation' => 'If values are empty, quotas are calculated automatically based on holidays in config',
 
 // Form titles.
 'title.login' => 'Aanmelden',
@@ -237,6 +245,7 @@ $i18n_key_words = array(
 'title.add_notification' => 'Notificatie toevoegen',
 'title.edit_notification' => 'Notificatie bewerken',
 'title.delete_notification' => 'Notificatie verwijderen',
+// 'title.monthly_quota' => 'Monthly quota',
 'title.export' => 'Exporteer teamgegevens',
 'title.import' => 'Importeer teamgegevens',
 'title.options' => 'Opties',
index b2abad4..35a5355 100644 (file)
@@ -156,6 +156,9 @@ $i18n_key_words = array(
 'label.cost' => 'Koszt',
 'label.week_total' => 'W tym tygodniu',
 'label.day_total' => 'Dziś',
+// 'label.month_total' => 'Month total',
+// 'label.month_left' => 'Time until quota is met',
+// 'label.month_over' => 'Over monthly quota',
 'label.today' => 'Dziś',
 'label.total_hours' => 'Całkowita liczba godzin',
 'label.total_cost' => 'Koszt całkowity',
@@ -197,6 +200,11 @@ $i18n_key_words = array(
 'label.fav_report' => 'Ulubiony raport',
 'label.cron_schedule' => 'Harmonogram crona',
 'label.what_is_it' => 'Co to jest?',
+// 'label.year' => 'Year',
+// 'label.month' => 'Month',
+// 'label.quota' => 'Quota',
+// 'label.dailyWorkingHours' => 'Daily working hours',
+// 'label.empty_values_explanation' => 'If values are empty, quotas are calculated automatically based on holidays in config',
 
 // Form titles.
 'title.login' => 'Logowanie',
@@ -242,6 +250,7 @@ $i18n_key_words = array(
 'title.add_notification' => 'Dodawanie powiadomienia',
 'title.edit_notification' => 'Edytowanie powiadomienia',
 'title.delete_notification' => 'Usuwanie powiadomienia',
+// 'title.monthly_quota' => 'Monthly quota',
 'title.export' => 'Eksport danych zespołu',
 'title.import' => 'Import danych zespołu',
 'title.options' => 'Opcje',
index 1dd84e3..021c138 100644 (file)
@@ -151,6 +151,9 @@ $i18n_key_words = array(
 'label.cost' => 'Custo',
 'label.week_total' => 'Total semanal',
 'label.day_total' => 'Total diĆ”rio',
+// 'label.month_total' => 'Month total',
+// 'label.month_left' => 'Time until quota is met',
+// 'label.month_over' => 'Over monthly quota',
 'label.today' => 'Hoje',
 'label.total_hours' => 'Total de horas',
 'label.total_cost' => 'Custo total',
@@ -191,6 +194,11 @@ $i18n_key_words = array(
 'label.fav_report' => 'RelatĆ³rio favorito',
 'label.cron_schedule' => 'Agenda cron',
 'label.what_is_it' => 'O que Ć©?',
+// 'label.year' => 'Year',
+// 'label.month' => 'Month',
+// 'label.quota' => 'Quota',
+// 'label.dailyWorkingHours' => 'Daily working hours',
+// 'label.empty_values_explanation' => 'If values are empty, quotas are calculated automatically based on holidays in config',
 
 // Form titles.
 'title.login' => 'Login',
@@ -236,6 +244,7 @@ $i18n_key_words = array(
 'title.add_notification' => 'Adicionando notificaĆ§Ć£o',
 'title.edit_notification' => 'Editando notificaĆ§Ć£o',
 'title.delete_notification' => 'Apagando notificaĆ§Ć£o',
+// 'title.monthly_quota' => 'Monthly quota',
 'title.export' => 'Exportando dados de equipe',
 'title.import' => 'Importando dados de equipe',
 'title.options' => 'OpƧƵes',
index 9c99e14..0c99c07 100644 (file)
@@ -153,6 +153,9 @@ $i18n_key_words = array(
 'label.cost' => 'Š”тŠ¾ŠøŠ¼Š¾ŃŃ‚ŃŒ',
 'label.week_total' => 'Š˜Ń‚Š¾Š³ Š·Š° Š½ŠµŠ“ŠµŠ»ŃŽ',
 'label.day_total' => 'Š˜Ń‚Š¾Š³ Š·Š° Š“ŠµŠ½ŃŒ',
+// 'label.month_total' => 'Month total',
+// 'label.month_left' => 'Time until quota is met',
+// 'label.month_over' => 'Over monthly quota',
 'label.today' => 'Š”ŠµŠ³Š¾Š“Š½Ń',
 'label.total_hours' => 'Š˜Ń‚Š¾Š³Š¾ Ń‡Š°ŃŠ¾Š²',
 'label.total_cost' => 'Š˜Ń‚Š¾Š³Š¾Š²Š°Ń ŃŃ‚Š¾ŠøŠ¼Š¾ŃŃ‚ŃŒ',
@@ -193,6 +196,11 @@ $i18n_key_words = array(
 'label.fav_report' => 'Š”тŠ°Š½Š“Š°Ń€Ń‚Š½Ń‹Š¹ Š¾Ń‚чёт',
 'label.cron_schedule' => 'Š Š°ŃŠæŠøсŠ°Š½ŠøŠµ cron',
 'label.what_is_it' => 'Š§Ń‚Š¾ ŃŃ‚Š¾?',
+// 'label.year' => 'Year',
+// 'label.month' => 'Month',
+// 'label.quota' => 'Quota',
+// 'label.dailyWorkingHours' => 'Daily working hours',
+// 'label.empty_values_explanation' => 'If values are empty, quotas are calculated automatically based on holidays in config',
 
 // Form titles.
 'title.login' => 'Š’Ń…Š¾Š“ Š² ŃŠøстŠµŠ¼Ńƒ',
@@ -238,6 +246,7 @@ $i18n_key_words = array(
 'title.add_notification' => 'Š”Š¾Š±Š°Š²Š»ŠµŠ½ŠøŠµ ŃƒŠ²ŠµŠ“Š¾Š¼Š»ŠµŠ½Šøя',
 'title.edit_notification' => 'Š ŠµŠ“Š°ŠŗтŠøрŠ¾Š²Š°Š½ŠøŠµ ŃƒŠ²ŠµŠ“Š¾Š¼Š»ŠµŠ½Šøя',
 'title.delete_notification' => 'Š£Š“Š°Š»ŠµŠ½ŠøŠµ ŃƒŠ²ŠµŠ“Š¾Š¼Š»ŠµŠ½Šøя',
+// 'title.monthly_quota' => 'Monthly quota',
 'title.export' => 'Š­ŠŗсŠæŠ¾Ń€Ń‚ŠøрŠ¾Š²Š°Š½ŠøŠµ Š“Š°Š½Š½Ń‹Ń… ŠŗŠ¾Š¼Š°Š½Š“ы',
 'title.import' => 'Š˜Š¼ŠæŠ¾Ń€Ń‚ŠøрŠ¾Š²Š°Š½ŠøŠµ Š“Š°Š½Š½Ń‹Ń… ŠŗŠ¾Š¼Š°Š½Š“ы',
 'title.options' => 'ŠžŠæцŠøŠø',
index bd3abd8..c8479ad 100644 (file)
@@ -164,6 +164,9 @@ $i18n_key_words = array(
 'label.week_total' => 'TĆ½Å¾deň celkom',
 // TODO: translate the following string.
 // 'label.day_total' => 'Day total',
+// 'label.month_total' => 'Month total',
+// 'label.month_left' => 'Time until quota is met',
+// 'label.month_over' => 'Over monthly quota',
 'label.today' => 'Dnes',
 'label.total_hours' => 'HodĆ­n celkom',
 'label.total_cost' => 'NĆ”klady celkom',
@@ -207,6 +210,11 @@ $i18n_key_words = array(
 // TODO: translate the following strings.
 // 'label.cron_schedule' => 'Cron schedule',
 // 'label.what_is_it' => 'What is it?',
+// 'label.year' => 'Year',
+// 'label.month' => 'Month',
+// 'label.quota' => 'Quota',
+// 'label.dailyWorkingHours' => 'Daily working hours',
+// 'label.empty_values_explanation' => 'If values are empty, quotas are calculated automatically based on holidays in config',
 
 // Form titles.
 'title.login' => 'PrihlĆ”senie',
@@ -255,6 +263,7 @@ $i18n_key_words = array(
 // 'title.add_notification' => 'Adding Notification',
 // 'title.edit_notification' => 'Editing Notification',
 // 'title.delete_notification' => 'Deleting Notification',
+// 'title.monthly_quota' => 'Monthly quota',
 'title.export' => 'Exportovanie Ćŗdajov o tĆ­me',
 'title.import' => 'Importovanie Ćŗdajov o tĆ­me',
 'title.options' => 'Nastavenia',
index 8e42f96..546515b 100644 (file)
@@ -153,6 +153,9 @@ $i18n_key_words = array(
 'label.cost' => 'Cena',
 'label.week_total' => 'Zbir Äasova nedeljno',
 'label.day_total' => 'Zbir Äasova dnevno',
+// 'label.month_total' => 'Month total',
+// 'label.month_left' => 'Time until quota is met',
+// 'label.month_over' => 'Over monthly quota',
 'label.today' => 'Danas',
 'label.total_hours' => 'Ukupno Äasova',
 'label.total_cost' => 'Ukupna cena',
@@ -193,6 +196,11 @@ $i18n_key_words = array(
 'label.fav_report' => 'Omiljeni izveÅ”taji',
 'label.cron_schedule' => 'Sredi raspored',
 'label.what_is_it' => 'Å ta je ovo?',
+// 'label.year' => 'Year',
+// 'label.month' => 'Month',
+// 'label.quota' => 'Quota',
+// 'label.dailyWorkingHours' => 'Daily working hours',
+// 'label.empty_values_explanation' => 'If values are empty, quotas are calculated automatically based on holidays in config',
 
 // Form titles.
 'title.login' => 'Prijava',
@@ -238,6 +246,7 @@ $i18n_key_words = array(
 'title.add_notification' => 'Dodavanje napomene',
 'title.edit_notification' => 'Izmena napomene',
 'title.delete_notification' => 'Brisanje napomene',
+// 'title.monthly_quota' => 'Monthly quota',
 'title.export' => 'Izvoz podataka tim-a',
 'title.import' => 'Uvoz podataka tim-a',
 'title.options' => 'Opcije',
diff --git a/WEB-INF/templates/cf_monthly_quota.tpl b/WEB-INF/templates/cf_monthly_quota.tpl
new file mode 100644 (file)
index 0000000..6e3d9fe
--- /dev/null
@@ -0,0 +1,59 @@
+{$forms.monthlyQuotaForm.open}
+<div style="padding: 0 0 10 0">
+    <table border="0" class="divider">
+        <tr>
+            <td align="center">
+                <table>
+                    <tr>
+                        <td>{$i18n.label.dailyWorkingHours}</td>
+                        <td>{$forms.monthlyQuotaForm.dailyWorkingHours.control}</td>
+                        <td><input type="submit" name="dailyHours" value="{$i18n.button.save}"></td>
+                    </tr>
+                </table>
+            </td>
+        </tr>        
+    </table>
+</div>
+<table>
+    <tr>
+        <td>{$i18n.label.year}:</td>
+        <td>{$forms.monthlyQuotaForm.years.control}</td>
+    </tr>
+    <tr>
+        <td colspan="2">
+            <table> 
+            <tr>
+                <td class="tableHeader">{$i18n.label.month}</td>
+                <td class="tableHeader">{$i18n.label.quota}</td>
+            </tr>
+ {foreach $months as $month}
+                <tr>
+                    <td>{$month}</td>
+                    <td>{$forms.monthlyQuotaForm.$month.control}</td>
+                </tr>
+ {/foreach}     
+                <tr>
+                    <td colspan="2" style="text-align:center;">
+                        <input type="submit" name="quotas" value="{$i18n.button.save}*">
+                    </td>
+                </tr>
+            </table>
+        </td>
+    </tr>
+</table>
+<div>* - {$i18n.label.empty_values_explanation}</div>
+{$forms.monthlyQuotaForm.close}
+<script>
+function yearChange(value){
+    var url = window.location.href;
+    
+    if (url.indexOf('?') > 0){
+        var parameter = url.substring(url.indexOf('?') + 1, url.length);
+        url = url.replace(parameter, 'year=' + value);
+    } else {
+        url = '?year=' + value;
+    }
+    
+    window.location = url;
+}
+</script>
\ No newline at end of file
index f9a7edd..5905435 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.9.25.3499 | Copyright &copy; <a href="https://www.anuko.com/lp/tt_3.htm" target="_blank">Anuko</a> |
+          <td align="center">&nbsp;Anuko Time Tracker 1.9.26.3503 | 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 8d68c3e..2da8041 100644 (file)
@@ -52,6 +52,15 @@ function handlePluginCheckboxes() {
   } else {
     configureLabel.style.visibility = "hidden";
   }
+  
+  var monthlyQuotaCheckBox = document.getElementById("monthly_quota");
+  configureLabel = document.getElementById("monthly_quota_config");
+  if (monthlyQuotaCheckBox.checked){
+    configureLabel.style.visibility = "visible";
+  } else {
+  configureLabel.style.visibility = "hidden";
+  }
+  
 }
 </script>
 
@@ -181,7 +190,11 @@ function handlePluginCheckboxes() {
           </tr>
           <tr>
             <td align="right" nowrap>{$forms.profileForm.locking.control}</td>
-            <td><label for="notifications">{$i18n.title.locking}</label> <span id="locking_config"><a href="locking.php">{$i18n.label.configure}</a></span></td>
+            <td><label for="locking">{$i18n.title.locking}</label> <span id="locking_config"><a href="locking.php">{$i18n.label.configure}</a></span></td>
+          </tr>
+          <tr>
+            <td align="right" nowrap>{$forms.profileForm.monthly_quota.control}</td>
+            <td><label for="monthly_quota">{$i18n.title.monthly_quota}</label> <span id="monthly_quota_config"><a href="cf_monthly_quota.php">{$i18n.label.configure}</a></span></td>
           </tr>
 {/if}
 
index bd5ab3f..ce804dc 100644 (file)
@@ -149,7 +149,7 @@ function handleCheckboxes() {
 
 {$forms.reportForm.open}
 <div style="padding: 0 0 10 0;">
-  <table border="0" bgcolor="#efefef" width="720">
+  <table border="0" class="divider">
     <tr>
       <td>
         <table cellspacing="1" cellpadding="3" border="0">
@@ -278,7 +278,7 @@ function handleCheckboxes() {
       </table>
 
 <div style="padding: 10 0 10 0;">
-  <table border="0" bgcolor="#efefef" width="720">
+  <table border="0" class="divider">
     <tr>
       <td align="center">
         <table cellspacing="1" cellpadding="3" border="0">
index 80b111b..d526c46 100644 (file)
@@ -354,6 +354,16 @@ function get_time() {
     <td align="left">{$i18n.label.week_total}: {$week_total}</td>
     <td align="right">{$i18n.label.day_total}: {$day_total}</td>
   </tr>
+  {if $month_total}
+  <tr>
+    <td align="left">{$i18n.label.month_total}: {$month_total}</td>
+    {if $month_left|strpos:'-' === 0}
+    <td align="right">{$i18n.label.month_over}: <span style="color: green;">{$month_left|substr:1}</span></td>
+    {else}
+    <td align="right">{$i18n.label.month_left}: <span style="color: red;">{$month_left}</span></td>
+    {/if}
+  </tr>
+  {/if}
 </table>
 {/if}
 {$forms.timeRecordForm.close}
diff --git a/cf_monthly_quota.php b/cf_monthly_quota.php
new file mode 100644 (file)
index 0000000..19b43bf
--- /dev/null
@@ -0,0 +1,88 @@
+<?php
+
+require_once('initialize.php');
+require_once('plugins/MonthlyQuota.class.php');
+import('form.Form');
+import('WEB-INF/lib/ttTeamHelper');
+
+// Access check.
+if (!ttAccessCheck(right_manage_team)) {
+  header('Location: access_denied.php');
+  exit();
+}
+
+// fallback values
+$yearStart = 2010;
+$yearEnd = 2030;
+
+if (defined('MONTHLY_QUOTA_YEARS_START')){
+  $yearStart = (int)MONTHLY_QUOTA_YEARS_START;
+}
+if (defined('MONTHLY_QUOTA_YEARS_END')){
+  $yearEnd = (int)MONTHLY_QUOTA_YEARS_END;
+}
+
+// create values for dropdown
+$years = array();
+for ($i=$yearStart; $i < $yearEnd; $i++) { 
+  array_push($years, array('id'=>$i, 'name'=>$i));
+}
+
+// get selected year from url parameters
+$selectedYear = $request->getParameter("year");
+if (!$selectedYear or !ttValidInteger($selectedYear)){
+  $selectedYear = date("Y");
+} else {
+  $selectedYear = intval($selectedYear);
+}
+
+// months are zero indexed
+$months = $i18n->monthNames;
+
+$quota = new MonthlyQuota();
+
+if ($request->isPost()){
+  $res = false;
+  // if user pressed save fpr monthly quotas
+  if ($_POST["quotas"]){
+    $postedYear = $request->getParameter("years");
+    $selectedYear = intval($postedYear);
+    for ($i=0; $i < count($months); $i++){
+      $res = $quota->update($postedYear, $i+1, $request->getParameter($months[$i]));
+    }
+  }
+  // if user saved required working hours for a day
+  if ($_POST["dailyHours"]){
+    $hours = $request->getParameter("dailyWorkingHours");
+    $teamDetails = ttTeamHelper::getTeamDetails($quota->usersTeamId);
+    $res = ttTeamHelper::update($quota->usersTeamId, array('name'=>$teamDetails['team_name'], 
+                                                           'working_hours'=>$hours));
+  }
+  if ($res) {
+    header('Location: profile_edit.php');
+    exit();
+  } else {
+    $err->add($i18n->getKey('error.db'));
+  }
+}
+
+// returns months where January is month 1, not 0
+$monthsData = $quota->get($selectedYear);
+
+$form = new Form('monthlyQuotaForm');
+
+$form->addInput(array('type'=>'combobox', 'name'=>'years', 'data'=>$years, 'datakeys'=>array('id', 'name'), 'value'=>$selectedYear, 'onchange'=>'yearChange(this.value);'));
+for ($i=0; $i < count($months); $i++) { 
+  $value = "";
+  if (array_key_exists($i+1, $monthsData)){
+    $value = $monthsData[$i+1];
+  }
+  $name = $months[$i];
+  $form->addInput(array('type'=>'text', 'name'=>$name, 'maxlength'=>3, 'value'=> $value, 'style'=>'width:50px'));
+}
+$form->addInput(array('type'=>'text', 'name'=>'dailyWorkingHours', 'value'=>$quota->getDailyWorkingHours(), 'style'=>'width:50px'));
+$smarty->assign('months', $months);
+$smarty->assign('forms', array($form->getName()=>$form->toArray()));
+$smarty->assign('title', $i18n->getKey('title.monthly_quota'));
+$smarty->assign('content_page_name', 'cf_monthly_quota.tpl');
+$smarty->display('index.tpl');
old mode 100644 (file)
new mode 100755 (executable)
index 39c967a..4a7bcd1
@@ -42,6 +42,99 @@ function setChange($sql) {
     print "successful update<br>\n";
 }
 
+if (!$request->isPost()) {
+  echo('<h2>Environment check</h2>');
+
+  // Check if WEB-INF/templates_c dir is writable.
+  if (is_writable(APP_DIR.'/WEB-INF/templates_c/')) {
+    echo('WEB-INF/templates_c/ directory is writable.<br>');
+  } else {
+    echo('<font color="red">Error: WEB-INF/templates_c/ directory is not writable.</font><br>');
+  }
+
+  // Require the configuration file with application settings.
+  if (file_exists(APP_DIR."/WEB-INF/config.php")) {
+    echo('WEB-INF/config.php file exists.<br>');
+  } else {
+    echo('<font color="red">Error: WEB-INF/config.php file does not exist.</font><br>');
+  }
+
+  // Check whether DSN is defined.
+  if (defined('DSN')) {
+    // echo('DSN is defined as '.DSN.'<br>');
+    echo('DSN is defined.<br>');
+  } else {
+    echo('<font color="red">Error: DSN value is not defined. Check your config.php file.</font><br>');
+  }
+
+  // Depending on DSN, require either mysqli or mysql extensions.
+  if (strrpos(DSN, 'mysqli://', -strlen(DSN)) !== FALSE) {
+    if (extension_loaded('mysqli')) {
+      echo('mysqli PHP extension is loaded.<br>');
+    } else {
+      echo('<font color="red">Error: mysqli PHP extension is required but is not loaded.</font><br>');
+    }
+  }
+  if (strrpos(DSN, 'mysql://', -strlen(DSN)) !== FALSE) {
+    if (extension_loaded('mysql')) {
+      echo('mysql PHP extension is loaded.<br>');
+    } else {
+      echo('<font color="red">Error: mysql PHP extension is required but is not loaded.</font><br>');
+    }
+  }
+
+  // Check mbstring extension.
+  if (extension_loaded('mbstring')) {
+    echo('mbstring PHP extension is loaded.<br>');
+  } else {
+    echo('<font color="red">Error: mbstring PHP extension is not loaded.</font><br>');
+  }
+
+  // Check gd extension.
+  if (extension_loaded('gd')) {
+    echo('gd PHP extension is loaded.<br>');
+  } else {
+    echo('<font color="red">Error: gd PHP extension is not loaded. It is required for charts plugin.</font><br>');
+  }
+
+  // Check ldap extension.
+  if (AUTH_MODULE == 'ldap') {
+    if (extension_loaded('ldap_')) {
+      echo('ldap PHP extension is loaded.<br>');
+    } else {
+      echo('<font color="red">Error: ldap PHP extension is not loaded. It is required for LDAP authentication.</font><br>');
+    }
+  }
+
+  // Check database access.
+  require_once('MDB2.php');
+  $conn = MDB2::connect(DSN);
+  if (!is_a($conn, 'MDB2_Error')) {
+    echo('Connection to database successful.<br>');
+  } else {
+    die('<font color="red">Error: connection to database failed. '.$conn->getMessage().'</font><br>');
+  }
+
+  $conn->setOption('debug', true);
+  $conn->setFetchMode(MDB2_FETCHMODE_ASSOC);
+
+  $sql = "show tables";
+  $res = $conn->query($sql);
+  if (is_a($res, 'MDB2_Error')) {
+    die('<font color="red">Error: show tables returned an error. '.$res->getMessage().'</font><br>');
+  }
+  $tblCnt = 0;
+  while ($val = $res->fetchRow()) {
+    $tblCnt++;
+  }
+  if ($tblCnt > 0) {
+    echo("There are $tblCnt tables in database.<br>");
+  } else {
+    echo('<font color="red">There are no tables in database. Execute step 1 - Create database structure.</font><br>');
+  }
+  $conn->disconnect();
+}
+
 
 if ($_POST) {
   print "Processing...<br>\n";
@@ -518,8 +611,12 @@ if ($_POST) {
     setChange("create unique index name_idx on tt_invoices(team_id, name, status)");
     setChange("ALTER TABLE tt_teams ADD COLUMN lock_spec varchar(255) default NULL");
     setChange("ALTER TABLE tt_teams DROP locktime");
+    setChange("CREATE TABLE `tt_monthly_quota` (`team_id` int(11) NOT NULL, `year` smallint(5) UNSIGNED NOT NULL, `month` tinyint(3) UNSIGNED NOT NULL, `quota` smallint(5) UNSIGNED NOT NULL, PRIMARY KEY (`year`,`month`,`team_id`))");
+    setChange("ALTER TABLE `tt_monthly_quota` ADD CONSTRAINT `FK_TT_TEAM_CONSTRAING` FOREIGN KEY (`team_id`) REFERENCES `tt_teams` (`id`) ON DELETE CASCADE ON UPDATE CASCADE");
+    setChange("ALTER TABLE `tt_teams` ADD `daily_working_hours` SMALLINT NULL DEFAULT '8' AFTER `lock_spec`");
+    setChange("UPDATE `tt_teams` SET `daily_working_hours` = 8");
   }
-
+  
   // The update_clients function updates projects field in tt_clients table.
   if ($_POST["update_clients"]) {
     $mdb2 = getConnection();
@@ -603,6 +700,7 @@ if ($_POST) {
     setChange("OPTIMIZE TABLE tt_fav_reports");
     setChange("OPTIMIZE TABLE tt_invoices");
     setChange("OPTIMIZE TABLE tt_log");
+    setChange("OPTIMIZE TABLE tt_monthly_quota");
     setChange("OPTIMIZE TABLE tt_project_task_binds");
     setChange("OPTIMIZE TABLE tt_projects");
     setChange("OPTIMIZE TABLE tt_tasks");
@@ -622,7 +720,7 @@ if ($_POST) {
 <h2>DB Install</h2>
 <table width="80%" border="1" cellpadding="10" cellspacing="0">
   <tr>
-    <td width="80%"><b>Create database structure (v1.9)</b>
+    <td width="80%"><b>Create database structure (v1.9.30)</b>
     <br>(applies only to new installations, do not execute when updating)</br></td><td><input type="submit" name="crstructure" value="Create"></td>
   </tr>
 </table>
index a24eda7..62032fc 100644 (file)
@@ -117,6 +117,10 @@ select{ font-size: 10pt; font-family: verdana; }
   border-bottom: 1px solid silver;
 }
 
+.borderTop td {
+  border-top: 1px solid silver;
+}
+
 .sectionHeaderNoBorder {
   font-weight: bold;
 }
@@ -131,4 +135,11 @@ select{ font-size: 10pt; font-family: verdana; }
   color: #0000c0;
 }
 
+.divider {
+  background-color: #efefef;
+}
+table.divider {
+  width: 720px;
+}
+
 div#LoginAboutText { width:400px; }
index f566df7..bfc198a 100644 (file)
--- a/mysql.sql
+++ b/mysql.sql
@@ -28,6 +28,7 @@ CREATE TABLE `tt_teams` (
   `plugins` varchar(255) default NULL,                   # a list of enabled plugins for team
   `lock_spec` varchar(255) default NULL,                 # Cron specification for record locking,
                                                          # for example: "0 10 * * 1" for "weekly on Mon at 10:00".
+  `daily_working_hours` smallint(6) DEFAULT '8',         # number of working hours per days, a worker is suppose to work
   `custom_logo` tinyint(4) default '0',                  # whether to use a custom logo or not
   `status` tinyint(4) default '1',                       # team status
   PRIMARY KEY (`id`)
@@ -338,3 +339,20 @@ create index user_idx on tt_expense_items(user_id);
 create index client_idx on tt_expense_items(client_id);
 create index project_idx on tt_expense_items(project_id);
 create index invoice_idx on tt_expense_items(invoice_id);
+
+#
+# Structure for table tt_monthly_quota.
+# This table lists monthly quota per team.
+#
+
+CREATE TABLE `tt_monthly_quota` (
+  `team_id` int(11) NOT NULL,             # team's id
+  `year` smallint(5) UNSIGNED NOT NULL,   # year we'setting monthly quota for
+  `month` tinyint(3) UNSIGNED NOT NULL,   # month we're settng monthly quota for
+  `quota` smallint(5) UNSIGNED NOT NULL,  # the monthly quota
+  PRIMARY KEY (`year`,`month`,`team_id`)
+);
+
+ALTER TABLE `tt_monthly_quota`
+  ADD CONSTRAINT `FK_TT_TEAM_CONSTRAING` FOREIGN KEY (`team_id`) REFERENCES `tt_teams` (`id`) ON DELETE CASCADE ON UPDATE CASCADE;
+
diff --git a/plugins/MonthlyQuota.class.php b/plugins/MonthlyQuota.class.php
new file mode 100644 (file)
index 0000000..b50469b
--- /dev/null
@@ -0,0 +1,160 @@
+<?php
+
+class MonthlyQuota {
+    
+    var $db;
+    var $holidays;
+    var $usersTeamId;
+    // Old style constructors are DEPRECATED in PHP 7.0, and will be removed in a future version. You should always use __construct() in new code.
+    function __construct() {
+        $this->db = getConnection();
+        $i18n = $GLOBALS['I18N'];
+        $this->holidays = $i18n->holidays;
+        global $user;
+        $this->usersTeamId = $user->team_id;
+    }
+    
+    public function update($year, $month, $quota) {
+        $teamId = $this->usersTeamId;
+        $deleteSql = "DELETE FROM tt_monthly_quota WHERE year = $year AND month = $month AND team_id = $teamId";
+        $this->db->exec($deleteSql);
+        if ($quota){
+            $insertSql = "INSERT INTO tt_monthly_quota (team_id, year, month, quota) values ($teamId, $year, $month, $quota)";
+            $affected = $this->db->exec($insertSql);
+            return (!is_a($affected, 'PEAR_Error'));
+        }
+        return true;
+    }
+        
+    public function get($year, $month) {
+        if (is_null($month)){
+            return $this->getMany($year);
+        }
+        
+        return $this->getSingle($year, $month);
+    }
+
+    public function getDailyWorkingHours(){
+        $teamId = $this->usersTeamId;
+        $sql = "SELECT daily_working_hours FROM tt_teams where id = $teamId";
+        $reader = $this->db->query($sql);
+        if (is_a($reader, 'PEAR_Error')) {
+            return false;
+        }
+
+        $row = $reader->fetchRow();
+        return $row["daily_working_hours"];
+    }
+    
+    private function getSingle($year, $month) {
+        $teamId = $this->usersTeamId;
+        $sql = "SELECT quota FROM tt_monthly_quota WHERE year = $year AND month = $month AND team_id = $teamId";
+        $reader = $this->db->query($sql);
+        if (is_a($reader, 'PEAR_Error')) {
+            return false;
+        }
+        
+        $row = $reader->fetchRow();
+        
+        // if we don't find a record, return calculated monthly quota
+        if (is_null($row)){
+            
+            $holidaysWithYear = array();
+            foreach ($this->holidays as $day) {
+                $parts = explode("/", $day);
+                $holiday = "$year-$parts[0]-$parts[1]";
+                array_push($holidaysWithYear, $holiday);
+            }
+            
+            $daysInMonth = cal_days_in_month(CAL_GREGORIAN, $month, $year);
+            return $this->getWorkingDays("$year-$month-01", "$year-$month-$daysInMonth", $holidaysWithYear) * $this->getDailyWorkingHours();
+        }
+        
+        return $row["quota"];  
+    }
+    
+    private function getMany($year){
+        $teamId = $this->usersTeamId;
+        $sql = "SELECT month, quota FROM tt_monthly_quota WHERE year = $year AND team_id = $teamId";
+        $result = array();
+        $res = $this->db->query($sql);
+        if (is_a($res, 'PEAR_Error')) {
+            return false;
+        }
+        
+        while ($val = $res->fetchRow()) {
+            $result[$val["month"]] = $val["quota"];
+            // $result[] = $val;
+        }        
+        
+        return $result;
+    }
+    
+    //The function returns the no. of business days between two dates and it skips the holidays
+    private function getWorkingDays($startDate, $endDate, $holidays) {
+        // do strtotime calculations just once
+        $endDate = strtotime($endDate);
+        $startDate = strtotime($startDate);
+
+        //The total number of days between the two dates. We compute the no. of seconds and divide it to 60*60*24
+        //We add one to inlude both dates in the interval.
+        $days = ($endDate - $startDate) / 86400 + 1;
+
+        $noOfFullWeeks = floor($days / 7);
+        $noOfRemainingDays = fmod($days, 7);
+
+        //It will return 1 if it's Monday,.. ,7 for Sunday
+        $firstDayofWeek = date("N", $startDate);
+        $lastDayofWeek = date("N", $endDate);
+
+        //---->The two can be equal in leap years when february has 29 days, the equal sign is added here
+        //In the first case the whole interval is within a week, in the second case the interval falls in two weeks.
+        if ($firstDayofWeek <= $lastDayofWeek) {
+            if ($firstDayofWeek <= 6 && 6 <= $lastDayofWeek) {
+                $noOfRemainingDays--;                
+            }
+            
+            if ($firstDayofWeek <= 7 && 7 <= $lastDayofWeek) {
+                $noOfRemainingDays--;
+            }
+        }
+        else {
+            // (edit by Tokes to fix an edge case where the start day was a Sunday
+            // and the end day was NOT a Saturday)
+
+            // the day of the week for start is later than the day of the week for end
+            if ($firstDayofWeek == 7) {
+                // if the start date is a Sunday, then we definitely subtract 1 day
+                $noOfRemainingDays--;
+
+                if ($lastDayofWeek == 6) {
+                    // if the end date is a Saturday, then we subtract another day
+                    $noOfRemainingDays--;
+                }
+            }
+            else {
+                // the start date was a Saturday (or earlier), and the end date was (Mon..Fri)
+                // so we skip an entire weekend and subtract 2 days
+                $noOfRemainingDays -= 2;
+            }
+        }
+
+        //T he no. of business days is: (number of weeks between the two dates) * (5 working days) + the remainder
+        // ---->february in none leap years gave a remainder of 0 but still calculated weekends between first and last day, this is one way to fix it
+        $workingDays = $noOfFullWeeks * 5;
+        if ($noOfRemainingDays > 0 ) {
+            $workingDays += $noOfRemainingDays;
+        }
+
+        // We subtract the holidays
+        foreach($holidays as $holiday){
+            $timeStamp = strtotime($holiday);
+            // If the holiday doesn't fall in weekend
+            // TODO: add handling for countries where they move non working day to first working day if holiday is on weekends
+            if ($startDate <= $timeStamp && $timeStamp <= $endDate && date("N", $timeStamp) != 6 && date("N", $timeStamp ) != 7)
+                $workingDays--;
+        }
+
+        return $workingDays;
+    }
+}
\ No newline at end of file
index 5ccccf5..d5c4967 100644 (file)
@@ -69,6 +69,7 @@ if ($request->isPost()) {
     $cl_tax_expenses = $request->getParameter('tax_expenses');
     $cl_notifications = $request->getParameter('notifications');
     $cl_locking = $request->getParameter('locking');
+    $cl_monthly_quota = $request->getParameter('monthly_quota');
   }
 } else {
   $cl_name = $user->name;
@@ -92,11 +93,12 @@ if ($request->isPost()) {
     $cl_clients = in_array('cl', $plugins);
     $cl_client_required = in_array('cm', $plugins);
     $cl_invoices = in_array('iv', $plugins);
-    $cl_custom_fields = in_array('cf', $plugins);
+    $cl_custom_fields = in_array('cf', $plugins);    
     $cl_expenses = in_array('ex', $plugins);
     $cl_tax_expenses = in_array('et', $plugins);
     $cl_notifications = in_array('no', $plugins);
     $cl_locking = in_array('lk', $plugins);
+    $cl_monthly_quota = in_array('mq', $plugins);
   }
 }
 
@@ -176,6 +178,7 @@ if ($user->canManageTeam()) {
   $form->addInput(array('type'=>'checkbox','name'=>'tax_expenses','data'=>1,'value'=>$cl_tax_expenses));
   $form->addInput(array('type'=>'checkbox','name'=>'notifications','data'=>1,'value'=>$cl_notifications,'onchange'=>'handlePluginCheckboxes()'));
   $form->addInput(array('type'=>'checkbox','name'=>'locking','data'=>1,'value'=>$cl_locking,'onchange'=>'handlePluginCheckboxes()'));
+  $form->addInput(array('type'=>'checkbox','name'=>'monthly_quota','data'=>1,'value'=>$cl_monthly_quota,'onchange'=>'handlePluginCheckboxes()'));
 }
 $form->addInput(array('type'=>'submit','name'=>'btn_save','value'=>$i18n->getKey('button.save')));
 
@@ -226,6 +229,8 @@ if ($request->isPost()) {
         $plugins .= ',no';
       if ($cl_locking)
         $plugins .= ',lk';
+      if ($cl_monthly_quota)
+        $plugins .= ',mq';
       $plugins = trim($plugins, ',');
 
       $update_result = ttTeamHelper::update($user->team_id, array(
index e0efafa..4042d7c 100644 (file)
--- a/time.php
+++ b/time.php
@@ -63,6 +63,17 @@ if ($user->isPluginEnabled('cf')) {
   $smarty->assign('custom_fields', $custom_fields);
 }
 
+if ($user->isPluginEnabled('mq')){
+  require_once('plugins/MonthlyQuota.class.php');
+  $quota = new MonthlyQuota();
+  $monthlyQuota = $quota->get($selected_date->mYear, $selected_date->mMonth);
+  $month_total = ttTimeHelper::getTimeForMonth($user->getActiveUser(), $selected_date);
+  $minutesLeft = ttTimeHelper::toMinutes($monthlyQuota) - ttTimeHelper::toMinutes($month_total);
+  
+  $smarty->assign('month_total', $month_total);
+  $smarty->assign('month_left', ttTimeHelper::fromMinutes($minutesLeft));
+}
+
 // Initialize variables.
 $cl_start = trim($request->getParameter('start'));
 $cl_finish = trim($request->getParameter('finish'));