# Audit du backoffice CHAPS — juillet 2026

Inventaire des pistes d'amélioration UI et du code mort de `chaps-api` (backoffice Blade + Tailwind + Alpine.js). Issu de trois passes d'exploration croisées (UI, code mort, backend) menées le 21/07/2026.

**Statut** : les 4 irritants prioritaires (§1.1 à §1.4) ont été **corrigés** dans la foulée de cet audit — le détail des correctifs figure dans chaque section. Le reste du document est un backlog priorisé.

---

## 1. Pistes d'amélioration UI

### 1.1 Tableaux : header perdu au scroll — ✅ corrigé

**Diagnostic.** Tous les `<thead>` du backoffice portent `sticky top-0`, mais `position: sticky` ne colle que par rapport au plus proche ancêtre *scrollable*. Les écrans qui fonctionnaient (4 matrices, organizer) enveloppent leur tableau dans un conteneur `overflow-auto` + `max-height: calc(100vh - Npx)` ; les écrans cassés (`roles`, `menus`, `ctas`, vue CRUD générique `chaps-admin/module`) n'avaient qu'un `overflow-x-auto` non borné — c'est le `<main>` qui scrollait, emportant le header.

**Correctif appliqué** (patron des matrices standardisé) :
- `resources/views/admin/roles/index.blade.php` — wrapper borné + shadow
- `resources/views/admin/menus/index.blade.php` — idem
- `resources/views/admin/ctas/index.blade.php` — idem
- `resources/views/admin/chaps-admin/module.blade.php` — wrapper + `sticky top-0 z-20` sur le thead (il ne l'avait même pas)

**Piste suivante** : extraire un composant Blade `<x-data-table>` — les 4 matrices dupliquent ~150-300 lignes de markup identique et il n'existe **aucun** composant Blade dans le projet (`resources/views/components` absent).

### 1.2 Organizer : sélecteur de module — ✅ corrigé

**Diagnostic.** ~44 modules rendus en onglets à défilement horizontal (`metadatas-organizer.blade.php`), inutilisables. Incohérence interne : `admin/metadatas/matrix.blade.php` offrait déjà un **select2 4.1** filtrable (jQuery 3.6 CDN) pour la même population de modules.

**Correctif appliqué** : la nav d'onglets est remplacée par un `<select>` select2 avec autocomplete (`#organizer-module-select`), patron repris de la matrice. Options injectées par `initModuleSelect()` après chargement, pont select2 → Alpine sur l'événement `change`, persistance `localStorage` conservée. Le filtrage reste 100 % client (aucun rechargement).

**Piste suivante** : migrer vers tom-select (sans dépendance jQuery) et centraliser le chargement des libs (jQuery/select2/SortableJS sont rechargés par page en CDN).

### 1.3 Organizer : pas d'onglet « Vue Contexte » — ✅ corrigé

**Diagnostic.** Le flag `metadatas.context` existait et la `ContextView` du JSON généré aussi (`MetadataController::generateMetadata`), mais elle était triée sur `sequencelist` faute de colonne de séquence dédiée. Impossible d'ordonner la vue contextuelle indépendamment de la vue liste.

**Correctif appliqué** :
- migration `2026_07_21_000001_add_sequencecontext_to_metadatas` (+ backfill `sequencecontext = sequencelist` pour les champs déjà en contexte) ;
- `sequencecontext` ajouté au `$fillable` de `Metadatas`, à l'allowlist batch de `CrudController::batchUpdate`, au garde-fou BUG-META-004, aux listes de colonnes sauvegardées de `MetadataController` (survie via `Metadatas_backup`) ;
- tri de la `ContextView` sur `sequencecontext` avec fallback `sequencelist` (rétrocompatible) ;
- onglet « Vue Contexte » dans l'organizer, clone drag & drop de « Vue Liste » (conteneurs `available-context`/`visible-context`, handler `handleContextSort` écrivant `context` + `sequencecontext`).

### 1.4 Picklists inéditables (dont le label) — ✅ corrigé

**Diagnostic (cause racine, vérifiée en base).** `MetadataSourceService::softDeleteAbsentMetadatas` ne scopait **pas** par source de module : le JSON de l'API ne décrivant que les modules `source='data'`, chaque `metadata:regenerate-all` soft-deletait la totalité des metadatas des modules internes `source='backoffice'` (picklists, badges, colors, menus, ctas, blocs, domaines, modules, metadatas, metadatas_generated) — 147 lignes `is_deleted=1` constatées. Résultat pour `/admin/picklists` : plus aucune metadata active → `UpdateView` dégradée, champ `label` inéditable. S'y ajoutaient : module `picklists` `is_active=0`, tous les flags `update=0`, et 65/83 picklists avec `label` NULL (créées par `MetadataDispatchService::syncPicklists` qui ne pose pas de label).

**Correctif appliqué** :
- **Garde** dans `MetadataSourceService` : `softDeleteAbsentMetadatas` et `softDeleteAbsentPicklists` sont désormais scopés aux modules `source='data'` — les metadatas/picklists internes CHAPS ne sont plus jamais touchées par la régénération ;
- **Réparation data** : `Database\Seeders\Common\PicklistsModuleRepairSeeder` (idempotent, branché dans `EnjayDatabaseSeeder`) — réactive les metadatas backoffice, réactive le module `picklists`, ouvre `update=1` sur `label`, `sequence`, `is_active`, `colors_id` (les clés techniques `name`/`value`/`field` restent en lecture seule : ce sont les clés de matching de la synchro), backfill `label = value` sur les labels vides ;
- **Garantie de survie du label** : `processPicklistIncremental` skippe les picklists existantes (matching par `name`) — un label édité survit à la régénération (vérifié par double régénération avec label modifié à la main). Un commentaire-garde exige d'exclure `label` si un UPDATE des existantes est un jour introduit. ⚠️ La persistance repose sur la **stabilité de `picklists.name`** côté source.

### 1.5 Problèmes transverses (backlog)

| # | Problème | Détail | Effort |
|---|---|---|---|
| T1 | Zéro composant Blade | Duplication massive : 4 matrices quasi identiques ; panneaux Create/Edit de `module.blade.php` copiés-collés (~190 lignes) | Moyen |
| T2 | Deux systèmes de sauvegarde concurrents | `BatchSave` (roles, menus, ctas, matrices) vs `ChangeTracker`+Alpine (organizer), deux UX de bouton « Sauvegarder » | Moyen |
| T3 | JS massivement inline | ~520 lignes de `<script>` dans l'organizer, ~460 dans `module.blade.php` ; à extraire en fichiers `public/js/` | Moyen |
| T4 | Libs CDN par page | jQuery/select2/SortableJS/Alpine/FontAwesome chargés en CDN, redondants ; `package.json` déclare alpinejs/axios/lodash jamais utilisés ; le bundle mix `public/js/app.js` n'est chargé nulle part | Faible |
| T5 | i18n présente mais inutilisée | `resources/lang/fr|en` + `docs/INTERNATIONALISATION.md` existent, **aucune** vue admin n'utilise `__()`/`@lang` — tout est en français codé en dur | Élevé |
| T6 | Feedback utilisateur mixte | Toast JS global vs bannières `session('success'/'error')` du layout : deux canaux | Faible |
| T7 | `confirm()`/`alert()` natifs | organizer, module.blade — remplacer par une modale cohérente | Faible |
| T8 | Modales incohérentes | Slide-over empilables (module.blade) vs modale plein écran (backups) | Faible |
| T9 | Couplage inter-modules | Le reorder des rôles poste vers `admin.modules.reorder` | Faible |

---

## 2. Code mort (classé par confiance)

> ⚠️ Inventaire seulement — **aucune suppression effectuée**. Avant de purger, se méfier des appels dynamiques (`CrudService` metadata-driven, `view($variable)`, `constant()` dynamique).

### 2.1 🔴 Sécurité (urgent)

- **`.env.estair.bak`** à la racine : secrets réels committés (Keycloak, Redis), toujours présent (modifié 14/07/2026). À supprimer **et purger de l'historique git**, puis révoquer les secrets.

### 2.2 🔴 Bugs latents

- **11 constantes `ConstantBO::*` référencées mais non définies** (fatal si la branche est atteinte) : `NAME_USERS`, `NAME_ACCOUNTS`, `NAME_SERVICES`, `LABEL_AFFAIRE`, `LABEL_LEADS`, `ID_ACCOUNTS`, `SERVICE_URL` (`CommonService.php:113,134,140,233,706`), `TYPE_FIELD` (`DocumentService.php:97`), `LABEL_BSPS`, `LABEL_FACTURES`, `LABEL_COMPANYDETAILS` (`ReportingService.php` — lui-même mort, cf. 2.3). En miroir, la plupart des `LABEL_*` **définies** ne sont jamais lues.
- **Typo `KEYCLOACK_CLIENT_ID`** en lookup *primaire* dans `config/identity.php:7,9` (sauvée par le fallback vers la bonne orthographe). À corriger.
- **Lookups `constant()` dynamiques cassés** : `CommonService.php:530` (`ConstantBO::MODULE_*` inexistantes), `CommonService.php:676` (mauvais namespace `App\Constant\File` + constantes `BASEDIR*` inexistantes).

### 2.3 🔴 Cluster legacy Estair ininstanciable

Références aux namespaces **inexistants** `App\Env\Env` et `App\Service\*` (singulier) :
- `app/Services/ReportingService.php` (jamais instancié), `app/Services/MailService.php` (namespace PSR-4 cassé), `app/Services/RedisService.php` ;
- `app/Http/Controllers/AccountController.php`, `app/Http/Controllers/BoController.php` (non routés, constructeurs sur services inexistants) ;
- `app/Http/Controllers/Auth/SsoLoginController.php` (non routé, SSO géré par le package vizir) — entraîne `resources/views/auth/login.blade.php` ;
- `app/Librairies/File.php`, `app/Librairies/FtpService.php` : méthodes truffées de `Env::` — fatales si appelées. NB : `BoService`, `CommonService`, `DocumentService`, `MappingService` sont **vivants** mais contiennent des méthodes mortes du même acabit.

### 2.4 🔴/🟠 Nettoyage

- **Blades orphelines** : `welcome.blade.php`, `emailSend.blade.php`, `emails/sendMail.blade.php` (le Mailable `SendMail` pointe `view('view.name')` placeholder), `layouts/app.blade.php`, `auth/login.blade.php`.
- **Modèles morts** : `Ctas_backup`, `Menus_backup`, `Picklists_backup` (tables manipulées via `DB::table`, jamais via le modèle — contrairement à `Metadatas_backup`, vivant) ; 🟠 `ModulesDomaines`.
- **Assets** : `resources/js/app.js` + `bootstrap.js` → bundle `public/js/app.js` jamais chargé par le layout ; deps npm `alpinejs`, `@alpinejs/collapse`, `axios`, `lodash` inutilisées (tout est en CDN). Vivants : `public/js/batch-save.js`, `public/js/change-tracker.js`, `public/css/app.css`.
- 🟠 **`app/Constants/`** (pluriel) : 4 classes « aspirationnelles » (`ApiEndpoints`, `Business`, `Modules`, `Status`) citées seulement par la doc. (`app/Constant/` singulier : `ConstantField`/`ConstantPost` vivants, `ConstantBO` partiel.)
- 🟠 **7 commandes `Test*Command`** (outillage de debug jetable, non planifiées).
- 🟠 **Racine** : 9 scripts debug (`test_*.php`, `verify_*.php`, `final_verification.php`), dumps `estair_bo.sql` (~2,3 Mo) et `restore_data.sql`, dossier `save/` (quarantaine explicitement purgeable).
- 🟡 **Routes legacy** `routes/api.php:121-134` (bloc `*.legacy`) : cibles valides mais redondantes avec `/api/v1` — obsolescence fonctionnelle à confirmer.
- 🟡 **Seeders Estair « à l'unité »** non branchés dans `EstairDatabaseSeeder` (documenté comme intentionnel) : candidats à archivage si les données sont en base.
- 🟡 `app/Services/EstairClientService.php` : alias mince de `DataClientService` (« ALIAS-COMPAT »), aucune référence externe.

### 2.5 Récap priorisé

| Priorité | Action | Confiance |
|---|---|---|
| P0 | Purger `.env.estair.bak` + révoquer les secrets | 🔴 |
| P1 | Corriger typo `KEYCLOACK_CLIENT_ID` ; traiter les constantes `ConstantBO` fantômes | 🔴 |
| P2 | Supprimer le cluster legacy (ReportingService, MailService, RedisService, AccountController, BoController, SsoLoginController) | 🔴 |
| P3 | Blades orphelines + modèles `*_backup` morts + scripts debug racine + dumps SQL | 🔴/🟠 |
| P4 | Assets/deps npm morts ; `app/Constants/` ; commandes `Test*` | 🟠 |
| P5 | Statuer sur routes `*.legacy`, seeders Estair unitaires, `EstairClientService` | 🟡 |

---

## 3. Rappels d'exploitation

- Après tout déploiement de chaps-api : `php artisan metadata:regenerate-all --force` (la garde de §1.4 rend l'opération désormais sans danger pour les modules internes).
- La réparation data (`PicklistsModuleRepairSeeder`) est **idempotente** et incluse dans `db:seed` (setup enjay) ; elle peut être rejouée à l'unité : `php artisan db:seed --class="Database\Seeders\Common\PicklistsModuleRepairSeeder" --force`.
- En **production**, rejouer dans l'ordre : `migrate --force` → seeder de réparation → `metadata:regenerate-all --force`.
