# Rapport v1.5.3 — Ce qui se déduit, ce qui s'écarte, et ce qui s'annonce

Une cause, plusieurs conséquences. Le tableau de bord affichait « Erreur lors du
chargement de la page » et laissait un cadre blanc à la place du graphique
d'évolution mensuelle :

```
#1 app/Filament/Widgets/EvolutionMensuelleWidget.php(52):
   Carbon::createFromFormat('Y-m', '')
```

Quatre opérations du journal — 1851, 1852, 1854 et 1856, saisies entre le 17 et
le 31 juillet 2026 — portaient un `month` à NULL. Elles ont été réparées en base
avant ce lot ; ce lot fait en sorte qu'il ne s'en crée plus, qu'un graphique ne
meure pas pour autant, et que les deux résultats du tableau de bord cessent de
se contredire.

**Recette** : 723 tests, 3 535 assertions, tout au vert (692 avant le lot).
`./vendor/bin/pint` passé, `npm run build` relancé. Aucune migration.

---

## 1. Le mois n'est plus jamais posé par l'interface

**Le défaut.** Dans `TransactionResource` :

```php
DatePicker::make('date')
    ->afterStateUpdated(fn ($state, callable $set) => $set('month', $state ? Carbon::parse($state)->format('Y-m') : null)),
```

Le mois était calculé par un effet de bord du formulaire, déclenché quand
l'utilisateur *modifie* le champ date. Or ce champ arrive pré-rempli à la date du
jour : celui qui l'accepte tel quel — le cas ordinaire — ne déclenche jamais le
rappel, et l'opération naît sans mois. C'est arrivé quatre fois.

**Le correctif.** Le mois est une donnée déduite : il ne se saisit pas. Il est
désormais calculé par le modèle `Transaction` lui-même, au `saving`, à partir de
la date — création comme modification, quel que soit le chemin : formulaire,
action rapide, import comptable, encaissement d'échéance, versement de prime,
recatégorisation. Tous les endroits qui le posaient à la main ont été retirés :
`TransactionResource` (formulaire, duplication, recatégorisation en masse),
`NouvelleOperationAction`, `MouvementCompteCourantAction`,
`DomiciliationPayment::creerOperationTresorerie()`, `CommissionService`,
`ImportComptabilite`, `RattachementCompteCourant`.

C'est la même règle que les soldes de compte courant en v1.4.1 : ce qui se déduit
ne se stocke pas à la main. Elle est appliquée jusqu'au bout, et écrite dans
[ARCHITECTURE.md](../../ARCHITECTURE.md).

**La reprise.** `php artisan reprise:reparer-les-mois`, avec `--simulation`,
recalcule le mois depuis la date partout où il est absent ou mal formé — corbeille
comprise, car une écriture restaurée demain ne repasserait pas par là. Elle ne
servira qu'une fois, mais elle documente ce qui a été corrigé, et elle est sans
danger si on la rejoue.

## 2. Un graphique ne meurt plus sur une donnée aberrante

**Le défaut.** Une seule valeur vide a suffi à faire tomber le widget entier, et
avec lui l'affichage du tableau de bord.

**Le correctif.** `ComptaReport::evolutionMensuelle()` ne rend plus que les mois
lisibles ; `evolutionMensuelleEcartee()` dit combien d'opérations ont été mises
de côté, pour quel montant et sous quel libellé. Le graphique l'affiche sous la
courbe :

> 1 opération écartée de la courbe ((mois absent), soit 900,00 DH hors taxes) :
> leur mois est illisible. Le journal les compte, la courbe ne sait pas où les
> poser — « php artisan reprise:reparer-les-mois » le recalcule depuis leur date.

Une donnée aberrante qu'on ignore en silence finit par fausser un chiffre sans
que personne ne s'en aperçoive ; une donnée aberrante qui tue l'écran est encore
pire.

