```
✨ feat(contrat): Affiche les détails du contrat et gère les paiements. 📝 chore(contrat/view): Traduit "ID Yousign" en "Numéro de signature". 🎨 style(reservation/search): Supprime une condition d'affichage des produits. ♻️ refactor(ContratController): Modifie les routes de paiement du contrat. 🐛 fix(Signature/Client): Corrige les valeurs par défaut des champs signature. 🎨 style(revervation/produit): Améliore l'affichage de l'image du produit. ```
This commit is contained in:
@@ -42,7 +42,7 @@ use Vich\UploaderBundle\Templating\Helper\UploaderHelper;
|
||||
class ContratController extends AbstractController
|
||||
{
|
||||
|
||||
#[Route('/reservation/contrat/payment/cancel/{id}', name: 'gestion_contrat_cancel')]
|
||||
#[Route('/contrat/payment/cancel/{id}', name: 'gestion_contrat_cancel')]
|
||||
public function gestionContratCancel(
|
||||
Contrats $contrat,
|
||||
Request $request
|
||||
@@ -54,7 +54,7 @@ class ContratController extends AbstractController
|
||||
'contrat' => $contrat
|
||||
]);
|
||||
}
|
||||
#[Route('/reservation/contrat/payment/success/{id}', name: 'gestion_contrat_success')]
|
||||
#[Route('/contrat/payment/success/{id}', name: 'gestion_contrat_success')]
|
||||
public function gestionContratSuccess(
|
||||
Contrats $contrat,
|
||||
Request $request,
|
||||
@@ -226,6 +226,73 @@ class ContratController extends AbstractController
|
||||
}
|
||||
|
||||
|
||||
if ($request->query->has('act') && $request->query->get('act') === 'accomptePay') {
|
||||
// On vérifie s'il n'y a pas déjà un acompte payé ou en cours
|
||||
$existingPayment = $entityManager->getRepository(ContratsPayments::class)->findOneBy([
|
||||
'contrat' => $contrat,
|
||||
'type' => 'accompte',
|
||||
'state' => ['complete', 'created']
|
||||
]);
|
||||
|
||||
if (!$existingPayment) {
|
||||
$result = $stripeClient->createPaymentAccompte($arrhes, $contrat);
|
||||
if ($result['state']) {
|
||||
$pl = new ContratsPayments();
|
||||
$pl->setContrat($contrat);
|
||||
$pl->setType("accompte");
|
||||
$pl->setAmount($arrhes);
|
||||
$pl->setPaymentAt(new \DateTimeImmutable('now'));
|
||||
$pl->setState("created");
|
||||
$pl->setPaymentId($result['id']);
|
||||
|
||||
$entityManager->persist($pl);
|
||||
$entityManager->flush();
|
||||
|
||||
return new RedirectResponse($result['url']);
|
||||
}
|
||||
} elseif ($existingPayment->getState() === 'created') {
|
||||
// Si une session existe déjà, on redirige vers Stripe (ou on en recrée une si expirée via ton service)
|
||||
return new RedirectResponse($stripeClient->linkPaymentAccompte($arrhes,$contrat,$existingPayment)['url']);
|
||||
}
|
||||
}
|
||||
|
||||
// --- GESTION PAIEMENT CAUTION (GARANTIE) ---
|
||||
if ($request->query->get('act') === 'cautionPay') {
|
||||
// On vérifie si une caution n'est pas déjà validée
|
||||
$existingCaution = $entityManager->getRepository(ContratsPayments::class)->findOneBy([
|
||||
'contrat' => $contrat,
|
||||
'type' => 'caution',
|
||||
'state' => 'complete'
|
||||
]);
|
||||
|
||||
if (!$existingCaution) {
|
||||
// Appel au service Stripe pour la caution (montant totalCaution calculé plus haut)
|
||||
$result = $stripeClient->createPaymentCaution($totalCaution, $contrat);
|
||||
|
||||
if ($result['state']) {
|
||||
$pl = new ContratsPayments();
|
||||
$pl->setContrat($contrat);
|
||||
$pl->setType("caution");
|
||||
$pl->setAmount($totalCaution);
|
||||
$pl->setPaymentAt(new \DateTimeImmutable('now'));
|
||||
$pl->setState("created");
|
||||
$pl->setPaymentId($result['id']);
|
||||
|
||||
$entityManager->persist($pl);
|
||||
$entityManager->flush();
|
||||
|
||||
return new RedirectResponse($result['url']);
|
||||
}
|
||||
} else {
|
||||
// SCÉNARIO 2 : RÉCUPÉRATION OU MISE À JOUR (si le montant a changé par exemple)
|
||||
$result = $stripeClient->linkPaymentCaution($totalCaution, $contrat, $existingCaution);
|
||||
|
||||
if ($result['state']) {
|
||||
return new RedirectResponse($result['url']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($request->query->has('act') && $request->query->get('act') === 'soldePay') {
|
||||
// 1. Récupération et sécurisation du montant
|
||||
$amountRequested = (float) $request->query->get('amountToPay', $solde);
|
||||
|
||||
@@ -75,6 +75,7 @@ class ContratsController extends AbstractController
|
||||
|
||||
#[Route(path: '/crm/contrats/view/{id}', name: 'app_crm_contrats_view', options: ['sitemap' => false], methods: ['GET', 'POST'])]
|
||||
public function contratsView(
|
||||
?Contrats $contrat,
|
||||
EntityManagerInterface $entityManager,
|
||||
Request $request,
|
||||
Client $client,
|
||||
@@ -83,7 +84,59 @@ class ContratsController extends AbstractController
|
||||
EventDispatcherInterface $eventDispatcher,
|
||||
KernelInterface $kernel,
|
||||
): Response {
|
||||
if (!$contrat) {
|
||||
throw $this->createNotFoundException('Contrat non trouvé.');
|
||||
}
|
||||
|
||||
$totalHt = 0;
|
||||
$totalCaution = 0;
|
||||
|
||||
// 1. Calcul de la durée en jours
|
||||
$dateStart = $contrat->getDateAt();
|
||||
$dateEnd = $contrat->getEndAt();
|
||||
|
||||
// On ajoute +1 pour inclure le jour de début et de fin
|
||||
$interval = $dateStart->diff($dateEnd);
|
||||
$days = $interval->days + 1;
|
||||
|
||||
// 2. Calcul des lignes avec tarif dégressif
|
||||
foreach ($contrat->getContratsLines() as $line) {
|
||||
// Premier jour
|
||||
$priceLine = $line->getPrice1DayHt();
|
||||
|
||||
// Jours supplémentaires
|
||||
if ($days > 1) {
|
||||
$priceLine += ($line->getPriceSupDayHt() * ($days - 1));
|
||||
}
|
||||
|
||||
$totalHt += $priceLine;
|
||||
$totalCaution += $line->getCaution();
|
||||
}
|
||||
|
||||
// 3. Ajout des options (forfaitaires)
|
||||
foreach ($contrat->getContratsOptions() as $option) {
|
||||
$totalHt += $option->getPrice();
|
||||
}
|
||||
|
||||
// 4. Calcul du solde (Total - paiements déjà effectués)
|
||||
$dejaPaye = 0;
|
||||
foreach ($contrat->getContratsPayments() as $payment) {
|
||||
if ($payment->getState() === 'complete' && $payment->getType() !== 'caution') {
|
||||
$dejaPaye += $payment->getAmount();
|
||||
}
|
||||
}
|
||||
|
||||
$solde = $totalHt - $dejaPaye;
|
||||
|
||||
return $this->render('dashboard/contrats/view.twig', [
|
||||
'contrat' => $contrat,
|
||||
'days' => $days,
|
||||
'solde' => $solde,
|
||||
'totalHT' => $totalHt,
|
||||
'signedNumber' => $contrat->getSignID(),
|
||||
'arrhes' => $totalHt * 0.25,
|
||||
'totalCaution' => $totalCaution,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -306,6 +306,7 @@ class ReserverController extends AbstractController
|
||||
"priceSup" => $p->getPriceSup(),
|
||||
'link' => $this->generateUrl('reservation_product_show',['id'=>$p->slug()]),
|
||||
];
|
||||
|
||||
}
|
||||
}
|
||||
return $this->render('revervation/search.twig',[
|
||||
|
||||
@@ -77,10 +77,10 @@ class Client
|
||||
'email' => $devis->getCustomer()->getEmail(),
|
||||
'name' => $devis->getCustomer()->getSurname() . ' ' . $devis->getCustomer()->getName(),
|
||||
'fields' => [
|
||||
['name'=>'cgv','default_value'=>true],
|
||||
['name'=>'assurance','default_value'=>true],
|
||||
['name'=>'securite','default_value'=>true],
|
||||
['name'=>'arrhes','default_value'=>true],
|
||||
['name'=>'cgv','default_value'=>false],
|
||||
['name'=>'assurance','default_value'=>false],
|
||||
['name'=>'securite','default_value'=>false],
|
||||
['name'=>'arrhes','default_value'=>false],
|
||||
],
|
||||
'metadata' => [
|
||||
'id' => $devis->getId(),
|
||||
|
||||
@@ -110,7 +110,7 @@
|
||||
|
||||
{# --- COLONNE 5 : ACTIONS (2/12) --- #}
|
||||
<div class="lg:col-span-2 p-6 flex flex-row lg:flex-col justify-center gap-2">
|
||||
<a data-turbo="false" href="{{ path('app_crm_contrats', {id: contrat.id}) }}"
|
||||
<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>
|
||||
|
||||
274
templates/dashboard/contrats/view.twig
Normal file
274
templates/dashboard/contrats/view.twig
Normal file
@@ -0,0 +1,274 @@
|
||||
{% extends 'dashboard/base.twig' %}
|
||||
|
||||
{% block title %}Contrat N°{{ contrat.numReservation }}{% endblock %}
|
||||
|
||||
{% 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 Contrat PDF
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
{# Définition des états de paiement basés sur les variables du contrôleur #}
|
||||
{% 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>
|
||||
{% else %}
|
||||
<a href="{{ signUrl }}" target="_blank" class="text-amber-500 font-black italic hover:underline">À SIGNER ICI</a>
|
||||
{% 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">
|
||||
<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 %}
|
||||
{# Affichage simple du statut sans lien #}
|
||||
<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 : Caution déposée - Actions d'administration #}
|
||||
<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">
|
||||
{# ACTION 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>
|
||||
|
||||
{# ACTION 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 partiel ou total ?')"
|
||||
class="ml-auto text-rose-500 text-[9px] font-black uppercase italic hover:text-white transition-colors">
|
||||
Encaisser
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</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>
|
||||
</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 %}
|
||||
</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>
|
||||
{{ 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>
|
||||
{{ contrat.customer.phone }}
|
||||
</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>
|
||||
<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>
|
||||
</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>
|
||||
<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>
|
||||
</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">
|
||||
<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>
|
||||
</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">
|
||||
<span class="px-3 py-1 rounded-full text-[9px] 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 %}">
|
||||
{{ payment.type|replace({'_': ' '}) }}
|
||||
</span>
|
||||
</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>
|
||||
</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>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -235,7 +235,7 @@
|
||||
<p class="text-sm font-black uppercase italic leading-none">Contrat Signé</p>
|
||||
</div>
|
||||
<div class="p-6 text-center">
|
||||
<p class="text-[10px] text-slate-400 font-black uppercase mb-1 tracking-widest">ID Yousign</p>
|
||||
<p class="text-[10px] text-slate-400 font-black uppercase mb-1 tracking-widest">Numéro de signature</p>
|
||||
<p class="text-[10px] font-mono font-bold text-slate-800 break-all bg-slate-50 p-3 rounded-xl border border-slate-100">{{ signedNumber }}</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -309,7 +309,7 @@
|
||||
<p class="text-3xl font-black text-slate-900 italic mb-4">{{ totalCaution|number_format(2, ',', ' ') }}€</p>
|
||||
{% set canPayCaution = (date('now') >= contrat.dateAt.modify('-7 days')) %}
|
||||
{% if canPayCaution and contratPaymentPay(contrat, 'accompte') %}
|
||||
<a href="{{ path('gestion_contrat_view', {'num': contrat.numReservation,'act':'cautionPay'}) }}" class="block w-full bg-slate-900 text-white py-4 rounded-xl font-black uppercase text-xs hover:bg-blue-600 transition-all shadow-md">Déposer l'empreinte</a>
|
||||
<a data-turbo="false" href="{{ path('gestion_contrat_view', {'num': contrat.numReservation,'act':'cautionPay'}) }}" class="block w-full bg-slate-900 text-white py-4 rounded-xl font-black uppercase text-xs hover:bg-blue-600 transition-all shadow-md">Déposer l'empreinte</a>
|
||||
{% else %}
|
||||
<div class="p-3 bg-slate-50 rounded-xl border border-slate-100">
|
||||
<p class="text-[9px] text-slate-400 font-black uppercase tracking-tighter">Lien actif le {{ contrat.dateAt.modify('-7 days')|date('d/m/Y') }}</p>
|
||||
|
||||
@@ -81,14 +81,14 @@
|
||||
|
||||
{# --- COLONNE GAUCHE : VISUEL --- #}
|
||||
<div class="sticky top-24">
|
||||
<div class="relative overflow-hidden rounded-[4rem] bg-slate-50 aspect-[4/5] shadow-2xl">
|
||||
<div class="relative overflow-hidden rounded-[4rem] bg-slate-50 aspect-[4/5]">
|
||||
{% if product.imageName %}
|
||||
<img src="{{ vich_uploader_asset(product,'imageFile') | imagine_filter('webp') }}"
|
||||
alt="{{ product.name }}"
|
||||
class="w-full h-full object-cover">
|
||||
class="w-full h-full object-contain">
|
||||
{% else %}
|
||||
<div class="h-full flex flex-col items-center justify-center p-12 text-center opacity-20">
|
||||
<img src="{{ asset('provider/images/favicon.png') }}" alt="Ludik Event" class="w-40 h-40">
|
||||
<div class="h-full flex flex-col items-center justify-center p-12 text-center opacity-50">
|
||||
<img src="{{ asset('provider/images/favicon.png') }}" alt="Ludik Event" class="w-75 h-75">
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
@@ -139,7 +139,7 @@
|
||||
</div>
|
||||
|
||||
{# SÉLECTEUR DE PÉRIODE --- #}
|
||||
<div class="bg-blue-50/50 p-8 rounded-[3rem] border border-blue-100">
|
||||
<div class="bg-blue-50/50 p-8 rounded-[3rem] border border-blue-100 hidden">
|
||||
<span class="block text-[10px] font-black text-blue-600 uppercase tracking-[0.2em] mb-6 italic text-center">Planifiez votre événement</span>
|
||||
<div class="flex flex-col sm:flex-row gap-4">
|
||||
<div class="flex-1">
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
{% for product in products %}
|
||||
{# Chaque card utilise des spans différents pour créer l'effet Bento #}
|
||||
<div class="group relative bg-white rounded-3xl shadow-sm hover:shadow-xl transition-all duration-300 overflow-hidden border border-gray-100
|
||||
{% if loop.index0 % 5 == 0 %} md:col-span-2 md:row-span-2 {% else %} md:col-span-2 md:row-span-1 {% endif %}">
|
||||
md:col-span-2 md:row-span-2">
|
||||
|
||||
{# Image de fond avec overlay #}
|
||||
<div class="absolute inset-0 z-0">
|
||||
|
||||
Reference in New Issue
Block a user