Aller au contenu
Kalivins
Menu

Toutes les démonstrations

Démonstration2026

Gestion de stock hors réseau

Entrées, sorties, seuils, inventaire au scan depuis un téléphone. Deux rôles, des règles métier qui vivent dans PostgreSQL, et deux contraintes qui se contredisent : le sous-sol sans réseau et deux magasiniers sur la même dernière pièce. Un écran exécute ses propres preuves devant vous, avec les réponses de la base telles quelles.

Technologies
Next.js · TypeScript · PostgreSQL · IndexedDB · Docker
Démontre
Web sur mesure

Ouvrir la démonstrationLes identifiants sont affichés sur la page de connexion. Vous pouvez tout modifier : les données sont fictives et remises à zéro toutes les heures.

Lire le code source

Capture de Gestion de stock hors réseau

D’où vient cet outil

Dans un parking en sous-sol, j’avais besoin de remplir une formalité administrative. J’ai rempli le formulaire hors réseau, la page était déjà chargée. Une fois sorti du parking, je l’ai envoyé, et il a refusé mon contenu parce que j’avais attendu trop longtemps. Le jeton anti-bot avait expiré.

J’avais tout bien fait, et l’outil l’a jeté pour une contre-mesure technique dont je ne suis pas censé me soucier.

Le problème

Cette démonstration part de là. Une métallerie de 15 personnes tiendrait son magasin dans un classeur partagé. 64 références : tubes, cornières, bobines de fil, disques, visserie, bouteilles de gaz. Deux magasiniers saisiraient dedans. Le gérant regarderait la colonne « stock » pour décider quoi commander.

La colonne mentirait, pour trois raisons qui reviennent dans tous les classeurs partagés :

  • quelqu’un prend cinq mètres de fer plat sans rien noter ;
  • deux personnes ouvrent le fichier en même temps, et la dernière à enregistrer efface le travail de l’autre ;
  • un rechargement malheureux ramène une version d’avant-hier.

À l’inventaire annuel, personne ne saurait expliquer les écarts. Et ça coûterait bien avant l’inventaire : une bobine de fil fourré manquante un mardi matin, deux soudeurs qui attendent.

Ce qui change au quotidien

Le stock affiché correspond à ce qu’il y a sur l’étagère. Quand ce n’est plus le cas, l’inventaire dit de combien et depuis quand. C’est toute la différence entre commander au jugé et commander sur un chiffre.

Saisir se fait debout, une main sur le téléphone, l’autre sur la pièce : un code, un sens, une quantité, un motif dans une liste courte. Un magasin en sous-sol, entre des rayonnages métalliques, c’est là où le réseau tombe. Ça marche quand même, et j’y reviens plus bas parce que c’est le cœur du sujet.

Chaque référence porte un seuil, et le gérant est prévenu quand elle passe dessous, sans avoir à ouvrir quoi que ce soit.

L’installation tient en deux conteneurs et une commande, sans abonnement. Rien ne sort de la machine : aucune police d’écriture téléchargée ailleurs, aucune mesure d’audience, aucun service extérieur, et la base de données n’ouvre même pas de porte vers l’extérieur.

Comment ça marche

Deux faits se contredisent : le sous-sol coupe le réseau, et deux magasiniers travaillent sur les mêmes références.

Si le téléphone accepte une sortie sans réseau, il l’accepte sans pouvoir demander au serveur s’il reste de la marchandise. Prenez trois joints toriques en rayon. Deux personnes en sortent trois chacune. Le stock tombe à moins trois.

On ne peut pas avoir -1 bobine de fil de 15 kg. Logique dans la réalité, possible dans une application, et ça ne doit pas l’être. On reflète la réalité.

Aucun réglage ne fait disparaître ça. Il faut choisir quel geste on protège, et le dire à l’écran.

J’ai préparé le PWA avant tout, mais je savais que la problématique était différente de mon expérience dans le parking. Si on doit pouvoir tout faire sans réseau, alors une entrée doit toujours être conservée et passer, un comptage aussi, mais une sortie peut être refusée si, quand on revient dans le réseau, le stock est à zéro.

Le choix suit le sens physique du geste :

  • Une entrée passe toujours. De la marchandise arrive, elle ne peut pas manquer. Le téléphone l’enregistre définitivement, réseau ou pas.
  • Un comptage d’inventaire aussi. Il décrit ce que quelqu’un a vu sur une étagère, il ne retire rien à personne.
  • Une sortie sans réseau est provisoire. L’écran le dit avant le geste, et le serveur pourra la refuser si un collègue a pris les mêmes pièces.

Ce que le serveur refuse ne disparaît pas : ça atterrit dans un écran de rattrapage. Ces gestes ont eu lieu. Les pièces ont quitté le rayon, et c’est à quelqu’un de dire ce qu’on en fait. Un outil qui les avalerait en silence fabriquerait l’écart inexplicable qu’on cherchait à supprimer.

Reste le cas que personne n’anticipe. Le téléphone envoie sa saisie, le serveur l’enregistre, et la réponse se perd en route. Le téléphone croit avoir échoué et renvoie. Chaque saisie porte donc un numéro unique, et un envoi parti deux fois ne compte qu’une fois.

