# CHANGELOG Technique — Kohinos

> Suivi technique des évolutions par version.
> Pour le changelog fonctionnel (utilisateurs / administrateurs), voir [`CHANGELOG_USER.md`](./CHANGELOG_USER.md).

---

# v2.8.8 (2026-03-14)

## fix(entity) — Correction NotNullConstraintViolationException sur geoloc_prestataire

### Changements

- **`Prestataire.$geolocs`** : ajout de `@Assert\Valid` pour cascader la validation vers chaque `GeolocPrestataire` de la collection.
  Sans cette annotation, Sonata Admin (et tout formulaire utilisant ce champ) ne validait pas les sous-entités de la collection avant le flush, permettant à un `GeolocPrestataire` avec `name=null` d'atteindre la couche base de données et de déclencher `SQLSTATE[23000]: Integrity constraint violation: 1048 Column 'name' cannot be null`.
  Désormais, la validation Symfony renvoie une erreur lisible à l'utilisateur avant toute tentative d'INSERT.
- **`Prestataire.$contacts`** : même correction — `@Assert\Valid` ajouté sur la collection `ContactPrestataire` (même trait `NameSlugContentEntityTrait`, même risque).

---

# v2.8.7 (2026-03-14)

## fix(entity) — Correction UniqueConstraintViolationException sur user_usergroup

- **`User::setGroups()`** : remplacement de `$this->groups = new ArrayCollection()` par `$this->getGroups()->clear()`.
  Remplacer la `PersistentCollection` de Doctrine par un nouvel `ArrayCollection` cassait le change-tracking de l'UnitOfWork, ce qui provoquait un `INSERT INTO user_usergroup` en doublon lors du flush (erreur `SQLSTATE[23000]: Duplicate entry`).
  La méthode `clear()` vide correctement la collection existante sans la remplacer, permettant à Doctrine de calculer le diff (DELETE + INSERT) de façon fiable.

## fix(geocoding) — Gestion de l'exception InvalidCredentials du geocodeur

- **`GeolocListener::updateGeoLoc()`** : ajout d'un catch `\Geocoder\Exception\InvalidCredentials` avant `InvalidServerResponse`.
  Auparavant, une clé API invalide déclenchait une exception non catchée qui remontait en 500 lors du flush d'une entité géolocalisée.
- **`IndexController::geoLocAction()`** : même ajout — l'action AJAX de géolocalisation manuelle retourne désormais `status=error` au lieu de lever une exception non catchée.

## fix(import) — Correction parsing de la date de cotisation dans l'import adhérents

- **`ImportController::importAdherentCotisations()`** : remplacement de `DateTime::createFromFormat('Ymd', intval($year) . '0101')` par `new DateTime($year . '-01-01 00:00:00')`.
  `createFromFormat` avec concaténation d'entier pouvait retourner `false` silencieusement si le format ne correspondait pas, rendant les dates de début/fin de cotisation `null` en base.
  La solution `new DateTime('YYYY-01-01 00:00:00')` est explicite et sans ambiguïté de format.

## feat(form) — TierRuleType : passage à NumberType pour les tailles de foyer décimales

- **`TierRuleType`** : `minPersons` et `maxPersons` passent de `IntegerType` à `NumberType` avec `scale=2`.
  Permet les valeurs décimales (ex : 1.5, 2.3) pour représenter les situations de garde alternée dans les paliers SSA.
- `min` changé de `1` à `0` pour autoriser le palier `[0, ∞[` (adhérent sans enfant à charge).

### Tests

- **`tests/Unit/Entity/UserSetGroupsTest.php`** : 5 tests pour `User::setGroups()` — vérifient le remplacement, la préservation de l'instance de collection (`assertSame`), le vidage, le chaînage et les cas multi-groupes.
- **`tests/Unit/Form/TierRuleTypeTest.php`** : 4 tests pour `TierRuleType` — vérifient les valeurs décimales, `minPersons=0`, champs optionnels, rétrocompatibilité entiers.

### Fichiers modifiés

- `src/Entity/User.php`
- `src/EventListener/GeolocListener.php`
- `src/Controller/IndexController.php`
- `src/Controller/ImportController.php`
- `src/Form/Type/TierRuleType.php`
- `tests/Unit/Entity/UserSetGroupsTest.php` (nouveau)
- `tests/Unit/Form/TierRuleTypeTest.php` (nouveau)

---

# v2.8.6 (2026-03-10)

## fix(import) — Correction colonne `cotisation_min` + helpers descriptifs par type d'import

### Changements

- **Bug corrigé** : `importAdherentSsa()` lisait la colonne `cotisation_min` mais le template CSV et `services.yaml` déclaraient `cotisation_amount`. La colonne SSA était silencieusement ignorée à l'import.
  - `public/csv/adherent_ssa.csv` : header corrigé `cotisation_amount` → `cotisation_min`
  - `config/services.yaml` : header `adherent_ssa` corrigé (`prenom;nom` au lieu de `firstname;lastname`, suppression `ecompte`, `cotisation_amount` → `cotisation_min`)
- **Typo** : `config/services.yaml` flux helper — "uniqueemnt" → "uniquement"
- **`config/services.yaml`** : ajout du champ `helper` sur tous les types d'import (descriptif affiché sous le type dans le tableau de la page import)
- **`templates/themes/kohinos/admin/import.html.twig`** : affichage du `helper` en italique sous le nom du type d'import — conditionnel (`not empty`)
- **fix(twig)** : correction syntaxe Twig invalide `not empty csvparam.helper` → `csvparam.helper is not empty`
- **fix(gdpr)** : `layout.html.twig` — double protection côté template : la modale de consentement RGPD ne s'affiche plus sur `gdpr_privacy_policy` (complément du fix `ConsentCheckListener` de v2.8.5)

### Tests

- **`tests/Controller/SsaControllerBasicTest.php`** : seuil de complexité `ssaAdhererAction` ajusté 60 → 80 lignes
- **`tests/Controller/SsaControllerRefactoringTest.php`** : seuil 50 → 65 lignes + pattern nommage étendu à `is[A-Z]` pour les méthodes booléennes
- **`tests/Service/Gdpr/ConsentServiceTest.php`** : ajout 6ème argument `HouseholdSizeDisplayService` au setUp + mock `shouldDisplayHouseholdSize()→true` pour `testGetRequiredConsentTypesWithSsa`
- **`tests/Service/Gdpr/GdprExportServiceTest.php`** : `UserConsent::class` ajouté dans tous les `willReturnMap` ; propriété `$consentRepo` centralisée dans `setUp()`

### Fichiers modifiés

- `config/services.yaml`
- `public/csv/adherent_ssa.csv` (header corrigé)
- `templates/themes/kohinos/admin/import.html.twig`
- `templates/themes/kohinos/common/layout.html.twig`
- `tests/Controller/SsaControllerBasicTest.php`
- `tests/Controller/SsaControllerRefactoringTest.php`
- `tests/Service/Gdpr/ConsentServiceTest.php`
- `tests/Service/Gdpr/GdprExportServiceTest.php`

---

# v2.8.5 (2026-03-09)

## feat(boffice) — Adaptation specs API Boffice v2.1.0

### Changements

#### 1. Description optionnelle (conformité spec v2.1.0)
- Suppression de la validation côté front qui bloquait la soumission si `description` était vide.
- Retrait de l'astérisque obligatoire sur le label "Description" dans la modale.
- Un ticket bug peut désormais être soumis avec uniquement les 3 champs séparés et sans description.

#### 2. Modale rich ticket (kanban / timeline) — affichage des champs bug
- `populateTicketModal()` dans `boffice-api.js` affiche désormais les 3 blocs colorés
  (`stepsToReproduce`, `currentBehavior`, `expectedBehavior`) dans la modale d'aperçu rapide
  (kanban, timeline), en plus ou à la place de `description`.
- Cohérence visuelle identique à la vue détail `boffice-ticket-show.js` :
  gris (étapes), rouge (courant), vert (attendu).
