```
✨ feat(reservation): Ajoute la gestion des contrats client et améliore l'authentification.
Corrige l'URL de retour, ajoute la gestion des contrats client et le logout client.
```
This commit is contained in:
6
.env
6
.env
@@ -83,9 +83,9 @@ STRIPE_PK=pk_test_51SUA22173W4aeFB1nO6oFfDZ12HOTffDKtCshhZ8rkUg6kUO2ZaQC0tK72rhE
|
||||
STRIPE_SK=sk_test_51SUA22173W4aeFB16EB2LxGI0hNvNJzFshDI98zRImWBIhSfzqOGAz5TlPxSpUWbj3x4COm6kmSsaal9FpQR1A7M0022DvjbbR
|
||||
STRIPE_WEBHOOKS_SECRET=
|
||||
|
||||
SIGN_URL=https://785fe10a414b.ngrok-free.app
|
||||
STRIPE_BASEURL=https://785fe10a414b.ngrok-free.app
|
||||
CONTRAT_BASEURL=https://785fe10a414b.ngrok-free.app
|
||||
SIGN_URL=https://a292239e6756.ngrok-free.app
|
||||
STRIPE_BASEURL=https://a292239e6756.ngrok-free.app
|
||||
CONTRAT_BASEURL=https://a292239e6756.ngrok-free.app
|
||||
|
||||
MINIO_S3_URL=
|
||||
MINIO_S3_CLIENT_ID=
|
||||
|
||||
@@ -24,7 +24,7 @@ security:
|
||||
custom_authenticator: App\Security\CustomerAuthenticator
|
||||
user_checker: App\Security\UserChecker # Si vous voulez vérifier l'activation du compte
|
||||
logout:
|
||||
path: app_logout
|
||||
path: reservation_logout
|
||||
target: reservation_login # Redirige vers le login client après déconnexion
|
||||
remember_me:
|
||||
secret: '%kernel.secret%'
|
||||
|
||||
@@ -116,6 +116,11 @@ class ReserverController extends AbstractController
|
||||
|
||||
]);
|
||||
}
|
||||
#[Route('/reservation/logout', name: 'reservation_logout')]
|
||||
public function revervationLogout(): Response
|
||||
{
|
||||
return $this->redirectToRoute('reservation');
|
||||
}
|
||||
#[Route('/reservation/creation-compte', name: 'reservation_register')]
|
||||
public function revervationRegister(): Response
|
||||
{
|
||||
|
||||
@@ -90,11 +90,6 @@ class Devis
|
||||
#[ORM\Column]
|
||||
private ?\DateTimeImmutable $endAt = null;
|
||||
|
||||
/**
|
||||
* @var Collection<int, Options>
|
||||
*/
|
||||
#[ORM\OneToMany(targetEntity: Options::class, mappedBy: 'devis')]
|
||||
private Collection $options;
|
||||
|
||||
/**
|
||||
* @var Collection<int, DevisOptions>
|
||||
@@ -105,7 +100,6 @@ class Devis
|
||||
public function __construct()
|
||||
{
|
||||
$this->devisLines = new ArrayCollection();
|
||||
$this->options = new ArrayCollection();
|
||||
$this->devisOptions = new ArrayCollection();
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ class Client
|
||||
{
|
||||
private \Docuseal\Api $docuseal;
|
||||
private string $baseUrl;
|
||||
private string $logo;
|
||||
|
||||
public function __construct(
|
||||
private readonly RequestStack $requestStack,
|
||||
|
||||
@@ -24,6 +24,7 @@ class StripeExtension extends AbstractExtension
|
||||
new TwigFilter('totalQuoto',[$this,'totalQuoto']),
|
||||
new TwigFilter('totalQuotoAccompte', [$this, 'totalQuotoAccompte']),
|
||||
new TwigFilter('totalContrat',[$this,'totalContrat']),
|
||||
new TwigFilter('totalContratAccompte',[$this,'totalContratAccompte']),
|
||||
new TwigFilter('devisSignUrl',[$this,'devisSignUrl'])
|
||||
];
|
||||
}
|
||||
@@ -56,6 +57,19 @@ class StripeExtension extends AbstractExtension
|
||||
}
|
||||
}
|
||||
|
||||
return (float) $totalHT;
|
||||
}
|
||||
public function totalContratAccompte(Contrats $devis): float
|
||||
{
|
||||
$totalHT = 0;
|
||||
|
||||
// 1. Calcul des lignes de produits (Location)
|
||||
foreach ($devis->getContratsLines() as $line) {
|
||||
$totalHT = $totalHT + $line->getCaution();
|
||||
}
|
||||
|
||||
|
||||
|
||||
return (float) $totalHT;
|
||||
}
|
||||
public function totalQuotoAccompte(Devis $devis): float
|
||||
@@ -64,7 +78,7 @@ class StripeExtension extends AbstractExtension
|
||||
|
||||
// 1. Calcul des lignes de produits (Location)
|
||||
foreach ($devis->getDevisLines() as $line) {
|
||||
$$totalHT = $totalHT + $line->getProduct()->getCaution();
|
||||
$totalHT = $totalHT + $line->getProduct()->getCaution();
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
{# Header de bienvenue #}
|
||||
<div class="mb-10 flex flex-col md:flex-row md:items-end md:justify-between gap-4">
|
||||
<div>
|
||||
<h1 class="text-3xl font-extrabold text-gray-900">Bonjour, {{ app.user.firstname|default('Client') }} !</h1>
|
||||
<h1 class="text-3xl font-extrabold text-gray-900">Bonjour, {{ app.user.name|default('Client') }} !</h1>
|
||||
<p class="text-gray-600">Gérez vos réservations et documents Ludik Event en un clic.</p>
|
||||
</div>
|
||||
{% if active != '' %}
|
||||
|
||||
129
templates/reservation/contrat/gestion_contrat/contrats.twig
Normal file
129
templates/reservation/contrat/gestion_contrat/contrats.twig
Normal file
@@ -0,0 +1,129 @@
|
||||
<div class="space-y-8 animate-fade-in">
|
||||
{# --- EN-TÊTE --- #}
|
||||
<div class="border-b border-gray-100 pb-6 flex flex-col md:flex-row md:items-center justify-between gap-4">
|
||||
<div>
|
||||
<h2 class="text-2xl font-bold text-gray-900">Mes Réservations</h2>
|
||||
<p class="text-sm text-gray-500">Suivez l'avancement de vos contrats et l'état de vos règlements.</p>
|
||||
</div>
|
||||
<div class="bg-blue-600 text-white px-5 py-2 rounded-2xl text-xs font-bold uppercase tracking-wider shadow-lg shadow-blue-100">
|
||||
{{ customer.contrats|length }} Contrat(s) actif(s)
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# --- LISTE DES CONTRATS --- #}
|
||||
<div class="grid grid-cols-1 gap-6">
|
||||
{% for contrat in customer.contrats %}
|
||||
<div class="bg-white rounded-[2.5rem] border border-gray-100 p-6 md:p-8 shadow-sm hover:shadow-xl transition-all duration-300 group">
|
||||
<div class="flex flex-col lg:flex-row gap-8">
|
||||
|
||||
{# COLONNE 1 : IDENTIFICATION ET LIEU #}
|
||||
<div class="lg:w-1/3 space-y-4">
|
||||
<div class="flex items-center gap-4">
|
||||
<div class="bg-gray-900 text-white p-3.5 rounded-2xl shadow-lg">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
|
||||
</svg>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-[10px] font-bold text-gray-400 uppercase tracking-widest">Référence Contrat</p>
|
||||
<h3 class="text-xl font-black text-gray-900 tracking-tight">#{{ contrat.numReservation }}</h3>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-gray-50 rounded-2xl p-4 border border-gray-100">
|
||||
<div class="flex items-center gap-2 mb-2 text-gray-400">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor"><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>
|
||||
<p class="text-[10px] font-bold uppercase tracking-widest">Lieu de l'événement</p>
|
||||
</div>
|
||||
<p class="text-sm font-bold text-gray-800 leading-snug">
|
||||
{{ contrat.adressEvent }}<br>
|
||||
{% if contrat.adress2Event %}{{ contrat.adress2Event }}<br>{% endif %}
|
||||
{{ contrat.zipCodeEvent }} {{ contrat.townEvent|upper }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# COLONNE 2 : FINANCES ET ÉTAT DES PAIEMENTS #}
|
||||
<div class="lg:w-2/3 flex flex-col justify-between">
|
||||
|
||||
{# Chiffres clés #}
|
||||
<div class="grid grid-cols-1 sm:grid-cols-3 gap-4 mb-8">
|
||||
<div class="p-4 bg-blue-50/50 rounded-2xl border border-blue-100/50">
|
||||
<p class="text-[10px] font-bold text-blue-400 uppercase mb-1 tracking-tighter">Total Contrat</p>
|
||||
<p class="text-lg font-black text-blue-700">{{ contrat|totalContrat }} €</p>
|
||||
</div>
|
||||
<div class="p-4 bg-orange-50/50 rounded-2xl border border-orange-100/50">
|
||||
<p class="text-[10px] font-bold text-orange-400 uppercase mb-1 tracking-tighter">Arrhes (25%)</p>
|
||||
<p class="text-lg font-black text-orange-700">{{ (contrat|totalContrat * 0.25)|number_format(2, ',', ' ') }} €</p>
|
||||
</div>
|
||||
<div class="p-4 bg-green-50/50 rounded-2xl border border-green-100/50">
|
||||
<p class="text-[10px] font-bold text-green-400 uppercase mb-1 tracking-tighter">Déjà versé</p>
|
||||
<p class="text-lg font-black text-green-700">{{ contrat|totalContratAccompte }} €</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# Badges de Statuts (Paiements) #}
|
||||
<div class="flex flex-wrap items-center gap-3">
|
||||
|
||||
{# ACOMPTE #}
|
||||
<div class="flex items-center gap-2 px-3 py-2 bg-gray-50 rounded-xl border border-gray-100">
|
||||
<span class="text-[10px] font-bold text-gray-400 uppercase">Acompte</span>
|
||||
{% if contratPaymentPay(contrat, 'accompte') %}
|
||||
<span class="flex items-center gap-1 text-[10px] font-black text-green-600 bg-green-100 px-2 py-0.5 rounded-lg uppercase">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-3 w-3" viewBox="0 0 20 20" fill="currentColor"><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" /></svg>
|
||||
Réglé
|
||||
</span>
|
||||
{% else %}
|
||||
<span class="text-[10px] font-black text-orange-500 bg-orange-100 px-2 py-0.5 rounded-lg uppercase">À payer</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{# CAUTION #}
|
||||
<div class="flex items-center gap-2 px-3 py-2 bg-gray-50 rounded-xl border border-gray-100">
|
||||
<span class="text-[10px] font-bold text-gray-400 uppercase">Caution</span>
|
||||
{% if contratPaymentPay(contrat, 'caution') %}
|
||||
<span class="text-[10px] font-black text-green-600 bg-green-100 px-2 py-0.5 rounded-lg uppercase tracking-tight">Réceptionnée</span>
|
||||
{% else %}
|
||||
<span class="text-[10px] font-black text-red-500 bg-red-100 px-2 py-0.5 rounded-lg uppercase tracking-tight">Manquante</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{# SOLDE #}
|
||||
<div class="flex items-center gap-2 px-3 py-2 bg-gray-50 rounded-xl border border-gray-100">
|
||||
<span class="text-[10px] font-bold text-gray-400 uppercase">Solde</span>
|
||||
{% if contratPaymentPay(contrat, 'solde') %}
|
||||
<span class="text-[10px] font-black text-green-600 bg-green-100 px-2 py-0.5 rounded-lg uppercase">Payé</span>
|
||||
{% else %}
|
||||
<span class="text-[10px] font-black text-gray-400 bg-gray-200 px-2 py-0.5 rounded-lg uppercase">Attente</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{# BOUTON ACTION #}
|
||||
<div class="ml-auto pt-4 md:pt-0 w-full md:w-auto">
|
||||
<a href="{{ path('gestion_contrat_view', {num: contrat.numReservation}) }}"
|
||||
class="flex items-center justify-center gap-3 bg-gray-900 text-white px-8 py-3.5 rounded-2xl font-bold text-sm hover:bg-blue-600 transition-all shadow-xl shadow-gray-200 active:scale-95 group-hover:bg-blue-600">
|
||||
Voir les détails
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 transition-transform group-hover:translate-x-1" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14 5l7 7m0 0l-7 7m7-7H3" />
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
{# --- ÉTAT VIDE --- #}
|
||||
<div class="py-24 text-center bg-white rounded-[3rem] border-2 border-dashed border-gray-100">
|
||||
<div class="mb-4 inline-block p-4 bg-gray-50 rounded-full text-gray-300">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-12 w-12" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4" />
|
||||
</svg>
|
||||
</div>
|
||||
<p class="text-gray-400 font-medium">Vous n'avez aucun contrat actif pour le moment.</p>
|
||||
<a href="{{ path('reservation') }}" class="mt-4 inline-block text-blue-600 font-bold hover:underline">Effectuer une nouvelle réservation</a>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
@@ -9,7 +9,7 @@
|
||||
{# HEADER : NAVIGATION, TITRE, DATES & LIEU #}
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-6 mb-10">
|
||||
<div class="md:col-span-2 flex items-center gap-6">
|
||||
<a href="{{ path('reservation') }}" class="w-12 h-12 bg-white rounded-2xl border border-slate-100 flex items-center justify-center text-slate-400 hover:text-blue-600 transition-all shadow-sm">
|
||||
<a href="{{ path('gestion_contrat') }}" class="w-12 h-12 bg-white rounded-2xl border border-slate-100 flex items-center justify-center text-slate-400 hover:text-blue-600 transition-all shadow-sm">
|
||||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"/></svg>
|
||||
</a>
|
||||
<div class="flex flex-col md:flex-row md:items-center gap-4 md:gap-8">
|
||||
|
||||
@@ -130,7 +130,7 @@
|
||||
Mon Espace
|
||||
</a>
|
||||
|
||||
<a href="{{ path('app_logout') }}" class="text-gray-500 hover:text-red-600 transition-colors" title="Déconnexion" aria-label="Se déconnecter">
|
||||
<a href="{{ path('reservation_logout') }}" class="text-gray-500 hover:text-red-600 transition-colors" title="Déconnexion" aria-label="Se déconnecter">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1"/></svg>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user