Suppression complète du système de calcul de frais de livraison (rayon 30km depuis Danizy) : - Route /estimer-la-livraison et template estimate_delivery.twig - Calcul automatique livraison dans FlowController et ReserverController - Champs distance/prix livraison dans la vue admin flow - Ligne "Frais de livraison" sur les devis générés - Section 7.2 (mise en relation + rayon 30km) dans les CGV (twig + PDF contrat/devis) - Liens navigation "Estimer la livraison" (desktop + mobile) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
325 lines
22 KiB
Twig
325 lines
22 KiB
Twig
{% extends 'dashboard/base.twig' %}
|
|
|
|
{% block title %}Détail de la réservation #{{ session.id }}{% endblock %}
|
|
{% block title_header %}Réservation #{{ session.id }}{% endblock %}
|
|
|
|
{% block body %}
|
|
<div class="space-y-8 pb-20">
|
|
|
|
{% set isActionable = session.typePaiement %}
|
|
<div class="flex items-center gap-3 justify-end">
|
|
<a href="{{ path('app_crm_flow') }}"
|
|
class="group relative flex items-center gap-2 px-6 py-3 bg-slate-700 hover:bg-slate-600 rounded-2xl text-white text-xs font-bold uppercase tracking-wider transition-all shadow-lg">
|
|
<svg class="w-4 h-4 transition-transform group-hover:-translate-x-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 19l-7-7m0 0l7-7m-7 7h18" />
|
|
</svg>
|
|
Retour
|
|
</a>
|
|
<a href="{{ path('app_crm_flow_delete', {id: session.id}) }}"
|
|
onclick="return confirm('Voulez-vous vraiment supprimer cette demande ? Cette action est irréversible.')"
|
|
data-turbo="false"
|
|
class="group relative flex items-center gap-2 px-6 py-3 bg-red-600 hover:bg-red-500 rounded-2xl text-white text-xs font-bold uppercase tracking-wider transition-all shadow-lg shadow-red-500/20 {{ not isActionable ? 'opacity-50 pointer-events-none cursor-not-allowed' : '' }}">
|
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
|
|
</svg>
|
|
Refuser
|
|
</a>
|
|
<a href="{{ path('app_crm_flow_allow', {id: session.id}) }}"
|
|
data-turbo="false"
|
|
class="group relative flex items-center gap-2 px-6 py-3 bg-emerald-600 hover:bg-emerald-500 rounded-2xl text-white text-xs font-bold uppercase tracking-wider transition-all shadow-lg shadow-emerald-500/20 {{ not isActionable ? 'opacity-50 pointer-events-none cursor-not-allowed' : '' }}">
|
|
<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="M5 13l4 4L19 7" />
|
|
</svg>
|
|
Valider la demande
|
|
</a>
|
|
</div>
|
|
|
|
|
|
|
|
{# BILLING CONFIG #}
|
|
<div class="bg-slate-800/40 backdrop-blur-xl border border-slate-700/50 rounded-[2rem] p-8">
|
|
<h3 class="text-lg font-bold text-white mb-6 flex items-center gap-3">
|
|
<div class="p-2 bg-indigo-500/10 rounded-lg text-indigo-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 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"></path></svg>
|
|
</div>
|
|
Configuration Facturation
|
|
</h3>
|
|
<form action="{{ path('app_crm_flow_update', {id: session.id}) }}" method="post" class="space-y-4">
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
<div>
|
|
<label class="block text-[10px] font-bold text-slate-400 uppercase tracking-widest mb-1">Mode de paiement</label>
|
|
<select name="typePaiement" class="w-full bg-slate-900/50 border border-slate-700 rounded-lg px-3 py-2 text-white text-sm focus:border-blue-500 focus:ring-1 focus:ring-blue-500 outline-none transition-all">
|
|
<option value="">Sélectionner...</option>
|
|
{% for type in ['Paiement Via Chorus', 'Paiement En ligne', 'Paiement Après événement', 'Autre mode de paiement'] %}
|
|
<option value="{{ type }}" {% if session.typePaiement == type %}selected{% endif %}>{{ type }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
</div>
|
|
<div>
|
|
<label class="block text-[10px] font-bold text-slate-400 uppercase tracking-widest mb-1">Prestataire</label>
|
|
<select name="prestataire" class="w-full bg-slate-900/50 border border-slate-700 rounded-lg px-3 py-2 text-white text-sm focus:border-blue-500 focus:ring-1 focus:ring-blue-500 outline-none transition-all">
|
|
<option value="">-- Aucun prestataire assigné --</option>
|
|
{% for p in prestataires %}
|
|
<option value="{{ p.id }}" {% if session.prestataire and session.prestataire.id == p.id %}selected{% endif %}>
|
|
{{ p.surname }} {{ p.name }} ({{ p.email }})
|
|
</option>
|
|
{% endfor %}
|
|
</select>
|
|
</div>
|
|
</div>
|
|
<div class="flex justify-end mt-4">
|
|
<button type="submit" class="py-2 px-6 bg-indigo-600 hover:bg-indigo-500 text-white text-xs font-bold uppercase tracking-widest rounded-lg transition-all">
|
|
Enregistrer les modifications
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
|
|
<div class="grid grid-cols-1 lg:grid-cols-2 gap-8">
|
|
|
|
{# CLIENT INFO #}
|
|
<div class="bg-slate-800/40 backdrop-blur-xl border border-slate-700/50 rounded-[2rem] p-8">
|
|
<h3 class="text-lg font-bold text-white mb-6 flex items-center gap-3">
|
|
<div class="p-2 bg-blue-500/10 rounded-lg text-blue-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="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>
|
|
</div>
|
|
Informations Client
|
|
</h3>
|
|
|
|
{% if session.customer %}
|
|
<div class="space-y-4">
|
|
<div class="flex items-center justify-between p-4 bg-slate-900/50 rounded-xl border border-slate-700/50">
|
|
<span class="text-slate-400 text-xs uppercase tracking-wider font-bold">Nom complet</span>
|
|
<span class="text-white font-bold">{{ session.customer.surname }} {{ session.customer.name }}</span>
|
|
</div>
|
|
<div class="flex items-center justify-between p-4 bg-slate-900/50 rounded-xl border border-slate-700/50">
|
|
<span class="text-slate-400 text-xs uppercase tracking-wider font-bold">Email</span>
|
|
<a href="mailto:{{ session.customer.email }}" class="text-blue-400 hover:text-blue-300 transition-colors font-medium">{{ session.customer.email }}</a>
|
|
</div>
|
|
<div class="flex items-center justify-between p-4 bg-slate-900/50 rounded-xl border border-slate-700/50">
|
|
<span class="text-slate-400 text-xs uppercase tracking-wider font-bold">Téléphone</span>
|
|
<a href="tel:{{ session.customer.phone }}" class="text-white font-medium hover:text-blue-400 transition-colors">{{ session.customer.phone }}</a>
|
|
</div>
|
|
</div>
|
|
{% else %}
|
|
<div class="p-8 text-center border-2 border-dashed border-slate-700 rounded-2xl">
|
|
<p class="text-slate-500 font-medium">Aucun client associé pour le moment.</p>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
|
|
{# LOCATION & TECHNICAL INFO #}
|
|
<div class="bg-slate-800/40 backdrop-blur-xl border border-slate-700/50 rounded-[2rem] p-8">
|
|
<h3 class="text-lg font-bold text-white mb-6 flex items-center gap-3">
|
|
<div class="p-2 bg-purple-500/10 rounded-lg text-purple-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="M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z" /><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 11a3 3 0 11-6 0 3 3 0 016 0z"></path></svg>
|
|
</div>
|
|
Lieu & Technique
|
|
</h3>
|
|
|
|
<div class="space-y-4">
|
|
<div class="p-4 bg-slate-900/50 rounded-xl border border-slate-700/50">
|
|
<span class="block text-slate-400 text-[10px] uppercase tracking-wider font-bold mb-1">Adresse de l'événement</span>
|
|
<p class="text-white font-medium">{{ session.adressEvent|default('Non renseignée') }}</p>
|
|
{% if session.adress2Event %}<p class="text-slate-400 text-sm mt-1">{{ session.adress2Event }}</p>{% endif %}
|
|
<p class="text-blue-400 font-bold mt-1">{{ session.zipCodeEvent }} {{ session.townEvent }}</p>
|
|
</div>
|
|
|
|
<div class="grid grid-cols-2 gap-4">
|
|
<div class="p-4 bg-slate-900/50 rounded-xl border border-slate-700/50">
|
|
<span class="block text-slate-400 text-[10px] uppercase tracking-wider font-bold mb-1">Type de sol</span>
|
|
<p class="text-white font-medium">{{ session.typeSol|default('-') }}</p>
|
|
</div>
|
|
<div class="p-4 bg-slate-900/50 rounded-xl border border-slate-700/50">
|
|
<span class="block text-slate-400 text-[10px] uppercase tracking-wider font-bold mb-1">Pente</span>
|
|
<p class="text-white font-medium">{{ session.pente|default('-') }}</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="p-4 bg-slate-900/50 rounded-xl border border-slate-700/50">
|
|
<span class="block text-slate-400 text-[10px] uppercase tracking-wider font-bold mb-1">Accès</span>
|
|
<p class="text-slate-300 text-sm leading-relaxed">{{ session.access|default('Aucune information') }}</p>
|
|
</div>
|
|
|
|
<div class="p-4 bg-slate-900/50 rounded-xl border border-slate-700/50 flex justify-between items-center">
|
|
<span class="text-slate-400 text-xs uppercase tracking-wider font-bold">Distance Prise élec.</span>
|
|
<span class="text-white font-bold">{{ session.distancePower|default(0) }} m</span>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
|
|
{# CART CONTENT #}
|
|
<div class="bg-slate-800/40 backdrop-blur-xl border border-slate-700/50 rounded-[2rem] p-8">
|
|
<h3 class="text-lg font-bold text-white mb-6 flex items-center gap-3">
|
|
<div class="p-2 bg-emerald-500/10 rounded-lg text-emerald-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="M16 11V7a4 4 0 00-8 0v4M5 9h14l1 12H4L5 9z"></path></svg>
|
|
</div>
|
|
Contenu de la demande
|
|
</h3>
|
|
|
|
{% if session.formule %}
|
|
<div class="mb-6 p-4 bg-blue-500/10 border border-blue-500/20 rounded-xl flex items-center gap-3">
|
|
<div class="p-2 bg-blue-500/20 rounded-lg text-blue-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="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10"/></svg>
|
|
</div>
|
|
<div>
|
|
<p class="text-[10px] font-bold text-blue-400 uppercase tracking-widest">Formule appliquée</p>
|
|
<p class="text-white font-bold">{{ session.formule.name }}</p>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
{% if session.products['start'] is defined and session.products['end'] is defined %}
|
|
<div class="flex items-center gap-4 mb-8 p-4 bg-slate-900/50 rounded-xl border border-slate-700/50 w-fit">
|
|
<div class="flex items-center gap-2">
|
|
<span class="text-slate-400 text-xs font-bold uppercase">Du</span>
|
|
<span class="text-white font-bold">{{ session.products['start']|date('d/m/Y') }}</span>
|
|
</div>
|
|
<span class="text-slate-600">➔</span>
|
|
<div class="flex items-center gap-2">
|
|
<span class="text-slate-400 text-xs font-bold uppercase">Au</span>
|
|
<span class="text-white font-bold">{{ session.products['end']|date('d/m/Y') }}</span>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<div class="space-y-4">
|
|
{% if session.products['ids'] is defined %}
|
|
{% for productId in session.products['ids'] %}
|
|
{% set product = loadProductById(productId) %}
|
|
{% if product %}
|
|
<div class="flex items-center gap-4 p-4 bg-slate-900/50 rounded-xl border border-slate-700/50">
|
|
{% if product.image %}
|
|
<img src="{{ product.image }}" alt="{{ product.name }}" class="w-12 h-12 rounded-lg object-cover bg-slate-800">
|
|
{% else %}
|
|
<div class="w-12 h-12 rounded-lg bg-slate-800 flex items-center justify-center text-slate-500">
|
|
<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="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z"></path></svg>
|
|
</div>
|
|
{% endif %}
|
|
<div>
|
|
<p class="text-white font-bold">{{ product.name }}</p>
|
|
<span class="text-xs text-slate-500">Réf: #{{ productId }}</span>
|
|
<div class="mt-1 text-xs text-slate-400">
|
|
<span class="block">1er jour : {{ product.price1day|number_format(2, ',', ' ') }} €</span>
|
|
{% if product.priceSup > 0 %}
|
|
<span class="block">Jours supp : {{ product.priceSup|number_format(2, ',', ' ') }} €</span>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{# LINKED OPTIONS #}
|
|
{% if session.products.options[productId] is defined and session.products.options[productId]|length > 0 %}
|
|
<div class="mt-2 ml-16 space-y-1">
|
|
{% for optionId in session.products.options[productId] %}
|
|
{% set option = loadOptionById(optionId) %}
|
|
{% if option %}
|
|
<div class="flex items-center gap-2 text-xs text-slate-400">
|
|
<span class="bg-indigo-500/10 text-indigo-400 px-1.5 py-0.5 rounded text-[10px] font-bold uppercase border border-indigo-500/20">Option</span>
|
|
<span>{{ option.name }}</span>
|
|
<span class="font-bold text-slate-300">+ {{ option.price|number_format(2, ',', ' ') }} €</span>
|
|
</div>
|
|
{% endif %}
|
|
{% endfor %}
|
|
</div>
|
|
{% endif %}
|
|
{% else %}
|
|
<div class="flex items-center justify-between p-4 bg-slate-900/50 rounded-xl border border-slate-700/50">
|
|
<span class="text-slate-400 italic">Produit introuvable (ID #{{ productId }})</span>
|
|
</div>
|
|
{% endif %}
|
|
{% endfor %}
|
|
{% else %}
|
|
<p class="text-slate-500 italic">Aucun produit.</p>
|
|
{% endif %}
|
|
|
|
{# ORPHAN OPTIONS #}
|
|
{% if session.options is defined %}
|
|
{% set hasOrphans = false %}
|
|
{% for prodId, opts in session.options %}
|
|
{% if prodId not in session.products['ids']|default([]) %}
|
|
{% set hasOrphans = true %}
|
|
{% endif %}
|
|
{% endfor %}
|
|
|
|
{% if hasOrphans %}
|
|
<div class="mt-6 pt-6 border-t border-slate-700/50">
|
|
<span class="text-[10px] font-bold text-slate-400 uppercase tracking-[0.2em] mb-3 block">Options supplémentaires</span>
|
|
{% for prodId, opts in session.options %}
|
|
{% if prodId not in session.products['ids']|default([]) %}
|
|
{% for optionId in opts %}
|
|
{% set option = loadOptionById(optionId) %}
|
|
{% if option %}
|
|
<div class="flex items-center gap-3 p-3 bg-slate-900/30 rounded-xl border border-slate-700/30 mb-2">
|
|
<div class="h-8 w-8 bg-indigo-500/10 rounded-lg flex items-center justify-center text-indigo-500 border border-indigo-500/20">
|
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6"/></svg>
|
|
</div>
|
|
<span class="text-sm font-medium text-slate-300">{{ option.name }}</span>
|
|
<span class="ml-auto text-sm font-bold text-white">{{ option.price|number_format(2, ',', ' ') }} € HT</span>
|
|
</div>
|
|
{% endif %}
|
|
{% endfor %}
|
|
{% endif %}
|
|
{% endfor %}
|
|
</div>
|
|
{% endif %}
|
|
{% endif %}
|
|
</div>
|
|
|
|
{% if session.details %}
|
|
<div class="mt-8">
|
|
<span class="text-[10px] font-bold text-slate-400 uppercase tracking-[0.2em] mb-2 block">Notes client</span>
|
|
<div class="p-4 bg-amber-500/10 border border-amber-500/20 rounded-xl text-amber-200 text-sm leading-relaxed">
|
|
{{ session.details }}
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
{# TOTAL #}
|
|
{% set totalData = totalSession(session) %}
|
|
<div class="mt-8 pt-6 border-t border-slate-700">
|
|
<div class="flex flex-col gap-2 items-end">
|
|
{% if session.promotion %}
|
|
<div class="flex items-center gap-2 text-sm text-amber-400 font-bold uppercase mb-1">
|
|
<span>Promotion : {{ session.promotion.name }}</span>
|
|
<span class="bg-amber-500/10 px-2 py-0.5 rounded text-amber-500">-{{ session.promotion.percentage }}%</span>
|
|
</div>
|
|
<div class="text-xs text-slate-500 font-bold line-through">
|
|
{{ totalData.originalHT|number_format(2, ',', ' ') }} € HT
|
|
</div>
|
|
{% endif %}
|
|
|
|
<div class="text-sm text-slate-400">
|
|
Durée : <span class="text-white font-bold">{{ totalData.duration }} jour{{ totalData.duration > 1 ? 's' : '' }}</span>
|
|
</div>
|
|
<div class="text-xl text-white">
|
|
Total HT : <span class="font-black">{{ totalData.ht|number_format(2, ',', ' ') }} €</span>
|
|
</div>
|
|
{% if totalData.tvaEnabled %}
|
|
<div class="text-sm text-slate-500">
|
|
Soit <span class="font-bold text-slate-300">{{ (totalData.ht * 1.20)|number_format(2, ',', ' ') }} € TTC</span> (TVA 20%)
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div> </div>
|
|
|
|
{# BILLING INFO #}
|
|
<div class="bg-slate-800/40 backdrop-blur-xl border border-slate-700/50 rounded-[2rem] p-8">
|
|
<h3 class="text-lg font-bold text-white mb-6 flex items-center gap-3">
|
|
<div class="p-2 bg-indigo-500/10 rounded-lg text-indigo-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 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"></path></svg>
|
|
</div>
|
|
Facturation
|
|
</h3>
|
|
|
|
<div class="p-4 bg-slate-900/50 rounded-xl border border-slate-700/50">
|
|
<p class="text-white font-medium">{{ session.billingAddress|default('Même que livraison') }}</p>
|
|
<p class="text-blue-400 font-bold mt-1">{{ session.billingZipCode }} {{ session.billingTown }}</p>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
{% endblock %}
|