This commit is contained in:
2025-08-04 22:53:49 +02:00
parent 8c21a69556
commit fc7494be40
6 changed files with 107 additions and 3 deletions

View File

@ -102,6 +102,26 @@ class ProjectController extends AbstractController
]);
}
#[Route('/admin/project/moveto/{id}/{status}', name: 'app_admin_project_move')]
#[Route('/user/project/moveto/{id}/{status}', name: 'app_user_project_move')]
public function move(int $id, string $status, Request $request, ProjectRepository $projectRepository, EntityManagerInterface $em): Response
{
$project = $projectRepository->find($id);
if (!$project) {
throw new NotFoundHttpException('La ressource demandée est introuvable.');
}
$attribute = constant(ProjectVoter::class.'::MOVE'.$status);
$this->denyAccessUnlessGranted($attribute, $project);
$project->setStatus(constant(Project::class.'::'.$status));
$em->flush();
$isAdmin = str_starts_with($request->attributes->get('_route'), 'app_admin');
return $this->redirectToRoute($isAdmin ? 'app_admin_project' : 'app_home');
}
#[Route('/admin/project/delete/{id}', name: 'app_admin_project_delete')]
#[Route('/user/project/delete/{id}', name: 'app_user_project_delete')]
public function delete(int $id, Request $request, ProjectRepository $projectRepository, EntityManagerInterface $em): Response
@ -127,4 +147,23 @@ class ProjectController extends AbstractController
return $this->redirectToRoute($isAdmin ? 'app_admin_project' : 'app_home');
}
#[Route('/user/project/view/{id}', name: 'app_user_project_view')]
public function view(int $id, Request $request, ProjectRepository $projectRepository, EntityManagerInterface $em): Response
{
$project = $projectRepository->find($id);
if (!$project) {
throw new NotFoundHttpException('La ressource demandée est introuvable.');
}
$this->denyAccessUnlessGranted(ProjectVoter::VIEW, $project);
return $this->render('project/view.html.twig', [
'usemenu' => true,
'usesidebar' => false,
'title' => 'Projet = '.$project->getTitle(),
'routecancel' => 'app_home',
'project' => $project,
]);
}
}

View File

@ -15,7 +15,6 @@ class Project
public const TOVOTE = 'A Voter';
public const VOTED = 'Voté';
public const ARCHIVED = 'Archivé';
public const NATURE_COLLECTIVE = 'Collective';
public const NATURE_STRATEGIC = 'Stratégique';
public const NATURE_TACTICAL = 'Tactique';

View File

@ -15,10 +15,11 @@ class ProjectVoter extends Voter
public const UPDATE = 'UPDATE';
public const DELETE = 'DELETE';
public const MOVEDRAFT = 'MOVEDRAFT';
public const MOVETOVOTE = 'MOVETOVOTE';
protected function supports(string $attribute, $subject): bool
{
$attributes = [self::VIEW, self::SUBMIT, self::UPDATE, self::DELETE, self::MOVEDRAFT];
$attributes = [self::VIEW, self::SUBMIT, self::UPDATE, self::DELETE, self::MOVEDRAFT, self::MOVETOVOTE];
return in_array($attribute, $attributes) && $subject instanceof Project;
}
@ -48,6 +49,11 @@ class ProjectVoter extends Voter
return $user->hasRole('ROLE_ADMIN') || (Project::TOVOTE === $project->getStatus() && $project->getUsers()->contains($user));
}
private function canMoveToVote(Project $project, User $user): bool
{
return $user->hasRole('ROLE_ADMIN') || (Project::DRAFT === $project->getStatus() && $project->getUsers()->contains($user));
}
protected function voteOnAttribute(string $attribute, $project, TokenInterface $token): bool
{
$user = $token->getUser();
@ -61,6 +67,7 @@ class ProjectVoter extends Voter
self::UPDATE => $this->canUpdate($project, $user),
self::DELETE => $this->canDelete($project, $user),
self::MOVEDRAFT => $this->canMoveDraft($project, $user),
self::MOVETOVOTE => $this->canMoveToVote($project, $user),
default => false,
};
}