**La revue des autres.** Les dix widgets du tableau de bord ont été relus.
`EvolutionMensuelleWidget` était le seul à interpréter une valeur stockée comme
une date. Les autres lisent des nombres, des comptages, ou des dates déjà typées
par Eloquent, et leurs `match` ont tous une branche par défaut. Un second défaut
y a tout de même été corrigé au passage : `Carbon::createFromFormat('Y-m', …)`
complète par le *jour d'aujourd'hui*, si bien qu'un 31 juillet fait basculer
« 2026-02 » sur mars. Le premier du mois est maintenant écrit explicitement.

## 3. Le groupe et le poste CPC souffraient du même défaut

**Le défaut, et ce qu'il révélait.** L'exploitant l'avait remarqué : ses
opérations saisies à la main n'affichaient pas de groupe, là où celles créées par
l'application en portaient un. Même mécanique que le mois — le formulaire posait
`group` et `cpc_line` par un effet de bord déclenché au changement de catégorie,
et qui choisit la bonne catégorie du premier coup ne le déclenche jamais.

La conséquence a été mesurée. Après réparation en base des quatre opérations, le
« Résultat du mois » est passé de −3 323,67 à −7 908,83 DH — un écart de
4 585,16 DH, exactement la somme des quatre. Mais le « Résultat 2026 » n'avait
pas bougé d'un centime : −88 681,84 avant comme après. Deux indicateurs côte à
côte sur le même écran, qui ne bornaient pas leur période sur la même chose :
celui de l'année sur la date de l'opération — il était donc juste —, celui du
mois sur la colonne stockée, vide sur ces quatre lignes.

**Le correctif, en trois temps.**

*Le stockage.* `group` et `cpc_line` sont désormais posés par le modèle, et par
lui seul, depuis la catégorie. Les colonnes restent — une ligne de reprise peut
n'avoir aucune catégorie et porter tout de même le poste que le tableur du
comptable lui a donné, et une catégorie reclassée demain ne doit pas réécrire
l'histoire d'écritures déjà remises au comptable —, mais dès qu'une catégorie
existe, c'est elle qui décide. À défaut de tout, le poste vaut
`Transaction::POSTE_NON_VENTILE` : aucune opération n'est enregistrée avec un
poste CPC vide, parce qu'une colonne vide ne se voit pas, alors qu'un poste
« Non ventilé » saute aux yeux dans un rapport.

*L'affichage.* La colonne « Groupe » du journal et son filtre lisent
`category.group`, comme les rapports le faisaient déjà.

*Le calcul.* `resultatExercice()` et `resultatMois()` passent tous deux par
`ComptaReport::resultat()`, qui borne les deux sur la **date**. La somme des
douze mois d'un exercice égale son résultat par construction ; deux tests le
vérifient, dont un dans `UneSeuleRegleTest`.

Le choix est écrit dans [ARCHITECTURE.md](../../ARCHITECTURE.md), section « Ce
qui se déduit ne se stocke pas à la main ».

## 4. Le tableau de bord montre d'abord ce que l'exploitant regarde

**Ce qu'il a demandé.** Voir en premier les comptes courants d'associés, les
dettes de financement, la caisse et ses deux comptes bancaires — chacun pour
lui-même, pas noyé dans un total. La trésorerie paraissait en un seul chiffre
avec le détail en petit dessous ; les dettes, par leur seul total.

**Le correctif.** Un bandeau `PositionDuJourWidget` en tête d'écran (sort −4,
devant le recouvrement), six cartes :

| Carte | Valeur | Ce qu'elle dit en second |
|---|---|---|
| Caisse | solde du jour | variation depuis le 1er du mois |
| BP | solde du jour | variation depuis le 1er du mois |
| Attijari | solde du jour | variation depuis le 1er du mois |
| Comptes courants d'associés | dette totale | le solde dû à **chaque** associé, et le mouvement du mois |
| Dettes de financement | reste dû aux tiers | par prêteur, et ce qui a été remboursé ce mois-ci |
| Prochaine échéance Forsa | sa date | son numéro, sa mensualité, le capital restant |

La caisse, la BP et l'Attijari séparément : ce sont trois poches différentes, et
savoir laquelle est vide n'est pas la même information que connaître leur somme.
Le recouvrement et les dossiers à traiter restent, mais après. La carte
« Trésorerie totale » de la situation financière garde la somme et ne répète plus
le détail, qui est maintenant plus haut et lisible.

