From 8580911c1a54901d0d475da34256644affaf9ba1 Mon Sep 17 00:00:00 2001 From: sermandm Date: Thu, 8 May 2025 12:31:40 +0200 Subject: [PATCH] =?UTF-8?q?feat:=20gestion=20des=20remarques=20chauffagist?= =?UTF-8?q?es=20+=20refacto=20s=C3=A9curit=C3=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Ajout du champ 'Remarque' dans l'entité Intervention - Création d'un formulaire RemarqueType dédié - Ajout d'une route /intervention/{id}/remarque accessible uniquement au chauffagiste assigné - Mise en place d'un contrôleur sécurisé pour ajouter une remarque - Création de la vue intervention/remarque.html.twig - Affichage conditionnel du bouton 'Ajouter une remarque' dans show.html.twig - Séparation stricte des rôles : seuls les chauffagistes peuvent ajouter leur remarque - Mise à jour de tous les contrôleurs avec denyUnlessAdminOrSecretaire() pour clarifier les accès - Redirection des dashboards et calendriers selon rôle (admin, secrétaire, chauffagiste) ✅ Prochaine étape : liaison compétences ↔ pannes ou gestion des stocks associés --- .idea/dataSources.xml | 4 +- README.md | 10 ++- src/Controller/AuthenticationController.php | 15 ++-- src/Controller/DashboardController.php | 32 ++++--- src/Controller/FaultController.php | 57 +++++-------- src/Controller/InterventionController.php | 95 +++++++++------------ src/Controller/SkillController.php | 38 ++++++--- src/Controller/StockController.php | 35 +++++--- src/Controller/UserController.php | 29 +++---- src/Controller/VehicleController.php | 57 +++++-------- src/Entity/Intervention.php | 13 +++ src/Form/RemarqueType.php | 31 +++++++ templates/intervention/remarque.html.twig | 18 ++++ templates/intervention/show.html.twig | 5 ++ 14 files changed, 241 insertions(+), 198 deletions(-) create mode 100644 src/Form/RemarqueType.php create mode 100644 templates/intervention/remarque.html.twig diff --git a/.idea/dataSources.xml b/.idea/dataSources.xml index d95fc33..14fb0ce 100644 --- a/.idea/dataSources.xml +++ b/.idea/dataSources.xml @@ -1,11 +1,11 @@ - + postgresql true org.postgresql.Driver - jdbc:postgresql://localhost:5432/hegreetconfort + jdbc:postgresql://localhost:5433/ $ProjectFileDir$ diff --git a/README.md b/README.md index 5e824f1..b77de51 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ Cette application permet de gérer les interventions, les utilisateurs (chauffag ``` 2. Installez les dépendances avec Composer : ```bash - cd chauffagiste-app + cd HegreEtConfort composer install ``` 3. Créez la base de données : @@ -40,7 +40,13 @@ Cette application permet de gérer les interventions, les utilisateurs (chauffag ```bash php bin/console doctrine:migrations:migrate ``` -5. Lancez le serveur Symfony : +5. Exécuter cette insertion dans la console PostgreSQL pour créer votre premier utilisateur admin : + ```bash + INSERT INTO "HegreEtConfort".public.utilisateur (id, email, first_name, last_name, birth_date, phone, roles, password) + VALUES (1000, 'admin@admin.admin', 'admin', 'admin', '2025-04-10', 'admin', '["ROLE_ADMIN"]', '$2y$13$4jqoZVgncgDJ6oPFDswZeeiVmt9TF2AC.xoBwyyrrbNl5Xz8r.50e'); + ``` + +6. Lancez le serveur Symfony : ```bash symfony server:start ``` diff --git a/src/Controller/AuthenticationController.php b/src/Controller/AuthenticationController.php index f80b8c1..ffc40ab 100644 --- a/src/Controller/AuthenticationController.php +++ b/src/Controller/AuthenticationController.php @@ -3,25 +3,22 @@ namespace App\Controller; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; +use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\Routing\Attribute\Route; +use Symfony\Component\Routing\Annotation\Route; use Symfony\Component\Security\Http\Authentication\AuthenticationUtils; class AuthenticationController extends AbstractController { - #[Route(path: '/', name: 'app_login')] + #[Route(path: '/login', name: 'app_login')] public function login(AuthenticationUtils $authenticationUtils): Response { - // Get the login error if there is one + // get the login error if there is one $error = $authenticationUtils->getLastAuthenticationError(); - - // Last username entered by the user + // last username entered by the user $lastUsername = $authenticationUtils->getLastUsername(); - return $this->render('login/index.html.twig', [ - 'last_username' => $lastUsername, - 'error' => $error, - ]); + return $this->render('authentication/login.html.twig', ['last_username' => $lastUsername, 'error' => $error]); } #[Route(path: '/logout', name: 'app_logout')] diff --git a/src/Controller/DashboardController.php b/src/Controller/DashboardController.php index 2039863..729bd41 100644 --- a/src/Controller/DashboardController.php +++ b/src/Controller/DashboardController.php @@ -4,28 +4,26 @@ namespace App\Controller; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\Routing\Attribute\Route; +use Symfony\Component\Routing\Annotation\Route; +#[Route('/dashboard')] class DashboardController extends AbstractController { - #[Route('/admin/dashboard', name: 'admin_dashboard')] - public function admin(): Response + #[Route('/', name: 'dashboard')] + public function index(): Response { - $this->denyAccessUnlessGranted('ROLE_ADMIN'); - return $this->render('dashboard/admin.html.twig'); - } + if ($this->isGranted('ROLE_ADMIN')) { + return $this->render('admin.html.twig'); + } - #[Route('/secretaire/dashboard', name: 'secretaire_dashboard')] - public function secretaire(): Response - { - $this->denyAccessUnlessGranted('ROLE_SECRETAIRE'); - return $this->render('dashboard/secretaire.html.twig'); - } + if ($this->isGranted('ROLE_SECRETAIRE')) { + return $this->render('secretaire.html.twig'); + } - #[Route('/chauffagiste/dashboard', name: 'chauffagiste_dashboard')] - public function chauffagiste(): Response - { - $this->denyAccessUnlessGranted('ROLE_CHAUFFAGISTE'); - return $this->render('dashboard/chauffagiste.html.twig'); + if ($this->isGranted('ROLE_CHAUFFAGISTE')) { + return $this->render('chauffagiste.html.twig'); + } + + throw $this->createAccessDeniedException('Vous ne pouvez pas accéder à ce tableau de bord.'); } } diff --git a/src/Controller/FaultController.php b/src/Controller/FaultController.php index cd1b979..3238042 100644 --- a/src/Controller/FaultController.php +++ b/src/Controller/FaultController.php @@ -9,45 +9,36 @@ use Doctrine\ORM\EntityManagerInterface; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\Routing\Attribute\Route; +use Symfony\Component\Routing\Annotation\Route; #[Route('/fault')] -final class FaultController extends AbstractController +class FaultController extends AbstractController { - #[Route(name: 'app_fault_index', methods: ['GET'])] + #[Route('/', name: 'app_fault_index', methods: ['GET'])] public function index(FaultRepository $faultRepository): Response { - // Filtrage des pannes : un chauffagiste ne peut voir que ses pannes - $faults = $this->isGranted('ROLE_CHAUFFAGISTE') - ? $faultRepository->findByUser($this->getUser()) // Filtre les pannes par utilisateur - : $faultRepository->findAll(); // Admins voient toutes les pannes - + $this->denyUnlessAdminOrSecretaire(); return $this->render('fault/index.html.twig', [ - 'faults' => $faults, + 'faults' => $faultRepository->findAll(), ]); } #[Route('/new', name: 'app_fault_new', methods: ['GET', 'POST'])] public function new(Request $request, EntityManagerInterface $entityManager): Response { + $this->denyUnlessAdminOrSecretaire(); + $fault = new Fault(); $form = $this->createForm(FaultType::class, $fault); $form->handleRequest($request); if ($form->isSubmitted() && $form->isValid()) { - // Associe la panne à un chauffagiste si c'est un chauffagiste - if ($this->isGranted('ROLE_CHAUFFAGISTE')) { - $fault->setUser($this->getUser()); - } - $entityManager->persist($fault); $entityManager->flush(); - - return $this->redirectToRoute('app_fault_index', [], Response::HTTP_SEE_OTHER); + return $this->redirectToRoute('app_fault_index'); } return $this->render('fault/new.html.twig', [ - 'fault' => $fault, 'form' => $form, ]); } @@ -55,11 +46,7 @@ final class FaultController extends AbstractController #[Route('/{id}', name: 'app_fault_show', methods: ['GET'])] public function show(Fault $fault): Response { - // Un chauffagiste ne peut voir que ses pannes - if ($this->isGranted('ROLE_CHAUFFAGISTE') && $fault->getUser() !== $this->getUser()) { - throw $this->createAccessDeniedException('Vous ne pouvez pas voir cette panne.'); - } - + $this->denyUnlessAdminOrSecretaire(); return $this->render('fault/show.html.twig', [ 'fault' => $fault, ]); @@ -68,39 +55,39 @@ final class FaultController extends AbstractController #[Route('/{id}/edit', name: 'app_fault_edit', methods: ['GET', 'POST'])] public function edit(Request $request, Fault $fault, EntityManagerInterface $entityManager): Response { - // Un chauffagiste ne peut modifier que ses propres pannes - if ($this->isGranted('ROLE_CHAUFFAGISTE') && $fault->getUser() !== $this->getUser()) { - throw $this->createAccessDeniedException('Vous ne pouvez pas modifier cette panne.'); - } + $this->denyUnlessAdminOrSecretaire(); $form = $this->createForm(FaultType::class, $fault); $form->handleRequest($request); if ($form->isSubmitted() && $form->isValid()) { $entityManager->flush(); - - return $this->redirectToRoute('app_fault_index', [], Response::HTTP_SEE_OTHER); + return $this->redirectToRoute('app_fault_index'); } return $this->render('fault/edit.html.twig', [ - 'fault' => $fault, 'form' => $form, + 'fault' => $fault, ]); } #[Route('/{id}', name: 'app_fault_delete', methods: ['POST'])] public function delete(Request $request, Fault $fault, EntityManagerInterface $entityManager): Response { - // Un chauffagiste ne peut supprimer que ses propres pannes - if ($this->isGranted('ROLE_CHAUFFAGISTE') && $fault->getUser() !== $this->getUser()) { - throw $this->createAccessDeniedException('Vous ne pouvez pas supprimer cette panne.'); - } + $this->denyUnlessAdminOrSecretaire(); - if ($this->isCsrfTokenValid('delete'.$fault->getId(), $request->get('csrf_token'))) { + if ($this->isCsrfTokenValid('delete'.$fault->getId(), $request->request->get('_token'))) { $entityManager->remove($fault); $entityManager->flush(); } - return $this->redirectToRoute('app_fault_index', [], Response::HTTP_SEE_OTHER); + return $this->redirectToRoute('app_fault_index'); + } + + private function denyUnlessAdminOrSecretaire(): void + { + if (!$this->isGranted('ROLE_ADMIN') && !$this->isGranted('ROLE_SECRETAIRE')) { + throw $this->createAccessDeniedException(); + } } } diff --git a/src/Controller/InterventionController.php b/src/Controller/InterventionController.php index 8f9e352..bb6b415 100644 --- a/src/Controller/InterventionController.php +++ b/src/Controller/InterventionController.php @@ -7,51 +7,39 @@ use App\Form\InterventionType; use App\Repository\InterventionRepository; use Doctrine\ORM\EntityManagerInterface; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; -use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\Routing\Attribute\Route; +use Symfony\Component\Routing\Annotation\Route; #[Route('/intervention')] -final class InterventionController extends AbstractController +class InterventionController extends AbstractController { - #[Route(name: 'app_intervention_index', methods: ['GET'])] + #[Route('/', name: 'app_intervention_index', methods: ['GET'])] public function index(InterventionRepository $interventionRepository): Response { - // Vérifier si l'utilisateur est un chauffagiste, pour filtrer ses interventions - if ($this->isGranted('ROLE_CHAUFFAGISTE')) { - $interventions = $interventionRepository->findByUser($this->getUser()); // On filtre par utilisateur connecté - } else { - // Les autres rôles (admin) peuvent voir toutes les interventions - $interventions = $interventionRepository->findAll(); - } - + $this->denyUnlessAdminOrSecretaire(); return $this->render('intervention/index.html.twig', [ - 'interventions' => $interventions, + 'interventions' => $interventionRepository->findAll(), ]); } #[Route('/new', name: 'app_intervention_new', methods: ['GET', 'POST'])] public function new(Request $request, EntityManagerInterface $entityManager): Response { + $this->denyUnlessAdminOrSecretaire(); + $intervention = new Intervention(); $form = $this->createForm(InterventionType::class, $intervention); $form->handleRequest($request); if ($form->isSubmitted() && $form->isValid()) { - // Si l'utilisateur est un chauffagiste, on associe l'intervention à lui - if ($this->isGranted('ROLE_CHAUFFAGISTE')) { - $intervention->setUser($this->getUser()); - } - $entityManager->persist($intervention); $entityManager->flush(); - return $this->redirectToRoute('app_intervention_index', [], Response::HTTP_SEE_OTHER); + return $this->redirectToRoute('app_intervention_index'); } return $this->render('intervention/new.html.twig', [ - 'intervention' => $intervention, 'form' => $form, ]); } @@ -59,11 +47,7 @@ final class InterventionController extends AbstractController #[Route('/{id}', name: 'app_intervention_show', methods: ['GET'])] public function show(Intervention $intervention): Response { - // Vérifier si l'utilisateur peut voir cette intervention (chauffagiste ne voit que ses interventions) - if ($this->isGranted('ROLE_CHAUFFAGISTE') && $intervention->getUser() !== $this->getUser()) { - throw $this->createAccessDeniedException('Vous ne pouvez pas voir cette intervention.'); - } - + $this->denyUnlessAdminOrSecretaire(); return $this->render('intervention/show.html.twig', [ 'intervention' => $intervention, ]); @@ -72,10 +56,7 @@ final class InterventionController extends AbstractController #[Route('/{id}/edit', name: 'app_intervention_edit', methods: ['GET', 'POST'])] public function edit(Request $request, Intervention $intervention, EntityManagerInterface $entityManager): Response { - // Vérification de sécurité : un chauffagiste ne peut modifier que ses propres interventions - if ($this->isGranted('ROLE_CHAUFFAGISTE') && $intervention->getUser() !== $this->getUser()) { - throw $this->createAccessDeniedException('Vous ne pouvez pas modifier cette intervention.'); - } + $this->denyUnlessAdminOrSecretaire(); $form = $this->createForm(InterventionType::class, $intervention); $form->handleRequest($request); @@ -83,49 +64,57 @@ final class InterventionController extends AbstractController if ($form->isSubmitted() && $form->isValid()) { $entityManager->flush(); - return $this->redirectToRoute('app_intervention_index', [], Response::HTTP_SEE_OTHER); + return $this->redirectToRoute('app_intervention_index'); } return $this->render('intervention/edit.html.twig', [ - 'intervention' => $intervention, 'form' => $form, + 'intervention' => $intervention, ]); } #[Route('/{id}', name: 'app_intervention_delete', methods: ['POST'])] public function delete(Request $request, Intervention $intervention, EntityManagerInterface $entityManager): Response { - // Vérification de sécurité : un chauffagiste ne peut supprimer que ses propres interventions - if ($this->isGranted('ROLE_CHAUFFAGISTE') && $intervention->getUser() !== $this->getUser()) { - throw $this->createAccessDeniedException('Vous ne pouvez pas supprimer cette intervention.'); - } + $this->denyUnlessAdminOrSecretaire(); - if ($this->isCsrfTokenValid('delete'.$intervention->getId(), $request->get('csrf_token'))) { + if ($this->isCsrfTokenValid('delete' . $intervention->getId(), $request->request->get('_token'))) { $entityManager->remove($intervention); $entityManager->flush(); } - return $this->redirectToRoute('app_intervention_index', [], Response::HTTP_SEE_OTHER); + return $this->redirectToRoute('app_intervention_index'); } - - - #[Route('/api/interventions', name: 'api_interventions')] - public function apiInterventions(InterventionRepository $repo): JsonResponse + #[Route('/{id}/remarque', name: 'app_intervention_remarque', methods: ['GET', 'POST'])] + public function ajouterRemarque(Request $request, Intervention $intervention, EntityManagerInterface $entityManager): Response { - $interventions = $repo->findAll(); - - $events = []; - - foreach ($interventions as $intervention) { - $events[] = [ - 'id' => $intervention->getId(), - 'title' => $intervention->getWording(), // ou getTitre() selon ton entité - 'start' => $intervention->getDate()->format('Y-m-d\TH:i:s'), - // ajoute 'end' si tu veux une durée - ]; + // 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."); } - return $this->json($events); + $form = $this->createForm(RemarqueType::class, $intervention); + $form->handleRequest($request); + + 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()]); + } + + return $this->render('intervention/remarque.html.twig', [ + 'form' => $form, + 'intervention' => $intervention, + ]); + } + + private function denyUnlessAdminOrSecretaire(): void + { + if (!$this->isGranted('ROLE_ADMIN') && !$this->isGranted('ROLE_SECRETAIRE')) { + throw $this->createAccessDeniedException(); + } } } diff --git a/src/Controller/SkillController.php b/src/Controller/SkillController.php index e8d4e1f..db97833 100644 --- a/src/Controller/SkillController.php +++ b/src/Controller/SkillController.php @@ -9,16 +9,16 @@ use Doctrine\ORM\EntityManagerInterface; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\Routing\Attribute\Route; +use Symfony\Component\Routing\Annotation\Route; #[Route('/skill')] -final class SkillController extends AbstractController +class SkillController extends AbstractController { - #[Route(name: 'app_skill_index', methods: ['GET'])] + #[Route('/', name: 'app_skill_index', methods: ['GET'])] public function index(SkillRepository $skillRepository): Response { - $this->denyAccessUnlessGranted('ROLE_ADMIN'); - return $this->render('skill/admin.html.twig', [ + $this->denyUnlessAdminOrSecretaire(); + return $this->render('skill/index.html.twig', [ 'skills' => $skillRepository->findAll(), ]); } @@ -26,7 +26,8 @@ final class SkillController extends AbstractController #[Route('/new', name: 'app_skill_new', methods: ['GET', 'POST'])] public function new(Request $request, EntityManagerInterface $entityManager): Response { - $this->denyAccessUnlessGranted('ROLE_ADMIN'); + $this->denyUnlessAdminOrSecretaire(); + $skill = new Skill(); $form = $this->createForm(SkillType::class, $skill); $form->handleRequest($request); @@ -35,11 +36,10 @@ final class SkillController extends AbstractController $entityManager->persist($skill); $entityManager->flush(); - return $this->redirectToRoute('app_skill_index', [], Response::HTTP_SEE_OTHER); + return $this->redirectToRoute('app_skill_index'); } return $this->render('skill/new.html.twig', [ - 'skill' => $skill, 'form' => $form, ]); } @@ -47,6 +47,7 @@ final class SkillController extends AbstractController #[Route('/{id}', name: 'app_skill_show', methods: ['GET'])] public function show(Skill $skill): Response { + $this->denyUnlessAdminOrSecretaire(); return $this->render('skill/show.html.twig', [ 'skill' => $skill, ]); @@ -55,31 +56,40 @@ final class SkillController extends AbstractController #[Route('/{id}/edit', name: 'app_skill_edit', methods: ['GET', 'POST'])] public function edit(Request $request, Skill $skill, EntityManagerInterface $entityManager): Response { - $this->denyAccessUnlessGranted('ROLE_ADMIN'); + $this->denyUnlessAdminOrSecretaire(); + $form = $this->createForm(SkillType::class, $skill); $form->handleRequest($request); if ($form->isSubmitted() && $form->isValid()) { $entityManager->flush(); - return $this->redirectToRoute('app_skill_index', [], Response::HTTP_SEE_OTHER); + return $this->redirectToRoute('app_skill_index'); } return $this->render('skill/edit.html.twig', [ - 'skill' => $skill, 'form' => $form, + 'skill' => $skill, ]); } #[Route('/{id}', name: 'app_skill_delete', methods: ['POST'])] public function delete(Request $request, Skill $skill, EntityManagerInterface $entityManager): Response { - $this->denyAccessUnlessGranted('ROLE_ADMIN'); - if ($this->isCsrfTokenValid('delete'.$skill->getId(), $request->get('csrf_token'))) { + $this->denyUnlessAdminOrSecretaire(); + + if ($this->isCsrfTokenValid('delete' . $skill->getId(), $request->request->get('_token'))) { $entityManager->remove($skill); $entityManager->flush(); } - return $this->redirectToRoute('app_skill_index', [], Response::HTTP_SEE_OTHER); + return $this->redirectToRoute('app_skill_index'); + } + + private function denyUnlessAdminOrSecretaire(): void + { + if (!$this->isGranted('ROLE_ADMIN') && !$this->isGranted('ROLE_SECRETAIRE')) { + throw $this->createAccessDeniedException(); + } } } diff --git a/src/Controller/StockController.php b/src/Controller/StockController.php index 1168128..74cb18f 100644 --- a/src/Controller/StockController.php +++ b/src/Controller/StockController.php @@ -9,14 +9,15 @@ use Doctrine\ORM\EntityManagerInterface; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\Routing\Attribute\Route; +use Symfony\Component\Routing\Annotation\Route; #[Route('/stock')] -final class StockController extends AbstractController +class StockController extends AbstractController { - #[Route(name: 'app_stock_index', methods: ['GET'])] + #[Route('/', name: 'app_stock_index', methods: ['GET'])] public function index(StockRepository $stockRepository): Response { + $this->denyUnlessAdminOrSecretaire(); return $this->render('stock/index.html.twig', [ 'stocks' => $stockRepository->findAll(), ]); @@ -25,6 +26,8 @@ final class StockController extends AbstractController #[Route('/new', name: 'app_stock_new', methods: ['GET', 'POST'])] public function new(Request $request, EntityManagerInterface $entityManager): Response { + $this->denyUnlessAdminOrSecretaire(); + $stock = new Stock(); $form = $this->createForm(StockType::class, $stock); $form->handleRequest($request); @@ -33,11 +36,10 @@ final class StockController extends AbstractController $entityManager->persist($stock); $entityManager->flush(); - return $this->redirectToRoute('app_stock_index', [], Response::HTTP_SEE_OTHER); + return $this->redirectToRoute('app_stock_index'); } return $this->render('stock/new.html.twig', [ - 'stock' => $stock, 'form' => $form, ]); } @@ -45,6 +47,7 @@ final class StockController extends AbstractController #[Route('/{id}', name: 'app_stock_show', methods: ['GET'])] public function show(Stock $stock): Response { + $this->denyUnlessAdminOrSecretaire(); return $this->render('stock/show.html.twig', [ 'stock' => $stock, ]); @@ -53,10 +56,7 @@ final class StockController extends AbstractController #[Route('/{id}/edit', name: 'app_stock_edit', methods: ['GET', 'POST'])] public function edit(Request $request, Stock $stock, EntityManagerInterface $entityManager): Response { - // Si un chauffagiste essaie de modifier un stock d'admin - if ($this->isGranted('ROLE_CHAUFFAGISTE')) { - throw $this->createAccessDeniedException('Vous ne pouvez pas modifier ce stock.'); - } + $this->denyUnlessAdminOrSecretaire(); $form = $this->createForm(StockType::class, $stock); $form->handleRequest($request); @@ -64,23 +64,32 @@ final class StockController extends AbstractController if ($form->isSubmitted() && $form->isValid()) { $entityManager->flush(); - return $this->redirectToRoute('app_stock_index', [], Response::HTTP_SEE_OTHER); + return $this->redirectToRoute('app_stock_index'); } return $this->render('stock/edit.html.twig', [ - 'stock' => $stock, 'form' => $form, + 'stock' => $stock, ]); } #[Route('/{id}', name: 'app_stock_delete', methods: ['POST'])] public function delete(Request $request, Stock $stock, EntityManagerInterface $entityManager): Response { - if ($this->isCsrfTokenValid('delete'.$stock->getId(), $request->get('csrf_token'))) { + $this->denyUnlessAdminOrSecretaire(); + + if ($this->isCsrfTokenValid('delete' . $stock->getId(), $request->request->get('_token'))) { $entityManager->remove($stock); $entityManager->flush(); } - return $this->redirectToRoute('app_stock_index', [], Response::HTTP_SEE_OTHER); + return $this->redirectToRoute('app_stock_index'); + } + + private function denyUnlessAdminOrSecretaire(): void + { + if (!$this->isGranted('ROLE_ADMIN') && !$this->isGranted('ROLE_SECRETAIRE')) { + throw $this->createAccessDeniedException(); + } } } diff --git a/src/Controller/UserController.php b/src/Controller/UserController.php index b33737f..04786a7 100644 --- a/src/Controller/UserController.php +++ b/src/Controller/UserController.php @@ -11,6 +11,7 @@ use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface; use Symfony\Component\Routing\Annotation\Route; +use Symfony\Component\Security\Http\Attribute\IsGranted; final class UserController extends AbstractController { @@ -89,27 +90,19 @@ 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 $user, EntityManagerInterface $entityManager): Response + public function delete(Request $request, Utilisateur $utilisateur): Response { - // Si l'utilisateur est un secrétaire et qu'il essaie de supprimer un autre secrétaire - if ($this->isGranted('ROLE_SECRETAIRE') && $user->hasRole('ROLE_SECRETAIRE')) { - throw $this->createAccessDeniedException('Vous ne pouvez pas supprimer un autre secrétaire ou un administrateur.'); + if ($this->isCsrfTokenValid('delete' . $utilisateur->getId(), $request->request->get('_token'))) { + $this->entityManager->remove($utilisateur); + $this->entityManager->flush(); + + $this->addFlash('success', 'Utilisateur supprimé avec succès.'); + } else { + $this->addFlash('error', 'Token CSRF invalide.'); } - // Si l'utilisateur est un chauffagiste et qu'il essaie de supprimer un admin - if ($this->isGranted('ROLE_CHAUFFAGISTE') && ($user->hasRole('ROLE_SECRETAIRE') || $user->hasRole('ROLE_CHAUFFAGISTE'))) { - throw $this->createAccessDeniedException('Vous ne pouvez pas supprimer un utilisateur.'); - } - - // On s'assure que seul un admin peut supprimer un autre admin - $this->denyAccessUnlessGranted("ROLE_ADMIN"); - - if ($this->isCsrfTokenValid('delete' . $user->getId(), $request->get('csrf_token'))) { - $entityManager->remove($user); - $entityManager->flush(); - } - - return $this->redirectToRoute('app_user_index', [], Response::HTTP_SEE_OTHER); + return $this->redirectToRoute('app_user_index'); } } diff --git a/src/Controller/VehicleController.php b/src/Controller/VehicleController.php index 6267c48..ab9e064 100644 --- a/src/Controller/VehicleController.php +++ b/src/Controller/VehicleController.php @@ -9,45 +9,36 @@ use Doctrine\ORM\EntityManagerInterface; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\Routing\Attribute\Route; +use Symfony\Component\Routing\Annotation\Route; #[Route('/vehicle')] -final class VehicleController extends AbstractController +class VehicleController extends AbstractController { - #[Route(name: 'app_vehicle_index', methods: ['GET'])] + #[Route('/', name: 'app_vehicle_index', methods: ['GET'])] public function index(VehicleRepository $vehicleRepository): Response { - // Admin peut voir tous les véhicules, chauffagiste ne peut voir que ses véhicules - $vehicles = $this->isGranted('ROLE_CHAUFFAGISTE') - ? $vehicleRepository->findByUser($this->getUser()) // Filtre les véhicules par utilisateur - : $vehicleRepository->findAll(); // Les admins voient tout - + $this->denyUnlessAdminOrSecretaire(); return $this->render('vehicle/index.html.twig', [ - 'vehicles' => $vehicles, + 'vehicles' => $vehicleRepository->findAll(), ]); } #[Route('/new', name: 'app_vehicle_new', methods: ['GET', 'POST'])] public function new(Request $request, EntityManagerInterface $entityManager): Response { + $this->denyUnlessAdminOrSecretaire(); + $vehicle = new Vehicle(); $form = $this->createForm(VehicleType::class, $vehicle); $form->handleRequest($request); if ($form->isSubmitted() && $form->isValid()) { - // Si l'utilisateur est un chauffagiste, on associe le véhicule à lui - if ($this->isGranted('ROLE_CHAUFFAGISTE')) { - $vehicle->setUser($this->getUser()); - } - $entityManager->persist($vehicle); $entityManager->flush(); - - return $this->redirectToRoute('app_vehicle_index', [], Response::HTTP_SEE_OTHER); + return $this->redirectToRoute('app_vehicle_index'); } return $this->render('vehicle/new.html.twig', [ - 'vehicle' => $vehicle, 'form' => $form, ]); } @@ -55,11 +46,7 @@ final class VehicleController extends AbstractController #[Route('/{id}', name: 'app_vehicle_show', methods: ['GET'])] public function show(Vehicle $vehicle): Response { - // Si l'utilisateur est un chauffagiste et essaie de voir un véhicule d'un autre chauffagiste, on bloque - if ($this->isGranted('ROLE_CHAUFFAGISTE') && $vehicle->getUser() !== $this->getUser()) { - throw $this->createAccessDeniedException('Vous ne pouvez pas voir ce véhicule.'); - } - + $this->denyUnlessAdminOrSecretaire(); return $this->render('vehicle/show.html.twig', [ 'vehicle' => $vehicle, ]); @@ -68,39 +55,39 @@ final class VehicleController extends AbstractController #[Route('/{id}/edit', name: 'app_vehicle_edit', methods: ['GET', 'POST'])] public function edit(Request $request, Vehicle $vehicle, EntityManagerInterface $entityManager): Response { - // Vérifier si un chauffagiste essaie de modifier un véhicule d'un autre chauffagiste - if ($this->isGranted('ROLE_CHAUFFAGISTE') && $vehicle->getUser() !== $this->getUser()) { - throw $this->createAccessDeniedException('Vous ne pouvez pas modifier ce véhicule.'); - } + $this->denyUnlessAdminOrSecretaire(); $form = $this->createForm(VehicleType::class, $vehicle); $form->handleRequest($request); if ($form->isSubmitted() && $form->isValid()) { $entityManager->flush(); - - return $this->redirectToRoute('app_vehicle_index', [], Response::HTTP_SEE_OTHER); + return $this->redirectToRoute('app_vehicle_index'); } return $this->render('vehicle/edit.html.twig', [ - 'vehicle' => $vehicle, 'form' => $form, + 'vehicle' => $vehicle, ]); } #[Route('/{id}', name: 'app_vehicle_delete', methods: ['POST'])] public function delete(Request $request, Vehicle $vehicle, EntityManagerInterface $entityManager): Response { - // Vérification de sécurité : un chauffagiste ne peut supprimer un véhicule d'un autre chauffagiste - if ($this->isGranted('ROLE_CHAUFFAGISTE') && $vehicle->getUser() !== $this->getUser()) { - throw $this->createAccessDeniedException('Vous ne pouvez pas supprimer ce véhicule.'); - } + $this->denyUnlessAdminOrSecretaire(); - if ($this->isCsrfTokenValid('delete'.$vehicle->getId(), $request->get('csrf_token'))) { + if ($this->isCsrfTokenValid('delete' . $vehicle->getId(), $request->request->get('_token'))) { $entityManager->remove($vehicle); $entityManager->flush(); } - return $this->redirectToRoute('app_vehicle_index', [], Response::HTTP_SEE_OTHER); + return $this->redirectToRoute('app_vehicle_index'); + } + + private function denyUnlessAdminOrSecretaire(): void + { + if (!$this->isGranted('ROLE_ADMIN') && !$this->isGranted('ROLE_SECRETAIRE')) { + throw $this->createAccessDeniedException(); + } } } diff --git a/src/Entity/Intervention.php b/src/Entity/Intervention.php index 4ced921..5bd8beb 100644 --- a/src/Entity/Intervention.php +++ b/src/Entity/Intervention.php @@ -202,6 +202,9 @@ class Intervention #[ORM\Column(length: 255)] private ?string $Status = null; + #[ORM\Column(type: 'text', nullable: true)] + private ?string $Remarque = null; + #[ORM\ManyToOne(inversedBy: 'interventions')] #[ORM\JoinColumn(nullable: false)] private ?Utilisateur $user = null; @@ -355,5 +358,15 @@ class Intervention { return $this->Wording; } + + public function getRemarque(): ?string + { + return $this->Remarque; + } + + public function setRemarque(?string $Remarque): void + { + $this->Remarque = $Remarque; + } } diff --git a/src/Form/RemarqueType.php b/src/Form/RemarqueType.php new file mode 100644 index 0000000..1500372 --- /dev/null +++ b/src/Form/RemarqueType.php @@ -0,0 +1,31 @@ +add('Remarque', TextareaType::class, [ + 'label' => 'Ajouter une remarque', + 'attr' => [ + 'rows' => 5, + 'placeholder' => 'Renseignez les observations de l’intervention…' + ] + ]); + } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ + 'data_class' => Intervention::class, + ]); + } +} diff --git a/templates/intervention/remarque.html.twig b/templates/intervention/remarque.html.twig new file mode 100644 index 0000000..39c2ef1 --- /dev/null +++ b/templates/intervention/remarque.html.twig @@ -0,0 +1,18 @@ +{% extends 'base.html.twig' %} + +{% block title %}Ajouter une remarque{% endblock %} + +{% block body %} +

📝 Ajouter une remarque à l'intervention #{{ intervention.id }}

+ + {{ form_start(form) }} + {{ form_widget(form) }} + + {{ form_end(form) }} + + +{% endblock %} diff --git a/templates/intervention/show.html.twig b/templates/intervention/show.html.twig index ab5617e..d118dff 100644 --- a/templates/intervention/show.html.twig +++ b/templates/intervention/show.html.twig @@ -63,6 +63,11 @@ {% endif %} + {% if is_granted('ROLE_CHAUFFAGISTE') and intervention.user == app.user %} + + 📝 Ajouter une remarque + + {% endif %}