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