- La section description/blocs bug est masquée si aucun champ n'est renseigné.

#### 3. Test unitaire supplémentaire
- **`tests/Unit/Service/BofficeTicketFieldsTest.php`** : ajout de
  `testCreateBugTicketWithoutDescriptionIsAllowed` (7ème test) :
  - Payload sans `description` + 3 champs bug → POST transmis à l'API
  - Vérifie que `description` est absente du payload et que `stepsToReproduce` est non null

#### 4. Documentation CLAUDE.md
- Ajout de la section **Boffice Module (SAV — Ticket Management)** :
  - Tableau des fichiers clés
  - Spec API v2.1.0 complète (objet ticket, règles snake_case/camelCase)
  - Conventions frontend (vanilla JS vs jQuery, CKEditor lifecycle)
  - Tableau des TTL de cache
  - Contrôle d'accès GLOBAL_ADMIN

#### 5. fix(gdpr) — ConsentCheckListener : exemption de `gdpr_privacy_policy`
- Ajout de `'gdpr_privacy_policy'` dans `$exemptedRoutes` de `isExemptedRoute()`.
- **Avant** : la page politique de confidentialité déclenchait une boucle de redirection
  lorsqu'un utilisateur sans consentement tentait d'y accéder.
- **Après** : la route est exemptée, le modal RGPD ne bloque plus l'accès à la politique.

#### 6. fix(api) — Correction des erreurs HTTP 500 sur l'API Boffice locale
- La migration `Version20260309000000` (colonnes `steps_to_reproduce`, `current_behavior`,
  `expected_behavior` sur la table `ticket`) n'avait pas été exécutée sur l'environnement
  de développement local. Tous les endpoints `/api/tickets*` et `/api/kanban/columns`
  retournaient HTTP 500 (`SQLSTATE[42S22]: Column not found`).
- Résolution : `php8.2 bin/console doctrine:migrations:migrate` dans `kohinosticket/`.
- **À faire en production** : exécuter la même migration sur le serveur Boffice distant.

### Fichiers modifiés

- **`templates/themes/kohinos/admin/boffice/layout.html.twig`** :
  - Suppression du bloc de validation `description` obligatoire
  - Retrait du `<span style="color:#c00;">*</span>` sur le label description
- **`assets/js/boffice-api.js`** :
  - `populateTicketModal()` : affichage des 3 blocs colorés bug (stepsToReproduce / currentBehavior / expectedBehavior)
  - La section description est affichée si description OU au moins un champ bug est renseigné
- **`tests/Unit/Service/BofficeTicketFieldsTest.php`** : 7ème test ajouté
- **`CLAUDE.md`** : section Boffice Module ajoutée
- **`src/EventListener/ConsentCheckListener.php`** : `gdpr_privacy_policy` ajouté aux routes exemptées

---

# v2.8.4 (2026-03-10)

## fix(boffice) — Champs bug séparés + fix CKEditor modale + migration jQuery → vanilla JS

### Problèmes corrigés

#### 1. Description disparaît à la 2ème ouverture de la modale d'édition (bug CKEditor)
- **Cause** : `initCKEditor()` ne recréait pas l'instance si elle existait déjà. Au premier
  appel, `setData` pouvait être invoqué avant que l'instance soit prête (`instanceReady`),
  laissant la description vide.
- **Correction** : `initCKEditor(initialData)` détruit maintenant l'instance existante avant
  d'en créer une nouvelle. La donnée est passée via l'event `instanceReady` (callback
  `editor.on('instanceReady', ...)`) pour garantir que `setData` est toujours appelé sur une
  instance prête.
- **Ajout** : `destroyCKEditor()` appelé dans `closeModal()` pour nettoyer l'instance à chaque
  fermeture.

#### 2. Champs bug séparés — unification front/API
- Les champs `steps_to_reproduce`, `current_behavior`, `expected_behavior` sont envoyés comme
  champs JSON indépendants (pas concaténés dans `description`) aussi bien à la création qu'à
  la mise à jour d'un ticket.
- La modale pré-remplit ces champs depuis `ticket.stepsToReproduce`, `ticket.currentBehavior`,
  `ticket.expectedBehavior` retournés par l'API.
- `renderTicket` dans `boffice-ticket-show.js` les affiche dans des blocs colorés distincts
  (gris / rouge / vert).
- `resetFormFields()` vide explicitement ces textareas à chaque réinitialisation du formulaire
  (un simple `form.reset()` ne suffisait pas car leurs valeurs étaient positionnées par JS).

#### 3. Migration jQuery → vanilla JS (`boffice-ticket-form.js`)
- Suppression de la dépendance jQuery dans `assets/js/boffice-ticket-form.js`.
- Toute la logique (toggle type, clear errors, CKEditor, soumission) est unifiée dans
  `layout.html.twig` (JS natif ES2020).
- `boffice-ticket-form.js` conservé comme stub d'entrée Webpack (no-op).

### Fichiers modifiés

- **`templates/themes/kohinos/admin/boffice/layout.html.twig`** :
  - `initCKEditor(initialData)` : destroy + replace + `instanceReady` event
  - `destroyCKEditor()` : nettoyage à la fermeture
  - `resetFormFields()` : vide explicitement les 3 textareas bug
  - `closeModal()` : appel de `destroyCKEditor()`
  - `openCreateModal()` / `bofficeOpenEditTicketModal()` : passage de `initialData` à `initCKEditor`
- **`assets/js/boffice-ticket-form.js`** : migration jQuery → stub vanilla JS
- **`assets/js/boffice-ticket-show.js`** : correction vérifiée (bloc `expected` déjà complet)

### Tests ajoutés

- **`tests/Unit/Service/BofficeTicketFieldsTest.php`** (6 tests) :
  - POST `/tickets` avec les 3 champs bug séparés → transmis à l'API
  - PATCH `/tickets/{id}` avec les 3 champs bug → transmis + cache invalidé
  - GET `/tickets/{id}` → champs camelCase retournés (`stepsToReproduce`, etc.)
  - Ticket évolution → aucun champ bug envoyé
  - Champs partiels (seulement `steps_to_reproduce`)
  - PATCH partiel → cache invalidé

---

# v2.8.3 (2026-03-09)

## feat(boffice) — Modification de ticket via la modale de création (GLOBAL_ADMIN)

Factorisation du formulaire d'édition de ticket Boffice : suppression du bloc
"Modifier" inline dans la page de ticket, remplacé par la modale de création
réutilisée en mode édition.

### Changements

- **`templates/themes/kohinos/admin/boffice/layout.html.twig`** :
  - Modale ticket unifiée (mode `create` + mode `edit` via champs cachés `#boffice-ticket-mode` et `#boffice-ticket-edit-id`)
  - Ajout des champs édition-only (colonne Kanban, monnaie associée, catégorie, date de début), affichés uniquement en mode édition
  - Titre, icône et bouton de la modale changent dynamiquement selon le mode
  - Soumission : POST création ou PATCH mise à jour selon le mode
  - Suppression de tout le JS jQuery inline → migration vers JS natif (ES2020)
  - Exposition de `window.bofficeOpenEditTicketModal(ticket, columns, installations)`
- **`templates/themes/kohinos/admin/boffice/ticket_show.html.twig`** :
  - Suppression du bloc "Full edit form — GLOBAL_ADMIN only" (box collapsible avec `#boffice-edit-form`)
  - Ajout d'un bouton "Modifier" (`#boffice-edit-btn`) dans le header du ticket
- **`assets/js/boffice-ticket-show.js`** :
  - Migration complète jQuery → JS natif (ES2020 — `fetch`, `document.getElementById`, `addEventListener`, etc.)
  - Suppression des fonctions : `populateEditForm`, `populateColumnSelect`, `populateInstallationSelect`, `submitEdit`
  - Ajout du handler sur `#boffice-edit-btn` → appel de `window.bofficeOpenEditTicketModal`
  - La chargement des installations et colonnes Kanban est conservé pour alimentation de la modale

---

# v2.8.2 (2026-03-06)

