Add stock management, order notifications, webhooks, expiration cron, and billet type validation
- Decrement billet quantity after purchase in BilletOrderService::generateOrderTickets
- Block purchase when stock is exhausted (quantity <= 0) in OrderController::buildOrderItems
- Add organizer email notification on new order (order_notification_orga template)
- Add organizer email notification on cancel/refund (order_cancelled_orga template)
- Add ExpirePendingOrdersCommand (app:orders:expire-pending) cron every 5min via Ansible
- Cancels pending orders older than 30 minutes, restores stock, invalidates tickets
- Includes BilletBuyerRepository::findExpiredPending query method
- 3 unit tests covering: no expired orders, stock restoration, unlimited billets
- Add payment_intent.payment_failed webhook: cancels order, logs audit, emails buyer
- Add charge.refunded webhook: sets order to refunded, invalidates tickets, notifies orga and buyer
- Validate billet type (billet/reservation_brocante/vote) against organizer offer
- getAllowedBilletTypes: gratuit=billet only, basic/sur-mesure=all types
- Server-side validation in hydrateBilletFromRequest, UI filtering in templates
- Update TASK_CHECKUP.md: all Billetterie & Commandes items now complete
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 00:12:30 +01:00
{% extends 'email/base.html.twig' %}
{% block title %} Commande {{ action }} - {{ order .event .title }} {% endblock %}
{% block content %}
<h2>Commande {{ action }} </h2>
<p>Bonjour,</p>
<p>La commande <strong> {{ order .orderNumber }} </strong> pour l'evenement <strong> {{ order .event .title }} </strong> a ete <strong> {{ action }} </strong>.</p>
<table style="width: 100%; border-collapse: collapse; margin: 20px 0; border: 2px solid #111827;">
<tr style="border-bottom: 1px solid #e5e7eb;">
<td style="padding: 10px 12px; font-weight: 900; text-transform: uppercase; font-size: 11px; color: #6b7280; width: 140px;">Commande</td>
<td style="padding: 10px 12px; font-weight: 700; font-size: 14px;"> {{ order .orderNumber }} </td>
</tr>
<tr style="border-bottom: 1px solid #e5e7eb;">
<td style="padding: 10px 12px; font-weight: 900; text-transform: uppercase; font-size: 11px; color: #6b7280;">Statut</td>
<td style="padding: 10px 12px; font-weight: 700; color: #dc2626;"> {{ action | upper }} </td>
</tr>
<tr style="border-bottom: 1px solid #e5e7eb;">
<td style="padding: 10px 12px; font-weight: 900; text-transform: uppercase; font-size: 11px; color: #6b7280;">Acheteur</td>
<td style="padding: 10px 12px; font-weight: 700;"> {{ order .firstName }} {{ order .lastName }} </td>
</tr>
<tr style="border-bottom: 1px solid #e5e7eb;">
<td style="padding: 10px 12px; font-weight: 900; text-transform: uppercase; font-size: 11px; color: #6b7280;">Email</td>
<td style="padding: 10px 12px;"> {{ order .email }} </td>
</tr>
<tr>
<td style="padding: 10px 12px; font-weight: 900; text-transform: uppercase; font-size: 11px; color: #6b7280;">Montant HT</td>
<td style="padding: 10px 12px; font-weight: 900; color: #4f46e5;"> {{ order .totalHTDecimal | number_format ( 2 , ',' , ' ' ) }} €</td>
</tr>
</table>
Add LibreTranslate auto-translation, improve test coverage, fix code duplication
Translation system:
- Add LibreTranslate container (dev + prod), CPU-only, no port exposed, FR/EN/ES/DE/IT
- Create app:translate command: reads *.fr.yaml, translates incrementally, preserves placeholders
- Makefile: make trans / make trans_prod (stops container after translation)
- Ansible: start libretranslate -> translate -> stop during deploy
- Prod container restart: "no" (only runs during deploy)
- .gitignore: ignore generated *.en/es/de/it.yaml files
- 11 tests for TranslateCommand (API unreachable, empty, incremental, obsolete keys, placeholders, fallback)
Test coverage improvements:
- OrderController: event ended (400), invalid cart JSON, invalid email, stock zero (4 new tests)
- AccountController: finance stats all statuses (paid/pending/refunded/cancelled), soldCounts (2 new tests)
- JS cart: checkout without error elements, hide error on retry, stock polling edge cases (singular, no label, qty zero, unknown billet) (8 new tests)
- JS editor: comment node sanitization (1 new test)
- JS tabs: missing panel, generated id, parent null, click no-panel (5 new tests)
Code duplication fixes:
- MeilisearchConsistencyCommand: extract diffAndReport() method (was duplicated 3x)
- Email templates: extract _order_items_table.html.twig partial (shared by notification + cancelled)
- SonarQube: exclude src/Entity/** from CPD (getters/setters duplication)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 11:44:13 +01:00
{{ include ( 'email/_order_items_table.html.twig' , { order : order } ) }}
Add stock management, order notifications, webhooks, expiration cron, and billet type validation
- Decrement billet quantity after purchase in BilletOrderService::generateOrderTickets
- Block purchase when stock is exhausted (quantity <= 0) in OrderController::buildOrderItems
- Add organizer email notification on new order (order_notification_orga template)
- Add organizer email notification on cancel/refund (order_cancelled_orga template)
- Add ExpirePendingOrdersCommand (app:orders:expire-pending) cron every 5min via Ansible
- Cancels pending orders older than 30 minutes, restores stock, invalidates tickets
- Includes BilletBuyerRepository::findExpiredPending query method
- 3 unit tests covering: no expired orders, stock restoration, unlimited billets
- Add payment_intent.payment_failed webhook: cancels order, logs audit, emails buyer
- Add charge.refunded webhook: sets order to refunded, invalidates tickets, notifies orga and buyer
- Validate billet type (billet/reservation_brocante/vote) against organizer offer
- getAllowedBilletTypes: gratuit=billet only, basic/sur-mesure=all types
- Server-side validation in hydrateBilletFromRequest, UI filtering in templates
- Update TASK_CHECKUP.md: all Billetterie & Commandes items now complete
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 00:12:30 +01:00
<p style="font-size: 12px; color: #9ca3af;">Les billets associes a cette commande ont ete invalides. Vous pouvez consulter le detail depuis votre espace organisateur.</p>
{% endblock %}