feat(promotion): Intègre la gestion et l'application des promotions au flux de réservation et au calcul du panier.

This commit is contained in:
Serreau Jovann
2026-02-09 11:26:52 +01:00
parent 81c4fb0df9
commit e305c21e94
12 changed files with 433 additions and 13 deletions

View File

@@ -50,6 +50,7 @@
{{ menu.nav_link(path('app_template_point_controle_index'), 'Modèles de contrôle', '<path stroke-linecap="round" stroke-linejoin="round" d="M11.35 3.836c-.065.21-.1.433-.1.664 0 .414.336.75.75.75h4.5a.75.75 0 00.75-.75 2.25 2.25 0 00-.1-.664m-5.8 0A2.251 2.251 0 0113.5 2.25H15c1.012 0 1.867.668 2.15 1.586m-5.8 0c-.376.023-.75.05-1.124.08C9.095 4.01 8.25 4.973 8.25 6.108V8.25m0 0H4.875c-.621 0-1.125.504-1.125 1.125v11.25c0 .621.504 1.125 1.125 1.125h9.75c.621 0 1.125-.504 1.125-1.125V9.375c0-.621-.504-1.125-1.125-1.125H8.25zM6.75 12h.008v.008H6.75V12zm0 3h.008v.008H6.75V15zm0 3h.008v.008H6.75V18z" />', 'app_template_point_controle_index') }}
{{ menu.nav_link(path('app_crm_product'), 'Produits', '<path stroke-linecap="round" stroke-linejoin="round" d="M20.25 7.5l-.625 10.632a2.25 2.25 0 01-2.247 2.118H6.622a2.25 2.25 0 01-2.247-2.118L3.75 7.5M10 11.25h4M3.375 7.5h17.25c.621 0 1.125-.504 1.125-1.125v-1.5c0-.621-.504-1.125-1.125-1.125H3.375c-.621 0-1.125.504-1.125 1.125v1.5c0 .621.504 1.125 1.125 1.125z" />', 'app_crm_product') }}
{{ menu.nav_link(path('app_crm_formules'), 'Formules', '<path stroke-linecap="round" stroke-linejoin="round" d="M21 7.5l-9-5.25L3 7.5m18 0l-9 5.25m9-5.25v9l-9 5.25M3 7.5l9 5.25M3 7.5v9l9 5.25m0-9v9" />', 'app_crm_formules') }}
{{ menu.nav_link(path('app_crm_promotion'), 'Promotions', '<path stroke-linecap="round" stroke-linejoin="round" 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" />', 'app_crm_promotion') }}
{{ menu.nav_link(path('app_crm_facture'), 'Facture', '<path stroke-linecap="round" stroke-linejoin="round" d="M19.5 14.25v-2.625a3.375 3.375 0 00-3.375-3.375h-1.5A1.125 1.125 0 0113.5 7.125v-1.5a3.375 3.375 0 00-3.375-3.375H8.25m2.25 0H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 00-9-9z" />', 'app_crm_facture') }}
{{ menu.nav_link(path('app_crm_customer'), 'Clients', '<path stroke-linecap="round" stroke-linejoin="round" d="M15 19.128a9.38 9.38 0 002.625.372 9.337 9.337 0 004.121-.952 4.125 4.125 0 00-7.533-2.493M15 19.128v-.003c0-1.113-.285-2.16-.786-3.07M15 19.128v.106A12.318 12.318 0 018.624 21c-2.331 0-4.512-.645-6.374-1.766l-.001-.109a6.375 6.375 0 0111.964-3.07M12 6.375a3.375 3.375 0 11-6.75 0 3.375 3.375 0 016.75 0zm8.25 2.25a2.625 2.625 0 11-5.25 0 2.625 2.625 0 015.25 0z" />', 'app_crm_customer') }}
{{ menu.nav_link(path('app_crm_devis'), 'Devis', '<path stroke-linecap="round" stroke-linejoin="round" d="M19.5 12c0-1.232-.046-2.453-.138-3.662a4.006 4.006 0 00-3.7-3.7 48.678 48.678 0 00-7.324 0 4.006 4.006 0 00-3.7 3.7c-.017.22-.032.441-.046.662M19.5 12l3-3m-3 3l-3-3m-12 3c0 1.232.046 2.453.138 3.662a4.006 4.006 0 003.7 3.7 48.656 48.656 0 007.324 0 4.006 4.006 0 003.7-3.7c.017-.22.032-.441.046-.662M4.5 12l3 3m-3-3l-3 3" />', 'app_crm_devis') }}

