
À propos du projet
Une suite de caisse pour la restauration, le retail et l'épicerie, bâtie autour d'un hub local pour que la caisse continue de vendre même sans internet. Un nouveau secteur est un profil de réglages, pas une duplication du code.
Mon rôle: Architecte et développeur full stack des trois applications, du modèle de synchronisation hors ligne et du déploiement cloud.
J'ai conçu cette suite autour d'une contrainte que la plupart des solutions cloud passent sous silence : un commerce ne peut pas s'arrêter de vendre parce que la connexion est tombée. En Tunisie ce n'est pas un cas limite, c'est un mardi ordinaire. L'architecture place donc un hub local entre les caisses et le cloud, plutôt que de faire dépendre chaque vente d'un aller retour vers un centre de données.
La suite est un monorepo Turborepo de trois applications. La caisse et le back office tournent en application web progressive React 19, avec Zustand pour l'état et Workbox pour le cache hors ligne. L'API est en Express 5 avec Drizzle sur PostgreSQL 18 hébergé sur Cloud SQL, déployée sur Cloud Run. Entre les deux, un hub de bureau bâti avec Electron embarque son propre serveur Express et une base better-sqlite3, dessert toutes les caisses du réseau local et se réconcilie avec le cloud dès le retour de la connexion.
Un seul produit devait couvrir la restauration, le retail généraliste et l'épicerie, soit trois façons différentes de vendre. Plutôt que de dupliquer le code par secteur, j'ai modélisé les écarts en configuration et conservé un catalogue unique, un moteur de tarification unique et un journal de ventes unique. L'authentification s'appuie sur Firebase avec des claims portant le locataire et le rôle, si bien qu'un exploitant multi boutiques les gère depuis un seul compte sans qu'aucune donnée ne circule entre elles. La suite tourne aujourd'hui en production chez un exploitant, et en accueillir un second revient à créer un locataire et à choisir un profil sectoriel, pas à monter un autre système.
Fonctionnalités principales
- 🔌 Vente hors ligne d'abord : Un hub local Electron avec base SQLite embarquée maintient toutes les caisses opérationnelles sans connexion.
- 🍽️ Multi-secteur par configuration : Restauration, retail généraliste et épicerie tournent sur un seul code, leurs écarts exprimés en paramètres.
- 📱 Interface de caisse installable : L'écran caissier est une application web progressive mise en cache par Workbox, qui se lance comme une application native.
- 🏪 Multi-tenant par conception : Les claims Firebase portent le locataire et le rôle, si bien qu'un exploitant gère plusieurs boutiques depuis un compte sans mélange de données.
- 🔄 Réconciliation bidirectionnelle : Les ventes locales sont rejouées vers le cloud au retour de la connexion, et les changements de catalogue redescendent vers le hub.
- ⚡ API serverless sur Cloud Run : Express 5 avec Drizzle sur PostgreSQL 18 descend à zéro entre les coups de feu et remonte pendant le service.
- 🧾 Journal de ventes unique : Un seul catalogue et un seul moteur de tarification alimentent tous les secteurs, si bien que le reporting n'a jamais à fusionner des sources incompatibles.
- 🚦 État actuel : Un exploitant fait tourner la suite en production. Un second, c'est un locataire à créer et un profil sectoriel à choisir, pas un autre système à monter.
Problèmes et solutions
Problème: Les caisses cloud cessent d'encaisser dès que l'internet tombe, ce qui revient à arrêter le commerce. Les systèmes purement locaux évitent le problème mais privent l'exploitant d'une vue consolidée entre boutiques.
Solution: J'ai placé un hub local entre les deux. Les caisses parlent à un hub du réseau de la boutique qui possède sa base SQLite embarquée, si bien que la vente ne dépend jamais d'internet. Le hub se réconcilie avec PostgreSQL sur Cloud SQL dès le retour du lien, ce qui préserve la vue consolidée multi boutiques.
Problème: Restauration, retail généraliste et épicerie vendent de manières réellement différentes, et la réponse habituelle est un produit distinct ou un code dupliqué pour chacun. Chaque duplication dérive ensuite, et un correctif écrit pour un secteur doit être reporté à la main sur les autres.
Solution: J'ai modélisé les écarts sectoriels en configuration au dessus d'un catalogue, d'un moteur de tarification et d'un journal de ventes partagés. Un seul code sert les trois, et un nouveau secteur devient un profil de paramètres plutôt qu'une duplication. C'est aussi ce qui rend la suite vendable à un second exploitant sans un second code à maintenir.

