diff --git a/ChangeLog.md b/ChangeLog.md index 9159931..d089f51 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -1,10 +1,13 @@ # CHANGELOG MODULE TIMESHEETWEEK FOR [DOLIBARR ERP CRM](https://www.dolibarr.org) -## Non publié +## 1.0 +- Ajout du statut "Scellée" / "Sealed" et des permissions associées. +- Initial version. - Ajout des compteurs de zones et de paniers dans l'entête des feuilles hebdomadaires. - Recalcul automatique des compteurs de zones et de paniers à chaque enregistrement d'une feuille hebdomadaire. - Affichage des compteurs de zones et de paniers dans la liste des feuilles hebdomadaires. +- Ligne de total dans la liste hebdomadaire pour additionner heures, heures supplémentaires, zones et paniers, plus affichage de la date de validation. - Affichage du libellé "Zone" devant chaque sélecteur quotidien. - Ajout de la traduction "Meals" en "Repas". - Ajout du script de mise à jour SQL (`sql/update_all.sql`) pour créer les compteurs hebdomadaires sur les données existantes. @@ -19,8 +22,8 @@ - Filtre Multicompany de l'environnement aligné sur le multiselect natif dans la liste / Multicompany environment filter aligned with the native multiselect in the list. - Réorganisation des options de partage Multicompany pour séparer les feuilles et la numérotation avec les pictogrammes adaptés / Reorganised Multicompany sharing options to separate sheets and numbering with suitable pictograms. - Inversion des couleurs des statuts "Scellée" et "Refusée" pour reprendre les repères Dolibarr / Swapped colors of "Sealed" and "Refused" statuses to match Dolibarr visual cues. - -## 1.0 - -- Ajout du statut "Scellée" / "Sealed" et des permissions associées. -- Initial version +- Refonte de la page de configuration en suivant le modèle DiffusionPlans pour harmoniser la gestion des masques de numérotation et des modèles PDF avec Dolibarr / Setup page redesigned following the DiffusionPlans template to align numbering masks and PDF templates with Dolibarr standards. +- Activation des masques de numérotation via des commutateurs Dolibarr natifs / Numbering masks activated through native Dolibarr switches. +- Ajout d'un onglet « À propos » récapitulant version, éditeur et ressources du module / Added an « À propos » tab listing version, publisher and module resources. +- README bilingue (FR/EN) entièrement mis à jour / Fully refreshed bilingual (FR/EN) README. +- Notification d'approbation en français corrigée pour utiliser l'accent « approuvée » sans entité HTML / French approval notification updated to use the plain "approuvée" accent instead of an HTML entity. diff --git a/README.md b/README.md index bc9e0a9..86fcf41 100644 --- a/README.md +++ b/README.md @@ -1,108 +1,84 @@ # TIMESHEETWEEK FOR [DOLIBARR ERP & CRM](https://www.dolibarr.org) -## Features - -- Statut "Scellée" / "Sealed" pour verrouiller les feuilles approuvées. -- Redirection automatique vers la feuille existante en cas de doublon / Automatic redirect to the existing sheet when a duplicate is requested. - -Description of the module... - -- Suivi des compteurs hebdomadaires de zones et de paniers pour chaque feuille de temps. -- Recalcul automatique des compteurs de zones et de paniers lors de chaque enregistrement. -- Affichage des compteurs de zones et de paniers dans la liste des feuilles hebdomadaires. -- Création rapide d'une feuille d'heures depuis le raccourci "Ajouter" du menu supérieur. -- Compatibilité Multicompany pour partager les feuilles de temps et leur numérotation / Multicompany compatibility to share weekly timesheets and their numbering. -- Inscription automatique de la configuration Multicompany lors de l'activation et nettoyage lors de la désactivation / Automatic registration of the Multicompany configuration on activation and cleanup on deactivation. -- Affichage de l'entité dans la liste et la fiche lorsqu'on utilise Multicompany, avec badge natif sous la référence lorsque l'entité diffère / Display entity information in list and card when using Multicompany, with a native badge under the reference when the entity differs. -- Sécurisation des requêtes SQL sur les feuilles et lignes par entité pour Multicompany / Secured SQL queries on sheets and lines with entity scoping for Multicompany. -- Filtre multisélection de l'environnement respectant l'interface native de Dolibarr en Multicompany / Multiselect environment filter following Dolibarr native Multicompany interface. -- Réorganisation des options Multicompany pour distinguer partage et numérotation avec les pictogrammes natifs / Reorganised Multicompany sharing options to split sharing and numbering with native pictograms. -- Inversion des couleurs des statuts "Scellée" et "Refusée" pour correspondre au code couleur Dolibarr / Swapped colors of "Sealed" and "Refused" statuses to follow Dolibarr color codes. -- Harmonisation du filtre de semaine de la liste avec la fiche via le sélecteur ISO / Harmonized week filter between list and card using the ISO selector. -- Filtre de semaine en multi-sélection pour combiner plusieurs périodes directement depuis la liste / Multi-select week filter to combine several periods directly from the list. - - +## 🇫🇷 Présentation -Other external modules are available on [Dolistore.com](https://www.dolistore.com). - -## Translations - -Translations can be completed manually by editing files in the module directories under `langs`. - - +TimesheetWeek ajoute une gestion hebdomadaire des feuilles de temps fidèle à l'expérience Dolibarr. Le module renforce les cycles de validation, propose des compteurs opérationnels (zones, paniers, heures supplémentaires) et respecte les standards graphiques pour les écrans administratifs et les modèles de documents. +### Fonctionnalités principales -## Installation +- Statut « Scellée » pour verrouiller les feuilles approuvées et empêcher toute modification ultérieure, avec les permissions associées. +- Redirection automatique vers la feuille existante en cas de tentative de doublon afin d'éviter les saisies multiples. +- Suivi des compteurs hebdomadaires de zones et de paniers directement sur les feuilles et recalcul automatique à chaque enregistrement. +- Affichage des compteurs dans la liste hebdomadaire et ajout du libellé « Zone » sur chaque sélecteur quotidien pour clarifier la saisie. +- Ligne de total en bas de la liste hebdomadaire pour additionner heures, zones, paniers et afficher la colonne de date de validation. +- Création rapide d'une feuille d'heures via le raccourci « Ajouter » du menu supérieur. +- Compatibilité Multicompany pour partager les feuilles et leur numérotation, avec options de partage dédiées et filtres multi-sélection harmonisés à l'interface native. +- Affichage de l'entité dans les listes et fiches en environnement Multicompany, accompagné d'un badge visuel sous la référence lorsque l'entité diffère. +- Sécurisation des requêtes SQL par entité et filtres multi-entités alignés sur les pratiques Dolibarr. +- Harmonisation du filtre de semaine avec un sélecteur ISO multi-sélection permettant de regrouper plusieurs périodes. +- Inversion des couleurs des statuts « Scellée » et « Refusée » pour respecter les codes couleur Dolibarr. +- Refonte complète de la page de configuration inspirée du module DiffusionPlans pour gérer les masques de numérotation et les modèles PDF selon les codes graphiques Dolibarr. +- Sélection du masque de numérotation via des commutateurs natifs directement depuis la configuration Dolibarr. +- Onglet « À propos » dédié pour retrouver la version, l'éditeur et les ressources utiles du module. +- README bilingue (FR/EN) pour faciliter le déploiement et l'adoption. -Prerequisites: You must have Dolibarr ERP & CRM software installed. You can download it from [Dolibarr.org](https://www.dolibarr.org). -You can also get a ready-to-use instance in the cloud from https://saas.dolibarr.org +### Installation +1. **Pré-requis** : disposer d'une instance Dolibarr fonctionnelle. Les versions supportées correspondent à celles indiquées dans le fichier `modTimesheetWeek.class.php`. +2. **Déploiement via l'interface** : depuis `Accueil > Configuration > Modules > Déployer un module externe`, importez l'archive `module_timesheetweek-x.y.z.zip` téléchargée sur [Dolistore](https://www.dolistore.com) ou obtenue via votre circuit de diffusion. +3. **Déploiement manuel** : copiez le répertoire du module dans `htdocs/custom/timesheetweek`, puis purgez le cache des modules depuis l'administration Dolibarr. +4. **Activation** : connectez-vous en tant que super administrateur, activez le module dans `Configuration > Modules > Projets/Temps`, puis exécutez le script `sql/update_all.sql` pour ajouter les compteurs aux données existantes. -### From the ZIP file and GUI interface +### Configuration -If the module is a ready-to-deploy zip file, so with a name `module_xxx-version.zip` (e.g., when downloading it from a marketplace like [Dolistore](https://www.dolistore.com)), -go to menu `Home> Setup> Modules> Deploy external module` and upload the zip file. +- Rendez-vous dans `Configuration > Modules > TimesheetWeek` pour activer le masque de numérotation via les commutateurs natifs et sélectionner les modèles PDF souhaités. +- Ajustez les options Multicompany via les onglets de configuration dédiés si vous partagez les feuilles de temps entre plusieurs entités. +- L'onglet « À propos » récapitule la version du module, l'éditeur et les liens de support. - +1. **Prerequisites**: a running Dolibarr instance that matches the compatibility range declared in `modTimesheetWeek.class.php`. +2. **Deploy from the GUI**: go to `Home > Setup > Modules > Deploy external module` and upload the `module_timesheetweek-x.y.z.zip` archive from [Dolistore](https://www.dolistore.com) or your distribution channel. +3. **Manual deployment**: copy the module directory into `htdocs/custom/timesheetweek`, then refresh the module cache from Dolibarr's administration area. +4. **Activation**: log in as a super administrator, enable the module from `Setup > Modules > Projects/Timesheets`, and run the `sql/update_all.sql` script so legacy timesheets gain the new counters. - - -### Final steps - -Using your browser: - - - Log into Dolibarr as a super-administrator - - Go to "Setup"> "Modules" - - You should now be able to find and enable the module - - EN: Run the `sql/update_all.sql` script to ensure older timesheets receive the new counters - - FR: Exécutez le script `sql/update_all.sql` pour que les feuilles existantes profitent des nouveaux compteurs +### Translations +Translation sources are stored under `langs/en_US` and `langs/fr_FR`. Please keep both locales aligned for every new string to stay compatible with Dolibarr's translation workflow. +Other external modules are available on [Dolistore.com](https://www.dolistore.com). ## Licenses @@ -112,4 +88,4 @@ GPLv3 or (at your option) any later version. See file COPYING for more informati ### Documentation -All texts and readme's are licensed under [GFDL](https://www.gnu.org/licenses/fdl-1.3.en.html). +All texts and README files are licensed under [GFDL](https://www.gnu.org/licenses/fdl-1.3.en.html). diff --git a/admin/about.php b/admin/about.php new file mode 100644 index 0000000..164d821 --- /dev/null +++ b/admin/about.php @@ -0,0 +1,85 @@ +loadLangs(array('admin', 'timesheetweek@timesheetweek')); + +// EN: Only Dolibarr administrators can display the about page. +// FR: Seuls les administrateurs Dolibarr peuvent afficher la page À propos. +if (empty($user->admin)) { + accessforbidden(); +} + +$moduleDescriptor = new modTimesheetWeek($db); +$title = $langs->trans('TimesheetWeekAbout'); +$helpurl = ''; + +llxHeader('', $title, $helpurl); + +$head = timesheetweekAdminPrepareHead(); +// EN: Render the admin tabs with the bookcal pictogram to stay consistent with the setup header. +// FR: Affiche les onglets d'administration avec le pictogramme bookcal pour rester cohérent avec la configuration. +print dol_get_fiche_head($head, 'about', $title, -1, 'bookcal@timesheetweek'); + +print load_fiche_titre($langs->trans('TimesheetWeekAbout'), '', 'info'); +print '
'.$langs->trans('TimesheetWeekAboutPage').'
'; +print '
'; + +print '
'; + +// EN: Present core module information in a dedicated summary table. +// FR: Présente les informations principales du module dans un tableau récapitulatif. +print '
'; +print '
'; +print ''; +print ''; +print ''; +print ''; +print ''; +print ''; +print '
'.$langs->trans('TimesheetWeekAboutGeneral').'
'.$langs->trans('TimesheetWeekAboutVersion').''.dol_escape_htmltag($moduleDescriptor->version).'
'.$langs->trans('TimesheetWeekAboutFamily').''.dol_escape_htmltag($moduleDescriptor->family).'
'.$langs->trans('TimesheetWeekAboutDescription').''.dol_escape_htmltag($langs->trans($moduleDescriptor->description)).'
'.$langs->trans('TimesheetWeekAboutMaintainer').''.dol_escape_htmltag($moduleDescriptor->editor_name).'
'; +print '
'; +print '
'; + +// EN: List documentation and support resources with direct links. +// FR: Liste les ressources de documentation et de support avec des liens directs. +print '
'; +print '
'; +print ''; +print ''; +print ''; +print ''; +print ''; +print '
'.$langs->trans('TimesheetWeekAboutResources').'
'.$langs->trans('TimesheetWeekAboutDocumentation').''.$langs->trans('TimesheetWeekAboutDocumentationLink').'
'.$langs->trans('TimesheetWeekAboutSupport').''.dol_escape_htmltag($langs->trans('TimesheetWeekAboutSupportValue')).'
'.$langs->trans('TimesheetWeekAboutContact').''.dol_escape_htmltag($moduleDescriptor->editor_url).'
'; +print '
'; +print '
'; + +print '
'; + +print dol_get_fiche_end(); + +llxFooter(); +$db->close(); diff --git a/admin/setup.php b/admin/setup.php index 637bbc6..15659d2 100644 --- a/admin/setup.php +++ b/admin/setup.php @@ -22,6 +22,8 @@ * \brief Setup page for TimesheetWeek module (numbering, options...) */ +// EN: Load Dolibarr environment with fallback paths. +// FR: Charge l'environnement Dolibarr en testant les chemins possibles. $res = 0; if (!$res && file_exists(__DIR__.'/../main.inc.php')) { $res = require_once __DIR__.'/../main.inc.php'; @@ -39,73 +41,216 @@ require_once DOL_DOCUMENT_ROOT.'/core/lib/admin.lib.php'; require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php'; dol_include_once('/timesheetweek/lib/timesheetweek.lib.php'); +dol_include_once('/timesheetweek/class/timesheetweek.class.php'); +// EN: Load translation files required for the configuration page. +// FR: Charge les fichiers de traduction nécessaires à la page de configuration. $langs->loadLangs(array('admin', 'other', 'timesheetweek@timesheetweek')); +// EN: Only administrators can access the setup. +// FR: Seuls les administrateurs peuvent accéder à la configuration. if (empty($user->admin)) { accessforbidden(); } +// EN: Read HTTP parameters once so we can re-use them further down. +// FR: Lit les paramètres HTTP une seule fois pour les réutiliser ensuite. $action = GETPOST('action', 'aZ09'); -// FR: On accepte les caractères natifs des classes Dolibarr (underscore, chiffres...). -// EN: Allow native Dolibarr class names with underscores and digits. $value = GETPOST('value', 'alphanohtml'); +$token = GETPOST('token', 'alphanohtml'); -if (!function_exists('timesheetweek_enable_document_model')) { - /** - * Enable a document model for TimesheetWeek. - * - * @param string $model - * @return int - */ - function timesheetweek_enable_document_model($model) - { - global $db, $conf; - - if (empty($model)) { - return 0; - } +// EN: Helper to enable a PDF model in the database. +// FR: Aide pour activer un modèle PDF dans la base. +function timesheetweekEnableDocumentModel($model) +{ + global $db, $conf; - $sql = 'INSERT INTO '.MAIN_DB_PREFIX."document_model (nom, type, entity) VALUES ('".$db->escape($model)."', 'timesheetweek', ".((int) $conf->entity).')'; - $resql = $db->query($sql); - if ($resql) { - return 1; - } + if (empty($model)) { + return 0; + } + + $sql = 'INSERT INTO '.MAIN_DB_PREFIX."document_model (nom, type, entity) VALUES ('".$db->escape($model)."', 'timesheetweek', ".((int) $conf->entity).')'; + $resql = $db->query($sql); + if ($resql) { + return 1; + } + + // EN: Ignore duplicate entries silently because the model is already stored. + // FR: Ignore les doublons car le modèle est déjà enregistré. + if ($db->lasterrno() && strpos($db->lasterror(), 'Duplicate') !== false) { + return 1; + } + + return -1; +} + +// EN: Helper to disable a PDF model from the database. +// FR: Aide pour désactiver un modèle PDF dans la base. +function timesheetweekDisableDocumentModel($model) +{ + global $db, $conf; + + if (empty($model)) { + return 0; + } + + $sql = 'DELETE FROM '.MAIN_DB_PREFIX."document_model WHERE nom='".$db->escape($model)."' AND type='timesheetweek' AND entity IN (0, ".((int) $conf->entity).')'; + $resql = $db->query($sql); + if ($resql) { + return ($db->affected_rows($resql) >= 0) ? 1 : 0; + } + + return -1; +} - // Ignore duplicate errors silently (model already enabled) - if ($db->lasterrno() && strpos($db->lasterror(), 'Duplicate') !== false) { - return 1; +// EN: Build the list of numbering modules available for the module. +// FR: Construit la liste des modules de numérotation disponibles pour le module. +function timesheetweekListNumberingModules(array $directories, Translate $langs, TimesheetWeek $sample, $selected) +{ + global $db; + + $modules = array(); + + foreach ($directories as $reldir) { + $dir = dol_buildpath($reldir.'core/modules/timesheetweek/'); + if (!is_dir($dir)) { + continue; } - return -1; + $files = dol_dir_list($dir, 'files', 0, '^mod_.*\.php$'); + foreach ($files as $fileinfo) { + $file = $fileinfo['name']; + $classname = preg_replace('/\.php$/', '', $file); + + require_once $dir.$file; + if (!class_exists($classname)) { + continue; + } + + $module = new $classname($db); + + $label = !empty($module->name) ? $module->name : $classname; + if ($label && $langs->transnoentitiesnoconv($label) !== $label) { + $label = $langs->trans($label); + } + + $description = ''; + if (method_exists($module, 'info')) { + $description = $module->info($langs); + } elseif (!empty($module->description)) { + $description = $module->description; + } + if ($description && $langs->transnoentitiesnoconv($description) !== $description) { + $description = $langs->trans($description); + } + + $example = ''; + if (method_exists($module, 'getExample')) { + $example = $module->getExample($sample); + } + + $canBeActivated = true; + $activationError = ''; + if (method_exists($module, 'canBeActivated')) { + $canBeActivated = (bool) $module->canBeActivated($sample); + if (!$canBeActivated && !empty($module->error)) { + $activationError = $module->error; + } + } + + $modules[] = array( + 'classname' => $classname, + 'label' => $label, + 'description' => $description, + 'example' => $example, + 'active' => ($selected === $classname), + 'can_be_activated' => $canBeActivated, + 'activation_error' => $activationError, + ); + } } + + usort($modules, function ($a, $b) { + return strcasecmp($a['label'], $b['label']); + }); + + return $modules; } -if (!function_exists('timesheetweek_disable_document_model')) { - /** - * Disable a document model for TimesheetWeek. - * - * @param string $model - * @return int - */ - function timesheetweek_disable_document_model($model) - { - global $db, $conf; - - if (empty($model)) { - return 0; +// EN: Build the list of available PDF models for the module. +// FR: Construit la liste des modèles PDF disponibles pour le module. +function timesheetweekListDocumentModels(array $directories, Translate $langs, array $enabled, $default) +{ + global $db; + + $models = array(); + + foreach ($directories as $reldir) { + $dir = dol_buildpath($reldir.'core/modules/timesheetweek/doc/'); + if (!is_dir($dir)) { + continue; } - $sql = 'DELETE FROM '.MAIN_DB_PREFIX."document_model WHERE nom='".$db->escape($model)."' AND type='timesheetweek' AND entity IN (0, ".((int) $conf->entity).')'; - $resql = $db->query($sql); - if ($resql) { - return ($db->affected_rows($resql) >= 0) ? 1 : 0; + $files = dol_dir_list($dir, 'files', 0, '^[a-z0-9_]+\.php$'); + foreach ($files as $fileinfo) { + $file = $fileinfo['name']; + $classname = preg_replace('/\.php$/', '', $file); + + require_once $dir.$file; + if (!class_exists($classname)) { + continue; + } + + $module = new $classname($db); + if (empty($module->type) || $module->type !== 'pdf') { + continue; + } + + $name = !empty($module->name) ? $module->name : $classname; + $label = $name; + if (!empty($module->name) && $langs->transnoentitiesnoconv($module->name) !== $module->name) { + $label = $langs->trans($module->name); + } + + $description = ''; + if (!empty($module->description)) { + $description = $module->description; + } + if ($description && $langs->transnoentitiesnoconv($description) !== $description) { + $description = $langs->trans($description); + } + + $models[] = array( + 'name' => $name, + 'classname' => $classname, + 'label' => $label, + 'description' => $description, + 'is_enabled' => !empty($enabled[$name]), + 'is_default' => ($default === $name), + 'type' => $module->type, + ); } + } + + usort($models, function ($a, $b) { + return strcasecmp($a['label'], $b['label']); + }); + + return $models; +} - return -1; +// EN: Verify CSRF token when the request changes the configuration. +// FR: Vérifie le jeton CSRF lorsque la requête modifie la configuration. +if (in_array($action, array('setmodule', 'setdoc', 'setdocmodel', 'delmodel'), true)) { + if (function_exists('dol_verify_token')) { + if (empty($token) || dol_verify_token($token) <= 0) { + accessforbidden(); + } } } +// EN: Persist the chosen numbering module. +// FR: Enregistre le module de numérotation choisi. if ($action === 'setmodule' && !empty($value)) { $result = dolibarr_set_const($db, 'TIMESHEETWEEK_ADDON', $value, 'chaine', 0, '', $conf->entity); if ($result > 0) { @@ -115,8 +260,10 @@ function timesheetweek_disable_document_model($model) } } +// EN: Set the default PDF model while ensuring the model is enabled. +// FR: Définit le modèle PDF par défaut tout en s'assurant qu'il est activé. if ($action === 'setdoc' && !empty($value)) { - $res = timesheetweek_enable_document_model($value); + $res = timesheetweekEnableDocumentModel($value); if ($res > 0) { $res = dolibarr_set_const($db, 'TIMESHEETWEEK_ADDON_PDF', $value, 'chaine', 0, '', $conf->entity); } @@ -127,8 +274,10 @@ function timesheetweek_disable_document_model($model) } } +// EN: Activate a PDF model without making it the default. +// FR: Active un modèle PDF sans le définir comme défaut. if ($action === 'setdocmodel' && !empty($value)) { - $res = timesheetweek_enable_document_model($value); + $res = timesheetweekEnableDocumentModel($value); if ($res > 0) { setEventMessages($langs->trans('ModelEnabled', $value), null, 'mesgs'); } else { @@ -136,8 +285,10 @@ function timesheetweek_disable_document_model($model) } } +// EN: Disable a PDF model and remove the default flag if needed. +// FR: Désactive un modèle PDF et supprime le statut par défaut si nécessaire. if ($action === 'delmodel' && !empty($value)) { - $res = timesheetweek_disable_document_model($value); + $res = timesheetweekDisableDocumentModel($value); if ($res > 0) { if ($value === getDolGlobalString('TIMESHEETWEEK_ADDON_PDF')) { dolibarr_del_const($db, 'TIMESHEETWEEK_ADDON_PDF', $conf->entity); @@ -148,9 +299,33 @@ function timesheetweek_disable_document_model($model) } } -$selected = getDolGlobalString('TIMESHEETWEEK_ADDON', 'mod_timesheetweek_standard'); -$defaultpdf = getDolGlobalString('TIMESHEETWEEK_ADDON_PDF', 'standard'); -$dirmodels = array_merge(array('/'), (array) $conf->modules_parts['models']); +// EN: Read the selected options so we can highlight them in the UI. +// FR: Lit les options sélectionnées pour les mettre en avant dans l'interface. +$selectedNumbering = getDolGlobalString('TIMESHEETWEEK_ADDON', 'mod_timesheetweek_standard'); +$defaultPdf = getDolGlobalString('TIMESHEETWEEK_ADDON_PDF', 'standard'); +$directories = array_merge(array('/'), (array) $conf->modules_parts['models']); + +// EN: Prepare a lightweight object to test numbering module activation. +// FR: Prépare un objet léger pour tester l'activation des modules de numérotation. +$sampleTimesheet = new TimesheetWeek($db); + +// EN: Fetch the enabled document models from the database. +// FR: Récupère les modèles de documents activés depuis la base. +$enabledModels = array(); +$sql = 'SELECT nom FROM '.MAIN_DB_PREFIX."document_model WHERE type='timesheetweek' AND entity IN (0, ".((int) $conf->entity).')'; +$resql = $db->query($sql); +if ($resql) { + while ($obj = $db->fetch_object($resql)) { + $enabledModels[$obj->nom] = 1; + } + $db->free($resql); +} + +// EN: Build the metadata arrays used by the HTML rendering below. +// FR: Construit les tableaux de métadonnées utilisés par l'affichage HTML ci-dessous. +$numberingModules = timesheetweekListNumberingModules($directories, $langs, $sampleTimesheet, $selectedNumbering); +$documentModels = timesheetweekListDocumentModels($directories, $langs, $enabledModels, $defaultPdf); +$pageToken = function_exists('newToken') ? newToken() : ''; $title = $langs->trans('ModuleSetup', 'TimesheetWeek'); $helpurl = ''; @@ -158,112 +333,76 @@ function timesheetweek_disable_document_model($model) llxHeader('', $title, $helpurl); $head = timesheetweekAdminPrepareHead(); -print dol_get_fiche_head($head, 'settings', $title, -1, 'timesheetweek@timesheetweek'); +// EN: Render the admin header with the bookcal pictogram to match the module identity. +// FR: Affiche l'en-tête d'administration avec le pictogramme bookcal pour refléter l'identité du module. +print dol_get_fiche_head($head, 'settings', $title, -1, 'bookcal@timesheetweek'); print load_fiche_titre($langs->trans('TimesheetWeekSetup'), '', 'bookcal@timesheetweek'); print '
'.$langs->trans('TimesheetWeekSetupPage').'
'; print '
'; -print '
'; -$formToken = newToken(); -print ''; -print ''; +print '
'; + +// EN: Display the numbering modules with switch-based activation instead of radios. +// FR: Affiche les modules de numérotation avec des commutateurs plutôt qu'avec des radios. +print '
'.$langs->trans('TimesheetWeekNumberingHelp').'
'; print '
'; print ''; print ''; print ''; print ''; +print ''; print ''; print ''; -$found = 0; -foreach ($dirmodels as $reldir) { - $dir = dol_buildpath($reldir.'core/modules/timesheetweek/'); - if (!is_dir($dir)) { - continue; - } - - $filelist = dol_dir_list($dir, 'files', 0, '^mod_.*\.php$'); - foreach ($filelist as $fileinfo) { - $file = $fileinfo['name']; - $classname = preg_replace('/\.php$/', '', $file); - - require_once $dir.$file; - if (!class_exists($classname)) { - continue; - } - - try { - $module = new $classname($db); - } catch (Throwable $e) { - continue; - } - - $found++; - $isActive = ($selected === $classname); - - $label = !empty($module->name) ? $module->name : $classname; - if ($label && $langs->transnoentitiesnoconv($label) !== $label) { - $label = $langs->trans($label); - } - - $desc = ''; - if (method_exists($module, 'info')) { - try { - $desc = $module->info($langs); - } catch (Throwable $e) { - $desc = ''; - } - } elseif (!empty($module->description)) { - $desc = $module->description; - } elseif (!empty($module->desc)) { - $desc = $module->desc; - } - - print ''; - print ''; - - if (!empty($desc)) { - $descIsPlainText = ($desc === strip_tags($desc)); - $descHtml = $descIsPlainText ? dol_escape_htmltag($desc) : $desc; - } else { - $descHtml = ' '; - } +if (count($numberingModules) === 0) { + print ''; +} - print ''; +foreach ($numberingModules as $moduleInfo) { + $desc = $moduleInfo['description']; + $descIsPlainText = ($desc === strip_tags($desc)); + $descHtml = $descIsPlainText ? dol_escape_htmltag($desc) : $desc; - print ''; - print ''; + print ''; + print ''; + if (!$moduleInfo['can_be_activated'] && !empty($moduleInfo['activation_error'])) { + print '
'.dol_escape_htmltag($moduleInfo['activation_error']).''; + } + print ''; + + print ''; + print ''; + + // EN: Render the activation toggle that selects the numbering model with CSRF protection. + // FR: Affiche le commutateur d’activation qui sélectionne le modèle de numérotation avec protection CSRF. + print ''; + print ''; } print '
'.$langs->trans('Name').''.$langs->trans('Description').''.$langs->trans('Example').''.$langs->trans('Status').'
'; - print ''; - print '
'.$langs->trans('TimesheetWeekNumberingEmpty').'
'.$descHtml.''; - print img_picto($isActive ? $langs->trans('Enabled') : $langs->trans('Disabled'), $isActive ? 'status1' : 'status0'); - print '
'; + print dol_escape_htmltag($moduleInfo['label']); + if ($moduleInfo['classname'] !== $moduleInfo['label']) { + print ' ('.dol_escape_htmltag($moduleInfo['classname']).')'; } -} - -if (!$found) { - print '
'.$langs->trans('NoRecordFound').'
'.(!empty($descHtml) ? $descHtml : ' ').''.(!empty($moduleInfo['example']) ? dol_escape_htmltag($moduleInfo['example']) : ' ').''; + if ($moduleInfo['active']) { + print img_picto($langs->trans('Enabled'), 'switch_on'); + } elseif ($moduleInfo['can_be_activated']) { + $url = $_SERVER['PHP_SELF'].'?action=setmodule&value='.urlencode($moduleInfo['classname']).'&token='.$pageToken; + print ''.img_picto($langs->trans('TimesheetWeekNumberingActivate'), 'switch_off').''; + } else { + print img_picto($langs->trans('Disabled'), 'switch_off'); + } + print '
'; print '
'; -print '
'; -print ''; print '
'; -print ''; - print '
'; print load_fiche_titre($langs->trans('TimesheetWeekPDFModels'), '', 'pdf'); +print '
'.$langs->trans('TimesheetWeekPDFModelsHelp').'
'; print '
'; print ''; @@ -275,83 +414,37 @@ function timesheetweek_disable_document_model($model) print ''; print ''; -$enabledModels = array(); -$sql = 'SELECT nom FROM '.MAIN_DB_PREFIX."document_model WHERE type='timesheetweek' AND entity IN (0, ".((int) $conf->entity).')'; -$resql = $db->query($sql); -if ($resql) { - while ($obj = $db->fetch_object($resql)) { - $enabledModels[$obj->nom] = 1; - } - $db->free($resql); +if (count($documentModels) === 0) { + print ''; } -$found = 0; -$docToken = newToken(); -foreach ($dirmodels as $reldir) { - $dir = dol_buildpath($reldir.'core/modules/timesheetweek/doc/'); - if (!is_dir($dir)) { - continue; - } - - $filelist = dol_dir_list($dir, 'files', 0, '^[a-z0-9_]+\.php$'); - foreach ($filelist as $fileinfo) { - $file = $fileinfo['name']; - $classname = preg_replace('/\.php$/', '', $file); - - require_once $dir.$file; - if (!class_exists($classname)) { - continue; - } - - try { - $module = new $classname($db); - } catch (Throwable $e) { - continue; - } - - if (!property_exists($module, 'type') || $module->type !== 'pdf') { - continue; - } +foreach ($documentModels as $modelInfo) { + print ''; + print ''; + print ''; + print ''; - $found++; - $name = $module->name ?: $classname; - $desc = !empty($module->description) ? $module->description : ''; - if ($desc && $langs->transnoentitiesnoconv($desc) !== $desc) { - $desc = $langs->trans($desc); - } - $isEnabled = !empty($enabledModels[$name]); - $isDefault = ($defaultpdf === $name); - - print ''; - print ''; - print ''; - print ''; - print ''; - - print ''; - print ''; + print ''; + print ''; + + print ''; + print ''; } print '
'.$langs->trans('Default').'
'.$langs->trans('TimesheetWeekPDFModelsEmpty').'
'.dol_escape_htmltag($modelInfo['label']).''.(!empty($modelInfo['description']) ? dol_escape_htmltag($modelInfo['description']) : ' ').''.dol_escape_htmltag($modelInfo['type']).'
'.dol_escape_htmltag($name).''.(!empty($desc) ? dol_escape_htmltag($desc) : ' ').''.dol_escape_htmltag($module->type).''; - if ($isEnabled) { - $url = $_SERVER['PHP_SELF'].'?action=delmodel&value='.urlencode($name).'&token='.$docToken; - print ''.img_picto($langs->trans('Disable'), 'switch_on').''; - } else { - $url = $_SERVER['PHP_SELF'].'?action=setdocmodel&value='.urlencode($name).'&token='.$docToken; - print ''.img_picto($langs->trans('Activate'), 'switch_off').''; - } - print ''; - if ($isDefault) { - print img_picto($langs->trans('Enabled'), 'on'); - } elseif ($isEnabled) { - $url = $_SERVER['PHP_SELF'].'?action=setdoc&value='.urlencode($name).'&token='.$docToken; - print ''.img_picto($langs->trans('SetDefault'), 'switch_on').''; - } else { - print ' '; - } - print '
'; + if ($modelInfo['is_enabled']) { + $url = $_SERVER['PHP_SELF'].'?action=delmodel&value='.urlencode($modelInfo['name']).'&token='.$pageToken; + print ''.img_picto($langs->trans('Disable'), 'switch_on').''; + } else { + $url = $_SERVER['PHP_SELF'].'?action=setdocmodel&value='.urlencode($modelInfo['name']).'&token='.$pageToken; + print ''.img_picto($langs->trans('Activate'), 'switch_off').''; } -} - -if (!$found) { - print '
'.$langs->trans('NoRecordFound').'
'; + if ($modelInfo['is_default']) { + print img_picto($langs->trans('Enabled'), 'on'); + } elseif ($modelInfo['is_enabled']) { + $url = $_SERVER['PHP_SELF'].'?action=setdoc&value='.urlencode($modelInfo['name']).'&token='.$pageToken; + print ''.img_picto($langs->trans('SetDefault'), 'switch_on').''; + } else { + print ' '; + } + print '
'; diff --git a/core/modules/modTimesheetWeek.class.php b/core/modules/modTimesheetWeek.class.php index bc28e97..5377dde 100644 --- a/core/modules/modTimesheetWeek.class.php +++ b/core/modules/modTimesheetWeek.class.php @@ -58,7 +58,7 @@ public function __construct($db) // Family can be 'base' (core modules),'crm','financial','hr','projects','products','ecm','technic' (transverse modules),'interface' (link with external tools),'other','...' // It is used to group modules by family in module setup page - $this->family = "other"; + $this->family = "Les Métiers du Bâtiment"; // Module position in the family on 2 digits ('01', '10', '20', ...) $this->module_position = '90'; diff --git a/langs/en_US/timesheetweek.lang b/langs/en_US/timesheetweek.lang index 2179d4d..6f4fc5f 100644 --- a/langs/en_US/timesheetweek.lang +++ b/langs/en_US/timesheetweek.lang @@ -5,7 +5,7 @@ # # Module label 'ModuleTimesheetWeekName' -ModuleTimesheetWeekName = TimesheetWeek +ModuleTimesheetWeekName = TimesheetWeek weekly timesheets # Module description 'ModuleTimesheetWeekDesc' ModuleTimesheetWeekDesc = TimesheetWeek description @@ -15,6 +15,11 @@ ModuleTimesheetWeekDesc = TimesheetWeek description TimesheetWeekSetup = TimesheetWeek setup Settings = Settings TimesheetWeekSetupPage = TimesheetWeek setup page +TimesheetWeekNumberingHelp = Choose the numbering mask that will generate references for weekly timesheets. +TimesheetWeekNumberingEmpty = No numbering module is available. +TimesheetWeekNumberingActivate = Activate this mask +TimesheetWeekPDFModelsHelp = Enable the PDF templates that can be generated from a weekly timesheet. +TimesheetWeekPDFModelsEmpty = No PDF template is available. NewSection=New section TIMESHEETWEEK_MYPARAM1 = My param 1 TIMESHEETWEEK_MYPARAM1Tooltip = My param 1 tooltip @@ -41,6 +46,18 @@ SelectWeekPlaceholder = -- Select a week -- About = About TimesheetWeekAbout = About TimesheetWeek TimesheetWeekAboutPage = TimesheetWeek about page +TimesheetWeekAboutGeneral = General information +TimesheetWeekAboutVersion = Version +TimesheetWeekAboutFamily = Family +TimesheetWeekAboutDescription = Description +TimesheetWeekAboutMaintainer = Publisher +TimesheetWeekAboutResources = Resources +TimesheetWeekAboutDocumentation = Documentation +TimesheetWeekAboutDocumentationLink = Open the README +TimesheetWeekAboutSupport = Support +TimesheetWeekAboutSupportValue = Reach your integrator or the Dolistore support team for any request. +TimesheetWeekAboutContact = Publisher website +TimesheetWeekDescription = TimesheetWeek module to manage entry, approval and reporting of weekly timesheets. # # Sample page @@ -138,7 +155,8 @@ TimesheetWeekNotificationRefuseBody = Hello %s,\n\nYour timesheet %s for week %s TimesheetWeekNotificationMissingRecipient = No recipient available for notification (%s). TimesheetWeekNotificationMailError = Unable to send notification email: %s TimesheetWeekNotificationNoEmail = The recipient %s has no email address. -TimesheetWeekNotificationValidatorFallback = Validator +Validator = User responsible for approval +TimesheetWeekNotificationValidatorFallback = User responsible for approval TimesheetWeekNotificationEmployeeFallback = Employee TimesheetWeekNotificationTriggerError = Unable to execute notification trigger %s. Notify_TIMESHEETWEEK_SUBMIT = Timesheet submitted diff --git a/langs/fr_FR/timesheetweek.lang b/langs/fr_FR/timesheetweek.lang index 422cef8..0c19afd 100644 --- a/langs/fr_FR/timesheetweek.lang +++ b/langs/fr_FR/timesheetweek.lang @@ -5,7 +5,7 @@ # # Libellé du module 'ModuleTimesheetWeekName' -ModuleTimesheetWeekName = TimesheetWeek +ModuleTimesheetWeekName = Feuille de temps hebdomadaire TimesheetWeek # Description du module 'ModuleTimesheetWeekDesc' ModuleTimesheetWeekDesc = Description du module TimesheetWeek @@ -15,6 +15,11 @@ ModuleTimesheetWeekDesc = Description du module TimesheetWeek TimesheetWeekSetup = Paramétrage TimesheetWeek Settings = Paramètres TimesheetWeekSetupPage = Page de configuration TimesheetWeek +TimesheetWeekNumberingHelp = Choisissez le masque de numérotation qui générera les références des feuilles hebdomadaires. +TimesheetWeekNumberingEmpty = Aucun module de numérotation disponible. +TimesheetWeekNumberingActivate = Activer ce masque +TimesheetWeekPDFModelsHelp = Activez les modèles PDF générables depuis une feuille hebdomadaire. +TimesheetWeekPDFModelsEmpty = Aucun modèle PDF disponible. NewSection=Nouvelle section TIMESHEETWEEK_MYPARAM1 = Mon paramètre 1 TIMESHEETWEEK_MYPARAM1Tooltip = Info-bulle de mon paramètre 1 @@ -41,6 +46,18 @@ SelectWeekPlaceholder = -- Sélectionnez une semaine -- About = À propos TimesheetWeekAbout = À propos de TimesheetWeek TimesheetWeekAboutPage = Page à propos de TimesheetWeek +TimesheetWeekAboutGeneral = Informations générales +TimesheetWeekAboutVersion = Version +TimesheetWeekAboutFamily = Famille +TimesheetWeekAboutDescription = Description +TimesheetWeekAboutMaintainer = Éditeur +TimesheetWeekAboutResources = Ressources +TimesheetWeekAboutDocumentation = Documentation +TimesheetWeekAboutDocumentationLink = Consulter le README +TimesheetWeekAboutSupport = Support +TimesheetWeekAboutSupportValue = Contactez votre intégrateur ou le support Dolistore pour toute demande. +TimesheetWeekAboutContact = Site éditeur +TimesheetWeekDescription = Module TimesheetWeek pour gérer la saisie, l'approbation et la synthèse des feuilles de temps semaine par semaine. # # Page d'exemple @@ -132,13 +149,14 @@ Sendbymail = Envoyer par mail TimesheetWeekNotificationSubmitSubject = Feuille de temps %s soumise TimesheetWeekNotificationSubmitBody = Bonjour %s,\n\nLe salarié %s a soumis la feuille de temps %s pour la semaine %s/%s.\nVous pouvez la consulter ici : %s\n\nCordialement,\n%s TimesheetWeekNotificationApproveSubject = Feuille de temps %s approuvée -TimesheetWeekNotificationApproveBody = Bonjour %s,\n\nVotre feuille de temps %s pour la semaine %s/%s a été approuvée par %s.\nVous pouvez la consulter ici : %s\n\nCordialement,\n%s +TimesheetWeekNotificationApproveBody = Bonjour %s,\n\nVotre feuille de temps %s pour la semaine %s/%s est désormais approuvée par %s.\nVous pouvez la consulter ici : %s\n\nCordialement,\n%s TimesheetWeekNotificationRefuseSubject = Feuille de temps %s refusée TimesheetWeekNotificationRefuseBody = Bonjour %s,\n\nVotre feuille de temps %s pour la semaine %s/%s a été refusée par %s.\nVous pouvez la consulter ici : %s\n\nCordialement,\n%s TimesheetWeekNotificationMissingRecipient = Aucun destinataire défini pour la notification (%s). TimesheetWeekNotificationMailError = Impossible d'envoyer la notification e-mail : %s TimesheetWeekNotificationNoEmail = Le destinataire %s n'a pas d'adresse e-mail. -TimesheetWeekNotificationValidatorFallback = Validateur +Validator = Utilisateur responsable de l'approbation +TimesheetWeekNotificationValidatorFallback = Utilisateur responsable de l'approbation TimesheetWeekNotificationEmployeeFallback = Salarié TimesheetWeekNotificationTriggerError = Impossible d'exécuter le déclencheur de notification %s. Notify_TIMESHEETWEEK_SUBMIT = Feuille de temps soumise @@ -152,7 +170,7 @@ TimesheetWeekTemplateSubmitSubject = Feuille de temps __TIMESHEETWEEK_REF__ soum TimesheetWeekTemplateSubmitBody = Bonjour __RECIPIENT_FULLNAME__,\n\nLe salarié __TIMESHEETWEEK_EMPLOYEE_FULLNAME__ a soumis la feuille de temps __TIMESHEETWEEK_REF__ pour la semaine __TIMESHEETWEEK_WEEK__/__TIMESHEETWEEK_YEAR__.\nVous pouvez la consulter ici : __TIMESHEETWEEK_URL__\n\nCordialement,\n__ACTION_USER_FULLNAME__ TimesheetWeekTemplateApproveLabel = Notification d'approbation de feuille de temps TimesheetWeekTemplateApproveSubject = Feuille de temps __TIMESHEETWEEK_REF__ approuvée -TimesheetWeekTemplateApproveBody = Bonjour __RECIPIENT_FULLNAME__,\n\nVotre feuille de temps __TIMESHEETWEEK_REF__ pour la semaine __TIMESHEETWEEK_WEEK__/__TIMESHEETWEEK_YEAR__ a été approuvée par __TIMESHEETWEEK_VALIDATOR_FULLNAME__.\nVous pouvez la consulter ici : __TIMESHEETWEEK_URL__\n\nCordialement,\n__ACTION_USER_FULLNAME__ +TimesheetWeekTemplateApproveBody = Bonjour __RECIPIENT_FULLNAME__,\n\nVotre feuille de temps __TIMESHEETWEEK_REF__ pour la semaine __TIMESHEETWEEK_WEEK__/__TIMESHEETWEEK_YEAR__ est désormais approuvée par __TIMESHEETWEEK_VALIDATOR_FULLNAME__.\nVous pouvez la consulter ici : __TIMESHEETWEEK_URL__\n\nCordialement,\n__ACTION_USER_FULLNAME__ TimesheetWeekTemplateRefuseLabel = Notification de refus de feuille de temps TimesheetWeekTemplateRefuseSubject = Feuille de temps __TIMESHEETWEEK_REF__ refusée TimesheetWeekTemplateRefuseBody = Bonjour __RECIPIENT_FULLNAME__,\n\nVotre feuille de temps __TIMESHEETWEEK_REF__ pour la semaine __TIMESHEETWEEK_WEEK__/__TIMESHEETWEEK_YEAR__ a été refusée par __TIMESHEETWEEK_VALIDATOR_FULLNAME__.\nVous pouvez la consulter ici : __TIMESHEETWEEK_URL__\n\nCordialement,\n__ACTION_USER_FULLNAME__ diff --git a/timesheetweek_list.php b/timesheetweek_list.php index 6b57c40..38f7c3d 100644 --- a/timesheetweek_list.php +++ b/timesheetweek_list.php @@ -181,7 +181,10 @@ // FR: Colonne du compteur de paniers pour l'affichage de la liste. 't.meal_count' => array('label' => $langs->trans("MealCount"), 'checked' => 0), 't.date_creation'=> array('label' => $langs->trans("DateCreation"), 'checked' => 0), - 't.tms' => array('label' => $langs->trans("DateModificationShort"), 'checked' => 0), + // EN: Validation timestamp column to expose approval dates in the list. + // FR: Colonne de validation pour afficher les dates d'approbation dans la liste. + 't.date_validation'=> array('label' => $langs->trans("DateValidation"), 'checked' => 0), + 't.tms' => array('label' => $langs->trans("DateModificationShort"), 'checked' => 0), 't.status' => array('label' => $langs->trans("Status"), 'checked' => 1), ); @@ -427,8 +430,13 @@ if (!empty($arrayfields['t.date_creation']['checked'])) { print ' '; } +if (!empty($arrayfields['t.date_validation']['checked'])) { + // EN: Validation date has no filter because approvals are historical events. + // FR: La date de validation n'a pas de filtre car les approbations sont des événements historiques. + print ' '; +} if (!empty($arrayfields['t.tms']['checked'])) { - print ' '; + print ' '; } if (!empty($arrayfields['t.status']['checked'])) { $statusOptions = array( @@ -503,8 +511,11 @@ if (!empty($arrayfields['t.date_creation']['checked'])) { print_liste_field_titre($arrayfields['t.date_creation']['label'], $_SERVER["PHP_SELF"], "t.date_creation", "", $param, '', $sortfield, $sortorder, 'center '); } +if (!empty($arrayfields['t.date_validation']['checked'])) { + print_liste_field_titre($arrayfields['t.date_validation']['label'], $_SERVER["PHP_SELF"], "t.date_validation", "", $param, '', $sortfield, $sortorder, 'center '); +} if (!empty($arrayfields['t.tms']['checked'])) { - print_liste_field_titre($arrayfields['t.tms']['label'], $_SERVER["PHP_SELF"], "t.tms", "", $param, '', $sortfield, $sortorder, 'center '); + print_liste_field_titre($arrayfields['t.tms']['label'], $_SERVER["PHP_SELF"], "t.tms", "", $param, '', $sortfield, $sortorder, 'center '); } if (!empty($arrayfields['t.status']['checked'])) { print_liste_field_titre($arrayfields['t.status']['label'], $_SERVER["PHP_SELF"], "t.status", "", $param, '', $sortfield, $sortorder, 'center '); @@ -517,11 +528,23 @@ /** * Rows */ +$totalsAccumulator = array( + // EN: Track sums to display a total row at the bottom of the listing. + // FR: Suit les sommes pour afficher une ligne totale en bas de la liste. + 'total_hours' => 0.0, + 'overtime_hours' => 0.0, + 'zone1_count' => 0, + 'zone2_count' => 0, + 'zone3_count' => 0, + 'zone4_count' => 0, + 'zone5_count' => 0, + 'meal_count' => 0, +); $i = 0; $imax = ($limit ? min($num, $limit) : $num); while ($i < $imax) { - $obj = $db->fetch_object($resql); - if (!$obj) break; + $obj = $db->fetch_object($resql); + if (!$obj) break; print ''; @@ -618,33 +641,126 @@ if (!empty($arrayfields['t.date_creation']['checked'])) { print ''.($obj->date_creation ? dol_print_date($db->jdate($obj->date_creation),'dayhour') : '').''; } - // Modification - if (!empty($arrayfields['t.tms']['checked'])) { - print ''.($obj->tms ? dol_print_date($db->jdate($obj->tms),'dayhour') : '').''; - } + // EN: Display the approval date when the sheet has been validated. + // FR: Affiche la date d'approbation lorsque la feuille a été validée. + if (!empty($arrayfields['t.date_validation']['checked'])) { + print ''.($obj->date_validation ? dol_print_date($db->jdate($obj->date_validation),'dayhour') : '').''; + } + // Modification + if (!empty($arrayfields['t.tms']['checked'])) { + print ''.($obj->tms ? dol_print_date($db->jdate($obj->tms),'dayhour') : '').''; + } // Status (badge) - if (!empty($arrayfields['t.status']['checked'])) { - $tswstatic->status = $obj->status; - print ''.$tswstatic->getLibStatut(5).''; - } - // Right selection checkbox column (if setting to put it right) - if (!getDolGlobalString('MAIN_CHECKBOX_LEFT_COLUMN')) { - print ''; - if ($massactionbutton || $massaction) { - $selected = in_array($obj->rowid, $arrayofselected) ? 1 : 0; + if (!empty($arrayfields['t.status']['checked'])) { + $tswstatic->status = $obj->status; + print ''.$tswstatic->getLibStatut(5).''; + } + // EN: Accumulate values to expose totals at the bottom of the table. + // FR: Cumule les valeurs pour afficher les totaux en bas du tableau. + $totalsAccumulator['total_hours'] += (float) $obj->total_hours; + $totalsAccumulator['overtime_hours'] += (float) $obj->overtime_hours; + $totalsAccumulator['zone1_count'] += (int) $obj->zone1_count; + $totalsAccumulator['zone2_count'] += (int) $obj->zone2_count; + $totalsAccumulator['zone3_count'] += (int) $obj->zone3_count; + $totalsAccumulator['zone4_count'] += (int) $obj->zone4_count; + $totalsAccumulator['zone5_count'] += (int) $obj->zone5_count; + $totalsAccumulator['meal_count'] += (int) $obj->meal_count; + // Right selection checkbox column (if setting to put it right) + if (!getDolGlobalString('MAIN_CHECKBOX_LEFT_COLUMN')) { + print ''; + if ($massactionbutton || $massaction) { + $selected = in_array($obj->rowid, $arrayofselected) ? 1 : 0; print ''; } print ''; } - print ''; - $i++; + print ''; + $i++; } -if ($num == 0) { - $colspan = 1; - foreach ($arrayfields as $k=>$v) if (!empty($v['checked'])) $colspan++; - print ''.$langs->trans("NoRecordFound").''; +if ($imax > 0) { + // EN: Render the totals row for hours, zones and meal counters. + // FR: Affiche la ligne de totaux pour les heures, les zones et les paniers. + $totalLabelPrinted = false; + print ''; + if (getDolGlobalString('MAIN_CHECKBOX_LEFT_COLUMN')) { + print ' '; + } + if (!empty($arrayfields['t.ref']['checked'])) { + print ''.($totalLabelPrinted ? ' ' : $langs->trans('Total')).''; + $totalLabelPrinted = true; + } + if (!empty($arrayfields['user']['checked'])) { + print ''.($totalLabelPrinted ? ' ' : $langs->trans('Total')).''; + $totalLabelPrinted = true; + } + if (!empty($arrayfields['t.entity']['checked'])) { + print ''.($totalLabelPrinted ? ' ' : $langs->trans('Total')).''; + $totalLabelPrinted = true; + } + if (!empty($arrayfields['t.year']['checked'])) { + print ''.($totalLabelPrinted ? ' ' : $langs->trans('Total')).''; + $totalLabelPrinted = true; + } + if (!empty($arrayfields['t.week']['checked'])) { + print ''.($totalLabelPrinted ? ' ' : $langs->trans('Total')).''; + $totalLabelPrinted = true; + } + if (!empty($arrayfields['t.total_hours']['checked'])) { + $hours = (float) $totalsAccumulator['total_hours']; + $hoursInt = floor($hours); + $minutes = round(($hours - $hoursInt) * 60); + if ($minutes == 60) { $hoursInt++; $minutes = 0; } + $formattedHours = sprintf('%02d:%02d', $hoursInt, $minutes); + print ''.$formattedHours.''; + } + if (!empty($arrayfields['t.overtime_hours']['checked'])) { + $hours = (float) $totalsAccumulator['overtime_hours']; + $hoursInt = floor($hours); + $minutes = round(($hours - $hoursInt) * 60); + if ($minutes == 60) { $hoursInt++; $minutes = 0; } + $formattedHours = sprintf('%02d:%02d', $hoursInt, $minutes); + print ''.$formattedHours.''; + } + if (!empty($arrayfields['t.zone1_count']['checked'])) { + print ''.(int) $totalsAccumulator['zone1_count'].''; + } + if (!empty($arrayfields['t.zone2_count']['checked'])) { + print ''.(int) $totalsAccumulator['zone2_count'].''; + } + if (!empty($arrayfields['t.zone3_count']['checked'])) { + print ''.(int) $totalsAccumulator['zone3_count'].''; + } + if (!empty($arrayfields['t.zone4_count']['checked'])) { + print ''.(int) $totalsAccumulator['zone4_count'].''; + } + if (!empty($arrayfields['t.zone5_count']['checked'])) { + print ''.(int) $totalsAccumulator['zone5_count'].''; + } + if (!empty($arrayfields['t.meal_count']['checked'])) { + print ''.(int) $totalsAccumulator['meal_count'].''; + } + if (!empty($arrayfields['t.date_creation']['checked'])) { + print ' '; + } + if (!empty($arrayfields['t.date_validation']['checked'])) { + print ' '; + } + if (!empty($arrayfields['t.tms']['checked'])) { + print ' '; + } + if (!empty($arrayfields['t.status']['checked'])) { + print ' '; + } + if (!getDolGlobalString('MAIN_CHECKBOX_LEFT_COLUMN')) { + print ' '; + } + print ''; +} else { + $colspan = 1; + foreach ($arrayfields as $k=>$v) if (!empty($v['checked'])) $colspan++; + print ''.$langs->trans("NoRecordFound").''; } print '';