Lorsqu'un super-admin crée un établissement via l'interface, le système doit automatiquement créer la base tenant, exécuter les migrations, créer le premier utilisateur admin et envoyer l'invitation — le tout de manière asynchrone pour ne pas bloquer la réponse HTTP. Ce mécanisme rend chaque établissement opérationnel dès sa création sans intervention manuelle sur l'infrastructure.
528 lines
23 KiB
PHP
528 lines
23 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Tests\Functional\Scolarite\Api;
|
|
|
|
use ApiPlatform\Symfony\Bundle\Test\ApiTestCase;
|
|
use App\Administration\Domain\Model\SchoolClass\ClassId;
|
|
use App\Administration\Domain\Model\Subject\SubjectId;
|
|
use App\Administration\Domain\Model\User\UserId;
|
|
use App\Administration\Infrastructure\Security\SecurityUser;
|
|
use App\Scolarite\Domain\Model\Evaluation\Coefficient;
|
|
use App\Scolarite\Domain\Model\Evaluation\Evaluation;
|
|
use App\Scolarite\Domain\Model\Evaluation\GradeScale;
|
|
use App\Scolarite\Domain\Model\Grade\Grade;
|
|
use App\Scolarite\Domain\Model\Grade\GradeStatus;
|
|
use App\Scolarite\Domain\Model\Grade\GradeValue;
|
|
use App\Scolarite\Domain\Repository\EvaluationRepository;
|
|
use App\Scolarite\Domain\Repository\GradeRepository;
|
|
use App\Shared\Domain\Tenant\TenantId;
|
|
use DateTimeImmutable;
|
|
use Doctrine\DBAL\Connection;
|
|
|
|
use const JSON_THROW_ON_ERROR;
|
|
|
|
use PHPUnit\Framework\Attributes\Test;
|
|
|
|
final class TeacherStatisticsEndpointsTest extends ApiTestCase
|
|
{
|
|
protected static ?bool $alwaysBootKernel = true;
|
|
|
|
private const string TENANT_ID = 'a1b2c3d4-e5f6-7890-abcd-ef1234567890';
|
|
private const string TEACHER_ID = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa';
|
|
private const string STUDENT_ID = 'bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb';
|
|
private const string STUDENT2_ID = 'cccccccc-cccc-cccc-cccc-cccccccccccc';
|
|
private const string CLASS_ID = 'dddddddd-dddd-dddd-dddd-dddddddddddd';
|
|
private const string SUBJECT_ID = 'eeeeeeee-eeee-eeee-eeee-eeeeeeeeeeee';
|
|
private const string PERIOD_ID = 'ffffffff-ffff-ffff-ffff-ffffffffffff';
|
|
private const string BASE_URL = 'http://ecole-alpha.classeo.local/api';
|
|
|
|
protected function setUp(): void
|
|
{
|
|
parent::setUp();
|
|
$this->seedFixtures();
|
|
}
|
|
|
|
protected function tearDown(): void
|
|
{
|
|
/** @var Connection $connection */
|
|
$connection = static::getContainer()->get(Connection::class);
|
|
$connection->executeStatement('DELETE FROM evaluation_statistics WHERE evaluation_id IN (SELECT id FROM evaluations WHERE tenant_id = :tid AND teacher_id = :teach)', ['tid' => self::TENANT_ID, 'teach' => self::TEACHER_ID]);
|
|
$connection->executeStatement('DELETE FROM student_averages WHERE tenant_id = :tid', ['tid' => self::TENANT_ID]);
|
|
$connection->executeStatement('DELETE FROM student_general_averages WHERE tenant_id = :tid', ['tid' => self::TENANT_ID]);
|
|
$connection->executeStatement('DELETE FROM grade_events WHERE grade_id IN (SELECT id FROM grades WHERE tenant_id = :tid AND created_by = :teach)', ['tid' => self::TENANT_ID, 'teach' => self::TEACHER_ID]);
|
|
$connection->executeStatement('DELETE FROM grades WHERE tenant_id = :tid AND created_by = :teach', ['tid' => self::TENANT_ID, 'teach' => self::TEACHER_ID]);
|
|
$connection->executeStatement('DELETE FROM evaluations WHERE tenant_id = :tid AND teacher_id = :teach', ['tid' => self::TENANT_ID, 'teach' => self::TEACHER_ID]);
|
|
$connection->executeStatement('DELETE FROM teacher_assignments WHERE tenant_id = :tid AND teacher_id = :teach', ['tid' => self::TENANT_ID, 'teach' => self::TEACHER_ID]);
|
|
$connection->executeStatement('DELETE FROM class_assignments WHERE tenant_id = :tid AND school_class_id = :class', ['tid' => self::TENANT_ID, 'class' => self::CLASS_ID]);
|
|
$connection->executeStatement('DELETE FROM academic_periods WHERE id = :id', ['id' => self::PERIOD_ID]);
|
|
$connection->executeStatement('DELETE FROM users WHERE id = :id', ['id' => self::TEACHER_ID]);
|
|
$connection->executeStatement('DELETE FROM users WHERE id = :id', ['id' => self::STUDENT_ID]);
|
|
$connection->executeStatement('DELETE FROM users WHERE id = :id', ['id' => self::STUDENT2_ID]);
|
|
|
|
parent::tearDown();
|
|
}
|
|
|
|
// =========================================================================
|
|
// GET /me/statistics — Auth & Access
|
|
// =========================================================================
|
|
|
|
#[Test]
|
|
public function overviewReturns401WithoutAuthentication(): void
|
|
{
|
|
$client = static::createClient();
|
|
$client->request('GET', self::BASE_URL . '/me/statistics', [
|
|
'headers' => ['Accept' => 'application/json'],
|
|
]);
|
|
|
|
self::assertResponseStatusCodeSame(401);
|
|
}
|
|
|
|
#[Test]
|
|
public function overviewReturns403ForStudent(): void
|
|
{
|
|
$client = $this->createAuthenticatedClient(self::STUDENT_ID, ['ROLE_ELEVE']);
|
|
$client->request('GET', self::BASE_URL . '/me/statistics', [
|
|
'headers' => ['Accept' => 'application/json'],
|
|
]);
|
|
|
|
self::assertResponseStatusCodeSame(403);
|
|
}
|
|
|
|
#[Test]
|
|
public function overviewReturns403ForParent(): void
|
|
{
|
|
$parentId = '99999999-9999-9999-9999-999999999999';
|
|
$client = $this->createAuthenticatedClient($parentId, ['ROLE_PARENT']);
|
|
$client->request('GET', self::BASE_URL . '/me/statistics', [
|
|
'headers' => ['Accept' => 'application/json'],
|
|
]);
|
|
|
|
self::assertResponseStatusCodeSame(403);
|
|
}
|
|
|
|
// =========================================================================
|
|
// GET /me/statistics — Happy path
|
|
// =========================================================================
|
|
|
|
#[Test]
|
|
public function overviewReturnsClassSummaryForTeacher(): void
|
|
{
|
|
$client = $this->createAuthenticatedClient(self::TEACHER_ID, ['ROLE_PROF']);
|
|
$client->request('GET', self::BASE_URL . '/me/statistics', [
|
|
'headers' => ['Accept' => 'application/json'],
|
|
]);
|
|
|
|
self::assertResponseIsSuccessful();
|
|
|
|
/** @var string $content */
|
|
$content = $client->getResponse()->getContent();
|
|
/** @var array<string, mixed> $data */
|
|
$data = json_decode($content, true, 512, JSON_THROW_ON_ERROR);
|
|
|
|
self::assertArrayHasKey('teacherId', $data);
|
|
self::assertSame(self::TEACHER_ID, $data['teacherId']);
|
|
self::assertArrayHasKey('classes', $data);
|
|
self::assertNotEmpty($data['classes']);
|
|
|
|
$class = $data['classes'][0];
|
|
self::assertSame(self::CLASS_ID, $class['classId']);
|
|
self::assertSame(self::SUBJECT_ID, $class['subjectId']);
|
|
self::assertArrayHasKey('evaluationCount', $class);
|
|
self::assertArrayHasKey('studentCount', $class);
|
|
self::assertArrayHasKey('average', $class);
|
|
self::assertArrayHasKey('successRate', $class);
|
|
}
|
|
|
|
// =========================================================================
|
|
// GET /me/statistics/classes/{classId} — Auth & Validation
|
|
// =========================================================================
|
|
|
|
#[Test]
|
|
public function classDetailReturns401WithoutAuthentication(): void
|
|
{
|
|
$client = static::createClient();
|
|
$client->request('GET', self::BASE_URL . '/me/statistics/classes/' . self::CLASS_ID . '?subjectId=' . self::SUBJECT_ID, [
|
|
'headers' => ['Accept' => 'application/json'],
|
|
]);
|
|
|
|
self::assertResponseStatusCodeSame(401);
|
|
}
|
|
|
|
#[Test]
|
|
public function classDetailReturns403ForStudent(): void
|
|
{
|
|
$client = $this->createAuthenticatedClient(self::STUDENT_ID, ['ROLE_ELEVE']);
|
|
$client->request('GET', self::BASE_URL . '/me/statistics/classes/' . self::CLASS_ID . '?subjectId=' . self::SUBJECT_ID, [
|
|
'headers' => ['Accept' => 'application/json'],
|
|
]);
|
|
|
|
self::assertResponseStatusCodeSame(403);
|
|
}
|
|
|
|
#[Test]
|
|
public function classDetailReturns400WithoutSubjectId(): void
|
|
{
|
|
$client = $this->createAuthenticatedClient(self::TEACHER_ID, ['ROLE_PROF']);
|
|
$client->request('GET', self::BASE_URL . '/me/statistics/classes/' . self::CLASS_ID, [
|
|
'headers' => ['Accept' => 'application/json'],
|
|
]);
|
|
|
|
self::assertResponseStatusCodeSame(400);
|
|
}
|
|
|
|
#[Test]
|
|
public function classDetailReturns400WithInvalidThreshold(): void
|
|
{
|
|
$client = $this->createAuthenticatedClient(self::TEACHER_ID, ['ROLE_PROF']);
|
|
$client->request('GET', self::BASE_URL . '/me/statistics/classes/' . self::CLASS_ID . '?subjectId=' . self::SUBJECT_ID . '&threshold=25', [
|
|
'headers' => ['Accept' => 'application/json'],
|
|
]);
|
|
|
|
self::assertResponseStatusCodeSame(400);
|
|
}
|
|
|
|
// =========================================================================
|
|
// GET /me/statistics/classes/{classId} — Happy path
|
|
// =========================================================================
|
|
|
|
#[Test]
|
|
public function classDetailReturnsStatisticsForTeacher(): void
|
|
{
|
|
$client = $this->createAuthenticatedClient(self::TEACHER_ID, ['ROLE_PROF']);
|
|
$client->request('GET', self::BASE_URL . '/me/statistics/classes/' . self::CLASS_ID . '?subjectId=' . self::SUBJECT_ID, [
|
|
'headers' => ['Accept' => 'application/json'],
|
|
]);
|
|
|
|
self::assertResponseIsSuccessful();
|
|
|
|
/** @var string $content */
|
|
$content = $client->getResponse()->getContent();
|
|
/** @var array<string, mixed> $data */
|
|
$data = json_decode($content, true, 512, JSON_THROW_ON_ERROR);
|
|
|
|
self::assertSame(self::CLASS_ID, $data['classId']);
|
|
self::assertSame(self::SUBJECT_ID, $data['subjectId']);
|
|
self::assertArrayHasKey('average', $data);
|
|
self::assertArrayHasKey('successRate', $data);
|
|
self::assertArrayHasKey('distribution', $data);
|
|
self::assertCount(8, $data['distribution']);
|
|
self::assertArrayHasKey('evolution', $data);
|
|
self::assertArrayHasKey('students', $data);
|
|
self::assertNotEmpty($data['students']);
|
|
|
|
$student = $data['students'][0];
|
|
self::assertArrayHasKey('studentId', $student);
|
|
self::assertArrayHasKey('studentName', $student);
|
|
self::assertArrayHasKey('average', $student);
|
|
self::assertArrayHasKey('inDifficulty', $student);
|
|
self::assertArrayHasKey('trend', $student);
|
|
}
|
|
|
|
// =========================================================================
|
|
// GET /me/statistics/export — Auth & Validation
|
|
// =========================================================================
|
|
|
|
#[Test]
|
|
public function exportReturns401WithoutAuthentication(): void
|
|
{
|
|
$client = static::createClient();
|
|
$client->request('GET', self::BASE_URL . '/me/statistics/export?classId=' . self::CLASS_ID . '&subjectId=' . self::SUBJECT_ID);
|
|
|
|
self::assertResponseStatusCodeSame(401);
|
|
}
|
|
|
|
#[Test]
|
|
public function exportReturns403ForStudent(): void
|
|
{
|
|
$client = $this->createAuthenticatedClient(self::STUDENT_ID, ['ROLE_ELEVE']);
|
|
$client->request('GET', self::BASE_URL . '/me/statistics/export?classId=' . self::CLASS_ID . '&subjectId=' . self::SUBJECT_ID);
|
|
|
|
self::assertResponseStatusCodeSame(403);
|
|
}
|
|
|
|
#[Test]
|
|
public function exportReturns400WithoutClassId(): void
|
|
{
|
|
$client = $this->createAuthenticatedClient(self::TEACHER_ID, ['ROLE_PROF']);
|
|
$client->request('GET', self::BASE_URL . '/me/statistics/export?subjectId=' . self::SUBJECT_ID);
|
|
|
|
self::assertResponseStatusCodeSame(400);
|
|
}
|
|
|
|
#[Test]
|
|
public function exportReturns400WithoutSubjectId(): void
|
|
{
|
|
$client = $this->createAuthenticatedClient(self::TEACHER_ID, ['ROLE_PROF']);
|
|
$client->request('GET', self::BASE_URL . '/me/statistics/export?classId=' . self::CLASS_ID);
|
|
|
|
self::assertResponseStatusCodeSame(400);
|
|
}
|
|
|
|
// =========================================================================
|
|
// GET /me/statistics/export — Happy path
|
|
// =========================================================================
|
|
|
|
#[Test]
|
|
public function exportReturnsCsvWithCorrectHeaders(): void
|
|
{
|
|
$client = $this->createAuthenticatedClient(self::TEACHER_ID, ['ROLE_PROF']);
|
|
$client->request('GET', self::BASE_URL . '/me/statistics/export?classId=' . self::CLASS_ID . '&subjectId=' . self::SUBJECT_ID . '&className=6eB&subjectName=Math%C3%A9matiques');
|
|
|
|
self::assertResponseIsSuccessful();
|
|
self::assertResponseHeaderSame('content-type', 'text/csv; charset=UTF-8');
|
|
|
|
/** @var string $csv */
|
|
$csv = $client->getResponse()->getContent();
|
|
self::assertNotEmpty($csv);
|
|
self::assertStringContainsString('Moyenne', $csv);
|
|
}
|
|
|
|
// =========================================================================
|
|
// GET /me/statistics/evaluations — Auth & Access
|
|
// =========================================================================
|
|
|
|
#[Test]
|
|
public function evaluationDifficultyReturns401WithoutAuthentication(): void
|
|
{
|
|
$client = static::createClient();
|
|
$client->request('GET', self::BASE_URL . '/me/statistics/evaluations', [
|
|
'headers' => ['Accept' => 'application/json'],
|
|
]);
|
|
|
|
self::assertResponseStatusCodeSame(401);
|
|
}
|
|
|
|
#[Test]
|
|
public function evaluationDifficultyReturns403ForStudent(): void
|
|
{
|
|
$client = $this->createAuthenticatedClient(self::STUDENT_ID, ['ROLE_ELEVE']);
|
|
$client->request('GET', self::BASE_URL . '/me/statistics/evaluations', [
|
|
'headers' => ['Accept' => 'application/json'],
|
|
]);
|
|
|
|
self::assertResponseStatusCodeSame(403);
|
|
}
|
|
|
|
#[Test]
|
|
public function evaluationDifficultyReturnsDataForTeacher(): void
|
|
{
|
|
$client = $this->createAuthenticatedClient(self::TEACHER_ID, ['ROLE_PROF']);
|
|
$client->request('GET', self::BASE_URL . '/me/statistics/evaluations', [
|
|
'headers' => ['Accept' => 'application/json'],
|
|
]);
|
|
|
|
self::assertResponseIsSuccessful();
|
|
|
|
/** @var string $content */
|
|
$content = $client->getResponse()->getContent();
|
|
/** @var array<string, mixed> $payload */
|
|
$payload = json_decode($content, true, 512, JSON_THROW_ON_ERROR);
|
|
|
|
self::assertArrayHasKey('evaluations', $payload);
|
|
/** @var list<array<string, mixed>> $evaluations */
|
|
$evaluations = $payload['evaluations'];
|
|
self::assertIsArray($evaluations);
|
|
self::assertNotEmpty($evaluations);
|
|
|
|
$eval = $evaluations[0];
|
|
self::assertArrayHasKey('evaluationId', $eval);
|
|
self::assertArrayHasKey('title', $eval);
|
|
self::assertArrayHasKey('gradedCount', $eval);
|
|
}
|
|
|
|
// =========================================================================
|
|
// GET /me/statistics/students/{studentId} — Auth & Validation
|
|
// =========================================================================
|
|
|
|
#[Test]
|
|
public function studentProgressionReturns401WithoutAuthentication(): void
|
|
{
|
|
$client = static::createClient();
|
|
$client->request('GET', self::BASE_URL . '/me/statistics/students/' . self::STUDENT_ID . '?subjectId=' . self::SUBJECT_ID . '&classId=' . self::CLASS_ID, [
|
|
'headers' => ['Accept' => 'application/json'],
|
|
]);
|
|
|
|
self::assertResponseStatusCodeSame(401);
|
|
}
|
|
|
|
#[Test]
|
|
public function studentProgressionReturns403ForStudent(): void
|
|
{
|
|
$client = $this->createAuthenticatedClient(self::STUDENT_ID, ['ROLE_ELEVE']);
|
|
$client->request('GET', self::BASE_URL . '/me/statistics/students/' . self::STUDENT_ID . '?subjectId=' . self::SUBJECT_ID . '&classId=' . self::CLASS_ID, [
|
|
'headers' => ['Accept' => 'application/json'],
|
|
]);
|
|
|
|
self::assertResponseStatusCodeSame(403);
|
|
}
|
|
|
|
#[Test]
|
|
public function studentProgressionReturnsDataForTeacher(): void
|
|
{
|
|
$client = $this->createAuthenticatedClient(self::TEACHER_ID, ['ROLE_PROF']);
|
|
$client->request('GET', self::BASE_URL . '/me/statistics/students/' . self::STUDENT_ID . '?subjectId=' . self::SUBJECT_ID . '&classId=' . self::CLASS_ID, [
|
|
'headers' => ['Accept' => 'application/json'],
|
|
]);
|
|
|
|
self::assertResponseIsSuccessful();
|
|
|
|
/** @var string $content */
|
|
$content = $client->getResponse()->getContent();
|
|
/** @var array<string, mixed> $data */
|
|
$data = json_decode($content, true, 512, JSON_THROW_ON_ERROR);
|
|
|
|
self::assertArrayHasKey('grades', $data);
|
|
self::assertIsArray($data['grades']);
|
|
}
|
|
|
|
// =========================================================================
|
|
// Helpers
|
|
// =========================================================================
|
|
|
|
/**
|
|
* @param list<string> $roles
|
|
*/
|
|
private function createAuthenticatedClient(string $userId, array $roles): \ApiPlatform\Symfony\Bundle\Test\Client
|
|
{
|
|
$client = static::createClient();
|
|
|
|
$user = new SecurityUser(
|
|
userId: UserId::fromString($userId),
|
|
email: 'test-stats@classeo.local',
|
|
hashedPassword: '',
|
|
tenantId: TenantId::fromString(self::TENANT_ID),
|
|
roles: $roles,
|
|
);
|
|
|
|
$client->loginUser($user, 'api');
|
|
|
|
return $client;
|
|
}
|
|
|
|
private function seedFixtures(): void
|
|
{
|
|
$container = static::getContainer();
|
|
/** @var Connection $connection */
|
|
$connection = $container->get(Connection::class);
|
|
$tenantId = TenantId::fromString(self::TENANT_ID);
|
|
$now = new DateTimeImmutable();
|
|
|
|
$schoolId = '550e8400-e29b-41d4-a716-ff6655440001';
|
|
$academicYearId = '550e8400-e29b-41d4-a716-ff6655440002';
|
|
|
|
// Seed users
|
|
$connection->executeStatement(
|
|
"INSERT INTO users (id, tenant_id, email, hashed_password, first_name, last_name, roles, statut, created_at, updated_at)
|
|
VALUES (:id, :tid, 'teacher-stats@test.local', '', 'Marc', 'Dupont', '[\"ROLE_PROF\"]', 'active', NOW(), NOW())
|
|
ON CONFLICT (id) DO NOTHING",
|
|
['id' => self::TEACHER_ID, 'tid' => self::TENANT_ID],
|
|
);
|
|
$connection->executeStatement(
|
|
"INSERT INTO users (id, tenant_id, email, hashed_password, first_name, last_name, roles, statut, created_at, updated_at)
|
|
VALUES (:id, :tid, 'student-stats1@test.local', '', 'Alice', 'Durand', '[\"ROLE_ELEVE\"]', 'active', NOW(), NOW())
|
|
ON CONFLICT (id) DO NOTHING",
|
|
['id' => self::STUDENT_ID, 'tid' => self::TENANT_ID],
|
|
);
|
|
$connection->executeStatement(
|
|
"INSERT INTO users (id, tenant_id, email, hashed_password, first_name, last_name, roles, statut, created_at, updated_at)
|
|
VALUES (:id, :tid, 'student-stats2@test.local', '', 'Bob', 'Martin', '[\"ROLE_ELEVE\"]', 'active', NOW(), NOW())
|
|
ON CONFLICT (id) DO NOTHING",
|
|
['id' => self::STUDENT2_ID, 'tid' => self::TENANT_ID],
|
|
);
|
|
|
|
// Seed class and subject
|
|
$connection->executeStatement(
|
|
"INSERT INTO school_classes (id, tenant_id, school_id, academic_year_id, name, status, created_at, updated_at)
|
|
VALUES (:id, :tid, :sid, :ayid, 'Stats-6eB', 'active', NOW(), NOW())
|
|
ON CONFLICT (id) DO NOTHING",
|
|
['id' => self::CLASS_ID, 'tid' => self::TENANT_ID, 'sid' => $schoolId, 'ayid' => $academicYearId],
|
|
);
|
|
$connection->executeStatement(
|
|
"INSERT INTO subjects (id, tenant_id, school_id, name, code, status, created_at, updated_at)
|
|
VALUES (:id, :tid, :sid, 'Mathématiques', 'MATH', 'active', NOW(), NOW())
|
|
ON CONFLICT (id) DO NOTHING",
|
|
['id' => self::SUBJECT_ID, 'tid' => self::TENANT_ID, 'sid' => $schoolId],
|
|
);
|
|
|
|
// Seed academic period (must cover current date for queries to return data)
|
|
// Clean up any conflicting rows first (unique constraint on tenant_id, academic_year_id, sequence)
|
|
$connection->executeStatement(
|
|
'DELETE FROM academic_periods WHERE tenant_id = :tid AND academic_year_id = :ayid AND sequence = 2',
|
|
['tid' => self::TENANT_ID, 'ayid' => $academicYearId],
|
|
);
|
|
$connection->executeStatement(
|
|
'DELETE FROM academic_periods WHERE id = :id',
|
|
['id' => self::PERIOD_ID],
|
|
);
|
|
$connection->executeStatement(
|
|
"INSERT INTO academic_periods (id, tenant_id, academic_year_id, period_type, sequence, label, start_date, end_date)
|
|
VALUES (:id, :tid, :ayid, 'trimester', 2, 'Trimestre 2', '2026-01-01', '2026-06-30')",
|
|
['id' => self::PERIOD_ID, 'tid' => self::TENANT_ID, 'ayid' => $academicYearId],
|
|
);
|
|
|
|
// Seed teacher assignment (required for statistics reader queries)
|
|
$connection->executeStatement(
|
|
"INSERT INTO teacher_assignments (id, tenant_id, teacher_id, school_class_id, subject_id, academic_year_id, status, start_date, created_at, updated_at)
|
|
VALUES (gen_random_uuid(), :tid, :teach, :class, :subj, :ayid, 'active', NOW(), NOW(), NOW())
|
|
ON CONFLICT DO NOTHING",
|
|
['tid' => self::TENANT_ID, 'teach' => self::TEACHER_ID, 'class' => self::CLASS_ID, 'subj' => self::SUBJECT_ID, 'ayid' => $academicYearId],
|
|
);
|
|
|
|
// Seed student class assignments (class_assignments links students to classes)
|
|
$connection->executeStatement(
|
|
'INSERT INTO class_assignments (id, tenant_id, user_id, school_class_id, academic_year_id, created_at, updated_at)
|
|
VALUES (gen_random_uuid(), :tid, :sid, :class, :ayid, NOW(), NOW())
|
|
ON CONFLICT DO NOTHING',
|
|
['tid' => self::TENANT_ID, 'sid' => self::STUDENT_ID, 'class' => self::CLASS_ID, 'ayid' => $academicYearId],
|
|
);
|
|
$connection->executeStatement(
|
|
'INSERT INTO class_assignments (id, tenant_id, user_id, school_class_id, academic_year_id, created_at, updated_at)
|
|
VALUES (gen_random_uuid(), :tid, :sid, :class, :ayid, NOW(), NOW())
|
|
ON CONFLICT DO NOTHING',
|
|
['tid' => self::TENANT_ID, 'sid' => self::STUDENT2_ID, 'class' => self::CLASS_ID, 'ayid' => $academicYearId],
|
|
);
|
|
|
|
// Create and publish evaluations with grades
|
|
/** @var EvaluationRepository $evalRepo */
|
|
$evalRepo = $container->get(EvaluationRepository::class);
|
|
/** @var GradeRepository $gradeRepo */
|
|
$gradeRepo = $container->get(GradeRepository::class);
|
|
|
|
$eval = Evaluation::creer(
|
|
tenantId: $tenantId,
|
|
classId: ClassId::fromString(self::CLASS_ID),
|
|
subjectId: SubjectId::fromString(self::SUBJECT_ID),
|
|
teacherId: UserId::fromString(self::TEACHER_ID),
|
|
title: 'DS Maths Stats',
|
|
description: null,
|
|
evaluationDate: new DateTimeImmutable('2026-03-15'),
|
|
gradeScale: new GradeScale(20),
|
|
coefficient: new Coefficient(1.0),
|
|
now: $now,
|
|
);
|
|
$eval->publierNotes($now);
|
|
$eval->pullDomainEvents();
|
|
$evalRepo->save($eval);
|
|
|
|
foreach ([
|
|
[self::STUDENT_ID, 15.0],
|
|
[self::STUDENT2_ID, 8.0],
|
|
] as [$studentId, $value]) {
|
|
$grade = Grade::saisir(
|
|
tenantId: $tenantId,
|
|
evaluationId: $eval->id,
|
|
studentId: UserId::fromString($studentId),
|
|
value: new GradeValue($value),
|
|
status: GradeStatus::GRADED,
|
|
gradeScale: new GradeScale(20),
|
|
createdBy: UserId::fromString(self::TEACHER_ID),
|
|
now: $now,
|
|
);
|
|
$grade->pullDomainEvents();
|
|
$gradeRepo->save($grade);
|
|
}
|
|
}
|
|
}
|