# Rapport v1.16.1 — « plus jamais de SQL à la main » : les gestes qui manquaient, les homonymes, le paiement d'avance

Base : `v1.16.0` (`baaa8ab`, en production depuis le 16/10/2026). Tag : `v1.16.1`.
Six commits, un par partie (A à F), puis les suites de la relecture indépendante.
**Suite complète : 1 604 tests au vert, 10 093 assertions** (1 561 à v1.16.0).
Aucune dépendance composer ni npm. `public/build/` recompilé.

## Les migrations — toutes additives et idempotentes

| Migration | Ce qu'elle fait |
|---|---|
| `2026_10_16_200000_les_versements_de_prime` | table `commission_versements` (prime, opération, montant, date ; unique prime × opération), **remplie depuis les primes versées** : montant = celui de la prime, date = `date_versement`, opération = `commissions.transaction_id`. Une prime qui a déjà sa ligne n'en reçoit pas une seconde. `commissions.transaction_id` reste (première opération). |
| `2026_10_16_210000_les_constats_ecartes` | table `constats_ecartes` (type, clé, empreinte, libellé, motif, auteur, date ; unique type × clé). |
| `2026_10_16_220000_l_expediteur_des_modeles` | `email_templates.adresse_expedition_id` (nullable) ; `portail_releve_primes` → facturation@, `portail_recap_cabinet` → domiciliation@, **si ces adresses existent** et seulement si le modèle n'a pas déjà une adresse. |

Aucune colonne supprimée ni convertie. Aucun fichier supprimé ni déplacé (`git diff --diff-filter=DR v1.16.0 v1.16.1` vide).

## Requête SQL du 16/10 → geste qui la remplace

| Requête passée par phpMyAdmin le 16/10 | Geste désormais |
|---|---|
| Prime n° 36 (YNNOVA AUTO) : `apporteur_id` 3, « Amina » | Primes → **« Changer le bénéficiaire… »** (nom sans compte + cabinet ; aperçu ; motif ; trace) |
| Client n° 131 (YNNOVA AUTO) → cabinet n° 3 | même geste : case **« Rattacher aussi le client »** (jamais d'office s'il a déjà un autre cabinet) |
| Prime FES HOSTING : montant 300 → 400 | Primes → **« Corriger le montant… »** (prime versée ; jamais sous le versé ; la différence redevient due ; motif ; trace) — *créé par le balayage F3* |
| Prime FES HOSTING : « Rachida Elfassi », note citant op. 1900 et 1932 | **« Changer le bénéficiaire… »** + **« Rattacher un versement déjà au journal… »** (op. 1932) ou `reprise:versement-de-prime 23 --operation=1932` : la prime porte ses deux opérations, plus besoin de note |
| Tiers des op. 1900 / 1932 → « Rachida Elfassi » | Journal → Modifier l'opération (existait) |
| Cabinet n° 2 renommé, contact Rachida Elfassi, téléphone | Fiche du cabinet → **« Modifier le contact… »** (nom du cabinet, contact, téléphone, e-mail) ; la fiche montre « Contact : nom · téléphone · e-mail » |
| Allam Ghita : C1 → 14/04/2027 (6 mois), C2 15/04 → 14/10/2027, échéance C2, limites 31/03 et 30/09/2027 | Onglet Contrats → **« Corriger les dates… »** (même payé ; montants, factures, numéros inchangés). À l'avenir : **« Encaisser d'avance… »** ou « Enregistrer un paiement » de 1 440 DH → un seul contrat prolongé |
| Op. n° 1950 « voulue » (aucune requête, mais citée à vie) | Contrôles → **« C'est voulu »** (motif) |

## A — Les bénéficiaires sans compte ne se confondent plus

- **Clé** : `Commission::cle(user, nom, cabinet)` → `u:3`, `n:laaroussi`, `n:rachida@2`.
  `scopeDuBeneficiaire(user, nom, cabinet)` : sans compte, le nom **et** le cabinet
  (null = sans cabinet ; `'*'` = le nom seul, pour la seule suggestion de montant de
  `reprise:paiement-hors-echeancier`). Page Primes (synthèse, Verser), rapport,
  `SituationDesPrimes`, espace cabinet, relevé, verrou anti-double et diagnostic
  regroupent par cette clé. Le choix « Prime pour » porte le cabinet (`c:2:Rachida`).
