Add organizer invitation system: invite, accept, refuse
- OrganizerInvitation entity: companyName, firstName, lastName, email,
message, status (sent/opened/accepted/refused), unique token (64 hex chars)
- Admin route /admin/organisateurs/inviter: form + invitation list with status
- Button "Inviter un organisateur" on admin organizers page
- Email with accept/refuse links using unique token
- Public route /invitation/{token}/{action}: accept or refuse without auth
- Response page: confirmation message for accept/refuse
- Migration, PHPStan config, 7 entity tests
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
27
migrations/Version20260322100000.php
Normal file
27
migrations/Version20260322100000.php
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
final class Version20260322100000 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Create organizer_invitation table';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$this->addSql('CREATE TABLE organizer_invitation (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, company_name VARCHAR(255) NOT NULL, first_name VARCHAR(255) NOT NULL, last_name VARCHAR(255) NOT NULL, email VARCHAR(255) NOT NULL, message TEXT DEFAULT NULL, status VARCHAR(20) NOT NULL, token VARCHAR(64) NOT NULL, created_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, responded_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, PRIMARY KEY(id))');
|
||||
$this->addSql('CREATE UNIQUE INDEX UNIQ_ORG_INV_TOKEN ON organizer_invitation (token)');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$this->addSql('DROP TABLE organizer_invitation');
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@ parameters:
|
||||
- src/Kernel.php
|
||||
ignoreErrors:
|
||||
-
|
||||
message: '#Property App\\Entity\\(EmailTracking|MessengerLog|User|Payout|Event|Category|Billet|BilletDesign|BilletBuyer|BilletBuyerItem|BilletOrder)::\$id .* never assigned#'
|
||||
message: '#Property App\\Entity\\(EmailTracking|MessengerLog|User|Payout|Event|Category|Billet|BilletDesign|BilletBuyer|BilletBuyerItem|BilletOrder|OrganizerInvitation)::\$id .* never assigned#'
|
||||
reportUnmatched: false
|
||||
paths:
|
||||
- src/Entity/EmailTracking.php
|
||||
@@ -20,6 +20,7 @@ parameters:
|
||||
- src/Entity/BilletBuyer.php
|
||||
- src/Entity/BilletBuyerItem.php
|
||||
- src/Entity/BilletOrder.php
|
||||
- src/Entity/OrganizerInvitation.php
|
||||
-
|
||||
message: '#Parameter \#1 \$params of method Stripe\\Service\\.*::create\(\) expects#'
|
||||
path: src/Controller/OrderController.php
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Controller;
|
||||
|
||||
use App\Entity\OrganizerInvitation;
|
||||
use App\Entity\User;
|
||||
use App\Service\MailerService;
|
||||
use App\Service\MeilisearchService;
|
||||
@@ -441,4 +442,68 @@ class AdminController extends AbstractController
|
||||
'searchQuery' => $searchQuery,
|
||||
]);
|
||||
}
|
||||
|
||||
#[Route('/organisateurs/inviter', name: 'app_admin_invite_organizer', methods: ['GET', 'POST'])]
|
||||
public function inviteOrganizer(Request $request, EntityManagerInterface $em, MailerService $mailerService): Response
|
||||
{
|
||||
$invitations = $em->getRepository(OrganizerInvitation::class)->findBy([], ['createdAt' => 'DESC']);
|
||||
|
||||
if ($request->isMethod('POST')) {
|
||||
$companyName = trim($request->request->getString('company_name'));
|
||||
$firstName = trim($request->request->getString('first_name'));
|
||||
$lastName = trim($request->request->getString('last_name'));
|
||||
$email = trim($request->request->getString('email'));
|
||||
$message = trim($request->request->getString('message')) ?: null;
|
||||
|
||||
if ('' === $companyName || '' === $firstName || '' === $lastName || '' === $email) {
|
||||
$this->addFlash('error', 'Tous les champs obligatoires doivent etre remplis.');
|
||||
|
||||
return $this->redirectToRoute('app_admin_invite_organizer');
|
||||
}
|
||||
|
||||
$invitation = new OrganizerInvitation();
|
||||
$invitation->setCompanyName($companyName);
|
||||
$invitation->setFirstName($firstName);
|
||||
$invitation->setLastName($lastName);
|
||||
$invitation->setEmail($email);
|
||||
$invitation->setMessage($message);
|
||||
|
||||
$em->persist($invitation);
|
||||
$em->flush();
|
||||
|
||||
$acceptUrl = $this->generateUrl('app_invitation_respond', [
|
||||
'token' => $invitation->getToken(),
|
||||
'action' => 'accept',
|
||||
], \Symfony\Component\Routing\Generator\UrlGeneratorInterface::ABSOLUTE_URL);
|
||||
|
||||
$refuseUrl = $this->generateUrl('app_invitation_respond', [
|
||||
'token' => $invitation->getToken(),
|
||||
'action' => 'refuse',
|
||||
], \Symfony\Component\Routing\Generator\UrlGeneratorInterface::ABSOLUTE_URL);
|
||||
|
||||
$html = $this->renderView('email/organizer_invitation.html.twig', [
|
||||
'invitation' => $invitation,
|
||||
'acceptUrl' => $acceptUrl,
|
||||
'refuseUrl' => $refuseUrl,
|
||||
]);
|
||||
|
||||
$mailerService->sendEmail(
|
||||
$email,
|
||||
'Invitation organisateur - E-Ticket',
|
||||
$html,
|
||||
'E-Ticket <contact@e-cosplay.fr>',
|
||||
null,
|
||||
false,
|
||||
);
|
||||
|
||||
$this->addFlash('success', 'Invitation envoyee a '.$email.'.');
|
||||
|
||||
return $this->redirectToRoute('app_admin_invite_organizer');
|
||||
}
|
||||
|
||||
return $this->render('admin/invite_organizer.html.twig', [
|
||||
'invitations' => $invitations,
|
||||
]);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ use App\Entity\Billet;
|
||||
use App\Entity\BilletBuyer;
|
||||
use App\Entity\BilletOrder;
|
||||
use App\Entity\Category;
|
||||
use App\Entity\OrganizerInvitation;
|
||||
use App\Entity\Event;
|
||||
use App\Entity\User;
|
||||
use App\Service\EventIndexService;
|
||||
@@ -236,4 +237,27 @@ class HomeController extends AbstractController
|
||||
{
|
||||
return $this->render('home/offline.html.twig');
|
||||
}
|
||||
|
||||
#[Route('/invitation/{token}/{action}', name: 'app_invitation_respond', requirements: ['action' => 'accept|refuse'], methods: ['GET'])]
|
||||
public function respondInvitation(string $token, string $action, EntityManagerInterface $em): Response
|
||||
{
|
||||
$invitation = $em->getRepository(OrganizerInvitation::class)->findOneBy(['token' => $token]);
|
||||
if (!$invitation || OrganizerInvitation::STATUS_SENT !== $invitation->getStatus()) {
|
||||
throw $this->createNotFoundException();
|
||||
}
|
||||
|
||||
if ('accept' === $action) {
|
||||
$invitation->setStatus(OrganizerInvitation::STATUS_ACCEPTED);
|
||||
} else {
|
||||
$invitation->setStatus(OrganizerInvitation::STATUS_REFUSED);
|
||||
}
|
||||
|
||||
$invitation->setRespondedAt(new \DateTimeImmutable());
|
||||
$em->flush();
|
||||
|
||||
return $this->render('home/invitation_response.html.twig', [
|
||||
'invitation' => $invitation,
|
||||
'accepted' => 'accept' === $action,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
152
src/Entity/OrganizerInvitation.php
Normal file
152
src/Entity/OrganizerInvitation.php
Normal file
@@ -0,0 +1,152 @@
|
||||
<?php
|
||||
|
||||
namespace App\Entity;
|
||||
|
||||
use App\Repository\OrganizerInvitationRepository;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
#[ORM\Entity(repositoryClass: OrganizerInvitationRepository::class)]
|
||||
class OrganizerInvitation
|
||||
{
|
||||
public const STATUS_SENT = 'sent';
|
||||
public const STATUS_OPENED = 'opened';
|
||||
public const STATUS_ACCEPTED = 'accepted';
|
||||
public const STATUS_REFUSED = 'refused';
|
||||
|
||||
#[ORM\Id]
|
||||
#[ORM\GeneratedValue]
|
||||
#[ORM\Column]
|
||||
private ?int $id = null;
|
||||
|
||||
#[ORM\Column(length: 255)]
|
||||
private ?string $companyName = null;
|
||||
|
||||
#[ORM\Column(length: 255)]
|
||||
private ?string $firstName = null;
|
||||
|
||||
#[ORM\Column(length: 255)]
|
||||
private ?string $lastName = null;
|
||||
|
||||
#[ORM\Column(length: 255)]
|
||||
private ?string $email = null;
|
||||
|
||||
#[ORM\Column(type: 'text', nullable: true)]
|
||||
private ?string $message = null;
|
||||
|
||||
#[ORM\Column(length: 20)]
|
||||
private string $status = self::STATUS_SENT;
|
||||
|
||||
#[ORM\Column(length: 64, unique: true)]
|
||||
private string $token;
|
||||
|
||||
#[ORM\Column]
|
||||
private \DateTimeImmutable $createdAt;
|
||||
|
||||
#[ORM\Column(nullable: true)]
|
||||
private ?\DateTimeImmutable $respondedAt = null;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->token = bin2hex(random_bytes(32));
|
||||
$this->createdAt = new \DateTimeImmutable();
|
||||
}
|
||||
|
||||
public function getId(): ?int
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function getCompanyName(): ?string
|
||||
{
|
||||
return $this->companyName;
|
||||
}
|
||||
|
||||
public function setCompanyName(string $companyName): static
|
||||
{
|
||||
$this->companyName = $companyName;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getFirstName(): ?string
|
||||
{
|
||||
return $this->firstName;
|
||||
}
|
||||
|
||||
public function setFirstName(string $firstName): static
|
||||
{
|
||||
$this->firstName = $firstName;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getLastName(): ?string
|
||||
{
|
||||
return $this->lastName;
|
||||
}
|
||||
|
||||
public function setLastName(string $lastName): static
|
||||
{
|
||||
$this->lastName = $lastName;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getEmail(): ?string
|
||||
{
|
||||
return $this->email;
|
||||
}
|
||||
|
||||
public function setEmail(string $email): static
|
||||
{
|
||||
$this->email = $email;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getMessage(): ?string
|
||||
{
|
||||
return $this->message;
|
||||
}
|
||||
|
||||
public function setMessage(?string $message): static
|
||||
{
|
||||
$this->message = $message;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getStatus(): string
|
||||
{
|
||||
return $this->status;
|
||||
}
|
||||
|
||||
public function setStatus(string $status): static
|
||||
{
|
||||
$this->status = $status;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getToken(): string
|
||||
{
|
||||
return $this->token;
|
||||
}
|
||||
|
||||
public function getCreatedAt(): \DateTimeImmutable
|
||||
{
|
||||
return $this->createdAt;
|
||||
}
|
||||
|
||||
public function getRespondedAt(): ?\DateTimeImmutable
|
||||
{
|
||||
return $this->respondedAt;
|
||||
}
|
||||
|
||||
public function setRespondedAt(?\DateTimeImmutable $respondedAt): static
|
||||
{
|
||||
$this->respondedAt = $respondedAt;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
18
src/Repository/OrganizerInvitationRepository.php
Normal file
18
src/Repository/OrganizerInvitationRepository.php
Normal file
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace App\Repository;
|
||||
|
||||
use App\Entity\OrganizerInvitation;
|
||||
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||
use Doctrine\Persistence\ManagerRegistry;
|
||||
|
||||
/**
|
||||
* @extends ServiceEntityRepository<OrganizerInvitation>
|
||||
*/
|
||||
class OrganizerInvitationRepository extends ServiceEntityRepository
|
||||
{
|
||||
public function __construct(ManagerRegistry $registry)
|
||||
{
|
||||
parent::__construct($registry, OrganizerInvitation::class);
|
||||
}
|
||||
}
|
||||
89
templates/admin/invite_organizer.html.twig
Normal file
89
templates/admin/invite_organizer.html.twig
Normal file
@@ -0,0 +1,89 @@
|
||||
{% extends 'admin/base.html.twig' %}
|
||||
|
||||
{% block title %}Inviter un organisateur - Admin{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<div class="w-full md:w-[80%] mx-auto py-12 px-4">
|
||||
<h1 class="text-3xl font-black uppercase tracking-tighter italic heading-page">Inviter un organisateur</h1>
|
||||
<p class="font-bold text-gray-600 italic mb-8">Envoyer une invitation par email a un futur organisateur.</p>
|
||||
|
||||
{% for message in app.flashes('success') %}
|
||||
<div class="flash-success"><p class="font-black text-sm">{{ message }}</p></div>
|
||||
{% endfor %}
|
||||
{% for message in app.flashes('error') %}
|
||||
<div class="flash-error"><p class="font-black text-sm">{{ message }}</p></div>
|
||||
{% endfor %}
|
||||
|
||||
<div class="card-brutal overflow-hidden mb-8">
|
||||
<div class="section-header">
|
||||
<h2 class="text-[10px] font-black uppercase tracking-widest text-white">Nouvelle invitation</h2>
|
||||
</div>
|
||||
<div class="p-6">
|
||||
<form method="post" action="{{ path('app_admin_invite_organizer') }}" class="form-col">
|
||||
<div>
|
||||
<label for="inv_company" class="text-xs font-black uppercase tracking-widest form-label">Raison sociale</label>
|
||||
<input type="text" id="inv_company" name="company_name" required class="form-input focus:border-indigo-600" placeholder="Association / Entreprise">
|
||||
</div>
|
||||
|
||||
<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="contact@association.fr">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="inv_message" class="text-xs font-black uppercase tracking-widest form-label">Message personnalise (optionnel)</label>
|
||||
<textarea id="inv_message" name="message" rows="4" class="form-input focus:border-indigo-600" placeholder="Bonjour, nous vous invitons a rejoindre E-Ticket..."></textarea>
|
||||
</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>
|
||||
|
||||
{% if invitations|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 inv in invitations %}
|
||||
<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">{{ inv.companyName }}</p>
|
||||
<p class="text-xs font-bold text-gray-500">{{ inv.firstName }} {{ inv.lastName }} — {{ inv.email }}</p>
|
||||
</div>
|
||||
<span class="text-xs font-bold text-gray-400">{{ inv.createdAt|date('d/m/Y H:i') }}</span>
|
||||
{% if inv.status == 'sent' %}
|
||||
<span class="badge-yellow text-[10px] font-black uppercase">Envoyee</span>
|
||||
{% elseif inv.status == 'opened' %}
|
||||
<span class="badge-yellow text-[10px] font-black uppercase">Ouverte</span>
|
||||
{% elseif inv.status == 'accepted' %}
|
||||
<span class="badge-green text-[10px] font-black uppercase">Acceptee</span>
|
||||
{% elseif inv.status == 'refused' %}
|
||||
<span class="badge-red text-[10px] font-black uppercase">Refusee</span>
|
||||
{% endif %}
|
||||
{% if inv.respondedAt %}
|
||||
<span class="text-[10px] font-bold text-gray-400">{{ inv.respondedAt|date('d/m/Y H:i') }}</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -3,9 +3,14 @@
|
||||
{% block title %}Organisateurs{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<div class="mb-8">
|
||||
<div class="flex flex-wrap items-center justify-between gap-4 mb-8">
|
||||
<div>
|
||||
<h1 class="text-3xl font-black uppercase tracking-tighter italic heading-page">Organisateurs</h1>
|
||||
<p class="font-bold text-gray-500 italic">{{ organizers.getTotalItemCount }} organisateur{{ organizers.getTotalItemCount > 1 ? 's' : '' }}.</p>
|
||||
</div>
|
||||
<a href="{{ path('app_admin_invite_organizer') }}" class="btn-brutal font-black uppercase text-xs tracking-widest hover:bg-indigo-600 hover:text-white transition-all">
|
||||
Inviter un organisateur
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="admin-card mb-8">
|
||||
|
||||
33
templates/email/organizer_invitation.html.twig
Normal file
33
templates/email/organizer_invitation.html.twig
Normal file
@@ -0,0 +1,33 @@
|
||||
{% extends 'email/base.html.twig' %}
|
||||
|
||||
{% block title %}Invitation organisateur - E-Ticket{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h2>Vous etes invite !</h2>
|
||||
<p>Bonjour {{ invitation.firstName }},</p>
|
||||
<p>L'equipe E-Ticket vous invite a rejoindre la plateforme en tant qu'organisateur pour <strong>{{ invitation.companyName }}</strong>.</p>
|
||||
|
||||
{% if invitation.message %}
|
||||
<div style="padding: 16px; background: #f9fafb; border-left: 4px solid #fabf04; margin: 20px 0;">
|
||||
<p style="margin: 0; font-style: italic; color: #374151;">{{ invitation.message }}</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<p>E-Ticket est une plateforme de billetterie en ligne qui vous permet de :</p>
|
||||
<ul style="margin: 16px 0; padding-left: 20px;">
|
||||
<li style="margin-bottom: 8px; font-weight: 700;">Creer et gerer vos evenements</li>
|
||||
<li style="margin-bottom: 8px; font-weight: 700;">Vendre des billets en ligne avec paiement securise</li>
|
||||
<li style="margin-bottom: 8px; font-weight: 700;">Suivre vos ventes et statistiques en temps reel</li>
|
||||
<li style="margin-bottom: 8px; font-weight: 700;">Generer des billets PDF avec QR code</li>
|
||||
</ul>
|
||||
|
||||
<p style="text-align: center; margin: 24px 0;">
|
||||
<a href="{{ acceptUrl }}" class="btn" style="margin-right: 8px;">Accepter l'invitation</a>
|
||||
</p>
|
||||
|
||||
<p style="text-align: center; font-size: 13px; color: #6b7280;">
|
||||
<a href="{{ refuseUrl }}" style="color: #6b7280; text-decoration: underline;">Non merci, refuser l'invitation</a>
|
||||
</p>
|
||||
|
||||
<p style="font-size: 12px; color: #9ca3af; margin-top: 24px;">Cette invitation a ete envoyee a {{ invitation.email }} le {{ invitation.createdAt|date('d/m/Y') }}. Si vous n'etes pas concerne, ignorez cet email.</p>
|
||||
{% endblock %}
|
||||
28
templates/home/invitation_response.html.twig
Normal file
28
templates/home/invitation_response.html.twig
Normal file
@@ -0,0 +1,28 @@
|
||||
{% extends 'base.html.twig' %}
|
||||
|
||||
{% block title %}{{ accepted ? 'Invitation acceptee' : 'Invitation refusee' }} - E-Ticket{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<div class="page-container">
|
||||
<div class="max-w-xl mx-auto text-center">
|
||||
<div class="card-brutal p-8">
|
||||
{% if accepted %}
|
||||
<div class="text-6xl mb-4 text-green-600">✓</div>
|
||||
<h1 class="text-3xl font-black uppercase tracking-tighter italic heading-page mb-4">Invitation acceptee</h1>
|
||||
<p class="font-bold text-gray-600 mb-2">Merci {{ invitation.firstName }} !</p>
|
||||
<p class="text-sm font-bold text-gray-500 mb-6">Votre compte organisateur pour <strong>{{ invitation.companyName }}</strong> sera bientot active. Vous recevrez un email de confirmation.</p>
|
||||
<a href="{{ path('app_home') }}" class="btn-brutal font-black uppercase text-sm tracking-widest hover:bg-indigo-600 hover:text-white transition-all">
|
||||
Decouvrir E-Ticket
|
||||
</a>
|
||||
{% else %}
|
||||
<div class="text-6xl mb-4 text-gray-400">✕</div>
|
||||
<h1 class="text-3xl font-black uppercase tracking-tighter italic heading-page mb-4">Invitation refusee</h1>
|
||||
<p class="font-bold text-gray-600 mb-6">Nous avons bien pris en compte votre decision. Merci d'avoir pris le temps de repondre.</p>
|
||||
<a href="{{ path('app_home') }}" class="btn-brutal font-black uppercase text-sm tracking-widest hover:bg-gray-100 transition-all">
|
||||
Retour a l'accueil
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
85
tests/Entity/OrganizerInvitationTest.php
Normal file
85
tests/Entity/OrganizerInvitationTest.php
Normal file
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
|
||||
namespace App\Tests\Entity;
|
||||
|
||||
use App\Entity\OrganizerInvitation;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class OrganizerInvitationTest extends TestCase
|
||||
{
|
||||
public function testDefaults(): void
|
||||
{
|
||||
$inv = new OrganizerInvitation();
|
||||
|
||||
self::assertNull($inv->getId());
|
||||
self::assertNull($inv->getCompanyName());
|
||||
self::assertNull($inv->getFirstName());
|
||||
self::assertNull($inv->getLastName());
|
||||
self::assertNull($inv->getEmail());
|
||||
self::assertNull($inv->getMessage());
|
||||
self::assertSame(OrganizerInvitation::STATUS_SENT, $inv->getStatus());
|
||||
self::assertSame(64, \strlen($inv->getToken()));
|
||||
self::assertNull($inv->getRespondedAt());
|
||||
self::assertInstanceOf(\DateTimeImmutable::class, $inv->getCreatedAt());
|
||||
}
|
||||
|
||||
public function testSetAndGetCompanyName(): void
|
||||
{
|
||||
$inv = new OrganizerInvitation();
|
||||
$result = $inv->setCompanyName('Asso Test');
|
||||
|
||||
self::assertSame('Asso Test', $inv->getCompanyName());
|
||||
self::assertSame($inv, $result);
|
||||
}
|
||||
|
||||
public function testSetAndGetNames(): void
|
||||
{
|
||||
$inv = new OrganizerInvitation();
|
||||
$inv->setFirstName('Jean');
|
||||
$inv->setLastName('Dupont');
|
||||
$inv->setEmail('jean@test.fr');
|
||||
|
||||
self::assertSame('Jean', $inv->getFirstName());
|
||||
self::assertSame('Dupont', $inv->getLastName());
|
||||
self::assertSame('jean@test.fr', $inv->getEmail());
|
||||
}
|
||||
|
||||
public function testSetAndGetMessage(): void
|
||||
{
|
||||
$inv = new OrganizerInvitation();
|
||||
$result = $inv->setMessage('Bienvenue !');
|
||||
|
||||
self::assertSame('Bienvenue !', $inv->getMessage());
|
||||
self::assertSame($inv, $result);
|
||||
|
||||
$inv->setMessage(null);
|
||||
self::assertNull($inv->getMessage());
|
||||
}
|
||||
|
||||
public function testSetAndGetStatus(): void
|
||||
{
|
||||
$inv = new OrganizerInvitation();
|
||||
$result = $inv->setStatus(OrganizerInvitation::STATUS_ACCEPTED);
|
||||
|
||||
self::assertSame(OrganizerInvitation::STATUS_ACCEPTED, $inv->getStatus());
|
||||
self::assertSame($inv, $result);
|
||||
}
|
||||
|
||||
public function testSetAndGetRespondedAt(): void
|
||||
{
|
||||
$inv = new OrganizerInvitation();
|
||||
$date = new \DateTimeImmutable();
|
||||
$result = $inv->setRespondedAt($date);
|
||||
|
||||
self::assertSame($date, $inv->getRespondedAt());
|
||||
self::assertSame($inv, $result);
|
||||
}
|
||||
|
||||
public function testUniqueTokens(): void
|
||||
{
|
||||
$inv1 = new OrganizerInvitation();
|
||||
$inv2 = new OrganizerInvitation();
|
||||
|
||||
self::assertNotSame($inv1->getToken(), $inv2->getToken());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user