Compare commits

...

4 Commits

Author SHA1 Message Date
aaa66849be 📝 Mise à jour du README avec les fonctionnalités finales et instructions d'installation 2025-05-08 15:20:40 +02:00
461e661f62 Finalisation application : sécurité, calendrier, vérifs d’unicité, filtres chauffagiste 2025-05-08 15:07:12 +02:00
a992c2ea6a Réparation du DashboardController et AuthenticationController 2025-05-08 13:58:02 +02:00
8580911c1a 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
2025-05-08 12:31:40 +02:00
23 changed files with 506 additions and 382 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>

110
README.md
View File

@ -1,54 +1,83 @@
# 🚀 Application de Gestion Chauffagiste
## Description
Cette application permet de gérer les interventions, les utilisateurs (chauffagistes, secrétaires, admin), les pièces détachées, les véhicules, et les plannings dans une entreprise de chauffagistes.
Cette application permet de gérer les interventions, les utilisateurs (chauffagistes, secrétaires, admins), les pièces détachées, les véhicules, et les plannings dans une entreprise de chauffagistes.
---
## Fonctionnalités principales
- **Gestion des utilisateurs** : création de chauffagistes, secrétaires, et admins avec des rôles spécifiques.
- **Gestion des interventions** : assignation des chauffagistes, suivi des véhicules et des pièces détachées.
- **Gestion des stocks et des véhicules** : gestion des pièces détachées et des véhicules.
- **Planning des interventions** : chaque rôle a accès à un planning adapté (chauffagiste, secrétaire, admin).
- **Gestion des interventions** : assignation des chauffagistes, véhicules, pièces détachées, ajout de remarques.
- **Gestion des stocks et des véhicules** : CRUD complet pour les pièces détachées et les véhicules.
- **Planning des interventions** : chaque rôle accède à un planning personnalisé (chauffagiste, secrétaire, admin).
- **Sécurisation par rôles** : accès aux pages limité par rôle (Admin, Secrétaire, Chauffagiste).
### Précisions pour les rôles :
- **Admin** : Accès complet à toutes les fonctionnalités (gestion des utilisateurs, véhicules, stocks, plannings, etc.)
- **Secrétaire** : Peut gérer les chauffagistes, les interventions et le planning des chauffagistes, mais **ne peut pas gérer d'autres secrétaires ni les admins**.
- **Chauffagiste** : Accède uniquement à ses propres interventions et à son planning.
---
## Fonctionnalités avancées
- ✅ **Contrôle dunicité** : interdiction de double-assignation dun même chauffagiste ou véhicule sur une même date/heure.
- ✅ **Calendrier FullCalendar dynamique** : affichage différent selon le rôle + clic pour afficher lintervention.
- ✅ **Sélecteurs intelligents** : seuls les chauffagistes apparaissent pour lassignation dans les interventions.
- ✅ **Remarque chauffagiste** : chaque chauffagiste peut ajouter une remarque à ses interventions uniquement.
- ✅ **Sécurité renforcée** : vérifications daccès sur toutes les routes sensibles.
---
## Rôles & restrictions
| Rôle | Droits |
|---------------|------------------------------------------------------------------------|
| **Admin** | Accès complet à tous les modules (utilisateurs, stocks, véhicules...) |
| **Secrétaire** | Accès complet à tous les modules **mais pas aux utilisateurs** |
| **Chauffagiste** | Accède uniquement à ses interventions et peut y ajouter des remarques |
---
## Installation
### Prérequis :
- PHP 8.x
- Composer
- Symfony 7.x
- Base de données PostgreSQL ou MySQL
### Étapes d'installation :
1. Clonez le projet :
```bash
git clone https://gitea.btssio-poitiers.fr/sermandm/HegreEtConfort.git
```
2. Installez les dépendances avec Composer :
```bash
cd chauffagiste-app
composer install
```
3. Créez la base de données :
```bash
php bin/console doctrine:database:create
```
4. Exécutez les migrations pour créer les tables :
```bash
php bin/console doctrine:migrations:migrate
```
5. Lancez le serveur Symfony :
```bash
symfony server:start
```
### Étapes :
### 1. Clonez le projet
```bash
git clone https://gitea.btssio-poitiers.fr/sermandm/HegreEtConfort.git
cd HegreEtConfort
```
### 2. Installez les dépendances
```bash
composer install
```
### 3. Créez la base de données
```bash
php bin/console doctrine:database:create
```
### 4. Appliquez les migrations
```bash
php bin/console doctrine:migrations:migrate
```
Accédez ensuite à l'application sur `http://localhost:8000`.
### 5. Créez un utilisateur admin (exemple PostgreSQL) :
```sql
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', '0000000000',
'["ROLE_ADMIN"]',
'$2y$13$4jqoZVgncgDJ6oPFDswZeeiVmt9TF2AC.xoBwyyrrbNl5Xz8r.50e'
);
```
## Sécurisation des accès
Les secrétaires ont accès à toutes les pages **sauf celles concernant d'autres secrétaires et les admins**. Cela est géré par les contrôleurs via la méthode `denyAccessUnlessGranted()` pour vérifier le rôle de l'utilisateur. Par exemple, un secrétaire ne pourra pas modifier un autre secrétaire.
### 6. Démarrez le serveur Symfony :
```bash
symfony server:start
```
➡️ Accédez ensuite à lapplication : [http://localhost:8000](http://localhost:8000)
---
@ -56,4 +85,15 @@ Les secrétaires ont accès à toutes les pages **sauf celles concernant d'autre
- Symfony 7.x
- Doctrine ORM
- Twig
- PHP 8.x
- PHP 8.x
- FullCalendar.js
- PostgreSQL (ou MySQL selon config)
---
## Auteur
Développé dans le cadre du BTS SIO - D'Hegre Et Confort
- Maxim SERMAND
- Alyssa ALLARD
- Giovanny BRUNET
- Lucas RAGUENEAU

View File

@ -0,0 +1,44 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20250508121539 extends AbstractMigration
{
public function getDescription(): string
{
return '';
}
public function up(Schema $schema): void
{
// this up() migration is auto-generated, please modify it to your needs
$this->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);
}
}