View File

@@ -0,0 +1,82 @@
{% extends 'dashboard/base.twig' %}
{% block title %}Gestion des Promotions{% endblock %}
{% block title_header %}Gestion des <span class="text-blue-500">Promotions</span>{% endblock %}
{% block actions %}
<a data-turbo="false" href="{{ path('app_crm_promotion_add') }}" class="flex items-center space-x-2 px-6 py-3 bg-blue-600 hover:bg-blue-500 text-white text-[10px] font-black uppercase tracking-[0.2em] rounded-xl transition-all shadow-lg shadow-blue-600/20 group">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="3" d="M12 4v16m8-8H4" />
</svg>
<span>Nouvelle Promotion</span>
</a>
{% endblock %}
{% block body %}
<div class="backdrop-blur-xl bg-[#1e293b]/40 border border-white/5 rounded-[2.5rem] overflow-hidden shadow-2xl animate-in fade-in duration-700">
<div class="overflow-x-auto">
<table class="w-full text-left border-collapse">
<thead>
<tr class="border-b border-white/5 bg-black/20">
<th class="px-6 py-5 text-[10px] font-black text-slate-300 uppercase tracking-[0.2em]">Nom</th>
<th class="px-6 py-5 text-[10px] font-black text-slate-300 uppercase tracking-[0.2em]">Pourcentage</th>
<th class="px-6 py-5 text-[10px] font-black text-slate-300 uppercase tracking-[0.2em]">Période</th>
<th class="px-6 py-5 text-[10px] font-black text-slate-300 uppercase tracking-[0.2em] text-right">Actions</th>
</tr>
</thead>
<tbody class="divide-y divide-white/5">
{% for promotion in promotions %}
<tr class="group hover:bg-white/[0.02] transition-colors">
<td class="px-6 py-4">
<span class="text-sm font-bold text-white group-hover:text-blue-400 transition-colors capitalize">{{ promotion.name }}</span>
</td>
<td class="px-6 py-4">
<span class="px-3 py-1 bg-purple-500/10 border border-purple-500/20 text-purple-400 rounded-lg text-[10px] font-black uppercase tracking-widest">-{{ promotion.percentage }}%</span>
</td>
<td class="px-6 py-4">
<div class="flex flex-col text-xs text-slate-400">
<span>Du {{ promotion.dateStart|date('d/m/Y H:i') }}</span>
<span>Au {{ promotion.dateEnd|date('d/m/Y H:i') }}</span>
</div>
</td>
<td class="px-6 py-4 text-right">
<div class="flex items-center justify-end space-x-2">
<a data-turbo="false" href="{{ path('app_crm_promotion_edit', {id: promotion.id}) }}" class="p-2 bg-blue-600/10 hover:bg-blue-600 text-blue-500 hover:text-white rounded-xl transition-all border border-blue-500/20 shadow-lg shadow-blue-600/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="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" /></svg>
</a>
<form method="post" action="{{ path('app_crm_promotion_delete', {id: promotion.id}) }}" onsubmit="return confirm('Êtes-vous sûr de vouloir supprimer cette promotion ?');" class="inline-block">
<input type="hidden" name="_token" value="{{ csrf_token('delete' ~ promotion.id) }}">
<button type="submit" class="p-2 bg-rose-500/10 hover:bg-rose-500 text-rose-500 hover:text-white rounded-xl transition-all border border-rose-500/20 shadow-lg shadow-rose-500/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="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" /></svg>
</button>
</form>
</div>
</td>
</tr>
{% else %}
<tr><td colspan="4" class="py-24 text-center italic text-slate-300 text-[10px] font-black uppercase tracking-[0.2em]">Aucune promotion</td></tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
{# PAGINATION #}
{% if promotions.getTotalItemCount is defined and promotions.getTotalItemCount > promotions.getItemNumberPerPage %}
<div class="mt-8 flex justify-center custom-pagination">{{ knp_pagination_render(promotions) }}</div>
{% endif %}
<style>
.custom-pagination nav ul { @apply flex space-x-2; }
.custom-pagination nav ul li span,
.custom-pagination nav ul li a {
@apply px-4 py-2 rounded-xl bg-[#1e293b]/40 backdrop-blur-md border border-white/5 text-slate-400 text-xs font-bold transition-all;
}
.custom-pagination nav ul li.active span {
@apply bg-blue-600 border-blue-500 text-white shadow-lg shadow-blue-600/20;
}
.custom-pagination nav ul li a:hover {
@apply bg-white/10 text-white border-white/20;
}
</style>
{% endblock %}

View File

@@ -0,0 +1,61 @@
{% extends 'dashboard/base.twig' %}
{% block title %}{{ is_edit ? 'Modifier' : 'Nouvelle' }} Promotion{% endblock %}
{% block title_header %}Gestion des <span class="text-blue-500">Promotions</span>{% endblock %}
{% block body %}
<div class="max-w-4xl mx-auto animate-in fade-in slide-in-from-bottom-4 duration-700">
{{ form_start(form) }}
<div class="backdrop-blur-xl bg-[#1e293b]/40 border border-white/5 rounded-[2.5rem] p-8 shadow-2xl">
<h3 class="text-lg font-bold text-white mb-6 flex items-center">
<span class="w-8 h-8 bg-blue-600/20 text-blue-500 rounded-lg flex items-center justify-center mr-3 text-[10px] font-black">01</span>
Détails de la promotion
</h3>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<div class="md:col-span-2">
{{ form_label(form.name, 'Nom de la promotion', {'label_attr': {'class': 'text-[10px] font-black text-slate-300 uppercase tracking-[0.2em] ml-1 mb-2 block'}}) }}
{{ form_widget(form.name, {'attr': {'placeholder': 'Ex: Soldes d\'été', 'class': 'w-full bg-slate-900/50 border-white/5 rounded-2xl text-white focus:ring-blue-500/20 focus:border-blue-500 transition-all py-3.5 px-5'}}) }}
</div>
<div>
{{ form_label(form.percentage, 'Pourcentage de réduction', {'label_attr': {'class': 'text-[10px] font-black text-slate-300 uppercase tracking-[0.2em] ml-1 mb-2 block'}}) }}
<div class="relative">
{{ form_widget(form.percentage, {'attr': {'placeholder': 'Ex: 20', 'class': 'w-full bg-slate-900/50 border-white/5 rounded-2xl text-white focus:ring-blue-500/20 focus:border-blue-500 transition-all py-3.5 px-5'}}) }}
<span class="absolute right-5 top-1/2 -translate-y-1/2 text-slate-500 font-bold">%</span>
</div>
</div>
<div></div> {# Spacer #}
<div>
{{ form_label(form.dateStart, 'Date de début', {'label_attr': {'class': 'text-[10px] font-black text-slate-300 uppercase tracking-[0.2em] ml-1 mb-2 block'}}) }}
{{ form_widget(form.dateStart, {'attr': {'class': 'w-full bg-slate-900/50 border-white/5 rounded-2xl text-white focus:ring-blue-500/20 focus:border-blue-500 transition-all py-3.5 px-5'}}) }}
</div>
<div>
{{ form_label(form.dateEnd, 'Date de fin', {'label_attr': {'class': 'text-[10px] font-black text-slate-300 uppercase tracking-[0.2em] ml-1 mb-2 block'}}) }}
{{ form_widget(form.dateEnd, {'attr': {'class': 'w-full bg-slate-900/50 border-white/5 rounded-2xl text-white focus:ring-blue-500/20 focus:border-blue-500 transition-all py-3.5 px-5'}}) }}
</div>
</div>
</div>
{# FOOTER ACTIONS #}
<div class="mt-12 mb-20 flex items-center justify-between backdrop-blur-xl bg-slate-900/40 p-6 rounded-[2rem] border border-white/5 shadow-xl">
<a href="{{ path('app_crm_promotion') }}" class="text-slate-400 hover:text-white text-xs font-bold uppercase tracking-widest transition-colors flex items-center gap-2">
<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="M10 19l-7-7m0 0l7-7m-7 7h18" /></svg>
Annuler
</a>
<button type="submit" class="relative overflow-hidden group px-12 py-4 bg-blue-600 hover:bg-blue-500 text-white text-[10px] font-black uppercase tracking-[0.2em] rounded-2xl transition-all shadow-lg shadow-blue-600/30">
<span class="relative z-10 flex items-center">
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="3" d="M5 13l4 4L19 7" /></svg>
{{ is_edit ? 'Enregistrer les modifications' : 'Créer la promotion' }}
</span>
<div class="absolute inset-0 bg-gradient-to-r from-transparent via-white/10 to-transparent -translate-x-full group-hover:translate-x-full transition-transform duration-1000"></div>
</button>
</div>
{{ form_end(form) }}
</div>
{% endblock %}

View File

@@ -141,8 +141,18 @@
</div>
<div class="mt-6 border-t border-slate-200 pt-4 space-y-2">
{% if cart.discount > 0 %}
<div class="flex justify-between text-sm text-slate-400">
<span>Total HT</span>
<span class="font-medium line-through">{{ (cart.totalHT + cart.discount)|number_format(2, ',', ' ') }} €</span>
</div>
<div class="flex justify-between text-sm text-[#f39e36] font-bold">
<span>Promotion : {{ cart.promotion }}</span>
<span>-{{ cart.discount|number_format(2, ',', ' ') }} €</span>
</div>
{% endif %}
<div class="flex justify-between text-sm text-slate-600">
<span>Total HT</span>
<span>Total HT {% if cart.discount > 0 %}Remisé{% endif %}</span>
<span class="font-medium">{{ cart.totalHT|number_format(2, ',', ' ') }} €</span>
</div>
{% if cart.tvaEnabled %}

View File

@@ -104,8 +104,18 @@
</div>
<div class="mt-6 border-t border-slate-200 pt-4 space-y-2">
{% if cart.discount > 0 %}
<div class="flex justify-between text-sm text-slate-400">
<span>Total HT</span>
<span class="font-medium line-through">{{ (cart.totalHT + cart.discount)|number_format(2, ',', ' ') }} €</span>
</div>
<div class="flex justify-between text-sm text-[#f39e36] font-bold">
<span>Promotion : {{ cart.promotion }}</span>
<span>-{{ cart.discount|number_format(2, ',', ' ') }} €</span>
</div>
{% endif %}
<div class="flex justify-between text-sm text-slate-600">
<span>Total HT</span>
<span>Total HT {% if cart.discount > 0 %}Remisé{% endif %}</span>
<span class="font-medium">{{ cart.totalHT|number_format(2, ',', ' ') }} €</span>
</div>
{% if cart.tvaEnabled %}

View File

@@ -102,6 +102,15 @@
<span class="text-[11px] md:text-[12px] font-black text-slate-300 uppercase tracking-[0.4em] mb-4 block italic text-center md:text-left">
Référence : {{ product.ref }}
</span>
{% if promotion %}
<div class="mb-6 text-center md:text-left">
<span class="inline-block bg-gradient-to-r from-pink-500 to-rose-500 text-white px-4 py-2 rounded-xl text-[10px] md:text-xs font-black uppercase tracking-widest shadow-lg transform -rotate-2">
PROMO : {{ promotion.name }} (-{{ promotion.percentage }}%)
</span>
</div>
{% endif %}
<h1 class="text-5xl md:text-8xl font-black text-slate-900 uppercase italic tracking-tighter leading-[0.9] mb-10 text-center md:text-left">
{{ product.name }}
</h1>