From 4a97dad74f2b0d874226c938c26f85d455328cb3 Mon Sep 17 00:00:00 2001 From: afornerot Date: Mon, 7 Jul 2025 21:48:40 +0200 Subject: [PATCH] svg --- src/Controller/HomeController.php | 8 ++- src/Controller/IssueController.php | 61 ++++++++++++++------- src/Service/RedmineService.php | 87 ++++++++++++++++++++++++++++++ templates/base.html.twig | 11 ++++ templates/project/view.html.twig | 30 +++++------ 5 files changed, 159 insertions(+), 38 deletions(-) diff --git a/src/Controller/HomeController.php b/src/Controller/HomeController.php index 3ca3aa5..70e579e 100644 --- a/src/Controller/HomeController.php +++ b/src/Controller/HomeController.php @@ -2,7 +2,9 @@ namespace App\Controller; +use App\Entity\User; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; +use Symfony\Component\HttpFoundation\File\Exception\AccessDeniedException; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Attribute\Route; @@ -12,7 +14,11 @@ class HomeController extends AbstractController #[Route('/', name: 'app_home')] public function home(Request $request): Response { - $projects = $this->getUser()->getProjects(); + $user = $this->getUser(); + if (!$user instanceof User) { + throw new AccessDeniedException('Vous n\'avez pas accès à cette ressource.'); + } + $projects = $user->getProjects(); return $this->render('home/home.html.twig', [ 'usemenu' => true, diff --git a/src/Controller/IssueController.php b/src/Controller/IssueController.php index 418bbca..6f6756e 100644 --- a/src/Controller/IssueController.php +++ b/src/Controller/IssueController.php @@ -42,12 +42,6 @@ class IssueController extends AbstractController { $data = $request->request; - $sourceIssues = $data->all('sourceIssues'); - $source = explode('|', $data->get('source')); - $sourceStatus = $source[0]; - $sourceSprint = $source[1]; - $sourceVersion = $source[2]; - $target = explode('|', $data->get('target')); $targetStatus = $target[0]; $targetSprint = $target[1]; @@ -55,31 +49,60 @@ class IssueController extends AbstractController $targetIssues = $data->all('targetIssues'); - if (!$sourceIssues || !$source || !$targetIssues || !$target) { - return new JsonResponse(['error' => 'Données incomplètes.'], 400); + if (!$targetIssues || !$target) { + return new JsonResponse(['message' => 'target vide on fait rien']); } - $payload = [ - 'fixed_version_id' => $target, - ]; + // Modification de l'issue si issue présent dans targetIssues + $allowed = false; + if (in_array($id, $targetIssues)) { + // Verifier que l'on a la permission de changer de statut + $rissue = $this->redmineService->getIssue($id, $this->getParameter('redmineApikey')); + foreach ($rissue['allowed_statuses'] as $status) { + if ($status['id'] == $targetStatus) { + $allowed = true; + break; + } + } - // $redmineService->updateIssue($id, $payload); + if (!$allowed) { + return new JsonResponse(['message' => 'Erreur Redmine : Déplacement Interdit'], 400); + } - // ✅ Tu peux stocker l’ordre si besoin dans Redmine via custom field (optionnel) - // ou en interne selon ta logique métier + $payload = [ + 'fixed_version_id' => $targetVersion, + 'agile_data_attributes' => ['agile_sprint_id' => $targetSprint], + 'status_id' => $targetStatus, + ]; + $this->redmineService->updateIssue($id, $payload, $this->getParameter('redmineApikey')); + + /* + $payload = + [ + 'id' => $id, + 'issue' => [ + 'status_id' => $targetStatus, + 'fixed_version_id' => $targetVersion, + ], + 'positions' => [], + ]; + + foreach ($targetIssues as $key => $issue) { + $payload['positions'][$issue] = ['position' => $key]; + } + + $this->redmineService->updatePosition($payload, $this->getParameter('redmineApikey')); + */ + } return new JsonResponse([ 'message' => 'Ordre mis à jour', 'moved' => $id, - 'sourceIssues' => $sourceIssues, - 'sourceStatus' => $targetStatus, - 'sourceSprint' => $sourceSprint, - 'sourceVersion' => $sourceVersion, - 'targetIssues' => $targetIssues, 'targetStatus' => $targetStatus, 'targetSprint' => $targetSprint, 'targetVersion' => $targetVersion, + 'allowed' => $allowed, ]); } } diff --git a/src/Service/RedmineService.php b/src/Service/RedmineService.php index 8868d60..14a2709 100644 --- a/src/Service/RedmineService.php +++ b/src/Service/RedmineService.php @@ -7,6 +7,8 @@ use App\Entity\Project; use App\Repository\IssueRepository; use Doctrine\ORM\EntityManagerInterface; use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface; +use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface; +use Symfony\Contracts\HttpClient\Exception\ServerExceptionInterface; use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface; use Symfony\Contracts\HttpClient\HttpClientInterface; @@ -273,6 +275,31 @@ class RedmineService return $allIssues; } + public function getIssue(int $issueId, string $apiKey): array + { + try { + $url = "{$this->baseUrl}/issues/{$issueId}.json?include=allowed_statuses"; + + $response = $this->client->request('GET', $url, [ + 'headers' => [ + 'X-Redmine-API-Key' => $apiKey, + 'Accept' => 'application/json', + ], + ]); + + if (200 !== $response->getStatusCode()) { + throw new \RuntimeException('Erreur de communication avec Redmine : '.$response->getStatusCode()); + } + + $data = $response->toArray(); + $data['issue']['sprint'] = $this->getIssueAgile($issueId, $apiKey); + + return $data['issue'] ?? []; + } catch (TransportExceptionInterface $e) { + throw new \RuntimeException('Erreur de communication avec Redmine : '.$e->getMessage()); + } + } + public function getIssueAgile(int $issueId, string $apiKey): array { try { @@ -296,4 +323,64 @@ class RedmineService throw new \RuntimeException('Erreur de communication avec Redmine : '.$e->getMessage()); } } + + public function updateIssue(int $id, array $data, string $apiKey): array + { + $url = $this->baseUrl.'/issues/'.$id.'.json'; + + try { + $response = $this->client->request('PUT', $url, [ + 'headers' => [ + 'X-Redmine-API-Key' => $apiKey, + 'Content-Type' => 'application/json', + ], + 'json' => ['issue' => $data], + ]); + + $statusCode = $response->getStatusCode(); + $content = trim($response->getContent(false)); + + // Si vide et code 200, c’est peut-être une réussite silencieuse + if ('' === $content) { + return ['success' => true, 'message' => 'OK, mais pas de contenu']; + } + + $decoded = json_decode($content, true); + + if (isset($decoded['errors']) && is_array($decoded['errors']) && count($decoded['errors']) > 0) { + throw new \RuntimeException('Erreur Redmine : '.implode(', ', $decoded['errors'])); + } + + return $decoded; + } catch (ClientExceptionInterface|ServerExceptionInterface $e) { + // Erreur HTTP (4xx ou 5xx) + $errorBody = $e->getResponse()->getContent(false); + throw new \RuntimeException('Erreur Redmine: '.$errorBody, $e->getCode(), $e); + } catch (TransportExceptionInterface $e) { + throw new \RuntimeException('Erreur de communication avec Redmine : '.$e->getMessage()); + } + } + + public function updatePosition(array $data, string $apiKey): array + { + $url = $this->baseUrl.'/agile/board'; + + try { + $response = $this->client->request('PUT', $url, [ + 'headers' => [ + 'X-Redmine-API-Key' => $apiKey, + 'Content-Type' => 'application/json', + ], + 'json' => ['issue' => $data], + ]); + + return ['ok']; + } catch (ClientExceptionInterface|ServerExceptionInterface $e) { + // Erreur HTTP (4xx ou 5xx) + $errorBody = $e->getResponse()->getContent(false); + throw new \RuntimeException('Erreur Redmine: '.$errorBody, $e->getCode(), $e); + } catch (TransportExceptionInterface $e) { + throw new \RuntimeException('Erreur de communication avec Redmine : '.$e->getMessage()); + } + } } diff --git a/templates/base.html.twig b/templates/base.html.twig index 05a7046..bcd163e 100644 --- a/templates/base.html.twig +++ b/templates/base.html.twig @@ -98,6 +98,17 @@ +
+ +
+