Add invitations tab: create free invitation with ticket generation and email
- New tab "Invitations" on event edit page - Form: name, email, billet type, quantity - Creates BilletBuyer with totalHT=0 (no payment), generates BilletOrders with isInvitation=true, sends email with PDF tickets - List of sent invitations below the form Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -10,6 +10,7 @@ use App\Entity\Category;
|
||||
use App\Entity\Event;
|
||||
use App\Entity\Payout;
|
||||
use App\Entity\User;
|
||||
use App\Service\BilletOrderService;
|
||||
use App\Service\EventIndexService;
|
||||
use App\Service\MailerService;
|
||||
use App\Service\OrderIndexService;
|
||||
@@ -697,6 +698,69 @@ class AccountController extends AbstractController
|
||||
return $this->json(['success' => true]);
|
||||
}
|
||||
|
||||
#[Route('/mon-compte/evenement/{id}/invitation', name: 'app_account_event_create_invitation', methods: ['POST'])]
|
||||
public function createInvitation(Event $event, Request $request, EntityManagerInterface $em, BilletOrderService $billetOrderService): Response
|
||||
{
|
||||
$this->denyAccessUnlessGranted('ROLE_ORGANIZER');
|
||||
|
||||
/** @var User $user */
|
||||
$user = $this->getUser();
|
||||
if ($event->getAccount()->getId() !== $user->getId()) {
|
||||
throw $this->createAccessDeniedException();
|
||||
}
|
||||
|
||||
$firstName = trim($request->request->getString('first_name'));
|
||||
$lastName = trim($request->request->getString('last_name'));
|
||||
$email = trim($request->request->getString('email'));
|
||||
$billetId = $request->request->getInt('billet_id');
|
||||
$quantity = $request->request->getInt('quantity', 1);
|
||||
|
||||
if ('' === $firstName || '' === $lastName || '' === $email || 0 === $billetId) {
|
||||
$this->addFlash('error', 'Tous les champs sont requis.');
|
||||
|
||||
return $this->redirectToRoute('app_account_edit_event', ['id' => $event->getId(), 'tab' => 'invitations']);
|
||||
}
|
||||
|
||||
$billet = $em->getRepository(Billet::class)->find($billetId);
|
||||
if (!$billet || $billet->getCategory()->getEvent()->getId() !== $event->getId()) {
|
||||
throw $this->createNotFoundException();
|
||||
}
|
||||
|
||||
$count = $em->getRepository(BilletBuyer::class)->count([]) + 1;
|
||||
|
||||
$order = new BilletBuyer();
|
||||
$order->setEvent($event);
|
||||
$order->setFirstName($firstName);
|
||||
$order->setLastName($lastName);
|
||||
$order->setEmail($email);
|
||||
$order->setOrderNumber(date('Y-m-d').'-'.$count);
|
||||
$order->setTotalHT(0);
|
||||
|
||||
$item = new BilletBuyerItem();
|
||||
$item->setBillet($billet);
|
||||
$item->setBilletName($billet->getName());
|
||||
$item->setQuantity($quantity);
|
||||
$item->setUnitPriceHT(0);
|
||||
$order->addItem($item);
|
||||
|
||||
$em->persist($order);
|
||||
$em->flush();
|
||||
|
||||
$billetOrderService->generateOrderTickets($order);
|
||||
|
||||
$tickets = $em->getRepository(BilletOrder::class)->findBy(['billetBuyer' => $order]);
|
||||
foreach ($tickets as $ticket) {
|
||||
$ticket->setIsInvitation(true);
|
||||
}
|
||||
$em->flush();
|
||||
|
||||
$billetOrderService->generateAndSendTickets($order);
|
||||
|
||||
$this->addFlash('success', 'Invitation envoyee a '.$email.'.');
|
||||
|
||||
return $this->redirectToRoute('app_account_edit_event', ['id' => $event->getId(), 'tab' => 'invitations']);
|
||||
}
|
||||
|
||||
#[Route('/mon-compte/evenement/{id}/commande/{orderId}/annuler', name: 'app_account_event_cancel_order', methods: ['POST'])]
|
||||
public function cancelOrder(Event $event, int $orderId, EntityManagerInterface $em): Response
|
||||
{
|
||||
|
||||
@@ -83,6 +83,7 @@
|
||||
{% if is_granted('ROLE_ROOT') or app.user.offer == 'custom' %}
|
||||
<a href="{{ path('app_account_edit_event', {id: event.id, tab: 'billets'}) }}" class="flex-1 min-w-[100px] text-center py-3 border-3 border-gray-900 border-r-0 {{ current_tab == 'billets' ? 'bg-yellow-400' : 'bg-white' }} font-black uppercase text-xs tracking-widest transition-all">Billets</a>
|
||||
{% endif %}
|
||||
<a href="{{ path('app_account_edit_event', {id: event.id, tab: 'invitations'}) }}" class="flex-1 min-w-[100px] text-center py-3 border-3 border-gray-900 border-r-0 {{ current_tab == 'invitations' ? 'bg-yellow-400' : 'bg-white' }} font-black uppercase text-xs tracking-widest transition-all">Invitations</a>
|
||||
<a href="{{ path('app_account_edit_event', {id: event.id, tab: 'stats'}) }}" class="flex-1 min-w-[100px] text-center py-3 border-3 border-gray-900 {{ current_tab == 'stats' ? 'bg-yellow-400' : 'bg-white' }} font-black uppercase text-xs tracking-widest transition-all">Statistiques</a>
|
||||
</div>
|
||||
|
||||
@@ -352,6 +353,84 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% elseif current_tab == 'invitations' %}
|
||||
|
||||
<div class="card-brutal overflow-hidden mb-6">
|
||||
<div class="section-header">
|
||||
<h2 class="text-[10px] font-black uppercase tracking-widest text-white">Creer une invitation</h2>
|
||||
</div>
|
||||
<div class="p-6">
|
||||
<form method="post" action="{{ path('app_account_event_create_invitation', {id: event.id}) }}" class="form-col">
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label for="inv_last_name" class="text-xs font-black uppercase tracking-widest form-label">Nom</label>
|
||||
<input type="text" id="inv_last_name" name="last_name" required class="form-input focus:border-indigo-600" placeholder="Dupont">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="inv_first_name" class="text-xs font-black uppercase tracking-widest form-label">Prenom</label>
|
||||
<input type="text" id="inv_first_name" name="first_name" required class="form-input focus:border-indigo-600" placeholder="Jean">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="inv_email" class="text-xs font-black uppercase tracking-widest form-label">Email</label>
|
||||
<input type="email" id="inv_email" name="email" required class="form-input focus:border-indigo-600" placeholder="jean.dupont@exemple.fr">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="inv_billet" class="text-xs font-black uppercase tracking-widest form-label">Type de billet</label>
|
||||
<select id="inv_billet" name="billet_id" required class="form-input focus:border-indigo-600">
|
||||
{% for cat_billets in billets %}
|
||||
{% for billet in cat_billets %}
|
||||
<option value="{{ billet.id }}">{{ billet.category.name }} — {{ billet.name }} ({{ billet.priceHTDecimal|number_format(2, ',', ' ') }} €)</option>
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="inv_qty" class="text-xs font-black uppercase tracking-widest form-label">Quantite</label>
|
||||
<input type="number" id="inv_qty" name="quantity" required min="1" value="1" class="form-input focus:border-indigo-600">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<button type="submit" class="btn-brutal font-black uppercase text-sm tracking-widest hover:bg-indigo-600 hover:text-white transition-all">
|
||||
Envoyer l'invitation
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% set invitation_orders = event_orders|filter(o => o.totalHT == 0) %}
|
||||
{% if invitation_orders|length > 0 %}
|
||||
<div class="card-brutal overflow-hidden">
|
||||
<div class="section-header">
|
||||
<h2 class="text-[10px] font-black uppercase tracking-widest text-white">Invitations envoyees</h2>
|
||||
</div>
|
||||
<div class="p-6">
|
||||
{% for order in invitation_orders %}
|
||||
<div class="flex flex-wrap items-center gap-4 py-3 {{ not loop.last ? 'border-b border-gray-200' : '' }}">
|
||||
<div class="flex-1 min-w-0">
|
||||
<p class="font-black text-sm">{{ order.firstName }} {{ order.lastName }}</p>
|
||||
<p class="text-xs font-bold text-gray-400">{{ order.email }}</p>
|
||||
</div>
|
||||
<span class="text-xs font-bold text-gray-500">{{ order.orderNumber }}</span>
|
||||
{% for item in order.items %}
|
||||
<span class="text-xs font-bold text-gray-500">{{ item.billetName }} x{{ item.quantity }}</span>
|
||||
{% endfor %}
|
||||
<span class="text-xs font-bold text-gray-400">{{ order.createdAt|date('d/m/Y H:i') }}</span>
|
||||
{% if order.status == 'paid' %}
|
||||
<span class="badge-green text-[10px] font-black uppercase">Envoyee</span>
|
||||
{% else %}
|
||||
<span class="badge-yellow text-[10px] font-black uppercase">{{ order.status }}</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% elseif current_tab == 'stats' %}
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-4 gap-4 mb-6">
|
||||
|
||||
Reference in New Issue
Block a user