- **`--beneficiaire`** : un nom qui désigne des primes de plusieurs clients ou une
  prime déjà rattachée ailleurs → tableau (prime, client, montant, statut, date,
  opération, cabinet actuel) et **rien n'est relié** sans `--prime=ID` (répétable) ;
  `--appliquer` sans `--prime` échoue. Le diagnostic (famille 4) le classe « à décider »
  avec la commande `--prime=`.
- **« Changer le bénéficiaire… »** : compte, ou nom + cabinet ; montant, statut, dates
  et opérations inchangés ; motif obligatoire ; trace (`App\Support\Trace`). Refusé sur
  la commission « nouveau client » d'un cabinet. Le libellé figé de l'opération ne
  change pas : la page dit le bénéficiaire actuel à côté (B6).
- **Contact du cabinet** : affiché en une ligne, rien de cliquable ; « Modifier le
  contact… ». La ressemblance de la commande compare au contact **et** aux noms sans
  compte déjà rattachés au cabinet.

Tests : `tests/Feature/V1161/HomonymesSansCompteTest.php` (deux Rachida → deux lignes,
deux soldes ; cas du 16/10 sans `--prime` → rien ; prime d'un autre cabinet jamais
déplacée ; changer le bénéficiaire à l'écran ; client d'un autre cabinet non déplacé ;
contact). Deux tests v1.16.0 adaptés à la nouvelle clé (`n:rachida@ID`, `verser(…,
apporteurId:)`).

## B — Une prime versée en plusieurs fois

- **Versée** quand Σ versements = montant ; sinon **« Versée en partie — reste N DH »**
  (le statut reste « acquise » : tout ce qui lit « acquise » comme « due » reste juste).
- `SituationDesPrimes` compte **chaque versement à sa date et pour sa part** : Dû(D),
  versées sur la période, reste (une prime en partie versée reste due pour son
  reste), avances, relevé (la part de chaque prime dans un versement). Le fil
  « Quoi de neuf » du cabinet aussi (jumeau trouvé). Lecture de repli : une prime
  versée sans ligne vaut un versement unique (lecture seule).
- **« Verser »** reste le seul geste qui crée de l'argent : une opération ; le reste,
  après les primes soldées, va **en partie** sur la suivante (avant : la prime non
  couverte était laissée de côté, l'opération portait de l'argent rattaché à rien).
