Sauvegarde — modèles système BillingTemplate (Teadle)
Fichier de référence si les deux modèles système (Factures / CRA) sont perdus en base ou à réinitialiser avec l’identité visuelle Teadle.
Ce qui doit figurer dans settings (minimum)
Seuls trois axes sont stockés dans le JSON : couleur, taille de police, logo (affichage). Tout le reste (footer, paiement, champs émetteur/client, tableau, etc.) est laissé implicite : l’application applique ses valeurs par défaut quand les clés sont absentes.
| Sujet | Clés settings |
Valeurs Teadle |
|---|---|---|
| Couleur | style.accentColor |
#EF4444 |
| Taille de police | style.fontSize |
normal (Normal dans l’éditeur) |
| Logo | header.showLogo |
true (afficher le bloc logo ; le fichier image est hors JSON, voir ci-dessous) |
Note : le fichier binaire du logo reste hors JSON (upload S3,
logoUrlsur la ressource modèle). Pour POST …/preview, le front peut toutefois envoyerstyle.logoUrl(URL signée / publique) afin de rendre l’aperçu — clé autorisée dansInvoiceBillingTemplateSettingKeyetActivityReportBillingTemplateSettingKey. La référence Teadle en doc reste le PNG versionné dans le dépôt (/billing/teadle_logo_min.png).
Fichier logo (dépôt, hors settings)
- Chemin dans le repo :
frontend/public/billing/teadle_logo_min.png(asset statique Vite) - URL publique (origine du front) :
/billing/teadle_logo_min.png - Usage : identité Teadle (aperçu, doc, réimport manuel). Constante côté app :
TEADLE_BILLING_LOGO_MIN_URLdansfrontend/src/config/billingTemplatePreview.js.
JSON minimal (settings)
{
"style": {
"accentColor": "#EF4444",
"fontSize": "normal"
},
"header": {
"showLogo": true
}
}
UUIDs des modèles système (seed initial)
| Nom (exemple) | type |
uuid |
|---|---|---|
| Factures | INVOICE |
b4f7e4b6-03f8-4c11-bf8a-14db8df8ff10 |
| CRA | ACTIVITY_REPORT |
83196988-f6ef-40de-8e08-ab31c2ed4ed2 |
Les modèles système ont trainer_id NULL. Le caractère « système » est trainer_id IS NULL (plus de colonne is_system).
Exemple SQL (PostgreSQL)
UPDATE billing_template
SET settings = '{"style":{"accentColor":"#EF4444","fontSize":"normal"},"header":{"showLogo":true}}'::json,
updated_at = NOW()
WHERE trainer_id IS NULL AND uuid IN (
'b4f7e4b6-03f8-4c11-bf8a-14db8df8ff10',
'83196988-f6ef-40de-8e08-ab31c2ed4ed2'
);
Pour recréer une ligne système supprimée, reprendre le seed dans Version20260326120000 ou un script adapté au schéma actuel de billing_template.
PDF facture et CRA (même rendu que l’aperçu HTML)
La génération PDF (GenerateInvoicePdfCommand, GenerateActivityReportPdfCommand) ne passe plus par les anciens Twig pdf/invoice.html.twig et pdf/activity_report.html.twig. Elle enchaîne :
BillingTemplatePdfResolver: modèle lié à la facture / au CRA s’il est actif et du bonDocumentType, sinon modèle par défaut du formateur, sinon modèle système du type.BillingTemplatePdfAppearanceMerger: copie du JSONsettingsdu modèle + injection couleur / taille /logoUrldepuisGetTrainerBillingSettingsHandlersiuseBillingSettingsAppearance, sinon logo éventuel sur l’entitéBillingTemplate.BillingDocumentTemplatePdfDataFactory: émetteur / client réels ; facture :lineItems(euros + TVA),document(numéro, dates, objet), colonnes depuissettings.table, IBAN/BIC entreprise, blocs légaux facture (invoiceBodyLegalMentionsdepuis la facture, pénalités / confidentialité / texte custom depuisTrainerBillingSettings, comme l’aperçu HTML) ; CRA :craLineItems(date, formation, classe, durée, notes, source iCal/manuel — mêmes clés que l’aperçu YAML),craDocument(code + période affichée, fusionnée avec les défauts d’aperçu),craSummary(totaux / modules / sessions), colonnes CRA depuissettings.table; dans les deux cas, tranchedocument_data(customFields,footerCustomText). Les options visuelles du modèle (modules/sessions, signature, pied, logo, etc.) sont appliquées par les compileurs Twig à partir dessettingsfusionnés.PreviewBillingTemplateCommand::processForDompdf()(données viaPreviewInvoiceBillingTemplateRenderCompiler/PreviewActivityReportBillingTemplateRenderCompiler) utilise un Twig dédié PDF :pdf/billing_template_pdf.html.twig(facture + CRA) pour une structure compatible Dompdf (tables/layout stables), avec conversion du logo en data URI. Les marges@pagediffèrent selon le type : CRA conserve une marge bas large (réserve pour le pied fixe signatures / légal), la facture utilise une marge bas plus faible pour mieux remplir la première page et éviter un saut isolé avant le bloc Paiement. Espacement des champs personnalisés (métadonnées) resserré en PDF et aligné visuellement sur l’aperçu. Le tableau des lignes facture est inclus viapdf/billing_template_pdf_invoice_table.html.twiget rend désormais par défaut : Date, Classe, Désignation (sur une seule ligne), Qté, Prix unitaire, Total HT (la classe est alimentée depuisInterventionEvent -> CalendarConfiguratorAssignment.className, avec fallbackclassLocation). Le compileur facture exposevatDetailRows(TVA par taux + bases HT) pour le bloc « Détails TVA » et le récapitulatif ;summary.subtotalGrossetsummary.discountTotalpermettent au PDF d’afficher Sous-total HT, Remise puis Total HT lorsque des remises lignes sont présentes. L’aperçu navigateur continue d’utiliserpdf/billing_template_preview.html.twigviaprocess().
Ajustements de rendu PDF (Facture + CRA)
- Typographie unifiée (facture + CRA) : une seule police par support — DejaVu Sans (PDF Dompdf) / Inter (aperçu HTML) — et une seule taille de base (
fontSizePx: 12 / 13 / 14 px selonstyle.fontSize). Corps, titres, libellés, tableaux, montants, SIRET/TVA et pied de page héritent de cette base ; la hiérarchie visuelle repose sur la graisse et la couleur, pas sur des tailles ou polices différentes (plus de0.85emni de monospace pour les chiffres). - Logo conditionnel : si l’option logo est active mais qu’aucun fichier n’est disponible, aucun placeholder ni espace réservé n’est rendu.
- Footer optionnel réel : un pied personnalisé vide n’affiche plus de texte placeholder (« zone texte libre ») ; le bloc de pied est omis s’il est totalement vide.
- Bloc paiement compact : section virement/RIB condensée (coordonnées sur 1 à 2 lignes max) pour limiter les sauts de page.
- Compaction verticale globale : paddings, marges et hauteurs de ligne réduits côté facture et CRA pour favoriser un rendu sur une seule page quand le volume le permet.
Colonne Classe sur facture
- Le
lineItemfacture persiste désormaisclass_name(entitéInvoiceLineItem) comme pour les lignes de CRA. - En génération de facture depuis CRA, la valeur est copiée depuis la ligne CRA quand disponible.
- Si un
InterventionEventest lié, un fallback reste possible viaCalendarConfiguratorAssignment.classNamepuisInterventionEvent.classLocation, sans écraser une valeur déjà saisie. - La colonne
Classeest optionnelle dans le modèle facture (commeRemise/Total TTC) et n’est rendue en PDF que si activée danstable.columnsVisible.
Le logo distant (settings.style.logoUrl) est téléchargé et converti en data URI via Domain/Service/Invoicing/UrlToDataUriConverterInterface (impl. Infrastructure/Pdf/GuzzleUrlToDataUriConverter, Guzzle, JPEG/PNG/GIF, taille max ~2,5 Mo) pour éviter les images cassées dans le PDF.
Extension PHP : Dompdf exige ext-gd pour intégrer les images dans le PDF. Les images Docker (.infra/dev/Dockerfile, .infra/prod/Dockerfile) incluent gd. En local hors Docker : activer le paquet php-gd / php8.3-gd selon l’OS.
Les champs libres et le pied personnalisé du document sont pris en compte dans les deux Twig (fusion document + valeurs issues de BillingDocumentDynamicDataKey).
Cycle de vie stockage (facture vs CRA)
- Facture : tant que le statut est
DRAFT,GET …/invoices/{uuid}/pdfappelle uniquementGenerateInvoicePdfCommand(pas d’écriture S3). À la finalisation, après persistance,FinalizeInvoiceCommandémetInvoiceFinalizedEvent; le subscriberInvoiceFinalizedEventSubscriberappelleInvoicePdfObjectStorageService::writeFinalizedPdf()(génération + écriture Flysystem/S3). Le préfixe de dossier estStoragePathType::TRAINER_INVOICE_PDF({trainerUuid}/billing/invoices/) ; le nom d’objet est l’UUID de la facture (pas de colonne dédiée en base). Pour les factures non brouillon,GenerateInvoicePdfHandlerpasse parreadOrGenerateAndPersist(): lecture S3 si présent, sinon génération + écriture + corps de réponse (rattrapage si l’écriture à la finalisation a échoué). - CRA : chaque
GET …/activity-reports/{uuid}/pdfregénère le PDF viaGenerateActivityReportPdfCommand; aucun archivage S3.