Aller au contenu principal

ENG-001.5J2 — Migration des écritures des informations municipales — Rapport d'implémentation

MissionENG-001.5J2 — Migration des écritures des informations municipales
Rattaché àENG-001.5J — Migration des infos pratiques vers Municipal Management ; ENG-001.5J — Audit d'ownership des sections d'infos ; ENG-001.5J1 — Rapport d'implémentation
Dépôtdmv_api
Branchefeature/eng-001-5j2-municipal-infos-write
StatutImplémenté localement — aucun commit applicatif

1. Divergence initiale

  • MunicipalManagementWriter exposait déjà createInfo(), updateInfo() et deleteInfo(), mais ces trois méthodes levaient encore une LogicException.
  • aucune capacité d'écriture n'existait dans le contrat public pour commune_info_sections.
  • MairieCommuneController déléguait encore les écritures commune_infos à MairieWriteService.
  • AdminCommuneInfoController écrivait encore directement dans commune_infos et commune_info_sections.

2. Arbitrage validé

  • commune_info_sections appartient à Municipal Management.
  • toutes les écritures commune_infos et commune_info_sections côté Mairie/Admin passent désormais par MunicipalManagementWriter.
  • Territory/SIRENE reste explicitement hors périmètre J2 ; sa réconciliation vers commune_infos est reportée à J3.

3. Extension du contrat

Signatures ajoutées

public function createInfoSection(array $data): MunicipalInfoSectionDTO;

public function updateInfoSection(string $sectionId, array $data): MunicipalInfoSectionDTO;

public function deleteInfoSection(string $sectionId): void;

Signatures commune_infos désormais implémentées

public function createInfo(string $communeId, array $data): MunicipalInfoDTO;

public function updateInfo(string $communeId, string $infoId, array $data): MunicipalInfoDTO;

public function deleteInfo(string $communeId, string $infoId): void;

4. Écritures commune_infos migrées

Mairie

  • MairieCommuneController::storeInfo()
  • MairieCommuneController::updateInfo()
  • MairieCommuneController::destroyInfo()

Comportement conservé :

  • mêmes routes ;
  • mêmes codes HTTP ;
  • mêmes payloads ;
  • aucune validation Laravel ajoutée ;
  • mêmes valeurs par défaut historiques (section, icon_color, ordre) ;
  • même format JSON de réponse.

Évolution de sûreté conservatrice :

  • l'isolation inter-communes est désormais garantie sur updateInfo() via une lecture de retour scopée par commune_id.

Admin

  • AdminCommuneInfoController::storeInfo()
  • AdminCommuneInfoController::updateInfo()
  • AdminCommuneInfoController::destroyInfo()

Comportement conservé :

  • validations Laravel historiques inchangées ;
  • messages d'erreur historiques inchangés pour la façade ;
  • mêmes routes ;
  • mêmes structures JSON ;
  • aucune écriture directe restante vers commune_infos dans la façade.

5. Écritures commune_info_sections migrées

  • AdminCommuneInfoController::storeSection()
  • AdminCommuneInfoController::updateSection()
  • AdminCommuneInfoController::destroySection()

Comportement conservé :

  • validation Laravel inchangée ;
  • contrôle d'unicité du slug inchangé dans la façade Admin ;
  • mêmes valeurs par défaut (icone, ordre, active) ;
  • mêmes codes HTTP ;
  • mêmes formats JSON ;
  • aucune écriture directe restante vers commune_info_sections dans la façade.

6. Responsabilités restantes

Mairie

  • autorisation via middleware inchangée ;
  • adaptation HTTP ;
  • vérification d'existence de la commune pour la création ;
  • délégation vers MunicipalManagementWriter.

Admin

  • validation Laravel ;
  • contrôles d'existence et messages d'erreur historiques ;
  • contrôle d'unicité du slug ;
  • construction des réponses agrégées (fullCommune()) ;
  • délégation vers MunicipalManagementReader / MunicipalManagementWriter.

