diff --git a/migrations/Version20250508121539.php b/migrations/Version20250508121539.php new file mode 100644 index 0000000..dee4f9d --- /dev/null +++ b/migrations/Version20250508121539.php @@ -0,0 +1,44 @@ +addSql(<<<'SQL' + ALTER TABLE intervention ADD remarque TEXT DEFAULT NULL + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE intervention ADD start_date TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL + SQL); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql(<<<'SQL' + CREATE SCHEMA public + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE intervention DROP remarque + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE intervention DROP start_date + SQL); + } +} diff --git a/src/Controller/CalendrierController.php b/src/Controller/CalendrierController.php index c7c2f04..3084840 100644 --- a/src/Controller/CalendrierController.php +++ b/src/Controller/CalendrierController.php @@ -13,24 +13,21 @@ class CalendrierController extends AbstractController #[Route('/chauffagiste', name: 'app_calendrier_indexChauffagiste')] public function indexChauffagiste(InterventionRepository $interventionRepository): Response { - // Vérification : seul le chauffagiste connecté peut voir son propre calendrier $this->denyAccessUnlessGranted('ROLE_CHAUFFAGISTE'); - // Récupérer les interventions du chauffagiste connecté $interventions = $interventionRepository->findByUser($this->getUser()); - // Préparer les événements pour FullCalendar $events = []; foreach ($interventions as $intervention) { $events[] = [ - 'title' => $intervention->getTitle(), - 'start' => $intervention->getStartDate()->format('Y-m-d H:i:s'), - 'end' => $intervention->getEndDate()->format('Y-m-d H:i:s'), + 'title' => ' - ' . $intervention->getWording() . ' (' . $intervention->getStatus() . ')', + 'start' => $intervention->getTimestamp()?->format('Y-m-d\TH:i:s') ?? '', + 'end' => $intervention->getTimestamp()?->format('Y-m-d\TH:i:s') ?? '', 'description' => $intervention->getDescription(), + 'url' => $this->generateUrl('app_intervention_show', ['id' => $intervention->getId()]) ]; } - // Passer les événements à la vue return $this->render('calendrier/indexChauffagiste.html.twig', [ 'events' => json_encode($events), ]); @@ -39,24 +36,21 @@ class CalendrierController extends AbstractController #[Route('/secretaire', name: 'app_calendrier_indexSecretaire')] public function indexSecretaire(InterventionRepository $interventionRepository): Response { - // Vérification : seul le secrétaire peut accéder à ce calendrier $this->denyAccessUnlessGranted('ROLE_SECRETAIRE'); - // Récupérer toutes les interventions de tous les chauffagistes $interventions = $interventionRepository->findAll(); - // Préparer les événements pour FullCalendar $events = []; foreach ($interventions as $intervention) { $events[] = [ - 'title' => $intervention->getTitle(), - 'start' => $intervention->getStartDate()->format('Y-m-d H:i:s'), - 'end' => $intervention->getEndDate()->format('Y-m-d H:i:s'), + 'title' => ' - ' . $intervention->getWording() . ' (' . $intervention->getStatus() . ')', + 'start' => $intervention->getTimestamp()?->format('Y-m-d\TH:i:s') ?? '', + 'end' => $intervention->getTimestamp()?->format('Y-m-d\TH:i:s') ?? '', 'description' => $intervention->getDescription(), + 'url' => $this->generateUrl('app_intervention_show', ['id' => $intervention->getId()]) ]; } - // Passer les événements à la vue return $this->render('calendrier/indexSecretaire.html.twig', [ 'events' => json_encode($events), ]); @@ -65,24 +59,21 @@ class CalendrierController extends AbstractController #[Route('/admin', name: 'app_calendrier_index')] public function indexAdmin(InterventionRepository $interventionRepository): Response { - // Vérification : seul un admin peut accéder à ce calendrier $this->denyAccessUnlessGranted('ROLE_ADMIN'); - // Récupérer toutes les interventions de tous les chauffagistes $interventions = $interventionRepository->findAll(); - // Préparer les événements pour FullCalendar $events = []; foreach ($interventions as $intervention) { $events[] = [ - 'title' => $intervention->getTitle(), - 'start' => $intervention->getStartDate()->format('Y-m-d H:i:s'), - 'end' => $intervention->getEndDate()->format('Y-m-d H:i:s'), + 'title' => ' - ' . $intervention->getWording() . ' (' . $intervention->getStatus() . ')', + 'start' => $intervention->getTimestamp()?->format('Y-m-d\TH:i:s') ?? '', + 'end' => $intervention->getTimestamp()?->format('Y-m-d\TH:i:s') ?? '', 'description' => $intervention->getDescription(), + 'url' => $this->generateUrl('app_intervention_show', ['id' => $intervention->getId()]) ]; } - // Passer les événements à la vue return $this->render('calendrier/index.html.twig', [ 'events' => json_encode($events), ]); diff --git a/src/Controller/InterventionController.php b/src/Controller/InterventionController.php index bb6b415..da1f290 100644 --- a/src/Controller/InterventionController.php +++ b/src/Controller/InterventionController.php @@ -4,6 +4,7 @@ namespace App\Controller; use App\Entity\Intervention; use App\Form\InterventionType; +use App\Form\RemarqueType; use App\Repository\InterventionRepository; use Doctrine\ORM\EntityManagerInterface; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; @@ -24,7 +25,7 @@ class InterventionController extends AbstractController } #[Route('/new', name: 'app_intervention_new', methods: ['GET', 'POST'])] - public function new(Request $request, EntityManagerInterface $entityManager): Response + public function new(Request $request, EntityManagerInterface $entityManager, InterventionRepository $interventionRepository): Response { $this->denyUnlessAdminOrSecretaire(); @@ -33,6 +34,36 @@ class InterventionController extends AbstractController $form->handleRequest($request); if ($form->isSubmitted() && $form->isValid()) { + $timestamp = $intervention->getTimestamp(); + $chauffagiste = $intervention->getUser(); + $vehicule = $intervention->getVehicle(); + + $conflictsUser = $interventionRepository->findBy([ + 'Timestamp' => $timestamp, + 'user' => $chauffagiste, + ]); + + if ($conflictsUser) { + $this->addFlash('error', 'Ce chauffagiste a déjà une intervention à cette date.'); + return $this->render('intervention/new.html.twig', [ + 'form' => $form, + ]); + } + + if ($vehicule) { + $conflictsVehicule = $interventionRepository->findBy([ + 'Timestamp' => $timestamp, + 'vehicle' => $vehicule, + ]); + + if ($conflictsVehicule) { + $this->addFlash('error', 'Ce véhicule est déjà utilisé à cette date.'); + return $this->render('intervention/new.html.twig', [ + 'form' => $form, + ]); + } + } + $entityManager->persist($intervention); $entityManager->flush(); @@ -54,7 +85,7 @@ class InterventionController extends AbstractController } #[Route('/{id}/edit', name: 'app_intervention_edit', methods: ['GET', 'POST'])] - public function edit(Request $request, Intervention $intervention, EntityManagerInterface $entityManager): Response + public function edit(Request $request, Intervention $intervention, EntityManagerInterface $entityManager, InterventionRepository $interventionRepository): Response { $this->denyUnlessAdminOrSecretaire(); @@ -62,8 +93,49 @@ class InterventionController extends AbstractController $form->handleRequest($request); if ($form->isSubmitted() && $form->isValid()) { - $entityManager->flush(); + $timestamp = $intervention->getTimestamp(); + $chauffagiste = $intervention->getUser(); + $vehicule = $intervention->getVehicle(); + $conflictUser = $interventionRepository->createQueryBuilder('i') + ->where('i.Timestamp = :time') + ->andWhere('i.user = :user') + ->andWhere('i != :current') + ->setParameter('time', $timestamp) + ->setParameter('user', $chauffagiste) + ->setParameter('current', $intervention) + ->getQuery() + ->getResult(); + + if ($conflictUser) { + $this->addFlash('error', 'Ce chauffagiste a déjà une autre intervention à cette date.'); + return $this->render('intervention/edit.html.twig', [ + 'form' => $form, + 'intervention' => $intervention, + ]); + } + + if ($vehicule) { + $conflictVehicule = $interventionRepository->createQueryBuilder('i') + ->where('i.Timestamp = :time') + ->andWhere('i.vehicle = :vehicule') + ->andWhere('i != :current') + ->setParameter('time', $timestamp) + ->setParameter('user', $chauffagiste) + ->setParameter('current', $intervention) + ->getQuery() + ->getResult(); + + if ($conflictVehicule) { + $this->addFlash('error', 'Ce véhicule est déjà utilisé à cette date.'); + return $this->render('intervention/edit.html.twig', [ + 'form' => $form, + 'intervention' => $intervention, + ]); + } + } + + $entityManager->flush(); return $this->redirectToRoute('app_intervention_index'); } @@ -89,7 +161,6 @@ class InterventionController extends AbstractController #[Route('/{id}/remarque', name: 'app_intervention_remarque', methods: ['GET', 'POST'])] public function ajouterRemarque(Request $request, Intervention $intervention, EntityManagerInterface $entityManager): Response { - // Vérifie que l'utilisateur est le chauffagiste assigné à l'intervention $user = $this->getUser(); if (!$this->isGranted('ROLE_CHAUFFAGISTE') || $intervention->getUser() !== $user) { throw $this->createAccessDeniedException("Vous ne pouvez modifier que vos propres interventions."); @@ -100,7 +171,6 @@ class InterventionController extends AbstractController if ($form->isSubmitted() && $form->isValid()) { $entityManager->flush(); - $this->addFlash('success', 'Remarque ajoutée avec succès.'); return $this->redirectToRoute('app_intervention_show', ['id' => $intervention->getId()]); } diff --git a/src/Controller/UserController.php b/src/Controller/UserController.php index 04786a7..337e58a 100644 --- a/src/Controller/UserController.php +++ b/src/Controller/UserController.php @@ -13,9 +13,9 @@ use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface; use Symfony\Component\Routing\Annotation\Route; use Symfony\Component\Security\Http\Attribute\IsGranted; +#[IsGranted('ROLE_ADMIN')] // accès global restreint final class UserController extends AbstractController { - // Route pour afficher tous les utilisateurs #[Route('/user', name: 'app_user_index', methods: ['GET'])] public function index(UserRepository $userRepository): Response { @@ -24,7 +24,6 @@ final class UserController extends AbstractController ]); } - // Route pour créer un nouvel utilisateur #[Route('/user/new', name: 'app_user_new', methods: ['GET', 'POST'])] public function new(Request $request, EntityManagerInterface $entityManager, UserPasswordHasherInterface $passwordHasher): Response { @@ -33,7 +32,6 @@ final class UserController extends AbstractController $form->handleRequest($request); if ($form->isSubmitted() && $form->isValid()) { - // Hash du mot de passe $plainPassword = $form->get('plainPassword')->getData(); $hashedPassword = $passwordHasher->hashPassword($user, $plainPassword); $user->setPassword($hashedPassword); @@ -50,7 +48,6 @@ final class UserController extends AbstractController ]); } - // Route pour afficher un utilisateur spécifique #[Route('/user/{id}', name: 'app_user_show', methods: ['GET'])] public function show(Utilisateur $user): Response { @@ -59,21 +56,9 @@ final class UserController extends AbstractController ]); } - // Route pour modifier un utilisateur spécifique #[Route('/user/{id}/edit', name: 'app_user_edit', methods: ['GET', 'POST'])] public function edit(Request $request, Utilisateur $user, EntityManagerInterface $entityManager): Response { - if ($this->isGranted('ROLE_SECRETAIRE') && $user->hasRole('ROLE_SECRETAIRE')) { - throw $this->createAccessDeniedException('Vous ne pouvez pas modifier un autre secrétaire.'); - } - - if ($this->isGranted('ROLE_CHAUFFAGISTE') && ($user->hasRole('ROLE_CHAUFFAGISTE') || $user->hasRole('ROLE_SECRETAIRE'))) { - throw $this->createAccessDeniedException('Vous ne pouvez pas modifier un admin.'); - } - - // On s'assure que seul un admin peut éditer un autre admin - $this->denyAccessUnlessGranted('ROLE_ADMIN'); - $form = $this->createForm(UserType::class, $user); $form->handleRequest($request); @@ -89,15 +74,12 @@ final class UserController extends AbstractController ]); } - // Route pour supprimer un utilisateur spécifique - #[IsGranted('ROLE_ADMIN', 'ROLE_SECRETAIRE')] #[Route('/user/{id}', name: 'app_user_delete', methods: ['POST'])] - public function delete(Request $request, Utilisateur $utilisateur): Response + public function delete(Request $request, Utilisateur $utilisateur, EntityManagerInterface $entityManager): Response { if ($this->isCsrfTokenValid('delete' . $utilisateur->getId(), $request->request->get('_token'))) { - $this->entityManager->remove($utilisateur); - $this->entityManager->flush(); - + $entityManager->remove($utilisateur); + $entityManager->flush(); $this->addFlash('success', 'Utilisateur supprimé avec succès.'); } else { $this->addFlash('error', 'Token CSRF invalide.'); diff --git a/src/Form/InterventionType.php b/src/Form/InterventionType.php index 01a6d9a..ae2aa9b 100644 --- a/src/Form/InterventionType.php +++ b/src/Form/InterventionType.php @@ -7,6 +7,7 @@ use App\Entity\Intervention; use App\Entity\Stock; use App\Entity\Utilisateur; use App\Entity\Vehicle; +use Doctrine\ORM\EntityRepository; use Symfony\Bridge\Doctrine\Form\Type\EntityType; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\Extension\Core\Type\DateTimeType; @@ -28,7 +29,15 @@ class InterventionType extends AbstractType ->add('Status', TextType::class) ->add('user', EntityType::class, [ 'class' => Utilisateur::class, - 'choice_label' => 'FirstName', // ou autre (LastName, email) + 'choice_label' => function (Utilisateur $user) { + return $user->getFirstName() . ' ' . $user->getLastName(); + }, + 'query_builder' => function (EntityRepository $er) { + return $er->createQueryBuilder('u') + ->where('JSON_CONTAINS(u.roles, :role) = 1') + ->setParameter('role', '"ROLE_CHAUFFAGISTE"'); + }, + 'label' => 'Chauffagiste assigné', ]) ->add('fault', EntityType::class, [ 'class' => Fault::class, @@ -44,16 +53,7 @@ class InterventionType extends AbstractType 'choice_label' => 'Wording', 'multiple' => true, 'expanded' => true, - ]) - ; - -// // 👉 Sélecteur de véhicule -// ->add('vehicule', EntityType::class, [ -// 'class' => Vehicule::class, -// 'choice_label' => 'immatriculation', // ou n'importe quel champ que tu veux afficher -// 'placeholder' => 'Aucun véhicule sélectionné', -// 'required' => false, -// ]); + ]); } public function configureOptions(OptionsResolver $resolver): void diff --git a/templates/base.html.twig b/templates/base.html.twig index d841d1b..900a8e7 100644 --- a/templates/base.html.twig +++ b/templates/base.html.twig @@ -95,18 +95,23 @@