feat: Permettre aux enseignants de créer et gérer les devoirs
Some checks failed
CI / Backend Tests (push) Has been cancelled
CI / Frontend Tests (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled
CI / Naming Conventions (push) Has been cancelled
CI / Build Check (push) Has been cancelled

Les enseignants avaient besoin d'un outil pour créer des devoirs assignés
à leurs classes, avec filtrage automatique par matière selon la classe
sélectionnée. Le système valide que la date d'échéance tombe un jour
ouvrable (lundi-vendredi) et empêche les dates dans le passé.

Le domaine modélise le devoir comme un agrégat avec pièces jointes,
statut brouillon/publié, et événements métier (création, modification,
suppression). Les handlers de notification écoutent ces événements pour
les futurs envois aux parents et élèves.
This commit is contained in:
2026-03-12 10:11:06 +01:00
parent 56bc808d85
commit e9efb90f59
51 changed files with 4776 additions and 7 deletions

View File

@@ -0,0 +1,144 @@
<?php
declare(strict_types=1);
namespace App\Scolarite\Domain\Model\Homework;
use App\Administration\Domain\Model\SchoolClass\ClassId;
use App\Administration\Domain\Model\Subject\SubjectId;
use App\Administration\Domain\Model\User\UserId;
use App\Scolarite\Domain\Event\DevoirCree;
use App\Scolarite\Domain\Event\DevoirModifie;
use App\Scolarite\Domain\Event\DevoirSupprime;
use App\Scolarite\Domain\Exception\DevoirDejaSupprimeException;
use App\Shared\Domain\AggregateRoot;
use App\Shared\Domain\Tenant\TenantId;
use DateTimeImmutable;
final class Homework extends AggregateRoot
{
public private(set) DateTimeImmutable $updatedAt;
private function __construct(
public private(set) HomeworkId $id,
public private(set) TenantId $tenantId,
public private(set) ClassId $classId,
public private(set) SubjectId $subjectId,
public private(set) UserId $teacherId,
public private(set) string $title,
public private(set) ?string $description,
public private(set) DateTimeImmutable $dueDate,
public private(set) HomeworkStatus $status,
public private(set) DateTimeImmutable $createdAt,
) {
$this->updatedAt = $createdAt;
}
public static function creer(
TenantId $tenantId,
ClassId $classId,
SubjectId $subjectId,
UserId $teacherId,
string $title,
?string $description,
DateTimeImmutable $dueDate,
DateTimeImmutable $now,
): self {
$homework = new self(
id: HomeworkId::generate(),
tenantId: $tenantId,
classId: $classId,
subjectId: $subjectId,
teacherId: $teacherId,
title: $title,
description: $description,
dueDate: $dueDate,
status: HomeworkStatus::PUBLISHED,
createdAt: $now,
);
$homework->recordEvent(new DevoirCree(
homeworkId: $homework->id,
classId: (string) $classId,
subjectId: (string) $subjectId,
teacherId: (string) $teacherId,
title: $title,
dueDate: $dueDate,
occurredOn: $now,
));
return $homework;
}
public function modifier(
string $title,
?string $description,
DateTimeImmutable $dueDate,
DateTimeImmutable $now,
): void {
if ($this->status === HomeworkStatus::DELETED) {
throw DevoirDejaSupprimeException::withId($this->id);
}
$this->title = $title;
$this->description = $description;
$this->dueDate = $dueDate;
$this->updatedAt = $now;
$this->recordEvent(new DevoirModifie(
homeworkId: $this->id,
title: $title,
dueDate: $dueDate,
occurredOn: $now,
));
}
public function supprimer(DateTimeImmutable $now): void
{
if ($this->status === HomeworkStatus::DELETED) {
throw DevoirDejaSupprimeException::withId($this->id);
}
$this->status = HomeworkStatus::DELETED;
$this->updatedAt = $now;
$this->recordEvent(new DevoirSupprime(
homeworkId: $this->id,
occurredOn: $now,
));
}
/**
* @internal Pour usage Infrastructure uniquement
*/
public static function reconstitute(
HomeworkId $id,
TenantId $tenantId,
ClassId $classId,
SubjectId $subjectId,
UserId $teacherId,
string $title,
?string $description,
DateTimeImmutable $dueDate,
HomeworkStatus $status,
DateTimeImmutable $createdAt,
DateTimeImmutable $updatedAt,
): self {
$homework = new self(
id: $id,
tenantId: $tenantId,
classId: $classId,
subjectId: $subjectId,
teacherId: $teacherId,
title: $title,
description: $description,
dueDate: $dueDate,
status: $status,
createdAt: $createdAt,
);
$homework->updatedAt = $updatedAt;
return $homework;
}
}

View File

@@ -0,0 +1,45 @@
<?php
declare(strict_types=1);
namespace App\Scolarite\Domain\Model\Homework;
use App\Scolarite\Domain\Exception\PieceJointeInvalideException;
use DateTimeImmutable;
use function in_array;
final class HomeworkAttachment
{
private const int MAX_FILE_SIZE = 10 * 1024 * 1024; // 10 Mo
private const array ALLOWED_MIME_TYPES = [
'application/pdf',
'image/jpeg',
'image/png',
];
public function __construct(
public private(set) HomeworkAttachmentId $id,
public private(set) string $filename,
public private(set) string $filePath,
public private(set) int $fileSize {
set(int $fileSize) {
if ($fileSize > self::MAX_FILE_SIZE) {
throw PieceJointeInvalideException::fichierTropGros($fileSize, self::MAX_FILE_SIZE);
}
$this->fileSize = $fileSize;
}
},
public private(set) string $mimeType {
set(string $mimeType) {
if (!in_array($mimeType, self::ALLOWED_MIME_TYPES, true)) {
throw PieceJointeInvalideException::typeFichierNonAutorise($mimeType);
}
$this->mimeType = $mimeType;
}
},
public private(set) DateTimeImmutable $uploadedAt,
) {
}
}

View File

@@ -0,0 +1,11 @@
<?php
declare(strict_types=1);
namespace App\Scolarite\Domain\Model\Homework;
use App\Shared\Domain\EntityId;
final readonly class HomeworkAttachmentId extends EntityId
{
}

View File

@@ -0,0 +1,11 @@
<?php
declare(strict_types=1);
namespace App\Scolarite\Domain\Model\Homework;
use App\Shared\Domain\EntityId;
final readonly class HomeworkId extends EntityId
{
}

View File

@@ -0,0 +1,11 @@
<?php
declare(strict_types=1);
namespace App\Scolarite\Domain\Model\Homework;
enum HomeworkStatus: string
{
case PUBLISHED = 'published';
case DELETED = 'deleted';
}