Deux méthodes nouvelles portent la variation : `Account::mouvementDepuis()` et
`CompteCourantAssocie::variationDepuis()`, plus `Lender::rembourseDepuis()` — la
même requête que le reste dû, bornée à une date.

## 5. Les charges à venir, calculées puis expliquées

**Ce qu'il a demandé.** Une lecture des opérations passées qui annonce les
charges prévisionnelles. C'est utile, et c'est un piège si on s'y prend mal.

**Le calcul d'abord.** `ChargesRecurrentesObservees` relit les trente-six
derniers mois du journal, regroupe les dépenses par **catégorie et par tiers**
(une catégorie fourre-tout alimentée par trois fournisseurs n'est pas une charge
régulière), et ne retient une série que si :

- ses passages se répètent à cadence **mensuelle, trimestrielle ou annuelle**,
  l'écart dominant représentant au moins la moitié des écarts observés ;
- elle compte au moins trois passages (deux pour une charge annuelle) ;
- ses montants restent **voisins de leur médiane** — ±25 % —, et la régularité
  finale, produit de la présence et de la proximité, atteint 0,5.

Le montant annoncé est la **médiane**, non la moyenne, qu'un passage exceptionnel
suffirait à emporter. Chaque prévision porte la liste des opérations qui la
fondent : l'écran les ouvre en une modale, ligne à ligne. Un chiffre qu'on ne
peut pas remonter jusqu'à ses écritures n'a rien à faire sur cet écran.

Trois signaux sont levés sur constat, pas sur intuition : une charge qui a
**doublé** (dernier passage ≥ 1,8 × médiane), une qui **dérive** (> 15 % d'écart),
une qui a **disparu** (rien depuis deux cadences). Une charge disparue n'annonce
plus rien : la dernière chose à faire est d'inscrire au prévisionnel une charge
qui ne vient plus.

**L'explication ensuite, et seulement si on la demande.** `LectureDesCharges` met
en mots ce que le calcul a trouvé. Elle ne produit aucun chiffre : les faits
transmis au modèle sont exactement ceux du tableau, et la réponse leur est
confrontée par `Support\GardeFouDesFaits` — tout nombre qui n'y figurait pas fait
rejeter la lecture entière, le tableau restant affiché. Un test le vérifie sur le
cas le plus tentant : le modèle qui « aide » en additionnant un trimestre.

`GardeFouDesFaits` a quitté le domaine Courrier pour `app/Support` : il sert
désormais à deux domaines, et deux écritures d'une même règle finissent toujours
par diverger.

**Le rapprochement.** La table des charges récurrentes déclarées existe et elle
est vide. L'écran propose d'y inscrire ce qui est manifestement régulier, d'un
bouton qui dit d'avance ce qu'il inscrirait — libellé, cadence, montant, jour du
mois, date de début. C'est une proposition, pas une écriture automatique, et elle
ne crée aucune opération de trésorerie.

L'écran est `Comptabilité → Charges à venir`, ouvert à `tresorerie.voir` ;
l'inscription demande `tresorerie.gerer`.

---

## Ce qu'il faut vérifier après déploiement

1. `php artisan reprise:reparer-les-mois --simulation` : elle doit annoncer
   « Aucune opération à réparer », les quatre lignes ayant déjà été corrigées en
   base. Si elle en trouve, la jouer sans `--simulation`.
2. Le tableau de bord s'ouvre, le graphique d'évolution mensuelle s'affiche, et
   aucune mention d'opérations écartées ne paraît sous la courbe.
3. « Résultat du mois » et « Résultat 2026 » : le premier doit désormais
   comprendre les quatre opérations de juillet.
4. Saisir une opération au journal **sans toucher au champ date** et **sans
   rouvrir la liste des catégories** : le mois, le groupe et le poste CPC doivent
   être renseignés.
5. `Comptabilité → Charges à venir` : vérifier qu'au moins une prévision s'ouvre
   sur ses opérations fondatrices, et que les montants correspondent au journal.
