Files
pta/app/Controllers/AnnualOverviewController.php
Loic Masi 5fbf76868f pour prod
2026-08-07 16:13:43 +02:00

211 lines
8.1 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?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];
}
}