feat: Permettre au parent de consulter les devoirs de ses enfants
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 parents avaient accès à l'emploi du temps de leurs enfants mais
pas à leurs devoirs. Sans cette visibilité, ils ne pouvaient pas
accompagner efficacement le travail scolaire à la maison, notamment
identifier les devoirs urgents ou contacter l'enseignant en cas
de besoin.

Le parent dispose désormais d'une vue consolidée multi-enfants avec
filtrage par enfant et par matière, badges d'urgence différenciés
(en retard / aujourd'hui / pour demain), lien de contact enseignant
pré-rempli, et cache offline scopé par utilisateur.
This commit is contained in:
2026-03-23 00:34:55 +01:00
parent 2e2328c6ca
commit 1a990951a7
17 changed files with 2044 additions and 13 deletions

View File

@@ -0,0 +1,338 @@
<?php
declare(strict_types=1);
namespace App\Tests\Unit\Scolarite\Application\Query\GetChildrenHomework;
use App\Administration\Domain\Model\SchoolClass\ClassId;
use App\Administration\Domain\Model\Subject\SubjectId;
use App\Administration\Domain\Model\User\UserId;
use App\Scolarite\Application\Port\ParentChildrenReader;
use App\Scolarite\Application\Port\ScheduleDisplayReader;
use App\Scolarite\Application\Port\StudentClassReader;
use App\Scolarite\Application\Query\GetChildrenHomework\GetChildrenHomeworkHandler;
use App\Scolarite\Application\Query\GetChildrenHomework\GetChildrenHomeworkQuery;
use App\Scolarite\Domain\Model\Homework\Homework;
use App\Scolarite\Infrastructure\Persistence\InMemory\InMemoryHomeworkAttachmentRepository;
use App\Scolarite\Infrastructure\Persistence\InMemory\InMemoryHomeworkRepository;
use App\Shared\Domain\Tenant\TenantId;
use DateTimeImmutable;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\TestCase;
final class GetChildrenHomeworkHandlerTest extends TestCase
{
private const string TENANT_ID = '550e8400-e29b-41d4-a716-446655440001';
private const string PARENT_ID = '550e8400-e29b-41d4-a716-446655440060';
private const string CHILD_A_ID = '550e8400-e29b-41d4-a716-446655440050';
private const string CHILD_B_ID = '550e8400-e29b-41d4-a716-446655440051';
private const string CLASS_A_ID = '550e8400-e29b-41d4-a716-446655440020';
private const string CLASS_B_ID = '550e8400-e29b-41d4-a716-446655440021';
private const string SUBJECT_MATH_ID = '550e8400-e29b-41d4-a716-446655440030';
private const string SUBJECT_FRENCH_ID = '550e8400-e29b-41d4-a716-446655440031';
private const string TEACHER_ID = '550e8400-e29b-41d4-a716-446655440010';
private InMemoryHomeworkRepository $homeworkRepository;
private InMemoryHomeworkAttachmentRepository $attachmentRepository;
protected function setUp(): void
{
$this->homeworkRepository = new InMemoryHomeworkRepository();
$this->attachmentRepository = new InMemoryHomeworkAttachmentRepository();
}
#[Test]
public function itReturnsEmptyWhenParentHasNoChildren(): void
{
$handler = $this->createHandler(children: []);
$result = $handler(new GetChildrenHomeworkQuery(
parentId: self::PARENT_ID,
tenantId: self::TENANT_ID,
));
self::assertSame([], $result);
}
#[Test]
public function itReturnsHomeworkForSingleChild(): void
{
$this->givenHomework(title: 'Exercices chapitre 5', dueDate: '2026-04-15', classId: self::CLASS_A_ID);
$handler = $this->createHandler(
children: [
['studentId' => self::CHILD_A_ID, 'firstName' => 'Emma', 'lastName' => 'Dupont'],
],
classMap: [self::CHILD_A_ID => self::CLASS_A_ID],
);
$result = $handler(new GetChildrenHomeworkQuery(
parentId: self::PARENT_ID,
tenantId: self::TENANT_ID,
));
self::assertCount(1, $result);
self::assertSame(self::CHILD_A_ID, $result[0]->childId);
self::assertSame('Emma', $result[0]->firstName);
self::assertSame('Dupont', $result[0]->lastName);
self::assertCount(1, $result[0]->homework);
self::assertSame('Exercices chapitre 5', $result[0]->homework[0]->title);
}
#[Test]
public function itReturnsHomeworkForMultipleChildren(): void
{
$this->givenHomework(title: 'Maths 6A', dueDate: '2026-04-15', classId: self::CLASS_A_ID);
$this->givenHomework(title: 'Maths 6B', dueDate: '2026-04-16', classId: self::CLASS_B_ID);
$handler = $this->createHandler(
children: [
['studentId' => self::CHILD_A_ID, 'firstName' => 'Emma', 'lastName' => 'Dupont'],
['studentId' => self::CHILD_B_ID, 'firstName' => 'Lucas', 'lastName' => 'Dupont'],
],
classMap: [
self::CHILD_A_ID => self::CLASS_A_ID,
self::CHILD_B_ID => self::CLASS_B_ID,
],
);
$result = $handler(new GetChildrenHomeworkQuery(
parentId: self::PARENT_ID,
tenantId: self::TENANT_ID,
));
self::assertCount(2, $result);
self::assertSame('Emma', $result[0]->firstName);
self::assertCount(1, $result[0]->homework);
self::assertSame('Maths 6A', $result[0]->homework[0]->title);
self::assertSame('Lucas', $result[1]->firstName);
self::assertCount(1, $result[1]->homework);
self::assertSame('Maths 6B', $result[1]->homework[0]->title);
}
#[Test]
public function itFiltersToSpecificChildWhenChildIdProvided(): void
{
$this->givenHomework(title: 'Maths 6A', dueDate: '2026-04-15', classId: self::CLASS_A_ID);
$this->givenHomework(title: 'Maths 6B', dueDate: '2026-04-16', classId: self::CLASS_B_ID);
$handler = $this->createHandler(
children: [
['studentId' => self::CHILD_A_ID, 'firstName' => 'Emma', 'lastName' => 'Dupont'],
['studentId' => self::CHILD_B_ID, 'firstName' => 'Lucas', 'lastName' => 'Dupont'],
],
classMap: [
self::CHILD_A_ID => self::CLASS_A_ID,
self::CHILD_B_ID => self::CLASS_B_ID,
],
);
$result = $handler(new GetChildrenHomeworkQuery(
parentId: self::PARENT_ID,
tenantId: self::TENANT_ID,
childId: self::CHILD_B_ID,
));
self::assertCount(1, $result);
self::assertSame('Lucas', $result[0]->firstName);
self::assertCount(1, $result[0]->homework);
self::assertSame('Maths 6B', $result[0]->homework[0]->title);
}
#[Test]
public function itReturnsEmptyHomeworkWhenChildHasNoClass(): void
{
$handler = $this->createHandler(
children: [
['studentId' => self::CHILD_A_ID, 'firstName' => 'Emma', 'lastName' => 'Dupont'],
],
classMap: [],
);
$result = $handler(new GetChildrenHomeworkQuery(
parentId: self::PARENT_ID,
tenantId: self::TENANT_ID,
));
self::assertCount(1, $result);
self::assertSame('Emma', $result[0]->firstName);
self::assertSame([], $result[0]->homework);
}
#[Test]
public function itReturnsEmptyWhenChildIdDoesNotMatchAnyChild(): void
{
$handler = $this->createHandler(
children: [
['studentId' => self::CHILD_A_ID, 'firstName' => 'Emma', 'lastName' => 'Dupont'],
],
classMap: [self::CHILD_A_ID => self::CLASS_A_ID],
);
$result = $handler(new GetChildrenHomeworkQuery(
parentId: self::PARENT_ID,
tenantId: self::TENANT_ID,
childId: '550e8400-e29b-41d4-a716-446655449999',
));
self::assertSame([], $result);
}
#[Test]
public function itExcludesPastHomework(): void
{
$this->givenHomework(title: 'Passé', dueDate: '2026-01-01', classId: self::CLASS_A_ID);
$this->givenHomework(title: 'Futur', dueDate: '2026-12-15', classId: self::CLASS_A_ID);
$handler = $this->createHandler(
children: [
['studentId' => self::CHILD_A_ID, 'firstName' => 'Emma', 'lastName' => 'Dupont'],
],
classMap: [self::CHILD_A_ID => self::CLASS_A_ID],
);
$result = $handler(new GetChildrenHomeworkQuery(
parentId: self::PARENT_ID,
tenantId: self::TENANT_ID,
));
self::assertCount(1, $result);
self::assertCount(1, $result[0]->homework);
self::assertSame('Futur', $result[0]->homework[0]->title);
}
#[Test]
public function itFiltersBySubjectWhenProvided(): void
{
$this->givenHomework(title: 'Maths', dueDate: '2026-04-15', classId: self::CLASS_A_ID, subjectId: self::SUBJECT_MATH_ID);
$this->givenHomework(title: 'Français', dueDate: '2026-04-16', classId: self::CLASS_A_ID, subjectId: self::SUBJECT_FRENCH_ID);
$handler = $this->createHandler(
children: [
['studentId' => self::CHILD_A_ID, 'firstName' => 'Emma', 'lastName' => 'Dupont'],
],
classMap: [self::CHILD_A_ID => self::CLASS_A_ID],
);
$result = $handler(new GetChildrenHomeworkQuery(
parentId: self::PARENT_ID,
tenantId: self::TENANT_ID,
subjectId: self::SUBJECT_MATH_ID,
));
self::assertCount(1, $result);
self::assertCount(1, $result[0]->homework);
self::assertSame('Maths', $result[0]->homework[0]->title);
}
#[Test]
public function itSortsHomeworkByDueDateAscending(): void
{
$this->givenHomework(title: 'Lointain', dueDate: '2026-05-20', classId: self::CLASS_A_ID);
$this->givenHomework(title: 'Proche', dueDate: '2026-04-10', classId: self::CLASS_A_ID);
$this->givenHomework(title: 'Milieu', dueDate: '2026-04-25', classId: self::CLASS_A_ID);
$handler = $this->createHandler(
children: [
['studentId' => self::CHILD_A_ID, 'firstName' => 'Emma', 'lastName' => 'Dupont'],
],
classMap: [self::CHILD_A_ID => self::CLASS_A_ID],
);
$result = $handler(new GetChildrenHomeworkQuery(
parentId: self::PARENT_ID,
tenantId: self::TENANT_ID,
));
self::assertCount(1, $result);
$titles = array_map(static fn ($hw) => $hw->title, $result[0]->homework);
self::assertSame(['Proche', 'Milieu', 'Lointain'], $titles);
}
/**
* @param array<array{studentId: string, firstName: string, lastName: string}> $children
* @param array<string, string> $classMap studentId => classId
*/
private function createHandler(
array $children = [],
array $classMap = [],
): GetChildrenHomeworkHandler {
$parentChildrenReader = new class($children) implements ParentChildrenReader {
/** @param array<array{studentId: string, firstName: string, lastName: string}> $children */
public function __construct(private readonly array $children)
{
}
public function childrenOf(string $guardianId, TenantId $tenantId): array
{
return $this->children;
}
};
$studentClassReader = new class($classMap) implements StudentClassReader {
/** @param array<string, string> $classMap */
public function __construct(private readonly array $classMap)
{
}
public function currentClassId(string $studentId, TenantId $tenantId): ?string
{
return $this->classMap[$studentId] ?? null;
}
};
$displayReader = new class implements ScheduleDisplayReader {
public function subjectDisplay(string $tenantId, string ...$subjectIds): array
{
$map = [];
foreach ($subjectIds as $id) {
$map[$id] = ['name' => 'Mathématiques', 'color' => '#3b82f6'];
}
return $map;
}
public function teacherNames(string $tenantId, string ...$teacherIds): array
{
$map = [];
foreach ($teacherIds as $id) {
$map[$id] = 'Jean Dupont';
}
return $map;
}
};
return new GetChildrenHomeworkHandler(
$parentChildrenReader,
$studentClassReader,
$this->homeworkRepository,
$this->attachmentRepository,
$displayReader,
);
}
private function givenHomework(
string $title,
string $dueDate,
string $classId = self::CLASS_A_ID,
string $subjectId = self::SUBJECT_MATH_ID,
): Homework {
$homework = Homework::creer(
tenantId: TenantId::fromString(self::TENANT_ID),
classId: ClassId::fromString($classId),
subjectId: SubjectId::fromString($subjectId),
teacherId: UserId::fromString(self::TEACHER_ID),
title: $title,
description: null,
dueDate: new DateTimeImmutable($dueDate),
now: new DateTimeImmutable('2026-03-12 10:00:00'),
);
$this->homeworkRepository->save($homework);
return $homework;
}
}