# Rapport de version — v1.3.5

*STE MOBDIE PRIVÉ — application de gestion — 28 juillet 2026*

## L'assistant échouait encore, et pour la moitié qui n'avait pas été regardée

La v1.3.4 avait établi la cause et l'avait corrigée sur le dépôt depuis la fiche client : `TemporaryUploadedFile::storeAs()` déplace au lieu de copier quand le disque de destination est celui du fichier temporaire, et toute lecture de métadonnée postérieure porte sur un fichier qui n'existe plus. Elle n'avait pas regardé l'assistant, et l'assistant tombait exactement de la même façon.

Ranger les pièces ne suffisait pas : le fichier temporaire, une fois consommé, **restait dans l'état du champ de dépôt**. Or cet état est relu deux fois après coup, et chacune de ces relectures interroge le disque — la validation, qui demande sa taille au fichier pour la comparer au plafond de 20 Mo (`Filament\Forms\Components\BaseFileUpload::getValidationRules()` applique `max:20480` fichier par fichier, dans une règle de fermeture), et le rendu de la zone, qui la redemande pour la vignette. Deux lectures distinctes, les deux erreurs du journal, à treize secondes d'intervalle sur la même empreinte.

La correction est double, et la seconde moitié est la vraie. Tout est relevé au moment du dépôt — chemin, nom d'origine, format, taille — et rangé avec la pièce en attente ; puis **l'état de la zone est vidé** sitôt les fichiers rangés. Plus aucun fichier temporaire consommé ne subsiste dans le formulaire : ni la validation ni le rendu n'ont plus rien à interroger, et la création ne redemande jamais rien. Les pièces mises de côté sont désormais indexées par l'empreinte de leur fichier temporaire et non par le rang dans la zone, et l'étape des pièces en affiche la liste — avec de quoi en retirer une, puisque la zone, elle, est vide.

`DepotPieces::ranger()` efface en outre la source une fois le rangement vérifié. `storeAs()` ne la consomme que lorsqu'il déplace ; dans le cas contraire il copie et laisse derrière lui un double de la pièce d'un client dans un répertoire que personne ne nettoie. Le comportement est maintenant le même dans les deux cas.

Le balayage demandé a été fait : les trois entrées de dépôt passent toutes par `DepotPieces`, qui relève avant d'écrire ; les deux lectures de nom d'origine restantes (`DeposerPiecesAction`, archive des documents) ont bien lieu avant le rangement ; la sonde de diagnostic, elle, lisait `getSize()` **après** avoir rangé le fichier par le chemin de code de l'application — elle relève désormais la taille annoncée une fois pour toutes, au début. Il ne reste plus une seule lecture de métadonnée postérieure à un `store()`.

**Le test qui manquait est écrit.** Il parcourt l'assistant en entier — carte du type, dépôt, analyse, vérification, clic sur « Créer le dossier » — avec des fichiers temporaires fabriqués comme la production les fabrique : même disque, fiche sans taille ni format, disque en mode « throw ». Il vérifie que le client, ses deux pièces (format et taille compris), le contrat, ses échéances et le document généré existent bien. Un point mérite d'être noté : l'erreur elle-même reste hors de portée d'un test, parce que le constructeur de `TemporaryUploadedFile` appelle `touch()` sur le chemin temporaire en contexte de test et recrée donc le fichier à chaque réhydratation. Ce que le test garde est l'invariant qui rend l'erreur impossible — après le rangement, aucune zone ne contient plus de fichier temporaire — et retirer le vidage des zones le fait tomber.

## Le bouton « Créer le dossier »

Il était écrit à la main : `fi-btn fi-btn-size-md fi-color-primary fi-btn-color-primary … text-white`. `fi-btn-color-primary` n'existe pas, et `fi-color-primary` ne teinte rien sans la classe `fi-color` ni les variables CSS que Filament pose lui-même. Le bouton retombait donc sur le fond très clair par défaut, avec un texte clair par-dessus : illisible, et si terne qu'on le croyait désactivé. Il rend désormais le composant officiel de Filament, qui donne le violet de la charte, le texte contrasté, l'icône et l'état de chargement — sans état désactivé hérité, seulement le `wire:loading.attr` normal. La revue demandée n'a rien trouvé d'autre : c'était la seule imitation de bouton du projet, tous les autres passent par `<x-filament::button>` ou par les actions de Filament.

