pour prod

This commit is contained in:
Loic Masi
2026-08-07 16:13:43 +02:00
commit 5fbf76868f
157 changed files with 24085 additions and 0 deletions

View File

@@ -0,0 +1,210 @@
<?php
declare(strict_types=1);
namespace App\Controllers;
use App\Core\Access;
use App\Core\Csrf;
use App\Core\JsonResponse;
use App\Core\Request;
use App\Models\AnnualOverviewModel;
use App\Models\PlanningModel;
use App\Models\QuotaModel;
use PDO;
use Throwable;
final class AnnualOverviewController
{
public function __construct(private PDO $pdo)
{
}
public function overview(): void
{
(new Access($this->pdo))->requireService();
$agentId = filter_input(INPUT_GET, 'agent_id', FILTER_VALIDATE_INT);
$year = filter_input(INPUT_GET, 'year', FILTER_VALIDATE_INT);
$year ??= (int) date('Y');
if (!$agentId || $year < 2000 || $year > 2100) {
JsonResponse::send(422, ['error' => 'Agent ou année invalide.']);
}
try {
JsonResponse::send(200, (new AnnualOverviewModel($this->pdo))->overview((int) $agentId, (int) $year));
} catch (Throwable $e) {
JsonResponse::send(500, [
'error' => 'Impossible de générer la vue annuelle.',
'details' => (getenv('PTA_DEBUG') ?: '1') !== '0' ? $e->getMessage() : null,
]);
}
}
/**
* Contrôle puis validation en une seule opération de tous les plannings
* en brouillon de l'agent pour l'année civile affichée.
*
* Aucune validation partielle : si une erreur est trouvée, aucun planning
* n'est validé. Les semaines concernées sont retournées au front afin de
* pouvoir les ouvrir dans un nouvel onglet et les corriger.
*/
public function validateYear(): void
{
(new Access($this->pdo))->requireService();
Request::requireMethod('POST');
$payload = Request::json();
Csrf::assertPayload($payload);
$agentId = filter_var($payload['agent_id'] ?? null, FILTER_VALIDATE_INT);
$year = filter_var($payload['year'] ?? null, FILTER_VALIDATE_INT);
$forceQuota = filter_var($payload['force_quota'] ?? false, FILTER_VALIDATE_BOOLEAN);
if (!$agentId || !$year || $year < 2000 || $year > 2100) {
JsonResponse::send(422, ['error' => 'Agent ou année invalide.']);
}
try {
$planningModel = new PlanningModel($this->pdo);
$quotaModel = new QuotaModel($this->pdo);
$plannings = $planningModel->pendingValidationForAgentYear((int) $agentId, (int) $year);
if ($plannings === []) {
JsonResponse::send(200, [
'success' => true,
'status' => 'nothing_to_validate',
'message' => 'Aucun planning en brouillon avec des créneaux nest à valider pour cette année.',
'validated_count' => 0,
'plannings' => [],
'quota' => $quotaModel->annualForApi((int) $agentId, (int) $year),
]);
}
$results = [];
$hasErrors = false;
foreach ($plannings as $planning) {
$controls = $this->controlsForPlanning($planningModel, $planning);
$errors = array_values(array_filter(
$controls,
static fn(array $row): bool => $row['niveau'] === 'ERREUR'
));
$hasErrors = $hasErrors || $errors !== [];
$results[] = [
'id_planning' => (int) $planning['id_planning'],
'id_structure' => (int) $planning['id_structure'],
'structure_nom' => (string) $planning['structure_nom'],
'annee' => (int) $planning['annee'],
'numero_semaine' => (int) $planning['numero_semaine'],
'date_debut' => (string) $planning['date_debut'],
'date_fin' => (string) $planning['date_fin'],
'nombre_creneaux' => (int) $planning['nombre_creneaux'],
'total_minutes' => (int) $planning['total_minutes'],
'controls' => $controls,
'has_errors' => $errors !== [],
];
}
$quota = $quotaModel->annualForApi((int) $agentId, (int) $year);
$quotaWarning = (int) $quota['minutes_restantes'] < 0;
if ($hasErrors) {
JsonResponse::send(422, [
'success' => false,
'status' => 'errors',
'error' => 'Certaines semaines contiennent des erreurs. Aucune semaine na été validée.',
'plannings' => $results,
'quota' => $quota,
]);
}
if ($quotaWarning && !$forceQuota) {
JsonResponse::send(409, [
'success' => false,
'status' => 'warning',
'message' => sprintf(
'Le quota annuel est dépassé de %s. Confirmez pour valider malgré cet avertissement.',
$quotaModel->formatMinutes(abs((int) $quota['minutes_restantes']))['libelle']
),
'plannings' => $results,
'quota' => $quota,
]);
}
$ids = array_map(static fn(array $planning): int => (int) $planning['id_planning'], $plannings);
$validatedCount = $planningModel->markValidatedMany($ids);
JsonResponse::send(200, [
'success' => true,
'status' => 'validated',
'validated_count' => $validatedCount,
'message' => sprintf(
'%d planning(s) ont été validés pour lannée %d.',
$validatedCount,
(int) $year
),
'plannings' => $results,
'quota' => $quotaModel->annualForApi((int) $agentId, (int) $year),
]);
} catch (Throwable $e) {
JsonResponse::send(500, [
'error' => 'Impossible de contrôler ou valider lannée complète.',
'details' => (getenv('PTA_DEBUG') ?: '1') !== '0' ? $e->getMessage() : null,
]);
}
}
private function controlsForPlanning(PlanningModel $model, array $planning): array
{
$planningId = (int) $planning['id_planning'];
$controls = [];
if ($model->countEntries($planningId) === 0) {
$this->addControl($controls, 'ERREUR', 'AUCUN_CRENEAU', 'Le planning ne contient aucune affectation.');
}
foreach ($model->entriesOutsideWeek($planningId, (string) $planning['date_debut'], (string) $planning['date_fin']) as $row) {
$this->addControl(
$controls,
'ERREUR',
'CRENEAU_HORS_SEMAINE',
sprintf('Laffectation #%d du %s est en dehors de la semaine sélectionnée.', $row['id_creneau'], $row['date_jour'])
);
}
foreach ($model->internalOverlaps($planningId) as $row) {
$this->addControl(
$controls,
'ERREUR',
'CHEVAUCHEMENT_INTERNE',
sprintf('Les affectations #%d et #%d se chevauchent le %s.', $row['c1'], $row['c2'], $row['date_jour'])
);
}
foreach ($model->externalOverlaps($planningId) as $row) {
$this->addControl(
$controls,
'ERREUR',
'CHEVAUCHEMENT_AUTRE_STRUCTURE',
sprintf(
'Laffectation #%d chevauche laffectation #%d sur le lieu « %s » le %s (%s).',
$row['c1'],
$row['c2'],
$row['structure_nom'],
$row['date_jour'],
strtolower((string) $row['autre_statut'])
)
);
}
if ($controls === []) {
$this->addControl($controls, 'INFO', 'OK', 'Aucune anomalie bloquante détectée pour cette semaine.');
}
return $controls;
}
private function addControl(array &$controls, string $level, string $code, string $message): void
{
$controls[] = ['niveau' => $level, 'code' => $code, 'message' => $message];
}
}