Aller au contenu principal

ENG-001.5C — Retrait de la duplication des alertes municipales — Rapport d'implémentation

MissionENG-001.5C — Retrait de la duplication des alertes municipales
Rattaché àENG-001.5 — Extraction de Municipal Management ; ENG-001.5B — Rapport d'implémentation ; Audit API alertes
Dépôtdmv_api
Branchefeature/eng-001-5c-remove-municipal-alert-duplication (basée sur main + fondations Municipal-A/B non commitées)
StatutImplémenté
Comportement fonctionnelInchangé pour les routes Actor (façade publique). Routes Mairie dédiées aux alertes supprimées (zéro consommateur identifié).

0. Écart procédural préalable

ENG-001-5A-municipal-management-foundation.md, cité comme lecture obligatoire, reste introuvable (déjà signalé en ENG-001.5A et ENG-001.5B, non résolu depuis). Non bloquant : le corps de la mission, combiné à ENG-001-5-municipal-management.md et aux rapports ENG-001.5A/5B déjà produits, suffit à cadrer le travail sans ambiguïté.


1. Fichiers supprimés

FichierRaison
app/Modules/Mairie/Controllers/MairieAlerteController.php100 % logique alertes, appelait des méthodes de service désormais supprimées ; aucune route ne le référence plus
app/Modules/Mairie/Requests/CreateAlerteRequest.phpUtilisé uniquement par MairieAlerteController::store()
app/Modules/Mairie/Requests/UpdateAlerteRequest.phpUtilisé uniquement par MairieAlerteController::update()
app/Modules/Mairie/DTOs/MairieAlerteDTO.phpUtilisé uniquement par les méthodes alertes de MairieReadService/MairieWriteService, elles-mêmes supprimées
app/Modules/Mairie/Models/MairieAlerte.phpModèle Eloquent devenu orphelin une fois expireAlertes() relocalisée hors Eloquent (§3) ; vérifié : aucune relation d'un autre modèle ne pointe vers MairieAlerte::class

Vérifié après chaque suppression (grep -rln sur app/, tests/, routes/) : zéro référence résiduelle en dehors des mentions descriptives dans les docblocks de Municipal Management (§3).


2. Fichiers modifiés