## La personne physique

**L'assistant** commence par le type, en deux grandes cartes cliquables — « Société » et « Personne physique » — et non par une liste déroulante. Rien n'est pré-coché : le choix commande tout ce qui suit, et le deviner reviendrait à créer des personnes physiques en société sans que personne ne s'en aperçoive. Cliquer une carte enchaîne directement sur le dépôt.

Pour une personne physique, l'étape des pièces n'affiche que « CIN » et « Autres pièces » ; la zone « Certificat négatif » disparaît, et un certificat déjà mis de côté avant un changement d'avis est retiré du transit. L'analyse IA ne travaille alors que sur la CIN. L'étape de vérification n'affiche que ce qui a un sens : nom et prénom, CIN, date de naissance, adresse personnelle, activité exercée, téléphone, e-mail. Ni ICE, ni RC, ni tribunal, ni certificat négatif. À la création, ces champs sont écrits à vide et le nom est recopié en représentant : une personne physique est son propre domicilié.

**Le formulaire client** rend ses rubriques conditionnelles au type plutôt que de toutes les afficher : l'onglet « Certificat négatif » disparaît, les champs ICE, RC, ville du RC et tribunal aussi, et « raison sociale » s'intitule « Nom et prénom ».

**Les documents.** La désignation du domicilié est calculée une fois dans `DocumentGenerator::resolveVariables()`, et rendue par une seule variable — `{{domicilie_designation}}` pour les blocs de parties, `{{domicilie_designation_courte}}` pour les phrases — plutôt que par un empilement de conditions dans chacun des quatre gabarits. Pour une société : *la société « X », ICE n° …, immatriculée au registre de commerce de … sous le n° …, représentée par Y en qualité de gérant*. Pour une personne physique : *Monsieur ou Madame X, titulaire de la carte nationale d'identité n° …, né(e) le …, demeurant à …, exerçant l'activité de …*. Le contrat, l'avenant, l'acte de résiliation et l'attestation en bénéficient tous les quatre. Une migration porte le changement aux gabarits déjà en base, fragment par fragment reconnu mot pour mot : un gabarit personnalisé à la main n'est pas touché.

**La complétude légale.** `LegalCompleteness` distinguait déjà les deux cas mais réclamait à la personne physique un « représentant légal » qu'elle n'a pas, sous le libellé « Raison sociale / nom ». Sa liste est désormais : nom et prénom, CIN, adresse personnelle, siège attribué, activité exercée, lieu d'exercice, lieu de conservation des documents comptables — et deux pièces, la CIN et le contrat signé. Ni ICE, ni RC, ni tribunal, ni certificat négatif : l'écran « Dossiers incomplets » ne réclame plus l'impossible. Les voyants de pièces obligatoires de la fiche passent de trois à deux, et le filtre « sans certificat négatif » de la liste ne l'inscrit plus.

**La liste des clients** gagne une colonne « Type », triable, avec son icône, et le filtre par type était déjà là.

## L'aperçu du contrat

L'action « Générer un document » ouvre une fenêtre où les options sont à gauche et le PDF à droite, rendu pour de bon — c'est exactement celui qui sera enregistré — et refait à chaque changement d'option. Deux boutons : « Enregistrer dans le dossier » et « Annuler ».

L'aperçu n'est enregistré nulle part : il vit dans un répertoire de transit propre à chaque utilisateur, hors de `client-documents`, invisible dans les pièces d'un domicilié comme dans l'archive, et balayé au bout d'une heure. Un jeton d'un autre utilisateur ne se lit pas.

L'action « Aperçu » des pièces déjà générées existait déjà, dans l'onglet des pièces comme dans l'archive, mais elle ne marchait pas : elle pointait sur le téléchargement, dont l'en-tête `Content-Disposition: attachment` interdit précisément l'affichage. Le cadre restait vide et le navigateur déposait un fichier de plus dans le dossier des téléchargements — c'est ce que l'exploitant appelait « il faut le télécharger pour le voir ». Une adresse d'affichage en ligne a été ajoutée, et l'aperçu montre enfin le document.

