✨ feat(etl/mission): Implémente la gestion des missions avec dashboard, liste et démarrage
This commit is contained in:
@@ -1,3 +1,63 @@
|
|||||||
import './etl.scss';
|
import './etl.scss';
|
||||||
|
|
||||||
console.log('ETL Mobile Loaded');
|
console.log('ETL Mobile Loaded');
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
const modal = document.getElementById('lightbox-modal');
|
||||||
|
if (!modal) return;
|
||||||
|
|
||||||
|
const img = document.getElementById('lightbox-img');
|
||||||
|
const video = document.getElementById('lightbox-video');
|
||||||
|
const closeBtn = document.getElementById('lightbox-close');
|
||||||
|
const triggers = document.querySelectorAll('.lightbox-trigger');
|
||||||
|
|
||||||
|
function openModal(src, type) {
|
||||||
|
if (type === 'photo') {
|
||||||
|
img.src = src;
|
||||||
|
img.classList.remove('hidden');
|
||||||
|
video.classList.add('hidden');
|
||||||
|
video.pause();
|
||||||
|
} else {
|
||||||
|
video.src = src;
|
||||||
|
video.classList.remove('hidden');
|
||||||
|
img.classList.add('hidden');
|
||||||
|
video.play(); // Auto-play video
|
||||||
|
}
|
||||||
|
|
||||||
|
modal.classList.remove('hidden');
|
||||||
|
// Small timeout to allow transition
|
||||||
|
setTimeout(() => {
|
||||||
|
modal.classList.remove('opacity-0', 'pointer-events-none');
|
||||||
|
}, 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeModal() {
|
||||||
|
modal.classList.add('opacity-0', 'pointer-events-none');
|
||||||
|
setTimeout(() => {
|
||||||
|
modal.classList.add('hidden');
|
||||||
|
video.pause();
|
||||||
|
video.src = ""; // Stop buffering
|
||||||
|
img.src = "";
|
||||||
|
}, 300);
|
||||||
|
}
|
||||||
|
|
||||||
|
triggers.forEach(trigger => {
|
||||||
|
trigger.addEventListener('click', (e) => {
|
||||||
|
e.preventDefault(); // Prevent default link behavior if any
|
||||||
|
const src = trigger.dataset.src;
|
||||||
|
const type = trigger.dataset.type;
|
||||||
|
if (src && type) {
|
||||||
|
openModal(src, type);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
closeBtn.addEventListener('click', closeModal);
|
||||||
|
|
||||||
|
// Close on background click
|
||||||
|
modal.addEventListener('click', (e) => {
|
||||||
|
if (e.target === modal) {
|
||||||
|
closeModal();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -69,3 +69,31 @@ vich_uploader:
|
|||||||
uri_prefix: /pdf/facture
|
uri_prefix: /pdf/facture
|
||||||
upload_destination: '%kernel.project_dir%/public/pdf/facture'
|
upload_destination: '%kernel.project_dir%/public/pdf/facture'
|
||||||
namer: Vich\UploaderBundle\Naming\SmartUniqueNamer
|
namer: Vich\UploaderBundle\Naming\SmartUniqueNamer
|
||||||
|
etat_lieux_media:
|
||||||
|
uri_prefix: /media/etat_lieux/media
|
||||||
|
upload_destination: '%kernel.project_dir%/public/media/etat_lieux/media'
|
||||||
|
namer: Vich\UploaderBundle\Naming\SmartUniqueNamer
|
||||||
|
etat_lieux_unsign:
|
||||||
|
uri_prefix: /media/etat_lieux/unsign
|
||||||
|
upload_destination: '%kernel.project_dir%/public/media/etat_lieux/unsign'
|
||||||
|
namer: Vich\UploaderBundle\Naming\SmartUniqueNamer
|
||||||
|
etat_lieux_sign:
|
||||||
|
uri_prefix: /media/etat_lieux/sign
|
||||||
|
upload_destination: '%kernel.project_dir%/public/media/etat_lieux/sign'
|
||||||
|
namer: Vich\UploaderBundle\Naming\SmartUniqueNamer
|
||||||
|
etat_lieux_audit:
|
||||||
|
uri_prefix: /media/etat_lieux/audit
|
||||||
|
upload_destination: '%kernel.project_dir%/public/media/etat_lieux/audit'
|
||||||
|
namer: Vich\UploaderBundle\Naming\SmartUniqueNamer
|
||||||
|
etat_lieux_unsign_return:
|
||||||
|
uri_prefix: /media/etat_lieux/unsign_return
|
||||||
|
upload_destination: '%kernel.project_dir%/public/media/etat_lieux/unsign_return'
|
||||||
|
namer: Vich\UploaderBundle\Naming\SmartUniqueNamer
|
||||||
|
etat_lieux_sign_return:
|
||||||
|
uri_prefix: /media/etat_lieux/sign_return
|
||||||
|
upload_destination: '%kernel.project_dir%/public/media/etat_lieux/sign_return'
|
||||||
|
namer: Vich\UploaderBundle\Naming\SmartUniqueNamer
|
||||||
|
etat_lieux_audit_return:
|
||||||
|
uri_prefix: /media/etat_lieux/audit_return
|
||||||
|
upload_destination: '%kernel.project_dir%/public/media/etat_lieux/audit_return'
|
||||||
|
namer: Vich\UploaderBundle\Naming\SmartUniqueNamer
|
||||||
|
|||||||
35
migrations/Version20260206220000.php
Normal file
35
migrations/Version20260206220000.php
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
<?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 Version20260206220000 extends AbstractMigration
|
||||||
|
{
|
||||||
|
public function getDescription(): string
|
||||||
|
{
|
||||||
|
return 'Create etat_lieux_file table';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function up(Schema $schema): void
|
||||||
|
{
|
||||||
|
// this up() migration is auto-generated, please modify it to your needs
|
||||||
|
$this->addSql("CREATE TABLE etat_lieux_file (id SERIAL NOT NULL, etat_lieux_id INT NOT NULL, file_name VARCHAR(255) DEFAULT NULL, file_size INT DEFAULT NULL, mime_type VARCHAR(255) DEFAULT NULL, type VARCHAR(50) NOT NULL, created_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, PRIMARY KEY(id))");
|
||||||
|
$this->addSql("COMMENT ON COLUMN etat_lieux_file.created_at IS '(DC2Type:datetime_immutable)'");
|
||||||
|
$this->addSql("CREATE INDEX IDX_5F7E1C87D6F39243 ON etat_lieux_file (etat_lieux_id)");
|
||||||
|
$this->addSql('ALTER TABLE etat_lieux_file ADD CONSTRAINT FK_5F7E1C87D6F39243 FOREIGN KEY (etat_lieux_id) REFERENCES etat_lieux (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(Schema $schema): void
|
||||||
|
{
|
||||||
|
// this down() migration is auto-generated, please modify it to your needs
|
||||||
|
$this->addSql('ALTER TABLE etat_lieux_file DROP FOREIGN KEY FK_5F7E1C87D6F39243');
|
||||||
|
$this->addSql('DROP TABLE etat_lieux_file');
|
||||||
|
}
|
||||||
|
}
|
||||||
56
migrations/Version20260206230000.php
Normal file
56
migrations/Version20260206230000.php
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
<?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 Version20260206230000 extends AbstractMigration
|
||||||
|
{
|
||||||
|
public function getDescription(): string
|
||||||
|
{
|
||||||
|
return 'Create etat_lieux_comment table and add fields to etat_lieux';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function up(Schema $schema): void
|
||||||
|
{
|
||||||
|
// this up() migration is auto-generated, please modify it to your needs
|
||||||
|
$this->addSql("CREATE TABLE etat_lieux_comment (id SERIAL NOT NULL, etat_lieux_id INT NOT NULL, content TEXT NOT NULL, created_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, PRIMARY KEY(id))");
|
||||||
|
$this->addSql("CREATE INDEX IDX_ETAT_LIEUX_COMMENT ON etat_lieux_comment (etat_lieux_id)");
|
||||||
|
$this->addSql("COMMENT ON COLUMN etat_lieux_comment.created_at IS '(DC2Type:datetime_immutable)'");
|
||||||
|
$this->addSql('ALTER TABLE etat_lieux_comment ADD CONSTRAINT FK_ETAT_LIEUX_COMMENT FOREIGN KEY (etat_lieux_id) REFERENCES etat_lieux (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
|
||||||
|
|
||||||
|
$this->addSql('ALTER TABLE etat_lieux ADD sign_id_delivery VARCHAR(255) DEFAULT NULL');
|
||||||
|
$this->addSql('ALTER TABLE etat_lieux ADD sign_id_customer VARCHAR(255) DEFAULT NULL');
|
||||||
|
$this->addSql('ALTER TABLE etat_lieux ADD etat_lieux_unsign_file_name VARCHAR(255) DEFAULT NULL');
|
||||||
|
$this->addSql('ALTER TABLE etat_lieux ADD etat_lieux_unsign_file_size INT DEFAULT NULL');
|
||||||
|
$this->addSql('ALTER TABLE etat_lieux ADD etat_lieux_sign_file_name VARCHAR(255) DEFAULT NULL');
|
||||||
|
$this->addSql('ALTER TABLE etat_lieux ADD etat_lieux_sign_file_size INT DEFAULT NULL');
|
||||||
|
$this->addSql('ALTER TABLE etat_lieux ADD etat_lieux_audit_file_name VARCHAR(255) DEFAULT NULL');
|
||||||
|
$this->addSql('ALTER TABLE etat_lieux ADD etat_lieux_audit_file_size INT DEFAULT NULL');
|
||||||
|
$this->addSql('ALTER TABLE etat_lieux ADD updated_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL');
|
||||||
|
$this->addSql("COMMENT ON COLUMN etat_lieux.updated_at IS '(DC2Type:datetime_immutable)'");
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(Schema $schema): void
|
||||||
|
{
|
||||||
|
// this down() migration is auto-generated, please modify it to your needs
|
||||||
|
$this->addSql('ALTER TABLE etat_lieux_comment DROP FOREIGN KEY FK_ETAT_LIEUX_COMMENT');
|
||||||
|
$this->addSql('DROP TABLE etat_lieux_comment');
|
||||||
|
|
||||||
|
$this->addSql('ALTER TABLE etat_lieux DROP sign_id_delivery');
|
||||||
|
$this->addSql('ALTER TABLE etat_lieux DROP sign_id_customer');
|
||||||
|
$this->addSql('ALTER TABLE etat_lieux DROP etat_lieux_unsign_file_name');
|
||||||
|
$this->addSql('ALTER TABLE etat_lieux DROP etat_lieux_unsign_file_size');
|
||||||
|
$this->addSql('ALTER TABLE etat_lieux DROP etat_lieux_sign_file_name');
|
||||||
|
$this->addSql('ALTER TABLE etat_lieux DROP etat_lieux_sign_file_size');
|
||||||
|
$this->addSql('ALTER TABLE etat_lieux DROP etat_lieux_audit_file_name');
|
||||||
|
$this->addSql('ALTER TABLE etat_lieux DROP etat_lieux_audit_file_size');
|
||||||
|
$this->addSql('ALTER TABLE etat_lieux DROP updated_at');
|
||||||
|
}
|
||||||
|
}
|
||||||
45
migrations/Version20260206233000.php
Normal file
45
migrations/Version20260206233000.php
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
<?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 Version20260206233000 extends AbstractMigration
|
||||||
|
{
|
||||||
|
public function getDescription(): string
|
||||||
|
{
|
||||||
|
return 'Add return fields to etat_lieux';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function up(Schema $schema): void
|
||||||
|
{
|
||||||
|
// this up() migration is auto-generated, please modify it to your needs
|
||||||
|
$this->addSql('ALTER TABLE etat_lieux ADD sign_id_return VARCHAR(255) DEFAULT NULL');
|
||||||
|
$this->addSql('ALTER TABLE etat_lieux ADD sign_id_customer_return VARCHAR(255) DEFAULT NULL');
|
||||||
|
$this->addSql('ALTER TABLE etat_lieux ADD etat_lieux_unsign_return_file_name VARCHAR(255) DEFAULT NULL');
|
||||||
|
$this->addSql('ALTER TABLE etat_lieux ADD etat_lieux_unsign_return_file_size INT DEFAULT NULL');
|
||||||
|
$this->addSql('ALTER TABLE etat_lieux ADD etat_lieux_sign_return_file_name VARCHAR(255) DEFAULT NULL');
|
||||||
|
$this->addSql('ALTER TABLE etat_lieux ADD etat_lieux_sign_return_file_size INT DEFAULT NULL');
|
||||||
|
$this->addSql('ALTER TABLE etat_lieux ADD etat_lieux_audit_return_file_name VARCHAR(255) DEFAULT NULL');
|
||||||
|
$this->addSql('ALTER TABLE etat_lieux ADD etat_lieux_audit_return_file_size INT DEFAULT NULL');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(Schema $schema): void
|
||||||
|
{
|
||||||
|
// this down() migration is auto-generated, please modify it to your needs
|
||||||
|
$this->addSql('ALTER TABLE etat_lieux DROP sign_id_return');
|
||||||
|
$this->addSql('ALTER TABLE etat_lieux DROP sign_id_customer_return');
|
||||||
|
$this->addSql('ALTER TABLE etat_lieux DROP etat_lieux_unsign_return_file_name');
|
||||||
|
$this->addSql('ALTER TABLE etat_lieux DROP etat_lieux_unsign_return_file_size');
|
||||||
|
$this->addSql('ALTER TABLE etat_lieux DROP etat_lieux_sign_return_file_name');
|
||||||
|
$this->addSql('ALTER TABLE etat_lieux DROP etat_lieux_sign_return_file_size');
|
||||||
|
$this->addSql('ALTER TABLE etat_lieux DROP etat_lieux_audit_return_file_name');
|
||||||
|
$this->addSql('ALTER TABLE etat_lieux DROP etat_lieux_audit_return_file_size');
|
||||||
|
}
|
||||||
|
}
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 180 KiB |
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 295 KiB |
@@ -6,17 +6,22 @@ use App\Entity\Account;
|
|||||||
use App\Entity\Contrats;
|
use App\Entity\Contrats;
|
||||||
use App\Entity\ContratsPayments;
|
use App\Entity\ContratsPayments;
|
||||||
use App\Entity\EtatLieux;
|
use App\Entity\EtatLieux;
|
||||||
|
use App\Entity\EtatLieuxComment;
|
||||||
|
use App\Entity\EtatLieuxFile;
|
||||||
use App\Entity\Prestaire;
|
use App\Entity\Prestaire;
|
||||||
use App\Form\PrestairePasswordType;
|
use App\Form\PrestairePasswordType;
|
||||||
use App\Repository\ContratsRepository;
|
use App\Repository\ContratsRepository;
|
||||||
use App\Service\Mailer\Mailer;
|
use App\Service\Mailer\Mailer;
|
||||||
|
use App\Service\Stripe\Client as StripeClient;
|
||||||
use Doctrine\ORM\EntityManagerInterface;
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
|
use Symfony\Component\HttpFoundation\File\UploadedFile;
|
||||||
use Symfony\Component\HttpFoundation\RedirectResponse;
|
use Symfony\Component\HttpFoundation\RedirectResponse;
|
||||||
use Symfony\Component\HttpFoundation\Request;
|
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\Attribute\Route;
|
use Symfony\Component\Routing\Attribute\Route;
|
||||||
|
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
|
||||||
use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;
|
use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;
|
||||||
use KnpU\OAuth2ClientBundle\Client\ClientRegistry;
|
use KnpU\OAuth2ClientBundle\Client\ClientRegistry;
|
||||||
|
|
||||||
@@ -97,8 +102,42 @@ class EtlController extends AbstractController
|
|||||||
throw $this->createAccessDeniedException('Vous n\'avez pas accès à cette mission.');
|
throw $this->createAccessDeniedException('Vous n\'avez pas accès à cette mission.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Calculate Totals
|
||||||
|
$totalHt = 0;
|
||||||
|
$totalCaution = 0;
|
||||||
|
$days = ($contrat->getDateAt() && $contrat->getEndAt()) ? ($contrat->getDateAt()->diff($contrat->getEndAt())->days + 1) : 1;
|
||||||
|
|
||||||
|
foreach ($contrat->getContratsLines() as $line) {
|
||||||
|
$totalHt += $line->getPrice1DayHt() + ($line->getPriceSupDayHt() * max(0, $days - 1));
|
||||||
|
$totalCaution += $line->getCaution();
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($contrat->getContratsOptions() as $option) {
|
||||||
|
$totalHt += $option->getPrice();
|
||||||
|
}
|
||||||
|
|
||||||
|
$dejaPaye = 0;
|
||||||
|
foreach ($contrat->getContratsPayments() as $p) {
|
||||||
|
if ($p->getState() === 'complete' && $p->getType() !== 'caution') {
|
||||||
|
$dejaPaye += $p->getAmount();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$solde = $totalHt - $dejaPaye;
|
||||||
|
|
||||||
|
// Check if Caution is already paid
|
||||||
|
$cautionPaid = false;
|
||||||
|
foreach ($contrat->getContratsPayments() as $p) {
|
||||||
|
if ($p->getType() === 'caution' && $p->getState() === 'complete') {
|
||||||
|
$cautionPaid = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return $this->render('etl/view.twig', [
|
return $this->render('etl/view.twig', [
|
||||||
'mission' => $contrat
|
'mission' => $contrat,
|
||||||
|
'totalCaution' => $totalCaution,
|
||||||
|
'solde' => $solde,
|
||||||
|
'cautionPaid' => $cautionPaid
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -144,6 +183,376 @@ class EtlController extends AbstractController
|
|||||||
return $this->redirectToRoute('etl_contrat_view', ['id' => $contrat->getId()]);
|
return $this->redirectToRoute('etl_contrat_view', ['id' => $contrat->getId()]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[Route('/etl/mission/{id}/finish', name: 'etl_mission_finish', methods: ['POST'])]
|
||||||
|
public function eltMissionFinish(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('delivery_done');
|
||||||
|
$em->flush();
|
||||||
|
$this->addFlash('success', 'Livraison terminée et confirmée.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->redirectToRoute('etl_contrat_view', ['id' => $contrat->getId()]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Route('/etl/mission/{id}/caution', name: 'etl_mission_caution', methods: ['POST'])]
|
||||||
|
public function eltMissionCaution(Contrats $contrat, EntityManagerInterface $em): Response
|
||||||
|
{
|
||||||
|
$user = $this->getUser();
|
||||||
|
if (!$user) {
|
||||||
|
return $this->redirectToRoute('etl_login');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check already paid
|
||||||
|
foreach ($contrat->getContratsPayments() as $p) {
|
||||||
|
if ($p->getType() === 'caution' && $p->getState() === 'complete') {
|
||||||
|
$this->addFlash('warning', 'Caution déjà validée.');
|
||||||
|
return $this->redirectToRoute('etl_contrat_view', ['id' => $contrat->getId()]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate Caution Amount
|
||||||
|
$totalCaution = 0;
|
||||||
|
$days = ($contrat->getDateAt() && $contrat->getEndAt()) ? ($contrat->getDateAt()->diff($contrat->getEndAt())->days + 1) : 1;
|
||||||
|
foreach ($contrat->getContratsLines() as $line) {
|
||||||
|
$totalCaution += $line->getCaution();
|
||||||
|
}
|
||||||
|
|
||||||
|
$payment = new ContratsPayments();
|
||||||
|
$payment->setContrat($contrat)
|
||||||
|
->setType('caution')
|
||||||
|
->setAmount($totalCaution)
|
||||||
|
->setState('complete')
|
||||||
|
->setPaymentAt(new \DateTimeImmutable())
|
||||||
|
->setValidateAt(new \DateTimeImmutable())
|
||||||
|
->setCard(['type' => 'manuel', 'method' => 'Prestataire'])
|
||||||
|
->setPaymentId("CAUTION-" . uniqid());
|
||||||
|
|
||||||
|
$em->persist($payment);
|
||||||
|
$em->flush();
|
||||||
|
|
||||||
|
$this->addFlash('success', 'Caution validée.');
|
||||||
|
return $this->redirectToRoute('etl_contrat_view', ['id' => $contrat->getId()]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Route('/etl/mission/{id}/confirme', name: 'elt_mission_confirme', methods: ['POST'])]
|
||||||
|
public function eltMissionConfirme(Contrats $contrat, StripeClient $stripeClient, EntityManagerInterface $em): Response
|
||||||
|
{
|
||||||
|
$user = $this->getUser();
|
||||||
|
if (!$user) {
|
||||||
|
return $this->redirectToRoute('etl_login');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate Solde
|
||||||
|
$totalHt = 0;
|
||||||
|
$days = ($contrat->getDateAt() && $contrat->getEndAt()) ? ($contrat->getDateAt()->diff($contrat->getEndAt())->days + 1) : 1;
|
||||||
|
foreach ($contrat->getContratsLines() as $line) {
|
||||||
|
$totalHt += $line->getPrice1DayHt() + ($line->getPriceSupDayHt() * max(0, $days - 1));
|
||||||
|
}
|
||||||
|
foreach ($contrat->getContratsOptions() as $option) {
|
||||||
|
$totalHt += $option->getPrice();
|
||||||
|
}
|
||||||
|
$dejaPaye = 0;
|
||||||
|
foreach ($contrat->getContratsPayments() as $p) {
|
||||||
|
if ($p->getState() === 'complete' && $p->getType() !== 'caution') {
|
||||||
|
$dejaPaye += $p->getAmount();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$solde = $totalHt - $dejaPaye;
|
||||||
|
|
||||||
|
if ($solde <= 0) {
|
||||||
|
$this->addFlash('info', 'Le solde est déjà réglé.');
|
||||||
|
return $this->redirectToRoute('etl_contrat_view', ['id' => $contrat->getId()]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create Payment Intent/Session using StripeClient (Using createPaymentSolde logic but manual or new method)
|
||||||
|
// Since createPaymentSolde redirects to specific success URL, I might need to adjust or create a generic one.
|
||||||
|
// Assuming createPaymentSolde can be reused but we might want custom success URL?
|
||||||
|
// Actually, createPaymentSolde sets success_url to /contrat/payment/success/... which is the customer success page.
|
||||||
|
// That might be fine, or we want redirection back to ETL?
|
||||||
|
// If we want redirection back to ETL, we need a new method in StripeClient or pass options.
|
||||||
|
// For now, I will use getNativeClient to build session manually to control success_url.
|
||||||
|
|
||||||
|
try {
|
||||||
|
$stripe = $stripeClient->getNativeClient();
|
||||||
|
$customer = $contrat->getCustomer();
|
||||||
|
|
||||||
|
// Ensure Stripe Customer exists (Controller helper or reuse existing logic if possible)
|
||||||
|
if (!$customer->getCustomerId()) {
|
||||||
|
$stripeClient->createCustomer($customer);
|
||||||
|
$em->flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
$session = $stripe->checkout->sessions->create([
|
||||||
|
'customer' => $customer->getCustomerId(),
|
||||||
|
'line_items' => [[
|
||||||
|
'price_data' => [
|
||||||
|
'currency' => 'eur',
|
||||||
|
'product_data' => [
|
||||||
|
'name' => 'Solde Réservation #' . $contrat->getNumReservation(),
|
||||||
|
'description' => 'Règlement du solde via interface prestataire',
|
||||||
|
],
|
||||||
|
'unit_amount' => (int)round($solde * 100),
|
||||||
|
],
|
||||||
|
'quantity' => 1,
|
||||||
|
]],
|
||||||
|
'mode' => 'payment',
|
||||||
|
'success_url' => $this->generateUrl('etl_contrat_view', ['id' => $contrat->getId()], UrlGeneratorInterface::ABSOLUTE_URL), // Back to ETL view
|
||||||
|
'cancel_url' => $this->generateUrl('etl_contrat_view', ['id' => $contrat->getId()], UrlGeneratorInterface::ABSOLUTE_URL),
|
||||||
|
'metadata' => [
|
||||||
|
'contrat_id' => $contrat->getId(),
|
||||||
|
'type' => 'etl_payment' // Special type to trigger specific webhook logic
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Persist pending payment
|
||||||
|
$payment = new ContratsPayments();
|
||||||
|
$payment->setContrat($contrat)
|
||||||
|
->setType('etl_payment') // Or 'solde' if we want it standard
|
||||||
|
->setAmount($solde)
|
||||||
|
->setState('created')
|
||||||
|
->setPaymentAt(new \DateTimeImmutable())
|
||||||
|
->setPaymentId($session->id);
|
||||||
|
|
||||||
|
$em->persist($payment);
|
||||||
|
$em->flush();
|
||||||
|
|
||||||
|
return new RedirectResponse($session->url);
|
||||||
|
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$this->addFlash('error', 'Erreur Stripe: ' . $e->getMessage());
|
||||||
|
return $this->redirectToRoute('etl_contrat_view', ['id' => $contrat->getId()]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Route('/etl/mission/{id}/manual-pay', name: 'etl_mission_manual_pay', methods: ['POST'])]
|
||||||
|
public function eltMissionManualPayment(Contrats $contrat, Request $request, EntityManagerInterface $em): Response
|
||||||
|
{
|
||||||
|
$user = $this->getUser();
|
||||||
|
if (!$user) {
|
||||||
|
return $this->redirectToRoute('etl_login');
|
||||||
|
}
|
||||||
|
|
||||||
|
$method = $request->request->get('method');
|
||||||
|
$amount = (float) $request->request->get('amount'); // Optional, or calculate solde
|
||||||
|
|
||||||
|
if ($amount <= 0) {
|
||||||
|
// 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) {
|
||||||
|
$totalHt += $line->getPrice1DayHt() + ($line->getPriceSupDayHt() * max(0, $days - 1));
|
||||||
|
}
|
||||||
|
foreach ($contrat->getContratsOptions() as $option) {
|
||||||
|
$totalHt += $option->getPrice();
|
||||||
|
}
|
||||||
|
$dejaPaye = 0;
|
||||||
|
foreach ($contrat->getContratsPayments() as $p) {
|
||||||
|
if ($p->getState() === 'complete' && $p->getType() !== 'caution') {
|
||||||
|
$dejaPaye += $p->getAmount();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$amount = $totalHt - $dejaPaye;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($amount > 0) {
|
||||||
|
$payment = new ContratsPayments();
|
||||||
|
$payment->setContrat($contrat)
|
||||||
|
->setType('etl_payment')
|
||||||
|
->setAmount($amount)
|
||||||
|
->setState('complete')
|
||||||
|
->setPaymentAt(new \DateTimeImmutable())
|
||||||
|
->setValidateAt(new \DateTimeImmutable())
|
||||||
|
->setCard(['type' => 'manuel', 'method' => $method])
|
||||||
|
->setPaymentId("MANUAL-" . uniqid());
|
||||||
|
|
||||||
|
$em->persist($payment);
|
||||||
|
$em->flush();
|
||||||
|
$this->addFlash('success', 'Paiement manuel enregistré.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->redirectToRoute('etl_contrat_view', ['id' => $contrat->getId()]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Route('/etl/mission/{id}/edl/start', name: 'etl_mission_edl_start', methods: ['POST'])]
|
||||||
|
public function eltMissionEdlStart(Contrats $contrat, EntityManagerInterface $em): Response
|
||||||
|
{
|
||||||
|
$user = $this->getUser();
|
||||||
|
if (!$user) {
|
||||||
|
return $this->redirectToRoute('etl_login');
|
||||||
|
}
|
||||||
|
|
||||||
|
$etatLieux = $contrat->getEtatLieux();
|
||||||
|
if ($etatLieux) {
|
||||||
|
$etatLieux->setStatus('edl_progress');
|
||||||
|
$em->flush();
|
||||||
|
$this->addFlash('success', 'État des lieux commencé.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->redirectToRoute('etl_mission_edl', ['id' => $contrat->getId()]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Route('/etl/mission/{id}/edl', name: 'etl_mission_edl', methods: ['GET'])]
|
||||||
|
public function eltEdl(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()
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Route('/etl/mission/{id}/edl/comment', name: 'etl_edl_add_comment', methods: ['POST'])]
|
||||||
|
public function eltEdlAddComment(Contrats $contrat, Request $request, EntityManagerInterface $em): Response
|
||||||
|
{
|
||||||
|
$user = $this->getUser();
|
||||||
|
if (!$user) {
|
||||||
|
return $this->redirectToRoute('etl_login');
|
||||||
|
}
|
||||||
|
|
||||||
|
$content = $request->request->get('content');
|
||||||
|
if ($content) {
|
||||||
|
$etatLieux = $contrat->getEtatLieux();
|
||||||
|
$comment = new EtatLieuxComment();
|
||||||
|
$comment->setContent($content);
|
||||||
|
$comment->setEtatLieux($etatLieux);
|
||||||
|
$em->persist($comment);
|
||||||
|
$em->flush();
|
||||||
|
$this->addFlash('success', 'Commentaire ajouté.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->redirectToRoute('etl_mission_edl', ['id' => $contrat->getId()]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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');
|
||||||
|
}
|
||||||
|
|
||||||
|
$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.');
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
{
|
||||||
|
|
||||||
|
$user = $this->getUser();
|
||||||
|
|
||||||
|
if (!$user) {
|
||||||
|
|
||||||
|
return $this->redirectToRoute('etl_login');
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
$etatLieux = $contrat->getEtatLieux();
|
||||||
|
|
||||||
|
$file = $em->getRepository(EtatLieuxFile::class)->find($fileId);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if ($file && $file->getEtatLieux() === $etatLieux) {
|
||||||
|
|
||||||
|
$em->remove($file);
|
||||||
|
|
||||||
|
$em->flush();
|
||||||
|
|
||||||
|
$this->addFlash('success', 'Fichier supprimé.');
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return $this->redirectToRoute('etl_mission_edl', ['id' => $contrat->getId()]);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#[Route('/etl/mission/{id}/edl/finish', name: 'etl_edl_finish', methods: ['POST'])]
|
||||||
|
public function eltEdlFinish(Contrats $contrat, EntityManagerInterface $em): Response
|
||||||
|
{
|
||||||
|
$user = $this->getUser();
|
||||||
|
if (!$user) {
|
||||||
|
return $this->redirectToRoute('etl_login');
|
||||||
|
}
|
||||||
|
|
||||||
|
$etatLieux = $contrat->getEtatLieux();
|
||||||
|
// Here we could update status to 'edl_done' or similar if needed.
|
||||||
|
// For now, let's assume it stays in 'edl_progress' or we have another step.
|
||||||
|
// The prompt says "button terminer l'etat des lieux".
|
||||||
|
// Maybe redirect to view page?
|
||||||
|
|
||||||
|
$this->addFlash('success', 'État des lieux terminé.');
|
||||||
|
return $this->redirectToRoute('etl_contrat_view', ['id' => $contrat->getId()]);
|
||||||
|
}
|
||||||
|
|
||||||
#[Route('/etl/account', name: 'etl_account', methods: ['GET', 'POST'])]
|
#[Route('/etl/account', name: 'etl_account', methods: ['GET', 'POST'])]
|
||||||
public function eltAccount(
|
public function eltAccount(
|
||||||
Request $request,
|
Request $request,
|
||||||
@@ -214,4 +623,69 @@ class EtlController extends AbstractController
|
|||||||
{
|
{
|
||||||
return new Response('Auth check', 200); // Intercepted by authenticator
|
return new Response('Auth check', 200); // Intercepted by authenticator
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function compressImage(UploadedFile $file): UploadedFile
|
||||||
|
{
|
||||||
|
if (!extension_loaded('gd')) {
|
||||||
|
return $file;
|
||||||
|
}
|
||||||
|
|
||||||
|
$mime = $file->getMimeType();
|
||||||
|
$path = $file->getPathname();
|
||||||
|
|
||||||
|
// Simple compression logic
|
||||||
|
switch ($mime) {
|
||||||
|
case 'image/jpeg':
|
||||||
|
$image = @imagecreatefromjpeg($path);
|
||||||
|
if ($image) {
|
||||||
|
imagejpeg($image, $path, 75); // Quality 75
|
||||||
|
imagedestroy($image);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'image/png':
|
||||||
|
$image = @imagecreatefrompng($path);
|
||||||
|
if ($image) {
|
||||||
|
// PNG compression 0-9
|
||||||
|
imagepng($image, $path, 6);
|
||||||
|
imagedestroy($image);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $file;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function compressVideo(UploadedFile $file): UploadedFile
|
||||||
|
{
|
||||||
|
// Check if ffmpeg is available
|
||||||
|
$ffmpegPath = shell_exec('which ffmpeg');
|
||||||
|
if (empty($ffmpegPath)) {
|
||||||
|
return $file;
|
||||||
|
}
|
||||||
|
|
||||||
|
$inputPath = $file->getPathname();
|
||||||
|
$outputPath = $inputPath . '_compressed.mp4';
|
||||||
|
|
||||||
|
// Compress video using ffmpeg (CRF 28 for reasonable quality/size trade-off)
|
||||||
|
// -y to overwrite, -vcodec libx264, -crf 28, -preset fast
|
||||||
|
$command = sprintf(
|
||||||
|
'ffmpeg -y -i %s -vcodec libx264 -crf 28 -preset fast %s 2>&1',
|
||||||
|
escapeshellarg($inputPath),
|
||||||
|
escapeshellarg($outputPath)
|
||||||
|
);
|
||||||
|
|
||||||
|
exec($command, $output, $returnVar);
|
||||||
|
|
||||||
|
if ($returnVar === 0 && file_exists($outputPath)) {
|
||||||
|
// Replace original file with compressed one
|
||||||
|
if (rename($outputPath, $inputPath)) {
|
||||||
|
// Success
|
||||||
|
} else {
|
||||||
|
// Fallback cleanup
|
||||||
|
@unlink($outputPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $file;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -88,6 +88,7 @@ class Webhooks extends AbstractController
|
|||||||
'caution' => "[Ludikevent] Confirmation de votre caution - #" . $contrat->getNumReservation(),
|
'caution' => "[Ludikevent] Confirmation de votre caution - #" . $contrat->getNumReservation(),
|
||||||
'solde_partiel' => "[Ludikevent] Confirmation de votre versement partiel - #" . $contrat->getNumReservation(),
|
'solde_partiel' => "[Ludikevent] Confirmation de votre versement partiel - #" . $contrat->getNumReservation(),
|
||||||
'solde' => "[Ludikevent] Votre réservation est désormais soldée ! - #" . $contrat->getNumReservation(),
|
'solde' => "[Ludikevent] Votre réservation est désormais soldée ! - #" . $contrat->getNumReservation(),
|
||||||
|
'etl_payment' => "[Ludikevent] Confirmation de paiement (via Prestataire) - #" . $contrat->getNumReservation(),
|
||||||
default => "[Ludikevent] Confirmation de paiement - #" . $contrat->getNumReservation(),
|
default => "[Ludikevent] Confirmation de paiement - #" . $contrat->getNumReservation(),
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -111,6 +112,7 @@ class Webhooks extends AbstractController
|
|||||||
'caution' => "🛡️ CAUTION DÉPOSÉE : " . $customer->getSurname() . " (#" . $contrat->getNumReservation() . ")",
|
'caution' => "🛡️ CAUTION DÉPOSÉE : " . $customer->getSurname() . " (#" . $contrat->getNumReservation() . ")",
|
||||||
'solde_partiel' => "💰 PAIEMENT PARTIEL : " . $customer->getSurname() . " (#" . $contrat->getNumReservation() . ")",
|
'solde_partiel' => "💰 PAIEMENT PARTIEL : " . $customer->getSurname() . " (#" . $contrat->getNumReservation() . ")",
|
||||||
'solde' => "✅ DOSSIER SOLDÉ : " . $customer->getSurname() . " (#" . $contrat->getNumReservation() . ")",
|
'solde' => "✅ DOSSIER SOLDÉ : " . $customer->getSurname() . " (#" . $contrat->getNumReservation() . ")",
|
||||||
|
'etl_payment' => "📱 PAIEMENT MOBILE (ETL) : " . $customer->getSurname() . " (#" . $contrat->getNumReservation() . ")",
|
||||||
default => "💳 Nouveau paiement reçu - #" . $contrat->getNumReservation(),
|
default => "💳 Nouveau paiement reçu - #" . $contrat->getNumReservation(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -3,9 +3,15 @@
|
|||||||
namespace App\Entity;
|
namespace App\Entity;
|
||||||
|
|
||||||
use App\Repository\EtatLieuxRepository;
|
use App\Repository\EtatLieuxRepository;
|
||||||
|
use Doctrine\Common\Collections\ArrayCollection;
|
||||||
|
use Doctrine\Common\Collections\Collection;
|
||||||
use Doctrine\ORM\Mapping as ORM;
|
use Doctrine\ORM\Mapping as ORM;
|
||||||
|
use Symfony\Component\HttpFoundation\File\File;
|
||||||
|
use Vich\UploaderBundle\Mapping\Attribute\Uploadable;
|
||||||
|
use Vich\UploaderBundle\Mapping\Attribute\UploadableField;
|
||||||
|
|
||||||
#[ORM\Entity(repositoryClass: EtatLieuxRepository::class)]
|
#[ORM\Entity(repositoryClass: EtatLieuxRepository::class)]
|
||||||
|
#[Uploadable]
|
||||||
class EtatLieux
|
class EtatLieux
|
||||||
{
|
{
|
||||||
#[ORM\Id]
|
#[ORM\Id]
|
||||||
@@ -25,11 +31,132 @@ class EtatLieux
|
|||||||
#[ORM\Column(length: 50, options: ['default' => 'delivery_ready'])]
|
#[ORM\Column(length: 50, options: ['default' => 'delivery_ready'])]
|
||||||
private string $status = 'delivery_ready';
|
private string $status = 'delivery_ready';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var Collection<int, EtatLieuxFile>
|
||||||
|
*/
|
||||||
|
#[ORM\OneToMany(targetEntity: EtatLieuxFile::class, mappedBy: 'etatLieux', cascade: ['persist', 'remove'])]
|
||||||
|
private Collection $files;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var Collection<int, EtatLieuxComment>
|
||||||
|
*/
|
||||||
|
#[ORM\OneToMany(targetEntity: EtatLieuxComment::class, mappedBy: 'etatLieux', cascade: ['persist', 'remove'])]
|
||||||
|
private Collection $comments;
|
||||||
|
|
||||||
|
#[ORM\Column(length: 255, nullable: true)]
|
||||||
|
private ?string $signIdDelivery = null;
|
||||||
|
|
||||||
|
#[ORM\Column(length: 255, nullable: true)]
|
||||||
|
private ?string $signIdCustomer = null;
|
||||||
|
|
||||||
|
#[ORM\Column(length: 255, nullable: true)]
|
||||||
|
private ?string $signIdReturn = null;
|
||||||
|
|
||||||
|
#[ORM\Column(length: 255, nullable: true)]
|
||||||
|
private ?string $signIdCustomerReturn = null;
|
||||||
|
|
||||||
|
// --- FILES DELIVERY ---
|
||||||
|
|
||||||
|
#[UploadableField(mapping: 'etat_lieux_unsign', fileNameProperty: 'etatLieuxUnsignFileName', size: 'etatLieuxUnsignFileSize')]
|
||||||
|
private ?File $etatLieuxUnsignFile = null;
|
||||||
|
|
||||||
|
#[ORM\Column(nullable: true)]
|
||||||
|
private ?string $etatLieuxUnsignFileName = null;
|
||||||
|
|
||||||
|
#[ORM\Column(nullable: true)]
|
||||||
|
private ?int $etatLieuxUnsignFileSize = null;
|
||||||
|
|
||||||
|
#[UploadableField(mapping: 'etat_lieux_sign', fileNameProperty: 'etatLieuxSignFileName', size: 'etatLieuxSignFileSize')]
|
||||||
|
private ?File $etatLieuxSignFile = null;
|
||||||
|
|
||||||
|
#[ORM\Column(nullable: true)]
|
||||||
|
private ?string $etatLieuxSignFileName = null;
|
||||||
|
|
||||||
|
#[ORM\Column(nullable: true)]
|
||||||
|
private ?int $etatLieuxSignFileSize = null;
|
||||||
|
|
||||||
|
#[UploadableField(mapping: 'etat_lieux_audit', fileNameProperty: 'etatLieuxAuditFileName', size: 'etatLieuxAuditFileSize')]
|
||||||
|
private ?File $etatLieuxAuditFile = null;
|
||||||
|
|
||||||
|
#[ORM\Column(nullable: true)]
|
||||||
|
private ?string $etatLieuxAuditFileName = null;
|
||||||
|
|
||||||
|
#[ORM\Column(nullable: true)]
|
||||||
|
private ?int $etatLieuxAuditFileSize = null;
|
||||||
|
|
||||||
|
// --- FILES RETURN ---
|
||||||
|
|
||||||
|
#[UploadableField(mapping: 'etat_lieux_unsign_return', fileNameProperty: 'etatLieuxUnsignReturnFileName', size: 'etatLieuxUnsignReturnFileSize')]
|
||||||
|
private ?File $etatLieuxUnsignReturnFile = null;
|
||||||
|
|
||||||
|
#[ORM\Column(nullable: true)]
|
||||||
|
private ?string $etatLieuxUnsignReturnFileName = null;
|
||||||
|
|
||||||
|
#[ORM\Column(nullable: true)]
|
||||||
|
private ?int $etatLieuxUnsignReturnFileSize = null;
|
||||||
|
|
||||||
|
#[UploadableField(mapping: 'etat_lieux_sign_return', fileNameProperty: 'etatLieuxSignReturnFileName', size: 'etatLieuxSignReturnFileSize')]
|
||||||
|
private ?File $etatLieuxSignReturnFile = null;
|
||||||
|
|
||||||
|
#[ORM\Column(nullable: true)]
|
||||||
|
private ?string $etatLieuxSignReturnFileName = null;
|
||||||
|
|
||||||
|
#[ORM\Column(nullable: true)]
|
||||||
|
private ?int $etatLieuxSignReturnFileSize = null;
|
||||||
|
|
||||||
|
#[UploadableField(mapping: 'etat_lieux_audit_return', fileNameProperty: 'etatLieuxAuditReturnFileName', size: 'etatLieuxAuditReturnFileSize')]
|
||||||
|
private ?File $etatLieuxAuditReturnFile = null;
|
||||||
|
|
||||||
|
#[ORM\Column(nullable: true)]
|
||||||
|
private ?string $etatLieuxAuditReturnFileName = null;
|
||||||
|
|
||||||
|
#[ORM\Column(nullable: true)]
|
||||||
|
private ?int $etatLieuxAuditReturnFileSize = null;
|
||||||
|
|
||||||
|
#[ORM\Column(nullable: true)]
|
||||||
|
private ?\DateTimeImmutable $updatedAt = null;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->files = new ArrayCollection();
|
||||||
|
$this->comments = new ArrayCollection();
|
||||||
|
}
|
||||||
|
|
||||||
public function getId(): ?int
|
public function getId(): ?int
|
||||||
{
|
{
|
||||||
return $this->id;
|
return $this->id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Collection<int, EtatLieuxFile>
|
||||||
|
*/
|
||||||
|
public function getFiles(): Collection
|
||||||
|
{
|
||||||
|
return $this->files;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addFile(EtatLieuxFile $file): static
|
||||||
|
{
|
||||||
|
if (!$this->files->contains($file)) {
|
||||||
|
$this->files->add($file);
|
||||||
|
$file->setEtatLieux($this);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function removeFile(EtatLieuxFile $file): static
|
||||||
|
{
|
||||||
|
if ($this->files->removeElement($file)) {
|
||||||
|
// set the owning side to null (unless already changed)
|
||||||
|
if ($file->getEtatLieux() === $this) {
|
||||||
|
$file->setEtatLieux(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
public function getStatus(): string
|
public function getStatus(): string
|
||||||
{
|
{
|
||||||
return $this->status;
|
return $this->status;
|
||||||
@@ -77,4 +204,291 @@ class EtatLieux
|
|||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Collection<int, EtatLieuxComment>
|
||||||
|
*/
|
||||||
|
public function getComments(): Collection
|
||||||
|
{
|
||||||
|
return $this->comments;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addComment(EtatLieuxComment $comment): static
|
||||||
|
{
|
||||||
|
if (!$this->comments->contains($comment)) {
|
||||||
|
$this->comments->add($comment);
|
||||||
|
$comment->setEtatLieux($this);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function removeComment(EtatLieuxComment $comment): static
|
||||||
|
{
|
||||||
|
if ($this->comments->removeElement($comment)) {
|
||||||
|
if ($comment->getEtatLieux() === $this) {
|
||||||
|
$comment->setEtatLieux(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getSignIdDelivery(): ?string
|
||||||
|
{
|
||||||
|
return $this->signIdDelivery;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setSignIdDelivery(?string $signIdDelivery): static
|
||||||
|
{
|
||||||
|
$this->signIdDelivery = $signIdDelivery;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getSignIdCustomer(): ?string
|
||||||
|
{
|
||||||
|
return $this->signIdCustomer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setSignIdCustomer(?string $signIdCustomer): static
|
||||||
|
{
|
||||||
|
$this->signIdCustomer = $signIdCustomer;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getEtatLieuxUnsignFile(): ?File
|
||||||
|
{
|
||||||
|
return $this->etatLieuxUnsignFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setEtatLieuxUnsignFile(?File $etatLieuxUnsignFile): void
|
||||||
|
{
|
||||||
|
$this->etatLieuxUnsignFile = $etatLieuxUnsignFile;
|
||||||
|
if (null !== $etatLieuxUnsignFile) {
|
||||||
|
$this->updatedAt = new \DateTimeImmutable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getEtatLieuxUnsignFileName(): ?string
|
||||||
|
{
|
||||||
|
return $this->etatLieuxUnsignFileName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setEtatLieuxUnsignFileName(?string $etatLieuxUnsignFileName): void
|
||||||
|
{
|
||||||
|
$this->etatLieuxUnsignFileName = $etatLieuxUnsignFileName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getEtatLieuxUnsignFileSize(): ?int
|
||||||
|
{
|
||||||
|
return $this->etatLieuxUnsignFileSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setEtatLieuxUnsignFileSize(?int $etatLieuxUnsignFileSize): void
|
||||||
|
{
|
||||||
|
$this->etatLieuxUnsignFileSize = $etatLieuxUnsignFileSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getEtatLieuxSignFile(): ?File
|
||||||
|
{
|
||||||
|
return $this->etatLieuxSignFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setEtatLieuxSignFile(?File $etatLieuxSignFile): void
|
||||||
|
{
|
||||||
|
$this->etatLieuxSignFile = $etatLieuxSignFile;
|
||||||
|
if (null !== $etatLieuxSignFile) {
|
||||||
|
$this->updatedAt = new \DateTimeImmutable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getEtatLieuxSignFileName(): ?string
|
||||||
|
{
|
||||||
|
return $this->etatLieuxSignFileName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setEtatLieuxSignFileName(?string $etatLieuxSignFileName): void
|
||||||
|
{
|
||||||
|
$this->etatLieuxSignFileName = $etatLieuxSignFileName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getEtatLieuxSignFileSize(): ?int
|
||||||
|
{
|
||||||
|
return $this->etatLieuxSignFileSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setEtatLieuxSignFileSize(?int $etatLieuxSignFileSize): void
|
||||||
|
{
|
||||||
|
$this->etatLieuxSignFileSize = $etatLieuxSignFileSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getEtatLieuxAuditFile(): ?File
|
||||||
|
{
|
||||||
|
return $this->etatLieuxAuditFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setEtatLieuxAuditFile(?File $etatLieuxAuditFile): void
|
||||||
|
{
|
||||||
|
$this->etatLieuxAuditFile = $etatLieuxAuditFile;
|
||||||
|
if (null !== $etatLieuxAuditFile) {
|
||||||
|
$this->updatedAt = new \DateTimeImmutable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getEtatLieuxAuditFileName(): ?string
|
||||||
|
{
|
||||||
|
return $this->etatLieuxAuditFileName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setEtatLieuxAuditFileName(?string $etatLieuxAuditFileName): void
|
||||||
|
{
|
||||||
|
$this->etatLieuxAuditFileName = $etatLieuxAuditFileName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getEtatLieuxAuditFileSize(): ?int
|
||||||
|
{
|
||||||
|
return $this->etatLieuxAuditFileSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setEtatLieuxAuditFileSize(?int $etatLieuxAuditFileSize): void
|
||||||
|
{
|
||||||
|
$this->etatLieuxAuditFileSize = $etatLieuxAuditFileSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getUpdatedAt(): ?\DateTimeImmutable
|
||||||
|
{
|
||||||
|
return $this->updatedAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setUpdatedAt(?\DateTimeImmutable $updatedAt): static
|
||||||
|
{
|
||||||
|
$this->updatedAt = $updatedAt;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getSignIdReturn(): ?string
|
||||||
|
{
|
||||||
|
return $this->signIdReturn;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setSignIdReturn(?string $signIdReturn): static
|
||||||
|
{
|
||||||
|
$this->signIdReturn = $signIdReturn;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getSignIdCustomerReturn(): ?string
|
||||||
|
{
|
||||||
|
return $this->signIdCustomerReturn;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setSignIdCustomerReturn(?string $signIdCustomerReturn): static
|
||||||
|
{
|
||||||
|
$this->signIdCustomerReturn = $signIdCustomerReturn;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getEtatLieuxUnsignReturnFile(): ?File
|
||||||
|
{
|
||||||
|
return $this->etatLieuxUnsignReturnFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setEtatLieuxUnsignReturnFile(?File $etatLieuxUnsignReturnFile): void
|
||||||
|
{
|
||||||
|
$this->etatLieuxUnsignReturnFile = $etatLieuxUnsignReturnFile;
|
||||||
|
if (null !== $etatLieuxUnsignReturnFile) {
|
||||||
|
$this->updatedAt = new \DateTimeImmutable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getEtatLieuxUnsignReturnFileName(): ?string
|
||||||
|
{
|
||||||
|
return $this->etatLieuxUnsignReturnFileName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setEtatLieuxUnsignReturnFileName(?string $etatLieuxUnsignReturnFileName): void
|
||||||
|
{
|
||||||
|
$this->etatLieuxUnsignReturnFileName = $etatLieuxUnsignReturnFileName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getEtatLieuxUnsignReturnFileSize(): ?int
|
||||||
|
{
|
||||||
|
return $this->etatLieuxUnsignReturnFileSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setEtatLieuxUnsignReturnFileSize(?int $etatLieuxUnsignReturnFileSize): void
|
||||||
|
{
|
||||||
|
$this->etatLieuxUnsignReturnFileSize = $etatLieuxUnsignReturnFileSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getEtatLieuxSignReturnFile(): ?File
|
||||||
|
{
|
||||||
|
return $this->etatLieuxSignReturnFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setEtatLieuxSignReturnFile(?File $etatLieuxSignReturnFile): void
|
||||||
|
{
|
||||||
|
$this->etatLieuxSignReturnFile = $etatLieuxSignReturnFile;
|
||||||
|
if (null !== $etatLieuxSignReturnFile) {
|
||||||
|
$this->updatedAt = new \DateTimeImmutable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getEtatLieuxSignReturnFileName(): ?string
|
||||||
|
{
|
||||||
|
return $this->etatLieuxSignReturnFileName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setEtatLieuxSignReturnFileName(?string $etatLieuxSignReturnFileName): void
|
||||||
|
{
|
||||||
|
$this->etatLieuxSignReturnFileName = $etatLieuxSignReturnFileName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getEtatLieuxSignReturnFileSize(): ?int
|
||||||
|
{
|
||||||
|
return $this->etatLieuxSignReturnFileSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setEtatLieuxSignReturnFileSize(?int $etatLieuxSignReturnFileSize): void
|
||||||
|
{
|
||||||
|
$this->etatLieuxSignReturnFileSize = $etatLieuxSignReturnFileSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getEtatLieuxAuditReturnFile(): ?File
|
||||||
|
{
|
||||||
|
return $this->etatLieuxAuditReturnFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setEtatLieuxAuditReturnFile(?File $etatLieuxAuditReturnFile): void
|
||||||
|
{
|
||||||
|
$this->etatLieuxAuditReturnFile = $etatLieuxAuditReturnFile;
|
||||||
|
if (null !== $etatLieuxAuditReturnFile) {
|
||||||
|
$this->updatedAt = new \DateTimeImmutable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getEtatLieuxAuditReturnFileName(): ?string
|
||||||
|
{
|
||||||
|
return $this->etatLieuxAuditReturnFileName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setEtatLieuxAuditReturnFileName(?string $etatLieuxAuditReturnFileName): void
|
||||||
|
{
|
||||||
|
$this->etatLieuxAuditReturnFileName = $etatLieuxAuditReturnFileName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getEtatLieuxAuditReturnFileSize(): ?int
|
||||||
|
{
|
||||||
|
return $this->etatLieuxAuditReturnFileSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setEtatLieuxAuditReturnFileSize(?int $etatLieuxAuditReturnFileSize): void
|
||||||
|
{
|
||||||
|
$this->etatLieuxAuditReturnFileSize = $etatLieuxAuditReturnFileSize;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
72
src/Entity/EtatLieuxComment.php
Normal file
72
src/Entity/EtatLieuxComment.php
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Entity;
|
||||||
|
|
||||||
|
use App\Repository\EtatLieuxCommentRepository;
|
||||||
|
use Doctrine\DBAL\Types\Types;
|
||||||
|
use Doctrine\ORM\Mapping as ORM;
|
||||||
|
|
||||||
|
#[ORM\Entity(repositoryClass: EtatLieuxCommentRepository::class)]
|
||||||
|
class EtatLieuxComment
|
||||||
|
{
|
||||||
|
#[ORM\Id]
|
||||||
|
#[ORM\GeneratedValue]
|
||||||
|
#[ORM\Column]
|
||||||
|
private ?int $id = null;
|
||||||
|
|
||||||
|
#[ORM\Column(type: Types::TEXT)]
|
||||||
|
private ?string $content = null;
|
||||||
|
|
||||||
|
#[ORM\Column]
|
||||||
|
private ?\DateTimeImmutable $createdAt = null;
|
||||||
|
|
||||||
|
#[ORM\ManyToOne(inversedBy: 'comments')]
|
||||||
|
#[ORM\JoinColumn(nullable: false)]
|
||||||
|
private ?EtatLieux $etatLieux = null;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->createdAt = new \DateTimeImmutable();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getId(): ?int
|
||||||
|
{
|
||||||
|
return $this->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getContent(): ?string
|
||||||
|
{
|
||||||
|
return $this->content;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setContent(string $content): static
|
||||||
|
{
|
||||||
|
$this->content = $content;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCreatedAt(): ?\DateTimeImmutable
|
||||||
|
{
|
||||||
|
return $this->createdAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setCreatedAt(\DateTimeImmutable $createdAt): static
|
||||||
|
{
|
||||||
|
$this->createdAt = $createdAt;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getEtatLieux(): ?EtatLieux
|
||||||
|
{
|
||||||
|
return $this->etatLieux;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setEtatLieux(?EtatLieux $etatLieux): static
|
||||||
|
{
|
||||||
|
$this->etatLieux = $etatLieux;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
}
|
||||||
130
src/Entity/EtatLieuxFile.php
Normal file
130
src/Entity/EtatLieuxFile.php
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Entity;
|
||||||
|
|
||||||
|
use App\Repository\EtatLieuxFileRepository;
|
||||||
|
use Doctrine\ORM\Mapping as ORM;
|
||||||
|
use Symfony\Component\HttpFoundation\File\File;
|
||||||
|
use Vich\UploaderBundle\Mapping\Attribute\Uploadable;
|
||||||
|
use Vich\UploaderBundle\Mapping\Attribute\UploadableField;
|
||||||
|
|
||||||
|
#[ORM\Entity(repositoryClass: EtatLieuxFileRepository::class)]
|
||||||
|
#[Uploadable]
|
||||||
|
class EtatLieuxFile
|
||||||
|
{
|
||||||
|
#[ORM\Id]
|
||||||
|
#[ORM\GeneratedValue]
|
||||||
|
#[ORM\Column]
|
||||||
|
private ?int $id = null;
|
||||||
|
|
||||||
|
#[ORM\ManyToOne(inversedBy: 'files')]
|
||||||
|
#[ORM\JoinColumn(nullable: false)]
|
||||||
|
private ?EtatLieux $etatLieux = null;
|
||||||
|
|
||||||
|
#[UploadableField(mapping: 'etat_lieux_media', fileNameProperty: 'fileName', size: 'fileSize', mimeType: 'mimeType')]
|
||||||
|
private ?File $file = null;
|
||||||
|
|
||||||
|
#[ORM\Column(nullable: true)]
|
||||||
|
private ?string $fileName = null;
|
||||||
|
|
||||||
|
#[ORM\Column(nullable: true)]
|
||||||
|
private ?int $fileSize = null;
|
||||||
|
|
||||||
|
#[ORM\Column(length: 255, nullable: true)]
|
||||||
|
private ?string $mimeType = null;
|
||||||
|
|
||||||
|
#[ORM\Column(length: 50)]
|
||||||
|
private ?string $type = null; // 'photo' or 'video'
|
||||||
|
|
||||||
|
#[ORM\Column]
|
||||||
|
private ?\DateTimeImmutable $createdAt = null;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->createdAt = new \DateTimeImmutable();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getId(): ?int
|
||||||
|
{
|
||||||
|
return $this->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getEtatLieux(): ?EtatLieux
|
||||||
|
{
|
||||||
|
return $this->etatLieux;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setEtatLieux(?EtatLieux $etatLieux): static
|
||||||
|
{
|
||||||
|
$this->etatLieux = $etatLieux;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getFile(): ?File
|
||||||
|
{
|
||||||
|
return $this->file;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setFile(?File $file): void
|
||||||
|
{
|
||||||
|
$this->file = $file;
|
||||||
|
if (null !== $file) {
|
||||||
|
$this->createdAt = new \DateTimeImmutable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getFileName(): ?string
|
||||||
|
{
|
||||||
|
return $this->fileName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setFileName(?string $fileName): void
|
||||||
|
{
|
||||||
|
$this->fileName = $fileName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getFileSize(): ?int
|
||||||
|
{
|
||||||
|
return $this->fileSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setFileSize(?int $fileSize): void
|
||||||
|
{
|
||||||
|
$this->fileSize = $fileSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getMimeType(): ?string
|
||||||
|
{
|
||||||
|
return $this->mimeType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setMimeType(?string $mimeType): void
|
||||||
|
{
|
||||||
|
$this->mimeType = $mimeType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getType(): ?string
|
||||||
|
{
|
||||||
|
return $this->type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setType(string $type): static
|
||||||
|
{
|
||||||
|
$this->type = $type;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCreatedAt(): ?\DateTimeImmutable
|
||||||
|
{
|
||||||
|
return $this->createdAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setCreatedAt(\DateTimeImmutable $createdAt): static
|
||||||
|
{
|
||||||
|
$this->createdAt = $createdAt;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
}
|
||||||
23
src/Repository/EtatLieuxCommentRepository.php
Normal file
23
src/Repository/EtatLieuxCommentRepository.php
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Repository;
|
||||||
|
|
||||||
|
use App\Entity\EtatLieuxComment;
|
||||||
|
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||||
|
use Doctrine\Persistence\ManagerRegistry;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @extends ServiceEntityRepository<EtatLieuxComment>
|
||||||
|
*
|
||||||
|
* @method EtatLieuxComment|null find($id, $lockMode = null, $lockVersion = null)
|
||||||
|
* @method EtatLieuxComment|null findOneBy(array $criteria, array $orderBy = null)
|
||||||
|
* @method EtatLieuxComment[] findAll()
|
||||||
|
* @method EtatLieuxComment[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
|
||||||
|
*/
|
||||||
|
class EtatLieuxCommentRepository extends ServiceEntityRepository
|
||||||
|
{
|
||||||
|
public function __construct(ManagerRegistry $registry)
|
||||||
|
{
|
||||||
|
parent::__construct($registry, EtatLieuxComment::class);
|
||||||
|
}
|
||||||
|
}
|
||||||
23
src/Repository/EtatLieuxFileRepository.php
Normal file
23
src/Repository/EtatLieuxFileRepository.php
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Repository;
|
||||||
|
|
||||||
|
use App\Entity\EtatLieuxFile;
|
||||||
|
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||||
|
use Doctrine\Persistence\ManagerRegistry;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @extends ServiceEntityRepository<EtatLieuxFile>
|
||||||
|
*
|
||||||
|
* @method EtatLieuxFile|null find($id, $lockMode = null, $lockVersion = null)
|
||||||
|
* @method EtatLieuxFile|null findOneBy(array $criteria, array $orderBy = null)
|
||||||
|
* @method EtatLieuxFile[] findAll()
|
||||||
|
* @method EtatLieuxFile[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
|
||||||
|
*/
|
||||||
|
class EtatLieuxFileRepository extends ServiceEntityRepository
|
||||||
|
{
|
||||||
|
public function __construct(ManagerRegistry $registry)
|
||||||
|
{
|
||||||
|
parent::__construct($registry, EtatLieuxFile::class);
|
||||||
|
}
|
||||||
|
}
|
||||||
111
templates/etl/edl.twig
Normal file
111
templates/etl/edl.twig
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
{% extends 'etl/base.twig' %}
|
||||||
|
|
||||||
|
{% block title %}État des Lieux - #{{ mission.numReservation }}{% endblock %}
|
||||||
|
|
||||||
|
{% block body %}
|
||||||
|
<div class="space-y-6 animate-in fade-in slide-in-from-bottom-4 duration-500">
|
||||||
|
|
||||||
|
{# HEADER #}
|
||||||
|
<div class="flex items-center gap-4">
|
||||||
|
<a href="{{ path('etl_contrat_view', {id: mission.id}) }}" class="w-10 h-10 bg-white rounded-xl border border-slate-100 flex items-center justify-center text-slate-400 hover:text-blue-600 transition-all shadow-sm">
|
||||||
|
<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 19l-7-7 7-7" /></svg>
|
||||||
|
</a>
|
||||||
|
<div>
|
||||||
|
<h1 class="text-xl font-black text-slate-900 tracking-tight">État des Lieux</h1>
|
||||||
|
<p class="text-[10px] font-bold text-slate-400 uppercase tracking-widest">Réf: #{{ mission.numReservation }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{# PHOTOS / VIDEOS #}
|
||||||
|
<div class="bg-white rounded-[2rem] p-6 border border-slate-100 shadow-sm">
|
||||||
|
<h3 class="text-xs font-black text-slate-400 uppercase tracking-widest mb-4">Photos & Vidéos</h3>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-3 gap-2 mb-4">
|
||||||
|
{% for file in etatLieux.files %}
|
||||||
|
<div class="aspect-square bg-slate-100 rounded-xl overflow-hidden relative group">
|
||||||
|
{% if file.type == 'photo' %}
|
||||||
|
<img src="{{ vich_uploader_asset(file, 'file') }}" alt="Photo" class="w-full h-full object-cover cursor-pointer lightbox-trigger" data-type="photo" data-src="{{ vich_uploader_asset(file, 'file') }}">
|
||||||
|
{% else %}
|
||||||
|
<div class="w-full h-full relative cursor-pointer lightbox-trigger" data-type="video" data-src="{{ vich_uploader_asset(file, 'file') }}">
|
||||||
|
<video src="{{ vich_uploader_asset(file, 'file') }}" class="w-full h-full object-cover pointer-events-none"></video>
|
||||||
|
<div class="absolute inset-0 flex items-center justify-center bg-black/20 text-white pointer-events-none">
|
||||||
|
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14.752 11.168l-3.197-2.132A1 1 0 0010 9.87v4.263a1 1 0 001.555.832l3.197-2.132a1 1 0 000-1.664z" /><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 12a9 9 0 11-18 0 9 9 0 0118 0z" /></svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<form action="{{ path('etl_edl_delete_file', {id: mission.id, fileId: file.id}) }}" method="post" class="absolute top-1 right-1 z-10">
|
||||||
|
<button type="submit" class="bg-red-500/80 text-white p-1 rounded-full hover:bg-red-600 transition-colors shadow-sm" onclick="return confirm('Supprimer ce fichier ?')">
|
||||||
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" /></svg>
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<div class="col-span-3 text-center py-4 text-slate-400 text-xs italic">Aucun média ajouté.</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{# LIGHTBOX MODAL #}
|
||||||
|
<div id="lightbox-modal" class="fixed inset-0 z-[100] bg-black/90 hidden items-center justify-center p-2 backdrop-blur-sm transition-opacity duration-300 opacity-0 pointer-events-none">
|
||||||
|
<button id="lightbox-close" class="absolute top-4 right-4 text-white/80 hover:text-white z-50 p-2 bg-black/50 rounded-full">
|
||||||
|
<svg class="w-8 h-8" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" /></svg>
|
||||||
|
</button>
|
||||||
|
<div class="w-full h-full flex items-center justify-center relative">
|
||||||
|
<img id="lightbox-img" src="" class="max-w-full max-h-full object-contain rounded-lg shadow-2xl hidden" alt="Full view">
|
||||||
|
<video id="lightbox-video" src="" controls class="max-w-full max-h-full rounded-lg shadow-2xl hidden"></video>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form action="{{ path('etl_edl_add_file', {id: mission.id}) }}" method="post" enctype="multipart/form-data" class="space-y-4">
|
||||||
|
<div class="flex gap-2">
|
||||||
|
<label class="flex-1 cursor-pointer bg-blue-50 text-blue-600 rounded-xl border border-blue-100 py-3 flex items-center justify-center gap-2 text-xs font-bold uppercase tracking-wide hover:bg-blue-100 transition-colors">
|
||||||
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 9a2 2 0 012-2h.93a2 2 0 001.664-.89l.812-1.22A2 2 0 0110.07 4h3.86a2 2 0 011.664.89l.812 1.22A2 2 0 0018.07 7H19a2 2 0 012 2v9a2 2 0 01-2 2H5a2 2 0 01-2-2V9z" /><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 13a3 3 0 11-6 0 3 3 0 016 0z" /></svg>
|
||||||
|
Sélectionner Photos
|
||||||
|
<input type="file" name="photos[]" accept="image/*" multiple class="hidden">
|
||||||
|
</label>
|
||||||
|
<label class="flex-1 cursor-pointer bg-purple-50 text-purple-600 rounded-xl border border-purple-100 py-3 flex items-center justify-center gap-2 text-xs font-bold uppercase tracking-wide hover:bg-purple-100 transition-colors">
|
||||||
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 10l4.553-2.276A1 1 0 0121 8.618v6.764a1 1 0 01-1.447.894L15 14M5 18h8a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v8a2 2 0 002 2z" /></svg>
|
||||||
|
Sélectionner Vidéos
|
||||||
|
<input type="file" name="videos[]" accept="video/*" multiple class="hidden">
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button type="submit" class="w-full py-3 bg-slate-900 text-white rounded-xl text-xs font-bold uppercase tracking-wide hover:bg-slate-800 transition-colors shadow-lg">
|
||||||
|
Envoyer les fichiers
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{# COMMENTAIRES #}
|
||||||
|
<div class="bg-white rounded-[2rem] p-6 border border-slate-100 shadow-sm">
|
||||||
|
<h3 class="text-xs font-black text-slate-400 uppercase tracking-widest mb-4">Commentaires</h3>
|
||||||
|
|
||||||
|
<div class="space-y-3 mb-4">
|
||||||
|
{% for comment in etatLieux.comments %}
|
||||||
|
<div class="bg-slate-50 p-3 rounded-xl">
|
||||||
|
<p class="text-[10px] font-bold text-slate-400 mb-1">{{ comment.createdAt|date('d/m H:i') }}</p>
|
||||||
|
<p class="text-sm text-slate-700">{{ comment.content }}</p>
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<p class="text-center py-2 text-slate-400 text-xs italic">Aucun commentaire.</p>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form action="{{ path('etl_edl_add_comment', {id: mission.id}) }}" method="post" class="flex gap-2">
|
||||||
|
<input type="text" name="content" placeholder="Votre commentaire..." class="flex-1 bg-slate-50 border border-slate-200 rounded-xl px-4 py-3 text-sm focus:outline-none focus:border-blue-500 transition-colors" required>
|
||||||
|
<button type="submit" class="bg-slate-900 text-white p-3 rounded-xl hover:bg-slate-700 transition-colors">
|
||||||
|
<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="M12 19l9 2-9-18-9 18 9-2zm0 0v-8" /></svg>
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{# ACTION TERMINER #}
|
||||||
|
<form action="{{ path('etl_edl_finish', {id: mission.id}) }}" method="post">
|
||||||
|
<button type="submit" class="w-full py-4 bg-emerald-500 hover:bg-emerald-600 text-white rounded-2xl font-black uppercase text-sm tracking-widest shadow-lg shadow-emerald-500/30 transition-all active:scale-95 flex items-center justify-center gap-3">
|
||||||
|
<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="M5 13l4 4L19 7" /></svg>
|
||||||
|
Terminer l'état des lieux
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
{% block body %}
|
{% block body %}
|
||||||
<div class="space-y-6 animate-in fade-in slide-in-from-bottom-4 duration-500">
|
<div class="space-y-6 animate-in fade-in slide-in-from-bottom-4 duration-500">
|
||||||
|
|
||||||
{# HEADER #}
|
{# HEADER #}
|
||||||
<div class="flex items-center gap-4">
|
<div class="flex items-center gap-4">
|
||||||
<a href="{{ path('etl_home') }}" class="w-10 h-10 bg-white rounded-xl border border-slate-100 flex items-center justify-center text-slate-400 hover:text-blue-600 transition-all shadow-sm">
|
<a href="{{ path('etl_home') }}" class="w-10 h-10 bg-white rounded-xl border border-slate-100 flex items-center justify-center text-slate-400 hover:text-blue-600 transition-all shadow-sm">
|
||||||
@@ -25,13 +25,93 @@
|
|||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
{% elseif mission.etatLieux.status == 'delivery_progress' %}
|
{% elseif mission.etatLieux.status == 'delivery_progress' %}
|
||||||
<div class="w-full py-4 bg-amber-500/10 border border-amber-500/20 text-amber-500 rounded-2xl font-black uppercase text-sm tracking-widest flex items-center justify-center gap-3">
|
<form action="{{ path('etl_mission_finish', {id: mission.id}) }}" method="post">
|
||||||
<span class="relative flex h-3 w-3">
|
<button type="submit" class="w-full py-4 bg-amber-500 hover:bg-amber-600 text-white rounded-2xl font-black uppercase text-sm tracking-widest shadow-lg shadow-amber-500/30 transition-all active:scale-95 flex items-center justify-center gap-3">
|
||||||
<span class="animate-ping absolute inline-flex h-full w-full rounded-full bg-amber-400 opacity-75"></span>
|
<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="M5 13l4 4L19 7" /></svg>
|
||||||
<span class="relative inline-flex rounded-full h-3 w-3 bg-amber-500"></span>
|
Confirmer la livraison
|
||||||
</span>
|
</button>
|
||||||
Livraison en cours
|
</form>
|
||||||
|
{% elseif mission.etatLieux.status == 'delivery_done' %}
|
||||||
|
<div class="w-full py-4 bg-emerald-500/10 border border-emerald-500/20 text-emerald-600 rounded-2xl font-black uppercase text-sm tracking-widest 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="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" /></svg>
|
||||||
|
Livraison Terminée
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{% 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>
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<span class="text-2xl font-black text-slate-900">{{ totalCaution|number_format(2, ',', ' ') }}€</span>
|
||||||
|
{% if cautionPaid %}
|
||||||
|
<span class="px-3 py-1 bg-emerald-100 text-emerald-600 rounded-lg text-[10px] font-black uppercase">Reçue</span>
|
||||||
|
{% else %}
|
||||||
|
<form action="{{ path('etl_mission_caution', {id: mission.id}) }}" method="post">
|
||||||
|
<button type="submit" class="px-4 py-2 bg-slate-900 text-white rounded-xl text-[10px] font-black uppercase tracking-wide hover:bg-blue-600 transition-colors">
|
||||||
|
Valider Réception
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{# SOLDE #}
|
||||||
|
{% if solde > 0 %}
|
||||||
|
<div class="bg-slate-900 rounded-[2rem] p-6 text-white shadow-xl mb-4">
|
||||||
|
<div class="flex justify-between items-start mb-4">
|
||||||
|
<div>
|
||||||
|
<p class="text-[10px] font-black text-blue-400 uppercase tracking-widest mb-1">Reste à payer</p>
|
||||||
|
<p class="text-3xl font-black">{{ solde|number_format(2, ',', ' ') }}€</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{# Paiement en ligne #}
|
||||||
|
<form action="{{ path('elt_mission_confirme', {id: mission.id}) }}" method="post" class="mb-4">
|
||||||
|
<button type="submit" class="w-full py-3 bg-blue-600 hover:bg-blue-500 text-white rounded-xl font-bold text-xs uppercase tracking-wider shadow-lg shadow-blue-900/50 transition-all active:scale-95 flex items-center justify-center gap-2">
|
||||||
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 10h18M7 15h1m4 0h1m-7 4h12a3 3 0 003-3V8a3 3 0 00-3-3H6a3 3 0 00-3 3v8a3 3 0 003 3z"/></svg>
|
||||||
|
Paiement Carte Bancaire
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
{# Paiements Manuels #}
|
||||||
|
<p class="text-[9px] font-bold text-slate-500 uppercase tracking-widest mb-2 text-center">Encaissement direct</p>
|
||||||
|
<div class="grid grid-cols-3 gap-2">
|
||||||
|
{% for method in ['Chèque', 'Espèces', 'Autre'] %}
|
||||||
|
<form action="{{ path('etl_mission_manual_pay', {id: mission.id}) }}" method="post">
|
||||||
|
<input type="hidden" name="method" value="{{ method }}">
|
||||||
|
<button type="submit" class="w-full py-2 bg-slate-800 hover:bg-slate-700 text-slate-300 hover:text-white rounded-xl font-bold text-[10px] uppercase tracking-wide transition-all active:scale-95 border border-slate-700">
|
||||||
|
{{ method }}
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<div class="bg-emerald-500/10 border border-emerald-500/20 text-emerald-500 rounded-[2rem] p-6 text-center mb-4">
|
||||||
|
<p class="text-xs font-black uppercase tracking-widest">Solde Réglé</p>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{# ACTION EDL #}
|
||||||
|
{% if is_chorus or solde <= 0 %}
|
||||||
|
<form action="{{ path('etl_mission_edl_start', {id: mission.id}) }}" method="post" class="mt-6">
|
||||||
|
<button type="submit" 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">
|
||||||
|
<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 2" /></svg>
|
||||||
|
Commencer l'état des lieux
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
{% 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>
|
||||||
|
Reprendre l'état des lieux
|
||||||
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{# DATES #}
|
{# DATES #}
|
||||||
@@ -68,7 +148,7 @@
|
|||||||
{# ADRESSE & GPS #}
|
{# ADRESSE & GPS #}
|
||||||
<div class="bg-white rounded-[2rem] p-6 border border-slate-100 shadow-sm">
|
<div class="bg-white rounded-[2rem] p-6 border border-slate-100 shadow-sm">
|
||||||
<h3 class="text-xs font-black text-slate-400 uppercase tracking-widest mb-4">Lieu de l'événement</h3>
|
<h3 class="text-xs font-black text-slate-400 uppercase tracking-widest mb-4">Lieu de l'événement</h3>
|
||||||
|
|
||||||
<div class="mb-6">
|
<div class="mb-6">
|
||||||
<p class="font-bold text-slate-900 text-sm leading-relaxed">
|
<p class="font-bold text-slate-900 text-sm leading-relaxed">
|
||||||
{{ mission.addressEvent }}<br>
|
{{ mission.addressEvent }}<br>
|
||||||
@@ -92,7 +172,7 @@
|
|||||||
{# PRODUCTS & OPTIONS #}
|
{# PRODUCTS & OPTIONS #}
|
||||||
<div class="bg-white rounded-[2rem] p-6 border border-slate-100 shadow-sm">
|
<div class="bg-white rounded-[2rem] p-6 border border-slate-100 shadow-sm">
|
||||||
<h3 class="text-xs font-black text-slate-400 uppercase tracking-widest mb-4">Matériel & Options</h3>
|
<h3 class="text-xs font-black text-slate-400 uppercase tracking-widest mb-4">Matériel & Options</h3>
|
||||||
|
|
||||||
<div class="space-y-3">
|
<div class="space-y-3">
|
||||||
{% for line in mission.contratsLines %}
|
{% for line in mission.contratsLines %}
|
||||||
{% if 'livraison' not in line.name|lower %}
|
{% if 'livraison' not in line.name|lower %}
|
||||||
@@ -127,7 +207,7 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{# DETAILS / NOTES #}
|
{# DETAILS / NOTES #}
|
||||||
{% if mission.details or mission.notes %}
|
{% if mission.details or mission.notes %}
|
||||||
<div class="bg-white rounded-[2rem] p-6 border border-slate-100 shadow-sm">
|
<div class="bg-white rounded-[2rem] p-6 border border-slate-100 shadow-sm">
|
||||||
|
|||||||
Reference in New Issue
Block a user