Inventaire de l'atelier
Retrouver une meule ou un diamant depuis n'importe quel poste, et commander les rachats en un seul envoi le soir.
01 — Contexte
Le point de départ
Pour savoir où était rangée une meule, ou si l'outil d'un client était parti au rebut, il fallait passer par Clipper, l'ERP de l'atelier. Il est installé sur un seul poste, sous licence, avec une interface faite pour la compta. Du coup, les gens se déplaçaient jusqu'à ce poste ou demandaient à un collègue. Et les demandes de rachat partaient par mail, une par une.
J'ai écrit une appli web qui lit les exports de Clipper et met l'inventaire sur tous les postes de l'atelier.

02
Les contraintes
- Ne rien écrire dans Clipper.C'est la référence de l'atelier, y compris pour la compta. La seule porte d'entrée, c'est un export XML déposé sur un partage réseau.
- Pas de base de données.Le serveur est un poste Windows de l'atelier, sans SGBD, et personne sur place pour en maintenir un.
- Rien d'accessible depuis Internet.L'appli ne répond que sur le réseau local.
03
Comment ça marche
Les données ne vont que dans un sens. Un script Python lit les exports XML de Clipper et produit des fichiers JSON. Une API PHP de 11 points d'entrée les sert aux navigateurs de l'atelier. Tous les soirs à 17 h 30, une tâche planifiée envoie les demandes de rachat de la journée, groupées par fournisseur, avec un bon de commande en PDF à l'en-tête et au tampon de l'entreprise. L'appli n'écrit jamais dans Clipper.
04
Les choix
-
Des fichiers JSON sous verrou
Écarté :
SQLite, MySQLPas de base de données possible, donc des fichiers JSON. Chaque écriture passe par un verrou exclusif : ouverture en
c+,flock(LOCK_EX), lecture avecfread()à l'intérieur du verrou,ftruncate(), réécriture,fflush(), et seulement ensuite le déverrouillage. Si on déverrouille avant lefflush(), un autre processus peut lire un fichier à moitié écrit. Et ça ne se voit qu'à la première vraie collision.Trois points d'entrée écrivent dans le même fichier de commandes. J'ai mis la séquence dans une seule fonction, qui prend en paramètre la modification à faire, pour ne pas avoir trois copies du même code qui divergent un jour.
-
Un seul langage écrit dans les fichiers
Écarté :
écrire le JSON depuis PythonL'envoi du soir est en Python, et il doit marquer les commandes comme envoyées. Le plus simple aurait été d'écrire le JSON directement. Sauf que le module
fcntln'existe pas sous Windows, et rien ne garantit qu'un verrou posé côté Python soit respecté par leflock()de PHP. Python appelle donc un script PHP en ligne de commande pour chaque écriture. Ça coûte un processus de plus par envoi, et un seul mécanisme de verrou touche aux fichiers.
05
Ce qui a coincé
- Des fichiers XML qui mentent sur leur encodageLes exports annoncent
encoding="ISO-8859-1"dans leur en-tête. Un des fichiers l'est vraiment, l'autre non. En faisant confiance à l'en-tête, un des deux s'affichait avec des accents cassés, à chaque fois. J'ai écrit un chargement qui détecte l'encodage réel au lieu de croire la déclaration.Les tests construisent les octets bruts directement en mémoire. Avec des fichiers de test, l'éditeur qui les enregistre aurait choisi son propre encodage, et le test aurait vérifié autre chose que le vrai cas. - Toutes les commandes de diamants dans le même groupeL'envoi du soir regroupe les commandes par fournisseur. Les meules ont un nom de fournisseur dans l'export de Clipper. Les diamants, non : le champ est toujours vide. Résultat, toutes les commandes de diamants tombaient dans un seul groupe, adressé au même destinataire. J'ai ajouté un repli sur le code court du fournisseur quand le nom manque, avec un test dédié : c'est typiquement le bug qui revient après une refonte.
- Le fichier .env qu'on pouvait téléchargerAu début du projet, je me suis rendu compte que le
.envétait à la racine du site. Le serveur intégré de PHP sert tout ce qui se trouve dans son dossier, fichiers cachés compris : quelqu'un du réseau de l'atelier qui tapait la bonne adresse pouvait le télécharger. Il était encore presque vide, et l'appli n'est accessible qu'en local. Je l'ai remonté d'un niveau, hors de la racine web, là où le serveur ne va pas chercher.
06
Où ça en est
- 446outils visibles depuis tous les postes, au lieu d'un seul
- 113clients avec leurs outils, disponibles ou au rebut
- 17 h 30un envoi par fournisseur, au lieu de mails un par un
L'appli démarre toute seule avec le serveur, par une tâche planifiée. Si l'envoi du soir échoue, les commandes restent en attente et repartent au passage suivant.

07
À reprendre
- SQLite plutôt que des fichiers JSON.J'avais écarté toute base de données à cause de la contrainte d'installation. À tort : SQLite n'est pas un serveur, c'est un fichier, et l'extension est livrée avec PHP. J'aurais eu les transactions et les verrous sans écrire une ligne de gestion de concurrence. C'est la décision que je défends le moins.
- Le serveur intégré de PHP traite une requête à la fois.Pour une dizaine de personnes ça passe. Si l'usage grandit, non. La doc le signale et renvoie vers Apache, je ne l'ai pas fait.
- Les journaux ne sont jamais purgés.Un fichier par jour, qui s'accumule. Une rotation, c'est quelques lignes. Je ne les ai pas écrites.