FichierChangement
app/Modules/Mairie/Services/MairieWriteService.phpSuppression de createAlerte(), updateAlerte(), desactiverAlerte(), expireAlertes() (relocalisée, §3) et du helper privé devenu mort getCommuneIdForActeur(). Imports MairieAlerteDTO/MairieAlerte retirés. Services/publications/commune/élus/collectes/infos strictement inchangés.
app/Modules/Mairie/Services/MairieReadService.phpSuppression de listAlertes() et getAlerte() (cette dernière déjà sans aucun appelant avant cette PR — confirmé par grep). Imports MairieAlerteDTO/MairieAlerte retirés. Services/commune/élus/collectes/infos/publications strictement inchangés.
app/Modules/Mairie/Routes/api.phpRetrait des 4 routes /mairie/communes/{communeId}/alertes, /mairie/acteurs/{acteurId}/alertes, /mairie/alertes/{alerteId}, /mairie/alertes/{alerteId}/desactiver, et de l'import MairieAlerteController. Les autres routes Mairie (services, publications, commune, élus, collectes, infos) inchangées.
app/Modules/Mairie/Jobs/ExpireAlertes.phpDépend désormais de MunicipalManagementWriter au lieu de MairieWriteService. Reste dans Mairie\Jobs pour ne pas modifier la déclaration du scheduler dans bootstrap/app.php (référencée par nom de classe, inchangée).
app/Modules/MunicipalManagement/Contracts/MunicipalManagementWriter.phpAjout de expireAlertes(): int (voir §3).
app/Modules/MunicipalManagement/Services/MunicipalManagementWriteService.phpImplémentation de expireAlertes() (voir §3). Docblock de classe mis à jour.
tests/Feature/Mairie/MairieTest.phpRetrait des 7 tests alertes (publics ×2, protégés ×5) et du helper createAlerte() devenu mort. Import MairieWriteService retiré (devenu inutilisé). Helper createActeurAutre() retiré (devenu orphelin — n'était utilisé que par un test alertes supprimé). Tests services/publications/commune inchangés.
tests/Feature/MunicipalManagement/MunicipalAlertDelegationTest.phpAjout du test expire_alertes_desactive_les_alertes_expirees_via_municipal_management_writer (déplacé/adapté depuis MairieTest.php, voir §3) et du garde-fou il_nexiste_plus_quune_seule_implementation_metier_des_alertes (§6).
tests/Feature/Actor/ActorAccessPhaseOneTest.phpRetrait de test_mairie_alert_route_requires_module_activation, qui exerçait la route Mairie désormais supprimée (POST /mairie/acteurs/{acteurId}/alertes). Couverture équivalente déjà présente côté route Actor : MunicipalAlertDelegationTest::creation_avec_module_alertes_desactive_retourne_403. Découvert par l'exécution de la suite complète, non par l'audit initial de fichiers (voir §7).

3. Logique supprimée / relocalisée

Supprimée définitivement (duplication pure, aucun remplacement nécessaire)

Les méthodes MairieReadService::listAlertes()/getAlerte() et MairieWriteService::createAlerte()/updateAlerte()/desactiverAlerte() faisaient doublon exact avec MunicipalManagementReadService/WriteService, déjà seules invoquées par la façade Actor depuis ENG-001.5B. Elles n'avaient plus aucun appelant : la route publique Mairie était leur seul consommateur, et cette route n'a elle-même aucun consommateur frontend (voir §5).

Relocalisée : expireAlertes()

MairieWriteService::expireAlertes() avait un appelant réel — le Job planifié Mairie\Jobs\ExpireAlertes (bootstrap/app.php, hourly()). Ce n'était donc pas du code mort, mais de la logique métier « Alertes » restée hors de Municipal Management, en contradiction avec la cible explicite de la mission (« Aucune logique métier "Alertes" ne doit rester ailleurs »).

Décision : relocaliser vers MunicipalManagementWriter::expireAlertes(), avec un comportement strictement identique (même requête, reconstituée à l'identique depuis le code original) :

public function expireAlertes(): int
{
return DB::table('mairie_alertes')
->whereRaw('"is_active" IS TRUE')
->whereNotNull('expires_at')
->where('expires_at', '<=', now())
->update(['is_active' => DB::raw('false')]);
}

Le Job ExpireAlertes est mis à jour pour injecter MunicipalManagementWriter au lieu de MairieWriteService, sans être déplacé de module : il reste une façade fine dans Mairie\Jobs qui délègue via le contrat, sur le même principe que ActeurModulesController (façade Actor qui délègue à Municipal Management sans porter de logique métier). Le déplacer physiquement de module aurait nécessité de modifier la déclaration du scheduler dans bootstrap/app.php, hors du périmètre strict de cette mission et sans bénéfice architectural supplémentaire (ADR-014 n'impose pas l'emplacement des adaptateurs, seulement l'ownership de la logique métier).


4. Logique conservée

  • Toute la logique services municipaux, élus, collectes, infos pratiques, commune, publications dans MairieReadService/MairieWriteService/MairieCommuneController/MairieServiceController/ MairiePublicationController — strictement hors périmètre, non touchée.
  • Les 4 routes Actor (façade publique) : GET/POST /acteurs/{acteurId}/alertes, PATCH /acteurs/{acteurId}/alertes/{alerteId}, PATCH .../desactiver — inchangées depuis ENG-001.5B, revérifiées par php artisan route:list (§7).
  • ActeurModulesController, MunicipalManagementReader/Writer (hors ajout expireAlertes()), MunicipalManagementReadService/WriteService (hors ajout expireAlertes()) — inchangés, travail déjà réalisé en ENG-001.5A/5B.

5. Vérification des consommateurs (avant suppression des routes Mairie)

Conformément à l'instruction explicite de la mission (« avant chaque suppression : vérifier les consommateurs »), une vérification fraîche a été effectuée dans cette mission, en plus de l'audit déjà produit par ENG-001-5-alert-api-consumer-audit.md :

  • Backendgrep -rn sur app/, routes/ pour les URIs mairie/communes/.../alertes, mairie/acteurs/.../alertes, mairie/alertes/... : aucune occurrence en dehors du contrôleur/des routes supprimés eux-mêmes.
  • Frontends (dmv-workspace, dmv-public, dmv-backoffice, dmv_backoffice, hors node_modules/.next/dist/build) — un seul faux positif détecté : dmv-public/app/components/wall/hooks/useWallUserSignals.ts contient une variable locale nommée mairieAlertes, mais appelle en réalité GET /acteurs/{acteurId}/alertes (route Actor, inchangée) — vérifié en lisant le code source de la fonction. Aucun consommateur réel des routes Mairie n'a été trouvé.

Confirme et complète le constat de l'audit ENG-001.5 : zéro consommateur des routes Mairie dédiées aux alertes.


6. Tests créés

Dans tests/Feature/MunicipalManagement/MunicipalAlertDelegationTest.php :

  1. expire_alertes_desactive_les_alertes_expirees_via_municipal_management_writer — migré/adapté depuis MairieTest::expire_alertes_job_desactive_alertes_expirees, appelle désormais MunicipalManagementWriter::expireAlertes() au lieu de MairieWriteService::expireAlertes(). Mêmes assertions (1 alerte expirée désactivée, alerte future et alerte sans expiration inchangées).
  2. il_nexiste_plus_quune_seule_implementation_metier_des_alertes — garde-fou explicitement demandé par la mission (« Ajouter un test garantissant qu'il n'existe plus qu'une seule implémentation métier des alertes »). Vérifie :
    • MairieAlerteController, MairieAlerte (modèle), MairieAlerteDTO, CreateAlerteRequest, UpdateAlerteRequest n'existent plus (class_exists(..., false) — sans déclencher l'autoloader, pour ne pas provoquer d'erreur sur une classe volontairement supprimée) ;
    • MairieWriteService n'expose plus createAlerte/updateAlerte/desactiverAlerte/ expireAlertes ;
    • MairieReadService n'expose plus listAlertes/getAlerte ;
    • aucune route enregistrée sous api/v1/mairie/... ne contient plus le mot « alerte » ;
    • MunicipalManagementWriter (résolu depuis le conteneur) expose bien createAlerte et expireAlertes.

Retrait net de 7 tests obsolètes dans MairieTest.php (§2) et de 1 test obsolète dans ActorAccessPhaseOneTest.php (§2, §7) — remplacés par une couverture équivalente ou supérieure côté Municipal Management/Actor.


7. Validations exécutées

CommandeRésultat
php -l sur tous les fichiers créés/modifiésOK
php artisan route:list --path=mairie28 routes, aucune ne contient « alerte »
php artisan route:list --path=acteurs | grep alerte4 routes Actor, strictement identiques à avant cette PR
vendor/bin/pint --test (fichiers touchés)OK après 2 corrections automatiques (MairieWriteService.php, MunicipalAlertDelegationTest.php — la seconde a converti mes références \Fully\Qualified\Class::class en imports use + noms courts, comportement standard du fixer fully_qualified_strict_types, sans effet fonctionnel car Class::class ne déclenche pas l'autoload)
vendor/bin/pint --test (repo entier)1 seul fichier signalé : database/seeders/ActorNotorietyConfigSeeder.php, pré-existant, sans rapport avec cette mission (déjà signalé dans tous les rapports précédents)
php artisan test (suite complète, 1ère exécution)462 tests — 1 échec (ActorAccessPhaseOneTest::test_mairie_alert_route_requires_module_activation, testait la route Mairie supprimée) + 1 erreur (class_exists() sans 2ᵉ argument déclenchait l'autoload sur une classe volontairement supprimée)
Corrections apportéesRetrait du test obsolète (§2) ; ajout de false en 2ᵉ argument de class_exists()
php artisan test (suite complète, exécution finale)461 tests, 1730 assertions, 0 échec
vendor/bin/phpstan analyseAbsent du projet, comme constaté à chaque mission précédente
composer analyseNon défini dans composer.json

8. Divergences

  1. §0 — fichier de lecture obligatoire manquant, non bloquant (déjà signalé en ENG-001.5A/5B).
  2. Relocalisation de expireAlertes() vers Municipal Management (§3) — la mission ne mentionnait pas explicitement cette méthode, mais son maintien dans Mairie aurait laissé de la logique métier « Alertes » hors de Municipal Management, contredisant directement l'objectif de clôture énoncé par la mission (« une seule implémentation métier », « Aucune logique métier "Alertes" ne doit rester ailleurs »). Traité comme une extension mineure et cohérente du travail déjà engagé en ENG-001.5B (même contrat, même service), pas comme une nouvelle décision d'architecture — signalé ici conformément à la gouvernance.
  3. Suppression des 4 routes Mairie dédiées aux alertes — impact potentiel sur des contrats HTTP publics, donc signalé explicitement : vérifié à deux reprises (audit initial ENG-001.5 + re-vérification fraîche dans cette mission, §5) qu'aucun frontend ni service backend ne les consomme. Décision prise sur la base de cette double vérification, conformément à l'autorisation explicite de la mission (« Si l'API Mairie n'a réellement plus aucun consommateur : préparer sa suppression »).
  4. Suppression du test test_mairie_alert_route_requires_module_activation — un test pré-existant hors du périmètre initialement audité (tests/Feature/Actor/ActorAccessPhaseOneTest.php) exerçait une route désormais supprimée. Retiré après vérification qu'une couverture équivalente existe déjà côté route Actor (§2, §6) — pas de perte de couverture fonctionnelle.

Aucune autre divergence. Aucune décision d'ownership nouvelle : la table mairie_alertes reste physiquement dans le domaine de données historique, seule la couche applicative change.


9. Confirmations

Une seule implémentation métier des alertes existe désormais. MunicipalManagementReadService/ WriteService sont l'unique code qui lit/écrit mairie_alertes (hors la ligne SQL brute expireAlertes(), également dans Municipal Management). Vérifié par le garde-fou automatisé il_nexiste_plus_quune_seule_implementation_metier_des_alertes (§6) et par grep exhaustif (§1, §2).

Aucune route publique Actor n'a changé. Les 4 routes (GET/POST /acteurs/{acteurId}/alertes, PATCH /acteurs/{acteurId}/alertes/{alerteId}, PATCH .../desactiver) existent avec les mêmes URIs, mêmes verbes, mêmes noms qu'avant cette PR — vérifié par php artisan route:list. Les 4 routes Mairie dédiées aux alertes ont été supprimées (zéro consommateur, §5) ; toutes les autres routes Mairie (services, publications, commune, élus, collectes, infos) sont inchangées.

Aucun frontend ne nécessite de modification. Les routes Actor consommées par dmv-workspace/dmv-public (identifiées par l'audit ENG-001.5 et revérifiées §5) sont strictement inchangées. Les routes Mairie supprimées n'avaient aucun consommateur identifié, ni avant ni après re-vérification.

Aucun commit n'a été créé. Branche feature/eng-001-5c-remove-municipal-alert-duplication, tous les fichiers ci-dessus en working tree, aucun git add/git commit exécuté. git status ne montre que les fichiers de cette mission et de Municipal-A/B (non committées non plus), plus les deux fichiers pré-existants sans rapport déjà signalés dans tous les rapports précédents (database/seeders/DatabaseSeeder.php, database/seeders/ActorNotorietyConfigSeeder.php).