```text
✨ feat(contrats): Ajoute détails option, actions paiements et style liste
Ajoute un champ détails pour les options de contrat, permet la validation
manuelle des paiements (accompte, caution, solde) et améliore le style
de la liste des contrats.
```
This commit is contained in:
32
migrations/Version20260129091410.php
Normal file
32
migrations/Version20260129091410.php
Normal file
@@ -0,0 +1,32 @@
|
||||
<?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 Version20260129091410 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 contrats_option ADD details TEXT DEFAULT NULL');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
// this down() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('CREATE SCHEMA public');
|
||||
$this->addSql('ALTER TABLE contrats_option DROP details');
|
||||
}
|
||||
}
|
||||
@@ -7,15 +7,19 @@ use App\Entity\ContratsLine;
|
||||
use App\Entity\ContratsOption;
|
||||
use App\Entity\ContratsPayments;
|
||||
use App\Entity\Devis;
|
||||
use App\Entity\Product;
|
||||
use App\Event\Signature\ContratEvent;
|
||||
use App\Form\Type\ContratsType;
|
||||
use App\Logger\AppLogger;
|
||||
use App\Repository\AccountRepository;
|
||||
use App\Repository\DevisRepository;
|
||||
use App\Repository\ContratsRepository;
|
||||
use App\Service\Mailer\Mailer;
|
||||
use App\Service\Pdf\ContratPdfService;
|
||||
use App\Service\Pdf\PlPdf;
|
||||
use App\Service\Signature\Client;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
use Knp\Component\Pager\PaginatorInterface;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||
@@ -85,6 +89,7 @@ class ContratsController extends AbstractController
|
||||
AppLogger $appLogger,
|
||||
EventDispatcherInterface $eventDispatcher,
|
||||
KernelInterface $kernel,
|
||||
Mailer $mailer,
|
||||
): Response {
|
||||
if (!$contrat) {
|
||||
throw $this->createNotFoundException('Contrat non trouvé.');
|
||||
@@ -131,37 +136,182 @@ class ContratsController extends AbstractController
|
||||
$solde = $totalHt - $dejaPaye;
|
||||
|
||||
|
||||
if($request->query->has('act') && $request->query->get('act') === 'cautionCapture') {
|
||||
$amount = $request->query->get('amountToCapture');
|
||||
$paiementCaution = $entityManager->getRepository(ContratsPayments::class)->findOneBy([
|
||||
if($request->query->has('type') && $request->query->get('type') === 'accompte') {
|
||||
$paiementAccompte = $entityManager->getRepository(ContratsPayments::class)->findOneBy([
|
||||
'contrat' => $contrat,
|
||||
'type' => 'caution',
|
||||
'type' => 'accompte',
|
||||
]);
|
||||
$result = $stripeClient->capture($paiementCaution->getPaymentId());
|
||||
if($result['state']) {
|
||||
$contrat->setCautionState("recover");
|
||||
$entityManager->persist($contrat);
|
||||
$entityManager->flush();
|
||||
$this->addFlash("success","Caution restitué");
|
||||
} else {
|
||||
$this->addFlash("error",$result['message']);
|
||||
if(!$paiementAccompte) {
|
||||
$paiementAccompte = new ContratsPayments();
|
||||
$paiementAccompte->setContrat($contrat);
|
||||
$paiementAccompte->setType('accompte');
|
||||
}
|
||||
}
|
||||
if($request->query->has('act') && $request->query->get('act') === 'cautionRelease') {
|
||||
$paiementAccompte->setValidateAt(new \DateTimeImmutable());
|
||||
$paiementAccompte->setUpdateAt(new \DateTimeImmutable());
|
||||
$paiementAccompte->setPaymentAt(new \DateTimeImmutable());
|
||||
$paiementAccompte->setAmount( $totalHt * 0.25);
|
||||
$paiementAccompte->setState("complete");
|
||||
$paiementAccompte->setPaymentId("");
|
||||
$paiementAccompte->setCard([
|
||||
'type' => 'manuel'
|
||||
]);
|
||||
$pdf = new PlPdf($kernel, $paiementAccompte, $contrat);
|
||||
$pdf->generate();
|
||||
$content = $pdf->Output('S');
|
||||
$tmpSigned = sys_get_temp_dir() . '/sign_' . uniqid() . '.pdf';
|
||||
file_put_contents($tmpSigned, $content);
|
||||
|
||||
$paiementCaution = $entityManager->getRepository(ContratsPayments::class)->findOneBy([
|
||||
// On utilise UploadedFile pour simuler un upload propre pour VichUploader
|
||||
$paiementAccompte->setPaymentFile(new UploadedFile($tmpSigned, "confirmed-" . $paiementAccompte->getId() . ".pdf", "application/pdf", null, true));
|
||||
$paiementAccompte->setUpdateAt(new \DateTimeImmutable('now'));
|
||||
$entityManager->persist($paiementAccompte);
|
||||
$entityManager->flush();
|
||||
$data = $client->autoSignConfirmedPayment($paiementAccompte);
|
||||
// 1. Gestion du PDF SIGNÉ
|
||||
$tmpSigned = sys_get_temp_dir() . '/sign_' . uniqid() . '.pdf';
|
||||
$signedContent = file_get_contents($data);
|
||||
file_put_contents($tmpSigned, $signedContent);
|
||||
|
||||
// On utilise UploadedFile pour simuler un upload propre pour VichUploader
|
||||
$paiementAccompte->setPaymentSignedFile(new UploadedFile($tmpSigned, "confirmed-certificate-" . $paiementAccompte->getId() . ".pdf", "application/pdf", null, true));
|
||||
$paiementAccompte->setUpdateAt(new \DateTimeImmutable('now'));
|
||||
$entityManager->persist($paiementAccompte);
|
||||
$entityManager->flush();
|
||||
$customer = $contrat->getCustomer();
|
||||
$subjectCustomer = "[Ludikevent] Confirmation de votre acompte - #" . $contrat->getNumReservation();
|
||||
$mailer->send(
|
||||
$customer->getEmail(),
|
||||
$customer->getSurname() . ' ' . $customer->getName(),
|
||||
$subjectCustomer,
|
||||
"mails/customer/accompte_confirmation.twig",
|
||||
[
|
||||
'contrat' => $contrat,
|
||||
'payment' => $paiementAccompte,
|
||||
'customer' => $customer,
|
||||
'reservationLink' => "https://reservation.ludikevent.fr" . $this->generateUrl('gestion_contrat_view', ['num' => $contrat->getNumReservation()])
|
||||
]
|
||||
);
|
||||
$appLogger->record('PAYMENT','Validation accompte manuel pour contrat #' . $contrat->getNumReservation());
|
||||
$this->addFlash("success","Validation accompte effectuée");
|
||||
return $this->redirectToRoute('app_crm_contrats_view', ['id' => $contrat->getId()]);
|
||||
}
|
||||
if($request->query->has('type') && $request->query->get('type') === 'caution') {
|
||||
$paiementAccompte = $entityManager->getRepository(ContratsPayments::class)->findOneBy([
|
||||
'contrat' => $contrat,
|
||||
'type' => 'caution',
|
||||
]);
|
||||
$result = $stripeClient->cancelPayment($paiementCaution->getPaymentId());
|
||||
if($result['state']) {
|
||||
$contrat->setCautionState("restitue");
|
||||
$entityManager->persist($contrat);
|
||||
$entityManager->flush();
|
||||
$this->addFlash("success","Caution restitué");
|
||||
} else {
|
||||
$this->addFlash("error",$result['message']);
|
||||
if(!$paiementAccompte) {
|
||||
$paiementAccompte = new ContratsPayments();
|
||||
$paiementAccompte->setContrat($contrat);
|
||||
$paiementAccompte->setType('caution');
|
||||
}
|
||||
$paiementAccompte->setValidateAt(new \DateTimeImmutable());
|
||||
$paiementAccompte->setUpdateAt(new \DateTimeImmutable());
|
||||
$paiementAccompte->setPaymentAt(new \DateTimeImmutable());
|
||||
$paiementAccompte->setAmount( $totalCaution);
|
||||
$paiementAccompte->setState("complete");
|
||||
$paiementAccompte->setPaymentId("");
|
||||
$paiementAccompte->setCard([
|
||||
'type' => 'manuel'
|
||||
]);
|
||||
$pdf = new PlPdf($kernel, $paiementAccompte, $contrat);
|
||||
$pdf->generate();
|
||||
$content = $pdf->Output('S');
|
||||
$tmpSigned = sys_get_temp_dir() . '/sign_' . uniqid() . '.pdf';
|
||||
file_put_contents($tmpSigned, $content);
|
||||
|
||||
// On utilise UploadedFile pour simuler un upload propre pour VichUploader
|
||||
$paiementAccompte->setPaymentFile(new UploadedFile($tmpSigned, "confirmed-" . $paiementAccompte->getId() . ".pdf", "application/pdf", null, true));
|
||||
$paiementAccompte->setUpdateAt(new \DateTimeImmutable('now'));
|
||||
$entityManager->persist($paiementAccompte);
|
||||
$entityManager->flush();
|
||||
$data = $client->autoSignConfirmedPayment($paiementAccompte);
|
||||
// 1. Gestion du PDF SIGNÉ
|
||||
$tmpSigned = sys_get_temp_dir() . '/sign_' . uniqid() . '.pdf';
|
||||
$signedContent = file_get_contents($data);
|
||||
file_put_contents($tmpSigned, $signedContent);
|
||||
|
||||
// On utilise UploadedFile pour simuler un upload propre pour VichUploader
|
||||
$paiementAccompte->setPaymentSignedFile(new UploadedFile($tmpSigned, "confirmed-certificate-" . $paiementAccompte->getId() . ".pdf", "application/pdf", null, true));
|
||||
$paiementAccompte->setUpdateAt(new \DateTimeImmutable('now'));
|
||||
$entityManager->persist($paiementAccompte);
|
||||
$entityManager->flush();
|
||||
$customer = $contrat->getCustomer();
|
||||
$subjectCustomer = "[Ludikevent] Confirmation de votre caution - #" . $contrat->getNumReservation();
|
||||
$mailer->send(
|
||||
$customer->getEmail(),
|
||||
$customer->getSurname() . ' ' . $customer->getName(),
|
||||
$subjectCustomer,
|
||||
"mails/customer/accompte_confirmation.twig",
|
||||
[
|
||||
'contrat' => $contrat,
|
||||
'payment' => $paiementAccompte,
|
||||
'customer' => $customer,
|
||||
'reservationLink' => "https://reservation.ludikevent.fr" . $this->generateUrl('gestion_contrat_view', ['num' => $contrat->getNumReservation()])
|
||||
]
|
||||
);
|
||||
$appLogger->record('PAYMENT','Validation caution manuel pour contrat #' . $contrat->getNumReservation());
|
||||
$this->addFlash("success","Validation caution effectuée");
|
||||
return $this->redirectToRoute('app_crm_contrats_view', ['id' => $contrat->getId()]);
|
||||
}
|
||||
if($request->query->has('type') && $request->query->get('type') === 'solde') {
|
||||
$paiementAccompte = $entityManager->getRepository(ContratsPayments::class)->findOneBy([
|
||||
'contrat' => $contrat,
|
||||
'type' => 'solde',
|
||||
]);
|
||||
if(!$paiementAccompte) {
|
||||
$paiementAccompte = new ContratsPayments();
|
||||
$paiementAccompte->setContrat($contrat);
|
||||
$paiementAccompte->setType('solde');
|
||||
}
|
||||
$paiementAccompte->setValidateAt(new \DateTimeImmutable());
|
||||
$paiementAccompte->setUpdateAt(new \DateTimeImmutable());
|
||||
$paiementAccompte->setPaymentAt(new \DateTimeImmutable());
|
||||
$paiementAccompte->setAmount( $totalHt);
|
||||
$paiementAccompte->setState("complete");
|
||||
$paiementAccompte->setPaymentId("");
|
||||
$paiementAccompte->setCard([
|
||||
'type' => 'manuel'
|
||||
]);
|
||||
$pdf = new PlPdf($kernel, $paiementAccompte, $contrat);
|
||||
$pdf->generate();
|
||||
$content = $pdf->Output('S');
|
||||
$tmpSigned = sys_get_temp_dir() . '/sign_' . uniqid() . '.pdf';
|
||||
file_put_contents($tmpSigned, $content);
|
||||
|
||||
// On utilise UploadedFile pour simuler un upload propre pour VichUploader
|
||||
$paiementAccompte->setPaymentFile(new UploadedFile($tmpSigned, "confirmed-" . $paiementAccompte->getId() . ".pdf", "application/pdf", null, true));
|
||||
$paiementAccompte->setUpdateAt(new \DateTimeImmutable('now'));
|
||||
$entityManager->persist($paiementAccompte);
|
||||
$entityManager->flush();
|
||||
$data = $client->autoSignConfirmedPayment($paiementAccompte);
|
||||
// 1. Gestion du PDF SIGNÉ
|
||||
$tmpSigned = sys_get_temp_dir() . '/sign_' . uniqid() . '.pdf';
|
||||
$signedContent = file_get_contents($data);
|
||||
file_put_contents($tmpSigned, $signedContent);
|
||||
|
||||
// On utilise UploadedFile pour simuler un upload propre pour VichUploader
|
||||
$paiementAccompte->setPaymentSignedFile(new UploadedFile($tmpSigned, "confirmed-certificate-" . $paiementAccompte->getId() . ".pdf", "application/pdf", null, true));
|
||||
$paiementAccompte->setUpdateAt(new \DateTimeImmutable('now'));
|
||||
$entityManager->persist($paiementAccompte);
|
||||
$entityManager->flush();
|
||||
$customer = $contrat->getCustomer();
|
||||
$subjectCustomer = "[Ludikevent] Votre réservation est désormais soldée - #" . $contrat->getNumReservation();
|
||||
$mailer->send(
|
||||
$customer->getEmail(),
|
||||
$customer->getSurname() . ' ' . $customer->getName(),
|
||||
$subjectCustomer,
|
||||
"mails/customer/accompte_confirmation.twig",
|
||||
[
|
||||
'contrat' => $contrat,
|
||||
'payment' => $paiementAccompte,
|
||||
'customer' => $customer,
|
||||
'reservationLink' => "https://reservation.ludikevent.fr" . $this->generateUrl('gestion_contrat_view', ['num' => $contrat->getNumReservation()])
|
||||
]
|
||||
);
|
||||
$appLogger->record('PAYMENT','Validation solde manuel pour contrat #' . $contrat->getNumReservation());
|
||||
$this->addFlash("success","Validation solde effectuée");
|
||||
return $this->redirectToRoute('app_crm_contrats_view', ['id' => $contrat->getId()]);
|
||||
}
|
||||
|
||||
return $this->render('dashboard/contrats/view.twig', [
|
||||
@@ -192,7 +342,7 @@ class ContratsController extends AbstractController
|
||||
|
||||
$c = new Contrats();
|
||||
$lines = [['id' => 0, 'name' => '', 'priceHt1Day' => 0, 'priceHtSupDay' => 0, 'caution' => 0]];
|
||||
$options = [['id' => 0, 'name' => '', 'priceHt' => 0]];
|
||||
$options = [['id' => 0, 'name' => '', 'priceHt' => 0,'details'=>'']];
|
||||
|
||||
if ($devis instanceof Devis) {
|
||||
$c->setDateAt($devis->getStartAt());
|
||||
@@ -200,6 +350,7 @@ class ContratsController extends AbstractController
|
||||
$c->setCustomer($devis->getCustomer());
|
||||
$c->setDevis($devis);
|
||||
|
||||
|
||||
// Mapping adresse de l'événement
|
||||
if ($devis->getAddressShip()) {
|
||||
$c->setAddressEvent($devis->getAddressShip()->getAddress());
|
||||
@@ -213,19 +364,22 @@ class ContratsController extends AbstractController
|
||||
$options = [];
|
||||
|
||||
foreach ($devis->getDevisLines() as $line) {
|
||||
$p = $entityManager->getRepository(Product::class)->findOneBy(['name'=>$line->getProduct()]);
|
||||
|
||||
$lines[] = [
|
||||
'id' => $line->getId(),
|
||||
'name' => $line->getProduct()->getName() . " - " . $line->getProduct()->getRef(),
|
||||
'name' =>$p->getName() . " - " . $p->getRef(),
|
||||
'priceHt1Day' => $line->getPriceHt(),
|
||||
'priceHtSupDay' => $line->getPriceHtSup(),
|
||||
'caution' => $line->getProduct()->getCaution(),
|
||||
'caution' => $p->getCaution(),
|
||||
];
|
||||
}
|
||||
|
||||
foreach ($devis->getDevisOptions() as $line) {
|
||||
$options[] = [
|
||||
'id' => $line->getId(),
|
||||
'name' => $line->getOption()->getName(),
|
||||
'name' => $line->getOption(),
|
||||
'details' => $line->getDetails(),
|
||||
'priceHt' => $line->getPriceHt(),
|
||||
];
|
||||
}
|
||||
@@ -257,6 +411,7 @@ class ContratsController extends AbstractController
|
||||
$vc = new ContratsOption();
|
||||
$vc->setContrat($c);
|
||||
$vc->setName($line['name']);
|
||||
$vc->setDetails($line['details']);
|
||||
$vc->setPrice($line['priceHt']);
|
||||
$entityManager->persist($vc);
|
||||
}
|
||||
|
||||
@@ -158,18 +158,6 @@ class SignatureController extends AbstractController
|
||||
|
||||
// 4. Sauvegarde en base de données
|
||||
$devis->setUpdateAt(new \DateTimeImmutable());
|
||||
|
||||
foreach ($devis->getDevisLines() as $line) {
|
||||
$product = $line->getProduct();
|
||||
|
||||
$productReserve = new ProductReserve();
|
||||
$productReserve->setProduct($product);
|
||||
$productReserve->setCustomer($devis->getCustomer());
|
||||
$productReserve->setStartAt($devis->getStartAt());
|
||||
$productReserve->setEndAt($devis->getEndAt());
|
||||
$productReserve->setDevis($devis);
|
||||
$entityManager->persist($productReserve);
|
||||
}
|
||||
$entityManager->persist($devis);
|
||||
$entityManager->flush();
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace App\Entity;
|
||||
|
||||
use App\Repository\ContratsOptionRepository;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
#[ORM\Entity(repositoryClass: ContratsOptionRepository::class)]
|
||||
@@ -22,6 +23,9 @@ class ContratsOption
|
||||
#[ORM\Column]
|
||||
private ?float $price = null;
|
||||
|
||||
#[ORM\Column(type: Types::TEXT, nullable: true)]
|
||||
private ?string $details = null;
|
||||
|
||||
public function getId(): ?int
|
||||
{
|
||||
return $this->id;
|
||||
@@ -62,4 +66,16 @@ class ContratsOption
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getDetails(): ?string
|
||||
{
|
||||
return $this->details;
|
||||
}
|
||||
|
||||
public function setDetails(?string $details): static
|
||||
{
|
||||
$this->details = $details;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,7 +134,12 @@ class PlPdf extends Fpdf
|
||||
$this->SetFont('Arial', 'B', 10);
|
||||
$this->Cell(50, 8, $this->clean("Mode de règlement :"), 0, 0);
|
||||
$this->SetFont('Arial', '', 10);
|
||||
if($this->contratsPayments->getCard()['type'] == "manuel") {
|
||||
$this->Cell(0, 8, $this->clean("Paiement valider par Ludikevent"), 0, 1);
|
||||
} else {
|
||||
$this->Cell(0, 8, $this->clean($this->contratsPayments->getCard()['type'] ?? 'Carte Bancaire'), 0, 1);
|
||||
}
|
||||
|
||||
|
||||
$this->SetFont('Arial', 'B', 10);
|
||||
$this->Cell(50, 8, $this->clean("Référence transaction :"), 0, 0);
|
||||
|
||||
@@ -224,7 +224,7 @@
|
||||
<div class="grid grid-cols-1 lg:grid-cols-12 gap-6 items-end">
|
||||
|
||||
{# 1. PRODUIT AVEC BOUTON RECHERCHE #}
|
||||
<div class="lg:col-span-8">
|
||||
<div class="lg:col-span-6">
|
||||
<label class="text-[9px] font-black text-slate-500 uppercase tracking-widest ml-1 mb-2 block">Produit / Prestation</label>
|
||||
<div class="relative flex items-center">
|
||||
<input type="text" name="options[{{ key }}][name]" value="{{ line.name }}" required class="w-full bg-slate-950/50 border-white/5 rounded-2xl text-white focus:ring-purple-500/20 focus:border-purple-500 transition-all py-3 pl-5 pr-12 text-sm">
|
||||
@@ -235,7 +235,10 @@
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="lg:col-span-2">
|
||||
<label class="text-[9px] font-black text-slate-500 uppercase tracking-widest ml-1 mb-2 block">Détails</label>
|
||||
<input type="text" name="options[{{ key }}][details]" value="{{ line.details }}" required class="w-full bg-slate-950/50 border-white/5 rounded-2xl text-white focus:ring-purple-500/20 focus:border-purple-500 transition-all py-3 px-5 text-sm font-mono">
|
||||
</div>
|
||||
{# 2. PRIX 1J #}
|
||||
<div class="lg:col-span-3">
|
||||
<label class="text-[9px] font-black text-slate-500 uppercase tracking-widest ml-1 mb-2 block">Prix 1J HT</label>
|
||||
|
||||
@@ -3,138 +3,186 @@
|
||||
{% block title %}Contrats de locations{% endblock %}
|
||||
{% block title_header %}Contrats de locations{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<div class="space-y-6 pb-20">
|
||||
{% block actions %}
|
||||
<div class="flex items-center gap-3">
|
||||
<a href="{{ path('app_crm_contrats_create') }}"
|
||||
class="group relative flex items-center gap-2 px-6 py-3 bg-gradient-to-br from-blue-600 to-indigo-700 hover:from-blue-500 hover:to-indigo-600 rounded-2xl text-white text-xs font-bold uppercase tracking-wider transition-all shadow-[0_8px_20px_-6px_rgba(37,99,235,0.5)]">
|
||||
<svg class="w-4 h-4 transition-transform group-hover:rotate-12" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4" />
|
||||
</svg>
|
||||
Créer un contrat
|
||||
</a>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{# --- BARRE DE RECHERCHE --- #}
|
||||
<div class="relative group mb-12">
|
||||
{% block body %}
|
||||
<div class="space-y-8 pb-20">
|
||||
|
||||
{# --- RECHERCHE STYLE NÉO-GLASS --- #}
|
||||
<div class="relative group max-w-2xl mx-auto">
|
||||
<div class="absolute -inset-1 bg-gradient-to-r from-blue-500 to-indigo-500 rounded-[2rem] blur opacity-20 group-focus-within:opacity-40 transition duration-1000"></div>
|
||||
<div class="relative flex items-center">
|
||||
<div class="absolute inset-y-0 left-0 pl-6 flex items-center pointer-events-none">
|
||||
<svg class="w-5 h-5 text-slate-500 group-focus-within:text-blue-500 transition-colors" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<svg class="w-5 h-5 text-slate-400 group-focus-within:text-blue-400 transition-colors" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<input type="text"
|
||||
id="searchContrat"
|
||||
placeholder="Rechercher un contrat (Nom, N° ou Ville)..."
|
||||
class="w-full bg-white/5 border border-white/10 backdrop-blur-xl text-white text-sm rounded-[1.5rem] pl-14 pr-6 py-5 focus:outline-none focus:ring-2 focus:ring-blue-500/50 focus:border-blue-500 transition-all placeholder:text-slate-500 font-medium">
|
||||
<input type="text" id="searchContrat"
|
||||
placeholder="Rechercher un contrat, un client ou une ville..."
|
||||
class="w-full bg-slate-900/40 border border-white/10 backdrop-blur-2xl text-white text-sm rounded-[1.8rem] pl-14 pr-6 py-5 focus:outline-none focus:ring-2 focus:ring-blue-500/50 transition-all placeholder:text-slate-500">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="contratsList" class="space-y-6">
|
||||
<div id="contratsList" class="grid gap-6">
|
||||
{% for contrat in contrats %}
|
||||
{# CALCUL DES ÉTATS FINANCIERS VIA TES FILTRES TWIG #}
|
||||
{% set acompteOk = contratPaymentPay(contrat, 'accompte') %}
|
||||
{% set cautionOk = contratPaymentPay(contrat, 'caution') %}
|
||||
{% set soldeOk = contratPaymentPay(contrat, 'solde') %}
|
||||
|
||||
<div class="contrat-card bg-white/5 border border-white/10 backdrop-blur-md rounded-[2rem] overflow-hidden hover:border-blue-500/40 transition-all group">
|
||||
<div class="grid grid-cols-1 lg:grid-cols-12">
|
||||
<div class="contrat-card relative overflow-hidden group">
|
||||
{# Background Glow Effect #}
|
||||
<div class="absolute -inset-px bg-gradient-to-r from-transparent via-white/5 to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-500 rounded-[2rem]"></div>
|
||||
|
||||
<div class="relative bg-white/[0.03] border border-white/10 backdrop-blur-md rounded-[2rem] transition-all duration-300 group-hover:bg-white/[0.06] group-hover:translate-y-[-2px] group-hover:shadow-2xl group-hover:shadow-blue-500/10">
|
||||
<div class="grid grid-cols-1 lg:grid-cols-12 items-center">
|
||||
|
||||
{# 1. REF & STATUS #}
|
||||
<div class="lg:col-span-2 p-8 border-b lg:border-b-0 lg:border-r border-white/5 text-center lg:text-left">
|
||||
<span class="text-[10px] font-bold text-blue-400 uppercase tracking-[0.2em] mb-1 block">Contrat</span>
|
||||
<h3 class="text-white font-black text-xl tracking-tighter mb-3">{{ contrat.numReservation }}</h3>
|
||||
|
||||
{# --- COLONNE 1 : RÉFÉRENCE & SIGNATURE (2/12) --- #}
|
||||
<div class="lg:col-span-2 p-8 bg-white/[0.02] flex flex-col justify-center border-r border-white/5">
|
||||
<span class="text-[9px] font-black text-blue-500 uppercase tracking-widest mb-2 block text-search">Référence</span>
|
||||
<h3 class="text-white font-black italic text-lg tracking-tighter text-search">{{ contrat.numReservation }}</h3>
|
||||
<div class="mt-4">
|
||||
{% if contrat.isSigned %}
|
||||
<span class="px-3 py-1 bg-emerald-500/10 text-emerald-500 text-[8px] font-black uppercase rounded-lg border border-emerald-500/20 inline-flex items-center gap-1">
|
||||
<svg class="w-2.5 h-2.5" fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clip-rule="evenodd"></path></svg>
|
||||
Signé
|
||||
</span>
|
||||
<div class="inline-flex items-center gap-1.5 px-3 py-1 rounded-full bg-emerald-500/10 border border-emerald-500/20 text-emerald-400 text-[9px] font-bold uppercase tracking-wider">
|
||||
<span class="w-1.5 h-1.5 rounded-full bg-emerald-500 animate-pulse"></span> Signé
|
||||
</div>
|
||||
{% else %}
|
||||
<span class="px-3 py-1 bg-amber-500/10 text-amber-500 text-[8px] font-black uppercase rounded-lg border border-amber-500/20 inline-flex items-center gap-1">
|
||||
<svg class="w-2.5 h-2.5" fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm1-12a1 1 0 10-2 0v4a1 1 0 00.293.707l2.828 2.829a1 1 0 101.415-1.415L11 9.586V6z" clip-rule="evenodd"></path></svg>
|
||||
En attente
|
||||
</span>
|
||||
<div class="inline-flex items-center gap-1.5 px-3 py-1 rounded-full bg-amber-500/10 border border-amber-500/20 text-amber-400 text-[9px] font-bold uppercase tracking-wider">
|
||||
<span class="w-1.5 h-1.5 rounded-full bg-amber-500"></span> Attente
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# --- COLONNE 2 : CLIENT (3/12) --- #}
|
||||
<div class="lg:col-span-3 p-8 border-r border-white/5">
|
||||
<div class="flex items-start gap-4">
|
||||
<div class="w-10 h-10 bg-blue-600/10 rounded-xl flex items-center justify-center text-blue-500 shrink-0">
|
||||
<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"></path></svg>
|
||||
{# 2. CLIENT #}
|
||||
<div class="lg:col-span-3 p-8 border-b lg:border-b-0 lg:border-r border-white/5">
|
||||
<div class="flex items-center gap-4">
|
||||
<div class="relative">
|
||||
<div class="w-12 h-12 bg-gradient-to-tr from-blue-600/20 to-indigo-600/20 rounded-2xl flex items-center justify-center border border-white/10 group-hover:scale-110 transition-transform">
|
||||
<svg class="w-6 h-6 text-blue-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" stroke-width="1.5"></path></svg>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<span class="text-[9px] font-black text-slate-500 uppercase tracking-widest mb-1 block">Locataire</span>
|
||||
<p class="text-white font-bold text-base uppercase italic text-search">{{ contrat.customer.surname }} {{ contrat.customer.name }}</p>
|
||||
<p class="text-slate-400 text-[11px] mt-1">{{ contrat.customer.email }}</p>
|
||||
<p class="text-white font-bold text-base tracking-tight uppercase">{{ contrat.customer.surname }} {{ contrat.customer.name }}</p>
|
||||
<p class="text-slate-500 text-xs font-medium">{{ contrat.customer.email }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# --- COLONNE 3 : LIEU & VILLE (2/12) --- #}
|
||||
<div class="lg:col-span-2 p-8 border-r border-white/5">
|
||||
<span class="text-[9px] font-black text-slate-500 uppercase tracking-widest mb-2 block">Lieu</span>
|
||||
<p class="text-white font-bold text-sm leading-tight text-search">
|
||||
{{ contrat.townEvent }}
|
||||
</p>
|
||||
<p class="text-amber-500 font-black text-[10px] text-search">{{ contrat.zipCodeEvent }}</p>
|
||||
{# 3. LIEU #}
|
||||
<div class="lg:col-span-2 p-8 border-b lg:border-b-0 lg:border-r border-white/5">
|
||||
<div class="flex flex-col">
|
||||
<span class="text-[10px] font-bold text-slate-500 uppercase tracking-widest mb-1 text-search">Destination</span>
|
||||
<p class="text-slate-200 font-bold text-sm">{{ contrat.townEvent }}</p>
|
||||
<p class="text-blue-500 font-black text-[11px]">{{ contrat.zipCodeEvent }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# --- COLONNE 4 : STATUT FINANCIER (3/12) --- #}
|
||||
<div class="lg:col-span-3 p-8 border-r border-white/5 bg-white/[0.01]">
|
||||
<span class="text-[9px] font-black text-slate-500 uppercase tracking-widest mb-4 block">Suivi Paiements</span>
|
||||
<div class="space-y-3">
|
||||
{# ACOMPTE #}
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="w-1.5 h-1.5 rounded-full {{ acompteOk ? 'bg-emerald-500 shadow-[0_0_8px_#10b981]' : 'bg-slate-600' }}"></div>
|
||||
<span class="text-[10px] {{ acompteOk ? 'text-slate-200' : 'text-slate-500' }}">Acompte</span>
|
||||
</div>
|
||||
{# 4. PAIEMENTS #}
|
||||
<div class="lg:col-span-3 p-8 bg-black/10 lg:bg-transparent">
|
||||
<div class="grid grid-cols-3 gap-2">
|
||||
|
||||
{# --- ACOMPTE --- #}
|
||||
<div class="flex flex-col items-center gap-2">
|
||||
<div class="w-8 h-8 rounded-xl flex items-center justify-center transition-all duration-300
|
||||
{{ acompteOk
|
||||
? 'bg-emerald-500/20 text-emerald-400 border border-emerald-500/30'
|
||||
: 'bg-rose-500/10 text-rose-500 border border-rose-500/20 shadow-[0_0_15px_rgba(244,63,94,0.1)]'
|
||||
}}">
|
||||
{% if acompteOk %}
|
||||
<svg class="w-3 h-3 text-emerald-500" fill="currentColor" viewBox="0 0 20 20"><path d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z"></path></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.5" d="M5 13l4 4L19 7"></path></svg>
|
||||
{% else %}
|
||||
<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.5" d="M6 18L18 6M6 6l12 12"></path></svg>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{# CAUTION #}
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="w-1.5 h-1.5 rounded-full {{ cautionOk ? 'bg-blue-500 shadow-[0_0_8px_#3b82f6]' : 'bg-rose-500 animate-pulse' }}"></div>
|
||||
<span class="text-[10px] {{ cautionOk ? 'text-slate-200' : 'text-slate-500' }}">Caution</span>
|
||||
</div>
|
||||
<span class="text-[8px] font-black {{ cautionOk ? 'text-blue-400' : 'text-rose-500' }} uppercase">{{ cautionOk ? 'OK' : 'REQUISE' }}</span>
|
||||
</div>
|
||||
|
||||
{# SOLDE #}
|
||||
<div class="pt-2 border-t border-white/10 flex items-center justify-between">
|
||||
<span class="text-[9px] font-black {{ soldeOk ? 'text-emerald-400' : 'text-white' }} uppercase italic">{{ soldeOk ? 'SOLDÉ' : 'À PERCEVOIR' }}</span>
|
||||
{% if soldeOk %}
|
||||
<span class="relative flex h-2 w-2">
|
||||
<span class="animate-ping absolute inline-flex h-full w-full rounded-full bg-emerald-400 opacity-75"></span>
|
||||
<span class="relative inline-flex rounded-full h-2 w-2 bg-emerald-500"></span>
|
||||
<span class="text-[8px] font-black uppercase tracking-tighter {{ acompteOk ? 'text-emerald-500' : 'text-rose-500' }}">
|
||||
Acompte
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{# --- CAUTION --- #}
|
||||
<div class="flex flex-col items-center gap-2">
|
||||
<div class="w-8 h-8 rounded-xl flex items-center justify-center transition-all duration-300
|
||||
{{ cautionOk
|
||||
? 'bg-emerald-500/20 text-emerald-400 border border-emerald-500/30'
|
||||
: 'bg-rose-500/20 text-rose-500 border border-rose-500/30 animate-pulse shadow-[0_0_15px_rgba(244,63,94,0.2)]'
|
||||
}}">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
{% if cautionOk %}
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z"></path>
|
||||
{% else %}
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z"></path>
|
||||
{% endif %}
|
||||
</svg>
|
||||
</div>
|
||||
<span class="text-[8px] font-black uppercase tracking-tighter {{ cautionOk ? 'text-emerald-500' : 'text-rose-500' }}">
|
||||
Caution
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{# --- SOLDE --- #}
|
||||
<div class="flex flex-col items-center gap-2">
|
||||
<div class="w-8 h-8 rounded-xl flex items-center justify-center transition-all duration-300
|
||||
{{ soldeOk
|
||||
? 'bg-emerald-500/20 text-emerald-400 border border-emerald-500/30'
|
||||
: 'bg-rose-500/10 text-rose-500 border border-rose-500/20'
|
||||
}}">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
{% if soldeOk %}
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M5 13l4 4L19 7"></path>
|
||||
{% else %}
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8c-1.657 0-3 .895-3 2s1.343 2 3 2 3 .895 3 2-1.343 2-3 2m0-8c1.11 0 2.08.402 2.599 1M12 8V7m0 1v8m0 0v1m0-1c-1.11 0-2.08-.402-2.599-1M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
|
||||
{% endif %}
|
||||
</svg>
|
||||
</div>
|
||||
<span class="text-[8px] font-black uppercase tracking-tighter {{ soldeOk ? 'text-emerald-500' : 'text-rose-500' }}">
|
||||
Solde
|
||||
</span>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# --- COLONNE 5 : ACTIONS (2/12) --- #}
|
||||
<div class="lg:col-span-2 p-6 flex flex-row lg:flex-col justify-center gap-2">
|
||||
{# 5. ACTIONS #}
|
||||
<div class="lg:col-span-2 p-6 flex lg:flex-col items-center justify-center gap-3">
|
||||
<a data-turbo="false" href="{{ path('app_crm_contrats_view', {id: contrat.id}) }}"
|
||||
title="Détails"
|
||||
class="flex-1 lg:flex-none py-3 bg-white/5 hover:bg-blue-600 text-white rounded-xl flex items-center justify-center transition-all border border-white/5">
|
||||
<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 12a3 3 0 11-6 0 3 3 0 016 0z"></path><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"></path></svg>
|
||||
class="w-full lg:w-12 h-12 rounded-2xl bg-white/5 border border-white/10 flex items-center justify-center text-white hover:bg-blue-600 hover:border-blue-400 transition-all group/btn" title="Voir">
|
||||
<svg class="w-5 h-5 group-hover/btn:scale-110 transition-transform" 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><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"></path></svg>
|
||||
</a>
|
||||
|
||||
<a data-turbo="false" download="contrat-{{ contrat.numReservation }}"
|
||||
href="{{ vich_uploader_asset(contrat,'devisFile') }}"
|
||||
class="flex-1 lg:flex-none py-3 bg-white/5 hover:bg-emerald-600 text-white rounded-xl flex items-center justify-center transition-all border border-white/5">
|
||||
<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="M7 21h10a2 2 0 002-2V9.414a1 1 0 00-.293-.707l-5.414-5.414A1 1 0 0012.586 3H7a2 2 0 00-2 2v14a2 2 0 002 2z"></path></svg>
|
||||
</a>
|
||||
|
||||
<a data-turbo="false" href="{{ path('app_crm_contrats', {idSend: contrat.id}) }}"
|
||||
onclick="return confirm('Envoyer à {{ contrat.customer.email }} ?')"
|
||||
class="flex-1 lg:flex-none py-3 bg-white/5 hover:bg-indigo-500 text-white rounded-xl flex items-center justify-center transition-all border border-white/5">
|
||||
<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="M12 19l9 2-9-18-9 18 9-2zm0 0v-8"></path></svg>
|
||||
class="w-full lg:w-12 h-12 rounded-2xl bg-white/5 border border-white/10 flex items-center justify-center text-white hover:bg-indigo-600 hover:border-indigo-400 transition-all group/btn" title="Envoyer">
|
||||
<svg class="w-5 h-5 group-hover/btn:translate-x-1 group-hover/btn:-translate-y-1 transition-transform" 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"></path></svg>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<div class="mt-12 glass-pagination">
|
||||
{{ knp_pagination_render(contrats) }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{ knp_pagination_render(contrats) }}
|
||||
|
||||
<style>
|
||||
/* Optionnel : Custom style pour la pagination KNP pour matcher le glassmorphism */
|
||||
.glass-pagination nav ul { @apply flex justify-center gap-2; }
|
||||
.glass-pagination nav ul li span,
|
||||
.glass-pagination nav ul li a {
|
||||
@apply px-4 py-2 bg-white/5 border border-white/10 rounded-xl text-white text-sm transition-all backdrop-blur-md;
|
||||
}
|
||||
.glass-pagination nav ul li.active span { @apply bg-blue-600 border-blue-500 font-bold; }
|
||||
.glass-pagination nav ul li a:hover { @apply bg-white/10 border-white/20; }
|
||||
</style>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
@@ -4,38 +4,25 @@
|
||||
|
||||
{% block actions %}
|
||||
<div class="flex items-center gap-3">
|
||||
{% if contrat.devis %}
|
||||
<a href="{{ vich_uploader_asset(contrat.devis,'devisFile') }}"
|
||||
download
|
||||
class="flex items-center gap-2 px-6 py-2.5 bg-white/5 hover:bg-white/10 border border-white/10 backdrop-blur-md rounded-xl text-white text-xs font-black uppercase italic transition-all group">
|
||||
<svg class="w-4 h-4 text-red-500 group-hover:scale-110 transition-transform" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 21h10a2 2 0 002-2V9.414a1 1 0 00-.293-.707l-5.414-5.414A1 1 0 0012.586 3H7a2 2 0 00-2 2v14a2 2 0 002 2z" />
|
||||
</svg>
|
||||
Télécharger Devis PDF
|
||||
</a>
|
||||
{% endif %}
|
||||
{% if contrat.signed %}
|
||||
<a href="{{ vich_uploader_asset(contrat,'devisSignFile') }}"
|
||||
download
|
||||
class="flex items-center gap-2 px-6 py-2.5 bg-white/5 hover:bg-white/10 border border-white/10 backdrop-blur-md rounded-xl text-white text-xs font-black uppercase italic transition-all group">
|
||||
<svg class="w-4 h-4 text-red-500 group-hover:scale-110 transition-transform" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 21h10a2 2 0 002-2V9.414a1 1 0 00-.293-.707l-5.414-5.414A1 1 0 0012.586 3H7a2 2 0 00-2 2v14a2 2 0 002 2z" />
|
||||
<a href="{{ vich_uploader_asset(contrat,'devisSignFile') }}" download
|
||||
class="flex items-center gap-2 px-6 py-2.5 bg-emerald-500/10 hover:bg-emerald-500/20 border border-emerald-500/20 backdrop-blur-md rounded-xl text-emerald-400 text-[10px] font-black uppercase italic transition-all group">
|
||||
<svg class="w-4 h-4 group-hover:scale-110 transition-transform" 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>
|
||||
Télécharger Contrat Signée PDF
|
||||
Contrat Signé PDF
|
||||
</a>
|
||||
<a href="{{ vich_uploader_asset(contrat,'devisAuditFile') }}"
|
||||
download
|
||||
class="flex items-center gap-2 px-6 py-2.5 bg-white/5 hover:bg-white/10 border border-white/10 backdrop-blur-md rounded-xl text-white text-xs font-black uppercase italic transition-all group">
|
||||
<svg class="w-4 h-4 text-red-500 group-hover:scale-110 transition-transform" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 21h10a2 2 0 002-2V9.414a1 1 0 00-.293-.707l-5.414-5.414A1 1 0 0012.586 3H7a2 2 0 00-2 2v14a2 2 0 002 2z" />
|
||||
<a href="{{ vich_uploader_asset(contrat,'devisAuditFile') }}" download
|
||||
class="flex items-center gap-2 px-6 py-2.5 bg-white/5 hover:bg-white/10 border border-white/10 backdrop-blur-md rounded-xl text-white text-[10px] font-black uppercase italic transition-all group">
|
||||
<svg class="w-4 h-4 text-blue-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 17v-2a4 4 0 00-4-4H5m11 9a2 2 0 01-2 2H5a2 2 0 01-2-2V7a2 2 0 012-2h11a2 2 0 012 2v11z" />
|
||||
</svg>
|
||||
Télécharger Audit Signée PDF
|
||||
Audit Signature
|
||||
</a>
|
||||
{% else %}
|
||||
<a href="{{ vich_uploader_asset(contrat,'devisFile') }}"
|
||||
download
|
||||
class="flex items-center gap-2 px-6 py-2.5 bg-white/5 hover:bg-white/10 border border-white/10 backdrop-blur-md rounded-xl text-white text-xs font-black uppercase italic transition-all group">
|
||||
<svg class="w-4 h-4 text-red-500 group-hover:scale-110 transition-transform" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<a href="{{ vich_uploader_asset(contrat,'devisFile') }}" download
|
||||
class="flex items-center gap-2 px-6 py-2.5 bg-rose-500/10 hover:bg-rose-500/20 border border-rose-500/20 backdrop-blur-md rounded-xl text-rose-500 text-[10px] font-black uppercase italic transition-all group">
|
||||
<svg class="w-4 h-4 group-hover:rotate-12 transition-transform" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 21h10a2 2 0 002-2V9.414a1 1 0 00-.293-.707l-5.414-5.414A1 1 0 0012.586 3H7a2 2 0 00-2 2v14a2 2 0 002 2z" />
|
||||
</svg>
|
||||
Télécharger Contrat PDF
|
||||
@@ -45,245 +32,235 @@
|
||||
{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
{# Définition des états de paiement basés sur les variables du contrôleur #}
|
||||
{# Définition des états de paiement #}
|
||||
{% set acompteOk = contratPaymentPay(contrat, 'accompte') %}
|
||||
{% set cautionOk = contratPaymentPay(contrat, 'caution') %}
|
||||
{% set soldeOk = (solde <= 0.05) %}
|
||||
|
||||
<div class="space-y-8 pb-20">
|
||||
|
||||
{# --- GRILLE DES 4 CARTES DE STATUT --- #}
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
|
||||
|
||||
{# 1. SIGNATURE #}
|
||||
<div class="relative overflow-hidden bg-white/5 border border-white/10 backdrop-blur-xl p-6 rounded-[2rem] transition-all hover:border-white/20">
|
||||
<div class="flex flex-col gap-4">
|
||||
<div class="w-10 h-10 rounded-xl flex items-center justify-center {{ contrat.isSigned ? 'bg-emerald-500/20 text-emerald-400' : 'bg-amber-500/20 text-amber-500 animate-pulse' }}">
|
||||
<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.232 5.232l3.536 3.536m-2.036-5.036a2.5 2.5 0 113.536 3.536L6.5 21.036H3v-3.572L16.732 3.732z"/></svg>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-slate-500 text-[9px] font-black uppercase tracking-widest">Contrat</p>
|
||||
{% if contrat.isSigned %}
|
||||
<p class="text-white font-bold italic uppercase">Signé</p>
|
||||
{# --- 1. BANDEAU DE STATUT RAPIDE --- #}
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
<div class="p-4 rounded-[1.5rem] bg-white/5 border border-white/10 backdrop-blur-md flex items-center justify-between">
|
||||
<span class="text-[10px] font-black uppercase text-slate-500 tracking-widest">État Juridique</span>
|
||||
{% if contrat.signed %}
|
||||
<span class="px-3 py-1 bg-emerald-500/10 text-emerald-400 text-[9px] font-black rounded-lg border border-emerald-500/20">SIGNÉ</span>
|
||||
{% else %}
|
||||
<a href="{{ signUrl }}" target="_blank" class="text-amber-500 font-black italic hover:underline">À SIGNER ICI</a>
|
||||
<span class="px-3 py-1 bg-amber-500/10 text-amber-400 text-[9px] font-black rounded-lg border border-amber-500/20 animate-pulse">EN ATTENTE</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# 2. ARRHES (ACOMPTE) #}
|
||||
<div class="relative overflow-hidden bg-white/5 border border-white/10 backdrop-blur-xl p-6 rounded-[2rem]">
|
||||
<div class="flex flex-col gap-4">
|
||||
<div class="w-10 h-10 rounded-xl flex items-center justify-center {{ acompteOk ? 'bg-blue-500/20 text-blue-400' : 'bg-slate-500/20 text-slate-400' }}">
|
||||
<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="M17 9V7a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2m2 4h10a2 2 0 002-2v-6a2 2 0 00-2-2H9a2 2 0 00-2 2v6a2 2 0 002 2zm7-5a2 2 0 11-4 0 2 2 0 014 0z"/></svg>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-slate-500 text-[9px] font-black uppercase tracking-widest">Acompte (Arrhes)</p>
|
||||
{% if acompteOk %}
|
||||
<p class="text-white font-bold italic uppercase">Encaissé</p>
|
||||
{% else %}
|
||||
<a href="{{ path('gestion_contrat_view', {num: contrat.numReservation, act: 'accomptePay'}) }}" class="text-blue-400 font-black italic hover:underline uppercase">Régler {{ arrhes|number_format(2, ',', ' ') }}€</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{# 3. CAUTION (MODE ADMINISTRATION) #}
|
||||
<div class="relative overflow-hidden bg-white/5 border border-white/10 backdrop-blur-xl p-6 rounded-[2rem]">
|
||||
<div class="flex flex-col gap-4">
|
||||
{# Icône dynamique : Pulse si non déposée, fixe si OK #}
|
||||
<div class="w-10 h-10 rounded-xl flex items-center justify-center {{ cautionOk ? 'bg-purple-500/20 text-purple-400' : 'bg-rose-500/20 text-rose-500 animate-pulse' }}">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p class="text-slate-500 text-[9px] font-black uppercase tracking-widest">Garantie (Caution)</p>
|
||||
|
||||
{% if not cautionOk %}
|
||||
{# ÉTAT 1 : ATTENTE DE DÉPÔT #}
|
||||
<div class="mt-1">
|
||||
<p class="text-rose-500 font-black italic uppercase text-sm leading-tight">Non Déposée</p>
|
||||
<p class="text-[9px] text-slate-500 uppercase font-bold italic">Attendu : {{ totalCaution|number_format(2, ',', ' ') }}€</p>
|
||||
</div>
|
||||
{% else %}
|
||||
{# ÉTAT 2 : CAUTION DÉPOSÉE #}
|
||||
{% if contrat.cautionState == null %}
|
||||
{# ACTIONS DISPONIBLES #}
|
||||
<div class="space-y-3 mt-2">
|
||||
<p class="text-white font-bold italic uppercase text-[10px] mb-2 opacity-70">Empreinte active</p>
|
||||
<div class="flex flex-col gap-2">
|
||||
{# LIBÉRER #}
|
||||
<a href="{{ path('app_crm_contrats_view', {id: contrat.id, act: 'cautionRelease'}) }}"
|
||||
data-turbo="false"
|
||||
onclick="return confirm('Confirmer la libération de la caution ?')"
|
||||
class="w-full py-1.5 bg-emerald-500/10 hover:bg-emerald-500/20 border border-emerald-500/30 rounded-lg text-emerald-400 text-[9px] font-black uppercase italic text-center transition-all">
|
||||
Libérer la caution
|
||||
</a>
|
||||
|
||||
{# ENCAISSER #}
|
||||
<form action="{{ path('app_crm_contrats_view', {id: contrat.id}) }}" method="GET" data-turbo="false" class="space-y-1">
|
||||
<input type="hidden" name="act" value="cautionCapture">
|
||||
<div class="flex items-center bg-rose-500/5 rounded-lg border border-rose-500/20 px-2 py-1">
|
||||
<input type="number" name="amountToCapture" step="0.01" max="{{ totalCaution }}" value="{{ totalCaution }}"
|
||||
class="bg-transparent border-none text-rose-500 text-[10px] font-black w-14 p-0 focus:ring-0">
|
||||
<button type="submit" onclick="return confirm('Confirmer l\'encaissement ?')"
|
||||
class="ml-auto text-rose-500 text-[9px] font-black uppercase italic hover:text-white transition-colors">
|
||||
Encaisser
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
{# ÉTAT 3 : ARCHIVÉ (RESTITUÉ OU RÉCUPÉRÉ) #}
|
||||
<div class="mt-2">
|
||||
{% if contrat.cautionState == 'restitue' %}
|
||||
<span class="px-3 py-1 bg-emerald-500/10 border border-emerald-500/20 text-emerald-400 text-[10px] font-black uppercase italic rounded-full">
|
||||
Restituée
|
||||
<div class="p-4 rounded-[1.5rem] bg-white/5 border border-white/10 backdrop-blur-md flex items-center justify-between">
|
||||
<span class="text-[10px] font-black uppercase text-slate-500 tracking-widest">Reste à percevoir</span>
|
||||
<span class="text-xs font-black {{ soldeOk ? 'text-emerald-400' : 'text-rose-500' }} italic">
|
||||
{{ soldeOk ? 'CONTRAT SOLDÉ' : (solde|number_format(2, ',', ' ') ~ ' €') }}
|
||||
</span>
|
||||
{% elseif contrat.cautionState == 'recover' %}
|
||||
<span class="px-3 py-1 bg-amber-500/10 border border-amber-500/20 text-amber-400 text-[10px] font-black uppercase italic rounded-full">
|
||||
Encaissée
|
||||
</span>
|
||||
{% else %}
|
||||
<span class="text-white font-bold text-xs uppercase opacity-50">{{ contrat.cautionState }}</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
<div class="p-4 rounded-[1.5rem] bg-white/5 border border-white/10 backdrop-blur-md flex items-center justify-between">
|
||||
<span class="text-[10px] font-black uppercase text-slate-500 tracking-widest">Référence</span>
|
||||
<span class="text-xs font-black text-blue-400 italic">#{{ contrat.numReservation }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{# 4. SOLDE #}
|
||||
<div class="relative overflow-hidden bg-white/5 border border-white/10 backdrop-blur-xl p-6 rounded-[2rem]">
|
||||
<div class="flex flex-col gap-4">
|
||||
<div class="w-10 h-10 rounded-xl flex items-center justify-center {{ soldeOk ? 'bg-emerald-500 text-white' : 'bg-amber-500/20 text-amber-500' }}">
|
||||
<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>
|
||||
|
||||
{# --- 2. INFOS CLIENT & ÉVÉNEMENT --- #}
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-8">
|
||||
{# CARTE CLIENT #}
|
||||
<div class="relative group">
|
||||
<div class="absolute -inset-0.5 bg-gradient-to-br from-blue-500/20 to-purple-500/20 rounded-[2.5rem] blur opacity-50"></div>
|
||||
<div class="relative h-full p-8 bg-slate-900/40 border border-white/10 backdrop-blur-xl rounded-[2.5rem]">
|
||||
<div class="flex items-center gap-4 mb-8">
|
||||
<div class="w-14 h-14 bg-blue-600/20 rounded-2xl flex items-center justify-center border border-blue-500/30">
|
||||
<svg class="w-7 h-7 text-blue-400" 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>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-slate-500 text-[9px] font-black uppercase tracking-widest">État du solde</p>
|
||||
{% if soldeOk %}
|
||||
<p class="text-emerald-400 font-black italic uppercase">Dossier Soldé</p>
|
||||
{% else %}
|
||||
<form action="{{ path('gestion_contrat_view', {num: contrat.numReservation}) }}" method="GET" class="flex flex-col gap-2">
|
||||
<input type="hidden" name="act" value="soldePay">
|
||||
<div class="flex items-center bg-white/5 rounded-lg border border-white/10 px-2 py-1">
|
||||
<input type="number" name="amountToPay" step="0.01" max="{{ solde }}" value="{{ solde }}"
|
||||
class="bg-transparent border-none text-white text-xs font-black w-20 p-0 focus:ring-0">
|
||||
<button type="submit" class="text-amber-500 text-[10px] font-black uppercase italic hover:text-white">Payer</button>
|
||||
</div>
|
||||
<span class="text-[8px] text-slate-500 uppercase">Reste : {{ solde|number_format(2, ',', ' ') }}€</span>
|
||||
</form>
|
||||
{% endif %}
|
||||
<h2 class="text-xl font-black text-white italic tracking-tighter uppercase">{{ contrat.customer.surname }} {{ contrat.customer.name }}</h2>
|
||||
<p class="text-slate-500 font-bold text-[10px] uppercase tracking-widest">Informations Locataire</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# --- SECTION DÉTAILS : CLIENT / LOGISTIQUE / FINANCES --- #}
|
||||
<div class="grid grid-cols-1 lg:grid-cols-3 gap-8">
|
||||
|
||||
{# CARD 1 : CLIENT #}
|
||||
<div class="bg-white/5 border border-white/10 backdrop-blur-md rounded-[2.5rem] p-8 relative overflow-hidden group">
|
||||
<div class="absolute -top-10 -right-10 w-32 h-32 bg-blue-500/5 rounded-full blur-3xl group-hover:bg-blue-500/10 transition-all"></div>
|
||||
<h2 class="text-blue-500 font-black text-[10px] uppercase tracking-[0.3em] mb-6 flex items-center gap-2">
|
||||
<svg class="w-3 h-3" 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>
|
||||
Locataire
|
||||
</h2>
|
||||
<div class="space-y-1">
|
||||
<p class="text-white font-black text-2xl uppercase italic tracking-tighter">{{ contrat.customer.surname }}</p>
|
||||
<p class="text-white font-medium text-xl uppercase italic opacity-80">{{ contrat.customer.name }}</p>
|
||||
</div>
|
||||
<div class="mt-6 pt-6 border-t border-white/5 space-y-3">
|
||||
<div class="flex items-center gap-3 text-slate-400 text-xs font-medium">
|
||||
<svg class="w-4 h-4 text-blue-500/50" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"/></svg>
|
||||
<div class="space-y-3">
|
||||
<div class="flex items-center gap-3 p-4 rounded-2xl bg-white/[0.02] border border-white/5 text-slate-300 text-sm">
|
||||
<svg class="w-4 h-4 text-blue-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" stroke-width="2"/></svg>
|
||||
{{ contrat.customer.email }}
|
||||
</div>
|
||||
<div class="flex items-center gap-3 text-slate-400 text-xs font-medium">
|
||||
<svg class="w-4 h-4 text-blue-500/50" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 5a2 2 0 012-2h3.28a1 1 0 01.948.684l1.498 4.493a1 1 0 01-.502 1.21l-2.257 1.13a11.042 11.042 0 005.516 5.516l1.13-2.257a1 1 0 011.21-.502l4.493 1.498a1 1 0 01.684.949V19a2 2 0 01-2 2h-1C9.716 21 3 14.284 3 6V5z"/></svg>
|
||||
<div class="flex items-center gap-3 p-4 rounded-2xl bg-white/[0.02] border border-white/5 text-slate-300 text-sm">
|
||||
<svg class="w-4 h-4 text-blue-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path d="M3 5a2 2 0 012-2h3.28a1 1 0 01.948.684l1.498 4.493a1 1 0 01-.502 1.21l-2.257 1.13a11.042 11.042 0 005.516 5.516l1.13-2.257a1 1 0 011.21-.502l4.493 1.498a1 1 0 01.684.949V19a2 2 0 01-2 2h-1C9.716 21 3 14.284 3 6V5z" stroke-width="2"/></svg>
|
||||
{{ contrat.customer.phone }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# CARD 2 : LOGISTIQUE #}
|
||||
<div class="bg-white/5 border border-white/10 backdrop-blur-md rounded-[2.5rem] p-8 relative overflow-hidden group">
|
||||
<h2 class="text-amber-500 font-black text-[10px] uppercase tracking-[0.3em] mb-6 flex items-center gap-2">
|
||||
<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z"/></svg>
|
||||
Lieu de l'événement
|
||||
</h2>
|
||||
{# CARTE ÉVÉNEMENT #}
|
||||
<div class="relative group">
|
||||
<div class="absolute -inset-0.5 bg-gradient-to-br from-emerald-500/20 to-teal-500/20 rounded-[2.5rem] blur opacity-50"></div>
|
||||
<div class="relative h-full p-8 bg-slate-900/40 border border-white/10 backdrop-blur-xl rounded-[2.5rem]">
|
||||
<div class="flex items-center gap-4 mb-8">
|
||||
<div class="w-14 h-14 bg-emerald-600/20 rounded-2xl flex items-center justify-center border border-emerald-500/30">
|
||||
<svg class="w-7 h-7 text-emerald-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z" /></svg>
|
||||
</div>
|
||||
<div>
|
||||
<h2 class="text-xl font-black text-white italic tracking-tighter uppercase">Lieu de l'événement</h2>
|
||||
<p class="text-slate-500 font-bold text-[10px] uppercase tracking-widest">{{ contrat.dateAt|date('d/m/Y') }} - {{ contrat.endAt|date('d/m/Y') }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="p-5 rounded-2xl bg-white/[0.02] border border-white/5">
|
||||
<span class="text-[9px] font-black text-slate-500 uppercase tracking-widest block mb-1">Adresse de livraison</span>
|
||||
<p class="text-white text-base font-bold">{{ contrat.addressEvent }}</p>
|
||||
<p class="text-emerald-400 font-black text-sm uppercase italic">{{ contrat.zipCodeEvent }} {{ contrat.townEvent }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# --- 3. ÉTAT DES PAIEMENTS AVEC ACTIONS DE GESTION --- #}
|
||||
<div class="p-8 bg-white/[0.02] border border-white/10 rounded-[2.5rem] backdrop-blur-md shadow-2xl">
|
||||
<div class="flex flex-col md:flex-row gap-8 justify-around items-start">
|
||||
|
||||
{# --- ACOMPTE --- #}
|
||||
<div class="flex flex-col items-center gap-4 text-center w-full">
|
||||
<div class="w-16 h-16 rounded-2xl flex items-center justify-center border transition-all duration-500
|
||||
{{ acompteOk ? 'bg-emerald-500/20 text-emerald-400 border-emerald-500/30 shadow-[0_0_20px_rgba(16,185,129,0.1)]' : 'bg-rose-500/10 text-rose-500 border-rose-500/20' }}">
|
||||
<svg class="w-8 h-8" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
{% if acompteOk %}<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M5 13l4 4L19 7"/>{% else %}<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>{% endif %}
|
||||
</svg>
|
||||
</div>
|
||||
<div>
|
||||
<span class="block text-[9px] font-black uppercase tracking-widest {{ acompteOk ? 'text-emerald-500' : 'text-rose-500' }}">Acompte</span>
|
||||
{% if not acompteOk %}
|
||||
<a data-turbo="false" href="{{ path('app_crm_contrats_view', {id: contrat.id, type: 'accompte'}) }}"
|
||||
class="mt-3 inline-flex px-4 py-1.5 bg-rose-500/20 hover:bg-rose-500/30 border border-rose-500/30 rounded-lg text-[10px] font-black text-rose-400 uppercase transition-all">
|
||||
Marquer réglé
|
||||
</a>
|
||||
{% else %}
|
||||
<p class="mt-2 text-[10px] text-emerald-500/60 font-bold uppercase italic">Encaissé</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="hidden md:block w-px h-20 bg-white/5 self-center"></div>
|
||||
|
||||
{# --- CAUTION --- #}
|
||||
<div class="flex flex-col items-center gap-4 text-center w-full">
|
||||
<div class="w-16 h-16 rounded-2xl flex items-center justify-center border transition-all duration-500
|
||||
{{ cautionOk ? 'bg-emerald-500/20 text-emerald-400 border-emerald-500/30 shadow-[0_0_20px_rgba(16,185,129,0.1)]' : 'bg-rose-500/20 text-rose-500 border-rose-500/30 animate-pulse' }}">
|
||||
<svg class="w-8 h-8" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
{% if cautionOk %}<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z"/>{% else %}<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z"/>{% endif %}
|
||||
</svg>
|
||||
</div>
|
||||
<div>
|
||||
<span class="block text-[9px] font-black uppercase tracking-widest {{ cautionOk ? 'text-emerald-500' : 'text-rose-500' }}">Caution</span>
|
||||
<div class="flex flex-col gap-2 mt-3">
|
||||
{% if not cautionOk %}
|
||||
<a data-turbo="false" href="{{ path('app_crm_contrats_view', {id: contrat.id, type: 'caution'}) }}"
|
||||
class="px-4 py-1.5 bg-rose-500/20 hover:bg-rose-500/30 border border-rose-500/30 rounded-lg text-[10px] font-black text-rose-400 uppercase transition-all">
|
||||
Marquer reçue
|
||||
</a>
|
||||
{% else %}
|
||||
<div class="flex gap-2">
|
||||
<a data-turbo="false" href="{{ path('app_crm_contrats_view', {id: contrat.id, action: 'encaisser'}) }}"
|
||||
class="px-3 py-1.5 bg-amber-500/20 hover:bg-amber-500/30 border border-amber-500/30 rounded-lg text-[9px] font-black text-amber-500 uppercase transition-all">
|
||||
Encaisser
|
||||
</a>
|
||||
<a data-turbo="false" href="{{ path('app_crm_contrats_view', {id: contrat.id, action: 'liberer'}) }}"
|
||||
class="px-3 py-1.5 bg-emerald-500/20 hover:bg-emerald-500/30 border border-emerald-500/30 rounded-lg text-[9px] font-black text-emerald-400 uppercase transition-all">
|
||||
Libérer
|
||||
</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="hidden md:block w-px h-20 bg-white/5 self-center"></div>
|
||||
|
||||
{# --- SOLDE --- #}
|
||||
<div class="flex flex-col items-center gap-4 text-center w-full">
|
||||
<div class="w-16 h-16 rounded-2xl flex items-center justify-center border transition-all duration-500
|
||||
{{ soldeOk ? 'bg-emerald-500/20 text-emerald-400 border-emerald-500/30 shadow-[0_0_20px_rgba(16,185,129,0.1)]' : 'bg-rose-500/10 text-rose-500 border-rose-500/20' }}">
|
||||
<svg class="w-8 h-8" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
{% if soldeOk %}<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/>{% else %}<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 9V7a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2m2 4h10a2 2 0 002-2v-6a2 2 0 00-2-2H9a2 2 0 00-2 2v6a2 2 0 002 2zm7-5a2 2 0 11-4 0 2 2 0 014 0z"/>{% endif %}
|
||||
</svg>
|
||||
</div>
|
||||
<div>
|
||||
<span class="block text-[9px] font-black uppercase tracking-widest {{ soldeOk ? 'text-emerald-500' : 'text-rose-500' }}">Solde Final</span>
|
||||
{% if not soldeOk %}
|
||||
<a data-turbo="false" href="{{ path('app_crm_contrats_view', {id: contrat.id, type: 'solde'}) }}"
|
||||
class="px-4 py-1.5 bg-rose-500/20 hover:bg-rose-500/30 border border-rose-500/30 rounded-lg text-[10px] font-black text-rose-400 uppercase transition-all">
|
||||
Régler le solde
|
||||
</a>
|
||||
{% else %}
|
||||
<p class="mt-2 text-[10px] text-emerald-500/60 font-bold uppercase italic">Totalité payée</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
{# --- 4. PRODUITS ET OPTIONS --- #}
|
||||
<div class="grid grid-cols-1 xl:grid-cols-2 gap-8">
|
||||
{# PRODUITS #}
|
||||
<div class="space-y-4">
|
||||
<div class="p-4 bg-white/[0.03] border border-white/5 rounded-2xl">
|
||||
<p class="text-slate-500 text-[10px] font-bold uppercase mb-2 text-white/50">Adresse</p>
|
||||
<p class="text-white text-sm leading-relaxed font-bold italic">
|
||||
{{ contrat.addressEvent }}<br>
|
||||
{{ contrat.zipCodeEvent }} {{ contrat.townEvent|upper }}
|
||||
</p>
|
||||
<h3 class="px-2 text-sm font-black text-slate-400 uppercase tracking-[0.3em]">Équipements loués</h3>
|
||||
<div class="bg-white/[0.02] border border-white/10 rounded-[2rem] divide-y divide-white/5 overflow-hidden">
|
||||
{% for product in contrat.contratsLines %}
|
||||
<div class="p-6 hover:bg-white/[0.03] transition-colors">
|
||||
<div class="flex justify-between items-start">
|
||||
<div>
|
||||
<h4 class="text-white font-bold text-base uppercase italic">{{ product.name }}</h4>
|
||||
<p class="text-[10px] text-slate-500 font-bold uppercase mt-1">Caution : {{ product.caution }}€</p>
|
||||
</div>
|
||||
<div class="flex items-center gap-4">
|
||||
<div class="flex-1 p-3 bg-white/[0.03] border border-white/5 rounded-2xl">
|
||||
<p class="text-[9px] font-bold uppercase text-white/50">Du</p>
|
||||
<p class="text-white font-bold text-xs italic">{{ contrat.dateAt|date('d/m/Y') }}</p>
|
||||
<div class="text-right">
|
||||
<span class="text-blue-400 font-black italic">{{ product.price1DayHt }}€</span>
|
||||
<span class="block text-[8px] text-slate-500 uppercase font-black">HT / Jour</span>
|
||||
</div>
|
||||
<div class="flex-1 p-3 bg-white/[0.03] border border-white/5 rounded-2xl">
|
||||
<p class="text-[9px] font-bold uppercase text-white/50">Au</p>
|
||||
<p class="text-white font-bold text-xs italic">{{ contrat.endAt|date('d/m/Y') }}</p>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# OPTIONS #}
|
||||
<div class="space-y-4">
|
||||
<h3 class="px-2 text-sm font-black text-slate-400 uppercase tracking-[0.3em]">Options & Services</h3>
|
||||
<div class="bg-white/[0.02] border border-white/10 rounded-[2rem] divide-y divide-white/5 overflow-hidden">
|
||||
{% for product in contrat.contratsOptions %}
|
||||
<div class="p-6 hover:bg-white/[0.03] transition-colors">
|
||||
<div class="flex justify-between items-center">
|
||||
<div class="max-w-[70%]">
|
||||
<h4 class="text-white font-bold text-sm uppercase">{{ product.name }}</h4>
|
||||
<p class="text-slate-500 text-[10px] italic mt-1">{{ product.details }}</p>
|
||||
</div>
|
||||
<div class="bg-purple-500/10 border border-purple-500/20 px-3 py-1 rounded-xl">
|
||||
<span class="text-purple-400 font-black text-xs italic">{{ product.price }}€</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="p-10 text-center text-slate-600 text-[10px] font-black uppercase tracking-widest opacity-30">Aucun supplément</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# CARD 3 : RÉCAPITULATIF FINANCIER #}
|
||||
<div class="bg-slate-900/40 border border-white/10 backdrop-blur-2xl rounded-[2.5rem] p-8 relative overflow-hidden group">
|
||||
<div class="absolute top-0 right-0 w-32 h-32 bg-blue-600/10 blur-[80px]"></div>
|
||||
<h2 class="text-white font-black text-[10px] uppercase tracking-[0.3em] mb-6 flex items-center gap-2">
|
||||
<svg class="w-3 h-3 text-blue-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 7h6m0 10v-3m-3 3h.01M9 17h.01"/></svg>
|
||||
Détail Règlement
|
||||
</h2>
|
||||
<div class="space-y-5">
|
||||
<div class="flex items-end justify-between">
|
||||
<span class="text-slate-400 text-xs font-bold uppercase italic">Total Prestation</span>
|
||||
<span class="text-2xl font-black text-white italic tracking-tighter">{{ totalHT|number_format(2, ',', ' ') }}€</span>
|
||||
</div>
|
||||
<div class="p-4 bg-white/5 border border-white/10 rounded-2xl space-y-3">
|
||||
<div class="flex items-center justify-between">
|
||||
<p class="text-blue-400 text-[9px] font-black uppercase tracking-wider">Acompte Requis</p>
|
||||
<p class="text-white font-black text-sm italic tracking-tighter">{{ arrhes|number_format(2, ',', ' ') }}€</p>
|
||||
</div>
|
||||
<div class="h-px bg-white/5"></div>
|
||||
<div class="flex items-center justify-between">
|
||||
<p class="text-purple-400 text-[9px] font-black uppercase tracking-wider">Montant Caution</p>
|
||||
<p class="text-white font-black text-sm italic tracking-tighter">{{ totalCaution|number_format(2, ',', ' ') }}€</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# --- HISTORIQUE DES PAIEMENTS --- #}
|
||||
<div class="mt-12">
|
||||
<h2 class="text-white font-black text-xl italic uppercase tracking-tighter mb-6 flex items-center gap-3">
|
||||
<span class="w-8 h-px bg-white/20"></span> Historique des transactions
|
||||
</h2>
|
||||
<div class="bg-white/5 border border-white/10 backdrop-blur-xl rounded-[2.5rem] overflow-hidden">
|
||||
<table class="w-full text-left border-collapse">
|
||||
{# --- 5. HISTORIQUE DES PAIEMENTS --- #}
|
||||
<div class="space-y-4">
|
||||
<h3 class="px-2 text-sm font-black text-slate-400 uppercase tracking-[0.3em]">Historique Financier</h3>
|
||||
<div class="bg-white/[0.02] border border-white/10 rounded-[2rem] overflow-hidden">
|
||||
<table class="w-full text-left">
|
||||
<thead>
|
||||
<tr class="border-b border-white/5 bg-white/[0.02]">
|
||||
<th class="px-8 py-5 text-[10px] font-black text-slate-500 uppercase tracking-widest">Date</th>
|
||||
<th class="px-8 py-5 text-[10px] font-black text-slate-500 uppercase tracking-widest">Type</th>
|
||||
<th class="px-8 py-5 text-[10px] font-black text-slate-500 uppercase tracking-widest">Montant</th>
|
||||
<th class="px-8 py-5 text-[10px] font-black text-slate-500 uppercase tracking-widest text-right">Action</th>
|
||||
<tr class="border-b border-white/5 bg-white/[0.03]">
|
||||
<th class="px-8 py-4 text-[9px] font-black text-slate-500 uppercase tracking-widest">Transaction</th>
|
||||
<th class="px-8 py-4 text-[9px] font-black text-slate-500 uppercase tracking-widest">Type</th>
|
||||
<th class="px-8 py-4 text-[9px] font-black text-slate-500 uppercase tracking-widest">Montant</th>
|
||||
<th class="px-8 py-4 text-[9px] font-black text-slate-500 uppercase tracking-widest text-right">Justificatif</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-white/5">
|
||||
{% for payment in contrat.contratsPayments %}
|
||||
{% if payment.state == 'complete' %}
|
||||
<tr class="group hover:bg-white/[0.02] transition-colors">
|
||||
<td class="px-8 py-5 text-xs text-white font-medium">{{ payment.paymentAt|date('d/m/Y H:i') }}</td>
|
||||
<td class="px-8 py-5 text-xs text-white font-bold tracking-tight">{{ payment.paymentAt|date('d/m/Y H:i') }}</td>
|
||||
<td class="px-8 py-5">
|
||||
<span class="px-3 py-1 rounded-full text-[9px] font-black uppercase italic
|
||||
<span class="px-2 py-1 rounded-lg text-[8px] font-black uppercase italic
|
||||
{% if payment.type == 'caution' %}bg-purple-500/10 text-purple-400 border border-purple-500/20
|
||||
{% elseif payment.type == 'accompte' %}bg-blue-500/10 text-blue-400 border border-blue-500/20
|
||||
{% else %}bg-emerald-500/10 text-emerald-400 border border-emerald-500/20{% endif %}">
|
||||
@@ -292,23 +269,21 @@
|
||||
</td>
|
||||
<td class="px-8 py-5 text-sm text-white font-black italic">{{ payment.amount|number_format(2, ',', ' ') }}€</td>
|
||||
<td class="px-8 py-5 text-right">
|
||||
<a href="{{ path('app_crm_contrats_view', {id: contrat.id, idPaymentPdf: payment.id}) }}"
|
||||
target="_blank"
|
||||
class="inline-flex items-center gap-2 text-[10px] font-black uppercase italic text-slate-400 hover:text-white group transition-all">
|
||||
<svg class="w-4 h-4 text-red-500 group-hover:scale-110 transition-transform" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path d="M7 21h10a2 2 0 002-2V9.414a1 1 0 00-.293-.707l-5.414-5.414A1 1 0 0012.586 3H7a2 2 0 00-2 2v14a2 2 0 002 2z" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||
Reçu
|
||||
<a href="{{ path('app_crm_contrats_view', {id: contrat.id, idPaymentPdf: payment.id}) }}" target="_blank"
|
||||
class="inline-flex items-center gap-2 text-[9px] font-black uppercase text-slate-400 hover:text-white transition-all">
|
||||
<svg class="w-4 h-4 text-rose-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path d="M7 21h10a2 2 0 002-2V9.414a1 1 0 00-.293-.707l-5.414-5.414A1 1 0 0012.586 3H7a2 2 0 00-2 2v14a2 2 0 002 2z" stroke-width="2"/></svg>
|
||||
REÇU PDF
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<tr>
|
||||
<td colspan="4" class="px-8 py-10 text-center text-slate-500 italic text-xs uppercase opacity-50">Aucune transaction enregistrée</td>
|
||||
</tr>
|
||||
<tr><td colspan="4" class="py-12 text-center text-slate-600 text-[10px] font-black uppercase opacity-40">Aucun paiement effectué</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
@@ -49,7 +49,11 @@
|
||||
<mj-column width="50%">
|
||||
<mj-text font-size="10px" font-weight="800" color="#94a3b8" text-transform="uppercase" align="right">Méthode</mj-text>
|
||||
<mj-text font-size="12px" font-weight="700" color="#475569" align="right" padding-top="10px">
|
||||
{% if datas.payment.card.type is defined and datas.payment.card.type == "manuel" %}
|
||||
Paiement effectuer par Ludikevent
|
||||
{% else %}
|
||||
{{ datas.payment.card.method_label|default('Carte Bancaire') }}
|
||||
{% endif %}
|
||||
</mj-text>
|
||||
</mj-column>
|
||||
</mj-section>
|
||||
|
||||
Reference in New Issue
Block a user