View File

@ -4,7 +4,7 @@ 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;
use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;
class AuthenticationController extends AbstractController
@ -12,10 +12,11 @@ class AuthenticationController extends AbstractController
#[Route(path: '/', name: 'app_login')]
public function login(AuthenticationUtils $authenticationUtils): Response
{
// Get the login error if there is one
$error = $authenticationUtils->getLastAuthenticationError();
if ($this->getUser()) {
return $this->redirectToRoute('dashboard');
}
// Last username entered by the user
$error = $authenticationUtils->getLastAuthenticationError();
$lastUsername = $authenticationUtils->getLastUsername();
return $this->render('login/index.html.twig', [
@ -27,6 +28,6 @@ class AuthenticationController extends AbstractController
#[Route(path: '/logout', name: 'app_logout')]
public function logout(): void
{
throw new \LogicException('This method can be blank - it will be intercepted by the logout key on your firewall.');
throw new \LogicException('Cette méthode est interceptée par Symfony pour déconnecter.');
}
}

View File

@ -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),
]);

View File

@ -4,28 +4,29 @@ 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')]
#[Route('/admin', name: 'admin_dashboard')]
#[Route('/secretaire', name: 'secretaire_dashboard')]
#[Route('/chauffagiste', name: 'chauffagiste_dashboard')]
public function index(): Response
{
$this->denyAccessUnlessGranted('ROLE_ADMIN');
return $this->render('dashboard/admin.html.twig');
}
if ($this->isGranted('ROLE_ADMIN')) {
return $this->render('dashboard/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('dashboard/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('dashboard/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

@ -4,54 +4,73 @@ 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;
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
public function new(Request $request, EntityManagerInterface $entityManager, InterventionRepository $interventionRepository): 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());
$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();
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,73 +78,113 @@ 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,
]);
}
#[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
{
// 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);
if ($form->isSubmitted() && $form->isValid()) {
$entityManager->flush();
$timestamp = $intervention->getTimestamp();
$chauffagiste = $intervention->getUser();
$vehicule = $intervention->getVehicle();
return $this->redirectToRoute('app_intervention_index', [], Response::HTTP_SEE_OTHER);
$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');
}
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
];
$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,10 +11,11 @@ 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;
#[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
{
@ -23,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
{
@ -32,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);
@ -49,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
{
@ -58,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);
@ -88,28 +74,17 @@ final class UserController extends AbstractController
]);
}
// Route pour supprimer un utilisateur spécifique
#[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, EntityManagerInterface $entityManager): 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.');
}
// 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);
if ($this->isCsrfTokenValid('delete' . $utilisateur->getId(), $request->request->get('_token'))) {
$entityManager->remove($utilisateur);
$entityManager->flush();
$this->addFlash('success', 'Utilisateur supprimé avec succès.');
} else {
$this->addFlash('error', 'Token CSRF invalide.');
}
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;
}
}

View File

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

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

