pour prod
This commit is contained in:
335
app/Controllers/WeekTemplateController.php
Normal file
335
app/Controllers/WeekTemplateController.php
Normal file
@@ -0,0 +1,335 @@
|
||||
<?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\AgentModel;
|
||||
use App\Models\QuotaModel;
|
||||
use App\Models\WeekTemplateModel;
|
||||
use DateTimeImmutable;
|
||||
use DomainException;
|
||||
use PDO;
|
||||
use Throwable;
|
||||
|
||||
final class WeekTemplateController
|
||||
{
|
||||
private WeekTemplateModel $templateModel;
|
||||
private AgentModel $agentModel;
|
||||
private QuotaModel $quotaModel;
|
||||
private Access $access;
|
||||
|
||||
public function __construct(private PDO $pdo)
|
||||
{
|
||||
$this->templateModel = new WeekTemplateModel($pdo);
|
||||
$this->agentModel = new AgentModel($pdo);
|
||||
$this->quotaModel = new QuotaModel($pdo);
|
||||
$this->access = new Access($pdo);
|
||||
}
|
||||
|
||||
public function index(): void
|
||||
{
|
||||
$this->access->requireService();
|
||||
$agentId = filter_input(INPUT_GET, 'agent_id', FILTER_VALIDATE_INT);
|
||||
if (!$agentId) {
|
||||
JsonResponse::send(422, ['error' => 'Agent invalide.']);
|
||||
}
|
||||
if ($this->agentModel->findActive((int) $agentId) === null) {
|
||||
JsonResponse::send(404, ['error' => 'Agent introuvable ou inactif.']);
|
||||
}
|
||||
|
||||
$templates = array_map(static function (array $template): array {
|
||||
return [
|
||||
'id' => (int) $template['id_modele'],
|
||||
'name' => (string) $template['nom'],
|
||||
'entry_count' => (int) $template['nombre_creneaux'],
|
||||
'location_count' => (int) $template['nombre_lieux'],
|
||||
'updated_at' => (string) $template['date_modification'],
|
||||
];
|
||||
}, $this->templateModel->listForAgent((int) $agentId));
|
||||
|
||||
JsonResponse::send(200, ['templates' => $templates]);
|
||||
}
|
||||
|
||||
public function store(): void
|
||||
{
|
||||
$this->access->requireService();
|
||||
Request::requireMethod('POST');
|
||||
$payload = Request::json();
|
||||
Csrf::assertPayload($payload);
|
||||
|
||||
$agentId = filter_var($payload['agent_id'] ?? null, FILTER_VALIDATE_INT);
|
||||
$week = $this->parseWeek(trim((string) ($payload['week'] ?? '')));
|
||||
$name = trim((string) ($payload['name'] ?? ''));
|
||||
$replace = filter_var($payload['replace'] ?? false, FILTER_VALIDATE_BOOLEAN);
|
||||
|
||||
if (!$agentId || $week === null) {
|
||||
JsonResponse::send(422, ['error' => 'Agent ou semaine invalide.']);
|
||||
}
|
||||
if ($this->agentModel->findActive((int) $agentId) === null) {
|
||||
JsonResponse::send(404, ['error' => 'Agent introuvable ou inactif.']);
|
||||
}
|
||||
|
||||
try {
|
||||
[$year, $weekNumber] = $week;
|
||||
$template = $this->templateModel->saveFromWeek((int) $agentId, $year, $weekNumber, $name, $replace);
|
||||
} catch (DomainException $e) {
|
||||
$status = str_contains($e->getMessage(), 'déjà ce nom') ? 409 : 422;
|
||||
JsonResponse::send($status, [
|
||||
'error' => $e->getMessage(),
|
||||
'code' => $status === 409 ? 'TEMPLATE_EXISTS' : 'TEMPLATE_INVALID',
|
||||
]);
|
||||
} catch (Throwable $e) {
|
||||
JsonResponse::send(500, [
|
||||
'error' => 'Impossible d’enregistrer la semaine type.',
|
||||
'details' => (getenv('PTA_DEBUG') ?: '1') !== '0' ? $e->getMessage() : null,
|
||||
]);
|
||||
}
|
||||
|
||||
JsonResponse::send(200, [
|
||||
'success' => true,
|
||||
'template' => $template,
|
||||
'message' => sprintf('La semaine type « %s » a été enregistrée avec %d créneau%s.', $template['nom'], $template['nombre_creneaux'], $template['nombre_creneaux'] > 1 ? 'x' : ''),
|
||||
]);
|
||||
}
|
||||
|
||||
public function apply(): void
|
||||
{
|
||||
$this->access->requireService();
|
||||
Request::requireMethod('POST');
|
||||
$payload = Request::json();
|
||||
Csrf::assertPayload($payload);
|
||||
|
||||
$templateId = filter_var($payload['template_id'] ?? null, FILTER_VALIDATE_INT);
|
||||
$agentId = filter_var($payload['agent_id'] ?? null, FILTER_VALIDATE_INT);
|
||||
$week = $this->parseWeek(trim((string) ($payload['week'] ?? '')));
|
||||
$mode = strtolower(trim((string) ($payload['mode'] ?? 'merge')));
|
||||
|
||||
if (!$templateId || !$agentId || $week === null || !in_array($mode, ['merge', 'replace'], true)) {
|
||||
JsonResponse::send(422, ['error' => 'Semaine type, agent, semaine ou mode d’import invalide.']);
|
||||
}
|
||||
if ($this->agentModel->findActive((int) $agentId) === null) {
|
||||
JsonResponse::send(404, ['error' => 'Agent introuvable ou inactif.']);
|
||||
}
|
||||
|
||||
try {
|
||||
[$year, $weekNumber] = $week;
|
||||
$result = $this->templateModel->applyToWeek((int) $templateId, (int) $agentId, $year, $weekNumber, $mode);
|
||||
} catch (DomainException $e) {
|
||||
JsonResponse::send(409, ['error' => $e->getMessage()]);
|
||||
} catch (Throwable $e) {
|
||||
JsonResponse::send(500, [
|
||||
'error' => 'Impossible d’importer la semaine type.',
|
||||
'details' => (getenv('PTA_DEBUG') ?: '1') !== '0' ? $e->getMessage() : null,
|
||||
]);
|
||||
}
|
||||
|
||||
$reopened = count($result['reopened_planning_ids']) > 0;
|
||||
$message = sprintf(
|
||||
'La semaine type « %s » a été appliquée : %d créneau%s importé%s.',
|
||||
$result['template']['nom'],
|
||||
$result['inserted'],
|
||||
$result['inserted'] > 1 ? 'x' : '',
|
||||
$result['inserted'] > 1 ? 's' : ''
|
||||
);
|
||||
if ($reopened) {
|
||||
$message .= ' Les plannings validés modifiés ont été repassés en brouillon.';
|
||||
}
|
||||
|
||||
JsonResponse::send(200, [
|
||||
'success' => true,
|
||||
'message' => $message,
|
||||
'requires_revalidation' => $reopened,
|
||||
'quota' => $this->quotaModel->forDateForApi(
|
||||
(int) $agentId,
|
||||
(new DateTimeImmutable())->setISODate($year, $weekNumber, 1)->format('Y-m-d')
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
public function previewPeriod(): void
|
||||
{
|
||||
$this->access->requireService();
|
||||
Request::requireMethod('POST');
|
||||
$payload = Request::json();
|
||||
Csrf::assertPayload($payload);
|
||||
|
||||
$templateId = filter_var($payload['template_id'] ?? null, FILTER_VALIDATE_INT);
|
||||
$agentId = filter_var($payload['agent_id'] ?? null, FILTER_VALIDATE_INT);
|
||||
$startDate = trim((string) ($payload['start_date'] ?? ''));
|
||||
$endDate = trim((string) ($payload['end_date'] ?? ''));
|
||||
$mode = strtolower(trim((string) ($payload['mode'] ?? 'merge')));
|
||||
$periodFilter = strtolower(trim((string) ($payload['period_filter'] ?? 'all')));
|
||||
|
||||
if (
|
||||
!$templateId
|
||||
|| !$agentId
|
||||
|| $startDate === ''
|
||||
|| $endDate === ''
|
||||
|| !in_array($mode, ['merge', 'replace'], true)
|
||||
|| !in_array($periodFilter, ['all', 'school', 'extra'], true)
|
||||
) {
|
||||
JsonResponse::send(422, ['error' => 'Semaine type, agent, période, filtre de calendrier ou mode d’application invalide.']);
|
||||
}
|
||||
if ($this->agentModel->findActive((int) $agentId) === null) {
|
||||
JsonResponse::send(404, ['error' => 'Agent introuvable ou inactif.']);
|
||||
}
|
||||
|
||||
try {
|
||||
$preview = $this->templateModel->previewPeriod(
|
||||
(int) $templateId,
|
||||
(int) $agentId,
|
||||
$startDate,
|
||||
$endDate,
|
||||
$mode,
|
||||
$periodFilter
|
||||
);
|
||||
} catch (DomainException $e) {
|
||||
JsonResponse::send(422, ['error' => $e->getMessage()]);
|
||||
} catch (Throwable $e) {
|
||||
JsonResponse::send(500, [
|
||||
'error' => 'Impossible de prévisualiser l’application de la semaine type.',
|
||||
'details' => (getenv('PTA_DEBUG') ?: '1') !== '0' ? $e->getMessage() : null,
|
||||
]);
|
||||
}
|
||||
|
||||
// Les créneaux détaillés sont utiles au modèle mais pas nécessaires au navigateur.
|
||||
unset($preview['target_entries']);
|
||||
|
||||
JsonResponse::send(200, [
|
||||
'success' => true,
|
||||
'preview' => $preview,
|
||||
]);
|
||||
}
|
||||
|
||||
public function applyPeriod(): void
|
||||
{
|
||||
$this->access->requireService();
|
||||
Request::requireMethod('POST');
|
||||
$payload = Request::json();
|
||||
Csrf::assertPayload($payload);
|
||||
|
||||
$templateId = filter_var($payload['template_id'] ?? null, FILTER_VALIDATE_INT);
|
||||
$agentId = filter_var($payload['agent_id'] ?? null, FILTER_VALIDATE_INT);
|
||||
$startDate = trim((string) ($payload['start_date'] ?? ''));
|
||||
$endDate = trim((string) ($payload['end_date'] ?? ''));
|
||||
$mode = strtolower(trim((string) ($payload['mode'] ?? 'merge')));
|
||||
$periodFilter = strtolower(trim((string) ($payload['period_filter'] ?? 'all')));
|
||||
|
||||
if (
|
||||
!$templateId
|
||||
|| !$agentId
|
||||
|| $startDate === ''
|
||||
|| $endDate === ''
|
||||
|| !in_array($mode, ['merge', 'replace'], true)
|
||||
|| !in_array($periodFilter, ['all', 'school', 'extra'], true)
|
||||
) {
|
||||
JsonResponse::send(422, ['error' => 'Semaine type, agent, période, filtre de calendrier ou mode d’application invalide.']);
|
||||
}
|
||||
if ($this->agentModel->findActive((int) $agentId) === null) {
|
||||
JsonResponse::send(404, ['error' => 'Agent introuvable ou inactif.']);
|
||||
}
|
||||
|
||||
try {
|
||||
$result = $this->templateModel->applyToPeriod(
|
||||
(int) $templateId,
|
||||
(int) $agentId,
|
||||
$startDate,
|
||||
$endDate,
|
||||
$mode,
|
||||
$periodFilter
|
||||
);
|
||||
} catch (DomainException $e) {
|
||||
JsonResponse::send(409, ['error' => $e->getMessage()]);
|
||||
} catch (Throwable $e) {
|
||||
JsonResponse::send(500, [
|
||||
'error' => 'Impossible d’appliquer la semaine type sur la période.',
|
||||
'details' => (getenv('PTA_DEBUG') ?: '1') !== '0' ? $e->getMessage() : null,
|
||||
]);
|
||||
}
|
||||
|
||||
$message = sprintf(
|
||||
'La semaine type « %s » a été appliquée du %s au %s : %d créneau%s créé%s sur %d jour%s et %d semaine%s.',
|
||||
$result['template']['name'],
|
||||
$result['start_date'],
|
||||
$result['end_date'],
|
||||
$result['inserted'],
|
||||
$result['inserted'] > 1 ? 'x' : '',
|
||||
$result['inserted'] > 1 ? 's' : '',
|
||||
$result['days_touched'],
|
||||
$result['days_touched'] > 1 ? 's' : '',
|
||||
$result['weeks_touched'],
|
||||
$result['weeks_touched'] > 1 ? 's' : ''
|
||||
);
|
||||
|
||||
$scopeLabel = match ($result['period_filter']) {
|
||||
'school' => 'uniquement sur les périodes scolaires',
|
||||
'extra' => 'uniquement sur les périodes extrascolaires',
|
||||
default => 'sur toutes les semaines de la période',
|
||||
};
|
||||
$message .= ' Périmètre : ' . $scopeLabel . '.';
|
||||
|
||||
if ($result['skipped_count'] > 0) {
|
||||
$message .= sprintf(
|
||||
' %d créneau%s ignoré%s pour respecter les périodes scolaires/vacances.',
|
||||
$result['skipped_count'],
|
||||
$result['skipped_count'] > 1 ? 'x' : '',
|
||||
$result['skipped_count'] > 1 ? 's' : ''
|
||||
);
|
||||
}
|
||||
if ($result['deleted'] > 0) {
|
||||
$message .= sprintf(' %d ancien%s créneau%s remplacé%s.', $result['deleted'], $result['deleted'] > 1 ? 's' : '', $result['deleted'] > 1 ? 'x' : '', $result['deleted'] > 1 ? 's' : '');
|
||||
}
|
||||
if ($result['reopened_planning_ids'] !== []) {
|
||||
$message .= ' Les plannings validés modifiés ont été repassés en brouillon.';
|
||||
}
|
||||
|
||||
$quotas = [];
|
||||
foreach (array_unique([(string) $result['start_date'], (string) $result['end_date']]) as $contextDate) {
|
||||
$quota = $this->quotaModel->forDateForApi((int) $agentId, $contextDate);
|
||||
$quotas[(string) $quota['date_debut_periode']] = $quota;
|
||||
}
|
||||
|
||||
JsonResponse::send(200, [
|
||||
'success' => true,
|
||||
'message' => $message,
|
||||
'result' => $result,
|
||||
'requires_revalidation' => $result['reopened_planning_ids'] !== [],
|
||||
'quotas' => $quotas,
|
||||
]);
|
||||
}
|
||||
|
||||
public function destroy(): void
|
||||
{
|
||||
$this->access->requireService();
|
||||
Request::requireMethod('POST');
|
||||
$payload = Request::json();
|
||||
Csrf::assertPayload($payload);
|
||||
|
||||
$templateId = filter_var($payload['template_id'] ?? null, FILTER_VALIDATE_INT);
|
||||
$agentId = filter_var($payload['agent_id'] ?? null, FILTER_VALIDATE_INT);
|
||||
if (!$templateId || !$agentId) {
|
||||
JsonResponse::send(422, ['error' => 'Semaine type ou agent invalide.']);
|
||||
}
|
||||
|
||||
if (!$this->templateModel->delete((int) $templateId, (int) $agentId)) {
|
||||
JsonResponse::send(404, ['error' => 'Semaine type introuvable.']);
|
||||
}
|
||||
|
||||
JsonResponse::send(200, ['success' => true, 'message' => 'Semaine type supprimée.']);
|
||||
}
|
||||
|
||||
private function parseWeek(string $value): ?array
|
||||
{
|
||||
if (!preg_match('/^(\d{4})-W(\d{2})$/', $value, $matches)) {
|
||||
return null;
|
||||
}
|
||||
$year = (int) $matches[1];
|
||||
$week = (int) $matches[2];
|
||||
return $week >= 1 && $week <= 53 ? [$year, $week] : null;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user