## feat(ssa) — Composition du foyer en valeur décimale

Le champ `householdSize` (taille du foyer) accepte désormais des valeurs décimales (ex : `1.3`, `2.5`) pour permettre des compositions de foyer pondérées.

### Changements

- **`Adherent` (entité)** : colonne `household_size` passée de `INT` à `DECIMAL(5,2)` ; getters/setters de `?int` à `?float`.
- **Migration** : `Version20260306000000` — `ALTER TABLE adherent MODIFY household_size DECIMAL(5,2)`.
- **Formulaires** (`AdherentInfosFormType`, `SolidoumeFormType`) : `IntegerType` → `NumberType` avec `scale=2`, `html5=true`, `step=0.01`.
- **Admin** (`AdherentAdmin`) : idem `IntegerType` → `NumberType`.
- **Contrôleurs** (`UserAdherentController`, `SsaController`, `ImportController`) : casts `(int)` → `(float)` ; validation CSV `ctype_digit` → `is_numeric`.
- **Services** (`HouseholdSizeDisplayService`, `TiersStrategy`, `SsaReportService`) : comparaisons et casts mis à jour en `float`.
- **Template** `modale_household_size.html.twig` : `step="0.01"` ajouté sur l'`<input>`.
- **Tests** : cas décimaux ajoutés (`1.5`, `3.5`) dans `RedistributionStrategyTest` ; signatures et comparaisons flottantes dans `RedistributionStrategiesTest`.

---

# v2.8.1 (2026-03-05)

## Fix — SsaFormerParticipantReminderCommand : 3 bugs corrigés

- **Bug 1 (production release/32)** : `SolidoumeItem::getMontantCalcule()` inexistante → remplacée par `getAmount()` (confirmé et protégé par test).
- **Bug 2** : `sendReminderEmail()` créait un `Symfony\Component\Mime\Email` mais le mailer injecté est `\Swift_Mailer` → remplacé par `\Swift_Message` avec l'API Swift correcte.
- **Bug 3** : `Command::FAILURE` (Symfony 5.1+) utilisée en Symfony 4.4 → remplacée par `1`. Adresse expéditeur dynamique via `GlobalParameter::MLC_NOTIF_EMAIL` au lieu d'une valeur hardcodée.
- **Tests** : `tests/Command/SsaFormerParticipantReminderCommandTest.php` (7 tests, 15 assertions).

---

## Import CSV — Nouveau type : Adhérent avec Caisse SSA

- **`ImportEnum`** : constante `IMPORT_ADHERENT_SSA` + label `'Adhérent(s) avec Caisse SSA'`.
- **`ImportController`** : handler `importAdherentSsa()` — import CSV avec colonnes `idmlc;groupe;firstname;...;caisse_ssa`.
- **`config/services.yaml`** : template CSV `adherent_ssa` avec header, exemple et fichiers de référence.

---

## Admin — Améliorations liste Flux & SolidoumeAdmin

- **`BaseFluxAdmin`** : ajout de la colonne `moyen` dans la liste des flux.
- **`FluxAdmin`** : nouveaux types dans les filtres datagrid — `Cotisations SSA`, `Don`, `Reconversion`, `Vente emlc`, `Ticket Fix`.
- **`FluxEurosAdmin`** : ajout de `TYPE_COTISATION_SSA` dans les types euros (cotisations SSA en euros visibles dans la vue "Flux euros").
- **`SolidoumeAdmin`** : export dynamique avec colonnes conditionnelles (`householdSize`, `minimumCotisation` selon config caisse). Label menu renommé.

---

## SSA — Restriction de participation par cotisation individuelle paramétrée

### Nouvelle fonctionnalité

Pour les caisses SSA avec cotisation de type **Recommandée** (`suggested`) ou **Minimum fixe** (`fixed_min`), les administrateurs peuvent désormais activer une restriction qui empêche les adhérents ayant une participation non archivée mais sans cotisation individuelle paramétrée (`minimumCotisation`) de cotiser ou participer via le front-end. Les adhérents concernés conservent néanmoins l'usage normal de leur solde eMLc pour leurs transactions.

### Configuration (SolidoumeParamAdmin — onglet Cotisation)

- **Case à cocher** : *Restreindre la participation aux adhérents sans cotisation paramétrée* — visible uniquement quand le type de cotisation est `Recommandée` ou `Minimum fixe`. Masquée automatiquement pour les types `Libre` et `Choix restreints` (JS).
- **Champ CKEditor** : *Message affiché aux adhérents bloqués* — message HTML personnalisé affiché à l'adhérent lorsqu'il est bloqué. Un message par défaut est utilisé si ce champ est laissé vide.

### Règle métier

La restriction s'applique quand **toutes** les conditions suivantes sont réunies :
1. `requireCotisationToParticipate = true` sur la caisse SSA, avec type `suggested` ou `fixed_min`
2. L'adhérent possède déjà une `SolidoumeItem` en base pour cette caisse (participation existante)
3. Cette `SolidoumeItem` n'est **pas archivée** (quel que soit son état activé/désactivé)
4. La `SolidoumeItem` n'a **pas** de `minimumCotisation` individuel paramétré par un admin

### Fichiers modifiés

- **`src/Entity/SolidoumeParameter.php`** : ajout des propriétés `requireCotisationToParticipate` (bool, défaut `false`) et `cotisationRestrictionMessage` (?string) avec getters/setters + méthode helper `isParticipationRestrictedForAdherent()`.
- **`src/Migrations/Version20260303100000.php`** : migration Doctrine ajoutant les colonnes `require_cotisation_to_participate` et `cotisation_restriction_message` sur `solidoume_parameter`.
- **`src/Admin/SolidoumeParamAdmin.php`** : deux nouveaux champs dans le tab *Cotisation* (checkbox + CKEditor), avec classes CSS pour le toggle JS.
- **`assets/js/admin-solidoume.js`** : extension de `doToggle()` pour afficher/masquer les nouveaux champs selon le type de cotisation sélectionné.
- **`src/Controller/SsaController.php`** : remplacement de l'ancien check hardcodé (suggested uniquement) par la nouvelle méthode privée `isCotisationRestrictionActive()`, configurable et étendue à `fixed_min`. Le message est passé en données de vue pour un rendu HTML sécurisé (`|raw`).
- **`src/Controller/SsaSepaController.php`** : même vérification ajoutée avant le formulaire SEPA. Le message est passé via `strip_tags()` dans le flash warning.
- **`templates/themes/kohinos/solidoume/ssa.html.twig`** : remplacement du bloc de restriction hardcodé par un bloc conditionnel utilisant `cotisationRestrictionActive` et `cotisationRestrictionMessage|raw`.

### Tests

- **`tests/Unit/Entity/SolidoumeParameterRestrictionTest.php`** : 21 tests couvrant `isParticipationRestrictedForAdherent()`, les getters/setters des nouveaux champs, et les combinaisons flag × type.
- **`tests/Unit/Entity/SolidoumeItemRestrictionTest.php`** : 21 tests couvrant les conditions `getId()`, `getArchived()`, `getMinimumCotisation()` et la logique combinée des quatre conditions de restriction.

---

## SSA — Page "Toutes les opérations SSA" avec filtres

### Nouvelle fonctionnalité
Le lien "Voir toutes les opérations" du bloc dashboard SSA (`operationsssa.html.twig`) pointait sur la route générique `show_operations` (opérations eMLc uniquement). Il est remplacé par une nouvelle route dédiée `show_ssa_operations/{ssaId}` qui affiche **toutes** les opérations SSA de l'adhérent pour une caisse donnée :

1. **Opérations eMLc du compte SSA** (prélèvements, redistributions, cotisations eMLc) — filtrées en SQL par `getUserOperationsByCurrency(CURRENCY_EMLC, $ssa)`.
2. **Cotisations euros** (CB, HelloAsso, SEPA, virement…) — chargées via `FluxRepository::getQueryByAdherentAndDestinataire` et filtrées en PHP (ces paiements ne génèrent aucune opération côté adhérent).

