- Add POST /admin/commandes/{id}/forcer-validation to force validate pending
orders (generates tickets, sends emails, notifies organizer)
- Add "Forcer validation" button in orders template for pending orders
- Fix retrievePaymentIntent to query on organizer's Connect account
- Update stripe:sync to pass organizer stripeAccountId when checking payments
- Add 3 tests for force validation (pending, non-pending, not found)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
134 lines
7.8 KiB
Twig
134 lines
7.8 KiB
Twig
{% extends 'admin/base.html.twig' %}
|
|
|
|
{% block title %}Commandes{% endblock %}
|
|
|
|
{% block body %}
|
|
<div class="mb-8">
|
|
<h1 class="text-3xl font-black uppercase tracking-tighter italic heading-page">Commandes</h1>
|
|
<p class="font-bold text-gray-500 italic">{{ orders.getTotalItemCount }} commande{{ orders.getTotalItemCount > 1 ? 's' : '' }}.</p>
|
|
</div>
|
|
|
|
<div class="grid grid-cols-2 md:grid-cols-4 gap-4 mb-8">
|
|
<div class="admin-card text-center">
|
|
<p class="admin-stat-label font-black uppercase text-gray-500">Payees</p>
|
|
<p class="text-2xl font-black text-green-600">{{ totalOrders }}</p>
|
|
</div>
|
|
<div class="admin-card text-center">
|
|
<p class="admin-stat-label font-black uppercase text-gray-500">CA Total HT</p>
|
|
<p class="text-2xl font-black text-indigo-600">{{ totalCA|number_format(2, ',', ' ') }} €</p>
|
|
</div>
|
|
<div class="admin-card text-center">
|
|
<p class="admin-stat-label font-black uppercase text-gray-500">Remboursees</p>
|
|
<p class="text-2xl font-black text-yellow-600">{{ totalRefunded }}</p>
|
|
</div>
|
|
<div class="admin-card text-center">
|
|
<p class="admin-stat-label font-black uppercase text-gray-500">Annulees</p>
|
|
<p class="text-2xl font-black text-red-600">{{ totalCancelled }}</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="admin-card mb-8">
|
|
<h2 class="text-sm font-black uppercase tracking-widest mb-4">Filtrer</h2>
|
|
<form method="get" action="{{ path('app_admin_orders') }}" class="flex flex-wrap gap-4 items-end">
|
|
<div class="flex-1 min-w-[200px]">
|
|
<label for="orders-q" class="block text-[10px] font-black uppercase tracking-widest text-gray-500 mb-1">Recherche</label>
|
|
<input type="text" id="orders-q" name="q" value="{{ search }}" class="admin-form-input" placeholder="Numero, nom, email...">
|
|
</div>
|
|
<div>
|
|
<label for="orders-status" class="block text-[10px] font-black uppercase tracking-widest text-gray-500 mb-1">Statut</label>
|
|
<select id="orders-status" name="status" class="admin-form-input">
|
|
<option value="">Tous</option>
|
|
<option value="pending" {{ status == 'pending' ? 'selected' : '' }}>En attente</option>
|
|
<option value="paid" {{ status == 'paid' ? 'selected' : '' }}>Payee</option>
|
|
<option value="cancelled" {{ status == 'cancelled' ? 'selected' : '' }}>Annulee</option>
|
|
<option value="refunded" {{ status == 'refunded' ? 'selected' : '' }}>Remboursee</option>
|
|
</select>
|
|
</div>
|
|
<button type="submit" class="admin-btn-search font-black uppercase text-xs tracking-widest hover:bg-indigo-600 hover:text-black transition-all">Filtrer</button>
|
|
{% if search or status %}
|
|
<a href="{{ path('app_admin_orders') }}" class="admin-btn-clear font-black uppercase text-xs tracking-widest hover:bg-gray-100 transition-all">Effacer</a>
|
|
{% endif %}
|
|
</form>
|
|
</div>
|
|
|
|
<div class="admin-card !p-0">
|
|
<table class="admin-table">
|
|
<thead>
|
|
<tr>
|
|
<th class="text-[10px] font-black uppercase tracking-widest text-white">Commande</th>
|
|
<th class="text-[10px] font-black uppercase tracking-widest text-white">Acheteur</th>
|
|
<th class="text-[10px] font-black uppercase tracking-widest text-white">Evenement</th>
|
|
<th class="text-[10px] font-black uppercase tracking-widest text-white">Billets</th>
|
|
<th class="text-[10px] font-black uppercase tracking-widest text-white">Total HT</th>
|
|
<th class="text-[10px] font-black uppercase tracking-widest text-white">Date</th>
|
|
<th class="text-[10px] font-black uppercase tracking-widest text-white text-right">Statut</th>
|
|
<th class="text-[10px] font-black uppercase tracking-widest text-white text-right">Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for order in orders %}
|
|
<tr class="hover:bg-gray-50 transition-all">
|
|
<td>
|
|
<p class="font-bold text-sm">{{ order.orderNumber }}</p>
|
|
{% if order.paymentMethod %}
|
|
<p class="text-[10px] text-gray-400">{{ order.paymentMethod }}{% if order.cardBrand %} {{ order.cardBrand|upper }}{% endif %}{% if order.cardLast4 %} **** {{ order.cardLast4 }}{% endif %}</p>
|
|
{% endif %}
|
|
</td>
|
|
<td>
|
|
<p class="font-bold text-sm">{{ order.firstName }} {{ order.lastName }}</p>
|
|
<p class="text-xs text-gray-400">{{ order.email }}</p>
|
|
</td>
|
|
<td class="text-sm text-gray-600">{{ order.event.title }}</td>
|
|
<td class="text-xs text-gray-500">
|
|
{% for item in order.items %}
|
|
{{ item.billetName }} x{{ item.quantity }}{% if not loop.last %}, {% endif %}
|
|
{% endfor %}
|
|
</td>
|
|
<td class="font-black text-sm text-indigo-600">{{ order.totalHTDecimal|number_format(2, ',', ' ') }} €</td>
|
|
<td class="text-xs text-gray-400 whitespace-nowrap">{{ order.createdAt|date('d/m/Y H:i') }}</td>
|
|
<td class="text-right">
|
|
{% if order.status == 'paid' %}
|
|
<span class="admin-badge-green text-xs font-black uppercase">Payee</span>
|
|
{% elseif order.status == 'pending' %}
|
|
<span class="admin-badge-yellow text-xs font-black uppercase">En attente</span>
|
|
{% elseif order.status == 'refunded' %}
|
|
<span class="admin-badge-yellow text-xs font-black uppercase">Remboursee</span>
|
|
{% elseif order.status == 'cancelled' %}
|
|
<span class="admin-badge-red text-xs font-black uppercase">Annulee</span>
|
|
{% endif %}
|
|
{% if order.invitation %}
|
|
<span class="admin-badge-indigo text-xs font-black uppercase ml-1">Invitation</span>
|
|
{% endif %}
|
|
</td>
|
|
<td class="text-right whitespace-nowrap">
|
|
{% if order.status == 'paid' %}
|
|
<a href="{{ path('app_admin_order_tickets', {id: order.id}) }}" target="_blank" class="inline-block text-[10px] font-black uppercase tracking-widest text-indigo-600 hover:text-indigo-800 transition-all" title="Telecharger les billets">Billets</a>
|
|
{% elseif order.status == 'pending' %}
|
|
<form method="post" action="{{ path('app_admin_order_force_validate', {id: order.id}) }}" onsubmit="return confirm('Forcer la validation de la commande {{ order.orderNumber }} ? Les billets seront generes et envoyes au client.')">
|
|
<button type="submit" class="text-[10px] font-black uppercase tracking-widest text-green-600 hover:text-green-800 transition-all">Forcer validation</button>
|
|
</form>
|
|
{% endif %}
|
|
</td>
|
|
</tr>
|
|
{% else %}
|
|
<tr>
|
|
<td colspan="8" class="!text-center !py-12 text-gray-400 font-bold text-sm">Aucune commande.</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
{% if orders.getTotalItemCount > 20 %}
|
|
<div class="flex justify-center gap-2 mt-6">
|
|
{% for page in 1..orders.getPageCount %}
|
|
{% if page == orders.getCurrentPageNumber %}
|
|
<span class="admin-page-active text-xs font-black">{{ page }}</span>
|
|
{% else %}
|
|
<a href="{{ path('app_admin_orders', {page: page, status: status, q: search}) }}" class="admin-page-link text-xs font-black hover:bg-gray-100 transition-all">{{ page }}</a>
|
|
{% endif %}
|
|
{% endfor %}
|
|
</div>
|
|
{% endif %}
|
|
{% endblock %}
|