# Rapport v1.6.1 — Le courrier entre, le quotidien aboutit, les données disent vrai

Le plus gros lot du projet, en trois parties commitées séparément :
A — la messagerie (envoi et réception), B — le quotidien (relances,
compteurs, notifications, mode relevé), C — la vérité des données
(registre légal, caisse, tiers, archive).

**Recette** : 823 tests, 4 117 assertions, tout au vert (813 avant le lot).
`./vendor/bin/pint` passé, `npm run build` relancé, trois migrations,
aucune dépendance composer ajoutée.

---

## Partie A — La messagerie : envoi et réception

**L'envoi passe par la file d'attente, avec réessais.** `EnvoiEmail` ne parle
plus au serveur : il inscrit le message au registre — statut, corps HTML tel
qu'il partira, pièces en références — et `EnvoyerCourrierJob` expédie : trois
essais à attente croissante (1 min, 5 min) sur les erreurs passagères, échec
posé au premier essai sur les erreurs franches (adresse invalide,
authentification refusée), la raison en français au registre, « Renvoyer »
sur les échecs. Tous les canaux sont passés : zone d'envoi, relances (unité
et lot), envoi de documents et de pièces, liens de coordonnées, formulaire
public, alertes de conformité, compte rendu de sauvegarde. L'écran confirme
« votre message part dans un instant » sans attendre le serveur.

**Le registre est l'historique complet.** Chaque envoi garde son HTML rendu
— il se rouvre des mois plus tard —, sa signature, ses pièces, son fil de
conversation (Message-ID) ; le formulaire public et les alertes internes,
qui n'écrivaient rien, écrivent (client facultatif au registre). La fiche
client gagne un onglet Courrier : la conversation chronologique, envoyés et
reçus mêlés, aperçu au clic, renvoi possible.

**Joindre depuis le dossier** : pièces d'archive cochées telles quelles,
factures et devis chacun en version avec ou sans cachet — le PDF se produit
au moment de l'envoi —, poids total annoncé (« 3 pièces, 2,1 Mo ») et
avertissement avant 20 Mo. **Brouillons** : un geste explicite, tout l'état
conservé (pièces comprises, posées sur le disque), liste avec reprise en un
clic, disparition à l'envoi. **Envoi programmé** : « Envoyer le », le
planificateur dépile à la minute, annulable et modifiable au registre
jusqu'au départ. **Envoi groupé** : « Écrire aux sélectionnés » depuis la
liste des clients et un mode « plusieurs destinataires » — un message par
client, ses variables remplies, aperçu « voir pour : X », confirmation qui
annonce le compte exact et nomme les sans-adresse AVANT l'envoi, débit
d'une poignée par minute (réglable : `courrier.envois_par_minute`), et le
compte rendu nominatif à la cloche quand le dernier message est passé.

**La réception.** Un client IMAP écrit à la maison sur l'extension PHP
`imap` — **aucune dépendance composer** — et borné au besoin : lister les
nouveaux UID, télécharger un message, tester la connexion. Jamais de
suppression : la boîte reste intacte. La relève tourne toutes les cinq
minutes (plus « Relever maintenant »), incrémentale par UID, pièces jointes
bornées à 5 Mo (au-delà : listées, téléchargées à la demande), échec de
connexion journalisé sans casser le planificateur. Les adresses
d'expédition portent le serveur entrant (pré-rempli : même hôte, 993, SSL)
et « Tester la relève » rapporte l'erreur du serveur en clair.

