# v1.4.9 — La prime se décide, elle ne se calcule pas

## 1. Le modèle retourné

La v1.4.8 supposait un calcul : un barème par commercial et par événement,
appliqué automatiquement à l'encaissement. Ce n'est pas ainsi que la maison
travaille. Le modèle suit désormais le tableur : un montant décidé au cas par
cas, un compte de primes par personne, un versement qui solde.

**Ce qui a été gardé.** La table, son statut acquise/versée, le lien vers
l'échéance, la catégorie « Commissions commerciales » du journal.

**Ce qui a changé.**

* **La saisie.** Deux champs facultatifs — *Prime pour* et *Montant* — dans les
  trois fenêtres d'encaissement : barre d'actions rapides, échéancier d'un
  client, encaissement d'une facture. Écrits une fois, dans
  `App\Filament\Support\ChampsPrime`, pour que trois copies ne dérivent pas.
* **Le barème est devenu une commodité.** Il pré-remplit le montant quand il en
  existe un, laisse le champ vide sinon, et n'empêche plus rien. La génération
  automatique à l'encaissement a été retirée : c'est elle qui produisait un
  montant que personne n'avait décidé.
* **Après coup.** Action « Ajouter une prime » sur la ligne d'une échéance
  encaissée — une à la fois, aucun bénéficiaire deviné. C'est le chemin des
  vingt-neuf.
* **Le bénéficiaire s'est ouvert.** Un utilisateur, ou une personne nommée qui
  n'en est pas une — le parti pris des prêteurs. Laaroussi et Rami existent
  désormais dans les comptes sans avoir de compte.
* **Le solde et le versement.** Écran *Facturation → Primes* : une ligne par
  bénéficiaire, acquis / versé / solde dû, et « Verser la prime ». Le versement
  solde de la plus ancienne à la plus récente, écrit **une seule** opération de
  trésorerie en sortie, et chaque prime soldée garde le lien vers elle. Partiel :
  le reliquat reste dû, et la prime qu'il ne couvre pas reste entière — on ne
  coupe pas une prime en deux.
* **Le rattachement.** La fenêtre de versement propose, à côté de « créer
  l'opération », de rattacher une écriture existante non encore rattachée. Sans
  cela, régulariser les 14 400 DH de Laaroussi aurait fait sortir deux fois le
  même argent.

**Ce qui a disparu.** L'encart des barèmes manquants — un barème absent n'est
plus un défaut. L'encart des vingt-neuf est devenu une invitation : « ces
échéances n'ont pas de prime, ce n'est pas un défaut ; si l'une d'elles revient à
quelqu'un, ajoutez-la ». Le bouton « générer les commissions manquantes » a
quitté l'écran ; la commande subsiste pour appliquer un barème en lot, et son
aide le dit.

**Une garantie tenue par le code, pas par la consigne.** Acquérir n'écrit rien en
trésorerie ; verser écrit une opération, une seule, et refuse un montant
supérieur au solde.

## 2. Le widget muet

`IntegriteSerieWidget` produisait une sortie vide quand la série était saine.
Livewire exige une racine unique : d'où « missing root tag », l'erreur au journal
et le cadre blanc.

Trois choses le corrigent :

1. **L'enveloppe `<x-filament-widgets::widget>` est hors de la condition** — il y
   a toujours une racine, quoi que le widget ait à dire.
2. **`canView()` rend faux quand il n'y a rien à montrer** — Filament le retire
   de la grille au lieu d'y laisser un cadre vide. L'enveloppe seule n'aurait pas
   fait disparaître le rectangle.
3. **`isDiscovered = false`** — et c'est la cause du symptôme que vous avez vu :
   sans lui, la découverte automatique posait un widget de facturation sur le
   tableau de bord, où il n'a rien à faire.

Le calcul, qui sert deux fois, est mémorisé — mais **par le service, enregistré
en portée de requête**, et non par une propriété statique sur le widget : une
mémoire statique aurait survécu à la requête et rendu une réponse d'avant.

`BaremesManquantsWidget` portait le même défaut ; il a disparu avec le modèle
qu'il servait, comme vous l'aviez prévu.

**Le balayage.** Les seize vues de composants Filament et les deux vues Livewire
du projet ont été vérifiées : toutes ouvrent sur un élément, aucune sur une
condition. Un test le rejoue à chaque exécution — il lit les vues, retire les
commentaires Blade et les blocs `@php` (qui ne comptent pas comme racine), et
refuse une première ligne qui commence par `@if`, `@unless` ou `@foreach`.

## Recette

`551 tests, 2 840 assertions, tous au vert.` `./vendor/bin/pint` : rien à
reprendre. `npm run build` : 56 modules.

`CommissionsTest` est réécrit de bout en bout (18 cas) sur le nouveau modèle ;
`RacineDesComposantsTest` est nouveau (6 cas, dont le balayage). Trois tests
existants qui affirmaient la génération automatique disent maintenant la vérité :
le barème propose, l'encaissement ne décide pas.

**Un mot sur l'état actuel.** La commande de rattrapage ne crée toujours rien, et
c'est normal : 29 encaissements n'ont pas de commercial de recouvrement, 9 n'ont
pas de barème. Ce n'est plus un blocage — ces 38 lignes attendent une prime
saisie, ou n'en porteront jamais, ce qui est un cas parfaitement ordinaire.