Territory

Chemins volontairement laissés en place pour J3 :

  • CommuneMairieDataRefreshService::refreshInfos()
  • CommuneMairieDataRefreshService::upsertInfo()

Ces écritures restent limitées à la section identite issue de SIRENE et n'ont pas été modifiées dans J2.

7. Classification des contrôles

MairieCommuneController

  • storeInfo() :
    • validation syntaxique HTTP : aucune validation Laravel historique ;
    • autorisation : middleware commune.manager ;
    • adaptation de réponse : sérialisation JSON ['info' => ...], statut 201 ;
    • invariant métier : aucun restant ;
    • contrainte de persistance : aucune restante.
  • updateInfo() :
    • validation syntaxique HTTP : aucune validation Laravel historique ;
    • autorisation : middleware commune.manager ;
    • adaptation de réponse : sérialisation JSON ['info' => ...], statut 200 ;
    • invariant métier : aucun restant ;
    • contrainte de persistance : aucune restante.
  • destroyInfo() :
    • validation syntaxique HTTP : aucune ;
    • autorisation : middleware commune.manager ;
    • adaptation de réponse : conservation du 204 historique, y compris si la ressource est absente ;
    • invariant métier : aucun restant dans la façade ; l’existence ciblée est vérifiée dans Municipal Management puis volontairement traduite en suppression idempotente côté Mairie ;
    • contrainte de persistance : aucune restante.

AdminCommuneInfoController

  • storeSection() :
    • validation syntaxique HTTP : validate() Laravel sur types et formats ;
    • autorisation : garde Admin existante ;
    • adaptation de réponse : 201 + message historique Ce slug existe déjà. en cas de doublon ;
    • invariant métier : l’unicité du slug vit désormais dans Municipal Management ;
    • contrainte de persistance : aucune restante.
  • updateSection() / destroySection() :
    • validation syntaxique HTTP : filtrage des champs modifiables ;
    • autorisation : garde Admin existante ;
    • adaptation de réponse : message historique Rubrique introuvable. + 404 ;
    • invariant métier : l’existence de la rubrique ciblée vit désormais dans Municipal Management ;
    • contrainte de persistance : aucune restante.
  • storeInfo() :
    • validation syntaxique HTTP : validate() Laravel ;
    • autorisation : garde Admin existante ;
    • adaptation de réponse : 201 et format JSON historique ;
    • invariant métier : l’existence de la commune cible vit désormais dans Municipal Management ;
    • contrainte de persistance : aucune restante.
  • updateInfo() / destroyInfo() :
    • validation syntaxique HTTP : filtrage des champs modifiables ;
    • autorisation : garde Admin existante ;
    • adaptation de réponse : message historique Info introuvable. + 404 ;
    • invariant métier : existence de l’info ciblée et isolation inter-communes vivent désormais dans Municipal Management ;
    • contrainte de persistance : aucune restante.

MunicipalManagementWriteService

  • vérification d’existence de la commune pour createInfo() :
    • classification : invariant métier Municipal Management adossé à une contrainte de persistance ;
    • justification : la création d’une info n’est valide que pour une commune existante.
  • unicité du slug pour createInfoSection() :
    • classification : invariant métier Municipal Management ;
    • justification : la validité métier du référentiel global impose un slug unique.
  • existence de l’info ciblée pour updateInfo() / deleteInfo() :
    • classification : invariant métier Municipal Management ;
    • justification : impossible de modifier ou supprimer une info inexistante ou appartenant à une autre commune.
  • isolation inter-communes pour updateInfo() / deleteInfo() :
    • classification : invariant métier Municipal Management ;
    • justification : l’opération doit être refusée si la ressource appartient à une autre commune.
  • valeurs par défaut section, icon_color, ordre dans createInfo() :
    • classification : invariant métier Municipal Management conservant le comportement historique ;
    • justification : la façade Mairie n’applique aucune validation/défaut explicite.
  • existence de la section ciblée pour updateInfoSection() / deleteInfoSection() :
    • classification : invariant métier Municipal Management ;
    • justification : impossible de modifier ou supprimer une rubrique inexistante.
  • valeurs par défaut icone, ordre, active dans createInfoSection() :
    • classification : invariant métier Municipal Management ;
    • justification : elles déterminent l’état métier initial du référentiel.
  • contrainte avant suppression :
    • commune_infos : existence ciblée et appartenance à la commune ;
    • commune_info_sections : existence ciblée uniquement ;
    • aucune autre contrainte métier de suppression confirmée dans le code observé.

