# RAPPORT v1.8.0 — La facture et le devis rejoignent la génération du socle, sans rien perdre du métier

**879 tests au vert (4 415 assertions).** Quatre commits : A-B (sections,
préférences, facture), C (devis + conversion), D (hors-trésorerie), clôture.

## Ce qui a été fait

### A. Sections et options d'affichage (factures ET devis)

- Migration **additive et idempotente** (`Schema::hasColumn`) :
  `invoice_lines.section_title`, `quote_lines.section_title` ; sur `invoices`
  et `quotes` : `show_section_subtotals` et `show_line_numbers`, **défauts à
  OUI = rendu actuel** — l'existant ne bouge pas sans action de l'exploitant.
- Formulaire des lignes (`LigneRepeater`, commun devis/facture) : champ
  facultatif « Sous-titre de section ». Les lignes consécutives de même titre
  forment une section sur le PDF (bandeau + « Sous-total HT — titre »),
  regroupées par le nouveau `App\Support\SectionsDeLignes` (repris du socle).
- Deux bascules par pièce (`App\Filament\Support\PresentationDuPdf`, section
  repliée « Présentation du PDF » sur les deux fiches) : sous-totaux par
  section (visible seulement si des sous-titres existent) et numéros de lignes.

### B. Le PDF de facture nouvelle génération

- **« FACTURE N° 36/2026 » sur une seule ligne** (numéro en gras, taille
  réduite) ; en dessous Date + Échéance. Un brouillon affiche « — brouillon ».
  Un avoir dit désormais **AVOIR**, pas FACTURE.
- **Marges 1,0 cm** (1,8 cm en bas, réservée au pied), **désignation 58 %**
  (62 % sans numéros), P.U. et Total HT à 13 % — aucun montant tronqué.
  Montant arrêté en lettres conservé, ainsi que toute la logique retenue à la
  source / net à encaisser.
- **Pied de page 2 lignes fixé au bas de CHAQUE page** (recette dompdf :
  `position: fixed; bottom: -1.5cm` dans la marge basse de `@page`) : identité
  fiscale complète sur la première ligne, banque/RIB sur la seconde.
- **Cadre « Cachet et signature de l'émetteur » à taille de tampon physique** :
  hauteur 165 px (≥ 3,8 cm utiles sous le libellé), **identique dans les deux
  densités**, largeur 49 %, image du cachet à ~90 % de la hauteur intérieure,
  strictement contenue, ratio conservé.
- **Densité adaptative** — nouveau `App\Support\DensitePdf` (repris du socle
  v3.13.5 + hotfix v3.13.6) : rendu « confort » mesuré ; s'il déborde, essai
  « compact » ; sinon confort multi-pages. **Règle d'or respectée : les rendus
  de comptage sont des instances jetables, la méthode retourne toujours une
  instance dompdf NEUVE** — le `output()` final fait l'unique rendu.
  Multi-pages propre : en-tête de tableau répété (`table-header-group`), pied
  partout, bandeau de section jamais orphelin (`page-break-after: avoid`).
- La feuille commune `pdf/document-styles.blade.php` porte les deux densités
  (rendu direct sans densité → confort) ; `FacturePdf::render()` passe par
  `DensitePdf::resoudre()` et expose `documentAdapte()` pour les tests.

### C. Le devis, exactement au niveau de la facture

- Même gabarit : « DEVIS N° X » sur une ligne, Date + Validité, bloc Objet +
  Durée (le bloc références de MOBDIE reste le sien — aucun concept de marché
  public), sections/options du § A, densité du § B.
- **Deux cadres à taille de tampon** : « Cachet et signature du prestataire »
  et « Bon pour accord » (date et signature du client) — libellés inchangés,
  les tests v1.3.7 les verrouillent toujours.
- **Conversion devis → facture** : sections, sous-titres et préférences
  d'affichage suivent (aussi sur duplication de devis/facture et sur l'avoir).
  La traçabilité (`quote_id`) ne bouge pas.

### D. Encaissement HORS trésorerie

- Action explicite « Encaisser hors trésorerie » sur une facture émise
  (permission `paiements.enregistrer`, comme l'encaissement) : colonnes
  additives `invoices.hors_tresorerie` + `hors_tresorerie_motif` (**motif
  obligatoire**). La facture passe encaissée **sans écrire une seule ligne au
  journal** — ni transaction, ni paiement, ni échéance touchée. Badge « Hors
  trésorerie » sur la liste, motif en infobulle.
- « Annuler l'encaissement hors trésorerie » restaure l'état antérieur
  (émise, ou envoyée si elle l'était) ; le flux guidé « annuler le
  règlement » sait aussi le défaire proprement.
- **L'encaissement normal est strictement inchangé** — y compris « la facture
  née d'une échéance déjà payée naît payée » (test de non-régression dédié).

## La recette (tout mesuré sur le rendu dompdf réel)

- `tests/Domaines/Facturation/MiseEnPagePdfTest.php` (8 tests) : 3 lignes →
  1 page **confort** · 10 lignes + 2 sections + sous-totaux → 1 page ·
  40 lignes → ≥ 2 pages avec en-tête répété et pied partout · cadre ≥ 3,8 cm
  vérifié dans le HTML des **deux gabarits × deux densités** · masquage des
  sous-totaux et des numéros · défaut = rendu actuel · conversion conserve tout.
- `tests/Domaines/Facturation/EncaissementHorsTresorerieTest.php` (7 tests) :
  le journal (`transactions`) ne bouge pas, aucun paiement créé, l'échéance
  intacte, motif exigé, refus (brouillon/avoir/déjà réglée), annulation qui
  restaure, chemin normal inchangé.
- Un test existant adapté : `ModifierAnnulerSupprimerTest` moquait
  `Pdf::loadHTML` ; il moque `Pdf::loadView` (deux appels : la mesure, puis
  l'instance livrée). Tout le reste de la suite passe sans retouche, dont
  l'anti-clés-brutes (tout en français).

## Contraintes tenues

Aucune dépendance composer (dompdf ^3.1 déjà là, `composer.lock` intact).
Migrations additives idempotentes uniquement. Numérotation, échéancier, prêt
Forsa, `public/.htaccess` : intouchés. PDF rendus en contrôleur/file
uniquement. Aucun fichier déplacé ni supprimé.

## Déploiement

Rien de délicat : `artisan migrate --force` (deux migrations additives à
défauts sûrs) puis les quatre caches, comme d'habitude. `public/build/`
recompilé voyage dans le paquet. À vérifier à l'écran : le PDF d'une facture
existante (rendu confort, une page, pied 2 lignes), un devis, et l'action
« Encaisser hors trésorerie » dans le menu d'une facture émise.