@ -95,18 +95,23 @@
<ul>
{% if is_granted('ROLE_ADMIN') %}
<li><a href="{{ path('admin_dashboard') }}">Dashboard Admin</a></li>
<li><a href="{{ path('app_intervention_index') }}">Gérer les interventions</a></li>
<li><a href="{{ path('app_user_index') }}">Gérer les utilisateurs</a></li>
<li><a href="{{ path('app_vehicle_index') }}">Gérer les véhicules</a></li>
<li><a href="{{ path('app_stock_index') }}">Gérer les stocks</a></li>
<li><a href="{{ path('app_fault_index') }}">Gérer les pannes</a></li>
<li><a href="{{ path('app_skill_index') }}">Gérer les compétences</a></li>
<li><a href="{{ path('app_calendrier_index') }}">Tous les plannings</a></li>
{% endif %}
{% if is_granted('ROLE_SECRETAIRE') %}
<li><a href="{{ path('secretaire_dashboard') }}">Dashboard Secrétaire</a></li>
<li><a href="{{ path('app_intervention_index') }}">Gérer les interventions</a></li>
<li><a href="{{ path('app_user_index') }}">Créer un chauffagiste</a></li>
<li><a href="{{ path('app_vehicle_index') }}">Gérer les véhicules</a></li>
<li><a href="{{ path('app_stock_index') }}">Gérer les stocks</a></li>
<li><a href="{{ path('app_fault_index') }}">Gérer les pannes</a></li>
<li><a href="{{ path('app_skill_index') }}">Gérer les compétences</a></li>
<li><a href="{{ path('app_calendrier_indexSecretaire') }}">Plannings chauffagistes</a></li>
{% endif %}

View File

