diff --git a/.idea/.gitignore b/.idea/.gitignore deleted file mode 100644 index 73f69e0..0000000 --- a/.idea/.gitignore +++ /dev/null @@ -1,8 +0,0 @@ -# Default ignored files -/shelf/ -/workspace.xml -# Datasource local storage ignored files -/dataSources/ -/dataSources.local.xml -# Editor-based HTTP Client requests -/httpRequests/ diff --git a/.idea/doliproject.iml b/.idea/doliproject.iml deleted file mode 100644 index c956989..0000000 --- a/.idea/doliproject.iml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml deleted file mode 100644 index 28a804d..0000000 --- a/.idea/misc.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml deleted file mode 100644 index 3cb885f..0000000 --- a/.idea/modules.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index 94a25f7..0000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/README.md b/README.md index e6f7824..c6c4519 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,25 @@ # DOLIPROJECT POUR [DOLIBARR ERP CRM](https://www.dolibarr.org) +## Informations + +Version du module: 1.3.0 + +Dernière mise à jour: 18/08/2022 + +Prérequis: +* Dolibarr min version 14.0.0 +* Dolibarr min version 15.0.3 + +Thème: Eldy Menu + +Editeur/Licence: [Eoxia](https://www.eoxia.com) / GPL-v3 + +Assitance: [Forum www.dolibarr.fr](https://www.dolibarr.fr) / Par mail à contact@eoxia.com + +Demo: [Demo DoliProject](https://www.doliproject.projetm.com) - ID: demo - Password: demo + +Documentation: [Wiki DoliProject](https://wiki.dolibarr.org/index.php/Module_DoliProject) + ## Fonctionnalités - Module ajoutant la possibilité de simplifier la création de tâche liée à un projet à partir d'une facture. @@ -10,7 +30,7 @@ # Méthode 1 : - Depuis le menu "Déployer/Installer un module externe" de Dolibarr : -- Glisser l'archive ZIP 'module_doliproject-1.1.0' et cliquer sur "SEND" +- Glisser l'archive ZIP 'module_doliproject-1.2.0' et cliquer sur "SEND" - Activer le module dans la liste des Modules/Applications installés # Méthode 2 : @@ -18,4 +38,4 @@ - Dans le dossier "dolibarr/htdocs/custom" copier la ligne suivante : ``` git clone https://github.com/Eoxia/doliproject.git -``` \ No newline at end of file +``` diff --git a/admin/project.php b/admin/project.php new file mode 100644 index 0000000..ae24453 --- /dev/null +++ b/admin/project.php @@ -0,0 +1,217 @@ + + * Copyright (C) 2020 SuperAdmin + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/** + * \file doliproject/admin/about.php + * \ingroup doliproject + * \brief About page of module Doliproject. + */ + +// Load Dolibarr environment +$res = 0; +// Try main.inc.php into web root known defined into CONTEXT_DOCUMENT_ROOT (not always defined) +if (!$res && !empty($_SERVER["CONTEXT_DOCUMENT_ROOT"])) $res = @include $_SERVER["CONTEXT_DOCUMENT_ROOT"]."/main.inc.php"; +// Try main.inc.php into web root detected using web root calculated from SCRIPT_FILENAME +$tmp = empty($_SERVER['SCRIPT_FILENAME']) ? '' : $_SERVER['SCRIPT_FILENAME']; $tmp2 = realpath(__FILE__); $i = strlen($tmp) - 1; $j = strlen($tmp2) - 1; +while ($i > 0 && $j > 0 && isset($tmp[$i]) && isset($tmp2[$j]) && $tmp[$i] == $tmp2[$j]) { $i--; $j--; } +if (!$res && $i > 0 && file_exists(substr($tmp, 0, ($i + 1))."/main.inc.php")) $res = @include substr($tmp, 0, ($i + 1))."/main.inc.php"; +if (!$res && $i > 0 && file_exists(dirname(substr($tmp, 0, ($i + 1)))."/main.inc.php")) $res = @include dirname(substr($tmp, 0, ($i + 1)))."/main.inc.php"; +// Try main.inc.php using relative path +if (!$res && file_exists("../../main.inc.php")) $res = @include "../../main.inc.php"; +if (!$res && file_exists("../../../main.inc.php")) $res = @include "../../../main.inc.php"; +if (!$res) die("Include of main fails"); + +// Libraries +require_once DOL_DOCUMENT_ROOT.'/core/lib/admin.lib.php'; +require_once DOL_DOCUMENT_ROOT.'/core/lib/functions2.lib.php'; +require_once DOL_DOCUMENT_ROOT . "/core/class/html.formother.class.php"; +require_once DOL_DOCUMENT_ROOT . "/core/class/html.formprojet.class.php"; + +require_once '../lib/doliproject.lib.php'; + +// Translations +$langs->loadLangs(array("errors", "admin", "doliproject@doliproject")); + +// Access control +if (!$user->admin) accessforbidden(); + +// Parameters +$action = GETPOST('action', 'alpha'); +$backtopage = GETPOST('backtopage', 'alpha'); + + +/* + * Actions + */ + +if (($action == 'update' && ! GETPOST("cancel", 'alpha')) || ($action == 'updateedit')) { + $HRProject = GETPOST('HRProject', 'none'); + $HRProject = preg_split('/_/', $HRProject); + + dolibarr_set_const($db, "DOLIPROJECT_HR_PROJECT", $HRProject[0], 'integer', 0, '', $conf->entity); + setEventMessages($langs->transnoentities('TicketProjectUpdated'), array()); + + if ($action != 'updateedit' && ! $error) { + header("Location: " . $_SERVER["PHP_SELF"]); + exit; + } +} + +if ($action == 'updateThemeColor') { + $val = (implode(',', (colorStringToArray(GETPOST('DOLIPROJECT_EXCEEDED_TIME_SPENT_COLOR'), array())))); + if ($val == '') { + dolibarr_del_const($db, 'DOLIPROJECT_EXCEEDED_TIME_SPENT_COLOR', $conf->entity); + } else { + dolibarr_set_const($db, 'DOLIPROJECT_EXCEEDED_TIME_SPENT_COLOR', $val, 'chaine', 0, '', $conf->entity); + } + + $val = (implode(',', (colorStringToArray(GETPOST('DOLIPROJECT_NOT_EXCEEDED_TIME_SPENT_COLOR'), array())))); + if ($val == '') { + dolibarr_del_const($db, 'DOLIPROJECT_NOT_EXCEEDED_TIME_SPENT_COLOR', $conf->entity); + } else { + dolibarr_set_const($db, 'DOLIPROJECT_NOT_EXCEEDED_TIME_SPENT_COLOR', $val, 'chaine', 0, '', $conf->entity); + } + + $val = (implode(',', (colorStringToArray(GETPOST('DOLIPROJECT_PERFECT_TIME_SPENT_COLOR'), array())))); + if ($val == '') { + dolibarr_del_const($db, 'DOLIPROJECT_PERFECT_TIME_SPENT_COLOR', $conf->entity); + } else { + dolibarr_set_const($db, 'DOLIPROJECT_PERFECT_TIME_SPENT_COLOR', $val, 'chaine', 0, '', $conf->entity); + } +} + +/* + * View + */ + +$form = new Form($db); +$formother = new FormOther($db); +if ( ! empty($conf->projet->enabled)) { $formproject = new FormProjets($db); } + +$page_name = "DoliprojectAbout"; +llxHeader('', $langs->trans($page_name)); + +// Subheader +$linkback = ''.$langs->trans("BackToModuleList").''; + +print load_fiche_titre($langs->trans($page_name), $linkback, 'object_doliproject@doliproject'); + +// Configuration header +$head = doliprojectAdminPrepareHead(); +dol_fiche_head($head, 'projecttasks', '', -1, 'doliproject@doliproject'); +dol_get_fiche_head($head, 'projecttasks', '', -1, 'doliproject@doliproject'); + +// Project +print load_fiche_titre($langs->transnoentities("HRProject"), '', ''); + +print '
'; +print ''; +print ''; +print ''; +print ''; +print ''; +print ''; +print ''; +print ''; + +if ( ! empty($conf->projet->enabled)) { + $langs->load("projects"); + print ''; +} + +print '
' . $langs->transnoentities("Name") . '' . $langs->transnoentities("SelectProject") . '' . $langs->transnoentities("Action") . '
'; + $numprojet = $formproject->select_projects(0, $conf->global->DOLIPROJECT_HR_PROJECT, 'HRProject', 0, 0, 0, 0, 0, 0, 0, '', 0, 0, 'maxwidth500'); + print ' '; + print ''; + print '
'; +print '
'; + +//Time spent +print load_fiche_titre($langs->transnoentities("TimeSpent"), '', ''); + +print ''; + +print ''; +print ''; +print ''; +print ''; +print ''; +print ''; + +print ''; +print ''; +print ''; +print ''; +print ''; + +print '
' . $langs->transnoentities("Parameters") . '' . $langs->transnoentities("Status") . '' . $langs->transnoentities("Action") . '' . $langs->transnoentities("ShortInfo") . '
' . $langs->transnoentities("SpendMoreTimeThanPlanned") . ''; +print ajax_constantonoff('DOLIPROJECT_SPEND_MORE_TIME_THAN_PLANNED'); +print ''; +print ''; +print ''; +print $form->textwithpicto('', $langs->transnoentities("SpendMoreTimeThanPlannedHelp"), 1, 'help'); +print '
'; + +//Theme dasboard time spent +print load_fiche_titre($langs->transnoentities("ThemeDashboardTimeSpent"), '', ''); + +print '
'; +print ''; +print ''; +print ''; + +print ''; +print ''; +print ''; +print ''; + +print ''; +print ''; +print ''; +print ''; + +print ''; +print ''; +print ''; +print ''; + +print ''; +print ''; +print ''; +print ''; + +print '
' . $langs->transnoentities("Parameters") . '' . $langs->transnoentities("Value") . '
'.$langs->trans("ExceededTimeSpentColor").''; +print $formother->selectColor(colorArrayToHex(colorStringToArray((!empty($conf->global->DOLIPROJECT_EXCEEDED_TIME_SPENT_COLOR) ? $conf->global->DOLIPROJECT_EXCEEDED_TIME_SPENT_COLOR : ''), array()), ''), 'DOLIPROJECT_EXCEEDED_TIME_SPENT_COLOR', '', 1, '', '', 'doliprojectexceededtimespentcolor'); +print ''.$langs->trans("Default").': #FF0000'; +print '
'.$langs->trans("NotExceededTimeSpentColor").''; +print $formother->selectColor(colorArrayToHex(colorStringToArray((!empty($conf->global->DOLIPROJECT_NOT_EXCEEDED_TIME_SPENT_COLOR) ? $conf->global->DOLIPROJECT_NOT_EXCEEDED_TIME_SPENT_COLOR : ''), array()), ''), 'DOLIPROJECT_NOT_EXCEEDED_TIME_SPENT_COLOR', '', 1, '', '', 'doliprojectnotexceededtimespentcolor'); +print ''.$langs->trans("Default").': #FFA500'; +print '
'.$langs->trans("PerfectTimeSpentColor").''; +print $formother->selectColor(colorArrayToHex(colorStringToArray((!empty($conf->global->DOLIPROJECT_PERFECT_TIME_SPENT_COLOR) ? $conf->global->DOLIPROJECT_PERFECT_TIME_SPENT_COLOR : ''), array()), ''), 'DOLIPROJECT_PERFECT_TIME_SPENT_COLOR', '', 1, '', '', 'doliprojectperfecttimespentcolor'); +print ''.$langs->trans("Default").': #008000'; +print '
'; + +print '
'; +print ''; +print '
'; + +print '
'; + +// Page end +llxFooter(); +$db->close(); diff --git a/class/actions_doliproject.class.php b/class/actions_doliproject.class.php index 07d14fa..02e94e3 100644 --- a/class/actions_doliproject.class.php +++ b/class/actions_doliproject.class.php @@ -503,7 +503,7 @@ public function printCommonFooter($parameters, &$object, &$action, $hookmanager) InputTime.type = "number"; InputTime.value = global->DOLIPROJECT_DEFAUT_TICKET_TIME)?$conf->global->DOLIPROJECT_DEFAUT_TICKET_TIME:0); ?>; let $tr = $(''); - $tr.append($('').append('trans('NewTimeSpent');?>')); + $tr.append($('').append('trans('DoliProjectNewTimeSpent');?>')); $tr.append($('').append(InputTime)); let currElement = $("form[name='ticket'] > table tbody"); @@ -517,26 +517,67 @@ public function printCommonFooter($parameters, &$object, &$action, $hookmanager) setEventMessages($ticket->error,$ticket->errors,'errors'); } dol_htmloutput_events(); + } else if ((GETPOST('action') == '' || empty(GETPOST('action')) || GETPOST('action') == 'view')) { + require_once __DIR__ . '/../../../projet/class/task.class.php'; + + $task = new Task($this->db); + $ticket = new Ticket($this->db); + + $ticket->fetch(GETPOST('id')); + $ticket->fetch_optionals(); + + $task_id = $ticket->array_options['options_fk_task']; + + $task->fetch($task_id); + + if (!empty($task_id) && $task_id > 0) { ?> + + db); + $ticket = new Ticket($this->db); + + $ticket->fetch(GETPOST('id')); + $ticket->fetch_optionals(); + + $alltasks = $task->getTasksArray(null, null, $ticket->fk_project); + if (is_array($alltasks) && !empty($alltasks)) { + foreach ($alltasks as $tasksingle) { + $taskArray[$tasksingle->id] = $tasksingle->ref . ' - ' . $tasksingle->label; + } + } + + if (is_array($taskArray) && !empty($taskArray)) { ?> + + id); } - -// $result = isTaskFavorite(GETPOST('id'), $user->id); if (isTaskFavorite(GETPOST('id'), $user->id)) { - $favoriteStar = '
'; + $favoriteStar = ''; } else { - $favoriteStar = '
'; + $favoriteStar = ''; } ?> - getTasksArray(0, 0, GETPOST('id')); + if (is_array($tasksarray) && !empty($tasksarray)) { + foreach ($tasksarray as $linked_task) { + + if (isTaskFavorite($linked_task->id, $user->id)) { + $favoriteStar = ''; + } else { + $favoriteStar = ''; + } + ?> + + getTasksArray(0, 0, GETPOST('id')); + + if (is_array($tasksarray) && !empty($tasksarray)) { + foreach ($tasksarray as $linked_task) { + + if (isTaskFavorite($linked_task->id, $user->id)) { + $favoriteStar = ''; + } else { + $favoriteStar = ''; + } + ?> + + db); + $invoice_id = GETPOST('facid'); + + // Categories + if ($conf->categorie->enabled) { + $html = ''.$langs->trans("Categories").''; + $html .= $form->showCategories($invoice_id, 'invoice', 1); + $html .= ''; ?> + ; + id); + } + ?> + + array( + 'id' => 436370001, + 'code' => 'invoice', + 'obj_class' => 'Facture', + 'obj_table' => 'facture', + ), + 'invoicerec' => array( + 'id' => 436370002, + 'code' => 'invoicerec', + 'obj_class' => 'Facture', + 'obj_table' => 'facture', + ) + ); + } + + if (!$error) { + $this->results = $tags; + return 0; // or return 1 to replace standard code + } else { + $this->errors[] = 'Error message'; + return -1; + } + } + + public function formObjectOptions($parameters, &$object, &$action, $hookmanager) + { + global $conf, $langs; + + if (in_array($parameters['currentcontext'], array('invoicecard'))) { + require_once DOL_DOCUMENT_ROOT . '/categories/class/categorie.class.php'; + + $form = new Form($this->db); + + if ($action == 'create') { + if (!empty($conf->categorie->enabled)) { + // Categories + print '' . $langs->trans("Categories") . ''; + $cate_arbo = $form->select_all_categories('invoice', '', 'parent', 64, 0, 1); + print img_picto('', 'category') . $form->multiselectarray('categories', $cate_arbo, GETPOST('categories', 'array'), '', 0, 'quatrevingtpercent widthcentpercentminusx', 0, 0); + print ""; + } + } else if ($action == 'edit') { +// // Tags-Categories +// if ($conf->categorie->enabled) { +// print ''.$langs->trans("Categories").''; +// $cate_arbo = $form->select_all_categories('invoice', '', 'parent', 64, 0, 1); +// $c = new Categorie($this->db); +// $cats = $c->containing($object->id, 'invoice'); +// $arrayselected = array(); +// if (is_array($cats)) { +// foreach ($cats as $cat) { +// $arrayselected[] = $cat->id; +// } +// } +// print img_picto('', 'category').$form->multiselectarray('categories', $cate_arbo, $arrayselected, '', 0, 'quatrevingtpercent widthcentpercentminusx', 0, 0); +// print ""; +// } + } else if($action == '') { + // Categories + if ($conf->categorie->enabled) { + print ''.$langs->trans("Categories").''; + print $form->showCategories($object->id, 'invoice', 1); + print ""; + } + } + } + if (in_array($parameters['currentcontext'], array('invoicereccard'))) { + require_once DOL_DOCUMENT_ROOT . '/categories/class/categorie.class.php'; + + $form = new Form($this->db); + + if ($action == '') { + // Categories + if ($conf->categorie->enabled) { + print ''.$langs->trans("Categories").''; + print $form->showCategories($object->id, 'invoicerec', 1); + print ""; + } + } + } + } + + public function afterCreationOfRecurringInvoice($parameters, &$object) { + if (in_array($parameters['currentcontext'], array('cron', 'cronjoblist'))) { + require_once DOL_DOCUMENT_ROOT . '/categories/class/categorie.class.php'; + require_once __DIR__ . '/../lib/doliproject_functions.lib.php'; + $cat = new Categorie($this->db); + $categories = $cat->containing($parameters['facturerec']->id, 'invoicerec'); + if (is_array($categories) && !empty($categories)) { + foreach ($categories as $category) { + $categoryArray[] = $category->id; + } + } + setCategoriesObject($categoryArray, 'invoice', false, $object); + } } - /* Add here any other hooked methods... */ } diff --git a/class/doliprojectstats.php b/class/doliprojectstats.php new file mode 100644 index 0000000..7a9d2c6 --- /dev/null +++ b/class/doliprojectstats.php @@ -0,0 +1,593 @@ + + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/** + * \file class/doliprojectstats.class.php + * \ingroup doliproject + * \brief Common class to manage statistics reports + */ + +/** + * Parent class of statistics class + */ +abstract class DoliProjectStats +{ + protected $db; + protected $lastfetchdate = array(); // Dates of cache file read by methods + public $cachefilesuffix = ''; // Suffix to add to name of cache file (to avoid file name conflicts) + + /** + * Return nb of elements by month for several years + * + * @param int $endyear Start year + * @param int $startyear End year + * @param int $cachedelay Delay we accept for cache file (0=No read, no save of cache, -1=No read but save) + * @param int $format 0=Label of abscissa is a translated text, 1=Label of abscissa is month number, 2=Label of abscissa is first letter of month + * @param int $startmonth month of the fiscal year start min 1 max 12 ; if 1 = january + * @return array Array of values + */ + public function getNbByMonthWithPrevYear($endyear, $startyear, $cachedelay = 0, $format = 0, $startmonth = 1) + { + global $conf, $user, $langs; + + if ($startyear > $endyear) { + return -1; + } + + $datay = array(); + + // Search into cache + if (!empty($cachedelay)) { + include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php'; + include_once DOL_DOCUMENT_ROOT.'/core/lib/json.lib.php'; + } + + $newpathofdestfile = $conf->user->dir_temp.'/'.get_class($this).'_'.__FUNCTION__.'_'.(empty($this->cachefilesuffix) ? '' : $this->cachefilesuffix.'_').$langs->defaultlang.'_entity.'.$conf->entity.'_user'.$user->id.'.cache'; + $newmask = '0644'; + + $nowgmt = dol_now(); + + $foundintocache = 0; + if ($cachedelay > 0) { + $filedate = dol_filemtime($newpathofdestfile); + if ($filedate >= ($nowgmt - $cachedelay)) { + $foundintocache = 1; + + $this->lastfetchdate[get_class($this).'_'.__FUNCTION__] = $filedate; + } else { + dol_syslog(get_class($this).'::'.__FUNCTION__." cache file ".$newpathofdestfile." is not found or older than now - cachedelay (".$nowgmt." - ".$cachedelay.") so we can't use it."); + } + } + // Load file into $data + if ($foundintocache) { // Cache file found and is not too old + dol_syslog(get_class($this).'::'.__FUNCTION__." read data from cache file ".$newpathofdestfile." ".$filedate."."); + $data = json_decode(file_get_contents($newpathofdestfile), true); + } else { + $year = $startyear; + $sm = $startmonth - 1; + if ($sm != 0) { + $year = $year - 1; + } + while ($year <= $endyear) { + $datay[$year] = $this->getNbByMonth($year, $format); + $year++; + } + + $data = array(); + + for ($i = 0; $i < 12; $i++) { + $data[$i][] = $datay[$endyear][($i + $sm) % 12][0]; + $year = $startyear; + while ($year <= $endyear) { + $data[$i][] = $datay[$year][($i + $sm) % 12][1]; + $year++; + } + } + + } + + // Save cache file + if (empty($foundintocache) && ($cachedelay > 0 || $cachedelay == -1)) { + dol_syslog(get_class($this).'::'.__FUNCTION__." save cache file ".$newpathofdestfile." onto disk."); + if (!dol_is_dir($conf->user->dir_temp)) { + dol_mkdir($conf->user->dir_temp); + } + $fp = fopen($newpathofdestfile, 'w'); + fwrite($fp, json_encode($data)); + fclose($fp); + if (!empty($conf->global->MAIN_UMASK)) { + $newmask = $conf->global->MAIN_UMASK; + } + @chmod($newpathofdestfile, octdec($newmask)); + + $this->lastfetchdate[get_class($this).'_'.__FUNCTION__] = $nowgmt; + } + + // return array(array('Month',val1,val2,val3),...) + return $data; + } + + /** + * Return amount of elements by month for several years. + * Criterias used to build request are defined into the constructor of parent class into xxx/class/xxxstats.class.php + * The caller of class can add more filters into sql request by adding criteris into the $stats->where property just after + * calling constructor. + * + * @param int $endyear Start year + * @param int $startyear End year + * @param int $cachedelay Delay we accept for cache file (0=No read, no save of cache, -1=No read but save) + * @param int $format 0=Label of abscissa is a translated text, 1=Label of abscissa is month number, 2=Label of abscissa is first letter of month + * @param int $startmonth month of the fiscal year start min 1 max 12 ; if 1 = january + * @return array Array of values + */ + public function getAmountByMonthWithPrevYear($endyear, $startyear, $cachedelay = 0, $format = 0, $startmonth = 1) + { + global $conf, $user, $langs; + + if ($startyear > $endyear) { + return -1; + } + + $datay = array(); + + // Search into cache + if (!empty($cachedelay)) { + include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php'; + include_once DOL_DOCUMENT_ROOT.'/core/lib/json.lib.php'; + } + + $newpathofdestfile = $conf->user->dir_temp.'/'.get_class($this).'_'.__FUNCTION__.'_'.(empty($this->cachefilesuffix) ? '' : $this->cachefilesuffix.'_').$langs->defaultlang.'_entity.'.$conf->entity.'_user'.$user->id.'.cache'; + $newmask = '0644'; + + $nowgmt = dol_now(); + + $foundintocache = 0; + if ($cachedelay > 0) { + $filedate = dol_filemtime($newpathofdestfile); + if ($filedate >= ($nowgmt - $cachedelay)) { + $foundintocache = 1; + + $this->lastfetchdate[get_class($this).'_'.__FUNCTION__] = $filedate; + } else { + dol_syslog(get_class($this).'::'.__FUNCTION__." cache file ".$newpathofdestfile." is not found or older than now - cachedelay (".$nowgmt." - ".$cachedelay.") so we can't use it."); + } + } + + // Load file into $data + if ($foundintocache) { // Cache file found and is not too old + dol_syslog(get_class($this).'::'.__FUNCTION__." read data from cache file ".$newpathofdestfile." ".$filedate."."); + $data = json_decode(file_get_contents($newpathofdestfile), true); + } else { + $year = $startyear; + $sm = $startmonth - 1; + if ($sm != 0) { + $year = $year - 1; + } + while ($year <= $endyear) { + $datay[$year] = $this->getAmountByMonth($year, $format); + $year++; + } + + $data = array(); + // $data = array('xval'=>array(0=>xlabel,1=>yval1,2=>yval2...),...) + for ($i = 0; $i < 12; $i++) { + $data[$i][] = isset($datay[$endyear][($i + $sm) % 12]['label']) ? $datay[$endyear][($i + $sm) % 12]['label'] : $datay[$endyear][($i + $sm) % 12][0]; // set label + $year = $startyear; + while ($year <= $endyear) { + $data[$i][] = $datay[$year][($i + $sm) % 12][1]; // set yval for x=i + $year++; + } + } + } + + // Save cache file + if (empty($foundintocache) && ($cachedelay > 0 || $cachedelay == -1)) { + dol_syslog(get_class($this).'::'.__FUNCTION__." save cache file ".$newpathofdestfile." onto disk."); + if (!dol_is_dir($conf->user->dir_temp)) { + dol_mkdir($conf->user->dir_temp); + } + $fp = fopen($newpathofdestfile, 'w'); + if ($fp) { + fwrite($fp, json_encode($data)); + fclose($fp); + if (!empty($conf->global->MAIN_UMASK)) { + $newmask = $conf->global->MAIN_UMASK; + } + @chmod($newpathofdestfile, octdec($newmask)); + } else { + dol_syslog("Failed to write cache file", LOG_ERR); + } + $this->lastfetchdate[get_class($this).'_'.__FUNCTION__] = $nowgmt; + } + + return $data; + } + + /** + * Return average of entity by month for several years + * + * @param int $endyear Start year + * @param int $startyear End year + * @param int $cachedelay Delay we accept for cache file (0=No read, no save of cache, -1=No read but save) + * @param int $format 0=Label of abscissa is a translated text, 1=Label of abscissa is month number, 2=Label of abscissa is first letter of month + * @param int $startmonth month of the fiscal year start min 1 max 12 ; if 1 = january + * @return array Array of values + */ + public function getAverageByMonthWithPrevYear($endyear, $startyear, $cachedelay = 0, $format = 0, $startmonth = 1) + { + global $conf, $user, $langs; + + if ($startyear > $endyear) { + return -1; + } + + $datay = array(); + + // Search into cache + if (!empty($cachedelay)) { + include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php'; + include_once DOL_DOCUMENT_ROOT.'/core/lib/json.lib.php'; + } + + $newpathofdestfile = $conf->user->dir_temp.'/'.get_class($this).'_'.__FUNCTION__.'_'.(empty($this->cachefilesuffix) ? '' : $this->cachefilesuffix.'_').$langs->defaultlang.'_entity.'.$conf->entity.'_user'.$user->id.'.cache'; + $newmask = '0644'; + + $nowgmt = dol_now(); + + $foundintocache = 0; + if ($cachedelay > 0) { + $filedate = dol_filemtime($newpathofdestfile); + if ($filedate >= ($nowgmt - $cachedelay)) { + $foundintocache = 1; + + $this->lastfetchdate[get_class($this).'_'.__FUNCTION__] = $filedate; + } else { + dol_syslog(get_class($this).'::'.__FUNCTION__." cache file ".$newpathofdestfile." is not found or older than now - cachedelay (".$nowgmt." - ".$cachedelay.") so we can't use it."); + } + } + + // Load file into $data + if ($foundintocache) { // Cache file found and is not too old + dol_syslog(get_class($this).'::'.__FUNCTION__." read data from cache file ".$newpathofdestfile." ".$filedate."."); + $data = json_decode(file_get_contents($newpathofdestfile), true); + } else { + $year = $startyear; + $sm = $startmonth - 1; + if ($sm != 0) { + $year = $year - 1; + } + while ($year <= $endyear) { + $datay[$year] = $this->getAverageByMonth($year); + $year++; + } + + $data = array(); + + for ($i = 0; $i < 12; $i++) { + $data[$i][] = $datay[$endyear][($i + $sm) % 12][0]; + $year = $startyear; + while ($year <= $endyear) { + $data[$i][] = $datay[$year][($i + $sm) % 12][1]; + $year++; + } + } + } + + // Save cache file + if (empty($foundintocache) && ($cachedelay > 0 || $cachedelay == -1)) { + dol_syslog(get_class($this).'::'.__FUNCTION__." save cache file ".$newpathofdestfile." onto disk."); + if (!dol_is_dir($conf->user->dir_temp)) { + dol_mkdir($conf->user->dir_temp); + } + $fp = fopen($newpathofdestfile, 'w'); + if ($fp) { + fwrite($fp, json_encode($data)); + fclose($fp); + if (!empty($conf->global->MAIN_UMASK)) { + $newmask = $conf->global->MAIN_UMASK; + } + @chmod($newpathofdestfile, octdec($newmask)); + } else { + dol_syslog("Failed to write cache file", LOG_ERR); + } + $this->lastfetchdate[get_class($this).'_'.__FUNCTION__] = $nowgmt; + } + + return $data; + } + + // Here we have low level of shared code called by XxxStats.class.php + + // phpcs:disable PEAR.NamingConventions.ValidFunctionName.PublicUnderscore + /** + * Return nb of elements by year + * + * @param string $sql SQL request + * @return array + */ + protected function _getNbByYear($sql) + { + // phpcs:enable + $result = array(); + + dol_syslog(get_class($this).'::'.__FUNCTION__."", LOG_DEBUG); + $resql = $this->db->query($sql); + if ($resql) { + $num = $this->db->num_rows($resql); + $i = 0; + while ($i < $num) { + $row = $this->db->fetch_row($resql); + $result[$i] = $row; + $i++; + } + $this->db->free($resql); + } else { + dol_print_error($this->db); + } + return $result; + } + + // phpcs:disable PEAR.NamingConventions.ValidFunctionName.PublicUnderscore + /** + * Return nb of elements, total amount and avg amount each year + * + * @param string $sql SQL request + * @return array Array with nb, total amount, average for each year + */ + protected function _getAllByYear($sql) + { + // phpcs:enable + $result = array(); + + dol_syslog(get_class($this).'::'.__FUNCTION__."", LOG_DEBUG); + $resql = $this->db->query($sql); + if ($resql) { + $num = $this->db->num_rows($resql); + $i = 0; + while ($i < $num) { + $row = $this->db->fetch_object($resql); + $result[$i]['year'] = $row->year; + $result[$i]['nb'] = $row->nb; + if ($i > 0 && $row->nb > 0) { + $result[$i - 1]['nb_diff'] = ($result[$i - 1]['nb'] - $row->nb) / $row->nb * 100; + } + $result[$i]['total'] = $row->total; + if ($i > 0 && $row->total > 0) { + $result[$i - 1]['total_diff'] = ($result[$i - 1]['total'] - $row->total) / $row->total * 100; + } + $result[$i]['avg'] = $row->avg; + if ($i > 0 && $row->avg > 0) { + $result[$i - 1]['avg_diff'] = ($result[$i - 1]['avg'] - $row->avg) / $row->avg * 100; + } + // For some $sql only + if (isset($row->weighted)) { + $result[$i]['weighted'] = $row->weighted; + if ($i > 0 && $row->weighted > 0) { + $result[$i - 1]['avg_weighted'] = ($result[$i - 1]['weighted'] - $row->weighted) / $row->weighted * 100; + } + } + $i++; + } + $this->db->free($resql); + } else { + dol_print_error($this->db); + } + return $result; + } + + // phpcs:disable PEAR.NamingConventions.ValidFunctionName.PublicUnderscore + /** + * Renvoie le nombre de documents par mois pour une annee donnee + * Return number of documents per month for a given year + * + * @param int $year Year + * @param string $sql SQL + * @param int $format 0=Label of abscissa is a translated text, 1=Label of abscissa is month number, 2=Label of abscissa is first letter of month + * @return array Array of nb each month + */ + protected function _getNbByMonth($year, $sql, $format = 0) + { + // phpcs:enable + global $langs; + + $result = array(); + $res = array(); + + dol_syslog(get_class($this).'::'.__FUNCTION__."", LOG_DEBUG); + $resql = $this->db->query($sql); + if ($resql) { + $num = $this->db->num_rows($resql); + $i = 0; + $j = 0; + while ($i < $num) { + $row = $this->db->fetch_row($resql); + $j = $row[0] * 1; + $result[$j] = $row[1]; + $i++; + } + $this->db->free($resql); + } else { + dol_print_error($this->db); + } + + for ($i = 1; $i < 13; $i++) { + $res[$i] = (isset($result[$i]) ? $result[$i] : 0); + } + + $data = array(); + + for ($i = 1; $i < 13; $i++) { + $month = 'unknown'; + if ($format == 0) { + $month = $langs->transnoentitiesnoconv('MonthShort'.sprintf("%02d", $i)); + } elseif ($format == 1) { + $month = $i; + } elseif ($format == 2) { + $month = $langs->transnoentitiesnoconv('MonthVeryShort'.sprintf("%02d", $i)); + } + //$month=dol_print_date(dol_mktime(12,0,0,$i,1,$year),($format?"%m":"%b")); + //$month=dol_substr($month,0,3); + $data[$i - 1] = array($month, $res[$i]); + } + + return $data; + } + + + // phpcs:disable PEAR.NamingConventions.ValidFunctionName.PublicUnderscore + /** + * Return the amount per month for a given year + * + * @param int $year Year + * @param string $sql SQL + * @param int $format 0=Label of abscissa is a translated text, 1=Label of abscissa is month number, 2=Label of abscissa is first letter of month + * @return array + */ + protected function _getAmountByMonth($year, $sql, $format = 0) + { + // phpcs:enable + global $langs; + + $result = array(); + $res = array(); + + dol_syslog(get_class($this).'::'.__FUNCTION__."", LOG_DEBUG); + + $resql = $this->db->query($sql); + if ($resql) { + $num = $this->db->num_rows($resql); + $i = 0; + while ($i < $num) { + $row = $this->db->fetch_row($resql); + $j = $row[0] * 1; + $result[$j] = $row[1]; + $i++; + } + $this->db->free($resql); + } else { + dol_print_error($this->db); + } + + for ($i = 1; $i < 13; $i++) { + $res[$i] = (int) round((isset($result[$i]) ? $result[$i] : 0)); + } + + $data = array(); + + for ($i = 1; $i < 13; $i++) { + $month = 'unknown'; + if ($format == 0) { + $month = $langs->transnoentitiesnoconv('MonthShort'.sprintf("%02d", $i)); + } elseif ($format == 1) { + $month = $i; + } elseif ($format == 2) { + $month = $langs->transnoentitiesnoconv('MonthVeryShort'.sprintf("%02d", $i)); + } + //$month=dol_print_date(dol_mktime(12,0,0,$i,1,$year),($format?"%m":"%b")); + //$month=dol_substr($month,0,3); + $data[$i - 1] = array($month, $res[$i]); + } + + return $data; + } + + // phpcs:disable PEAR.NamingConventions.ValidFunctionName.PublicUnderscore + /** + * Renvoie le montant moyen par mois pour une annee donnee + * Return the amount average par month for a given year + * + * @param int $year Year + * @param string $sql SQL + * @param int $format 0=Label of abscissa is a translated text, 1=Label of abscissa is month number, 2=Label of abscissa is first letter of month + * @return array + */ + protected function _getAverageByMonth($year, $sql, $format = 0) + { + // phpcs:enable + global $langs; + + $result = array(); + $res = array(); + + dol_syslog(get_class($this).'::'.__FUNCTION__."", LOG_DEBUG); + $resql = $this->db->query($sql); + if ($resql) { + $num = $this->db->num_rows($resql); + $i = 0; + $j = 0; + while ($i < $num) { + $row = $this->db->fetch_row($resql); + $j = $row[0] * 1; + $result[$j] = $row[1]; + $i++; + } + $this->db->free($resql); + } else { + dol_print_error($this->db); + } + + for ($i = 1; $i < 13; $i++) { + $res[$i] = (isset($result[$i]) ? $result[$i] : 0); + } + + $data = array(); + + for ($i = 1; $i < 13; $i++) { + $month = 'unknown'; + if ($format == 0) { + $month = $langs->transnoentitiesnoconv('MonthShort'.sprintf("%02d", $i)); + } elseif ($format == 1) { + $month = $i; + } elseif ($format == 2) { + $month = $langs->transnoentitiesnoconv('MonthVeryShort'.sprintf("%02d", $i)); + } + //$month=dol_print_date(dol_mktime(12,0,0,$i,1,$year),($format?"%m":"%b")); + //$month=dol_substr($month,0,3); + $data[$i - 1] = array($month, $res[$i]); + } + + return $data; + } + + // phpcs:disable PEAR.NamingConventions.ValidFunctionName.PublicUnderscore + /** + * Returns the summed amounts per year for a given number of past years ending now + * @param string $sql SQL + * @return array + */ + protected function _getAmountByYear($sql) + { + $result = array(); + $resql = $this->db->query($sql); + if ($resql) { + $num = $this->db->num_rows($resql); + $i = 0; + while ($i < $num) { + $row = $this->db->fetch_row($resql); + $j = (int) $row[0]; + $result[] = [ + 0 => (int) $row[0], + 1 => (int) $row[1], + ]; + $i++; + } + $this->db->free($resql); + } + return $result; + } +} + diff --git a/class/facturerecstats.class.php b/class/facturerecstats.class.php new file mode 100644 index 0000000..0296e07 --- /dev/null +++ b/class/facturerecstats.class.php @@ -0,0 +1,273 @@ + + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/** + * \file class/facturerecstats.class.php + * \ingroup doliproject + * \brief Fichier de la classe de gestion des stats des factures récurrentes + */ +include_once DOL_DOCUMENT_ROOT.'/custom/doliproject/class/doliprojectstats.php'; +include_once DOL_DOCUMENT_ROOT.'/compta/facture/class/facture-rec.class.php'; +//include_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.facture.class.php'; +include_once DOL_DOCUMENT_ROOT.'/core/lib/date.lib.php'; + +/** + * Class to manage stats for recurring invoices (customer and supplier) + */ +class FactureRecStats extends DoliProjectStats +{ + public $socid; + public $userid; + + /** + * @var string Name of table without prefix where object is stored + */ + public $table_element; + + public $from; + public $field; + public $where; + public $join; + + /** + * Constructor + * + * @param DoliDB $db Database handler + * @param int $socid Id third party for filter. This value must be forced during the new to external user company if user is an external user. + * @param string $mode Option ('customer', 'supplier') + * @param int $userid Id user for filter (creation user) + * @param int $typentid Id typent of thirdpary for filter + * @param int $categid Id category of thirdpary for filter + * @param int $categinvoicerecid Id category of Invoice rec for filter + */ + public function __construct($db, $socid, $mode, $userid = 0, $typentid = 0, $categid = 0, $categinvoicerecid = 0) + { + global $user, $conf; + + $this->db = $db; + $this->socid = ($socid > 0 ? $socid : 0); + $this->userid = $userid; + $this->cachefilesuffix = $mode; + $this->join = ''; + + if ($mode == 'customer') { + $object = new FactureRec($this->db); + $this->from = MAIN_DB_PREFIX.$object->table_element." as fr"; + $this->from_line = MAIN_DB_PREFIX.$object->table_element_line." as tl"; + $this->field = 'total_ht'; + $this->field_line = 'total_ht'; + } +// if ($mode == 'supplier') { +// $object = new FactureFournisseur($this->db); +// $this->from = MAIN_DB_PREFIX.$object->table_element." as f"; +// $this->from_line = MAIN_DB_PREFIX.$object->table_element_line." as tl"; +// $this->field = 'total_ht'; +// $this->field_line = 'total_ht'; +// } + + $this->where = " fr.suspended >= 0"; + $this->where .= " AND fr.entity IN (".getEntity('invoicerec').")"; + if (empty($user->rights->societe->client->voir) && !$this->socid) { + $this->where .= " AND fr.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id); + } +// if ($mode == 'customer') { +// $this->where .= " AND (fr.suspended <> 3 OR fr.close_code <> 'replaced')"; // Exclude replaced invoices as they are duplicated (we count closed invoices for other reasons) +// } + if ($this->socid) { + $this->where .= " AND fr.fk_soc = ".((int) $this->socid); + } + if ($this->userid > 0) { + $this->where .= ' AND fr.fk_user_author = '.((int) $this->userid); + } +// if (!empty($conf->global->FACTURE_DEPOSITS_ARE_JUST_PAYMENTS)) { +// $this->where .= " AND f.type IN (0,1,2,5)"; +// } else { +// $this->where .= " AND f.type IN (0,1,2,3,5)"; +// } + + if ($typentid) { + $this->join .= ' LEFT JOIN '.MAIN_DB_PREFIX.'societe as s ON s.rowid = fr.fk_soc'; + $this->where .= ' AND s.fk_typent = '.((int) $typentid); + } + + if ($categid) { + $this->join .= ' LEFT JOIN '.MAIN_DB_PREFIX.'categorie_societe as cs ON cs.fk_soc = fr.fk_soc'; + $this->join .= ' LEFT JOIN '.MAIN_DB_PREFIX.'categorie as c ON c.rowid = cs.fk_categorie'; + $this->where .= ' AND c.rowid = '.((int) $categid); + } + + if ($categinvoicerecid) { + $this->join .= ' LEFT JOIN '.MAIN_DB_PREFIX.'categorie_invoicerec as cir ON cir.fk_invoicerec = fr.rowid'; + $this->join .= ' LEFT JOIN '.MAIN_DB_PREFIX.'categorie as c ON c.rowid = cir.fk_categorie'; + $this->where .= ' AND c.rowid = '.((int) $categinvoicerecid); + } + } + + + /** + * Return recurring invoices number by month for a year + * + * @param int $year Year to scan + * @param int $format 0=Label of abscissa is a translated text, 1=Label of abscissa is month number, 2=Label of abscissa is first letter of month + * @return array Array of values + */ + public function getNbByMonth($year, $format = 0) + { + global $user; + + $sql = "SELECT date_format(fr.date_when,'%m') as dm, COUNT(*) as nb"; + $sql .= " FROM ".$this->from; + if (empty($user->rights->societe->client->voir) && !$this->socid) { + $sql .= ", ".MAIN_DB_PREFIX."societe_commerciaux as sc"; + } + $sql .= $this->join; + $sql .= " WHERE fr.date_when BETWEEN '".$this->db->idate(dol_get_first_day($year))."' AND '".$this->db->idate(dol_get_last_day($year))."'"; + $sql .= " AND ".$this->where; + $sql .= " GROUP BY dm"; + $sql .= $this->db->order('dm', 'DESC'); + + $res = $this->_getNbByMonth($year, $sql, $format); + + return $res; + } + + + /** + * Return recurring invoices number per year + * + * @return array Array with number by year + */ + public function getNbByYear() + { + global $user; + + $sql = "SELECT date_format(fr.date_when,'%Y') as dm, COUNT(*), SUM(c.".$this->field.")"; + $sql .= " FROM ".$this->from; + if (empty($user->rights->societe->client->voir) && !$this->socid) { + $sql .= ", ".MAIN_DB_PREFIX."societe_commerciaux as sc"; + } + $sql .= $this->join; + $sql .= " WHERE ".$this->where; + $sql .= " GROUP BY dm"; + $sql .= $this->db->order('dm', 'DESC'); + + return $this->_getNbByYear($sql); + } + + + /** + * Return the recurring invoices amount by month for a year + * + * @param int $year Year to scan + * @param int $format 0=Label of abscissa is a translated text, 1=Label of abscissa is month number, 2=Label of abscissa is first letter of month + * @return array Array with amount by month + */ + public function getAmountByMonth($year, $format = 0) + { + global $user; + + $sql = "SELECT date_format(date_when,'%m') as dm, SUM(fr.".$this->field.")"; + $sql .= " FROM ".$this->from; + if (empty($user->rights->societe->client->voir) && !$this->socid) { + $sql .= ", ".MAIN_DB_PREFIX."societe_commerciaux as sc"; + } + $sql .= $this->join; + $sql .= " WHERE fr.date_when BETWEEN '".$this->db->idate(dol_get_first_day($year))."' AND '".$this->db->idate(dol_get_last_day($year))."'"; + $sql .= " AND ".$this->where; + $sql .= " GROUP BY dm"; + $sql .= $this->db->order('dm', 'DESC'); + + $res = $this->_getAmountByMonth($year, $sql, $format); + + return $res; + } + + /** + * Return average amount + * + * @param int $year Year to scan + * @return array Array of values + */ + public function getAverageByMonth($year) + { + global $user; + + $sql = "SELECT date_format(date_when,'%m') as dm, AVG(fr.".$this->field.")"; + $sql .= " FROM ".$this->from; + if (empty($user->rights->societe->client->voir) && !$this->socid) { + $sql .= ", ".MAIN_DB_PREFIX."societe_commerciaux as sc"; + } + $sql .= $this->join; + $sql .= " WHERE fr.date_when BETWEEN '".$this->db->idate(dol_get_first_day($year))."' AND '".$this->db->idate(dol_get_last_day($year))."'"; + $sql .= " AND ".$this->where; + $sql .= " GROUP BY dm"; + $sql .= $this->db->order('dm', 'DESC'); + + return $this->_getAverageByMonth($year, $sql); + } + + /** + * Return nb, total and average + * + * @return array Array of values + */ + public function getAllByYear() + { + global $user; + + $sql = "SELECT date_format(date_when,'%Y') as year, COUNT(*) as nb, SUM(fr.".$this->field.") as total, AVG(fr.".$this->field.") as avg"; + $sql .= " FROM ".$this->from; + if (empty($user->rights->societe->client->voir) && !$this->socid) { + $sql .= ", ".MAIN_DB_PREFIX."societe_commerciaux as sc"; + } + $sql .= $this->join; + $sql .= " WHERE ".$this->where; + $sql .= " GROUP BY year"; + $sql .= $this->db->order('year', 'DESC'); + + return $this->_getAllByYear($sql); + } + + /** + * Return the recurring invoices amount by year for a number of past years + * + * @param int $numberYears Years to scan + * @param int $format 0=Label of abscissa is a translated text, 1=Label of abscissa is year, 2=Label of abscissa is last number of year + * @return array Array with amount by year + */ + public function getAmountByYear($numberYears, $format = 0) + { + global $user; + + $endYear = date('Y'); + $startYear = $endYear - $numberYears; + $sql = "SELECT date_format(date_when,'%Y') as dm, SUM(fr.".$this->field.")"; + $sql .= " FROM ".$this->from; + if (empty($user->rights->societe->client->voir) && !$this->socid) { + $sql .= ", ".MAIN_DB_PREFIX."societe_commerciaux as sc"; + } + $sql .= $this->join; + $sql .= " WHERE fr.date_when BETWEEN '".$this->db->idate(dol_get_first_day($startYear))."' AND '".$this->db->idate(dol_get_last_day($endYear))."'"; + $sql .= " AND ".$this->where; + $sql .= " GROUP BY dm"; + $sql .= $this->db->order('dm', 'ASC'); + + $res = $this->_getAmountByYear($sql); + return $res; + } +} + diff --git a/class/workinghours.class.php b/class/workinghours.class.php index 5713623..eca0e3c 100644 --- a/class/workinghours.class.php +++ b/class/workinghours.class.php @@ -178,6 +178,100 @@ public function fetch($id, $ref = null, $morewhere = '') return $this->fetchCommon($id, $ref, $morewhere); } + /** + * Load list of objects in memory from the database. + * + * @param string $sortorder Sort Order + * @param string $sortfield Sort field + * @param int $limit limit + * @param int $offset Offset + * @param array $filter Filter array. Example array('field'=>'valueforlike', 'customurl'=>...) + * @param string $filtermode Filter mode (AND or OR) + * @return array|int int <0 if KO, array of pages if OK + */ + public function fetchAll($sortorder = '', $sortfield = '', $limit = 0, $offset = 0, array $filter = array(), $filtermode = 'AND') + { + global $conf; + + dol_syslog(__METHOD__, LOG_DEBUG); + + $records = array(); + + $sql = 'SELECT '; + $sql .= $this->getFieldList(); + $sql .= ' FROM '.MAIN_DB_PREFIX.$this->table_element.' as t'; + if ($this->ismultientitymanaged) { + $sql .= ' WHERE t.entity IN ('.getEntity($this->table_element).')'; + } else { + $sql .= ' WHERE 1 = 1'; + } + // Manage filter + $sqlwhere = array(); + if (count($filter) > 0) { + foreach ($filter as $key => $value) { + if ($key == 't.rowid') { + $sqlwhere[] = $key." = ".((int) $value); + } elseif (strpos($key, 'date') !== false) { + $sqlwhere[] = $key." = '".$this->db->idate($value)."'"; + } elseif ($key == 'customsql') { + $sqlwhere[] = $value; + } else { + $sqlwhere[] = $key." LIKE '%".$this->db->escape($value)."%'"; + } + } + } + if (count($sqlwhere) > 0) { + $sql .= " AND (".implode(" ".$filtermode." ", $sqlwhere).")"; + } + + if (!empty($sortfield)) { + $sql .= $this->db->order($sortfield, $sortorder); + } + if (!empty($limit)) { + $sql .= $this->db->plimit($limit, $offset); + } + + $resql = $this->db->query($sql); + if ($resql) { + $num = $this->db->num_rows($resql); + + while ($obj = $this->db->fetch_object($resql)) { + $record = new self($this->db); + $record->setVarsFromFetchObj($obj); + + $records[$record->id] = $record; + } + $this->db->free($resql); + + return $records; + } else { + $this->errors[] = 'Error '.$this->db->lasterror(); + dol_syslog(__METHOD__.' '.join(',', $this->errors), LOG_ERR); + + return -1; + } + } + + /** + * Load list of objects in memory from the database. + * + * @param string $sortorder Sort Order + * @param string $sortfield Sort field + * @param int $limit limit + * @param int $offset Offset + * @param array $filter Filter array. Example array('field'=>'valueforlike', 'customurl'=>...) + * @param string $filtermode Filter mode (AND or OR) + * @return array|int int <0 if KO, array of pages if OK + */ + public function fetchCurrentWorkingHours($id, $type) + { + $current_workinghours = $this->fetchAll('', '',0,0, array('element_type' => $type, 'element_id' => $id, 'status' => 1)); + if (is_array($current_workinghours) && !empty($current_workinghours)) { + $current_workinghours = array_shift($current_workinghours); + } + return $current_workinghours; + } + /** * Return label of contact status * diff --git a/core/modules/modDoliproject.class.php b/core/modules/modDoliproject.class.php index 3e0b66c..4955199 100644 --- a/core/modules/modDoliproject.class.php +++ b/core/modules/modDoliproject.class.php @@ -43,7 +43,7 @@ public function __construct($db) global $langs, $conf; $this->db = $db; - $this->numero = 500000; // TODO Go on page https://wiki.dolibarr.org/index.php/List_of_modules_id to reserve an id number for your module + $this->numero = 436370; // TODO Go on page https://wiki.dolibarr.org/index.php/List_of_modules_id to reserve an id number for your module $this->rights_class = 'doliproject'; $this->family = "other"; $this->module_position = '90'; @@ -52,9 +52,9 @@ public function __construct($db) $this->descriptionlong = "Doliproject description (Long)"; $this->editor_name = 'Eoxia'; $this->editor_url = 'https://eoxia.com'; - $this->version = '1.1.1'; + $this->version = '1.3.0'; $this->const_name = 'MAIN_MODULE_'.strtoupper($this->name); - $this->picto = 'generic'; + $this->picto = 'doliproject256px@doliproject'; $this->module_parts = array( 'triggers' => 1, @@ -72,6 +72,14 @@ public function __construct($db) 'invoicecard', 'ticketcard', 'projecttaskcard', + 'projecttaskscard', + 'tasklist', + 'category', + 'categoryindex', + 'invoicereccard', + 'cron', + 'cronjoblist', + 'projecttasktime' ), ), 'moduleforexternal' => 0, @@ -80,7 +88,7 @@ public function __construct($db) $this->dirs = array("/doliproject/temp"); $this->config_page_url = array("setup.php@doliproject"); $this->hidden = false; - $this->depends = array('modProjet'); + $this->depends = array('modProjet', 'modBookmark', 'modHoliday'); $this->requiredby = array(); // List of module class names as string to disable if this one is disabled. Example: array('modModuleToDisable1', ...) $this->conflictwith = array(); // List of module class names as string this module is in conflict with. Example: array('modModuleToDisable1', ...) $this->langfiles = array("doliproject@doliproject"); @@ -90,15 +98,16 @@ public function __construct($db) $this->warnings_activation_ext = array(); // Warning to show when we activate an external module. array('always'='text') or array('FR'='textfr','ES'='textes'...) $this->const = array(); - $r ++; - $this->const[$r][0] = "DOLIPROJECT_DEFAUT_TICKET_TIME"; - $this->const[$r][1] = "chaine"; - $this->const[$r][2] = '15'; - $this->const[$r][3] = 'Default Time'; - $this->const[$r][4] = 0; - $this->const[$r][5] = 'current'; - $this->const[$r][6] = 0; - + $this->const = array( + // CONST CONFIGURATION + 1 => array('DOLIPROJECT_DEFAUT_TICKET_TIME', 'chaine', '15', 'Default Time', 0, 'current'), + 2 => array('DOLIPROJECT_SHOW_ONLY_FAVORITE_TASKS', 'integer', 1, '', 0, 'current'), + 3 => array('DOLIPROJECT_HR_PROJECT', 'integer', 0, '', 0, 'current'), + 4 => array('DOLIPROJECT_TIMESPENT_BOOKMARK_SET', 'integer', 0, '', 0, 'current'), + 5 => array('DOLIPROJECT_EXCEEDED_TIME_SPENT_COLOR', 'chaine', '#FF0000', '', 0, 'current'), + 6 => array('DOLIPROJECT_NOT_EXCEEDED_TIME_SPENT_COLOR', 'chaine', '#FFA500', '', 0, 'current'), + 7 => array('DOLIPROJECT_PERFECT_TIME_SPENT_COLOR', 'chaine', '#008000', '', 0, 'current'), + ); if (!isset($conf->doliproject) || !isset($conf->doliproject->enabled)) { $conf->doliproject = new stdClass(); @@ -142,12 +151,12 @@ public function __construct($db) /* BEGIN MODULEBUILDER TOPMENU */ $r = 0; $this->menu[$r++] = array( - // 'fk_menu'=>'', // '' if this is a top menu. For left menu, use 'fk_mainmenu=xxx' or 'fk_mainmenu=xxx,fk_leftmenu=yyy' where xxx is mainmenucode and yyy is a leftmenucode - // 'type'=>'top', // This is a Top menu entry - // 'titre'=>'ModuleDoliprojectName', - // 'mainmenu'=>'doliproject', - // 'leftmenu'=>'', - // 'url'=>'/doliproject/doliprojectindex.php', + 'fk_menu'=>'fk_mainmenu=project,fk_leftmenu=timespent', // '' if this is a top menu. For left menu, use 'fk_mainmenu=xxx' or 'fk_mainmenu=xxx,fk_leftmenu=yyy' where xxx is mainmenucode and yyy is a leftmenucode + 'type'=>'left', // This is a Top menu entry + 'titre'=>'DoliprojectTimeSpent', + 'mainmenu'=>'project', + 'leftmenu'=>'doliproject_timespent_list', + 'url'=>'/doliproject/view/timespent_list.php', 'langs'=>'doliproject@doliproject', // Lang file to use (without .lang) by module. File must be in langs/code_CODE/ directory. 'position'=>1000 + $r, 'enabled'=>'$conf->doliproject->enabled', // Define condition to show or hide menu entry. Use '$conf->doliproject->enabled' if entry must be visible if module is enabled. @@ -156,26 +165,40 @@ public function __construct($db) 'user'=>2, // 0=Menu for internal users, 1=external users, 2=both ); $this->menu[$r++] = array( - 'fk_menu' => '', // '' if this is a top menu. For left menu, use 'fk_mainmenu=xxx' or 'fk_mainmenu=xxx,fk_leftmenu=yyy' where xxx is mainmenucode and yyy is a leftmenucode - 'type' => 'top', // This is a Top menu entry - 'titre' => 'Doliproject', - 'mainmenu' => 'doliproject', - 'leftmenu' => '', - 'url' => '/doliproject/doliprojectindex.php', + 'fk_menu' =>'fk_mainmenu=project,fk_leftmenu=timespent', // '' if this is a top menu. For left menu, use 'fk_mainmenu=xxx' or 'fk_mainmenu=xxx,fk_leftmenu=yyy' where xxx is mainmenucode and yyy is a leftmenucode + 'type' => 'left', // This is a Top menu entry + 'titre' => $langs->trans('AddTimeSpent'), + 'mainmenu' => 'project', + 'leftmenu' => 'timespent', + 'url' => '/doliproject/view/timespent_day.php', 'langs' => 'doliproject@doliproject', // Lang file to use (without .lang) by module. File must be in langs/code_CODE/ directory. 'position' => 48520 + $r, 'enabled' => '$conf->doliproject->enabled', // Define condition to show or hide menu entry. Use '$conf->doliproject->enabled' if entry must be visible if module is enabled. - 'perms' => '$user->rights->doliproject->lire', // Use 'perms'=>'$user->rights->doliproject->level1->level2' if you want your menu with a permission rules + 'perms' => '$user->rights->doliproject->lire', // Use 'perms'=>'$user->rights->doliproject->digiriskconst->read' if you want your menu with a permission rules 'target' => '', 'user' => 2, // 0=Menu for internal users, 1=external users, 2=both ); $this->menu[$r++] = array( - 'fk_menu' => 'fk_mainmenu=doliproject', // '' if this is a top menu. For left menu, use 'fk_mainmenu=xxx' or 'fk_mainmenu=xxx,fk_leftmenu=yyy' where xxx is mainmenucode and yyy is a leftmenucode + 'fk_menu'=>'fk_mainmenu=hrm,fk_leftmenu=timespent', // '' if this is a top menu. For left menu, use 'fk_mainmenu=xxx' or 'fk_mainmenu=xxx,fk_leftmenu=yyy' where xxx is mainmenucode and yyy is a leftmenucode + 'type'=>'left', // This is a Top menu entry + 'titre'=>'DoliprojectTimeSpent', + 'mainmenu'=>'hrm', + 'leftmenu'=>'doliproject_timespent_list', + 'url'=>'/doliproject/view/timespent_list.php', + 'langs'=>'doliproject@doliproject', // Lang file to use (without .lang) by module. File must be in langs/code_CODE/ directory. + 'position'=>1000 + $r, + 'enabled'=>'$conf->doliproject->enabled', // Define condition to show or hide menu entry. Use '$conf->doliproject->enabled' if entry must be visible if module is enabled. + 'perms'=>'1', // Use 'perms'=>'$user->rights->doliproject->myobject->read' if you want your menu with a permission rules + 'target'=>'', + 'user'=>2, // 0=Menu for internal users, 1=external users, 2=both + ); + $this->menu[$r++] = array( + 'fk_menu' =>'fk_mainmenu=hrm,fk_leftmenu=timespent', // '' if this is a top menu. For left menu, use 'fk_mainmenu=xxx' or 'fk_mainmenu=xxx,fk_leftmenu=yyy' where xxx is mainmenucode and yyy is a leftmenucode 'type' => 'left', // This is a Top menu entry - 'titre' => ' ' . $langs->trans('TimeSpent'), - 'mainmenu' => 'doliproject', - 'leftmenu' => '', - 'url' => '/doliproject/view/timespent.php', + 'titre' => $langs->trans('AddTimeSpent'), + 'mainmenu' => 'hrm', + 'leftmenu' => 'timespent', + 'url' => '/doliproject/view/timespent_day.php', 'langs' => 'doliproject@doliproject', // Lang file to use (without .lang) by module. File must be in langs/code_CODE/ directory. 'position' => 48520 + $r, 'enabled' => '$conf->doliproject->enabled', // Define condition to show or hide menu entry. Use '$conf->doliproject->enabled' if entry must be visible if module is enabled. @@ -183,6 +206,34 @@ public function __construct($db) 'target' => '', 'user' => 2, // 0=Menu for internal users, 1=external users, 2=both ); + $this->menu[$r++] = array( + 'fk_menu' =>'fk_mainmenu=billing,fk_leftmenu=customers_bills', // '' if this is a top menu. For left menu, use 'fk_mainmenu=xxx' or 'fk_mainmenu=xxx,fk_leftmenu=yyy' where xxx is mainmenucode and yyy is a leftmenucode + 'type' => 'left', // This is a Top menu entry + 'titre' => $langs->trans('RecurringInvoicesStatistics'), + 'mainmenu' => 'billing', + 'leftmenu' => 'customers_bills', + 'url' => '/doliproject/view/recurringinvoicestatistics.php', + 'langs' => 'doliproject@doliproject', // Lang file to use (without .lang) by module. File must be in langs/code_CODE/ directory. + 'position' => 1000 + $r, + 'enabled' => '$conf->doliproject->enabled && $conf->facture->enabled', // Define condition to show or hide menu entry. Use '$conf->doliproject->enabled' if entry must be visible if module is enabled. + 'perms' => '$user->rights->doliproject->lire && $user->rights->facture->lire', // Use 'perms'=>'$user->rights->doliproject->digiriskconst->read' if you want your menu with a permission rules + 'target' => '', + 'user' => 2, // 0=Menu for internal users, 1=external users, 2=both + ); + $this->menu[$r++] = array( + 'fk_menu' => 'fk_mainmenu=billing,fk_leftmenu=customers_bills', + 'type' => 'left', + 'titre' => $langs->trans('Categories'), + 'mainmenu' => 'billing', + 'leftmenu' => 'customers_bills', + 'url' => '/categories/index.php?type=invoice', + 'langs' => 'doliproject@doliproject', + 'position' => 1100 + $r, + 'enabled' => '$conf->doliproject->enabled && $conf->categorie->enabled', + 'perms' => '$user->rights->doliproject->lire && $user->rights->facture->lire', + 'target' => '', + 'user' => 0, + ); /* END MODULEBUILDER TOPMENU */ } @@ -196,11 +247,119 @@ public function __construct($db) */ public function init($options = '') { - global $conf, $langs; + global $conf, $langs, $db, $user; + $langs->load('doliproject@doliproject'); $result = $this->_load_tables('/doliproject/sql/'); if ($result < 0) return -1; // Do not activate module if error 'not allowed' returned when loading module SQL queries (the _load_table run sql with run_sql with the error allowed parameter set to 'default') + if ($conf->global->DOLIPROJECT_HR_PROJECT < 1) { + + require_once DOL_DOCUMENT_ROOT . '/projet/class/project.class.php'; + require_once DOL_DOCUMENT_ROOT . '/projet/class/task.class.php'; + require_once DOL_DOCUMENT_ROOT . '/core/lib/date.lib.php'; + require_once DOL_DOCUMENT_ROOT . '/core/modules/project/mod_project_simple.php'; + + $project = new Project($db); + $projectRef = new $conf->global->PROJECT_ADDON(); + + $project->ref = $projectRef->getNextValue('', $project); + $project->title = $langs->trans('HumanResources') . ' - ' . $conf->global->MAIN_INFO_SOCIETE_NOM; + $project->description = $langs->trans('HRDescription'); + $project->date_c = dol_now(); + $currentYear = dol_print_date(dol_now(), '%Y'); + $fiscalMonthStart = $conf->global->SOCIETE_FISCAL_MONTH_START; + $startdate = dol_mktime('0', '0', '0', $fiscalMonthStart ? $fiscalMonthStart : '1', '1', $currentYear); + $project->date_start = $startdate; + + $project->usage_task = 1; + + $startdateAddYear = dol_time_plus_duree($startdate, 1, 'y'); + $startdateAddYearMonth = dol_time_plus_duree($startdateAddYear, -1, 'd'); + $enddate = dol_print_date($startdateAddYearMonth, 'dayrfc'); + $project->date_end = $enddate; + $project->statut = 1; + + $result = $project->create($user); + + if ($result > 0) { + dolibarr_set_const($db, 'DOLIPROJECT_HR_PROJECT', $result, 'integer', 0, '', $conf->entity); + $task = new Task($db); + $defaultref = ''; + $obj = empty($conf->global->PROJECT_TASK_ADDON) ? 'mod_task_simple' : $conf->global->PROJECT_TASK_ADDON; + + if (!empty($conf->global->PROJECT_TASK_ADDON) && is_readable(DOL_DOCUMENT_ROOT . "/core/modules/project/task/" . $conf->global->PROJECT_TASK_ADDON . ".php")) { + require_once DOL_DOCUMENT_ROOT . "/core/modules/project/task/" . $conf->global->PROJECT_TASK_ADDON . '.php'; + $modTask = new $obj; + $defaultref = $modTask->getNextValue('', null); + } + + $task->fk_project = $result; + $task->ref = $defaultref; + $task->label = $langs->trans('Holidays'); + $task->date_c = dol_now(); + $task->create($user); + + $task->fk_project = $result; + $task->ref = $modTask->getNextValue('', null);; + $task->label = $langs->trans('PaidHolidays'); + $task->date_c = dol_now(); + $task->create($user); + + $task->fk_project = $result; + $task->ref = $modTask->getNextValue('', null);; + $task->label = $langs->trans('SickLeave'); + $task->date_c = dol_now(); + $task->create($user); + + $task->fk_project = $result; + $task->ref = $modTask->getNextValue('', null);; + $task->label = $langs->trans('PublicHoliday'); + $task->date_c = dol_now(); + $task->create($user); + + $task->fk_project = $result; + $task->ref = $modTask->getNextValue('', null);; + $task->label = $langs->trans('RTT'); + $task->date_c = dol_now(); + $task->create($user); + + dolibarr_set_const($db, 'DOLIPROJECT_RTT_TASK', 1, 'integer', 0, '', $conf->entity); + } + + } + if ($conf->global->DOLIPROJECT_RTT_TASK < 1) { + require_once DOL_DOCUMENT_ROOT . '/projet/class/task.class.php'; + + $task = new Task($db); + $obj = empty($conf->global->PROJECT_TASK_ADDON) ? 'mod_task_simple' : $conf->global->PROJECT_TASK_ADDON; + + if (!empty($conf->global->PROJECT_TASK_ADDON) && is_readable(DOL_DOCUMENT_ROOT . "/core/modules/project/task/" . $conf->global->PROJECT_TASK_ADDON . ".php")) { + require_once DOL_DOCUMENT_ROOT . "/core/modules/project/task/" . $conf->global->PROJECT_TASK_ADDON . '.php'; + $modTask = new $obj; + } + + $task->fk_project = $conf->global->DOLIPROJECT_HR_PROJECT; + $task->ref = $modTask->getNextValue('', null);; + $task->label = $langs->trans('RTT'); + $task->date_c = dol_now(); + $rtt_task_id = $task->create($user); + + dolibarr_set_const($db, 'DOLIPROJECT_RTT_TASK', $rtt_task_id, 'integer', 0, '', $conf->entity); + } + + if ($conf->global->DOLIPROJECT_TIMESPENT_BOOKMARK_SET < 1) { + include_once DOL_DOCUMENT_ROOT.'/bookmarks/class/bookmark.class.php'; + + $bookmark = new Bookmark($db); + + $bookmark->title = $langs->trans('TimeSpent'); + $bookmark->url = DOL_URL_ROOT . '/custom/doliproject/view/timespent_day.php?mainmenu=project'; + $bookmark->target = 0; + $bookmark->position = 10; + $bookmark->create($user); + } + // Create extrafields during init include_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php'; $extra_fields = new ExtraFields($this->db); @@ -211,10 +370,10 @@ public function init($options = '') $param['options']['Facture:compta/facture/class/facture.class.php'] = NULL; $extra_fields->addExtraField('fk_facture_name', 'Facture', 'link', 100, NULL, 'projet_task', 1, 0, NULL, $param, 1, 1, 1); //extrafields task unset($param); - $extra_fields->update('fk_task', 'Tâche', 'sellist', '', 'ticket', 0, 0, 100, 'a:1:{s:7:"options";a:1:{s:110:"projet_task:ref:rowid::entity = $ENTITY$ AND fk_projet = ($SEL$ fk_project FROM llx_ticket WHERE rowid = $ID$)";N;}}', 1, 1, 'preg_match(\'/public/\',$_SERVER[\'PHP_SELF\'])?0:1'); - $extra_fields->addExtraField('fk_task', 'Tâche', 'sellist', 100, NULL, 'ticket', 0, 0, NULL, 'a:1:{s:7:"options";a:1:{s:110:"projet_task:ref:rowid::entity = $ENTITY$ AND fk_projet = ($SEL$ fk_project FROM llx_ticket WHERE rowid = $ID$)";N;}}', 1, 1, 'preg_match(\'/public/\',$_SERVER[\'PHP_SELF\'])?0:1'); //extrafields ticket + $extra_fields->update('fk_task', 'Tâche', 'sellist', '', 'ticket', 0, 0, 100, 'a:1:{s:7:"options";a:1:{s:110:"projet_task:ref:rowid::entity = $ENTITY$ AND fk_projet = ($SEL$ fk_project FROM '. MAIN_DB_PREFIX .'ticket WHERE rowid = $ID$)";N;}}', 1, 1, '1'); + $extra_fields->addExtraField('fk_task', 'Tâche', 'sellist', 100, NULL, 'ticket', 0, 0, NULL, 'a:1:{s:7:"options";a:1:{s:110:"projet_task:ref:rowid::entity = $ENTITY$ AND fk_projet = ($SEL$ fk_project FROM '. MAIN_DB_PREFIX .'ticket WHERE rowid = $ID$)";N;}}', 1, 1, '1'); //extrafields ticket - return $this->_init($sql, $options); + return $this->_init(array(), $options); } /** diff --git a/core/triggers/interface_99_modDoliproject_DoliprojectTriggers.class.php b/core/triggers/interface_99_modDoliproject_DoliprojectTriggers.class.php index e3634cc..5a883fc 100644 --- a/core/triggers/interface_99_modDoliproject_DoliprojectTriggers.class.php +++ b/core/triggers/interface_99_modDoliproject_DoliprojectTriggers.class.php @@ -58,7 +58,7 @@ public function __construct($db) $this->family = "demo"; $this->description = "Doliproject triggers."; // 'development', 'experimental', 'dolibarr' or version - $this->version = 'development'; + $this->version = '1.3.0'; $this->picto = 'doliproject@doliproject'; } @@ -142,6 +142,27 @@ public function runTrigger($action, $object, User $user, Translate $langs, Conf } break; + case 'BILL_CREATE': + dol_syslog("Trigger '".$this->name."' for action '$action' launched by ".__FILE__.". id=".$object->id); + require_once __DIR__ . '/../../lib/doliproject_functions.lib.php'; + $categories = GETPOST('categories', 'array:int'); + setCategoriesObject($categories, 'invoice', false, $object); + break; + + case 'BILLREC_CREATE': + dol_syslog("Trigger '".$this->name."' for action '$action' launched by ".__FILE__.". id=".$object->id); + require_once DOL_DOCUMENT_ROOT . '/categories/class/categorie.class.php'; + require_once __DIR__ . '/../../lib/doliproject_functions.lib.php'; + $cat = new Categorie($this->db); + $categories = $cat->containing(GETPOST('facid'),'invoice'); + if (is_array($categories) && !empty($categories)) { + foreach ($categories as $category) { + $categoryArray[] = $category->id; + } + } + setCategoriesObject($categoryArray, 'invoicerec', false, $object); + break; + default: dol_syslog("Trigger '".$this->name."' for action '$action' launched by ".__FILE__.". id=".$object->id); break; diff --git a/img/doliproject.png b/img/doliproject.png new file mode 100644 index 0000000..2c282f6 Binary files /dev/null and b/img/doliproject.png differ diff --git a/img/doliproject32px.png b/img/doliproject32px.png new file mode 100644 index 0000000..2c282f6 Binary files /dev/null and b/img/doliproject32px.png differ diff --git a/img/object_doliproject.png b/img/object_doliproject.png index b421fe3..2c282f6 100644 Binary files a/img/object_doliproject.png and b/img/object_doliproject.png differ diff --git a/img/object_doliproject256px.png b/img/object_doliproject256px.png new file mode 100644 index 0000000..2c282f6 Binary files /dev/null and b/img/object_doliproject256px.png differ diff --git a/js/doliproject.js.php b/js/doliproject.js.php index 10bea82..7a0f2ba 100644 --- a/js/doliproject.js.php +++ b/js/doliproject.js.php @@ -122,7 +122,7 @@ /** - * Initialise l'objet "risk" ainsi que la méthode "init" obligatoire pour la bibliothèque EoxiaJS. + * Initialise l'objet "task" ainsi que la méthode "init" obligatoire pour la bibliothèque EoxiaJS. * * @since 1.0.0 * @version 1.0.0 @@ -150,11 +150,13 @@ * @return {void} */ window.eoxiaJS.task.event = function() { - $( document ).on( 'click', '.tabBar', window.eoxiaJS.task.toggleTaskFavorite ); + $( document ).on( 'click', '.auto-fill-timespent', window.eoxiaJS.task.addTimeSpent ); + $( document ).on( 'click', '.auto-fill-timespent-project', window.eoxiaJS.task.divideTimeSpent ); + $( document ).on( 'click', '.show-only-favorite-tasks', window.eoxiaJS.task.showOnlyFavoriteTasks ); }; /** - * Lors du clic sur un taskCategory, remplaces le contenu du toggle et met l'image du risque sélectionné. + * Remplit automatiquement le temps à pointer disponible sur une tâche * * @since 1.0.0 * @version 1.0.0 @@ -162,18 +164,74 @@ * @param {MouseEvent} event [description] * @return {void} */ -window.eoxiaJS.task.toggleTaskFavorite = function( event ) { -console.log('fgff') +window.eoxiaJS.task.addTimeSpent = function( event ) { + let nonConsumedMinutes = $('.non-consumed-time-minute').val() + let nonConsumedHours = $('.non-consumed-time-hour').val() + $('.inputhour').val('') + $('.inputminute').val('') + $(this).closest('.duration').find('.inputhour').val(nonConsumedHours) + $(this).closest('.duration').find('.inputminute').val(nonConsumedMinutes) +}; + +/** + * Répartit automatiquement le temps à pointer disponible entre les tâches du projet + * + * @since 1.0.0 + * @version 1.0.0 + * + * @param {MouseEvent} event [description] + * @return {void} + */ +window.eoxiaJS.task.divideTimeSpent = function( event ) { + let projectId = $(this).closest('.project-line').attr('id') + + let taskMinute = 0 + let taskHour = 0 + + let nonConsumedMinutes = $('.non-consumed-time-minute').val() + let nonConsumedHours = $('.non-consumed-time-hour').val() + let totalTimeInMinutes = +nonConsumedMinutes + +nonConsumedHours*60 + + let taskLinkedCounter = $('.'+projectId).length + let minutesToSpend = parseInt(totalTimeInMinutes/taskLinkedCounter) + + $('.inputhour').val('') + $('.inputminute').val('') + + $('.'+projectId).each(function() { + taskHour = parseInt(minutesToSpend/60) + taskMinute = minutesToSpend%60 + + $(this).find('.inputhour').val(taskHour) + $(this).find('.inputminute').val(taskMinute) + }) +}; + +/** + * Active/désactive la configuration pour n'afficher que les tâches favorites + * + * @since 1.0.0 + * @version 1.0.0 + * + * @param {MouseEvent} event [description] + * @return {void} + */ +window.eoxiaJS.task.showOnlyFavoriteTasks = function( event ) { + let token = $('.id-container').find('input[name="token"]').val(); + let querySeparator = '?'; + + document.URL.match(/\?/) ? querySeparator = '&' : 1 + $.ajax({ - url: document.URL + '&action=toggleTaskFavorite&token='+token, + url: document.URL + querySeparator + "action=showOnlyFavoriteTasks&token=" + token, type: "POST", processData: false, contentType: false, success: function ( resp ) { - + window.location.reload() }, - error: function ( resp ) { - + error: function ( ) { } }); }; + diff --git a/langs/fr_FR/doliproject.lang b/langs/fr_FR/doliproject.lang index e8480f7..57b1838 100644 --- a/langs/fr_FR/doliproject.lang +++ b/langs/fr_FR/doliproject.lang @@ -63,6 +63,10 @@ ErrorServiceTime = Le temps du(des) service(s) est nul MessageTimeSpentCreate = Temps consommé crée MessageNoTaskLink = Pas de tâche affectée, pas de temps passé disponible +DoliprojectTimeSpent=Rapport de temps passé +ActionAC_TEL_IN = Appel téléphonique entrant +ActionAC_TEL_OUT = Appel téléphonique sortant + # # Temps consommé # @@ -74,6 +78,7 @@ Thursday = Jeudi Friday = Vendredi Saturday = Samedi Sunday = Dimanche +Schedule = Horaires MondayWorkingHours = Heures de travail du lundi TuesdayWorkingHours = Heures de travail du mardi WednesdayWorkingHours = Heures de travail du mercredi @@ -81,13 +86,54 @@ ThursdayWorkingHours = Heures de travail du jeudi FridayWorkingHours = Heures de travail du vendredi SaturdayWorkingHours = Heures de travail du samedi SundayWorkingHours = Heures de travail du dimanche +WorkingHoursFormatDesc = Renseigner le temps travaillé par jour (en minutes) UserWorkingHours = Horaires de l'utilisateur WrongWorkingHoursFormat = Mauvais format pour les heures travaillées : %s UserWorkingHoursSaved = Horaires de l'utilisateurs sauvegardés WorkingHours(min) = Temps travaillé (en minutes) +DayWorkTime = Temps du jour +NonConsumedTime = Temps non pointé TimeSpent = Temps consommé +Holidays = Congés +PaidHolidays = Congés payés +SickLeave = Congés maladie +PublicHoliday = Jour férié +AdditionalHour = Heure supplémentaire +TooMuchTimeSpent = Vous avez pointé plus de temps que le temps non pointé disponible +AddTimeSpent = Pointage de temps +HumanResources = Ressources humaines +ProjectsAndTasks = Projets et tâches +HRProject = Projet Ressources Humaines +HRDescription = Projet comprenant les tâches liées aux ressources humaines +DoliProjectNewTimeSpent = Temps consommé (en minutes) +DivideTimeIntoTasks = Répartir entre les tâches du projet +ShowOnlyFavoriteTasks = Afficher uniquement les tâches favorites +SpendMoreTimeThanPlanned = Pointer plus de temps que planifié +SpendMoreTimeThanPlannedHelp = Cette option permet de pointer plus de temps que le temps renseigné dans l'onglet "Horaires" de l'utilisateur +ConsumedTime = Temps pointé +ExpectedWorkedHoursMonth = Heures de travail prévues au mois de %s +SpentWorkedHoursMonth = Heures de travail passées du %s au %s +ConsumedWorkedHoursMonth = Heures de travail consommées du %s au %s +DiffSpentAndConsumedWorkedHoursMonth = Différence d'heures de travail passées/consommées du %s au %s +WarningShowOnlyFavoriteTasks = Attention quand l'option 'Afficher uniquement les tâches favorites' est activée, les valeurs totales récupèrent l'ensembles des tâches. +ExpectedWorkedHoursWeek = Heures de travail prévues du %s au %s +ThemeDashboardTimeSpent = Thème du Rapport de pointage de temps # # Setup Page # DOLIPROJECT_DEFAUT_TICKET_TIME = Temps passé par défaut + + +# +# Facture récurrente +# +RecurringInvoicesStatistics = Statistiques facture récurrentes +NumberOfRecurringBills = Nb de factures récurrentes +NumberOfRecurringBillsByMonth = Nb de factures récurrentes par mois +AmountOfRecurringBills = Montant de factures récurrentes +AmountOfRecurringBillsByMonthHT = Montant de factures récurrentes par mois (HT) +ExceededTimeSpentColor = Couleur temps consommés dépassés +NotExceededTimeSpentColor = Couleur temps consommés pas dépassés ou non pointés +PerfectTimeSpentColor = Couleur temps consommés parfait +InvoicesCategoriesArea = Espace des tags/catégories des factures diff --git a/lib/doliproject.lib.php b/lib/doliproject.lib.php index fd82f51..de06f51 100644 --- a/lib/doliproject.lib.php +++ b/lib/doliproject.lib.php @@ -52,6 +52,11 @@ function doliprojectAdminPrepareHead() $head[$h][2] = 'about'; $h++; + $head[$h][0] = dol_buildpath("/doliproject/admin/project.php", 1); + $head[$h][1] = $langs->trans("ProjectsAndTasks"); + $head[$h][2] = 'projecttasks'; + $h++; + // Show more tabs from modules // Entries must be declared in modules descriptor with line //$this->tabs = array( diff --git a/lib/doliproject_functions.lib.php b/lib/doliproject_functions.lib.php index 463b292..2af174a 100644 --- a/lib/doliproject_functions.lib.php +++ b/lib/doliproject_functions.lib.php @@ -65,3 +65,1627 @@ function isTaskFavorite($task_id, $user_id) return $link_exists; } +/** + * Return list of tasks for all projects or for one particular project + * Sort order is on project, then on position of task, and last on start date of first level task + * + * @param User $usert Object user to limit tasks affected to a particular user + * @param User $userp Object user to limit projects of a particular user and public projects + * @param int $projectid Project id + * @param int $socid Third party id + * @param int $mode 0=Return list of tasks and their projects, 1=Return projects and tasks if exists + * @param string $filteronproj Filter on project ref or label + * @param string $filteronprojstatus Filter on project status ('-1'=no filter, '0,1'=Draft+Validated only) + * @param string $morewherefilter Add more filter into where SQL request (must start with ' AND ...') + * @param string $filteronprojuser Filter on user that is a contact of project + * @param string $filterontaskuser Filter on user assigned to task + * @param array $extrafields Show additional column from project or task + * @param int $includebilltime Calculate also the time to bill and billed + * @param array $search_array_options Array of search + * @param int $loadextras Fetch all Extrafields on each task + * @return array Array of tasks + */ +function getFavoriteTasksArray($task_id = 0, $usert = null, $userp = null, $projectid = 0, $socid = 0, $mode = 0, $filteronproj = '', $filteronprojstatus = '-1', $morewherefilter = '', $filteronprojuser = 0, $filterontaskuser = 0, $extrafields = array(), $includebilltime = 0, $search_array_options = array(), $loadextras = 0) +{ + global $conf, $hookmanager, $db, $user; + require_once DOL_DOCUMENT_ROOT . '/projet/class/task.class.php'; + + $task = new Task($db); + $task->fetch($task_id); + + $tasks = array(); + + //print $usert.'-'.$userp.'-'.$projectid.'-'.$socid.'-'.$mode.'
'; + + // List of tasks (does not care about permissions. Filtering will be done later) + $sql = "SELECT "; + if ($filteronprojuser > 0 || $filterontaskuser > 0) { + $sql .= " DISTINCT"; // We may get several time the same record if user has several roles on same project/task + } + $sql .= " p.rowid as projectid, p.ref, p.title as plabel, p.public, p.fk_statut as projectstatus, p.usage_bill_time,"; + $sql .= " t.rowid as taskid, t.ref as taskref, t.label, t.description, t.fk_task_parent, t.duration_effective, t.progress, t.fk_statut as status,"; + $sql .= " t.dateo as date_start, t.datee as date_end, t.planned_workload, t.rang,"; + $sql .= " t.description, "; + $sql .= " t.budget_amount, "; + $sql .= " s.rowid as thirdparty_id, s.nom as thirdparty_name, s.email as thirdparty_email,"; + $sql .= " p.fk_opp_status, p.opp_amount, p.opp_percent, p.budget_amount as project_budget_amount"; + if (!empty($extrafields->attributes['projet']['label'])) { + foreach ($extrafields->attributes['projet']['label'] as $key => $val) { + $sql .= ($extrafields->attributes['projet']['type'][$key] != 'separate' ? ",efp.".$key." as options_".$key : ''); + } + } + if (!empty($extrafields->attributes['projet_task']['label'])) { + foreach ($extrafields->attributes['projet_task']['label'] as $key => $val) { + $sql .= ($extrafields->attributes['projet_task']['type'][$key] != 'separate' ? ",efpt.".$key." as options_".$key : ''); + } + } + if ($includebilltime) { + $sql .= ", SUM(tt.task_duration * ".$db->ifsql("invoice_id IS NULL", "1", "0").") as tobill, SUM(tt.task_duration * ".$db->ifsql("invoice_id IS NULL", "0", "1").") as billed"; + } + + $sql .= " FROM ".MAIN_DB_PREFIX."projet as p"; + $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe as s ON p.fk_soc = s.rowid"; + $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."projet_extrafields as efp ON (p.rowid = efp.fk_object)"; + + if ($mode == 0) { + if ($filteronprojuser > 0) { + $sql .= ", ".MAIN_DB_PREFIX."element_contact as ec"; + $sql .= ", ".MAIN_DB_PREFIX."c_type_contact as ctc"; + } + $sql .= ", ".MAIN_DB_PREFIX."projet_task as t"; + if ($includebilltime) { + $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."projet_task_time as tt ON tt.fk_task = t.rowid"; + } + if ($filterontaskuser > 0) { + $sql .= ", ".MAIN_DB_PREFIX."element_contact as ec2"; + $sql .= ", ".MAIN_DB_PREFIX."c_type_contact as ctc2"; + } + if ($conf->global->DOLIPROJECT_SHOW_ONLY_FAVORITE_TASKS) { + $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."element_element as elel ON (t.rowid = elel.fk_target AND elel.targettype='project_task')"; + } + $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."projet_task_extrafields as efpt ON (t.rowid = efpt.fk_object)"; + $sql .= " WHERE p.entity IN (".getEntity('project').")"; + $sql .= " AND t.fk_projet = p.rowid"; + if ($conf->global->DOLIPROJECT_SHOW_ONLY_FAVORITE_TASKS) { + $sql .= " AND elel.fk_target = t.rowid"; + $sql .= " AND elel.fk_source = " . $user->id; + } + + } elseif ($mode == 1) { + if ($filteronprojuser > 0) { + $sql .= ", ".MAIN_DB_PREFIX."element_contact as ec"; + $sql .= ", ".MAIN_DB_PREFIX."c_type_contact as ctc"; + } + if ($filterontaskuser > 0) { + $sql .= ", ".MAIN_DB_PREFIX."projet_task as t"; + if ($includebilltime) { + $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."projet_task_time as tt ON tt.fk_task = t.rowid"; + } + $sql .= ", ".MAIN_DB_PREFIX."element_contact as ec2"; + $sql .= ", ".MAIN_DB_PREFIX."c_type_contact as ctc2"; + } else { + $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."projet_task as t on t.fk_projet = p.rowid"; + if ($includebilltime) { + $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."projet_task_time as tt ON tt.fk_task = t.rowid"; + } + } + $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."projet_task_extrafields as efpt ON (t.rowid = efpt.fk_object)"; + $sql .= " WHERE p.entity IN (".getEntity('project').")"; + } else { + return 'BadValueForParameterMode'; + } + + if ($filteronprojuser > 0) { + $sql .= " AND p.rowid = ec.element_id"; + $sql .= " AND ctc.rowid = ec.fk_c_type_contact"; + $sql .= " AND ctc.element = 'project'"; + $sql .= " AND ec.fk_socpeople = ".((int) $filteronprojuser); + $sql .= " AND ec.statut = 4"; + $sql .= " AND ctc.source = 'internal'"; + } + if ($filterontaskuser > 0) { + $sql .= " AND t.fk_projet = p.rowid"; + $sql .= " AND p.rowid = ec2.element_id"; + $sql .= " AND ctc2.rowid = ec2.fk_c_type_contact"; + $sql .= " AND ctc2.element = 'project_task'"; + $sql .= " AND ec2.fk_socpeople = ".((int) $filterontaskuser); + $sql .= " AND ec2.statut = 4"; + $sql .= " AND ctc2.source = 'internal'"; + } + if ($socid) { + $sql .= " AND p.fk_soc = ".((int) $socid); + } + if ($projectid) { + $sql .= " AND p.rowid IN (".$db->sanitize($projectid).")"; + } + if ($filteronproj) { + $sql .= natural_search(array("p.ref", "p.title"), $filteronproj); + } + if ($filteronprojstatus && $filteronprojstatus != '-1') { + $sql .= " AND p.fk_statut IN (".$db->sanitize($filteronprojstatus).")"; + } + if ($morewherefilter) { + $sql .= $morewherefilter; + } + // Add where from extra fields + $extrafieldsobjectkey = 'projet_task'; + $extrafieldsobjectprefix = 'efpt.'; + include DOL_DOCUMENT_ROOT.'/core/tpl/extrafields_list_search_sql.tpl.php'; + // Add where from hooks + $parameters = array(); + $reshook = $hookmanager->executeHooks('printFieldListWhere', $parameters); // Note that $action and $object may have been modified by hook + $sql .= $hookmanager->resPrint; + if ($includebilltime) { + $sql .= " GROUP BY p.rowid, p.ref, p.title, p.public, p.fk_statut, p.usage_bill_time,"; + $sql .= " t.datec, t.dateo, t.datee, t.tms,"; + $sql .= " t.rowid, t.ref, t.label, t.description, t.fk_task_parent, t.duration_effective, t.progress, t.fk_statut,"; + $sql .= " t.dateo, t.datee, t.planned_workload, t.rang,"; + $sql .= " t.description, "; + $sql .= " t.budget_amount, "; + $sql .= " s.rowid, s.nom, s.email,"; + $sql .= " p.fk_opp_status, p.opp_amount, p.opp_percent, p.budget_amount"; + if (!empty($extrafields->attributes['projet']['label'])) { + foreach ($extrafields->attributes['projet']['label'] as $key => $val) { + $sql .= ($extrafields->attributes['projet']['type'][$key] != 'separate' ? ",efp.".$key : ''); + } + } + if (!empty($extrafields->attributes['projet_task']['label'])) { + foreach ($extrafields->attributes['projet_task']['label'] as $key => $val) { + $sql .= ($extrafields->attributes['projet_task']['type'][$key] != 'separate' ? ",efpt.".$key : ''); + } + } + } + + + $sql .= " ORDER BY p.ref, t.rang, t.dateo"; + + //print $sql;exit; + dol_syslog(get_class($task)."::getTasksArray", LOG_DEBUG); + $resql = $db->query($sql); + if ($resql) { + $num = $db->num_rows($resql); + $i = 0; + // Loop on each record found, so each couple (project id, task id) + while ($i < $num) { + $error = 0; + + $obj = $db->fetch_object($resql); + + if ((!$obj->public) && (is_object($userp))) { // If not public project and we ask a filter on project owned by a user + if (!$task->getUserRolesForProjectsOrTasks($userp, 0, $obj->projectid, 0)) { + $error++; + } + } + if (is_object($usert)) { // If we ask a filter on a user affected to a task + if (!$task->getUserRolesForProjectsOrTasks(0, $usert, $obj->projectid, $obj->taskid)) { + $error++; + } + } + + if (!$error) { + $tasks[$i] = new Task($db); + $tasks[$i]->id = $obj->taskid; + $tasks[$i]->ref = $obj->taskref; + $tasks[$i]->fk_project = $obj->projectid; + $tasks[$i]->projectref = $obj->ref; + $tasks[$i]->projectlabel = $obj->plabel; + $tasks[$i]->projectstatus = $obj->projectstatus; + + $tasks[$i]->fk_opp_status = $obj->fk_opp_status; + $tasks[$i]->opp_amount = $obj->opp_amount; + $tasks[$i]->opp_percent = $obj->opp_percent; + $tasks[$i]->budget_amount = $obj->budget_amount; + $tasks[$i]->project_budget_amount = $obj->project_budget_amount; + $tasks[$i]->usage_bill_time = $obj->usage_bill_time; + + $tasks[$i]->label = $obj->label; + $tasks[$i]->description = $obj->description; + $tasks[$i]->fk_parent = $obj->fk_task_parent; // deprecated + $tasks[$i]->fk_task_parent = $obj->fk_task_parent; + $tasks[$i]->duration = $obj->duration_effective; + $tasks[$i]->planned_workload = $obj->planned_workload; + + if ($includebilltime) { + $tasks[$i]->tobill = $obj->tobill; + $tasks[$i]->billed = $obj->billed; + } + + $tasks[$i]->progress = $obj->progress; + $tasks[$i]->fk_statut = $obj->status; + $tasks[$i]->public = $obj->public; + $tasks[$i]->date_start = $db->jdate($obj->date_start); + $tasks[$i]->date_end = $db->jdate($obj->date_end); + $tasks[$i]->rang = $obj->rang; + + $tasks[$i]->socid = $obj->thirdparty_id; // For backward compatibility + $tasks[$i]->thirdparty_id = $obj->thirdparty_id; + $tasks[$i]->thirdparty_name = $obj->thirdparty_name; + $tasks[$i]->thirdparty_email = $obj->thirdparty_email; + + if (!empty($extrafields->attributes['projet']['label'])) { + foreach ($extrafields->attributes['projet']['label'] as $key => $val) { + if ($extrafields->attributes['projet']['type'][$key] != 'separate') { + $tasks[$i]->{'options_'.$key} = $obj->{'options_'.$key}; + } + } + } + + if (!empty($extrafields->attributes['projet_task']['label'])) { + foreach ($extrafields->attributes['projet_task']['label'] as $key => $val) { + if ($extrafields->attributes['projet_task']['type'][$key] != 'separate') { + $tasks[$i]->{'options_'.$key} = $obj->{'options_'.$key}; + } + } + } + + if ($loadextras) { + $tasks[$i]->fetch_optionals(); + } + } + + $i++; + } + $db->free($resql); + } else { + dol_print_error($db); + } + + return $tasks; +} + +/** + * Output a task line into a pertime intput mode + * + * @param string $inc Line number (start to 0, then increased by recursive call) + * @param string $parent Id of parent task to show (0 to show all) + * @param User|null $fuser Restrict list to user if defined + * @param Task[] $lines Array of lines + * @param int $level Level (start to 0, then increased/decrease by recursive call) + * @param string $projectsrole Array of roles user has on project + * @param string $tasksrole Array of roles user has on task + * @param string $mine Show only task lines I am assigned to + * @param int $restricteditformytask 0=No restriction, 1=Enable add time only if task is assigned to me, 2=Enable add time only if tasks is assigned to me and hide others + * @param int $preselectedday Preselected day + * @param array $isavailable Array with data that say if user is available for several days for morning and afternoon + * @param int $oldprojectforbreak Old project id of last project break + * @param array $arrayfields Array of additional column + * @param Extrafields $extrafields Object extrafields + * @return array Array with time spent for $fuser for each day of week on tasks in $lines and substasks + */ +function doliprojectLinesPerDay(&$inc, $parent, $fuser, $lines, &$level, &$projectsrole, &$tasksrole, $mine, $restricteditformytask, $preselectedday, &$isavailable, $oldprojectforbreak = 0, $arrayfields = array(), $extrafields = null) +{ + global $conf, $db, $user, $langs; + global $form, $formother, $projectstatic, $taskstatic, $thirdpartystatic; + + $lastprojectid = 0; + $totalforeachday = array(); + $workloadforid = array(); + $lineswithoutlevel0 = array(); + + $numlines = count($lines); + + // Create a smaller array with sublevels only to be used later. This increase dramatically performances. + if ($parent == 0) { // Always and only if at first level + for ($i = 0; $i < $numlines; $i++) { + if ($lines[$i]->fk_task_parent) { + $lineswithoutlevel0[] = $lines[$i]; + } + } + } + + if (empty($oldprojectforbreak)) { + $oldprojectforbreak = (empty($conf->global->PROJECT_TIMESHEET_DISABLEBREAK_ON_PROJECT) ? 0 : -1); // 0 to start break , -1 no break + } + + $restrictBefore = null; + + if (! empty($conf->global->PROJECT_TIMESHEET_PREVENT_AFTER_MONTHS)) { + require_once DOL_DOCUMENT_ROOT.'/core/lib/date.lib.php'; + $restrictBefore = dol_time_plus_duree(dol_now(), - $conf->global->PROJECT_TIMESHEET_PREVENT_AFTER_MONTHS, 'm'); + } + + //dol_syslog('projectLinesPerDay inc='.$inc.' preselectedday='.$preselectedday.' task parent id='.$parent.' level='.$level." count(lines)=".$numlines." count(lineswithoutlevel0)=".count($lineswithoutlevel0)); + for ($i = 0; $i < $numlines; $i++) { + if ($parent == 0) { + $level = 0; + } + + if ($lines[$i]->fk_task_parent == $parent) { + $obj = &$lines[$i]; // To display extrafields + + // If we want all or we have a role on task, we show it + if (empty($mine) || !empty($tasksrole[$lines[$i]->id])) { + //dol_syslog("projectLinesPerWeek Found line ".$i.", a qualified task (i have role or want to show all tasks) with id=".$lines[$i]->id." project id=".$lines[$i]->fk_project); + + if ($restricteditformytask == 2 && empty($tasksrole[$lines[$i]->id])) { // we have no role on task and we request to hide such cases + continue; + } + + // Break on a new project + if ($parent == 0 && $lines[$i]->fk_project != $lastprojectid) { + $lastprojectid = $lines[$i]->fk_project; + if ($preselectedday) { + $projectstatic->id = $lines[$i]->fk_project; + } + } + + if (empty($workloadforid[$projectstatic->id])) { + if ($preselectedday) { + $projectstatic->loadTimeSpent($preselectedday, 0, $fuser->id); // Load time spent from table projet_task_time for the project into this->weekWorkLoad and this->weekWorkLoadPerTask for all days of a week + $workloadforid[$projectstatic->id] = 1; + } + } + + $projectstatic->id = $lines[$i]->fk_project; + $projectstatic->ref = $lines[$i]->projectref; + $projectstatic->title = $lines[$i]->projectlabel; + $projectstatic->public = $lines[$i]->public; + $projectstatic->status = $lines[$i]->projectstatus; + + $taskstatic->id = $lines[$i]->id; + $taskstatic->ref = ($lines[$i]->ref ? $lines[$i]->ref : $lines[$i]->id); + $taskstatic->label = $lines[$i]->label; + $taskstatic->date_start = $lines[$i]->date_start; + $taskstatic->date_end = $lines[$i]->date_end; + + $thirdpartystatic->id = $lines[$i]->socid; + $thirdpartystatic->name = $lines[$i]->thirdparty_name; + $thirdpartystatic->email = $lines[$i]->thirdparty_email; + + $addcolspan = 2; + if (!empty($arrayfields['timeconsumed']['checked'])) { + $addcolspan++; + $addcolspan++; + } + foreach ($arrayfields as $key => $val) { + if ($val['checked'] && substr($key, 0, 5) == 'efpt.') { + $addcolspan++; + } + } + + if (empty($oldprojectforbreak) || ($oldprojectforbreak != -1 && $oldprojectforbreak != $projectstatic->id)) { + print ''."\n"; + print ''; + print $projectstatic->getNomUrl(1, '', 0, ''.$langs->transnoentitiesnoconv("YourRole").': '.$projectsrole[$lines[$i]->fk_project]); + if ($thirdpartystatic->id > 0) { + print ' - '.$thirdpartystatic->getNomUrl(1); + } + if ($projectstatic->title) { + print ' - '; + print ''.$projectstatic->title.''; + } + + print ''; + print ''; + if (!empty($conf->use_javascript_ajax)) { + print img_picto("Auto fill", 'rightarrow', "class='auto-fill-timespent-project' data-rowname='".$namef."' data-value='".($sign * $remaintopay)."'"); + } + print ' ' . $langs->trans('DivideTimeIntoTasks'); + print ''; + + print ''; + + print ''; + + print ''; + + print ''; + print ''; + } + + if ($oldprojectforbreak != -1) { + $oldprojectforbreak = $projectstatic->id; + } + + print ''."\n"; + + // User + /* + print ''; + print $fuser->getNomUrl(1, 'withproject', 'time'); + print ''; + */ + + // Project + if (!empty($conf->global->PROJECT_TIMESHEET_DISABLEBREAK_ON_PROJECT)) { + print ""; + if ($oldprojectforbreak == -1) { + print $projectstatic->getNomUrl(1, '', 0, $langs->transnoentitiesnoconv("YourRole").': '.$projectsrole[$lines[$i]->fk_project]); + } + print ""; + } + + // Thirdparty + if (!empty($conf->global->PROJECT_TIMESHEET_DISABLEBREAK_ON_PROJECT)) { + print ''; + if ($thirdpartystatic->id > 0) { + print $thirdpartystatic->getNomUrl(1, 'project', 10); + } + print ''; + } + + // Ref + print ''; + print ''; + for ($k = 0; $k < $level; $k++) { + print '
'; + } + print $taskstatic->getNomUrl(1, 'withproject', 'time'); + // Label task + print '
'; + print ''.$taskstatic->label.''; + for ($k = 0; $k < $level; $k++) { + print "
"; + } + print "\n"; + + // TASK extrafields + $extrafieldsobjectkey = 'projet_task'; + $extrafieldsobjectprefix = 'efpt.'; + include DOL_DOCUMENT_ROOT.'/core/tpl/extrafields_list_print_fields.tpl.php'; + + if (!empty($arrayfields['timeconsumed']['checked'])) { + // Time spent by everybody + print ''; + // $lines[$i]->duration is a denormalised field = summ of time spent by everybody for task. What we need is time consummed by user + if ($lines[$i]->duration) { + print ''; + print convertSecondToTime($lines[$i]->duration, 'allhourmin'); + print ''; + } else { + print '--:--'; + } + print "\n"; + + // Time spent by user + print ''; + $tmptimespent = $taskstatic->getSummaryOfTimeSpent($fuser->id); + if ($tmptimespent['total_duration']) { + print convertSecondToTime($tmptimespent['total_duration'], 'allhourmin'); + } else { + print '--:--'; + } + print "\n"; + } + + $disabledproject = 1; + $disabledtask = 1; + + // If at least one role for project + if ($lines[$i]->public || !empty($projectsrole[$lines[$i]->fk_project]) || $user->rights->projet->all->creer) { + $disabledproject = 0; + $disabledtask = 0; + } + // If $restricteditformytask is on and I have no role on task, i disable edit + if ($restricteditformytask && empty($tasksrole[$lines[$i]->id])) { + $disabledtask = 1; + } + + if ($restrictBefore && $preselectedday < $restrictBefore) { + $disabledtask = 1; + } + + // Select hour + print ''; + $tableCell = $form->selectDate($preselectedday, $lines[$i]->id, 1, 1, 2, "addtime", 0, 0, $disabledtask); + print $tableCell; + print ''; + + $cssonholiday = ''; + if (!$isavailable[$preselectedday]['morning'] && !$isavailable[$preselectedday]['afternoon']) { + $cssonholiday .= 'onholidayallday '; + } elseif (!$isavailable[$preselectedday]['morning']) { + $cssonholiday .= 'onholidaymorning '; + } elseif (!$isavailable[$preselectedday]['afternoon']) { + $cssonholiday .= 'onholidayafternoon '; + } + + global $daytoparse; + $tmparray = dol_getdate($daytoparse, true); // detail of current day + + $idw = ($tmparray['wday'] - (empty($conf->global->MAIN_START_WEEK) ? 0 : 1)); + global $numstartworkingday, $numendworkingday; + $cssweekend = ''; + if ((($idw + 1) < $numstartworkingday) || (($idw + 1) > $numendworkingday)) { // This is a day is not inside the setup of working days, so we use a week-end css. + $cssweekend = 'weekend'; + } + + // Duration + print ''; + $dayWorkLoad = $projectstatic->weekWorkLoadPerTask[$preselectedday][$lines[$i]->id]; + $totalforeachday[$preselectedday] += $dayWorkLoad; + + $alreadyspent = ''; + if ($dayWorkLoad > 0) { + $alreadyspent = convertSecondToTime($dayWorkLoad, 'allhourmin'); + } + + $idw = 0; + + $tableCell = ''; + $tableCell .= ''; + $tableCell .= ' + '; + //$tableCell.='   '; + if (!empty($conf->use_javascript_ajax)) { + $tableCell .= img_picto("Auto fill", 'rightarrow', "class='auto-fill-timespent' data-rowname='".$namef."' data-value='".($sign * $remaintopay)."'"); + } + $tableCell .= $form->select_duration($lines[$i]->id.'duration', '', $disabledtask, 'text', 0, 1); + //$tableCell.=' '; + print $tableCell; + + $modeinput = 'hours'; + + print ''; + + print ''; + + // Note + print ''; + print ''; + print ''; + + // Warning + print ''; + if ((!$lines[$i]->public) && $disabledproject) { + print $form->textwithpicto('', $langs->trans("UserIsNotContactOfProject")); + } elseif ($disabledtask) { + $titleassigntask = $langs->trans("AssignTaskToMe"); + if ($fuser->id != $user->id) { + $titleassigntask = $langs->trans("AssignTaskToUser", '...'); + } + + print $form->textwithpicto('', $langs->trans("TaskIsNotAssignedToUser", $titleassigntask)); + } + print ''; + + print "\n"; + } + + $inc++; + $level++; + if ($lines[$i]->id > 0) { + //var_dump('totalforeachday after taskid='.$lines[$i]->id.' and previous one on level '.$level); + //var_dump($totalforeachday); + $ret = doliprojectLinesPerDay($inc, $lines[$i]->id, $fuser, ($parent == 0 ? $lineswithoutlevel0 : $lines), $level, $projectsrole, $tasksrole, $mine, $restricteditformytask, $preselectedday, $isavailable, $oldprojectforbreak, $arrayfields, $extrafields); + //var_dump('ret with parent='.$lines[$i]->id.' level='.$level); + //var_dump($ret); + foreach ($ret as $key => $val) { + $totalforeachday[$key] += $val; + } + //var_dump('totalforeachday after taskid='.$lines[$i]->id.' and previous one on level '.$level.' + subtasks'); + //var_dump($totalforeachday); + } + $level--; + } else { + //$level--; + } + } + + return $totalforeachday; +} + +/** + * Prepare array with list of tabs + * + * @param string $mode Mode + * @param string $fuser Filter on user + * @return array Array of tabs to show + */ +function doliproject_timesheet_prepare_head($mode, $fuser = null) +{ + global $langs, $conf, $user; + $h = 0; + $head = array(); + + $h = 0; + + $param = ''; + $param .= ($mode ? '&mode='.$mode : ''); + if (is_object($fuser) && $fuser->id > 0 && $fuser->id != $user->id) { + $param .= '&search_usertoprocessid='.$fuser->id; + } + + if (empty($conf->global->PROJECT_DISABLE_TIMESHEET_PERMONTH)) { + $head[$h][0] = DOL_URL_ROOT."/custom/doliproject/view/timespent_month.php".($param ? '?'.$param : ''); + $head[$h][1] = $langs->trans("InputPerMonth"); + $head[$h][2] = 'inputpermonth'; + $h++; + } + + if (empty($conf->global->PROJECT_DISABLE_TIMESHEET_PERWEEK)) { + $head[$h][0] = DOL_URL_ROOT."/custom/doliproject/view/timespent_week.php".($param ? '?'.$param : ''); + $head[$h][1] = $langs->trans("InputPerWeek"); + $head[$h][2] = 'inputperweek'; + $h++; + } + + if (empty($conf->global->PROJECT_DISABLE_TIMESHEET_PERTIME)) { + $head[$h][0] = DOL_URL_ROOT."/custom/doliproject/view/timespent_day.php".($param ? '?'.$param : ''); + $head[$h][1] = $langs->trans("InputPerDay"); + $head[$h][2] = 'inputperday'; + $h++; + } + + complete_head_from_modules($conf, $langs, null, $head, $h, 'project_timesheet'); + + complete_head_from_modules($conf, $langs, null, $head, $h, 'project_timesheet', 'remove'); + + return $head; +} + +/** + * Sets object to given categories. + * + * Adds it to non existing supplied categories. + * Deletes object from existing categories not supplied (if remove_existing==true). + * Existing categories are left untouch. + * + * @param int[]|int $categories Category ID or array of Categories IDs + * @param string $type_categ Category type ('customer', 'supplier', 'website_page', ...) definied into const class Categorie type + * @param boolean $remove_existing True: Remove existings categories from Object if not supplies by $categories, False: let them + * @return int <0 if KO, >0 if OK + */ +function setCategoriesObject($categories, $type_categ = '', $remove_existing = true, $object) +{ + // Handle single category + if (!is_array($categories)) { + $categories = array($categories); + } + + dol_syslog(get_class($object)."::setCategoriesCommon Oject Id:".$object->id.' type_categ:'.$type_categ.' nb tag add:'.count($categories), LOG_DEBUG); + + require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php'; + + if (empty($type_categ)) { + dol_syslog(__METHOD__.': Type '.$type_categ.'is an unknown category type. Done nothing.', LOG_ERR); + return -1; + } + + // Get current categories + $c = new Categorie($object->db); + $existing = $c->containing($object->id, $type_categ, 'id'); + if ($remove_existing) { + // Diff + if (is_array($existing)) { + $to_del = array_diff($existing, $categories); + $to_add = array_diff($categories, $existing); + } else { + $to_del = array(); // Nothing to delete + $to_add = $categories; + } + } else { + $to_del = array(); // Nothing to delete + $to_add = array_diff($categories, $existing); + } + + $error = 0; + $ok = 0; + + // Process + foreach ($to_del as $del) { + if ($c->fetch($del) > 0) { + $result=$c->del_type($object, $type_categ); + if ($result < 0) { + $error++; + $object->error = $c->error; + $object->errors = $c->errors; + break; + } else { + $ok += $result; + } + } + } + foreach ($to_add as $add) { + if ($c->fetch($add) > 0) { + $result = $c->add_type($object, $type_categ); + if ($result < 0) { + $error++; + $object->error = $c->error; + $object->errors = $c->errors; + break; + } else { + $ok += $result; + } + } + } + + return $error ? -1 * $error : $ok; +} + +/** + * Output a task line into a perday intput mode + * + * @param string $inc Line output identificator (start to 0, then increased by recursive call) + * @param int $firstdaytoshow First day to show + * @param User|null $fuser Restrict list to user if defined + * @param string $parent Id of parent task to show (0 to show all) + * @param Task[] $lines Array of lines (list of tasks but we will show only if we have a specific role on task) + * @param int $level Level (start to 0, then increased/decrease by recursive call) + * @param string $projectsrole Array of roles user has on project + * @param string $tasksrole Array of roles user has on task + * @param string $mine Show only task lines I am assigned to + * @param int $restricteditformytask 0=No restriction, 1=Enable add time only if task is assigned to me, 2=Enable add time only if tasks is assigned to me and hide others + * @param array $isavailable Array with data that say if user is available for several days for morning and afternoon + * @param int $oldprojectforbreak Old project id of last project break + * @param array $arrayfields Array of additional column + * @param Extrafields $extrafields Object extrafields + * @return array Array with time spent for $fuser for each day of week on tasks in $lines and substasks + */ +function projectLinesPerDayOnMonth(&$inc, $firstdaytoshow, $fuser, $parent, $lines, &$level, &$projectsrole, &$tasksrole, $mine, $restricteditformytask, &$isavailable, $oldprojectforbreak = 0, $arrayfields = array(), $extrafields = null, $dayInMonth) +{ + global $conf, $db, $user, $langs; + global $form, $formother, $projectstatic, $taskstatic, $thirdpartystatic; + + $numlines = count($lines); + + $lastprojectid = 0; + $workloadforid = array(); + $totalforeachday = array(); + $lineswithoutlevel0 = array(); + + // Create a smaller array with sublevels only to be used later. This increase dramatically performances. + if ($parent == 0) { // Always and only if at first level + for ($i = 0; $i < $numlines; $i++) { + if ($lines[$i]->fk_task_parent) { + $lineswithoutlevel0[] = $lines[$i]; + } + } + } + + //dol_syslog('projectLinesPerWeek inc='.$inc.' firstdaytoshow='.$firstdaytoshow.' task parent id='.$parent.' level='.$level." count(lines)=".$numlines." count(lineswithoutlevel0)=".count($lineswithoutlevel0)); + + if (empty($oldprojectforbreak)) { + $oldprojectforbreak = (empty($conf->global->PROJECT_TIMESHEET_DISABLEBREAK_ON_PROJECT) ? 0 : -1); // 0 = start break, -1 = never break + } + + $restrictBefore = null; + + if (! empty($conf->global->PROJECT_TIMESHEET_PREVENT_AFTER_MONTHS)) { + require_once DOL_DOCUMENT_ROOT.'/core/lib/date.lib.php'; + $restrictBefore = dol_time_plus_duree(dol_now(), - $conf->global->PROJECT_TIMESHEET_PREVENT_AFTER_MONTHS, 'm'); + } + + for ($i = 0; $i < $numlines; $i++) { + if ($parent == 0) { + $level = 0; + } + + if ($lines[$i]->fk_task_parent == $parent) { + $obj = &$lines[$i]; // To display extrafields + + // If we want all or we have a role on task, we show it + if (empty($mine) || !empty($tasksrole[$lines[$i]->id])) { + //dol_syslog("projectLinesPerWeek Found line ".$i.", a qualified task (i have role or want to show all tasks) with id=".$lines[$i]->id." project id=".$lines[$i]->fk_project); + + if ($restricteditformytask == 2 && empty($tasksrole[$lines[$i]->id])) { // we have no role on task and we request to hide such cases + continue; + } + + // Break on a new project + if ($parent == 0 && $lines[$i]->fk_project != $lastprojectid) { + $lastprojectid = $lines[$i]->fk_project; + $projectstatic->id = $lines[$i]->fk_project; + } + + //var_dump('--- '.$level.' '.$firstdaytoshow.' '.$fuser->id.' '.$projectstatic->id.' '.$workloadforid[$projectstatic->id]); + //var_dump($projectstatic->monthWorkLoadPerTask); + if (empty($workloadforid[$projectstatic->id])) { + loadTimeSpentMonthByDay($firstdaytoshow, 0, $fuser->id, $projectstatic); // Load time spent from table projet_task_time for the project into this->weekWorkLoad and this->monthWorkLoadPerTask for all days of a week + $workloadforid[$projectstatic->id] = 1; + } + //var_dump('--- '.$projectstatic->id.' '.$workloadforid[$projectstatic->id]); + + $projectstatic->id = $lines[$i]->fk_project; + $projectstatic->ref = $lines[$i]->projectref; + $projectstatic->title = $lines[$i]->projectlabel; + $projectstatic->public = $lines[$i]->public; + $projectstatic->thirdparty_name = $lines[$i]->thirdparty_name; + $projectstatic->status = $lines[$i]->projectstatus; + + $taskstatic->id = $lines[$i]->id; + $taskstatic->ref = ($lines[$i]->ref ? $lines[$i]->ref : $lines[$i]->id); + $taskstatic->label = $lines[$i]->label; + $taskstatic->date_start = $lines[$i]->date_start; + $taskstatic->date_end = $lines[$i]->date_end; + + $thirdpartystatic->id = $lines[$i]->thirdparty_id; + $thirdpartystatic->name = $lines[$i]->thirdparty_name; + $thirdpartystatic->email = $lines[$i]->thirdparty_email; + + if (empty($oldprojectforbreak) || ($oldprojectforbreak != -1 && $oldprojectforbreak != $projectstatic->id)) { + $addcolspan = 0; + if (!empty($arrayfields['t.planned_workload']['checked'])) { + $addcolspan++; + } + if (!empty($arrayfields['t.progress']['checked'])) { + $addcolspan++; + } + if (!empty($arrayfields['timeconsumed']['checked'])) { + $addcolspan++; + } + foreach ($arrayfields as $key => $val) { + if ($val['checked'] && substr($key, 0, 5) == 'efpt.') { + $addcolspan++; + } + } + + if ($conf->global->DOLIPROJECT_SHOW_ONLY_FAVORITE_TASKS) { + $taskfavorite = isTaskFavorite($lines[$i]->id, $fuser->id); + } else { + $taskfavorite = 1; + } + + print ''."\n"; + print ''; + print $projectstatic->getNomUrl(1, '', 0, ''.$langs->transnoentitiesnoconv("YourRole").': '.$projectsrole[$lines[$i]->fk_project]); + if ($thirdpartystatic->id > 0) { + print ' - '.$thirdpartystatic->getNomUrl(1); + } + if ($projectstatic->title) { + print ' - '; + print ''.dol_trunc($projectstatic->title, '64').''; + } + + /*$colspan=5+(empty($conf->global->PROJECT_TIMESHEET_DISABLEBREAK_ON_PROJECT)?0:2); + print ''; + + print ''; + + // PROJECT fields + if (! empty($arrayfields['p.fk_opp_status']['checked'])) print_liste_field_titre($arrayfields['p.fk_opp_status']['label'], $_SERVER["PHP_SELF"], 'p.fk_opp_status', "", $param, '', $sortfield, $sortorder, 'center '); + if (! empty($arrayfields['p.opp_amount']['checked'])) print_liste_field_titre($arrayfields['p.opp_amount']['label'], $_SERVER["PHP_SELF"], 'p.opp_amount', "", $param, '', $sortfield, $sortorder, 'right '); + if (! empty($arrayfields['p.opp_percent']['checked'])) print_liste_field_titre($arrayfields['p.opp_percent']['label'], $_SERVER["PHP_SELF"], 'p.opp_percent', "", $param, '', $sortfield, $sortorder, 'right '); + if (! empty($arrayfields['p.budget_amount']['checked'])) print_liste_field_titre($arrayfields['p.budget_amount']['label'], $_SERVER["PHP_SELF"], 'p.budget_amount', "", $param, '', $sortfield, $sortorder, 'right '); + if (! empty($arrayfields['p.usage_bill_time']['checked'])) print_liste_field_titre($arrayfields['p.usage_bill_time']['label'], $_SERVER["PHP_SELF"], 'p.usage_bill_time', "", $param, '', $sortfield, $sortorder, 'right '); + + $extrafieldsobjectkey='projet'; + $extrafieldsobjectprefix='efp.'; + include DOL_DOCUMENT_ROOT.'/core/tpl/extrafields_list_search_title.tpl.php'; + + print ''; + print ''; + + // PROJECT fields + if (! empty($arrayfields['p.fk_opp_status']['checked'])) + { + print '\n"; + } + if (! empty($arrayfields['p.opp_amount']['checked'])) + { + print '\n"; + } + if (! empty($arrayfields['p.opp_percent']['checked'])) + { + print '\n"; + } + if (! empty($arrayfields['p.budget_amount']['checked'])) + { + print '\n"; + } + if (! empty($arrayfields['p.usage_bill_time']['checked'])) + { + print '\n"; + } + + $extrafieldsobjectkey='projet'; + $extrafieldsobjectprefix='efp.'; + include DOL_DOCUMENT_ROOT.'/core/tpl/extrafields_list_print_fields.tpl.php'; + + print ''; + print '
'; + $code = dol_getIdFromCode($db, $lines[$i]->fk_opp_status, 'c_lead_status', 'rowid', 'code'); + if ($code) print $langs->trans("OppStatus".$code); + print "'; + print price($lines[$i]->opp_amount, 0, $langs, 1, 0, -1, $conf->currency); + print "'; + print price($lines[$i]->opp_percent, 0, $langs, 1, 0).' %'; + print "'; + print price($lines[$i]->budget_amount, 0, $langs, 1, 0, 0, $conf->currency); + print "'; + print yn($lines[$i]->usage_bill_time); + print "
'; + */ + + print ''; + print ''; + } + + if ($oldprojectforbreak != -1) { + $oldprojectforbreak = $projectstatic->id; + } + + if ($conf->global->DOLIPROJECT_SHOW_ONLY_FAVORITE_TASKS) { + $taskfavorite = isTaskFavorite($lines[$i]->id, $fuser->id); + } else { + $taskfavorite = 1; + } + + print ''."\n"; + + // User + /* + print ''; + print $fuser->getNomUrl(1, 'withproject', 'time'); + print ''; + */ + + // Project + if (!empty($conf->global->PROJECT_TIMESHEET_DISABLEBREAK_ON_PROJECT)) { + print ''; + if ($oldprojectforbreak == -1) { + print $projectstatic->getNomUrl(1, '', 0, $langs->transnoentitiesnoconv("YourRole").': '.$projectsrole[$lines[$i]->fk_project]); + } + print ""; + } + + // Thirdparty + if (!empty($conf->global->PROJECT_TIMESHEET_DISABLEBREAK_ON_PROJECT)) { + print ''; + if ($thirdpartystatic->id > 0) { + print $thirdpartystatic->getNomUrl(1, 'project'); + } + print ''; + } + + // Ref + print ''; + print ''; + for ($k = 0; $k < $level; $k++) { + print '
'; + } + print $taskstatic->getNomUrl(1, 'withproject', 'time'); + // Label task + print '
'; + print ''.dol_trunc($taskstatic->label, '64').''; + for ($k = 0; $k < $level; $k++) { + print "
"; + } + print "\n"; + + // TASK extrafields + $extrafieldsobjectkey = 'projet_task'; + $extrafieldsobjectprefix = 'efpt.'; + include DOL_DOCUMENT_ROOT.'/core/tpl/extrafields_list_print_fields.tpl.php'; + + // Planned Workload + if (!empty($arrayfields['t.planned_workload']['checked'])) { + print ''; + if ($lines[$i]->planned_workload) { + print convertSecondToTime($lines[$i]->planned_workload, 'allhourmin'); + } else { + print '--:--'; + } + print ''; + } + + if (!empty($arrayfields['t.progress']['checked'])) { + // Progress declared % + print ''; + print $formother->select_percent($lines[$i]->progress, $lines[$i]->id.'progress'); + print ''; + } + + if (!empty($arrayfields['timeconsumed']['checked'])) { + // Time spent by user + print ''; + $firstday = dol_print_date($firstdaytoshow, 'dayrfc'); + $currentMonth = date( 'm', dol_now()); + $year = GETPOST('reyear', 'int') ?GETPOST('reyear', 'int') : (GETPOST("year", 'int') ?GETPOST("year", "int") : date("Y")); + $month = GETPOST('remonth', 'int') ?GETPOST('remonth', 'int') : (GETPOST("month", 'int') ?GETPOST("month", "int") : date("m")); + if ($currentMonth == $month) { + $lastday = dol_print_date(dol_now(), 'dayrfc'); + } else { + $lastdaytoshow = dol_get_last_day($year, $month); + $lastday = dol_print_date($lastdaytoshow, 'dayrfc'); + } + $filter = ' AND t.task_datehour BETWEEN ' . "'" . $firstday . "'" . ' AND ' . "'" . $lastday . "'"; + $tmptimespent = $taskstatic->getSummaryOfTimeSpent($fuser->id, $filter); + if ($tmptimespent['total_duration']) { + print convertSecondToTime($tmptimespent['total_duration'], 'allhourmin'); + } else { + print '--:--'; + } + print "\n"; + } + + $disabledproject = 1; + $disabledtask = 1; + //print "x".$lines[$i]->fk_project; + //var_dump($lines[$i]); + //var_dump($projectsrole[$lines[$i]->fk_project]); + // If at least one role for project + if ($lines[$i]->public || !empty($projectsrole[$lines[$i]->fk_project]) || $user->rights->projet->all->creer) { + $disabledproject = 0; + $disabledtask = 0; + } + // If $restricteditformytask is on and I have no role on task, i disable edit + if ($restricteditformytask && empty($tasksrole[$lines[$i]->id])) { + $disabledtask = 1; + } + + //var_dump($projectstatic->monthWorkLoadPerTask); + + // Fields to show current time + $tableCell = ''; + $modeinput = 'hours'; + for ($idw = 0; $idw < $dayInMonth; $idw++) { + $tmpday = dol_time_plus_duree($firstdaytoshow, $idw, 'd'); + + $cssonholiday = ''; + if (!$isavailable[$tmpday]['morning'] && !$isavailable[$tmpday]['afternoon']) { + $cssonholiday .= 'onholidayallday '; + } elseif (!$isavailable[$tmpday]['morning']) { + $cssonholiday .= 'onholidaymorning '; + } elseif (!$isavailable[$tmpday]['afternoon']) { + $cssonholiday .= 'onholidayafternoon '; + } + + $tmparray = dol_getdate($tmpday); + $dayWorkLoad = $projectstatic->monthWorkLoadPerTask[$tmpday][$lines[$i]->id]; + $totalforeachday[$tmpday] += $dayWorkLoad; + + $alreadyspent = ''; + if ($dayWorkLoad > 0) { + $alreadyspent = convertSecondToTime($dayWorkLoad, 'allhourmin'); + } + $alttitle = $langs->trans("AddHereTimeSpentForDay", $tmparray['day'], $tmparray['mon']); + + global $numstartworkingday, $numendworkingday; + $cssweekend = ''; + if (($idw + 1 < $numstartworkingday) || ($idw + 1 > $numendworkingday)) { // This is a day is not inside the setup of working days, so we use a week-end css. + //$cssweekend = 'weekend'; + } + + $disabledtaskday = $disabledtask; + + if (! $disabledtask && $restrictBefore && $tmpday < $restrictBefore) { + $disabledtaskday = 1; + } + + $tableCell = ''; + //$tableCell .= 'idw='.$idw.' '.$conf->global->MAIN_START_WEEK.' '.$numstartworkingday.'-'.$numendworkingday; + $placeholder = ''; + if ($alreadyspent) { + $tableCell .= ''; + //$placeholder=' placeholder="00:00"'; + //$tableCell.='+'; + } + $tableCell .= ''; + $tableCell .= ''; + print $tableCell; + } + + // Warning + print ''; + if ((!$lines[$i]->public) && $disabledproject) { + print $form->textwithpicto('', $langs->trans("UserIsNotContactOfProject")); + } elseif ($disabledtask) { + $titleassigntask = $langs->trans("AssignTaskToMe"); + if ($fuser->id != $user->id) { + $titleassigntask = $langs->trans("AssignTaskToUser", '...'); + } + + print $form->textwithpicto('', $langs->trans("TaskIsNotAssignedToUser", $titleassigntask)); + } + print ''; + + print "\n"; + } + + // Call to show task with a lower level (task under the current task) + $inc++; + $level++; + if ($lines[$i]->id > 0) { + //var_dump('totalforeachday after taskid='.$lines[$i]->id.' and previous one on level '.$level); + //var_dump($totalforeachday); + $ret = projectLinesPerDayOnMonth($inc, $firstdaytoshow, $fuser, $lines[$i]->id, ($parent == 0 ? $lineswithoutlevel0 : $lines), $level, $projectsrole, $tasksrole, $mine, $restricteditformytask, $isavailable, $oldprojectforbreak, $arrayfields, $extrafields, $dayInMonth); + //var_dump('ret with parent='.$lines[$i]->id.' level='.$level); + //var_dump($ret); + foreach ($ret as $key => $val) { + $totalforeachday[$key] += $val; + } + //var_dump('totalforeachday after taskid='.$lines[$i]->id.' and previous one on level '.$level.' + subtasks'); + //var_dump($totalforeachday); + } + $level--; + } else { + //$level--; + } + } + + return $totalforeachday; +} + +/** + * Load time spent into this->weekWorkLoad and this->weekWorkLoadPerTask for all day of a week of project. + * Note: array weekWorkLoad and weekWorkLoadPerTask are reset and filled at each call. + * + * @param int $datestart First day of week (use dol_get_first_day to find this date) + * @param int $taskid Filter on a task id + * @param int $userid Time spent by a particular user + * @return int <0 if OK, >0 if KO + */ +function loadTimeSpentMonthByDay($datestart, $taskid = 0, $userid = 0, $project) +{ + $error = 0; + + $project->monthWorkLoad = array(); + $project->monthWorkLoadPerTask = array(); + + if (empty($datestart)) { + dol_print_error('', 'Error datestart parameter is empty'); + } + + $sql = "SELECT ptt.rowid as taskid, ptt.task_duration, ptt.task_date, ptt.task_datehour, ptt.fk_task"; + $sql .= " FROM ".MAIN_DB_PREFIX."projet_task_time AS ptt, ".MAIN_DB_PREFIX."projet_task as pt"; + $sql .= " WHERE ptt.fk_task = pt.rowid"; + $sql .= " AND pt.fk_projet = ".((int) $project->id); + $sql .= " AND (ptt.task_date >= '".$project->db->idate($datestart)."' "; + $sql .= " AND ptt.task_date <= '".$project->db->idate(dol_time_plus_duree($datestart, 1, 'm') - 1)."')"; + if ($task_id) { + $sql .= " AND ptt.fk_task=".((int) $taskid); + } + if (is_numeric($userid)) { + $sql .= " AND ptt.fk_user=".((int) $userid); + } + + //print $sql; + $resql = $project->db->query($sql); + if ($resql) { + $daylareadyfound = array(); + + $num = $project->db->num_rows($resql); + $i = 0; + // Loop on each record found, so each couple (project id, task id) + while ($i < $num) { + $obj = $project->db->fetch_object($resql); + $day = $project->db->jdate($obj->task_date); // task_date is date without hours + if (empty($daylareadyfound[$day])) { + $project->monthWorkLoad[$day] = $obj->task_duration; + $project->monthWorkLoadPerTask[$day][$obj->fk_task] = $obj->task_duration; + } else { + $project->monthWorkLoad[$day] += $obj->task_duration; + $project->monthWorkLoadPerTask[$day][$obj->fk_task] += $obj->task_duration; + } + $daylareadyfound[$day] = 1; + $i++; + } + $project->db->free($resql); + return 1; + } else { + $project->error = "Error ".$project->db->lasterror(); + dol_syslog(get_class($project)."::fetch ".$project->error, LOG_ERR); + return -1; + } +} + +/** + * Output a task line into a perday intput mode + * + * @param string $inc Line output identificator (start to 0, then increased by recursive call) + * @param int $firstdaytoshow First day to show + * @param User|null $fuser Restrict list to user if defined + * @param string $parent Id of parent task to show (0 to show all) + * @param Task[] $lines Array of lines (list of tasks but we will show only if we have a specific role on task) + * @param int $level Level (start to 0, then increased/decrease by recursive call) + * @param string $projectsrole Array of roles user has on project + * @param string $tasksrole Array of roles user has on task + * @param string $mine Show only task lines I am assigned to + * @param int $restricteditformytask 0=No restriction, 1=Enable add time only if task is assigned to me, 2=Enable add time only if tasks is assigned to me and hide others + * @param array $isavailable Array with data that say if user is available for several days for morning and afternoon + * @param int $oldprojectforbreak Old project id of last project break + * @param array $arrayfields Array of additional column + * @param Extrafields $extrafields Object extrafields + * @return array Array with time spent for $fuser for each day of week on tasks in $lines and substasks + */ +function projectLinesPerWeekDoliProject(&$inc, $firstdaytoshow, $fuser, $parent, $lines, &$level, &$projectsrole, &$tasksrole, $mine, $restricteditformytask, &$isavailable, $oldprojectforbreak = 0, $arrayfields = array(), $extrafields = null) +{ + global $conf, $db, $user, $langs; + global $form, $formother, $projectstatic, $taskstatic, $thirdpartystatic; + + $numlines = count($lines); + + $lastprojectid = 0; + $workloadforid = array(); + $totalforeachday = array(); + $lineswithoutlevel0 = array(); + + // Create a smaller array with sublevels only to be used later. This increase dramatically performances. + if ($parent == 0) { // Always and only if at first level + for ($i = 0; $i < $numlines; $i++) { + if ($lines[$i]->fk_task_parent) { + $lineswithoutlevel0[] = $lines[$i]; + } + } + } + + //dol_syslog('projectLinesPerWeek inc='.$inc.' firstdaytoshow='.$firstdaytoshow.' task parent id='.$parent.' level='.$level." count(lines)=".$numlines." count(lineswithoutlevel0)=".count($lineswithoutlevel0)); + + if (empty($oldprojectforbreak)) { + $oldprojectforbreak = (empty($conf->global->PROJECT_TIMESHEET_DISABLEBREAK_ON_PROJECT) ? 0 : -1); // 0 = start break, -1 = never break + } + + $restrictBefore = null; + + if (! empty($conf->global->PROJECT_TIMESHEET_PREVENT_AFTER_MONTHS)) { + require_once DOL_DOCUMENT_ROOT.'/core/lib/date.lib.php'; + $restrictBefore = dol_time_plus_duree(dol_now(), - $conf->global->PROJECT_TIMESHEET_PREVENT_AFTER_MONTHS, 'm'); + } + + for ($i = 0; $i < $numlines; $i++) { + if ($parent == 0) { + $level = 0; + } + + if ($lines[$i]->fk_task_parent == $parent) { + $obj = &$lines[$i]; // To display extrafields + + // If we want all or we have a role on task, we show it + if (empty($mine) || !empty($tasksrole[$lines[$i]->id])) { + //dol_syslog("projectLinesPerWeek Found line ".$i.", a qualified task (i have role or want to show all tasks) with id=".$lines[$i]->id." project id=".$lines[$i]->fk_project); + + if ($restricteditformytask == 2 && empty($tasksrole[$lines[$i]->id])) { // we have no role on task and we request to hide such cases + continue; + } + + // Break on a new project + if ($parent == 0 && $lines[$i]->fk_project != $lastprojectid) { + $lastprojectid = $lines[$i]->fk_project; + $projectstatic->id = $lines[$i]->fk_project; + } + + //var_dump('--- '.$level.' '.$firstdaytoshow.' '.$fuser->id.' '.$projectstatic->id.' '.$workloadforid[$projectstatic->id]); + //var_dump($projectstatic->weekWorkLoadPerTask); + if (empty($workloadforid[$projectstatic->id])) { + $projectstatic->loadTimeSpent($firstdaytoshow, 0, $fuser->id); // Load time spent from table projet_task_time for the project into this->weekWorkLoad and this->weekWorkLoadPerTask for all days of a week + $workloadforid[$projectstatic->id] = 1; + } + //var_dump($projectstatic->weekWorkLoadPerTask); + //var_dump('--- '.$projectstatic->id.' '.$workloadforid[$projectstatic->id]); + + $projectstatic->id = $lines[$i]->fk_project; + $projectstatic->ref = $lines[$i]->projectref; + $projectstatic->title = $lines[$i]->projectlabel; + $projectstatic->public = $lines[$i]->public; + $projectstatic->thirdparty_name = $lines[$i]->thirdparty_name; + $projectstatic->status = $lines[$i]->projectstatus; + + $taskstatic->id = $lines[$i]->id; + $taskstatic->ref = ($lines[$i]->ref ? $lines[$i]->ref : $lines[$i]->id); + $taskstatic->label = $lines[$i]->label; + $taskstatic->date_start = $lines[$i]->date_start; + $taskstatic->date_end = $lines[$i]->date_end; + + $thirdpartystatic->id = $lines[$i]->thirdparty_id; + $thirdpartystatic->name = $lines[$i]->thirdparty_name; + $thirdpartystatic->email = $lines[$i]->thirdparty_email; + + if (empty($oldprojectforbreak) || ($oldprojectforbreak != -1 && $oldprojectforbreak != $projectstatic->id)) { + $addcolspan = 0; + if (!empty($arrayfields['t.planned_workload']['checked'])) { + $addcolspan++; + } + if (!empty($arrayfields['t.progress']['checked'])) { + $addcolspan++; + } + foreach ($arrayfields as $key => $val) { + if ($val['checked'] && substr($key, 0, 5) == 'efpt.') { + $addcolspan++; + } + } + + if ($conf->global->DOLIPROJECT_SHOW_ONLY_FAVORITE_TASKS) { + $taskfavorite = isTaskFavorite($lines[$i]->id, $fuser->id); + } else { + $taskfavorite = 1; + } + + print ''."\n"; + print ''; + print $projectstatic->getNomUrl(1, '', 0, ''.$langs->transnoentitiesnoconv("YourRole").': '.$projectsrole[$lines[$i]->fk_project]); + if ($thirdpartystatic->id > 0) { + print ' - '.$thirdpartystatic->getNomUrl(1); + } + if ($projectstatic->title) { + print ' - '; + print ''.dol_trunc($projectstatic->title, '64').''; + } + + /*$colspan=5+(empty($conf->global->PROJECT_TIMESHEET_DISABLEBREAK_ON_PROJECT)?0:2); + print ''; + + print ''; + + // PROJECT fields + if (! empty($arrayfields['p.fk_opp_status']['checked'])) print_liste_field_titre($arrayfields['p.fk_opp_status']['label'], $_SERVER["PHP_SELF"], 'p.fk_opp_status', "", $param, '', $sortfield, $sortorder, 'center '); + if (! empty($arrayfields['p.opp_amount']['checked'])) print_liste_field_titre($arrayfields['p.opp_amount']['label'], $_SERVER["PHP_SELF"], 'p.opp_amount', "", $param, '', $sortfield, $sortorder, 'right '); + if (! empty($arrayfields['p.opp_percent']['checked'])) print_liste_field_titre($arrayfields['p.opp_percent']['label'], $_SERVER["PHP_SELF"], 'p.opp_percent', "", $param, '', $sortfield, $sortorder, 'right '); + if (! empty($arrayfields['p.budget_amount']['checked'])) print_liste_field_titre($arrayfields['p.budget_amount']['label'], $_SERVER["PHP_SELF"], 'p.budget_amount', "", $param, '', $sortfield, $sortorder, 'right '); + if (! empty($arrayfields['p.usage_bill_time']['checked'])) print_liste_field_titre($arrayfields['p.usage_bill_time']['label'], $_SERVER["PHP_SELF"], 'p.usage_bill_time', "", $param, '', $sortfield, $sortorder, 'right '); + + $extrafieldsobjectkey='projet'; + $extrafieldsobjectprefix='efp.'; + include DOL_DOCUMENT_ROOT.'/core/tpl/extrafields_list_search_title.tpl.php'; + + print ''; + print ''; + + // PROJECT fields + if (! empty($arrayfields['p.fk_opp_status']['checked'])) + { + print '\n"; + } + if (! empty($arrayfields['p.opp_amount']['checked'])) + { + print '\n"; + } + if (! empty($arrayfields['p.opp_percent']['checked'])) + { + print '\n"; + } + if (! empty($arrayfields['p.budget_amount']['checked'])) + { + print '\n"; + } + if (! empty($arrayfields['p.usage_bill_time']['checked'])) + { + print '\n"; + } + + $extrafieldsobjectkey='projet'; + $extrafieldsobjectprefix='efp.'; + include DOL_DOCUMENT_ROOT.'/core/tpl/extrafields_list_print_fields.tpl.php'; + + print ''; + print '
'; + $code = dol_getIdFromCode($db, $lines[$i]->fk_opp_status, 'c_lead_status', 'rowid', 'code'); + if ($code) print $langs->trans("OppStatus".$code); + print "'; + print price($lines[$i]->opp_amount, 0, $langs, 1, 0, -1, $conf->currency); + print "'; + print price($lines[$i]->opp_percent, 0, $langs, 1, 0).' %'; + print "'; + print price($lines[$i]->budget_amount, 0, $langs, 1, 0, 0, $conf->currency); + print "'; + print yn($lines[$i]->usage_bill_time); + print "
'; + */ + + print ''; + print ''; + } + + if ($oldprojectforbreak != -1) { + $oldprojectforbreak = $projectstatic->id; + } + + if ($conf->global->DOLIPROJECT_SHOW_ONLY_FAVORITE_TASKS) { + $taskfavorite = isTaskFavorite($lines[$i]->id, $fuser->id); + } else { + $taskfavorite = 1; + } + + print ''."\n"; + + // User + /* + print ''; + print $fuser->getNomUrl(1, 'withproject', 'time'); + print ''; + */ + + // Project + if (!empty($conf->global->PROJECT_TIMESHEET_DISABLEBREAK_ON_PROJECT)) { + print ''; + if ($oldprojectforbreak == -1) { + print $projectstatic->getNomUrl(1, '', 0, $langs->transnoentitiesnoconv("YourRole").': '.$projectsrole[$lines[$i]->fk_project]); + } + print ""; + } + + // Thirdparty + if (!empty($conf->global->PROJECT_TIMESHEET_DISABLEBREAK_ON_PROJECT)) { + print ''; + if ($thirdpartystatic->id > 0) { + print $thirdpartystatic->getNomUrl(1, 'project'); + } + print ''; + } + + // Ref + print ''; + print ''; + for ($k = 0; $k < $level; $k++) { + print '
'; + } + print $taskstatic->getNomUrl(1, 'withproject', 'time'); + // Label task + print '
'; + print ''.dol_trunc($taskstatic->label, '64').''; + for ($k = 0; $k < $level; $k++) { + print "
"; + } + print "\n"; + + // TASK extrafields + $extrafieldsobjectkey = 'projet_task'; + $extrafieldsobjectprefix = 'efpt.'; + include DOL_DOCUMENT_ROOT.'/core/tpl/extrafields_list_print_fields.tpl.php'; + + // Planned Workload + if (!empty($arrayfields['t.planned_workload']['checked'])) { + print ''; + if ($lines[$i]->planned_workload) { + print convertSecondToTime($lines[$i]->planned_workload, 'allhourmin'); + } else { + print '--:--'; + } + print ''; + } + + if (!empty($arrayfields['t.progress']['checked'])) { + // Progress declared % + print ''; + print $formother->select_percent($lines[$i]->progress, $lines[$i]->id.'progress'); + print ''; + } + + if (!empty($arrayfields['timeconsumed']['checked'])) { + // Time spent by everybody + print ''; + // $lines[$i]->duration is a denormalised field = summ of time spent by everybody for task. What we need is time consummed by user + if ($lines[$i]->duration) { + print ''; + print convertSecondToTime($lines[$i]->duration, 'allhourmin'); + print ''; + } else { + print '--:--'; + } + print "\n"; + + // Time spent by user + print ''; + $firstday = dol_print_date($firstdaytoshow, 'dayrfc'); + $CurrentDate = dol_getdate(dol_now()); + $currentWeek = getWeekNumber($CurrentDate['mday'], $CurrentDate['mon'], $CurrentDate['year']); + $Date = dol_getdate($firstdaytoshow); + $Week = getWeekNumber($Date['mday'], $Date['mon'], $Date['year']); + + if ($currentWeek == $Week) { + $firstDay = date( 'd', $firstdaytoshow); + $currentDay = date( 'd', dol_now()); + $nbday = $currentDay - $firstDay; + } else { + $nbday = 6; + } + $lastdaytoshow = dol_time_plus_duree($firstdaytoshow, $nbday, 'd'); + + $lastday = dol_print_date($lastdaytoshow, 'dayrfc'); + $filter = ' AND t.task_datehour BETWEEN ' . "'" . $firstday . "'" . ' AND ' . "'" . $lastday . "'"; + $tmptimespent = $taskstatic->getSummaryOfTimeSpent($fuser->id, $filter); + if ($tmptimespent['total_duration']) { + print convertSecondToTime($tmptimespent['total_duration'], 'allhourmin'); + } else { + print '--:--'; + } + print "\n"; + } + + $disabledproject = 1; + $disabledtask = 1; + //print "x".$lines[$i]->fk_project; + //var_dump($lines[$i]); + //var_dump($projectsrole[$lines[$i]->fk_project]); + // If at least one role for project + if ($lines[$i]->public || !empty($projectsrole[$lines[$i]->fk_project]) || $user->rights->projet->all->creer) { + $disabledproject = 0; + $disabledtask = 0; + } + // If $restricteditformytask is on and I have no role on task, i disable edit + if ($restricteditformytask && empty($tasksrole[$lines[$i]->id])) { + $disabledtask = 1; + } + + //var_dump($projectstatic->weekWorkLoadPerTask); + + // Fields to show current time + $tableCell = ''; + $modeinput = 'hours'; + for ($idw = 0; $idw < 7; $idw++) { + $tmpday = dol_time_plus_duree($firstdaytoshow, $idw, 'd'); + + $cssonholiday = ''; + if (!$isavailable[$tmpday]['morning'] && !$isavailable[$tmpday]['afternoon']) { + $cssonholiday .= 'onholidayallday '; + } elseif (!$isavailable[$tmpday]['morning']) { + $cssonholiday .= 'onholidaymorning '; + } elseif (!$isavailable[$tmpday]['afternoon']) { + $cssonholiday .= 'onholidayafternoon '; + } + + $tmparray = dol_getdate($tmpday); + $dayWorkLoad = $projectstatic->weekWorkLoadPerTask[$tmpday][$lines[$i]->id]; + $totalforeachday[$tmpday] += $dayWorkLoad; + + $alreadyspent = ''; + if ($dayWorkLoad > 0) { + $alreadyspent = convertSecondToTime($dayWorkLoad, 'allhourmin'); + } + $alttitle = $langs->trans("AddHereTimeSpentForDay", $tmparray['day'], $tmparray['mon']); + + global $numstartworkingday, $numendworkingday; + $cssweekend = ''; + if (($idw + 1 < $numstartworkingday) || ($idw + 1 > $numendworkingday)) { // This is a day is not inside the setup of working days, so we use a week-end css. + $cssweekend = 'weekend'; + } + + $disabledtaskday = $disabledtask; + + if (! $disabledtask && $restrictBefore && $tmpday < $restrictBefore) { + $disabledtaskday = 1; + } + + $tableCell = ''; + //$tableCell .= 'idw='.$idw.' '.$conf->global->MAIN_START_WEEK.' '.$numstartworkingday.'-'.$numendworkingday; + $placeholder = ''; + if ($alreadyspent) { + $tableCell .= ''; + //$placeholder=' placeholder="00:00"'; + //$tableCell.='+'; + } + $tableCell .= ''; + $tableCell .= ''; + print $tableCell; + } + + // Warning + print ''; + if ((!$lines[$i]->public) && $disabledproject) { + print $form->textwithpicto('', $langs->trans("UserIsNotContactOfProject")); + } elseif ($disabledtask) { + $titleassigntask = $langs->trans("AssignTaskToMe"); + if ($fuser->id != $user->id) { + $titleassigntask = $langs->trans("AssignTaskToUser", '...'); + } + + print $form->textwithpicto('', $langs->trans("TaskIsNotAssignedToUser", $titleassigntask)); + } + print ''; + + print "\n"; + } + + // Call to show task with a lower level (task under the current task) + $inc++; + $level++; + if ($lines[$i]->id > 0) { + //var_dump('totalforeachday after taskid='.$lines[$i]->id.' and previous one on level '.$level); + //var_dump($totalforeachday); + $ret = projectLinesPerWeekDoliProject($inc, $firstdaytoshow, $fuser, $lines[$i]->id, ($parent == 0 ? $lineswithoutlevel0 : $lines), $level, $projectsrole, $tasksrole, $mine, $restricteditformytask, $isavailable, $oldprojectforbreak, $arrayfields, $extrafields); + //var_dump('ret with parent='.$lines[$i]->id.' level='.$level); + //var_dump($ret); + foreach ($ret as $key => $val) { + $totalforeachday[$key] += $val; + } + //var_dump('totalforeachday after taskid='.$lines[$i]->id.' and previous one on level '.$level.' + subtasks'); + //var_dump($totalforeachday); + } + $level--; + } else { + //$level--; + } + } + + return $totalforeachday; +} diff --git a/sql/data.sql b/sql/data.sql new file mode 100644 index 0000000..88107e1 --- /dev/null +++ b/sql/data.sql @@ -0,0 +1,2 @@ +INSERT INTO `llx_c_actioncomm` (`id`, `code`, `type`, `libelle`, `module`, `active`, `todo`, `color`, `picto`, `position`) VALUES (70, 'AC_TEL_IN', 'system', 'Incoming phone call', NULL, 1, NULL, NULL, NULL, 10); +INSERT INTO `llx_c_actioncomm` (`id`, `code`, `type`, `libelle`, `module`, `active`, `todo`, `color`, `picto`, `position`) VALUES (71, 'AC_TEL_OUT', 'system', 'Outgoing phone call', NULL, 1, NULL, NULL, NULL, 11); diff --git a/sql/llx_categorie_invoice.key.sql b/sql/llx_categorie_invoice.key.sql new file mode 100644 index 0000000..cd00af7 --- /dev/null +++ b/sql/llx_categorie_invoice.key.sql @@ -0,0 +1,19 @@ +-- Copyright (C) 2022 EOXIA +-- +-- This program is free software: you can redistribute it and/or modify +-- it under the terms of the GNU General Public License as published by +-- the Free Software Foundation, either version 3 of the License, or +-- (at your option) any later version. +-- +-- This program is distributed in the hope that it will be useful, +-- but WITHOUT ANY WARRANTY; without even the implied warranty of +-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +-- GNU General Public License for more details. +-- +-- You should have received a copy of the GNU General Public License +-- along with this program. If not, see https://www.gnu.org/licenses/. + +ALTER TABLE llx_categorie_invoice ADD PRIMARY KEY pk_categorie_invoice (fk_categorie, fk_invoice); +ALTER TABLE llx_categorie_invoice ADD INDEX idx_categorie_invoice_fk_categorie (fk_categorie); +ALTER TABLE llx_categorie_invoice ADD INDEX idx_categorie_invoice_fk_invoice (fk_invoice); +ALTER TABLE llx_categorie_invoice ADD CONSTRAINT fk_categorie_invoice_categorie_rowid FOREIGN KEY (fk_categorie) REFERENCES llx_categorie (rowid); diff --git a/sql/llx_categorie_invoice.sql b/sql/llx_categorie_invoice.sql new file mode 100644 index 0000000..41ba2e4 --- /dev/null +++ b/sql/llx_categorie_invoice.sql @@ -0,0 +1,20 @@ +-- Copyright (C) 2022 EOXIA +-- +-- This program is free software: you can redistribute it and/or modify +-- it under the terms of the GNU General Public License as published by +-- the Free Software Foundation, either version 3 of the License, or +-- (at your option) any later version. +-- +-- This program is distributed in the hope that it will be useful, +-- but WITHOUT ANY WARRANTY; without even the implied warranty of +-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +-- GNU General Public License for more details. +-- +-- You should have received a copy of the GNU General Public License +-- along with this program. If not, see https://www.gnu.org/licenses/. + +CREATE TABLE llx_categorie_invoice( + fk_categorie integer NOT NULL, + fk_invoice integer NOT NULL, + import_key varchar(14) +) ENGINE=innodb; diff --git a/sql/llx_categorie_invoicerec.key.sql b/sql/llx_categorie_invoicerec.key.sql new file mode 100644 index 0000000..a9c55e1 --- /dev/null +++ b/sql/llx_categorie_invoicerec.key.sql @@ -0,0 +1,19 @@ +-- Copyright (C) 2022 EOXIA +-- +-- This program is free software: you can redistribute it and/or modify +-- it under the terms of the GNU General Public License as published by +-- the Free Software Foundation, either version 3 of the License, or +-- (at your option) any later version. +-- +-- This program is distributed in the hope that it will be useful, +-- but WITHOUT ANY WARRANTY; without even the implied warranty of +-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +-- GNU General Public License for more details. +-- +-- You should have received a copy of the GNU General Public License +-- along with this program. If not, see https://www.gnu.org/licenses/. + +ALTER TABLE llx_categorie_invoicerec ADD PRIMARY KEY pk_categorie_invoicerec (fk_categorie, fk_invoicerec); +ALTER TABLE llx_categorie_invoicerec ADD INDEX idx_categorie_invoicerec_fk_categorie (fk_categorie); +ALTER TABLE llx_categorie_invoicerec ADD INDEX idx_categorie_invoicerec_fk_invoicerec (fk_invoicerec); +ALTER TABLE llx_categorie_invoicerec ADD CONSTRAINT fk_categorie_invoicerec_categorie_rowid FOREIGN KEY (fk_categorie) REFERENCES llx_categorie (rowid); diff --git a/sql/llx_categorie_invoicerec.sql b/sql/llx_categorie_invoicerec.sql new file mode 100644 index 0000000..8247d50 --- /dev/null +++ b/sql/llx_categorie_invoicerec.sql @@ -0,0 +1,20 @@ +-- Copyright (C) 2022 EOXIA +-- +-- This program is free software: you can redistribute it and/or modify +-- it under the terms of the GNU General Public License as published by +-- the Free Software Foundation, either version 3 of the License, or +-- (at your option) any later version. +-- +-- This program is distributed in the hope that it will be useful, +-- but WITHOUT ANY WARRANTY; without even the implied warranty of +-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +-- GNU General Public License for more details. +-- +-- You should have received a copy of the GNU General Public License +-- along with this program. If not, see https://www.gnu.org/licenses/. + +CREATE TABLE llx_categorie_invoicerec( + fk_categorie integer NOT NULL, + fk_invoicerec integer NOT NULL, + import_key varchar(14) +) ENGINE=innodb; diff --git a/view/recurringinvoicestatistics.php b/view/recurringinvoicestatistics.php new file mode 100644 index 0000000..3244d13 --- /dev/null +++ b/view/recurringinvoicestatistics.php @@ -0,0 +1,446 @@ + + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/** + * \file view/recurringinvoicestatistics.php + * \ingroup doliproject + * \brief Recurring invoice statistics page + */ + +// Load Dolibarr environment +$res = 0; +// Try main.inc.php into web root known defined into CONTEXT_DOCUMENT_ROOT (not always defined) +if ( ! $res && ! empty($_SERVER["CONTEXT_DOCUMENT_ROOT"])) $res = @include $_SERVER["CONTEXT_DOCUMENT_ROOT"] . "/main.inc.php"; +// Try main.inc.php into web root detected using web root calculated from SCRIPT_FILENAME +$tmp = empty($_SERVER['SCRIPT_FILENAME']) ? '' : $_SERVER['SCRIPT_FILENAME']; $tmp2 = realpath(__FILE__); $i = strlen($tmp) - 1; $j = strlen($tmp2) - 1; +while ($i > 0 && $j > 0 && isset($tmp[$i]) && isset($tmp2[$j]) && $tmp[$i] == $tmp2[$j]) { $i--; $j--; } +if ( ! $res && $i > 0 && file_exists(substr($tmp, 0, ($i + 1)) . "/main.inc.php")) $res = @include substr($tmp, 0, ($i + 1)) . "/main.inc.php"; +if ( ! $res && $i > 0 && file_exists(dirname(substr($tmp, 0, ($i + 1))) . "/main.inc.php")) $res = @include dirname(substr($tmp, 0, ($i + 1))) . "/main.inc.php"; +// Try main.inc.php using relative path +if ( ! $res && file_exists("../../main.inc.php")) $res = @include "../../main.inc.php"; +if ( ! $res && file_exists("../../../main.inc.php")) $res = @include "../../../main.inc.php"; +if ( ! $res) die("Include of main fails"); + +// Libraries +require_once DOL_DOCUMENT_ROOT.'/core/class/dolgraph.class.php'; +require_once DOL_DOCUMENT_ROOT.'/core/class/html.formcompany.class.php'; +require_once DOL_DOCUMENT_ROOT.'/core/class/html.formother.class.php'; +require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php'; +if (!empty($conf->category->enabled)) { + require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php'; +} + +require_once DOL_DOCUMENT_ROOT.'/custom/doliproject/class/facturerecstats.class.php'; + +// Global variables definitions +global $db, $langs, $user; + +$WIDTH = DolGraph::getDefaultGraphSizeForStats('width'); +$HEIGHT = DolGraph::getDefaultGraphSizeForStats('height'); + +// Load translation files required by the page +$langs->loadLangs(array('bills', 'companies', 'other')); + +$mode = GETPOST("mode") ? GETPOST("mode") : 'customer'; +if ($mode == 'customer' && !$user->rights->facture->lire) { + accessforbidden(); +} +//if ($mode == 'supplier' && empty($user->rights->fournisseur->facture->lire)) { +// accessforbidden(); +//} + +$object_status = GETPOST('object_status', 'intcomma'); +$typent_id = GETPOST('typent_id', 'int'); +$categ_id = GETPOST('categ_id', 'categ_id'); +$categinvoicerec_id = GETPOST('categinvoicerec_id'); +$userid = GETPOST('userid', 'int'); +$socid = GETPOST('socid', 'int'); +$custcats = GETPOST('custcats', 'array'); +$invoicereccats = GETPOST('invoicereccats', 'array'); + +// Security check +if ($user->socid > 0) { + $action = ''; + $socid = $user->socid; +} + +$nowyear = strftime("%Y", dol_now()); +$year = GETPOST('year') > 0 ? GETPOST('year', 'int') : $nowyear; +$startyear = $year - (empty($conf->global->MAIN_STATS_GRAPHS_SHOW_N_YEARS) ? 2 : max(1, min(10, $conf->global->MAIN_STATS_GRAPHS_SHOW_N_YEARS))); +$endyear = $year; + +/* + * View + */ +if (!empty($conf->category->enabled)) { + $langs->load('categories'); +} +$form = new Form($db); +$formcompany = new FormCompany($db); +$formother = new FormOther($db); + +llxHeader(); + +$picto = 'bill'; +$title = $langs->trans("RecurringInvoicesStatistics"); +$dir = $conf->facture->dir_temp; + +//if ($mode == 'supplier') { +// $picto = 'supplier_invoice'; +// $title = $langs->trans("BillsStatisticsSuppliers"); +// $dir = $conf->fournisseur->facture->dir_temp; +//} + +print load_fiche_titre($title, '', $picto); + +dol_mkdir($dir); + +$stats = new FactureRecStats($db, $socid, $mode, ($userid > 0 ? $userid : 0), ($typent_id > 0 ? $typent_id : 0), ($categ_id > 0 ? $categ_id : 0), ($categinvoicerec_id > 0 ? $categinvoicerec_id : 0)); +if ($mode == 'customer') { + if ($object_status != '' && $object_status >= 0) { + $stats->where .= ' AND f.suspended IN ('.$db->sanitize($object_status).')'; + } + if (is_array($custcats) && !empty($custcats)) { + $stats->from .= ' LEFT JOIN '.MAIN_DB_PREFIX.'categorie_societe as cat ON (fr.fk_soc = cat.fk_soc)'; + $stats->where .= ' AND cat.fk_categorie IN ('.$db->sanitize(implode(',', $custcats)).')'; + } + if (is_array($invoicereccats) && !empty($invoicereccats)) { + $stats->from .= ' LEFT JOIN '.MAIN_DB_PREFIX.'categorie_invoicerec as catinv ON (fr.rowid = catinv.fk_invoicerec)'; + $stats->where .= ' AND catinv.fk_categorie IN ('.$db->sanitize(implode(',', $invoicereccats)).')'; + } +} + +//if ($mode == 'supplier') { +// if ($object_status != '' && $object_status >= 0) { +// $stats->where .= ' AND f.fk_statut IN ('.$db->sanitize($object_status).')'; +// } +// if (is_array($custcats) && !empty($custcats)) { +// $stats->from .= ' LEFT JOIN '.MAIN_DB_PREFIX.'categorie_fournisseur as cat ON (f.fk_soc = cat.fk_soc)'; +// $stats->where .= ' AND cat.fk_categorie IN ('.$db->sanitize(implode(',', $custcats)).')'; +// } +//} + +// Build graphic number of object +$data = $stats->getNbByMonthWithPrevYear($endyear, $startyear, 0, 0, $conf->global->SOCIETE_FISCAL_MONTH_START); + +$filenamenb = $dir."/invoicerecsnbinyear-".$year.".png"; +if ($mode == 'customer') { + $fileurlnb = DOL_URL_ROOT.'/viewimage.php?modulepart=doliproject&file=invoicerecsnbinyear-'.$year.'.png'; +} +//if ($mode == 'supplier') { +// $fileurlnb = DOL_URL_ROOT.'/viewimage.php?modulepart=doliproject&file=invoicesnbinyear-'.$year.'.png'; +//} + +$px1 = new DolGraph(); +$mesg = $px1->isGraphKo(); +if (!$mesg) { + $px1->SetData($data); + $i = $startyear; + $legend = array(); + while ($i <= $endyear) { + $legend[] = $i; + $i++; + } + $px1->SetLegend($legend); + $px1->SetMaxValue($px1->GetCeilMaxValue()); + $px1->SetWidth($WIDTH); + $px1->SetHeight($HEIGHT); + $px1->SetYLabel($langs->trans("NumberOfRecurringBills")); + $px1->SetShading(3); + $px1->SetHorizTickIncrement(1); + $px1->mode = 'depth'; + $px1->SetTitle($langs->trans("NumberOfRecurringBillsByMonth")); + + $px1->draw($filenamenb, $fileurlnb); +} + +// Build graphic amount of object +$data = $stats->getAmountByMonthWithPrevYear($endyear, $startyear, 0, 0, $conf->global->SOCIETE_FISCAL_MONTH_START); + +$filenameamount = $dir."/invoicerecsamountinyear-".$year.".png"; +if ($mode == 'customer') { + $fileurlamount = DOL_URL_ROOT.'/viewimage.php?modulepart=doliproject&file=invoicerecsamountinyear-'.$year.'.png'; +} +//if ($mode == 'supplier') { +// $fileurlamount = DOL_URL_ROOT.'/viewimage.php?modulepart=billstatssupplier&file=invoicesamountinyear-'.$year.'.png'; +//} + +$px2 = new DolGraph(); +$mesg = $px2->isGraphKo(); +if (!$mesg) { + $px2->SetData($data); + $i = $startyear; + $legend = array(); + while ($i <= $endyear) { + $legend[] = $i; + $i++; + } + $px2->SetLegend($legend); + $px2->SetMaxValue($px2->GetCeilMaxValue()); + $px2->SetMinValue(min(0, $px2->GetFloorMinValue())); + $px2->SetWidth($WIDTH); + $px2->SetHeight($HEIGHT); + $px2->SetYLabel($langs->trans("AmountOfRecurringBills")); + $px2->SetShading(3); + $px2->SetHorizTickIncrement(1); + $px2->mode = 'depth'; + $px2->SetTitle($langs->trans("AmountOfRecurringBillsByMonthHT")); + + $px2->draw($filenameamount, $fileurlamount); +} + +// Build graphic average amount of object +$data = $stats->getAverageByMonthWithPrevYear($endyear, $startyear, 0, 0, $conf->global->SOCIETE_FISCAL_MONTH_START); + +if (empty($user->rights->societe->client->voir) || $user->socid) { + $filename_avg = $dir.'/invoicerecsaverage-'.$user->id.'-'.$year.'.png'; + if ($mode == 'customer') { + $fileurl_avg = DOL_URL_ROOT.'/viewimage.php?modulepart=doliproject&file=invoicerecsaverage-'.$user->id.'-'.$year.'.png'; + } +// if ($mode == 'supplier') { +// $fileurl_avg = DOL_URL_ROOT.'/viewimage.php?modulepart=orderstatssupplier&file=ordersaverage-'.$user->id.'-'.$year.'.png'; +// } +} else { + $filename_avg = $dir.'/invoicerecsaverage-'.$year.'.png'; + if ($mode == 'customer') { + $fileurl_avg = DOL_URL_ROOT.'/viewimage.php?modulepart=doliproject&file=invoicerecsaverage-'.$year.'.png'; + } +// if ($mode == 'supplier') { +// $fileurl_avg = DOL_URL_ROOT.'/viewimage.php?modulepart=orderstatssupplier&file=ordersaverage-'.$year.'.png'; +// } +} + +$px3 = new DolGraph(); +$mesg = $px3->isGraphKo(); +if (!$mesg) { + $px3->SetData($data); + $i = $startyear; + $legend = array(); + while ($i <= $endyear) { + $legend[] = $i; + $i++; + } + $px3->SetLegend($legend); + $px3->SetYLabel($langs->trans("AmountAverage")); + $px3->SetMaxValue($px3->GetCeilMaxValue()); + $px3->SetMinValue($px3->GetFloorMinValue()); + $px3->SetWidth($WIDTH); + $px3->SetHeight($HEIGHT); + $px3->SetShading(3); + $px3->SetHorizTickIncrement(1); + $px3->mode = 'depth'; + $px3->SetTitle($langs->trans("AmountAverage")); + + $px3->draw($filename_avg, $fileurl_avg); +} + +// Show array +$data = $stats->getAllByYear(); +$arrayyears = array(); +foreach ($data as $val) { + $arrayyears[$val['year']] = $val['year']; +} +if (!count($arrayyears)) { + $arrayyears[$nowyear] = $nowyear; +} + +$h = 0; +$head = array(); +$head[$h][0] = DOL_URL_ROOT.'/custom/doliproject/view/recurringinvoicestatistics.php?mode='.urlencode($mode); +$head[$h][1] = $langs->trans("ByMonthYear"); +$head[$h][2] = 'byyear'; +$h++; + +if ($mode == 'customer') { + $type = 'invoice_stats'; +} +//if ($mode == 'supplier') { +// $type = 'supplier_invoice_stats'; +//} + +complete_head_from_modules($conf, $langs, null, $head, $h, $type); + +print dol_get_fiche_head($head, 'byyear', $langs->trans("Statistics"), -1); + +// We use select_thirdparty_list instead of select_company so we can use $filter and share same code for customer and supplier. +$filter = ''; +if ($mode == 'customer') { + $filter = 's.client in (1,2,3)'; +} +//if ($mode == 'supplier') { +// $filter = 's.fournisseur = 1'; +//} + +print '
'; + +// Show filter box +print '
'; +print ''; +print ''; + +print ''; +print ''; +// Company +print ''; + +// ThirdParty Type +print ''; + +// Category +if (!empty($conf->category->enabled)) { + if ($mode == 'customer') { + $cat_type = Categorie::TYPE_CUSTOMER; + $cat_label = $langs->trans("Category").' '.lcfirst($langs->trans("Customer")); + } +// if ($mode == 'supplier') { +// $cat_type = Categorie::TYPE_SUPPLIER; +// $cat_label = $langs->trans("Category").' '.lcfirst($langs->trans("Supplier")); +// } + print ''; +} + +// Category invoice rec +if (!empty($conf->category->enabled)) { + if ($mode == 'customer') { + $cat_type = 'invoice'; + $cat_label = $langs->trans("Category").' '.lcfirst($langs->trans("RecurringInvoice")); + } +// if ($mode == 'supplier') { +// $cat_type = Categorie::TYPE_SUPPLIER; +// $cat_label = $langs->trans("Category").' '.lcfirst($langs->trans("Supplier")); +// } + print ''; +} + +// User +print ''; +// Status +print ''; +// Year +print ''; +print ''; +print '
'.$langs->trans("Filter").'
'.$langs->trans("ThirdParty").''; +print img_picto('', 'company', 'class="pictofixedwidth"'); +print $form->select_company($socid, 'socid', $filter, 1, 0, 0, array(), 0, 'widthcentpercentminusx maxwidth300'); +print '
'.$langs->trans("ThirdPartyType").''; +$sortparam_typent = (empty($conf->global->SOCIETE_SORT_ON_TYPEENT) ? 'ASC' : $conf->global->SOCIETE_SORT_ON_TYPEENT); // NONE means we keep sort of original array, so we sort on position. ASC, means next function will sort on label. +print $form->selectarray("typent_id", $formcompany->typent_array(0), $typent_id, 1, 0, 0, '', 0, 0, 0, $sortparam_typent, '', 1); +if ($user->admin) { + print ' '.info_admin($langs->trans("YouCanChangeValuesForThisListFromDictionarySetup"), 1); +} +print '
'.$cat_label.''; + $cate_arbo = $form->select_all_categories($cat_type, null, 'parent', null, null, 1); + print img_picto('', 'category', 'class="pictofixedwidth"'); + print $form->multiselectarray('custcats', $cate_arbo, GETPOST('custcats', 'array'), 0, 0, 'widthcentpercentminusx maxwidth300'); + //print $formother->select_categories($cat_type, $categ_id, 'categ_id', true); + print '
'.$cat_label.''; + $cate_arbo = $form->select_all_categories($cat_type, null, 'parent', null, null, 1); + print img_picto('', 'category', 'class="pictofixedwidth"'); + print $form->multiselectarray('invoicereccats', $cate_arbo, GETPOST('invoicereccats', 'array'), 0, 0, 'widthcentpercentminusx maxwidth300'); + //print $formother->select_categories($cat_type, $categ_id, 'categ_id', true); + print '
'.$langs->trans("CreatedBy").''; +print img_picto('', 'user', 'class="pictofixedwidth"'); +print $form->select_dolusers($userid, 'userid', 1, '', 0, '', '', 0, 0, 0, '', 0, '', 'widthcentpercentminusx maxwidth300'); +print '
'.$langs->trans("Status").''; +if ($mode == 'customer') { + $liststatus = array('0'=>$langs->trans("Draft"), '1'=>$langs->trans("Disable")); + print $form->selectarray('object_status', $liststatus, $object_status, 1); +} +//if ($mode == 'supplier') { +// $liststatus = array('0'=>$langs->trans("BillStatusDraft"), '1'=>$langs->trans("BillStatusNotPaid"), '2'=>$langs->trans("BillStatusPaid")); +// print $form->selectarray('object_status', $liststatus, $object_status, 1); +//} +print '
'.$langs->trans("Year").''; +if (!in_array($year, $arrayyears)) { + $arrayyears[$year] = $year; +} +if (!in_array($nowyear, $arrayyears)) { + $arrayyears[$nowyear] = $nowyear; +} +arsort($arrayyears); +print $form->selectarray('year', $arrayyears, $year, 0, 0, 0, '', 0, 0, 0, '', 'width75'); +print '
'; +print '
'; +print '

'; + +print '
'; +print ''; +print ''; +print ''; +print ''; +print ''; +print ''; +print ''; +print ''; +print ''; +print ''; + +$oldyear = 0; +foreach ($data as $val) { + $year = $val['year']; + while ($year && $oldyear > $year + 1) { // If we have empty year + $oldyear--; + + print ''; + print ''; + print ''; + print ''; + print ''; + print ''; + print ''; + print ''; + print ''; + } + + print ''; + print ''; + print ''; + print ''; + print ''; + print ''; + print ''; + print ''; + print ''; + $oldyear = $year; +} + +print '
'.$langs->trans("Year").''.$langs->trans("NumberOfRecurringBills").'%'.$langs->trans("AmountTotal").'%'.$langs->trans("AmountAverage").'%
0 ? '&userid='.$userid : '').'">'.$oldyear.'000
0 ? '&userid='.$userid : '').'">'.$year.''.$val['nb'].''.(!empty($val['nb_diff']) && $val['nb_diff'] < 0 ? '' : '+').round(!empty($val['nb_diff']) ? $val['nb_diff'] : 0).'%'.price(price2num($val['total'], 'MT'), 1).''.( !empty($val['total_diff']) && $val['total_diff'] < 0 ? '' : '+').round(!empty($val['total_diff']) ? $val['total_diff'] : 0).'%'.price(price2num($val['avg'], 'MT'), 1).''.(!empty($val['avg_diff']) && $val['avg_diff'] < 0 ? '' : '+').round(!empty($val['avg_diff']) ? $val['avg_diff'] : 0).'%
'; +print '
'; + +print '
'; + +// Show graphs +print '
'; +if ($mesg) { + print $mesg; +} else { + print $px1->show(); + print "
\n"; + print $px2->show(); + print "
\n"; + print $px3->show(); +} +print '
'; + +print '
'; +print '
'; + +print dol_get_fiche_end(); + +// End of page +llxFooter(); +$db->close(); diff --git a/view/timespent.php b/view/timespent_day.php similarity index 81% rename from view/timespent.php rename to view/timespent_day.php index 3f10466..ac997bb 100644 --- a/view/timespent.php +++ b/view/timespent_day.php @@ -42,12 +42,18 @@ require_once DOL_DOCUMENT_ROOT.'/projet/class/project.class.php'; require_once DOL_DOCUMENT_ROOT.'/projet/class/task.class.php'; require_once DOL_DOCUMENT_ROOT.'/core/lib/project.lib.php'; +require_once DOL_DOCUMENT_ROOT.'/core/lib/admin.lib.php'; require_once DOL_DOCUMENT_ROOT.'/core/lib/date.lib.php'; require_once DOL_DOCUMENT_ROOT.'/core/class/html.formother.class.php'; require_once DOL_DOCUMENT_ROOT.'/core/class/html.formprojet.class.php'; require_once DOL_DOCUMENT_ROOT.'/core/class/html.formcompany.class.php'; require_once DOL_DOCUMENT_ROOT.'/holiday/class/holiday.class.php'; +require_once DOL_DOCUMENT_ROOT.'/custom/doliproject/lib/doliproject_functions.lib.php'; +require_once DOL_DOCUMENT_ROOT.'/custom/doliproject/class/workinghours.class.php'; + +global $conf, $user, $langs, $db; + // Load translation files required by the page $langs->loadLangs(array('projects', 'users', 'companies')); @@ -125,6 +131,9 @@ $object = new Task($db); $project = new Project($db); +$workinghours = new WorkingHours($db); + +$current_workinghours = $workinghours->fetchCurrentWorkingHours($user->id, 'user'); // Extra fields $extrafields = new ExtraFields($db); @@ -265,7 +274,6 @@ if ($action == 'addtime' && $user->rights->projet->lire && GETPOST('formfilteraction') != 'listafterchangingselectedfields') { $timespent_duration = array(); - if (is_array($_POST)) { foreach ($_POST as $key => $time) { if (intval($time) > 0) { @@ -290,6 +298,17 @@ } if (count($timespent_duration) > 0) { + $timespent_minutes = 0; + foreach($timespent_duration as $key => $val) { + $timespent_minutes += $val / 60; + } + if (!$conf->global->DOLIPROJECT_SPEND_MORE_TIME_THAN_PLANNED) { + if ($timespent_minutes > GETPOST('nonconsumedtime')) { + setEventMessages($langs->trans("TooMuchTimeSpent"), null, 'errors'); + header('Location: '.$_SERVER["PHP_SELF"].'?'.($projectid ? 'id='.$projectid : '').($search_usertoprocessid ? '&search_usertoprocessid='.urlencode($search_usertoprocessid) : '').($mode ? '&mode='.$mode : '').'&year='.$yearofday.'&month='.$monthofday.'&day='.$dayofday); + exit; + } + } foreach ($timespent_duration as $key => $val) { $object->fetch($key); $taskid = $object->id; @@ -336,8 +355,16 @@ } else { setEventMessages($langs->trans("ErrorTimeSpentIsEmpty"), null, 'errors'); } + } +if ($action == 'showOnlyFavoriteTasks') { + if ($conf->global->DOLIPROJECT_SHOW_ONLY_FAVORITE_TASKS == 1) { + dolibarr_set_const($db, 'DOLIPROJECT_SHOW_ONLY_FAVORITE_TASKS', 0, 'integer', 0, '', $conf->entity); + } else { + dolibarr_set_const($db, 'DOLIPROJECT_SHOW_ONLY_FAVORITE_TASKS', 1, 'integer', 0, '', $conf->entity); + } +} /* @@ -373,6 +400,57 @@ $project->fetch_thirdparty(); } +if (GETPOST('year')) { + $year_post = GETPOST('year'); +} else if (GETPOST('reyear')) { + $year_post = GETPOST('reyear'); +} else if (GETPOST('addtimeyear')) { + $year_post = GETPOST('addtimeyear'); +} +if (GETPOST('month')) { + $month_post = GETPOST('month'); +} else if (GETPOST('remonth')) { + $month_post = GETPOST('remonth'); +} else if (GETPOST('addtimemonth')) { + $month_post = GETPOST('addtimemonth'); +} +if (GETPOST('day')) { + $day_post = GETPOST('day'); +} else if (GETPOST('reday')) { + $day_post = GETPOST('reday'); +} else if (GETPOST('addtimeday')) { + $day_post = GETPOST('addtimeday'); +} + +$wanted_date = $year_post ? $year_post . '-' . ($month_post > 9 ? $month_post : 0 . $month_post) . '-' . ($day_post > 9 ? $day_post : 0 . $day_post) : date("Y-m-d", dol_now()); +$today_workinghours = 'workinghours_' . strtolower(date("l", dol_strlen($wanted_date) > 0 ? strtotime($wanted_date) : dol_now())); + +$worked_hours = floor($current_workinghours->$today_workinghours / 60); +$worked_minutes = ($current_workinghours->$today_workinghours % 60); +$worked_minutes = $worked_minutes < 10 ? 0 . $worked_minutes : $worked_minutes; + +$today_consumed_time = $taskstatic->fetchAllTimeSpent($user, ' AND ptt.task_date = "' . $wanted_date . '"'); +$already_consumed_time = 0; +if (is_array($today_consumed_time) && !empty($today_consumed_time)) { + foreach ($today_consumed_time as $consumed_time) { + $already_consumed_time += $consumed_time->timespent_duration; + } +} + +$non_consumed_time = $current_workinghours->$today_workinghours - floor($already_consumed_time / 60); +$non_consumed_hours = floor($non_consumed_time / 60); +$non_consumed_minutes = ($non_consumed_time % 60); +$non_consumed_minutes = $non_consumed_minutes < 10 ? 0 . $non_consumed_minutes : $non_consumed_minutes; +$non_consumed_hours = $non_consumed_hours > 0 ? $non_consumed_hours : '00'; +$non_consumed_minutes = $non_consumed_minutes > 0 ? $non_consumed_minutes : '00'; + +$already_consumed_time = $already_consumed_time / 60; +$consumed_hours = floor($already_consumed_time / 60); +$consumed_minutes = $already_consumed_time % 60; +$consumed_minutes = $consumed_minutes < 10 ? 0 . $consumed_minutes : $consumed_minutes; +$consumed_hours = $consumed_hours > 0 ? $consumed_hours : '00'; +$consumed_minutes = $consumed_minutes > 0 ? $consumed_minutes : '00'; + $onlyopenedproject = 1; // or -1 $morewherefilter = ''; @@ -406,10 +484,11 @@ $extrafieldsobjectkey = 'projet_task'; include DOL_DOCUMENT_ROOT.'/core/tpl/extrafields_list_search_sql.tpl.php'; -$tasksarray = $taskstatic->getTasksArray(0, 0, ($project->id ? $project->id : 0), $socid, 0, $search_project_ref, $onlyopenedproject, $morewherefilter, ($search_usertoprocessid ? $search_usertoprocessid : 0), 0, $extrafields); // We want to see all task of opened project i am allowed to see and that match filter, not only my tasks. Later only mine will be editable later. +$tasksarray = getFavoriteTasksArray($taskstatic->id, 0, 0, ($project->id ? $project->id : 0), $socid, 0, $search_project_ref, $onlyopenedproject, $morewherefilter, ($search_usertoprocessid ? $search_usertoprocessid : 0), 0, $extrafields); // We want to see all task of opened project i am allowed to see and that match filter, not only my tasks. Later only mine will be editable later. if ($morewherefilter) { // Get all task without any filter, so we can show total of time spent for not visible tasks - $tasksarraywithoutfilter = $taskstatic->getTasksArray(0, 0, ($project->id ? $project->id : 0), $socid, 0, '', $onlyopenedproject, '', ($search_usertoprocessid ? $search_usertoprocessid : 0)); // We want to see all task of opened project i am allowed to see and that match filter, not only my tasks. Later only mine will be editable later. + $tasksarraywithoutfilter = getFavoriteTasksArray($taskstatic->id, 0, 0, ($project->id ? $project->id : 0), $socid, 0, '', $onlyopenedproject, '', ($search_usertoprocessid ? $search_usertoprocessid : 0)); // We want to see all task of opened project i am allowed to see and that match filter, not only my tasks. Later only mine will be editable later. } + $projectsrole = $taskstatic->getUserRolesForProjectsOrTasks($usertoprocess, 0, ($project->id ? $project->id : 0), 0, $onlyopenedproject); $tasksrole = $taskstatic->getUserRolesForProjectsOrTasks(0, $usertoprocess, ($project->id ? $project->id : 0), 0, $onlyopenedproject); //var_dump($usertoprocess); @@ -454,12 +533,13 @@ print ''; print ''; print ''; +print ''; $tmp = dol_getdate($daytoparse); print ''; print ''; print ''; -$head = project_timesheet_prepare_head($mode, $usertoprocess); +$head = doliproject_timesheet_prepare_head($mode, $usertoprocess); print dol_get_fiche_head($head, 'inputperday', $langs->trans('TimeSpent'), -1, $picto); // Show description of content @@ -486,23 +566,24 @@ print '
'.$nav.'
'; // We move this before the assign to components so, the default submit button is not the assign to. - -print '
'; -$titleassigntask = $langs->transnoentities("AssignTaskToMe"); -if ($usertoprocess->id != $user->id) { - $titleassigntask = $langs->transnoentities("AssignTaskToUser", $usertoprocess->getFullName($langs)); -} -print '
'; -print img_picto('', 'projecttask', 'class="pictofixedwidth"'); -$formproject->selectTasks($socid ? $socid : -1, $taskid, 'taskid', 32, 0, '-- '.$langs->trans("ChooseANotYetAssignedTask").' --', 1, 0, 0, '', '', 'all', $usertoprocess); -print '
'; -print ' '; -print $formcompany->selectTypeContact($object, '', 'type', 'internal', 'rowid', 0, 'maxwidth150onsmartphone'); -print ''; -print '
'; +// +//print '
'; +//$titleassigntask = $langs->transnoentities("AssignTaskToMe"); +//if ($usertoprocess->id != $user->id) { +// $titleassigntask = $langs->transnoentities("AssignTaskToUser", $usertoprocess->getFullName($langs)); +//} +//print '
'; +//print img_picto('', 'projecttask', 'class="pictofixedwidth"'); +//$formproject->selectTasks($socid ? $socid : -1, $taskid, 'taskid', 32, 0, '-- '.$langs->trans("ChooseANotYetAssignedTask").' --', 1, 0, 0, '', '', 'all', $usertoprocess); +//print '
'; +//print ' '; +//print $formcompany->selectTypeContact($object, '', 'type', 'internal', 'rowid', 0, 'maxwidth150onsmartphone'); +//print ''; +//print '
'; print '
'; - +print $langs->trans('ShowOnlyFavoriteTasks'); +print 'global->DOLIPROJECT_SHOW_ONLY_FAVORITE_TASKS ? ' checked' : '').' >'; $moreforfilter = ''; @@ -538,24 +619,22 @@ $moreforfilter .= ''; } -if (!empty($moreforfilter)) { - print '
'; - print $moreforfilter; - $parameters = array(); - $reshook = $hookmanager->executeHooks('printFieldPreListTitle', $parameters); // Note that $action and $object may have been modified by hook - print $hookmanager->resPrint; - print '
'; -} +//if (!empty($moreforfilter)) { +// print '
'; +// print $moreforfilter; +// $parameters = array(); +// $reshook = $hookmanager->executeHooks('printFieldPreListTitle', $parameters); // Note that $action and $object may have been modified by hook +// print $hookmanager->resPrint; +// print '
'; +//} $varpage = empty($contextpage) ? $_SERVER["PHP_SELF"] : $contextpage; $selectedfields = $form->multiSelectArrayWithCheckbox('selectedfields', $arrayfields, $varpage); // This also change content of $arrayfields // This must be after the $selectedfields -$addcolspan = 0; -if (!empty($arrayfields['t.planned_workload']['checked'])) { +$addcolspan = 1; +if (!empty($arrayfields['timeconsumed']['checked'])) { $addcolspan++; -} -if (!empty($arrayfields['t.progress']['checked'])) { $addcolspan++; } foreach ($arrayfields as $key => $val) { @@ -580,19 +659,15 @@ $extrafieldsobjectkey = 'projet_task'; $extrafieldsobjectprefix = 'efpt.'; include DOL_DOCUMENT_ROOT.'/core/tpl/extrafields_list_search_input.tpl.php'; -if (!empty($arrayfields['t.planned_workload']['checked'])) { - print ''; -} -if (!empty($arrayfields['t.progress']['checked'])) { - print ''; -} +print ''; +print ''; if (!empty($arrayfields['timeconsumed']['checked'])) { print ''; print ''; } print ''; -print ''; -print ''; +//print ''; +//print ''; // Action column print ''; $searchpicto = $form->showFilterAndCheckAddButtons(0); @@ -612,12 +687,12 @@ $extrafieldsobjectkey = 'projet_task'; $extrafieldsobjectprefix = 'efpt.'; include DOL_DOCUMENT_ROOT.'/core/tpl/extrafields_list_search_title.tpl.php'; -if (!empty($arrayfields['t.planned_workload']['checked'])) { - print ''.$langs->trans("PlannedWorkload").''; -} -if (!empty($arrayfields['t.progress']['checked'])) { - print 'trans("ProgressDeclared")).'">'.$langs->trans("ProgressDeclared").''; -} +//if (!empty($arrayfields['t.planned_workload']['checked'])) { +// print ''.$langs->trans("PlannedWorkload").''; +//} +//if (!empty($arrayfields['t.progress']['checked'])) { +// print 'trans("ProgressDeclared")).'">'.$langs->trans("ProgressDeclared").''; +//} if (!empty($arrayfields['timeconsumed']['checked'])) { print ''.$langs->trans("TimeSpent").'
'; print ''; @@ -678,7 +753,13 @@ $cssonholiday .= 'onholidayafternoon '; } -print ''.$langs->trans("Duration").''; +print ''.$langs->trans("Duration"); +print '
' . $langs->trans('DayWorkTime') . ' : ' . $worked_hours . ':' . $worked_minutes; +print '
' . $langs->trans('ConsumedTime') . ' : ' . $consumed_hours . ':' . $consumed_minutes ; +print '
' . $langs->trans('NonConsumedTime') . ' : ' . $non_consumed_hours . ':' . $non_consumed_minutes; +print ''; +print ''; +print ''; print ''.$langs->trans("Note").''; //print ''; print_liste_field_titre($selectedfields, $_SERVER["PHP_SELF"], "", '', '', '', $sortfield, $sortorder, 'center maxwidthsearch '); @@ -689,7 +770,7 @@ if ($conf->use_javascript_ajax) { print ''; - print ''; + print ''; print $langs->trans("Total"); print ''; if (!empty($arrayfields['timeconsumed']['checked'])) { @@ -702,8 +783,8 @@ print '
 
'; - print ''; - print ''; +// print ''; +// print ''; print ''; } @@ -715,7 +796,7 @@ $j = 0; $level = 0; - $totalforvisibletasks = projectLinesPerDay($j, 0, $usertoprocess, $tasksarray, $level, $projectsrole, $tasksrole, $mine, $restrictviewformytask, $daytoparse, $isavailable, 0, $arrayfields, $extrafields); + $totalforvisibletasks = doliprojectLinesPerDay($j, 0, $usertoprocess, $tasksarray, $level, $projectsrole, $tasksrole, $mine, $restrictviewformytask, $daytoparse, $isavailable, 0, $arrayfields, $extrafields); //var_dump($totalforvisibletasks); // Show total for all other tasks @@ -751,7 +832,7 @@ // There is a diff between total shown on screen and total spent by user, so we add a line with all other cumulated time of user if ($isdiff) { print ''; - print ''; + print ''; print $langs->trans("OtherFilteredTasks"); print ''; if (!empty($arrayfields['timeconsumed']['checked'])) { @@ -777,7 +858,7 @@ if ($conf->use_javascript_ajax) { print ''; - print ''; + print ''; print $langs->trans("Total"); print ''; if (!empty($arrayfields['timeconsumed']['checked'])) { @@ -790,9 +871,8 @@ print '
 
'; - print ' - - '; +// print ''; + print ''; } } else { print ''.$langs->trans("NoAssignedTasks").''; diff --git a/view/timespent_list.php b/view/timespent_list.php new file mode 100644 index 0000000..84c576d --- /dev/null +++ b/view/timespent_list.php @@ -0,0 +1,578 @@ + + * Copyright (C) 2022 Florian HENRY + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/** + * \file doliprojecttimespend.php + * \ingroup doliproject + * \brief List page for book + */ + +//if (! defined('NOREQUIREDB')) define('NOREQUIREDB', '1'); // Do not create database handler $db +//if (! defined('NOREQUIREUSER')) define('NOREQUIREUSER', '1'); // Do not load object $user +//if (! defined('NOREQUIRESOC')) define('NOREQUIRESOC', '1'); // Do not load object $mysoc +//if (! defined('NOREQUIRETRAN')) define('NOREQUIRETRAN', '1'); // Do not load object $langs +//if (! defined('NOSCANGETFORINJECTION')) define('NOSCANGETFORINJECTION', '1'); // Do not check injection attack on GET parameters +//if (! defined('NOSCANPOSTFORINJECTION')) define('NOSCANPOSTFORINJECTION', '1'); // Do not check injection attack on POST parameters +//if (! defined('NOCSRFCHECK')) define('NOCSRFCHECK', '1'); // Do not check CSRF attack (test on referer + on token if option MAIN_SECURITY_CSRF_WITH_TOKEN is on). +//if (! defined('NOTOKENRENEWAL')) define('NOTOKENRENEWAL', '1'); // Do not roll the Anti CSRF token (used if MAIN_SECURITY_CSRF_WITH_TOKEN is on) +//if (! defined('NOSTYLECHECK')) define('NOSTYLECHECK', '1'); // Do not check style html tag into posted data +//if (! defined('NOREQUIREMENU')) define('NOREQUIREMENU', '1'); // If there is no need to load and show top and left menu +//if (! defined('NOREQUIREHTML')) define('NOREQUIREHTML', '1'); // If we don't need to load the html.form.class.php +//if (! defined('NOREQUIREAJAX')) define('NOREQUIREAJAX', '1'); // Do not load ajax.lib.php library +//if (! defined("NOLOGIN")) define("NOLOGIN", '1'); // If this page is public (can be called outside logged session). This include the NOIPCHECK too. +//if (! defined('NOIPCHECK')) define('NOIPCHECK', '1'); // Do not check IP defined into conf $dolibarr_main_restrict_ip +//if (! defined("MAIN_LANG_DEFAULT")) define('MAIN_LANG_DEFAULT', 'auto'); // Force lang to a particular value +//if (! defined("MAIN_AUTHENTICATION_MODE")) define('MAIN_AUTHENTICATION_MODE', 'aloginmodule'); // Force authentication handler +//if (! defined("NOREDIRECTBYMAINTOLOGIN")) define('NOREDIRECTBYMAINTOLOGIN', 1); // The main.inc.php does not make a redirect if not logged, instead show simple error message +//if (! defined("FORCECSP")) define('FORCECSP', 'none'); // Disable all Content Security Policies +//if (! defined('CSRFCHECK_WITH_TOKEN')) define('CSRFCHECK_WITH_TOKEN', '1'); // Force use of CSRF protection with tokens even for GET +//if (! defined('NOBROWSERNOTIF')) define('NOBROWSERNOTIF', '1'); // Disable browser notification +//if (! defined('NOSESSION')) define('NOSESSION', '1'); // On CLI mode, no need to use web sessions + +// Load Dolibarr environment +$res = 0; +// Try main.inc.php into web root known defined into CONTEXT_DOCUMENT_ROOT (not always defined) +if (!$res && !empty($_SERVER["CONTEXT_DOCUMENT_ROOT"])) { + $res = @include $_SERVER["CONTEXT_DOCUMENT_ROOT"] . "/main.inc.php"; +} +// Try main.inc.php into web root detected using web root calculated from SCRIPT_FILENAME +$tmp = empty($_SERVER['SCRIPT_FILENAME']) ? '' : $_SERVER['SCRIPT_FILENAME']; +$tmp2 = realpath(__FILE__); +$i = strlen($tmp) - 1; +$j = strlen($tmp2) - 1; +while ($i > 0 && $j > 0 && isset($tmp[$i]) && isset($tmp2[$j]) && $tmp[$i] == $tmp2[$j]) { + $i--; + $j--; +} +if (!$res && $i > 0 && file_exists(substr($tmp, 0, ($i + 1)) . "/main.inc.php")) { + $res = @include substr($tmp, 0, ($i + 1)) . "/main.inc.php"; +} +if (!$res && $i > 0 && file_exists(dirname(substr($tmp, 0, ($i + 1))) . "/main.inc.php")) { + $res = @include dirname(substr($tmp, 0, ($i + 1))) . "/main.inc.php"; +} +// Try main.inc.php using relative path +if (!$res && file_exists("../main.inc.php")) { + $res = @include "../main.inc.php"; +} +if (!$res && file_exists("../../main.inc.php")) { + $res = @include "../../main.inc.php"; +} +if (!$res && file_exists("../../../main.inc.php")) { + $res = @include "../../../main.inc.php"; +} +if (!$res && file_exists("../../../../main.inc.php")) { + $res = @include "../../../../main.inc.php"; +} +if (!$res) { + die("Include of main fails"); +} + +require_once DOL_DOCUMENT_ROOT . '/core/class/html.formcompany.class.php'; +require_once DOL_DOCUMENT_ROOT . '/core/lib/date.lib.php'; +require_once DOL_DOCUMENT_ROOT . '/core/lib/company.lib.php'; +require_once DOL_DOCUMENT_ROOT . '/projet/class/project.class.php'; +require_once DOL_DOCUMENT_ROOT . '/projet/class/task.class.php'; + +// Load translation files required by the page +$langs->loadLangs(array("projects", "other")); + + +$action = GETPOST('action', 'aZ09') ? GETPOST('action', 'aZ09') : 'view'; // The action 'add', 'create', 'edit', 'update', 'view', ... +$massaction = GETPOST('massaction', 'alpha'); // The bulk action (combo box choice into lists) +$show_files = GETPOST('show_files', 'int'); // Show files area generated by bulk actions ? +$confirm = GETPOST('confirm', 'alpha'); // Result of a confirmation +$cancel = GETPOST('cancel', 'alpha'); // We click on a Cancel button +$toselect = GETPOST('toselect', 'array'); // Array of ids of elements selected into a list +$contextpage = GETPOST('contextpage', 'aZ') ? GETPOST('contextpage', 'aZ') : 'booklist'; // To manage different context of search +$backtopage = GETPOST('backtopage', 'alpha'); // Go back to a dedicated page +$optioncss = GETPOST('optioncss', 'aZ'); // Option for the css output (always '' except when 'print') + +$id = GETPOST('id', 'int'); + +// Load variable for pagination +$limit = GETPOST('limit', 'int') ? GETPOST('limit', 'int') : $conf->liste_limit; +$sortfield = GETPOST('sortfield', 'aZ09comma'); +$sortorder = GETPOST('sortorder', 'aZ09comma'); +$page = GETPOSTISSET('pageplusone') ? (GETPOST('pageplusone') - 1) : GETPOST("page", 'int'); +if (empty($page) || $page < 0 || GETPOST('button_search', 'alpha') || GETPOST('button_removefilter', 'alpha')) { + // If $page is not defined, or '' or -1 or if we click on clear filters + $page = 0; +} +$offset = $limit * $page; +$pageprev = $page - 1; +$pagenext = $page + 1; + +$arrayfields = array( + 'rowid' => array('tablealias' => 's.', 'fieldalias' => 'socid', 'label' => "Customer", 'checked' => 1, + 'position' => 5, + 'type' => 'Societe:societe/class/societe.class.php:1:status=1 AND entity IN (__SHARED_ENTITIES__)', + 'visible' => 1), + 'ref' => array('tablealias' => 'p.', 'fieldalias' => 'projectref', 'label' => "Project", 'checked' => 1, 'position' => 10, + 'type' => 'Project:projet/class/project.class.php:1', 'visible' => 1), + 'p.rowid' => array('fieldalias' => 'projectid', 'label' => "ProjectId", 'visible' => 0), + 'title' => array('tablealias' => 'p.', 'label' => "Title", 'checked' => 1, 'position' => 11, + 'type' => 'text', 'visible' => 1), + 'pt.rowid' => array('fieldalias' => 'taskid', 'label' => "Task", 'checked' => 1, + 'position' => 12, + 'type' => 'Task:projet/class/task.class.php:1', 'visible' => 1), + 'label' => array('tablealias' => 'pt.', 'label' => "Label", 'checked' => 1, 'position' => 13, + 'type' => 'text', 'visible' => 1), + 'task_date' => array('tablealias' => 'ptt.', 'label' => "Date", 'checked' => 1, 'position' => 13, + 'type' => 'datetime', 'visible' => 1), + 'fk_user' => array('tablealias' => 'ptt.', 'fieldalias' => 'fk_user', 'label' => "User", 'checked' => 1, 'position' => 14, + 'type' => 'User:user/class/user.class.php', 'visible' => 1), + 'task_duration' => array('tablealias' => 'ptt.', 'label' => "Duration", 'checked' => 1, 'position' => 15, + 'type' => 'duration', 'visible' => 1,'isameasure'=>1), + 'note' => array('tablealias' => 'ptt.', 'label' => "Note", 'checked' => 1, 'position' => 16, + 'type' => 'text', 'visible' => 1), +); + + +// Default sort order (if not yet defined by previous GETPOST) +if (!$sortfield) { + $sortfield = 'ptt.task_date'; // Set here default search field. By default 1st field in definition. +} +if (!$sortorder) { + $sortorder = "ASC"; +} + +$arrayfields = dol_sort_array($arrayfields, 'position'); + +// Initialize array of search criterias +$search_all = GETPOST('search_all', 'alphanohtml'); +$search = array(); +foreach ($arrayfields as $key => $val) { + $keysearch = (array_key_exists('fieldalias', $val) ? $val['fieldalias'] : $key); + if (GETPOST('search_' . $keysearch, 'alpha') !== '' && GETPOST('search_' . $keysearch, 'alpha') !== '-1') { + $search[$keysearch] = GETPOST('search_' . $keysearch, 'alpha'); + } + if (preg_match('/^(date|timestamp|datetime)/', $val['type'])) { + $search[$keysearch . '_dtstart'] = dol_mktime(0, 0, 0, GETPOST('search_' . $keysearch . '_dtstartmonth', 'int'), GETPOST('search_' . $keysearch . '_dtstartday', 'int'), GETPOST('search_' . $keysearch . '_dtstartyear', 'int')); + $search[$keysearch . '_dtend'] = dol_mktime(23, 59, 59, GETPOST('search_' . $keysearch . '_dtendmonth', 'int'), GETPOST('search_' . $keysearch . '_dtendday', 'int'), GETPOST('search_' . $keysearch . '_dtendyear', 'int')); + } +} + +// Security check (enable the most restrictive one) +if ($user->socid > 0) accessforbidden(); + +if (empty($conf->doliproject->enabled)) accessforbidden('Moule not enabled'); + +/* + * Actions + */ + +if (GETPOST('cancel', 'alpha')) { + $action = 'list'; + $massaction = ''; +} +if (!GETPOST('confirmmassaction', 'alpha') && $massaction != 'presend' && $massaction != 'confirm_presend') { + $massaction = ''; +} + +$parameters = array(); +$reshook = $hookmanager->executeHooks('doActions', $parameters, $object, $action); // Note that $action and $object may have been modified by some hooks +if ($reshook < 0) { + setEventMessages($hookmanager->error, $hookmanager->errors, 'errors'); +} + +if (empty($reshook)) { + + // Purge search criteria + if (GETPOST('button_removefilter_x', 'alpha') || GETPOST('button_removefilter.x', 'alpha') || GETPOST('button_removefilter', 'alpha')) { // All tests are required to be compatible with all browsers + foreach ($arrayfields as $key => $val) { + $keysearch = (array_key_exists('fieldalias', $val) ? $val['fieldalias'] : $key); + $search[$keysearch] = ''; + if (preg_match('/^(date|timestamp|datetime)/', $val['type'])) { + $search[$keysearch . '_dtstart'] = ''; + $search[$keysearch . '_dtend'] = ''; + } + } + $toselect = array(); + $search_array_options = array(); + } + if (GETPOST('button_removefilter_x', 'alpha') || GETPOST('button_removefilter.x', 'alpha') || GETPOST('button_removefilter', 'alpha') + || GETPOST('button_search_x', 'alpha') || GETPOST('button_search.x', 'alpha') || GETPOST('button_search', 'alpha')) { + $massaction = ''; // Protection to avoid mass action if we force a new search during a mass action confirmation + } +} + + +/* + * View + */ + +$form = new Form($db); + +$now = dol_now(); + +//$help_url="EN:Module_Book|FR:Module_Book_FR|ES:Módulo_Book"; +$help_url = ''; +$title = $langs->trans('ListOf', $langs->transnoentitiesnoconv("NewTimeSpent")); +$morejs = array(); +$morecss = array(); + + +// Build and execute select +// -------------------------------------------------------------------- +$sql = 'SELECT '; +$sql .= 'DISTINCT '; +$sqlfields = array(); +foreach ($arrayfields as $field => $data) { + $sqlfields[] = $data['tablealias'] . $field . ((array_key_exists('fieldalias', $data) ? ' as ' . $data['fieldalias'] : '')); +} +$sql .= implode(',', $sqlfields); + + +// Add fields from hooks +$parameters = array(); +$reshook = $hookmanager->executeHooks('printFieldListSelect', $parameters, $object); // Note that $action and $object may have been modified by hook +$sql .= preg_replace('/^,/', '', $hookmanager->resPrint); +$sql = preg_replace('/,\s*$/', '', $sql); +$sql .= ' FROM ' . MAIN_DB_PREFIX . 'projet as p'; +$sql .= ' LEFT JOIN ' . MAIN_DB_PREFIX . 'projet_extrafields as extra ON p.rowid = extra.fk_object'; +$sql .= ' LEFT JOIN ' . MAIN_DB_PREFIX . 'c_lead_status as cls ON p.fk_opp_status = cls.rowid'; +$sql .= ' LEFT JOIN ' . MAIN_DB_PREFIX . 'projet_task as pt ON p.rowid = pt.fk_projet'; +$sql .= ' LEFT JOIN ' . MAIN_DB_PREFIX . 'projet_task_extrafields as ef ON pt.rowid = ef.fk_object'; +$sql .= ' LEFT JOIN ' . MAIN_DB_PREFIX . 'projet_task_time as ptt ON pt.rowid = ptt.fk_task'; +$sql .= ' LEFT JOIN ' . MAIN_DB_PREFIX . 'societe as s ON p.fk_soc = s.rowid'; +$sql .= ' LEFT JOIN ' . MAIN_DB_PREFIX . 'facture as f ON ptt.invoice_id = f.rowid'; + +/*if (isset($extrafields->attributes[$object->table_element]['label']) && is_array($extrafields->attributes[$object->table_element]['label']) && count($extrafields->attributes[$object->table_element]['label'])) { + $sql .= " LEFT JOIN ".MAIN_DB_PREFIX.$object->table_element."_extrafields as ef on (t.rowid = ef.fk_object)"; +}*/ +// Add table from hooks +$parameters = array(); +$reshook = $hookmanager->executeHooks('printFieldListFrom', $parameters, $object); // Note that $action and $object may have been modified by hook +$sql .= $hookmanager->resPrint; +if ($object->ismultientitymanaged == 1) { + $sql .= " WHERE p.entity IN (" . getEntity($object->element) . ")"; +} else { + $sql .= " WHERE 1 = 1"; +} +$sql .= ' AND ptt.task_duration IS NOT NULL'; + +foreach ($search as $key => $val) { + if (preg_match('/(_dtstart|_dtend)$/', $key) && $search[$key] != '') { + if (preg_match('/_dtstart$/', $key)) { + $sql .= " AND ptt.task_date >= '" . $db->idate($search[$key]) . "'"; + } + if (preg_match('/_dtend$/', $key)) { + $sql .= " AND ptt.task_date <= '" . $db->idate($search[$key]) . "'"; + } + } + if ($key == 'socid' && !empty($val)) $sql .= ' AND s.rowid=' . (int)$val; + if ($key == 'projectref' && !empty($val)) $sql .= ' AND p.rowid=' . (int)$val; + if ($key == 'taskid' && !empty($val)) $sql .= ' AND pt.rowid=' . (int)$val; + if ($key == 'fk_user' && !empty($val)) $sql .= ' AND ptt.fk_user=' . (int)$val; + +} + +// Add where from hooks +$parameters = array(); +$reshook = $hookmanager->executeHooks('printFieldListWhere', $parameters, $object); // Note that $action and $object may have been modified by hook +$sql .= $hookmanager->resPrint; + + +// Count total nb of records +$nbtotalofrecords = ''; +if (empty($conf->global->MAIN_DISABLE_FULL_SCANLIST)) { + $sqlforcount = preg_replace('/^SELECT[a-z0-9\._\s\(\),]+FROM/i', 'SELECT COUNT(*) as nbtotalofrecords FROM', $sql); + $resql = $db->query($sqlforcount); + $objforcount = $db->fetch_object($resql); + $nbtotalofrecords = $objforcount->nbtotalofrecords; + if (($page * $limit) > $nbtotalofrecords) { // if total of record found is smaller than page * limit, goto and load page 0 + $page = 0; + $offset = 0; + } + $db->free($resql); +} + +// Complete request and execute it with limit +$sql .= $db->order($sortfield, $sortorder); +if ($limit) { + $sql .= $db->plimit($limit + 1, $offset); +} + +$resql = $db->query($sql); +if (!$resql) { + dol_print_error($db); + exit; +} + +$num = $db->num_rows($resql); + + +// Direct jump if only one record found +if ($num == 1 && !empty($conf->global->MAIN_SEARCH_DIRECT_OPEN_IF_ONLY_ONE) && $search_all && !$page) { + $obj = $db->fetch_object($resql); + $id = $obj->rowid; + header("Location: " . dol_buildpath('/bibliotheque/book_card.php', 1) . '?id=' . $id); + exit; +} + +// Output page +// -------------------------------------------------------------------- + +llxHeader('', $title, $help_url, '', 0, 0, $morejs, $morecss, '', ''); + +$arrayofselected = is_array($toselect) ? $toselect : array(); + +$param = ''; +if (!empty($contextpage) && $contextpage != $_SERVER["PHP_SELF"]) { + $param .= '&contextpage=' . urlencode($contextpage); +} +if ($limit > 0 && $limit != $conf->liste_limit) { + $param .= '&limit=' . urlencode($limit); +} +foreach ($search as $key => $val) { + if (is_array($search[$key]) && count($search[$key])) { + foreach ($search[$key] as $skey) { + if ($skey != '') { + $param .= '&search_' . $key . '[]=' . urlencode($skey); + } + } + } elseif ($search[$key] != '') { + $param .= '&search_' . $key . '=' . urlencode($search[$key]); + } +} +if ($optioncss != '') { + $param .= '&optioncss=' . urlencode($optioncss); +} +// Add $param from extra fields +include DOL_DOCUMENT_ROOT . '/core/tpl/extrafields_list_search_param.tpl.php'; +// Add $param from hooks +$parameters = array(); +$reshook = $hookmanager->executeHooks('printFieldListSearchParam', $parameters, $object); // Note that $action and $object may have been modified by hook +$param .= $hookmanager->resPrint; + +//print $sql; + +print '
' . "\n"; +if ($optioncss != '') { + print ''; +} +print ''; +print ''; +print ''; +print ''; +print ''; +print ''; +print ''; + +print_barre_liste($title, $page, $_SERVER["PHP_SELF"], $param, $sortfield, $sortorder, '', $num, $nbtotalofrecords, 'object_' . $object->picto, 0, 0, '', $limit, 0, 0, 1); + +$parameters = array(); +$reshook = $hookmanager->executeHooks('printFieldPreListTitle', $parameters, $object); // Note that $action and $object may have been modified by hook +if (empty($reshook)) { + $moreforfilter .= $hookmanager->resPrint; +} else { + $moreforfilter = $hookmanager->resPrint; +} + +if (!empty($moreforfilter)) { + print '
'; + print $moreforfilter; + print '
'; +} + +$varpage = empty($contextpage) ? $_SERVER["PHP_SELF"] : $contextpage; + + +print '
'; // You can use div-table-responsive-no-min if you dont need reserved height for your table +print '' . "\n"; + + +// -------------------------------------------------------------------- +print ''; +foreach ($arrayfields as $key => $val) { + $keysearch = (array_key_exists('fieldalias', $val) ? $val['fieldalias'] : $key); + $cssforfield = (empty($val['csslist']) ? (empty($val['css']) ? '' : $val['css']) : $val['csslist']); + if (!empty($arrayfields[$key]['checked'])) { + print ''; + } +} +// Extra fields +include DOL_DOCUMENT_ROOT . '/core/tpl/extrafields_list_search_input.tpl.php'; + +// Fields from hook +$parameters = array('arrayfields' => $arrayfields); +$reshook = $hookmanager->executeHooks('printFieldListOption', $parameters, $object); // Note that $action and $object may have been modified by hook +print $hookmanager->resPrint; +// Action column +print ''; +print '' . "\n"; + + +// Fields title label +// -------------------------------------------------------------------- +print ''; +foreach ($arrayfields as $key => $val) { + $cssforfield = (empty($val['csslist']) ? (empty($val['css']) ? '' : $val['css']) : $val['csslist']); + if ($key == 'status') { + $cssforfield .= ($cssforfield ? ' ' : '') . 'center'; + } elseif (in_array($val['type'], array('date', 'datetime', 'timestamp'))) { + $cssforfield .= ($cssforfield ? ' ' : '') . 'center'; + } elseif (in_array($val['type'], array('timestamp'))) { + $cssforfield .= ($cssforfield ? ' ' : '') . 'nowrap'; + } elseif (in_array($val['type'], array('double(24,8)', 'double(6,3)', 'integer', 'real', + 'price')) && $val['label'] != 'TechnicalID' && empty($val['arrayofkeyval'])) { + $cssforfield .= ($cssforfield ? ' ' : '') . 'right'; + } + if (!empty($arrayfields[$key]['checked'])) { + print getTitleFieldOfList($arrayfields[$key]['label'], 0, $_SERVER['PHP_SELF'], $val['tablealias'] . $key, '', $param, ($cssforfield ? 'class="' . $cssforfield . '"' : ''), $sortfield, $sortorder, ($cssforfield ? $cssforfield . ' ' : '')) . "\n"; + } +} +// Extra fields +include DOL_DOCUMENT_ROOT . '/core/tpl/extrafields_list_search_title.tpl.php'; +// Hook fields +$parameters = array('arrayfields' => $arrayfields, 'param' => $param, 'sortfield' => $sortfield, + 'sortorder' => $sortorder); +$reshook = $hookmanager->executeHooks('printFieldListTitle', $parameters, $object); // Note that $action and $object may have been modified by hook +print $hookmanager->resPrint; +// Action column +print getTitleFieldOfList('', 0, $_SERVER["PHP_SELF"], '', '', '', '', $sortfield, $sortorder, 'center maxwidthsearch ') . "\n"; +print '' . "\n"; + + +//print $sql; + +// Loop on record +// -------------------------------------------------------------------- +$i = 0; +$totalarray = array(); +$totalarray['nbfield'] = 0; +$totalarray['type'][8]='duration'; +while ($i < ($limit ? min($num, $limit) : $num)) { + $obj = $db->fetch_object($resql); + if (empty($obj)) { + break; // Should not happen + } + + // Show here line of result + print ''; + foreach ($arrayfields as $key => $val) { + if ($val['visible'] == 1) { + $cssforfield = (empty($val['csslist']) ? (empty($val['css']) ? '' : $val['css']) : $val['csslist']); + if (in_array($val['type'], array('date', 'datetime', 'timestamp'))) { + $cssforfield .= ($cssforfield ? ' ' : '') . 'center'; + } + //if (in_array($key, array('fk_soc', 'fk_user', 'fk_warehouse'))) $cssforfield = 'tdoverflowmax100'; + if (!empty($arrayfields[$key]['checked'])) { + print ''; + if (in_array($val['fieldalias'], array('socid', 'projectref', 'fk_user', 'taskid'))) { + $InfoFieldList = explode(':', $val['type']); + $classname = $InfoFieldList[0]; + $classpath = $InfoFieldList[1]; + if (!empty($classpath)) { + dol_include_once($InfoFieldList[1]); + if ($classname && class_exists($classname)) { + $object = new $classname($db); + if ($val['fieldalias'] == 'projectref') { + $object->fetch($obj->projectid); + print $object->getNomUrl(1, '', 1); + } else { + $object->fetch($obj->{$val['fieldalias']}); + print $object->getNomUrl(1); + } + } + } + } elseif ($key == 'task_date') { + print dol_print_date($obj->task_date, 'day'); + } elseif ($key == 'title' || $key === 'label' || $key == 'note') { + print $obj->{$key}; + } elseif ($key == 'task_duration') { + print convertSecondToTime($obj->task_duration, 'allhourmin'); + } + print ''; + if (!$i) { + $totalarray['nbfield']++; + } + if (!empty($val['isameasure']) && $val['isameasure'] == 1) { + if (!$i) { + $totalarray['pos'][$totalarray['nbfield']] = $key; + } + if (!isset($totalarray['val'])) { + $totalarray['val'] = array(); + } + if (!isset($totalarray['val'][$key])) { + $totalarray['val'][$key] = 0; + } + $totalarray['val'][$key] += convertSecondToTime($obj->{$key}, 'allhourmin'); + } + } + } + } + // Fields from hook + $parameters = array('arrayfields' => $arrayfields, 'object' => $object, 'obj' => $obj, 'i' => $i, + 'totalarray' => &$totalarray); + $reshook = $hookmanager->executeHooks('printFieldListValue', $parameters, $object); // Note that $action and $object may have been modified by hook + print $hookmanager->resPrint; + if (!$i) { + $totalarray['nbfield']++; + } + print ''; + print '' . "\n"; + + $i++; +} + + +// Show total line +include DOL_DOCUMENT_ROOT . '/core/tpl/list_print_total.tpl.php'; + +// If no record found +if ($num == 0) { + $colspan = 1; + foreach ($arrayfields as $key => $val) { + if (!empty($val['checked'])) { + $colspan++; + } + } + print ''; +} + + +$db->free($resql); + +$parameters = array('arrayfields' => $arrayfields, 'sql' => $sql); +$reshook = $hookmanager->executeHooks('printFieldListFooter', $parameters, $object); // Note that $action and $object may have been modified by hook +print $hookmanager->resPrint; + +print '
'; + if (in_array($val['fieldalias'], array('socid','projectref', 'fk_user','taskid'))) { + print $form->selectForForms($val['type'], 'search_' . $keysearch, $search[$keysearch], 1, '', '', $morecss); + } elseif (preg_match('/^(date|timestamp|datetime)/', $val['type'])) { + print '
'; + print $form->selectDate($search[$key . '_dtstart'], "search_" . $key . "_dtstart", 0, 0, 1, '', 1, 0, 0, '', '', '', '', 1, '', $langs->trans('From')); + print '
'; + print '
'; + print $form->selectDate($search[$key . '_dtend'], "search_" . $key . "_dtend", 0, 0, 1, '', 1, 0, 0, '', '', '', '', 1, '', $langs->trans('to')); + print '
'; + } + print '
'; +$searchpicto = $form->showFilterButtons(); +print $searchpicto; +print '
' . $langs->trans("NoRecordFound") . '
' . "\n"; +print '
' . "\n"; + +print '
' . "\n"; + +// End of page +llxFooter(); +$db->close(); + diff --git a/view/timespent_month.php b/view/timespent_month.php new file mode 100644 index 0000000..c6cf9ba --- /dev/null +++ b/view/timespent_month.php @@ -0,0 +1,1051 @@ + + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/** + * \file view/timespent_month.php + * \ingroup doliprojet + * \brief List timespent of tasks per day on each month + */ + +// Load Dolibarr environment +$res = 0; +// Try main.inc.php into web root known defined into CONTEXT_DOCUMENT_ROOT (not always defined) +if ( ! $res && ! empty($_SERVER["CONTEXT_DOCUMENT_ROOT"])) $res = @include $_SERVER["CONTEXT_DOCUMENT_ROOT"] . "/main.inc.php"; +// Try main.inc.php into web root detected using web root calculated from SCRIPT_FILENAME +$tmp = empty($_SERVER['SCRIPT_FILENAME']) ? '' : $_SERVER['SCRIPT_FILENAME']; $tmp2 = realpath(__FILE__); $i = strlen($tmp) - 1; $j = strlen($tmp2) - 1; +while ($i > 0 && $j > 0 && isset($tmp[$i]) && isset($tmp2[$j]) && $tmp[$i] == $tmp2[$j]) { $i--; $j--; } +if ( ! $res && $i > 0 && file_exists(substr($tmp, 0, ($i + 1)) . "/main.inc.php")) $res = @include substr($tmp, 0, ($i + 1)) . "/main.inc.php"; +if ( ! $res && $i > 0 && file_exists(dirname(substr($tmp, 0, ($i + 1))) . "/main.inc.php")) $res = @include dirname(substr($tmp, 0, ($i + 1))) . "/main.inc.php"; +// Try main.inc.php using relative path +if ( ! $res && file_exists("../../main.inc.php")) $res = @include "../../main.inc.php"; +if ( ! $res && file_exists("../../../main.inc.php")) $res = @include "../../../main.inc.php"; +if ( ! $res) die("Include of main fails"); + +// Libraries +require_once DOL_DOCUMENT_ROOT.'/projet/class/project.class.php'; +require_once DOL_DOCUMENT_ROOT.'/projet/class/task.class.php'; +require_once DOL_DOCUMENT_ROOT.'/core/lib/project.lib.php'; +require_once DOL_DOCUMENT_ROOT.'/core/lib/admin.lib.php'; +require_once DOL_DOCUMENT_ROOT.'/core/lib/date.lib.php'; +require_once DOL_DOCUMENT_ROOT.'/core/class/html.formother.class.php'; +require_once DOL_DOCUMENT_ROOT.'/core/class/html.formprojet.class.php'; +require_once DOL_DOCUMENT_ROOT.'/core/class/html.formcompany.class.php'; +require_once DOL_DOCUMENT_ROOT.'/holiday/class/holiday.class.php'; + +require_once DOL_DOCUMENT_ROOT.'/custom/doliproject/lib/doliproject_functions.lib.php'; +require_once DOL_DOCUMENT_ROOT.'/custom/doliproject/class/workinghours.class.php'; + +// Global variables definitions +global $conf, $db, $hookmanager, $langs, $user; + +// Load translation files required by the page +$langs->loadLangs(array('projects', 'users', 'companies')); + +// Get parameters +$action = GETPOST('action', 'aZ09'); +$mode = GETPOST("mode", 'alpha'); +$id = GETPOST('id', 'int'); +$taskid = GETPOST('taskid', 'int'); +$contextpage = GETPOST('contextpage', 'aZ') ?GETPOST('contextpage', 'aZ') : 'permonthcard'; + +$mine = 0; +if ($mode == 'mine') { + $mine = 1; +} + +$projectid = GETPOSTISSET("id") ? GETPOST("id", "int", 1) : GETPOST("projectid", "int"); + +$hookmanager->initHooks(array('timesheetpermonthcard')); + +// Security check +$socid = 0; +// For external user, no check is done on company because readability is managed by public status of project and assignement. +// if ($user->socid > 0) $socid=$user->socid; +$result = restrictedArea($user, 'projet', $projectid); + +$now = dol_now(); +$year = GETPOST('reyear', 'int') ?GETPOST('reyear', 'int') : (GETPOST("year", 'int') ?GETPOST("year", "int") : date("Y")); +$month = GETPOST('remonth', 'int') ?GETPOST('remonth', 'int') : (GETPOST("month", 'int') ?GETPOST("month", "int") : date("m")); +$day = GETPOST('reday', 'int') ?GETPOST('reday', 'int') : (GETPOST("day", 'int') ?GETPOST("day", "int") : date("d")); +$week = GETPOST("week", "int") ?GETPOST("week", "int") : date("W"); + +$day = (int) $day; +$dayInMonth = cal_days_in_month(CAL_GREGORIAN, $month, $year); + +//$search_categ = GETPOST("search_categ", 'alpha'); +$search_usertoprocessid = GETPOST('search_usertoprocessid', 'int'); +$search_task_ref = GETPOST('search_task_ref', 'alpha'); +$search_task_label = GETPOST('search_task_label', 'alpha'); +$search_project_ref = GETPOST('search_project_ref', 'alpha'); +$search_thirdparty = GETPOST('search_thirdparty', 'alpha'); +$search_declared_progress = GETPOST('search_declared_progress', 'alpha'); + +$sortfield = GETPOST('sortfield', 'aZ09comma'); +$sortorder = GETPOST('sortorder', 'aZ09comma'); + +// Define firstdaytoshow and lastdaytoshow (warning: lastdaytoshow is last second to show + 1) +$firstdaytoshow = dol_get_first_day($year, $month); +$firstdaytoshowgmt = dol_get_first_day($year, $month, true); +$lastdaytoshow = dol_get_last_day($year, $month); + +$prev = dol_get_prev_month($month, $year); +$prev_year = $prev['year']; +$prev_month = $prev['month']; + +$next = dol_get_next_month($month, $year); +$next_year = $next['year']; +$next_month = $next['month']; + +if (empty($search_usertoprocessid) || $search_usertoprocessid == $user->id) { + $usertoprocess = $user; + $search_usertoprocessid = $usertoprocess->id; +} elseif ($search_usertoprocessid > 0) { + $usertoprocess = new User($db); + $usertoprocess->fetch($search_usertoprocessid); + $search_usertoprocessid = $usertoprocess->id; +} else { + $usertoprocess = new User($db); +} + +$object = new Task($db); + +// Extra fields +$extrafields = new ExtraFields($db); + +// fetch optionals attributes and labels +$extrafields->fetch_name_optionals_label($object->table_element); + +// Definition of fields for list +$arrayfields = array(); +/*$arrayfields=array( + // Project + 'p.opp_amount'=>array('label'=>$langs->trans("OpportunityAmountShort"), 'checked'=>0, 'enabled'=>($conf->global->PROJECT_USE_OPPORTUNITIES?1:0), 'position'=>103), + 'p.fk_opp_status'=>array('label'=>$langs->trans("OpportunityStatusShort"), 'checked'=>0, 'enabled'=>($conf->global->PROJECT_USE_OPPORTUNITIES?1:0), 'position'=>104), + 'p.opp_percent'=>array('label'=>$langs->trans("OpportunityProbabilityShort"), 'checked'=>0, 'enabled'=>($conf->global->PROJECT_USE_OPPORTUNITIES?1:0), 'position'=>105), + 'p.budget_amount'=>array('label'=>$langs->trans("Budget"), 'checked'=>0, 'position'=>110), + 'p.usage_bill_time'=>array('label'=>$langs->trans("BillTimeShort"), 'checked'=>0, 'position'=>115), + );*/ +$arrayfields['timeconsumed'] = array('label'=>'TimeConsumed', 'checked'=>1, 'enabled'=>1, 'position'=>15); +/*foreach($object->fields as $key => $val) + { + // If $val['visible']==0, then we never show the field + if (! empty($val['visible'])) $arrayfields['t.'.$key]=array('label'=>$val['label'], 'checked'=>(($val['visible']<0)?0:1), 'enabled'=>$val['enabled'], 'position'=>$val['position']); + }*/ +// Definition of fields for list +// Extra fields +if (!empty($extrafields->attributes['projet_task']['label']) && is_array($extrafields->attributes['projet_task']['label']) && count($extrafields->attributes['projet_task']['label']) > 0) { + foreach ($extrafields->attributes['projet_task']['label'] as $key => $val) { + if (!empty($extrafields->attributes['projet_task']['list'][$key])) { + $arrayfields["efpt.".$key] = array('label'=>$extrafields->attributes['projet_task']['label'][$key], 'checked'=>(($extrafields->attributes['projet_task']['list'][$key] < 0) ? 0 : 1), 'position'=>$extrafields->attributes['projet_task']['pos'][$key], 'enabled'=>(abs((int) $extrafields->attributes['projet_task']['list'][$key]) != 3 && $extrafields->attributes['projet_task']['perms'][$key])); + } + } +} +$arrayfields = dol_sort_array($arrayfields, 'position'); + +$search_array_options = array(); +$search_array_options_project = $extrafields->getOptionalsFromPost('projet', '', 'search_'); +$search_array_options_task = $extrafields->getOptionalsFromPost('projet_task', '', 'search_task_'); + +/* + * Actions + */ + +$parameters = array('id' => $id, 'taskid' => $taskid, 'projectid' => $projectid); +$reshook = $hookmanager->executeHooks('doActions', $parameters, $object, $action); // Note that $action and $object may have been modified by some hooks +if ($reshook < 0) { + setEventMessages($hookmanager->error, $hookmanager->errors, 'errors'); +} +// Purge criteria +if (GETPOST('button_removefilter_x', 'alpha') || GETPOST('button_removefilter.x', 'alpha') || GETPOST('button_removefilter', 'alpha')) { // All tests are required to be compatible with all browsers + $action = ''; + //$search_categ = ''; + $search_usertoprocessid = $user->id; + $search_task_ref = ''; + $search_task_label = ''; + $search_project_ref = ''; + $search_thirdparty = ''; + $search_declared_progress = ''; + + $search_array_options_project = array(); + $search_array_options_task = array(); + + // We redefine $usertoprocess + $usertoprocess = $user; +} +if (GETPOST("button_search_x", 'alpha') || GETPOST("button_search.x", 'alpha') || GETPOST("button_search", 'alpha')) { + $action = ''; +} + +if (GETPOST('submitdateselect')) { + if (GETPOST('remonth', 'int') && GETPOST('reday', 'int') && GETPOST('reyear', 'int')) { + $daytoparse = dol_mktime(0, 0, 0, GETPOST('remonth', 'int'), GETPOST('reday', 'int'), GETPOST('reyear', 'int')); + } + + $action = ''; +} + +include DOL_DOCUMENT_ROOT.'/core/actions_changeselectedfields.inc.php'; + +if ($action == 'addtime' && $user->rights->projet->lire && GETPOST('assigntask') && GETPOST('formfilteraction') != 'listafterchangingselectedfields') { + $action = 'assigntask'; + + if ($taskid > 0) { + $result = $object->fetch($taskid, $ref); + if ($result < 0) { + $error++; + } + } else { + setEventMessages($langs->transnoentitiesnoconv("ErrorFieldRequired", $langs->transnoentitiesnoconv("Task")), '', 'errors'); + $error++; + } + if (!GETPOST('type')) { + setEventMessages($langs->transnoentitiesnoconv("ErrorFieldRequired", $langs->transnoentitiesnoconv("Type")), '', 'errors'); + $error++; + } + + if (!$error) { + $idfortaskuser = $usertoprocess->id; + $result = $object->add_contact($idfortaskuser, GETPOST("type"), 'internal'); + + if ($result >= 0 || $result == -2) { // Contact add ok or already contact of task + // Test if we are already contact of the project (should be rare but sometimes we can add as task contact without being contact of project, like when admin user has been removed from contact of project) + $sql = 'SELECT ec.rowid FROM '.MAIN_DB_PREFIX.'element_contact as ec, '.MAIN_DB_PREFIX.'c_type_contact as tc WHERE tc.rowid = ec.fk_c_type_contact'; + $sql .= ' AND ec.fk_socpeople = '.((int) $idfortaskuser)." AND ec.element_id = ".((int) $object->fk_project)." AND tc.element = 'project' AND source = 'internal'"; + $resql = $db->query($sql); + if ($resql) { + $obj = $db->fetch_object($resql); + if (!$obj) { // User is not already linked to project, so we will create link to first type + $project = new Project($db); + $project->fetch($object->fk_project); + // Get type + $listofprojcontact = $project->liste_type_contact('internal'); + + if (count($listofprojcontact)) { + $typeforprojectcontact = reset(array_keys($listofprojcontact)); + $result = $project->add_contact($idfortaskuser, $typeforprojectcontact, 'internal'); + } + } + } else { + dol_print_error($db); + } + } + } + + if ($result < 0) { + $error++; + if ($object->error == 'DB_ERROR_RECORD_ALREADY_EXISTS') { + $langs->load("errors"); + setEventMessages($langs->trans("ErrorTaskAlreadyAssigned"), null, 'warnings'); + } else { + setEventMessages($object->error, $object->errors, 'errors'); + } + } + + if (!$error) { + setEventMessages("TaskAssignedToEnterTime", null); + $taskid = 0; + } + + $action = ''; +} + +if ($action == 'addtime' && $user->rights->projet->lire && GETPOST('formfilteraction') != 'listafterchangingselectedfields') { + $timetoadd = $_POST['task']; + if (empty($timetoadd)) { + setEventMessages($langs->trans("ErrorTimeSpentIsEmpty"), null, 'errors'); + } else { + foreach ($timetoadd as $taskid => $value) { // Loop on each task + $updateoftaskdone = 0; + foreach ($value as $key => $val) { // Loop on each day + $amountoadd = $timetoadd[$taskid][$key]; + if (!empty($amountoadd)) { + $tmpduration = explode(':', $amountoadd); + $newduration = 0; + if (!empty($tmpduration[0])) { + $newduration += ($tmpduration[0] * 3600); + } + if (!empty($tmpduration[1])) { + $newduration += ($tmpduration[1] * 60); + } + if (!empty($tmpduration[2])) { + $newduration += ($tmpduration[2]); + } + + if ($newduration > 0) { + $object->fetch($taskid); + + if (GETPOSTISSET($taskid.'progress')) { + $object->progress = GETPOST($taskid.'progress', 'int'); + } else { + unset($object->progress); + } + + $object->timespent_duration = $newduration; + $object->timespent_fk_user = $usertoprocess->id; + $object->timespent_date = dol_time_plus_duree($firstdaytoshow, $key, 'd'); + $object->timespent_datehour = $object->timespent_date; + $object->timespent_note = $object->description; + + $result = $object->addTimeSpent($user); + if ($result < 0) { + setEventMessages($object->error, $object->errors, 'errors'); + $error++; + break; + } + + $updateoftaskdone++; + } + } + } + + if (!$updateoftaskdone) { // Check to update progress if no update were done on task. + $object->fetch($taskid); + //var_dump($object->progress);var_dump(GETPOST($taskid . 'progress', 'int')); exit; + if ($object->progress != GETPOST($taskid.'progress', 'int')) { + $object->progress = GETPOST($taskid.'progress', 'int'); + $result = $object->update($user); + if ($result < 0) { + setEventMessages($object->error, $object->errors, 'errors'); + $error++; + break; + } + } + } + } + + if (!$error) { + setEventMessages($langs->trans("RecordSaved"), null, 'mesgs'); + + $param = ''; + $param .= ($mode ? '&mode='.urlencode($mode) : ''); + $param .= ($projectid ? 'id='.urlencode($projectid) : ''); + $param .= ($search_usertoprocessid ? '&search_usertoprocessid='.urlencode($search_usertoprocessid) : ''); + $param .= ($day ? '&day='.urlencode($day) : '').($month ? '&month='.urlencode($month) : '').($year ? '&year='.urlencode($year) : ''); + $param .= ($search_project_ref ? '&search_project_ref='.urlencode($search_project_ref) : ''); + $param .= ($search_usertoprocessid > 0 ? '&search_usertoprocessid='.urlencode($search_usertoprocessid) : ''); + $param .= ($search_thirdparty ? '&search_thirdparty='.urlencode($search_thirdparty) : ''); + $param .= ($search_declared_progress ? '&search_declared_progress='.urlencode($search_declared_progress) : ''); + $param .= ($search_task_ref ? '&search_task_ref='.urlencode($search_task_ref) : ''); + $param .= ($search_task_label ? '&search_task_label='.urlencode($search_task_label) : ''); + + /*$search_array_options=$search_array_options_project; + $search_options_pattern='search_options_'; + include DOL_DOCUMENT_ROOT.'/core/tpl/extrafields_list_search_param.tpl.php'; + */ + + $search_array_options = $search_array_options_task; + $search_options_pattern = 'search_task_options_'; + include DOL_DOCUMENT_ROOT.'/core/tpl/extrafields_list_search_param.tpl.php'; + + // Redirect to avoid submit twice on back + header('Location: '.$_SERVER["PHP_SELF"].'?'.$param); + exit; + } + } +} + +if ($action == 'showOnlyFavoriteTasks') { + if ($conf->global->DOLIPROJECT_SHOW_ONLY_FAVORITE_TASKS == 1) { + dolibarr_set_const($db, 'DOLIPROJECT_SHOW_ONLY_FAVORITE_TASKS', 0, 'integer', 0, '', $conf->entity); + } else { + dolibarr_set_const($db, 'DOLIPROJECT_SHOW_ONLY_FAVORITE_TASKS', 1, 'integer', 0, '', $conf->entity); + } +} + + +/* + * View + */ + +$form = new Form($db); +$formother = new FormOther($db); +$formcompany = new FormCompany($db); +$formproject = new FormProjets($db); +$projectstatic = new Project($db); +$project = new Project($db); +$taskstatic = new Task($db); +$thirdpartystatic = new Societe($db); +$holiday = new Holiday($db); + +$title = $langs->trans("TimeSpent"); + +$projectsListId = $projectstatic->getProjectsAuthorizedForUser($usertoprocess, (empty($usertoprocess->id) ? 2 : 0), 1); // Return all project i have permission on (assigned to me+public). I want my tasks and some of my task may be on a public projet that is not my project +//var_dump($projectsListId); +if ($id) { + $project->fetch($id); + $project->fetch_thirdparty(); +} + +$onlyopenedproject = 1; // or -1 +$morewherefilter = ''; + +if ($search_project_ref) { + $morewherefilter .= natural_search(array("p.ref", "p.title"), $search_project_ref); +} +if ($search_task_ref) { + $morewherefilter .= natural_search("t.ref", $search_task_ref); +} +if ($search_task_label) { + $morewherefilter .= natural_search(array("t.ref", "t.label"), $search_task_label); +} +if ($search_thirdparty) { + $morewherefilter .= natural_search("s.nom", $search_thirdparty); +} +if ($search_declared_progress) { + $morewherefilter .= natural_search("t.progress", $search_declared_progress, 1); +} + +$sql = &$morewherefilter; + +/*$search_array_options = $search_array_options_project; + $extrafieldsobjectprefix='efp.'; + $search_options_pattern='search_options_'; + $extrafieldsobjectkey='projet'; + include DOL_DOCUMENT_ROOT.'/core/tpl/extrafields_list_search_sql.tpl.php'; + */ +$search_array_options = $search_array_options_task; +$extrafieldsobjectprefix = 'efpt.'; +$search_options_pattern = 'search_task_options_'; +$extrafieldsobjectkey = 'projet_task'; +include DOL_DOCUMENT_ROOT.'/core/tpl/extrafields_list_search_sql.tpl.php'; + +$tasksarray = $taskstatic->getTasksArray(0, 0, ($project->id ? $project->id : 0), $socid, 0, '', $onlyopenedproject, '', ($search_usertoprocessid ? $search_usertoprocessid : 0), 0, $extrafields); +//$tasksarray = getFavoriteTasksArray($taskstatic->id, 0, 0, ($project->id ? $project->id : 0), $socid, 0, $search_project_ref, $onlyopenedproject, $morewherefilter, ($search_usertoprocessid ? $search_usertoprocessid : 0), 0, $extrafields); // We want to see all tasks of open project i am allowed to see and that match filter, not only my tasks. Later only mine will be editable later. +if (!empty($conf->global->DOLIPROJECT_SHOW_ONLY_FAVORITE_TASKS)) { // Get all task without any filter, so we can show total of time spent for not visible tasks + $tasksarraywithoutfilter = $taskstatic->getTasksArray(0, 0, ($project->id ? $project->id : 0), $socid, 0, '', $onlyopenedproject, '', ($search_usertoprocessid ? $search_usertoprocessid : 0)); // We want to see all tasks of open project i am allowed to see and that match filter, not only my tasks. Later only mine will be editable later. +} +$projectsrole = $taskstatic->getUserRolesForProjectsOrTasks($usertoprocess, 0, ($project->id ? $project->id : 0), 0, $onlyopenedproject); +$tasksrole = $taskstatic->getUserRolesForProjectsOrTasks(0, $usertoprocess, ($project->id ? $project->id : 0), 0, $onlyopenedproject); + +llxHeader("", $title, "", '', '', '', array('/core/js/timesheet.js')); + +//print_barre_liste($title, $page, $_SERVER["PHP_SELF"], "", $sortfield, $sortorder, "", $num, '', 'project'); + +$param = ''; +$param .= ($mode ? '&mode='.urlencode($mode) : ''); +$param .= ($search_project_ref ? '&search_project_ref='.urlencode($search_project_ref) : ''); +$param .= ($search_usertoprocessid > 0 ? '&search_usertoprocessid='.urlencode($search_usertoprocessid) : ''); +$param .= ($search_thirdparty ? '&search_thirdparty='.urlencode($search_thirdparty) : ''); +$param .= ($search_task_ref ? '&search_task_ref='.urlencode($search_task_ref) : ''); +$param .= ($search_task_label ? '&search_task_label='.urlencode($search_task_label) : ''); + +$search_array_options = $search_array_options_project; +$search_options_pattern = 'search_options_'; +include DOL_DOCUMENT_ROOT.'/core/tpl/extrafields_list_search_param.tpl.php'; + +$search_array_options = $search_array_options_task; +$search_options_pattern = 'search_task_options_'; +include DOL_DOCUMENT_ROOT.'/core/tpl/extrafields_list_search_param.tpl.php'; + +// Show navigation bar +$nav = ''.img_previous($langs->trans("Previous")).''; +$nav .= ''.dol_print_date(dol_mktime(0, 0, 0, $month, $day, $year), "%B %Y").''; +$nav .= ''.img_next($langs->trans("Next")).''; +$nav .= $form->selectDate(-1, '', 0, 0, 2, "addtime", 1, 1); +$nav .= ''; + +$picto = 'clock'; + +print '
'; +print ''; +print ''; +print ''; +print ''; +print ''; +print ''; +print ''; +print ''; + +$head = doliproject_timesheet_prepare_head($mode, $usertoprocess); +print dol_get_fiche_head($head, 'inputpermonth', $langs->trans('TimeSpent'), -1, $picto); + +// Show description of content +print '
'; +if ($mine || ($usertoprocess->id == $user->id)) { + print $langs->trans("MyTasksDesc").'.'.($onlyopenedproject ? ' '.$langs->trans("OnlyOpenedProject") : '').'
'; +} else { + if (empty($usertoprocess->id) || $usertoprocess->id < 0) { + if ($user->rights->projet->all->lire && !$socid) { + print $langs->trans("ProjectsDesc").'.'.($onlyopenedproject ? ' '.$langs->trans("OnlyOpenedProject") : '').'
'; + } else { + print $langs->trans("ProjectsPublicTaskDesc").'.'.($onlyopenedproject ? ' '.$langs->trans("OnlyOpenedProject") : '').'
'; + } + } +} +if ($mine || ($usertoprocess->id == $user->id)) { + print $langs->trans("OnlyYourTaskAreVisible").'
'; +} else { + print $langs->trans("AllTaskVisibleButEditIfYouAreAssigned").'
'; +} +print '
'; + +print dol_get_fiche_end(); + +print '
'.$nav.'
'; // We move this before the assign to components so, the default submit button is not the assign to. + +print '
'; +$titleassigntask = $langs->transnoentities("AssignTaskToMe"); +if ($usertoprocess->id != $user->id) { + $titleassigntask = $langs->transnoentities("AssignTaskToUser", $usertoprocess->getFullName($langs)); +} +print '
'; +print img_picto('', 'projecttask', 'class="pictofixedwidth"'); +$formproject->selectTasks($socid ? $socid : -1, $taskid, 'taskid', 32, 0, '-- '.$langs->trans("ChooseANotYetAssignedTask").' --', 1, 0, 0, '', '', 'all', $usertoprocess); +print '
'; +print ' '; +print $formcompany->selectTypeContact($object, '', 'type', 'internal', 'rowid', 0, 'maxwidth150onsmartphone'); +print ''; +print '
'; + +print '
'; +print $langs->trans('ShowOnlyFavoriteTasks'); +print 'global->DOLIPROJECT_SHOW_ONLY_FAVORITE_TASKS ? ' checked' : '').' >'; +if ($conf->global->DOLIPROJECT_SHOW_ONLY_FAVORITE_TASKS) { + print '
'; + print '
'.' '.$langs->trans('WarningShowOnlyFavoriteTasks').'
'; +} + + +$numendworkingday = 0; +$numstartworkingday = 0; +// Get if user is available or not for each day +$isavailable = array(); + +// Assume from Monday to Friday if conf empty or badly formed +$numstartworkingday = 1; +$numendworkingday = 5; + +if (!empty($conf->global->MAIN_DEFAULT_WORKING_DAYS)) { + $tmparray = explode('-', $conf->global->MAIN_DEFAULT_WORKING_DAYS); + if (count($tmparray) >= 2) { + $numstartworkingday = $tmparray[0]; + $numendworkingday = $tmparray[1]; + } +} + +for ($idw = 0; $idw < $dayInMonth; $idw++) { + $dayinloopfromfirstdaytoshow = dol_time_plus_duree($firstdaytoshow, $idw, 'd'); // $firstdaytoshow is a date with hours = 0 + $dayinloopfromfirstdaytoshowgmt = dol_time_plus_duree($firstdaytoshowgmt, $idw, 'd'); // $firstdaytoshow is a date with hours = 0 + + $statusofholidaytocheck = Holiday::STATUS_APPROVED; + + $isavailablefordayanduser = $holiday->verifDateHolidayForTimestamp($usertoprocess->id, $dayinloopfromfirstdaytoshow, $statusofholidaytocheck); + $isavailable[$dayinloopfromfirstdaytoshow] = $isavailablefordayanduser; // in projectLinesPerWeek later, we are using $firstdaytoshow and dol_time_plus_duree to loop on each day + + $test = num_public_holiday($dayinloopfromfirstdaytoshowgmt, $dayinloopfromfirstdaytoshowgmt + 86400, $mysoc->country_code); + if ($test) { + $isavailable[$dayinloopfromfirstdaytoshow] = array('morning'=>false, 'afternoon'=>false, 'morning_reason'=>'public_holiday', 'afternoon_reason'=>'public_holiday'); + } +} + + + +$moreforfilter = ''; + +// Filter on categories +/* + if (! empty($conf->categorie->enabled)) + { + require_once DOL_DOCUMENT_ROOT . '/categories/class/categorie.class.php'; + $moreforfilter.='
'; + $moreforfilter.=$langs->trans('ProjectCategories'). ': '; + $moreforfilter.=$formother->select_categories('project', $search_categ, 'search_categ', 1, 1, 'maxwidth300'); + $moreforfilter.='
'; + }*/ + +// If the user can view user other than himself +$moreforfilter .= '
'; +$moreforfilter .= '
'; +$includeonly = 'hierarchyme'; +if (empty($user->rights->user->user->lire)) { + $includeonly = array($user->id); +} +$moreforfilter .= img_picto($langs->trans('Filter').' '.$langs->trans('User'), 'user', 'class="paddingright pictofixedwidth"').$form->select_dolusers($search_usertoprocessid ? $search_usertoprocessid : $usertoprocess->id, 'search_usertoprocessid', $user->rights->user->user->lire ? 0 : 0, null, 0, $includeonly, null, 0, 0, 0, '', 0, '', 'maxwidth200'); +$moreforfilter .= '
'; + +if (empty($conf->global->PROJECT_TIMESHEET_DISABLEBREAK_ON_PROJECT)) { + $moreforfilter .= '
'; + $moreforfilter .= '
'; + $moreforfilter .= img_picto($langs->trans('Filter').' '.$langs->trans('Project'), 'project', 'class="paddingright pictofixedwidth"').''; + $moreforfilter .= '
'; + + $moreforfilter .= '
'; + $moreforfilter .= '
'; + $moreforfilter .= img_picto($langs->trans('Filter').' '.$langs->trans('ThirdParty'), 'company', 'class="paddingright pictofixedwidth"').''; + $moreforfilter .= '
'; +} + +if (!empty($moreforfilter)) { + print '
'; + print $moreforfilter; + $parameters = array(); + $reshook = $hookmanager->executeHooks('printFieldPreListTitle', $parameters); // Note that $action and $object may have been modified by hook + print $hookmanager->resPrint; + print '
'; +} + + +$varpage = empty($contextpage) ? $_SERVER["PHP_SELF"] : $contextpage; + +$selectedfields = $form->multiSelectArrayWithCheckbox('selectedfields', $arrayfields, $varpage); // This also change content of $arrayfields + +// This must be after the $selectedfields +$addcolspan = 0; +if (!empty($arrayfields['t.planned_workload']['checked'])) { + $addcolspan++; +} +if (!empty($arrayfields['t.progress']['checked'])) { + $addcolspan++; +} +foreach ($arrayfields as $key => $val) { + if ($val['checked'] && substr($key, 0, 5) == 'efpt.') { + $addcolspan++; + } +} + +print '
'; +print ''."\n"; + +print ''; +if (!empty($conf->global->PROJECT_TIMESHEET_DISABLEBREAK_ON_PROJECT)) { + print ''; +} +if (!empty($conf->global->PROJECT_TIMESHEET_DISABLEBREAK_ON_PROJECT)) { + print ''; +} +print ''; +// TASK fields +$search_options_pattern = 'search_task_options_'; +$extrafieldsobjectkey = 'projet_task'; +$extrafieldsobjectprefix = 'efpt.'; +include DOL_DOCUMENT_ROOT.'/core/tpl/extrafields_list_search_input.tpl.php'; +if (!empty($arrayfields['t.planned_workload']['checked'])) { + print ''; +} +if (!empty($arrayfields['t.progress']['checked'])) { + print ''; +} +if (!empty($arrayfields['timeconsumed']['checked'])) { + print ''; +} +for ($idw = 0; $idw < $dayInMonth; $idw++) { + print ''; +} +// Action column +print ''; +print "\n"; + +print ''; +if (!empty($conf->global->PROJECT_TIMESHEET_DISABLEBREAK_ON_PROJECT)) { + print ''; +} +if (!empty($conf->global->PROJECT_TIMESHEET_DISABLEBREAK_ON_PROJECT)) { + print ''; +} +print ''; +// TASK fields +$extrafieldsobjectkey = 'projet_task'; +$extrafieldsobjectprefix = 'efpt.'; +include DOL_DOCUMENT_ROOT.'/core/tpl/extrafields_list_search_title.tpl.php'; +if (!empty($arrayfields['t.planned_workload']['checked'])) { + print ''; +} +if (!empty($arrayfields['t.progress']['checked'])) { + print ''; +} +if (!empty($arrayfields['timeconsumed']['checked'])) { + print ''; +} +for ($idw = 0; $idw < $dayInMonth; $idw++) { + $dayinloopfromfirstdaytoshow = dol_time_plus_duree($firstdaytoshow, $idw, 'd'); // $firstdaytoshow is a date with hours = 0 + + $cssweekend = ''; + if ((($idw + 1) < $numstartworkingday) || (($idw + 1) > $numendworkingday)) { // This is a day is not inside the setup of working days, so we use a week-end css. + //$cssweekend = 'weekend'; + } + + $tmpday = dol_time_plus_duree($firstdaytoshow, $idw, 'd'); + + $cssonholiday = ''; + if (!$isavailable[$tmpday]['morning'] && !$isavailable[$tmpday]['afternoon']) { + $cssonholiday .= 'onholidayallday '; + } elseif (!$isavailable[$tmpday]['morning']) { + $cssonholiday .= 'onholidaymorning '; + } elseif (!$isavailable[$tmpday]['afternoon']) { + $cssonholiday .= 'onholidayafternoon '; + } + + print ''; +} +//print ''; +print_liste_field_titre($selectedfields, $_SERVER["PHP_SELF"], "", '', '', '', $sortfield, $sortorder, 'center maxwidthsearch '); + + +print "\n"; + +$colspan = 1 + (empty($conf->global->PROJECT_TIMESHEET_DISABLEBREAK_ON_PROJECT) ? 0 : 2); + +$workinghours = new Workinghours($db); +$workinghoursArray = $workinghours->fetchCurrentWorkingHours($usertoprocess->id, 'user'); +$workinghoursMonth = 0; + +if ($conf->use_javascript_ajax) { + print ''; + print ''; + if (!empty($arrayfields['timeconsumed']['checked'])) { + print ''; + } + for ($idw = 0; $idw < $dayInMonth; $idw++) { + $dayinloopfromfirstdaytoshow = dol_time_plus_duree($firstdaytoshow, $idw, 'd'); + if ($isavailable[$dayinloopfromfirstdaytoshow]['morning'] && $isavailable[$dayinloopfromfirstdaytoshow]['afternoon']) { + $currentDay = date('l', $dayinloopfromfirstdaytoshow); + $currentDay = 'workinghours_' . strtolower($currentDay); + $workinghoursMonth = $workinghoursArray->{$currentDay} * 60; + } else { + $workinghoursMonth = 0; + } + + $cssweekend = ''; + if ((($idw + 1) < $numstartworkingday) || (($idw + 1) > $numendworkingday)) { // This is a day is not inside the setup of working days, so we use a week-end css. + //$cssweekend = 'weekend'; + } + + $tmpday = dol_time_plus_duree($firstdaytoshow, $idw, 'd'); + + $cssonholiday = ''; + if (!$isavailable[$tmpday]['morning'] && !$isavailable[$tmpday]['afternoon']) { + $cssonholiday .= 'onholidayallday '; + } elseif (!$isavailable[$tmpday]['morning']) { + $cssonholiday .= 'onholidaymorning '; + } elseif (!$isavailable[$tmpday]['afternoon']) { + $cssonholiday .= 'onholidayafternoon '; + } + + print ''; + } + print ''; + print ''; +} + + +// By default, we can edit only tasks we are assigned to +$restrictviewformytask = ((!isset($conf->global->PROJECT_TIME_SHOW_TASK_NOT_ASSIGNED)) ? 2 : $conf->global->PROJECT_TIME_SHOW_TASK_NOT_ASSIGNED); +if (count($tasksarray) > 0) { + //var_dump($tasksarray); // contains only selected tasks + //var_dump($tasksarraywithoutfilter); // contains all tasks (if there is a filter, not defined if no filter) + //var_dump($tasksrole); + + $j = 0; + $level = 0; + $totalforvisibletasks = projectLinesPerDayOnMonth($j, $firstdaytoshow, $usertoprocess, 0, $tasksarray, $level, $projectsrole, $tasksrole, $mine, $restrictviewformytask, $isavailable, 0, $arrayfields, $extrafields, $dayInMonth); +// if (is_array($tasksarraywithoutfilter) && count($tasksarraywithoutfilter)) { +// $totalforalltasks = projectLinesPerDayOnMonth($j, $firstdaytoshow, $usertoprocess, 0, $tasksarraywithoutfilter, $level, $projectsrole, $tasksrole, $mine, $restrictviewformytask, $isavailable, 0, $arrayfields, $extrafields, $dayInMonth); +// } + //var_dump($totalforvisibletasks); + + + // Show total for all other tasks + + // Calculate total for all tasks +// $listofdistinctprojectid = array(); // List of all distinct projects +// if (is_array($tasksarraywithoutfilter) && count($tasksarraywithoutfilter)) { +// foreach ($tasksarraywithoutfilter as $tmptask) { +// $listofdistinctprojectid[$tmptask->fk_project] = $tmptask->fk_project; +// } +// } +// //var_dump($listofdistinctprojectid); +// $totalforeachday = array(); +// foreach ($listofdistinctprojectid as $tmpprojectid) { +// $projectstatic->id = $tmpprojectid; +// loadTimeSpentMonthByDay($firstdaytoshow, 0, $usertoprocess->id, $projectstatic); // Load time spent from table projet_task_time for the project into this->weekWorkLoad and this->weekWorkLoadPerTask for all days of a week +// for ($idw = 0; $idw < $dayInMonth; $idw++) { +// $tmpday = dol_time_plus_duree($firstdaytoshow, $idw, 'd'); +// $totalforeachday[$tmpday] += $projectstatic->monthWorkLoad[$tmpday]; +// } +// } +// +// //var_dump($totalforeachday); +// //var_dump($totalforvisibletasks); +// +// // Is there a diff between selected/filtered tasks and all tasks ? +// $isdiff = 0; +// if (count($totalforeachday)) { +// for ($idw = 0; $idw < $dayInMonth; $idw++) { +// $tmpday = dol_time_plus_duree($firstdaytoshow, $idw, 'd'); +// $timeonothertasks = ($totalforeachday[$tmpday] - $totalforvisibletasks[$tmpday]); +// if ($timeonothertasks) { +// $isdiff = 1; +// break; +// } +// } +// } +// +// // There is a diff between total shown on screen and total spent by user, so we add a line with all other cumulated time of user +// if ($isdiff) { +// print ''; +// print ''; +// if (!empty($arrayfields['timeconsumed']['checked'])) { +// print ''; +// } +// for ($idw = 0; $idw < $dayInMonth; $idw++) { +// $cssweekend = ''; +// if ((($idw + 1) < $numstartworkingday) || (($idw + 1) > $numendworkingday)) { // This is a day is not inside the setup of working days, so we use a week-end css. +// //$cssweekend = 'weekend'; +// } +// +// print ''; +// } +// print ' '; +// print ''; +// } + + if ($conf->use_javascript_ajax) { + $workinghoursMonth = 0; + print ''; + print ''; + if (!empty($arrayfields['timeconsumed']['checked'])) { + print ''; + } + + for ($idw = 0; $idw < $dayInMonthCurrent; $idw++) { + $dayinloopfromfirstdaytoshow = dol_time_plus_duree($firstdaytoshow, $idw, 'd'); + if ($isavailable[$dayinloopfromfirstdaytoshow]['morning'] && $isavailable[$dayinloopfromfirstdaytoshow]['afternoon']) { + $currentDay = date('l', $dayinloopfromfirstdaytoshow); + $currentDay = 'workinghours_' . strtolower($currentDay); + $workinghoursMonth = $workinghoursArray->{$currentDay} * 60; + } else { + $workinghoursMonth = 0; + } + + $cssweekend = ''; + if ((($idw + 1) < $numstartworkingday) || (($idw + 1) > $numendworkingday)) { // This is a day is not inside the setup of working days, so we use a week-end css. + //$cssweekend = 'weekend'; + } + + $tmpday = dol_time_plus_duree($firstdaytoshow, $idw, 'd'); + + $cssonholiday = ''; + if (!$isavailable[$tmpday]['morning'] && !$isavailable[$tmpday]['afternoon']) { + $cssonholiday .= 'onholidayallday '; + } elseif (!$isavailable[$tmpday]['morning']) { + $cssonholiday .= 'onholidaymorning '; + } elseif (!$isavailable[$tmpday]['afternoon']) { + $cssonholiday .= 'onholidayafternoon '; + } + + print ''; + } + if ($dayInMonth == $dayInMonthCurrent) { + print ''; + } + print ''; + + print ''; + print ''; + if (!empty($arrayfields['timeconsumed']['checked'])) { + print ''; + } + + for ($idw = 0; $idw < $dayInMonthCurrent; $idw++) { + $dayinloopfromfirstdaytoshow = dol_time_plus_duree($firstdaytoshow, $idw, 'd'); + if ($isavailable[$dayinloopfromfirstdaytoshow]['morning'] && $isavailable[$dayinloopfromfirstdaytoshow]['afternoon']) { + $currentDay = date('l', $dayinloopfromfirstdaytoshow); + $currentDay = 'workinghours_' . strtolower($currentDay); + $workinghoursMonth = $workinghoursArray->{$currentDay} * 60; + } else { + $workinghoursMonth = 0; + } + + $cssweekend = ''; + if ((($idw + 1) < $numstartworkingday) || (($idw + 1) > $numendworkingday)) { // This is a day is not inside the setup of working days, so we use a week-end css. + //$cssweekend = 'weekend'; + } + + $tmpday = dol_time_plus_duree($firstdaytoshow, $idw, 'd'); + + $cssonholiday = ''; + if (!$isavailable[$tmpday]['morning'] && !$isavailable[$tmpday]['afternoon']) { + $cssonholiday .= 'onholidayallday '; + } elseif (!$isavailable[$tmpday]['morning']) { + $cssonholiday .= 'onholidaymorning '; + } elseif (!$isavailable[$tmpday]['afternoon']) { + $cssonholiday .= 'onholidayafternoon '; + } + + print ''; + } + if ($dayInMonth == $dayInMonthCurrent) { + print ''; + } + print ''; + + print ''; + print ''; + if (!empty($arrayfields['timeconsumed']['checked'])) { + print ''; + } + + for ($idw = 0; $idw < $dayInMonthCurrent; $idw++) { + $dayinloopfromfirstdaytoshow = dol_time_plus_duree($firstdaytoshow, $idw, 'd'); + if ($isavailable[$dayinloopfromfirstdaytoshow]['morning'] && $isavailable[$dayinloopfromfirstdaytoshow]['afternoon']) { + $currentDay = date('l', $dayinloopfromfirstdaytoshow); + $currentDay = 'workinghours_' . strtolower($currentDay); + $workinghoursMonth = $workinghoursArray->{$currentDay} * 60; + } else { + $workinghoursMonth = 0; + } + $difftime = $workinghoursMonth - $totalforvisibletasks[$dayinloopfromfirstdaytoshow]; + if ($difftime < 0) { + $morecss = colorStringToArray($conf->global->DOLIPROJECT_EXCEEDED_TIME_SPENT_COLOR); + } else if ($difftime > 0) { + $morecss = colorStringToArray($conf->global->DOLIPROJECT_NOT_EXCEEDED_TIME_SPENT_COLOR); + } else if ($difftime == 0) { + $morecss = colorStringToArray($conf->global->DOLIPROJECT_PERFECT_TIME_SPENT_COLOR); + } + $cssweekend = ''; + if ((($idw + 1) < $numstartworkingday) || (($idw + 1) > $numendworkingday)) { // This is a day is not inside the setup of working days, so we use a week-end css. + //$cssweekend = 'weekend'; + } + + $tmpday = dol_time_plus_duree($firstdaytoshow, $idw, 'd'); + + $cssonholiday = ''; + if (!$isavailable[$tmpday]['morning'] && !$isavailable[$tmpday]['afternoon']) { + $cssonholiday .= 'onholidayallday '; + } elseif (!$isavailable[$tmpday]['morning']) { + $cssonholiday .= 'onholidaymorning '; + } elseif (!$isavailable[$tmpday]['afternoon']) { + $cssonholiday .= 'onholidayafternoon '; + } + + print ''; + } + if ($dayInMonth == $dayInMonthCurrent) { + print ''; + } + print ''; + } +} else { + print ''; +} +print "
'; +$searchpicto = $form->showFilterAndCheckAddButtons(0); +print $searchpicto; +print '
'.$langs->trans("Project").''.$langs->trans("ThirdParty").''.$langs->trans("Task").''.$langs->trans("PlannedWorkload").''.$langs->trans("ProgressDeclared").''.$langs->trans("TimeSpent").($usertoprocess->firstname ? '
'.$usertoprocess->getNomUrl(-2).''.dol_trunc($usertoprocess->firstname, 10).'' : '').'
'; + print dol_print_date($dayinloopfromfirstdaytoshow, '%a'); + $splitted_date = preg_split('/\//', dol_print_date($dayinloopfromfirstdaytoshow, "day")); + $day = $splitted_date[0]; + $month = $splitted_date[1]; + $year = $splitted_date[2]; + print ' '; + print '
'.dol_print_date($dayinloopfromfirstdaytoshow, '%d/%m').'
'; + print $langs->trans("Total"); + for ($idw = 0; $idw < $dayInMonth; $idw++) { + $dayinloopfromfirstdaytoshow = dol_time_plus_duree($firstdaytoshow, $idw, 'd'); + if ($isavailable[$dayinloopfromfirstdaytoshow]['morning'] && $isavailable[$dayinloopfromfirstdaytoshow]['afternoon']) { + $currentDay = date('l', $dayinloopfromfirstdaytoshow); + $currentDay = 'workinghours_' . strtolower($currentDay); + $workinghoursMonth += $workinghoursArray->{$currentDay} / 60; + } + } + print ' - '.$langs->trans("ExpectedWorkedHoursMonth", dol_print_date(dol_mktime(0, 0, 0, $month, $day, $year), "%B %Y")).' : '.price($workinghoursMonth, 1, $langs, 0, 0).''; + print '
'.(($workinghoursMonth != 0) ? convertSecondToTime($workinghoursMonth, 'allhourmin') : '00:00').'
'; +// print $langs->trans("OtherFilteredTasks"); +// print '
 
'; +// $tmpday = dol_time_plus_duree($firstdaytoshow, $idw, 'd'); +// $timeonothertasks = ($totalforeachday[$tmpday] - $totalforvisibletasks[$tmpday]); +// if ($timeonothertasks) { +// print ''; +// } +// print '
'; + print $langs->trans("Total"); + + $currentDayCurrent = date( 'd', $now); + $currentMonth = date( 'm', $now); + + if ($currentMonth == $month) { + $dayInMonthCurrent = $currentDayCurrent; + } else { + $dayInMonthCurrent = $dayInMonth; + } + + for ($idw = 0; $idw < $dayInMonthCurrent; $idw++) { + $dayinloopfromfirstdaytoshow = dol_time_plus_duree($firstdaytoshow, $idw, 'd'); + if ($isavailable[$dayinloopfromfirstdaytoshow]['morning'] && $isavailable[$dayinloopfromfirstdaytoshow]['afternoon']) { + $currentDay = date('l', $dayinloopfromfirstdaytoshow); + $currentDay = 'workinghours_' . strtolower($currentDay); + $workinghoursMonth += $workinghoursArray->{$currentDay} / 60; + } + } + $totalspenttime = $workinghoursMonth; + print ' - '.$langs->trans("SpentWorkedHoursMonth", dol_print_date($firstdaytoshow, "dayreduceformat"), (($dayInMonth == $dayInMonthCurrent) ? dol_print_date($lastdaytoshow, "dayreduceformat") : dol_print_date($now, "dayreduceformat"))).' : '.price($workinghoursMonth, 1, $langs, 0, 0).''; + print '
'.(($workinghoursMonth != 0) ? convertSecondToTime($workinghoursMonth, 'allhourmin') : '00:00').'
'; + print $langs->trans("Total"); + + foreach ($totalforvisibletasks as $task) { + $totalconsumedtime += $task; + } + print ' - '.$langs->trans("ConsumedWorkedHoursMonth", dol_print_date($firstdaytoshow, "dayreduceformat"), (($dayInMonth == $dayInMonthCurrent) ? dol_print_date($lastdaytoshow, "dayreduceformat") : dol_print_date($now, "dayreduceformat"))).' : '.convertSecondToTime($totalconsumedtime, 'allhourmin').''; + print ''.convertSecondToTime($totalconsumedtime, 'allhourmin').'
'.dol_print_date($workinghoursMonth, 'hour').'
'; + print $langs->trans("Total"); + $difftotaltime = $totalspenttime * 60 * 60 - $totalconsumedtime; + if ($difftotaltime < 0) { + $morecss = colorStringToArray($conf->global->DOLIPROJECT_EXCEEDED_TIME_SPENT_COLOR); + } else if ($difftotaltime > 0) { + $morecss = colorStringToArray($conf->global->DOLIPROJECT_NOT_EXCEEDED_TIME_SPENT_COLOR); + } else if ($difftotaltime == 0) { + $morecss = colorStringToArray($conf->global->DOLIPROJECT_PERFECT_TIME_SPENT_COLOR); + } + print ' - '.$langs->trans("DiffSpentAndConsumedWorkedHoursMonth", dol_print_date($firstdaytoshow, "dayreduceformat"), (($dayInMonth == $dayInMonthCurrent) ? dol_print_date($lastdaytoshow, "dayreduceformat") : dol_print_date($now, "dayreduceformat"))).' : '.(($difftotaltime != 0) ? convertSecondToTime(abs($difftotaltime), 'allhourmin') : '00:00').''; + print ''.(($difftotaltime != 0) ? convertSecondToTime(abs($difftotaltime), 'allhourmin') : '00:00').'
'.(($difftime != 0) ? convertSecondToTime(abs($difftime), 'allhourmin') : '00:00').'
'.$langs->trans("NoAssignedTasks").'
"; +print '
'; + +print ''."\n"; + +print $form->buttonsSaveCancel("Save", ''); + +print '
'."\n\n"; + +$modeinput = 'hours'; + +if ($conf->use_javascript_ajax) { + print "\n\n"; + print ''; +} + +// End of page +llxFooter(); +$db->close(); diff --git a/view/timespent_week.php b/view/timespent_week.php new file mode 100644 index 0000000..3bc5858 --- /dev/null +++ b/view/timespent_week.php @@ -0,0 +1,1078 @@ + + * Copyright (C) 2004-2015 Laurent Destailleur + * Copyright (C) 2005-2010 Regis Houssin + * Copyright (C) 2010 François Legastelois + * Copyright (C) 2018 Frédéric France + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/** + * \file htdocs/projet/activity/perweek.php + * \ingroup projet + * \brief List activities of tasks (per week entry) + */ + +// Load Dolibarr environment +$res = 0; +// Try main.inc.php into web root known defined into CONTEXT_DOCUMENT_ROOT (not always defined) +if ( ! $res && ! empty($_SERVER["CONTEXT_DOCUMENT_ROOT"])) $res = @include $_SERVER["CONTEXT_DOCUMENT_ROOT"] . "/main.inc.php"; +// Try main.inc.php into web root detected using web root calculated from SCRIPT_FILENAME +$tmp = empty($_SERVER['SCRIPT_FILENAME']) ? '' : $_SERVER['SCRIPT_FILENAME']; $tmp2 = realpath(__FILE__); $i = strlen($tmp) - 1; $j = strlen($tmp2) - 1; +while ($i > 0 && $j > 0 && isset($tmp[$i]) && isset($tmp2[$j]) && $tmp[$i] == $tmp2[$j]) { $i--; $j--; } +if ( ! $res && $i > 0 && file_exists(substr($tmp, 0, ($i + 1)) . "/main.inc.php")) $res = @include substr($tmp, 0, ($i + 1)) . "/main.inc.php"; +if ( ! $res && $i > 0 && file_exists(dirname(substr($tmp, 0, ($i + 1))) . "/main.inc.php")) $res = @include dirname(substr($tmp, 0, ($i + 1))) . "/main.inc.php"; +// Try main.inc.php using relative path +if ( ! $res && file_exists("../../main.inc.php")) $res = @include "../../main.inc.php"; +if ( ! $res && file_exists("../../../main.inc.php")) $res = @include "../../../main.inc.php"; +if ( ! $res) die("Include of main fails"); + +// Libraries +require_once DOL_DOCUMENT_ROOT.'/projet/class/project.class.php'; +require_once DOL_DOCUMENT_ROOT.'/projet/class/task.class.php'; +require_once DOL_DOCUMENT_ROOT.'/core/lib/project.lib.php'; +require_once DOL_DOCUMENT_ROOT.'/core/lib/admin.lib.php'; +require_once DOL_DOCUMENT_ROOT.'/core/lib/date.lib.php'; +require_once DOL_DOCUMENT_ROOT.'/core/class/html.formother.class.php'; +require_once DOL_DOCUMENT_ROOT.'/core/class/html.formprojet.class.php'; +require_once DOL_DOCUMENT_ROOT.'/core/class/html.formcompany.class.php'; +require_once DOL_DOCUMENT_ROOT.'/holiday/class/holiday.class.php'; + +require_once DOL_DOCUMENT_ROOT.'/custom/doliproject/lib/doliproject_functions.lib.php'; +require_once DOL_DOCUMENT_ROOT.'/custom/doliproject/class/workinghours.class.php'; + +// Global variables definitions +global $conf, $db, $hookmanager, $langs, $user; + +// Load translation files required by the page +$langs->loadLangs(array('projects', 'users', 'companies')); + +// Get parameters +$action = GETPOST('action', 'aZ09'); +$mode = GETPOST("mode", 'alpha'); +$id = GETPOST('id', 'int'); +$taskid = GETPOST('taskid', 'int'); +$contextpage = GETPOST('contextpage', 'aZ') ?GETPOST('contextpage', 'aZ') : 'perweekcard'; + +$mine = 0; +if ($mode == 'mine') { + $mine = 1; +} + +$projectid = GETPOSTISSET("id") ? GETPOST("id", "int", 1) : GETPOST("projectid", "int"); + +$hookmanager->initHooks(array('timesheetperweekcard')); + +// Security check +$socid = 0; +// For external user, no check is done on company because readability is managed by public status of project and assignement. +// if ($user->socid > 0) $socid=$user->socid; +$result = restrictedArea($user, 'projet', $projectid); + +$now = dol_now(); + +$year = GETPOST('reyear', 'int') ?GETPOST('reyear', 'int') : (GETPOST("year", 'int') ?GETPOST("year", "int") : date("Y")); +$month = GETPOST('remonth', 'int') ?GETPOST('remonth', 'int') : (GETPOST("month", 'int') ?GETPOST("month", "int") : date("m")); +$day = GETPOST('reday', 'int') ?GETPOST('reday', 'int') : (GETPOST("day", 'int') ?GETPOST("day", "int") : date("d")); +$week = GETPOST("week", "int") ?GETPOST("week", "int") : date("W"); + +$day = (int) $day; + +//$search_categ = GETPOST("search_categ", 'alpha'); +$search_usertoprocessid = GETPOST('search_usertoprocessid', 'int'); +$search_task_ref = GETPOST('search_task_ref', 'alpha'); +$search_task_label = GETPOST('search_task_label', 'alpha'); +$search_project_ref = GETPOST('search_project_ref', 'alpha'); +$search_thirdparty = GETPOST('search_thirdparty', 'alpha'); +$search_declared_progress = GETPOST('search_declared_progress', 'alpha'); + +$sortfield = GETPOST('sortfield', 'aZ09comma'); +$sortorder = GETPOST('sortorder', 'aZ09comma'); + +$startdayarray = dol_get_first_day_week($day, $month, $year); + +$prev = $startdayarray; +$prev_year = $prev['prev_year']; +$prev_month = $prev['prev_month']; +$prev_day = $prev['prev_day']; +$first_day = $prev['first_day']; +$first_month = $prev['first_month']; +$first_year = $prev['first_year']; +$week = $prev['week']; + +$next = dol_get_next_week($first_day, $week, $first_month, $first_year); +$next_year = $next['year']; +$next_month = $next['month']; +$next_day = $next['day']; + +// Define firstdaytoshow and lastdaytoshow (warning: lastdaytoshow is last second to show + 1) +$firstdaytoshow = dol_mktime(0, 0, 0, $first_month, $first_day, $first_year); +$firstdaytoshowgmt = dol_mktime(0, 0, 0, $first_month, $first_day, $first_year, 'gmt'); + +if (empty($search_usertoprocessid) || $search_usertoprocessid == $user->id) { + $usertoprocess = $user; + $search_usertoprocessid = $usertoprocess->id; +} elseif ($search_usertoprocessid > 0) { + $usertoprocess = new User($db); + $usertoprocess->fetch($search_usertoprocessid); + $search_usertoprocessid = $usertoprocess->id; +} else { + $usertoprocess = new User($db); +} + +$object = new Task($db); + +// Extra fields +$extrafields = new ExtraFields($db); + +// fetch optionals attributes and labels +$extrafields->fetch_name_optionals_label($object->table_element); + +// Definition of fields for list +$arrayfields = array(); +/*$arrayfields=array( + // Project + 'p.opp_amount'=>array('label'=>$langs->trans("OpportunityAmountShort"), 'checked'=>0, 'enabled'=>($conf->global->PROJECT_USE_OPPORTUNITIES?1:0), 'position'=>103), + 'p.fk_opp_status'=>array('label'=>$langs->trans("OpportunityStatusShort"), 'checked'=>0, 'enabled'=>($conf->global->PROJECT_USE_OPPORTUNITIES?1:0), 'position'=>104), + 'p.opp_percent'=>array('label'=>$langs->trans("OpportunityProbabilityShort"), 'checked'=>0, 'enabled'=>($conf->global->PROJECT_USE_OPPORTUNITIES?1:0), 'position'=>105), + 'p.budget_amount'=>array('label'=>$langs->trans("Budget"), 'checked'=>0, 'position'=>110), + 'p.usage_bill_time'=>array('label'=>$langs->trans("BillTimeShort"), 'checked'=>0, 'position'=>115), + );*/ +//$arrayfields['t.planned_workload'] = array('label'=>'PlannedWorkload', 'checked'=>1, 'enabled'=>1, 'position'=>5); +//$arrayfields['t.progress'] = array('label'=>'ProgressDeclared', 'checked'=>1, 'enabled'=>1, 'position'=>10); +$arrayfields['timeconsumed'] = array('label'=>'TimeConsumed', 'checked'=>1, 'enabled'=>1, 'position'=>15); +/*foreach($object->fields as $key => $val) + { + // If $val['visible']==0, then we never show the field + if (! empty($val['visible'])) $arrayfields['t.'.$key]=array('label'=>$val['label'], 'checked'=>(($val['visible']<0)?0:1), 'enabled'=>$val['enabled'], 'position'=>$val['position']); + }*/ +// Definition of fields for list +// Extra fields +if (!empty($extrafields->attributes['projet_task']['label']) && is_array($extrafields->attributes['projet_task']['label']) && count($extrafields->attributes['projet_task']['label']) > 0) { + foreach ($extrafields->attributes['projet_task']['label'] as $key => $val) { + if (!empty($extrafields->attributes['projet_task']['list'][$key])) { + $arrayfields["efpt.".$key] = array('label'=>$extrafields->attributes['projet_task']['label'][$key], 'checked'=>(($extrafields->attributes['projet_task']['list'][$key] < 0) ? 0 : 1), 'position'=>$extrafields->attributes['projet_task']['pos'][$key], 'enabled'=>(abs((int) $extrafields->attributes['projet_task']['list'][$key]) != 3 && $extrafields->attributes['projet_task']['perms'][$key])); + } + } +} +$arrayfields = dol_sort_array($arrayfields, 'position'); + +$search_array_options = array(); +$search_array_options_project = $extrafields->getOptionalsFromPost('projet', '', 'search_'); +$search_array_options_task = $extrafields->getOptionalsFromPost('projet_task', '', 'search_task_'); + + + +/* + * Actions + */ + +$parameters = array('id' => $id, 'taskid' => $taskid, 'projectid' => $projectid); +$reshook = $hookmanager->executeHooks('doActions', $parameters, $object, $action); // Note that $action and $object may have been modified by some hooks +if ($reshook < 0) { + setEventMessages($hookmanager->error, $hookmanager->errors, 'errors'); +} +// Purge criteria +if (GETPOST('button_removefilter_x', 'alpha') || GETPOST('button_removefilter.x', 'alpha') || GETPOST('button_removefilter', 'alpha')) { // All tests are required to be compatible with all browsers + $action = ''; + //$search_categ = ''; + $search_usertoprocessid = $user->id; + $search_task_ref = ''; + $search_task_label = ''; + $search_project_ref = ''; + $search_thirdparty = ''; + $search_declared_progress = ''; + + $search_array_options_project = array(); + $search_array_options_task = array(); + + // We redefine $usertoprocess + $usertoprocess = $user; +} +if (GETPOST("button_search_x", 'alpha') || GETPOST("button_search.x", 'alpha') || GETPOST("button_search", 'alpha')) { + $action = ''; +} + +if (GETPOST('submitdateselect')) { + if (GETPOST('remonth', 'int') && GETPOST('reday', 'int') && GETPOST('reyear', 'int')) { + $daytoparse = dol_mktime(0, 0, 0, GETPOST('remonth', 'int'), GETPOST('reday', 'int'), GETPOST('reyear', 'int')); + } + + $action = ''; +} + +include DOL_DOCUMENT_ROOT.'/core/actions_changeselectedfields.inc.php'; + +if ($action == 'addtime' && $user->rights->projet->lire && GETPOST('assigntask') && GETPOST('formfilteraction') != 'listafterchangingselectedfields') { + $action = 'assigntask'; + + if ($taskid > 0) { + $result = $object->fetch($taskid, $ref); + if ($result < 0) { + $error++; + } + } else { + setEventMessages($langs->transnoentitiesnoconv("ErrorFieldRequired", $langs->transnoentitiesnoconv("Task")), '', 'errors'); + $error++; + } + if (!GETPOST('type')) { + setEventMessages($langs->transnoentitiesnoconv("ErrorFieldRequired", $langs->transnoentitiesnoconv("Type")), '', 'errors'); + $error++; + } + + if (!$error) { + $idfortaskuser = $usertoprocess->id; + $result = $object->add_contact($idfortaskuser, GETPOST("type"), 'internal'); + + if ($result >= 0 || $result == -2) { // Contact add ok or already contact of task + // Test if we are already contact of the project (should be rare but sometimes we can add as task contact without being contact of project, like when admin user has been removed from contact of project) + $sql = 'SELECT ec.rowid FROM '.MAIN_DB_PREFIX.'element_contact as ec, '.MAIN_DB_PREFIX.'c_type_contact as tc WHERE tc.rowid = ec.fk_c_type_contact'; + $sql .= ' AND ec.fk_socpeople = '.((int) $idfortaskuser)." AND ec.element_id = ".((int) $object->fk_project)." AND tc.element = 'project' AND source = 'internal'"; + $resql = $db->query($sql); + if ($resql) { + $obj = $db->fetch_object($resql); + if (!$obj) { // User is not already linked to project, so we will create link to first type + $project = new Project($db); + $project->fetch($object->fk_project); + // Get type + $listofprojcontact = $project->liste_type_contact('internal'); + + if (count($listofprojcontact)) { + $typeforprojectcontact = reset(array_keys($listofprojcontact)); + $result = $project->add_contact($idfortaskuser, $typeforprojectcontact, 'internal'); + } + } + } else { + dol_print_error($db); + } + } + } + + if ($result < 0) { + $error++; + if ($object->error == 'DB_ERROR_RECORD_ALREADY_EXISTS') { + $langs->load("errors"); + setEventMessages($langs->trans("ErrorTaskAlreadyAssigned"), null, 'warnings'); + } else { + setEventMessages($object->error, $object->errors, 'errors'); + } + } + + if (!$error) { + setEventMessages("TaskAssignedToEnterTime", null); + $taskid = 0; + } + + $action = ''; +} + +if ($action == 'addtime' && $user->rights->projet->lire && GETPOST('formfilteraction') != 'listafterchangingselectedfields') { + $timetoadd = $_POST['task']; + if (empty($timetoadd)) { + setEventMessages($langs->trans("ErrorTimeSpentIsEmpty"), null, 'errors'); + } else { + foreach ($timetoadd as $taskid => $value) { // Loop on each task + $updateoftaskdone = 0; + foreach ($value as $key => $val) { // Loop on each day + $amountoadd = $timetoadd[$taskid][$key]; + if (!empty($amountoadd)) { + $tmpduration = explode(':', $amountoadd); + $newduration = 0; + if (!empty($tmpduration[0])) { + $newduration += ($tmpduration[0] * 3600); + } + if (!empty($tmpduration[1])) { + $newduration += ($tmpduration[1] * 60); + } + if (!empty($tmpduration[2])) { + $newduration += ($tmpduration[2]); + } + + if ($newduration > 0) { + $object->fetch($taskid); + + if (GETPOSTISSET($taskid.'progress')) { + $object->progress = GETPOST($taskid.'progress', 'int'); + } else { + unset($object->progress); + } + + $object->timespent_duration = $newduration; + $object->timespent_fk_user = $usertoprocess->id; + $object->timespent_date = dol_time_plus_duree($firstdaytoshow, $key, 'd'); + $object->timespent_datehour = $object->timespent_date; + $object->timespent_note = $object->description; + + $result = $object->addTimeSpent($user); + if ($result < 0) { + setEventMessages($object->error, $object->errors, 'errors'); + $error++; + break; + } + + $updateoftaskdone++; + } + } + } + + if (!$updateoftaskdone) { // Check to update progress if no update were done on task. + $object->fetch($taskid); + //var_dump($object->progress);var_dump(GETPOST($taskid . 'progress', 'int')); exit; + if ($object->progress != GETPOST($taskid.'progress', 'int')) { + $object->progress = GETPOST($taskid.'progress', 'int'); + $result = $object->update($user); + if ($result < 0) { + setEventMessages($object->error, $object->errors, 'errors'); + $error++; + break; + } + } + } + } + + if (!$error) { + setEventMessages($langs->trans("RecordSaved"), null, 'mesgs'); + + $param = ''; + $param .= ($mode ? '&mode='.urlencode($mode) : ''); + $param .= ($projectid ? 'id='.urlencode($projectid) : ''); + $param .= ($search_usertoprocessid ? '&search_usertoprocessid='.urlencode($search_usertoprocessid) : ''); + $param .= ($day ? '&day='.urlencode($day) : '').($month ? '&month='.urlencode($month) : '').($year ? '&year='.urlencode($year) : ''); + $param .= ($search_project_ref ? '&search_project_ref='.urlencode($search_project_ref) : ''); + $param .= ($search_usertoprocessid > 0 ? '&search_usertoprocessid='.urlencode($search_usertoprocessid) : ''); + $param .= ($search_thirdparty ? '&search_thirdparty='.urlencode($search_thirdparty) : ''); + $param .= ($search_declared_progress ? '&search_declared_progress='.urlencode($search_declared_progress) : ''); + $param .= ($search_task_ref ? '&search_task_ref='.urlencode($search_task_ref) : ''); + $param .= ($search_task_label ? '&search_task_label='.urlencode($search_task_label) : ''); + + /*$search_array_options=$search_array_options_project; + $search_options_pattern='search_options_'; + include DOL_DOCUMENT_ROOT.'/core/tpl/extrafields_list_search_param.tpl.php'; + */ + + $search_array_options = $search_array_options_task; + $search_options_pattern = 'search_task_options_'; + include DOL_DOCUMENT_ROOT.'/core/tpl/extrafields_list_search_param.tpl.php'; + + // Redirect to avoid submit twice on back + header('Location: '.$_SERVER["PHP_SELF"].'?'.$param); + exit; + } + } +} + +if ($action == 'showOnlyFavoriteTasks') { + if ($conf->global->DOLIPROJECT_SHOW_ONLY_FAVORITE_TASKS == 1) { + dolibarr_set_const($db, 'DOLIPROJECT_SHOW_ONLY_FAVORITE_TASKS', 0, 'integer', 0, '', $conf->entity); + } else { + dolibarr_set_const($db, 'DOLIPROJECT_SHOW_ONLY_FAVORITE_TASKS', 1, 'integer', 0, '', $conf->entity); + } +} + +/* + * View + */ + +$form = new Form($db); +$formother = new FormOther($db); +$formcompany = new FormCompany($db); +$formproject = new FormProjets($db); +$projectstatic = new Project($db); +$project = new Project($db); +$taskstatic = new Task($db); +$thirdpartystatic = new Societe($db); +$holiday = new Holiday($db); + +$title = $langs->trans("TimeSpent"); + +$projectsListId = $projectstatic->getProjectsAuthorizedForUser($usertoprocess, (empty($usertoprocess->id) ? 2 : 0), 1); // Return all project i have permission on (assigned to me+public). I want my tasks and some of my task may be on a public projet that is not my project +//var_dump($projectsListId); +if ($id) { + $project->fetch($id); + $project->fetch_thirdparty(); +} + +$onlyopenedproject = 1; // or -1 +$morewherefilter = ''; + +if ($search_project_ref) { + $morewherefilter .= natural_search(array("p.ref", "p.title"), $search_project_ref); +} +if ($search_task_ref) { + $morewherefilter .= natural_search("t.ref", $search_task_ref); +} +if ($search_task_label) { + $morewherefilter .= natural_search(array("t.ref", "t.label"), $search_task_label); +} +if ($search_thirdparty) { + $morewherefilter .= natural_search("s.nom", $search_thirdparty); +} +if ($search_declared_progress) { + $morewherefilter .= natural_search("t.progress", $search_declared_progress, 1); +} + +$sql = &$morewherefilter; + +/*$search_array_options = $search_array_options_project; + $extrafieldsobjectprefix='efp.'; + $search_options_pattern='search_options_'; + $extrafieldsobjectkey='projet'; + include DOL_DOCUMENT_ROOT.'/core/tpl/extrafields_list_search_sql.tpl.php'; + */ +$search_array_options = $search_array_options_task; +$extrafieldsobjectprefix = 'efpt.'; +$search_options_pattern = 'search_task_options_'; +$extrafieldsobjectkey = 'projet_task'; +include DOL_DOCUMENT_ROOT.'/core/tpl/extrafields_list_search_sql.tpl.php'; + +$tasksarray = $taskstatic->getTasksArray( 0, 0, ($project->id ? $project->id : 0), $socid, 0, $search_project_ref, $onlyopenedproject, $morewherefilter, ($search_usertoprocessid ? $search_usertoprocessid : 0), 0, $extrafields); // We want to see all tasks of open project i am allowed to see and that match filter, not only my tasks. Later only mine will be editable later. +if ($morewherefilter) { // Get all task without any filter, so we can show total of time spent for not visible tasks + $tasksarraywithoutfilter = getFavoriteTasksArray($taskstatic->id, 0, 0, ($project->id ? $project->id : 0), $socid, 0, '', $onlyopenedproject, '', ($search_usertoprocessid ? $search_usertoprocessid : 0)); // We want to see all tasks of open project i am allowed to see and that match filter, not only my tasks. Later only mine will be editable later. +} +$projectsrole = $taskstatic->getUserRolesForProjectsOrTasks($usertoprocess, 0, ($project->id ? $project->id : 0), 0, $onlyopenedproject); +$tasksrole = $taskstatic->getUserRolesForProjectsOrTasks(0, $usertoprocess, ($project->id ? $project->id : 0), 0, $onlyopenedproject); +//var_dump($tasksarray); +//var_dump($projectsrole); +//var_dump($taskrole); + + +llxHeader("", $title, "", '', '', '', array('/core/js/timesheet.js')); + +//print_barre_liste($title, $page, $_SERVER["PHP_SELF"], "", $sortfield, $sortorder, "", $num, '', 'project'); + +$param = ''; +$param .= ($mode ? '&mode='.urlencode($mode) : ''); +$param .= ($search_project_ref ? '&search_project_ref='.urlencode($search_project_ref) : ''); +$param .= ($search_usertoprocessid > 0 ? '&search_usertoprocessid='.urlencode($search_usertoprocessid) : ''); +$param .= ($search_thirdparty ? '&search_thirdparty='.urlencode($search_thirdparty) : ''); +$param .= ($search_task_ref ? '&search_task_ref='.urlencode($search_task_ref) : ''); +$param .= ($search_task_label ? '&search_task_label='.urlencode($search_task_label) : ''); + +$search_array_options = $search_array_options_project; +$search_options_pattern = 'search_options_'; +include DOL_DOCUMENT_ROOT.'/core/tpl/extrafields_list_search_param.tpl.php'; + +$search_array_options = $search_array_options_task; +$search_options_pattern = 'search_task_options_'; +include DOL_DOCUMENT_ROOT.'/core/tpl/extrafields_list_search_param.tpl.php'; + +// Show navigation bar +$nav = ''.img_previous($langs->trans("Previous"))."\n"; +$nav .= ' '.dol_print_date(dol_mktime(0, 0, 0, $first_month, $first_day, $first_year), "%Y").", ".$langs->trans("WeekShort")." ".$week." \n"; +$nav .= ''.img_next($langs->trans("Next"))."\n"; +$nav .= ' '.$form->selectDate(-1, '', 0, 0, 2, "addtime", 1, 1).' '; +$nav .= ' '; + +$picto = 'clock'; + +print '
'; +print ''; +print ''; +print ''; +print ''; +print ''; +print ''; +print ''; +print ''; + +$head = doliproject_timesheet_prepare_head($mode, $usertoprocess); +print dol_get_fiche_head($head, 'inputperweek', $langs->trans('TimeSpent'), -1, $picto); + +// Show description of content +print '
'; +if ($mine || ($usertoprocess->id == $user->id)) { + print $langs->trans("MyTasksDesc").'.'.($onlyopenedproject ? ' '.$langs->trans("OnlyOpenedProject") : '').'
'; +} else { + if (empty($usertoprocess->id) || $usertoprocess->id < 0) { + if ($user->rights->projet->all->lire && !$socid) { + print $langs->trans("ProjectsDesc").'.'.($onlyopenedproject ? ' '.$langs->trans("OnlyOpenedProject") : '').'
'; + } else { + print $langs->trans("ProjectsPublicTaskDesc").'.'.($onlyopenedproject ? ' '.$langs->trans("OnlyOpenedProject") : '').'
'; + } + } +} +if ($mine || ($usertoprocess->id == $user->id)) { + print $langs->trans("OnlyYourTaskAreVisible").'
'; +} else { + print $langs->trans("AllTaskVisibleButEditIfYouAreAssigned").'
'; +} +print '
'; + +print dol_get_fiche_end(); + +print '
'.$nav.'
'; // We move this before the assign to components so, the default submit button is not the assign to. + +print '
'; +$titleassigntask = $langs->transnoentities("AssignTaskToMe"); +if ($usertoprocess->id != $user->id) { + $titleassigntask = $langs->transnoentities("AssignTaskToUser", $usertoprocess->getFullName($langs)); +} +print '
'; +print img_picto('', 'projecttask', 'class="pictofixedwidth"'); +$formproject->selectTasks($socid ? $socid : -1, $taskid, 'taskid', 32, 0, '-- '.$langs->trans("ChooseANotYetAssignedTask").' --', 1, 0, 0, '', '', 'all', $usertoprocess); +print '
'; +print ' '; +print $formcompany->selectTypeContact($object, '', 'type', 'internal', 'rowid', 0, 'maxwidth150onsmartphone'); +print ''; +print '
'; + +print '
'; +print $langs->trans('ShowOnlyFavoriteTasks'); +print 'global->DOLIPROJECT_SHOW_ONLY_FAVORITE_TASKS ? ' checked' : '').' >'; +if ($conf->global->DOLIPROJECT_SHOW_ONLY_FAVORITE_TASKS) { + print '
'; + print '
'.' '.$langs->trans('WarningShowOnlyFavoriteTasks').'
'; +} + +$startday = dol_mktime(12, 0, 0, $startdayarray['first_month'], $startdayarray['first_day'], $startdayarray['first_year']); + +$numendworkingday = 0; +$numstartworkingday = 0; +// Get if user is available or not for each day +$isavailable = array(); + +// Assume from Monday to Friday if conf empty or badly formed +$numstartworkingday = 1; +$numendworkingday = 5; + +if (!empty($conf->global->MAIN_DEFAULT_WORKING_DAYS)) { + $tmparray = explode('-', $conf->global->MAIN_DEFAULT_WORKING_DAYS); + if (count($tmparray) >= 2) { + $numstartworkingday = $tmparray[0]; + $numendworkingday = $tmparray[1]; + } +} + +for ($idw = 0; $idw < 7; $idw++) { + $dayinloopfromfirstdaytoshow = dol_time_plus_duree($firstdaytoshow, $idw, 'd'); // $firstdaytoshow is a date with hours = 0 + $dayinloopfromfirstdaytoshowgmt = dol_time_plus_duree($firstdaytoshowgmt, $idw, 'd'); // $firstdaytoshow is a date with hours = 0 + + $statusofholidaytocheck = Holiday::STATUS_APPROVED; + + $isavailablefordayanduser = $holiday->verifDateHolidayForTimestamp($usertoprocess->id, $dayinloopfromfirstdaytoshow, $statusofholidaytocheck); + $isavailable[$dayinloopfromfirstdaytoshow] = $isavailablefordayanduser; // in projectLinesPerWeek later, we are using $firstdaytoshow and dol_time_plus_duree to loop on each day + + $test = num_public_holiday($dayinloopfromfirstdaytoshowgmt, $dayinloopfromfirstdaytoshowgmt + 86400, $mysoc->country_code); + if ($test) { + $isavailable[$dayinloopfromfirstdaytoshow] = array('morning'=>false, 'afternoon'=>false, 'morning_reason'=>'public_holiday', 'afternoon_reason'=>'public_holiday'); + } +} + + + +$moreforfilter = ''; + +// Filter on categories +/* + if (! empty($conf->categorie->enabled)) + { + require_once DOL_DOCUMENT_ROOT . '/categories/class/categorie.class.php'; + $moreforfilter.='
'; + $moreforfilter.=$langs->trans('ProjectCategories'). ': '; + $moreforfilter.=$formother->select_categories('project', $search_categ, 'search_categ', 1, 1, 'maxwidth300'); + $moreforfilter.='
'; + }*/ + +// If the user can view user other than himself +$moreforfilter .= '
'; +$moreforfilter .= '
'; +$includeonly = 'hierarchyme'; +if (empty($user->rights->user->user->lire)) { + $includeonly = array($user->id); +} +$moreforfilter .= img_picto($langs->trans('Filter').' '.$langs->trans('User'), 'user', 'class="paddingright pictofixedwidth"').$form->select_dolusers($search_usertoprocessid ? $search_usertoprocessid : $usertoprocess->id, 'search_usertoprocessid', $user->rights->user->user->lire ? 0 : 0, null, 0, $includeonly, null, 0, 0, 0, '', 0, '', 'maxwidth200'); +$moreforfilter .= '
'; + +if (empty($conf->global->PROJECT_TIMESHEET_DISABLEBREAK_ON_PROJECT)) { + $moreforfilter .= '
'; + $moreforfilter .= '
'; + $moreforfilter .= img_picto($langs->trans('Filter').' '.$langs->trans('Project'), 'project', 'class="paddingright pictofixedwidth"').''; + $moreforfilter .= '
'; + + $moreforfilter .= '
'; + $moreforfilter .= '
'; + $moreforfilter .= img_picto($langs->trans('Filter').' '.$langs->trans('ThirdParty'), 'company', 'class="paddingright pictofixedwidth"').''; + $moreforfilter .= '
'; +} + +if (!empty($moreforfilter)) { + print '
'; + print $moreforfilter; + $parameters = array(); + $reshook = $hookmanager->executeHooks('printFieldPreListTitle', $parameters); // Note that $action and $object may have been modified by hook + print $hookmanager->resPrint; + print '
'; +} + + +$varpage = empty($contextpage) ? $_SERVER["PHP_SELF"] : $contextpage; + +$selectedfields = $form->multiSelectArrayWithCheckbox('selectedfields', $arrayfields, $varpage); // This also change content of $arrayfields + +// This must be after the $selectedfields +$addcolspan = 0; +if (!empty($arrayfields['t.planned_workload']['checked'])) { + $addcolspan++; +} +if (!empty($arrayfields['t.progress']['checked'])) { + $addcolspan++; +} +foreach ($arrayfields as $key => $val) { + if ($val['checked'] && substr($key, 0, 5) == 'efpt.') { + $addcolspan++; + } +} + +print '
'; +print ''."\n"; + +print ''; +if (!empty($conf->global->PROJECT_TIMESHEET_DISABLEBREAK_ON_PROJECT)) { + print ''; +} +if (!empty($conf->global->PROJECT_TIMESHEET_DISABLEBREAK_ON_PROJECT)) { + print ''; +} +print ''; +// TASK fields +$search_options_pattern = 'search_task_options_'; +$extrafieldsobjectkey = 'projet_task'; +$extrafieldsobjectprefix = 'efpt.'; +include DOL_DOCUMENT_ROOT.'/core/tpl/extrafields_list_search_input.tpl.php'; +if (!empty($arrayfields['t.planned_workload']['checked'])) { + print ''; +} +if (!empty($arrayfields['t.progress']['checked'])) { + print ''; +} +if (!empty($arrayfields['timeconsumed']['checked'])) { + print ''; + print ''; +} +for ($idw = 0; $idw < 7; $idw++) { + print ''; +} +// Action column +print ''; +print "\n"; + +print ''; +if (!empty($conf->global->PROJECT_TIMESHEET_DISABLEBREAK_ON_PROJECT)) { + print ''; +} +if (!empty($conf->global->PROJECT_TIMESHEET_DISABLEBREAK_ON_PROJECT)) { + print ''; +} +print ''; +// TASK fields +$extrafieldsobjectkey = 'projet_task'; +$extrafieldsobjectprefix = 'efpt.'; +include DOL_DOCUMENT_ROOT.'/core/tpl/extrafields_list_search_title.tpl.php'; +if (!empty($arrayfields['t.planned_workload']['checked'])) { + print ''; +} +if (!empty($arrayfields['t.progress']['checked'])) { + print ''; +} +if (!empty($arrayfields['timeconsumed']['checked'])) { + print ''; + print ''; +} +for ($idw = 0; $idw < 7; $idw++) { + $dayinloopfromfirstdaytoshow = dol_time_plus_duree($firstdaytoshow, $idw, 'd'); // $firstdaytoshow is a date with hours = 0 + + $cssweekend = ''; + if ((($idw + 1) < $numstartworkingday) || (($idw + 1) > $numendworkingday)) { // This is a day is not inside the setup of working days, so we use a week-end css. + $cssweekend = 'weekend'; + } + + $tmpday = dol_time_plus_duree($firstdaytoshow, $idw, 'd'); + + $cssonholiday = ''; + if (!$isavailable[$tmpday]['morning'] && !$isavailable[$tmpday]['afternoon']) { + $cssonholiday .= 'onholidayallday '; + } elseif (!$isavailable[$tmpday]['morning']) { + $cssonholiday .= 'onholidaymorning '; + } elseif (!$isavailable[$tmpday]['afternoon']) { + $cssonholiday .= 'onholidayafternoon '; + } + + print ''; +} +//print ''; +print_liste_field_titre($selectedfields, $_SERVER["PHP_SELF"], "", '', '', '', $sortfield, $sortorder, 'center maxwidthsearch '); + + +print "\n"; + +$colspan = 1 + (empty($conf->global->PROJECT_TIMESHEET_DISABLEBREAK_ON_PROJECT) ? 0 : 2); + +$workinghours = new Workinghours($db); +$workinghoursArray = $workinghours->fetchCurrentWorkingHours($usertoprocess->id, 'user'); +$workinghoursWeek = 0; + +if ($conf->use_javascript_ajax) { + print ''; + print ''; + if (!empty($arrayfields['timeconsumed']['checked'])) { + print ''; + print ''; + } + for ($idw = 0; $idw < 7; $idw++) { + $dayinloopfromfirstdaytoshow = dol_time_plus_duree($firstdaytoshow, $idw, 'd'); + if ($isavailable[$dayinloopfromfirstdaytoshow]['morning'] && $isavailable[$dayinloopfromfirstdaytoshow]['afternoon']) { + $currentDay = date('l', $dayinloopfromfirstdaytoshow); + $currentDay = 'workinghours_' . strtolower($currentDay); + $workinghoursWeek = $workinghoursArray->{$currentDay} * 60; + } else { + $workinghoursWeek = 0; + } + + $cssweekend = ''; + if ((($idw + 1) < $numstartworkingday) || (($idw + 1) > $numendworkingday)) { // This is a day is not inside the setup of working days, so we use a week-end css. + $cssweekend = 'weekend'; + } + + $tmpday = dol_time_plus_duree($firstdaytoshow, $idw, 'd'); + + $cssonholiday = ''; + if (!$isavailable[$tmpday]['morning'] && !$isavailable[$tmpday]['afternoon']) { + $cssonholiday .= 'onholidayallday '; + } elseif (!$isavailable[$tmpday]['morning']) { + $cssonholiday .= 'onholidaymorning '; + } elseif (!$isavailable[$tmpday]['afternoon']) { + $cssonholiday .= 'onholidayafternoon '; + } + print ''; + } + print ''; + print ''; +} + + + +// By default, we can edit only tasks we are assigned to +$restrictviewformytask = ((!isset($conf->global->PROJECT_TIME_SHOW_TASK_NOT_ASSIGNED)) ? 2 : $conf->global->PROJECT_TIME_SHOW_TASK_NOT_ASSIGNED); +if (count($tasksarray) > 0) { + //var_dump($tasksarray); // contains only selected tasks + //var_dump($tasksarraywithoutfilter); // contains all tasks (if there is a filter, not defined if no filter) + //var_dump($tasksrole); + + $j = 0; + $level = 0; + $totalforvisibletasks = projectLinesPerWeekDoliProject($j, $firstdaytoshow, $usertoprocess, 0, $tasksarray, $level, $projectsrole, $tasksrole, $mine, $restrictviewformytask, $isavailable, 0, $arrayfields, $extrafields); + //var_dump($totalforvisibletasks); + + // Show total for all other tasks + + // Calculate total for all tasks + $listofdistinctprojectid = array(); // List of all distinct projects + if (is_array($tasksarraywithoutfilter) && count($tasksarraywithoutfilter)) { + foreach ($tasksarraywithoutfilter as $tmptask) { + $listofdistinctprojectid[$tmptask->fk_project] = $tmptask->fk_project; + } + } + //var_dump($listofdistinctprojectid); + $totalforeachday = array(); + foreach ($listofdistinctprojectid as $tmpprojectid) { + $projectstatic->id = $tmpprojectid; + $projectstatic->loadTimeSpent($firstdaytoshow, 0, $usertoprocess->id); // Load time spent from table projet_task_time for the project into this->weekWorkLoad and this->weekWorkLoadPerTask for all days of a week + for ($idw = 0; $idw < 7; $idw++) { + $tmpday = dol_time_plus_duree($firstdaytoshow, $idw, 'd'); + $totalforeachday[$tmpday] += $projectstatic->weekWorkLoad[$tmpday]; + } + } + + //var_dump($totalforeachday); + //var_dump($totalforvisibletasks); + + // Is there a diff between selected/filtered tasks and all tasks ? + $isdiff = 0; + if (count($totalforeachday)) { + for ($idw = 0; $idw < 7; $idw++) { + $tmpday = dol_time_plus_duree($firstdaytoshow, $idw, 'd'); + $timeonothertasks = ($totalforeachday[$tmpday] - $totalforvisibletasks[$tmpday]); + if ($timeonothertasks) { + $isdiff = 1; + break; + } + } + } + + // There is a diff between total shown on screen and total spent by user, so we add a line with all other cumulated time of user + if ($isdiff) { + print ''; + print ''; + if (!empty($arrayfields['timeconsumed']['checked'])) { + print ''; + print ''; + } + for ($idw = 0; $idw < 7; $idw++) { + $cssweekend = ''; + if ((($idw + 1) < $numstartworkingday) || (($idw + 1) > $numendworkingday)) { // This is a day is not inside the setup of working days, so we use a week-end css. + $cssweekend = 'weekend'; + } + + print ''; + } + print ' '; + print ''; + } + + if ($conf->use_javascript_ajax) { + print ''; + print ''; + if (!empty($arrayfields['timeconsumed']['checked'])) { + print ''; + print ''; + } + + for ($idw = 0; $idw < $nbday; $idw++) { + $dayinloopfromfirstdaytoshow = dol_time_plus_duree($firstdaytoshow, $idw, 'd'); + if ($isavailable[$dayinloopfromfirstdaytoshow]['morning'] && $isavailable[$dayinloopfromfirstdaytoshow]['afternoon']) { + $currentDay = date('l', $dayinloopfromfirstdaytoshow); + $currentDay = 'workinghours_' . strtolower($currentDay); + $workinghoursWeek = $workinghoursArray->{$currentDay} * 60; + } else { + $workinghoursWeek = 0; + } + + $cssweekend = ''; + if ((($idw + 1) < $numstartworkingday) || (($idw + 1) > $numendworkingday)) { // This is a day is not inside the setup of working days, so we use a week-end css. + $cssweekend = 'weekend'; + } + + $tmpday = dol_time_plus_duree($firstdaytoshow, $idw, 'd'); + + $cssonholiday = ''; + if (!$isavailable[$tmpday]['morning'] && !$isavailable[$tmpday]['afternoon']) { + $cssonholiday .= 'onholidayallday '; + } elseif (!$isavailable[$tmpday]['morning']) { + $cssonholiday .= 'onholidaymorning '; + } elseif (!$isavailable[$tmpday]['afternoon']) { + $cssonholiday .= 'onholidayafternoon '; + } + + print ''; + } + if ($nbday == 7) { + print ''; + } + print ''; + + + print ''; + print ''; + if (!empty($arrayfields['timeconsumed']['checked'])) { + print ''; + print ''; + } + + for ($idw = 0; $idw < $nbday; $idw++) { + $dayinloopfromfirstdaytoshow = dol_time_plus_duree($firstdaytoshow, $idw, 'd'); + if ($isavailable[$dayinloopfromfirstdaytoshow]['morning'] && $isavailable[$dayinloopfromfirstdaytoshow]['afternoon']) { + $currentDay = date('l', $dayinloopfromfirstdaytoshow); + $currentDay = 'workinghours_' . strtolower($currentDay); + $workinghoursWeek = $workinghoursArray->{$currentDay} * 60; + } else { + $workinghoursWeek = 0; + } + + $cssweekend = ''; + if ((($idw + 1) < $numstartworkingday) || (($idw + 1) > $numendworkingday)) { // This is a day is not inside the setup of working days, so we use a week-end css. + $cssweekend = 'weekend'; + } + + $tmpday = dol_time_plus_duree($firstdaytoshow, $idw, 'd'); + + $cssonholiday = ''; + if (!$isavailable[$tmpday]['morning'] && !$isavailable[$tmpday]['afternoon']) { + $cssonholiday .= 'onholidayallday '; + } elseif (!$isavailable[$tmpday]['morning']) { + $cssonholiday .= 'onholidaymorning '; + } elseif (!$isavailable[$tmpday]['afternoon']) { + $cssonholiday .= 'onholidayafternoon '; + } + + print ''; + } + if ($nbday == 7) { + print ''; + } + print ''; + + print ''; + print ''; + if (!empty($arrayfields['timeconsumed']['checked'])) { + print ''; + print ''; + } + + for ($idw = 0; $idw < $nbday; $idw++) { + $dayinloopfromfirstdaytoshow = dol_time_plus_duree($firstdaytoshow, $idw, 'd'); + if ($isavailable[$dayinloopfromfirstdaytoshow]['morning'] && $isavailable[$dayinloopfromfirstdaytoshow]['afternoon']) { + $currentDay = date('l', $dayinloopfromfirstdaytoshow); + $currentDay = 'workinghours_' . strtolower($currentDay); + $workinghoursWeek = $workinghoursArray->{$currentDay} * 60; + } else { + $workinghoursWeek = 0; + } + $difftime = $workinghoursWeek - $totalforvisibletasks[$dayinloopfromfirstdaytoshow]; + if ($difftime < 0) { + $morecss = colorStringToArray($conf->global->DOLIPROJECT_EXCEEDED_TIME_SPENT_COLOR); + } else if ($difftime > 0) { + $morecss = colorStringToArray($conf->global->DOLIPROJECT_NOT_EXCEEDED_TIME_SPENT_COLOR); + } else if ($difftime == 0) { + $morecss = colorStringToArray($conf->global->DOLIPROJECT_PERFECT_TIME_SPENT_COLOR); + } + $cssweekend = ''; + if ((($idw + 1) < $numstartworkingday) || (($idw + 1) > $numendworkingday)) { // This is a day is not inside the setup of working days, so we use a week-end css. + $cssweekend = 'weekend'; + } + + $tmpday = dol_time_plus_duree($firstdaytoshow, $idw, 'd'); + + $cssonholiday = ''; + if (!$isavailable[$tmpday]['morning'] && !$isavailable[$tmpday]['afternoon']) { + $cssonholiday .= 'onholidayallday '; + } elseif (!$isavailable[$tmpday]['morning']) { + $cssonholiday .= 'onholidaymorning '; + } elseif (!$isavailable[$tmpday]['afternoon']) { + $cssonholiday .= 'onholidayafternoon '; + } + + print ''; + } + if ($nbday == 7) { + print ''; + } + print ''; + } +} else { + print ''; +} +print "
'; +$searchpicto = $form->showFilterAndCheckAddButtons(0); +print $searchpicto; +print '
'.$langs->trans("Project").''.$langs->trans("ThirdParty").''.$langs->trans("Task").''.$langs->trans("PlannedWorkload").''.$langs->trans("ProgressDeclared").''.$langs->trans("TimeSpent").'
'; + print ''; + print 'Photo'; + print ''.$langs->trans("Everybody").''; + print ''; + print '
'.$langs->trans("TimeSpent").($usertoprocess->firstname ? '
'.$usertoprocess->getNomUrl(-2).''.dol_trunc($usertoprocess->firstname, 10).'' : '').'
'; + print dol_print_date($dayinloopfromfirstdaytoshow, '%a'); + $splitted_date = preg_split('/\//', dol_print_date($dayinloopfromfirstdaytoshow, "day")); + $day = $splitted_date[0]; + $month = $splitted_date[1]; + $year = $splitted_date[2]; + print ' '; + print '
'.dol_print_date($dayinloopfromfirstdaytoshow, 'dayreduceformat').'
'; + print $langs->trans("Total"); + $lastdaytoshow = dol_time_plus_duree($firstdaytoshow, 6, 'd'); + for ($idw = 0; $idw < 7; $idw++) { + $dayinloopfromfirstdaytoshow = dol_time_plus_duree($firstdaytoshow, $idw, 'd'); + if ($isavailable[$dayinloopfromfirstdaytoshow]['morning'] && $isavailable[$dayinloopfromfirstdaytoshow]['afternoon']) { + $currentDay = date('l', $dayinloopfromfirstdaytoshow); + $currentDay = 'workinghours_' . strtolower($currentDay); + $workinghoursWeek += $workinghoursArray->{$currentDay} / 60; + } + } + print ' - '.$langs->trans("ExpectedWorkedHoursWeek", dol_print_date($firstdaytoshow, "dayreduceformat"), dol_print_date($lastdaytoshow, "dayreduceformat")).' : '.price($workinghoursWeek, 1, $langs, 0, 0).''; + print '
'.(($workinghoursWeek != 0) ? convertSecondToTime($workinghoursWeek, 'allhourmin') : '00:00').'
'; + print $langs->trans("OtherFilteredTasks"); + print ''; + $tmpday = dol_time_plus_duree($firstdaytoshow, $idw, 'd'); + $timeonothertasks = ($totalforeachday[$tmpday] - $totalforvisibletasks[$tmpday]); + if ($timeonothertasks) { + print ''; + } + print '
'; + print $langs->trans("Total"); + $CurrentDate = dol_getdate($now); + $currentWeek = getWeekNumber($CurrentDate['mday'], $CurrentDate['mon'], $CurrentDate['year']); + if ($currentWeek == $week) { + $firstDay = date( 'd', $firstdaytoshow); + $currentDay = date( 'd', $now); + $nbday = $currentDay - $firstDay + 1; + } else { + $nbday = 7; + } + + for ($idw = 0; $idw < $nbday; $idw++) { + $dayinloopfromfirstdaytoshow = dol_time_plus_duree($firstdaytoshow, $idw, 'd'); + if ($isavailable[$dayinloopfromfirstdaytoshow]['morning'] && $isavailable[$dayinloopfromfirstdaytoshow]['afternoon']) { + $currentDay = date('l', $dayinloopfromfirstdaytoshow); + $currentDay = 'workinghours_' . strtolower($currentDay); + $workinghoursWeek += $workinghoursArray->{$currentDay} / 60; + } + } + $totalspenttime = $workinghoursWeek; + print ' - '.$langs->trans("SpentWorkedHoursMonth", dol_print_date($firstdaytoshow, "dayreduceformat"), (($nbday == 7) ? dol_print_date($lastdaytoshow, "dayreduceformat") : dol_print_date($now, "dayreduceformat"))).' : '.price($workinghoursWeek, 1, $langs, 0, 0).''; + print '
'.(($workinghoursWeek != 0) ? convertSecondToTime($workinghoursWeek, 'allhourmin') : '00:00').'
'; + print $langs->trans("Total"); + + foreach ($totalforvisibletasks as $task) { + $totalconsumedtime += $task; + } + print ' - '.$langs->trans("ConsumedWorkedHoursMonth", dol_print_date($firstdaytoshow, "dayreduceformat"), (($nbday == 7) ? dol_print_date($lastdaytoshow, "dayreduceformat") : dol_print_date($now, "dayreduceformat"))).' : '.convertSecondToTime($totalconsumedtime, 'allhourmin').''; + print ''.convertSecondToTime($totalconsumedtime, 'allhourmin').'
'.dol_print_date($workinghoursWeek, 'hour').'
'; + print $langs->trans("Total"); + $difftotaltime = $totalspenttime * 60 * 60 - $totalconsumedtime; + if ($difftotaltime < 0) { + $morecss = colorStringToArray($conf->global->DOLIPROJECT_EXCEEDED_TIME_SPENT_COLOR); + } else if ($difftotaltime > 0) { + $morecss = colorStringToArray($conf->global->DOLIPROJECT_NOT_EXCEEDED_TIME_SPENT_COLOR); + } else if ($difftotaltime == 0) { + $morecss = colorStringToArray($conf->global->DOLIPROJECT_PERFECT_TIME_SPENT_COLOR); + } + print ' - '.$langs->trans("DiffSpentAndConsumedWorkedHoursMonth", dol_print_date($firstdaytoshow, "dayreduceformat"), (($nbday == 7) ? dol_print_date($lastdaytoshow, "dayreduceformat") : dol_print_date($now, "dayreduceformat"))).' : '.(($difftotaltime != 0) ? convertSecondToTime(abs($difftotaltime), 'allhourmin') : '00:00').''; + print ''.(($difftotaltime != 0) ? convertSecondToTime(abs($difftotaltime), 'allhourmin') : '00:00').'
'.(($difftime != 0) ? convertSecondToTime(abs($difftime), 'allhourmin') : '00:00').'
'.$langs->trans("NoAssignedTasks").'
"; +print '
'; + +print ''."\n"; + +print $form->buttonsSaveCancel("Save", ''); + +print '
'."\n\n"; + +$modeinput = 'hours'; + +if ($conf->use_javascript_ajax) { + print "\n\n"; + print ''; +} + +// End of page +llxFooter(); +$db->close(); diff --git a/view/workinghours_card.php b/view/workinghours_card.php index 87ee2e3..69f7058 100644 --- a/view/workinghours_card.php +++ b/view/workinghours_card.php @@ -86,29 +86,6 @@ $object->schedule_saturday = GETPOST('schedule_saturday', 'string'); $object->schedule_sunday = GETPOST('schedule_sunday', 'string'); - if (((int) GETPOST('workinghours_monday')) == 0) { - setEventMessages($langs->trans('WrongWorkingHoursFormat', $langs->trans('MondayWorkingHours')), null, 'errors'); - } - if (((int) GETPOST('workinghours_tuesday')) == 0) { - setEventMessages($langs->trans('WrongWorkingHoursFormat', $langs->trans('TuesdayWorkingHours')), null, 'errors'); - } - if (((int) GETPOST('workinghours_wednesday')) == 0) { - setEventMessages($langs->trans('WrongWorkingHoursFormat', $langs->trans('WednesdayWorkingHours')), null, 'errors'); - } - if (((int) GETPOST('workinghours_thursday')) == 0) { - setEventMessages($langs->trans('WrongWorkingHoursFormat', $langs->trans('ThursdayWorkingHours')), null, 'errors'); - } - if (((int) GETPOST('workinghours_friday')) == 0) { - setEventMessages($langs->trans('WrongWorkingHoursFormat', $langs->trans('FridayWorkingHours')), null, 'errors'); - } - if (((int) GETPOST('workinghours_saturday')) == 0) { - setEventMessages($langs->trans('WrongWorkingHoursFormat', $langs->trans('SaturdayWorkingHours')), null, 'errors'); - } - if (((int) GETPOST('workinghours_sunday')) == 0) { - setEventMessages($langs->trans('WrongWorkingHoursFormat', $langs->trans('SundayWorkingHours')), null, 'errors'); - } - - $object->workinghours_monday = GETPOST('workinghours_monday', 'integer'); $object->workinghours_tuesday = GETPOST('workinghours_tuesday', 'integer'); $object->workinghours_wednesday = GETPOST('workinghours_wednesday', 'integer'); @@ -178,72 +155,87 @@ print '' . $langs->trans("Day") . '' . $langs->trans("Schedule") . ''. $langs->trans('WorkingHours(min)') .'' . "\n"; print ''; -print $form->textwithpicto($langs->trans("Monday"), $langs->trans("WorkingHoursFormatDesc")); +print $form->textwithpicto($langs->trans("Monday"), ''); print ''; print 'global->MAIN_OPTIMIZEFORTEXTBROWSER) ? '' : ' autofocus="autofocus"') . '>'; +print $form->textwithpicto('', $langs->trans("OpeningHoursFormatDesc")); print ''; + print ''; -print 'global->MAIN_OPTIMIZEFORTEXTBROWSER) ? '' : ' autofocus="autofocus"') . '>'; +print 'global->MAIN_OPTIMIZEFORTEXTBROWSER) ? '' : ' autofocus="autofocus"') . '>'; +print $form->textwithpicto('', $langs->trans("WorkingHoursFormatDesc")); print ''; print '' . "\n"; print ''; -print $form->textwithpicto($langs->trans("Tuesday"), $langs->trans("WorkingHoursFormatDesc")); +print $form->textwithpicto($langs->trans("Tuesday"),''); print ''; print 'global->MAIN_OPTIMIZEFORTEXTBROWSER) ? '' : ' autofocus="autofocus"') . '>'; +print $form->textwithpicto('', $langs->trans("OpeningHoursFormatDesc")); print ''; print ''; -print 'global->MAIN_OPTIMIZEFORTEXTBROWSER) ? '' : ' autofocus="autofocus"') . '>'; +print 'global->MAIN_OPTIMIZEFORTEXTBROWSER) ? '' : ' autofocus="autofocus"') . '>'; +print $form->textwithpicto('', $langs->trans("WorkingHoursFormatDesc")); print ''; print '' . "\n"; print ''; -print $form->textwithpicto($langs->trans("Wednesday"), $langs->trans("WorkingHoursFormatDesc")); +print $form->textwithpicto($langs->trans("Wednesday"),''); print ''; print 'global->MAIN_OPTIMIZEFORTEXTBROWSER) ? '' : ' autofocus="autofocus"') . '>'; +print $form->textwithpicto('', $langs->trans("OpeningHoursFormatDesc")); print ''; print ''; -print 'global->MAIN_OPTIMIZEFORTEXTBROWSER) ? '' : ' autofocus="autofocus"') . '>'; +print 'global->MAIN_OPTIMIZEFORTEXTBROWSER) ? '' : ' autofocus="autofocus"') . '>'; +print $form->textwithpicto('', $langs->trans("WorkingHoursFormatDesc")); print ''; print '' . "\n"; print ''; -print $form->textwithpicto($langs->trans("Thursday"), $langs->trans("WorkingHoursFormatDesc")); +print $form->textwithpicto($langs->trans("Thursday"),''); print ''; print 'global->MAIN_OPTIMIZEFORTEXTBROWSER) ? '' : ' autofocus="autofocus"') . '>'; +print $form->textwithpicto('', $langs->trans("OpeningHoursFormatDesc")); print ''; print ''; -print 'global->MAIN_OPTIMIZEFORTEXTBROWSER) ? '' : ' autofocus="autofocus"') . '>'; +print 'global->MAIN_OPTIMIZEFORTEXTBROWSER) ? '' : ' autofocus="autofocus"') . '>'; +print $form->textwithpicto('', $langs->trans("WorkingHoursFormatDesc")); print ''; print '' . "\n"; print ''; -print $form->textwithpicto($langs->trans("Friday"), $langs->trans("WorkingHoursFormatDesc")); +print $form->textwithpicto($langs->trans("Friday"), ''); print ''; print 'global->MAIN_OPTIMIZEFORTEXTBROWSER) ? '' : ' autofocus="autofocus"') . '>'; +print $form->textwithpicto('', $langs->trans("OpeningHoursFormatDesc")); print ''; print ''; -print 'global->MAIN_OPTIMIZEFORTEXTBROWSER) ? '' : ' autofocus="autofocus"') . '>'; +print 'global->MAIN_OPTIMIZEFORTEXTBROWSER) ? '' : ' autofocus="autofocus"') . '>'; +print $form->textwithpicto('', $langs->trans("WorkingHoursFormatDesc")); print ''; print '' . "\n"; print ''; -print $form->textwithpicto($langs->trans("Saturday"), $langs->trans("WorkingHoursFormatDesc")); +print $form->textwithpicto($langs->trans("Saturday"), ''); print ''; print 'global->MAIN_OPTIMIZEFORTEXTBROWSER) ? '' : ' autofocus="autofocus"') . '>'; +print $form->textwithpicto('', $langs->trans("OpeningHoursFormatDesc")); print ''; print ''; -print 'global->MAIN_OPTIMIZEFORTEXTBROWSER) ? '' : ' autofocus="autofocus"') . '>'; +print 'global->MAIN_OPTIMIZEFORTEXTBROWSER) ? '' : ' autofocus="autofocus"') . '>'; +print $form->textwithpicto('', $langs->trans("WorkingHoursFormatDesc")); print ''; print '' . "\n"; print ''; -print $form->textwithpicto($langs->trans("Sunday"), $langs->trans("WorkingHoursFormatDesc")); +print $form->textwithpicto($langs->trans("Sunday"),''); print ''; print 'global->MAIN_OPTIMIZEFORTEXTBROWSER) ? '' : ' autofocus="autofocus"') . '>'; +print $form->textwithpicto('', $langs->trans("OpeningHoursFormatDesc")); print ''; print ''; -print 'global->MAIN_OPTIMIZEFORTEXTBROWSER) ? '' : ' autofocus="autofocus"') . '>'; +print 'global->MAIN_OPTIMIZEFORTEXTBROWSER) ? '' : ' autofocus="autofocus"') . '>'; +print $form->textwithpicto('', $langs->trans("WorkingHoursFormatDesc")); print ''; print '' . "\n";