Aller au contenu

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, logoUrl sur la ressource modèle). Pour POST …/preview, le front peut toutefois envoyer style.logoUrl (URL signée / publique) afin de rendre l’aperçu — clé autorisée dans InvoiceBillingTemplateSettingKey et ActivityReportBillingTemplateSettingKey. 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_URL dans frontend/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 :

  1. BillingTemplatePdfResolver : modèle lié à la facture / au CRA s’il est actif et du bon DocumentType, sinon modèle par défaut du formateur, sinon modèle système du type.
  2. BillingTemplatePdfAppearanceMerger : copie du JSON settings du modèle + injection couleur / taille / logoUrl depuis GetTrainerBillingSettingsHandler si useBillingSettingsAppearance, sinon logo éventuel sur l’entité BillingTemplate.
  3. BillingDocumentTemplatePdfDataFactory : émetteur / client réels ; facture : lineItems (euros + TVA), document (numéro, dates, objet), colonnes depuis settings.table, IBAN/BIC entreprise, blocs légaux facture (invoiceBodyLegalMentions depuis la facture, pénalités / confidentialité / texte custom depuis TrainerBillingSettings, 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 depuis settings.table ; dans les deux cas, tranche document_data (customFields, footerCustomText). Les options visuelles du modèle (modules/sessions, signature, pied, logo, etc.) sont appliquées par les compileurs Twig à partir des settings fusionnés.
  4. PreviewBillingTemplateCommand::processForDompdf() (données via PreviewInvoiceBillingTemplateRenderCompiler / 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 @page diffè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 via pdf/billing_template_pdf_invoice_table.html.twig et rend désormais par défaut : Date, Classe, Désignation (sur une seule ligne), Qté, Prix unitaire, Total HT (la classe est alimentée depuis InterventionEvent -> CalendarConfiguratorAssignment.className, avec fallback classLocation). Le compileur facture expose vatDetailRows (TVA par taux + bases HT) pour le bloc « Détails TVA » et le récapitulatif ; summary.subtotalGross et summary.discountTotal permettent au PDF d’afficher Sous-total HT, Remise puis Total HT lorsque des remises lignes sont présentes. L’aperçu navigateur continue d’utiliser pdf/billing_template_preview.html.twig via process().

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 selon style.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 de 0.85em ni 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 lineItem facture persiste désormais class_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 InterventionEvent est lié, un fallback reste possible via CalendarConfiguratorAssignment.className puis InterventionEvent.classLocation, sans écraser une valeur déjà saisie.
  • La colonne Classe est optionnelle dans le modèle facture (comme Remise / Total TTC) et n’est rendue en PDF que si activée dans table.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}/pdf appelle uniquement GenerateInvoicePdfCommand (pas d’écriture S3). À la finalisation, après persistance, FinalizeInvoiceCommand émet InvoiceFinalizedEvent ; le subscriber InvoiceFinalizedEventSubscriber appelle InvoicePdfObjectStorageService::writeFinalizedPdf() (génération + écriture Flysystem/S3). Le préfixe de dossier est StoragePathType::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, GenerateInvoicePdfHandler passe par readOrGenerateAndPersist() : 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}/pdf regénère le PDF via GenerateActivityReportPdfCommand ; aucun archivage S3.