NexStock
Stocks, ventes et point de vente pour les entreprises que le tableur ne suit plus.
Web · iOS · Android

Tableau de bord 
Produits 
Factures

Accueil 
Caisse 
Historique de stock 
Produits
Les PME échouent rarement par manque d’effort. Elles perdent de la marge dans les interstices — un stock compté ici et vendu là, un devis jamais devenu facture, un transfert que personne n’a enregistré. NexStock les referme en faisant passer chaque vente, livraison, transfert et inventaire par un seul registre de stock, succursale par succursale.
NexStock est le produit de StyloTech. Nous le définissons, le concevons, le développons, l’exploitons et le supportons — l’API Laravel, l’application web React, l’application Flutter pour iOS et Android, la console d’exploitation, le site public et le déploiement.
Parce que nous l’exploitons nous-mêmes, l’architecture que nous recommandons est celle avec laquelle nous vivons : l’entreprise est vérifiée à chaque requête, les rôles sont appliqués par le serveur et non par les écrans, les frontières entre modules sont contrôlées en intégration continue, et chaque migration est éprouvée sur une base vide avant une mise en production.
- App Store
- Google Play
- Application web
Les deux fiches restent à publier. En attendant, l’application web est en ligne.
- Web
- iOS
- Android
- Backend
- Laravel 12
- PHP 8.3
- JWT
- TOTP two-factor
- DomPDF
- Client web
- React 18
- TypeScript
- Vite
- Tailwind CSS
- TanStack Query
- Zod
- Client mobile
- Flutter
- BLoC
- go_router
- Dio
- mobile_scanner
- Données
- PostgreSQL 16
- Redis
- S3-compatible storage
- Qualité & livraison
- PHPUnit
- Deptrac
- Larastan
- Pint
- GitHub Actions
- Docker
- Stratégie produit
- Design UI/UX
- Design system
- Architecture technique
- Développement frontend
- Développement backend
- Développement mobile
- Conception de la base de données
- Intégrations
- Tests & assurance qualité
- Mise en production
- Maintenance
Ce que ça fait
Chaque élément répond à une tâche réelle le jour de l’épreuve, pas à une liste de fonctionnalités. Le site du produit les détaille un par un.
Le stock par succursale, avec un registre
Chaque livraison, vente, correction et transfert écrit un mouvement avec le solde qu’il laisse, et le stock de la succursale est mis à jour sous verrou de ligne — chaque chiffre à l’écran a donc son historique.
Une caisse qui vend hors ligne
La caisse du téléphone envoie chaque vente avec sa propre référence. Sans connexion, elle la met en file sous un numéro de reçu provisoire et la synchronise plus tard ; un renvoi retourne la facture d’origine au lieu de vendre deux fois.
Une caisse dans le navigateur aussi
La caisse web travaille sur le même catalogue et le même stock, avec des commandes en attente et une caisse ouverte avec un fond et fermée en fin de journée.
Des transferts entre succursales
Un transfert passe de brouillon à en attente, en transit puis terminé. En attente réserve le stock à la source, l’expédition le sort et la réception le fait entrer — et une étape dans le désordre est refusée.
Des devis qui deviennent des factures
Un devis se convertit en facture sans ressaisie, sauf s’il a expiré, été refusé ou déjà été converti. Une tâche nocturne marque en retard les factures impayées.
Avoirs et remboursements
Un remboursement en caisse est enregistré comme un avoir, et l’émission d’un avoir remet la marchandise en stock. Les avoirs portent leurs propres paiements.
Des achats bien réceptionnés
Les bons de commande passent de l’approbation à la réception. Chaque livraison reçoit son propre bon de réception, une réception supérieure à la commande est refusée, et les factures fournisseurs sont rapprochées des deux.
Un catalogue fournisseurs
La référence propre à chaque fournisseur, son prix de revient et son délai par produit, avec un fournisseur préféré pour le réassort.
Trente-deux rapports
Rapports de stock, de ventes, d’achats et de créances générés sur le serveur, enregistrés et envoyés par e-mail selon un calendrier — avec import et export CSV du catalogue et un export complet des données.
Des documents à l’image de l’entreprise
Un éditeur web règle le modèle, la couleur, la police, l’en-tête, le pied de page et le filigrane des factures, devis et bons de commande ; le téléphone imprime les reçus en 58 mm, 80 mm ou A4.
Rôles et double authentification
Propriétaire, administrateur, responsable et employé, appliqués par l’API sur chaque groupe de routes. Double authentification par application avec codes de secours, et compte verrouillé après cinq échecs.
Clés d’API et webhooks signés
Des clés liées à une seule entreprise, et des webhooks pour les factures, devis, paiements et avoirs, signés en HMAC-SHA256, retentés, puis désactivés après des échecs répétés.
Une vente en caisse, retracée
Un caissier enregistre une perceuse sans fil et deux perforateurs sur le téléphone et encaisse. Voici tout ce qui se passe entre le bouton « Encaisser » et le mouvement du registre de stock.
Relevé dans pos_repository.dart, bootstrap/app.php, ResolveTenantContext, PosSaleController, RecordPosSaleAction et StockWriter. Chaque nom ci-dessous apparaît dans le code exactement tel quel.
- 01
Le téléphone l’envoie une fois, ou la garde
PosBloc soumet la vente avec son propre client_reference et l’heure de vente en UTC. Hors ligne, ou sur une erreur réseau, la vente part dans l’Outbox sous un numéro de reçu provisoire OFF- et est envoyée au retour de la connexion.
- PosSaleSubmitted
- client_reference
- Outbox
- OutboxKind.sale
Ce qui s’affiche en cas de refus
Une vente que le serveur rejette sur le fond n’est pas mise en file : retenter une requête vouée à l’échec cacherait le problème au caissier, elle lui est donc affichée.
- 02
Les contrôles à l’entrée
Une clé d’API, si elle est présentée, est résolue avant tout le reste ; l’appelant est limité en débit, et une plateforme en maintenance répond avant toute logique métier.
- AuthenticateApiKey
- throttle:api
- MaintenanceMode
Ce qui s’affiche en cas de refus
Plus de 300 requêtes par minute d’un même utilisateur reçoivent RATE_LIMITED ; pendant une maintenance, chaque requête reçoit platform.maintenance avec un code 503.
- 03
Un utilisateur connecté
Les routes de la caisse sont protégées par une authentification JWT.
- auth:api
- AUTH_UNAUTHENTICATED
Ce qui s’affiche en cas de refus
Sans jeton, ou avec un jeton expiré, la réponse est 401 AUTH_UNAUTHENTICATED.
- 04
… qui appartient à cette entreprise
Le middleware d’entreprise lit l’entreprise dans X-Business-Id, charge l’appartenance de l’appelant et lie l’entreprise pour tout ce qui suit — y compris le filtre d’entreprise de chaque requête SQL.
- X-Business-Id
- ResolveTenantContext
- tenant_id
- tenant_role
Ce qui s’affiche en cas de refus
Un en-tête manquant, une entreprise dont l’appelant n’est pas membre, une appartenance suspendue ou une entreprise suspendue ont chacun leur clé : TENANT_HEADER_MISSING, TENANT_NOT_FOUND, MEMBERSHIP_INACTIVE, TENANT_SUSPENDED.
- 05
Tous les rôles peuvent vendre
La caisse n’a volontairement aucun contrôle de rôle : propriétaires, administrateurs, responsables et employés encaissent tous. Ailleurs, les mêmes quatre rôles sont appliqués par groupe de routes, et un rôle non admis est refusé par un 403.
- MembershipRole
- owner
- admin
- manager
- staff
- 06
Validée, et rattachée à une succursale
Chaque ligne exige une quantité entière d’au moins un, et le total ne peut pas être négatif. La succursale vient de X-Branch-Id, puis du nom de la succursale, puis de la première succursale autorisée à vendre ; les ventes au comptoir partagent un seul client Walk-in, et une heure de vente dans le futur est ramenée à maintenant.
- RecordPosSaleRequest
- X-Branch-Id
- pos.no_branch
Ce qui s’affiche en cas de refus
Une vente mal formée reçoit 422 validation_failed avec les erreurs par champ ; une entreprise sans succursale autorisée à vendre reçoit pos.no_branch.
- 07
Une facture, écrite une seule fois
RecordPosSaleAction cherche d’abord le client_reference. Un renvoi depuis l’Outbox retourne la facture déjà créée ; toute nouvelle vente est écrite avec ses lignes comme une facture de caisse payée, dans une seule transaction.
- RecordPosSaleAction
- replayOf
- invoices
- invoice_items
- 08
Le stock passe par le contrat de l’inventaire
Le module des ventes ne touche jamais aux tables de stock. Il appelle le StockWriterContract du module d’inventaire, qui verrouille la ligne de stock de la succursale, retire la quantité et enregistre un mouvement avec le solde restant — les lots étant d’abord décomposés en leurs composants.
- StockWriterContract
- withdraw
- branch_stock
- lockForUpdate
- stock_movements
- 09
Payée, annoncée, imprimée
Une ligne de paiement par moyen de règlement, chacune plafonnée au total pour que la monnaie rendue ne soit jamais comptée comme chiffre d’affaires. L’alerte de stock bas et l’événement de facture partent après l’écriture, et le téléphone génère lui-même le reçu — en 58 mm, 80 mm ou A4.
- payments
- StockDepleted
- InvoiceRaised
- buildReceiptPdf
Détail technique
Relevé dans le code source du projet, pas dans une plaquette commerciale.
- API
- Laravel 12 sur PHP 8.3 en monolithe modulaire : 17 modules, chacun découpé en couches domaine, application et infrastructure ; 332 routes ; 116 migrations construisant 94 tables sous PostgreSQL 16 ; Deptrac relève 0 violation de frontière et bloque l’intégration continue
- Multi-entreprise
- L’entreprise vient de X-Business-Id et l’appartenance de l’appelant est revérifiée à chaque requête sur 245 routes ; des contrôles de rôle couvrent 123 autres routes par groupe ; la liaison des modèles s’exécute après le contrôle d’entreprise, si bien qu’un enregistrement d’une autre entreprise ne peut pas être lié
- Client web
- React 18 avec TypeScript strict et Vite, TanStack Router et Query, Zod à chaque frontière d’API, Tailwind CSS et Radix ; 16 modules fonctionnels et 59 routes en anglais et en français, plus une console d’exploitation sur la même pile
- Client mobile
- Flutter avec BLoC, go_router, Dio et get_it — 193 fichiers Dart écrits à la main répartis en 19 fonctionnalités et 85 routes, cinq onglets, lecture par caméra, une file hors ligne pour cinq types d’écriture et des reçus générés sur l’appareil
- Données & exploitation
- PostgreSQL 16, Redis pour le cache et les files, stockage objet compatible S3 ; des conteneurs distincts pour les tâches de fond et la planification, qui marquent les factures en retard, envoient les alertes de péremption et sauvegardent la base chaque nuit
- Contrôles qualité
- 601 tests PHPUnit et 4 662 assertions, Pint et Deptrac bloquants en intégration continue et Larastan au niveau 6 en rapport ; chaque migration appliquée à un PostgreSQL 16 vide et les caches de l’image de production construits avant qu’elle soit jugée déployable ; 109 tests Flutter en plus
Stocks, ventes et point de vente pour les entreprises que le tableur ne suit plus.
