Doctrine entity listeners n'exigent pas le second parametre EventArgs.
Suppression des 3 params $_event et des imports associes.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- TrackingService : suppression TODO, retour valeurs par defaut
(trackPageView/trackEvent logguent, getVisitorStats/getPageViews
retournent structure vide avec periode)
- Suppression templates/pdf/facture.html.twig et devis.html.twig
(non utilises, PDF genere via FPDF)
- app.test.js : ajout assertion manquante ligne 541
- StatsController : constantes DQL, COLOR_GOLD, CC getServiceStats
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- DQL_BETWEEN_DATES, DQL_IS_PAID, COLOR_GOLD constantes
- Suppression $paymentsByMethod inutilisee
- getServiceStats CC 23->~10 : extraction groupServiceLines,
mergeServiceGroups, resolveServiceStatus
- Ternaire imbrique remplace par resolveServiceStatus()
- Tests mis a jour (retrait getPaymentsByMethod de toutes les sequences)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- computeStripeCommissions et computeServiceCostsFromFactures
deplaces dans ComptaExportService
- groupFactureLinesByTypeFromList public pour accepter des factures
- ComptabiliteController : 19 methodes (etait 21)
- ClientsController : persistNewContact extrait de handleContactForm
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Constantes WELCOME_SUBJECT et WELCOME_TEMPLATE (literals dupliques 3x)
- Suppression dispatchPostAction (10 params) : match inline dans show()
- handleContactForm : elseif au lieu de 2 if independants (CC reduite)
- 21 -> 19 methodes (sous la limite de 20)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Suppression .gitea/workflows/discord-notify.yml (plus de notification Discord a chaque push)
- Suppression controller_resolver.auto_mapping deprecie dans les 2 fichiers doctrine.yaml
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
VaultService — chiffrement/déchiffrement via Vault Transit engine :
Gestion des clés :
- createKey(keyName, type) : crée une clé Transit (défaut aes256-gcm96)
- deleteKey(keyName) : marque deletable + supprime
- updateKey(keyName, config) : met à jour la config (rotation, export...)
- listKeys() : liste toutes les clés Transit
- keyExists(keyName) : vérifie l'existence d'une clé
- checkOrCreateKey(keyName) : crée la clé si elle n'existe pas
Chiffrement :
- encrypt(keyName, plaintext) : chiffre avec Transit, retourne vault:v1:...
Auto-crée la clé si inexistante
- decrypt(keyName, ciphertext) : déchiffre le ciphertext Transit
Communication HTTP avec X-Vault-Token, gestion erreurs 4xx/5xx.
Configuration :
- .env : VAULT_URL, VAULT_TOKEN (vides par défaut)
- .env.local : VAULT_URL=http://vault:8200, VAULT_TOKEN=crm_siteconseil
- ansible/vault.yml : vault_url=https://kms.esy-web.dev pour la prod
- Transit engine activé sur le container Vault dev
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Navbar admin :
- Barre de recherche persistante en haut de toutes les pages admin
- Recherche dans tous les index Meilisearch simultanément :
clients (5), NDD (5), sites (5), contacts (5), revendeurs (3)
- Résultats en dropdown glassmorphism avec icône par type
- Clic sur un résultat → page + tab correspondant :
Client → /admin/clients/{id}
NDD → /admin/clients/{id}?tab=ndd
Site → /admin/clients/{id}?tab=sites
Contact → /admin/clients/{id}?tab=contacts
Revendeur → /admin/revendeurs/{id}/edit
DashboardController::globalSearch :
- Route GET /admin/global-search?q=...
- Agrège les résultats de 5 index Meilisearch
- Retourne [{type, label, sub, url}]
app.js :
- Debounce 250ms, min 2 chars
- Badges type (Client, NDD, Site, Contact, Revendeur)
- Fermeture Escape / clic extérieur
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Pages services :
- /admin/services/ndd : liste tous les NDD avec client, registrar,
Cloudflare, gestion, facturation, expiration + barre recherche
- /admin/services/esyweb : liste tous les sites avec client, UUID,
type, statut + barre recherche
- Liens sidebar mis à jour (Esy-Web → esyweb, Nom de domaine → ndd)
MeilisearchService :
- Index customer_ndd : searchable fqdn/registrar/customerName/customerEmail,
filterable customerId/isGestion/isBilling
- Index customer_website : searchable name/uuid/customerName/customerEmail,
filterable customerId/type/state
- CRUD : indexDomain/removeDomain/searchDomains, indexWebsite/removeWebsite/searchWebsites
- Serializers avec infos client intégrées (customerName, customerEmail, customerId)
SyncController :
- Route POST /admin/sync/domains : sync tous les Domain dans Meilisearch
- Route POST /admin/sync/websites : sync tous les Website dans Meilisearch
- Compteurs totalDomains et totalWebsites dans index
Template admin/sync :
- Bloc "Noms de domaine" (slate) avec bouton sync
- Bloc "Sites Internet" (blue) avec bouton sync
Recherche (app.js) :
- renderHit adapté : affiche fqdn/name pour NDD/sites, customerName en sous-texte
- Lien vers la fiche client (customerId) pour les résultats NDD/Website
- setupSearch configuré pour search-ndd et search-websites
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Remplace les 2 sections (changer mot de passe + générer temporaire) par
un seul bouton "Envoyer un lien de reinitialisation" qui :
1. Génère un mot de passe temporaire (bin2hex 16 chars)
2. Hash et stocke dans User (password + tempPassword)
3. Envoie l'email bienvenue avec le lien set_password au client
Plus de champ mot de passe à saisir manuellement — tout est automatique.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Onglet Securite (tab=securite) :
Statut du compte :
- Email, statut mot de passe (Temporaire jaune / Defini vert)
- 2FA Email (Active/Desactive), 2FA Google (Active/Desactive)
Changer le mot de passe :
- Formulaire avec nouveau mot de passe (min 8 chars)
- Hash via UserPasswordHasherInterface, clearTempPassword
Generer mot de passe temporaire :
- Genere 16 chars aleatoires, hash + setTempPassword
- Affiche le mot de passe en flash (pour renvoi email bienvenue)
- Modal confirmation avant action
Desactiver 2FA :
- Desactive 2FA Email + Google Authenticator + supprime secret + backup codes
- Bouton rouge avec modal confirmation
- Section visible uniquement si au moins 1 methode 2FA active
handleSecurityForm() :
- Actions : reset_password, disable_2fa, generate_temp_password
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Onglet Sites Internet (tab=sites) :
- Table : nom, UUID, type (Vitrine bleu / E-Commerce violet),
statut (Cree / Installation / En ligne / Suspendu / Ferme), date
- Badges colorés par statut
- Message "Aucun site internet" si vide
buildCustomersInfo :
- sites compte maintenant les Website liés au customer (plus hardcodé à 0)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
EsyMailService - 2 nouvelles méthodes de vérification DNS :
checkDnsEsyMail(domain) — config réception (Dovecot) :
- MX → doit pointer vers ESYMAIL_HOSTNAME (mail.esy-web.dev)
- SPF → doit contenir le hostname mail ou include:_spf
- DKIM → sélecteur dkim._domainkey.domain (TXT ou CNAME)
- DMARC → _dmarc.domain doit contenir v=DMARC1
- Retourne ok=true si les 4 checks passent
checkDnsEsyMailer(domain) — config envoi (AWS SES) :
- SES domaine vérifié (isDomainVerified = Success)
- SES DKIM activé et vérifié (getDkimStatus enabled+verified)
- SPF → doit contenir include:amazonses.com
- MAIL FROM → configuré et vérifié (getMailFromStatus = Success)
- Retourne ok=true si les 4 checks passent
Intégration :
- Les checks DNS sont exécutés seulement sur l'onglet NDD (pas sur les
autres onglets pour éviter les appels réseau inutiles)
- Les résultats alimentent configDnsEsyMail et configDnsEsyMailer
dans la sous-ligne de chaque domaine (OK vert / KO rouge)
Configuration :
- .env : ESYMAIL_HOSTNAME (vide par défaut)
- .env.local : ESYMAIL_HOSTNAME=mail.esy-web.dev
- ansible/vault.yml : esymail_hostname pour la prod
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2 configurations DNS distinctes par domaine :
- Config Esy-Mail : MX, SPF, DKIM, DMARC pour la réception (Dovecot/Mailcow)
- Config Esy-Mailer : SPF, DKIM SES, MAIL FROM pour l'envoi (AWS SES)
Les 2 sont à false/KO par défaut — seront branchés sur les checks
DNS réels quand les services seront activés sur le domaine.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sous chaque NDD dans l'onglet Noms de domaine, une ligne affiche :
- Esy-Mail : check vert si au moins 1 DomainEmail lié + nombre de boîtes
- Esy-Mailer : check vert/rouge (placeholder, false pour le moment)
- Config DNS : OK (vert) si zone Cloudflare configurée, KO (rouge) sinon
buildDomainsInfo() :
- Compte les DomainEmail par domaine
- esyMail = emailCount > 0
- esyMailerConfig = zoneIdCloudflare != null (DNS géré)
- esyMailer = false (sera branché sur l'entité service)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
DnsCheckService :
- Ajout getExpirationDate(domain) : requête RDAP pour récupérer
la date d'expiration d'un domaine (events[].eventAction=expiration)
autoDetectDomain :
- Fallback RDAP en fin de détection : si expiredAt est encore null
après check OVH/Cloudflare, interroge RDAP pour l'expiration
- Fonctionne pour tous les TLD (.fr, .com, .dev, etc.)
Flux : OVH serviceInfos → Cloudflare (pas d'expiration) → RDAP fallback
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
L'ajout d'un NDD ne doit pas créer automatiquement le domaine dans
la base esymail/Dovecot. La création de domaine mail sera manuelle
quand on active le service Esy-Mail sur un domaine.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
OvhService (ovh/ovh SDK) :
- listDomains() : liste tous les NDD du compte OVH
- isDomainManaged(fqdn) : vérifie si un domaine est chez OVH
- getDomainInfo(fqdn) : infos domaine (nameServerType, offer, etc.)
- getDomainServiceInfo(fqdn) : expiration, création, status, contacts
- getZoneInfo(fqdn) : zone DNS (OVH ou externe, DNSSEC)
Ajout NDD (onglet Noms de domaine, fiche client) :
- Formulaire : nom de domaine + registrar (auto-détection ou manuel)
- autoDetectDomain() au submit :
1. Check OVH : si trouvé → registrar=OVH, isGestion=true, isBilling=true,
expiredAt depuis serviceInfos, check zone DNS OVH
2. Check Cloudflare : si zone trouvée → zoneCloudflare=active,
zoneIdCloudflare=zoneId. Si registrar=Cloudflare → gestion+billing actifs
3. Si ni OVH ni CF : registrar manuel (Gandi/Autre), isGestion=false
- Suppression NDD avec data-confirm modal
- Colonne Actions ajoutée dans la table
Configuration :
- .env : OVH_KEY, OVH_SECRET, OVH_CUSTOMER
- .env.local : credentials OVH
- ansible/vault.yml : credentials OVH pour prod
- composer.json : ovh/ovh ^3.5
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
UnsubscribeController :
- Route GET /unsubscribe/{email}/{token} (app_unsubscribe)
- Vérifie le token HMAC via UnsubscribeManager::isValidToken
- Si valide : désabonne l'email + page succès
- Si invalide : page erreur avec contact unsubscribe@siteconseil.fr
Templates :
- unsubscribe/success.html.twig : confirmation glassmorphism
- unsubscribe/invalid.html.twig : erreur glassmorphism
ClientsController :
- sendWelcomeEmail : suppression try/catch silencieux pour laisser
remonter les erreurs (sinon mail jamais envoyé sans diagnostic)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>