feat(edl): Ajoute la gestion de l'état des lieux de retour et met à jour les URLs de l'environnement.

This commit is contained in:
Serreau Jovann
2026-02-11 15:38:55 +01:00
parent 03a08f0a14
commit 2e7a006fed
4 changed files with 134 additions and 81 deletions

6
.env
View File

@@ -83,9 +83,9 @@ STRIPE_PK=pk_test_51SUA22173W4aeFB1nO6oFfDZ12HOTffDKtCshhZ8rkUg6kUO2ZaQC0tK72rhE
STRIPE_SK=sk_test_51SUA22173W4aeFB16EB2LxGI0hNvNJzFshDI98zRImWBIhSfzqOGAz5TlPxSpUWbj3x4COm6kmSsaal9FpQR1A7M0022DvjbbR
STRIPE_WEBHOOKS_SECRET=
SIGN_URL=https://eefa-82-67-166-187.ngrok-free.app
STRIPE_BASEURL=https://eefa-82-67-166-187.ngrok-free.app
CONTRAT_BASEURL=https://eefa-82-67-166-187.ngrok-free.app
SIGN_URL=https://c55e-82-67-166-187.ngrok-free.app
STRIPE_BASEURL=https://c55e-82-67-166-187.ngrok-free.app
CONTRAT_BASEURL=https://c55e-82-67-166-187.ngrok-free.app
MINIO_S3_URL=
MINIO_S3_CLIENT_ID=

View File

