{ aobarrydev }
Retour aux projets// étude de cas

Store — ERP SaaS

Un ERP multi-tenant qui permet aux commerçants de gérer ventes, achats, stock et abonnements — de bout en bout, sans paperasse.

Store ERP — page d'accueil publique
17modules métier
~215endpoints REST
49entités du domaine
741tests automatisés
85+permissions RBAC
49migrations Flyway

Contexte

Beaucoup de commerçants gèrent encore leur activité sur des cahiers ou des feuilles de calcul : stock approximatif, marges invisibles, aucune traçabilité. L'objectif du Store était de proposer un ERP web et mobile, accessible et abordable, qui couvre tout le cycle commercial d'une boutique — du réapprovisionnement à la caisse — tout en restant simple à prendre en main.

C'est un produit SaaS : chaque entreprise dispose de son espace isolé, peut gérer plusieurs magasins et plusieurs employés aux rôles distincts, et souscrit à un abonnement. J'ai conçu et développé l'ensemble du backend ainsi que l'architecture du système.

Architecture

Le backend suit une approche Domain-Driven Design : chacun des 17 modules métier est un contexte autonome, découpé en quatre couches strictes. Cette séparation garde le cœur métier indépendant du framework et des détails d'infrastructure, et rend chaque module testable et évolutif isolément.

domainCœur métier : entités, règles, interfaces de dépôts (ports).
applicationOrchestration des cas d'usage, DTO et mappers.
infrastructureAdaptateurs Spring Data, specifications JPA, intégrations externes.
presentationContrôleurs REST, validation des entrées, documentation OpenAPI.

Isolation multi-tenant

L'isolation repose sur une stratégie applicative plutôt que sur un schéma par client. À la connexion, un jeton JWT embarque l'identité du tenant (entreprise et magasin), le rôle et les permissions. Chaque requête est ensuite systématiquement filtrée sur ce périmètre, garantissant qu'une entreprise n'accède jamais aux données d'une autre.

Le périmètre est hiérarchique : l'entreprise est le tenant racine, et chaque entreprise peut gérer plusieurs magasins. Une base PostgreSQL unique conserve toutes les données — un choix assumé qui simplifie l'exploitation et maîtrise les coûts à l'échelle visée, tout en gardant la porte ouverte à un cloisonnement plus fort si le besoin émerge.

JWT · entrepriseId · magasinId · permissions
entreprise (tenant)
magasin 1magasin 2magasin n

Frontend & cohérence full-stack

Le frontend (Next.js 16 / React 19 en TypeScript) reprend la même discipline que le backend : une organisation par feature qui calque les modules métier, et une couche commune découpée en domain / application / infrastructure / presentation. Cette symétrie front/back rend le système prévisible — on retrouve les mêmes frontières d'un bout à l'autre de la stack.

Les données serveur sont gérées avec TanStack Query (cache, invalidation), l'état client avec Zustand, et les formulaires avec React Hook Form validés par Zod. L'interface s'appuie sur Tailwind CSS et shadcn/ui, est bilingue (next-intl) et installable en PWA — d'où l'accès web et mobile promis aux commerçants.

Modules clés

  • Ventes & caisse

    Cycle de commande complet, consommation du stock en FIFO, paiements échelonnés, clients anonymes au comptoir et résumé de caisse.

  • Achats & fournisseurs

    Commandes fournisseurs de la création à la réception, réceptions multiples, lots traçables et paiements fractionnés.

  • Stock & inventaire

    Suivi par lot avec dates de péremption, valorisation au prix d'achat moyen, journal des mouvements et inventaires physiques avec rapport profit/perte.

  • Abonnements SaaS

    Plans (Trial, Standard, Pro) avec limites, coupons et promotions, paiement manuel validé sur preuve (Wave, Orange Money).

  • Sécurité & RBAC

    Authentification JWT, refresh tokens, contrôle d'accès piloté par les données : plus de 85 permissions et des rôles personnalisables par entreprise.

  • Reporting & audit

    Tableaux de bord (top produits, marges, valorisation du stock) et journal d'audit dont la portée s'adapte au rôle de l'utilisateur.

Défis & solutions

  • Défi

    Modéliser des utilisateurs très différents — un ADMIN qui administre la plateforme, un propriétaire qui pilote son entreprise, des employés rattachés à ses magasins — chacun lié à des entités distinctes, sans multiplier les mécanismes d'authentification ni d'autorisation.

    Solution

    Séparer l'identité des accès. Une hiérarchie Person → Utilisateur (héritage JOINED) décrit qui est l'utilisateur et à quoi il est rattaché : le propriétaire porte son entreprise, l'employé son magasin. En face, une entité Account unique gère les identifiants et pointe vers un rôle porteur de permissions. Résultat : ADMIN, propriétaire et employés s'authentifient tous par le même point d'entrée et sont autorisés par le même RBAC — chaque endpoint vérifie une permission, jamais le type concret de l'utilisateur. Les rôles système restent non délégables, et le périmètre résolu (entreprise, magasin) est projeté dans le JWT pour une autorisation uniforme.

  • Défi

    Garantir qu'aucune entreprise ne puisse voir les données d'une autre, sur une base de données partagée.

    Solution

    Le tenant (entreprise + magasin) est porté par le JWT et appliqué à chaque requête via le contexte de sécurité. Le filtrage par périmètre est centralisé pour éviter toute fuite par oubli.

  • Défi

    Connaître à tout instant la valeur du stock et tracer chaque unité, y compris les produits périssables.

    Solution

    Un modèle de stock FIFO par lot (numéro de lot, date d'expiration, prix d'achat) avec journal des mouvements horodaté et détection des lots qui expirent. La valorisation s'appuie sur le prix d'achat moyen.

  • Défi

    Offrir des droits fins et différents selon les rôles, sans recompiler à chaque nouveau besoin.

    Solution

    Un RBAC piloté par les données : les permissions et les rôles vivent en base, les rôles sont personnalisables par entreprise et chaque endpoint déclare la permission requise.

  • Défi

    Encaisser les abonnements alors que les paiements locaux passent par Wave, Orange Money ou virement, hors application.

    Solution

    Un flux d'abonnement à paiement manuel : le client téléverse une preuve, un administrateur valide ou rejette, et l'activation est calculée (plan, durée, promotion, coupon). Le modèle est prêt pour une passerelle automatique le moment venu.

  • Défi

    Tenir la qualité sur un périmètre large et garder la base de données maîtrisée.

    Solution

    741 tests exécutés sur une vraie base PostgreSQL, une quality gate SonarQube (couverture du code neuf ≥ 80 %), un schéma versionné par 49 migrations Flyway, le tout livré via une CI GitLab et une image Docker multistage.

Stack technique

Backend
  • Java
  • Spring Boot
  • Spring Security
  • Spring Data JPA
  • JWT
Données
  • PostgreSQL
  • Flyway
Frontend
  • TypeScript
  • React
  • Next.js
  • Tailwind CSS
  • shadcn/ui
  • TanStack Query
  • Zustand
  • React Hook Form
  • Zod
  • PWA
Qualité
  • JUnit 5
  • Mockito
  • JaCoCo
  • SonarQube
  • Vitest
Ops & déploiement
  • Docker
  • GitLab
  • CI/CD
  • Railway
  • Vercel

Envie d'en savoir plus ?

Je peux vous présenter l'architecture en détail et répondre à vos questions techniques.

Me contacter