# Rapport — v3.13.1 (factures : bouton « Émettre » — sortir une facture du brouillon)

**Lot du socle : se livre aux DEUX installations (IRADA et RUWAD), à l'identique.**
**Aucune migration. Aucune commande de reprise. Aucune dépendance composer nouvelle.**

---

## Le manque (constaté en prod)

Une facture créée dans l'app naît en **Brouillon** (`InvoiceService::create()` →
`InvoiceStatus::Brouillon` par défaut), mais **aucune action de l'interface ne permettait de la
passer en Émise** : le formulaire n'a pas de champ statut (voulu), et la table ne proposait que
« Marquer encaissée », Modifier, PDF, Supprimer. Les factures historiques venaient de la reprise
déjà « émises » — le trou n'était jamais apparu.

Conséquence : un brouillon est **exclu** des registres de revenu, des créances et des positions
de marché (`whereNotIn(Brouillon, Annulee)`) — la facture n'existait « nulle part » tant qu'on ne
pouvait pas l'émettre.

## Ce qui a été fait

### Action « Émettre »

- **Deux emplacements** : le menu de ligne de la liste des factures (`InvoicesTable::issueAction()`)
  et l'en-tête de la page d'édition (`EditInvoice`).
- **Visible seulement** quand `status === Brouillon`.
- **Confirmation** : « La facture N° … sera émise ; son numéro sera alors définitif —
  art. 145 CGI. »
- **Passage par le service** : `InvoiceService::markIssued()` — jamais un update inline. Les
  totaux sont refigés au passage (`refreshTotals`) pour que la facture entre dans les registres
  avec ses montants exacts. Statut → `Emise`, changement audité (owen-it) comme tout update.

### Permission

Gated par la policy **`issue`**, qui exige **`invoices.collect`** — la même famille que
l'encaissement : qui peut encaisser une facture peut l'émettre (super-admin, admin, gestionnaire,
comptable — grilles inchangées).

**Pourquoi pas une permission `invoices.issue` dédiée ?** La mise à jour automatique ne rejoue
pas les semences (`FinaliserMiseAJour` n'exécute que `migrate`) : une permission neuve resterait
**sans titulaire** sur les rôles déjà en production — seule la bypass super-admin passerait. La
contrainte « aucune migration » interdit aussi de la semer par migration. Réutiliser la famille
`collect` est le seul choix qui se comporte à l'identique sur IRADA, RUWAD et une installation
neuve.

### Verrou de numéro respecté (v3.13.0 §A intact)

Une fois émise, le numéro reste verrouillé : champ désactivé et non soumis (`InvoiceForm`),
renumérotation ignorée côté serveur (`EditInvoice::mutateFormDataBeforeSave` ne re-numérote que
les brouillons). Rien de cette mécanique n'a bougé — la recette le re-vérifie via l'action.

### Retour Émise → Brouillon : super-admin seulement

`InvoiceService::markDraft()` + action « Repasser en brouillon » (liste et page d'édition) :

- policy **`unissue`** retourne `false` pour tout le monde — **seul le super-admin** passe, par
  `Gate::before` (règle métier §6). Même une grille complète ne suffit pas ;
- refusé dès que la facture est **encaissée ou annulée** (garde service + visibilité) — pas de
  retour arrière automatique ;
- utile parce que l'émission verrouille le numéro : sans ce geste, corriger une émission par
  erreur imposerait de supprimer la facture.

### « Marquer encaissée » ne s'offre plus à un brouillon

- **Visibilité** : `collectAction` n'apparaît que sur une facture **Émise** (avant : tout sauf
  encaissée/annulée).
- **Garde service** : `markCollected()` refuse un brouillon avec un message clair (« Cette facture
  est encore en brouillon : émettez-la d'abord, puis encaissez-la. ») — la règle tient quelle que
  soit la voie d'appel, pas seulement l'UI.

### Badge

Rien à faire : `InvoiceStatus::color()` rendait déjà le brouillon en **badge gris** dans la liste
(gray / émise warning / encaissée success / annulée danger). Constaté, non modifié.

## Recette (tout au vert)

- Brouillon + action Émettre (liste ET page d'édition) → statut `emise` ; numéro verrouillé
  ensuite (renumérotation ignorée). — `InvoiceResourceTest`, `InvoiceServiceTest`
- L'action Émettre est invisible sur une facture émise / encaissée / annulée. — `InvoiceResourceTest`
- Un brouillon ne peut plus être « marqué encaissé » : action masquée + refus du service. —
  `InvoiceResourceTest`, `InvoiceServiceTest`
- `unissue` réservé au super-admin ; jamais offert sur une facture encaissée. — `InvoiceResourceTest`
- Non-régression : totaux, PDF (24 fixtures, deux variantes), numérotation v3.13.0 (suggestion
  16 avec {15, 900, 905}, unicité) — suite complète verte + `pint` + `npm run build`.

## Fichiers touchés

- `app/Services/Invoices/InvoiceService.php` — `markIssued()`, `markDraft()`, garde brouillon
  dans `markCollected()`
- `app/Policies/InvoicePolicy.php` — `issue()` (famille `collect`), `unissue()` (super-admin)
- `app/Filament/Admin/Resources/Invoices/Tables/InvoicesTable.php` — actions `issue`/`unissue`,
  `collect` réservée aux factures émises
- `app/Filament/Admin/Resources/Invoices/Pages/EditInvoice.php` — actions d'en-tête `issue`/`unissue`
- `tests/Feature/Invoices/{InvoiceServiceTest,InvoiceResourceTest}.php`
