V7.10 — Tableau de bord, guide, semaine type et enveloppe de formation
Reprise dans le répertoire de production du travail mené dans le worktree ux-service-enfance : - tableau de bord Service Enfance (dashboard.php) et navigation regroupée ; - guide d'utilisation (faq.php) accessible depuis toute la navigation ; - éditeur de semaine type mis en avant sur l'écran de saisie ; - droit à l'enveloppe de formation par agent, en remplacement de la case « formation déjà prévue dans la répartition annuelle » ; - correctif de « [object Object] » sur les cartes de quota du PTA ; - motif de travail par défaut à la saisie, heures travaillées complètes dans les PDF. Le docker-compose.yml de production est conservé : celui du worktree décrit une pile de test (conteneurs et volume suffixés « test », ports 9080/9081), qui aurait fait redémarrer l'application sur un volume MariaDB vide. Les migrations database/migration_v7_9*.sql sont obligatoires ; le service db-migrate les applique au déploiement. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -8,8 +8,11 @@ use App\Core\Access;
|
||||
use App\Core\Csrf;
|
||||
use App\Core\JsonResponse;
|
||||
use App\Core\Request;
|
||||
use App\Core\Security;
|
||||
use App\Models\AgentModel;
|
||||
use App\Models\MotifModel;
|
||||
use App\Models\QuotaModel;
|
||||
use App\Models\StructureModel;
|
||||
use App\Models\WeekTemplateModel;
|
||||
use DateTimeImmutable;
|
||||
use DomainException;
|
||||
@@ -55,6 +58,120 @@ final class WeekTemplateController
|
||||
JsonResponse::send(200, ['templates' => $templates]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Contenu détaillé d'une semaine type, pour l'ouvrir dans l'éditeur.
|
||||
*/
|
||||
public function show(): void
|
||||
{
|
||||
$this->access->requireService();
|
||||
$agentId = filter_input(INPUT_GET, 'agent_id', FILTER_VALIDATE_INT);
|
||||
$templateId = filter_input(INPUT_GET, 'template_id', FILTER_VALIDATE_INT);
|
||||
|
||||
if (!$agentId || !$templateId) {
|
||||
JsonResponse::send(422, ['error' => 'Agent ou semaine type invalide.']);
|
||||
}
|
||||
|
||||
$template = $this->templateModel->findForAgent((int) $templateId, (int) $agentId);
|
||||
if ($template === null) {
|
||||
JsonResponse::send(404, ['error' => 'Semaine type introuvable pour cet agent.']);
|
||||
}
|
||||
|
||||
$entries = array_map(static function (array $entry): array {
|
||||
return [
|
||||
'jour_semaine' => (int) $entry['jour_semaine'],
|
||||
'id_structure' => (int) $entry['id_structure'],
|
||||
'id_motif' => (int) $entry['id_motif'],
|
||||
// L'éditeur manipule des heures au format HH:MM.
|
||||
'heure_debut' => substr((string) $entry['heure_debut'], 0, 5),
|
||||
'heure_fin' => substr((string) $entry['heure_fin'], 0, 5),
|
||||
'structure_nom' => (string) $entry['structure_nom'],
|
||||
'structure_active' => (int) $entry['structure_actif'] === 1,
|
||||
'motif_libelle' => (string) $entry['motif_libelle'],
|
||||
'motif_actif' => (int) $entry['motif_actif'] === 1,
|
||||
'compte_dans_quota' => (int) $entry['compte_dans_quota'] === 1,
|
||||
];
|
||||
}, $this->templateModel->entries((int) $templateId));
|
||||
|
||||
JsonResponse::send(200, [
|
||||
'template' => [
|
||||
'id' => (int) $template['id_modele'],
|
||||
'name' => (string) $template['nom'],
|
||||
'updated_at' => (string) $template['date_modification'],
|
||||
],
|
||||
'entries' => $entries,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enregistre une semaine type composée dans l'éditeur.
|
||||
*/
|
||||
public function storeEntries(): void
|
||||
{
|
||||
$this->access->requireService();
|
||||
Request::requireMethod('POST');
|
||||
$payload = Request::json();
|
||||
Csrf::assertPayload($payload);
|
||||
|
||||
$agentId = filter_var($payload['agent_id'] ?? null, FILTER_VALIDATE_INT);
|
||||
$name = (string) ($payload['name'] ?? '');
|
||||
$entries = $payload['entries'] ?? null;
|
||||
$replace = filter_var($payload['replace'] ?? false, FILTER_VALIDATE_BOOLEAN);
|
||||
|
||||
if (!$agentId || !is_array($entries)) {
|
||||
JsonResponse::send(422, ['error' => 'Agent ou contenu de la semaine type invalide.']);
|
||||
}
|
||||
|
||||
$agent = $this->agentModel->findActive((int) $agentId);
|
||||
if ($agent === null) {
|
||||
JsonResponse::send(404, ['error' => 'Agent introuvable ou inactif.']);
|
||||
}
|
||||
|
||||
// Mêmes règles qu'à la saisie : « Formation » n'est pas affectable à un
|
||||
// agent sans droit à l'enveloppe, et un lieu inactif n'est pas choisissable.
|
||||
$allowedMotifIds = (new MotifModel($this->pdo))->activeIdsForAgent($agent);
|
||||
$allowedStructureIds = array_map(
|
||||
static fn (array $structure): int => (int) $structure['id_structure'],
|
||||
(new StructureModel($this->pdo))->allActive()
|
||||
);
|
||||
|
||||
try {
|
||||
$template = $this->templateModel->saveFromEntries(
|
||||
(int) $agentId,
|
||||
$name,
|
||||
$entries,
|
||||
$allowedMotifIds,
|
||||
$allowedStructureIds,
|
||||
$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' => Security::debugEnabled() ? $e->getMessage() : null,
|
||||
]);
|
||||
}
|
||||
|
||||
JsonResponse::send(200, [
|
||||
'success' => true,
|
||||
'template' => [
|
||||
'id' => (int) $template['id_modele'],
|
||||
'name' => (string) $template['nom'],
|
||||
'entry_count' => (int) $template['nombre_creneaux'],
|
||||
],
|
||||
'message' => sprintf(
|
||||
'La semaine type « %s » a été enregistrée avec %d plage%s.',
|
||||
$template['nom'],
|
||||
$template['nombre_creneaux'],
|
||||
$template['nombre_creneaux'] > 1 ? 's' : ''
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
public function store(): void
|
||||
{
|
||||
$this->access->requireService();
|
||||
|
||||
Reference in New Issue
Block a user