✨ feat(etatLieux): Implémente l'état des lieux de retour (médias, commentaires, points de contrôle, signatures et refus client).
This commit is contained in:
6
.env
6
.env
@@ -83,9 +83,9 @@ STRIPE_PK=pk_test_51SUA22173W4aeFB1nO6oFfDZ12HOTffDKtCshhZ8rkUg6kUO2ZaQC0tK72rhE
|
|||||||
STRIPE_SK=sk_test_51SUA22173W4aeFB16EB2LxGI0hNvNJzFshDI98zRImWBIhSfzqOGAz5TlPxSpUWbj3x4COm6kmSsaal9FpQR1A7M0022DvjbbR
|
STRIPE_SK=sk_test_51SUA22173W4aeFB16EB2LxGI0hNvNJzFshDI98zRImWBIhSfzqOGAz5TlPxSpUWbj3x4COm6kmSsaal9FpQR1A7M0022DvjbbR
|
||||||
STRIPE_WEBHOOKS_SECRET=
|
STRIPE_WEBHOOKS_SECRET=
|
||||||
|
|
||||||
SIGN_URL=https://c55e-82-67-166-187.ngrok-free.app
|
SIGN_URL=https://eb44-82-67-166-187.ngrok-free.app
|
||||||
STRIPE_BASEURL=https://c55e-82-67-166-187.ngrok-free.app
|
STRIPE_BASEURL=https://eb44-82-67-166-187.ngrok-free.app
|
||||||
CONTRAT_BASEURL=https://c55e-82-67-166-187.ngrok-free.app
|
CONTRAT_BASEURL=https://eb44-82-67-166-187.ngrok-free.app
|
||||||
|
|
||||||
MINIO_S3_URL=
|
MINIO_S3_URL=
|
||||||
MINIO_S3_CLIENT_ID=
|
MINIO_S3_CLIENT_ID=
|
||||||
|
|||||||
34
migrations/Version20260211170445.php
Normal file
34
migrations/Version20260211170445.php
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
<?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 Version20260211170445 extends AbstractMigration
|
||||||
|
{
|
||||||
|
public function getDescription(): string
|
||||||
|
{
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function up(Schema $schema): void
|
||||||
|
{
|
||||||
|
// this up() migration is auto-generated, please modify it to your needs
|
||||||
|
$this->addSql('CREATE TABLE etat_lieux_return_comment (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, content TEXT NOT NULL, created_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, etat_lieux_id INT NOT NULL, PRIMARY KEY (id))');
|
||||||
|
$this->addSql('CREATE INDEX IDX_5503D02A3F1DAE3C ON etat_lieux_return_comment (etat_lieux_id)');
|
||||||
|
$this->addSql('ALTER TABLE etat_lieux_return_comment ADD CONSTRAINT FK_5503D02A3F1DAE3C FOREIGN KEY (etat_lieux_id) REFERENCES etat_lieux (id) NOT DEFERRABLE');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(Schema $schema): void
|
||||||
|
{
|
||||||
|
// this down() migration is auto-generated, please modify it to your needs
|
||||||
|
$this->addSql('ALTER TABLE etat_lieux_return_comment DROP CONSTRAINT FK_5503D02A3F1DAE3C');
|
||||||
|
$this->addSql('DROP TABLE etat_lieux_return_comment');
|
||||||
|
}
|
||||||
|
}
|
||||||
39
migrations/Version20260212074558.php
Normal file
39
migrations/Version20260212074558.php
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
<?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 Version20260212074558 extends AbstractMigration
|
||||||
|
{
|
||||||
|
public function getDescription(): string
|
||||||
|
{
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function up(Schema $schema): void
|
||||||
|
{
|
||||||
|
// this up() migration is auto-generated, please modify it to your needs
|
||||||
|
$this->addSql('CREATE TABLE etat_lieux_return_point_control (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, name VARCHAR(255) NOT NULL, status BOOLEAN DEFAULT false NOT NULL, details TEXT DEFAULT NULL, etat_lieux_id INT NOT NULL, PRIMARY KEY (id))');
|
||||||
|
$this->addSql('CREATE INDEX IDX_2EADBB563F1DAE3C ON etat_lieux_return_point_control (etat_lieux_id)');
|
||||||
|
$this->addSql('CREATE TABLE etat_return_lieux_file (id INT GENERATED BY DEFAULT AS IDENTITY 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, etat_lieux_id INT NOT NULL, PRIMARY KEY (id))');
|
||||||
|
$this->addSql('CREATE INDEX IDX_2E89E9D43F1DAE3C ON etat_return_lieux_file (etat_lieux_id)');
|
||||||
|
$this->addSql('ALTER TABLE etat_lieux_return_point_control ADD CONSTRAINT FK_2EADBB563F1DAE3C FOREIGN KEY (etat_lieux_id) REFERENCES etat_lieux (id) NOT DEFERRABLE');
|
||||||
|
$this->addSql('ALTER TABLE etat_return_lieux_file ADD CONSTRAINT FK_2E89E9D43F1DAE3C FOREIGN KEY (etat_lieux_id) REFERENCES etat_lieux (id) NOT DEFERRABLE');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(Schema $schema): void
|
||||||
|
{
|
||||||
|
// this down() migration is auto-generated, please modify it to your needs
|
||||||
|
$this->addSql('ALTER TABLE etat_lieux_return_point_control DROP CONSTRAINT FK_2EADBB563F1DAE3C');
|
||||||
|
$this->addSql('ALTER TABLE etat_return_lieux_file DROP CONSTRAINT FK_2E89E9D43F1DAE3C');
|
||||||
|
$this->addSql('DROP TABLE etat_lieux_return_point_control');
|
||||||
|
$this->addSql('DROP TABLE etat_return_lieux_file');
|
||||||
|
}
|
||||||
|
}
|
||||||
31
migrations/Version20260212090115.php
Normal file
31
migrations/Version20260212090115.php
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
<?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 Version20260212090115 extends AbstractMigration
|
||||||
|
{
|
||||||
|
public function getDescription(): string
|
||||||
|
{
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
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 raison_refused TEXT 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 raison_refused');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,6 +9,9 @@ use App\Entity\EtatLieux;
|
|||||||
use App\Entity\EtatLieuxComment;
|
use App\Entity\EtatLieuxComment;
|
||||||
use App\Entity\EtatLieuxFile;
|
use App\Entity\EtatLieuxFile;
|
||||||
use App\Entity\EtatLieuxPointControl;
|
use App\Entity\EtatLieuxPointControl;
|
||||||
|
use App\Entity\EtatLieuxReturnComment;
|
||||||
|
use App\Entity\EtatLieuxReturnPointControl;
|
||||||
|
use App\Entity\EtatReturnLieuxFile;
|
||||||
use App\Entity\ProductPointControll;
|
use App\Entity\ProductPointControll;
|
||||||
use App\Entity\Prestaire;
|
use App\Entity\Prestaire;
|
||||||
use App\Form\PrestairePasswordType;
|
use App\Form\PrestairePasswordType;
|
||||||
@@ -461,7 +464,12 @@ class EtlController extends AbstractController
|
|||||||
$content = $request->request->get('content');
|
$content = $request->request->get('content');
|
||||||
if ($content) {
|
if ($content) {
|
||||||
$etatLieux = $contrat->getEtatLieux();
|
$etatLieux = $contrat->getEtatLieux();
|
||||||
$comment = new EtatLieuxComment();
|
if($etatLieux->getStatus() == "return_edl_progress") {
|
||||||
|
$comment = new EtatLieuxReturnComment();
|
||||||
|
} else {
|
||||||
|
$comment = new EtatLieuxComment();
|
||||||
|
}
|
||||||
|
|
||||||
$comment->setContent($content);
|
$comment->setContent($content);
|
||||||
$comment->setEtatLieux($etatLieux);
|
$comment->setEtatLieux($etatLieux);
|
||||||
$em->persist($comment);
|
$em->persist($comment);
|
||||||
@@ -494,22 +502,42 @@ class EtlController extends AbstractController
|
|||||||
|
|
||||||
if ($productPoint) {
|
if ($productPoint) {
|
||||||
$existing = null;
|
$existing = null;
|
||||||
foreach ($etatLieux->getPointControls() as $ep) {
|
if($etatLieux->getStatus() == "return_edl_progress") {
|
||||||
if ($ep->getName() === $productPoint->getName()) {
|
foreach ($etatLieux->getPointControlsReturn() as $ep) {
|
||||||
$existing = $ep;
|
if ($ep->getName() === $productPoint->getName()) {
|
||||||
break;
|
$existing = $ep;
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!$existing) {
|
||||||
|
$existing = new EtatLieuxReturnPointControl();
|
||||||
|
$existing->setName($productPoint->getName());
|
||||||
|
$existing->setEtatLieux($etatLieux);
|
||||||
|
$em->persist($existing);
|
||||||
|
}
|
||||||
|
|
||||||
|
$existing->setStatus(isset($values['status']));
|
||||||
|
$existing->setDetails($values['details'] ?? null);
|
||||||
|
} else {
|
||||||
|
foreach ($etatLieux->getPointControls() as $ep) {
|
||||||
|
if ($ep->getName() === $productPoint->getName()) {
|
||||||
|
$existing = $ep;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$existing) {
|
||||||
|
$existing = new EtatLieuxPointControl();
|
||||||
|
$existing->setName($productPoint->getName());
|
||||||
|
$existing->setEtatLieux($etatLieux);
|
||||||
|
$em->persist($existing);
|
||||||
|
}
|
||||||
|
|
||||||
|
$existing->setStatus(isset($values['status']));
|
||||||
|
$existing->setDetails($values['details'] ?? null);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!$existing) {
|
|
||||||
$existing = new EtatLieuxPointControl();
|
|
||||||
$existing->setName($productPoint->getName());
|
|
||||||
$existing->setEtatLieux($etatLieux);
|
|
||||||
$em->persist($existing);
|
|
||||||
}
|
|
||||||
|
|
||||||
$existing->setStatus(isset($values['status']));
|
|
||||||
$existing->setDetails($values['details'] ?? null);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -533,17 +561,29 @@ class EtlController extends AbstractController
|
|||||||
$etatLieux = $contrat->getEtatLieux();
|
$etatLieux = $contrat->getEtatLieux();
|
||||||
$hasFiles = false;
|
$hasFiles = false;
|
||||||
|
|
||||||
|
|
||||||
if ($photos) {
|
if ($photos) {
|
||||||
if (!is_array($photos)) $photos = [$photos];
|
if (!is_array($photos)) $photos = [$photos];
|
||||||
foreach ($photos as $uploadedFile) {
|
foreach ($photos as $uploadedFile) {
|
||||||
if ($uploadedFile instanceof UploadedFile) {
|
if ($uploadedFile instanceof UploadedFile) {
|
||||||
$this->compressImage($uploadedFile);
|
$this->compressImage($uploadedFile);
|
||||||
$file = new EtatLieuxFile();
|
|
||||||
$file->setFile($uploadedFile);
|
if($etatLieux->getStatus() == "return_edl_progress") {
|
||||||
$file->setType('photo');
|
$file = new EtatReturnLieuxFile();
|
||||||
$file->setEtatLieux($etatLieux);
|
$file->setFile($uploadedFile);
|
||||||
$em->persist($file);
|
$file->setType('photo');
|
||||||
$hasFiles = true;
|
$file->setEtatLieux($etatLieux);
|
||||||
|
$em->persist($file);
|
||||||
|
$hasFiles = true;
|
||||||
|
} else {
|
||||||
|
$file = new EtatLieuxFile();
|
||||||
|
$file->setFile($uploadedFile);
|
||||||
|
$file->setType('photo');
|
||||||
|
$file->setEtatLieux($etatLieux);
|
||||||
|
$em->persist($file);
|
||||||
|
$hasFiles = true;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -552,12 +592,22 @@ class EtlController extends AbstractController
|
|||||||
foreach ($videos as $uploadedFile) {
|
foreach ($videos as $uploadedFile) {
|
||||||
if ($uploadedFile instanceof UploadedFile) {
|
if ($uploadedFile instanceof UploadedFile) {
|
||||||
$this->compressVideo($uploadedFile);
|
$this->compressVideo($uploadedFile);
|
||||||
$file = new EtatLieuxFile();
|
if($etatLieux->getStatus() == "return_edl_progress") {
|
||||||
$file->setFile($uploadedFile);
|
$file = new EtatReturnLieuxFile();
|
||||||
$file->setType('video');
|
$file->setFile($uploadedFile);
|
||||||
$file->setEtatLieux($etatLieux);
|
$file->setType('video');
|
||||||
$em->persist($file);
|
$file->setEtatLieux($etatLieux);
|
||||||
$hasFiles = true;
|
$em->persist($file);
|
||||||
|
$hasFiles = true;
|
||||||
|
} else {
|
||||||
|
$file = new EtatLieuxFile();
|
||||||
|
$file->setFile($uploadedFile);
|
||||||
|
$file->setType('video');
|
||||||
|
$file->setEtatLieux($etatLieux);
|
||||||
|
$em->persist($file);
|
||||||
|
$hasFiles = true;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -587,8 +637,11 @@ class EtlController extends AbstractController
|
|||||||
|
|
||||||
$etatLieux = $contrat->getEtatLieux();
|
$etatLieux = $contrat->getEtatLieux();
|
||||||
|
|
||||||
|
if($etatLieux->getStatus() == "return_edl_progress") {
|
||||||
|
$file = $em->getRepository(EtatReturnLieuxFile::class)->find($fileId);
|
||||||
|
} else {
|
||||||
$file = $em->getRepository(EtatLieuxFile::class)->find($fileId);
|
$file = $em->getRepository(EtatLieuxFile::class)->find($fileId);
|
||||||
|
}
|
||||||
|
|
||||||
if ($file && $file->getEtatLieux() === $etatLieux) {
|
if ($file && $file->getEtatLieux() === $etatLieux) {
|
||||||
|
|
||||||
@@ -615,11 +668,19 @@ class EtlController extends AbstractController
|
|||||||
}
|
}
|
||||||
|
|
||||||
$etatLieux = $contrat->getEtatLieux();
|
$etatLieux = $contrat->getEtatLieux();
|
||||||
if ($etatLieux) {
|
if($etatLieux->getStatus() == "return_edl_progress") {
|
||||||
$etatLieux->setStatus('edl_done');
|
$etatLieux->setStatus('edl_return_done');
|
||||||
$this->generateAndSendToDocuSeal($contrat, $em, $kernel, $signatureClient);
|
$this->generateAndSendToDocuSealReturn($contrat, $em, $kernel, $signatureClient);
|
||||||
$this->addFlash('success', 'État des lieux terminé et PDF généré.');
|
$this->addFlash('success', 'État des lieux terminé et PDF généré.');
|
||||||
return $this->redirectToRoute('etl_mission_signed_entry_state', ['id' => $contrat->getId()]);
|
return $this->redirectToRoute('etl_mission_signed_entry_state', ['id' => $contrat->getId()]);
|
||||||
|
} else {
|
||||||
|
if ($etatLieux) {
|
||||||
|
$etatLieux->setStatus('edl_done');
|
||||||
|
$this->generateAndSendToDocuSeal($contrat, $em, $kernel, $signatureClient);
|
||||||
|
|
||||||
|
$this->addFlash('success', 'État des lieux terminé et PDF généré.');
|
||||||
|
return $this->redirectToRoute('etl_mission_signed_entry_state', ['id' => $contrat->getId()]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->redirectToRoute('etl_contrat_view', ['id' => $contrat->getId()]);
|
return $this->redirectToRoute('etl_contrat_view', ['id' => $contrat->getId()]);
|
||||||
@@ -655,10 +716,16 @@ class EtlController extends AbstractController
|
|||||||
return $this->redirectToRoute('etl_login');
|
return $this->redirectToRoute('etl_login');
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->generateAndSendToDocuSeal($contrat, $em, $kernel, $signatureClient);
|
if($contrat->getEtatLieux()->getStatus() == "edl_return_done") {
|
||||||
|
$this->generateAndSendToDocuSealReturn($contrat, $em, $kernel, $signatureClient);
|
||||||
|
$etatLieux = $contrat->getEtatLieux();
|
||||||
|
$path = $uploaderHelper->asset($etatLieux, 'etatLieuxUnsignReturnFile');
|
||||||
|
} else {
|
||||||
|
$this->generateAndSendToDocuSeal($contrat, $em, $kernel, $signatureClient);
|
||||||
|
$etatLieux = $contrat->getEtatLieux();
|
||||||
|
$path = $uploaderHelper->asset($etatLieux, 'etatLieuxUnsignFile');
|
||||||
|
}
|
||||||
|
|
||||||
$etatLieux = $contrat->getEtatLieux();
|
|
||||||
$path = $uploaderHelper->asset($etatLieux, 'etatLieuxUnsignFile');
|
|
||||||
|
|
||||||
return new RedirectResponse($path);
|
return new RedirectResponse($path);
|
||||||
}
|
}
|
||||||
@@ -703,23 +770,40 @@ class EtlController extends AbstractController
|
|||||||
|
|
||||||
$providerSigned = false;
|
$providerSigned = false;
|
||||||
$customerSigned = false;
|
$customerSigned = false;
|
||||||
|
if($contrat->getEtatLieux()->getStatus() == "edl_return_done") {
|
||||||
|
if ($etatLieux->getSignIdReturn()) {
|
||||||
|
try {
|
||||||
|
$sub = $signatureClient->getSubmiter($etatLieux->getSignIdReturn());
|
||||||
|
if ($sub && ($sub['status'] ?? '') === 'completed') $providerSigned = true;
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if ($etatLieux->getSignIdDelivery()) {
|
if ($etatLieux->getSignIdCustomerReturn()) {
|
||||||
try {
|
try {
|
||||||
$sub = $signatureClient->getSubmiter($etatLieux->getSignIdDelivery());
|
$sub = $signatureClient->getSubmiter($etatLieux->getSignIdCustomerReturn());
|
||||||
if ($sub && ($sub['status'] ?? '') === 'completed') $providerSigned = true;
|
if ($sub && ($sub['status'] ?? '') === 'completed') $customerSigned = true;
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
|
||||||
|
if ($etatLieux->getSignIdDelivery()) {
|
||||||
|
try {
|
||||||
|
$sub = $signatureClient->getSubmiter($etatLieux->getSignIdDelivery());
|
||||||
|
if ($sub && ($sub['status'] ?? '') === 'completed') $providerSigned = true;
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($etatLieux->getSignIdCustomer()) {
|
||||||
|
try {
|
||||||
|
$sub = $signatureClient->getSubmiter($etatLieux->getSignIdCustomer());
|
||||||
|
if ($sub && ($sub['status'] ?? '') === 'completed') $customerSigned = true;
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($etatLieux->getSignIdCustomer()) {
|
|
||||||
try {
|
|
||||||
$sub = $signatureClient->getSubmiter($etatLieux->getSignIdCustomer());
|
|
||||||
if ($sub && ($sub['status'] ?? '') === 'completed') $customerSigned = true;
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->render('etl/signed_entry_state.twig', [
|
return $this->render('etl/signed_entry_state.twig', [
|
||||||
'mission' => $contrat,
|
'mission' => $contrat,
|
||||||
'etatLieux' => $etatLieux,
|
'etatLieux' => $etatLieux,
|
||||||
@@ -974,4 +1058,84 @@ class EtlController extends AbstractController
|
|||||||
|
|
||||||
return $file;
|
return $file;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function generateAndSendToDocuSealReturn(Contrats $contrat, EntityManagerInterface $em, KernelInterface $kernel, SignatureClient $signatureClient)
|
||||||
|
{
|
||||||
|
$etatLieux = $contrat->getEtatLieux();
|
||||||
|
|
||||||
|
// Generate PDF
|
||||||
|
$pdfService = new EtatLieuxPdfService($kernel, $contrat);
|
||||||
|
$pdfContent = $pdfService->generate();
|
||||||
|
|
||||||
|
|
||||||
|
// Save PDF
|
||||||
|
$tmpPath = sys_get_temp_dir() . '/edl_sortant_' . $contrat->getId() . '_' . uniqid() . '.pdf';
|
||||||
|
file_put_contents($tmpPath, $pdfContent);
|
||||||
|
|
||||||
|
// Update entity with file
|
||||||
|
$file = new UploadedFile($tmpPath, 'edl_sortant_.pdf', 'application/pdf', null, true);
|
||||||
|
$etatLieux->setEtatLieuxUnsignReturnFile($file);
|
||||||
|
$etatLieux->setUpdatedAt(new \DateTimeImmutable());
|
||||||
|
|
||||||
|
$em->persist($etatLieux);
|
||||||
|
$em->flush(); // Save file
|
||||||
|
|
||||||
|
// Send to DocuSeal
|
||||||
|
try {
|
||||||
|
$signatureClient->createSubmissionEtatLieuxSortant($etatLieux);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
// Fallback
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Route('/etl/mission/{id}/edl/refused', name: 'etl_edl_customer_refused', methods: ['POST'])]
|
||||||
|
public function eltEdlCustomerRefused(
|
||||||
|
Contrats $contrat,
|
||||||
|
Request $request,
|
||||||
|
EntityManagerInterface $em,
|
||||||
|
Mailer $mailer
|
||||||
|
): Response {
|
||||||
|
$user = $this->getUser();
|
||||||
|
if (!$user) {
|
||||||
|
return $this->redirectToRoute('etl_login');
|
||||||
|
}
|
||||||
|
|
||||||
|
$etatLieux = $contrat->getEtatLieux();
|
||||||
|
|
||||||
|
// On vérifie que l'on est bien en phase de retour
|
||||||
|
if (!$etatLieux || $etatLieux->getStatus() !== 'edl_return_done') {
|
||||||
|
$this->addFlash('error', 'Action impossible dans l\'état actuel.');
|
||||||
|
return $this->redirectToRoute('etl_mission_signed_entry_state', ['id' => $contrat->getId()]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Récupération de la raison saisie
|
||||||
|
$reason = $request->request->get('refusal_reason');
|
||||||
|
|
||||||
|
// Mise à jour de l'entité
|
||||||
|
// Note : Assurez-vous d'avoir le champ raisonRefus dans votre entité EtatLieux
|
||||||
|
$etatLieux->setRaisonRefused($reason);
|
||||||
|
$etatLieux->setStatus('edl_return_refused'); // Nouveau status pour différencier
|
||||||
|
|
||||||
|
// On clôture tout de même la mission côté réservation
|
||||||
|
$contrat->setReservationState('finished');
|
||||||
|
|
||||||
|
$em->flush();
|
||||||
|
|
||||||
|
// Notification par email (Optionnel mais recommandé)
|
||||||
|
$mailer->send(
|
||||||
|
'contact@ludikevent.fr',
|
||||||
|
'Admin Ludikevent',
|
||||||
|
"Signature refusée par le client - #" . $contrat->getNumReservation(),
|
||||||
|
"mails/etl/edl_refused_alert.twig",
|
||||||
|
[
|
||||||
|
'contrat' => $contrat,
|
||||||
|
'reason' => $reason,
|
||||||
|
'prestataire' => $user
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->addFlash('warning', 'Le refus a été enregistré. La mission est clôturée avec mention de litige.');
|
||||||
|
|
||||||
|
return $this->redirectToRoute('etl_contrat_view', ['id' => $contrat->getId()]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ namespace App\Entity;
|
|||||||
use App\Repository\EtatLieuxRepository;
|
use App\Repository\EtatLieuxRepository;
|
||||||
use Doctrine\Common\Collections\ArrayCollection;
|
use Doctrine\Common\Collections\ArrayCollection;
|
||||||
use Doctrine\Common\Collections\Collection;
|
use Doctrine\Common\Collections\Collection;
|
||||||
|
use Doctrine\DBAL\Types\Types;
|
||||||
use Doctrine\ORM\Mapping as ORM;
|
use Doctrine\ORM\Mapping as ORM;
|
||||||
use Symfony\Component\HttpFoundation\File\File;
|
use Symfony\Component\HttpFoundation\File\File;
|
||||||
use Vich\UploaderBundle\Mapping\Attribute\Uploadable;
|
use Vich\UploaderBundle\Mapping\Attribute\Uploadable;
|
||||||
@@ -37,17 +38,31 @@ class EtatLieux
|
|||||||
#[ORM\OneToMany(targetEntity: EtatLieuxFile::class, mappedBy: 'etatLieux', cascade: ['persist', 'remove'])]
|
#[ORM\OneToMany(targetEntity: EtatLieuxFile::class, mappedBy: 'etatLieux', cascade: ['persist', 'remove'])]
|
||||||
private Collection $files;
|
private Collection $files;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var Collection<int, EtatReturnLieuxFile>
|
||||||
|
*/
|
||||||
|
#[ORM\OneToMany(targetEntity: EtatReturnLieuxFile::class, mappedBy: 'etatLieux', cascade: ['persist', 'remove'])]
|
||||||
|
private Collection $filesReturn;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var Collection<int, EtatLieuxComment>
|
* @var Collection<int, EtatLieuxComment>
|
||||||
*/
|
*/
|
||||||
#[ORM\OneToMany(targetEntity: EtatLieuxComment::class, mappedBy: 'etatLieux', cascade: ['persist', 'remove'])]
|
#[ORM\OneToMany(targetEntity: EtatLieuxComment::class, mappedBy: 'etatLieux', cascade: ['persist', 'remove'])]
|
||||||
private Collection $comments;
|
private Collection $comments;
|
||||||
|
|
||||||
|
#[ORM\OneToMany(targetEntity: EtatLieuxReturnComment::class, mappedBy: 'etatLieux', cascade: ['persist', 'remove'])]
|
||||||
|
private Collection $commentsReturns;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var Collection<int, EtatLieuxPointControl>
|
* @var Collection<int, EtatLieuxPointControl>
|
||||||
*/
|
*/
|
||||||
#[ORM\OneToMany(targetEntity: EtatLieuxPointControl::class, mappedBy: 'etatLieux', cascade: ['persist', 'remove'])]
|
#[ORM\OneToMany(targetEntity: EtatLieuxPointControl::class, mappedBy: 'etatLieux', cascade: ['persist', 'remove'])]
|
||||||
private Collection $pointControls;
|
private Collection $pointControls;
|
||||||
|
/**
|
||||||
|
* @var Collection<int, EtatLieuxPointControl>
|
||||||
|
*/
|
||||||
|
#[ORM\OneToMany(targetEntity: EtatLieuxReturnPointControl::class, mappedBy: 'etatLieux', cascade: ['persist', 'remove'])]
|
||||||
|
private Collection $pointControlsReturn;
|
||||||
|
|
||||||
#[ORM\Column(length: 255, nullable: true)]
|
#[ORM\Column(length: 255, nullable: true)]
|
||||||
private ?string $signIdDelivery = null;
|
private ?string $signIdDelivery = null;
|
||||||
@@ -122,11 +137,17 @@ class EtatLieux
|
|||||||
#[ORM\Column(nullable: true)]
|
#[ORM\Column(nullable: true)]
|
||||||
private ?\DateTimeImmutable $updatedAt = null;
|
private ?\DateTimeImmutable $updatedAt = null;
|
||||||
|
|
||||||
|
#[ORM\Column(type: Types::TEXT, nullable: true)]
|
||||||
|
private ?string $raisonRefused = null;
|
||||||
|
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
$this->files = new ArrayCollection();
|
$this->files = new ArrayCollection();
|
||||||
|
$this->filesReturn = new ArrayCollection();
|
||||||
$this->comments = new ArrayCollection();
|
$this->comments = new ArrayCollection();
|
||||||
|
$this->commentsReturns = new ArrayCollection();
|
||||||
$this->pointControls = new ArrayCollection();
|
$this->pointControls = new ArrayCollection();
|
||||||
|
$this->pointControlsReturn = new ArrayCollection();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getId(): ?int
|
public function getId(): ?int
|
||||||
@@ -164,6 +185,37 @@ class EtatLieux
|
|||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Collection<int, EtatLieuxFile>
|
||||||
|
*/
|
||||||
|
public function getFileReturn(): Collection
|
||||||
|
{
|
||||||
|
return $this->filesReturn;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addFileReturn(EtatReturnLieuxFile $file): static
|
||||||
|
{
|
||||||
|
if (!$this->filesReturn->contains($file)) {
|
||||||
|
$this->filesReturn->add($file);
|
||||||
|
$file->setEtatLieux($this);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function removeFileReturn(EtatReturnLieuxFile $file): static
|
||||||
|
{
|
||||||
|
if ($this->filesReturn->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;
|
||||||
@@ -241,6 +293,93 @@ class EtatLieux
|
|||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @return Collection<int, EtatReturnLieuxFile>
|
||||||
|
*/
|
||||||
|
public function getEtatReturnLieuxFile(): Collection
|
||||||
|
{
|
||||||
|
return $this->filesReturn;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addEtatReturnLieuxFile(EtatReturnLieuxFile $comment): static
|
||||||
|
{
|
||||||
|
if (!$this->filesReturn->contains($comment)) {
|
||||||
|
$this->filesReturn->add($comment);
|
||||||
|
$comment->setEtatLieux($this);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function removeEtatReturnLieuxFile(EtatReturnLieuxFile $comment): static
|
||||||
|
{
|
||||||
|
if ($this->filesReturn->removeElement($comment)) {
|
||||||
|
if ($comment->getEtatLieux() === $this) {
|
||||||
|
$comment->setEtatLieux(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @return Collection<int, EtatLieuxReturnPointControl>
|
||||||
|
*/
|
||||||
|
public function getPointControlsReturn(): Collection
|
||||||
|
{
|
||||||
|
return $this->pointControlsReturn;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addPointControlsReturn(EtatLieuxReturnPointControl $comment): static
|
||||||
|
{
|
||||||
|
if (!$this->pointControlsReturn->contains($comment)) {
|
||||||
|
$this->pointControlsReturn->add($comment);
|
||||||
|
$comment->setEtatLieux($this);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function removeEtatLieuxReturnPointControl(EtatLieuxReturnPointControl $comment): static
|
||||||
|
{
|
||||||
|
if ($this->pointControlsReturn->removeElement($comment)) {
|
||||||
|
if ($comment->getEtatLieux() === $this) {
|
||||||
|
$comment->setEtatLieux(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Collection<int, EtatLieuxReturnComment>
|
||||||
|
*/
|
||||||
|
public function getCommentsReturn(): Collection
|
||||||
|
{
|
||||||
|
return $this->commentsReturns;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addCommentReturn(EtatLieuxReturnComment $comment): static
|
||||||
|
{
|
||||||
|
if (!$this->commentsReturns->contains($comment)) {
|
||||||
|
$this->commentsReturns->add($comment);
|
||||||
|
$comment->setEtatLieux($this);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function removeCommentReturn(EtatLieuxReturnComment $comment): static
|
||||||
|
{
|
||||||
|
if ($this->commentsReturns->removeElement($comment)) {
|
||||||
|
if ($comment->getEtatLieux() === $this) {
|
||||||
|
$comment->setEtatLieux(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
public function getSignIdDelivery(): ?string
|
public function getSignIdDelivery(): ?string
|
||||||
{
|
{
|
||||||
return $this->signIdDelivery;
|
return $this->signIdDelivery;
|
||||||
@@ -527,4 +666,16 @@ class EtatLieux
|
|||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getRaisonRefused(): ?string
|
||||||
|
{
|
||||||
|
return $this->raisonRefused;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setRaisonRefused(?string $raisonRefused): static
|
||||||
|
{
|
||||||
|
$this->raisonRefused = $raisonRefused;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
72
src/Entity/EtatLieuxReturnComment.php
Normal file
72
src/Entity/EtatLieuxReturnComment.php
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Entity;
|
||||||
|
|
||||||
|
use App\Repository\EtatLieuxReturnCommentRepository;
|
||||||
|
use Doctrine\DBAL\Types\Types;
|
||||||
|
use Doctrine\ORM\Mapping as ORM;
|
||||||
|
|
||||||
|
#[ORM\Entity(repositoryClass: EtatLieuxReturnCommentRepository::class)]
|
||||||
|
class EtatLieuxReturnComment
|
||||||
|
{
|
||||||
|
#[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: 'commentsReturns')]
|
||||||
|
#[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;
|
||||||
|
}
|
||||||
|
}
|
||||||
82
src/Entity/EtatLieuxReturnPointControl.php
Normal file
82
src/Entity/EtatLieuxReturnPointControl.php
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Entity;
|
||||||
|
|
||||||
|
use App\Repository\EtatLieuxReturnPointControlRepository;
|
||||||
|
use Doctrine\DBAL\Types\Types;
|
||||||
|
use Doctrine\ORM\Mapping as ORM;
|
||||||
|
|
||||||
|
#[ORM\Entity(repositoryClass: EtatLieuxReturnPointControlRepository::class)]
|
||||||
|
class EtatLieuxReturnPointControl
|
||||||
|
{
|
||||||
|
#[ORM\Id]
|
||||||
|
#[ORM\GeneratedValue]
|
||||||
|
#[ORM\Column]
|
||||||
|
private ?int $id = null;
|
||||||
|
|
||||||
|
#[ORM\Column(length: 255)]
|
||||||
|
private ?string $name = null;
|
||||||
|
|
||||||
|
#[ORM\Column(options: ['default' => false])]
|
||||||
|
private ?bool $status = false;
|
||||||
|
|
||||||
|
#[ORM\Column(type: Types::TEXT, nullable: true)]
|
||||||
|
private ?string $details = null;
|
||||||
|
|
||||||
|
#[ORM\ManyToOne(inversedBy: 'pointControlsReturn')]
|
||||||
|
#[ORM\JoinColumn(nullable: false)]
|
||||||
|
private ?EtatLieux $etatLieux = null;
|
||||||
|
|
||||||
|
public function getId(): ?int
|
||||||
|
{
|
||||||
|
return $this->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getName(): ?string
|
||||||
|
{
|
||||||
|
return $this->name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setName(string $name): static
|
||||||
|
{
|
||||||
|
$this->name = $name;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isStatus(): ?bool
|
||||||
|
{
|
||||||
|
return $this->status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setStatus(bool $status): static
|
||||||
|
{
|
||||||
|
$this->status = $status;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDetails(): ?string
|
||||||
|
{
|
||||||
|
return $this->details;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setDetails(?string $details): static
|
||||||
|
{
|
||||||
|
$this->details = $details;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getEtatLieux(): ?EtatLieux
|
||||||
|
{
|
||||||
|
return $this->etatLieux;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setEtatLieux(?EtatLieux $etatLieux): static
|
||||||
|
{
|
||||||
|
$this->etatLieux = $etatLieux;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
}
|
||||||
130
src/Entity/EtatReturnLieuxFile.php
Normal file
130
src/Entity/EtatReturnLieuxFile.php
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Entity;
|
||||||
|
|
||||||
|
use App\Repository\EtatLieuxReturnFileRepository;
|
||||||
|
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: EtatLieuxReturnFileRepository::class)]
|
||||||
|
#[Uploadable]
|
||||||
|
class EtatReturnLieuxFile
|
||||||
|
{
|
||||||
|
#[ORM\Id]
|
||||||
|
#[ORM\GeneratedValue]
|
||||||
|
#[ORM\Column]
|
||||||
|
private ?int $id = null;
|
||||||
|
|
||||||
|
#[ORM\ManyToOne(inversedBy: 'filesReturn')]
|
||||||
|
#[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;
|
||||||
|
}
|
||||||
|
}
|
||||||
24
src/Repository/EtatLieuxReturnCommentRepository.php
Normal file
24
src/Repository/EtatLieuxReturnCommentRepository.php
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Repository;
|
||||||
|
|
||||||
|
use App\Entity\EtatLieuxComment;
|
||||||
|
use App\Entity\EtatLieuxReturnComment;
|
||||||
|
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 EtatLieuxReturnCommentRepository extends ServiceEntityRepository
|
||||||
|
{
|
||||||
|
public function __construct(ManagerRegistry $registry)
|
||||||
|
{
|
||||||
|
parent::__construct($registry, EtatLieuxReturnComment::class);
|
||||||
|
}
|
||||||
|
}
|
||||||
23
src/Repository/EtatLieuxReturnFileRepository.php
Normal file
23
src/Repository/EtatLieuxReturnFileRepository.php
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Repository;
|
||||||
|
|
||||||
|
use App\Entity\EtatLieuxReturnFile;
|
||||||
|
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||||
|
use Doctrine\Persistence\ManagerRegistry;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @extends ServiceEntityRepository<EtatLieuxReturnFile>
|
||||||
|
*
|
||||||
|
* @method EtatLieuxReturnFile|null find($id, $lockMode = null, $lockVersion = null)
|
||||||
|
* @method EtatLieuxReturnFile|null findOneBy(array $criteria, array $orderBy = null)
|
||||||
|
* @method EtatLieuxReturnFile[] findAll()
|
||||||
|
* @method EtatLieuxReturnFile[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
|
||||||
|
*/
|
||||||
|
class EtatLieuxReturnFileRepository extends ServiceEntityRepository
|
||||||
|
{
|
||||||
|
public function __construct(ManagerRegistry $registry)
|
||||||
|
{
|
||||||
|
parent::__construct($registry, EtatLieuxReturnFile::class);
|
||||||
|
}
|
||||||
|
}
|
||||||
23
src/Repository/EtatLieuxReturnPointControlRepository.php
Normal file
23
src/Repository/EtatLieuxReturnPointControlRepository.php
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Repository;
|
||||||
|
|
||||||
|
use App\Entity\EtatLieuxReturnPointControl;
|
||||||
|
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||||
|
use Doctrine\Persistence\ManagerRegistry;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @extends ServiceEntityRepository<EtatLieuxReturnPointControl>
|
||||||
|
*
|
||||||
|
* @method EtatLieuxReturnPointControl|null find($id, $lockMode = null, $lockVersion = null)
|
||||||
|
* @method EtatLieuxReturnPointControl|null findOneBy(array $criteria, array $orderBy = null)
|
||||||
|
* @method EtatLieuxReturnPointControl[] findAll()
|
||||||
|
* @method EtatLieuxReturnPointControl[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
|
||||||
|
*/
|
||||||
|
class EtatLieuxReturnPointControlRepository extends ServiceEntityRepository
|
||||||
|
{
|
||||||
|
public function __construct(ManagerRegistry $registry)
|
||||||
|
{
|
||||||
|
parent::__construct($registry, EtatLieuxReturnPointControl::class);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -31,46 +31,50 @@ class EtatLieuxPdfService extends Fpdf
|
|||||||
{
|
{
|
||||||
$this->AddPage();
|
$this->AddPage();
|
||||||
$this->renderEtatLieuxEntrant();
|
$this->renderEtatLieuxEntrant();
|
||||||
|
|
||||||
$this->renderSignaturePage();
|
$this->renderSignaturePage();
|
||||||
|
|
||||||
return $this->Output('S');
|
return $this->Output('S');
|
||||||
}
|
}
|
||||||
|
|
||||||
private function renderSignaturePage(): void
|
private function renderSignaturePage(): void
|
||||||
{
|
{
|
||||||
$this->AddPage();
|
$this->AddPage();
|
||||||
|
|
||||||
$this->SetFont('Arial', 'B', 14);
|
$this->SetFont('Arial', 'B', 14);
|
||||||
$this->SetTextColor(37, 99, 235);
|
$this->SetTextColor(37, 99, 235);
|
||||||
$this->Cell(0, 10, $this->clean("SIGNATURES"), 0, 1, 'C');
|
$this->Cell(0, 10, $this->clean("SIGNATURES"), 0, 1, 'C');
|
||||||
$this->Ln(10);
|
$this->Ln(10);
|
||||||
|
|
||||||
$this->SetFont('Arial', '', 10);
|
$this->SetFont('Arial', '', 10);
|
||||||
$this->SetTextColor(0, 0, 0);
|
$this->SetTextColor(0, 0, 0);
|
||||||
$this->MultiCell(0, 5, $this->clean("En signant ce document, les parties valident l'état des lieux d'installation ci-dessus."), 0, 'C');
|
if($this->contrats->getEtatLieux()->getStatus() == "edl_return_done"){
|
||||||
|
$this->MultiCell(0, 5, $this->clean("En signant ce document, les parties valident l'état de retour ci-dessus."), 0, 'C');
|
||||||
|
} else {
|
||||||
|
$this->MultiCell(0, 5, $this->clean("En signant ce document, les parties valident l'état des lieux d'installation ci-dessus."), 0, 'C');
|
||||||
|
}
|
||||||
$this->Ln(20);
|
$this->Ln(20);
|
||||||
|
|
||||||
// --- SIGNATURES ---
|
// --- SIGNATURES ---
|
||||||
$ySign = $this->GetY();
|
$ySign = $this->GetY();
|
||||||
|
|
||||||
$this->SetFont('Arial', 'B', 10);
|
$this->SetFont('Arial', 'B', 10);
|
||||||
$this->Cell(95, 8, $this->clean("Le Prestataire"), 0, 0, 'C');
|
$this->Cell(95, 8, $this->clean("Le Prestataire"), 0, 0, 'C');
|
||||||
$this->Cell(95, 8, $this->clean("Le Client (Bon pour accord)"), 0, 1, 'C');
|
$this->Cell(95, 8, $this->clean("Le Client (Bon pour accord)"), 0, 1, 'C');
|
||||||
|
|
||||||
$this->Ln(8);
|
$this->Ln(8);
|
||||||
$this->Cell(95, 40, "", 1, 0);
|
$this->Cell(95, 40, "", 1, 0);
|
||||||
$this->Cell(95, 40, "", 1, 1);
|
$this->Cell(95, 40, "", 1, 1);
|
||||||
|
|
||||||
// DocuSeal tags
|
// DocuSeal tags
|
||||||
$this->SetXY(20, $ySign + 35);
|
$this->SetXY(20, $ySign + 35);
|
||||||
$this->SetFont('Arial', '', 8);
|
$this->SetFont('Arial', '', 8);
|
||||||
$this->SetTextColor(255, 255, 255);
|
$this->SetTextColor(255, 255, 255);
|
||||||
$this->Cell(50, 5, '{{Sign;type=signature;role=Ludikevent}}', 0, 0);
|
$this->Cell(50, 5, '{{Sign;type=signature;role=Ludikevent}}', 0, 0);
|
||||||
|
|
||||||
$this->SetXY(115, $ySign + 35);
|
$this->SetXY(115, $ySign + 35);
|
||||||
$this->Cell(50, 5, '{{Sign;type=signature;role=Client}}', 0, 0);
|
$this->Cell(50, 5, '{{Sign;type=signature;role=Client}}', 0, 0);
|
||||||
|
|
||||||
$this->SetTextColor(0, 0, 0);
|
$this->SetTextColor(0, 0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,19 +85,24 @@ class EtatLieuxPdfService extends Fpdf
|
|||||||
// Titre
|
// Titre
|
||||||
$this->SetFont('Arial', 'B', 14);
|
$this->SetFont('Arial', 'B', 14);
|
||||||
$this->SetTextColor(37, 99, 235);
|
$this->SetTextColor(37, 99, 235);
|
||||||
$this->Cell(0, 10, $this->clean("ÉTAT DES LIEUX D'INSTALLATION (ENTRANT)"), 0, 1, 'C');
|
if($this->contrats->getEtatLieux()->getStatus() == "edl_return_done"){
|
||||||
|
$this->Cell(0, 10, $this->clean("ÉTAT DES RETOUR"), 0, 1, 'C');
|
||||||
|
} else {
|
||||||
|
$this->Cell(0, 10, $this->clean("ÉTAT DES LIEUX D'INSTALLATION (ENTRANT)"), 0, 1, 'C');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
$this->SetFont('Arial', 'B', 10);
|
$this->SetFont('Arial', 'B', 10);
|
||||||
$this->SetTextColor(0, 0, 0);
|
$this->SetTextColor(0, 0, 0);
|
||||||
$this->Cell(0, 7, $this->clean("Contrat N° " . $this->contrats->getNumReservation()), 0, 1, 'C');
|
$this->Cell(0, 7, $this->clean("Contrat N° " . $this->contrats->getNumReservation()), 0, 1, 'C');
|
||||||
$this->Ln(5);
|
$this->Ln(5);
|
||||||
|
|
||||||
$this->SetFont('Arial', '', 10);
|
$this->SetFont('Arial', '', 10);
|
||||||
$this->MultiCell(0, 5, $this->clean("Le locataire reconnaît avoir reçu le matériel ci-dessous en bon état de fonctionnement, propre et conforme à la commande."), 0, 'C');
|
$this->MultiCell(0, 5, $this->clean("Le locataire reconnaît avoir lue et accepter l'état des retours."), 0, 'C');
|
||||||
$this->Ln(5);
|
$this->Ln(5);
|
||||||
|
|
||||||
// --- INFO PARTIES (2 BLOCS SÉPARÉS) ---
|
// --- INFO PARTIES (2 BLOCS SÉPARÉS) ---
|
||||||
|
|
||||||
// Detect Delivery/Installation
|
// Detect Delivery/Installation
|
||||||
$hasDelivery = false;
|
$hasDelivery = false;
|
||||||
foreach ($this->contrats->getContratsLines() as $line) {
|
foreach ($this->contrats->getContratsLines() as $line) {
|
||||||
@@ -117,37 +126,37 @@ class EtatLieuxPdfService extends Fpdf
|
|||||||
$colWidth = 90;
|
$colWidth = 90;
|
||||||
$gap = 10;
|
$gap = 10;
|
||||||
$xStart = 10;
|
$xStart = 10;
|
||||||
|
|
||||||
$this->SetFillColor(245, 245, 245);
|
$this->SetFillColor(245, 245, 245);
|
||||||
$this->SetFont('Arial', 'B', 9);
|
$this->SetFont('Arial', 'B', 9);
|
||||||
|
|
||||||
// Header Block 1
|
// Header Block 1
|
||||||
$this->Cell($colWidth, 6, $this->clean($prestataireTitle), 1, 0, 'L', true);
|
$this->Cell($colWidth, 6, $this->clean($prestataireTitle), 1, 0, 'L', true);
|
||||||
|
|
||||||
// Header Block 2
|
// Header Block 2
|
||||||
$this->SetX($xStart + $colWidth + $gap);
|
$this->SetX($xStart + $colWidth + $gap);
|
||||||
$this->Cell($colWidth, 6, $this->clean(" CLIENT / LIEU"), 1, 1, 'L', true);
|
$this->Cell($colWidth, 6, $this->clean(" CLIENT / LIEU"), 1, 1, 'L', true);
|
||||||
|
|
||||||
$this->SetFont('Arial', '', 8);
|
$this->SetFont('Arial', '', 8);
|
||||||
$yContent = $this->GetY();
|
$yContent = $this->GetY();
|
||||||
|
|
||||||
// Content Block 1 (Prestataire)
|
// Content Block 1 (Prestataire)
|
||||||
$this->SetXY($xStart, $yContent);
|
$this->SetXY($xStart, $yContent);
|
||||||
$prestataire = $this->contrats->getPrestataire();
|
$prestataire = $this->contrats->getPrestataire();
|
||||||
$prestataireTxt = $prestataire ? ($prestataire->getName() . "\n" . $prestataire->getEmail()) : "Ludikevent (Admin)\ncontact@ludikevent.fr";
|
$prestataireTxt = $prestataire ? ($prestataire->getName() . "\n" . $prestataire->getEmail()) : "Ludikevent (Admin)\ncontact@ludikevent.fr";
|
||||||
$this->MultiCell($colWidth, 5, $this->clean($prestataireTxt), 'LRB', 'L');
|
$this->MultiCell($colWidth, 5, $this->clean($prestataireTxt), 'LRB', 'L');
|
||||||
$h1 = $this->GetY() - $yContent;
|
$h1 = $this->GetY() - $yContent;
|
||||||
|
|
||||||
// Content Block 2 (Client)
|
// Content Block 2 (Client)
|
||||||
$this->SetXY($xStart + $colWidth + $gap, $yContent);
|
$this->SetXY($xStart + $colWidth + $gap, $yContent);
|
||||||
$customer = $this->contrats->getCustomer();
|
$customer = $this->contrats->getCustomer();
|
||||||
$clientTxt = $customer->getName() . " " . $customer->getSurname() . "\n" .
|
$clientTxt = $customer->getName() . " " . $customer->getSurname() . "\n" .
|
||||||
$customer->getEmail() . "\n" .
|
$customer->getEmail() . "\n" .
|
||||||
$customer->getPhone() . "\n" .
|
$customer->getPhone() . "\n" .
|
||||||
"Lieu: " . $this->contrats->getAddressEvent() . " " . $this->contrats->getZipCodeEvent() . " " . $this->contrats->getTownEvent();
|
"Lieu: " . $this->contrats->getAddressEvent() . " " . $this->contrats->getZipCodeEvent() . " " . $this->contrats->getTownEvent();
|
||||||
$this->MultiCell($colWidth, 5, $this->clean($clientTxt), 'LRB', 'L');
|
$this->MultiCell($colWidth, 5, $this->clean($clientTxt), 'LRB', 'L');
|
||||||
$h2 = $this->GetY() - $yContent;
|
$h2 = $this->GetY() - $yContent;
|
||||||
|
|
||||||
// Reset Y to max height
|
// Reset Y to max height
|
||||||
$this->SetY($yContent + max($h1, $h2) + 5);
|
$this->SetY($yContent + max($h1, $h2) + 5);
|
||||||
|
|
||||||
@@ -158,7 +167,7 @@ class EtatLieuxPdfService extends Fpdf
|
|||||||
$this->Cell(190, 8, $this->clean(" DÉSIGNATION DU MATÉRIEL"), 1, 1, 'L', true);
|
$this->Cell(190, 8, $this->clean(" DÉSIGNATION DU MATÉRIEL"), 1, 1, 'L', true);
|
||||||
|
|
||||||
$this->SetFont('Arial', '', 9);
|
$this->SetFont('Arial', '', 9);
|
||||||
|
|
||||||
foreach ($this->contrats->getContratsLines() as $line) {
|
foreach ($this->contrats->getContratsLines() as $line) {
|
||||||
// Skip livraison
|
// Skip livraison
|
||||||
if (stripos($line->getName(), 'livraison') !== false) {
|
if (stripos($line->getName(), 'livraison') !== false) {
|
||||||
@@ -174,7 +183,7 @@ class EtatLieuxPdfService extends Fpdf
|
|||||||
if (stripos($opt->getName(), 'livraison') !== false) {
|
if (stripos($opt->getName(), 'livraison') !== false) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->MultiCell(190, 8, $this->clean("[Option] " . $opt->getName()), 1, 'L');
|
$this->MultiCell(190, 8, $this->clean("[Option] " . $opt->getName()), 1, 'L');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -191,16 +200,16 @@ class EtatLieuxPdfService extends Fpdf
|
|||||||
foreach ($this->contrats->getProductReserves() as $reserve) {
|
foreach ($this->contrats->getProductReserves() as $reserve) {
|
||||||
$product = $reserve->getProduct();
|
$product = $reserve->getProduct();
|
||||||
if ($product && count($product->getProductPointControlls()) > 0) {
|
if ($product && count($product->getProductPointControlls()) > 0) {
|
||||||
|
|
||||||
$this->SetFillColor(230, 240, 255);
|
$this->SetFillColor(230, 240, 255);
|
||||||
$this->SetFont('Arial', 'B', 9);
|
$this->SetFont('Arial', 'B', 9);
|
||||||
$this->Cell(190, 6, $this->clean(" " . $product->getName()), 1, 1, 'L', true);
|
$this->Cell(190, 6, $this->clean(" " . $product->getName()), 1, 1, 'L', true);
|
||||||
|
|
||||||
$this->SetFillColor(250, 250, 250);
|
$this->SetFillColor(250, 250, 250);
|
||||||
$this->SetFont('Arial', 'B', 8);
|
$this->SetFont('Arial', 'B', 8);
|
||||||
$this->Cell(95, 5, $this->clean(" Nom du contrôle"), 1, 0, 'L', true);
|
$this->Cell(95, 5, $this->clean(" Nom du contrôle"), 1, 0, 'L', true);
|
||||||
$this->Cell(95, 5, $this->clean(" Commentaire / État"), 1, 1, 'L', true);
|
$this->Cell(95, 5, $this->clean(" Commentaire / État"), 1, 1, 'L', true);
|
||||||
|
|
||||||
$this->SetFont('Arial', '', 8);
|
$this->SetFont('Arial', '', 8);
|
||||||
foreach ($product->getProductPointControlls() as $pPoint) {
|
foreach ($product->getProductPointControlls() as $pPoint) {
|
||||||
$details = '';
|
$details = '';
|
||||||
@@ -210,17 +219,17 @@ class EtatLieuxPdfService extends Fpdf
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$y = $this->GetY();
|
$y = $this->GetY();
|
||||||
$x = $this->GetX();
|
$x = $this->GetX();
|
||||||
|
|
||||||
$this->MultiCell(95, 5, $this->clean($pPoint->getName()), 1, 'L');
|
$this->MultiCell(95, 5, $this->clean($pPoint->getName()), 1, 'L');
|
||||||
$h1 = $this->GetY() - $y;
|
$h1 = $this->GetY() - $y;
|
||||||
|
|
||||||
$this->SetXY($x + 95, $y);
|
$this->SetXY($x + 95, $y);
|
||||||
$this->MultiCell(95, 5, $this->clean($details), 1, 'L');
|
$this->MultiCell(95, 5, $this->clean($details), 1, 'L');
|
||||||
$h2 = $this->GetY() - $y;
|
$h2 = $this->GetY() - $y;
|
||||||
|
|
||||||
$this->SetY($y + max($h1, $h2));
|
$this->SetY($y + max($h1, $h2));
|
||||||
$this->SetX(10);
|
$this->SetX(10);
|
||||||
}
|
}
|
||||||
@@ -235,10 +244,10 @@ class EtatLieuxPdfService extends Fpdf
|
|||||||
// --- COMMENTAIRES ---
|
// --- COMMENTAIRES ---
|
||||||
$this->SetFont('Arial', 'B', 10);
|
$this->SetFont('Arial', 'B', 10);
|
||||||
$this->Cell(0, 8, $this->clean("COMMENTAIRES & MÉDIAS :"), 0, 1, 'L');
|
$this->Cell(0, 8, $this->clean("COMMENTAIRES & MÉDIAS :"), 0, 1, 'L');
|
||||||
|
|
||||||
$currentY = $this->GetY();
|
$currentY = $this->GetY();
|
||||||
$etatLieux = $this->contrats->getEtatLieux();
|
$etatLieux = $this->contrats->getEtatLieux();
|
||||||
|
|
||||||
if ($etatLieux) {
|
if ($etatLieux) {
|
||||||
// Comments
|
// Comments
|
||||||
$comments = $etatLieux->getComments();
|
$comments = $etatLieux->getComments();
|
||||||
@@ -250,7 +259,7 @@ class EtatLieuxPdfService extends Fpdf
|
|||||||
}
|
}
|
||||||
$this->Ln(2);
|
$this->Ln(2);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Files
|
// Files
|
||||||
$filesCount = $etatLieux->getFiles()->count();
|
$filesCount = $etatLieux->getFiles()->count();
|
||||||
if ($filesCount > 0) {
|
if ($filesCount > 0) {
|
||||||
@@ -258,7 +267,7 @@ class EtatLieuxPdfService extends Fpdf
|
|||||||
$this->Cell(0, 6, $this->clean(">> Nombre de photos/vidéos jointes au dossier numérique : " . $filesCount), 0, 1, 'L');
|
$this->Cell(0, 6, $this->clean(">> Nombre de photos/vidéos jointes au dossier numérique : " . $filesCount), 0, 1, 'L');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$endY = $this->GetY();
|
$endY = $this->GetY();
|
||||||
// Box removed as requested
|
// Box removed as requested
|
||||||
$this->Ln(30);
|
$this->Ln(30);
|
||||||
|
|||||||
@@ -381,11 +381,19 @@ class Client
|
|||||||
|
|
||||||
public function getSigningUrl(EtatLieux $etatLieux, string $role): ?string
|
public function getSigningUrl(EtatLieux $etatLieux, string $role): ?string
|
||||||
{
|
{
|
||||||
$submitterId = match ($role) {
|
if($etatLieux->getStatus() == "edl_return_done"){
|
||||||
'Ludikevent', 'Prestataire' => $etatLieux->getSignIdDelivery(),
|
$submitterId = match ($role) {
|
||||||
'Client' => $etatLieux->getSignIdCustomer(),
|
'Ludikevent', 'Prestataire' => $etatLieux->getSignIdReturn(),
|
||||||
default => null
|
'Client' => $etatLieux->getSignIdCustomerReturn(),
|
||||||
};
|
default => null
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
$submitterId = match ($role) {
|
||||||
|
'Ludikevent', 'Prestataire' => $etatLieux->getSignIdDelivery(),
|
||||||
|
'Client' => $etatLieux->getSignIdCustomer(),
|
||||||
|
default => null
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
if (!$submitterId) {
|
if (!$submitterId) {
|
||||||
return null;
|
return null;
|
||||||
@@ -393,4 +401,59 @@ class Client
|
|||||||
|
|
||||||
return $this->getLinkSign($submitterId);
|
return $this->getLinkSign($submitterId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function createSubmissionEtatLieuxSortant(?EtatLieux $etatLieux)
|
||||||
|
{
|
||||||
|
$contrat = $etatLieux->getContrat();
|
||||||
|
$customer = $contrat->getCustomer();
|
||||||
|
// Prestataire or Admin
|
||||||
|
$prestataireEmail = 'contact@ludikevent.fr';
|
||||||
|
if ($etatLieux->getPrestataire()) {
|
||||||
|
$prestataireEmail = $etatLieux->getPrestataire()->getEmail();
|
||||||
|
} elseif ($etatLieux->getAccount()) {
|
||||||
|
$prestataireEmail = $etatLieux->getAccount()->getEmail();
|
||||||
|
}
|
||||||
|
|
||||||
|
// URL où on redirige après signature
|
||||||
|
$completedRedirectUrl = $this->baseUrl . $this->urlGenerator->generate('etl_mission_signed_entry_state', ['id' => $contrat->getId()]);
|
||||||
|
|
||||||
|
// Récupération du fichier PDF EDL (Non signé)
|
||||||
|
$relativeFileUrl = $this->storage->resolveUri($etatLieux, 'etatLieuxUnsignReturnFile');
|
||||||
|
$fileUrl = $this->baseUrl . $relativeFileUrl;
|
||||||
|
|
||||||
|
$submission = $this->docuseal->createSubmissionFromPdf([
|
||||||
|
'name' => 'Etat des Lieux - Contrat #' . $contrat->getNumReservation(),
|
||||||
|
'completed_redirect_url' => $completedRedirectUrl,
|
||||||
|
'send_email' => true, // Envoi email aux deux parties
|
||||||
|
'documents' => [
|
||||||
|
[
|
||||||
|
'name' => 'edl_' . $contrat->getNumReservation() . '.pdf',
|
||||||
|
'file' => $fileUrl,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'submitters' => [
|
||||||
|
[
|
||||||
|
'role' => 'Ludikevent', // Prestataire
|
||||||
|
'email' => $prestataireEmail,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'role' => 'Client',
|
||||||
|
'email' => $customer->getEmail(),
|
||||||
|
'name' => $customer->getSurname() . ' ' . $customer->getName(),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Mapping des IDs
|
||||||
|
foreach ($submission['submitters'] as $submitter) {
|
||||||
|
if ($submitter['role'] === 'Ludikevent') {
|
||||||
|
$etatLieux->setSignIdReturn($submitter['id']);
|
||||||
|
} elseif ($submitter['role'] === 'Client') {
|
||||||
|
$etatLieux->setSignIdCustomerReturn($submitter['id']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->entityManager->persist($etatLieux);
|
||||||
|
$this->entityManager->flush();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,26 @@
|
|||||||
<h3 class="text-xs font-black text-slate-400 uppercase tracking-widest mb-4">Photos & Vidéos</h3>
|
<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">
|
<div class="grid grid-cols-3 gap-2 mb-4">
|
||||||
|
{% for file in etatLieux.fileReturn %}
|
||||||
|
<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>
|
||||||
|
{% endfor %}
|
||||||
{% for file in etatLieux.files %}
|
{% for file in etatLieux.files %}
|
||||||
<div class="aspect-square bg-slate-100 rounded-xl overflow-hidden relative group">
|
<div class="aspect-square bg-slate-100 rounded-xl overflow-hidden relative group">
|
||||||
{% if file.type == 'photo' %}
|
{% if file.type == 'photo' %}
|
||||||
@@ -33,16 +53,14 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<form action="{{ path('etl_edl_delete_file', {id: mission.id, fileId: file.id}) }}" method="post" class="absolute top-1 right-1 z-10">
|
<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 ?')">
|
<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>
|
<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>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% endfor %}
|
||||||
<div class="col-span-3 text-center py-4 text-slate-400 text-xs italic">Aucun média ajouté.</div>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{# LIGHTBOX MODAL #}
|
{# LIGHTBOX MODAL #}
|
||||||
@@ -69,7 +87,7 @@
|
|||||||
<input type="file" name="videos[]" accept="video/*" multiple class="hidden">
|
<input type="file" name="videos[]" accept="video/*" multiple class="hidden">
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</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">
|
<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
|
Envoyer les fichiers
|
||||||
</button>
|
</button>
|
||||||
@@ -79,7 +97,7 @@
|
|||||||
{# POINTS DE CONTROLE #}
|
{# POINTS DE CONTROLE #}
|
||||||
<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">Points de Contrôle</h3>
|
<h3 class="text-xs font-black text-slate-400 uppercase tracking-widest mb-4">Points de Contrôle</h3>
|
||||||
|
|
||||||
<form action="{{ path('etl_edl_save_points', {id: mission.id}) }}" method="post" id="form-points">
|
<form action="{{ path('etl_edl_save_points', {id: mission.id}) }}" method="post" id="form-points">
|
||||||
{% for reserve in mission.productReserves %}
|
{% for reserve in mission.productReserves %}
|
||||||
{% set product = reserve.product %}
|
{% set product = reserve.product %}
|
||||||
@@ -93,24 +111,31 @@
|
|||||||
{% for point in product.productPointControlls %}
|
{% for point in product.productPointControlls %}
|
||||||
{# Try to find existing status/comment in etatLieux.pointControls #}
|
{# Try to find existing status/comment in etatLieux.pointControls #}
|
||||||
{% set existingPoint = null %}
|
{% set existingPoint = null %}
|
||||||
{% for ep in etatLieux.pointControls %}
|
{% if etatLieux.status == "return_edl_progress" %}
|
||||||
{% if ep.name == point.name %}
|
{% for ep in etatLieux.pointControlsReturn %}
|
||||||
{% set existingPoint = ep %}
|
{% if ep.name == point.name %}
|
||||||
{% endif %}
|
{% set existingPoint = ep %}
|
||||||
{% endfor %}
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
{% else %}
|
||||||
|
{% for ep in etatLieux.pointControls %}
|
||||||
|
{% if ep.name == point.name %}
|
||||||
|
{% set existingPoint = ep %}
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
<div class="bg-slate-50 p-3 rounded-xl">
|
<div class="bg-slate-50 p-3 rounded-xl">
|
||||||
<div class="flex items-start gap-3">
|
<div class="flex items-start gap-3">
|
||||||
<div class="pt-1">
|
<div class="pt-1">
|
||||||
<input type="checkbox" name="points[{{ product.id }}][{{ point.id }}][status]" value="1"
|
<input type="checkbox" name="points[{{ product.id }}][{{ point.id }}][status]" value="1"
|
||||||
class="w-5 h-5 rounded-md border-slate-300 text-blue-600 focus:ring-blue-500"
|
class="w-5 h-5 rounded-md border-slate-300 text-blue-600 focus:ring-blue-500"
|
||||||
{{ existingPoint and existingPoint.status ? 'checked' : '' }}>
|
{{ existingPoint and existingPoint.status ? 'checked' : '' }}>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-1">
|
<div class="flex-1">
|
||||||
<p class="text-sm font-bold text-slate-700 mb-1">{{ point.name }}</p>
|
<p class="text-sm font-bold text-slate-700 mb-1">{{ point.name }}</p>
|
||||||
<input type="text" name="points[{{ product.id }}][{{ point.id }}][details]"
|
<input type="text" name="points[{{ product.id }}][{{ point.id }}][details]"
|
||||||
value="{{ existingPoint ? existingPoint.details : '' }}"
|
value="{{ existingPoint ? existingPoint.details : '' }}"
|
||||||
placeholder="Commentaire (optionnel)..."
|
placeholder="Commentaire (optionnel)..."
|
||||||
class="w-full bg-white border border-slate-200 rounded-lg px-3 py-2 text-xs focus:outline-none focus:border-blue-500 transition-colors">
|
class="w-full bg-white border border-slate-200 rounded-lg px-3 py-2 text-xs focus:outline-none focus:border-blue-500 transition-colors">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -120,7 +145,7 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
<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 mt-4">
|
<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 mt-4">
|
||||||
Enregistrer les contrôles
|
Enregistrer les contrôles
|
||||||
</button>
|
</button>
|
||||||
@@ -132,14 +157,22 @@
|
|||||||
<h3 class="text-xs font-black text-slate-400 uppercase tracking-widest mb-4">Commentaires</h3>
|
<h3 class="text-xs font-black text-slate-400 uppercase tracking-widest mb-4">Commentaires</h3>
|
||||||
|
|
||||||
<div class="space-y-3 mb-4">
|
<div class="space-y-3 mb-4">
|
||||||
{% for comment in etatLieux.comments %}
|
{% if etatLieux.status == "return_edl_progress" %}
|
||||||
<div class="bg-slate-50 p-3 rounded-xl">
|
{% for comment in etatLieux.commentsReturn %}
|
||||||
<p class="text-[10px] font-bold text-slate-400 mb-1">{{ comment.createdAt|date('d/m H:i') }}</p>
|
<div class="bg-slate-50 p-3 rounded-xl">
|
||||||
<p class="text-sm text-slate-700">{{ comment.content }}</p>
|
<p class="text-[10px] font-bold text-slate-400 mb-1">{{ comment.createdAt|date('d/m H:i') }}</p>
|
||||||
</div>
|
<p class="text-sm text-slate-700">{{ comment.content }}</p>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
{% else %}
|
{% else %}
|
||||||
<p class="text-center py-2 text-slate-400 text-xs italic">Aucun commentaire.</p>
|
{% for comment in etatLieux.comments %}
|
||||||
{% endfor %}
|
<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>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form action="{{ path('etl_edl_add_comment', {id: mission.id}) }}" method="post" class="flex gap-2">
|
<form action="{{ path('etl_edl_add_comment', {id: mission.id}) }}" method="post" class="flex gap-2">
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
{% block body %}
|
{% block body %}
|
||||||
<div class="space-y-8 animate-in fade-in slide-in-from-bottom-4 duration-500">
|
<div class="space-y-8 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_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">
|
<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">
|
||||||
@@ -20,7 +20,7 @@
|
|||||||
<div class="absolute top-0 right-0 -mr-8 -mt-8 w-32 h-32 bg-white/10 rounded-full blur-2xl"></div>
|
<div class="absolute top-0 right-0 -mr-8 -mt-8 w-32 h-32 bg-white/10 rounded-full blur-2xl"></div>
|
||||||
<h2 class="text-2xl font-black mb-2">État des Lieux Terminé</h2>
|
<h2 class="text-2xl font-black mb-2">État des Lieux Terminé</h2>
|
||||||
<p class="text-sm font-medium opacity-90 mb-6">Veuillez procéder à la signature du document.</p>
|
<p class="text-sm font-medium opacity-90 mb-6">Veuillez procéder à la signature du document.</p>
|
||||||
|
|
||||||
<a href="{{ path('etl_edl_regenerate_view', {id: mission.id}) }}" target="_blank" class="inline-flex items-center px-6 py-3 bg-white/20 hover:bg-white/30 text-white rounded-xl text-xs font-bold uppercase tracking-widest backdrop-blur-sm transition-all border border-white/30">
|
<a href="{{ path('etl_edl_regenerate_view', {id: mission.id}) }}" target="_blank" class="inline-flex items-center px-6 py-3 bg-white/20 hover:bg-white/30 text-white rounded-xl text-xs font-bold uppercase tracking-widest backdrop-blur-sm transition-all border border-white/30">
|
||||||
<svg class="w-4 h-4 mr-2" 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>
|
<svg class="w-4 h-4 mr-2" 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>
|
||||||
Voir le PDF (Actualiser)
|
Voir le PDF (Actualiser)
|
||||||
@@ -31,7 +31,7 @@
|
|||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
<div class="p-6 bg-white rounded-[2rem] border border-slate-100 shadow-sm">
|
<div class="p-6 bg-white rounded-[2rem] border border-slate-100 shadow-sm">
|
||||||
<h3 class="text-xs font-black text-slate-400 uppercase tracking-widest mb-4">Signatures Requises</h3>
|
<h3 class="text-xs font-black text-slate-400 uppercase tracking-widest mb-4">Signatures Requises</h3>
|
||||||
|
|
||||||
<div class="space-y-3">
|
<div class="space-y-3">
|
||||||
{% if providerSigned %}
|
{% if providerSigned %}
|
||||||
<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">
|
<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">
|
||||||
@@ -55,6 +55,32 @@
|
|||||||
<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="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" /></svg>
|
<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="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" /></svg>
|
||||||
Faire Signer (Client)
|
Faire Signer (Client)
|
||||||
</a>
|
</a>
|
||||||
|
{# ... (sous le bouton "Faire Signer (Client)") ... #}
|
||||||
|
|
||||||
|
{% if etatLieux.status == "edl_return_done" %}
|
||||||
|
<div class="mt-4 p-4 bg-red-50 rounded-2xl border border-red-100">
|
||||||
|
<form action="{{ path('etl_edl_customer_refused', {id: mission.id}) }}" method="post" onsubmit="return confirm('Confirmer le refus de signature ?');">
|
||||||
|
<label for="refusal_reason" class="block text-[10px] font-black text-red-400 uppercase tracking-widest mb-2 ml-1">
|
||||||
|
Raison du refus (Optionnel)
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<textarea
|
||||||
|
name="refusal_reason"
|
||||||
|
id="refusal_reason"
|
||||||
|
rows="2"
|
||||||
|
placeholder="Ex: Désaccord sur les dommages carrosserie..."
|
||||||
|
class="w-full px-4 py-3 rounded-xl border-none text-sm text-slate-900 placeholder:text-slate-300 focus:ring-2 focus:ring-red-200 transition-all mb-3 shadow-inner"
|
||||||
|
></textarea>
|
||||||
|
|
||||||
|
<button type="submit" class="w-full py-3 bg-red-600 text-white rounded-xl font-black uppercase text-[10px] tracking-widest hover:bg-red-700 transition-all flex items-center justify-center gap-2 shadow-lg shadow-red-600/20">
|
||||||
|
<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="M18.364 18.364A9 9 0 005.636 5.636m12.728 12.728A9 9 0 015.636 5.636m12.728 12.728L5.636 5.636" />
|
||||||
|
</svg>
|
||||||
|
Valider le refus de signer
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -117,10 +117,12 @@
|
|||||||
Reprendre l'état des lieux
|
Reprendre l'état des lieux
|
||||||
</a>
|
</a>
|
||||||
{% elseif mission.etatLieux.status == 'edl_validated' %}
|
{% 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">
|
<form action="{{ path('etl_mission_edl_return_start', {id: mission.id}) }}" method="post" class="mt-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>
|
<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">
|
||||||
Commenter l'état des lieux de retour
|
<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>
|
||||||
</a>
|
Commenter l'état des lieux de retour
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{# DATES #}
|
{# DATES #}
|
||||||
|
|||||||
54
templates/mails/etl/edl_refused_alert.twig
Normal file
54
templates/mails/etl/edl_refused_alert.twig
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
{% extends 'mails/base.twig' %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<mj-section background-color="#fef2f2" border-radius="12px" padding="20px">
|
||||||
|
<mj-column>
|
||||||
|
<mj-text color="#b91c1c" font-weight="black" font-size="18px" text-transform="uppercase" align="center">
|
||||||
|
⚠️ ALERTE : Signature Refusée (Retour)
|
||||||
|
</mj-text>
|
||||||
|
</mj-column>
|
||||||
|
</mj-section>
|
||||||
|
|
||||||
|
<mj-text padding-top="20px">
|
||||||
|
Bonjour l'équipe <strong>Ludikevent</strong>,
|
||||||
|
</mj-text>
|
||||||
|
<mj-text font-size="14px">
|
||||||
|
Lors de l'état des lieux de <strong>retour</strong> pour la réservation <strong>#{{ datas.contrat.numReservation }}</strong>, le client a refusé de procéder à la signature.
|
||||||
|
</mj-text>
|
||||||
|
|
||||||
|
<mj-text font-weight="bold" font-size="14px" padding-top="20px" color="#b91c1c">
|
||||||
|
RAISON DU REFUS INDIQUÉE :
|
||||||
|
</mj-text>
|
||||||
|
<mj-text font-style="italic" padding="10px 20px" background-color="#f8fafc" border-left="4px solid #b91c1c">
|
||||||
|
{{ datas.reason|default('Aucune raison spécifique n\'a été saisie par le prestataire.') }}
|
||||||
|
</mj-text>
|
||||||
|
|
||||||
|
<mj-text font-weight="bold" font-size="14px" padding-top="20px">
|
||||||
|
Détails de la mission :
|
||||||
|
</mj-text>
|
||||||
|
<mj-text>
|
||||||
|
<strong>Client :</strong> {{ datas.contrat.customer.surname }} {{ datas.contrat.customer.name }}<br>
|
||||||
|
<strong>Intervenant :</strong> {{ datas.prestataire.surname|default('') }} {{ datas.prestataire.name|default('Non assigné') }}<br>
|
||||||
|
<strong>Lieu :</strong> {{ datas.contrat.addressEvent }} {{ datas.contrat.zipCodeEvent }} {{ datas.contrat.townEvent }}
|
||||||
|
</mj-text>
|
||||||
|
|
||||||
|
{# Observations de retour (on utilise commentsReturn si c'est le champ lié aux remarques de sortie) #}
|
||||||
|
{% if datas.contrat.etatLieux.commentsReturn|length > 0 %}
|
||||||
|
<mj-text font-weight="bold" font-size="14px" padding-top="20px">
|
||||||
|
Observations saisies lors du retour :
|
||||||
|
</mj-text>
|
||||||
|
{% for comment in datas.contrat.etatLieux.commentsReturn %}
|
||||||
|
<mj-text padding-bottom="2px">
|
||||||
|
- {{ comment.content }}
|
||||||
|
</mj-text>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<mj-button href="{{ system.path }}{{ path('etl_contrat_view', {id: datas.contrat.id}) }}" background-color="#3b82f6" border-radius="8px" font-weight="bold" padding-top="30px">
|
||||||
|
Consulter le dossier complet
|
||||||
|
</mj-button>
|
||||||
|
|
||||||
|
<mj-text padding-top="30px" font-size="12px" color="#64748b" align="center">
|
||||||
|
L'état des lieux a été marqué avec le statut <strong>"Refusé"</strong>. Veuillez vérifier les fichiers médias joints pour constater d'éventuels dommages.
|
||||||
|
</mj-text>
|
||||||
|
{% endblock %}
|
||||||
Reference in New Issue
Block a user