8. Logique déplacée ou conservée

Logique déplacée vers Municipal Management

  • unicité du slug ;
  • existence de la commune pour createInfo() ;
  • existence de l’info ciblée ;
  • existence de la section ciblée ;
  • isolation inter-communes sur commune_infos ;
  • valeurs métier par défaut des créations commune_infos ;
  • valeurs métier par défaut des créations commune_info_sections ;
  • contrainte de suppression sur ressource ciblée.

Logique conservée dans les façades

  • validation Laravel des types et formats ;
  • autorisation ;
  • adaptation des statuts HTTP ;
  • adaptation des messages historiques ;
  • conservation explicite du 204 idempotent côté Mairie sur destroyInfo() malgré l’erreur métier interne.

9. Preuve d’ownership métier unique

  • les invariants ci-dessus ne sont plus évalués en SQL direct dans MairieCommuneController ou AdminCommuneInfoController ;
  • MunicipalManagementWriteService devient l’unique point qui décide si une écriture commune_infos ou commune_info_sections est valide ;
  • les façades ne font plus que valider la forme HTTP, autoriser, déléguer, puis traduire l’erreur métier en réponse historique.

10. Compatibilité préservée

  • routes inchangées ;
  • URLs inchangées ;
  • méthodes HTTP inchangées ;
  • validations inchangées ;
  • réponses JSON inchangées ;
  • codes HTTP inchangés ;
  • aucun frontend modifié ;
  • aucune écriture Territory déplacée.

11. Tests exécutés

  • création Mairie d'une info avec défauts historiques préservés ;
  • modification Mairie avec isolation inter-communes ;
  • suppression Mairie idempotente ;
  • création / modification / suppression Admin de commune_infos ;
  • création / modification / suppression Admin de commune_info_sections ;
  • unicité du slug directement dans Municipal Management ;
  • ressource inexistante directement dans Municipal Management ;
  • ressource inexistante via Admin ;
  • validation et unicité du slug côté Admin ;
  • formats de réponse inchangés ;
  • retour de DTO publics, jamais de modèle Eloquent ;
  • garde-fous de délégation vers MunicipalManagementWriter ;
  • garde-fous d'unicité d'implémentation métier ;
  • garde-fous d'absence de duplication des règles métier dans les deux façades ;
  • garde-fous confirmant l'absence de modification Territory ;
  • garde-fous confirmant les routes historiques inchangées.

12. Garde-fous ajoutés

  • plus aucune écriture directe commune_infos dans MairieCommuneController ;
  • plus aucune écriture directe commune_infos ou commune_info_sections dans AdminCommuneInfoController ;
  • plus aucun contrôle direct de slug, d'existence de commune ou d'existence d'info/rubrique dans les écritures des façades J2 ;
  • MairieWriteService n'expose plus de méthodes mortes createInfo(), updateInfo(), deleteInfo() ;
  • MunicipalManagementWriteService est l'unique implémentation métier des écritures J2 ;
  • CommuneMairieDataRefreshService reste intact pour J3.

13. Décisions restantes pour J3

  • définir le contrat de réconciliation externe pour commune_infos ;
  • déplacer CommuneMairieDataRefreshService::refreshInfos() vers Municipal Management ;
  • préciser la stratégie de convergence entre mises à jour manuelles et enrichissement SIRENE sur la section identite.