View File

@ -9,6 +9,7 @@
{% if is_granted('VIEW', project) %}
<div class='card' style='width:300px; margin-right:10px;'>
<div class='card-header d-flex justify-content-between align-items-center'>
{{project.status}}<br>
{{project.title}}
<div>
@ -16,7 +17,7 @@
<a href="{{ path("app_user_project_update",{id:project.id}) }}" class="btn btn-primary btn-sm"><i class="fas fa-pencil"></i></a>
{% endif %}
<button type="button" class="btn btn-secondary btn-sm"><i class="fas fa-eye"></i></button>
<a href="{{ path("app_user_project_view",{id:project.id}) }}" class="btn btn-secondary btn-sm"><i class="fas fa-eye"></i></a>
</div>
</div>
<div class='card-body'>

View File

@ -10,6 +10,12 @@
{{ form_widget(form.submit) }}
<a href="{{ path(routecancel) }}" class="btn btn-secondary ms-1">Annuler</a>
{%if mode=="update" %}
{% if is_granted('MOVEDRAFT', project) %}
<a href="{{ path("app_user_project_move",{id:form.vars.value.id, status:"DRAFT"}) }}" class="btn btn-primary" onclick="return confirm('Confirmez-vous le passage au vote ?')">Mettre en Brouillon</a>
{% endif %}
{% if is_granted('MOVETOVOTE', project) %}
<a href="{{ path("app_user_project_move",{id:form.vars.value.id, status:"TOVOTE"}) }}" class="btn btn-primary" onclick="return confirm('Confirmez-vous le passage au vote ?')">Mettre au Vote</a>
{% endif %}
{% if is_granted('DELETE', project) %}
<a href="{{ path(routedelete,{id:form.vars.value.id}) }}" class="btn btn-danger float-end" onclick="return confirm('Confirmez-vous la suppression de cet enregistrement ?')">Supprimer</a>
{% endif %}

View File

@ -0,0 +1,52 @@
{% extends 'base.html.twig' %}
{% block title %} = {{title}}{% endblock %}
{% block body %}
<h1>{{title}}</h1>
<a href="{{ path(routecancel) }}" class="btn btn-secondary ms-1">Annuler</a>
{% if is_granted('MOVEDRAFT', project) %}
<a href="{{ path("app_user_project_move",{id:project.id, status:"DRAFT"}) }}" class="btn btn-primary" onclick="return confirm('Confirmez-vous le passage au vote ?')">Mettre en Brouillon</a>
{% endif %}
{% if is_granted('MOVETOVOTE', project) %}
<a href="{{ path("app_user_project_move",{id:project.id, status:"TOVOTE"}) }}" class="btn btn-primary" onclick="return confirm('Confirmez-vous le passage au vote ?')">Mettre au Vote</a>
{% endif %}
{% if is_granted('DELETE', project) %}
<a href="{{ path(routedelete,{id:project.id}) }}" class="btn btn-danger float-end" onclick="return confirm('Confirmez-vous la suppression de cet enregistrement ?')">Supprimer</a>
{% endif %}
<div class="row">
<div class="col-md-4 mx-auto">
<div class="card mt-3">
<div class="card-header">Information</div>
<div class="card-body">
{{ project.title }}
{{ project.nature }}
{{ project.summary }}
</div>
</div>
{{ render(path("bninefiles_files",{domain:'project',id:project.id, editable:0})) }}
<div class="card mt-3">
<div class="card-header">Timeline</div>
<div class="card-body">
{% include('project/timeline.html.twig') %}
</div>
</div>
</div>
<div class="col-md-8 mx-auto">
<div class="card mt-3">
<div class="card-header">Description du Projet</div>
<div class="card-body">
{{ project.description }}
</div>
</div>
</div>
</div>
{% endblock %}