Le rattachement : les en-têtes In-Reply-To/References d'abord — la réponse
à un envoi de l'application rejoint son fil et son dossier à coup sûr —,
l'adresse de l'expéditeur ensuite (client, puis apporteur). Ce qui ne se
rattache pas va dans « À classer », avec rattachement manuel et
mémorisation de l'adresse sur la fiche. La **boîte de réception** ouvre le
groupe Messagerie : non-lus en évidence et en badge, aperçu d'une ligne,
HTML nettoyé par la liste de balises fermée de l'éditeur, **images
distantes bloquées** avec « Afficher les images » par message, pièces
jointes par route authentifiée sur le disque privé, lu/non-lu à l'unité et
en lot — rien d'autre. **Répondre** pré-remplit tout (Re :, citation, la
boîte qui a reçu) et part par le circuit de la première moitié. Les
**rebonds** ferment la boucle : MAILER-DAEMON reconnu, message d'origine
marqué en échec, adresse du client marquée morte sur la fiche, notification,
et les relances vers cette adresse repassent en « contact à corriger ».

**L'extension imap était ABSENTE sur la machine de développement** : le
chemin sans extension est donc celui qui a été réellement exercé — écran
explicatif, zéro erreur — et le client IMAP est éprouvé contre une boîte
simulée, comme la recette l'exigeait. Sur le serveur de production, si
l'extension manque aussi, la boîte de réception l'explique en une phrase et
tout le reste fonctionne.

## Partie B — Le quotidien qui aboutit

- **La file de relance se débloque sur place** : « Saisir le contact » sur
  chaque ligne bloquée (e-mail, téléphone), l'adressage refait aussitôt par
  la cascade habituelle, la relance repart. L'écran écrit ce que c'est :
  « Aucune adresse connue — saisissez le contact », « Aucune voie — ni
  e-mail, ni apporteur joignable » — plus de tirets à deviner. L'envoi en
  lot nomme les sans-adresse au lieu de les sauter en silence.
- **Plus jamais de ligne anonyme** : raison sociale, sinon représentant,
  sinon code du dossier, sinon « Dossier n° X » (`Identite::duClient`), sur
  le Recouvrement, l'Échéancier global et les Analyses IA ;
  `reprise:signaler-les-orphelins` liste ce qui a perdu son client.
- **Les badges disent ce que l'écran montre** : Dossiers incomplets compte
  les actifs — la liste qui s'ouvre — et l'écran annonce son filtre en
  toutes lettres ; le Recouvrement compte l'onglet « À relancer », celui
  sur lequel on arrive.
- **Le retard commence le lendemain, partout** : Forsa (le widget la disait
  « échue et non réglée » dès minuit une), factures (`estEnRetard`),
  bouton « Relancer cet impayé », montant échu du widget, validité des
  devis. L'ancienneté s'écrit d'une seule main (`Anciennete::dire`) :
  jours ≤ 60, mois au-delà, années après 24 mois. « À sortir » regroupe
  par client : un dossier, une ligne, le cumul dû, le nombre d'échéances.
- **La cloche** : le récapitulatif du matin (relances à valider, échéance
  Forsa qui approche, sauvegarde de la nuit — un clic vers la file
  d'envoi) ; et les incidents à l'instant, en danger : sauvegarde en échec
  ou muette 36 h, caisse passée en négatif (lien vers le journal filtré),
  analyse IA échouée.
- **Le mode relevé** : verrouille le compte, garde la fenêtre ouverte,
  enchaîne les dates en ordre croissant, et tient le compteur — « 12
  opérations saisies, total −4 230,00 DH » — à pointer contre le papier.
- **L'écran d'accueil du téléphone** : manifeste complété (standalone,
  /admin, icônes maskable, portée), balises iOS, et la vue des charges à
  venir défilable sur petit écran.

## Partie C — La vérité des données

- **Registre légal (le point sensible).** La cause : la « sortie » se
  calculait sur la fin du dernier contrat, même pour un dossier vivant — un
  contrat d'un an arrivé à terme sans avenant donnait « sortie 05/01/2026 »
  à KAM TRADIV, actif. La règle posée : la sortie est la fin EFFECTIVE de
  la domiciliation — date d'effet de la résiliation, ou fin du dernier
  contrat d'un dossier clos — et **un client actif n'a pas de date de
  sortie**. Le test du lot : client actif + échéance impayée + contrat
  échu = aucune sortie.
- **La caisse.** `reprise:expliquer-la-caisse` a rejoué le fil réel : le
  premier passage sous zéro est l'opération n° 1769 du 05/06/2026 (950 DH
  en sortie) ; le fil complet, solde après chaque ligne, est rendu à
  l'exploitant — rien n'est corrigé d'office. La garde des écrans de
  saisie bloque désormais la sortie qui rendrait une caisse négative, avec
  la case « Enregistrer quand même » pour l'assumer, et la cloche sonne le
  franchissement.
