Journal des modifications
Les évolutions de Cohez.io, de la plus récente à la plus ancienne.
93 versions correspondent à « inscription ».
Versions 21 à 30 sur 93
0.21.0.0 — 2026-07-26
Ajouts
- Alerte dashboard « campagnes bientôt clôturées » : le tableau de bord affiche désormais les campagnes d'inscription ouvertes dont la clôture programmée arrive dans les 7 jours, pour éviter de découvrir une fermeture automatique après coup.
- Page de clôture dédiée pour les inscriptions publiques (
/register/{token}/closed) : quand une campagne se ferme ou qu'un lien d'inscription expire, l'adhérent est redirigé vers une page qui explique clairement pourquoi, au lieu d'un message d'erreur générique.
Modifications
- Message d'erreur convivial à la clôture d'une campagne : le motif de fermeture (campagne close vs lien expiré) est désormais dérivé côté serveur (
CampaignPublicToken::closedReason()) plutôt que déclaré par le client, ce qui empêche un motif erroné ou falsifié d'être affiché. En cas de cumul des deux causes, le message priorise le lien expiré. - L'endpoint d'estimation tarifaire (
/register/{token}/estimate) répond désormais systématiquement en JSON 410 quand la campagne se ferme en cours de saisie, au lieu de rediriger vers une page HTML.
0.20.0.2 — 2026-07-20
Ajouts
- Preuve d'acceptation des conditions à l'inscription sur invitation (
users.terms_accepted_at) : la case « J'accepte les conditions d'utilisation » était validée côté serveur puis jetée — aucune trace de l'acceptation n'était conservée, la rendant inopposable (art. 7 RGPD). L'horodatage est désormais persisté sur les 3 parcours de création de compte (employé, membre lié, nouveau membre) ; laissénullpour les employés, pour qui la case n'est pas affichée. Aucun backfill des comptes existants : leur consentement n'a jamais été recueilli de façon prouvable, un backfill fabriquerait une fausse preuve.
Corrections
- Logs d'audit d'impersonation en UUID plutôt qu'en emails :
admin_email/user_emailne sont plus écrits dans les logs start/stop (ImpersonationService), qui partent en prod vers syslog Clever Cloud et Laravel Nightwatch.admin_id/user_id/tenant_idsuffisent à la traçabilité ; IP et user-agent restent journalisés. - Suppression de Gravatar (fuite d'email vers un tiers non déclaré) : l'email de l'utilisateur partait en clair dans l'URL Gravatar vers Automattic (États-Unis) à chaque affichage du header, sans figurer au registre des sous-traitants. L'avatar ne se résolvait de toute façon jamais (URL construite sans hash MD5/SHA-256) — le fallback initiales, déjà le rendu effectif, devient le rendu unique.
gravatar.comretiré de la CSPimg-src. - Documentation de
SESSION_SECURE_COOKIE: la variable était absente de.env.example, exposant au risque qu'un déploiement prod tourne sans le flagSecuresur le cookie de session (atténué par HSTS, mais à fermer proprement). Ajoutée à.env.exampleet à Docs/Deployment/Environment.md. Action de suivi en production :clever env set SESSION_SECURE_COOKIE true.
0.19.1.0 — 2026-07-06
Modifications
- Découpage du contrôleur des inscriptions par ressource HTTP : le
RegistrationControllermonolithique (2 170 lignes, 27 actions) est scindé en 6 contrôleurs dédiés — CRUD inscriptions, transitions de statut (valider/refuser/annuler), gestion de cours (ajout/retrait/échange + aperçus), panier (détail, paiement, facture PDF, membres, notes), relances et export CSV. Aucun changement de comportement : URLs, noms de routes et autorisations strictement identiques (déplacements verbatim vérifiés mécaniquement). - Les aides partagées (filtres de liste, représentant de panier, options de campagne, noms de cours) deviennent des traits réutilisables
Concerns/, et la liste des raisons comptant comme « relance manuelle » est centralisée sur l'énumRelanceReason::MANUAL_REASONS(valeurs inchangées).
Ajouts
- Test unitaire de l'énum
RelanceReason: libellés et contenu exact deMANUAL_REASONSdésormais verrouillés.
0.19.0.1 — 2026-07-03
Corrections
- Mail « Paiement reçu » reflète le solde du panier, pas la ventilation par inscription :
PaymentReceivedMailinterrogeait le solde de la seule inscription réglée, qui peut diverger du solde réel du panier après une re-ventilation des paiements. Le mail agrège désormais toutes les inscriptions non terminales du panier et affiche le soldeVersementService::cartOutstandingCents— élimine les mails annonçant un reste à payer alors que le panier est soldé.
0.19.0.0 — 2026-07-03
Ajouts
- Lien de paiement en ligne pour panier offline impayé : depuis le détail panier, un bouton du panneau de relance génère un lien de paiement HelloAsso pour un panier initialement réglé hors ligne (chèque, virement, espèces) qui reste impayé — sans obliger l'adhérent à repasser par tout le formulaire. Guardé par token public valide, solde panier > 0 relu sous verrou, et HelloAsso configuré pour le tenant (le bouton est masqué sinon).
- Verrou de checkout HelloAsso anti double-clic/double-onglet :
Cache::lockpar panier (30s, attente 5s) sérialise les checkouts concurrents sur le même jeu d'inscriptions ; le solde est relu sous verrou juste avant chaque appel HelloAsso, avec dégradation gracieuse (HelloAssoAlreadySettledException) si un règlement concurrent a déjà tout soldé. Les appels HTTP HelloAsso sont bornés à 10s pour ne jamais dépasser la durée du verrou.
Corrections
- Le montant réclamé par la relance et le checkout HelloAsso se base désormais sur le solde du panier (
VersementService::cartOutstandingCents), plus sur la ventilation par inscription — élimine les relances erronées après une re-ventilation des paiements à l'annulation. - Élimination de plusieurs N+1 (
registration.payments,registration.balance) dansHelloAssoRelanceServiceet le flux de retry HelloAsso. - Exclusion des paiements HelloAsso fantômes de la relance panier :
payableCartPayments()(etcanSendManualRelance(),createOnlinePaymentLink()) retournent vide dès que le panier n'a plus de solde dû au niveau versement (VersementService::cartOutstandingCents), même si unPaymentPending/Rejectedrésiduel survit sur une inscription réglée hors ligne entre-temps ; garde sur destinataire absent ; resynchronisation dePayment.amountau moment du checkout.
0.18.3.0 — 2026-07-01
Ajouts
- Refactoring
MemberController::show()→ CQRS read model (GetMemberDetailQuery) : la méthodeshow()passe de 263 lignes à ~12 lignes en déléguant à une query orchestratrice qui coordonne 7 assembleurs, chacun produisant un DTOfinal readonlytypé. Les DTOs couvrent :PersonSectionData,MemberSectionData,UsagerSectionData,LegalGuardianData,PayeurSectionData,RegistrationSummaryData,MeetingActionData. - Limitation des inscriptions à 50 +
hasMoreRegistrations: la query charge 51 inscriptions (LIMIT+1), n'en retourne que 50, et expose le flaghasMoreRegistrationspour signaler qu'il y en a davantage — sans pagination complète. - Migration index composite
(tenant_id, responsible_user_id, due_date)surmeeting_actions: élimine le filesort surORDER BY due_date LIMIT 50dansMeetingActionsAssembler.
Corrections
tenant_idajouté aux selects contraints surlegalGuardians.person.adherentetpayeurs.person.adherent(évite un null silencieux si$adherent->tenant_idest accédé ultérieurement).toISOString()dansMeetingActionsAssembler(cohérence avec les autres assembleurs qui utilisaient déjà cette forme).MemberDetailResponse::$memberdéclaré non-nullable (toujours valorisé parmemberAssembler->assemble()).
Tests
- Test 403 sur
members.showsans permission (couverture manquante sur l'accès à la fiche membre). - Test LIMIT+1 (
hasMoreRegistrations) avec 51 campagnes distinctes pour contourner la contrainte unique(member_id, campaign_id). - Test
ModelNotFoundExceptionsur unmember_idinexistant. - Test isolation cross-tenant sur
MeetingActionsAssembler: vérifie que le scopeBelongsToTenantfiltre les actions d'un autre tenant même siresponsible_user_idest identique. - Test positif
MeetingActionsAssembleravec fixtureMeetingActionréelle (l'ancien test ne créait aucune action).
0.18.2.0 — 2026-06-30
Ajouts
- Visibilité publique des moyens de paiement (
is_public) : nouveau flag indépendant deis_enabledsurTenantPaymentMethod. Permet de configurer un moyen de paiement disponible uniquement en back-office (ex. CB physique, virement interne) sans l'exposer sur le formulaire public d'inscription. - Toggle « Proposé au public » dans les paramètres moyens de paiement : second interrupteur par méthode (désactivé si la méthode est désactivée). Désactiver une méthode force automatiquement
is_public = falsecôté serveur. - Méthodes custom dans la saisie de versement back-office : les méthodes custom activées (
payment_method IS NULL) sont désormais proposées dans le dialog « Enregistrer un versement », avec validation ducustom_payment_method_id. - Scope
scopePublic()surTenantPaymentMethod: filtreis_enabled = true AND is_public = true, utilisé par le formulaire public d'inscription.
Modifications
- Le formulaire public d'inscription filtre désormais les moyens de paiement via
scopePublic()(au lieu descopeEnabled()) — seules les méthodes activées et marquées publiques sont proposées.
0.18.1.0 — 2026-06-29
Ajouts
- Formulaire pleine page « Ajouter un inscrit » (
AddCartMember.vue) : parcours guidé en 3 étapes (Identité → Activités → Récapitulatif) pour ajouter un membre à un panier depuis le back-office. Supporte la sélection d'un membre existant ou la création inline d'un nouveau membre (avec tuteur légal pour les mineurs). Les membres déjà actifs dans le panier sont automatiquement exclus de la liste. - Test d'isolation cross-tenant sur
CartMutationControllerTest: vérifie que le scopeBelongsToTenantrend un panier invisible hors de son tenant (couche Eloquent).
Modifications
MemberForm: nouveau mode simplifié (simplified) pour les contextes d'inscription back-office — masque les champs non nécessaires (photo, bio, nationalité…) et réduit le formulaire aux données essentielles.
Corrections
- Performance : requête
availableMembersdansbuildPanierPayload— ajout de->with('person')(évite N+1 sur l'accesseurfirst_name/last_name) et->limit(200)(borne la requête pour les tenants avec de nombreux membres).
0.18.0.0 — 2026-06-29
Ajouts
- Gérer le panier —
CartMutationService: trois nouvelles opérations atomiques sur le panier, chacune sous verroulockForUpdateglobal sur toutes les inscriptions du panier :- Ajouter un inscrit : sélection d'un membre existant (liste filtrée — inscrits actifs exclus) ou création inline d'un nouveau membre, avec choix du/des cours. Les tarifs multi-membres sont recalculés pour l'ensemble du panier à chaque ajout. La facture de chaque inscription déjà facturée est re-versionnée.
- Déplacer un cours : transfert atomique d'un cours d'un inscrit vers un autre inscrit du même panier. Les deux inscriptions sont recalculées et leur facture re-versionnée dans la même transaction.
- Supprimer le panier : annulation de toutes les inscriptions non-terminales avec remise à zéro des montants. Bloqué si des versements encaissés existent (atomique — vérification sous verrou).
- Dialogs back-office sur la page « Détail du panier » : trois dialogs dédiés (Ajouter un inscrit, Déplacer un cours, Supprimer le panier) accessibles via un menu « Gérer le panier ». Les actions destructives nécessitent une confirmation explicite.
- Sécurité — isolation tenant renforcée : les propositions sont désormais scopées explicitement au tenant du gestionnaire (via
rubriques.tenant_id) dans les Form Requests et dans le contrôleur, en complément de la vérificationcampaign_idexistante dans le service.
Corrections
- Sécurité : la vérification de versements encaissés dans
deleteCartest désormais effectuée à l'intérieur de la transaction souslockForUpdate, éliminant une fenêtre de race condition entre la vérification et l'annulation des inscriptions.
0.16.33.0 — 2026-06-26
Ajouts
- Versioning immuable du panier (cart snapshot). Chaque validation d'inscription et chaque mutation de cours (ajout, retrait, échange) crée désormais un snapshot versionné du panier, archivant les tarifs, remises et composition à l'instant t. La facture PDF lit systématiquement depuis ce snapshot ; le contenu de la facture est ainsi figé même si les inscriptions évoluent ultérieurement.
- Annulation avec recalcul automatique. L'annulation d'une inscription réindexe les member_index des inscrits restants et recalcule leurs tarifs (remises multi-membres incluses) de manière atomique. Un nouveau snapshot est créé après l'annulation pour refléter l'état final du panier.
Modifications
- La facture PDF d'un panier (
/facture-pdf) affiche désormais les inscriptions annulées de façon distincte (montant à 0, lignes vides) et les exclut du total et des remises affichés. - Le total du panier et les remises dans le back-office excluent les inscriptions terminales (annulées, refusées) — seules les inscriptions actives contribuent aux montants affichés.
- Le statut de paiement "Encaissé" est renommé "Reçu" dans l'interface.
Corrections
- La route
/facture-pdfne plante plus avecfunction max(uuid) does not existsur PostgreSQL (la relationlatestSnapshotutilisaitofMany()qui génère unMAX(id)incompatible avec les UUID).