Les deux sources sont normalisées dans un tableau unifié, triées par date décroissante, puis paginées avec KnpPaginator.

### Filtres disponibles (identiques à la page générique)
- **Moyen de paiement** — appliqué en SQL pour les opérations eMLc, en PHP pour les cotisations euros.
- **Date min / Date max** — même logique de filtrage.

### Fichiers modifiés / créés
- **`src/Controller/OperationsController.php`** : nouveau `showSsaOperationsAction()` avec route `@Route("/show/ssa-operations/{ssaId}", name="show_ssa_operations")`.
- **`templates/themes/kohinos/flux/operationsssa_full.html.twig`** (nouveau) : page dédiée avec formulaire de filtres, tableau unifié (Date, Type, Opération, Montant), collapse pour référence/ticket HelloAsso, et pagination KnpPaginator.
- **`templates/themes/kohinos/block/operationsssa.html.twig`** : le lien "Voir toutes les opérations" redirige vers `show_ssa_operations` (au lieu de `show_operations`) ; le bouton s'affiche dès qu'il y a au moins une opération eMLc **ou** une cotisation.

---

## SSA — Affichage des cotisations dans le bloc "Dernières opérations SSA"

### Problème résolu
Les cotisations SSA de l'adhérent (paiements mensuels vers la caisse) n'apparaissaient pas dans le bloc "Dernières opérations SSA" du tableau de bord. En particulier, les cotisations payées en **euros** (CB, HelloAsso, SEPA, virement, espèces, chèque) n'y figuraient jamais car le flux `CotisationSsaAdherent` ne génère **qu'une seule opération** côté prestataire (caisse), sans opération côté adhérent.

