Aller au contenu
SaaS métier · démonstrateur client2026en cours

Cabinet Médical

Sept métiers différents - analyses, parapharmacie, location, stock, facturation, RH, paie - un seul pivot qui les tient tous.

Signal reçu

Un centre médical algérien gère sept activités distinctes sur des supports séparés : commandes d'analyses, ventes parapharmaceutiques, location de véhicules sanitaires, stock, facturation, RH et cotisations CNAS. Le périmètre initial a été chiffré et validé, puis la RH et la paie s'y sont ajoutées en cours de route - sans qu'aucune de ces briques ne devienne un silo de plus.

Signal émis

Un SaaS construit autour d'une entité pivot, Commande, qui fait transiter les trois métiers générateurs de flux (analyses, parapharmacie, location) par le même circuit devis → bon de commande → livraison ou contrat → facture, avec un suivi QR public pour les entreprises clientes. La RH et la paie vivent dans un domaine séparé, avec leurs propres calculs de cotisations et de solde de congés.

Résultats

7
modules métier
22
modèles de données
23
pages d'administration
100 %
mutations auditées

Démonstration interactive

Sept métiers, un seul circuit de documents

Changez le type de commande et son statut : le même pipeline s'ouvre au fur et à mesure.

Type de commande

Statut courant

  1. Devis

    générable

  2. Bon de commande

    à partir de CONFIRMEE

  3. Bon de livraison

    à partir de EN_COURS

  4. Facture

    à partir de TERMINEE

Les trois métiers empruntent le même circuit : seule la location remplace le bon de livraison par un contrat. Le verrou porte sur un statut minimum, pas sur l'étape précédente, parce que les vraies commandes sautent des étapes : une analyse passe régulièrement du devis à la facture sans bon de livraison.

Mécanisme reconstruit pour ce site, ce n'est pas une capture du produit. Règle reprise de lib/documents.ts, documentsGenerables(). Aucune donnée : la démonstration ne montre que la règle.

Flux de traitement

De l'interface jusqu'aux données

  1. Interface

    23 pages en App Router, thème shadcn/ui générique en attente de la charte finale du client. Les fiches détail 360° (patient, entreprise, véhicule, produit) suivent toutes le même patron : deux colonnes, historique complet, formulaire d'édition repliable.

  2. Domaine pivot

    Commande, avec un type discriminant analyse | parapharma | location, porte statut, token QR et alimente le même pipeline devis → BC → BL/contrat → facture. La RH reste un domaine séparé, hors de ce pivot.

  3. Actions serveur

    13 modules de Server Actions validées par Zod. Chaque mutation écrit dans le journal d'audit append-only, dans la même transaction que l'écriture qu'elle documente.

  4. Persistance

    PostgreSQL via Prisma 7 et @prisma/adapter-pg, une contrainte EXCLUDE au niveau base contre le chevauchement de réservations, et un cabinetId déjà présent sur chaque table métier.

Décisions d'ingénierie

Les choix qui méritent d'être défendus, et pourquoi

01

Une entité pivot pour trois métiers, un domaine séparé pour la RH

Commande - discriminée par un type analyse | parapharma | location - porte statut, token QR et alimente le même circuit devis → bon de commande → livraison ou contrat → facture, quel que soit le métier. Quand la RH a été ajoutée après coup, la tentation était de la faire rentrer dans ce même pivot pour réutiliser le circuit de documents. Mauvaise idée : un contrat de travail n'est pas une commande. La RH est restée un domaine à part - Employé → Contrat de travail → Congé / Bulletin de paie - avec son propre cabinetId sur chaque table plutôt qu'une dépendance implicite à la relation employé.

  • lib/actions/commandes.ts
  • prisma/schema.prisma
02

Empêcher un double-booking dans la base, pas dans le code

Deux réservations ne doivent jamais se chevaucher sur le même véhicule. Plutôt que de le vérifier applicativement - donc de façon contournable par une écriture concurrente - la garantie est une contrainte EXCLUDE PostgreSQL. Le code applicatif détecte la violation via un code d'erreur (P2039) non documenté officiellement, obtenu par un chemin de repli du runtime Prisma et vérifié empiriquement contre la version installée. C'est un couplage fragile, assumé comme tel : un filet générique existe si ce code change au prochain upgrade de Prisma, et le correctif est déjà identifié - retester en déclenchant volontairement un chevauchement.

  • prisma/migrations/20260830130533_reservation_no_overlap
  • lib/actions/commandes.ts
03

Un numéro de facture qui ne peut jamais se dupliquer

La numérotation séquentielle par (cabinet, type de document, année) passe par un upsert sur un compteur dédié, dans la même transaction que la création du document. Chaque document fige son montant total à la génération et n'est plus jamais recalculé : corriger une erreur annule la commande, jamais un document déjà émis. Même prudence que pour les réservations - l'atomicité de cet upsert n'a été vérifiée qu'en écriture séquentielle, pas encore sous forte concurrence.

  • lib/actions/documents.ts
  • lib/documents.ts
04

Le suivi externe ne voit jamais de contenu clinique

Les entreprises clientes - médecine du travail, comptes B2B parapharma - n'ont jamais de compte staff, seulement un lien de suivi. Le token est opaque, aléatoire et expirant : jamais l'identifiant séquentiel de la commande dans une URL. La page publique filtre les champs exposés au niveau de la requête elle-même - statut et dates, jamais de donnée médicale - pour qu'une erreur d'interface ne puisse pas suffire à fuiter du contenu clinique.

  • app/suivi/[token]/page.tsx
  • lib/audit.ts
05

La conformité a choisi l'hébergement avant que le code n'existe

La loi algérienne sur les données de santé (18-07/25-11) impose un hébergement physiquement situé en Algérie dès qu'une donnée patient réelle entre en jeu - même en compute serverless soi-disant stateless. Cette démo, à données fictives, reste donc explicitement scopée à Vercel + Supabase pour la validation client ; le vrai déploiement basculera en Algérie le jour où de vraies données patients y entrent. Le schéma n'attend pas ce jour-là : un cabinetId est déjà posé sur chaque table métier alors que la V1 reste mono-cabinet, pour ne pas payer une migration lourde plus tard.

  • CLAUDE.md
  • docs/cadre-legal.html

Pile technique

  • Next.js 16
  • React 19
  • TypeScript
  • Prisma 7
  • PostgreSQL
  • Supabase
  • @react-pdf/renderer
  • Zod

Ce que ce projet démontre

Trois points à retenir

01

Une entité pivot pour trois métiers, un domaine séparé pour la RH

Commande - discriminée par un type analyse | parapharma | location - porte statut, token QR et alimente le même circuit devis → bon de commande → livraison ou contrat → facture, quel que soit le métier.

02

Empêcher un double-booking dans la base, pas dans le code

Deux réservations ne doivent jamais se chevaucher sur le même véhicule.

03

Un numéro de facture qui ne peut jamais se dupliquer

La numérotation séquentielle par (cabinet, type de document, année) passe par un upsert sur un compteur dédié, dans la même transaction que la création du document.

Un projet comparable ?

Ce dossier est la preuve des prestations suivantes