feat: gestion des remarques chauffagistes + refacto sécurité

- 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
This commit is contained in:
sermandm 2025-05-08 12:31:40 +02:00
parent 5cdf38794a
commit 8580911c1a
14 changed files with 241 additions and 198 deletions

4
.idea/dataSources.xml generated
View File

@ -1,11 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DataSourceManagerImpl" format="xml" multifile-model="true">
<data-source source="LOCAL" name="hegreetconfort@localhost" uuid="21423ae4-3232-4641-affb-06399f70655a">
<data-source source="LOCAL" name="@localhost" uuid="21423ae4-3232-4641-affb-06399f70655a">
<driver-ref>postgresql</driver-ref>
<synchronize>true</synchronize>
<jdbc-driver>org.postgresql.Driver</jdbc-driver>
<jdbc-url>jdbc:postgresql://localhost:5432/hegreetconfort</jdbc-url>
<jdbc-url>jdbc:postgresql://localhost:5433/</jdbc-url>
<working-dir>$ProjectFileDir$</working-dir>
</data-source>
</component>

View File

@ -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
```

View File

@ -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')]

View File

@ -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.');
}
}

View File

@ -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();
}
}
}

View File

@ -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();
}
}
}

View File

@ -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();
}
}
}

View File

@ -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();
}
}
}

View File

@ -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');
}
}

View File

@ -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();
}
}
}

View File

@ -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;
}
}

31
src/Form/RemarqueType.php Normal file
View File

@ -0,0 +1,31 @@
<?php
namespace App\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use App\Entity\Intervention;
class RemarqueType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('Remarque', TextareaType::class, [
'label' => 'Ajouter une remarque',
'attr' => [
'rows' => 5,
'placeholder' => 'Renseignez les observations de lintervention…'
]
]);
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'data_class' => Intervention::class,
]);
}
}

View File

@ -0,0 +1,18 @@
{% extends 'base.html.twig' %}
{% block title %}Ajouter une remarque{% endblock %}
{% block body %}
<h1 class="mb-4">📝 Ajouter une remarque à l'intervention #{{ intervention.id }}</h1>
{{ form_start(form) }}
{{ form_widget(form) }}
<button class="btn btn-primary mt-3">Enregistrer la remarque</button>
{{ form_end(form) }}
<div class="mt-3">
<a href="{{ path('app_intervention_show', {'id': intervention.id}) }}" class="btn btn-secondary">
← Retour à l'intervention
</a>
</div>
{% endblock %}

View File

@ -63,6 +63,11 @@
{% endif %}
</td>
</tr>
{% if is_granted('ROLE_CHAUFFAGISTE') and intervention.user == app.user %}
<a href="{{ path('app_intervention_remarque', {'id': intervention.id}) }}" class="btn btn-outline-primary">
📝 Ajouter une remarque
</a>
{% endif %}
</tbody>
</table>