- **Les charges prévisionnelles.** Les séries se rapprochent par le tiers
  rattaché plus la catégorie ; le libellé, normalisé, n'est qu'un
  départage : le loyer de 4 050 DH coupé en deux par un changement de
  libellé redevient une série, prévue le mois suivant (test du cas réel).
  Un bandeau compte les séries régulières non déclarées et pousse vers la
  charge récurrente déclarée — la table qui rendrait la prévision fiable.
- **Les tiers.** `reprise:normaliser-les-tiers --simulation` sur les
  données réelles propose une trentaine de fusions (« comission bp » →
  « commission bp », « attijati » → « attijari », « SUMMUR CAMP » →
  « SUMMER CAMP »…) et une centaine de mises en casse lisible ; la fusion
  re-rattache les opérations avant de supprimer (l'ordre de l'absorption
  des comptes courants), les associés et les tiers portés par un prêteur
  sont intouchables. À la création, l'existant proche se propose.
- **Les identités.** L'ICE se refuse hors quinze chiffres à la saisie et
  se complète à l'import (trouvé en réel : Ossana Industrie, 13 chiffres) ;
  les pointillés de gabarit se nettoient à l'enregistrement (trouvé : 10
  adresses « …………… ICE ou TP N° …………… ») ; la civilité devient une liste
  fermée M./Mme, extraite automatiquement des noms qui la portaient, et
  les actes la lisent en premier ; une date de pièce antérieure de plus de
  deux ans à l'entrée du dossier s'alerte « à vérifier » (aucune date en
  base locale au moment du lot : la garde attend les prochaines saisies).
  `reprise:signaler-les-identites-douteuses` liste le tout.
- **Les factures.** Une facture réglée affiche « Réglée le X » ; une
  facture émise après son échéance prend sa date d'émission comme limite
  (la 37/2026 du constat) ; l'avertissement de date corrigée devient un
  badge discret ; `reprise:nettoyer-les-designations` coupe les montants
  concaténés — rien trouvé par l'heuristique sur la base locale, la
  commande reste pour la production.
- **L'archive.** Le « double » de l'audit était la paire PDF + Word d'une
  même génération (918 Ko = le Word et ses images embarquées) : la colonne
  Format la distingue désormais. `reprise:dedoublonner-archive` résorbe
  les vrais doubles (même client, type, extension, cachet — gardée : la
  plus récente) et a listé, en réel, trois factures sans client avec leur
  chemin (`SANS DOSSIER/`) pour rattachement manuel. La génération refuse
  désormais une pièce sans dossier.

---

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

1. `php artisan migrate` (trois migrations, aucune destructive), puis
   `php artisan optimize:clear`.
2. Vérifier l'extension PHP `imap` (`php -m | grep imap`) ; si absente,
   demander son activation à l'hébergeur — la boîte de réception
   fonctionne en lecture des messages déjà relevés en attendant.
3. Renseigner le serveur entrant sur les adresses d'expédition et
   « Tester la relève ».
4. Passer les commandes de reprise, dans cet ordre, en simulation d'abord :
   `reprise:normaliser-les-tiers --simulation`, `reprise:expliquer-la-caisse`,
   `reprise:signaler-les-identites-douteuses`, `reprise:dedoublonner-archive
   --simulation`, `reprise:signaler-les-orphelins` — puis appliquer ce que
   l'exploitant valide.