- **« Rattacher un versement déjà au journal… »** : sorties rattachées à aucune
  prime, montant ≤ reste (catégorie des primes d'abord) ; aperçu ; trace ; jamais
  d'opération créée. « Modifier » ne descend pas sous le versé ; « Supprimer » est
  masqué dès qu'un versement existe.
- **Contrôles** (diagnostic, famille 3) : prime versée dont les versements ≠ montant ;
  **opération rattachée au-delà de son montant** (FES HOSTING après migration :
  op. 1900 de 300 portant 400) ; opération de la catégorie des primes rattachée à
  aucune prime (op. 1932, et le versement Laaroussi de 2025).
- **`reprise:versement-de-prime {prime} --operation=`** (simulation par défaut) :
  ramène la ligne qui dépasse son opération, rattache l'opération désignée.
  `reprise:prime-en-double` déplace désormais les lignes de versement avec la prime.
- Page Primes : « Op. n° 1900 (300 DH) + n° 1932 (100 DH) — à Rachida Elfassi ·
  libellé : « Versement de prime — Rachida » ».

Tests : `PrimeEnPlusieursVersementsTest.php` (migration idempotente ; diagnostic ;
reprise 400 = 300 + 100 sans opération ; Dû(D) 400 / 100 du 08/09 au 06/10 / 0 dès le
07/10 ; geste à l'écran ; versement qui ne tombe pas juste ; refus). Le jeu de recette
des primes de v1.16.0 reste vert.

## C — Le paiement d'avance

- **Tous les « Enregistrer un paiement »** (fiche client, Recouvrement, échéanciers,
  Nouvelles demandes, paiement déclaré par un cabinet) : au-delà de ce qui est dû,
  « 1 440,00 DH = échéance du 15/10/2026 (720,00 DH) + 1 semestre d'avance
  (15/04/2027 → 14/10/2027, 720,00 DH). » ; **choix obligatoire, rien de coché** :
  encaisser l'avance, ou tout garder en trop-perçu noté au paiement. Reliquat dit et
  rangé seulement comme le gérant le dit : acompte sur la période suivante (créée,
  reste due) ou trop-perçu (à rembourser / imputer).
- Une échéance **déjà prévue** du même contrat est payée d'abord (rien créé).
- L'avance **prolonge le même contrat** (`PaiementDAvance::prolonger`) : période =
  lendemain de la fin de la dernière échéance, durée = périodicité ; fin, durée et
  limite (fin + 1 − 15 j) portées ; un contrat « Expiré » redevient « Actif ».
- Une facture par échéance (née payée), une opération par encaissement ; **aucune
  prime** pour l'avance (le barème « renouvellement », s'il existe, la suggère).
- **« Encaisser d'avance… »** (fiche client, Autres actions) : 1, 2, 3 périodes ou 1 an,
  aperçu complet, et l'avertissement si de l'échu reste dû.

Tests : `PaiementDAvanceTest.php` — le cas Allam Ghita rejoué (par « Enregistrer un
paiement » et par « Encaisser d'avance… ») donne **un contrat** 15/10/2026 → 14/10/2027,
deux échéances 15/10/2026 et 15/04/2027 payées, limite 30/09/2027, deux factures
consécutives payées, aucune prime.

## D — Les contrats ne se saisissent plus de travers

- « Ajouter un contrat » : début = lendemain de la fin du dernier contrat ; durée =
  une période (elle suit la périodicité) ; fin et limite dites dans l'aperçu ;
  chevauchement : **« Prolonger plutôt C1 de N période(s) »** ou « Continuer quand
  même » (choix obligatoire ; remplace la case « Créer quand même »). Saisie
  détaillée : mêmes propositions.
- Cohérence : onglet Contrats (colonne Échéances en rouge, « 2 attendues pour 12
  mois »), notification à l'enregistrement, **famille 11** du diagnostic. Seul le
  **manque** est signalé (un contrat reconduit tacitement porte plus d'échéances que
  sa durée initiale : 56 faux signaux sur la copie du 01/10 avant ce choix, 7 après).
- « (Re)générer les échéances » ne recrée plus une échéance payée à côté d'elle
  (défaut ancien trouvé en écrivant D).
- **« Corriger les dates… »** : début, durée, fin, limite, dates et périodes des
  échéances (ou « recaler sur le début ») ; montants, paiements, factures, numéros
  inchangés ; aperçu avec avertissement de chevauchement ; motif ; trace par contrat et
  par échéance ; « nouvelle version du contrat (PDF) » sur demande.

Tests : `ContratsSaisisJusteTest.php` (C1/C2 d'Allam Ghita corrigés exactement comme le
SQL, factures et paiements identiques à l'octet).

## E — « C'est voulu »

Constats « à vérifier » des contrôles (jamais un bloquant) et « à décider » du
diagnostic : « C'est voulu » (motif) à l'écran **Contrôles** (le diagnostic s'y affiche
à la demande). « Écartés par le gérant (N) » avec motif, date, auteur, « Rétablir ».
Empreinte = la ligne entière (montants, dates, texte) : une donnée qui change fait
revenir le constat. `mobdie:controler-factures` et `mobdie:diagnostic` les disent ;
le diagnostic commence par **une ligne par famille** (`--famille=N` pour le détail).

Tests : `ConstatsEcartesTest.php` (op. 1950 écartée → plus citée ; montant modifié →
citée de nouveau ; écran ; diagnostic).

## F — Les petites douleurs

1. **Liens signés au clic** : `EspaceClient::lien()` écrit `…/ouvrir/{nature}/{clé}`
   (session du portail exigée), qui signe 15 min puis redirige ; `lienSigne()` garde
   l'ancienne forme. Rapport des primes : `primes.rapport.ouvrir`. Tests :
   `LiensSignesAuClicTest.php`, et trois tests v1.16.0 adaptés (ils visent désormais
   `lienSigne()` pour la signature et l'expiration).
2. **Expéditeur par modèle** : réglable dans Modèles de message ; `Messagerie::envoyer`
   prend l'adresse du modèle si elle est active, sinon l'adresse par défaut.
   Test : `ExpediteurParModeleTest.php`.
3. **Balayage des SQL historiques** (`3-sql/`, `0-CONTEXTE` §6) :

| Fichier | Geste aujourd'hui |
|---|---|
| `correctif-modele-ia.sql` (réglage `ia.modele`) | Réglages → IA (existe) |
| `mobdie-completude-et-anomalies.sql` (lieux d'exercice / de conservation) | fiche client → Modifier (existe) |
| `mobdie-coordonnees.sql`, `mobdie-enrichissement-ocr.sql`, `mobdie-nettoyage.sql` (champs vides remplis) | « Analyser les pièces » + vérification (existe) ; reprise ponctuelle, non rejouée |
| `mobdie-correctif-cin.sql` (CIN au mauvais représentant) | Pièces du dossier : refuser / remplacer, lecture oubliée (existe, v1.14) |
| `mobdie-corrections-journal(-v2).sql` (catégories) | Journal → Modifier l'opération (existe) |
| `mobdie-primes-salima.sql` (primes et versement rattaché) | saisie de prime + « Verser » / « Rattacher une opération » (existe) ; partiel : « Rattacher un versement déjà au journal… » (v1.16.1) |
| `mobdie-rattacher-comptes-courants.sql` (tiers) | Atelier des comptes courants + fusion de tiers (existe) |
| `mobdie-reparer-groupe-et-cpc.sql` | plus nécessaire : posé dans `Transaction::booted()` |
| `mobdie-resiliations.sql` (fiches manquantes) | assistant « Résilier… » (existe, v1.11) |
| 16/10 (six requêtes) | voir le tableau en tête |

Reste **nommé, non créé** : aucune requête historique sans geste.

## Recette sur la copie du 01/10 (base jetable) + faits du 16/10

Migrations : trois DONE ; 18 lignes de versement (2 100 DH). `reprise:versement-de-prime
23 --operation=1932` (simulation) : « Versement de l'opération n° 1900 ramené de 400,00 DH
à 300,00 DH … Opération n° 1932 du 07/10/2026 rattachée : 100,00 DH … Total versé :
400,00 DH sur 400,00 DH — soldée ». Diagnostic : famille 3 cite l'op. 1900 sur-rattachée,
l'op. 1932 et le versement Laaroussi (op. 1585, 14 400 DH) non rattachés ; famille 11 :
7 contrats ; contrôle 9 : l'op. 1950.

## Relecture indépendante (sous-agent, `v1.16.0..HEAD` contre le prompt) et suites

Règles d'or confirmées (aucune dépendance, migrations additives, seul « Verser »
crée de l'argent, numéros intacts, policies) ; Dû(D) juste avec versements partiels.
Corrigé avant le tag (commit « suites de la relecture ») :

- **moyen** — supprimer un encaissement effaçait une prime versée en partie et sa
  ligne de versement (et passait sa commission de cabinet « à revoir ») ;
- **moyen** — « À valider » : une avance refusée laissait la déclaration « encaissée »
  sans paiement ;
- **moyen** — l'avance prolongeait C1 par-dessus un renouvellement C2 déjà saisi, et
  pouvait créer une période après une échéance non couverte (trou) : désormais les
  échéances prévues du client se paient d'abord, l'acompte va sur la suivante, une
  prolongation qui recouvre un autre contrat est refusée ;
- **moyen** — la nouvelle version du PDF après « Corriger les dates… » réinscrivait la
  version de modèle du contrat (clause non signée) : gardée ;
- mineurs : note « Paiement d'avance » perdue ; « Modifier la prime » ouvert sur une
  prime versée en partie ; « Verser » en rattachant une écriture sans garde de part
  libre ni date ; `reprise:prime-en-double` sans fusion des lignes ; fins de mois
  (une règle) ; message après « prolonger plutôt » ; `--prime` avec plusieurs noms ;
  candidats sans virements internes ; migration de l'expéditeur.

Laissés, au rapport : une opération par échéance payée d'avance (1 440 DH = 2 × 720 au
journal, dit dans l'aperçu) ; le reçu de commission du portail n'est servi que pour une
prime entièrement versée ; une saisie libre « Une autre personne… » crée un bénéficiaire
sans cabinet (choisir dans la liste celui du cabinet). Le relecteur affirmait l'absence
de `ToutesLesPagesTest` / `ToutesLesActionsTest` : faux, ils sont dans
`tests/Feature/Diagnostic/` et ouvrent chaque nouveau geste ; le paiement d'avance est
testé depuis la fiche client, l'action partagée (Recouvrement, échéanciers, Nouvelles
demandes) et « À valider ».

## Ce qui reste tel quel

- Le versement Laaroussi (op. 1585) n'a aucune prime en base : il apparaîtra en
  famille 3 tant qu'il n'est pas déclaré « C'est voulu ».
- `.env` local : `APP_URL` porte `:8000` ; la suite se lance avec
  `APP_URL=https://app.mobdie.ma` (sinon `PortailSousDomaineTest` échoue, comme avant
  ce lot).
