svg
This commit is contained in:
@ -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,
|
||||
|
@ -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,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -98,6 +98,17 @@
|
||||
</content>
|
||||
</main>
|
||||
|
||||
<div class="toast-container position-fixed top-0 end-0 p-3" style="z-index: 9999;">
|
||||
<div id="ajaxErrorToast" class="toast align-items-center text-bg-danger border-0" role="alert" aria-live="assertive" aria-atomic="true">
|
||||
<div class="d-flex">
|
||||
<div class="toast-body" id="ajaxErrorMessage">
|
||||
Une erreur est survenue.
|
||||
</div>
|
||||
<button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast" aria-label="Fermer"></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="mymodal" class="modal" tabindex="-1">
|
||||
<div class="modal-dialog modal-lg">
|
||||
<div class="modal-content">
|
||||
|
@ -417,26 +417,15 @@
|
||||
},
|
||||
|
||||
update: function (event, ui) {
|
||||
// ❗ Se déclenche même pour tri dans la même colonne
|
||||
if (!event.originalEvent) return; // ← ignorer les appels indirects
|
||||
console.log("UPDATE");
|
||||
if (!event.originalEvent) return;
|
||||
const $targetContainer = $(this);
|
||||
|
||||
const sourceId = $sourceContainer.data('id');
|
||||
const targetId = $targetContainer.data('id');
|
||||
|
||||
// Ne pas dupliquer l'appel si c’est un vrai "receive"
|
||||
if (ui.sender) return;
|
||||
|
||||
sendUpdate(sourceId, targetId, $sourceContainer, $targetContainer, $movedItem, originalIndex);
|
||||
},
|
||||
|
||||
receive: function (event, ui) {
|
||||
const $targetContainer = $(this);
|
||||
const sourceId = $sourceContainer.data('id');
|
||||
const targetId = $targetContainer.data('id');
|
||||
|
||||
sendUpdate(sourceId, targetId, $sourceContainer, $targetContainer, $movedItem, originalIndex);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
function sendUpdate(sourceId, targetId, $sourceContainer, $targetContainer, $movedItem, originalIndex) {
|
||||
@ -455,17 +444,14 @@
|
||||
url: url,
|
||||
method: 'POST',
|
||||
data: {
|
||||
source: sourceId,
|
||||
target: targetId,
|
||||
sourceIssues: sourceIssues,
|
||||
targetIssues: targetIssues
|
||||
},
|
||||
success: function (response) {
|
||||
console.log('Déplacement réussi', response);
|
||||
},
|
||||
error: function (xhr) {
|
||||
console.error('Erreur AJAX', xhr);
|
||||
|
||||
console.log(xhr);
|
||||
// Annuler le déplacement
|
||||
if ($movedItem && $sourceContainer) {
|
||||
const items = $sourceContainer.children();
|
||||
@ -475,6 +461,14 @@
|
||||
$movedItem.insertBefore(items.eq(originalIndex));
|
||||
}
|
||||
}
|
||||
|
||||
const message = xhr.responseJSON?.message || 'Une erreur est survenue lors de la requête.';
|
||||
$('#ajaxErrorMessage').text(message);
|
||||
|
||||
const toast = new bootstrap.Toast(document.getElementById('ajaxErrorToast'), {
|
||||
delay: 5000
|
||||
});
|
||||
toast.show();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
Reference in New Issue
Block a user