feat: Permettre la consultation et gestion des droits à l'image des élèves
Les administrateurs et enseignants ont besoin de consulter et gérer les autorisations de droit à l'image des élèves pour respecter la réglementation lors de publications contenant des photos (FR82). Cette fonctionnalité ajoute une page dédiée avec liste filtrable par statut, modification individuelle via dropdown, export CSV avec BOM UTF-8 pour Excel, et préparation du système d'avertissement avant publication (query/handler prêts, intégration à faire quand le module publication existera). Le filtrage par classe (AC2) est bloqué en attente d'une table d'affectation élève↔classe qui n'existe pas encore.
This commit is contained in:
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Administration\Application\Command\UpdateImageRights;
|
||||
|
||||
final readonly class UpdateImageRightsCommand
|
||||
{
|
||||
public function __construct(
|
||||
public string $studentId,
|
||||
public string $status,
|
||||
public string $modifiedBy,
|
||||
public string $tenantId,
|
||||
) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Administration\Application\Command\UpdateImageRights;
|
||||
|
||||
use App\Administration\Domain\Exception\UserNotFoundException;
|
||||
use App\Administration\Domain\Model\User\ImageRightsStatus;
|
||||
use App\Administration\Domain\Model\User\Role;
|
||||
use App\Administration\Domain\Model\User\User;
|
||||
use App\Administration\Domain\Model\User\UserId;
|
||||
use App\Administration\Domain\Repository\UserRepository;
|
||||
use App\Shared\Domain\Clock;
|
||||
use App\Shared\Domain\Tenant\TenantId;
|
||||
use Symfony\Component\Messenger\Attribute\AsMessageHandler;
|
||||
|
||||
#[AsMessageHandler(bus: 'command.bus')]
|
||||
final readonly class UpdateImageRightsHandler
|
||||
{
|
||||
public function __construct(
|
||||
private UserRepository $userRepository,
|
||||
private Clock $clock,
|
||||
) {
|
||||
}
|
||||
|
||||
public function __invoke(UpdateImageRightsCommand $command): User
|
||||
{
|
||||
$studentId = UserId::fromString($command->studentId);
|
||||
$user = $this->userRepository->get($studentId);
|
||||
|
||||
if (!$user->tenantId->equals(TenantId::fromString($command->tenantId))) {
|
||||
throw UserNotFoundException::withId($studentId);
|
||||
}
|
||||
|
||||
if (!$user->aLeRole(Role::ELEVE)) {
|
||||
throw UserNotFoundException::withId($studentId);
|
||||
}
|
||||
|
||||
$modifierId = UserId::fromString($command->modifiedBy);
|
||||
$status = ImageRightsStatus::from($command->status);
|
||||
|
||||
$user->modifierDroitImage($status, $modifierId, $this->clock->now());
|
||||
|
||||
$this->userRepository->save($user);
|
||||
|
||||
return $user;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Administration\Application\Query\CheckImageRights;
|
||||
|
||||
use App\Administration\Domain\Exception\UserNotFoundException;
|
||||
use App\Administration\Domain\Model\User\ImageRightsStatus;
|
||||
use App\Administration\Domain\Model\User\UserId;
|
||||
use App\Administration\Domain\Repository\UserRepository;
|
||||
use App\Shared\Domain\Tenant\TenantId;
|
||||
use Symfony\Component\Messenger\Attribute\AsMessageHandler;
|
||||
|
||||
#[AsMessageHandler(bus: 'query.bus')]
|
||||
final readonly class CheckImageRightsHandler
|
||||
{
|
||||
public function __construct(
|
||||
private UserRepository $userRepository,
|
||||
) {
|
||||
}
|
||||
|
||||
public function __invoke(CheckImageRightsQuery $query): ImageRightsCheckResult
|
||||
{
|
||||
$studentId = UserId::fromString($query->studentId);
|
||||
$user = $this->userRepository->get($studentId);
|
||||
|
||||
if (!$user->tenantId->equals(TenantId::fromString($query->tenantId))) {
|
||||
throw UserNotFoundException::withId($studentId);
|
||||
}
|
||||
|
||||
$status = $user->imageRightsStatus;
|
||||
|
||||
return new ImageRightsCheckResult(
|
||||
status: $status,
|
||||
canPublish: $status->estAutorise(),
|
||||
warningMessage: match ($status) {
|
||||
ImageRightsStatus::REFUSED => "ATTENTION : Cet élève n'a PAS l'autorisation de droit à l'image. Publication interdite.",
|
||||
ImageRightsStatus::NOT_SPECIFIED => "Attention : Le statut droit à l'image n'est pas renseigné pour cet élève.",
|
||||
ImageRightsStatus::AUTHORIZED => null,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Administration\Application\Query\CheckImageRights;
|
||||
|
||||
final readonly class CheckImageRightsQuery
|
||||
{
|
||||
public function __construct(
|
||||
public string $studentId,
|
||||
public string $tenantId,
|
||||
) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Administration\Application\Query\CheckImageRights;
|
||||
|
||||
use App\Administration\Domain\Model\User\ImageRightsStatus;
|
||||
|
||||
final readonly class ImageRightsCheckResult
|
||||
{
|
||||
public function __construct(
|
||||
public ImageRightsStatus $status,
|
||||
public bool $canPublish,
|
||||
public ?string $warningMessage,
|
||||
) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Administration\Application\Query\GetStudentsImageRights;
|
||||
|
||||
use App\Administration\Domain\Model\User\ImageRightsStatus;
|
||||
use App\Administration\Domain\Repository\UserRepository;
|
||||
use App\Shared\Domain\Tenant\TenantId;
|
||||
use Symfony\Component\Messenger\Attribute\AsMessageHandler;
|
||||
|
||||
#[AsMessageHandler(bus: 'query.bus')]
|
||||
final readonly class GetStudentsImageRightsHandler
|
||||
{
|
||||
public function __construct(
|
||||
private UserRepository $userRepository,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @return StudentImageRightsDto[]
|
||||
*/
|
||||
public function __invoke(GetStudentsImageRightsQuery $query): array
|
||||
{
|
||||
$students = $this->userRepository->findStudentsByTenant(
|
||||
TenantId::fromString($query->tenantId),
|
||||
);
|
||||
|
||||
if ($query->status !== null) {
|
||||
$filterStatus = ImageRightsStatus::tryFrom($query->status);
|
||||
if ($filterStatus !== null) {
|
||||
$students = array_filter(
|
||||
$students,
|
||||
static fn ($user) => $user->imageRightsStatus === $filterStatus,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return array_values(array_map(
|
||||
static fn ($user) => StudentImageRightsDto::fromDomain($user),
|
||||
$students,
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Administration\Application\Query\GetStudentsImageRights;
|
||||
|
||||
final readonly class GetStudentsImageRightsQuery
|
||||
{
|
||||
public function __construct(
|
||||
public string $tenantId,
|
||||
public ?string $status = null,
|
||||
) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Administration\Application\Query\GetStudentsImageRights;
|
||||
|
||||
use App\Administration\Domain\Model\User\User;
|
||||
use DateTimeImmutable;
|
||||
|
||||
final readonly class StudentImageRightsDto
|
||||
{
|
||||
public function __construct(
|
||||
public string $id,
|
||||
public string $firstName,
|
||||
public string $lastName,
|
||||
public string $email,
|
||||
public string $imageRightsStatus,
|
||||
public string $imageRightsStatusLabel,
|
||||
public ?DateTimeImmutable $imageRightsUpdatedAt,
|
||||
public ?string $className,
|
||||
) {
|
||||
}
|
||||
|
||||
public static function fromDomain(User $user, ?string $className = null): self
|
||||
{
|
||||
return new self(
|
||||
id: (string) $user->id,
|
||||
firstName: $user->firstName,
|
||||
lastName: $user->lastName,
|
||||
email: (string) $user->email,
|
||||
imageRightsStatus: $user->imageRightsStatus->value,
|
||||
imageRightsStatusLabel: $user->imageRightsStatus->label(),
|
||||
imageRightsUpdatedAt: $user->imageRightsUpdatedAt,
|
||||
className: $className,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Administration\Application\Service;
|
||||
|
||||
use App\Administration\Application\Query\GetStudentsImageRights\StudentImageRightsDto;
|
||||
use RuntimeException;
|
||||
|
||||
final readonly class ImageRightsExporter
|
||||
{
|
||||
/**
|
||||
* @param StudentImageRightsDto[] $students
|
||||
*/
|
||||
public function export(array $students): string
|
||||
{
|
||||
$handle = fopen('php://temp', 'r+');
|
||||
|
||||
if ($handle === false) {
|
||||
throw new RuntimeException('Impossible d\'ouvrir le flux CSV.');
|
||||
}
|
||||
|
||||
fputcsv($handle, ['Nom', 'Prénom', 'Classe', 'Statut'], separator: ';', escape: '\\');
|
||||
|
||||
foreach ($students as $student) {
|
||||
fputcsv($handle, [
|
||||
$student->lastName,
|
||||
$student->firstName,
|
||||
$student->className ?? '',
|
||||
$student->imageRightsStatusLabel,
|
||||
], separator: ';', escape: '\\');
|
||||
}
|
||||
|
||||
rewind($handle);
|
||||
$csv = stream_get_contents($handle);
|
||||
fclose($handle);
|
||||
|
||||
// BOM UTF-8 pour que Excel Windows affiche correctement les accents
|
||||
return "\xEF\xBB\xBF" . ($csv !== false ? $csv : '');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user