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:
PTA
2026-08-17 16:53:04 +02:00
parent 57bd8da5c8
commit 4201894e28
69 changed files with 5228 additions and 246 deletions

153
docker/db/apply-migrations.sh Executable file
View File

@@ -0,0 +1,153 @@
#!/bin/sh
# ============================================================================
# PTA — application automatique des migrations SQL au démarrage de la stack
# ============================================================================
# Exécuté par le service jetable `db-migrate` du docker-compose, une fois que
# MariaDB est déclaré sain. Le service `php` attend la fin de ce script en
# succès : si une migration échoue, l'application ne démarre pas du tout.
#
# Le schéma initial (database/pta_prod_schema_mariadb.sql) reste joué par
# docker-entrypoint-initdb.d à la création du volume. Ce script ne traite que
# les fichiers `migration_*.sql`, dans l'ordre alphabétique, sur une base déjà
# existante comme sur une base fraîchement créée.
#
# Un registre `schema_migration` mémorise chaque fichier appliqué avec son
# empreinte. Un fichier déjà appliqué et inchangé est ignoré ; un fichier
# modifié est rejoué — les migrations PTA sont écrites idempotentes
# (CREATE TABLE IF NOT EXISTS, INSERT ... ON DUPLICATE KEY UPDATE,
# CREATE OR REPLACE VIEW), les rejouer est sans effet de bord.
# ============================================================================
set -eu
DB_HOST="${DB_HOST:-db}"
DB_PORT="${DB_PORT:-3306}"
DB_NAME="${DB_NAME:-pta}"
DB_USER="${DB_USER:-root}"
MIGRATIONS_DIR="${MIGRATIONS_DIR:-/migrations}"
# Le mot de passe passe par MYSQL_PWD et jamais par la ligne de commande :
# un `ps` dans le conteneur ne doit pas le révéler.
MYSQL_PWD="${DB_PASSWORD:-}"
export MYSQL_PWD
log() {
echo "[pta-migrate] $*"
}
sql() {
# --ssl-verify-server-cert=0 : le client MariaDB 11.4 désactive de toute
# façon cette vérification quand le mot de passe ne vient pas de la ligne
# de commande, en émettant un avertissement trompeur parlant de « login
# sans mot de passe ». Le poser explicitement supprime ce bruit dans les
# journaux de déploiement sans rien changer au comportement réel : la
# liaison reste interne au réseau bridge du compose.
mariadb --host="$DB_HOST" --port="$DB_PORT" --user="$DB_USER" \
--database="$DB_NAME" --default-character-set=utf8mb4 \
--ssl-verify-server-cert=0 "$@"
}
# ----------------------------------------------------------------------------
# Attente de la base
# ----------------------------------------------------------------------------
# Le compose garantit déjà `service_healthy`, mais le healthcheck interroge la
# boucle locale du conteneur : cette boucle couvre le court instant où le port
# n'accepte pas encore de connexion depuis le réseau.
attente=0
until sql -N -B -e 'SELECT 1' >/dev/null 2>&1; do
attente=$((attente + 1))
if [ "$attente" -ge 30 ]; then
log "ERREUR : base $DB_NAME injoignable sur $DB_HOST:$DB_PORT après 60 s."
exit 1
fi
log "Base pas encore joignable, nouvelle tentative dans 2 s ($attente/30)…"
sleep 2
done
log "Base $DB_NAME joignable sur $DB_HOST:$DB_PORT."
# ----------------------------------------------------------------------------
# Registre des migrations appliquées
# ----------------------------------------------------------------------------
sql <<'SQL'
CREATE TABLE IF NOT EXISTS schema_migration (
fichier VARCHAR(190) NOT NULL,
empreinte CHAR(32) NOT NULL,
applique_le DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (fichier)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
SQL
# ----------------------------------------------------------------------------
# Application des migrations
# ----------------------------------------------------------------------------
applique=0
ignore=0
for fichier in "$MIGRATIONS_DIR"/migration_*.sql; do
# Le glob non résolu se présente tel quel quand aucun fichier ne correspond.
[ -f "$fichier" ] || continue
nom=$(basename "$fichier")
empreinte=$(md5sum < "$fichier" | cut -d' ' -f1)
connue=$(sql -N -B -e \
"SELECT empreinte FROM schema_migration WHERE fichier = '$nom'")
if [ "$connue" = "$empreinte" ]; then
log "$nom : déjà appliquée, ignorée."
ignore=$((ignore + 1))
continue
fi
if [ -n "$connue" ]; then
log "$nom : contenu modifié depuis la dernière application, rejeu."
else
log "$nom : application…"
fi
if ! sql < "$fichier"; then
log "ERREUR : échec de $nom. L'application ne sera pas démarrée."
exit 1
fi
sql -e "INSERT INTO schema_migration (fichier, empreinte)
VALUES ('$nom', '$empreinte')
ON DUPLICATE KEY UPDATE empreinte = VALUES(empreinte),
applique_le = CURRENT_TIMESTAMP"
log "$nom : appliquée."
applique=$((applique + 1))
done
log "Bilan : $applique migration(s) appliquée(s), $ignore ignorée(s)."
# ----------------------------------------------------------------------------
# Contrôle final : les vues de droits doivent exister
# ----------------------------------------------------------------------------
# Même contrôle que SchemaModel::assertAccessViews() côté PHP. Le faire ici
# évite de laisser démarrer une pile dont la page d'accueil renverrait une
# erreur 500 incompréhensible.
manquantes=$(sql -N -B -e "
SELECT GROUP_CONCAT(attendue.nom ORDER BY attendue.nom SEPARATOR ', ')
FROM (
SELECT 'v_acces_role_page' AS nom
UNION ALL SELECT 'v_acces_role_permission'
UNION ALL SELECT 'v_acces_identite_agent'
UNION ALL SELECT 'v_acces_identite_structure'
UNION ALL SELECT 'v_acces_agent_par_structure'
UNION ALL SELECT 'v_acces_perimetre_creneau'
UNION ALL SELECT 'v_acces_perimetre_planning'
) AS attendue
LEFT JOIN information_schema.VIEWS v
ON v.TABLE_SCHEMA = DATABASE()
AND v.TABLE_NAME = attendue.nom
WHERE v.TABLE_NAME IS NULL")
if [ -n "$manquantes" ] && [ "$manquantes" != "NULL" ]; then
log "ERREUR : vue(s) de contrôle d'accès manquante(s) : $manquantes"
log "La migration v7.8 n'a pas produit le résultat attendu."
exit 1
fi
log "Les sept vues de contrôle d'accès sont présentes. Base à jour."