PharmaSys
Logiciel de gestion pour la pharmacie d'un centre médico-social au Togo : ventes, stock par lots, tiers payant, actes médicaux, achats, personnel et comptabilité dans un seul outil.
Développeur unique (produit, architecture, développement, déploiement)
PharmaSys fait tourner l'activité quotidienne d'une officine mono-site. Il couvre le point de vente, le stock suivi par lots, la répartition entre ce que paie le patient et ce que rembourse l'assureur, les consultations et actes médicaux, les achats fournisseurs, les sessions de caisse, le personnel et la paie, et les rapports. Il est pensé pour la pratique officinale en Afrique de l'Ouest : montants en francs CFA, paiements mobile money (TMoney, Flooz), et un flux tiers payant complet pour les assureurs publics et privés. Le personnel travaille sur des écrans limités à son rôle : un préparateur monte la vente, un caissier encaisse, un gérant gère les créances et le back-office. Le système est en production quotidienne au centre depuis mars 2026.
Le problème
Tenir une officine, c'est tenir deux choses à la fois. Chaque médicament est un lot daté qu'il faut vendre avant péremption, ranger dans la bonne zone et recommander avant la rupture. Et chaque vente pour un patient assuré doit être répartie au comptoir entre la part patient et la part assureur, puis cette créance doit être suivie et recouvrée dossier par dossier. Tenu à la main entre cahiers et tableurs, le stock qui périme passe entre les mailles, le taux de couverture est estimé au comptoir, et ce que doit chaque assureur est difficile à voir.
Les chiffres d’usage concrets de ce projet ne sont pas publiés.
L’approche
J'ai construit une seule application Laravel pour tout le flux de l'officine, au lieu de relier des outils séparés. Le stock est tenu en lots datés avec sortie premier périmé premier sorti et des zones officine et réserve distinctes, avec transferts entre les deux. Un moteur de couverture calcule la part assureur et la part patient sur chaque ligne au point de vente, selon un ordre de règles fixe (produit, catégorie, formule du patient, taux par défaut de l'assureur), et garde la trace du calcul. Les créances assureurs sont regroupées en bordereaux qui suivent le remboursement et le rejet par assureur. Toute l'application est rendue côté serveur, donc rapide à charger sur la connexion du centre et simple à déployer sur hébergement mutualisé.
Le résultat
- En production quotidienne au centre depuis mars 2026, sur une officine unique, pour les rôles préparateur, caissier, gérant et pharmacien.
- Une seule application couvre désormais ce qui était géré séparément : ventes, stock par lots et péremptions, part assurance et créances, achats, sessions de caisse, paie et comptabilité.
- Les ventes assurées sont réparties automatiquement au comptoir, et chaque créance garde le détail du calcul de son taux, donc un rejet peut être vérifié ligne à ligne.
- Les créances assureurs sont regroupées en bordereaux qui suivent le remboursement et le rejet par assureur, au lieu d'un total qui court.
Les chiffres d’usage concrets de ce projet ne sont pas publiés.
Fonctionnalités clés
- Point de vente en deux étapes : un préparateur monte le panier et le soumet, un caissier encaisse. Espèces, TMoney, Flooz, chèque, paiement mixte et vente à crédit.
- Stock suivi par lots : date de péremption par lot, vente premier périmé premier sorti, zones officine et réserve, transferts entre zones, inventaires et mise en quarantaine.
- Répartition assurance automatique sur chaque vente, avec un interrupteur par ligne pour facturer un article entièrement au patient, et un contrôle ordonnance ou accord préalable.
- Créances assurance regroupées en bordereaux, avec remboursement (réparti au prorata sur les créances) et rejet suivis par assureur.
- Consultations et actes médicaux facturés au même comptoir, avec leur propre tarification et un ticket thermique.
- Achats : commandes fournisseurs, réceptions partielles, factures, paiements et retours.
- Sessions de caisse avec comptage à l'ouverture et à la fermeture et un rapport de session.
- Personnel : plannings, pointage, demandes de congé avec approbation, et paie avec bulletins.
- Centre d'alertes pour ruptures, stock bas, lots expirant et périmés, et crédits en retard.
- Import et export CSV par type de donnée, plus une archive de sauvegarde complète.
- Rôles et permissions éditables, un journal d'audit de chaque modification, et un guide d'utilisation intégré couvrant chaque module.
Ingénierie — Architecture, décisions et compromis
Architecture
Application Laravel 12 / PHP 8.3 monolithique, rendue côté serveur avec Blade, Tailwind et Vite, sur PostgreSQL. Environ 56 modèles Eloquent avec clés primaires UUID et suppression douce, environ 40 enums pour les statuts et types, et cast décimal sur chaque champ monétaire. Les règles métier vivent dans des observers et des services plutôt que dans les contrôleurs : la transition de vente PENDING vers COMPLETED déclenche la sortie de stock premier périmé premier sorti au niveau du pivot lot-emplacement, les écritures comptables et la création de la créance assurance via un unique SaleObserver, et l'annulation d'une vente rejoue ces mouvements en sens inverse depuis le journal des mouvements de stock. Une couche rôle et permission maison (pas un package) protège chaque route, avec environ 52 permissions réparties sur 5 rôles système plus des rôles custom éditables, et un cache des permissions par requête. Les champs cliniques (diagnostic, notes) sont chiffrés au repos avec le chiffreur de Laravel. Les références lisibles (VNT-YYYYMMDD-0001 et similaires) sont générées avec une lecture de séquence verrouillée pour que deux ventes simultanées ne se percutent pas. Trois commandes planifiées gèrent les crédits en retard, les échéances de contestation des créances et la génération de notifications. Les documents PDF (reçus, bulletins, bordereaux, rapports de caisse, relevés assurance) sont produits avec DomPDF. Les assets sont compilés et commités ; l'application tourne sur hébergement mutualisé avec le scheduler sur cron et sans worker de queue. Une suite PHPUnit de 68 tests (193 assertions) couvre le flux POS, l'annulation de vente, la réception d'achat, la file de consultations et les bordereaux, sur SQLite en mémoire.
Défis d’ingénierie
- Sortie premier périmé premier sorti en concurrence : les lots sont pré-alloués dans la transaction de vente avec des verrous de ligne (
fefo()->lockForUpdate()), et le vrai décrément se fait à la transition de statut dans l'observer, sur le pivot lot-emplacement plutôt que sur une seule colonne quantité, donc officine et réserve restent cohérentes et une annulation se rejoue exactement. - Le stock vendable est une valeur calculée, pas une colonne : c'est seulement la quantité en zones marquées vendables, non périmée et non en quarantaine. Le POS, le centre d'alertes et les suggestions de transfert lisent tous cette même définition, exprimée à la fois en PHP et en sous-requête SQL corrélée pour les listes paginées.
- Le taux de couverture d'une ligne peut venir d'une règle produit, d'une règle catégorie, de la formule du patient ou du taux par défaut de l'assureur, chacune limitable à une formule. Un service résout cet ordre pour chaque ligne et stocke le détail obtenu sur la créance, pour qu'un rejet ultérieur puisse être contesté face au calcul exact.
- Annuler une vente terminée : l'annulation relit les
StockMovementde la référence de vente et défait le stock, les stats client, la comptabilité et la créance, au lieu de se fier à un seul instantané « avant ».
Décisions techniques
- Couche rôle et permission maison plutôt qu'un package (Spatie) : les contrôles sont liés à des actions métier précises (approuver, valider, soumettre, forcer), pas seulement au CRUD, et les permissions sont mises en cache par requête. Une couche faite main garde ça explicite et sans dépendance.
- Blade rendu côté serveur plutôt qu'une application monopage : le centre tourne sur hébergement mutualisé avec une connexion variable. Le rendu de page complet garde l'app rapide à charger et simple à déployer, assets commités et sans étape de build sur le serveur.
- Chiffrer les champs cliniques au repos : après avoir regardé comment les données de santé sensibles sont traitées ailleurs, le diagnostic et les notes cliniques sont stockés chiffrés, pour qu'un export de base n'expose pas l'historique patient. C'est un choix de conception, pas une exigence du client.
- Point de vente en deux étapes (préparateur, puis caissier) : ça reflète le fonctionnement réel du comptoir et sépare qui monte une commande de qui tient la caisse. Ça donne aussi un point net, la transition de statut, où tous les effets de bord (stock, comptabilité, créance) s'exécutent exactement une fois.
- Tout le modèle tiers payant (assureurs, formules, règles de couverture granulaires, bordereaux, remboursement au prorata) est ma conception, pas un cahier des charges remis par le client.
Stack
- Laravel 12
- PHP 8.3
- PostgreSQL
- Blade
- Tailwind CSS
- Vite
- DomPDF
- ApexCharts
- PHPUnit
Développeur unique (produit, architecture, développement, déploiement) — Centre médico-social privé à Kara, Togo (anonymisé). Pharmacie unique rattachée. (2026)