Compare commits
4 Commits
5cdf38794a
...
aaa66849be
Author | SHA1 | Date | |
---|---|---|---|
aaa66849be | |||
461e661f62 | |||
a992c2ea6a | |||
8580911c1a |
4
.idea/dataSources.xml
generated
4
.idea/dataSources.xml
generated
@ -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>
|
||||
|
80
README.md
80
README.md
@ -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 d’unicité** : interdiction de double-assignation d’un 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 l’intervention.
|
||||
- ✅ **Sélecteurs intelligents** : seuls les chauffagistes apparaissent pour l’assignation dans les interventions.
|
||||
- ✅ **Remarque chauffagiste** : chaque chauffagiste peut ajouter une remarque à ses interventions uniquement.
|
||||
- ✅ **Sécurité renforcée** : vérifications d’accè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 :
|
||||
### Étapes :
|
||||
### 1. Clonez le projet
|
||||
```bash
|
||||
git clone https://gitea.btssio-poitiers.fr/sermandm/HegreEtConfort.git
|
||||
cd HegreEtConfort
|
||||
```
|
||||
2. Installez les dépendances avec Composer :
|
||||
### 2. Installez les dépendances
|
||||
```bash
|
||||
cd chauffagiste-app
|
||||
composer install
|
||||
```
|
||||
3. Créez la base de données :
|
||||
### 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 :
|
||||
### 4. Appliquez les migrations
|
||||
```bash
|
||||
php bin/console doctrine:migrations:migrate
|
||||
```
|
||||
5. Lancez le serveur Symfony :
|
||||
|
||||
### 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'
|
||||
);
|
||||
```
|
||||
|
||||
### 6. Démarrez le serveur Symfony :
|
||||
```bash
|
||||
symfony server:start
|
||||
```
|
||||
|
||||
Accédez ensuite à l'application sur `http://localhost:8000`.
|
||||
|
||||
## 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.
|
||||
➡️ Accédez ensuite à l’application : [http://localhost:8000](http://localhost:8000)
|
||||
|
||||
---
|
||||
|
||||
@ -57,3 +86,14 @@ Les secrétaires ont accès à toutes les pages **sauf celles concernant d'autre
|
||||
- Doctrine ORM
|
||||
- Twig
|
||||
- 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
|
44
migrations/Version20250508121539.php
Normal file
44
migrations/Version20250508121539.php
Normal 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);
|
||||
}
|
||||
}
|
@ -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.');
|
||||
}
|
||||
}
|
||||
|
@ -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),
|
||||
]);
|
||||
|
@ -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');
|
||||
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');
|
||||
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');
|
||||
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.');
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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');
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
31
src/Form/RemarqueType.php
Normal 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 l’intervention…'
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'data_class' => Intervention::class,
|
||||
]);
|
||||
}
|
||||
}
|
@ -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 %}
|
||||
|
||||
|
@ -5,8 +5,6 @@
|
||||
{% 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>
|
||||
@ -14,29 +12,17 @@
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
var calendarEl = document.getElementById('calendar');
|
||||
|
||||
var calendar = new FullCalendar.Calendar(calendarEl, {
|
||||
initialView: 'dayGridMonth',
|
||||
events: '/api/interventions' // ✅ Point d’accè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 %}
|
||||
|
@ -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 () {
|
||||
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();
|
||||
});
|
||||
|
@ -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 () {
|
||||
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();
|
||||
});
|
||||
|
@ -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) }}
|
||||
|
@ -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>
|
||||
{% endblock %}
|
||||
{#>>>>>>> 4fc91211f0d814453d2ed97caf6a1d94d709058e#}
|
||||
|
18
templates/intervention/remarque.html.twig
Normal file
18
templates/intervention/remarque.html.twig
Normal 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 %}
|
@ -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>
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user