Files
Classeo/backend/src/Administration/Infrastructure/ReadModel/DbalPaginatedSubjectsReader.php
Mathias STRASSER 86d00ce733 feat: Afficher les statistiques de notes par matière côté administration
L'admin doit pouvoir voir en un coup d'œil quelles matières sont
actives (notes saisies) pour décider lesquelles peuvent être supprimées
sans perte de données. Auparavant, la suppression d'une matière était
silencieuse : elle cascade-deletait évaluations et notes sans avertir.

La liste des matières affiche désormais les compteurs d'enseignants,
classes, évaluations et notes. La suppression déclenche une confirmation
explicite quand la matière contient des notes, avec récapitulatif des
volumes impactés, pour rendre l'action irréversible consciente.

Côté tests, un endpoint de seeding HTTP remplace les appels docker exec
dans les E2E (gain ~30-60s → 5-10s par test), et un trait partagé
factorise le SQL de seeding entre les deux suites fonctionnelles.
2026-04-21 15:37:25 +02:00

119 lines
4.4 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Administration\Infrastructure\ReadModel;
use App\Administration\Application\Dto\PaginatedResult;
use App\Administration\Application\Port\PaginatedSubjectsReader;
use App\Administration\Application\Query\GetSubjects\SubjectDto;
use DateTimeImmutable;
use Doctrine\DBAL\Connection;
final readonly class DbalPaginatedSubjectsReader implements PaginatedSubjectsReader
{
public function __construct(
private Connection $connection,
) {
}
/**
* @return PaginatedResult<SubjectDto>
*/
public function findPaginated(
string $tenantId,
string $schoolId,
?string $search,
int $page,
int $limit,
): PaginatedResult {
$params = [
'tenant_id' => $tenantId,
'school_id' => $schoolId,
'status' => 'active',
];
$whereClause = 's.tenant_id = :tenant_id AND s.school_id = :school_id AND s.status = :status AND s.deleted_at IS NULL';
if ($search !== null && $search !== '') {
$whereClause .= ' AND (s.name ILIKE :search OR s.code ILIKE :search)';
$params['search'] = '%' . $search . '%';
}
$countSql = "SELECT COUNT(*) FROM subjects s WHERE {$whereClause}";
/** @var int|string|false $totalRaw */
$totalRaw = $this->connection->fetchOne($countSql, $params);
$total = (int) $totalRaw;
$offset = ($page - 1) * $limit;
$selectSql = <<<SQL
SELECT
s.id, s.name, s.code, s.color, s.description, s.status,
s.created_at, s.updated_at,
(SELECT COUNT(*) FROM teacher_assignments ta WHERE ta.subject_id = s.id AND ta.status = 'active') AS teacher_count,
(SELECT COUNT(DISTINCT ta.school_class_id) FROM teacher_assignments ta WHERE ta.subject_id = s.id AND ta.status = 'active') AS class_count,
(SELECT COUNT(*) FROM evaluations e WHERE e.subject_id = s.id AND e.tenant_id = s.tenant_id) AS evaluation_count,
(SELECT COUNT(g.id) FROM grades g INNER JOIN evaluations e ON e.id = g.evaluation_id WHERE e.subject_id = s.id AND e.tenant_id = s.tenant_id AND g.tenant_id = s.tenant_id) AS grade_count
FROM subjects s
WHERE {$whereClause}
ORDER BY s.name ASC
LIMIT :limit OFFSET :offset
SQL;
$params['limit'] = $limit;
$params['offset'] = $offset;
$rows = $this->connection->fetchAllAssociative($selectSql, $params);
$items = array_map(static function (array $row): SubjectDto {
/** @var string $id */
$id = $row['id'];
/** @var string $name */
$name = $row['name'];
/** @var string $code */
$code = $row['code'];
/** @var string|null $color */
$color = $row['color'];
/** @var string|null $description */
$description = $row['description'];
/** @var string $status */
$status = $row['status'];
/** @var string $createdAt */
$createdAt = $row['created_at'];
/** @var string $updatedAt */
$updatedAt = $row['updated_at'];
/** @var int|string $teacherCountRaw */
$teacherCountRaw = $row['teacher_count'] ?? 0;
/** @var int|string $classCountRaw */
$classCountRaw = $row['class_count'] ?? 0;
/** @var int|string $evaluationCountRaw */
$evaluationCountRaw = $row['evaluation_count'] ?? 0;
/** @var int|string $gradeCountRaw */
$gradeCountRaw = $row['grade_count'] ?? 0;
return new SubjectDto(
id: $id,
name: $name,
code: $code,
color: $color,
description: $description,
status: $status,
createdAt: new DateTimeImmutable($createdAt),
updatedAt: new DateTimeImmutable($updatedAt),
teacherCount: (int) $teacherCountRaw,
classCount: (int) $classCountRaw,
evaluationCount: (int) $evaluationCountRaw,
gradeCount: (int) $gradeCountRaw,
);
}, $rows);
return new PaginatedResult(
items: $items,
total: $total,
page: $page,
limit: $limit,
);
}
}