## La mise en page des actes

`resources/views/documents/layout.blade.php` ressemblait à un export Word. Il reprend maintenant le vocabulaire graphique du devis et de la facture, pour que tous les documents de la maison se ressemblent :

- **un vrai en-tête** — logo à 110 pixels de haut, aligné à gauche (hauteur seule, la largeur suit : le logo n'est jamais déformé), et en regard l'identité de la société sur plusieurs lignes ; filet violet épais, filet orange en rappel ;
- **un titre qui pose le document** — en capitales, centré, sur fond légèrement teinté, avec la référence et la date en petit juste en dessous ;
- **les parties dans deux encadrés côte à côte**, « Le domiciliataire » et « Le domicilié », comme les blocs émetteur et client de la facture. C'est ce qui manquait le plus : les parties étaient noyées dans le corps du texte, elles en sont sorties et les gabarits ne gardent que leur substance ;
- **des articles tenus** — titres en couleur de charte, texte justifié, interligne confortable ; le titre d'article et son premier paragraphe sont réunis dans un bloc en `page-break-inside: avoid`. Seuls ces deux-là : envelopper l'article entier ferait renvoyer à la page suivante, tronqué, un article plus long qu'une page — celui des obligations, par exemple ;
- **un encadré teinté pour les clauses financières**, celles qui portent le montant et la périodicité ;
- **un pied de page sur chaque page**, avec les mentions légales et la pagination « Page X / Y », écrite après le rendu par le script de numérotation du canevas — DomPDF ne sait pas compter ses pages en CSS, et activer l'exécution de PHP dans le HTML pour un simple numéro de page eût été cher payé ;
- **une ligne de paraphe discrète** en bas de chaque page ;
- **des signatures** plus hautes, la qualité du signataire sous chaque case, le lieu et la date au-dessus, et le cachet posé dans la case du prestataire plutôt qu'en vrac au milieu de la page.

Le même traitement vaut pour l'avenant, la résiliation et l'attestation, qui partagent ce gabarit — avec la mention de liaison adaptée (« il a été convenu », « les parties conviennent », « il est attesté ») et, pour l'attestation, une seule case de signature : elle est délivrée par le seul domiciliataire.

**Le rendu a été contrôlé de visu**, PDF ouverts et regardés, sur un contrat de société et un contrat de personne physique, plus l'avenant, la résiliation et l'attestation. DomPDF a réservé deux surprises, l'une et l'autre corrigées : les éléments en `position: fixed` se placent *dans* le cadre de contenu et chevauchaient les dernières lignes du texte — paraphe et pied de page sont désormais posés sous ce cadre, par des décalages négatifs ; et `@endif` accolé à une lettre n'est pas compilé par Blade, ce qui produisait une vue au PHP déséquilibré. Le logo n'est pas déformé, l'en-tête tient sur une page, aucun article n'est coupé au mauvais endroit, la pagination est juste et les accents passent. Le contrat tient sur deux pages au lieu de trois.

## Vérification

Suite complète : **251 tests, 1 171 assertions, au vert**. `./vendor/bin/pint` : conforme. Les tests ajoutés couvrent le parcours entier de l'assistant pour une société et pour une personne physique, le retrait d'une pièce mise de côté, la désignation dans les documents pour les deux types, l'aperçu qui n'écrit rien au dossier et l'enregistrement qui crée bien la pièce, la complétude légale d'une personne physique, ses rubriques de formulaire et son absence du filtre « sans certificat négatif ».

## Déploiement

```bash
git pull && composer install --no-dev -o
npm ci && npm run build            # les cartes du choix de type et la liste
                                   # des pièces en attente ajoutent des classes
php artisan migrate --force        # adapte les quatre gabarits déjà en base
php artisan optimize:clear && php artisan optimize
```

La reconstruction des assets n'est pas optionnelle cette fois : les deux nouveaux fragments d'interface introduisent des classes que la feuille de style compilée ne contient pas encore. La migration est réversible et ne touche que les gabarits qu'elle reconnaît mot pour mot.
