461 lines
15 KiB
Markdown
461 lines
15 KiB
Markdown
# Cahier des Charges — Back-Office Superviseur
|
||
## Projet : Laverie Connectée — Interface d'Administration
|
||
**Version :** 1.0
|
||
**Date :** 2026-06-27
|
||
**Stack principale :** Laravel 11 (API déjà existante) + Vue.js 3 + Inertia.js
|
||
|
||
---
|
||
|
||
## 1. Contexte & Objectifs
|
||
|
||
### 1.1 Contexte
|
||
Interface web d'administration destinée aux **superviseurs** (exploitants de laveries). Elle s'appuie sur les endpoints `/api/v1/supervisor/*` de l'API Laravel et s'authentifie avec le guard `supervisor`.
|
||
|
||
Le back-office est une **SPA (Single Page Application)** rendue côté serveur via Inertia.js pour simplifier le déploiement (pas d'API séparée pour le back-office, utilisation directe de Laravel comme backend Inertia).
|
||
|
||
### 1.2 Utilisateurs cibles
|
||
| Rôle | Droits |
|
||
|------|--------|
|
||
| `admin` | Accès complet multi-établissements, gestion des superviseurs |
|
||
| `manager` | Accès complet à son établissement |
|
||
| `viewer` | Lecture seule (statistiques, tableau de bord) |
|
||
|
||
### 1.3 Objectifs fonctionnels
|
||
- Visualiser l'occupation en temps réel du parc de machines
|
||
- Consulter les statistiques d'utilisation et le chiffre d'affaires
|
||
- Gérer la tarification et les promotions
|
||
- Recevoir et gérer les alertes machines (pannes, hors ligne)
|
||
- (v2) Envoyer des campagnes de notifications aux utilisateurs
|
||
|
||
---
|
||
|
||
## 2. Stack Technique
|
||
|
||
| Composant | Technologie | Version | Justification |
|
||
|-----------|-------------|---------|---------------|
|
||
| Backend | Laravel 11 | (partagé avec l'API) | Inertia server-side rendering |
|
||
| Intégration SPA | Inertia.js | 2.x | SSR Laravel → Vue.js sans API REST dédiée |
|
||
| Framework UI | Vue.js | 3.x (Composition API) | Réactivité, écosystème riche |
|
||
| Build tool | Vite | 5.x | HMR rapide, bundling optimisé |
|
||
| UI Components | PrimeVue | 4.x | DataTable, Chart, Calendar, etc. |
|
||
| CSS | Tailwind CSS | 3.x | Utility-first, cohérence design |
|
||
| Graphiques | Chart.js via PrimeVue | 4.x | Courbes CA, camembert machines |
|
||
| State management | Pinia | 2.x | Store réactif Vue 3 |
|
||
| Temps réel | Laravel Echo + Pusher (ou Soketi self-hosted) | — | Mise à jour statut machines |
|
||
| Validation forms | VeeValidate + Zod | — | Validation côté client |
|
||
| Tables | PrimeVue DataTable | — | Tri, filtre, pagination, export |
|
||
| Auth | Laravel Sanctum (session cookie) | — | SPA same-domain |
|
||
| Tests back | Pest | — | Tests controllers superviseur |
|
||
| Tests front | Vitest + Vue Test Utils | — | Composants Vue |
|
||
| E2E | Playwright | — | Parcours critiques superviseur |
|
||
|
||
---
|
||
|
||
## 3. Architecture
|
||
|
||
### 3.1 Structure des fichiers
|
||
|
||
```
|
||
resources/
|
||
├── js/
|
||
│ ├── app.js # Point d'entrée Inertia
|
||
│ ├── bootstrap.js # Echo, Axios
|
||
│ ├── Components/
|
||
│ │ ├── Layout/
|
||
│ │ │ ├── AppLayout.vue # Layout principal (sidebar + topbar)
|
||
│ │ │ ├── Sidebar.vue
|
||
│ │ │ └── Topbar.vue
|
||
│ │ ├── Charts/
|
||
│ │ │ ├── RevenueChart.vue
|
||
│ │ │ ├── OccupancyChart.vue
|
||
│ │ │ └── MachineStatusChart.vue
|
||
│ │ ├── Machines/
|
||
│ │ │ ├── MachineCard.vue # Carte statut en temps réel
|
||
│ │ │ └── MachineGrid.vue
|
||
│ │ └── UI/
|
||
│ │ ├── StatCard.vue
|
||
│ │ ├── AlertBadge.vue
|
||
│ │ └── ConfirmDialog.vue
|
||
│ │
|
||
│ └── Pages/
|
||
│ ├── Auth/
|
||
│ │ └── Login.vue
|
||
│ ├── Dashboard/
|
||
│ │ └── Index.vue
|
||
│ ├── Machines/
|
||
│ │ ├── Index.vue
|
||
│ │ └── Show.vue
|
||
│ ├── Pricing/
|
||
│ │ ├── Index.vue
|
||
│ │ └── Edit.vue
|
||
│ ├── Promotions/
|
||
│ │ ├── Index.vue
|
||
│ │ └── Create.vue
|
||
│ ├── Stats/
|
||
│ │ └── Index.vue
|
||
│ └── Settings/
|
||
│ └── Index.vue
|
||
│
|
||
app/
|
||
├── Http/Controllers/Supervisor/ # (déjà définis dans CDC API)
|
||
│ ├── DashboardController.php
|
||
│ ├── MachineController.php
|
||
│ ├── PricingController.php
|
||
│ ├── PromotionController.php
|
||
│ └── StatsController.php
|
||
└── Events/
|
||
└── MachineStatusUpdated.php # Broadcast temps réel
|
||
```
|
||
|
||
### 3.2 Routage Laravel (Inertia)
|
||
|
||
```php
|
||
// routes/supervisor.php (middleware: auth:supervisor, inertia)
|
||
Route::prefix('supervisor')->name('supervisor.')->group(function () {
|
||
Route::get('/dashboard', [DashboardController::class, 'index'])->name('dashboard');
|
||
Route::resource('/machines', MachineController::class)->only(['index','show','update']);
|
||
Route::resource('/pricing', PricingController::class);
|
||
Route::resource('/promotions', PromotionController::class);
|
||
Route::get('/stats', StatsController::class)->name('stats');
|
||
Route::get('/settings', SettingsController::class)->name('settings');
|
||
});
|
||
```
|
||
|
||
---
|
||
|
||
## 4. Écrans & Fonctionnalités
|
||
|
||
### 4.1 Connexion (`/supervisor/login`)
|
||
|
||
**Formulaire :**
|
||
- Email + Mot de passe
|
||
- Bouton "Se connecter"
|
||
- Lien "Mot de passe oublié"
|
||
|
||
**Comportement :**
|
||
- Session cookie Sanctum (SPA same-domain)
|
||
- Redirection automatique vers `/supervisor/dashboard` si déjà connecté
|
||
- Rate limiting : 5 tentatives / 10 min
|
||
|
||
**Données Inertia passées :**
|
||
```php
|
||
// Aucune — page statique
|
||
```
|
||
|
||
---
|
||
|
||
### 4.2 Tableau de Bord (`/supervisor/dashboard`)
|
||
|
||
C'est l'écran central. Il se rafraîchit partiellement en temps réel via Laravel Echo.
|
||
|
||
#### 4.2.1 Bandeau de KPIs (haut de page)
|
||
|
||
| KPI | Description | Période |
|
||
|-----|-------------|---------|
|
||
| CA du jour | Somme des `washes.cost` du jour | Aujourd'hui |
|
||
| Lavages du jour | Nombre de `washes` terminés | Aujourd'hui |
|
||
| Taux d'occupation | % machines actives / total | Temps réel |
|
||
| Solde moyen rechargé | Moyenne des top-ups | Cette semaine |
|
||
|
||
Chaque KPI affiche la variation vs. la veille (flèche + couleur).
|
||
|
||
#### 4.2.2 Grille des Machines (temps réel)
|
||
|
||
- Grille de cartes, une carte par machine
|
||
- Mise à jour via **WebSocket** (Laravel Echo, channel `establishment.{id}`, event `MachineStatusUpdated`)
|
||
- Chaque carte affiche :
|
||
- Nom et type de machine (icône)
|
||
- Statut (badge coloré)
|
||
- Temps restant si en cours (countdown)
|
||
- Utilisateur en cours (avatar anonymisé : initiales)
|
||
- Dernière activité
|
||
|
||
**Couleurs statut :**
|
||
| Statut | Couleur |
|
||
|--------|---------|
|
||
| `available` | 🟢 Vert |
|
||
| `running` | 🔵 Bleu + animation pulse |
|
||
| `reserved` | 🟡 Jaune |
|
||
| `maintenance` | 🟠 Orange |
|
||
| `offline` | 🔴 Rouge |
|
||
|
||
**Actions rapides (rôle manager/admin) :**
|
||
- Mettre en maintenance
|
||
- Forcer disponible
|
||
- Voir le détail
|
||
|
||
#### 4.2.3 Alertes Actives
|
||
|
||
Panel latéral ou section dédié listant :
|
||
- Machines hors ligne (dernier heartbeat > 5 min)
|
||
- Machines en erreur signalée
|
||
- Réservations no-show récentes
|
||
|
||
Chaque alerte : icône, machine concernée, heure, bouton "Traité".
|
||
|
||
#### 4.2.4 Graphique d'occupation journalière
|
||
|
||
Courbe Chart.js (via PrimeVue) :
|
||
- Axe X : heures (00h → 23h)
|
||
- Axe Y : % d'occupation
|
||
- Deux courbes : Aujourd'hui vs Moyenne 30 jours
|
||
|
||
**Données Inertia passées :**
|
||
```php
|
||
Inertia::render('Dashboard/Index', [
|
||
'machines' => MachineResource::collection($machines),
|
||
'kpis' => $dashboardService->getTodayKpis($establishment),
|
||
'alerts' => AlertResource::collection($activeAlerts),
|
||
'occupancy_chart' => $statsService->getHourlyOccupancy($establishment, today()),
|
||
]);
|
||
```
|
||
|
||
---
|
||
|
||
### 4.3 Statistiques (`/supervisor/stats`)
|
||
|
||
Écran dédié à l'analyse de données avec filtres temporels.
|
||
|
||
#### 4.3.1 Filtres
|
||
|
||
- Période : Aujourd'hui / 7 jours / 30 jours / 3 mois / Personnalisée (date picker)
|
||
- Machine : Toutes / Par type / Par machine spécifique
|
||
|
||
#### 4.3.2 Blocs de statistiques
|
||
|
||
**Chiffre d'affaires**
|
||
- Graphique en barres : CA par jour sur la période
|
||
- Ligne de tendance (moyenne mobile 7 jours)
|
||
- Total période, meilleur jour, pire jour
|
||
|
||
**Utilisation des machines**
|
||
- Graphique camembert : Répartition par type de machine
|
||
- Nombre de cycles par machine (tableau trié)
|
||
- Taux d'utilisation horaire moyen
|
||
|
||
**Utilisateurs**
|
||
- Nouveaux inscrits sur la période
|
||
- Utilisateurs actifs (au moins 1 lavage)
|
||
- Top 10 utilisateurs (anonymisés : "Utilisateur #XXX")
|
||
|
||
**Réservations**
|
||
- Nombre de réservations vs. lavages directs
|
||
- Taux de no-show
|
||
- Taux d'annulation
|
||
|
||
**Rechargements**
|
||
- CA par source de paiement (Stripe, etc.)
|
||
- Montant moyen rechargé
|
||
- Fréquence de rechargement
|
||
|
||
#### 4.3.3 Export
|
||
|
||
- Export CSV des données brutes de la période sélectionnée
|
||
- Export PDF du rapport (via impression navigateur, layout print-optimized)
|
||
|
||
---
|
||
|
||
### 4.4 Gestion de la Tarification (`/supervisor/pricing`)
|
||
|
||
#### 4.4.1 Vue Liste
|
||
|
||
Tableau (PrimeVue DataTable) listant toutes les règles tarifaires :
|
||
|
||
| Machine | Jours | Horaire | Prix | Exclusive App | Actions |
|
||
|---------|-------|---------|------|---------------|---------|
|
||
| Lave-linge 7kg | Semaine | 08h-12h | 3.50€ | Non | ✏️ 🗑 |
|
||
| Lave-linge 7kg | Semaine | 18h-22h | 4.50€ | Oui | ✏️ 🗑 |
|
||
|
||
- Filtrable par machine, type de jour
|
||
- Tri par colonne
|
||
- Badge "Actif" / "Inactif" si les horaires sont hors plage
|
||
|
||
#### 4.4.2 Formulaire Création / Édition
|
||
|
||
**Champs :**
|
||
- Machine (select, avec recherche)
|
||
- Jours applicables (checkboxes : Lun–Ven / Sam–Dim / Jours fériés / Tous)
|
||
- Heure de début / fin (time pickers, validation : début < fin)
|
||
- Prix en euros (input numérique, 2 décimales, min 0.50€)
|
||
- Label (ex: "Heure creuse", "Heure pleine")
|
||
- Exclusive app ✓ (si coché, ce tarif n'est visible que via l'application)
|
||
|
||
**Validation :**
|
||
- Vérification de non-chevauchement avec les règles existantes sur la même machine
|
||
- Avertissement si la plage couvre toute la journée sans tarif de base restant
|
||
|
||
**Règle de priorité affichée :**
|
||
> "Une règle spécifique à une machine a priorité sur une règle par type de machine."
|
||
|
||
---
|
||
|
||
### 4.5 Gestion des Promotions (`/supervisor/promotions`)
|
||
|
||
#### 4.5.1 Vue Liste
|
||
|
||
Tableau avec onglets : **Actives** / **À venir** / **Terminées**
|
||
|
||
Colonnes : Type machine, Réduction, Début, Fin, Description, Statut, Actions.
|
||
|
||
#### 4.5.2 Formulaire Création
|
||
|
||
**Champs :**
|
||
- Type de machine concernée (Lave-linge petit / grand / Sèche-linge / Tous)
|
||
- Type de réduction :
|
||
- Pourcentage (ex: -20%)
|
||
- Montant fixe (ex: -0.50€)
|
||
- Valeur de la réduction
|
||
- Date/heure de début (datetime picker)
|
||
- Date/heure de fin (datetime picker)
|
||
- Description interne (texte libre, visible uniquement dans le back-office)
|
||
|
||
**Validation :**
|
||
- Date de fin > Date de début
|
||
- Réduction % : 1–90%
|
||
- Réduction fixe : ne peut pas dépasser le prix minimum (0.50€ minimum après remise)
|
||
- Avertissement si chevauchement avec une promotion existante du même type
|
||
|
||
**Impact affiché en temps réel :**
|
||
Aperçu du nouveau prix après promotion pour chaque règle tarifaire concernée.
|
||
|
||
---
|
||
|
||
### 4.6 Détail Machine (`/supervisor/machines/{uuid}`)
|
||
|
||
- Informations générales (nom, type, QR code, ID LLDP)
|
||
- Statut actuel avec boutons d'action (Maintenance / Disponible)
|
||
- Historique des 30 derniers cycles (tableau paginé)
|
||
- Grille tarifaire appliquée
|
||
- Uptime sur 30 jours (%)
|
||
- Historique des alertes / pannes (30 derniers jours)
|
||
|
||
---
|
||
|
||
## 5. Temps Réel — Laravel Echo
|
||
|
||
### 5.1 Configuration
|
||
|
||
```javascript
|
||
// bootstrap.js
|
||
import Echo from 'laravel-echo';
|
||
import Pusher from 'pusher-js';
|
||
|
||
window.Echo = new Echo({
|
||
broadcaster: 'pusher',
|
||
key: import.meta.env.VITE_PUSHER_APP_KEY,
|
||
cluster: import.meta.env.VITE_PUSHER_APP_CLUSTER,
|
||
wsHost: import.meta.env.VITE_PUSHER_HOST, // Soketi en self-hosted
|
||
wsPort: 6001,
|
||
forceTLS: false,
|
||
enabledTransports: ['ws', 'wss'],
|
||
});
|
||
```
|
||
|
||
### 5.2 Channels & Events
|
||
|
||
| Channel | Event | Payload | Déclencheur |
|
||
|---------|-------|---------|-------------|
|
||
| `private-establishment.{id}` | `MachineStatusUpdated` | `{ machine_uuid, status, current_user_initials, cycle_ends_at }` | Heartbeat LLDP + fin de cycle |
|
||
| `private-establishment.{id}` | `NewAlert` | `{ type, machine_uuid, message, created_at }` | Offline détecté, erreur machine |
|
||
| `private-establishment.{id}` | `KpiUpdated` | `{ ca_today, washes_today, occupancy_rate }` | Fin de chaque lavage |
|
||
|
||
### 5.3 Implémentation Vue
|
||
|
||
```javascript
|
||
// DashboardController.vue (Composition API)
|
||
onMounted(() => {
|
||
window.Echo
|
||
.private(`establishment.${props.establishment.id}`)
|
||
.listen('MachineStatusUpdated', (event) => {
|
||
machineStore.updateMachineStatus(event.machine_uuid, event);
|
||
})
|
||
.listen('NewAlert', (event) => {
|
||
alertStore.addAlert(event);
|
||
});
|
||
});
|
||
|
||
onUnmounted(() => {
|
||
window.Echo.leave(`establishment.${props.establishment.id}`);
|
||
});
|
||
```
|
||
|
||
---
|
||
|
||
## 6. Sécurité
|
||
|
||
### 6.1 Authentification
|
||
- Session cookie Sanctum (SameSite=Lax, Secure en prod)
|
||
- CSRF token sur toutes les mutations
|
||
- Timeout de session : 8 heures d'inactivité
|
||
|
||
### 6.2 Autorisation (Policies Laravel)
|
||
```
|
||
MachinePolicy::update() → role manager ou admin ET même établissement
|
||
PricingPolicy::create() → role manager ou admin
|
||
PromotionPolicy::delete() → role admin seulement
|
||
StatsPolicy::view() → tous les rôles
|
||
```
|
||
|
||
### 6.3 Données
|
||
- Les données utilisateurs affichées sont **anonymisées** (initiales, jamais nom complet ou email)
|
||
- Logs d'audit sur toutes les mutations (qui a modifié quoi et quand)
|
||
|
||
---
|
||
|
||
## 7. Responsive & Accessibilité
|
||
|
||
- Layout responsive : sidebar collapsible sur tablette, menu bottom sur mobile
|
||
- Taille minimale cible : tablette 768px (utilisation en mobilité sur le terrain)
|
||
- Contraste WCAG AA sur tous les textes
|
||
- Navigation clavier complète (focus visible)
|
||
- Attributs ARIA sur les graphiques (descriptions alternatives)
|
||
|
||
---
|
||
|
||
## 8. Tests
|
||
|
||
| Type | Outil | Portée |
|
||
|------|-------|--------|
|
||
| Tests unitaires | Pest | Services stats, services tarification |
|
||
| Tests controllers | Pest | Tous les controllers superviseur (mocks auth) |
|
||
| Tests composants | Vitest + VTU | StatCard, MachineCard, PricingForm |
|
||
| Tests E2E | Playwright | Login, modifier un tarif, créer une promo, consulter stats |
|
||
|
||
**Scénarios Playwright critiques :**
|
||
1. Login échoué (mauvais mot de passe) → message d'erreur
|
||
2. Login réussi → redirection dashboard
|
||
3. Créer une règle tarifaire → vérification dans la liste
|
||
4. Créer une promotion avec chevauchement → message d'avertissement
|
||
5. Mise en maintenance d'une machine → badge mis à jour en temps réel
|
||
|
||
---
|
||
|
||
## 9. Variables d'environnement spécifiques
|
||
|
||
```env
|
||
# Pusher / Soketi (temps réel)
|
||
PUSHER_APP_ID=
|
||
PUSHER_APP_KEY=
|
||
PUSHER_APP_SECRET=
|
||
PUSHER_HOST=127.0.0.1
|
||
PUSHER_PORT=6001
|
||
PUSHER_SCHEME=http
|
||
PUSHER_APP_CLUSTER=mt1
|
||
|
||
VITE_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
|
||
VITE_PUSHER_HOST="${PUSHER_HOST}"
|
||
VITE_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"
|
||
```
|
||
|
||
---
|
||
|
||
## 10. Livrables attendus
|
||
|
||
- [ ] Code source Vue.js (dans le même dépôt Laravel ou dépôt séparé)
|
||
- [ ] Migrations et seeders pour les superviseurs de démo
|
||
- [ ] Documentation des rôles et permissions
|
||
- [ ] Tests Pest controllers (couverture > 80%)
|
||
- [ ] Tests Playwright (5 parcours critiques)
|
||
- [ ] Guide d'utilisation superviseur (PDF ou page dans le back-office)
|
||
- [ ] `README.md` avec instructions de déploiement front (Vite build + assets)
|