From 997b1d412d96597b784a63809bfdf472aea47102 Mon Sep 17 00:00:00 2001 From: Etienne Pallier Date: Mon, 29 Nov 2021 17:33:33 +0100 Subject: [PATCH] /pages/stats migré dans /materiels/stats et transformé en simple alias --- CHANGELOG | 84 ++++++++++++++++++++++++++++++++++++------------------------------------------------ README.md | 2 +- src/Controller/MaterielsController.php | 407 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++- src/Controller/PagesController.php | 10 +++++++--- 4 files changed, 450 insertions(+), 53 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 3c5017e..3231453 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -13,6 +13,12 @@ CHANGEMENTS ------- +29/11/2021 NEWS#7 (v5.4) : + +- Partitionnement possible des matériels par site + + +------- 22/11/2021 NEWS#6 (v5.3.4) : - Champs obligatoires, modifiables, et readonly, désormais configurables via page web @@ -367,8 +373,6 @@ Commencer à implémenter le nouveau workflow v5 : *) Relance auto lors de suivis périodiques - *) Validation multiple ne marche plus - *) DOMPDF : pb Qrcode absent sur fiche pdf (mais présent sur page web), alors que le QrCode est bien généré et que le chemin dans le pdf est OK !!! Par contre, ok avec FPDF @@ -503,26 +507,10 @@ Warning (2): Cannot modify header information - headers already sent by (output TODO : - besoin IP2I/LMA : -=> restreindre l’accès de certains équipements du LMA aux utilisateurs du LMA seulement et qu'ils ne soient pas visibles ou consultables par tous les utilisateurs IP2I -=> Implémentation possible : -2 possibilités : - - Définir LMA dans la liste des "Sites" => mais ça correspond pas vraiment à la notion de site (lieu géographique) - - Ajouter une nouvelle notion de "Département" -- Associer les matériels sensibles à ce Site (ou Dépt), et cocher la case "materiel sensible" -- Associer les utilisateurs du LMA à ce Site (ou Dépt) -=> Régle : les matériels sensibles ne sont vus que par les utilisateurs du même Site (ou Dépt) (ici LMA) : - pour materiels/view(/edit/delete) => le user courant ne peut pas voir un materiel "sensible" qui n'est pas du même Site (Dépt) que lui - pour materiels/index et /find et /export => il faut exclure les matos "sensibles" qui ne sont pas du même site que le user courant - materiels/index : on peut ajouter un filtre "Site" (et Dépt), et un filtre "tous/matos sensible/matos non sensible" -- add_or_edit() générique - -- fusionner elem/button_add_edit et MyHelper echoActionButton... -(ne garder que elem/) -- séparer elem/button_add, button_edit, button_delete, et button_tout_court (générique) - -- short_role AppController ligne 819 => généraliser - - prévoir une alerte quand on save un champ qui n'est pas dans la BD (genre resp_credit...) - gérer les liens url automatiquement dans champ description (rendre cliquable) @@ -535,26 +523,30 @@ TODO : +- GENERICITÉ & REFACTORISATION : + - add_or_edit() générique + + - fusionner elem/button_add_edit et MyHelper echoActionButton... + (ne garder que elem/) + - séparer elem/button_add, button_edit, button_delete, et button_tout_court (générique) + - short_role AppController ligne 819 => généraliser + - emprunts/index generique + - documents/index generique + - ProjetsController minimaliste, doit juste étendre AppController avec un minimum de changement + - fusionner groupe thematique et metier (et projet ?) : + => faire hériter les Controller et les Table d'une meme superclasse GroupController et GroupTable + => avoir un seul template + => c'est vraiment stupide d'avoir 2 classes qui font la meme chose... -- emprunts/index generique -- documents/index generique - -- ProjetsController minimaliste, doit juste étendre AppController avec un minimum de changement - -- add_edit generic + - Utiliser les vues "index" des entités associées pour la vue "view" de materiel (et suivi) : + => éviter la redondance, le contenu est pratiquement le meme (???, sauf que les colonnes ne sont pas triables) -- fusionner groupe thematique et metier (et projet ?) : - => faire hériter les Controller et les Table d'une meme superclasse GroupController et GroupTable - => avoir un seul template - => c'est vraiment stupide d'avoir 2 classes qui font la meme chose... -- Utiliser les vues "index" des entités associées pour la vue "view" de materiel (et suivi) : - => éviter la redondance, le contenu est pratiquement le meme (???, sauf que les colonnes ne sont pas triables) - Bien préciser quels sont les champs obligatoires avec une asterisque (et pour chaque LOT) @@ -578,13 +570,6 @@ Vues génériques (index et view) : - Suivis.statut => "en cours" ou "à terminer" => à calculer auto - *) - Rendre modifiable la config via page web "Gérer les champs obligatoires" - - OFF_nom_du_champ : 'libellé' - - Restaurer la config par défaut - - Réactiver une variable : OFF_nom_du_champ => nom_du_champ - - saisir les personnes du gt2i et de tous les groupes... groupe.users associés : ajouter "(responsable)" when relevant @@ -593,21 +578,12 @@ comment faire un tri sur la dernière colonne des stats (connexDurAvg) ? erreur download depuis page documents/ (ou depuis vue du matériel) sur inventirap : erreur 404 (action impossible) -TODO config fields : -- réadapter lecture config à new file format -- compléter le fichier config avec tous les champs possibles à chaque lot !!! -- fichier read-write par web server -- ./UPDATE cral et ip2i + new config file -- pub -- soigner la présentation du form (peu lisible now) - - - (b) Bugfix fournisseur perdu (et champ vide qui n'est plus modifiable !) après validation du matos +- (b) Bugfix fournisseur perdu (et champ vide qui n'est plus modifiable !) après validation du matos (quand il manque un champ pour valider), et pourtant bien enregistré dans listes des fournisseurs -config + jolie : fieldset dépliable et joli (voir echoSectionStart() de MyHelperHelper) Utiliser les champs 'comment' de la config pour les labels des champs dans materiels/view et /add_edit @@ -619,9 +595,16 @@ Ne pas autoriser la commande via url si le bouton order est désactivé dans la ... +- Quelle est cette action ? le mail est un peu court... (ajout par copie ?) +Titre "Ajout de matériel(s)" +Nathalie Oziol a ajouté des matériels (action 'add') +Vous recevez ce message car vous êtes concerné(e) par cette action effectuée sur l'inventaire des matériels du laboratoire +(vous êtes l'utilisateur du matériel, ou bien le gestionnaire, ou encore le responsable thématique, métier ou projet) +(ou alors, vous êtes dans la liste mail spécifique gérée via la page de configuration du logiciel LabInvent). + + Gestion multi-sites : -- matos.index : doit enlever les matos "sensibles" et qui ont un site différent du user - matos.view : interdit si matos "sensible" a un site différent du user - find() : par défaut, enlever les matos "sensibles" et qui ont un site différent du user - gestionnaires (admin) (et superadmin) continuent d'avoir accès à tout @@ -630,6 +613,11 @@ Gestion multi-sites : ======= CHANGES ======= ------- +29/11/2021 v5.4.1-3.7.9 + - (i) /pages/stats migré dans /materiels/stats et transformé en simple alias + - (b) bugfix pour IRAP seulement (vieux mysql pourri et vieux php5 pourri !!!) + +------- 29/11/2021 v5.4.0-3.7.9 - (e) Partitionnement des matériels par site opérationnel (multi-sites) => liste des matériels filtrée - (b) Bugfix action sur plusieurs matos : diff --git a/README.md b/README.md index 5b1db52..3d3077a 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,7 @@ Logiciel testé et validé sur les configurations suivantes : -------------------------------------------------------------------------------------------- Date: 29/11/2021 -Version: v5.4.0-3.7.9 +Version: v5.4.1-3.7.9 HISTORIQUE DES CHANGEMENTS DE VERSION : voir le fichier CHANGES.txt (ou la page web /pages/changes) diff --git a/src/Controller/MaterielsController.php b/src/Controller/MaterielsController.php index d7029c5..b494f63 100755 --- a/src/Controller/MaterielsController.php +++ b/src/Controller/MaterielsController.php @@ -457,6 +457,9 @@ class MaterielsController extends AppController { * */ + // - Action 'stats' (bugfix) => autorisée à tous + $this->setAuthorizationsForAction('stats', 0); + // - Action 'execSqlRequestForBugfix' (bugfix) => autorisé seulement à superadmin $this->setAuthorizationsForAction('execSqlRequestForBugfix (bugfix)', -1, [ 'super' => 0, @@ -1413,7 +1416,11 @@ class MaterielsController extends AppController { //debug($current_user->nom); //debug($current_user->site_id); //debug($materiels); exit; - $materiels = $materiels->find('filteredForUserSite', ['user_site_id' => $current_user->site_id]); + if ($this->confLabinvent->labNameShort) { + $labshortname = $this->confLabinvent->labNameShort; + if ($labshortname != 'IRAP') + $materiels = $materiels->find('filteredForUserSite', ['user_site_id' => $current_user->site_id]); + } //$materiels = $materiels->find('filteredForUserSite', ['user_site_id' => null]); //foreach ($materiels as $m) debug($m->id); //debug($materiels->count()); @@ -3315,6 +3322,404 @@ class MaterielsController extends AppController { // $this->sendEmail($this->Materiels->get($id)); } + public function stats() { + + /* + return $this->redirect([ + 'controller' => 'pages', + 'action' => 'stats', + ]); + */ + + // créer des FAKE DATA pour tester la fonctionnalité ? + $TEST = true; + $TEST = false; + + // Nb années demandées par l'utilisateur + $nbyears = $this->request->getQuery('nbyears'); + + // Initialisation des variables nécessaires + // - Données Totales (complètes) (tronquées à $nbyears) + $tot = [ + 'CREATED' => (int) 1, + 'VALIDATED' => (int) 0, + 'TOBEARCHIVED' => (int) 0, + 'ARCHIVED' => (int) 0, + 'suivis' => (int) 0, + 'prets' => (int) 0 + ]; + // - Donées Moyennes + $avg = [ + 'CREATED' => (int) 1, + 'VALIDATED' => (int) 0, + 'TOBEARCHIVED' => (int) 0, + 'ARCHIVED' => (int) 0, + 'suivis' => (int) 0, + 'prets' => (int) 0 + ]; + // - Année en cours + $now = new \DateTime('now'); + //debug($now); + // 2020 en 2020 + $current_year = (int) $now->format('Y'); + // - Données par Année + $years = + [ + $current_year => [ + 'CREATED' => (int) 1, + 'VALIDATED' => (int) 0, + 'TOBEARCHIVED' => (int) 0, + 'ARCHIVED' => (int) 0, + 'suivis' => (int) 0, + 'prets' => (int) 0 + ] + ]; + + // Tables + $materiels = TableRegistry::getTableLocator()->get('Materiels'); + $suivis = TableRegistry::getTableLocator()->get('Suivis'); + $prets = TableRegistry::getTableLocator()->get('Emprunts'); + $associated_entities = ['suivis','prets']; + $fk = 'materiel_id'; + + // Tous les matos qui ont une date d'achat non nulle + $all_matos = $materiels->find()->where(['year(date_acquisition) >' => 0]); + + // On ne fait pas de stats s'il n'y a aucun matériel dans la BD + if ($all_matos->count() == 0) { + $year_min = $current_year; + //$nbyears = 0; + $this->set(compact('current_year', 'year_min', 'nbyears', 'tot', 'avg', 'years')); + return; + } + + // - Année min + //$year_min = $materiels->find()->min(['year(date_acquisition)']); + //$matos_year_min = $materiels->find()->where(['year(date_acquisition) >' => 0]) + $matos_year_min = $all_matos->cleanCopy()->min(function($matos) { + return $matos->date_acquisition->format('Y'); + }); + $year_min = (int) $matos_year_min->date_acquisition->format('Y'); + + // - Nb années au total : au moins 1 + $nbyears_max = $current_year - $year_min + 1; + + // $nbyears demandé par l'utilisateur, par défaut TOUTES + $nbyears = ( is_numeric($nbyears) && $nbyears>0 ) ? round($nbyears) : $nbyears_max; + //debug($nbyears); + + // - Nb annnées à traiter (entre 1 et $nbyears_max) + // ($nbyears=null si pas passé en paramètre) + // ($nbyears='' si passé en paramètre mais sans valeur '?nbyears=') + //$nbyears = $nbyears ? abs((int)$nbyears) : $nbyears_max; + //debug($nbyears); + $nbyears = min($nbyears,$nbyears_max); + //debug($nbyears); + //debug($nbyears); + // Si on est au max (avec max>2), on enlève la 1ère et la dernière années (car a priori incomplètes) + //sif ($nbyears==$nbyears_max && $nbyears>2) $nbyears -= 2; + // Nouvelle année min + $year_min = $current_year-$nbyears + 1; + + // Nouveau $all_matos, en tronquant à $nbyears + $all_matos = $materiels->find() + ->where(['year(date_acquisition) <=' => $current_year]) + ->where(['year(date_acquisition) >=' => $year_min]); + $all_matos_ids = $all_matos->cleanCopy()->select(['id']); + + /* + $statuses = [ + 'CREATED' => 'created', + 'VALIDATED' => 'date_validated', + 'ARCHIVED' => 'date_archived' + ]; + */ + $statuses = [ 'CREATED', 'VALIDATED', 'TOBEARCHIVED', 'ARCHIVED' ]; + + // - tot : Données Totales (complètes) (tronquées à $nbyears) + foreach ($statuses as $status) { + $all_matos_copy = $all_matos->cleanCopy(); + // Tous les matos qui ont une date d'achat non nulle + if ($status != 'CREATED') $all_matos_copy->where(['status' => $status]); + $tot[$status] = $all_matos_copy->count(); + } + // Suivis et Prets + // Nb materiels qui ont des suivis (i.e. qui ont au moins 1 suivi) + foreach ($associated_entities as $e) { + //$nb_matos_suivis = $suivis->find() + $tot[$e] = $$e->find() + // materiel_id not null + ->where(["$fk >" => 0]) + ->select([$fk]) + ->distinct([$fk]) + ->where(["$fk IN" => $all_matos_ids]) + ->count(); + } + + /* + // - tot2 : Données Totales Partielles (= tot sauf 1ère et dernière années) + $all_matos2 = $materiels + ->find() + ->where(['year(date_acquisition) <' => $current_year]) + ->where(['year(date_acquisition) >' => $current_year-$nbyears]); + $all_matos_ids2 = $all_matos2->cleanCopy()->select(['id']); + + $tot2 = []; + foreach ($statuses as $status) { + $all_matos_copy = $all_matos2->cleanCopy(); + // Tous les matos qui ont une date d'achat non nulle + if ($status != 'CREATED') $all_matos_copy->where(['status' => $status]); + $tot2[$status] = $all_matos_copy->count(); + } + // Suivis et Prets + // Nb materiels qui sont suivis (i.e. qui ont au moins 1 suivi) + foreach ($associated_entities as $e) { + //$nb_matos_suivis = $suivis->find() + $tot2[$e] = $$e->find() + // materiel_id not null + ->where(["$fk >" => 0]) + ->select([$fk]) + ->distinct([$fk]) + ->where(["$fk IN" => $all_matos_ids2]) + ->count(); + } + */ + + // - Donées Moyennes (avg) + /* + $avg['CREATED'] = 10; + $avg['VALIDATED'] = 7; + $avg['TOBEARCHIVED'] = 1; + $avg['ARCHIVED'] = 1; + */ + foreach ($statuses as $status) { + $avg[$status] = round($tot[$status]/$nbyears, 1); + /* + * (EP) + * J'obtiens pas le meme resultat avec cette methode... + * Peut-être que ca compte pas les années où il n'y a rien (0) + */ + /* + //$all_matos = $materiels->find(); + //$all_matos = $materiels->find()->where(['year(date_acquisition) >' => 0]); + //->groupBy('year(date_acquisition)'); + $all_matos_copy = $all_matos->cleanCopy(); + if ($status != 'CREATED') $all_matos_copy->where(['status' => $status]); + $avg[$status] = $all_matos_copy + //->groupBy('year(date_acquisition)'); + ->countBy(function($matos) { + return $matos->date_acquisition->format('Y'); + }) + ->avg(); + //->toList(); + //->toArray(); + //->count(); + $avg[$status] = round($avg[$status]); + */ + } + // Suivis et Prets + // Nb materiels qui sont suivis (i.e. qui ont au moins 1 suivi) + foreach ($associated_entities as $e) { + $avg[$e] = round($tot[$e]/$nbyears, 1); + /* + $nb_matos_having_entity = $$e->find() + // materiel_id not null + ->where(["$fk >" => 0]) + ->select([ + 'year' => 'YEAR(created)' + ]) + ->group('year') + ->toArray(); + + //->groupBy(function($entity) { + ->group(function($entity) { + return $entity->created->format('Y'); + }) + ->distinct([$fk]) + ->count() + ->avg(); + $avg[$e] = $nb_matos_having_entity; + */ + //debug($nb_matos_having_entity); + } + + + // - Données par Année + $all_matos = $materiels->find(); + for ($y=$current_year ; $y>=$year_min ; $y--) { + ////debug($y); + $all_matos_for_year = $all_matos->cleanCopy()->where(['year(date_acquisition)' => $y]); + ////debug($all_matos_for_year->count()); + // - statut par année + try { + foreach ($statuses as $status) { + /* + $all_matos_for_year = $materiels->find(); + ->where(['year(date_acquisition)' => $y]); + $all_matos_for_year = $all_matos->cleanCopy()->where(['year(date_acquisition)' => $y]); + */ + $all_matos_for_year_and_status = $all_matos_for_year->cleanCopy(); + if ($status != 'CREATED') $all_matos_for_year_and_status->where(['status' => $status]); + $years[$y][$status] = $all_matos_for_year_and_status->count(); + } + } catch (\PDOException $e) { + debug("Mauvais format de requete SQL (PagesController/stats statuses), Exception PDO générée !"); + exit; + } + ////debug($years); + // Suivis et Prets, par année + $all_matos_suivis_for_year = $all_matos_for_year->cleanCopy(); + foreach ($associated_entities as $e) + try { + ////debug($e); + //debug($$e->find()->count()); + /* + * SQL equivalent : à tester... + * + * SELECT COUNT(DISTINCT(suivis.materiel_id)) FROM suivis LEFT JOIN materiels + * ON suivis.materiel_id = materiels.id + * WHERE YEAR(materiels.date_acquisition) = $y + * + * $years[$y]['suivis'] = $suivis->find()... + * puis + * $years[$y]['prets'] = $prets->find()... + */ + $years[$y][$e] = $$e + // Tous les suivis/emprunts... + ->find() + // ... et leur materiel associé... + ->leftJoinWith('Materiels') + // ... LEFT JOIN ON (suivis.materiel_id = materiels.id) ... // ou emprunts.materiel_id + //->enableAutoFields(true) + // ... (uniquement les materiels de l'année $y) ... + ->where(['year(date_acquisition)' => $y]) + // ... en ne gardant qu'1 seul suivi/emprunt par materiel (meme si ce materiel en a plusieurs) ... + ->distinct(['materiel_id']) + // ... et enfin, on compte le nombre de suivis/emprunts (total pour l'année $y) + ->count(); + //->toArray(); + //$all_matos_for_year + //->contain(['Suivis']) + //->select(['id', 'count_suivis => count(suivis)']) + //->where(['count_suivis >' => 0]) + //->count(); + } catch (\PDOException $ex) { + debug("Mauvais format de requete SQL (PagesController/stats suivis/prets), Exception PDO générée !"); + debug($ex); + exit; + } + //debug($years[$y]['suivis']); + //$years[$y]['suivis'] = 3; + //$years[$y]['prets'] = 4; + } // foreach year + //debug($years); + + // Set all variables pour la vue + //$this->set(compact('current_year', 'year_min', 'tot', 'avg', 'years', 'suivis', 'prets')); + //$this->set(compact('current_year', 'year_min', 'nbyears', 'tot', 'tot2', 'avg', 'years')); + + // FAKE DATA POUR TEST LOCAL + if ($TEST) { + + $current_year = 2021; + + $test_nb_years = 0; + $test_nb_years = 1; + $test_nb_years = 2; + $test_nb_years = 3; + + $i_year = $current_year; + $i_nbyears = 0; + + $years = []; + /* + $years[2019] = [ + 'CREATED' => (int) 27, + 'VALIDATED' => (int) 8, + 'TOBEARCHIVED' => (int) 1, + 'ARCHIVED' => (int) 1, + 'suivis' => (int) 2, + 'prets' => (int) 2 + ]; + */ + // 1 year + if ($test_nb_years > $i_nbyears) + $years[$i_year] = [ + 'CREATED' => (int) 27, + 'VALIDATED' => (int) 8, + 'TOBEARCHIVED' => (int) 1, + 'ARCHIVED' => (int) 1, + 'suivis' => (int) 2, + 'prets' => (int) 2 + ]; + // 2 years + if ($test_nb_years > $i_nbyears++) + $years[--$i_year] = [ + 'CREATED' => (int) 0, + 'VALIDATED' => (int) 0, + 'TOBEARCHIVED' => (int) 0, + 'ARCHIVED' => (int) 0, + 'suivis' => (int) 0, + 'prets' => (int) 0 + ]; + // 3 years + if ($test_nb_years > $i_nbyears++) + $years[--$i_year] = [ + 'CREATED' => (int) 27, + 'VALIDATED' => (int) 8, + 'TOBEARCHIVED' => (int) 1, + 'ARCHIVED' => (int) 1, + 'suivis' => (int) 2, + 'prets' => (int) 2 + ]; + if ($test_nb_years > $i_nbyears++) + $years[--$i_year] = [ + 'CREATED' => (int) 27, + 'VALIDATED' => (int) 8, + 'TOBEARCHIVED' => (int) 1, + 'ARCHIVED' => (int) 1, + 'suivis' => (int) 2, + 'prets' => (int) 2 + ]; + + $nbyears = count($years); + $year_min = $nbyears>0 ? min(array_keys($years)) : $current_year; + + $tot = [ + 'CREATED' => (int) 27, + 'VALIDATED' => (int) 8, + 'TOBEARCHIVED' => (int) 1, + 'ARCHIVED' => (int) 1, + 'suivis' => (int) 2, + 'prets' => (int) 2 + ]; + $avg = [ + 'CREATED' => (float) 27, + 'VALIDATED' => (float) 8, + 'TOBEARCHIVED' => (float) 1, + 'ARCHIVED' => (float) 1, + 'suivis' => (float) 2, + 'prets' => (float) 2 + ]; + + debug($current_year); + debug($year_min); + debug($years); + } // TEST DATA + + $this->set(compact('current_year', 'year_min', 'nbyears', 'tot', 'avg', 'years')); + /* + debug($tot); + //echo json_encode($avg); + debug($avg); + debug($years); + */ + + } // stats() + + + + /** * StatusArchived method * diff --git a/src/Controller/PagesController.php b/src/Controller/PagesController.php index 8547889..bf98a2f 100755 --- a/src/Controller/PagesController.php +++ b/src/Controller/PagesController.php @@ -306,8 +306,12 @@ class PagesController extends AppController // - Page des STATISTIQUES - if ($this->page=='stats') $this->page_stats_set_variables(); - + //if ($this->page=='stats') $this->page_stats_set_variables(); + if ($this->page=='stats') return $this->redirect([ + 'controller' => 'materiels', + 'action' => 'stats', + ]); + // - Page des OUTILS if ($this->page=='tools') { @@ -333,7 +337,7 @@ class PagesController extends AppController - private function page_stats_set_variables() { + private function OLD_page_stats_set_variables() { // créer des FAKE DATA pour tester la fonctionnalité ? $TEST = true; -- libgit2 0.21.2