Ce que vous pouvez vérifier sans moi

Vu du dehors, rien ne distingue une application sérieuse d’un formulaire qui écrit dans un fichier. Les deux montrent des champs et un bouton. Lire le code trancherait, sauf que personne ne lit le code.

L’outil embarque donc un écran qui exécute ses propres preuves devant vous. Cinq boutons, cinq situations qui font peur :

  • deux magasiniers sortent la dernière pièce au même instant ;
  • la même saisie part deux fois ;
  • quelqu’un tente de réécrire une ligne du registre ;
  • un magasinier essaie de valider un inventaire à la place du gérant ;
  • le stock est recalculé de bout en bout, pour vérifier qu’il n’a pas dérivé.

Chacun tourne pour de vrai. L’écran affiche la réponse de la base telle quelle, message d’erreur compris, avec le temps que ça a pris. La course entre les deux magasiniers se règle en une quarantaine de millisecondes. Les deux essais qui écrivent laissent leur trace horodatée dans le registre, à côté des mouvements de la semaine.

30 secondes, sans savoir programmer. Chez un client, un réglage fait disparaître cet écran.

23 tests automatiques tournent par ailleurs sur l’installation complète. Cinq pilotent un vrai navigateur : ils coupent son réseau, saisissent une sortie, remettent le réseau, et vérifient que tout repart seul.

Ses limites

Le scan est plus lent sur iPhone. Safari ne sait pas lire les codes-barres tout seul, donc l’application télécharge un lecteur de secours de 230 kilo-octets, et seulement sur iPhone. Comptez une seconde de plus. Sans doute deux sur un appareil qui a quelques années. De toute façon la moitié du magasin se saisit à la main, puisqu’une barre d’acier débitée à la longueur arrive sans étiquette.

Un seul magasin, un seul emplacement par article. Gérer plusieurs emplacements demanderait de reprendre chaque écran et chaque recherche. J’ai préféré tenir un seul flux jusqu’au bout.

Aucune commande fournisseur. Recevoir contre un bon de livraison, gérer un reliquat, rapprocher une facture : c’est un autre métier. La valeur du stock retient le dernier prix d’achat connu. La comptabilité analytique commence là où cet outil s’arrête.

Un détail sur les téléphones partagés. Pour fonctionner sans réseau, l’application garde une copie de la page en mémoire, et cette copie porte le nom de la personne connectée. Quelqu’un peut donc l’apercevoir après une déconnexion, une fraction de seconde, avant que la page fraîche arrive. La copie est effacée au retour sur l’écran de connexion. Aucune donnée de stock n’y figure.

Sous le capot

Cette section s’adresse au développeur qui vous accompagne, s’il y en a un. Vous pouvez la sauter sans rien perdre.

Le stock est la somme d’un journal en ajout seul, tenue par un déclencheur PostgreSQL, sous une contrainte qui interdit le négatif. Une erreur de saisie se corrige par un mouvement inverse. Rien ne réécrit une ligne passée. Deux sorties simultanées sur la même référence se mettent en file sur le verrou de ligne au lieu de s’ignorer. Aucune vérification écrite dans l’application ne produit ce résultat : entre un SELECT et un INSERT subsiste toujours une fenêtre, et c’est précisément là que deux magasiniers se croisent.

L’interface ne détient aucune des règles qui comptent. Six déclencheurs, dix contraintes de vérification et deux index uniques vivent en base, donc ils valent aussi devant psql et devant le script d’import que quelqu’un écrira un jour. L’écran de preuves lit le catalogue de PostgreSQL pour afficher ce que la base contient vraiment : une règle retirée du SQL disparaît de cet écran.

Deux choses apprises en construisant.

Le déclencheur qui applique un mouvement s’écrivait naturellement en INSERT ... ON CONFLICT DO UPDATE. Il était faux. PostgreSQL évalue les contraintes CHECK sur le tuple proposé à l’insertion, avant de détecter le conflit et de basculer sur la branche de mise à jour ; or ce tuple porte le delta brut, négatif pour une sortie. La première sortie d’un article était donc refusée alors que son stock valait 13. Il a fallu écrire les 600 mouvements du jeu d’exemple pour s’en apercevoir.

Le conteneur, lui, a corrigé ce que la machine laissait passer. Sans .dockerignore, le COPY de contexte recouvre l’arbre de liens symboliques que pnpm vient d’installer, et la construction meurt sur un module introuvable. Une application qui compile sur un portable et une image qui démarre restent deux choses différentes.

L’essayer

Ouvrez la démonstration, les identifiants sont affichés sur la page de connexion. Allez dans l’onglet « Sous le capot » et appuyez sur les cinq boutons.

Puis coupez le réseau de votre téléphone et continuez à travailler.

Captures

  • Le magasin, article par article
    Le magasin, article par article
  • Les alertes de seuil
    Les alertes de seuil
  • Le scan au téléphone, dans l’atelier
    Le scan au téléphone, dans l’atelier
  • Les preuves : deux sessions en concurrence sur la dernière pièce
    Les preuves : deux sessions en concurrence sur la dernière pièce