@@ -45,7 +45,7 @@ class EtlController extends AbstractController
}
$missions = [];
$states = ['ready', 'pending','progress'];
$states = ['ready', 'pending', 'progress'];
$qb = $contratsRepository->createQueryBuilder('c');
$qb->select('count(c.id)');
@@ -353,10 +353,10 @@ class EtlController extends AbstractController
}
$method = $request->request->get('method');
$amount = (float) $request->request->get('amount'); // Optional, or calculate solde
$amount = (float)$request->request->get('amount'); // Optional, or calculate solde
if ($amount <= 0) {
// Calculate Solde if not provided
// Calculate Solde if not provided
$totalHt = 0;
$days = ($contrat->getDateAt() && $contrat->getEndAt()) ? ($contrat->getDateAt()->diff($contrat->getEndAt())->days + 1) : 1;
foreach ($contrat->getContratsLines() as $line) {
@@ -430,6 +430,26 @@ class EtlController extends AbstractController
]);
}
#[Route('/etl/mission/{id}/edl/return', name: 'etl_mission_edl_return', methods: ['GET'])]
public function eltEdlReturn(Contrats $contrat): Response
{
$user = $this->getUser();
if (!$user) {
return $this->redirectToRoute('etl_login');
}
// Security check
if ($user instanceof Prestaire && $contrat->getPrestataire() !== $user) {
throw $this->createAccessDeniedException('Vous n\'avez pas accès à cette mission.');
}
return $this->render('etl/edl.twig', [
'mission' => $contrat,
'etatLieux' => $contrat->getEtatLieux(),
'isReturnEdl' => true // Flag to indicate it\'s a return EDL
]);
}
#[Route('/etl/mission/{id}/edl/comment', name: 'etl_edl_add_comment', methods: ['POST'])]
public function eltEdlAddComment(Contrats $contrat, Request $request, EntityManagerInterface $em): Response
{
@@ -462,7 +482,7 @@ class EtlController extends AbstractController
$etatLieux = $contrat->getEtatLieux();
if (!$etatLieux) {
return $this->redirectToRoute('etl_mission_edl', ['id' => $contrat->getId()]);
return $this->redirectToRoute('etl_mission_edl', ['id' => $contrat->getId()]);
}
$data = $request->request->all('points');
@@ -501,98 +521,92 @@ class EtlController extends AbstractController
}
#[Route('/etl/mission/{id}/edl/file', name: 'etl_edl_add_file', methods: ['POST'])]
public function eltEdlAddFile(Contrats $contrat, Request $request, EntityManagerInterface $em): Response
{
$user = $this->getUser();
if (!$user) {
return $this->redirectToRoute('etl_login');
public function eltEdlAddFile(Contrats $contrat, Request $request, EntityManagerInterface $em): Response
{
$user = $this->getUser();
if (!$user) {
return $this->redirectToRoute('etl_login');
}
$photos = $request->files->get('photos');
$videos = $request->files->get('videos');
$etatLieux = $contrat->getEtatLieux();
$hasFiles = false;
if ($photos) {
if (!is_array($photos)) $photos = [$photos];
foreach ($photos as $uploadedFile) {
if ($uploadedFile instanceof UploadedFile) {
$this->compressImage($uploadedFile);
$file = new EtatLieuxFile();
$file->setFile($uploadedFile);
$file->setType('photo');
$file->setEtatLieux($etatLieux);
$em->persist($file);
$hasFiles = true;
}
$photos = $request->files->get('photos');
$videos = $request->files->get('videos');
$etatLieux = $contrat->getEtatLieux();
$hasFiles = false;
if ($photos) {
if (!is_array($photos)) $photos = [$photos];
foreach ($photos as $uploadedFile) {
if ($uploadedFile instanceof UploadedFile) {
$this->compressImage($uploadedFile);
$file = new EtatLieuxFile();
$file->setFile($uploadedFile);
$file->setType('photo');
$file->setEtatLieux($etatLieux);
$em->persist($file);
$hasFiles = true;
}
}
}
if ($videos) {
if (!is_array($videos)) $videos = [$videos];
foreach ($videos as $uploadedFile) {
if ($uploadedFile instanceof UploadedFile) {
$this->compressVideo($uploadedFile);
$file = new EtatLieuxFile();
$file->setFile($uploadedFile);
$file->setType('video');
$file->setEtatLieux($etatLieux);
$em->persist($file);
$hasFiles = true;
}
}
}
if ($hasFiles) {
$em->flush();
$this->addFlash('success', 'Fichiers ajoutés.');
}
}
if ($videos) {
if (!is_array($videos)) $videos = [$videos];
foreach ($videos as $uploadedFile) {
if ($uploadedFile instanceof UploadedFile) {
$this->compressVideo($uploadedFile);
$file = new EtatLieuxFile();
$file->setFile($uploadedFile);
$file->setType('video');
$file->setEtatLieux($etatLieux);
$em->persist($file);
$hasFiles = true;
}
}
}
if ($hasFiles) {
$em->flush();
$this->addFlash('success', 'Fichiers ajoutés.');
}
return $this->redirectToRoute('etl_mission_edl', ['id' => $contrat->getId()]);
return $this->redirectToRoute('etl_mission_edl', ['id' => $contrat->getId()]);
}
}
#[Route('/etl/mission/{id}/edl/file/{fileId}/delete', name: 'etl_edl_delete_file', methods: ['POST'])]
public function eltEdlDeleteFile(Contrats $contrat, int $fileId, EntityManagerInterface $em): Response
#[Route('/etl/mission/{id}/edl/file/{fileId}/delete', name: 'etl_edl_delete_file', methods: ['POST'])]
{
public function eltEdlDeleteFile(Contrats $contrat, int $fileId, EntityManagerInterface $em): Response
$user = $this->getUser();
{
if (!$user) {
$user = $this->getUser();
return $this->redirectToRoute('etl_login');
if (!$user) {
return $this->redirectToRoute('etl_login');
}
}
$etatLieux = $contrat->getEtatLieux();
$etatLieux = $contrat->getEtatLieux();
$file = $em->getRepository(EtatLieuxFile::class)->find($fileId);
$file = $em->getRepository(EtatLieuxFile::class)->find($fileId);
if ($file && $file->getEtatLieux() === $etatLieux) {
if ($file && $file->getEtatLieux() === $etatLieux) {
$em->remove($file);
$em->remove($file);
$em->flush();
$em->flush();
$this->addFlash('success', 'Fichier supprimé.');
$this->addFlash('success', 'Fichier supprimé.');
}
}
return $this->redirectToRoute('etl_mission_edl', ['id' => $contrat->getId()]);
return $this->redirectToRoute('etl_mission_edl', ['id' => $contrat->getId()]);
}
}
#[Route('/etl/mission/{id}/edl/finish', name: 'etl_edl_finish', methods: ['POST'])]
#[Route('/etl/mission/{id}/edl/finish', name: 'etl_edl_finish', methods: ['POST'])]
public function eltEdlFinish(Contrats $contrat, EntityManagerInterface $em, KernelInterface $kernel, SignatureClient $signatureClient): Response
{
$user = $this->getUser();
@@ -611,6 +625,28 @@ class EtlController extends AbstractController
return $this->redirectToRoute('etl_contrat_view', ['id' => $contrat->getId()]);
}
#[Route('/etl/mission/{id}/edl/return/start', name: 'etl_mission_edl_return_start', methods: ['POST'])]
public function eltMissionEdlReturnStart(Contrats $contrat, EntityManagerInterface $em): Response
{
$user = $this->getUser();
if (!$user) {
return $this->redirectToRoute('etl_login');
}
if ($user instanceof Prestaire && $contrat->getPrestataire() !== $user) {
throw $this->createAccessDeniedException("Vous n'avez pas accès à cette mission.");
}
$etatLieux = $contrat->getEtatLieux();
if ($etatLieux) {
$etatLieux->setStatus('return_edl_progress');
$em->flush();
$this->addFlash('success', 'État des lieux de retour commencé.');
}
return $this->redirectToRoute('etl_mission_edl_return', ['id' => $contrat->getId()]);
}
#[Route('/etl/mission/{id}/edl/regenerate-view', name: 'etl_edl_regenerate_view', methods: ['GET'])]
public function eltEdlRegenerateAndView(Contrats $contrat, EntityManagerInterface $em, KernelInterface $kernel, SignatureClient $signatureClient, UploaderHelper $uploaderHelper): Response
{
@@ -672,14 +708,16 @@ class EtlController extends AbstractController
try {
$sub = $signatureClient->getSubmiter($etatLieux->getSignIdDelivery());
if ($sub && ($sub['status'] ?? '') === 'completed') $providerSigned = true;
} catch (\Exception $e) {}
} catch (\Exception $e) {
}
}
if ($etatLieux->getSignIdCustomer()) {
try {
$sub = $signatureClient->getSubmiter($etatLieux->getSignIdCustomer());
if ($sub && ($sub['status'] ?? '') === 'completed') $customerSigned = true;
} catch (\Exception $e) {}
} catch (\Exception $e) {
}
}
return $this->render('etl/signed_entry_state.twig', [
@@ -802,10 +840,11 @@ class EtlController extends AbstractController
#[Route('/etl/account', name: 'etl_account', methods: ['GET', 'POST'])]
public function eltAccount(
Request $request,
Request $request,
UserPasswordHasherInterface $passwordHasher,
EntityManagerInterface $entityManager
): Response {
EntityManagerInterface $entityManager
): Response
{
$user = $this->getUser();
if (!$user) {
return $this->redirectToRoute('etl_login');

View File

@@ -67,6 +67,15 @@
</button>
</form>
{% endif %}
{% if providerSigned and customerSigned and etatLieux.status == 'edl_validated' %}
<form action="{{ path('etl_mission_edl_return_start', {id: mission.id}) }}" method="post">
<button type="submit" class="w-full py-4 bg-orange-600 hover:bg-orange-700 text-white rounded-2xl font-black uppercase text-sm tracking-widest shadow-lg shadow-orange-600/30 transition-all active:scale-95 flex items-center justify-center gap-3 mt-4">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-3 7h3m-3 4h3m-6-4h.01M9 16h.01" /></svg>
Faire état des lieux retour
</button>
</form>
{% endif %}
</div>
<div class="text-center">

View File

@@ -40,7 +40,7 @@
{% set is_chorus = (mission.devis and 'Chorus' in mission.devis.paymentMethod) %}
{% if not is_chorus %}
{# CAUTION #}
<div class="bg-white rounded-[2rem] p-6 border border-slate-100 shadow-sm mb-4">
<h3 class="text-xs font-black text-slate-400 uppercase tracking-widest mb-4">Caution</h3>
@@ -94,7 +94,7 @@
<p class="text-xs font-black uppercase tracking-widest">Solde Réglé</p>
</div>
{% endif %}
{% elseif mission.etatLieux.status == 'edl_progress' %}
<a href="{{ path('etl_mission_edl', {id: mission.id}) }}" class="w-full py-4 bg-indigo-600 hover:bg-indigo-500 text-white rounded-2xl font-black uppercase text-sm tracking-widest shadow-lg shadow-indigo-600/30 transition-all active:scale-95 flex items-center justify-center gap-3 mb-6">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" /><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" /></svg>
@@ -116,6 +116,11 @@
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" /><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" /></svg>
Reprendre l'état des lieux
</a>
{% elseif mission.etatLieux.status == 'edl_validated' %}
<a href="{{ path('etl_mission_edl_return', {id: mission.id}) }}" class="w-full py-4 bg-indigo-600 hover:bg-indigo-500 text-white rounded-2xl font-black uppercase text-sm tracking-widest shadow-lg shadow-indigo-600/30 transition-all active:scale-95 flex items-center justify-center gap-3 mb-6">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" /><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" /></svg>
Commenter l'état des lieux de retour
</a>
{% endif %}
{# DATES #}