From: Nik Okuntseff Date: Mon, 18 Jul 2016 17:32:00 +0000 (+0000) Subject: Merge branch 'avidenic-master' X-Git-Tag: timetracker_1.19-1~1702 X-Git-Url: http://wagnertech.de/gitweb/gitweb.cgi/timetracker.git/commitdiff_plain/0576ffe7b0fdedf1e38df47d15d2112fb03ec1c6?hp=ed13397ab74c0892efe2cfff855a53cf11e15a36 Merge branch 'avidenic-master' --- diff --git a/WEB-INF/config.php.dist b/WEB-INF/config.php.dist index b52e0d80..5afca00d 100644 --- a/WEB-INF/config.php.dist +++ b/WEB-INF/config.php.dist @@ -214,3 +214,7 @@ define('AUTH_MODULE', 'db'); // 'member_of' => array()); // List of groups, membership in which is required for user to be authenticated. // 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 diff --git a/WEB-INF/lib/ttTeamHelper.class.php b/WEB-INF/lib/ttTeamHelper.class.php index e76482d0..e91a794d 100644 --- a/WEB-INF/lib/ttTeamHelper.class.php +++ b/WEB-INF/lib/ttTeamHelper.class.php @@ -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; diff --git a/WEB-INF/lib/ttTimeHelper.class.php b/WEB-INF/lib/ttTimeHelper.class.php index 29b74145..f378d0c0 100644 --- a/WEB-INF/lib/ttTimeHelper.class.php +++ b/WEB-INF/lib/ttTimeHelper.class.php @@ -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(); diff --git a/WEB-INF/resources/de.lang.php b/WEB-INF/resources/de.lang.php index b3b37fd4..ae05e9f7 100644 --- a/WEB-INF/resources/de.lang.php +++ b/WEB-INF/resources/de.lang.php @@ -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', diff --git a/WEB-INF/resources/en.lang.php b/WEB-INF/resources/en.lang.php index 8a4fe503..f82e5432 100644 --- a/WEB-INF/resources/en.lang.php +++ b/WEB-INF/resources/en.lang.php @@ -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', diff --git a/WEB-INF/resources/es.lang.php b/WEB-INF/resources/es.lang.php index 153df471..3f1696e5 100644 --- a/WEB-INF/resources/es.lang.php +++ b/WEB-INF/resources/es.lang.php @@ -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', diff --git a/WEB-INF/resources/fa.lang.php b/WEB-INF/resources/fa.lang.php index 22b04cb4..acdb8f06 100644 --- a/WEB-INF/resources/fa.lang.php +++ b/WEB-INF/resources/fa.lang.php @@ -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' => 'گزینه ها', diff --git a/WEB-INF/resources/fi.lang.php b/WEB-INF/resources/fi.lang.php index b0a022df..ee94cad0 100644 --- a/WEB-INF/resources/fi.lang.php +++ b/WEB-INF/resources/fi.lang.php @@ -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', diff --git a/WEB-INF/resources/fr.lang.php b/WEB-INF/resources/fr.lang.php index 52b17db5..ee899fa2 100644 --- a/WEB-INF/resources/fr.lang.php +++ b/WEB-INF/resources/fr.lang.php @@ -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', diff --git a/WEB-INF/resources/he.lang.php b/WEB-INF/resources/he.lang.php index 698a92bb..ce02dba8 100644 --- a/WEB-INF/resources/he.lang.php +++ b/WEB-INF/resources/he.lang.php @@ -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' => 'אפשרויות', diff --git a/WEB-INF/resources/nl.lang.php b/WEB-INF/resources/nl.lang.php index e2a9dd02..b2ad9bf8 100644 --- a/WEB-INF/resources/nl.lang.php +++ b/WEB-INF/resources/nl.lang.php @@ -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', diff --git a/WEB-INF/resources/pl.lang.php b/WEB-INF/resources/pl.lang.php index b2abad4e..35a53557 100644 --- a/WEB-INF/resources/pl.lang.php +++ b/WEB-INF/resources/pl.lang.php @@ -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', diff --git a/WEB-INF/resources/pt-br.lang.php b/WEB-INF/resources/pt-br.lang.php index 1dd84e37..021c1386 100644 --- a/WEB-INF/resources/pt-br.lang.php +++ b/WEB-INF/resources/pt-br.lang.php @@ -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', diff --git a/WEB-INF/resources/ru.lang.php b/WEB-INF/resources/ru.lang.php index 9c99e144..0c99c071 100644 --- a/WEB-INF/resources/ru.lang.php +++ b/WEB-INF/resources/ru.lang.php @@ -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' => 'Опции', diff --git a/WEB-INF/resources/sk.lang.php b/WEB-INF/resources/sk.lang.php index bd3abd81..c8479ad4 100644 --- a/WEB-INF/resources/sk.lang.php +++ b/WEB-INF/resources/sk.lang.php @@ -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', diff --git a/WEB-INF/resources/sr.lang.php b/WEB-INF/resources/sr.lang.php index 8e42f96f..546515b7 100644 --- a/WEB-INF/resources/sr.lang.php +++ b/WEB-INF/resources/sr.lang.php @@ -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 index 00000000..6e3d9fee --- /dev/null +++ b/WEB-INF/templates/cf_monthly_quota.tpl @@ -0,0 +1,59 @@ +{$forms.monthlyQuotaForm.open} +
+ + + + +
+ + + + + + +
{$i18n.label.dailyWorkingHours}{$forms.monthlyQuotaForm.dailyWorkingHours.control}
+
+
+ + + + + + + + +
{$i18n.label.year}:{$forms.monthlyQuotaForm.years.control}
+ + + + + + {foreach $months as $month} + + + + + {/foreach} + + + +
{$i18n.label.month}{$i18n.label.quota}
{$month}{$forms.monthlyQuotaForm.$month.control}
+ +
+
+
* - {$i18n.label.empty_values_explanation}
+{$forms.monthlyQuotaForm.close} + \ No newline at end of file diff --git a/WEB-INF/templates/footer.tpl b/WEB-INF/templates/footer.tpl index be849fff..6ff01ef6 100644 --- a/WEB-INF/templates/footer.tpl +++ b/WEB-INF/templates/footer.tpl @@ -12,7 +12,7 @@
- - + + + + + {/if} diff --git a/WEB-INF/templates/reports.tpl b/WEB-INF/templates/reports.tpl index bd5ab3f4..ce804dc5 100644 --- a/WEB-INF/templates/reports.tpl +++ b/WEB-INF/templates/reports.tpl @@ -149,7 +149,7 @@ function handleCheckboxes() { {$forms.reportForm.open}
-
 Anuko Time Tracker 1.9.25.3502 | Copyright © Anuko | +  Anuko Time Tracker 1.9.30.0000 | Copyright © Anuko | {$i18n.footer.credits} | {$i18n.footer.license} | {$i18n.footer.improve} diff --git a/WEB-INF/templates/profile_edit.tpl b/WEB-INF/templates/profile_edit.tpl index 8d68c3e4..2da8041d 100644 --- a/WEB-INF/templates/profile_edit.tpl +++ b/WEB-INF/templates/profile_edit.tpl @@ -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"; + } + } @@ -181,7 +190,11 @@ function handlePluginCheckboxes() {
{$forms.profileForm.locking.control} {$i18n.label.configure} {$i18n.label.configure}
{$forms.profileForm.monthly_quota.control} {$i18n.label.configure}
+
@@ -278,7 +278,7 @@ function handleCheckboxes() {
- +
+ + + +
diff --git a/WEB-INF/templates/time.tpl b/WEB-INF/templates/time.tpl index 80b111ba..d526c46f 100644 --- a/WEB-INF/templates/time.tpl +++ b/WEB-INF/templates/time.tpl @@ -354,6 +354,16 @@ function get_time() { + {if $month_total} + + + {if $month_left|strpos:'-' === 0} + + {else} + + {/if} + + {/if}
{$i18n.label.week_total}: {$week_total} {$i18n.label.day_total}: {$day_total}
{$i18n.label.month_total}: {$month_total}{$i18n.label.month_over}: {$month_left|substr:1}{$i18n.label.month_left}: {$month_left}
{/if} {$forms.timeRecordForm.close} diff --git a/cf_monthly_quota.php b/cf_monthly_quota.php new file mode 100644 index 00000000..19b43bf6 --- /dev/null +++ b/cf_monthly_quota.php @@ -0,0 +1,88 @@ +$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'); diff --git a/dbinstall.php b/dbinstall.php old mode 100644 new mode 100755 index 34f3a8b5..a511617b --- a/dbinstall.php +++ b/dbinstall.php @@ -612,7 +612,14 @@ if ($_POST) { setChange("ALTER TABLE tt_teams ADD COLUMN lock_spec varchar(255) default NULL"); setChange("ALTER TABLE tt_teams DROP locktime"); } - + + if ($_POST["convert1900to1930"]){ + 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(); @@ -696,6 +703,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"); @@ -715,7 +723,7 @@ if ($_POST) {

DB Install

-
Create database structure (v1.9) + Create database structure (v1.9.30)
(applies only to new installations, do not execute when updating)
@@ -750,6 +758,10 @@ if ($_POST) {
Update database structure (v1.6 to v1.9)
Update database structure (v1.9 to v1.9.30)

DB Maintenance

diff --git a/default.css b/default.css index a24eda7b..62032fca 100644 --- a/default.css +++ b/default.css @@ -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; } diff --git a/mysql.sql b/mysql.sql index f566df7b..bfc198a9 100644 --- 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 index 00000000..b50469b3 --- /dev/null +++ b/plugins/MonthlyQuota.class.php @@ -0,0 +1,160 @@ +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 diff --git a/profile_edit.php b/profile_edit.php index 5ccccf54..d5c49678 100644 --- a/profile_edit.php +++ b/profile_edit.php @@ -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( diff --git a/time.php b/time.php index f0f2c7de..9a34d30d 100644 --- 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'));