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"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="DataSourceManagerImpl" format="xml" multifile-model="true">
|
<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>
|
<driver-ref>postgresql</driver-ref>
|
||||||
<synchronize>true</synchronize>
|
<synchronize>true</synchronize>
|
||||||
<jdbc-driver>org.postgresql.Driver</jdbc-driver>
|
<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>
|
<working-dir>$ProjectFileDir$</working-dir>
|
||||||
</data-source>
|
</data-source>
|
||||||
</component>
|
</component>
|
||||||
|
108
README.md
108
README.md
@ -1,54 +1,83 @@
|
|||||||
|
|
||||||
# 🚀 Application de Gestion Chauffagiste
|
# 🚀 Application de Gestion Chauffagiste
|
||||||
|
|
||||||
## Description
|
## 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
|
## Fonctionnalités principales
|
||||||
- **Gestion des utilisateurs** : création de chauffagistes, secrétaires, et admins avec des rôles spécifiques.
|
- **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 interventions** : assignation des chauffagistes, véhicules, pièces détachées, ajout de remarques.
|
||||||
- **Gestion des stocks et des véhicules** : gestion des pièces détachées et des véhicules.
|
- **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 a accès à un planning adapté (chauffagiste, secrétaire, admin).
|
- **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).
|
- **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**.
|
## Fonctionnalités avancées
|
||||||
- **Chauffagiste** : Accède uniquement à ses propres interventions et à son planning.
|
- ✅ **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
|
## Installation
|
||||||
|
|
||||||
### Prérequis :
|
### Prérequis :
|
||||||
- PHP 8.x
|
- PHP 8.x
|
||||||
- Composer
|
- Composer
|
||||||
- Symfony 7.x
|
- Symfony 7.x
|
||||||
|
- Base de données PostgreSQL ou MySQL
|
||||||
|
|
||||||
### Étapes d'installation :
|
### Étapes :
|
||||||
1. Clonez le projet :
|
### 1. Clonez le projet
|
||||||
```bash
|
```bash
|
||||||
git clone https://gitea.btssio-poitiers.fr/sermandm/HegreEtConfort.git
|
git clone https://gitea.btssio-poitiers.fr/sermandm/HegreEtConfort.git
|
||||||
```
|
cd HegreEtConfort
|
||||||
2. Installez les dépendances avec Composer :
|
```
|
||||||
```bash
|
### 2. Installez les dépendances
|
||||||
cd chauffagiste-app
|
```bash
|
||||||
composer install
|
composer install
|
||||||
```
|
```
|
||||||
3. Créez la base de données :
|
### 3. Créez la base de données
|
||||||
```bash
|
```bash
|
||||||
php bin/console doctrine:database:create
|
php bin/console doctrine:database:create
|
||||||
```
|
```
|
||||||
4. Exécutez les migrations pour créer les tables :
|
### 4. Appliquez les migrations
|
||||||
```bash
|
```bash
|
||||||
php bin/console doctrine:migrations:migrate
|
php bin/console doctrine:migrations:migrate
|
||||||
```
|
```
|
||||||
5. Lancez le serveur Symfony :
|
|
||||||
```bash
|
|
||||||
symfony server:start
|
|
||||||
```
|
|
||||||
|
|
||||||
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
|
### 6. Démarrez le serveur Symfony :
|
||||||
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.
|
```bash
|
||||||
|
symfony server:start
|
||||||
|
```
|
||||||
|
|
||||||
|
➡️ 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
|
- Doctrine ORM
|
||||||
- Twig
|
- 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
|
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\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
use Symfony\Component\Routing\Attribute\Route;
|
use Symfony\Component\Routing\Annotation\Route;
|
||||||
use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;
|
use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;
|
||||||
|
|
||||||
class AuthenticationController extends AbstractController
|
class AuthenticationController extends AbstractController
|
||||||
@ -12,10 +12,11 @@ class AuthenticationController extends AbstractController
|
|||||||
#[Route(path: '/', name: 'app_login')]
|
#[Route(path: '/', name: 'app_login')]
|
||||||
public function login(AuthenticationUtils $authenticationUtils): Response
|
public function login(AuthenticationUtils $authenticationUtils): Response
|
||||||
{
|
{
|
||||||
// Get the login error if there is one
|
if ($this->getUser()) {
|
||||||
$error = $authenticationUtils->getLastAuthenticationError();
|
return $this->redirectToRoute('dashboard');
|
||||||
|
}
|
||||||
|
|
||||||
// Last username entered by the user
|
$error = $authenticationUtils->getLastAuthenticationError();
|
||||||
$lastUsername = $authenticationUtils->getLastUsername();
|
$lastUsername = $authenticationUtils->getLastUsername();
|
||||||
|
|
||||||
return $this->render('login/index.html.twig', [
|
return $this->render('login/index.html.twig', [
|
||||||
@ -27,6 +28,6 @@ class AuthenticationController extends AbstractController
|
|||||||
#[Route(path: '/logout', name: 'app_logout')]
|
#[Route(path: '/logout', name: 'app_logout')]
|
||||||
public function logout(): void
|
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')]
|
#[Route('/chauffagiste', name: 'app_calendrier_indexChauffagiste')]
|
||||||
public function indexChauffagiste(InterventionRepository $interventionRepository): Response
|
public function indexChauffagiste(InterventionRepository $interventionRepository): Response
|
||||||
{
|
{
|
||||||
// Vérification : seul le chauffagiste connecté peut voir son propre calendrier
|
|
||||||
$this->denyAccessUnlessGranted('ROLE_CHAUFFAGISTE');
|
$this->denyAccessUnlessGranted('ROLE_CHAUFFAGISTE');
|
||||||
|
|
||||||
// Récupérer les interventions du chauffagiste connecté
|
|
||||||
$interventions = $interventionRepository->findByUser($this->getUser());
|
$interventions = $interventionRepository->findByUser($this->getUser());
|
||||||
|
|
||||||
// Préparer les événements pour FullCalendar
|
|
||||||
$events = [];
|
$events = [];
|
||||||
foreach ($interventions as $intervention) {
|
foreach ($interventions as $intervention) {
|
||||||
$events[] = [
|
$events[] = [
|
||||||
'title' => $intervention->getTitle(),
|
'title' => ' - ' . $intervention->getWording() . ' (' . $intervention->getStatus() . ')',
|
||||||
'start' => $intervention->getStartDate()->format('Y-m-d H:i:s'),
|
'start' => $intervention->getTimestamp()?->format('Y-m-d\TH:i:s') ?? '',
|
||||||
'end' => $intervention->getEndDate()->format('Y-m-d H:i:s'),
|
'end' => $intervention->getTimestamp()?->format('Y-m-d\TH:i:s') ?? '',
|
||||||
'description' => $intervention->getDescription(),
|
'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', [
|
return $this->render('calendrier/indexChauffagiste.html.twig', [
|
||||||
'events' => json_encode($events),
|
'events' => json_encode($events),
|
||||||
]);
|
]);
|
||||||
@ -39,24 +36,21 @@ class CalendrierController extends AbstractController
|
|||||||
#[Route('/secretaire', name: 'app_calendrier_indexSecretaire')]
|
#[Route('/secretaire', name: 'app_calendrier_indexSecretaire')]
|
||||||
public function indexSecretaire(InterventionRepository $interventionRepository): Response
|
public function indexSecretaire(InterventionRepository $interventionRepository): Response
|
||||||
{
|
{
|
||||||
// Vérification : seul le secrétaire peut accéder à ce calendrier
|
|
||||||
$this->denyAccessUnlessGranted('ROLE_SECRETAIRE');
|
$this->denyAccessUnlessGranted('ROLE_SECRETAIRE');
|
||||||
|
|
||||||
// Récupérer toutes les interventions de tous les chauffagistes
|
|
||||||
$interventions = $interventionRepository->findAll();
|
$interventions = $interventionRepository->findAll();
|
||||||
|
|
||||||
// Préparer les événements pour FullCalendar
|
|
||||||
$events = [];
|
$events = [];
|
||||||
foreach ($interventions as $intervention) {
|
foreach ($interventions as $intervention) {
|
||||||
$events[] = [
|
$events[] = [
|
||||||
'title' => $intervention->getTitle(),
|
'title' => ' - ' . $intervention->getWording() . ' (' . $intervention->getStatus() . ')',
|
||||||
'start' => $intervention->getStartDate()->format('Y-m-d H:i:s'),
|
'start' => $intervention->getTimestamp()?->format('Y-m-d\TH:i:s') ?? '',
|
||||||
'end' => $intervention->getEndDate()->format('Y-m-d H:i:s'),
|
'end' => $intervention->getTimestamp()?->format('Y-m-d\TH:i:s') ?? '',
|
||||||
'description' => $intervention->getDescription(),
|
'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', [
|
return $this->render('calendrier/indexSecretaire.html.twig', [
|
||||||
'events' => json_encode($events),
|
'events' => json_encode($events),
|
||||||
]);
|
]);
|
||||||
@ -65,24 +59,21 @@ class CalendrierController extends AbstractController
|
|||||||
#[Route('/admin', name: 'app_calendrier_index')]
|
#[Route('/admin', name: 'app_calendrier_index')]
|
||||||
public function indexAdmin(InterventionRepository $interventionRepository): Response
|
public function indexAdmin(InterventionRepository $interventionRepository): Response
|
||||||
{
|
{
|
||||||
// Vérification : seul un admin peut accéder à ce calendrier
|
|
||||||
$this->denyAccessUnlessGranted('ROLE_ADMIN');
|
$this->denyAccessUnlessGranted('ROLE_ADMIN');
|
||||||
|
|
||||||
// Récupérer toutes les interventions de tous les chauffagistes
|
|
||||||
$interventions = $interventionRepository->findAll();
|
$interventions = $interventionRepository->findAll();
|
||||||
|
|
||||||
// Préparer les événements pour FullCalendar
|
|
||||||
$events = [];
|
$events = [];
|
||||||
foreach ($interventions as $intervention) {
|
foreach ($interventions as $intervention) {
|
||||||
$events[] = [
|
$events[] = [
|
||||||
'title' => $intervention->getTitle(),
|
'title' => ' - ' . $intervention->getWording() . ' (' . $intervention->getStatus() . ')',
|
||||||
'start' => $intervention->getStartDate()->format('Y-m-d H:i:s'),
|
'start' => $intervention->getTimestamp()?->format('Y-m-d\TH:i:s') ?? '',
|
||||||
'end' => $intervention->getEndDate()->format('Y-m-d H:i:s'),
|
'end' => $intervention->getTimestamp()?->format('Y-m-d\TH:i:s') ?? '',
|
||||||
'description' => $intervention->getDescription(),
|
'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', [
|
return $this->render('calendrier/index.html.twig', [
|
||||||
'events' => json_encode($events),
|
'events' => json_encode($events),
|
||||||
]);
|
]);
|
||||||
|
@ -4,28 +4,29 @@ namespace App\Controller;
|
|||||||
|
|
||||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
use Symfony\Component\Routing\Attribute\Route;
|
use Symfony\Component\Routing\Annotation\Route;
|
||||||
|
|
||||||
|
#[Route('/dashboard')]
|
||||||
class DashboardController extends AbstractController
|
class DashboardController extends AbstractController
|
||||||
{
|
{
|
||||||
#[Route('/admin/dashboard', name: 'admin_dashboard')]
|
#[Route('/', name: 'dashboard')]
|
||||||
public function admin(): Response
|
#[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');
|
return $this->render('dashboard/admin.html.twig');
|
||||||
}
|
}
|
||||||
|
|
||||||
#[Route('/secretaire/dashboard', name: 'secretaire_dashboard')]
|
if ($this->isGranted('ROLE_SECRETAIRE')) {
|
||||||
public function secretaire(): Response
|
|
||||||
{
|
|
||||||
$this->denyAccessUnlessGranted('ROLE_SECRETAIRE');
|
|
||||||
return $this->render('dashboard/secretaire.html.twig');
|
return $this->render('dashboard/secretaire.html.twig');
|
||||||
}
|
}
|
||||||
|
|
||||||
#[Route('/chauffagiste/dashboard', name: 'chauffagiste_dashboard')]
|
if ($this->isGranted('ROLE_CHAUFFAGISTE')) {
|
||||||
public function chauffagiste(): Response
|
|
||||||
{
|
|
||||||
$this->denyAccessUnlessGranted('ROLE_CHAUFFAGISTE');
|
|
||||||
return $this->render('dashboard/chauffagiste.html.twig');
|
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\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
use Symfony\Component\HttpFoundation\Request;
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
use Symfony\Component\Routing\Attribute\Route;
|
use Symfony\Component\Routing\Annotation\Route;
|
||||||
|
|
||||||
#[Route('/fault')]
|
#[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
|
public function index(FaultRepository $faultRepository): Response
|
||||||
{
|
{
|
||||||
// Filtrage des pannes : un chauffagiste ne peut voir que ses pannes
|
$this->denyUnlessAdminOrSecretaire();
|
||||||
$faults = $this->isGranted('ROLE_CHAUFFAGISTE')
|
|
||||||
? $faultRepository->findByUser($this->getUser()) // Filtre les pannes par utilisateur
|
|
||||||
: $faultRepository->findAll(); // Admins voient toutes les pannes
|
|
||||||
|
|
||||||
return $this->render('fault/index.html.twig', [
|
return $this->render('fault/index.html.twig', [
|
||||||
'faults' => $faults,
|
'faults' => $faultRepository->findAll(),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[Route('/new', name: 'app_fault_new', methods: ['GET', 'POST'])]
|
#[Route('/new', name: 'app_fault_new', methods: ['GET', 'POST'])]
|
||||||
public function new(Request $request, EntityManagerInterface $entityManager): Response
|
public function new(Request $request, EntityManagerInterface $entityManager): Response
|
||||||
{
|
{
|
||||||
|
$this->denyUnlessAdminOrSecretaire();
|
||||||
|
|
||||||
$fault = new Fault();
|
$fault = new Fault();
|
||||||
$form = $this->createForm(FaultType::class, $fault);
|
$form = $this->createForm(FaultType::class, $fault);
|
||||||
$form->handleRequest($request);
|
$form->handleRequest($request);
|
||||||
|
|
||||||
if ($form->isSubmitted() && $form->isValid()) {
|
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->persist($fault);
|
||||||
$entityManager->flush();
|
$entityManager->flush();
|
||||||
|
return $this->redirectToRoute('app_fault_index');
|
||||||
return $this->redirectToRoute('app_fault_index', [], Response::HTTP_SEE_OTHER);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->render('fault/new.html.twig', [
|
return $this->render('fault/new.html.twig', [
|
||||||
'fault' => $fault,
|
|
||||||
'form' => $form,
|
'form' => $form,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
@ -55,11 +46,7 @@ final class FaultController extends AbstractController
|
|||||||
#[Route('/{id}', name: 'app_fault_show', methods: ['GET'])]
|
#[Route('/{id}', name: 'app_fault_show', methods: ['GET'])]
|
||||||
public function show(Fault $fault): Response
|
public function show(Fault $fault): Response
|
||||||
{
|
{
|
||||||
// Un chauffagiste ne peut voir que ses pannes
|
$this->denyUnlessAdminOrSecretaire();
|
||||||
if ($this->isGranted('ROLE_CHAUFFAGISTE') && $fault->getUser() !== $this->getUser()) {
|
|
||||||
throw $this->createAccessDeniedException('Vous ne pouvez pas voir cette panne.');
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->render('fault/show.html.twig', [
|
return $this->render('fault/show.html.twig', [
|
||||||
'fault' => $fault,
|
'fault' => $fault,
|
||||||
]);
|
]);
|
||||||
@ -68,39 +55,39 @@ final class FaultController extends AbstractController
|
|||||||
#[Route('/{id}/edit', name: 'app_fault_edit', methods: ['GET', 'POST'])]
|
#[Route('/{id}/edit', name: 'app_fault_edit', methods: ['GET', 'POST'])]
|
||||||
public function edit(Request $request, Fault $fault, EntityManagerInterface $entityManager): Response
|
public function edit(Request $request, Fault $fault, EntityManagerInterface $entityManager): Response
|
||||||
{
|
{
|
||||||
// Un chauffagiste ne peut modifier que ses propres pannes
|
$this->denyUnlessAdminOrSecretaire();
|
||||||
if ($this->isGranted('ROLE_CHAUFFAGISTE') && $fault->getUser() !== $this->getUser()) {
|
|
||||||
throw $this->createAccessDeniedException('Vous ne pouvez pas modifier cette panne.');
|
|
||||||
}
|
|
||||||
|
|
||||||
$form = $this->createForm(FaultType::class, $fault);
|
$form = $this->createForm(FaultType::class, $fault);
|
||||||
$form->handleRequest($request);
|
$form->handleRequest($request);
|
||||||
|
|
||||||
if ($form->isSubmitted() && $form->isValid()) {
|
if ($form->isSubmitted() && $form->isValid()) {
|
||||||
$entityManager->flush();
|
$entityManager->flush();
|
||||||
|
return $this->redirectToRoute('app_fault_index');
|
||||||
return $this->redirectToRoute('app_fault_index', [], Response::HTTP_SEE_OTHER);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->render('fault/edit.html.twig', [
|
return $this->render('fault/edit.html.twig', [
|
||||||
'fault' => $fault,
|
|
||||||
'form' => $form,
|
'form' => $form,
|
||||||
|
'fault' => $fault,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[Route('/{id}', name: 'app_fault_delete', methods: ['POST'])]
|
#[Route('/{id}', name: 'app_fault_delete', methods: ['POST'])]
|
||||||
public function delete(Request $request, Fault $fault, EntityManagerInterface $entityManager): Response
|
public function delete(Request $request, Fault $fault, EntityManagerInterface $entityManager): Response
|
||||||
{
|
{
|
||||||
// Un chauffagiste ne peut supprimer que ses propres pannes
|
$this->denyUnlessAdminOrSecretaire();
|
||||||
if ($this->isGranted('ROLE_CHAUFFAGISTE') && $fault->getUser() !== $this->getUser()) {
|
|
||||||
throw $this->createAccessDeniedException('Vous ne pouvez pas supprimer cette panne.');
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($this->isCsrfTokenValid('delete'.$fault->getId(), $request->get('csrf_token'))) {
|
if ($this->isCsrfTokenValid('delete'.$fault->getId(), $request->request->get('_token'))) {
|
||||||
$entityManager->remove($fault);
|
$entityManager->remove($fault);
|
||||||
$entityManager->flush();
|
$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\Entity\Intervention;
|
||||||
use App\Form\InterventionType;
|
use App\Form\InterventionType;
|
||||||
|
use App\Form\RemarqueType;
|
||||||
use App\Repository\InterventionRepository;
|
use App\Repository\InterventionRepository;
|
||||||
use Doctrine\ORM\EntityManagerInterface;
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
|
||||||
use Symfony\Component\HttpFoundation\Request;
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
use Symfony\Component\Routing\Attribute\Route;
|
use Symfony\Component\Routing\Annotation\Route;
|
||||||
|
|
||||||
#[Route('/intervention')]
|
#[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
|
public function index(InterventionRepository $interventionRepository): Response
|
||||||
{
|
{
|
||||||
// Vérifier si l'utilisateur est un chauffagiste, pour filtrer ses interventions
|
$this->denyUnlessAdminOrSecretaire();
|
||||||
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->render('intervention/index.html.twig', [
|
return $this->render('intervention/index.html.twig', [
|
||||||
'interventions' => $interventions,
|
'interventions' => $interventionRepository->findAll(),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[Route('/new', name: 'app_intervention_new', methods: ['GET', 'POST'])]
|
#[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();
|
$intervention = new Intervention();
|
||||||
$form = $this->createForm(InterventionType::class, $intervention);
|
$form = $this->createForm(InterventionType::class, $intervention);
|
||||||
$form->handleRequest($request);
|
$form->handleRequest($request);
|
||||||
|
|
||||||
if ($form->isSubmitted() && $form->isValid()) {
|
if ($form->isSubmitted() && $form->isValid()) {
|
||||||
// Si l'utilisateur est un chauffagiste, on associe l'intervention à lui
|
$timestamp = $intervention->getTimestamp();
|
||||||
if ($this->isGranted('ROLE_CHAUFFAGISTE')) {
|
$chauffagiste = $intervention->getUser();
|
||||||
$intervention->setUser($this->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->persist($intervention);
|
||||||
$entityManager->flush();
|
$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', [
|
return $this->render('intervention/new.html.twig', [
|
||||||
'intervention' => $intervention,
|
|
||||||
'form' => $form,
|
'form' => $form,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
@ -59,73 +78,113 @@ final class InterventionController extends AbstractController
|
|||||||
#[Route('/{id}', name: 'app_intervention_show', methods: ['GET'])]
|
#[Route('/{id}', name: 'app_intervention_show', methods: ['GET'])]
|
||||||
public function show(Intervention $intervention): Response
|
public function show(Intervention $intervention): Response
|
||||||
{
|
{
|
||||||
// Vérifier si l'utilisateur peut voir cette intervention (chauffagiste ne voit que ses interventions)
|
$this->denyUnlessAdminOrSecretaire();
|
||||||
if ($this->isGranted('ROLE_CHAUFFAGISTE') && $intervention->getUser() !== $this->getUser()) {
|
|
||||||
throw $this->createAccessDeniedException('Vous ne pouvez pas voir cette intervention.');
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->render('intervention/show.html.twig', [
|
return $this->render('intervention/show.html.twig', [
|
||||||
'intervention' => $intervention,
|
'intervention' => $intervention,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[Route('/{id}/edit', name: 'app_intervention_edit', methods: ['GET', 'POST'])]
|
#[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
|
$this->denyUnlessAdminOrSecretaire();
|
||||||
if ($this->isGranted('ROLE_CHAUFFAGISTE') && $intervention->getUser() !== $this->getUser()) {
|
|
||||||
throw $this->createAccessDeniedException('Vous ne pouvez pas modifier cette intervention.');
|
|
||||||
}
|
|
||||||
|
|
||||||
$form = $this->createForm(InterventionType::class, $intervention);
|
$form = $this->createForm(InterventionType::class, $intervention);
|
||||||
$form->handleRequest($request);
|
$form->handleRequest($request);
|
||||||
|
|
||||||
if ($form->isSubmitted() && $form->isValid()) {
|
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', [
|
return $this->render('intervention/edit.html.twig', [
|
||||||
'intervention' => $intervention,
|
|
||||||
'form' => $form,
|
'form' => $form,
|
||||||
|
'intervention' => $intervention,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[Route('/{id}', name: 'app_intervention_delete', methods: ['POST'])]
|
#[Route('/{id}', name: 'app_intervention_delete', methods: ['POST'])]
|
||||||
public function delete(Request $request, Intervention $intervention, EntityManagerInterface $entityManager): Response
|
public function delete(Request $request, Intervention $intervention, EntityManagerInterface $entityManager): Response
|
||||||
{
|
{
|
||||||
// Vérification de sécurité : un chauffagiste ne peut supprimer que ses propres interventions
|
$this->denyUnlessAdminOrSecretaire();
|
||||||
if ($this->isGranted('ROLE_CHAUFFAGISTE') && $intervention->getUser() !== $this->getUser()) {
|
|
||||||
throw $this->createAccessDeniedException('Vous ne pouvez pas supprimer cette intervention.');
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($this->isCsrfTokenValid('delete'.$intervention->getId(), $request->get('csrf_token'))) {
|
if ($this->isCsrfTokenValid('delete' . $intervention->getId(), $request->request->get('_token'))) {
|
||||||
$entityManager->remove($intervention);
|
$entityManager->remove($intervention);
|
||||||
$entityManager->flush();
|
$entityManager->flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->redirectToRoute('app_intervention_index', [], Response::HTTP_SEE_OTHER);
|
return $this->redirectToRoute('app_intervention_index');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[Route('/{id}/remarque', name: 'app_intervention_remarque', methods: ['GET', 'POST'])]
|
||||||
|
public function ajouterRemarque(Request $request, Intervention $intervention, EntityManagerInterface $entityManager): Response
|
||||||
#[Route('/api/interventions', name: 'api_interventions')]
|
|
||||||
public function apiInterventions(InterventionRepository $repo): JsonResponse
|
|
||||||
{
|
{
|
||||||
$interventions = $repo->findAll();
|
$user = $this->getUser();
|
||||||
|
if (!$this->isGranted('ROLE_CHAUFFAGISTE') || $intervention->getUser() !== $user) {
|
||||||
$events = [];
|
throw $this->createAccessDeniedException("Vous ne pouvez modifier que vos propres interventions.");
|
||||||
|
|
||||||
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
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
use Symfony\Component\HttpFoundation\Request;
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
use Symfony\Component\Routing\Attribute\Route;
|
use Symfony\Component\Routing\Annotation\Route;
|
||||||
|
|
||||||
#[Route('/skill')]
|
#[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
|
public function index(SkillRepository $skillRepository): Response
|
||||||
{
|
{
|
||||||
$this->denyAccessUnlessGranted('ROLE_ADMIN');
|
$this->denyUnlessAdminOrSecretaire();
|
||||||
return $this->render('skill/admin.html.twig', [
|
return $this->render('skill/index.html.twig', [
|
||||||
'skills' => $skillRepository->findAll(),
|
'skills' => $skillRepository->findAll(),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
@ -26,7 +26,8 @@ final class SkillController extends AbstractController
|
|||||||
#[Route('/new', name: 'app_skill_new', methods: ['GET', 'POST'])]
|
#[Route('/new', name: 'app_skill_new', methods: ['GET', 'POST'])]
|
||||||
public function new(Request $request, EntityManagerInterface $entityManager): Response
|
public function new(Request $request, EntityManagerInterface $entityManager): Response
|
||||||
{
|
{
|
||||||
$this->denyAccessUnlessGranted('ROLE_ADMIN');
|
$this->denyUnlessAdminOrSecretaire();
|
||||||
|
|
||||||
$skill = new Skill();
|
$skill = new Skill();
|
||||||
$form = $this->createForm(SkillType::class, $skill);
|
$form = $this->createForm(SkillType::class, $skill);
|
||||||
$form->handleRequest($request);
|
$form->handleRequest($request);
|
||||||
@ -35,11 +36,10 @@ final class SkillController extends AbstractController
|
|||||||
$entityManager->persist($skill);
|
$entityManager->persist($skill);
|
||||||
$entityManager->flush();
|
$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', [
|
return $this->render('skill/new.html.twig', [
|
||||||
'skill' => $skill,
|
|
||||||
'form' => $form,
|
'form' => $form,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
@ -47,6 +47,7 @@ final class SkillController extends AbstractController
|
|||||||
#[Route('/{id}', name: 'app_skill_show', methods: ['GET'])]
|
#[Route('/{id}', name: 'app_skill_show', methods: ['GET'])]
|
||||||
public function show(Skill $skill): Response
|
public function show(Skill $skill): Response
|
||||||
{
|
{
|
||||||
|
$this->denyUnlessAdminOrSecretaire();
|
||||||
return $this->render('skill/show.html.twig', [
|
return $this->render('skill/show.html.twig', [
|
||||||
'skill' => $skill,
|
'skill' => $skill,
|
||||||
]);
|
]);
|
||||||
@ -55,31 +56,40 @@ final class SkillController extends AbstractController
|
|||||||
#[Route('/{id}/edit', name: 'app_skill_edit', methods: ['GET', 'POST'])]
|
#[Route('/{id}/edit', name: 'app_skill_edit', methods: ['GET', 'POST'])]
|
||||||
public function edit(Request $request, Skill $skill, EntityManagerInterface $entityManager): Response
|
public function edit(Request $request, Skill $skill, EntityManagerInterface $entityManager): Response
|
||||||
{
|
{
|
||||||
$this->denyAccessUnlessGranted('ROLE_ADMIN');
|
$this->denyUnlessAdminOrSecretaire();
|
||||||
|
|
||||||
$form = $this->createForm(SkillType::class, $skill);
|
$form = $this->createForm(SkillType::class, $skill);
|
||||||
$form->handleRequest($request);
|
$form->handleRequest($request);
|
||||||
|
|
||||||
if ($form->isSubmitted() && $form->isValid()) {
|
if ($form->isSubmitted() && $form->isValid()) {
|
||||||
$entityManager->flush();
|
$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', [
|
return $this->render('skill/edit.html.twig', [
|
||||||
'skill' => $skill,
|
|
||||||
'form' => $form,
|
'form' => $form,
|
||||||
|
'skill' => $skill,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[Route('/{id}', name: 'app_skill_delete', methods: ['POST'])]
|
#[Route('/{id}', name: 'app_skill_delete', methods: ['POST'])]
|
||||||
public function delete(Request $request, Skill $skill, EntityManagerInterface $entityManager): Response
|
public function delete(Request $request, Skill $skill, EntityManagerInterface $entityManager): Response
|
||||||
{
|
{
|
||||||
$this->denyAccessUnlessGranted('ROLE_ADMIN');
|
$this->denyUnlessAdminOrSecretaire();
|
||||||
if ($this->isCsrfTokenValid('delete'.$skill->getId(), $request->get('csrf_token'))) {
|
|
||||||
|
if ($this->isCsrfTokenValid('delete' . $skill->getId(), $request->request->get('_token'))) {
|
||||||
$entityManager->remove($skill);
|
$entityManager->remove($skill);
|
||||||
$entityManager->flush();
|
$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\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
use Symfony\Component\HttpFoundation\Request;
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
use Symfony\Component\Routing\Attribute\Route;
|
use Symfony\Component\Routing\Annotation\Route;
|
||||||
|
|
||||||
#[Route('/stock')]
|
#[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
|
public function index(StockRepository $stockRepository): Response
|
||||||
{
|
{
|
||||||
|
$this->denyUnlessAdminOrSecretaire();
|
||||||
return $this->render('stock/index.html.twig', [
|
return $this->render('stock/index.html.twig', [
|
||||||
'stocks' => $stockRepository->findAll(),
|
'stocks' => $stockRepository->findAll(),
|
||||||
]);
|
]);
|
||||||
@ -25,6 +26,8 @@ final class StockController extends AbstractController
|
|||||||
#[Route('/new', name: 'app_stock_new', methods: ['GET', 'POST'])]
|
#[Route('/new', name: 'app_stock_new', methods: ['GET', 'POST'])]
|
||||||
public function new(Request $request, EntityManagerInterface $entityManager): Response
|
public function new(Request $request, EntityManagerInterface $entityManager): Response
|
||||||
{
|
{
|
||||||
|
$this->denyUnlessAdminOrSecretaire();
|
||||||
|
|
||||||
$stock = new Stock();
|
$stock = new Stock();
|
||||||
$form = $this->createForm(StockType::class, $stock);
|
$form = $this->createForm(StockType::class, $stock);
|
||||||
$form->handleRequest($request);
|
$form->handleRequest($request);
|
||||||
@ -33,11 +36,10 @@ final class StockController extends AbstractController
|
|||||||
$entityManager->persist($stock);
|
$entityManager->persist($stock);
|
||||||
$entityManager->flush();
|
$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', [
|
return $this->render('stock/new.html.twig', [
|
||||||
'stock' => $stock,
|
|
||||||
'form' => $form,
|
'form' => $form,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
@ -45,6 +47,7 @@ final class StockController extends AbstractController
|
|||||||
#[Route('/{id}', name: 'app_stock_show', methods: ['GET'])]
|
#[Route('/{id}', name: 'app_stock_show', methods: ['GET'])]
|
||||||
public function show(Stock $stock): Response
|
public function show(Stock $stock): Response
|
||||||
{
|
{
|
||||||
|
$this->denyUnlessAdminOrSecretaire();
|
||||||
return $this->render('stock/show.html.twig', [
|
return $this->render('stock/show.html.twig', [
|
||||||
'stock' => $stock,
|
'stock' => $stock,
|
||||||
]);
|
]);
|
||||||
@ -53,10 +56,7 @@ final class StockController extends AbstractController
|
|||||||
#[Route('/{id}/edit', name: 'app_stock_edit', methods: ['GET', 'POST'])]
|
#[Route('/{id}/edit', name: 'app_stock_edit', methods: ['GET', 'POST'])]
|
||||||
public function edit(Request $request, Stock $stock, EntityManagerInterface $entityManager): Response
|
public function edit(Request $request, Stock $stock, EntityManagerInterface $entityManager): Response
|
||||||
{
|
{
|
||||||
// Si un chauffagiste essaie de modifier un stock d'admin
|
$this->denyUnlessAdminOrSecretaire();
|
||||||
if ($this->isGranted('ROLE_CHAUFFAGISTE')) {
|
|
||||||
throw $this->createAccessDeniedException('Vous ne pouvez pas modifier ce stock.');
|
|
||||||
}
|
|
||||||
|
|
||||||
$form = $this->createForm(StockType::class, $stock);
|
$form = $this->createForm(StockType::class, $stock);
|
||||||
$form->handleRequest($request);
|
$form->handleRequest($request);
|
||||||
@ -64,23 +64,32 @@ final class StockController extends AbstractController
|
|||||||
if ($form->isSubmitted() && $form->isValid()) {
|
if ($form->isSubmitted() && $form->isValid()) {
|
||||||
$entityManager->flush();
|
$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', [
|
return $this->render('stock/edit.html.twig', [
|
||||||
'stock' => $stock,
|
|
||||||
'form' => $form,
|
'form' => $form,
|
||||||
|
'stock' => $stock,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[Route('/{id}', name: 'app_stock_delete', methods: ['POST'])]
|
#[Route('/{id}', name: 'app_stock_delete', methods: ['POST'])]
|
||||||
public function delete(Request $request, Stock $stock, EntityManagerInterface $entityManager): Response
|
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->remove($stock);
|
||||||
$entityManager->flush();
|
$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\HttpFoundation\Response;
|
||||||
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
|
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
|
||||||
use Symfony\Component\Routing\Annotation\Route;
|
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
|
final class UserController extends AbstractController
|
||||||
{
|
{
|
||||||
// Route pour afficher tous les utilisateurs
|
|
||||||
#[Route('/user', name: 'app_user_index', methods: ['GET'])]
|
#[Route('/user', name: 'app_user_index', methods: ['GET'])]
|
||||||
public function index(UserRepository $userRepository): Response
|
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'])]
|
#[Route('/user/new', name: 'app_user_new', methods: ['GET', 'POST'])]
|
||||||
public function new(Request $request, EntityManagerInterface $entityManager, UserPasswordHasherInterface $passwordHasher): Response
|
public function new(Request $request, EntityManagerInterface $entityManager, UserPasswordHasherInterface $passwordHasher): Response
|
||||||
{
|
{
|
||||||
@ -32,7 +32,6 @@ final class UserController extends AbstractController
|
|||||||
$form->handleRequest($request);
|
$form->handleRequest($request);
|
||||||
|
|
||||||
if ($form->isSubmitted() && $form->isValid()) {
|
if ($form->isSubmitted() && $form->isValid()) {
|
||||||
// Hash du mot de passe
|
|
||||||
$plainPassword = $form->get('plainPassword')->getData();
|
$plainPassword = $form->get('plainPassword')->getData();
|
||||||
$hashedPassword = $passwordHasher->hashPassword($user, $plainPassword);
|
$hashedPassword = $passwordHasher->hashPassword($user, $plainPassword);
|
||||||
$user->setPassword($hashedPassword);
|
$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'])]
|
#[Route('/user/{id}', name: 'app_user_show', methods: ['GET'])]
|
||||||
public function show(Utilisateur $user): Response
|
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'])]
|
#[Route('/user/{id}/edit', name: 'app_user_edit', methods: ['GET', 'POST'])]
|
||||||
public function edit(Request $request, Utilisateur $user, EntityManagerInterface $entityManager): Response
|
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 = $this->createForm(UserType::class, $user);
|
||||||
$form->handleRequest($request);
|
$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'])]
|
#[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->isCsrfTokenValid('delete' . $utilisateur->getId(), $request->request->get('_token'))) {
|
||||||
if ($this->isGranted('ROLE_SECRETAIRE') && $user->hasRole('ROLE_SECRETAIRE')) {
|
$entityManager->remove($utilisateur);
|
||||||
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);
|
|
||||||
$entityManager->flush();
|
$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\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
use Symfony\Component\HttpFoundation\Request;
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
use Symfony\Component\Routing\Attribute\Route;
|
use Symfony\Component\Routing\Annotation\Route;
|
||||||
|
|
||||||
#[Route('/vehicle')]
|
#[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
|
public function index(VehicleRepository $vehicleRepository): Response
|
||||||
{
|
{
|
||||||
// Admin peut voir tous les véhicules, chauffagiste ne peut voir que ses véhicules
|
$this->denyUnlessAdminOrSecretaire();
|
||||||
$vehicles = $this->isGranted('ROLE_CHAUFFAGISTE')
|
|
||||||
? $vehicleRepository->findByUser($this->getUser()) // Filtre les véhicules par utilisateur
|
|
||||||
: $vehicleRepository->findAll(); // Les admins voient tout
|
|
||||||
|
|
||||||
return $this->render('vehicle/index.html.twig', [
|
return $this->render('vehicle/index.html.twig', [
|
||||||
'vehicles' => $vehicles,
|
'vehicles' => $vehicleRepository->findAll(),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[Route('/new', name: 'app_vehicle_new', methods: ['GET', 'POST'])]
|
#[Route('/new', name: 'app_vehicle_new', methods: ['GET', 'POST'])]
|
||||||
public function new(Request $request, EntityManagerInterface $entityManager): Response
|
public function new(Request $request, EntityManagerInterface $entityManager): Response
|
||||||
{
|
{
|
||||||
|
$this->denyUnlessAdminOrSecretaire();
|
||||||
|
|
||||||
$vehicle = new Vehicle();
|
$vehicle = new Vehicle();
|
||||||
$form = $this->createForm(VehicleType::class, $vehicle);
|
$form = $this->createForm(VehicleType::class, $vehicle);
|
||||||
$form->handleRequest($request);
|
$form->handleRequest($request);
|
||||||
|
|
||||||
if ($form->isSubmitted() && $form->isValid()) {
|
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->persist($vehicle);
|
||||||
$entityManager->flush();
|
$entityManager->flush();
|
||||||
|
return $this->redirectToRoute('app_vehicle_index');
|
||||||
return $this->redirectToRoute('app_vehicle_index', [], Response::HTTP_SEE_OTHER);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->render('vehicle/new.html.twig', [
|
return $this->render('vehicle/new.html.twig', [
|
||||||
'vehicle' => $vehicle,
|
|
||||||
'form' => $form,
|
'form' => $form,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
@ -55,11 +46,7 @@ final class VehicleController extends AbstractController
|
|||||||
#[Route('/{id}', name: 'app_vehicle_show', methods: ['GET'])]
|
#[Route('/{id}', name: 'app_vehicle_show', methods: ['GET'])]
|
||||||
public function show(Vehicle $vehicle): Response
|
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
|
$this->denyUnlessAdminOrSecretaire();
|
||||||
if ($this->isGranted('ROLE_CHAUFFAGISTE') && $vehicle->getUser() !== $this->getUser()) {
|
|
||||||
throw $this->createAccessDeniedException('Vous ne pouvez pas voir ce véhicule.');
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->render('vehicle/show.html.twig', [
|
return $this->render('vehicle/show.html.twig', [
|
||||||
'vehicle' => $vehicle,
|
'vehicle' => $vehicle,
|
||||||
]);
|
]);
|
||||||
@ -68,39 +55,39 @@ final class VehicleController extends AbstractController
|
|||||||
#[Route('/{id}/edit', name: 'app_vehicle_edit', methods: ['GET', 'POST'])]
|
#[Route('/{id}/edit', name: 'app_vehicle_edit', methods: ['GET', 'POST'])]
|
||||||
public function edit(Request $request, Vehicle $vehicle, EntityManagerInterface $entityManager): Response
|
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
|
$this->denyUnlessAdminOrSecretaire();
|
||||||
if ($this->isGranted('ROLE_CHAUFFAGISTE') && $vehicle->getUser() !== $this->getUser()) {
|
|
||||||
throw $this->createAccessDeniedException('Vous ne pouvez pas modifier ce véhicule.');
|
|
||||||
}
|
|
||||||
|
|
||||||
$form = $this->createForm(VehicleType::class, $vehicle);
|
$form = $this->createForm(VehicleType::class, $vehicle);
|
||||||
$form->handleRequest($request);
|
$form->handleRequest($request);
|
||||||
|
|
||||||
if ($form->isSubmitted() && $form->isValid()) {
|
if ($form->isSubmitted() && $form->isValid()) {
|
||||||
$entityManager->flush();
|
$entityManager->flush();
|
||||||
|
return $this->redirectToRoute('app_vehicle_index');
|
||||||
return $this->redirectToRoute('app_vehicle_index', [], Response::HTTP_SEE_OTHER);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->render('vehicle/edit.html.twig', [
|
return $this->render('vehicle/edit.html.twig', [
|
||||||
'vehicle' => $vehicle,
|
|
||||||
'form' => $form,
|
'form' => $form,
|
||||||
|
'vehicle' => $vehicle,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[Route('/{id}', name: 'app_vehicle_delete', methods: ['POST'])]
|
#[Route('/{id}', name: 'app_vehicle_delete', methods: ['POST'])]
|
||||||
public function delete(Request $request, Vehicle $vehicle, EntityManagerInterface $entityManager): Response
|
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
|
$this->denyUnlessAdminOrSecretaire();
|
||||||
if ($this->isGranted('ROLE_CHAUFFAGISTE') && $vehicle->getUser() !== $this->getUser()) {
|
|
||||||
throw $this->createAccessDeniedException('Vous ne pouvez pas supprimer ce véhicule.');
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($this->isCsrfTokenValid('delete'.$vehicle->getId(), $request->get('csrf_token'))) {
|
if ($this->isCsrfTokenValid('delete' . $vehicle->getId(), $request->request->get('_token'))) {
|
||||||
$entityManager->remove($vehicle);
|
$entityManager->remove($vehicle);
|
||||||
$entityManager->flush();
|
$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)]
|
#[ORM\Column(length: 255)]
|
||||||
private ?string $Status = null;
|
private ?string $Status = null;
|
||||||
|
|
||||||
|
#[ORM\Column(type: 'text', nullable: true)]
|
||||||
|
private ?string $Remarque = null;
|
||||||
|
|
||||||
#[ORM\ManyToOne(inversedBy: 'interventions')]
|
#[ORM\ManyToOne(inversedBy: 'interventions')]
|
||||||
#[ORM\JoinColumn(nullable: false)]
|
#[ORM\JoinColumn(nullable: false)]
|
||||||
private ?Utilisateur $user = null;
|
private ?Utilisateur $user = null;
|
||||||
@ -355,5 +358,15 @@ class Intervention
|
|||||||
{
|
{
|
||||||
return $this->Wording;
|
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\Stock;
|
||||||
use App\Entity\Utilisateur;
|
use App\Entity\Utilisateur;
|
||||||
use App\Entity\Vehicle;
|
use App\Entity\Vehicle;
|
||||||
|
use Doctrine\ORM\EntityRepository;
|
||||||
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
|
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
|
||||||
use Symfony\Component\Form\AbstractType;
|
use Symfony\Component\Form\AbstractType;
|
||||||
use Symfony\Component\Form\Extension\Core\Type\DateTimeType;
|
use Symfony\Component\Form\Extension\Core\Type\DateTimeType;
|
||||||
@ -28,7 +29,15 @@ class InterventionType extends AbstractType
|
|||||||
->add('Status', TextType::class)
|
->add('Status', TextType::class)
|
||||||
->add('user', EntityType::class, [
|
->add('user', EntityType::class, [
|
||||||
'class' => Utilisateur::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, [
|
->add('fault', EntityType::class, [
|
||||||
'class' => Fault::class,
|
'class' => Fault::class,
|
||||||
@ -44,16 +53,7 @@ class InterventionType extends AbstractType
|
|||||||
'choice_label' => 'Wording',
|
'choice_label' => 'Wording',
|
||||||
'multiple' => true,
|
'multiple' => true,
|
||||||
'expanded' => 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
|
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>
|
<ul>
|
||||||
{% if is_granted('ROLE_ADMIN') %}
|
{% if is_granted('ROLE_ADMIN') %}
|
||||||
<li><a href="{{ path('admin_dashboard') }}">Dashboard Admin</a></li>
|
<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_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_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_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_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>
|
<li><a href="{{ path('app_calendrier_index') }}">Tous les plannings</a></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if is_granted('ROLE_SECRETAIRE') %}
|
{% if is_granted('ROLE_SECRETAIRE') %}
|
||||||
<li><a href="{{ path('secretaire_dashboard') }}">Dashboard Secrétaire</a></li>
|
<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_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_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_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>
|
<li><a href="{{ path('app_calendrier_indexSecretaire') }}">Plannings chauffagistes</a></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
@ -5,38 +5,24 @@
|
|||||||
{% block body %}
|
{% block body %}
|
||||||
<h1>📅 Calendrier des interventions</h1>
|
<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>
|
<div id="calendar"></div>
|
||||||
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/fullcalendar@6.1.8/index.global.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/fullcalendar@6.1.8/index.global.min.js"></script>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function () {
|
||||||
var calendarEl = document.getElementById('calendar');
|
var calendarEl = document.getElementById('calendar');
|
||||||
|
|
||||||
var calendar = new FullCalendar.Calendar(calendarEl, {
|
var calendar = new FullCalendar.Calendar(calendarEl, {
|
||||||
initialView: 'dayGridMonth',
|
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();
|
calendar.render();
|
||||||
});
|
});
|
||||||
</script>
|
</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 %}
|
{% endblock %}
|
||||||
|
@ -7,15 +7,20 @@
|
|||||||
|
|
||||||
<div id="calendar"></div>
|
<div id="calendar"></div>
|
||||||
|
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/fullcalendar@6.1.8/index.global.min.js"></script>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function () {
|
||||||
var calendarEl = document.getElementById('calendar');
|
var calendarEl = document.getElementById('calendar');
|
||||||
var calendar = new FullCalendar.Calendar(calendarEl, {
|
var calendar = new FullCalendar.Calendar(calendarEl, {
|
||||||
initialView: 'dayGridMonth',
|
initialView: 'dayGridMonth',
|
||||||
events: {{ events | raw }},
|
locale: 'fr',
|
||||||
eventClick: function(info) {
|
eventTimeFormat: {
|
||||||
alert('Intervention : ' + info.event.title + '\n' + info.event.extendedProps.description);
|
hour: '2-digit',
|
||||||
}
|
minute: '2-digit',
|
||||||
|
hour12: false
|
||||||
|
},
|
||||||
|
events: {{ events|raw }}
|
||||||
});
|
});
|
||||||
calendar.render();
|
calendar.render();
|
||||||
});
|
});
|
||||||
|
@ -7,15 +7,20 @@
|
|||||||
|
|
||||||
<div id="calendar"></div>
|
<div id="calendar"></div>
|
||||||
|
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/fullcalendar@6.1.8/index.global.min.js"></script>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function () {
|
||||||
var calendarEl = document.getElementById('calendar');
|
var calendarEl = document.getElementById('calendar');
|
||||||
var calendar = new FullCalendar.Calendar(calendarEl, {
|
var calendar = new FullCalendar.Calendar(calendarEl, {
|
||||||
initialView: 'dayGridMonth',
|
initialView: 'dayGridMonth',
|
||||||
events: {{ events | raw }},
|
locale: 'fr',
|
||||||
eventClick: function(info) {
|
eventTimeFormat: {
|
||||||
alert('Intervention : ' + info.event.title + '\n' + info.event.extendedProps.description);
|
hour: '2-digit',
|
||||||
}
|
minute: '2-digit',
|
||||||
|
hour12: false
|
||||||
|
},
|
||||||
|
events: {{ events|raw }}
|
||||||
});
|
});
|
||||||
calendar.render();
|
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) }}
|
{{ form_start(form) }}
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
@ -36,12 +30,30 @@
|
|||||||
{{ form_errors(form.Status) }}
|
{{ form_errors(form.Status) }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#<div class="form-group">#}
|
<div class="form-group">
|
||||||
{# {{ form_label(form.vehicule, 'Véhicule associé') }}#}
|
{{ form_label(form.fault, 'Panne') }}
|
||||||
{# {{ form_widget(form.vehicule, {'attr': {'class': 'form-control'}}) }}#}
|
{{ form_widget(form.fault, {'attr': {'class': 'form-control'}}) }}
|
||||||
{# {{ form_errors(form.vehicule) }}#}
|
{{ form_errors(form.fault) }}
|
||||||
{#</div>#}
|
</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) }}
|
{{ form_end(form) }}
|
||||||
|
@ -5,67 +5,7 @@
|
|||||||
{% block body %}
|
{% block body %}
|
||||||
<h1 class="mb-4">➕ Créer une nouvelle intervention</h1>
|
<h1 class="mb-4">➕ Créer une nouvelle intervention</h1>
|
||||||
|
|
||||||
{#<<<<<<< HEAD#}
|
|
||||||
<div class="background-intervention">
|
<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') }}
|
{{ include('intervention/_form.html.twig') }}
|
||||||
</div>
|
</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 %}
|
{% 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 %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</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>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user