@ -5,38 +5,24 @@
{% block body %}
<h1>📅 Calendrier des interventions</h1>
<a href="{{ path('app_intervention_new') }}" class="btn btn-success mt-3"> Ajouter une intervention</a>
<div id="calendar"></div>
<script src="https://cdn.jsdelivr.net/npm/fullcalendar@6.1.8/index.global.min.js"></script>
<script>
document.addEventListener('DOMContentLoaded', function() {
document.addEventListener('DOMContentLoaded', function () {
var calendarEl = document.getElementById('calendar');
var calendar = new FullCalendar.Calendar(calendarEl, {
initialView: 'dayGridMonth',
events: '/api/interventions' // ✅ Point daccès API
locale: 'fr',
eventTimeFormat: {
hour: '2-digit',
minute: '2-digit',
hour12: false
},
events: {{ events|raw }}
});
calendar.render();
});
</script>
{# <div id="calendar"></div>#}
{# <script>#}
{# document.addEventListener('DOMContentLoaded', function() {#}
{# var calendarEl = document.getElementById('calendar');#}
{# var calendar = new FullCalendar.Calendar(calendarEl, {#}
{# initialView: 'dayGridMonth',#}
{# events: {{ events | raw }},#}
{# eventClick: function(info) {#}
{# alert('Intervention : ' + info.event.title + '\n' + info.event.extendedProps.description);#}
{# }#}
{# });#}
{# calendar.render();#}
{# });#}
{# </script>#}
{% endblock %}

View File

@ -7,15 +7,20 @@
<div id="calendar"></div>
<script src="https://cdn.jsdelivr.net/npm/fullcalendar@6.1.8/index.global.min.js"></script>
<script>
document.addEventListener('DOMContentLoaded', function() {
document.addEventListener('DOMContentLoaded', function () {
var calendarEl = document.getElementById('calendar');
var calendar = new FullCalendar.Calendar(calendarEl, {
initialView: 'dayGridMonth',
events: {{ events | raw }},
eventClick: function(info) {
alert('Intervention : ' + info.event.title + '\n' + info.event.extendedProps.description);
}
locale: 'fr',
eventTimeFormat: {
hour: '2-digit',
minute: '2-digit',
hour12: false
},
events: {{ events|raw }}
});
calendar.render();
});

View File

@ -7,15 +7,20 @@
<div id="calendar"></div>
<script src="https://cdn.jsdelivr.net/npm/fullcalendar@6.1.8/index.global.min.js"></script>
<script>
document.addEventListener('DOMContentLoaded', function() {
document.addEventListener('DOMContentLoaded', function () {
var calendarEl = document.getElementById('calendar');
var calendar = new FullCalendar.Calendar(calendarEl, {
initialView: 'dayGridMonth',
events: {{ events | raw }},
eventClick: function(info) {
alert('Intervention : ' + info.event.title + '\n' + info.event.extendedProps.description);
}
locale: 'fr',
eventTimeFormat: {
hour: '2-digit',
minute: '2-digit',
hour12: false
},
events: {{ events|raw }}
});
calendar.render();
});

View File

@ -1,9 +1,3 @@
{#{{ form_start(form) }}#}
{# {{ form_widget(form) }}#}
{# <button class="btn">{{ button_label|default('Save') }}</button>#}
{#{{ form_end(form) }}#}
{{ form_start(form) }}
<div class="form-group">
@ -36,12 +30,30 @@
{{ form_errors(form.Status) }}
</div>
{#<div class="form-group">#}
{# {{ form_label(form.vehicule, 'Véhicule associé') }}#}
{# {{ form_widget(form.vehicule, {'attr': {'class': 'form-control'}}) }}#}
{# {{ form_errors(form.vehicule) }}#}
{#</div>#}
<div class="form-group">
{{ form_label(form.fault, 'Panne') }}
{{ form_widget(form.fault, {'attr': {'class': 'form-control'}}) }}
{{ form_errors(form.fault) }}
</div>
<button class="btn btn-primary mt-3">{{ button_label|default('Enregistrer') }}</button>
<div class="form-group">
{{ form_label(form.user, 'Chauffagiste assigné') }}
{{ form_widget(form.user, {'attr': {'class': 'form-control'}}) }}
{{ form_errors(form.user) }}
</div>
<div class="form-group">
{{ form_label(form.vehicle, 'Véhicule') }}
{{ form_widget(form.vehicle, {'attr': {'class': 'form-control'}}) }}
{{ form_errors(form.vehicle) }}
</div>
<div class="form-group">
{{ form_label(form.stocks, 'Pièces utilisées') }}
{{ form_widget(form.stocks, {'attr': {'class': 'form-control'}}) }}
{{ form_errors(form.stocks) }}
</div>
<button class="btn btn-primary mt-4">{{ button_label|default('Enregistrer') }}</button>
{{ form_end(form) }}

View File

@ -5,67 +5,7 @@
{% block body %}
<h1 class="mb-4"> Créer une nouvelle intervention</h1>
{#<<<<<<< HEAD#}
<div class="background-intervention">
{# <input required id="nom" placeholder="Nom de l'intervention">#}
{# <select required type="select" name="pannes" id="pannes">#}
{# <option value="pan1">pan1</option>#}
{# <option value="pan2">pan2</option>#}
{# <option value="pan3">pan3</option>#}
{# <option value="pan4">pan4</option>#}
{# <option value="pan5">pan5</option>#}
{# <option value="pan6">pan6</option>#}
{# </select>#}
{# <select required type="select" name="pannes" id="employesCompetences">#}
{# <option value="emp1">employé1 - sa compétence</option>#}
{# <option value="emp2">employé2 - sa compétence</option>#}
{# <option value="emp3">employé3 - sa compétence</option>#}
{# <option value="emp4">employé4 - sa compétence</option>#}
{# <option value="emp5">employé5 - sa compétence</option>#}
{# <option value="emp6">employé6 - sa compétence</option>#}
{# </select>#}
{# <!-- <input required id="employesCompetences" placeholder="Liste des employés et de leurs compétences"> -->#}
{# <select required type="select" name="piece" id="piece">#}
{# <option value="pièce1">Pièce affecter 1</option>#}
{# <option value="pièce2">Pièce affecter 2</option>#}
{# <option value="pièce3">Pièce affecter 3</option>#}
{# <option value="pièce4">Pièce affecter 4</option>#}
{# <option value="pièce5">Pièce affecter 5</option>#}
{# <option value="pièce6">Pièce affecter 6</option>#}
{# </select>#}
{# <p id="vehicule">Véhicule nécessaire</p>#}
{# <form>#}
{# <label id="oui">#}
{# <input type="radio" name="choix" value="option1">#}
{# Oui </label>#}
{# <label id="non">#}
{# <input type="radio" name="choix" value="option2">#}
{# Non </label>#}
{# </form>#}
{# <p id="date">Jour de l'intervention</p>#}
{# <input type="date" id="calendar">#}
{# <input type="text" id="description" placeholder="Description">#}
{# <input type="text" id="adresse" placeholder="Adresse">#}
{# <button type="submit" class="applique"> Appliquée </button>#}
{# <button type="reset" class="supprimer"> Supprimer </button>#}
{{ include('intervention/_form.html.twig') }}
</div>
</body>
</html>
{#=======#}
<div class="mt-3">
<a href="{{ path('app_intervention_index') }}" class="btn btn-secondary">← Retour à la liste des interventions</a>
</div>
<div class="mt-3">
<a href="{{ path('app_calendrier_index') }}" class="btn btn-secondary">← Retour au calendrier</a>
<div class="background-intervention">
{{ include('intervention/_form.html.twig') }}
</div>
{% endblock %}
{#>>>>>>> 4fc91211f0d814453d2ed97caf6a1d94d709058e#}

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>