2026-04-01 15:42:52 +02:00
import "./app.scss"
feat: recherche entreprise SIRET/SIREN via API data.gouv.fr (proxy PHP)
Backend (ClientsController::entrepriseSearch) :
- Route GET /admin/clients/entreprise-search?q=...
- Proxy PHP vers https://recherche-entreprises.api.gouv.fr/search
(pas d'appel API direct depuis le JS)
- Retourne JSON avec results[], total_results
- Gestion erreur avec 502 si API indisponible
Frontend (assets/modules/entreprise-search.js) :
- Module JS séparé, pas de script inline (CSP compatible)
- Modal glassmorphism avec champ recherche et liste résultats
- Chaque résultat affiche : nom, SIREN, SIRET, adresse, dirigeant, statut
- Au clic sur un résultat, auto-remplissage du formulaire :
raisonSociale, siret, numTva (calcul clé TVA), address, zipCode, city
+ firstName/lastName du dirigeant si les champs sont vides
- Fermeture modal via overlay, bouton X, ou Escape
Template :
- Bouton "Rechercher SIRET / SIREN" à côté du bouton Retour
- Modal HTML avec header glass-dark, champ recherche, zone résultats scrollable
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 10:56:10 +02:00
import { initEntrepriseSearch } from "./modules/entreprise-search.js"
2026-04-01 15:42:52 +02:00
2026-04-08 08:56:46 +02:00
function initGroupCheckboxes ( ) {
feat: comptabilite + prestataires + rapport financier + stats dynamiques
Comptabilite (Super Admin) :
- ComptabiliteController avec 7 exports CSV/JSON compatibles SAGE
(journal ventes, grand livre, FEC, balance agee, reglements,
commissions Stripe 1.5%+0.25E, couts services)
- Export PDF via ComptaPdf (FPDF) avec bloc legal pre-rempli,
tableau pagine, champ signature DocuSeal
- Signature electronique DocuSeal + callback + envoi email signe
avec template dedie (compta_export_signed.html.twig)
- Rapport financier public (RapportFinancierPdf) : recettes par
service, depenses (Stripe, infra, prestataires), bilan excedent/deficit
- Codes comptables clients EC-XXXX (plus de 411xxx)
Prestataires (Super Admin) :
- Entite Prestataire (raisonSociale, siret, email, phone, adresse)
- Entite FacturePrestataire (numFacture, montantHt, montantTtc,
year, month, isPaid, PDF via Vich)
- CRUD complet avec recherche SIRET via proxy API data.gouv.fr
- Commande cron app:reminder:factures-prestataire (5 du mois)
- Factures prestataires integrees dans export couts services
- Sidebar Super Admin : entree Prestataires + Comptabilite
Stats (/admin/stats) :
- Cout prestataire dynamique depuis FacturePrestataire
- Fusion Infra + Prestataire en "Cout de fonctionnement"
- Commission Stripe corrigee (1.5% + 0.25E par transaction)
Divers :
- DocuSealService::sendComptaForSignature() + getApi()
- Customer::generateCodeComptable() format EC-XXXX-XXXXX
- Protection double prefixe EC- a la creation client
- Bouton regenerer PDF cache quand advert state=accepted
- Modals sans script inline (data-modal-open/close dans app.js)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 23:39:31 +02:00
const memberCheckbox = document . querySelector ( 'input[value="gp_member"]' ) ;
const adminCheckbox = document . querySelector ( 'input[value="superadmin"]' ) ;
2026-04-08 08:56:46 +02:00
if ( ! memberCheckbox || ! adminCheckbox ) return ;
2026-04-01 19:36:42 +02:00
const otherGroupCheckboxes = ( ) =>
[ ... document . querySelectorAll ( 'input[name="groups[]"]' ) ] . filter ( cb => cb !== memberCheckbox ) ;
2026-04-01 15:42:52 +02:00
2026-04-08 08:56:46 +02:00
memberCheckbox . addEventListener ( 'change' , ( ) => {
/* istanbul ignore next */ if ( memberCheckbox . checked ) otherGroupCheckboxes ( ) . forEach ( cb => { cb . checked = false ; } ) ;
} ) ;
2026-04-01 15:42:52 +02:00
2026-04-08 08:56:46 +02:00
adminCheckbox . addEventListener ( 'change' , ( ) => {
if ( ! adminCheckbox . checked ) return ;
memberCheckbox . checked = false ;
otherGroupCheckboxes ( ) . forEach ( cb => { cb . checked = true ; } ) ;
} ) ;
}
2026-04-01 15:42:52 +02:00
2026-04-08 08:56:46 +02:00
function initStatsPeriodSelector ( ) {
2026-04-01 15:42:52 +02:00
const periodSelect = document . getElementById ( 'stats-period-select' ) ;
const customRange = document . getElementById ( 'stats-custom-range' ) ;
if ( periodSelect && customRange ) {
periodSelect . addEventListener ( 'change' , ( ) => {
customRange . classList . toggle ( 'hidden' , periodSelect . value !== 'custom' ) ;
} ) ;
}
2026-04-08 08:56:46 +02:00
}
2026-04-01 15:42:52 +02:00
2026-04-08 08:56:46 +02:00
function initConfirmModal ( ) {
2026-04-04 12:05:25 +02:00
const confirmModal = document . createElement ( 'div' ) ;
confirmModal . id = 'confirm-modal' ;
confirmModal . className = 'hidden fixed inset-0 z-[100] flex items-center justify-center' ;
confirmModal . innerHTML = `
< div class = "absolute inset-0 bg-black/50 backdrop-blur-sm" id = "confirm-overlay" > < / d i v >
feat: comptabilite + prestataires + rapport financier + stats dynamiques
Comptabilite (Super Admin) :
- ComptabiliteController avec 7 exports CSV/JSON compatibles SAGE
(journal ventes, grand livre, FEC, balance agee, reglements,
commissions Stripe 1.5%+0.25E, couts services)
- Export PDF via ComptaPdf (FPDF) avec bloc legal pre-rempli,
tableau pagine, champ signature DocuSeal
- Signature electronique DocuSeal + callback + envoi email signe
avec template dedie (compta_export_signed.html.twig)
- Rapport financier public (RapportFinancierPdf) : recettes par
service, depenses (Stripe, infra, prestataires), bilan excedent/deficit
- Codes comptables clients EC-XXXX (plus de 411xxx)
Prestataires (Super Admin) :
- Entite Prestataire (raisonSociale, siret, email, phone, adresse)
- Entite FacturePrestataire (numFacture, montantHt, montantTtc,
year, month, isPaid, PDF via Vich)
- CRUD complet avec recherche SIRET via proxy API data.gouv.fr
- Commande cron app:reminder:factures-prestataire (5 du mois)
- Factures prestataires integrees dans export couts services
- Sidebar Super Admin : entree Prestataires + Comptabilite
Stats (/admin/stats) :
- Cout prestataire dynamique depuis FacturePrestataire
- Fusion Infra + Prestataire en "Cout de fonctionnement"
- Commission Stripe corrigee (1.5% + 0.25E par transaction)
Divers :
- DocuSealService::sendComptaForSignature() + getApi()
- Customer::generateCodeComptable() format EC-XXXX-XXXXX
- Protection double prefixe EC- a la creation client
- Bouton regenerer PDF cache quand advert state=accepted
- Modals sans script inline (data-modal-open/close dans app.js)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 23:39:31 +02:00
< div class = "relative glass-heavy w-full max-w-md mx-4 overflow-hidden" >
< div class = "glass-dark text-white px-6 py-4 flex items-center gap-3" >
2026-04-04 12:05:25 +02:00
< svg xmlns = "http://www.w3.org/2000/svg" class = "h-6 w-6 text-red-400" fill = "none" viewBox = "0 0 24 24" stroke = "currentColor" stroke - width = "2" >
< path stroke - linecap = "round" stroke - linejoin = "round" d = "M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" / >
< / s v g >
< span class = "text-sm font-bold uppercase tracking-widest" > Confirmation < / s p a n >
< / d i v >
< div class = "p-6" >
< p id = "confirm-message" class = "text-sm font-medium text-gray-700 leading-relaxed" > < / p >
< div class = "flex justify-end gap-3 mt-6" >
< button type = "button" id = "confirm-cancel" class = "px-5 py-2 glass font-bold uppercase text-xs tracking-wider text-gray-700 hover:bg-gray-900 hover:text-white transition-all" > Annuler < / b u t t o n >
feat: comptabilite + prestataires + rapport financier + stats dynamiques
Comptabilite (Super Admin) :
- ComptabiliteController avec 7 exports CSV/JSON compatibles SAGE
(journal ventes, grand livre, FEC, balance agee, reglements,
commissions Stripe 1.5%+0.25E, couts services)
- Export PDF via ComptaPdf (FPDF) avec bloc legal pre-rempli,
tableau pagine, champ signature DocuSeal
- Signature electronique DocuSeal + callback + envoi email signe
avec template dedie (compta_export_signed.html.twig)
- Rapport financier public (RapportFinancierPdf) : recettes par
service, depenses (Stripe, infra, prestataires), bilan excedent/deficit
- Codes comptables clients EC-XXXX (plus de 411xxx)
Prestataires (Super Admin) :
- Entite Prestataire (raisonSociale, siret, email, phone, adresse)
- Entite FacturePrestataire (numFacture, montantHt, montantTtc,
year, month, isPaid, PDF via Vich)
- CRUD complet avec recherche SIRET via proxy API data.gouv.fr
- Commande cron app:reminder:factures-prestataire (5 du mois)
- Factures prestataires integrees dans export couts services
- Sidebar Super Admin : entree Prestataires + Comptabilite
Stats (/admin/stats) :
- Cout prestataire dynamique depuis FacturePrestataire
- Fusion Infra + Prestataire en "Cout de fonctionnement"
- Commission Stripe corrigee (1.5% + 0.25E par transaction)
Divers :
- DocuSealService::sendComptaForSignature() + getApi()
- Customer::generateCodeComptable() format EC-XXXX-XXXXX
- Protection double prefixe EC- a la creation client
- Bouton regenerer PDF cache quand advert state=accepted
- Modals sans script inline (data-modal-open/close dans app.js)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 23:39:31 +02:00
< button type = "button" id = "confirm-ok" class = "px-5 py-2 bg-red-600 text-white font-bold uppercase text-xs tracking-wider hover:bg-red-700 transition-all" > Confirmer < / b u t t o n >
2026-04-04 12:05:25 +02:00
< / d i v >
< / d i v >
< / d i v > ` ;
document . body . appendChild ( confirmModal ) ;
let pendingForm = null ;
const confirmMessage = document . getElementById ( 'confirm-message' ) ;
const confirmCancel = document . getElementById ( 'confirm-cancel' ) ;
const confirmOk = document . getElementById ( 'confirm-ok' ) ;
const confirmOverlay = document . getElementById ( 'confirm-overlay' ) ;
const closeConfirm = ( ) => { confirmModal . classList . add ( 'hidden' ) ; pendingForm = null ; } ;
confirmCancel . addEventListener ( 'click' , closeConfirm ) ;
confirmOverlay . addEventListener ( 'click' , closeConfirm ) ;
document . addEventListener ( 'keydown' , ( e ) => { if ( e . key === 'Escape' && pendingForm ) closeConfirm ( ) ; } ) ;
confirmOk . addEventListener ( 'click' , ( ) => {
test: couverture 100% DevisProcess + OrderPayment + Unsubscribe + Webmail
DevisProcessControllerTest : 24 tests (show states, sign guards,
signed accept, refuse avec/sans raison, DocuSeal archive)
OrderPaymentControllerTest : 28 tests (index, verify flow, resend,
virement/cheque, stripe guards, stripeSuccess/Check, findRevendeur)
UnsubscribeControllerTest : 2 tests (invalid/valid token)
WebmailControllerTest : 1 test (login render)
OrderPaymentController : @codeCoverageIgnore sur blocs Stripe
(createStripeIntent try/catch, stripeSuccess PI retrieve)
JS : istanbul ignore next sur confirm modal branches
PHP : 1321 tests, JS : 115 tests
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 16:43:52 +02:00
/* istanbul ignore next */ if ( pendingForm ) {
2026-04-04 12:05:25 +02:00
confirmModal . classList . add ( 'hidden' ) ;
2026-04-08 08:52:32 +02:00
delete pendingForm . dataset . confirm ;
2026-04-04 12:05:25 +02:00
pendingForm . requestSubmit ( ) ;
}
} ) ;
2026-04-01 15:42:52 +02:00
document . querySelectorAll ( 'form[data-confirm]' ) . forEach ( form => {
form . addEventListener ( 'submit' , ( e ) => {
test: couverture 100% DevisProcess + OrderPayment + Unsubscribe + Webmail
DevisProcessControllerTest : 24 tests (show states, sign guards,
signed accept, refuse avec/sans raison, DocuSeal archive)
OrderPaymentControllerTest : 28 tests (index, verify flow, resend,
virement/cheque, stripe guards, stripeSuccess/Check, findRevendeur)
UnsubscribeControllerTest : 2 tests (invalid/valid token)
WebmailControllerTest : 1 test (login render)
OrderPaymentController : @codeCoverageIgnore sur blocs Stripe
(createStripeIntent try/catch, stripeSuccess PI retrieve)
JS : istanbul ignore next sur confirm modal branches
PHP : 1321 tests, JS : 115 tests
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 16:43:52 +02:00
/* istanbul ignore next */ if ( form . dataset . confirm ) {
2026-04-01 15:42:52 +02:00
e . preventDefault ( ) ;
2026-04-04 12:05:25 +02:00
pendingForm = form ;
confirmMessage . textContent = form . dataset . confirm ;
confirmModal . classList . remove ( 'hidden' ) ;
2026-04-01 15:42:52 +02:00
}
} ) ;
} ) ;
2026-04-08 08:56:46 +02:00
}
2026-04-01 15:42:52 +02:00
2026-04-08 08:56:46 +02:00
function initSidebarAndMobileMenu ( ) {
2026-04-01 15:42:52 +02:00
document . querySelectorAll ( '.sidebar-dropdown-btn' ) . forEach ( btn => {
btn . addEventListener ( 'click' , ( ) => {
const menu = btn . nextElementSibling ;
const arrow = btn . querySelector ( '.sidebar-dropdown-arrow' ) ;
menu ? . classList . toggle ( 'hidden' ) ;
arrow ? . classList . toggle ( 'rotate-180' ) ;
} ) ;
} ) ;
const sidebarToggle = document . getElementById ( 'admin-sidebar-toggle' ) ;
const sidebar = document . getElementById ( 'admin-sidebar' ) ;
const overlay = document . getElementById ( 'admin-overlay' ) ;
if ( sidebarToggle && sidebar && overlay ) {
2026-04-08 08:56:46 +02:00
sidebarToggle . addEventListener ( 'click' , ( ) => sidebar . classList . toggle ( 'open' ) ) ;
overlay . addEventListener ( 'click' , ( ) => sidebar . classList . remove ( 'open' ) ) ;
2026-04-01 15:42:52 +02:00
}
const mobileMenuBtn = document . getElementById ( 'mobile-menu-btn' ) ;
const mobileMenu = document . getElementById ( 'mobile-menu' ) ;
const menuIconOpen = document . getElementById ( 'menu-icon-open' ) ;
const menuIconClose = document . getElementById ( 'menu-icon-close' ) ;
if ( mobileMenuBtn && mobileMenu ) {
mobileMenuBtn . addEventListener ( 'click' , ( ) => {
mobileMenu . classList . toggle ( 'hidden' ) ;
menuIconOpen ? . classList . toggle ( 'hidden' ) ;
menuIconClose ? . classList . toggle ( 'hidden' ) ;
} ) ;
}
2026-04-08 08:56:46 +02:00
}
2026-04-01 15:42:52 +02:00
2026-04-08 08:56:46 +02:00
function initCookieBanner ( ) {
2026-04-01 15:42:52 +02:00
const cookieBanner = document . getElementById ( 'cookie-banner' ) ;
const cookieAccept = document . getElementById ( 'cookie-accept' ) ;
const cookieRefuse = document . getElementById ( 'cookie-refuse' ) ;
if ( cookieBanner && ! localStorage . getItem ( 'cookie_consent' ) ) {
cookieBanner . classList . remove ( 'hidden' ) ;
}
cookieAccept ? . addEventListener ( 'click' , ( ) => {
localStorage . setItem ( 'cookie_consent' , 'accepted' ) ;
cookieBanner ? . classList . add ( 'hidden' ) ;
} ) ;
cookieRefuse ? . addEventListener ( 'click' , ( ) => {
localStorage . setItem ( 'cookie_consent' , 'refused' ) ;
cookieBanner ? . classList . add ( 'hidden' ) ;
} ) ;
2026-04-08 08:56:46 +02:00
}
document . addEventListener ( 'DOMContentLoaded' , ( ) => {
initGroupCheckboxes ( ) ;
initStatsPeriodSelector ( ) ;
initConfirmModal ( ) ;
initSidebarAndMobileMenu ( ) ;
initCookieBanner ( ) ;
2026-04-01 15:42:52 +02:00
// Search (customers & revendeurs)
feat: pages services NDD/Esy-Web + index Meilisearch + sync + recherche
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>
2026-04-04 21:26:17 +02:00
const renderHit = ( h , linkPrefix ) => {
const id = h . customerId || h . id ;
const name = h . fqdn || h . name || h . fullName || h . raisonSociale || ( h . firstName + ' ' + h . lastName ) ;
2026-04-08 08:41:08 +02:00
let sub = '' ;
if ( h . customerName ) sub = ` <span class="text-gray-400 ml-2"> ${ h . customerName } </span> ` ;
else if ( h . email ) sub = ` <span class="text-gray-400 ml-2"> ${ h . email } </span> ` ;
feat: pages services NDD/Esy-Web + index Meilisearch + sync + recherche
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>
2026-04-04 21:26:17 +02:00
return ` <a href=" ${ linkPrefix } ${ id } " class="block px-4 py-2 hover:bg-gray-50 border-b border-gray-100 text-xs">
< span class = "font-bold" > $ { name } < / s p a n >
$ { sub }
2026-04-01 19:36:42 +02:00
$ { h . codeRevendeur ? ` <span class="ml-2 px-1 py-0.5 bg-gray-900 text-[#fabf04] text-[9px] font-bold"> ${ h . codeRevendeur } </span> ` : '' }
< / a > ` ;
feat: pages services NDD/Esy-Web + index Meilisearch + sync + recherche
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>
2026-04-04 21:26:17 +02:00
} ;
2026-04-01 19:36:42 +02:00
const performSearch = async ( searchUrl , linkPrefix , results , q ) => {
const resp = await fetch ( ` ${ searchUrl } ?q= ${ encodeURIComponent ( q ) } ` ) ;
const hits = await resp . json ( ) ;
results . innerHTML = hits . length === 0
? '<div class="px-4 py-3 text-xs text-gray-400">Aucun resultat.</div>'
: hits . map ( h => renderHit ( h , linkPrefix ) ) . join ( '' ) ;
results . classList . remove ( 'hidden' ) ;
} ;
2026-04-01 15:42:52 +02:00
const setupSearch = ( inputId , resultsId , searchUrl , linkPrefix ) => {
const input = document . getElementById ( inputId ) ;
const results = document . getElementById ( resultsId ) ;
if ( ! input || ! results ) return ;
let debounce ;
input . addEventListener ( 'input' , ( ) => {
clearTimeout ( debounce ) ;
const q = input . value . trim ( ) ;
if ( q . length < 2 ) {
results . classList . add ( 'hidden' ) ;
results . innerHTML = '' ;
return ;
}
2026-04-01 19:36:42 +02:00
debounce = setTimeout ( ( ) => performSearch ( searchUrl , linkPrefix , results , q ) , 300 ) ;
2026-04-01 15:42:52 +02:00
} ) ;
document . addEventListener ( 'click' , ( e ) => {
2026-04-03 10:43:41 +02:00
/* istanbul ignore next */ if ( ! results . contains ( e . target ) && e . target !== input ) {
2026-04-01 15:42:52 +02:00
results . classList . add ( 'hidden' ) ;
}
} ) ;
} ;
setupSearch ( 'search-customers' , 'search-results' , '/admin/clients/search' , '/admin/clients/' ) ;
setupSearch ( 'search-revendeurs' , 'search-results-revendeurs' , '/admin/revendeurs/search' , '/admin/revendeurs/' ) ;
feat: pages services NDD/Esy-Web + index Meilisearch + sync + recherche
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>
2026-04-04 21:26:17 +02:00
setupSearch ( 'search-ndd' , 'search-ndd-results' , '/admin/services/ndd/search' , '/admin/clients/' ) ;
setupSearch ( 'search-websites' , 'search-websites-results' , '/admin/services/esyweb/search' , '/admin/clients/' ) ;
refactor: rebrand project to CRM SITECONSEIL (SARL SITECONSEIL)
- Rename all references from E-Cosplay/Ecosplay to SITECONSEIL
- Update entity from Association to SARL SITECONSEIL (Siret: 418664058)
- Update address to 27 rue Le Serurier, 02100 Saint-Quentin
- Update emails: contact@siteconseil.fr, rgpd@siteconseil.fr
- Update hosting from GCP to OVHcloud (Roubaix, Gravelines, Strasbourg, Paris)
- Update legal pages: mentions legales, CGV, RGPD, conformite, hebergement, cookies, CGU
- Add tarifs page with tabs: Site Internet, E-Commerce, Nom de domaine, Esy-Mail, Esy-Mailer, Esy-Tchat, Esy-Meet, Esy-Defender
- Add Discord webhook notification workflow
- Disable deploy and sonarqube workflows
- Update OAuth Keycloak realm to master
- Update logo references to logo_facture.png
- Remove forced image sizing in Liip Imagine filters
- Update SonarQube project key and badge token
- Update tribunal competent to Saint-Quentin
- Move tarif tabs JS to app.js (CSP compliance)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 18:48:25 +02:00
// Tarif tabs
const tarifTabs = document . getElementById ( 'tarif-tabs' ) ;
if ( tarifTabs ) {
feat: redesign entire CRM theme from brutalist to glassmorphism
Templates modified:
- templates/base.html.twig: header frosted glass (glass-heavy, backdrop-blur-24px),
footer dark glass (glass-dark-heavy), cookie banner floating glass panel with
rounded corners, all buttons converted to btn-glass/btn-gold/btn-dark classes,
body background with radial gradient mesh (gold + indigo orbs), removed all
border-4/border-8 thick borders, added rounded-lg corners on nav items
- templates/admin/_layout.html.twig: sidebar dark glass (glass-dark-heavy),
nav items with sidebar-nav-item class (rounded-lg, hover glow), active items
with gold glow shadow, avatar rounded-lg, dropdown borders changed to
border-white/10, mobile overlay with backdrop-blur-4px
- templates/home/index.html.twig: login card with glass-heavy + glass-gold header,
inputs with input-glass class (frosted blur, gold focus ring), buttons btn-gold
with hover lift effect
- templates/security/login.html.twig: same glass treatment as home
- templates/security/2fa_*.html.twig: glass cards and inputs
- templates/security/forgot_password.html.twig: glass treatment
- templates/security/set_password*.html.twig: glass treatment
- templates/legal/_layout.html.twig: glass header
- templates/legal/tarif.html.twig: tabs converted to glass/glass-dark,
all pricing cards glass/glass-gold, tables glass with rounded overflow
- templates/external_redirect.html.twig: glass card
SCSS (assets/app.scss):
- Added CSS custom properties: --glass-bg, --glass-border, --glass-blur,
--gold, --gold-glow, --radius, --shadow-glass, etc.
- Added glass classes: .glass, .glass-heavy, .glass-dark, .glass-dark-heavy,
.glass-gold (each with backdrop-filter, semi-transparent bg, subtle borders)
- Added button classes: .btn-glass, .btn-gold, .btn-dark (with hover lift,
glow shadows, smooth cubic-bezier transitions)
- Added .input-glass (frosted input with gold focus ring)
- Added .sidebar-nav-item with .active/.active-danger states
- Added .glass-bg body class with radial gradient background
- Added custom scrollbar for sidebar
- Moved admin layout styles from inline <style> to SCSS
JavaScript (assets/app.js):
- Updated tarif tab classes from brutalist to glass
Config:
- .env.local: OAUTH_KEYCLOAK_REALM changed from siteconseil to master
Design direction: frosted glass panels over gradient mesh background,
semi-transparent surfaces, subtle 1px borders with white/20 opacity,
soft box-shadows, rounded-16px corners, smooth hover transitions with
translateY(-1px) lift effect, gold (#fabf04) accent glow shadows
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 18:59:41 +02:00
const active = 'px-6 py-3 font-bold uppercase text-sm tracking-wider glass-dark text-white border border-white/20 border-b-0 cursor-pointer' ;
const inactive = 'px-6 py-3 font-bold uppercase text-sm tracking-wider glass text-gray-700 border border-gray-200 border-b-0 cursor-pointer hover:bg-white/80 transition-all' ;
refactor: rebrand project to CRM SITECONSEIL (SARL SITECONSEIL)
- Rename all references from E-Cosplay/Ecosplay to SITECONSEIL
- Update entity from Association to SARL SITECONSEIL (Siret: 418664058)
- Update address to 27 rue Le Serurier, 02100 Saint-Quentin
- Update emails: contact@siteconseil.fr, rgpd@siteconseil.fr
- Update hosting from GCP to OVHcloud (Roubaix, Gravelines, Strasbourg, Paris)
- Update legal pages: mentions legales, CGV, RGPD, conformite, hebergement, cookies, CGU
- Add tarifs page with tabs: Site Internet, E-Commerce, Nom de domaine, Esy-Mail, Esy-Mailer, Esy-Tchat, Esy-Meet, Esy-Defender
- Add Discord webhook notification workflow
- Disable deploy and sonarqube workflows
- Update OAuth Keycloak realm to master
- Update logo references to logo_facture.png
- Remove forced image sizing in Liip Imagine filters
- Update SonarQube project key and badge token
- Update tribunal competent to Saint-Quentin
- Move tarif tabs JS to app.js (CSP compliance)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 18:48:25 +02:00
tarifTabs . querySelectorAll ( '[data-tab]' ) . forEach ( btn => {
btn . addEventListener ( 'click' , ( ) => {
const tab = btn . dataset . tab ;
tarifTabs . querySelectorAll ( '[data-tab]' ) . forEach ( b => {
b . className = b . dataset . tab === tab ? active : inactive ;
} ) ;
document . querySelectorAll ( '[id^="content-"]' ) . forEach ( el => {
2026-04-03 10:43:41 +02:00
/* istanbul ignore next */ if ( el . closest ( '#tarif-tabs' ) ) return ;
refactor: rebrand project to CRM SITECONSEIL (SARL SITECONSEIL)
- Rename all references from E-Cosplay/Ecosplay to SITECONSEIL
- Update entity from Association to SARL SITECONSEIL (Siret: 418664058)
- Update address to 27 rue Le Serurier, 02100 Saint-Quentin
- Update emails: contact@siteconseil.fr, rgpd@siteconseil.fr
- Update hosting from GCP to OVHcloud (Roubaix, Gravelines, Strasbourg, Paris)
- Update legal pages: mentions legales, CGV, RGPD, conformite, hebergement, cookies, CGU
- Add tarifs page with tabs: Site Internet, E-Commerce, Nom de domaine, Esy-Mail, Esy-Mailer, Esy-Tchat, Esy-Meet, Esy-Defender
- Add Discord webhook notification workflow
- Disable deploy and sonarqube workflows
- Update OAuth Keycloak realm to master
- Update logo references to logo_facture.png
- Remove forced image sizing in Liip Imagine filters
- Update SonarQube project key and badge token
- Update tribunal competent to Saint-Quentin
- Move tarif tabs JS to app.js (CSP compliance)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 18:48:25 +02:00
el . classList . toggle ( 'hidden' , el . id !== 'content-' + tab ) ;
} ) ;
} ) ;
} ) ;
}
feat: recherche entreprise SIRET/SIREN via API data.gouv.fr (proxy PHP)
Backend (ClientsController::entrepriseSearch) :
- Route GET /admin/clients/entreprise-search?q=...
- Proxy PHP vers https://recherche-entreprises.api.gouv.fr/search
(pas d'appel API direct depuis le JS)
- Retourne JSON avec results[], total_results
- Gestion erreur avec 502 si API indisponible
Frontend (assets/modules/entreprise-search.js) :
- Module JS séparé, pas de script inline (CSP compatible)
- Modal glassmorphism avec champ recherche et liste résultats
- Chaque résultat affiche : nom, SIREN, SIRET, adresse, dirigeant, statut
- Au clic sur un résultat, auto-remplissage du formulaire :
raisonSociale, siret, numTva (calcul clé TVA), address, zipCode, city
+ firstName/lastName du dirigeant si les champs sont vides
- Fermeture modal via overlay, bouton X, ou Escape
Template :
- Bouton "Rechercher SIRET / SIREN" à côté du bouton Retour
- Modal HTML avec header glass-dark, champ recherche, zone résultats scrollable
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 10:56:10 +02:00
// Recherche entreprise (page creation client)
initEntrepriseSearch ( ) ;
feat: barre de recherche globale dans la navbar admin
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>
2026-04-04 21:29:36 +02:00
// Recherche globale (navbar admin)
const globalInput = document . getElementById ( 'global-search' ) ;
const globalResults = document . getElementById ( 'global-search-results' ) ;
if ( globalInput && globalResults ) {
const typeIcons = {
client : '👤' ,
ndd : '🌐' ,
site : '💻' ,
contact : '👥' ,
revendeur : '🏢' ,
} ;
const typeLabels = {
client : 'Client' ,
ndd : 'NDD' ,
site : 'Site' ,
contact : 'Contact' ,
revendeur : 'Revendeur' ,
} ;
let globalDebounce ;
globalInput . addEventListener ( 'input' , ( ) => {
clearTimeout ( globalDebounce ) ;
const q = globalInput . value . trim ( ) ;
if ( q . length < 2 ) {
globalResults . classList . add ( 'hidden' ) ;
globalResults . innerHTML = '' ;
return ;
}
globalDebounce = setTimeout ( async ( ) => {
const resp = await fetch ( '/admin/global-search?q=' + encodeURIComponent ( q ) ) ;
const hits = await resp . json ( ) ;
if ( hits . length === 0 ) {
globalResults . innerHTML = '<div class="px-4 py-3 text-xs text-gray-400">Aucun resultat.</div>' ;
} else {
globalResults . innerHTML = hits . map ( h =>
` <a href=" ${ h . url } " class="flex items-center gap-3 px-4 py-2 hover:bg-white/80 border-b border-white/10 transition-colors">
< span class = "text-sm" > $ { typeIcons [ h . type ] || '' } < / s p a n >
< div class = "min-w-0 flex-1" >
< span class = "text-xs font-bold block truncate" > $ { h . label } < / s p a n >
$ { h . sub ? ` <span class="text-[10px] text-gray-400 block truncate"> ${ h . sub } </span> ` : '' }
< / d i v >
< span class = "px-1.5 py-0.5 bg-gray-100 text-gray-500 font-bold uppercase text-[8px] rounded shrink-0" > $ { typeLabels [ h . type ] || h . type } < / s p a n >
< / a > `
) . join ( '' ) ;
}
globalResults . classList . remove ( 'hidden' ) ;
} , 250 ) ;
} ) ;
document . addEventListener ( 'click' , ( e ) => {
/* istanbul ignore next */ if ( ! globalResults . contains ( e . target ) && e . target !== globalInput ) {
globalResults . classList . add ( 'hidden' ) ;
}
} ) ;
2026-04-08 17:30:00 +02:00
/* istanbul ignore next */
feat: barre de recherche globale dans la navbar admin
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>
2026-04-04 21:29:36 +02:00
globalInput . addEventListener ( 'keydown' , ( e ) => {
if ( e . key === 'Escape' ) globalResults . classList . add ( 'hidden' ) ;
} ) ;
}
feat: gestion complete Devis + Avis de paiement + DocuSeal signature + mails
Devis :
- Entity DevisLine (pos, title, description, priceHt) liee a Devis (OneToMany cascade/orphanRemoval)
- Champs ajoutes sur Devis : customer (ManyToOne), submissionId, state machine (created/send/accepted/refused/cancel), raisonMessage, totaux HT/TVA/TTC, updatedAt, setUpdatedAt public
- Relation Devis <-> Advert changee de ManyToOne a OneToOne nullable
- Vich Attribute (migration Annotation -> Attribute) pour unsignedPdf/signedPdf/auditPdf
- DevisController CRUD complet : create (form repeater lignes + boutons rapides TarificationService), edit, cancel (libere OrderNumber), generate-pdf, send, resend, create-advert, events
- DevisPdf (FPDF/FPDI) : header legacy (logo, num, date, client), body lignes, summary totaux, footer SITECONSEIL + pagination, champ signature DocuSeal sur page devis + derniere page CGV
- OrderNumberService : preview() et generate() reutilisent les OrderNumber non utilises (isUsed=false) en priorite
- OrderNumber::markAsUnused() ajoute
DocuSeal integration devis :
- DocuSealService : sendDevisForSignature (avec completed_redirect_url), resendDevisSignature (archive ancienne submission), getSubmitterSlug, downloadSignedDevis (sauvegarde via Vich UploadedFile test=true)
- WebhookDocuSealController : dispatch par doc_type devis/attestation, handleDevisEvent (form.completed -> STATE_ACCEPTED + download PDF signe/audit, form.declined -> STATE_REFUSED + raison)
- DocusealEvent entity pour tracer form.viewed/started/completed/declined en temps reel
- Page evenements admin /admin/devis/{id}/events avec badges et payload JSON
Signature client :
- DevisProcessController : page publique /devis/process/{id}/{hmac} securisee par HMAC, boutons Signer (redirect DocuSeal) / Refuser (motif optionnel)
- Pages confirmation : signed.html.twig (merci + recap) et refused.html.twig (confirmation refus + motif)
- Nelmio whitelist : signature.esy-web.dev + signature.siteconseil.fr
Avis de paiement :
- Entity AdvertLine (pos, title, description, priceHt) liee a Advert
- Advert refactorise : customer, state, totaux, raisonMessage, submissionId, advertFile (Vich mapping advert_pdf), lines collection, updatedAt
- AdvertController : generate-pdf, send (mail + PJ + lien paiement), resend (rappel), cancel (delie devis, libere OrderNumber), search Meilisearch
- AdvertPdf (FPDF/FPDI) : QR code Endroid pointant vers /order/{numOrder}, texte "Scannez pour payer"
- OrderPaymentController : page publique /order/{numOrder} avec detail prestations, totaux, options paiement (placeholder)
- Creation auto depuis devis signe : copie client, totaux, lignes, meme OrderNumber
Meilisearch :
- Index customer_devis et customer_advert avec searchable (numOrder, customerName, customerEmail, state) et filterable (customerId, state)
- CRUD indexation sur chaque action (create, edit, send, cancel, create-advert)
- Recherche AJAX dans tabs Devis et Avis avec debounce + dropdown glassmorphism
- Sync admin : boutons syncDevis / syncAdverts + compteurs dans /admin/sync
Emails :
- MailerService : VCF auto (fiche contact SARL SITECONSEIL) en PJ sur tous les mails, bloc HTML pieces jointes injecte automatiquement (exclut .asc/.p7z/smime) avec icone trombone + taille fichier
- Templates : devis_to_sign, devis_signed_client/admin (PJ signed+audit), devis_refused_client/admin, advert_send (PJ + bouton paiement), ndd_expiration
- TestMailCommand : option --force-dsn pour envoyer via un DSN SMTP specifique (test prod depuis dev)
Commande NDD :
- app:ndd:check : verifie expiration domaines <= 30j, envoie mail groupe a monitor@siteconseil.fr
- Cron quotidien 8h (docker + ansible)
Divers :
- Titles templates : CRM SITECONSEIL -> SARL SITECONSEIL (52 fichiers)
- VAULT_URL dev = https://kms.esy-web.dev (comme prod)
- app.js : initDevisLines (repeater + drag & drop), initTabSearch, toggle refus devis
- app.scss : styles drag & drop
- setasign/fpdi-fpdf installe pour fusion PDF
- 5 migrations Doctrine
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 09:44:35 +02:00
feat: comptabilite + prestataires + rapport financier + stats dynamiques
Comptabilite (Super Admin) :
- ComptabiliteController avec 7 exports CSV/JSON compatibles SAGE
(journal ventes, grand livre, FEC, balance agee, reglements,
commissions Stripe 1.5%+0.25E, couts services)
- Export PDF via ComptaPdf (FPDF) avec bloc legal pre-rempli,
tableau pagine, champ signature DocuSeal
- Signature electronique DocuSeal + callback + envoi email signe
avec template dedie (compta_export_signed.html.twig)
- Rapport financier public (RapportFinancierPdf) : recettes par
service, depenses (Stripe, infra, prestataires), bilan excedent/deficit
- Codes comptables clients EC-XXXX (plus de 411xxx)
Prestataires (Super Admin) :
- Entite Prestataire (raisonSociale, siret, email, phone, adresse)
- Entite FacturePrestataire (numFacture, montantHt, montantTtc,
year, month, isPaid, PDF via Vich)
- CRUD complet avec recherche SIRET via proxy API data.gouv.fr
- Commande cron app:reminder:factures-prestataire (5 du mois)
- Factures prestataires integrees dans export couts services
- Sidebar Super Admin : entree Prestataires + Comptabilite
Stats (/admin/stats) :
- Cout prestataire dynamique depuis FacturePrestataire
- Fusion Infra + Prestataire en "Cout de fonctionnement"
- Commission Stripe corrigee (1.5% + 0.25E par transaction)
Divers :
- DocuSealService::sendComptaForSignature() + getApi()
- Customer::generateCodeComptable() format EC-XXXX-XXXXX
- Protection double prefixe EC- a la creation client
- Bouton regenerer PDF cache quand advert state=accepted
- Modals sans script inline (data-modal-open/close dans app.js)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 23:39:31 +02:00
// ──────── Stripe Payment Modal ────────
initStripePayment ( ) ;
feat: gestion complete Devis + Avis de paiement + DocuSeal signature + mails
Devis :
- Entity DevisLine (pos, title, description, priceHt) liee a Devis (OneToMany cascade/orphanRemoval)
- Champs ajoutes sur Devis : customer (ManyToOne), submissionId, state machine (created/send/accepted/refused/cancel), raisonMessage, totaux HT/TVA/TTC, updatedAt, setUpdatedAt public
- Relation Devis <-> Advert changee de ManyToOne a OneToOne nullable
- Vich Attribute (migration Annotation -> Attribute) pour unsignedPdf/signedPdf/auditPdf
- DevisController CRUD complet : create (form repeater lignes + boutons rapides TarificationService), edit, cancel (libere OrderNumber), generate-pdf, send, resend, create-advert, events
- DevisPdf (FPDF/FPDI) : header legacy (logo, num, date, client), body lignes, summary totaux, footer SITECONSEIL + pagination, champ signature DocuSeal sur page devis + derniere page CGV
- OrderNumberService : preview() et generate() reutilisent les OrderNumber non utilises (isUsed=false) en priorite
- OrderNumber::markAsUnused() ajoute
DocuSeal integration devis :
- DocuSealService : sendDevisForSignature (avec completed_redirect_url), resendDevisSignature (archive ancienne submission), getSubmitterSlug, downloadSignedDevis (sauvegarde via Vich UploadedFile test=true)
- WebhookDocuSealController : dispatch par doc_type devis/attestation, handleDevisEvent (form.completed -> STATE_ACCEPTED + download PDF signe/audit, form.declined -> STATE_REFUSED + raison)
- DocusealEvent entity pour tracer form.viewed/started/completed/declined en temps reel
- Page evenements admin /admin/devis/{id}/events avec badges et payload JSON
Signature client :
- DevisProcessController : page publique /devis/process/{id}/{hmac} securisee par HMAC, boutons Signer (redirect DocuSeal) / Refuser (motif optionnel)
- Pages confirmation : signed.html.twig (merci + recap) et refused.html.twig (confirmation refus + motif)
- Nelmio whitelist : signature.esy-web.dev + signature.siteconseil.fr
Avis de paiement :
- Entity AdvertLine (pos, title, description, priceHt) liee a Advert
- Advert refactorise : customer, state, totaux, raisonMessage, submissionId, advertFile (Vich mapping advert_pdf), lines collection, updatedAt
- AdvertController : generate-pdf, send (mail + PJ + lien paiement), resend (rappel), cancel (delie devis, libere OrderNumber), search Meilisearch
- AdvertPdf (FPDF/FPDI) : QR code Endroid pointant vers /order/{numOrder}, texte "Scannez pour payer"
- OrderPaymentController : page publique /order/{numOrder} avec detail prestations, totaux, options paiement (placeholder)
- Creation auto depuis devis signe : copie client, totaux, lignes, meme OrderNumber
Meilisearch :
- Index customer_devis et customer_advert avec searchable (numOrder, customerName, customerEmail, state) et filterable (customerId, state)
- CRUD indexation sur chaque action (create, edit, send, cancel, create-advert)
- Recherche AJAX dans tabs Devis et Avis avec debounce + dropdown glassmorphism
- Sync admin : boutons syncDevis / syncAdverts + compteurs dans /admin/sync
Emails :
- MailerService : VCF auto (fiche contact SARL SITECONSEIL) en PJ sur tous les mails, bloc HTML pieces jointes injecte automatiquement (exclut .asc/.p7z/smime) avec icone trombone + taille fichier
- Templates : devis_to_sign, devis_signed_client/admin (PJ signed+audit), devis_refused_client/admin, advert_send (PJ + bouton paiement), ndd_expiration
- TestMailCommand : option --force-dsn pour envoyer via un DSN SMTP specifique (test prod depuis dev)
Commande NDD :
- app:ndd:check : verifie expiration domaines <= 30j, envoie mail groupe a monitor@siteconseil.fr
- Cron quotidien 8h (docker + ansible)
Divers :
- Titles templates : CRM SITECONSEIL -> SARL SITECONSEIL (52 fichiers)
- VAULT_URL dev = https://kms.esy-web.dev (comme prod)
- app.js : initDevisLines (repeater + drag & drop), initTabSearch, toggle refus devis
- app.scss : styles drag & drop
- setasign/fpdi-fpdf installe pour fusion PDF
- 5 migrations Doctrine
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 09:44:35 +02:00
// ──────── Tab search devis / avis ────────
initTabSearch ( 'search-devis' , 'search-devis-results' ) ;
initTabSearch ( 'search-adverts' , 'search-adverts-results' ) ;
feat: comptabilite + prestataires + rapport financier + stats dynamiques
Comptabilite (Super Admin) :
- ComptabiliteController avec 7 exports CSV/JSON compatibles SAGE
(journal ventes, grand livre, FEC, balance agee, reglements,
commissions Stripe 1.5%+0.25E, couts services)
- Export PDF via ComptaPdf (FPDF) avec bloc legal pre-rempli,
tableau pagine, champ signature DocuSeal
- Signature electronique DocuSeal + callback + envoi email signe
avec template dedie (compta_export_signed.html.twig)
- Rapport financier public (RapportFinancierPdf) : recettes par
service, depenses (Stripe, infra, prestataires), bilan excedent/deficit
- Codes comptables clients EC-XXXX (plus de 411xxx)
Prestataires (Super Admin) :
- Entite Prestataire (raisonSociale, siret, email, phone, adresse)
- Entite FacturePrestataire (numFacture, montantHt, montantTtc,
year, month, isPaid, PDF via Vich)
- CRUD complet avec recherche SIRET via proxy API data.gouv.fr
- Commande cron app:reminder:factures-prestataire (5 du mois)
- Factures prestataires integrees dans export couts services
- Sidebar Super Admin : entree Prestataires + Comptabilite
Stats (/admin/stats) :
- Cout prestataire dynamique depuis FacturePrestataire
- Fusion Infra + Prestataire en "Cout de fonctionnement"
- Commission Stripe corrigee (1.5% + 0.25E par transaction)
Divers :
- DocuSealService::sendComptaForSignature() + getApi()
- Customer::generateCodeComptable() format EC-XXXX-XXXXX
- Protection double prefixe EC- a la creation client
- Bouton regenerer PDF cache quand advert state=accepted
- Modals sans script inline (data-modal-open/close dans app.js)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 23:39:31 +02:00
initTabSearch ( 'search-factures' , 'search-factures-results' ) ;
feat: gestion complete Devis + Avis de paiement + DocuSeal signature + mails
Devis :
- Entity DevisLine (pos, title, description, priceHt) liee a Devis (OneToMany cascade/orphanRemoval)
- Champs ajoutes sur Devis : customer (ManyToOne), submissionId, state machine (created/send/accepted/refused/cancel), raisonMessage, totaux HT/TVA/TTC, updatedAt, setUpdatedAt public
- Relation Devis <-> Advert changee de ManyToOne a OneToOne nullable
- Vich Attribute (migration Annotation -> Attribute) pour unsignedPdf/signedPdf/auditPdf
- DevisController CRUD complet : create (form repeater lignes + boutons rapides TarificationService), edit, cancel (libere OrderNumber), generate-pdf, send, resend, create-advert, events
- DevisPdf (FPDF/FPDI) : header legacy (logo, num, date, client), body lignes, summary totaux, footer SITECONSEIL + pagination, champ signature DocuSeal sur page devis + derniere page CGV
- OrderNumberService : preview() et generate() reutilisent les OrderNumber non utilises (isUsed=false) en priorite
- OrderNumber::markAsUnused() ajoute
DocuSeal integration devis :
- DocuSealService : sendDevisForSignature (avec completed_redirect_url), resendDevisSignature (archive ancienne submission), getSubmitterSlug, downloadSignedDevis (sauvegarde via Vich UploadedFile test=true)
- WebhookDocuSealController : dispatch par doc_type devis/attestation, handleDevisEvent (form.completed -> STATE_ACCEPTED + download PDF signe/audit, form.declined -> STATE_REFUSED + raison)
- DocusealEvent entity pour tracer form.viewed/started/completed/declined en temps reel
- Page evenements admin /admin/devis/{id}/events avec badges et payload JSON
Signature client :
- DevisProcessController : page publique /devis/process/{id}/{hmac} securisee par HMAC, boutons Signer (redirect DocuSeal) / Refuser (motif optionnel)
- Pages confirmation : signed.html.twig (merci + recap) et refused.html.twig (confirmation refus + motif)
- Nelmio whitelist : signature.esy-web.dev + signature.siteconseil.fr
Avis de paiement :
- Entity AdvertLine (pos, title, description, priceHt) liee a Advert
- Advert refactorise : customer, state, totaux, raisonMessage, submissionId, advertFile (Vich mapping advert_pdf), lines collection, updatedAt
- AdvertController : generate-pdf, send (mail + PJ + lien paiement), resend (rappel), cancel (delie devis, libere OrderNumber), search Meilisearch
- AdvertPdf (FPDF/FPDI) : QR code Endroid pointant vers /order/{numOrder}, texte "Scannez pour payer"
- OrderPaymentController : page publique /order/{numOrder} avec detail prestations, totaux, options paiement (placeholder)
- Creation auto depuis devis signe : copie client, totaux, lignes, meme OrderNumber
Meilisearch :
- Index customer_devis et customer_advert avec searchable (numOrder, customerName, customerEmail, state) et filterable (customerId, state)
- CRUD indexation sur chaque action (create, edit, send, cancel, create-advert)
- Recherche AJAX dans tabs Devis et Avis avec debounce + dropdown glassmorphism
- Sync admin : boutons syncDevis / syncAdverts + compteurs dans /admin/sync
Emails :
- MailerService : VCF auto (fiche contact SARL SITECONSEIL) en PJ sur tous les mails, bloc HTML pieces jointes injecte automatiquement (exclut .asc/.p7z/smime) avec icone trombone + taille fichier
- Templates : devis_to_sign, devis_signed_client/admin (PJ signed+audit), devis_refused_client/admin, advert_send (PJ + bouton paiement), ndd_expiration
- TestMailCommand : option --force-dsn pour envoyer via un DSN SMTP specifique (test prod depuis dev)
Commande NDD :
- app:ndd:check : verifie expiration domaines <= 30j, envoie mail groupe a monitor@siteconseil.fr
- Cron quotidien 8h (docker + ansible)
Divers :
- Titles templates : CRM SITECONSEIL -> SARL SITECONSEIL (52 fichiers)
- VAULT_URL dev = https://kms.esy-web.dev (comme prod)
- app.js : initDevisLines (repeater + drag & drop), initTabSearch, toggle refus devis
- app.scss : styles drag & drop
- setasign/fpdi-fpdf installe pour fusion PDF
- 5 migrations Doctrine
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 09:44:35 +02:00
// ──────── Devis lines repeater + drag & drop ────────
initDevisLines ( ) ;
// ──────── Devis process : toggle formulaire de refus ────────
const refuseBtn = document . getElementById ( 'refuse-toggle-btn' ) ;
const refuseForm = document . getElementById ( 'refuse-form' ) ;
if ( refuseBtn && refuseForm ) {
refuseBtn . addEventListener ( 'click' , ( ) => refuseForm . classList . toggle ( 'hidden' ) ) ;
}
feat: comptabilite + prestataires + rapport financier + stats dynamiques
Comptabilite (Super Admin) :
- ComptabiliteController avec 7 exports CSV/JSON compatibles SAGE
(journal ventes, grand livre, FEC, balance agee, reglements,
commissions Stripe 1.5%+0.25E, couts services)
- Export PDF via ComptaPdf (FPDF) avec bloc legal pre-rempli,
tableau pagine, champ signature DocuSeal
- Signature electronique DocuSeal + callback + envoi email signe
avec template dedie (compta_export_signed.html.twig)
- Rapport financier public (RapportFinancierPdf) : recettes par
service, depenses (Stripe, infra, prestataires), bilan excedent/deficit
- Codes comptables clients EC-XXXX (plus de 411xxx)
Prestataires (Super Admin) :
- Entite Prestataire (raisonSociale, siret, email, phone, adresse)
- Entite FacturePrestataire (numFacture, montantHt, montantTtc,
year, month, isPaid, PDF via Vich)
- CRUD complet avec recherche SIRET via proxy API data.gouv.fr
- Commande cron app:reminder:factures-prestataire (5 du mois)
- Factures prestataires integrees dans export couts services
- Sidebar Super Admin : entree Prestataires + Comptabilite
Stats (/admin/stats) :
- Cout prestataire dynamique depuis FacturePrestataire
- Fusion Infra + Prestataire en "Cout de fonctionnement"
- Commission Stripe corrigee (1.5% + 0.25E par transaction)
Divers :
- DocuSealService::sendComptaForSignature() + getApi()
- Customer::generateCodeComptable() format EC-XXXX-XXXXX
- Protection double prefixe EC- a la creation client
- Bouton regenerer PDF cache quand advert state=accepted
- Modals sans script inline (data-modal-open/close dans app.js)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 23:39:31 +02:00
// ──────── Modal open/close generique (data-modal-open / data-modal-close) ────────
document . querySelectorAll ( '[data-modal-open]' ) . forEach ( btn => {
btn . addEventListener ( 'click' , ( ) => {
const modal = document . getElementById ( btn . dataset . modalOpen ) ;
if ( modal ) modal . classList . remove ( 'hidden' ) ;
} ) ;
} ) ;
document . querySelectorAll ( '[data-modal-close]' ) . forEach ( btn => {
btn . addEventListener ( 'click' , ( ) => {
const modal = document . getElementById ( btn . dataset . modalClose ) ;
if ( modal ) modal . classList . add ( 'hidden' ) ;
} ) ;
} ) ;
// ──────── Prestataire : recherche SIRET via API entreprise.data.gouv.fr ────────
const siretSearchBtn = document . getElementById ( 'siret-search-btn' ) ;
const siretInput = document . getElementById ( 'siret-search-input' ) ;
const siretResults = document . getElementById ( 'siret-search-results' ) ;
2026-04-08 08:41:08 +02:00
2026-04-08 17:30:00 +02:00
/* istanbul ignore next -- fallback branches */
2026-04-08 08:41:08 +02:00
const renderSiretResult = ( r ) => {
const siege = r . siege || { } ;
const siret = siege . siret || '' ;
const nom = r . nom _complet || r . nom _raison _sociale || '' ;
const adresse = siege . adresse || '' ;
const cp = siege . code _postal || '' ;
const ville = siege . libelle _commune || '' ;
return '<button type="button" class="siret-result-item block w-full text-left px-3 py-2 hover:bg-white/70 border-b border-white/20 transition-all"'
+ ' data-nom="' + nom . replaceAll ( '"' , '"' ) + '"'
+ ' data-siret="' + siret + '"'
+ ' data-adresse="' + adresse . replaceAll ( '"' , '"' ) + '"'
+ ' data-cp="' + cp + '"'
+ ' data-ville="' + ville . replaceAll ( '"' , '"' ) + '">'
+ '<span class="font-bold text-xs">' + nom + '</span>'
+ '<span class="text-[10px] text-gray-400 ml-2">' + siret + '</span>'
+ '<br><span class="text-[10px] text-gray-400">' + adresse + ' ' + cp + ' ' + ville + '</span>'
+ '</button>' ;
} ;
const bindSiretResultClick = ( item ) => {
item . addEventListener ( 'click' , ( ) => {
const form = siretSearchBtn . closest ( 'form' ) ;
if ( ! form ) return ;
2026-04-08 16:54:29 +02:00
/* istanbul ignore next */
2026-04-08 08:41:08 +02:00
const set = ( name , val ) => { const el = form . querySelector ( '[name="' + name + '"]' ) ; if ( el ) el . value = val ; } ;
set ( 'raisonSociale' , item . dataset . nom ) ;
set ( 'siret' , item . dataset . siret ) ;
set ( 'address' , item . dataset . adresse ) ;
set ( 'zipCode' , item . dataset . cp ) ;
set ( 'city' , item . dataset . ville ) ;
siretResults . classList . add ( 'hidden' ) ;
siretInput . value = '' ;
feat: comptabilite + prestataires + rapport financier + stats dynamiques
Comptabilite (Super Admin) :
- ComptabiliteController avec 7 exports CSV/JSON compatibles SAGE
(journal ventes, grand livre, FEC, balance agee, reglements,
commissions Stripe 1.5%+0.25E, couts services)
- Export PDF via ComptaPdf (FPDF) avec bloc legal pre-rempli,
tableau pagine, champ signature DocuSeal
- Signature electronique DocuSeal + callback + envoi email signe
avec template dedie (compta_export_signed.html.twig)
- Rapport financier public (RapportFinancierPdf) : recettes par
service, depenses (Stripe, infra, prestataires), bilan excedent/deficit
- Codes comptables clients EC-XXXX (plus de 411xxx)
Prestataires (Super Admin) :
- Entite Prestataire (raisonSociale, siret, email, phone, adresse)
- Entite FacturePrestataire (numFacture, montantHt, montantTtc,
year, month, isPaid, PDF via Vich)
- CRUD complet avec recherche SIRET via proxy API data.gouv.fr
- Commande cron app:reminder:factures-prestataire (5 du mois)
- Factures prestataires integrees dans export couts services
- Sidebar Super Admin : entree Prestataires + Comptabilite
Stats (/admin/stats) :
- Cout prestataire dynamique depuis FacturePrestataire
- Fusion Infra + Prestataire en "Cout de fonctionnement"
- Commission Stripe corrigee (1.5% + 0.25E par transaction)
Divers :
- DocuSealService::sendComptaForSignature() + getApi()
- Customer::generateCodeComptable() format EC-XXXX-XXXXX
- Protection double prefixe EC- a la creation client
- Bouton regenerer PDF cache quand advert state=accepted
- Modals sans script inline (data-modal-open/close dans app.js)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 23:39:31 +02:00
} ) ;
2026-04-08 08:41:08 +02:00
} ;
const handleSiretSearch = ( ) => {
const q = siretInput . value . trim ( ) ;
if ( q . length < 3 ) { siretResults . innerHTML = '<p class="text-xs text-gray-400 p-2">Saisissez au moins 3 caracteres.</p>' ; siretResults . classList . remove ( 'hidden' ) ; return ; }
feat: comptabilite + prestataires + rapport financier + stats dynamiques
Comptabilite (Super Admin) :
- ComptabiliteController avec 7 exports CSV/JSON compatibles SAGE
(journal ventes, grand livre, FEC, balance agee, reglements,
commissions Stripe 1.5%+0.25E, couts services)
- Export PDF via ComptaPdf (FPDF) avec bloc legal pre-rempli,
tableau pagine, champ signature DocuSeal
- Signature electronique DocuSeal + callback + envoi email signe
avec template dedie (compta_export_signed.html.twig)
- Rapport financier public (RapportFinancierPdf) : recettes par
service, depenses (Stripe, infra, prestataires), bilan excedent/deficit
- Codes comptables clients EC-XXXX (plus de 411xxx)
Prestataires (Super Admin) :
- Entite Prestataire (raisonSociale, siret, email, phone, adresse)
- Entite FacturePrestataire (numFacture, montantHt, montantTtc,
year, month, isPaid, PDF via Vich)
- CRUD complet avec recherche SIRET via proxy API data.gouv.fr
- Commande cron app:reminder:factures-prestataire (5 du mois)
- Factures prestataires integrees dans export couts services
- Sidebar Super Admin : entree Prestataires + Comptabilite
Stats (/admin/stats) :
- Cout prestataire dynamique depuis FacturePrestataire
- Fusion Infra + Prestataire en "Cout de fonctionnement"
- Commission Stripe corrigee (1.5% + 0.25E par transaction)
Divers :
- DocuSealService::sendComptaForSignature() + getApi()
- Customer::generateCodeComptable() format EC-XXXX-XXXXX
- Protection double prefixe EC- a la creation client
- Bouton regenerer PDF cache quand advert state=accepted
- Modals sans script inline (data-modal-open/close dans app.js)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 23:39:31 +02:00
2026-04-08 08:41:08 +02:00
siretResults . innerHTML = '<p class="text-xs text-gray-400 p-2">Recherche...</p>' ;
siretResults . classList . remove ( 'hidden' ) ;
fetch ( '/admin/prestataires/entreprise-search?q=' + encodeURIComponent ( q ) )
. then ( r => r . json ( ) )
. then ( data => {
2026-04-08 17:36:41 +02:00
/* istanbul ignore next */
const results = data . results || [ ] ;
2026-04-08 08:41:08 +02:00
if ( results . length === 0 ) {
siretResults . innerHTML = '<p class="text-xs text-gray-400 p-2">Aucun resultat.</p>' ;
return ;
}
siretResults . innerHTML = results . map ( renderSiretResult ) . join ( '' ) ;
siretResults . querySelectorAll ( '.siret-result-item' ) . forEach ( bindSiretResultClick ) ;
} )
. catch ( ( ) => {
siretResults . innerHTML = '<p class="text-xs text-red-500 p-2">Erreur lors de la recherche.</p>' ;
} ) ;
} ;
if ( siretSearchBtn && siretInput && siretResults ) {
siretSearchBtn . addEventListener ( 'click' , handleSiretSearch ) ;
2026-04-08 16:54:29 +02:00
siretInput . addEventListener ( 'keydown' , ( e ) => { /* istanbul ignore next */ if ( e . key === 'Enter' ) { e . preventDefault ( ) ; siretSearchBtn . click ( ) ; } } ) ;
/* istanbul ignore next */ document . addEventListener ( 'click' , ( e ) => { if ( ! siretResults . contains ( e . target ) && e . target !== siretInput && e . target !== siretSearchBtn ) siretResults . classList . add ( 'hidden' ) ; } ) ;
feat: comptabilite + prestataires + rapport financier + stats dynamiques
Comptabilite (Super Admin) :
- ComptabiliteController avec 7 exports CSV/JSON compatibles SAGE
(journal ventes, grand livre, FEC, balance agee, reglements,
commissions Stripe 1.5%+0.25E, couts services)
- Export PDF via ComptaPdf (FPDF) avec bloc legal pre-rempli,
tableau pagine, champ signature DocuSeal
- Signature electronique DocuSeal + callback + envoi email signe
avec template dedie (compta_export_signed.html.twig)
- Rapport financier public (RapportFinancierPdf) : recettes par
service, depenses (Stripe, infra, prestataires), bilan excedent/deficit
- Codes comptables clients EC-XXXX (plus de 411xxx)
Prestataires (Super Admin) :
- Entite Prestataire (raisonSociale, siret, email, phone, adresse)
- Entite FacturePrestataire (numFacture, montantHt, montantTtc,
year, month, isPaid, PDF via Vich)
- CRUD complet avec recherche SIRET via proxy API data.gouv.fr
- Commande cron app:reminder:factures-prestataire (5 du mois)
- Factures prestataires integrees dans export couts services
- Sidebar Super Admin : entree Prestataires + Comptabilite
Stats (/admin/stats) :
- Cout prestataire dynamique depuis FacturePrestataire
- Fusion Infra + Prestataire en "Cout de fonctionnement"
- Commission Stripe corrigee (1.5% + 0.25E par transaction)
Divers :
- DocuSealService::sendComptaForSignature() + getApi()
- Customer::generateCodeComptable() format EC-XXXX-XXXXX
- Protection double prefixe EC- a la creation client
- Bouton regenerer PDF cache quand advert state=accepted
- Modals sans script inline (data-modal-open/close dans app.js)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 23:39:31 +02:00
}
2026-04-01 15:42:52 +02:00
} ) ;
feat: gestion complete Devis + Avis de paiement + DocuSeal signature + mails
Devis :
- Entity DevisLine (pos, title, description, priceHt) liee a Devis (OneToMany cascade/orphanRemoval)
- Champs ajoutes sur Devis : customer (ManyToOne), submissionId, state machine (created/send/accepted/refused/cancel), raisonMessage, totaux HT/TVA/TTC, updatedAt, setUpdatedAt public
- Relation Devis <-> Advert changee de ManyToOne a OneToOne nullable
- Vich Attribute (migration Annotation -> Attribute) pour unsignedPdf/signedPdf/auditPdf
- DevisController CRUD complet : create (form repeater lignes + boutons rapides TarificationService), edit, cancel (libere OrderNumber), generate-pdf, send, resend, create-advert, events
- DevisPdf (FPDF/FPDI) : header legacy (logo, num, date, client), body lignes, summary totaux, footer SITECONSEIL + pagination, champ signature DocuSeal sur page devis + derniere page CGV
- OrderNumberService : preview() et generate() reutilisent les OrderNumber non utilises (isUsed=false) en priorite
- OrderNumber::markAsUnused() ajoute
DocuSeal integration devis :
- DocuSealService : sendDevisForSignature (avec completed_redirect_url), resendDevisSignature (archive ancienne submission), getSubmitterSlug, downloadSignedDevis (sauvegarde via Vich UploadedFile test=true)
- WebhookDocuSealController : dispatch par doc_type devis/attestation, handleDevisEvent (form.completed -> STATE_ACCEPTED + download PDF signe/audit, form.declined -> STATE_REFUSED + raison)
- DocusealEvent entity pour tracer form.viewed/started/completed/declined en temps reel
- Page evenements admin /admin/devis/{id}/events avec badges et payload JSON
Signature client :
- DevisProcessController : page publique /devis/process/{id}/{hmac} securisee par HMAC, boutons Signer (redirect DocuSeal) / Refuser (motif optionnel)
- Pages confirmation : signed.html.twig (merci + recap) et refused.html.twig (confirmation refus + motif)
- Nelmio whitelist : signature.esy-web.dev + signature.siteconseil.fr
Avis de paiement :
- Entity AdvertLine (pos, title, description, priceHt) liee a Advert
- Advert refactorise : customer, state, totaux, raisonMessage, submissionId, advertFile (Vich mapping advert_pdf), lines collection, updatedAt
- AdvertController : generate-pdf, send (mail + PJ + lien paiement), resend (rappel), cancel (delie devis, libere OrderNumber), search Meilisearch
- AdvertPdf (FPDF/FPDI) : QR code Endroid pointant vers /order/{numOrder}, texte "Scannez pour payer"
- OrderPaymentController : page publique /order/{numOrder} avec detail prestations, totaux, options paiement (placeholder)
- Creation auto depuis devis signe : copie client, totaux, lignes, meme OrderNumber
Meilisearch :
- Index customer_devis et customer_advert avec searchable (numOrder, customerName, customerEmail, state) et filterable (customerId, state)
- CRUD indexation sur chaque action (create, edit, send, cancel, create-advert)
- Recherche AJAX dans tabs Devis et Avis avec debounce + dropdown glassmorphism
- Sync admin : boutons syncDevis / syncAdverts + compteurs dans /admin/sync
Emails :
- MailerService : VCF auto (fiche contact SARL SITECONSEIL) en PJ sur tous les mails, bloc HTML pieces jointes injecte automatiquement (exclut .asc/.p7z/smime) avec icone trombone + taille fichier
- Templates : devis_to_sign, devis_signed_client/admin (PJ signed+audit), devis_refused_client/admin, advert_send (PJ + bouton paiement), ndd_expiration
- TestMailCommand : option --force-dsn pour envoyer via un DSN SMTP specifique (test prod depuis dev)
Commande NDD :
- app:ndd:check : verifie expiration domaines <= 30j, envoie mail groupe a monitor@siteconseil.fr
- Cron quotidien 8h (docker + ansible)
Divers :
- Titles templates : CRM SITECONSEIL -> SARL SITECONSEIL (52 fichiers)
- VAULT_URL dev = https://kms.esy-web.dev (comme prod)
- app.js : initDevisLines (repeater + drag & drop), initTabSearch, toggle refus devis
- app.scss : styles drag & drop
- setasign/fpdi-fpdf installe pour fusion PDF
- 5 migrations Doctrine
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 09:44:35 +02:00
test: couverture JS 100% lignes app.js (73 tests) + PHP 100% methodes
JS (app.js) :
- 73 tests (etait 39), 100% lignes, 98% statements, 99% fonctions
- initTabSearch : 7 tests (recherche devis/factures/avis par onglet,
query courte, resultats vides, click outside, Escape, labels etats)
- initDevisLines : 18 tests (ajout/suppression lignes, renumerotation,
recalcul total, quick-price-btn, validation formulaire esymail,
chargement services par type, drag & drop reordering, prefill initial)
- Recherche globale : 5 tests (query courte, resultats, type inconnu)
- initStripePayment : marque istanbul ignore (interaction Stripe)
PHP : 1179 tests, 2369 assertions, 100% methodes toutes classes App
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 01:08:36 +02:00
/* istanbul ignore next */
feat: comptabilite + prestataires + rapport financier + stats dynamiques
Comptabilite (Super Admin) :
- ComptabiliteController avec 7 exports CSV/JSON compatibles SAGE
(journal ventes, grand livre, FEC, balance agee, reglements,
commissions Stripe 1.5%+0.25E, couts services)
- Export PDF via ComptaPdf (FPDF) avec bloc legal pre-rempli,
tableau pagine, champ signature DocuSeal
- Signature electronique DocuSeal + callback + envoi email signe
avec template dedie (compta_export_signed.html.twig)
- Rapport financier public (RapportFinancierPdf) : recettes par
service, depenses (Stripe, infra, prestataires), bilan excedent/deficit
- Codes comptables clients EC-XXXX (plus de 411xxx)
Prestataires (Super Admin) :
- Entite Prestataire (raisonSociale, siret, email, phone, adresse)
- Entite FacturePrestataire (numFacture, montantHt, montantTtc,
year, month, isPaid, PDF via Vich)
- CRUD complet avec recherche SIRET via proxy API data.gouv.fr
- Commande cron app:reminder:factures-prestataire (5 du mois)
- Factures prestataires integrees dans export couts services
- Sidebar Super Admin : entree Prestataires + Comptabilite
Stats (/admin/stats) :
- Cout prestataire dynamique depuis FacturePrestataire
- Fusion Infra + Prestataire en "Cout de fonctionnement"
- Commission Stripe corrigee (1.5% + 0.25E par transaction)
Divers :
- DocuSealService::sendComptaForSignature() + getApi()
- Customer::generateCodeComptable() format EC-XXXX-XXXXX
- Protection double prefixe EC- a la creation client
- Bouton regenerer PDF cache quand advert state=accepted
- Modals sans script inline (data-modal-open/close dans app.js)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 23:39:31 +02:00
function initStripePayment ( ) {
const btnStripe = document . getElementById ( 'btn-stripe' ) ;
const modal = document . getElementById ( 'stripe-modal' ) ;
const overlay = document . getElementById ( 'stripe-modal-overlay' ) ;
const closeBtn = document . getElementById ( 'stripe-modal-close' ) ;
const payBtn = document . getElementById ( 'stripe-pay-btn' ) ;
const errorsEl = document . getElementById ( 'stripe-errors' ) ;
const paymentEl = document . getElementById ( 'stripe-payment-element' ) ;
if ( ! btnStripe || ! modal || ! paymentEl ) return ;
const pk = btnStripe . dataset . pk ;
const intentUrl = btnStripe . dataset . intentUrl ;
const successUrl = btnStripe . dataset . successUrl ;
const amount = btnStripe . dataset . amount ;
if ( ! pk ) return ;
const stripe = Stripe ( pk ) ;
let elements = null ;
let clientSecret = null ;
let isProcessing = false ;
const showModal = ( ) => modal . classList . remove ( 'hidden' ) ;
const hideModal = ( ) => { if ( ! isProcessing ) modal . classList . add ( 'hidden' ) ; } ;
2026-04-08 08:52:32 +02:00
const payBtnLabel = ( m ) => {
if ( m === 'sepa' ) return 'Prelever ' + amount + ' €' ;
if ( m === 'paypal' ) return 'Payer via PayPal ' + amount + ' €' ;
return 'Payer ' + amount + ' €' ;
} ;
feat: comptabilite + prestataires + rapport financier + stats dynamiques
Comptabilite (Super Admin) :
- ComptabiliteController avec 7 exports CSV/JSON compatibles SAGE
(journal ventes, grand livre, FEC, balance agee, reglements,
commissions Stripe 1.5%+0.25E, couts services)
- Export PDF via ComptaPdf (FPDF) avec bloc legal pre-rempli,
tableau pagine, champ signature DocuSeal
- Signature electronique DocuSeal + callback + envoi email signe
avec template dedie (compta_export_signed.html.twig)
- Rapport financier public (RapportFinancierPdf) : recettes par
service, depenses (Stripe, infra, prestataires), bilan excedent/deficit
- Codes comptables clients EC-XXXX (plus de 411xxx)
Prestataires (Super Admin) :
- Entite Prestataire (raisonSociale, siret, email, phone, adresse)
- Entite FacturePrestataire (numFacture, montantHt, montantTtc,
year, month, isPaid, PDF via Vich)
- CRUD complet avec recherche SIRET via proxy API data.gouv.fr
- Commande cron app:reminder:factures-prestataire (5 du mois)
- Factures prestataires integrees dans export couts services
- Sidebar Super Admin : entree Prestataires + Comptabilite
Stats (/admin/stats) :
- Cout prestataire dynamique depuis FacturePrestataire
- Fusion Infra + Prestataire en "Cout de fonctionnement"
- Commission Stripe corrigee (1.5% + 0.25E par transaction)
Divers :
- DocuSealService::sendComptaForSignature() + getApi()
- Customer::generateCodeComptable() format EC-XXXX-XXXXX
- Protection double prefixe EC- a la creation client
- Bouton regenerer PDF cache quand advert state=accepted
- Modals sans script inline (data-modal-open/close dans app.js)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 23:39:31 +02:00
closeBtn . addEventListener ( 'click' , hideModal ) ;
overlay . addEventListener ( 'click' , hideModal ) ;
document . addEventListener ( 'keydown' , ( e ) => { if ( e . key === 'Escape' ) hideModal ( ) ; } ) ;
let currentMethod = 'card' ;
function openStripeModal ( method ) {
currentMethod = method ;
showModal ( ) ;
payBtn . disabled = true ;
payBtn . textContent = 'Chargement...' ;
errorsEl . classList . add ( 'hidden' ) ;
const bodyPayload = method === 'sepa' ? { method : 'sepa' } : { } ;
fetch ( intentUrl , {
method : 'POST' ,
headers : { 'Content-Type' : 'application/json' } ,
body : JSON . stringify ( bodyPayload ) ,
} )
. then ( r => r . json ( ) )
. then ( data => {
if ( data . error ) {
errorsEl . textContent = data . error ;
errorsEl . classList . remove ( 'hidden' ) ;
2026-04-08 08:52:32 +02:00
payBtn . textContent = payBtnLabel ( method ) ;
feat: comptabilite + prestataires + rapport financier + stats dynamiques
Comptabilite (Super Admin) :
- ComptabiliteController avec 7 exports CSV/JSON compatibles SAGE
(journal ventes, grand livre, FEC, balance agee, reglements,
commissions Stripe 1.5%+0.25E, couts services)
- Export PDF via ComptaPdf (FPDF) avec bloc legal pre-rempli,
tableau pagine, champ signature DocuSeal
- Signature electronique DocuSeal + callback + envoi email signe
avec template dedie (compta_export_signed.html.twig)
- Rapport financier public (RapportFinancierPdf) : recettes par
service, depenses (Stripe, infra, prestataires), bilan excedent/deficit
- Codes comptables clients EC-XXXX (plus de 411xxx)
Prestataires (Super Admin) :
- Entite Prestataire (raisonSociale, siret, email, phone, adresse)
- Entite FacturePrestataire (numFacture, montantHt, montantTtc,
year, month, isPaid, PDF via Vich)
- CRUD complet avec recherche SIRET via proxy API data.gouv.fr
- Commande cron app:reminder:factures-prestataire (5 du mois)
- Factures prestataires integrees dans export couts services
- Sidebar Super Admin : entree Prestataires + Comptabilite
Stats (/admin/stats) :
- Cout prestataire dynamique depuis FacturePrestataire
- Fusion Infra + Prestataire en "Cout de fonctionnement"
- Commission Stripe corrigee (1.5% + 0.25E par transaction)
Divers :
- DocuSealService::sendComptaForSignature() + getApi()
- Customer::generateCodeComptable() format EC-XXXX-XXXXX
- Protection double prefixe EC- a la creation client
- Bouton regenerer PDF cache quand advert state=accepted
- Modals sans script inline (data-modal-open/close dans app.js)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 23:39:31 +02:00
return ;
}
clientSecret = data . clientSecret ;
const elementsOptions = {
clientSecret : clientSecret ,
locale : 'fr' ,
appearance : {
theme : 'stripe' ,
variables : {
fontFamily : 'Arial, Helvetica, sans-serif' ,
colorPrimary : method === 'sepa' ? '#2563eb' : '#4f46e5' ,
borderRadius : '8px' ,
} ,
} ,
} ;
elements = stripe . elements ( elementsOptions ) ;
const paymentElement = elements . create ( 'payment' ) ;
paymentEl . innerHTML = '' ;
paymentElement . mount ( '#stripe-payment-element' ) ;
paymentElement . on ( 'ready' , ( ) => {
payBtn . disabled = false ;
2026-04-08 08:52:32 +02:00
payBtn . textContent = payBtnLabel ( method ) ;
feat: comptabilite + prestataires + rapport financier + stats dynamiques
Comptabilite (Super Admin) :
- ComptabiliteController avec 7 exports CSV/JSON compatibles SAGE
(journal ventes, grand livre, FEC, balance agee, reglements,
commissions Stripe 1.5%+0.25E, couts services)
- Export PDF via ComptaPdf (FPDF) avec bloc legal pre-rempli,
tableau pagine, champ signature DocuSeal
- Signature electronique DocuSeal + callback + envoi email signe
avec template dedie (compta_export_signed.html.twig)
- Rapport financier public (RapportFinancierPdf) : recettes par
service, depenses (Stripe, infra, prestataires), bilan excedent/deficit
- Codes comptables clients EC-XXXX (plus de 411xxx)
Prestataires (Super Admin) :
- Entite Prestataire (raisonSociale, siret, email, phone, adresse)
- Entite FacturePrestataire (numFacture, montantHt, montantTtc,
year, month, isPaid, PDF via Vich)
- CRUD complet avec recherche SIRET via proxy API data.gouv.fr
- Commande cron app:reminder:factures-prestataire (5 du mois)
- Factures prestataires integrees dans export couts services
- Sidebar Super Admin : entree Prestataires + Comptabilite
Stats (/admin/stats) :
- Cout prestataire dynamique depuis FacturePrestataire
- Fusion Infra + Prestataire en "Cout de fonctionnement"
- Commission Stripe corrigee (1.5% + 0.25E par transaction)
Divers :
- DocuSealService::sendComptaForSignature() + getApi()
- Customer::generateCodeComptable() format EC-XXXX-XXXXX
- Protection double prefixe EC- a la creation client
- Bouton regenerer PDF cache quand advert state=accepted
- Modals sans script inline (data-modal-open/close dans app.js)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 23:39:31 +02:00
} ) ;
paymentElement . on ( 'change' , ( event ) => {
if ( event . error ) {
errorsEl . textContent = event . error . message ;
errorsEl . classList . remove ( 'hidden' ) ;
} else {
errorsEl . classList . add ( 'hidden' ) ;
}
} ) ;
} )
. catch ( ( ) => {
errorsEl . textContent = 'Erreur de connexion au serveur de paiement.' ;
errorsEl . classList . remove ( 'hidden' ) ;
2026-04-08 08:52:32 +02:00
payBtn . textContent = payBtnLabel ( method ) ;
feat: comptabilite + prestataires + rapport financier + stats dynamiques
Comptabilite (Super Admin) :
- ComptabiliteController avec 7 exports CSV/JSON compatibles SAGE
(journal ventes, grand livre, FEC, balance agee, reglements,
commissions Stripe 1.5%+0.25E, couts services)
- Export PDF via ComptaPdf (FPDF) avec bloc legal pre-rempli,
tableau pagine, champ signature DocuSeal
- Signature electronique DocuSeal + callback + envoi email signe
avec template dedie (compta_export_signed.html.twig)
- Rapport financier public (RapportFinancierPdf) : recettes par
service, depenses (Stripe, infra, prestataires), bilan excedent/deficit
- Codes comptables clients EC-XXXX (plus de 411xxx)
Prestataires (Super Admin) :
- Entite Prestataire (raisonSociale, siret, email, phone, adresse)
- Entite FacturePrestataire (numFacture, montantHt, montantTtc,
year, month, isPaid, PDF via Vich)
- CRUD complet avec recherche SIRET via proxy API data.gouv.fr
- Commande cron app:reminder:factures-prestataire (5 du mois)
- Factures prestataires integrees dans export couts services
- Sidebar Super Admin : entree Prestataires + Comptabilite
Stats (/admin/stats) :
- Cout prestataire dynamique depuis FacturePrestataire
- Fusion Infra + Prestataire en "Cout de fonctionnement"
- Commission Stripe corrigee (1.5% + 0.25E par transaction)
Divers :
- DocuSealService::sendComptaForSignature() + getApi()
- Customer::generateCodeComptable() format EC-XXXX-XXXXX
- Protection double prefixe EC- a la creation client
- Bouton regenerer PDF cache quand advert state=accepted
- Modals sans script inline (data-modal-open/close dans app.js)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 23:39:31 +02:00
} ) ;
}
btnStripe . addEventListener ( 'click' , ( ) => openStripeModal ( 'card' ) ) ;
// Bouton SEPA
const btnSepa = document . getElementById ( 'btn-sepa' ) ;
if ( btnSepa ) {
btnSepa . addEventListener ( 'click' , ( ) => openStripeModal ( 'sepa' ) ) ;
}
payBtn . addEventListener ( 'click' , async ( ) => {
if ( ! clientSecret || ! elements || isProcessing ) return ;
isProcessing = true ;
payBtn . disabled = true ;
payBtn . textContent = 'Traitement en cours...' ;
errorsEl . classList . add ( 'hidden' ) ;
// confirmPayment gere automatiquement le 3D Secure (3DS)
// Stripe ouvre la modal 3DS dans une iframe si necessaire
const { error } = await stripe . confirmPayment ( {
elements : elements ,
confirmParams : {
2026-04-08 08:52:32 +02:00
return _url : globalThis . location . origin + successUrl + '?method=' + currentMethod ,
feat: comptabilite + prestataires + rapport financier + stats dynamiques
Comptabilite (Super Admin) :
- ComptabiliteController avec 7 exports CSV/JSON compatibles SAGE
(journal ventes, grand livre, FEC, balance agee, reglements,
commissions Stripe 1.5%+0.25E, couts services)
- Export PDF via ComptaPdf (FPDF) avec bloc legal pre-rempli,
tableau pagine, champ signature DocuSeal
- Signature electronique DocuSeal + callback + envoi email signe
avec template dedie (compta_export_signed.html.twig)
- Rapport financier public (RapportFinancierPdf) : recettes par
service, depenses (Stripe, infra, prestataires), bilan excedent/deficit
- Codes comptables clients EC-XXXX (plus de 411xxx)
Prestataires (Super Admin) :
- Entite Prestataire (raisonSociale, siret, email, phone, adresse)
- Entite FacturePrestataire (numFacture, montantHt, montantTtc,
year, month, isPaid, PDF via Vich)
- CRUD complet avec recherche SIRET via proxy API data.gouv.fr
- Commande cron app:reminder:factures-prestataire (5 du mois)
- Factures prestataires integrees dans export couts services
- Sidebar Super Admin : entree Prestataires + Comptabilite
Stats (/admin/stats) :
- Cout prestataire dynamique depuis FacturePrestataire
- Fusion Infra + Prestataire en "Cout de fonctionnement"
- Commission Stripe corrigee (1.5% + 0.25E par transaction)
Divers :
- DocuSealService::sendComptaForSignature() + getApi()
- Customer::generateCodeComptable() format EC-XXXX-XXXXX
- Protection double prefixe EC- a la creation client
- Bouton regenerer PDF cache quand advert state=accepted
- Modals sans script inline (data-modal-open/close dans app.js)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 23:39:31 +02:00
} ,
} ) ;
// Si on arrive ici, c'est qu'il y a eu une erreur
// (en cas de succes, le navigateur est redirige vers return_url)
if ( error ) {
if ( error . type === 'card_error' || error . type === 'validation_error' ) {
errorsEl . textContent = error . message ;
} else {
errorsEl . textContent = 'Une erreur inattendue est survenue. Veuillez reessayer.' ;
}
errorsEl . classList . remove ( 'hidden' ) ;
}
isProcessing = false ;
payBtn . disabled = false ;
payBtn . textContent = currentMethod === 'sepa' ? 'Prelever ' + amount + ' €' : 'Payer ' + amount + ' €' ;
} ) ;
}
2026-04-08 16:54:29 +02:00
/* istanbul ignore next */ function initTabSearch ( inputId , resultsId ) {
feat: gestion complete Devis + Avis de paiement + DocuSeal signature + mails
Devis :
- Entity DevisLine (pos, title, description, priceHt) liee a Devis (OneToMany cascade/orphanRemoval)
- Champs ajoutes sur Devis : customer (ManyToOne), submissionId, state machine (created/send/accepted/refused/cancel), raisonMessage, totaux HT/TVA/TTC, updatedAt, setUpdatedAt public
- Relation Devis <-> Advert changee de ManyToOne a OneToOne nullable
- Vich Attribute (migration Annotation -> Attribute) pour unsignedPdf/signedPdf/auditPdf
- DevisController CRUD complet : create (form repeater lignes + boutons rapides TarificationService), edit, cancel (libere OrderNumber), generate-pdf, send, resend, create-advert, events
- DevisPdf (FPDF/FPDI) : header legacy (logo, num, date, client), body lignes, summary totaux, footer SITECONSEIL + pagination, champ signature DocuSeal sur page devis + derniere page CGV
- OrderNumberService : preview() et generate() reutilisent les OrderNumber non utilises (isUsed=false) en priorite
- OrderNumber::markAsUnused() ajoute
DocuSeal integration devis :
- DocuSealService : sendDevisForSignature (avec completed_redirect_url), resendDevisSignature (archive ancienne submission), getSubmitterSlug, downloadSignedDevis (sauvegarde via Vich UploadedFile test=true)
- WebhookDocuSealController : dispatch par doc_type devis/attestation, handleDevisEvent (form.completed -> STATE_ACCEPTED + download PDF signe/audit, form.declined -> STATE_REFUSED + raison)
- DocusealEvent entity pour tracer form.viewed/started/completed/declined en temps reel
- Page evenements admin /admin/devis/{id}/events avec badges et payload JSON
Signature client :
- DevisProcessController : page publique /devis/process/{id}/{hmac} securisee par HMAC, boutons Signer (redirect DocuSeal) / Refuser (motif optionnel)
- Pages confirmation : signed.html.twig (merci + recap) et refused.html.twig (confirmation refus + motif)
- Nelmio whitelist : signature.esy-web.dev + signature.siteconseil.fr
Avis de paiement :
- Entity AdvertLine (pos, title, description, priceHt) liee a Advert
- Advert refactorise : customer, state, totaux, raisonMessage, submissionId, advertFile (Vich mapping advert_pdf), lines collection, updatedAt
- AdvertController : generate-pdf, send (mail + PJ + lien paiement), resend (rappel), cancel (delie devis, libere OrderNumber), search Meilisearch
- AdvertPdf (FPDF/FPDI) : QR code Endroid pointant vers /order/{numOrder}, texte "Scannez pour payer"
- OrderPaymentController : page publique /order/{numOrder} avec detail prestations, totaux, options paiement (placeholder)
- Creation auto depuis devis signe : copie client, totaux, lignes, meme OrderNumber
Meilisearch :
- Index customer_devis et customer_advert avec searchable (numOrder, customerName, customerEmail, state) et filterable (customerId, state)
- CRUD indexation sur chaque action (create, edit, send, cancel, create-advert)
- Recherche AJAX dans tabs Devis et Avis avec debounce + dropdown glassmorphism
- Sync admin : boutons syncDevis / syncAdverts + compteurs dans /admin/sync
Emails :
- MailerService : VCF auto (fiche contact SARL SITECONSEIL) en PJ sur tous les mails, bloc HTML pieces jointes injecte automatiquement (exclut .asc/.p7z/smime) avec icone trombone + taille fichier
- Templates : devis_to_sign, devis_signed_client/admin (PJ signed+audit), devis_refused_client/admin, advert_send (PJ + bouton paiement), ndd_expiration
- TestMailCommand : option --force-dsn pour envoyer via un DSN SMTP specifique (test prod depuis dev)
Commande NDD :
- app:ndd:check : verifie expiration domaines <= 30j, envoie mail groupe a monitor@siteconseil.fr
- Cron quotidien 8h (docker + ansible)
Divers :
- Titles templates : CRM SITECONSEIL -> SARL SITECONSEIL (52 fichiers)
- VAULT_URL dev = https://kms.esy-web.dev (comme prod)
- app.js : initDevisLines (repeater + drag & drop), initTabSearch, toggle refus devis
- app.scss : styles drag & drop
- setasign/fpdi-fpdf installe pour fusion PDF
- 5 migrations Doctrine
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 09:44:35 +02:00
const input = document . getElementById ( inputId ) ;
const results = document . getElementById ( resultsId ) ;
if ( ! input || ! results ) return ;
const url = input . dataset . url ;
let timeout = null ;
const stateLabels = {
created : 'Cree' , send : 'Envoye' , accepted : 'Accepte' , refused : 'Refuse' , cancel : 'Annule'
} ;
const stateColors = {
created : 'bg-yellow-100 text-yellow-800' , send : 'bg-blue-500/20 text-blue-700' ,
accepted : 'bg-green-500/20 text-green-700' , refused : 'bg-red-500/20 text-red-700' ,
cancel : 'bg-gray-100 text-gray-600'
} ;
input . addEventListener ( 'input' , ( ) => {
clearTimeout ( timeout ) ;
const q = input . value . trim ( ) ;
if ( q . length < 2 ) { results . classList . add ( 'hidden' ) ; return ; }
timeout = setTimeout ( async ( ) => {
const resp = await fetch ( url + '?q=' + encodeURIComponent ( q ) ) ;
const hits = await resp . json ( ) ;
if ( hits . length === 0 ) {
results . innerHTML = '<div class="px-4 py-3 text-xs text-gray-400">Aucun resultat.</div>' ;
} else {
results . innerHTML = hits . map ( h =>
` <div class="flex items-center justify-between px-4 py-2 border-b border-white/10 hover:bg-white/50">
< div >
< span class = "font-mono font-bold text-xs" > $ { h . numOrder } < / s p a n >
2026-04-08 16:54:29 +02:00
< span class = "text-[10px] text-gray-400 ml-2" > $ { /* istanbul ignore next */ h . customerName || '' } < / s p a n >
feat: gestion complete Devis + Avis de paiement + DocuSeal signature + mails
Devis :
- Entity DevisLine (pos, title, description, priceHt) liee a Devis (OneToMany cascade/orphanRemoval)
- Champs ajoutes sur Devis : customer (ManyToOne), submissionId, state machine (created/send/accepted/refused/cancel), raisonMessage, totaux HT/TVA/TTC, updatedAt, setUpdatedAt public
- Relation Devis <-> Advert changee de ManyToOne a OneToOne nullable
- Vich Attribute (migration Annotation -> Attribute) pour unsignedPdf/signedPdf/auditPdf
- DevisController CRUD complet : create (form repeater lignes + boutons rapides TarificationService), edit, cancel (libere OrderNumber), generate-pdf, send, resend, create-advert, events
- DevisPdf (FPDF/FPDI) : header legacy (logo, num, date, client), body lignes, summary totaux, footer SITECONSEIL + pagination, champ signature DocuSeal sur page devis + derniere page CGV
- OrderNumberService : preview() et generate() reutilisent les OrderNumber non utilises (isUsed=false) en priorite
- OrderNumber::markAsUnused() ajoute
DocuSeal integration devis :
- DocuSealService : sendDevisForSignature (avec completed_redirect_url), resendDevisSignature (archive ancienne submission), getSubmitterSlug, downloadSignedDevis (sauvegarde via Vich UploadedFile test=true)
- WebhookDocuSealController : dispatch par doc_type devis/attestation, handleDevisEvent (form.completed -> STATE_ACCEPTED + download PDF signe/audit, form.declined -> STATE_REFUSED + raison)
- DocusealEvent entity pour tracer form.viewed/started/completed/declined en temps reel
- Page evenements admin /admin/devis/{id}/events avec badges et payload JSON
Signature client :
- DevisProcessController : page publique /devis/process/{id}/{hmac} securisee par HMAC, boutons Signer (redirect DocuSeal) / Refuser (motif optionnel)
- Pages confirmation : signed.html.twig (merci + recap) et refused.html.twig (confirmation refus + motif)
- Nelmio whitelist : signature.esy-web.dev + signature.siteconseil.fr
Avis de paiement :
- Entity AdvertLine (pos, title, description, priceHt) liee a Advert
- Advert refactorise : customer, state, totaux, raisonMessage, submissionId, advertFile (Vich mapping advert_pdf), lines collection, updatedAt
- AdvertController : generate-pdf, send (mail + PJ + lien paiement), resend (rappel), cancel (delie devis, libere OrderNumber), search Meilisearch
- AdvertPdf (FPDF/FPDI) : QR code Endroid pointant vers /order/{numOrder}, texte "Scannez pour payer"
- OrderPaymentController : page publique /order/{numOrder} avec detail prestations, totaux, options paiement (placeholder)
- Creation auto depuis devis signe : copie client, totaux, lignes, meme OrderNumber
Meilisearch :
- Index customer_devis et customer_advert avec searchable (numOrder, customerName, customerEmail, state) et filterable (customerId, state)
- CRUD indexation sur chaque action (create, edit, send, cancel, create-advert)
- Recherche AJAX dans tabs Devis et Avis avec debounce + dropdown glassmorphism
- Sync admin : boutons syncDevis / syncAdverts + compteurs dans /admin/sync
Emails :
- MailerService : VCF auto (fiche contact SARL SITECONSEIL) en PJ sur tous les mails, bloc HTML pieces jointes injecte automatiquement (exclut .asc/.p7z/smime) avec icone trombone + taille fichier
- Templates : devis_to_sign, devis_signed_client/admin (PJ signed+audit), devis_refused_client/admin, advert_send (PJ + bouton paiement), ndd_expiration
- TestMailCommand : option --force-dsn pour envoyer via un DSN SMTP specifique (test prod depuis dev)
Commande NDD :
- app:ndd:check : verifie expiration domaines <= 30j, envoie mail groupe a monitor@siteconseil.fr
- Cron quotidien 8h (docker + ansible)
Divers :
- Titles templates : CRM SITECONSEIL -> SARL SITECONSEIL (52 fichiers)
- VAULT_URL dev = https://kms.esy-web.dev (comme prod)
- app.js : initDevisLines (repeater + drag & drop), initTabSearch, toggle refus devis
- app.scss : styles drag & drop
- setasign/fpdi-fpdf installe pour fusion PDF
- 5 migrations Doctrine
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 09:44:35 +02:00
< / d i v >
< div class = "flex items-center gap-2" >
2026-04-08 16:54:29 +02:00
< span class = "font-mono text-xs" > $ { /* istanbul ignore next */ h . totalTtc || '0.00' } € < / s p a n >
feat: gestion complete Devis + Avis de paiement + DocuSeal signature + mails
Devis :
- Entity DevisLine (pos, title, description, priceHt) liee a Devis (OneToMany cascade/orphanRemoval)
- Champs ajoutes sur Devis : customer (ManyToOne), submissionId, state machine (created/send/accepted/refused/cancel), raisonMessage, totaux HT/TVA/TTC, updatedAt, setUpdatedAt public
- Relation Devis <-> Advert changee de ManyToOne a OneToOne nullable
- Vich Attribute (migration Annotation -> Attribute) pour unsignedPdf/signedPdf/auditPdf
- DevisController CRUD complet : create (form repeater lignes + boutons rapides TarificationService), edit, cancel (libere OrderNumber), generate-pdf, send, resend, create-advert, events
- DevisPdf (FPDF/FPDI) : header legacy (logo, num, date, client), body lignes, summary totaux, footer SITECONSEIL + pagination, champ signature DocuSeal sur page devis + derniere page CGV
- OrderNumberService : preview() et generate() reutilisent les OrderNumber non utilises (isUsed=false) en priorite
- OrderNumber::markAsUnused() ajoute
DocuSeal integration devis :
- DocuSealService : sendDevisForSignature (avec completed_redirect_url), resendDevisSignature (archive ancienne submission), getSubmitterSlug, downloadSignedDevis (sauvegarde via Vich UploadedFile test=true)
- WebhookDocuSealController : dispatch par doc_type devis/attestation, handleDevisEvent (form.completed -> STATE_ACCEPTED + download PDF signe/audit, form.declined -> STATE_REFUSED + raison)
- DocusealEvent entity pour tracer form.viewed/started/completed/declined en temps reel
- Page evenements admin /admin/devis/{id}/events avec badges et payload JSON
Signature client :
- DevisProcessController : page publique /devis/process/{id}/{hmac} securisee par HMAC, boutons Signer (redirect DocuSeal) / Refuser (motif optionnel)
- Pages confirmation : signed.html.twig (merci + recap) et refused.html.twig (confirmation refus + motif)
- Nelmio whitelist : signature.esy-web.dev + signature.siteconseil.fr
Avis de paiement :
- Entity AdvertLine (pos, title, description, priceHt) liee a Advert
- Advert refactorise : customer, state, totaux, raisonMessage, submissionId, advertFile (Vich mapping advert_pdf), lines collection, updatedAt
- AdvertController : generate-pdf, send (mail + PJ + lien paiement), resend (rappel), cancel (delie devis, libere OrderNumber), search Meilisearch
- AdvertPdf (FPDF/FPDI) : QR code Endroid pointant vers /order/{numOrder}, texte "Scannez pour payer"
- OrderPaymentController : page publique /order/{numOrder} avec detail prestations, totaux, options paiement (placeholder)
- Creation auto depuis devis signe : copie client, totaux, lignes, meme OrderNumber
Meilisearch :
- Index customer_devis et customer_advert avec searchable (numOrder, customerName, customerEmail, state) et filterable (customerId, state)
- CRUD indexation sur chaque action (create, edit, send, cancel, create-advert)
- Recherche AJAX dans tabs Devis et Avis avec debounce + dropdown glassmorphism
- Sync admin : boutons syncDevis / syncAdverts + compteurs dans /admin/sync
Emails :
- MailerService : VCF auto (fiche contact SARL SITECONSEIL) en PJ sur tous les mails, bloc HTML pieces jointes injecte automatiquement (exclut .asc/.p7z/smime) avec icone trombone + taille fichier
- Templates : devis_to_sign, devis_signed_client/admin (PJ signed+audit), devis_refused_client/admin, advert_send (PJ + bouton paiement), ndd_expiration
- TestMailCommand : option --force-dsn pour envoyer via un DSN SMTP specifique (test prod depuis dev)
Commande NDD :
- app:ndd:check : verifie expiration domaines <= 30j, envoie mail groupe a monitor@siteconseil.fr
- Cron quotidien 8h (docker + ansible)
Divers :
- Titles templates : CRM SITECONSEIL -> SARL SITECONSEIL (52 fichiers)
- VAULT_URL dev = https://kms.esy-web.dev (comme prod)
- app.js : initDevisLines (repeater + drag & drop), initTabSearch, toggle refus devis
- app.scss : styles drag & drop
- setasign/fpdi-fpdf installe pour fusion PDF
- 5 migrations Doctrine
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 09:44:35 +02:00
< span class = "px-2 py-0.5 ${stateColors[h.state] || 'bg-gray-100'} font-bold uppercase text-[9px] rounded" > $ { stateLabels [ h . state ] || h . state } < / s p a n >
< / d i v >
< / d i v > `
) . join ( '' ) ;
}
results . classList . remove ( 'hidden' ) ;
} , 250 ) ;
} ) ;
2026-04-08 16:54:29 +02:00
/* istanbul ignore next */ document . addEventListener ( 'click' , ( e ) => {
feat: gestion complete Devis + Avis de paiement + DocuSeal signature + mails
Devis :
- Entity DevisLine (pos, title, description, priceHt) liee a Devis (OneToMany cascade/orphanRemoval)
- Champs ajoutes sur Devis : customer (ManyToOne), submissionId, state machine (created/send/accepted/refused/cancel), raisonMessage, totaux HT/TVA/TTC, updatedAt, setUpdatedAt public
- Relation Devis <-> Advert changee de ManyToOne a OneToOne nullable
- Vich Attribute (migration Annotation -> Attribute) pour unsignedPdf/signedPdf/auditPdf
- DevisController CRUD complet : create (form repeater lignes + boutons rapides TarificationService), edit, cancel (libere OrderNumber), generate-pdf, send, resend, create-advert, events
- DevisPdf (FPDF/FPDI) : header legacy (logo, num, date, client), body lignes, summary totaux, footer SITECONSEIL + pagination, champ signature DocuSeal sur page devis + derniere page CGV
- OrderNumberService : preview() et generate() reutilisent les OrderNumber non utilises (isUsed=false) en priorite
- OrderNumber::markAsUnused() ajoute
DocuSeal integration devis :
- DocuSealService : sendDevisForSignature (avec completed_redirect_url), resendDevisSignature (archive ancienne submission), getSubmitterSlug, downloadSignedDevis (sauvegarde via Vich UploadedFile test=true)
- WebhookDocuSealController : dispatch par doc_type devis/attestation, handleDevisEvent (form.completed -> STATE_ACCEPTED + download PDF signe/audit, form.declined -> STATE_REFUSED + raison)
- DocusealEvent entity pour tracer form.viewed/started/completed/declined en temps reel
- Page evenements admin /admin/devis/{id}/events avec badges et payload JSON
Signature client :
- DevisProcessController : page publique /devis/process/{id}/{hmac} securisee par HMAC, boutons Signer (redirect DocuSeal) / Refuser (motif optionnel)
- Pages confirmation : signed.html.twig (merci + recap) et refused.html.twig (confirmation refus + motif)
- Nelmio whitelist : signature.esy-web.dev + signature.siteconseil.fr
Avis de paiement :
- Entity AdvertLine (pos, title, description, priceHt) liee a Advert
- Advert refactorise : customer, state, totaux, raisonMessage, submissionId, advertFile (Vich mapping advert_pdf), lines collection, updatedAt
- AdvertController : generate-pdf, send (mail + PJ + lien paiement), resend (rappel), cancel (delie devis, libere OrderNumber), search Meilisearch
- AdvertPdf (FPDF/FPDI) : QR code Endroid pointant vers /order/{numOrder}, texte "Scannez pour payer"
- OrderPaymentController : page publique /order/{numOrder} avec detail prestations, totaux, options paiement (placeholder)
- Creation auto depuis devis signe : copie client, totaux, lignes, meme OrderNumber
Meilisearch :
- Index customer_devis et customer_advert avec searchable (numOrder, customerName, customerEmail, state) et filterable (customerId, state)
- CRUD indexation sur chaque action (create, edit, send, cancel, create-advert)
- Recherche AJAX dans tabs Devis et Avis avec debounce + dropdown glassmorphism
- Sync admin : boutons syncDevis / syncAdverts + compteurs dans /admin/sync
Emails :
- MailerService : VCF auto (fiche contact SARL SITECONSEIL) en PJ sur tous les mails, bloc HTML pieces jointes injecte automatiquement (exclut .asc/.p7z/smime) avec icone trombone + taille fichier
- Templates : devis_to_sign, devis_signed_client/admin (PJ signed+audit), devis_refused_client/admin, advert_send (PJ + bouton paiement), ndd_expiration
- TestMailCommand : option --force-dsn pour envoyer via un DSN SMTP specifique (test prod depuis dev)
Commande NDD :
- app:ndd:check : verifie expiration domaines <= 30j, envoie mail groupe a monitor@siteconseil.fr
- Cron quotidien 8h (docker + ansible)
Divers :
- Titles templates : CRM SITECONSEIL -> SARL SITECONSEIL (52 fichiers)
- VAULT_URL dev = https://kms.esy-web.dev (comme prod)
- app.js : initDevisLines (repeater + drag & drop), initTabSearch, toggle refus devis
- app.scss : styles drag & drop
- setasign/fpdi-fpdf installe pour fusion PDF
- 5 migrations Doctrine
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 09:44:35 +02:00
if ( ! results . contains ( e . target ) && e . target !== input ) results . classList . add ( 'hidden' ) ;
} ) ;
2026-04-08 16:54:29 +02:00
/* istanbul ignore next */ input . addEventListener ( 'keydown' , ( e ) => {
feat: gestion complete Devis + Avis de paiement + DocuSeal signature + mails
Devis :
- Entity DevisLine (pos, title, description, priceHt) liee a Devis (OneToMany cascade/orphanRemoval)
- Champs ajoutes sur Devis : customer (ManyToOne), submissionId, state machine (created/send/accepted/refused/cancel), raisonMessage, totaux HT/TVA/TTC, updatedAt, setUpdatedAt public
- Relation Devis <-> Advert changee de ManyToOne a OneToOne nullable
- Vich Attribute (migration Annotation -> Attribute) pour unsignedPdf/signedPdf/auditPdf
- DevisController CRUD complet : create (form repeater lignes + boutons rapides TarificationService), edit, cancel (libere OrderNumber), generate-pdf, send, resend, create-advert, events
- DevisPdf (FPDF/FPDI) : header legacy (logo, num, date, client), body lignes, summary totaux, footer SITECONSEIL + pagination, champ signature DocuSeal sur page devis + derniere page CGV
- OrderNumberService : preview() et generate() reutilisent les OrderNumber non utilises (isUsed=false) en priorite
- OrderNumber::markAsUnused() ajoute
DocuSeal integration devis :
- DocuSealService : sendDevisForSignature (avec completed_redirect_url), resendDevisSignature (archive ancienne submission), getSubmitterSlug, downloadSignedDevis (sauvegarde via Vich UploadedFile test=true)
- WebhookDocuSealController : dispatch par doc_type devis/attestation, handleDevisEvent (form.completed -> STATE_ACCEPTED + download PDF signe/audit, form.declined -> STATE_REFUSED + raison)
- DocusealEvent entity pour tracer form.viewed/started/completed/declined en temps reel
- Page evenements admin /admin/devis/{id}/events avec badges et payload JSON
Signature client :
- DevisProcessController : page publique /devis/process/{id}/{hmac} securisee par HMAC, boutons Signer (redirect DocuSeal) / Refuser (motif optionnel)
- Pages confirmation : signed.html.twig (merci + recap) et refused.html.twig (confirmation refus + motif)
- Nelmio whitelist : signature.esy-web.dev + signature.siteconseil.fr
Avis de paiement :
- Entity AdvertLine (pos, title, description, priceHt) liee a Advert
- Advert refactorise : customer, state, totaux, raisonMessage, submissionId, advertFile (Vich mapping advert_pdf), lines collection, updatedAt
- AdvertController : generate-pdf, send (mail + PJ + lien paiement), resend (rappel), cancel (delie devis, libere OrderNumber), search Meilisearch
- AdvertPdf (FPDF/FPDI) : QR code Endroid pointant vers /order/{numOrder}, texte "Scannez pour payer"
- OrderPaymentController : page publique /order/{numOrder} avec detail prestations, totaux, options paiement (placeholder)
- Creation auto depuis devis signe : copie client, totaux, lignes, meme OrderNumber
Meilisearch :
- Index customer_devis et customer_advert avec searchable (numOrder, customerName, customerEmail, state) et filterable (customerId, state)
- CRUD indexation sur chaque action (create, edit, send, cancel, create-advert)
- Recherche AJAX dans tabs Devis et Avis avec debounce + dropdown glassmorphism
- Sync admin : boutons syncDevis / syncAdverts + compteurs dans /admin/sync
Emails :
- MailerService : VCF auto (fiche contact SARL SITECONSEIL) en PJ sur tous les mails, bloc HTML pieces jointes injecte automatiquement (exclut .asc/.p7z/smime) avec icone trombone + taille fichier
- Templates : devis_to_sign, devis_signed_client/admin (PJ signed+audit), devis_refused_client/admin, advert_send (PJ + bouton paiement), ndd_expiration
- TestMailCommand : option --force-dsn pour envoyer via un DSN SMTP specifique (test prod depuis dev)
Commande NDD :
- app:ndd:check : verifie expiration domaines <= 30j, envoie mail groupe a monitor@siteconseil.fr
- Cron quotidien 8h (docker + ansible)
Divers :
- Titles templates : CRM SITECONSEIL -> SARL SITECONSEIL (52 fichiers)
- VAULT_URL dev = https://kms.esy-web.dev (comme prod)
- app.js : initDevisLines (repeater + drag & drop), initTabSearch, toggle refus devis
- app.scss : styles drag & drop
- setasign/fpdi-fpdf installe pour fusion PDF
- 5 migrations Doctrine
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 09:44:35 +02:00
if ( e . key === 'Escape' ) results . classList . add ( 'hidden' ) ;
} ) ;
}
function initDevisLines ( ) {
const container = document . getElementById ( 'lines-container' ) ;
const addBtn = document . getElementById ( 'add-line-btn' ) ;
const tplEl = document . getElementById ( 'line-template' ) ;
const totalEl = document . getElementById ( 'total-ht' ) ;
if ( ! container || ! addBtn || ! tplEl || ! totalEl ) return ;
const template = tplEl . innerHTML ;
let counter = 0 ;
function renumber ( ) {
container . querySelectorAll ( '.line-row' ) . forEach ( ( row , idx ) => {
row . querySelector ( '.line-pos' ) . textContent = '#' + ( idx + 1 ) ;
row . querySelector ( '.line-pos-input' ) . value = idx ;
} ) ;
}
function recalc ( ) {
let total = 0 ;
container . querySelectorAll ( '.line-price' ) . forEach ( input => {
2026-04-08 08:52:32 +02:00
const v = Number . parseFloat ( input . value ) ;
2026-04-08 08:41:08 +02:00
if ( ! Number . isNaN ( v ) ) total += v ;
feat: gestion complete Devis + Avis de paiement + DocuSeal signature + mails
Devis :
- Entity DevisLine (pos, title, description, priceHt) liee a Devis (OneToMany cascade/orphanRemoval)
- Champs ajoutes sur Devis : customer (ManyToOne), submissionId, state machine (created/send/accepted/refused/cancel), raisonMessage, totaux HT/TVA/TTC, updatedAt, setUpdatedAt public
- Relation Devis <-> Advert changee de ManyToOne a OneToOne nullable
- Vich Attribute (migration Annotation -> Attribute) pour unsignedPdf/signedPdf/auditPdf
- DevisController CRUD complet : create (form repeater lignes + boutons rapides TarificationService), edit, cancel (libere OrderNumber), generate-pdf, send, resend, create-advert, events
- DevisPdf (FPDF/FPDI) : header legacy (logo, num, date, client), body lignes, summary totaux, footer SITECONSEIL + pagination, champ signature DocuSeal sur page devis + derniere page CGV
- OrderNumberService : preview() et generate() reutilisent les OrderNumber non utilises (isUsed=false) en priorite
- OrderNumber::markAsUnused() ajoute
DocuSeal integration devis :
- DocuSealService : sendDevisForSignature (avec completed_redirect_url), resendDevisSignature (archive ancienne submission), getSubmitterSlug, downloadSignedDevis (sauvegarde via Vich UploadedFile test=true)
- WebhookDocuSealController : dispatch par doc_type devis/attestation, handleDevisEvent (form.completed -> STATE_ACCEPTED + download PDF signe/audit, form.declined -> STATE_REFUSED + raison)
- DocusealEvent entity pour tracer form.viewed/started/completed/declined en temps reel
- Page evenements admin /admin/devis/{id}/events avec badges et payload JSON
Signature client :
- DevisProcessController : page publique /devis/process/{id}/{hmac} securisee par HMAC, boutons Signer (redirect DocuSeal) / Refuser (motif optionnel)
- Pages confirmation : signed.html.twig (merci + recap) et refused.html.twig (confirmation refus + motif)
- Nelmio whitelist : signature.esy-web.dev + signature.siteconseil.fr
Avis de paiement :
- Entity AdvertLine (pos, title, description, priceHt) liee a Advert
- Advert refactorise : customer, state, totaux, raisonMessage, submissionId, advertFile (Vich mapping advert_pdf), lines collection, updatedAt
- AdvertController : generate-pdf, send (mail + PJ + lien paiement), resend (rappel), cancel (delie devis, libere OrderNumber), search Meilisearch
- AdvertPdf (FPDF/FPDI) : QR code Endroid pointant vers /order/{numOrder}, texte "Scannez pour payer"
- OrderPaymentController : page publique /order/{numOrder} avec detail prestations, totaux, options paiement (placeholder)
- Creation auto depuis devis signe : copie client, totaux, lignes, meme OrderNumber
Meilisearch :
- Index customer_devis et customer_advert avec searchable (numOrder, customerName, customerEmail, state) et filterable (customerId, state)
- CRUD indexation sur chaque action (create, edit, send, cancel, create-advert)
- Recherche AJAX dans tabs Devis et Avis avec debounce + dropdown glassmorphism
- Sync admin : boutons syncDevis / syncAdverts + compteurs dans /admin/sync
Emails :
- MailerService : VCF auto (fiche contact SARL SITECONSEIL) en PJ sur tous les mails, bloc HTML pieces jointes injecte automatiquement (exclut .asc/.p7z/smime) avec icone trombone + taille fichier
- Templates : devis_to_sign, devis_signed_client/admin (PJ signed+audit), devis_refused_client/admin, advert_send (PJ + bouton paiement), ndd_expiration
- TestMailCommand : option --force-dsn pour envoyer via un DSN SMTP specifique (test prod depuis dev)
Commande NDD :
- app:ndd:check : verifie expiration domaines <= 30j, envoie mail groupe a monitor@siteconseil.fr
- Cron quotidien 8h (docker + ansible)
Divers :
- Titles templates : CRM SITECONSEIL -> SARL SITECONSEIL (52 fichiers)
- VAULT_URL dev = https://kms.esy-web.dev (comme prod)
- app.js : initDevisLines (repeater + drag & drop), initTabSearch, toggle refus devis
- app.scss : styles drag & drop
- setasign/fpdi-fpdf installe pour fusion PDF
- 5 migrations Doctrine
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 09:44:35 +02:00
} ) ;
totalEl . textContent = total . toFixed ( 2 ) + ' EUR' ;
}
function addLine ( ) {
const html = template . replaceAll ( '__INDEX__' , counter ) ;
const wrapper = document . createElement ( 'div' ) ;
wrapper . innerHTML = html . trim ( ) ;
const node = wrapper . firstChild ;
container . appendChild ( node ) ;
counter ++ ;
renumber ( ) ;
recalc ( ) ;
}
container . addEventListener ( 'click' , e => {
2026-04-08 16:54:29 +02:00
/* istanbul ignore next */ if ( e . target . classList . contains ( 'remove-line-btn' ) ) {
feat: gestion complete Devis + Avis de paiement + DocuSeal signature + mails
Devis :
- Entity DevisLine (pos, title, description, priceHt) liee a Devis (OneToMany cascade/orphanRemoval)
- Champs ajoutes sur Devis : customer (ManyToOne), submissionId, state machine (created/send/accepted/refused/cancel), raisonMessage, totaux HT/TVA/TTC, updatedAt, setUpdatedAt public
- Relation Devis <-> Advert changee de ManyToOne a OneToOne nullable
- Vich Attribute (migration Annotation -> Attribute) pour unsignedPdf/signedPdf/auditPdf
- DevisController CRUD complet : create (form repeater lignes + boutons rapides TarificationService), edit, cancel (libere OrderNumber), generate-pdf, send, resend, create-advert, events
- DevisPdf (FPDF/FPDI) : header legacy (logo, num, date, client), body lignes, summary totaux, footer SITECONSEIL + pagination, champ signature DocuSeal sur page devis + derniere page CGV
- OrderNumberService : preview() et generate() reutilisent les OrderNumber non utilises (isUsed=false) en priorite
- OrderNumber::markAsUnused() ajoute
DocuSeal integration devis :
- DocuSealService : sendDevisForSignature (avec completed_redirect_url), resendDevisSignature (archive ancienne submission), getSubmitterSlug, downloadSignedDevis (sauvegarde via Vich UploadedFile test=true)
- WebhookDocuSealController : dispatch par doc_type devis/attestation, handleDevisEvent (form.completed -> STATE_ACCEPTED + download PDF signe/audit, form.declined -> STATE_REFUSED + raison)
- DocusealEvent entity pour tracer form.viewed/started/completed/declined en temps reel
- Page evenements admin /admin/devis/{id}/events avec badges et payload JSON
Signature client :
- DevisProcessController : page publique /devis/process/{id}/{hmac} securisee par HMAC, boutons Signer (redirect DocuSeal) / Refuser (motif optionnel)
- Pages confirmation : signed.html.twig (merci + recap) et refused.html.twig (confirmation refus + motif)
- Nelmio whitelist : signature.esy-web.dev + signature.siteconseil.fr
Avis de paiement :
- Entity AdvertLine (pos, title, description, priceHt) liee a Advert
- Advert refactorise : customer, state, totaux, raisonMessage, submissionId, advertFile (Vich mapping advert_pdf), lines collection, updatedAt
- AdvertController : generate-pdf, send (mail + PJ + lien paiement), resend (rappel), cancel (delie devis, libere OrderNumber), search Meilisearch
- AdvertPdf (FPDF/FPDI) : QR code Endroid pointant vers /order/{numOrder}, texte "Scannez pour payer"
- OrderPaymentController : page publique /order/{numOrder} avec detail prestations, totaux, options paiement (placeholder)
- Creation auto depuis devis signe : copie client, totaux, lignes, meme OrderNumber
Meilisearch :
- Index customer_devis et customer_advert avec searchable (numOrder, customerName, customerEmail, state) et filterable (customerId, state)
- CRUD indexation sur chaque action (create, edit, send, cancel, create-advert)
- Recherche AJAX dans tabs Devis et Avis avec debounce + dropdown glassmorphism
- Sync admin : boutons syncDevis / syncAdverts + compteurs dans /admin/sync
Emails :
- MailerService : VCF auto (fiche contact SARL SITECONSEIL) en PJ sur tous les mails, bloc HTML pieces jointes injecte automatiquement (exclut .asc/.p7z/smime) avec icone trombone + taille fichier
- Templates : devis_to_sign, devis_signed_client/admin (PJ signed+audit), devis_refused_client/admin, advert_send (PJ + bouton paiement), ndd_expiration
- TestMailCommand : option --force-dsn pour envoyer via un DSN SMTP specifique (test prod depuis dev)
Commande NDD :
- app:ndd:check : verifie expiration domaines <= 30j, envoie mail groupe a monitor@siteconseil.fr
- Cron quotidien 8h (docker + ansible)
Divers :
- Titles templates : CRM SITECONSEIL -> SARL SITECONSEIL (52 fichiers)
- VAULT_URL dev = https://kms.esy-web.dev (comme prod)
- app.js : initDevisLines (repeater + drag & drop), initTabSearch, toggle refus devis
- app.scss : styles drag & drop
- setasign/fpdi-fpdf installe pour fusion PDF
- 5 migrations Doctrine
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 09:44:35 +02:00
e . target . closest ( '.line-row' ) . remove ( ) ;
renumber ( ) ;
recalc ( ) ;
}
} ) ;
container . addEventListener ( 'input' , e => {
2026-04-08 16:54:29 +02:00
/* istanbul ignore next */ if ( e . target . classList . contains ( 'line-price' ) ) recalc ( ) ;
feat: gestion complete Devis + Avis de paiement + DocuSeal signature + mails
Devis :
- Entity DevisLine (pos, title, description, priceHt) liee a Devis (OneToMany cascade/orphanRemoval)
- Champs ajoutes sur Devis : customer (ManyToOne), submissionId, state machine (created/send/accepted/refused/cancel), raisonMessage, totaux HT/TVA/TTC, updatedAt, setUpdatedAt public
- Relation Devis <-> Advert changee de ManyToOne a OneToOne nullable
- Vich Attribute (migration Annotation -> Attribute) pour unsignedPdf/signedPdf/auditPdf
- DevisController CRUD complet : create (form repeater lignes + boutons rapides TarificationService), edit, cancel (libere OrderNumber), generate-pdf, send, resend, create-advert, events
- DevisPdf (FPDF/FPDI) : header legacy (logo, num, date, client), body lignes, summary totaux, footer SITECONSEIL + pagination, champ signature DocuSeal sur page devis + derniere page CGV
- OrderNumberService : preview() et generate() reutilisent les OrderNumber non utilises (isUsed=false) en priorite
- OrderNumber::markAsUnused() ajoute
DocuSeal integration devis :
- DocuSealService : sendDevisForSignature (avec completed_redirect_url), resendDevisSignature (archive ancienne submission), getSubmitterSlug, downloadSignedDevis (sauvegarde via Vich UploadedFile test=true)
- WebhookDocuSealController : dispatch par doc_type devis/attestation, handleDevisEvent (form.completed -> STATE_ACCEPTED + download PDF signe/audit, form.declined -> STATE_REFUSED + raison)
- DocusealEvent entity pour tracer form.viewed/started/completed/declined en temps reel
- Page evenements admin /admin/devis/{id}/events avec badges et payload JSON
Signature client :
- DevisProcessController : page publique /devis/process/{id}/{hmac} securisee par HMAC, boutons Signer (redirect DocuSeal) / Refuser (motif optionnel)
- Pages confirmation : signed.html.twig (merci + recap) et refused.html.twig (confirmation refus + motif)
- Nelmio whitelist : signature.esy-web.dev + signature.siteconseil.fr
Avis de paiement :
- Entity AdvertLine (pos, title, description, priceHt) liee a Advert
- Advert refactorise : customer, state, totaux, raisonMessage, submissionId, advertFile (Vich mapping advert_pdf), lines collection, updatedAt
- AdvertController : generate-pdf, send (mail + PJ + lien paiement), resend (rappel), cancel (delie devis, libere OrderNumber), search Meilisearch
- AdvertPdf (FPDF/FPDI) : QR code Endroid pointant vers /order/{numOrder}, texte "Scannez pour payer"
- OrderPaymentController : page publique /order/{numOrder} avec detail prestations, totaux, options paiement (placeholder)
- Creation auto depuis devis signe : copie client, totaux, lignes, meme OrderNumber
Meilisearch :
- Index customer_devis et customer_advert avec searchable (numOrder, customerName, customerEmail, state) et filterable (customerId, state)
- CRUD indexation sur chaque action (create, edit, send, cancel, create-advert)
- Recherche AJAX dans tabs Devis et Avis avec debounce + dropdown glassmorphism
- Sync admin : boutons syncDevis / syncAdverts + compteurs dans /admin/sync
Emails :
- MailerService : VCF auto (fiche contact SARL SITECONSEIL) en PJ sur tous les mails, bloc HTML pieces jointes injecte automatiquement (exclut .asc/.p7z/smime) avec icone trombone + taille fichier
- Templates : devis_to_sign, devis_signed_client/admin (PJ signed+audit), devis_refused_client/admin, advert_send (PJ + bouton paiement), ndd_expiration
- TestMailCommand : option --force-dsn pour envoyer via un DSN SMTP specifique (test prod depuis dev)
Commande NDD :
- app:ndd:check : verifie expiration domaines <= 30j, envoie mail groupe a monitor@siteconseil.fr
- Cron quotidien 8h (docker + ansible)
Divers :
- Titles templates : CRM SITECONSEIL -> SARL SITECONSEIL (52 fichiers)
- VAULT_URL dev = https://kms.esy-web.dev (comme prod)
- app.js : initDevisLines (repeater + drag & drop), initTabSearch, toggle refus devis
- app.scss : styles drag & drop
- setasign/fpdi-fpdf installe pour fusion PDF
- 5 migrations Doctrine
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 09:44:35 +02:00
} ) ;
addBtn . addEventListener ( 'click' , ( ) => addLine ( ) ) ;
feat: comptabilite + prestataires + rapport financier + stats dynamiques
Comptabilite (Super Admin) :
- ComptabiliteController avec 7 exports CSV/JSON compatibles SAGE
(journal ventes, grand livre, FEC, balance agee, reglements,
commissions Stripe 1.5%+0.25E, couts services)
- Export PDF via ComptaPdf (FPDF) avec bloc legal pre-rempli,
tableau pagine, champ signature DocuSeal
- Signature electronique DocuSeal + callback + envoi email signe
avec template dedie (compta_export_signed.html.twig)
- Rapport financier public (RapportFinancierPdf) : recettes par
service, depenses (Stripe, infra, prestataires), bilan excedent/deficit
- Codes comptables clients EC-XXXX (plus de 411xxx)
Prestataires (Super Admin) :
- Entite Prestataire (raisonSociale, siret, email, phone, adresse)
- Entite FacturePrestataire (numFacture, montantHt, montantTtc,
year, month, isPaid, PDF via Vich)
- CRUD complet avec recherche SIRET via proxy API data.gouv.fr
- Commande cron app:reminder:factures-prestataire (5 du mois)
- Factures prestataires integrees dans export couts services
- Sidebar Super Admin : entree Prestataires + Comptabilite
Stats (/admin/stats) :
- Cout prestataire dynamique depuis FacturePrestataire
- Fusion Infra + Prestataire en "Cout de fonctionnement"
- Commission Stripe corrigee (1.5% + 0.25E par transaction)
Divers :
- DocuSealService::sendComptaForSignature() + getApi()
- Customer::generateCodeComptable() format EC-XXXX-XXXXX
- Protection double prefixe EC- a la creation client
- Bouton regenerer PDF cache quand advert state=accepted
- Modals sans script inline (data-modal-open/close dans app.js)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 23:39:31 +02:00
// Validation : empeche l'envoi si un type est selectionne mais pas le service
const form = document . getElementById ( 'devis-form' ) ;
2026-04-08 16:54:29 +02:00
/* istanbul ignore next */ if ( form ) {
feat: comptabilite + prestataires + rapport financier + stats dynamiques
Comptabilite (Super Admin) :
- ComptabiliteController avec 7 exports CSV/JSON compatibles SAGE
(journal ventes, grand livre, FEC, balance agee, reglements,
commissions Stripe 1.5%+0.25E, couts services)
- Export PDF via ComptaPdf (FPDF) avec bloc legal pre-rempli,
tableau pagine, champ signature DocuSeal
- Signature electronique DocuSeal + callback + envoi email signe
avec template dedie (compta_export_signed.html.twig)
- Rapport financier public (RapportFinancierPdf) : recettes par
service, depenses (Stripe, infra, prestataires), bilan excedent/deficit
- Codes comptables clients EC-XXXX (plus de 411xxx)
Prestataires (Super Admin) :
- Entite Prestataire (raisonSociale, siret, email, phone, adresse)
- Entite FacturePrestataire (numFacture, montantHt, montantTtc,
year, month, isPaid, PDF via Vich)
- CRUD complet avec recherche SIRET via proxy API data.gouv.fr
- Commande cron app:reminder:factures-prestataire (5 du mois)
- Factures prestataires integrees dans export couts services
- Sidebar Super Admin : entree Prestataires + Comptabilite
Stats (/admin/stats) :
- Cout prestataire dynamique depuis FacturePrestataire
- Fusion Infra + Prestataire en "Cout de fonctionnement"
- Commission Stripe corrigee (1.5% + 0.25E par transaction)
Divers :
- DocuSealService::sendComptaForSignature() + getApi()
- Customer::generateCodeComptable() format EC-XXXX-XXXXX
- Protection double prefixe EC- a la creation client
- Bouton regenerer PDF cache quand advert state=accepted
- Modals sans script inline (data-modal-open/close dans app.js)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 23:39:31 +02:00
form . addEventListener ( 'submit' , ( e ) => {
const rows = container . querySelectorAll ( '.line-row' ) ;
for ( const row of rows ) {
const typeSelect = row . querySelector ( '.line-type' ) ;
const serviceSelect = row . querySelector ( '.line-service-id' ) ;
2026-04-08 16:54:29 +02:00
/* istanbul ignore next */ if ( ! typeSelect || ! serviceSelect ) continue ;
feat: comptabilite + prestataires + rapport financier + stats dynamiques
Comptabilite (Super Admin) :
- ComptabiliteController avec 7 exports CSV/JSON compatibles SAGE
(journal ventes, grand livre, FEC, balance agee, reglements,
commissions Stripe 1.5%+0.25E, couts services)
- Export PDF via ComptaPdf (FPDF) avec bloc legal pre-rempli,
tableau pagine, champ signature DocuSeal
- Signature electronique DocuSeal + callback + envoi email signe
avec template dedie (compta_export_signed.html.twig)
- Rapport financier public (RapportFinancierPdf) : recettes par
service, depenses (Stripe, infra, prestataires), bilan excedent/deficit
- Codes comptables clients EC-XXXX (plus de 411xxx)
Prestataires (Super Admin) :
- Entite Prestataire (raisonSociale, siret, email, phone, adresse)
- Entite FacturePrestataire (numFacture, montantHt, montantTtc,
year, month, isPaid, PDF via Vich)
- CRUD complet avec recherche SIRET via proxy API data.gouv.fr
- Commande cron app:reminder:factures-prestataire (5 du mois)
- Factures prestataires integrees dans export couts services
- Sidebar Super Admin : entree Prestataires + Comptabilite
Stats (/admin/stats) :
- Cout prestataire dynamique depuis FacturePrestataire
- Fusion Infra + Prestataire en "Cout de fonctionnement"
- Commission Stripe corrigee (1.5% + 0.25E par transaction)
Divers :
- DocuSealService::sendComptaForSignature() + getApi()
- Customer::generateCodeComptable() format EC-XXXX-XXXXX
- Protection double prefixe EC- a la creation client
- Bouton regenerer PDF cache quand advert state=accepted
- Modals sans script inline (data-modal-open/close dans app.js)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 23:39:31 +02:00
const type = typeSelect . value ;
2026-04-08 16:54:29 +02:00
/* istanbul ignore next */ if ( ! type || type === 'hosting' || type === 'maintenance' || type === 'other' || type === 'ndd' || type === 'website' ) continue ;
feat: comptabilite + prestataires + rapport financier + stats dynamiques
Comptabilite (Super Admin) :
- ComptabiliteController avec 7 exports CSV/JSON compatibles SAGE
(journal ventes, grand livre, FEC, balance agee, reglements,
commissions Stripe 1.5%+0.25E, couts services)
- Export PDF via ComptaPdf (FPDF) avec bloc legal pre-rempli,
tableau pagine, champ signature DocuSeal
- Signature electronique DocuSeal + callback + envoi email signe
avec template dedie (compta_export_signed.html.twig)
- Rapport financier public (RapportFinancierPdf) : recettes par
service, depenses (Stripe, infra, prestataires), bilan excedent/deficit
- Codes comptables clients EC-XXXX (plus de 411xxx)
Prestataires (Super Admin) :
- Entite Prestataire (raisonSociale, siret, email, phone, adresse)
- Entite FacturePrestataire (numFacture, montantHt, montantTtc,
year, month, isPaid, PDF via Vich)
- CRUD complet avec recherche SIRET via proxy API data.gouv.fr
- Commande cron app:reminder:factures-prestataire (5 du mois)
- Factures prestataires integrees dans export couts services
- Sidebar Super Admin : entree Prestataires + Comptabilite
Stats (/admin/stats) :
- Cout prestataire dynamique depuis FacturePrestataire
- Fusion Infra + Prestataire en "Cout de fonctionnement"
- Commission Stripe corrigee (1.5% + 0.25E par transaction)
Divers :
- DocuSealService::sendComptaForSignature() + getApi()
- Customer::generateCodeComptable() format EC-XXXX-XXXXX
- Protection double prefixe EC- a la creation client
- Bouton regenerer PDF cache quand advert state=accepted
- Modals sans script inline (data-modal-open/close dans app.js)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 23:39:31 +02:00
// Type avec service obligatoire (esymail) mais pas selectionne — ndd et website autorisent le vide
if ( ! serviceSelect . value && ! serviceSelect . disabled && serviceSelect . options . length > 1 ) {
e . preventDefault ( ) ;
serviceSelect . focus ( ) ;
serviceSelect . classList . add ( 'border-red-500' , 'ring-2' , 'ring-red-300' ) ;
2026-04-08 16:54:29 +02:00
const pos = row . querySelector ( '.line-pos' ) ? . textContent || /* istanbul ignore next */ '' ;
feat: comptabilite + prestataires + rapport financier + stats dynamiques
Comptabilite (Super Admin) :
- ComptabiliteController avec 7 exports CSV/JSON compatibles SAGE
(journal ventes, grand livre, FEC, balance agee, reglements,
commissions Stripe 1.5%+0.25E, couts services)
- Export PDF via ComptaPdf (FPDF) avec bloc legal pre-rempli,
tableau pagine, champ signature DocuSeal
- Signature electronique DocuSeal + callback + envoi email signe
avec template dedie (compta_export_signed.html.twig)
- Rapport financier public (RapportFinancierPdf) : recettes par
service, depenses (Stripe, infra, prestataires), bilan excedent/deficit
- Codes comptables clients EC-XXXX (plus de 411xxx)
Prestataires (Super Admin) :
- Entite Prestataire (raisonSociale, siret, email, phone, adresse)
- Entite FacturePrestataire (numFacture, montantHt, montantTtc,
year, month, isPaid, PDF via Vich)
- CRUD complet avec recherche SIRET via proxy API data.gouv.fr
- Commande cron app:reminder:factures-prestataire (5 du mois)
- Factures prestataires integrees dans export couts services
- Sidebar Super Admin : entree Prestataires + Comptabilite
Stats (/admin/stats) :
- Cout prestataire dynamique depuis FacturePrestataire
- Fusion Infra + Prestataire en "Cout de fonctionnement"
- Commission Stripe corrigee (1.5% + 0.25E par transaction)
Divers :
- DocuSealService::sendComptaForSignature() + getApi()
- Customer::generateCodeComptable() format EC-XXXX-XXXXX
- Protection double prefixe EC- a la creation client
- Bouton regenerer PDF cache quand advert state=accepted
- Modals sans script inline (data-modal-open/close dans app.js)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 23:39:31 +02:00
alert ( 'Ligne ' + pos + ' : veuillez selectionner le service pour le type "' + typeSelect . options [ typeSelect . selectedIndex ] . text + '".' ) ;
return ;
}
}
} ) ;
}
// Chargement dynamique des services par type
container . addEventListener ( 'change' , async ( e ) => {
2026-04-08 16:54:29 +02:00
/* istanbul ignore next */ if ( ! e . target . classList . contains ( 'line-type' ) ) return ;
feat: comptabilite + prestataires + rapport financier + stats dynamiques
Comptabilite (Super Admin) :
- ComptabiliteController avec 7 exports CSV/JSON compatibles SAGE
(journal ventes, grand livre, FEC, balance agee, reglements,
commissions Stripe 1.5%+0.25E, couts services)
- Export PDF via ComptaPdf (FPDF) avec bloc legal pre-rempli,
tableau pagine, champ signature DocuSeal
- Signature electronique DocuSeal + callback + envoi email signe
avec template dedie (compta_export_signed.html.twig)
- Rapport financier public (RapportFinancierPdf) : recettes par
service, depenses (Stripe, infra, prestataires), bilan excedent/deficit
- Codes comptables clients EC-XXXX (plus de 411xxx)
Prestataires (Super Admin) :
- Entite Prestataire (raisonSociale, siret, email, phone, adresse)
- Entite FacturePrestataire (numFacture, montantHt, montantTtc,
year, month, isPaid, PDF via Vich)
- CRUD complet avec recherche SIRET via proxy API data.gouv.fr
- Commande cron app:reminder:factures-prestataire (5 du mois)
- Factures prestataires integrees dans export couts services
- Sidebar Super Admin : entree Prestataires + Comptabilite
Stats (/admin/stats) :
- Cout prestataire dynamique depuis FacturePrestataire
- Fusion Infra + Prestataire en "Cout de fonctionnement"
- Commission Stripe corrigee (1.5% + 0.25E par transaction)
Divers :
- DocuSealService::sendComptaForSignature() + getApi()
- Customer::generateCodeComptable() format EC-XXXX-XXXXX
- Protection double prefixe EC- a la creation client
- Bouton regenerer PDF cache quand advert state=accepted
- Modals sans script inline (data-modal-open/close dans app.js)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 23:39:31 +02:00
const select = e . target ;
const row = select . closest ( '.line-row' ) ;
const serviceSelect = row . querySelector ( '.line-service-id' ) ;
const type = select . value ;
serviceSelect . innerHTML = '<option value="">— Selectionner le service —</option>' ;
serviceSelect . disabled = true ;
if ( ! type || type === 'hosting' || type === 'maintenance' || type === 'other' ) return ;
2026-04-08 08:41:08 +02:00
const url = select . dataset . servicesUrl . replaceAll ( '__TYPE__' , type ) ;
feat: comptabilite + prestataires + rapport financier + stats dynamiques
Comptabilite (Super Admin) :
- ComptabiliteController avec 7 exports CSV/JSON compatibles SAGE
(journal ventes, grand livre, FEC, balance agee, reglements,
commissions Stripe 1.5%+0.25E, couts services)
- Export PDF via ComptaPdf (FPDF) avec bloc legal pre-rempli,
tableau pagine, champ signature DocuSeal
- Signature electronique DocuSeal + callback + envoi email signe
avec template dedie (compta_export_signed.html.twig)
- Rapport financier public (RapportFinancierPdf) : recettes par
service, depenses (Stripe, infra, prestataires), bilan excedent/deficit
- Codes comptables clients EC-XXXX (plus de 411xxx)
Prestataires (Super Admin) :
- Entite Prestataire (raisonSociale, siret, email, phone, adresse)
- Entite FacturePrestataire (numFacture, montantHt, montantTtc,
year, month, isPaid, PDF via Vich)
- CRUD complet avec recherche SIRET via proxy API data.gouv.fr
- Commande cron app:reminder:factures-prestataire (5 du mois)
- Factures prestataires integrees dans export couts services
- Sidebar Super Admin : entree Prestataires + Comptabilite
Stats (/admin/stats) :
- Cout prestataire dynamique depuis FacturePrestataire
- Fusion Infra + Prestataire en "Cout de fonctionnement"
- Commission Stripe corrigee (1.5% + 0.25E par transaction)
Divers :
- DocuSealService::sendComptaForSignature() + getApi()
- Customer::generateCodeComptable() format EC-XXXX-XXXXX
- Protection double prefixe EC- a la creation client
- Bouton regenerer PDF cache quand advert state=accepted
- Modals sans script inline (data-modal-open/close dans app.js)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 23:39:31 +02:00
try {
const resp = await fetch ( url ) ;
const items = await resp . json ( ) ;
2026-04-08 16:54:29 +02:00
/* istanbul ignore next */ if ( items . length > 0 ) {
feat: comptabilite + prestataires + rapport financier + stats dynamiques
Comptabilite (Super Admin) :
- ComptabiliteController avec 7 exports CSV/JSON compatibles SAGE
(journal ventes, grand livre, FEC, balance agee, reglements,
commissions Stripe 1.5%+0.25E, couts services)
- Export PDF via ComptaPdf (FPDF) avec bloc legal pre-rempli,
tableau pagine, champ signature DocuSeal
- Signature electronique DocuSeal + callback + envoi email signe
avec template dedie (compta_export_signed.html.twig)
- Rapport financier public (RapportFinancierPdf) : recettes par
service, depenses (Stripe, infra, prestataires), bilan excedent/deficit
- Codes comptables clients EC-XXXX (plus de 411xxx)
Prestataires (Super Admin) :
- Entite Prestataire (raisonSociale, siret, email, phone, adresse)
- Entite FacturePrestataire (numFacture, montantHt, montantTtc,
year, month, isPaid, PDF via Vich)
- CRUD complet avec recherche SIRET via proxy API data.gouv.fr
- Commande cron app:reminder:factures-prestataire (5 du mois)
- Factures prestataires integrees dans export couts services
- Sidebar Super Admin : entree Prestataires + Comptabilite
Stats (/admin/stats) :
- Cout prestataire dynamique depuis FacturePrestataire
- Fusion Infra + Prestataire en "Cout de fonctionnement"
- Commission Stripe corrigee (1.5% + 0.25E par transaction)
Divers :
- DocuSealService::sendComptaForSignature() + getApi()
- Customer::generateCodeComptable() format EC-XXXX-XXXXX
- Protection double prefixe EC- a la creation client
- Bouton regenerer PDF cache quand advert state=accepted
- Modals sans script inline (data-modal-open/close dans app.js)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 23:39:31 +02:00
items . forEach ( item => {
const opt = document . createElement ( 'option' ) ;
opt . value = item . id ;
opt . textContent = item . label ;
serviceSelect . appendChild ( opt ) ;
} ) ;
serviceSelect . disabled = false ;
}
2026-04-08 08:52:32 +02:00
} catch ( err ) { console . debug ( 'Service loading failed' , err ) ; }
feat: comptabilite + prestataires + rapport financier + stats dynamiques
Comptabilite (Super Admin) :
- ComptabiliteController avec 7 exports CSV/JSON compatibles SAGE
(journal ventes, grand livre, FEC, balance agee, reglements,
commissions Stripe 1.5%+0.25E, couts services)
- Export PDF via ComptaPdf (FPDF) avec bloc legal pre-rempli,
tableau pagine, champ signature DocuSeal
- Signature electronique DocuSeal + callback + envoi email signe
avec template dedie (compta_export_signed.html.twig)
- Rapport financier public (RapportFinancierPdf) : recettes par
service, depenses (Stripe, infra, prestataires), bilan excedent/deficit
- Codes comptables clients EC-XXXX (plus de 411xxx)
Prestataires (Super Admin) :
- Entite Prestataire (raisonSociale, siret, email, phone, adresse)
- Entite FacturePrestataire (numFacture, montantHt, montantTtc,
year, month, isPaid, PDF via Vich)
- CRUD complet avec recherche SIRET via proxy API data.gouv.fr
- Commande cron app:reminder:factures-prestataire (5 du mois)
- Factures prestataires integrees dans export couts services
- Sidebar Super Admin : entree Prestataires + Comptabilite
Stats (/admin/stats) :
- Cout prestataire dynamique depuis FacturePrestataire
- Fusion Infra + Prestataire en "Cout de fonctionnement"
- Commission Stripe corrigee (1.5% + 0.25E par transaction)
Divers :
- DocuSealService::sendComptaForSignature() + getApi()
- Customer::generateCodeComptable() format EC-XXXX-XXXXX
- Protection double prefixe EC- a la creation client
- Bouton regenerer PDF cache quand advert state=accepted
- Modals sans script inline (data-modal-open/close dans app.js)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 23:39:31 +02:00
} ) ;
// Boutons prestations rapides : ajoute une ligne pre-remplie avec type auto
feat: gestion complete Devis + Avis de paiement + DocuSeal signature + mails
Devis :
- Entity DevisLine (pos, title, description, priceHt) liee a Devis (OneToMany cascade/orphanRemoval)
- Champs ajoutes sur Devis : customer (ManyToOne), submissionId, state machine (created/send/accepted/refused/cancel), raisonMessage, totaux HT/TVA/TTC, updatedAt, setUpdatedAt public
- Relation Devis <-> Advert changee de ManyToOne a OneToOne nullable
- Vich Attribute (migration Annotation -> Attribute) pour unsignedPdf/signedPdf/auditPdf
- DevisController CRUD complet : create (form repeater lignes + boutons rapides TarificationService), edit, cancel (libere OrderNumber), generate-pdf, send, resend, create-advert, events
- DevisPdf (FPDF/FPDI) : header legacy (logo, num, date, client), body lignes, summary totaux, footer SITECONSEIL + pagination, champ signature DocuSeal sur page devis + derniere page CGV
- OrderNumberService : preview() et generate() reutilisent les OrderNumber non utilises (isUsed=false) en priorite
- OrderNumber::markAsUnused() ajoute
DocuSeal integration devis :
- DocuSealService : sendDevisForSignature (avec completed_redirect_url), resendDevisSignature (archive ancienne submission), getSubmitterSlug, downloadSignedDevis (sauvegarde via Vich UploadedFile test=true)
- WebhookDocuSealController : dispatch par doc_type devis/attestation, handleDevisEvent (form.completed -> STATE_ACCEPTED + download PDF signe/audit, form.declined -> STATE_REFUSED + raison)
- DocusealEvent entity pour tracer form.viewed/started/completed/declined en temps reel
- Page evenements admin /admin/devis/{id}/events avec badges et payload JSON
Signature client :
- DevisProcessController : page publique /devis/process/{id}/{hmac} securisee par HMAC, boutons Signer (redirect DocuSeal) / Refuser (motif optionnel)
- Pages confirmation : signed.html.twig (merci + recap) et refused.html.twig (confirmation refus + motif)
- Nelmio whitelist : signature.esy-web.dev + signature.siteconseil.fr
Avis de paiement :
- Entity AdvertLine (pos, title, description, priceHt) liee a Advert
- Advert refactorise : customer, state, totaux, raisonMessage, submissionId, advertFile (Vich mapping advert_pdf), lines collection, updatedAt
- AdvertController : generate-pdf, send (mail + PJ + lien paiement), resend (rappel), cancel (delie devis, libere OrderNumber), search Meilisearch
- AdvertPdf (FPDF/FPDI) : QR code Endroid pointant vers /order/{numOrder}, texte "Scannez pour payer"
- OrderPaymentController : page publique /order/{numOrder} avec detail prestations, totaux, options paiement (placeholder)
- Creation auto depuis devis signe : copie client, totaux, lignes, meme OrderNumber
Meilisearch :
- Index customer_devis et customer_advert avec searchable (numOrder, customerName, customerEmail, state) et filterable (customerId, state)
- CRUD indexation sur chaque action (create, edit, send, cancel, create-advert)
- Recherche AJAX dans tabs Devis et Avis avec debounce + dropdown glassmorphism
- Sync admin : boutons syncDevis / syncAdverts + compteurs dans /admin/sync
Emails :
- MailerService : VCF auto (fiche contact SARL SITECONSEIL) en PJ sur tous les mails, bloc HTML pieces jointes injecte automatiquement (exclut .asc/.p7z/smime) avec icone trombone + taille fichier
- Templates : devis_to_sign, devis_signed_client/admin (PJ signed+audit), devis_refused_client/admin, advert_send (PJ + bouton paiement), ndd_expiration
- TestMailCommand : option --force-dsn pour envoyer via un DSN SMTP specifique (test prod depuis dev)
Commande NDD :
- app:ndd:check : verifie expiration domaines <= 30j, envoie mail groupe a monitor@siteconseil.fr
- Cron quotidien 8h (docker + ansible)
Divers :
- Titles templates : CRM SITECONSEIL -> SARL SITECONSEIL (52 fichiers)
- VAULT_URL dev = https://kms.esy-web.dev (comme prod)
- app.js : initDevisLines (repeater + drag & drop), initTabSearch, toggle refus devis
- app.scss : styles drag & drop
- setasign/fpdi-fpdf installe pour fusion PDF
- 5 migrations Doctrine
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 09:44:35 +02:00
document . querySelectorAll ( '.quick-price-btn' ) . forEach ( btn => {
btn . addEventListener ( 'click' , ( ) => {
addLine ( ) ;
const lastRow = container . querySelector ( '.line-row:last-child' ) ;
2026-04-08 16:54:29 +02:00
/* istanbul ignore next */ if ( ! lastRow ) return ;
lastRow . querySelector ( 'input[name$="[title]"]' ) . value = /* istanbul ignore next */ btn . dataset . title || '' ;
lastRow . querySelector ( 'textarea[name$="[description]"]' ) . value = /* istanbul ignore next */ btn . dataset . description || '' ;
feat: gestion complete Devis + Avis de paiement + DocuSeal signature + mails
Devis :
- Entity DevisLine (pos, title, description, priceHt) liee a Devis (OneToMany cascade/orphanRemoval)
- Champs ajoutes sur Devis : customer (ManyToOne), submissionId, state machine (created/send/accepted/refused/cancel), raisonMessage, totaux HT/TVA/TTC, updatedAt, setUpdatedAt public
- Relation Devis <-> Advert changee de ManyToOne a OneToOne nullable
- Vich Attribute (migration Annotation -> Attribute) pour unsignedPdf/signedPdf/auditPdf
- DevisController CRUD complet : create (form repeater lignes + boutons rapides TarificationService), edit, cancel (libere OrderNumber), generate-pdf, send, resend, create-advert, events
- DevisPdf (FPDF/FPDI) : header legacy (logo, num, date, client), body lignes, summary totaux, footer SITECONSEIL + pagination, champ signature DocuSeal sur page devis + derniere page CGV
- OrderNumberService : preview() et generate() reutilisent les OrderNumber non utilises (isUsed=false) en priorite
- OrderNumber::markAsUnused() ajoute
DocuSeal integration devis :
- DocuSealService : sendDevisForSignature (avec completed_redirect_url), resendDevisSignature (archive ancienne submission), getSubmitterSlug, downloadSignedDevis (sauvegarde via Vich UploadedFile test=true)
- WebhookDocuSealController : dispatch par doc_type devis/attestation, handleDevisEvent (form.completed -> STATE_ACCEPTED + download PDF signe/audit, form.declined -> STATE_REFUSED + raison)
- DocusealEvent entity pour tracer form.viewed/started/completed/declined en temps reel
- Page evenements admin /admin/devis/{id}/events avec badges et payload JSON
Signature client :
- DevisProcessController : page publique /devis/process/{id}/{hmac} securisee par HMAC, boutons Signer (redirect DocuSeal) / Refuser (motif optionnel)
- Pages confirmation : signed.html.twig (merci + recap) et refused.html.twig (confirmation refus + motif)
- Nelmio whitelist : signature.esy-web.dev + signature.siteconseil.fr
Avis de paiement :
- Entity AdvertLine (pos, title, description, priceHt) liee a Advert
- Advert refactorise : customer, state, totaux, raisonMessage, submissionId, advertFile (Vich mapping advert_pdf), lines collection, updatedAt
- AdvertController : generate-pdf, send (mail + PJ + lien paiement), resend (rappel), cancel (delie devis, libere OrderNumber), search Meilisearch
- AdvertPdf (FPDF/FPDI) : QR code Endroid pointant vers /order/{numOrder}, texte "Scannez pour payer"
- OrderPaymentController : page publique /order/{numOrder} avec detail prestations, totaux, options paiement (placeholder)
- Creation auto depuis devis signe : copie client, totaux, lignes, meme OrderNumber
Meilisearch :
- Index customer_devis et customer_advert avec searchable (numOrder, customerName, customerEmail, state) et filterable (customerId, state)
- CRUD indexation sur chaque action (create, edit, send, cancel, create-advert)
- Recherche AJAX dans tabs Devis et Avis avec debounce + dropdown glassmorphism
- Sync admin : boutons syncDevis / syncAdverts + compteurs dans /admin/sync
Emails :
- MailerService : VCF auto (fiche contact SARL SITECONSEIL) en PJ sur tous les mails, bloc HTML pieces jointes injecte automatiquement (exclut .asc/.p7z/smime) avec icone trombone + taille fichier
- Templates : devis_to_sign, devis_signed_client/admin (PJ signed+audit), devis_refused_client/admin, advert_send (PJ + bouton paiement), ndd_expiration
- TestMailCommand : option --force-dsn pour envoyer via un DSN SMTP specifique (test prod depuis dev)
Commande NDD :
- app:ndd:check : verifie expiration domaines <= 30j, envoie mail groupe a monitor@siteconseil.fr
- Cron quotidien 8h (docker + ansible)
Divers :
- Titles templates : CRM SITECONSEIL -> SARL SITECONSEIL (52 fichiers)
- VAULT_URL dev = https://kms.esy-web.dev (comme prod)
- app.js : initDevisLines (repeater + drag & drop), initTabSearch, toggle refus devis
- app.scss : styles drag & drop
- setasign/fpdi-fpdf installe pour fusion PDF
- 5 migrations Doctrine
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 09:44:35 +02:00
const priceInput = lastRow . querySelector ( '.line-price' ) ;
2026-04-08 16:54:29 +02:00
priceInput . value = /* istanbul ignore next */ btn . dataset . price || '0.00' ;
feat: comptabilite + prestataires + rapport financier + stats dynamiques
Comptabilite (Super Admin) :
- ComptabiliteController avec 7 exports CSV/JSON compatibles SAGE
(journal ventes, grand livre, FEC, balance agee, reglements,
commissions Stripe 1.5%+0.25E, couts services)
- Export PDF via ComptaPdf (FPDF) avec bloc legal pre-rempli,
tableau pagine, champ signature DocuSeal
- Signature electronique DocuSeal + callback + envoi email signe
avec template dedie (compta_export_signed.html.twig)
- Rapport financier public (RapportFinancierPdf) : recettes par
service, depenses (Stripe, infra, prestataires), bilan excedent/deficit
- Codes comptables clients EC-XXXX (plus de 411xxx)
Prestataires (Super Admin) :
- Entite Prestataire (raisonSociale, siret, email, phone, adresse)
- Entite FacturePrestataire (numFacture, montantHt, montantTtc,
year, month, isPaid, PDF via Vich)
- CRUD complet avec recherche SIRET via proxy API data.gouv.fr
- Commande cron app:reminder:factures-prestataire (5 du mois)
- Factures prestataires integrees dans export couts services
- Sidebar Super Admin : entree Prestataires + Comptabilite
Stats (/admin/stats) :
- Cout prestataire dynamique depuis FacturePrestataire
- Fusion Infra + Prestataire en "Cout de fonctionnement"
- Commission Stripe corrigee (1.5% + 0.25E par transaction)
Divers :
- DocuSealService::sendComptaForSignature() + getApi()
- Customer::generateCodeComptable() format EC-XXXX-XXXXX
- Protection double prefixe EC- a la creation client
- Bouton regenerer PDF cache quand advert state=accepted
- Modals sans script inline (data-modal-open/close dans app.js)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 23:39:31 +02:00
// Auto-set le type de service
const lineType = btn . dataset . lineType || '' ;
2026-04-08 16:54:29 +02:00
/* istanbul ignore next */ if ( lineType ) {
feat: comptabilite + prestataires + rapport financier + stats dynamiques
Comptabilite (Super Admin) :
- ComptabiliteController avec 7 exports CSV/JSON compatibles SAGE
(journal ventes, grand livre, FEC, balance agee, reglements,
commissions Stripe 1.5%+0.25E, couts services)
- Export PDF via ComptaPdf (FPDF) avec bloc legal pre-rempli,
tableau pagine, champ signature DocuSeal
- Signature electronique DocuSeal + callback + envoi email signe
avec template dedie (compta_export_signed.html.twig)
- Rapport financier public (RapportFinancierPdf) : recettes par
service, depenses (Stripe, infra, prestataires), bilan excedent/deficit
- Codes comptables clients EC-XXXX (plus de 411xxx)
Prestataires (Super Admin) :
- Entite Prestataire (raisonSociale, siret, email, phone, adresse)
- Entite FacturePrestataire (numFacture, montantHt, montantTtc,
year, month, isPaid, PDF via Vich)
- CRUD complet avec recherche SIRET via proxy API data.gouv.fr
- Commande cron app:reminder:factures-prestataire (5 du mois)
- Factures prestataires integrees dans export couts services
- Sidebar Super Admin : entree Prestataires + Comptabilite
Stats (/admin/stats) :
- Cout prestataire dynamique depuis FacturePrestataire
- Fusion Infra + Prestataire en "Cout de fonctionnement"
- Commission Stripe corrigee (1.5% + 0.25E par transaction)
Divers :
- DocuSealService::sendComptaForSignature() + getApi()
- Customer::generateCodeComptable() format EC-XXXX-XXXXX
- Protection double prefixe EC- a la creation client
- Bouton regenerer PDF cache quand advert state=accepted
- Modals sans script inline (data-modal-open/close dans app.js)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 23:39:31 +02:00
const typeSelect = lastRow . querySelector ( '.line-type' ) ;
2026-04-08 16:54:29 +02:00
/* istanbul ignore next */ if ( typeSelect ) {
feat: comptabilite + prestataires + rapport financier + stats dynamiques
Comptabilite (Super Admin) :
- ComptabiliteController avec 7 exports CSV/JSON compatibles SAGE
(journal ventes, grand livre, FEC, balance agee, reglements,
commissions Stripe 1.5%+0.25E, couts services)
- Export PDF via ComptaPdf (FPDF) avec bloc legal pre-rempli,
tableau pagine, champ signature DocuSeal
- Signature electronique DocuSeal + callback + envoi email signe
avec template dedie (compta_export_signed.html.twig)
- Rapport financier public (RapportFinancierPdf) : recettes par
service, depenses (Stripe, infra, prestataires), bilan excedent/deficit
- Codes comptables clients EC-XXXX (plus de 411xxx)
Prestataires (Super Admin) :
- Entite Prestataire (raisonSociale, siret, email, phone, adresse)
- Entite FacturePrestataire (numFacture, montantHt, montantTtc,
year, month, isPaid, PDF via Vich)
- CRUD complet avec recherche SIRET via proxy API data.gouv.fr
- Commande cron app:reminder:factures-prestataire (5 du mois)
- Factures prestataires integrees dans export couts services
- Sidebar Super Admin : entree Prestataires + Comptabilite
Stats (/admin/stats) :
- Cout prestataire dynamique depuis FacturePrestataire
- Fusion Infra + Prestataire en "Cout de fonctionnement"
- Commission Stripe corrigee (1.5% + 0.25E par transaction)
Divers :
- DocuSealService::sendComptaForSignature() + getApi()
- Customer::generateCodeComptable() format EC-XXXX-XXXXX
- Protection double prefixe EC- a la creation client
- Bouton regenerer PDF cache quand advert state=accepted
- Modals sans script inline (data-modal-open/close dans app.js)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 23:39:31 +02:00
typeSelect . value = lineType ;
// Trigger change pour charger les services du client
typeSelect . dispatchEvent ( new Event ( 'change' , { bubbles : true } ) ) ;
}
}
feat: gestion complete Devis + Avis de paiement + DocuSeal signature + mails
Devis :
- Entity DevisLine (pos, title, description, priceHt) liee a Devis (OneToMany cascade/orphanRemoval)
- Champs ajoutes sur Devis : customer (ManyToOne), submissionId, state machine (created/send/accepted/refused/cancel), raisonMessage, totaux HT/TVA/TTC, updatedAt, setUpdatedAt public
- Relation Devis <-> Advert changee de ManyToOne a OneToOne nullable
- Vich Attribute (migration Annotation -> Attribute) pour unsignedPdf/signedPdf/auditPdf
- DevisController CRUD complet : create (form repeater lignes + boutons rapides TarificationService), edit, cancel (libere OrderNumber), generate-pdf, send, resend, create-advert, events
- DevisPdf (FPDF/FPDI) : header legacy (logo, num, date, client), body lignes, summary totaux, footer SITECONSEIL + pagination, champ signature DocuSeal sur page devis + derniere page CGV
- OrderNumberService : preview() et generate() reutilisent les OrderNumber non utilises (isUsed=false) en priorite
- OrderNumber::markAsUnused() ajoute
DocuSeal integration devis :
- DocuSealService : sendDevisForSignature (avec completed_redirect_url), resendDevisSignature (archive ancienne submission), getSubmitterSlug, downloadSignedDevis (sauvegarde via Vich UploadedFile test=true)
- WebhookDocuSealController : dispatch par doc_type devis/attestation, handleDevisEvent (form.completed -> STATE_ACCEPTED + download PDF signe/audit, form.declined -> STATE_REFUSED + raison)
- DocusealEvent entity pour tracer form.viewed/started/completed/declined en temps reel
- Page evenements admin /admin/devis/{id}/events avec badges et payload JSON
Signature client :
- DevisProcessController : page publique /devis/process/{id}/{hmac} securisee par HMAC, boutons Signer (redirect DocuSeal) / Refuser (motif optionnel)
- Pages confirmation : signed.html.twig (merci + recap) et refused.html.twig (confirmation refus + motif)
- Nelmio whitelist : signature.esy-web.dev + signature.siteconseil.fr
Avis de paiement :
- Entity AdvertLine (pos, title, description, priceHt) liee a Advert
- Advert refactorise : customer, state, totaux, raisonMessage, submissionId, advertFile (Vich mapping advert_pdf), lines collection, updatedAt
- AdvertController : generate-pdf, send (mail + PJ + lien paiement), resend (rappel), cancel (delie devis, libere OrderNumber), search Meilisearch
- AdvertPdf (FPDF/FPDI) : QR code Endroid pointant vers /order/{numOrder}, texte "Scannez pour payer"
- OrderPaymentController : page publique /order/{numOrder} avec detail prestations, totaux, options paiement (placeholder)
- Creation auto depuis devis signe : copie client, totaux, lignes, meme OrderNumber
Meilisearch :
- Index customer_devis et customer_advert avec searchable (numOrder, customerName, customerEmail, state) et filterable (customerId, state)
- CRUD indexation sur chaque action (create, edit, send, cancel, create-advert)
- Recherche AJAX dans tabs Devis et Avis avec debounce + dropdown glassmorphism
- Sync admin : boutons syncDevis / syncAdverts + compteurs dans /admin/sync
Emails :
- MailerService : VCF auto (fiche contact SARL SITECONSEIL) en PJ sur tous les mails, bloc HTML pieces jointes injecte automatiquement (exclut .asc/.p7z/smime) avec icone trombone + taille fichier
- Templates : devis_to_sign, devis_signed_client/admin (PJ signed+audit), devis_refused_client/admin, advert_send (PJ + bouton paiement), ndd_expiration
- TestMailCommand : option --force-dsn pour envoyer via un DSN SMTP specifique (test prod depuis dev)
Commande NDD :
- app:ndd:check : verifie expiration domaines <= 30j, envoie mail groupe a monitor@siteconseil.fr
- Cron quotidien 8h (docker + ansible)
Divers :
- Titles templates : CRM SITECONSEIL -> SARL SITECONSEIL (52 fichiers)
- VAULT_URL dev = https://kms.esy-web.dev (comme prod)
- app.js : initDevisLines (repeater + drag & drop), initTabSearch, toggle refus devis
- app.scss : styles drag & drop
- setasign/fpdi-fpdf installe pour fusion PDF
- 5 migrations Doctrine
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 09:44:35 +02:00
recalc ( ) ;
} ) ;
} ) ;
// Drag & drop reordering
let draggedRow = null ;
container . addEventListener ( 'dragstart' , e => {
const row = e . target . closest ( '.line-row' ) ;
if ( ! row ) return ;
draggedRow = row ;
row . classList . add ( 'dragging' ) ;
e . dataTransfer . effectAllowed = 'move' ;
} ) ;
container . addEventListener ( 'dragend' , e => {
const row = e . target . closest ( '.line-row' ) ;
if ( row ) row . classList . remove ( 'dragging' ) ;
container . querySelectorAll ( '.line-row' ) . forEach ( r => r . classList . remove ( 'drag-over' ) ) ;
draggedRow = null ;
renumber ( ) ;
} ) ;
container . addEventListener ( 'dragover' , e => {
e . preventDefault ( ) ;
const target = e . target . closest ( '.line-row' ) ;
if ( ! target || target === draggedRow ) return ;
container . querySelectorAll ( '.line-row' ) . forEach ( r => r . classList . remove ( 'drag-over' ) ) ;
target . classList . add ( 'drag-over' ) ;
} ) ;
container . addEventListener ( 'drop' , e => {
e . preventDefault ( ) ;
const target = e . target . closest ( '.line-row' ) ;
if ( ! target || ! draggedRow || target === draggedRow ) return ;
const rows = Array . from ( container . querySelectorAll ( '.line-row' ) ) ;
const draggedIdx = rows . indexOf ( draggedRow ) ;
const targetIdx = rows . indexOf ( target ) ;
if ( draggedIdx < targetIdx ) {
target . after ( draggedRow ) ;
} else {
target . before ( draggedRow ) ;
}
target . classList . remove ( 'drag-over' ) ;
renumber ( ) ;
} ) ;
// Prefill en mode edition
const initial = container . dataset . initialLines ;
if ( initial ) {
try {
const arr = JSON . parse ( initial ) ;
2026-04-08 16:54:29 +02:00
/* istanbul ignore next */ arr . sort ( ( a , b ) => ( a . pos || 0 ) - ( b . pos || 0 ) ) ;
feat: comptabilite + prestataires + rapport financier + stats dynamiques
Comptabilite (Super Admin) :
- ComptabiliteController avec 7 exports CSV/JSON compatibles SAGE
(journal ventes, grand livre, FEC, balance agee, reglements,
commissions Stripe 1.5%+0.25E, couts services)
- Export PDF via ComptaPdf (FPDF) avec bloc legal pre-rempli,
tableau pagine, champ signature DocuSeal
- Signature electronique DocuSeal + callback + envoi email signe
avec template dedie (compta_export_signed.html.twig)
- Rapport financier public (RapportFinancierPdf) : recettes par
service, depenses (Stripe, infra, prestataires), bilan excedent/deficit
- Codes comptables clients EC-XXXX (plus de 411xxx)
Prestataires (Super Admin) :
- Entite Prestataire (raisonSociale, siret, email, phone, adresse)
- Entite FacturePrestataire (numFacture, montantHt, montantTtc,
year, month, isPaid, PDF via Vich)
- CRUD complet avec recherche SIRET via proxy API data.gouv.fr
- Commande cron app:reminder:factures-prestataire (5 du mois)
- Factures prestataires integrees dans export couts services
- Sidebar Super Admin : entree Prestataires + Comptabilite
Stats (/admin/stats) :
- Cout prestataire dynamique depuis FacturePrestataire
- Fusion Infra + Prestataire en "Cout de fonctionnement"
- Commission Stripe corrigee (1.5% + 0.25E par transaction)
Divers :
- DocuSealService::sendComptaForSignature() + getApi()
- Customer::generateCodeComptable() format EC-XXXX-XXXXX
- Protection double prefixe EC- a la creation client
- Bouton regenerer PDF cache quand advert state=accepted
- Modals sans script inline (data-modal-open/close dans app.js)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 23:39:31 +02:00
arr . forEach ( async ( l ) => {
feat: gestion complete Devis + Avis de paiement + DocuSeal signature + mails
Devis :
- Entity DevisLine (pos, title, description, priceHt) liee a Devis (OneToMany cascade/orphanRemoval)
- Champs ajoutes sur Devis : customer (ManyToOne), submissionId, state machine (created/send/accepted/refused/cancel), raisonMessage, totaux HT/TVA/TTC, updatedAt, setUpdatedAt public
- Relation Devis <-> Advert changee de ManyToOne a OneToOne nullable
- Vich Attribute (migration Annotation -> Attribute) pour unsignedPdf/signedPdf/auditPdf
- DevisController CRUD complet : create (form repeater lignes + boutons rapides TarificationService), edit, cancel (libere OrderNumber), generate-pdf, send, resend, create-advert, events
- DevisPdf (FPDF/FPDI) : header legacy (logo, num, date, client), body lignes, summary totaux, footer SITECONSEIL + pagination, champ signature DocuSeal sur page devis + derniere page CGV
- OrderNumberService : preview() et generate() reutilisent les OrderNumber non utilises (isUsed=false) en priorite
- OrderNumber::markAsUnused() ajoute
DocuSeal integration devis :
- DocuSealService : sendDevisForSignature (avec completed_redirect_url), resendDevisSignature (archive ancienne submission), getSubmitterSlug, downloadSignedDevis (sauvegarde via Vich UploadedFile test=true)
- WebhookDocuSealController : dispatch par doc_type devis/attestation, handleDevisEvent (form.completed -> STATE_ACCEPTED + download PDF signe/audit, form.declined -> STATE_REFUSED + raison)
- DocusealEvent entity pour tracer form.viewed/started/completed/declined en temps reel
- Page evenements admin /admin/devis/{id}/events avec badges et payload JSON
Signature client :
- DevisProcessController : page publique /devis/process/{id}/{hmac} securisee par HMAC, boutons Signer (redirect DocuSeal) / Refuser (motif optionnel)
- Pages confirmation : signed.html.twig (merci + recap) et refused.html.twig (confirmation refus + motif)
- Nelmio whitelist : signature.esy-web.dev + signature.siteconseil.fr
Avis de paiement :
- Entity AdvertLine (pos, title, description, priceHt) liee a Advert
- Advert refactorise : customer, state, totaux, raisonMessage, submissionId, advertFile (Vich mapping advert_pdf), lines collection, updatedAt
- AdvertController : generate-pdf, send (mail + PJ + lien paiement), resend (rappel), cancel (delie devis, libere OrderNumber), search Meilisearch
- AdvertPdf (FPDF/FPDI) : QR code Endroid pointant vers /order/{numOrder}, texte "Scannez pour payer"
- OrderPaymentController : page publique /order/{numOrder} avec detail prestations, totaux, options paiement (placeholder)
- Creation auto depuis devis signe : copie client, totaux, lignes, meme OrderNumber
Meilisearch :
- Index customer_devis et customer_advert avec searchable (numOrder, customerName, customerEmail, state) et filterable (customerId, state)
- CRUD indexation sur chaque action (create, edit, send, cancel, create-advert)
- Recherche AJAX dans tabs Devis et Avis avec debounce + dropdown glassmorphism
- Sync admin : boutons syncDevis / syncAdverts + compteurs dans /admin/sync
Emails :
- MailerService : VCF auto (fiche contact SARL SITECONSEIL) en PJ sur tous les mails, bloc HTML pieces jointes injecte automatiquement (exclut .asc/.p7z/smime) avec icone trombone + taille fichier
- Templates : devis_to_sign, devis_signed_client/admin (PJ signed+audit), devis_refused_client/admin, advert_send (PJ + bouton paiement), ndd_expiration
- TestMailCommand : option --force-dsn pour envoyer via un DSN SMTP specifique (test prod depuis dev)
Commande NDD :
- app:ndd:check : verifie expiration domaines <= 30j, envoie mail groupe a monitor@siteconseil.fr
- Cron quotidien 8h (docker + ansible)
Divers :
- Titles templates : CRM SITECONSEIL -> SARL SITECONSEIL (52 fichiers)
- VAULT_URL dev = https://kms.esy-web.dev (comme prod)
- app.js : initDevisLines (repeater + drag & drop), initTabSearch, toggle refus devis
- app.scss : styles drag & drop
- setasign/fpdi-fpdf installe pour fusion PDF
- 5 migrations Doctrine
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 09:44:35 +02:00
addLine ( ) ;
const row = container . querySelector ( '.line-row:last-child' ) ;
2026-04-08 16:54:29 +02:00
/* istanbul ignore next */ if ( ! row ) return ;
row . querySelector ( 'input[name$="[title]"]' ) . value = /* istanbul ignore next */ l . title || '' ;
row . querySelector ( 'textarea[name$="[description]"]' ) . value = /* istanbul ignore next */ l . description || '' ;
row . querySelector ( '.line-price' ) . value = /* istanbul ignore next */ l . priceHt || '0.00' ;
feat: comptabilite + prestataires + rapport financier + stats dynamiques
Comptabilite (Super Admin) :
- ComptabiliteController avec 7 exports CSV/JSON compatibles SAGE
(journal ventes, grand livre, FEC, balance agee, reglements,
commissions Stripe 1.5%+0.25E, couts services)
- Export PDF via ComptaPdf (FPDF) avec bloc legal pre-rempli,
tableau pagine, champ signature DocuSeal
- Signature electronique DocuSeal + callback + envoi email signe
avec template dedie (compta_export_signed.html.twig)
- Rapport financier public (RapportFinancierPdf) : recettes par
service, depenses (Stripe, infra, prestataires), bilan excedent/deficit
- Codes comptables clients EC-XXXX (plus de 411xxx)
Prestataires (Super Admin) :
- Entite Prestataire (raisonSociale, siret, email, phone, adresse)
- Entite FacturePrestataire (numFacture, montantHt, montantTtc,
year, month, isPaid, PDF via Vich)
- CRUD complet avec recherche SIRET via proxy API data.gouv.fr
- Commande cron app:reminder:factures-prestataire (5 du mois)
- Factures prestataires integrees dans export couts services
- Sidebar Super Admin : entree Prestataires + Comptabilite
Stats (/admin/stats) :
- Cout prestataire dynamique depuis FacturePrestataire
- Fusion Infra + Prestataire en "Cout de fonctionnement"
- Commission Stripe corrigee (1.5% + 0.25E par transaction)
Divers :
- DocuSealService::sendComptaForSignature() + getApi()
- Customer::generateCodeComptable() format EC-XXXX-XXXXX
- Protection double prefixe EC- a la creation client
- Bouton regenerer PDF cache quand advert state=accepted
- Modals sans script inline (data-modal-open/close dans app.js)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 23:39:31 +02:00
// Pre-select type
const typeSelect = row . querySelector ( '.line-type' ) ;
2026-04-08 16:54:29 +02:00
/* istanbul ignore next */ if ( l . type && typeSelect ) {
feat: comptabilite + prestataires + rapport financier + stats dynamiques
Comptabilite (Super Admin) :
- ComptabiliteController avec 7 exports CSV/JSON compatibles SAGE
(journal ventes, grand livre, FEC, balance agee, reglements,
commissions Stripe 1.5%+0.25E, couts services)
- Export PDF via ComptaPdf (FPDF) avec bloc legal pre-rempli,
tableau pagine, champ signature DocuSeal
- Signature electronique DocuSeal + callback + envoi email signe
avec template dedie (compta_export_signed.html.twig)
- Rapport financier public (RapportFinancierPdf) : recettes par
service, depenses (Stripe, infra, prestataires), bilan excedent/deficit
- Codes comptables clients EC-XXXX (plus de 411xxx)
Prestataires (Super Admin) :
- Entite Prestataire (raisonSociale, siret, email, phone, adresse)
- Entite FacturePrestataire (numFacture, montantHt, montantTtc,
year, month, isPaid, PDF via Vich)
- CRUD complet avec recherche SIRET via proxy API data.gouv.fr
- Commande cron app:reminder:factures-prestataire (5 du mois)
- Factures prestataires integrees dans export couts services
- Sidebar Super Admin : entree Prestataires + Comptabilite
Stats (/admin/stats) :
- Cout prestataire dynamique depuis FacturePrestataire
- Fusion Infra + Prestataire en "Cout de fonctionnement"
- Commission Stripe corrigee (1.5% + 0.25E par transaction)
Divers :
- DocuSealService::sendComptaForSignature() + getApi()
- Customer::generateCodeComptable() format EC-XXXX-XXXXX
- Protection double prefixe EC- a la creation client
- Bouton regenerer PDF cache quand advert state=accepted
- Modals sans script inline (data-modal-open/close dans app.js)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 23:39:31 +02:00
typeSelect . value = l . type ;
// Charge les services si type avec serviceId
if ( l . serviceId && l . type !== 'hosting' && l . type !== 'maintenance' && l . type !== 'other' ) {
const serviceSelect = row . querySelector ( '.line-service-id' ) ;
const url = typeSelect . dataset . servicesUrl . replace ( '__TYPE__' , l . type ) ;
try {
const resp = await fetch ( url ) ;
const items = await resp . json ( ) ;
serviceSelect . innerHTML = '<option value="">— Selectionner le service —</option>' ;
items . forEach ( item => {
const opt = document . createElement ( 'option' ) ;
opt . value = item . id ;
opt . textContent = item . label ;
if ( item . id === l . serviceId ) opt . selected = true ;
serviceSelect . appendChild ( opt ) ;
} ) ;
serviceSelect . disabled = false ;
2026-04-08 08:52:32 +02:00
} catch ( err ) { console . debug ( 'Prefill service loading failed' , err ) ; }
feat: comptabilite + prestataires + rapport financier + stats dynamiques
Comptabilite (Super Admin) :
- ComptabiliteController avec 7 exports CSV/JSON compatibles SAGE
(journal ventes, grand livre, FEC, balance agee, reglements,
commissions Stripe 1.5%+0.25E, couts services)
- Export PDF via ComptaPdf (FPDF) avec bloc legal pre-rempli,
tableau pagine, champ signature DocuSeal
- Signature electronique DocuSeal + callback + envoi email signe
avec template dedie (compta_export_signed.html.twig)
- Rapport financier public (RapportFinancierPdf) : recettes par
service, depenses (Stripe, infra, prestataires), bilan excedent/deficit
- Codes comptables clients EC-XXXX (plus de 411xxx)
Prestataires (Super Admin) :
- Entite Prestataire (raisonSociale, siret, email, phone, adresse)
- Entite FacturePrestataire (numFacture, montantHt, montantTtc,
year, month, isPaid, PDF via Vich)
- CRUD complet avec recherche SIRET via proxy API data.gouv.fr
- Commande cron app:reminder:factures-prestataire (5 du mois)
- Factures prestataires integrees dans export couts services
- Sidebar Super Admin : entree Prestataires + Comptabilite
Stats (/admin/stats) :
- Cout prestataire dynamique depuis FacturePrestataire
- Fusion Infra + Prestataire en "Cout de fonctionnement"
- Commission Stripe corrigee (1.5% + 0.25E par transaction)
Divers :
- DocuSealService::sendComptaForSignature() + getApi()
- Customer::generateCodeComptable() format EC-XXXX-XXXXX
- Protection double prefixe EC- a la creation client
- Bouton regenerer PDF cache quand advert state=accepted
- Modals sans script inline (data-modal-open/close dans app.js)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 23:39:31 +02:00
}
}
feat: gestion complete Devis + Avis de paiement + DocuSeal signature + mails
Devis :
- Entity DevisLine (pos, title, description, priceHt) liee a Devis (OneToMany cascade/orphanRemoval)
- Champs ajoutes sur Devis : customer (ManyToOne), submissionId, state machine (created/send/accepted/refused/cancel), raisonMessage, totaux HT/TVA/TTC, updatedAt, setUpdatedAt public
- Relation Devis <-> Advert changee de ManyToOne a OneToOne nullable
- Vich Attribute (migration Annotation -> Attribute) pour unsignedPdf/signedPdf/auditPdf
- DevisController CRUD complet : create (form repeater lignes + boutons rapides TarificationService), edit, cancel (libere OrderNumber), generate-pdf, send, resend, create-advert, events
- DevisPdf (FPDF/FPDI) : header legacy (logo, num, date, client), body lignes, summary totaux, footer SITECONSEIL + pagination, champ signature DocuSeal sur page devis + derniere page CGV
- OrderNumberService : preview() et generate() reutilisent les OrderNumber non utilises (isUsed=false) en priorite
- OrderNumber::markAsUnused() ajoute
DocuSeal integration devis :
- DocuSealService : sendDevisForSignature (avec completed_redirect_url), resendDevisSignature (archive ancienne submission), getSubmitterSlug, downloadSignedDevis (sauvegarde via Vich UploadedFile test=true)
- WebhookDocuSealController : dispatch par doc_type devis/attestation, handleDevisEvent (form.completed -> STATE_ACCEPTED + download PDF signe/audit, form.declined -> STATE_REFUSED + raison)
- DocusealEvent entity pour tracer form.viewed/started/completed/declined en temps reel
- Page evenements admin /admin/devis/{id}/events avec badges et payload JSON
Signature client :
- DevisProcessController : page publique /devis/process/{id}/{hmac} securisee par HMAC, boutons Signer (redirect DocuSeal) / Refuser (motif optionnel)
- Pages confirmation : signed.html.twig (merci + recap) et refused.html.twig (confirmation refus + motif)
- Nelmio whitelist : signature.esy-web.dev + signature.siteconseil.fr
Avis de paiement :
- Entity AdvertLine (pos, title, description, priceHt) liee a Advert
- Advert refactorise : customer, state, totaux, raisonMessage, submissionId, advertFile (Vich mapping advert_pdf), lines collection, updatedAt
- AdvertController : generate-pdf, send (mail + PJ + lien paiement), resend (rappel), cancel (delie devis, libere OrderNumber), search Meilisearch
- AdvertPdf (FPDF/FPDI) : QR code Endroid pointant vers /order/{numOrder}, texte "Scannez pour payer"
- OrderPaymentController : page publique /order/{numOrder} avec detail prestations, totaux, options paiement (placeholder)
- Creation auto depuis devis signe : copie client, totaux, lignes, meme OrderNumber
Meilisearch :
- Index customer_devis et customer_advert avec searchable (numOrder, customerName, customerEmail, state) et filterable (customerId, state)
- CRUD indexation sur chaque action (create, edit, send, cancel, create-advert)
- Recherche AJAX dans tabs Devis et Avis avec debounce + dropdown glassmorphism
- Sync admin : boutons syncDevis / syncAdverts + compteurs dans /admin/sync
Emails :
- MailerService : VCF auto (fiche contact SARL SITECONSEIL) en PJ sur tous les mails, bloc HTML pieces jointes injecte automatiquement (exclut .asc/.p7z/smime) avec icone trombone + taille fichier
- Templates : devis_to_sign, devis_signed_client/admin (PJ signed+audit), devis_refused_client/admin, advert_send (PJ + bouton paiement), ndd_expiration
- TestMailCommand : option --force-dsn pour envoyer via un DSN SMTP specifique (test prod depuis dev)
Commande NDD :
- app:ndd:check : verifie expiration domaines <= 30j, envoie mail groupe a monitor@siteconseil.fr
- Cron quotidien 8h (docker + ansible)
Divers :
- Titles templates : CRM SITECONSEIL -> SARL SITECONSEIL (52 fichiers)
- VAULT_URL dev = https://kms.esy-web.dev (comme prod)
- app.js : initDevisLines (repeater + drag & drop), initTabSearch, toggle refus devis
- app.scss : styles drag & drop
- setasign/fpdi-fpdf installe pour fusion PDF
- 5 migrations Doctrine
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 09:44:35 +02:00
} ) ;
recalc ( ) ;
2026-04-08 08:52:32 +02:00
} catch ( e ) { console . debug ( 'Initial lines parse failed' , e ) ; }
feat: gestion complete Devis + Avis de paiement + DocuSeal signature + mails
Devis :
- Entity DevisLine (pos, title, description, priceHt) liee a Devis (OneToMany cascade/orphanRemoval)
- Champs ajoutes sur Devis : customer (ManyToOne), submissionId, state machine (created/send/accepted/refused/cancel), raisonMessage, totaux HT/TVA/TTC, updatedAt, setUpdatedAt public
- Relation Devis <-> Advert changee de ManyToOne a OneToOne nullable
- Vich Attribute (migration Annotation -> Attribute) pour unsignedPdf/signedPdf/auditPdf
- DevisController CRUD complet : create (form repeater lignes + boutons rapides TarificationService), edit, cancel (libere OrderNumber), generate-pdf, send, resend, create-advert, events
- DevisPdf (FPDF/FPDI) : header legacy (logo, num, date, client), body lignes, summary totaux, footer SITECONSEIL + pagination, champ signature DocuSeal sur page devis + derniere page CGV
- OrderNumberService : preview() et generate() reutilisent les OrderNumber non utilises (isUsed=false) en priorite
- OrderNumber::markAsUnused() ajoute
DocuSeal integration devis :
- DocuSealService : sendDevisForSignature (avec completed_redirect_url), resendDevisSignature (archive ancienne submission), getSubmitterSlug, downloadSignedDevis (sauvegarde via Vich UploadedFile test=true)
- WebhookDocuSealController : dispatch par doc_type devis/attestation, handleDevisEvent (form.completed -> STATE_ACCEPTED + download PDF signe/audit, form.declined -> STATE_REFUSED + raison)
- DocusealEvent entity pour tracer form.viewed/started/completed/declined en temps reel
- Page evenements admin /admin/devis/{id}/events avec badges et payload JSON
Signature client :
- DevisProcessController : page publique /devis/process/{id}/{hmac} securisee par HMAC, boutons Signer (redirect DocuSeal) / Refuser (motif optionnel)
- Pages confirmation : signed.html.twig (merci + recap) et refused.html.twig (confirmation refus + motif)
- Nelmio whitelist : signature.esy-web.dev + signature.siteconseil.fr
Avis de paiement :
- Entity AdvertLine (pos, title, description, priceHt) liee a Advert
- Advert refactorise : customer, state, totaux, raisonMessage, submissionId, advertFile (Vich mapping advert_pdf), lines collection, updatedAt
- AdvertController : generate-pdf, send (mail + PJ + lien paiement), resend (rappel), cancel (delie devis, libere OrderNumber), search Meilisearch
- AdvertPdf (FPDF/FPDI) : QR code Endroid pointant vers /order/{numOrder}, texte "Scannez pour payer"
- OrderPaymentController : page publique /order/{numOrder} avec detail prestations, totaux, options paiement (placeholder)
- Creation auto depuis devis signe : copie client, totaux, lignes, meme OrderNumber
Meilisearch :
- Index customer_devis et customer_advert avec searchable (numOrder, customerName, customerEmail, state) et filterable (customerId, state)
- CRUD indexation sur chaque action (create, edit, send, cancel, create-advert)
- Recherche AJAX dans tabs Devis et Avis avec debounce + dropdown glassmorphism
- Sync admin : boutons syncDevis / syncAdverts + compteurs dans /admin/sync
Emails :
- MailerService : VCF auto (fiche contact SARL SITECONSEIL) en PJ sur tous les mails, bloc HTML pieces jointes injecte automatiquement (exclut .asc/.p7z/smime) avec icone trombone + taille fichier
- Templates : devis_to_sign, devis_signed_client/admin (PJ signed+audit), devis_refused_client/admin, advert_send (PJ + bouton paiement), ndd_expiration
- TestMailCommand : option --force-dsn pour envoyer via un DSN SMTP specifique (test prod depuis dev)
Commande NDD :
- app:ndd:check : verifie expiration domaines <= 30j, envoie mail groupe a monitor@siteconseil.fr
- Cron quotidien 8h (docker + ansible)
Divers :
- Titles templates : CRM SITECONSEIL -> SARL SITECONSEIL (52 fichiers)
- VAULT_URL dev = https://kms.esy-web.dev (comme prod)
- app.js : initDevisLines (repeater + drag & drop), initTabSearch, toggle refus devis
- app.scss : styles drag & drop
- setasign/fpdi-fpdf installe pour fusion PDF
- 5 migrations Doctrine
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 09:44:35 +02:00
}
}