Projets 01 / 04

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.

État
En service
Année
2026
Durée
Juillet 2026
Rôle
Seul, de la conception au déploiement
Client
Garcia Rectification, Cormeilles-en-Vexin
Pour qui
Une dizaine de personnes, tous les jours
Contrainte
Un poste Windows de l'atelier, sans base de données, jamais exposé à Internet
Outils
PHP 8, Python 3, JavaScript, Tailwind, ReportLab, SMTP

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.

Tableau de l'inventaire : colonnes moyen, type, désignation, dimensions, fournisseur et gisement, avec filtres par catégorie et compteurs.
La liste des meules et diamants, 446 références, recherche instantanée. Données anonymisées pour la capture.

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.

Architecture de l'application d'inventaire Les exports XML de l'ERP Clipper sont lus par un script Python qui produit des fichiers JSON. Une API PHP lit et écrit ces fichiers sous verrou, et les sert aux navigateurs de l'atelier. Une tâche planifiée envoie chaque soir à 17 h 30 les commandes groupées par e-mail. SERVEUR ATELIER — PHP + PYTHON Partage réseau Export_*.xml parser.py vrai encodage export/*.json état de l'appli api/*.php 11 points d'entrée Envoi 17 h 30 tâche planifiée Postes atelier navigateur SMTP fournisseurs lit flock JSON PDF + mail XML
Clipper alimente l'appli, jamais l'inverse. Le seul trait en couleur, c'est l'écriture sous verrou.

04

Les choix

  • Des fichiers JSON sous verrou

    Écarté : SQLite, MySQL

    Pas de base de données possible, donc des fichiers JSON. Chaque écriture passe par un verrou exclusif : ouverture en c+, flock(LOCK_EX), lecture avec fread() à l'intérieur du verrou, ftruncate(), réécriture, fflush(), et seulement ensuite le déverrouillage. Si on déverrouille avant le fflush(), 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 Python

    L'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 fcntl n'existe pas sous Windows, et rien ne garantit qu'un verrou posé côté Python soit respecté par le flock() 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.

Page des outils clients : fiches par client avec le nombre d'outils enregistrés, statut disponible ou rebut, et création d'un nouveau client.
Le second module : les outils des clients, fiche par fiche. Noms de clients fictifs.

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.

Tous les projets

Contact

Parlons-en

pfery@outlook.fr