### Twig
- **`SsaExtension`** : nouvelle fonction `getLastSsaCotisationsForCurrentAdherent(SolidoumeParameter $ssa, int $limit = 10): array` — récupère les `CotisationSsaAdherent` via `FluxRepository::getQueryByAdherentAndDestinataire()` pour l'adhérent connecté, limitée à `$limit` résultats. Délègue à `getSsaCotisations()` pour la logique de requête.
- **`operationsssa.html.twig`** : intégration des cotisations dans le même tableau que les opérations eMLc. Les cotisations (section 2) sont affichées sous les opérations eMLc (section 1) avec :
  - devise déduite du moyen (`emlc` → eMLc, tout autre → Euro)
  - montant toujours négatif (débit de l'adhérent vers la caisse)
  - détail extensible (référence, ticket HelloAsso si disponible)
  - IDs de collapse préfixés `cotInfoCot` pour éviter tout conflit avec `operationInfo`
  - commentaires explicatifs sur la structure et les deux types d'entrées

### Traductions
- **`flux.fr.yml`** : ajout de `exp_cotisation_ssa: 'Cotisation SSA'` et `des_cotisation_ssa: 'Cotisation SSA'`.

### Tests
- **`tests/Twig/SsaExtensionTest.php`** (nouveau) : 6 tests unitaires couvrant `getLastSsaCotisationsForCurrentAdherent()` — utilisateur non connecté, sans rôle adhérent, sans méthode `getAdherent`, cas nominal, respect du `$limit`, aucune cotisation.

---

# v2.8.0 (2026-03-03)

## SSA — Stepper montant, effectiveMinimum par participant, archivage doublons

### Entités
- **`SolidoumeItem`** : suppression de `recommendedAmount` / `getRecommendedAmount()` / `setRecommendedAmount()` (remplacé par `minimumCotisation`). `getCotisationDiff()` utilise désormais `minimumCotisation` au lieu de `recommendedAmount`.
- **`SolidoumeParameter`** : `getCotisationTypeSummary()` pour `COTISATION_TYPE_SUGGESTED` affiche `x€` (montant dépend du `minimumCotisation` par participant) au lieu du global minimum.

### Formulaire
- **`SolidoumeFormType`** : refactorisation du calcul `effectiveMinimum` (item-specific `minimumCotisation` prioritaire sur global SSA minimum). Calcule `suggestedAmount`, `tolerancePercent`, `calculatedMin = round(effectiveMinimum * (1 - tolerance/100), 2)` en tête de `buildForm()`. Slider `minMontantSlider` basé sur `calculatedMin`. `defaultAmount` forcé si montant existant nul ou sous le minimum autorisé.

### Repository
- **`SolidoumeItemRepository`** : `getMonthlyCotisationDiffStats()` utilise `i.minimumCotisation` au lieu de `i.recommendedAmount` (colonne supprimée en migration).

### Twig
- **`SsaExtension::getSsaParticipation()`** : ajout filtre `archived = null`, return type `?SolidoumeItem`.
- **`ssa.html.twig`** : badge "Minimum accepté" passe en `bg-warning text-white p-2` (meilleur contraste).
- **`_ssa_stepper_theme.html.twig`** (nouveau) : thème Symfony Form pour le stepper montant SSA.

### CSS/JS
- **`ssa-stepper.css`** (nouveau) : styles du stepper +/− SSA, `margin-bottom: 0 !important` sur l'input.
- **`ssa.js`** : corrections — suppression `console.log` orphelins de debug (`'novalidateForm'`, `'ici'`, `'ici3'`, `'ici 2'`), correction typo `lenght` → `length`, ajout commentaires modes. Stepper +/− universel pour tous les modes (suggested, fixed_min, free).

### Admin Sonata
- **`SolidoumeAdmin`** : filtre `CallbackFilter` par adhérent (nom/prénom/email DQL LIKE). Colonne `minimumCotisation` éditable inline.
- **`SolidoumeParamAdmin`** : texte d'aide mis à jour pour le champ minimum.

## Migrations base de données

- **`Version20260226110000`** : `DROP COLUMN IF EXISTS recommended_amount` sur `solidoume_item`.
- **`Version20260226120000`** : archivage des doublons `SolidoumeItem` par groupe `(adherent, solidoume_parameter)`. Garde l'item prioritaire : `enabled=1 > last_month_payed DESC > updated_at DESC`. Irréversible.
- **`Version20260227130000`** : `ADD created_at/updated_at` sur `payment` et `payment_token` avec backfill. Backfill `payment` depuis `vads_effective_creation_date`/`vads_trans_date` (JSON PayZen). Backfill `payment_token` depuis l'UUID du `payment` lié (PHP sérialisé).

### Entités
- **`Payment`** / **`PaymentToken`** : ajout trait `TimestampableEntity`.

## Boffice — Actions GLOBAL_ADMIN

- **`BofficeController`** : nouvelles routes sécurisées `GLOBAL_ADMIN` uniquement :
  - `POST /ticket/{id}/archive` — archiver/désarchiver
  - `POST /ticket/{id}/delete` — suppression définitive
  - `POST /ticket/{id}/attachment/{attId}/delete` — suppression pièce jointe
  - `POST /ticket/{id}/update` — maintenant protégé `GLOBAL_ADMIN`
  - `POST /ticket/{id}/upload` — maintenant protégé `GLOBAL_ADMIN`
- **`boffice-list.js`** : bouton archive/unarchive dans la liste (GLOBAL_ADMIN), filtre toggle par installation via `BofficeApi`, console.log utiles, recherche debounce 250ms.
- **`boffice-ticket-show.js`** : chargement des installations pour le formulaire d'édition, boutons archive/delete, upload pièce jointe, formulaire d'édition complet (colonnes, priorité, installation, zone, financement).
- **`boffice-kanban.js`** : améliorations UX tableau kanban.
- **`config/packages/boffice.yaml`** : mise à jour configuration.

## Email — Flux template builder

- **`EmailTemplateService`** : `getAllFluxTemplates()` (scan default + custom `/flux/*.html.twig`), `getFluxDefaultContent()`.
- **`EmailVariableRegistryService`** : `getFluxVariables()` — variables groupées par objet (flux, user, globals Twig).
- **`EmailLogService`** / **`EmailLogAdmin`** : nouvelles colonnes et filtres.
- **`EmailTemplateController`** : route `flux_builder` pour l'éditeur admin.
- **`flux_builder.html.twig`** (nouveau) / **`admin-flux-template-editor.js`** (nouveau) : interface éditeur templates flux.

## Paiements / FluxController

- **`FluxController::preparePaymentAction()`** : neutralisation don si `montant === null || montant <= 0` avant sérialisation JSON (évite crash désérialisation).
- **`FluxController::doneAction()`** : activation du `$em->persist($payment)` + `flush()` lors de la notification (TODO levé).
- **`Flux::setMontant()`** : accepte `?float` (nullable).

## UX / Frontend

- **`app.js`** : spinner submit via `setTimeout(0)` — laisse les handlers `preventDefault` s'exécuter avant de désactiver le bouton. Suppression du `€` de `.achat_monnaie_montant_choisi`.
- **`achat-monnaie.css`** : suppression d'overrides de marges sur les boutons.
- **`admin-consent-stats.js`** (nouveau) : statistiques consentements RGPD.
- Vendors ajoutés : `sweetalert.min.js/css`, `vis-timeline.min.js/css`.

## Configuration / Infra

- **`webpack.config.js`** + **`package.json`** : nouveaux entrypoints boffice-*, ssa-stepper.
- **`config/packages/dev/swiftmailer.yaml`** : configuration SwiftMailer dev.
- **`rector.php`** : configuration Rector pour analyse statique.

## Tests

- **`SolidoumeParameterCotisationTypeTest`** : 9 nouveaux tests (effectiveMinimum, getCotisationDiff, calculatedMin avec tolérance, suppression recommendedAmount, summary updated).
- **`ConsentCheckListenerTest`** : correction constructeur (Security, ConsentService, HouseholdSizeDisplayService).
- **`ConsentAdminTest`** : correction constructeur + mock `getRequiredConsentTypes()`.

---

# v2.7.0 (2026-02-26)

## RGPD — Consentements utilisateurs

### Nouvelles entités & migrations
- **`UserConsent`** : entité Doctrine avec champs `type`, `granted`, `grantedAt`, `revokedAt`, `ipAddress`, `version`, `metadata` (JSON). Loggable Gedmo.
- **`Version20251220150000`** : création table `user_consent` + insertion paramètres RGPD (`DPO_EMAIL`, `PRIVACY_POLICY_VERSION`, `PRIVACY_POLICY_URL`)
- **`Version20260210160000`** : insertion paramètre `ALLOW_GEOLOCATION`
- **`GlobalParameter`** : nouvelles constantes `DPO_EMAIL`, `PRIVACY_POLICY_VERSION`, `PRIVACY_POLICY_URL`, `ALLOW_GEOLOCATION`
- **`AuditLog`** : nouvelles constantes `ACTION_CONSENT_GRANTED`, `ACTION_CONSENT_REVOKED`, `ACTION_ADMIN_EXPORT`, `ACTION_IMPERSONATE_END`

### Nouveaux services
- **`ConsentService`** : `grantConsent()`, `revokeConsent()`, `getMissingConsents()`, `hasAllRequiredConsents()`, `grantMultipleConsents()`, `getRequiredConsentTypes()` (dynamique selon `ALLOW_SSA`), `getCurrentPolicyVersion()`
- **`GdprExportService`** : export RGPD des données utilisateur

### Nouveaux event listeners
- **`ConsentCheckListener`** (kernel.request) : positionne `gdpr_consent_required` en session si consentements manquants. Routes exemptées : admin, API, assets, login/logout, gdpr.
- **`SonataExportListener`** : audit des exports Sonata Admin (action `ACTION_ADMIN_EXPORT`)
- **`SwitchUserListener`** : audit impersonation (action `ACTION_IMPERSONATE_END`)

### Nouveaux contrôleurs
- **`GdprPublicController`** : pages publiques `/gdpr/politique`, `/gdpr/consentement`
- **`GdprController`** : actions consentement, révocation, notification DPO par email
- **`GdprRequestAdminController`** : actions Sonata start_processing / complete / reject + notes AJAX
- **`ConsentAdminController`** : action `statsAction()` — tableau de bord Chart.js

### Nouveaux admins Sonata
- **`GdprRequestAdmin`** : gestion des demandes RGPD (accès, rectification, effacement, portabilité)
- **`ConsentAdmin`** : vue liste + show + stats — **lecture seule**, accès `ROLE_SUPER_ADMIN` uniquement

### Nouveaux templates
- `gdpr/_consent_modal.html.twig`, `gdpr/consent.html.twig`, `gdpr/privacy_policy.html.twig`
- `email/gdpr/dpo_notification.html.twig`
- `admin/consent/list.html.twig`, `admin/consent/stats.html.twig`
- `admin/gdpr_request/list_due_date.html.twig`
- `bundles/SonataAdminBundle/CRUD/gdpr_request/list_action_*.html.twig`

### Nouveaux repositories
- **`UserConsentRepository`** : `countActiveByType()`, `countUsersWithAnyActiveConsent()`, `countUsersWithActiveConsentByType()`, `findAllActiveWithUser()`, `getUserConsentStats()`

### Tests unitaires ajoutés (48 tests, 121 assertions)
- `tests/Unit/Entity/UserConsentTest.php` — 15 tests, 53 assertions
- `tests/Unit/Repository/UserConsentRepositoryTest.php` — 4 tests
- `tests/Unit/EventListener/ConsentCheckListenerTest.php` — 8 tests
- `tests/Unit/Admin/ConsentAdminTest.php` — 7 tests
- `tests/Service/Gdpr/ConsentServiceTest.php` — 14 tests

### Config
- `config/packages/security.yaml` : routes publiques GDPR ajoutées (`access_control`)
- `config/services.yaml` : déclaration `ConsentCheckListener`, `SonataExportListener`, `SwitchUserListener`
- `config/packages/sonata_admin.yaml` : déclaration `GdprRequestAdmin`, `ConsentAdmin`

---

## SSA — Types de cotisation configurables

### Entités & migrations
- **`SolidoumeParameter`** : nouveaux champs `cotisationType` (enum: `free`, `suggested`, `fixed_min`, `restricted`), `cotisationSuggestedAmount`, `cotisationTolerancePercent`, `cotisationRestrictedChoices` (JSON), constantes `COTISATION_TYPE_*`, méthodes `getCalculatedMinimumCotisation()`, `getCotisationTypeSummary()`, `getCotisationRestrictedChoicesArray()`
- **`SolidoumeItem`** : nouveaux champs `minimumCotisation` (snapshot du montant recommandé/minimum au moment de la participation), méthode `getCotisationDiff()` (différence `amount - minimumCotisation`)
- **`Version20260211200000`** : colonnes cotisation sur `solidoume_parameter` et `solidoume_item`
- **`Version20260212000000`** : correction/normalisation des types de colonnes
- **`Version20260226100000`** : ajout colonne `recommended_amount` sur `solidoume_item` (supprimée — remplacée par `minimum_cotisation`)

### Logique métier
- **`CalculatedSsa`** : exclusion des participants sans `minimumCotisation` en mode `COTISATION_TYPE_SUGGESTED`
- **`SsaController`** : guard avant affichage formulaire (adhérent existant sans cotisation recommandée → flash warning + rendu sans form), snapshot `setMinimumCotisation()` à la soumission, `$form !== null ? $form->createView() : null`

### Admin
- **`SolidoumeParamAdmin`** : champs cotisation type + formulaire inline
- **`SolidoumeAdmin`** : bouton stats par caisse
- Template `admin/solidoume_param_edit.html.twig`

### JS admin
- **`assets/js/admin-solidoume.js`** : module de toggle des champs cotisation selon type sélectionné — 4 stratégies d'écoute (native DOM, jQuery, MutationObserver Select2 container, MutationObserver `<select>`)

### Tests
- `tests/Unit/Entity/SolidoumeParameterCotisationTypeTest.php` — 20 tests, 46 assertions

---

## SSA — Formulaire cotisation suggérée : corrections & UX

### `SolidoumeFormType.php`
- **Correction slider** (`buildForm`) : calcul du slider robuste — `maximum` optionnel (fallback `min + 200`), step automatique "nice" (~10 ticks), division par zéro impossible, max toujours inclus comme dernier tick
- **Valeur par défaut du champ montant** : forcée à `suggestedAmount` (mode `suggested`) ou `effectiveMinimum` (autres modes) si l'item est nouveau, que le montant est 0, ou que le montant existant est **inférieur au minimum calculé** (corrige le cas montant=10€ < minimum=80€)
- **`$suggestedAmount`** : utilise désormais `$effectiveMinimum` (spécifique à l'item) au lieu de `$solidoumeParam->getMinimum()` (minimum global de la caisse)
- **`$calculatedMin`** : calculé comme `round($effectiveMinimum * (1 - $tolerancePercent / 100), 2)` au lieu de `$solidoumeParam->getCalculatedMinimumCotisation()`

### `ssa.html.twig`
- **Banner vert "Cotisation recommandée"** : `suggestedAmt` et `minAllowed` utilisent désormais `effectiveMinimum` (passé par le contrôleur) au lieu de `ssa.minimum` (minimum global)
- **Stepper montant** : même correction pour `suggestedVal` et `minAllowedVal`
- **Redesign stepper** : remplace le layout vertical cassé (`MoneyType` générant son propre `input-group`) par un `<input type="number">` brut dans un `input-group` Bootstrap pur — `[ − ] [ 100 ] € [ + ]` sur une seule ligne
  - Boutons `btn-success` avec `opacity: .85` → pas de décalage de taille au survol
  - Hover : `opacity: 1` + `box-shadow inset` sombre → effet visuel sans layout shift
  - Flèches natives `input[type=number]` masquées (webkit + moz)
  - Contour vert `border: 2px solid #198754` sur le container, `border-radius` cohérent
- **Init JS** : valeur forcée à `suggested` si `initVal < minAllowed` au chargement (pas seulement si 0)

### `SolidoumeAdmin.php`
- Filtre "Adhérent" corrigé : jointure `adherent → user` avec champs `firstname`/`lastname`/`email` (l'entité `Adherent` n'a pas ces champs directement — ils sont sur `User`)

---

## SSA — Nettoyage champ `recommendedAmount` & doublons SolidoumeItem

### Suppression de `SolidoumeItem::recommendedAmount`
- **`SolidoumeItem`** : suppression du champ `$recommendedAmount` et de ses accesseurs `getRecommendedAmount()` / `setRecommendedAmount()`. Le champ `minimumCotisation` remplit désormais le double rôle (snapshot du minimum ET du montant recommandé selon le type de cotisation).
- **`getCotisationDiff()`** : mise à jour pour calculer `amount - minimumCotisation` (était `amount - recommendedAmount`)
- **`SsaController`** : `setRecommendedAmount()` remplacé par `setMinimumCotisation()` lors du snapshot à la soumission
- **`SolidoumeItemRepository::getMonthlyCotisationDiffStats()`** : `SUM(i.recommendedAmount)` / `i.recommendedAmount IS NOT NULL` remplacés par `minimumCotisation`
- **`list_solidoume_cotisation_diff.html.twig`** : `object.recommendedAmount` remplacé par `object.minimumCotisation`
- **`Version20260226110000`** : suppression colonne `recommended_amount` sur `solidoume_item`

### Archivage des doublons SolidoumeItem
- **`Version20260226120000`** : archivage intelligent des doublons — 197 items archivés dans 25 groupes `(adherent, caisse SSA)`. Logique de sélection du keeper : `enabled DESC` → `last_month_payed DESC` → `updated_at DESC`. Les doublons sont archivés (`archived = NOW()`, `enabled = 0`).

### Admin SolidoumeItem
- **`SolidoumeAdmin`** : ajout d'un filtre texte "Adhérent (nom, prénom, email)" dans `configureDatagridFilters()` — recherche insensible à la casse via `LOWER()` + `leftJoin` sur `adherent`, affiché par défaut (`show_filter: true`)
- **`SolidoumeAdmin`** : correction du type de colonne `cotisationDiff` (`null` → `'string'`) pour que Sonata utilise correctement le template `list_solidoume_cotisation_diff.html.twig` au lieu d'afficher un badge booléen

---

## SSA — Dashboard statistiques & diff cotisation recommandée

### Nouveaux endpoints
- **`SolidoumeParameterController`** : action stats JSON (paiements journaliers, top prestataires, tailles foyers)

### Nouvelles méthodes de repository
- **`FluxRepository`** : `getSsaDailyPaymentStats()`, `getSsaTopPrestataires()`, `getSsaHouseholdSizes()`
- **`OperationSiegeRepository`** : stats opérations siège SSA
- **`SolidoumeItemRepository`** : `getMonthlyCotisationDiffStats()` — stats mensuelles diff payé/recommandé (12 mois glissants)

### Templates
- `admin/solidoume_stats.html.twig` — dashboard avec Chart.js
- `bundles/SonataAdminBundle/CRUD/list_solidoumeparam_action_stats.html.twig`
- `bundles/SonataAdminBundle/CRUD/list_solidoume_cotisation_diff.html.twig`

---

## SSA — Import CSV cotisations

- **`ImportEnum`** : constante `IMPORT_SSA_COTISATION`
- **`ImportController`** : handler CSV email → `minimumCotisation`. Format : `email;cotisation_amount;caisse_ssa` (caisse optionnelle). Mode test disponible.
- Template `admin/import.html.twig` : refactoring UI (radio buttons grille), support du nouveau type

---

## Cartes — MarkerCluster Leaflet

- Assets statiques `public/leaflet/` : `leaflet.markercluster.js`, `MarkerCluster.css`, `MarkerCluster.Default.css`
- Template `block/_map_cluster_geoloc.html.twig` : bouton géolocalisation conditionnel (`ALLOW_GEOLOCATION`)
- Modification de tous les `cartejs.html.twig` (comptoir, groupepresta, presta, ssa) pour init clustering
- `assets/js/app.js` : déclaration `global.bootstrap`, import clustering

---

## Boffice — Cache serveur PSR-6

- **`BofficeApiClient`** : cache `cache.app` pour les GET. TTLs : `/admin/users` 300s, `/admin/installations` 600s, `/kanban/columns` 30s, `/tickets/{id}` 60s. Invalidation automatique sur écriture.
- **Contrat HTTP Boffice** : tous les appels logiques `PATCH` et `PUT` sont désormais transportés en `POST` avec l'en-tête `X-HTTP-Method-Override` côté `assets/js/boffice-api.js`, `BofficeController::apiProxyAction()` et `BofficeApiClient`, suite à une évolution de l'API distante.
- Test : `tests/Unit/Service/BofficeApiClientCacheTest.php`

---

## Commande — Audit soldes comptes

- **`AccountBalanceAuditCommand`** (`kohinos:account:balance-audit`) : vérification de la cohérence des soldes via recalcul des flux
- Nouvelles méthodes dans `AdherentRepository`, `ComptoirRepository`, `PrestataireRepository`, `UserRepository`, `OperationAdherent/Comptoir/Prestataire/SiegeRepository`

---

## Divers

- `ExporterCustomDoctrineORMQuerySourceIterator` : améliorations export
- `MenuBuilderListener` : ajout badges consentements
- `GlobalConfigurationFormType` : nouveaux champs RGPD
- `SsaFormerParticipantReminderCommand` : corrections mineures
- `AdherentAdmin`, `AuditLogAdmin`, `GlobalParameterAdmin`, `SepaMandateAdmin` : modifications exports/affichage
- Templates common : `login.html.twig`, `menu.html.twig`, `myaccount.html.twig`, `footer.html.twig`, `layout.html.twig` — intégration modale RGPD et liens politique

---

# v2.6.9 (2026-01-20)

## Corrections
- Fix erreur quand rôle comptoir sans comptoir attaché (NullPointerException)
- Fix erreur quand rôle groupe local sans groupe local attaché
- Fix erreur dans `FluxAdmin` sur certains contextes
- Fix phrase "Renouvellement SSA automatique => cotisation déduite de l'allocation"
- Fix SSA : suppression bouton "Achat de eMLC" après validation cotisation si MLC désactivé

## RGPD (base)
- Audit logging complet (toutes les actions sensibles)
- Exports sécurisés avec traçabilité
- Purge/anonymisation automatique planifiable
- Module droits RGPD utilisateur (accès, rectification, effacement, portabilité)
- Documentation opérateur RGPD

## SEO
- `robots.txt` dynamique selon `ALLOW_INDEXATION`
- `sitemap.xml` dynamique (prestataires, news, pages, rubriques)
- Données structurées Schema.org (`Organization`, `LocalBusiness`, `BreadcrumbList`)
- Balise H1 sur page d'accueil
- Extension Twig SEO : `seo_truncate`, `seo_clean`, `seo_keywords`
- Meta tags complets : Open Graph, Twitter Cards, canonical URL
- Interface admin branding : upload logo, favicon, image OG
- Génération automatique favicons multi-tailles
- Migration paramètres SEO (`SEO_DESCRIPTION`, `SEO_KEYWORDS`, `SEO_OG_IMAGE`, etc.)

---

# v2.6.8 (2026-01-14)

## Corrections
- Fix erreurs rôle comptoir/groupe local sans entité attachée
- Fix URL de retour paiement PayZen

## Templates email
- Intégration CKEditor sur le champ content des templates d'email
- Ajout des variables liées à l'utilisateur
- Suppression des `|trans` dans Twig

---

# v2.6.7 (2026-01-09)

## Corrections SSA
- **Transferts prestataires** : correction du transfert eMLc prestataire → adhérents (remboursements)
- **Cotisation prestataire** : fix cotisation requise pour reconversions quand module cotisation désactivé
- **Permissions manager** : les managers SSA peuvent transférer des eMLc à tous les adhérents même sans compte SSA

## Améliorations admin
- Affichage IBAN prestataire dans liste reconversions (trésorier/super admin uniquement, sans espaces)
- Badge compteur reconversions en attente sur menu et dashboard (trésorier/super admin)
- Correction ordre d'affichage des mois dans les graphiques (chronologique jan→déc)

## Technique
- Nouveau `ReconversionRepository` avec `countPendingReconversions()`
- Fonction Twig `countPendingReconversions()` dans `AppExtension`
- `ChartUtils::sortMonthsChronologically()` : tri correct des mois

---

# v2.6.6 (2026-01-07)

## SSA
- **Redistribution sur mois glissant** : prise en compte des 30 derniers jours (ex: 8 déc → 8 jan) au lieu du mois calendaire
- **Rapport** : section "Adhérents non prélevés" masquée si `canBeRecurrent=false`
- **Logs** : correction des logs confus pour items exclus non activés
- **Option `--simulateYear`** : nouvelle option pour `kohinos:ssa:execute`

## Admin
- Fix affichage achat de monnaie dans admin prestataire
- Fix cotisation gratuite masquée si module cotisation désactivé
- Bloc SSA cotisation au comptoir : ajout case "Renouvellement avec SSA auto"

---

# v2.6.4 (2025-12-16)

## Système de gestion des emails

### Nouvelles entités
- `EmailLog` : tracking complet de tous les emails envoyés

### Nouveaux services
- `EmailLogService` : logging automatique via listener SwiftMailer
- `EmailStatisticsService` : statistiques avec cache
- `EmailTemplateService` : gestion templates personnalisés (`templates/themes/custom/email/`)
- `EmailVariableRegistryService` : documentation des variables disponibles

### Interface admin
- Historique emails : filtres par type, destinataire, date, statut ; vue détaillée ; export CSV/XLS
- Graphiques : emails/jour, emails/type, taux de succès
- Éditeur templates : aperçu, réinitialisation vers défaut, documentation variables

### Commande
- `app:email:archive-old-logs` : soft-delete des logs >6 mois, mode dry-run, traitement par batch

### Tests
- 39 nouveaux tests, 159 assertions

---

# v2.6.3 (2025-12-16)

## Validations anti-doublons
- Cotisations MLC : un adhérent/prestataire ne peut avoir qu'une cotisation active
- Achats récurrents : un seul achat récurrent actif par adhérent/prestataire
- Cotisations SSA : une seule cotisation active par adhérent par caisse SSA
- Validation côté serveur (PHP), messages d'erreur explicites

## UX SSA
- Badge "Cotisation payée - Paiement unique effectué" pour les paiements non-récurrents
- Affichage distinctif badge bleu (paiement unique) vs badge vert (SEPA récurrent)
- Alerte verte avec date/heure précises si paiement ce mois ; alerte orange sinon

## Corrections SEPA
- Fix affichage boutons Annuler/Révoquer dans admin cotisations SEPA
- Fix génération RUM unique pour les mandats
- Fix affichage sélection Paiement Récurrent/Unique (caractère X au lieu de coche)
- Fix cotisations MLC : affichage "ancien montant → nouveau montant" (objet temporaire)
- Fix chargement fixtures en test (bypass erreur normale)
- Fix redirection après actions erronées SSA
- Comptoir : suppression "Autre" comme moyen de paiement

---

# v2.6.2 (2025-12-10)

## Corrections
- **Code de paiement adhérent** : récupération depuis `$form->get('payment_code')->getData()`, affichage des erreurs de validation Symfony via flash messages
- **SSA paiement récurrent** : case à cocher "Renouvellement automatique" si `autopaywithSSA` activé dans la caisse

---

# v2.6.1 (2025-12-09)

## SEPA — Jours de prélèvement configurables

### Nouveaux paramètres
- `GlobalParameter::SEPA_RECURRING_PURCHASE_DEBIT_DAY` : jour fixe global pour tous les achats récurrents MLC (1-28). Si défini, l'utilisateur ne peut pas modifier.
- `SolidoumeParameter.sepaMandatoryDebitDay` : jour obligatoire par caisse SSA

### Comportement
- Formulaires adaptatifs : champs jour masqués si jour fixe configuré
- Priorité : Configuration système > Choix utilisateur > Valeur par défaut
  - MLC default : 9 / SSA default : 5

### Fichiers modifiés
- Entités : `GlobalParameter`, `SolidoumeParameter`
- Services : `RecurringPurchaseService`, `RecurringSubscriptionService`
- Formulaires : `RecurringPurchaseFormType`, `SsaSepaCotisationFormType`, `ModifyRecurringPurchaseFormType`
- Contrôleurs : `SepaController`, `SsaSepaController`

### Migration
- `Version20251209163257` : `sepa_mandatory_debit_day` sur `solidoume_parameter` + paramètre global

---

# v2.6.0 (2025-12-08)

## Configuration globale
- Correction édition des paramètres de carte (Centre, Zoom) — champs éditables au clic
- Nouveaux paramètres : "Cotisation obligatoire adhérents", "Cotisation obligatoire prestataires" — blocage transactions si non valide
- Configurable dans le wizard d'installation (étape 4)

## Notifications email SEPA
- Notification validation achat récurrent (trésorier → adhérent)
- Notification approbation modification achat récurrent
- Nouveau template email dédié

## Corrections
- Fix affichage double sur page admin adhérent
- Fix menu et e-compte
- Fix wizard installation étape 9bis (champs SEPA complets)
- Amélioration gestion erreurs serveurs OpenStreetMap indisponibles

---

# v2.5.9 (2025-12-02)

## SSA — Alertes anciens cotisants

### Nouvelle commande
- `kohinos:ssa:send-former-participant-alerts` : envoi emails de rappel aux anciens cotisants désactivés ayant reçu une redistribution le mois dernier. Options `--test`, `--force-date`.

### Nouveaux champs `SolidoumeParameter`
- `formerParticipantReminderEmail` (LONGTEXT) : template HTML email
- `formerParticipantReminderDaysBefore` (INT) : délai avant redistribution

### Repository
- `SolidoumeItemRepository::findFormerParticipantsForLastMonth()` : items avec `enabled=false`, `archived IS NULL`, `lastMonthPayed >= mois dernier`
- `SolidoumeItemRepository::hasAdherentReceivedRedistribution()` : vérifie via table Flux

### Migration
- `Version20251201213412` : colonnes + jobs cron #2 (SEPA monthly) et #3 (SSA former alerts)

---

# v2.5.8 (2025-11-25)

## SEPA — Support prestataires & révocations

### Nouveautés
- Achats récurrents pour prestataires (nouveau repository, service, extension Twig, templates)
- Nouveau statut `revoked` : différenciation `cancelled` (utilisateur) vs `revoked` (admin), révocation en cascade lors révocation mandat
- Simplification workflow modification : suppression champ `reason`

### Nouveaux contrôleurs admin
- `RecurringPurchaseRequestAdminController` : `approveModification`, `rejectModification`, `revoke`
- `RecurringMlcSubscriptionRequestAdminController` : idem + notifications
- `RecurringSsaSubscriptionRequestAdminController` : harmonisé avec MLC
- `SepaMandateAdminController` : révocation en cascade

### SepaNotificationService
- Nouvelles méthodes : notification révocation, approbation modification, rejet modification

### Tests
- +50 tests unitaires et fonctionnels
- 66 fichiers modifiés, +1586/-1627 lignes

---

# v2.5.7 (2025-11-24)

## Corrections & améliorations paiements

- **`SEPA_CONTACT_EMAIL`** : nouveau paramètre global + migration + intégration wizard + documents SEPA
- **HelloAsso** : fix chargement comptes adhérent sans compte SSA, bouton `safeDelete` sécurisé en admin, affichage JSON formatté
- **SEPA formulaires** : pré-remplissage IBAN/BIC/nom depuis dernier mandat, fix jour prélèvement dans modification achats récurrents, badge titulaire dans listes admin
- **SSA** : protection accès renforcée (cotisation valide requise), bloc SEPA MLC prestataire avec synthèse achat mensuel

---

# v2.5.6 (2025-11-17)

## SEPA & abonnements récurrents — intégration côté adhérent

- **`SepaProcessingCommand`** : finalisation avec tests unitaires/fonctionnels complets. Gestion cotisations MLC, achats récurrents, cotisations SSA SEPA.
- **`HelloAssoFormFilter`** : normalisation montants/statuts, appariement amélioré
- **`HelloAssoCallbackController`** : durcissement gestion cas limites, logs, corrélation `PendingPaymentData`
- **`AbstractRecurringRequest`** : suppression champ `year`, méthodes `getActiveMlcRequestForAdherent`, `getActiveSsaRequestForAdherent`, `getActiveMlcRequestForPrestataire`
- **`SepaMandateRepository`** : méthodes mandats actifs adhérent/prestataire, comptage mandats en attente
- **`SepaExtension`** : fonctions Twig `getRecurringMlcSubscriptionForAdherent()`, `getRecurringPurchaseForAdherent()`
- Templates adhérent : `sepa_mandates.html.twig`, `sepa_ssa.html.twig`, mise à jour `admin_adherent.html.twig`

### SSA
- Normalisation côté PHP dans `SsaController::processSsaFormSubmission()` : synchronisation `montantradio`/`montantslider` → `SolidoumeItem::amount`

---

# v2.5.5 (2025-11-01)

- Création automatique des comptes SSA pour tous les participants actifs avant redistribution
- `CreateAccountsCommand` : gestion comptes SSA avec tracking
- `ensureAllParticipantAccountsExist()` : vérification et création comptes manquants
- Gestion bidirectionnelle des relations lors de la création de comptes SSA
- Suppression détails redistribution non pertinents des rapports SSA

---

# v2.5.4 (2025-10-23)

- Fix `OperationUtils::executeOperations()` pour certains cas limites
- Valeurs par défaut paramètres HelloAsso dans `.env`

---

# v2.5.3 (2025-10-23)

## SEPA MLC — Cotisations et achats récurrents

- Bouton SEPA dans `/cotiser` si `USE_SEPA == 'true'`
- Template `adherent/block/sepa_mlc.html.twig` : mandats actifs, cotisation MLC récurrente, achat mensuel
- Service `SepaMandateGenerator` : génération PDF/HTML document officiel SEPA (dompdf)
- Routes `/adherent/sepa/mandat/{id}/download-pdf` et `/print`
- `CotiserFormType` : champ `paySepa` conditionnel
- Tests : `CotiserFormTypeTest`, `SepaExtensionMlcTest`, `UserControllerCotiserSepaTest`, `SepaMlcWorkflowTest`

## SEPA — Prélèvement automatique complet

- Système SEPA complet : mandats, cotisations annuelles, achats récurrents
- `USE_SEPA` global parameter (default: false)
- Admins : `SepaMandateAdmin`, `RecurringSubscriptionRequestAdmin`, `RecurringPurchaseRequestAdmin`
- Services : `SepaMandateService`, `RecurringSubscriptionService`, `RecurringPurchaseService`, `SepaNotificationService`
- Contrôleurs : `SepaController`, `SepaAdminController`
- Migrations : `Version20251002170718`, `Version20251003120000`, `Version20251003130000`
- 65 tests, 229 assertions

## Abonnements — Année civile

- `COTISATION_USE_CIVIL_YEAR` : choix entre année civile (1er jan) et année glissante (date + 1 an)
- `CivilYearService` refactorisé pour les deux modes

---

# v2.5.2 (2025-09-30)

## HelloAsso & SSA

- Service de paiement unifié eMLc + HelloAsso (paiements mixtes)
- SSA : 3 modes de distribution — `CALCULATED`, `FIXED`, `TIERS`
- Transactions adhérent-à-adhérent SSA
- +1500 lignes de tests paiements/transactions

---

# v2.4.19 (2025-09-25)

## Refactoring architecture SSA

- **SSA Orchestrator** : `SsaOrchestrator` — refactoring complet de la commande SSA
- **Strategy Pattern** : `FixedSsa`, `CalculatedSsa`, `TiersSsa`
- **`SsaReportService`** : rapports JSON exportables
- **`SsaCalculationService`** : calculs financiers centralisés
- Commandes : `app:test-critical-email`, `app:transfer:emlc-to-ssa`
- SwiftMailer production : spool désactivé pour livraison immédiate
- Tests : +8 fichiers de tests SSA, 350+ tests, 1200+ assertions
- `HouseholdSizeDisplayService` : gestion affichage taille foyer selon mode distribution
- Migration complète vers npm uniquement (suppression yarn)

---

# v2.4.18 (2025-09-20)

- Fix assets : npm uniquement, suppression yarn
- SSA Admin : type `tiers` + règles + email post-redistribution
- SSA Item Admin : champ `householdSize`
- SSA Admin : onglet emails + badges config tiers
- SSA Adhérent : saisie `householdSize` + modale si non renseigné

---

# v2.4.17 (2025-09-15)

- HelloAsso : intégration paiement SSA via callback webhook
- `PendingPaymentData` : système de gestion des données de paiement en attente
- `PaymentDataManager` : paiements mixtes eMLc + HelloAsso
- `HelloAssoCallbackController` : traitement atomique via `$em->transactional()`
- `SepaController` : jours de prélèvement configurables par caisse SSA
- Amélioration robustesse redistribution SSA en mode `TIERS`

---

# v2.4.16 (2025-06)

- Ajout rôle `ROLE_SECRETAIRE` et `ROLE_GESTIONNAIRE_SOLIDOUME`
- Charts admin : Chart.js, Stimulus, stats presta/adhérent/comptoir
- Autocomplétion front utilisateur (AJAX)
- Horaires comptoir dans la carte
- Infos groupepresta dans carte et liste

---

# v2.4.8 — v2.4.5

- Bootstrap 5 migration complète
- Résolution problèmes de rendu slider/margins
- Optimisation chargement Doctrine (lazy loading)
- Solidoume : modale confirmation, améliorations diverses
