# Rapport de version — v1.3.4

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

## La cause, démontrée

Le fichier n'était pas perdu à l'écriture : il était **déplacé à l'enregistrement**. L'instrumentation de la v1.3.3 a montré le dépôt parfaitement écrit — `put` renvoie vrai, `file_exists` vaut oui, `filesize` vaut 383 553 octets — et le fichier encore présent dans `livewire-tmp` juste avant le clic sur « Enregistrer les pièces », absent juste après. C'est donc l'enregistrement qui le consommait, et pour une raison qui tient en trois lignes de Livewire, dans `TemporaryUploadedFile::storeAs()` : quand le disque de destination est celui du fichier temporaire et qu'aucune option n'est passée — notre cas exact, tout étant sur `local` — Livewire **déplace au lieu de copier**. `DepotPieces::deposer()` lisait ensuite `getMimeType()` et `getSize()` pour construire la fiche : ces deux méthodes interrogent le disque au chemin temporaire, qui n'existait plus. Flysystem levait `UnableToRetrieveMetadata`, la requête rendait un 500, et l'utilisateur lisait « Erreur lors du chargement de la page ». Ce sont mot pour mot les deux seules erreurs du journal depuis le début : `Unable to retrieve the file_size…` et `Unable to retrieve the mime_type for file at location: livewire-tmp/XXXX.pdf. No such file exists.`

Conséquence secondaire, et c'est elle qui a semé le désordre : l'exception était levée pendant la construction du tableau passé à `create()`, donc **après** que le fichier eut été rangé sous `client-documents/`, mais **avant** qu'aucune ligne n'existe en base. Chaque tentative laissait un fichier correctement rangé et inconnu de l'application. Quant au dépôt qui « avait réussi » une fois sur treize, il n'avait rien réussi : personne n'avait cliqué sur Enregistrer.

## Ce qui a été corrigé

`DepotPieces::ranger()` relève désormais le nom d'origine, l'extension, le format et la taille **avant** `storeAs()`, et les rend à l'appelant ; plus personne n'interroge un fichier téléversé après l'avoir rangé, et personne ne le pourrait. Le rangement et la création de la fiche forment un tout : si la ligne ne peut pas être écrite, le fichier rangé est effacé et l'incident est journalisé — le disque reste propre, et un dépôt qui échoue le dit au lieu de laisser un orphelin. La vérification d'après rangement demeure : fichier présent à destination, et autant d'octets que ceux lus sur la source, faute de quoi une exception nomme la pièce.

Le même piège a été cherché partout ailleurs. Les trois seules entrées de dépôt de l'application — la modale « Déposer des pièces », l'archive des documents et l'assistant de nouveau dossier — passent toutes par ce service. L'assistant lisait en outre `getMimeType()` après le rangement, et souffrait d'un second défaut de la même famille : `rangerPieces()` est appelée deux fois, au passage à l'étape 2 puis à la création, et ne reconnaissait « déjà rangée » qu'à ce que l'état du champ ne contînt plus un fichier temporaire — or il en contient toujours un, c'est le fichier sur le disque qui a bougé, pas l'objet en mémoire. Le second passage retentait donc de ranger un fichier déjà consommé et faisait échouer la création du dossier. Une pièce est désormais reconnue à l'empreinte de son fichier temporaire. Enfin, les dépôts par lot rattrapent toute cause d'échec, et non plus seulement le fichier illisible : une pièce qui échoue se dit dans une notification nommant le fichier, elle n'emporte plus le lot entier dans un 500.

## Ce que la récupération a retrouvé

`mobdie:indexer-archive` traite bien `client-documents/<code client>/`, rattache par le segment de chemin qui porte le code, et devine le type malgré le préfixe en UUID (`…-cin-med-mezian.pdf` → CIN, `…-certificat-negatif-2026.pdf` → certificat négatif). Elle marque désormais ces pièces comme des **dépôts** — ce qu'elles sont — et non comme des archives, les compte à part et l'annonce en clair : « *n* pièce(s) déposée(s) mais jamais enregistrée(s) ont été rendues à leur dossier. »

Sur l'environnement de développement, la commande ne retrouve rien : quatre fichiers parcourus, quatre déjà indexés, aucun orphelin. **La récupération reste à faire sur le serveur**, où les orphelins ont été semés : `php artisan mobdie:indexer-archive --simulation` pour lire le compte sans rien écrire, puis la même commande sans l'option pour rendre les pièces à leurs dossiers.

## Vérification

Le test qui manquait a été écrit d'abord, et il échouait sur la v1.3.3 en reproduisant l'erreur de production mot pour mot — `Unable to retrieve the mime_type for file at location: …/livewire-tmp/XXXX.pdf. No such file exists.`, levée depuis `DepotPieces.php:46`. Il dépose une pièce de 383 553 octets, la taille exacte relevée en production, et vérifie qu'après enregistrement la ligne existe avec le bon format et la bonne taille, et que le fichier est lisible octet pour octet à sa destination. Trois autres l'accompagnent : plusieurs pièces d'affilée, l'absence d'orphelin quand la base refuse la ligne, et l'assistant qui range en deux temps. Un détail méritait attention : en contexte de test, Livewire lit la taille et le format dans la fiche `.json` au lieu du disque, et range ses fichiers temporaires sur un autre disque que `local` — deux raisons pour lesquelles un test écrit naïvement passerait avec le bogue comme sans lui. Les essais construisent donc leur fichier temporaire comme la production le fait : même disque, fiche sans taille ni format.

L'instrumentation de la v1.3.3 est conservée — trace, page de diagnostic, bouton de copie — et son interrupteur reste sur « désactivé » : elle a fait son travail, elle n'a plus à tourner.

Suite complète : 236 tests, 1 031 assertions, au vert. `./vendor/bin/pint` : conforme.
