Partage
  • Partager sur Facebook
  • Partager sur Twitter

[PHP] Tableau croisé : une vraie galère

    14 novembre 2019 à 14:18:00

    Bonjour à tous, 

    Je suis pompier volontaire dans une petite caserne de campagne, et mon chef de centre m'a demandé si je pouvais créer un outil assez simple permettant de planifier les futurs week-end de garde, avec des fonctions plus avancées que ce notre système d'alerte fait actuellement.

    J'en suis pour l'instant à la partie ou j'essaie déjà d'afficher une fiche de garde déjà faite au préalable, avant de me lancer dans l'affichage dynamique lorsque l'on choisit la feuille de garde à l'aide d'un select.

    Je galère depuis quelques jours sur la retranscription d'une maquette Excel en maquette PHP/MySQL :

    Voici un aperçu de mon code :

    <div class="col-lg-12">
    					<table class="table table-bordered table-sm">
    						<tr>
    							<td>Liste des agents inscrits</td>
    							<?php affichageCreneaux(); ?>
    						</tr>
    					
    							<?php
    								//On check la liste des agents inscrits pour cette journée
    							$check = $dbh->prepare("SELECT * FROM agents AS A, disponibilites AS D WHERE A.codeAgent = D.id_agent AND D.id_fdg = :codeFdg");
    							$check->execute(array(
    							'codeFdg' => 1));
    						
    							$dispo = $dbh->prepare("SELECT * FROM agents AS A, statuts AS S, disponibilites AS D, feuilles_garde AS FG, creneaux AS C WHERE C.codeCreneau = D.id_creneau AND A.codeAgent = D.id_agent AND S.codeStatut = D.id_statut AND FG.codeFdg = D.id_fdg AND D.id_fdg = :codeFdg");
    							$dispo->execute(array(
    							'codeFdg' => 1));
    						
    							while ($resFdg = $check->fetch())
    							{
    								echo "<tr>";
    								echo "<td>".$resFdg['nom']." ".$resFdg['prenom']."</td>";
    									while ($dispoFdg = $dispo->fetch())
    									{
    										//
    										$get = $dbh->query("SELECT * FROM creneaux");
    										while ($creneaux = $get->fetch())
    										{
    											if($creneaux['codeCreneau'] == $dispoFdg['id_creneau'])
    											{
    												echo "<td>".$dispoFdg['id_statut']."</td>";
    
    											}
    										}
    										//
    									}
    								echo "</tr>"; 
    							}
    							
    						?>
    
    				
    					</table>
    				</div>

    Voici le fichier functions :

    	function selectFdg($nameSelect)
    	{
    		require("connection.inc.php");
    		$fdg = $dbh->query("SELECT * FROM feuilles_garde");
    		echo "<select name=".$nameSelect." id=".$nameSelect." class='form-control'>";
    		while ($result = $fdg->fetch())
    		{
    			
    			echo "<option value=".$result['codeFdg'].">Garde du ".FormaterDate($result['dateFdg'], "d.m.Y")." (S".$result['num_semaine'].")</option>";
    		}
    		echo "</select>";
    	}
    
      function FormaterDate($date, $format)
      {
        $newDate = date($format, strtotime($date));
        return $newDate;
      }
    
      function FormaterHeure($hour, $format)
      {
        $newHour = date($format, strtotime($hour));
        return $newHour;
      }
    
    	function affichageCreneaux()
    	{
    		require("connection.inc.php");
    		$get = $dbh->query("SELECT * FROM creneaux");
    		while ($result = $get->fetch())
    		{
    			echo "<td style='font-size:9px;'>".FormaterHeure($result['timeCreneau'], "H:i")."</td>";
    		}
    	}

    Et voici le contenu de ma base de données :

    -- Structure de la table `agents`
    --
    
    CREATE TABLE `agents` (
      `codeAgent` int(11) NOT NULL,
      `matricule` int(6) NOT NULL,
      `password` varchar(255) NOT NULL,
      `id_grade` int(11) NOT NULL,
      `isActiveAccount` int(11) NOT NULL,
      `id_team` int(11) NOT NULL,
      `prenom` varchar(255) NOT NULL,
      `nom` varchar(255) NOT NULL,
      `mailAgent` varchar(255) NOT NULL
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
    
    -- --------------------------------------------------------
    
    --
    -- Structure de la table `creneaux`
    --
    
    CREATE TABLE `creneaux` (
      `codeCreneau` int(11) NOT NULL,
      `timeCreneau` time NOT NULL
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
    
    -- --------------------------------------------------------
    
    --
    -- Structure de la table `disponibilites`
    --
    
    CREATE TABLE `disponibilites` (
      `codeEnregistrement` int(11) NOT NULL,
      `id_fdg` int(11) NOT NULL,
      `id_creneau` int(11) NOT NULL,
      `id_agent` int(11) NOT NULL,
      `id_statut` int(11) NOT NULL
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
    
    -- --------------------------------------------------------
    
    --
    -- Structure de la table `equipes`
    --
    
    CREATE TABLE `equipes` (
      `codeTeam` int(11) NOT NULL,
      `lblTeam` varchar(255) NOT NULL,
      `id_resp_team` int(11) NOT NULL,
      `couleur` varchar(7) NOT NULL
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
    
    -- --------------------------------------------------------
    
    --
    -- Structure de la table `feuilles_garde`
    --
    
    CREATE TABLE `feuilles_garde` (
      `codeFdg` int(11) NOT NULL,
      `dateFdg` date NOT NULL,
      `num_semaine` int(2) NOT NULL,
      `team_garde_principale` int(11) NOT NULL COMMENT 'Renvoie l''id de l''équipe de garde concernée par la feuille de garde'
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
    
    -- --------------------------------------------------------
    
    --
    -- Structure de la table `grades`
    --
    
    CREATE TABLE `grades` (
      `codeGrade` int(11) NOT NULL,
      `lblGrade` varchar(255) NOT NULL,
      `abvGrade` varchar(5) NOT NULL
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
    
    -- --------------------------------------------------------
    
    --
    -- Structure de la table `statuts`
    --
    
    CREATE TABLE `statuts` (
      `codeStatut` int(11) NOT NULL,
      `lblStatut` varchar(255) NOT NULL,
      `couleur_statut` varchar(7) NOT NULL
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;

    En gros, je n'arrive pas à afficher de la façon suivante :

    - Chaque pompier qui s'inscrit pour une journée

    - Remplir les cases en fonction du créneau et de la disponibilité : exemple, si je suis disponible de 8h a 18h, je colore toutes mes cases en fonction du code couleur du statut, et le reste des cases reste incolore

    Pouvez-vous m'aider ? :)



    P.S : voici le lien en prod : http://projets.bsucreations.com/gestion_fdg.php

    Et voici ce que j'ai indiqué dans ma table disponibilités en test : 

    id_creneau : 3 (qui correspond à 9h)

    id_dispo : 1

    id_agent : 1

    id_fdg : 1

    -
    Edité par MaxooSPV 14 novembre 2019 à 14:20:38

    • Partager sur Facebook
    • Partager sur Twitter
      14 novembre 2019 à 16:21:20

      Salut !

      On verra dans un second temps que les deux requêtes peuvent probablement être réunies en une seule.

      Je pense qu'il faut regarder à chaque cellule si le pompier de la ligne est disponible, donc si sa date de début de disponibilité est inférieure ou égale à celle représentée par la cellule, et si la date de fin de disponibilité est plus petite que celle représentée par la cellule. Si ces deux conditions sont respectées, alors tu peux afficher 1 dans la cellule.

      Pour les totaux par demi-heure, ça va être plus complexe. Je sais que j'avais réalisé un truc du genre et je crois bien que j'avais demandé de l'aide sur un forum, seulement je ne me souviens plus de la solution que j'avais mise en place.
      Je suis en train de voir si j'arrive à retrouver un éventuel sujet qui me remettrait en tête ce que j'ai finalement utilisé.

      Edit

      Ça me revient, j'avais généré avec un système de jointures ou/et d'unions tous les jours dans une table temporaire et avec une jointure adaptée, j'avais pu faire les comptes. Je suis en train de rechercher comment c'était avec ces tranches.

      Edit 2

      Voilà, j'ai retrouvé ma source.
      L'idée est donc de sélectionner toutes les dates depuis une date de début (curdate() dans l'exemple lié, à remplacer par le lundi de la semaine à afficher) en y ajoutant un nombre de jour(s) à chaque fois. C'est donc à adapter pour avoir les demi-heures

      Attention toujours aux changements d'heures et aux fuseaux horaires, vu qu'on va traiter un intervalle en minutes…

      -
      Edité par Ymox 14 novembre 2019 à 16:42:45

      • Partager sur Facebook
      • Partager sur Twitter
        14 novembre 2019 à 16:33:32

        Sachant que la colonne des créneaux horaires est générée par la table créneaux ? L'heure c'est juste pour l'affichage.

        Je verrais plus : "est-ce que pour ce créneau la, il est disponible" : 

        - oui : j'affiche la couleur selon le code

        - non, je laisse sans couleur et je passe à la suivante

        • Partager sur Facebook
        • Partager sur Twitter
          14 novembre 2019 à 16:43:51

          MaxooSPV a écrit:

          Je verrais plus : "est-ce que pour ce créneau la, il est disponible"

          C'est la même chose que ce que j'ai expliqué au niveau "logique/mathématique", mais en français  :D

          Attention, j'ai complété ma réponse entre temps.

          -
          Edité par Ymox 14 novembre 2019 à 16:44:10

          • Partager sur Facebook
          • Partager sur Twitter
            14 novembre 2019 à 16:48:35

            Je vois, mais je suis pas persuadé qu'on est réellement besoin de faire de la gestion de temps .. plutôt jouer avec les code et les id non ? car techniquement, rien ne sera calculé en fonction du temps. Ou alors j'suis vraiment une bille.
            • Partager sur Facebook
            • Partager sur Twitter
              14 novembre 2019 à 16:56:08

              S'il souhaite avoir les effectifs par tranches de demi-heures, je ne vois pas vraiment d'alternative, compter des présences par tranches sur des durées n'est pas si simple.

              Si vraiment, on fera déplacer le sujet dans le forum Bases de données.

              Edit

              En attendant, voici de quoi générer les 48 tranches d'une demi-heure dans une journée

              SELECT (? + INTERVAL c.number MINUTE) AS date
              FROM   (
                              SELECT   30 * (singles + tens) number
                              FROM     (
                                              SELECT 0 singles
                                              UNION ALL
                                              SELECT 1
                                              UNION ALL
                                              SELECT 2
                                              UNION ALL
                                              SELECT 3
                                              UNION ALL
                                              SELECT 4
                                              UNION ALL
                                              SELECT 5
                                              UNION ALL
                                              SELECT 6
                                              UNION ALL
                                              SELECT 7
                                              UNION ALL
                                              SELECT 8
                                              UNION ALL
                                              SELECT 9 ) singles
                              JOIN
                                       (
                                              SELECT 0 tens
                                              UNION ALL
                                              SELECT 10
                                              UNION ALL
                                              SELECT 20
                                              UNION ALL
                                              SELECT 30
                                              UNION ALL
                                              SELECT 40 ) tens
                              order BY number ASC) c
              WHERE  c.number BETWEEN 0 AND    1410

              Le marqueur ? est à remplacer par le jour à considérer (ou le premier de la semaine à considérer), 08:00. Sur une semaine, on a 10080 de ces tranches, comme il y en a 1440 dans une journée.

              -
              Edité par Ymox 14 novembre 2019 à 17:46:33

              • Partager sur Facebook
              • Partager sur Twitter
                16 novembre 2019 à 12:40:44

                Après assimilation, je pense qu'il doit me manquer une donnée. Dans la table des créneaux, il n'y a qu'une heure, pas de durée ni d'heure de fin. Un créneau est d'une durée fixe ? Laquelle ? Et est-ce que c'est bien l'heure de début qu'il y a actuellement dans la table ?

                Quant à la table des feuilles de garde, je ne pense pas qu'on en aura besoin, dans la mesure ou la requête va avoir besoin en paramètre soit d'un jour particulier, soit d'une semaine particulière (l'un n'excluant pas la possibilité d'utiliser l'autre). Après, demander pour toute la brigade ou juste une équipe, c'est une partie de clause WHERE en plus. Je ne vois pas quelle information cette table contient et qui ne sera pas récupérable autrement.

                • Partager sur Facebook
                • Partager sur Twitter
                  16 novembre 2019 à 19:05:15

                  J'ai pas tout compris ce que tu veux, mais voici notre organisation :

                  1 année = 52 semaines.
                  Nous sommes 3 équipes, on est de garde 1 semaine sur 3. Mais l'idéal pour avoir vraiment le plein contrôle est d'affecter chaque semaine de garde à une équipe, car il peut y avoir des changements.

                  Notre semaine de garde débute du vendredi 19h au vendredi suivant 18h59.

                  • Partager sur Facebook
                  • Partager sur Twitter
                    17 novembre 2019 à 10:09:26

                    Donc en gros, sur une journée, on est de garde toute la journée, ou depuis 19:00, ou jusqu'à 18:59, pas d'autres créneaux ?

                    Et l'autre question reste en suspens : que représente cette heure qui est dans la table créneaux ?

                    Edit

                    En attendant, voici une requête qui permet de récupérer les effectifs par demi-heure sur une semaine, avec les totaux au début.

                    SELECT
                    		codeAgent,
                    		if(codeAgent IS NULL OR `date` IS NULL, null, prenom) AS prenom,
                    		if(codeAgent IS NULL OR `date` IS NULL, null, nom) AS nom,
                    		`date`,
                    		number
                    	FROM
                    				(
                    					SELECT
                    						codeAgent,
                    						prenom,
                    						nom,
                    						`date`,
                    						count(date) AS number
                    					FROM
                    								(
                    									SELECT
                    											(:date_debut + INTERVAL c.number MINUTE) AS date
                    										FROM
                    													(
                    														SELECT
                    																30 * (singles + tens + hundredths + thousands + then_thousands) number
                    															FROM
                    																		(
                    																			SELECT 0 singles
                    																			UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3
                    																			UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6
                    																			UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9
                    																		) singles
                    																JOIN
                    																		(
                    																			SELECT 0 tens
                    																			UNION ALL SELECT 10 UNION ALL SELECT 20 UNION ALL SELECT 30
                    																			UNION ALL SELECT 40 UNION ALL SELECT 50 UNION ALL SELECT 60
                    																			UNION ALL SELECT 70 UNION ALL SELECT 80 UNION ALL SELECT 90
                    																		) tens
                    																JOIN
                    																		(
                    																			SELECT 0 hundredths
                    																			UNION ALL SELECT 100 UNION ALL SELECT 200 UNION ALL SELECT 300
                    																			UNION ALL SELECT 400 UNION ALL SELECT 500 UNION ALL SELECT 600
                    																			UNION ALL SELECT 700 UNION ALL SELECT 800 UNION ALL SELECT 900
                    																		) hundredths
                    																JOIN
                    																		(
                    																			SELECT 0 thousands
                    																			UNION ALL SELECT 1000 UNION ALL SELECT 2000 UNION ALL SELECT 3000
                    																			UNION ALL SELECT 4000 UNION ALL SELECT 5000 UNION ALL SELECT 6000
                    																			UNION ALL SELECT 7000 UNION ALL SELECT 8000 UNION ALL SELECT 9000
                    																		) thousands
                    																JOIN
                    																		(
                    																			SELECT 0 then_thousands
                    																			UNION ALL SELECT 10000
                    																		) then_thousands
                    															ORDER BY
                    																number ASC
                    													) c
                    										WHERE
                    												c.number BETWEEN 0 AND 10030
                    								) tranche
                    						INNER JOIN
                    								agents
                    							ON
                    									tranche.date BETWEEN :date_debut AND :date_fin
                    					GROUP BY
                    						tranche.date,
                    						codeAgent
                    						WITH ROLLUP
                    				) temp
                    	ORDER BY
                            codeAgent,
                        	nom,
                        	`date`

                    -
                    Edité par Ymox 17 novembre 2019 à 21:19:25

                    • Partager sur Facebook
                    • Partager sur Twitter
                      18 novembre 2019 à 11:19:14

                      Héhé, pas facile de comprendre.
                      J'essaie de reformuler :

                      - En informatique, dans notre système d'alerte, notre feuille de garde journalière est chargée automatiquement tous les matins à 8h pétante, jusqu'au lendemain 7h59 et 59 secondes.

                      - Durant ces 24 heures, chaque pompier peut bien évidemment se mettre disponible, même si ce n'est pas sa semaine de garde, par contre la garde officielle (régime informatique "astreinte") ne démarre qu'à partir de 19h pour se terminer à 7h59.

                      En gros, si je ne suis pas de garde, en journée, je peux déclarer que je suis disponible de 10h à 16h, puis de 22h à 02h du matin (mais je ne serais pas d'astreinte, seulement disponible car ce n'est pas ma semaine de garde)

                      • Partager sur Facebook
                      • Partager sur Twitter
                        18 novembre 2019 à 13:04:09

                        D'accord.

                        Dans ton schéma, où sont enregistrées les dates de début et de fin de disponibilité ? Une disponibilité n'a qu'un créneau, un créneau n'a qu'une date, donc pour dire que je suis disponible de 19:00 à 22:00, quels enregistrements dans quelles tables diront cela ?

                        Edit

                        Voici déjà un script d'affichage, pas testé.

                        <table>
                        <?php
                        /* On admettra que
                         * - $disponibilities contient le résultat de la requête, avec les mêmes tris
                         *   au minimum
                         * - $dateStart contient la date et l'heure de début de la grille
                         * - $dateEnd contient la date et l'heure de fin de la grille
                         */
                        
                        /* C'est voulu. On initialise un élément précédent avec le premier résultat qui
                         * est ici le nombre de disponibilités totales sur l'entier de la période, donc
                         * quelque chose de probablement inutile. Mais au moins ça a la structure
                         * nécessaire */
                        $previous = $disponibilities->fetch();
                        $previous['date'] = $dateEnd;
                        
                        /* Et maintenant, on va boucler sur les autres résultats. Attention :
                         * normalement, chaque résultat représente une cellule, mais toutes ne sont pas
                         * nécessairement présentes pour chaque ligne ! */
                        while ($row = $disponibilities->fetch():
                        	$row['date'] = new \DateTimeImmutable($row['date']);
                        
                        	/* Du moment qu'on change d'agent, on change de ligne.
                             * Attention à la logique un peu particulière : on va traiter ici la fin de
                             * la ligne précédente ! */
                        	if ($row['codeAgent'] != $previous['codeAgent']):
                                /* L'agent de la ligne précédente n'était peut-être pas disponible pour
                                 * les derières tranches horaire ? Alors on doit finir de remplir la
                                 * ligne. Si vraiment il faut toutes les cellules, on utilisera une
                                 * simple boucle pour générer ce qu'il faut */
                                if ($dateEnd->format('Y-m-d') > $previous['date']->format('Y-m-d')):
                                    $skip = floor(($row['date']->getTimestamp() - $sdateStart->getTimestamp()) / (60 * 30));
                                ?>
                                <td colspan="<?= $skip ?>"></td>
                                <?php endif;
                                
                        		/* Une manière de déterminer si on est dans la première itération. On
                        		 * ne ferme une ligne que quand elle a été ouverte, donc dès la
                        		 * deuxième itération */
                        		if (!$previous['codeAgent'] && !$previous['date']): ?>
                        	</tr>
                        		<?php endif;?>
                        	<tr>
                        		<!-- Une des premières choses à afficher dans une (nouvelle) ligne,
                        			c'est le nom de l'agent -->
                        		<th><?= $row['nom'] . ' ' . $row['prenom']; ?></th>
                        	<?php endif;
                        
                        	/* Dans le cas où la personne n'est pas disponible dès le début de la
                        	 * grille, on va mettre une cellule unique qui remplace toutes celles qui
                        	 * seraient vides. C'est le même genre de cas qu'aux lignes 29 à 33, mais
                             * ici pour le début de la ligne */
                        	if ($dateStart->format('Y-m-d') < $row['date']->format('Y-m-d')):
                        		$skip = ceil(($row['date']->getTimestamp() - $sdateStart->getTimestamp()) / (60 * 30));
                        	?>
                        		<td colspan="<?= $skip ?>"></td>
                        	<?php endif; ?>
                        		<!-- C'est ici qu'il faut mettre les couleurs, icônes, nombres, etc. pour
                        			annoncer la disponibilité effective, le rôle, etc. -->
                        		<td>+1</td>
                        	<?php
                        	$previous = $row;
                        endwhile; ?>
                        	</tr>
                        </table>

                        -
                        Edité par Ymox 18 novembre 2019 à 16:27:47

                        • Partager sur Facebook
                        • Partager sur Twitter
                          18 novembre 2019 à 17:57:52

                          Etant donné que la création d'une feuille de garde pour une journée de 8h à 8h reste à la charge du chef d'équipe, j'avais pensé à la table disponibilites.

                          La feuille de garde est créée et stockée dans la table feuilles_garde.

                          Après si on peut faire mieux en moins de table, je suis preneur.

                          • Partager sur Facebook
                          • Partager sur Twitter
                            18 novembre 2019 à 19:41:11

                            Je suis désolé, mais je n'ai toujours pas les informations que je demande.

                            1. A quoi sert le champ timeCreneau dans la table creneaux ?
                            2. La table disponibilites ne contient qu'un lien vers un creneau, et un creneau n'a qu'une date/heure.
                              • C'est l'heure de début d'une disponibilité ?
                              • Ou c'est l'heure de fin d'une disponibilité ?
                              • Où se trouve de quoi calculer l'autre "limite" ?

                            Je t'ai demandé un exemple, c'était pour que tu puisses réfléchir à ces informations afin de me les fournir.
                            Si tu souhaites que je ne tienne pas compte de ton schéma, je peux en refaire un, mais je vais le simplifier et ce sera à toi d'ajouter ce qu'il te manquerait (les équipes, les grades, les statuts entre autres).

                            • Partager sur Facebook
                            • Partager sur Twitter
                              18 novembre 2019 à 20:07:06

                              Le champ timeCreneau sert uniquement à l'affichage dans les <td> que je génère. C'est pas la meilleure des méthodes, mais je suis dit pourquoi pas tenter comme ça ^^. Je pensais relier le timeCreneau à chaque case "carrée" comme sur mon Excel et ainsi affecter un code statut pour chaque créneau, ce qui aurait ainsi délimiter les créneaux de début et de fin.

                              Après si tu as plus simple et plus efficace de ton côté, je suis totalement preneur.

                              • Partager sur Facebook
                              • Partager sur Twitter
                                20 novembre 2019 à 18:32:33

                                Bonsoir,

                                J'ai récupéré ce que tu as mis dans ton premier post afin de tester un peu les possibilités.

                                Concernant la table "creneau", c'est une bonne idée. je l'ai réadapté :

                                CREATE TABLE IF NOT EXISTS `creneau` (
                                  `creneau_id` int(11) NOT NULL,
                                  `creneau_libelle` text NOT NULL,
                                  PRIMARY KEY (`creneau_id`)
                                ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
                                INSERT INTO `creneau` (`creneau_id`, `creneau_libelle`) VALUES
                                (1, '08:00'),
                                (2, '08:30'),
                                (3, '09:00'),
                                (4, '09:30'),
                                (5, '10:00'),
                                (6, '10:30'),
                                (7, '11:00'),
                                (8, '11:30'),
                                (9, '12:00'),
                                (10, '12:30'),
                                (11, '13:00'),
                                (12, '13:30'),
                                (13, '14:00'),
                                (14, '14:30'),
                                (15, '15:00'),
                                (16, '15:30'),
                                (17, '16:00'),
                                (18, '16:30'),
                                (19, '17:00'),
                                (20, '17:30'),
                                (21, '18:00'),
                                (22, '18:30'),
                                (23, '19:00'),
                                (24, '19:30'),
                                (25, '20:00'),
                                (26, '20:30'),
                                (27, '21:00'),
                                (28, '21:30'),
                                (29, '22:00'),
                                (30, '22:30'),
                                (31, '23:00'),
                                (32, '23:30'),
                                (33, '00:00'),
                                (34, '00:30'),
                                (35, '01:00'),
                                (36, '01:30'),
                                (37, '02:00'),
                                (38, '02:30'),
                                (39, '03:00'),
                                (40, '03:30'),
                                (41, '04:00'),
                                (42, '04:30'),
                                (43, '05:00'),
                                (44, '05:30'),
                                (45, '06:00'),
                                (46, '06:30'),
                                (47, '07:00'),
                                (48, '07:30');

                                Concernant l'affichage, je crois que tu pourrais avoir un truc comme ça :

                                Affichage test

                                • Partager sur Facebook
                                • Partager sur Twitter
                                Winter Is Coming - Explorez le forum : votre problème a déjà dû être traité ailleurs
                                  20 novembre 2019 à 22:28:06

                                  Voilà, j'ai quelque chose qui s'affiche comme je le souhaitais. La seule question que je me pose actuellement, c'est pourquoi la requête ne s'exécute pas ? Sachant que je n'ai aucun message d'erreur, que si je la mets dans phpMyAdmin et que j'y émule le remplacement des paramètres, j'ai bien les résultats attendus, mais en PHP, j'ai droit à une erreur d'appel à fetch() sur un booléen ligne 94. J'imagine un réglage quelconque de PHP ou une contrainte de MySQL par rapport aux requêtes préparées qui m'échappent totalement pour le coup.

                                  Sinon, faute d'avoir compris l'utilité de cette table creneaux plus tôt, j'ai placé une heure de début et une heure de fin dans la table disponibilites (sous forme de date et d'heure afin de pouvoir gérer plus que juste 24 heures, et de toute manière, du fait qu'une feuille de garde est à cheval sur deux jours civil, il faut cette notion de jour), et je n'ai pas utilisé le lien avec la feuille de garde — le résultat de la requête est en soi une feuille de garde.

                                  A noter que l'on pourrait remplir la table creneaux avec la sous-requête aux lignes 30 à 71, quitte à l'adapter pour ne générer que pour 24 heures à la fois, et ainsi l'utiliser comme table à joindre plutôt que l'actuelle table temporaire tranche, mais il faut vraiment conserver cette notion de date à mon avis, et ne pas avoir uniquement des heures. A toi de joindre sur les tables restantes afin de sélectionner de quoi faire une mise en forme différente. Je vois deux endroits pour ce faire : entre les lignes 76 et 77 ou entre les lignes 81 et 82. Attention cependant que dans le dernier cas, il faudra certainement utiliser des "jointures dirigées" plutôt que des INNER JOIN.

                                  <?php
                                  $cnx = new \PDO('mysql:host=localhost;dbname=tests;charset=utf8', 'root', null, array(\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION));
                                  $dateStart = new \DateTimeImmutable($_GET['date_start']);
                                  
                                  /* A voir comment faire ici. Soit on prévoit de spécifier une date de fin, soit
                                   * on prévoit de la calculer en ajoutant une durée à celle de début */
                                  $dateEnd = new \DateTimeImmutable($_GET['date_end']);
                                  $statement = $cnx->prepare("SELECT
                                          codeAgent,
                                          if(codeAgent IS NULL OR `date` IS NULL, null, prenom) AS prenom,
                                          if(codeAgent IS NULL OR `date` IS NULL, null, nom) AS nom,
                                          `date`,
                                          number
                                      FROM
                                                  (
                                                      SELECT
                                                          codeAgent,
                                                          prenom,
                                                          nom,
                                                          `date`,
                                                          count(date) AS number
                                                      FROM
                                                                  agents
                                                      	INNER JOIN
                                                      			disponibilites
                                                      		ON
                                                      			agents.codeAgent = disponibilites.id_agent
                                                          INNER JOIN
                                                                  (
                                                                      SELECT
                                                                              (:date_debut + INTERVAL c.number MINUTE) AS date
                                                                          FROM
                                                                                      (
                                                                                          SELECT
                                                                                                  30 * (singles + tens + hundredths + thousands + then_thousands) number
                                                                                              FROM
                                                                                                          (
                                                                                                              SELECT 0 singles
                                                                                                              UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3
                                                                                                              UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6
                                                                                                              UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9
                                                                                                          ) singles
                                                                                                  JOIN
                                                                                                          (
                                                                                                              SELECT 0 tens
                                                                                                              UNION ALL SELECT 10 UNION ALL SELECT 20 UNION ALL SELECT 30
                                                                                                              UNION ALL SELECT 40 UNION ALL SELECT 50 UNION ALL SELECT 60
                                                                                                              UNION ALL SELECT 70 UNION ALL SELECT 80 UNION ALL SELECT 90
                                                                                                          ) tens
                                                                                                  JOIN
                                                                                                          (
                                                                                                              SELECT 0 hundredths
                                                                                                              UNION ALL SELECT 100 UNION ALL SELECT 200 UNION ALL SELECT 300
                                                                                                              UNION ALL SELECT 400 UNION ALL SELECT 500 UNION ALL SELECT 600
                                                                                                              UNION ALL SELECT 700 UNION ALL SELECT 800 UNION ALL SELECT 900
                                                                                                          ) hundredths
                                                                                                  JOIN
                                                                                                          (
                                                                                                              SELECT 0 thousands
                                                                                                              UNION ALL SELECT 1000 UNION ALL SELECT 2000 UNION ALL SELECT 3000
                                                                                                              UNION ALL SELECT 4000 UNION ALL SELECT 5000 UNION ALL SELECT 6000
                                                                                                              UNION ALL SELECT 7000 UNION ALL SELECT 8000 UNION ALL SELECT 9000
                                                                                                          ) thousands
                                                                                                  JOIN
                                                                                                          (
                                                                                                              SELECT 0 then_thousands
                                                                                                              UNION ALL SELECT 10000
                                                                                                          ) then_thousands
                                                                                              ORDER BY
                                                                                                  number ASC
                                                                                      ) c
                                                                          WHERE
                                                                                  c.number BETWEEN 0 AND time_to_sec(timediff(:date_fin, :date_debut)) / 60 - 1
                                                                  ) tranche
                                                              ON
                                                                      tranche.date BETWEEN disponibilites.debut AND disponibilites.fin
                                                      GROUP BY
                                                          tranche.date,
                                                          codeAgent
                                                          WITH ROLLUP
                                                  ) temp
                                      ORDER BY
                                          codeAgent,
                                          nom,
                                          `date`");
                                  $disponibilities = $statement->execute([
                                      ':date_debut' => $dateStart->format('Y-m-d H:i:s'),
                                      ':date_fin' => $dateEnd->format('Y-m-d H:i:s'),
                                  ]);
                                  /* C'est voulu. On initialise un élément précédent avec le premier résultat qui
                                   * est ici le nombre de disponibilités totales sur l'entier de la période, donc
                                   * quelque chose de probablement inutile. Mais au moins ça a la structure
                                   * nécessaire */
                                  $previous = $disponibilities->fetch();
                                  $previous['date'] = $dateEnd;
                                  $previous['nom'] = 'Effectif par';
                                  $previous['prenom'] = 'tranche horaire';
                                  $previous['codeAgent'] = 0;
                                  ?>
                                  <table>
                                  	<tr>
                                  <?php 
                                  /* Et maintenant, on va boucler sur les autres résultats. Attention :
                                   * normalement, chaque résultat représente une cellule, mais toutes ne sont pas
                                   * nécessairement présentes pour chaque ligne ! */
                                  while ($row = $disponibilities->fetch()):
                                      $row['date'] = new \DateTimeImmutable($row['date']);
                                      /* Du moment qu'on change d'agent, on change de ligne.
                                       * Attention à la logique un peu particulière : on va traiter ici la fin de
                                       * la ligne précédente ! */
                                      if ($row['codeAgent'] !== $previous['codeAgent']):
                                          /* L'agent de la ligne précédente n'était peut-être pas disponible pour
                                           * les derières tranches horaire ? Alors on doit finir de remplir la
                                           * ligne. Si vraiment il faut toutes les cellules, on utilisera une
                                           * simple boucle pour générer ce qu'il faut */
                                          if ($dateEnd->format('Y-m-d H:i') > $previous['date']->format('Y-m-d H:i')):
                                              $skip = ceil(($dateEnd->getTimestamp() - $previous['date']->getTimestamp()) / (60 * 30)); ?>
                                          <td colspan="<?= $skip ?>"></td>
                                          <?php endif;
                                          /* Une manière de déterminer si on est dans la première itération. On
                                           * ne ferme une ligne que quand elle a été ouverte, donc dès la
                                           * deuxième itération */
                                          if (!$previous['codeAgent'] && !$previous['date']): ?>
                                      </tr>
                                          <?php endif;?>
                                      <tr>
                                          <!-- Une des premières choses à afficher dans une (nouvelle) ligne,
                                              c'est le nom de l'agent -->
                                          <th><?= $row['nom'] . ' ' . $row['prenom']; ?></th>
                                  		<?php
                                          /* Dans le cas où la personne n'est pas disponible dès le début de la
                                           * grille, on va mettre une cellule unique qui remplace toutes celles qui
                                           * seraient vides. C'est le même genre de cas qu'aux lignes 29 à 33, mais
                                           * ici pour le début de la ligne */
                                  		if ($dateStart->format('Y-m-d H:i') < $row['date']->format('Y-m-d H:i')):
                                              $skip = ceil(($row['date']->getTimestamp() - $dateStart->getTimestamp()) / (60 * 30)); ?>
                                              <td colspan="<?= $skip ?>"></td>
                                          <?php endif;
                                      endif; ?>
                                          <!-- C'est ici qu'il faut mettre les couleurs, icônes, nombres, etc. pour
                                              annoncer la disponibilité effective, le rôle, etc. -->
                                          <td><?= $row['number']; ?></td>
                                      <?php
                                      $previous = $row;
                                  endwhile;
                                  /* Comme auparavant, si la dernière personne n'est pas disponible jusqu'au bout
                                   * de la grille, on doit compléter */ 
                                  if ($dateEnd->format('Y-m-d H:i') > $previous['date']->format('Y-m-d H:i')):
                                      $skip = ceil(($dateEnd->getTimestamp() - $previous['date']->getTimestamp()) / (60 * 30)); ?>
                                          <td colspan="<?= $skip ?>"></td>
                                      </tr>
                                  <?php endif; ?>
                                  </table>
                                  • Partager sur Facebook
                                  • Partager sur Twitter
                                    21 novembre 2019 à 16:08:31

                                    @rga : la preview est pas mal, mais il est indispensable qu'on puisse renseigner sa disponibilité selon 3 codes couleurs comme le veut la table statuts :

                                    à savoir :

                                    Astreinte

                                    Disponible premier appel

                                    Disponible secondaire

                                    CREATE TABLE `statuts` (
                                    `codeStatut` int(11) NOT NULL,
                                    `lblStatut` varchar(255) NOT NULL,
                                    `couleur_statut` varchar(7) NOT NULL
                                    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
                                    • Partager sur Facebook
                                    • Partager sur Twitter
                                      21 novembre 2019 à 16:23:43

                                      Ah pardon, tu cherches à avoir un formulaire pour remplir la feuille de garde ?

                                      Il y a moyen d'adapter ce que j'ai fait assez facilement, mais je me demande si ce ne serait pas plus simple d'avoir deux champs à remplir que jusqu'à 24 cases à cocher (parce que pour plus, on va en mettre une "Tout cocher" par agent et jouer avec "j'enlève l'inutile" ou "j'ajoute juste ce qu'il faut, faut pas se fatiguer  :p )…

                                      • Partager sur Facebook
                                      • Partager sur Twitter
                                        21 novembre 2019 à 16:35:18

                                        Ymox, gros mea culpa, je me suis tellement concentré sur la partie affichage que j'ai oublié comment on remplissait tout ça.

                                        Le tableau de feuille de garde ne sera qu'un module parmi tant d'autres d'un portail WEB que je créé pour ma caserne. Le reste des modules sera développé par mes soins, car beaucoup moins complexe.

                                        Je compte partir de la table agents que j'ai décrite dans mon premier post, qui sera le socle commun de beaucoup de fonctionnalités de l'appli. En ce qui concerne le remplissage des feuilles de garde, sur notre logiciel actuel, cela se fait donc à la "journée (de 8h à 8h le lendemain matin)".

                                        Actuellement, on a un système de "palette de disponibilités" pour remplir les créneaux :

                                        Exemple ici :

                                        - Si je clique sur "Dispo" (couleur bleu), et que je veux me mettre dispo de 00h à 02h30, je clique sur la case 00 et la case de fin, et toutes les cases comprises dans cette intervalle prennent ma disponibilité en compte. Si je veux indiquer un autre régime de dispo, je clique sur une couleur différente et je sélectionne les cases souhaitées.

                                        Je trouve ça plutôt intuitif, mais je pense très difficile à reproduire.

                                        • Partager sur Facebook
                                        • Partager sur Twitter
                                          21 novembre 2019 à 16:44:53

                                          Alors oui et non, ça demandera pas mal de JavaScript, ça me paraît sûr.

                                          Moi je pensais bêtement à un "triplet" (début - fin - régime), et on pourrait en avoir autant qu'il en faudrait pour remplir les 24 heures, mais ce serait probablement trop de changements pour les utilisateurs  ^^

                                          • Partager sur Facebook
                                          • Partager sur Twitter
                                            21 novembre 2019 à 16:50:12

                                            Validé pour ça. Il faut juste que ça tape dans la table "statuts" pour qu'on soit libre d'ajouter des régimes autant que l'on souhaite, mais ça je pense que c'est peanut.
                                            • Partager sur Facebook
                                            • Partager sur Twitter
                                              4 décembre 2019 à 11:01:57

                                              C'est quoi la demande exactement ?

                                              • Partager sur Facebook
                                              • Partager sur Twitter

                                              [PHP] Tableau croisé : une vraie galère

                                              × Après avoir cliqué sur "Répondre" vous serez invité à vous connecter pour que votre message soit publié.
                                              × Attention, ce sujet est très ancien. Le déterrer n'est pas forcément approprié. Nous te conseillons de créer un nouveau sujet pour poser ta question.
                                              • Editeur
                                              • Markdown