Application SAV biomédicale
Projet

Application SAV biomédicale

Application métier développée from scratch pour une entreprise de maintenance d'équipements biomédicaux : parc machines identifié par QR codes, saisie d'interventions multi-machines sur mobile, rapports PDF verrouillés et espace client en lecture seule.

L'entreprise entretient des équipements biomédicaux dans des établissements de santé. Jusqu'ici, chaque intervention donnait lieu à un rapport papier signé sur place. L'application remplace ce flux de bout en bout : le technicien saisit l'intervention sur téléphone ou tablette, la clôture est validée électroniquement, le rapport PDF est généré automatiquement et l'établissement le retrouve dans son espace client.

Technologies utilisées

  • Backend : Django 5.2 LTS + Django REST Framework 3.18 (Python 3.13)
  • Frontend : Nuxt 4 (Vue 3, TypeScript strict), full CSR, servi en statique par Nginx
  • Base de données : PostgreSQL (psycopg 3), en développement comme en production
  • Authentification : JWT (djangorestframework-simplejwt) access token en mémoire, refresh token en cookie httpOnly, blacklist
  • Contrat API : schéma OpenAPI (drf-spectacular) → types TypeScript générés (openapi-typescript) + client typé (openapi-fetch)
  • CSS : Tailwind v4
  • État : Pinia
  • PDF : WeasyPrint
  • QR codes : segno
  • Exports : CSV (stdlib) + Excel (openpyxl)
  • Tests : pytest + factory_boy (back), Vitest + @nuxt/test-utils et Playwright (front)
  • Outillage : uv, ruff, pre-commit, ESLint (@nuxt/eslint)

Architecture

  • Deux repos indépendants (API Django / SPA Nuxt) reliés par le schéma OpenAPI : le back décrit chaque endpoint, le front génère ses types, aucune interface API écrite à la main.
  • Vues fines, services épais : les ViewSets ne portent que la couche HTTP ; la logique métier (clôture, numérotation, gel du contexte, PDF) vit dans des services typés, testables sans HTTP
  • Trois rôles (admin, technicien, client) portés par le User custom ; permissions DRF explicites et filtrage des querysets par rôle et par client. Un objet d'un autre client n'existe pas : 404, jamais 403
  • Aucune page publique : la SPA est générée en statique (ssr: false), aucun process Node en production
  • Fichiers générés privés : les PDF sont stockés hors racine web et servis via X-Accel-Redirect après contrôle d'accès ; les QR codes sont rendus en mémoire à la demande, rien n'est écrit sur disque

Authentification JWT

  • Access token court (15 min) gardé en mémoire côté front, jamais en localStorage
  • Refresh token (7 jours) en cookie httpOnly, SameSite=Strict, Path limité aux endpoints d'authentification
  • Refresh silencieux au démarrage : un QR code scanné ouvre une page à froid et retrouve sa session sans ressaisir le mot de passe
  • Composable API unique : header Authorization, rejeu automatique de la requête après refresh sur 401, requêtes concurrentes mises en file derrière un seul refresh
  • Blacklist activée : la désactivation d'un compte prend effet immédiatement
  • Throttling sur login et refresh, messages d'erreur non énumérants

Fonctionnalités principales

  • Accueil : compteurs (interventions à venir, machines, clients, derniers rapports) et agenda trié par date planifiée
  • Clients : fiches (coordonnées, contact), recherche, archivage logique, jamais de suppression physique
  • Machines : fiches rattachées à un client (n° de série, modèle, mise en service, garantie), rattachement figé après création pour éviter toute fuite d'historique entre clients
  • QR codes : un QR par machine, SVG généré à la demande, encodant l'URL de sa page d'historique ; étiquette imprimable depuis la fiche
  • Interventions : formulaire par étapes pensé mobile (contexte et machines → travaux et résultat par machine → temps, kilomètres et pièces → récapitulatif et clôture), plusieurs machines par dossier, brouillon enregistré à chaque étape
  • Cycle de vie : brouillon → planifiée → clôturée (+ annulée avec motif), transitions contrôlées côté serveur
  • Espace client : chaque établissement consulte ses machines, son historique (dossiers clôturés uniquement), ses prochains entretiens et télécharge ses rapports, en lecture seule
  • Exports CSV/Excel des clients, machines et interventions (réversibilité contractuelle), qui respectent les filtres de la liste
  • Création de techniciens et d'accès espace client depuis l'application, mot de passe initial défini par l'admin
  • Clôture d'intervention et génération de rapport PDF verrouillé

Clôture et rapport PDF

Le cœur du projet est le cycle intervention → clôture → rapport, conçu pour qu'un rapport émis ne puisse jamais être altéré silencieusement.

  • Validation électronique : technicien identifié par sa session, nom du signataire client saisi au clavier (obligatoire), date horodatée
  • Numérotation transactionnelle : le n° de rapport est attribué à la clôture via select_for_update sur une ligne compteur ; brouillons et annulés n'en consomment pas, la séquence des rapports émis est sans trou. Une contrainte en base garantit « clôturée ⟺ numérotée »
  • Contexte gelé : à la clôture, toutes les données imprimées (client, contact, machines, pièces) sont figées dans un instantané JSON ; le PDF est toujours rendu depuis cet instantané, jamais depuis les fiches vivantes
  • Génération WeasyPrint dans la transaction de clôture : un échec annule la clôture et rend le numéro au compteur
  • Verrouillage : toute écriture ultérieure est refusée ; déverrouillage réservé à l'admin, re-clôture qui conserve la validation d'origine et régénère le PDF avec la mention « corrigé le »
  • Garde de concurrence : version transmise à chaque enregistrement, conflit signalé (409) plutôt qu'écrasement silencieux