diff --git a/documentation/CDC_API_Backend.md b/documentation/CDC_API_Backend.md new file mode 100644 index 0000000..1e3c844 --- /dev/null +++ b/documentation/CDC_API_Backend.md @@ -0,0 +1,667 @@ +# Cahier des Charges — API & Base de Données +## Projet : Laverie Connectée — Backend +**Version :** 1.0 +**Date :** 2026-06-27 +**Stack principale :** Laravel 11, MySQL 8, Redis, Docker + +--- + +## 1. Contexte & Objectifs + +### 1.1 Contexte +Développement d'une API REST centrale servant : +- Une application mobile multi-plateforme (Flutter : Android / iOS / Web) +- Un back-office d'administration (Vue.js) +- Des automates de laverie connectés via passerelle LLDP + +L'API est le **seul point d'entrée** pour toute la logique métier. Aucun client ne communique directement avec la base de données. + +### 1.2 Objectifs +- Exposer des endpoints REST sécurisés (JWT) pour tous les clients +- Gérer les utilisateurs, l'authentification, les porte-monnaie et les paiements +- Orchestrer les communications avec les automates (commandes machines) +- Automatiser les notifications (cron, push, événementiel) +- Garantir la conformité RGPD sur toutes les données personnelles + +--- + +## 2. Stack Technique + +| Composant | Technologie | Version | Justification | +|-----------|-------------|---------|---------------| +| Framework | Laravel | 11.x | Robustesse, écosystème, ORM Eloquent | +| Base de données principale | MySQL | 8.0 | Transactions ACID, maturité | +| Cache & Queues | Redis | 7.x | Queues asynchrones, cache sessions | +| Auth | Laravel Sanctum | 4.x | JWT stateless multi-guard | +| ORM | Eloquent | (Laravel) | Relations, scopes, casting | +| Queues | Laravel Horizon | 5.x | Monitoring des workers Redis | +| Scheduler | Laravel Scheduler | (Laravel) | Cron jobs natifs | +| Notifications Push | Firebase FCM | HTTP v1 | Android + Web | +| Notifications Push | APNs (Apple) | HTTP/2 | iOS | +| Paiement | Abstraction PaymentProvider | — | Multi-partenaire | +| Tests | PHPUnit + Pest | — | TDD sur logique métier critique | +| Documentation API | Scramble (L5-Swagger) | — | OpenAPI 3.0 auto-générée | +| Conteneurisation | Docker + Docker Compose | — | Dev/staging/prod reproductibles | +| CI/CD | GitHub Actions | — | Tests + déploiement automatisé | + +--- + +## 3. Architecture + +### 3.1 Structure du projet Laravel + +``` +app/ +├── Http/ +│ ├── Controllers/ +│ │ ├── Auth/ +│ │ │ ├── UserAuthController.php +│ │ │ └── SupervisorAuthController.php +│ │ ├── User/ +│ │ │ ├── WalletController.php +│ │ │ ├── BookingController.php +│ │ │ └── WashController.php +│ │ ├── Supervisor/ +│ │ │ ├── DashboardController.php +│ │ │ ├── MachineController.php +│ │ │ └── PricingController.php +│ │ └── Webhook/ +│ │ └── PaymentWebhookController.php +│ ├── Middleware/ +│ │ ├── EnsureEmailVerified.php +│ │ ├── CheckWalletBalance.php +│ │ └── RateLimitPayment.php +│ └── Requests/ # Form Requests (validation) +├── Models/ +│ ├── User.php +│ ├── Supervisor.php +│ ├── Establishment.php +│ ├── Machine.php +│ ├── Booking.php +│ ├── Wash.php +│ ├── WalletTransaction.php +│ ├── PricingRule.php +│ ├── Promotion.php +│ ├── Notification.php +│ └── GdprConsent.php +├── Services/ +│ ├── WalletService.php # Logique porte-monnaie (atomique) +│ ├── BookingService.php # Réservation + débit auto no-show +│ ├── MachineCommandService.php # Abstraction commandes LLDP +│ ├── NotificationService.php # FCM + APNs +│ ├── Payment/ +│ │ ├── PaymentProviderInterface.php +│ │ ├── StripeProvider.php +│ │ └── LydiProvider.php # Exemple partenaire alternatif +│ └── PricingService.php # Calcul tarif dynamique +├── Jobs/ +│ ├── SendPushNotificationJob.php +│ ├── DebitNoShowBookingJob.php +│ └── SyncMachineStatusJob.php +├── Events/ & Listeners/ +├── Policies/ # Autorisation par modèle +└── Console/Commands/ # Artisan custom +``` + +### 3.2 Guards d'authentification + +``` +sanctum guards : +├── user → token scope [user] → app mobile +├── supervisor → token scope [supervisor] → back-office +└── machine → token scope [machine] → automates (optionnel) +``` + +### 3.3 Flux de données principal + +``` +App Flutter → HTTPS → API Laravel → MySQL + ↓ + Redis Queue → Job Workers + ↓ + FCM / APNs / LLDP Gateway +``` + +--- + +## 4. Base de Données — Schéma Complet + +### 4.1 Table `users` + +```sql +CREATE TABLE users ( + id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, + uuid CHAR(36) UNIQUE NOT NULL, -- Identifiant public exposé dans l'API + first_name VARCHAR(100) NOT NULL, + last_name VARCHAR(100) NOT NULL, + email VARCHAR(255) UNIQUE NOT NULL, + phone VARCHAR(20) UNIQUE, + birthdate DATE, + email_verified_at TIMESTAMP NULL, + password VARCHAR(255) NOT NULL, + wallet_balance DECIMAL(10,2) NOT NULL DEFAULT 0.00, + referral_code VARCHAR(20) UNIQUE, + referred_by BIGINT UNSIGNED NULL REFERENCES users(id), + loyalty_points INT UNSIGNED NOT NULL DEFAULT 0, + fcm_token VARCHAR(255) NULL, -- Token push Android/Web + apns_token VARCHAR(255) NULL, -- Token push iOS + locale VARCHAR(10) DEFAULT 'fr', + is_active BOOLEAN DEFAULT TRUE, + anonymized_at TIMESTAMP NULL, -- RGPD : suppression logique + created_at TIMESTAMP, + updated_at TIMESTAMP, + deleted_at TIMESTAMP NULL -- Soft delete +); +``` + +### 4.2 Table `gdpr_consents` + +```sql +CREATE TABLE gdpr_consents ( + id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, + user_id BIGINT UNSIGNED NOT NULL REFERENCES users(id), + type ENUM('data_processing','marketing','analytics') NOT NULL, + accepted BOOLEAN NOT NULL, + ip_address VARCHAR(45), + user_agent TEXT, + accepted_at TIMESTAMP NOT NULL, + revoked_at TIMESTAMP NULL +); +``` + +### 4.3 Table `supervisors` + +```sql +CREATE TABLE supervisors ( + id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, + establishment_id BIGINT UNSIGNED NOT NULL REFERENCES establishments(id), + name VARCHAR(200) NOT NULL, + email VARCHAR(255) UNIQUE NOT NULL, + password VARCHAR(255) NOT NULL, + role ENUM('admin','manager','viewer') DEFAULT 'manager', + is_active BOOLEAN DEFAULT TRUE, + created_at TIMESTAMP, + updated_at TIMESTAMP +); +``` + +### 4.4 Table `establishments` + +```sql +CREATE TABLE establishments ( + id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, + uuid CHAR(36) UNIQUE NOT NULL, + name VARCHAR(200) NOT NULL, + address TEXT NOT NULL, + city VARCHAR(100), + zip_code VARCHAR(10), + latitude DECIMAL(10,8), + longitude DECIMAL(11,8), + timezone VARCHAR(50) DEFAULT 'Europe/Paris', + is_active BOOLEAN DEFAULT TRUE, + created_at TIMESTAMP, + updated_at TIMESTAMP +); +``` + +### 4.5 Table `machines` + +```sql +CREATE TABLE machines ( + id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, + uuid CHAR(36) UNIQUE NOT NULL, + establishment_id BIGINT UNSIGNED NOT NULL REFERENCES establishments(id), + name VARCHAR(100) NOT NULL, + type ENUM('washer_small','washer_large','dryer_small','dryer_large') NOT NULL, + qr_code VARCHAR(255) UNIQUE NOT NULL, + lldp_device_id VARCHAR(100), -- Identifiant côté passerelle LLDP + status ENUM('available','running','reserved','maintenance','offline') DEFAULT 'available', + current_user_id BIGINT UNSIGNED NULL REFERENCES users(id), + cycle_started_at TIMESTAMP NULL, + cycle_ends_at TIMESTAMP NULL, + last_heartbeat TIMESTAMP NULL, -- Dernier ping de l'automate + created_at TIMESTAMP, + updated_at TIMESTAMP +); +``` + +### 4.6 Table `pricing_rules` + +```sql +CREATE TABLE pricing_rules ( + id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, + machine_id BIGINT UNSIGNED NOT NULL REFERENCES machines(id), + day_type ENUM('weekday','weekend','holiday','all') DEFAULT 'all', + slot_start TIME NOT NULL, + slot_end TIME NOT NULL, + price DECIMAL(6,2) NOT NULL, + label VARCHAR(100), -- Ex: "Heure creuse", "Heure pleine" + requires_app BOOLEAN DEFAULT FALSE, -- Tarif exclusif app + created_at TIMESTAMP, + updated_at TIMESTAMP +); +``` + +### 4.7 Table `promotions` + +```sql +CREATE TABLE promotions ( + id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, + establishment_id BIGINT UNSIGNED NOT NULL REFERENCES establishments(id), + machine_type ENUM('washer_small','washer_large','dryer_small','dryer_large','all') DEFAULT 'all', + discount_type ENUM('percent','fixed') NOT NULL, + discount_value DECIMAL(6,2) NOT NULL, + starts_at TIMESTAMP NOT NULL, + ends_at TIMESTAMP NOT NULL, + description TEXT, + is_active BOOLEAN DEFAULT TRUE, + created_at TIMESTAMP, + updated_at TIMESTAMP +); +``` + +### 4.8 Table `bookings` + +```sql +CREATE TABLE bookings ( + id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, + uuid CHAR(36) UNIQUE NOT NULL, + user_id BIGINT UNSIGNED NOT NULL REFERENCES users(id), + machine_id BIGINT UNSIGNED NOT NULL REFERENCES machines(id), + slot_start TIMESTAMP NOT NULL, + slot_end TIMESTAMP NOT NULL, + amount_reserved DECIMAL(6,2) NOT NULL, -- Montant débité au moment de la réservation + amount_penalty DECIMAL(6,2) DEFAULT 0.00, -- Pénalité no-show + status ENUM('pending','confirmed','active','completed','cancelled','no_show') DEFAULT 'pending', + cancelled_at TIMESTAMP NULL, + penalty_applied_at TIMESTAMP NULL, + created_at TIMESTAMP, + updated_at TIMESTAMP +); +``` + +### 4.9 Table `washes` + +```sql +CREATE TABLE washes ( + id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, + uuid CHAR(36) UNIQUE NOT NULL, + user_id BIGINT UNSIGNED NOT NULL REFERENCES users(id), + machine_id BIGINT UNSIGNED NOT NULL REFERENCES machines(id), + booking_id BIGINT UNSIGNED NULL REFERENCES bookings(id), + trigger_method ENUM('qr_code','booking','terminal') NOT NULL, + started_at TIMESTAMP NOT NULL, + ended_at TIMESTAMP NULL, + duration_minutes INT UNSIGNED, + cost DECIMAL(6,2) NOT NULL, + pricing_rule_id BIGINT UNSIGNED NULL REFERENCES pricing_rules(id), + created_at TIMESTAMP, + updated_at TIMESTAMP +); +``` + +### 4.10 Table `wallet_transactions` + +```sql +CREATE TABLE wallet_transactions ( + id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, + uuid CHAR(36) UNIQUE NOT NULL, + user_id BIGINT UNSIGNED NOT NULL REFERENCES users(id), + type ENUM('credit','debit') NOT NULL, + amount DECIMAL(10,2) NOT NULL, + balance_after DECIMAL(10,2) NOT NULL, + source ENUM('top_up','wash','booking','penalty','refund','referral_bonus') NOT NULL, + reference_id BIGINT UNSIGNED NULL, -- ID du wash ou booking associé + reference_type VARCHAR(100) NULL, -- Morph polymorphique + payment_provider VARCHAR(50) NULL, + payment_ref VARCHAR(255) NULL, -- Référence transaction externe + created_at TIMESTAMP +); +``` + +### 4.11 Table `push_notifications` + +```sql +CREATE TABLE push_notifications ( + id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, + user_id BIGINT UNSIGNED NULL REFERENCES users(id), -- NULL = broadcast + type VARCHAR(100) NOT NULL, + title VARCHAR(255) NOT NULL, + body TEXT NOT NULL, + data JSON NULL, + channel ENUM('fcm','apns','both') DEFAULT 'both', + status ENUM('pending','sent','failed') DEFAULT 'pending', + sent_at TIMESTAMP NULL, + created_at TIMESTAMP +); +``` + +--- + +## 5. Endpoints API — Spécification Complète + +### 5.1 Authentification utilisateur + +| Méthode | Endpoint | Description | Auth | +|---------|----------|-------------|------| +| POST | `/api/v1/auth/register` | Inscription + collecte consentements RGPD | Public | +| POST | `/api/v1/auth/login` | Connexion, retourne access_token + refresh_token | Public | +| POST | `/api/v1/auth/refresh` | Renouvelle le token | Public | +| POST | `/api/v1/auth/logout` | Révoque le token | User | +| POST | `/api/v1/auth/forgot-password` | Envoi email reset | Public | +| POST | `/api/v1/auth/reset-password` | Réinitialisation | Public | +| GET | `/api/v1/auth/verify-email/{token}` | Vérification email | Public | + +**Payload register :** +```json +{ + "first_name": "Jean", + "last_name": "Dupont", + "email": "jean@example.com", + "phone": "+33612345678", + "password": "••••••••", + "birthdate": "1990-01-15", + "referral_code": "ABC123", + "consents": { + "data_processing": true, + "marketing": false, + "analytics": true + } +} +``` + +**Réponse login :** +```json +{ + "access_token": "eyJ...", + "token_type": "Bearer", + "expires_in": 3600, + "refresh_token": "def50200...", + "user": { + "uuid": "550e8400-e29b-41d4-a716-446655440000", + "first_name": "Jean", + "wallet_balance": "12.50" + } +} +``` + +### 5.2 Porte-monnaie + +| Méthode | Endpoint | Description | Auth | +|---------|----------|-------------|------| +| GET | `/api/v1/wallet` | Solde actuel | User | +| GET | `/api/v1/wallet/transactions` | Historique paginé | User | +| POST | `/api/v1/wallet/top-up/initiate` | Initier rechargement | User | +| POST | `/api/v1/wallet/top-up/confirm` | Confirmer après paiement | User | +| GET | `/api/v1/wallet/top-up/providers` | Liste partenaires paiement disponibles | User | + +**Payload top-up initiate :** +```json +{ + "amount": 20.00, + "provider": "stripe", + "return_url": "laverie://wallet/topup-result" +} +``` + +**Réponse :** +```json +{ + "payment_intent_id": "pi_xxx", + "redirect_url": "https://checkout.stripe.com/...", + "expires_at": "2026-06-27T15:30:00Z" +} +``` + +### 5.3 Établissements & Machines + +| Méthode | Endpoint | Description | Auth | +|---------|----------|-------------|------| +| GET | `/api/v1/establishments` | Liste avec coordonnées GPS | User | +| GET | `/api/v1/establishments/{uuid}` | Détail + machines + disponibilités | User | +| GET | `/api/v1/machines/{uuid}` | Détail machine + tarif actuel | User | +| GET | `/api/v1/machines/{uuid}/availability` | Créneaux disponibles sur N jours | User | +| GET | `/api/v1/machines/{uuid}/pricing` | Grille tarifaire active | User | +| POST | `/api/v1/machines/{uuid}/heartbeat` | Ping de l'automate (machine scope) | Machine | + +### 5.4 Réservations + +| Méthode | Endpoint | Description | Auth | +|---------|----------|-------------|------| +| POST | `/api/v1/bookings` | Créer une réservation | User | +| GET | `/api/v1/bookings` | Mes réservations | User | +| GET | `/api/v1/bookings/{uuid}` | Détail réservation | User | +| PATCH | `/api/v1/bookings/{uuid}/cancel` | Annuler | User | +| PATCH | `/api/v1/bookings/{uuid}/move` | Déplacer le créneau | User | + +**Payload POST /bookings :** +```json +{ + "machine_uuid": "550e8400-...", + "slot_start": "2026-06-28T09:00:00Z", + "slot_end": "2026-06-28T10:00:00Z" +} +``` + +**Règles métier :** +- Vérification atomique du solde (mutex Redis sur `wallet:user:{id}`) +- Débit immédiat du montant + supplément réservation (1€) +- Si annulation > 2h avant : remboursement intégral +- Si annulation < 2h ou no-show : pénalité définie par `machine.no_show_penalty` +- Cron toutes les 5 minutes : détection no-show → `DebitNoShowBookingJob` + +### 5.5 Lavages + +| Méthode | Endpoint | Description | Auth | +|---------|----------|-------------|------| +| POST | `/api/v1/washes/start` | Démarrer via QR code | User | +| GET | `/api/v1/washes/{uuid}` | Statut en cours | User | +| GET | `/api/v1/washes` | Historique | User | + +**Payload POST /washes/start :** +```json +{ + "qr_code": "MACHINE_QR_ABC123", + "program": "60C_standard" +} +``` + +**Flux :** +1. Valider le QR → identifier la machine +2. Vérifier statut machine (`available`) +3. Vérifier solde suffisant +4. Calculer prix (tarif actuel + promo éventuelle) +5. Débit atomique du porte-monnaie +6. Envoyer commande à la passerelle LLDP via `MachineCommandService` +7. Mettre à jour le statut machine (`running`) +8. Retourner confirmation + durée estimée + +### 5.6 Authentification superviseur + +| Méthode | Endpoint | Description | Auth | +|---------|----------|-------------|------| +| POST | `/api/v1/supervisor/auth/login` | Connexion superviseur | Public | +| POST | `/api/v1/supervisor/auth/logout` | Déconnexion | Supervisor | +| GET | `/api/v1/supervisor/dashboard` | Statistiques + alertes | Supervisor | +| GET | `/api/v1/supervisor/machines` | Parc machines de l'établissement | Supervisor | +| PATCH | `/api/v1/supervisor/machines/{uuid}` | Mise à jour machine | Supervisor | +| GET | `/api/v1/supervisor/pricing` | Grille tarifaire | Supervisor | +| POST | `/api/v1/supervisor/pricing` | Créer règle tarifaire | Supervisor | +| PUT | `/api/v1/supervisor/pricing/{id}` | Modifier règle | Supervisor | +| DELETE | `/api/v1/supervisor/pricing/{id}` | Supprimer règle | Supervisor | +| GET | `/api/v1/supervisor/promotions` | Promotions actives | Supervisor | +| POST | `/api/v1/supervisor/promotions` | Créer promotion | Supervisor | + +--- + +## 6. Services Métier Critiques + +### 6.1 WalletService — Opérations atomiques + +Toute opération sur le porte-monnaie passe par ce service. Les débits/crédits sont encapsulés dans des transactions MySQL + verrou Redis pour éviter les race conditions. + +``` +WalletService::credit(user, amount, source, referenceId) +WalletService::debit(user, amount, source, referenceId) ← lance WalletInsufficientFundsException si insuffisant +WalletService::reserve(user, amount, bookingId) ← débit préventif réservation +WalletService::refund(user, walletTransactionId) +``` + +**Règle absolue :** Aucun controller ne doit modifier `users.wallet_balance` directement. Tout passe par `WalletService`. + +### 6.2 PricingService — Tarif dynamique + +``` +PricingService::getCurrentPrice(machine, datetime) + → Cherche la PricingRule active pour (machine_id, day_type, heure) + → Applique Promotion active si existante + → Retourne { base_price, discount, final_price, label, requires_app } +``` + +**Priorité des règles :** Règle spécifique machine > Règle par type machine > Tarif défaut établissement. + +### 6.3 MachineCommandService — Abstraction LLDP + +``` +MachineCommandService::startWash(machine, program) +MachineCommandService::stopWash(machine) +MachineCommandService::getStatus(machine) +``` + +L'implémentation concrète (`LldpGatewayAdapter`) communique avec la passerelle LLDP via HTTP interne. En cas d'indisponibilité de la passerelle, la commande est mise en queue Redis et retentée 3 fois (backoff exponentiel). + +### 6.4 PaymentProviderInterface + +```php +interface PaymentProviderInterface { + public function initiateTopUp(User $user, float $amount, string $returnUrl): TopUpIntent; + public function confirmTopUp(string $paymentRef): TopUpConfirmation; + public function handleWebhook(Request $request): void; +} +``` + +Implémentations : `StripeProvider`, `LydiProvider`, `SumUpProvider` (extensible). + +--- + +## 7. Tâches Planifiées (Cron) + +| Fréquence | Job | Description | +|-----------|-----|-------------| +| Toutes les 5 min | `CheckNoShowBookings` | Détecter et débiter les no-shows | +| Toutes les 5 min | `SyncMachineStatuses` | Interroger la passerelle LLDP | +| Toutes les heures | `SendScheduledNotifications` | Notifications planifiées en attente | +| Chaque jour 02:00 | `GenerateDailyStats` | Agrégation statistiques pour dashboard | +| Chaque jour 03:00 | `CleanExpiredTokens` | Purge tokens révoqués | +| Chaque semaine | `AnonymizeInactiveUsers` | RGPD : users inactifs > 3 ans | + +--- + +## 8. Notifications — Architecture + +### 8.1 Déclencheurs +| Événement | Canal | Timing | +|-----------|-------|--------| +| Rechargement confirmé | Push | Immédiat | +| Réservation confirmée | Push | Immédiat | +| Rappel créneau | Push | 30 min avant | +| Lavage terminé | Push | Immédiat | +| Solde faible (< 5€) | Push | À chaque débit | +| Promotion disponible | Push | Planifié par superviseur | +| No-show détecté | Push | À la détection | + +### 8.2 Implémentation +- Les notifications sont toujours créées en base (`push_notifications`) avant envoi +- L'envoi passe par un Job Redis (`SendPushNotificationJob`) → résilience +- FCM HTTP v1 pour Android/Web, APNs HTTP/2 pour iOS +- En cas d'échec, 3 retries avec backoff (1min, 5min, 15min) + +--- + +## 9. Sécurité + +### 9.1 Authentification +- Tokens JWT via Laravel Sanctum (stateless) +- Refresh token rotation (invalidation à chaque renouvellement) +- Rate limiting : 5 tentatives de login / 10 minutes / IP +- Rate limiting paiements : 3 initiations / minute / user + +### 9.2 Données +- Mots de passe hashés : `bcrypt` (cost 12) +- Données sensibles (IBAN, tokens paiement) : chiffrées `AES-256-CBC` en base +- HTTPS obligatoire (TLS 1.3 minimum) +- UUID exposés dans l'API (jamais les auto-increment IDs) +- Soft deletes sur `users` — anonymisation RGPD séparée + +### 9.3 API +- Validation stricte de tous les inputs (`FormRequest`) +- Politique CORS restrictive (origines whitelist) +- Headers sécurité : `Strict-Transport-Security`, `X-Frame-Options`, `Content-Security-Policy` + +--- + +## 10. Tests + +| Type | Outil | Couverture cible | +|------|-------|-----------------| +| Tests unitaires | Pest | WalletService, PricingService, BookingService | +| Tests d'intégration | Pest + SQLite in-memory | Tous les endpoints API | +| Tests de contrats | — | Webhooks paiement | +| Tests de charge | k6 | Endpoints critiques (start wash, top-up) | + +**Cas critiques à tester :** +- Race condition débit simultané du porte-monnaie +- Timeout passerelle LLDP → comportement de la queue +- Webhook paiement reçu en doublon (idempotence) +- No-show avec annulation simultanée + +--- + +## 11. Variables d'environnement requises + +```env +APP_ENV=production +APP_KEY=base64:... + +DB_HOST= +DB_DATABASE=laverie +DB_USERNAME= +DB_PASSWORD= + +REDIS_HOST= +REDIS_PASSWORD= + +# Paiement +STRIPE_SECRET_KEY= +STRIPE_WEBHOOK_SECRET= + +# Notifications +FIREBASE_PROJECT_ID= +FIREBASE_PRIVATE_KEY= +FIREBASE_CLIENT_EMAIL= +APNS_KEY_ID= +APNS_TEAM_ID= +APNS_PRIVATE_KEY_PATH= + +# LLDP Gateway +LLDP_GATEWAY_URL= +LLDP_GATEWAY_TOKEN= + +# URLs +FRONTEND_URL= +APP_URL= +``` + +--- + +## 12. Livrables attendus + +- [ ] Code source Laravel complet (GitHub) +- [ ] Migrations SQL versionnées +- [ ] Seeders (données de démo) +- [ ] Documentation OpenAPI 3.0 auto-générée (`/api/documentation`) +- [ ] Collection Postman exportée +- [ ] `docker-compose.yml` pour dev local +- [ ] `.env.example` documenté +- [ ] README avec instructions d'installation +- [ ] Suite de tests (couverture > 80% sur les services critiques) diff --git a/documentation/CDC_API_Backend_v2.md b/documentation/CDC_API_Backend_v2.md new file mode 100644 index 0000000..d859c1f --- /dev/null +++ b/documentation/CDC_API_Backend_v2.md @@ -0,0 +1,843 @@ +# Cahier des Charges — API & Base de Données +## Projet : Laverie Connectée — Backend +**Version :** 2.0 +**Stack principale :** Laravel 11, MySQL 8, Redis, Docker +**Positionnement :** V1 démontrable, extensible vers V2 sans refonte majeure + +--- + +## 1. Contexte & Objectifs + +### 1.1 Contexte +Développement d'une API centrale servant : +- une application utilisateur multi-plateforme, +- un back-office exploitant, +- une couche d'intégration avec les systèmes machines fournis par le client ou ses partenaires techniques. + +L'API est le **point d'entrée unique de la logique métier**. Les clients applicatifs ne communiquent jamais directement avec la base de données. + +### 1.2 Objectifs V1 +- authentifier les utilisateurs finaux et les exploitants, +- gérer plusieurs laveries et plusieurs exploitants dans une même plateforme, +- exposer les données établissements / machines / disponibilités, +- gérer un porte-monnaie électronique avec traçabilité, +- permettre la réservation d'un créneau, +- permettre le démarrage d'un lavage via intégration machine, +- historiser les événements machines, +- fournir un socle d'audit, de sécurité et de statistiques simples. + +### 1.3 Principes de conception +- **MVP strict** : seules les fonctionnalités nécessaires à la démonstration et à la première mise en service sont incluses en V1. +- **Extensibilité** : fidélité, abonnements, parrainage, campagnes marketing et analytics avancées sont prévues mais non implémentées en V1. +- **Traçabilité** : tout flux critique doit être rejouable et auditable. +- **Isolation métier** : chaque exploitant ne voit que ses établissements, ses machines et ses chiffres. +- **Intégration externe souple** : la couche machine doit supporter aussi bien un modèle où notre système appelle une API partenaire qu'un modèle où le partenaire pousse des événements vers notre API. + +--- + +## 2. Périmètre fonctionnel + +### 2.1 MVP V1 + +#### Utilisateur final +- inscription / connexion, +- consultation des laveries, +- consultation des machines et de leur statut, +- consultation du solde wallet, +- historique simple des transactions, +- rechargement du wallet, +- réservation d'un créneau, +- démarrage d'un lavage, +- historique des lavages, +- notifications transactionnelles. + +#### Exploitant +- connexion superviseur, +- consultation du parc machines, +- consultation d'un tableau de bord simple, +- consultation des réservations et lavages, +- gestion simple de la tarification, +- visualisation des alertes techniques de base. + +#### Plateforme +- multi-exploitants, +- audit logs, +- agrégats journaliers simples, +- intégration machine simulable pour démonstration. + +### 2.2 V2 prévue +- fidélité, +- parrainage, +- abonnements, +- campagnes marketing, +- promotions avancées, +- exports RGPD complets, +- analytics détaillées, +- règles tarifaires complexes, +- exports métier avancés. + +### 2.3 Hors périmètre V1 +- chat, +- avis, +- FAQ dynamique, +- recommandation de cycle par photo, +- moteur prédictif / deep learning, +- orchestration matérielle bas niveau. + +--- + +## 3. Stack Technique + +| Composant | Technologie | Version | Commentaire | +|-----------|-------------|---------|-------------| +| Framework API | Laravel | 11.x | Coeur métier principal | +| Base de données | MySQL | 8.0 | Référentiel transactionnel | +| Cache / queues | Redis | 7.x | Queues, verrous applicatifs, cache court | +| Workers | Laravel Horizon | 5.x | Supervision des jobs | +| Scheduler | Laravel Scheduler | natif | Cron applicatif | +| Documentation API | Scramble ou L5-Swagger | — | OpenAPI | +| Conteneurisation | Docker | — | Environnements reproductibles | +| CI/CD | GitHub Actions | — | Tests + build | +| Monitoring erreurs | Sentry | — | Backend | +| Logs | JSON structurés | — | Corrélation et exploitation | + +### 3.1 Authentification recommandée +- **App mobile / web utilisateur** : access token court + refresh token. +- **Back-office exploitant** : session cookie Laravel. +- **Intégrations machines / partenaires** : API key technique ou signature HMAC. + +Cette séparation évite de mélanger les contraintes des apps utilisateurs, du back-office et des systèmes techniques. + +--- + +## 4. Architecture logique + +### 4.1 Domaines métier principaux +- Authentification +- Organisations / exploitants +- Établissements +- Machines +- Tarification +- Wallet & paiements +- Réservations +- Lavages +- Notifications +- Intégrations machines +- Audit & statistiques + +### 4.2 Multi-tenant logique +Le système n'est pas multi-base ni multi-schémas. L'isolation est assurée au niveau applicatif par : +- `organization_id`, +- policies Laravel, +- query scopes explicites, +- ressources d'API filtrées selon l'utilisateur connecté. + +### 4.3 Structure recommandée Laravel + +``` +app/ +├── Domain/ +│ ├── Auth/ +│ ├── Organization/ +│ ├── Machine/ +│ ├── Wallet/ +│ ├── Booking/ +│ ├── Wash/ +│ ├── Pricing/ +│ ├── Notification/ +│ └── Integration/ +├── Http/ +│ ├── Controllers/ +│ ├── Middleware/ +│ └── Requests/ +├── Jobs/ +├── Events/ +├── Listeners/ +├── Policies/ +├── Models/ +└── Support/ +``` + +--- + +## 5. Base de Données — Schéma révisé + +## 5.0 Connexion bdd de test + +hostname: localhost +port: 3306 +user: root +password: +shema: laverie + +## 5.1 Exploitants et périmètre + +### Table `organizations` +```sql +CREATE TABLE organizations ( + id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, + uuid CHAR(36) UNIQUE NOT NULL, + name VARCHAR(200) NOT NULL, + code VARCHAR(50) UNIQUE NULL, + is_active BOOLEAN NOT NULL DEFAULT TRUE, + created_at TIMESTAMP NULL, + updated_at TIMESTAMP NULL +); +``` + +### Table `establishments` +```sql +CREATE TABLE establishments ( + id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, + organization_id BIGINT UNSIGNED NOT NULL, + uuid CHAR(36) UNIQUE NOT NULL, + name VARCHAR(200) NOT NULL, + address TEXT NOT NULL, + city VARCHAR(100) NULL, + zip_code VARCHAR(10) NULL, + latitude DECIMAL(10,8) NULL, + longitude DECIMAL(11,8) NULL, + timezone VARCHAR(50) NOT NULL DEFAULT 'Europe/Paris', + is_active BOOLEAN NOT NULL DEFAULT TRUE, + created_at TIMESTAMP NULL, + updated_at TIMESTAMP NULL, + CONSTRAINT fk_establishments_organization FOREIGN KEY (organization_id) REFERENCES organizations(id) +); +``` + +### Table `supervisors` +```sql +CREATE TABLE supervisors ( + id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, + organization_id BIGINT UNSIGNED NOT NULL, + establishment_id BIGINT UNSIGNED NULL, + uuid CHAR(36) UNIQUE NOT NULL, + first_name VARCHAR(100) NOT NULL, + last_name VARCHAR(100) NOT NULL, + email VARCHAR(255) UNIQUE NOT NULL, + password VARCHAR(255) NOT NULL, + role ENUM('platform_admin','owner','manager','viewer') NOT NULL DEFAULT 'manager', + is_active BOOLEAN NOT NULL DEFAULT TRUE, + last_login_at TIMESTAMP NULL, + created_at TIMESTAMP NULL, + updated_at TIMESTAMP NULL, + CONSTRAINT fk_supervisors_organization FOREIGN KEY (organization_id) REFERENCES organizations(id), + CONSTRAINT fk_supervisors_establishment FOREIGN KEY (establishment_id) REFERENCES establishments(id) +); +``` + +## 5.2 Utilisateurs finaux + +### Table `users` +```sql +CREATE TABLE users ( + id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, + uuid CHAR(36) UNIQUE NOT NULL, + first_name VARCHAR(100) NOT NULL, + last_name VARCHAR(100) NOT NULL, + email VARCHAR(255) UNIQUE NOT NULL, + phone VARCHAR(20) UNIQUE NULL, + birthdate DATE NULL, + email_verified_at TIMESTAMP NULL, + password VARCHAR(255) NOT NULL, + locale VARCHAR(10) NOT NULL DEFAULT 'fr', + is_active BOOLEAN NOT NULL DEFAULT TRUE, + anonymized_at TIMESTAMP NULL, + created_at TIMESTAMP NULL, + updated_at TIMESTAMP NULL, + deleted_at TIMESTAMP NULL +); +``` + +### Table `gdpr_consents` +```sql +CREATE TABLE gdpr_consents ( + id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, + user_id BIGINT UNSIGNED NOT NULL, + type ENUM('data_processing','marketing','analytics') NOT NULL, + accepted BOOLEAN NOT NULL, + policy_version VARCHAR(50) NOT NULL, + policy_text_hash VARCHAR(255) NOT NULL, + source ENUM('mobile','web','backoffice') NOT NULL, + consent_language VARCHAR(10) NOT NULL DEFAULT 'fr', + ip_address VARCHAR(45) NULL, + user_agent TEXT NULL, + accepted_at TIMESTAMP NOT NULL, + revoked_at TIMESTAMP NULL, + CONSTRAINT fk_gdpr_consents_user FOREIGN KEY (user_id) REFERENCES users(id) +); +``` + +### Table `user_devices` +```sql +CREATE TABLE user_devices ( + id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, + user_id BIGINT UNSIGNED NOT NULL, + platform ENUM('android','ios','web') NOT NULL, + push_token VARCHAR(255) NOT NULL, + app_version VARCHAR(50) NULL, + device_name VARCHAR(100) NULL, + last_seen_at TIMESTAMP NULL, + revoked_at TIMESTAMP NULL, + created_at TIMESTAMP NULL, + updated_at TIMESTAMP NULL, + UNIQUE KEY uniq_user_device_token (push_token), + CONSTRAINT fk_user_devices_user FOREIGN KEY (user_id) REFERENCES users(id) +); +``` + +### Table `notification_preferences` +```sql +CREATE TABLE notification_preferences ( + id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, + user_id BIGINT UNSIGNED NOT NULL, + transaction_enabled BOOLEAN NOT NULL DEFAULT TRUE, + reminder_enabled BOOLEAN NOT NULL DEFAULT TRUE, + marketing_enabled BOOLEAN NOT NULL DEFAULT FALSE, + system_enabled BOOLEAN NOT NULL DEFAULT TRUE, + created_at TIMESTAMP NULL, + updated_at TIMESTAMP NULL, + UNIQUE KEY uniq_notification_preferences_user (user_id), + CONSTRAINT fk_notification_preferences_user FOREIGN KEY (user_id) REFERENCES users(id) +); +``` + +## 5.3 Machines et intégrations + +### Table `machines` +```sql +CREATE TABLE machines ( + id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, + establishment_id BIGINT UNSIGNED NOT NULL, + uuid CHAR(36) UNIQUE NOT NULL, + name VARCHAR(100) NOT NULL, + type ENUM('washer_small','washer_large','dryer_small','dryer_large') NOT NULL, + qr_code VARCHAR(255) UNIQUE NOT NULL, + status ENUM('available','reserved','running','maintenance','offline','error') NOT NULL DEFAULT 'available', + current_user_id BIGINT UNSIGNED NULL, + cycle_started_at TIMESTAMP NULL, + cycle_ends_at TIMESTAMP NULL, + last_heartbeat_at TIMESTAMP NULL, + created_at TIMESTAMP NULL, + updated_at TIMESTAMP NULL, + CONSTRAINT fk_machines_establishment FOREIGN KEY (establishment_id) REFERENCES establishments(id), + CONSTRAINT fk_machines_current_user FOREIGN KEY (current_user_id) REFERENCES users(id) +); +``` + +### Table `machine_integrations` +```sql +CREATE TABLE machine_integrations ( + id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, + machine_id BIGINT UNSIGNED NOT NULL, + provider VARCHAR(100) NOT NULL, + external_machine_id VARCHAR(100) NOT NULL, + external_site_id VARCHAR(100) NULL, + mode ENUM('pull','push','hybrid','simulated') NOT NULL DEFAULT 'simulated', + config JSON NULL, + is_active BOOLEAN NOT NULL DEFAULT TRUE, + created_at TIMESTAMP NULL, + updated_at TIMESTAMP NULL, + UNIQUE KEY uniq_machine_provider_external (provider, external_machine_id), + CONSTRAINT fk_machine_integrations_machine FOREIGN KEY (machine_id) REFERENCES machines(id) +); +``` + +### Table `machine_commands` +```sql +CREATE TABLE machine_commands ( + id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, + uuid CHAR(36) UNIQUE NOT NULL, + machine_id BIGINT UNSIGNED NOT NULL, + provider VARCHAR(100) NOT NULL, + command_type ENUM('start_cycle','stop_cycle','refresh_status') NOT NULL, + payload JSON NULL, + status ENUM('pending','sent','acknowledged','failed','timeout','cancelled') NOT NULL DEFAULT 'pending', + external_reference VARCHAR(255) NULL, + correlation_id VARCHAR(255) NULL, + requested_by_user_id BIGINT UNSIGNED NULL, + requested_by_supervisor_id BIGINT UNSIGNED NULL, + sent_at TIMESTAMP NULL, + responded_at TIMESTAMP NULL, + created_at TIMESTAMP NULL, + updated_at TIMESTAMP NULL, + CONSTRAINT fk_machine_commands_machine FOREIGN KEY (machine_id) REFERENCES machines(id), + CONSTRAINT fk_machine_commands_user FOREIGN KEY (requested_by_user_id) REFERENCES users(id), + CONSTRAINT fk_machine_commands_supervisor FOREIGN KEY (requested_by_supervisor_id) REFERENCES supervisors(id) +); +``` + +### Table `machine_events` +```sql +CREATE TABLE machine_events ( + id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, + machine_id BIGINT UNSIGNED NOT NULL, + provider VARCHAR(100) NOT NULL, + external_event_id VARCHAR(255) NULL, + event_type ENUM('heartbeat','machine_online','machine_offline','cycle_started','cycle_completed','cycle_failed','error_reported','status_changed') NOT NULL, + payload JSON NULL, + occurred_at TIMESTAMP NOT NULL, + received_at TIMESTAMP NOT NULL, + processed_at TIMESTAMP NULL, + processing_status ENUM('pending','processed','failed','ignored') NOT NULL DEFAULT 'pending', + created_at TIMESTAMP NULL, + updated_at TIMESTAMP NULL, + CONSTRAINT fk_machine_events_machine FOREIGN KEY (machine_id) REFERENCES machines(id) +); +``` + +### Table `machine_status_history` +```sql +CREATE TABLE machine_status_history ( + id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, + machine_id BIGINT UNSIGNED NOT NULL, + previous_status VARCHAR(50) NULL, + new_status VARCHAR(50) NOT NULL, + source VARCHAR(100) NOT NULL, + reason VARCHAR(255) NULL, + created_at TIMESTAMP NULL, + CONSTRAINT fk_machine_status_history_machine FOREIGN KEY (machine_id) REFERENCES machines(id) +); +``` + +## 5.4 Tarification + +### Table `pricing_rules` +```sql +CREATE TABLE pricing_rules ( + id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, + establishment_id BIGINT UNSIGNED NOT NULL, + machine_id BIGINT UNSIGNED NULL, + machine_type ENUM('washer_small','washer_large','dryer_small','dryer_large') NULL, + day_type ENUM('weekday','weekend','holiday','all') NOT NULL DEFAULT 'all', + slot_start TIME NOT NULL, + slot_end TIME NOT NULL, + price DECIMAL(8,2) NOT NULL, + label VARCHAR(100) NULL, + requires_app BOOLEAN NOT NULL DEFAULT FALSE, + priority SMALLINT NOT NULL DEFAULT 100, + is_active BOOLEAN NOT NULL DEFAULT TRUE, + created_at TIMESTAMP NULL, + updated_at TIMESTAMP NULL, + CONSTRAINT fk_pricing_rules_establishment FOREIGN KEY (establishment_id) REFERENCES establishments(id), + CONSTRAINT fk_pricing_rules_machine FOREIGN KEY (machine_id) REFERENCES machines(id) +); +``` + +### Table `promotions` +```sql +CREATE TABLE promotions ( + id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, + establishment_id BIGINT UNSIGNED NOT NULL, + machine_type ENUM('washer_small','washer_large','dryer_small','dryer_large','all') NOT NULL DEFAULT 'all', + discount_type ENUM('percent','fixed') NOT NULL, + discount_value DECIMAL(8,2) NOT NULL, + starts_at TIMESTAMP NOT NULL, + ends_at TIMESTAMP NOT NULL, + description TEXT NULL, + is_active BOOLEAN NOT NULL DEFAULT TRUE, + created_at TIMESTAMP NULL, + updated_at TIMESTAMP NULL, + CONSTRAINT fk_promotions_establishment FOREIGN KEY (establishment_id) REFERENCES establishments(id) +); +``` + +## 5.5 Wallet et paiements + +### Table `wallets` +```sql +CREATE TABLE wallets ( + id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, + user_id BIGINT UNSIGNED NOT NULL, + currency CHAR(3) NOT NULL DEFAULT 'EUR', + current_balance DECIMAL(10,2) NOT NULL DEFAULT 0.00, + status ENUM('active','blocked','closed') NOT NULL DEFAULT 'active', + created_at TIMESTAMP NULL, + updated_at TIMESTAMP NULL, + UNIQUE KEY uniq_wallets_user (user_id), + CONSTRAINT fk_wallets_user FOREIGN KEY (user_id) REFERENCES users(id) +); +``` + +### Table `payment_transactions` +```sql +CREATE TABLE payment_transactions ( + id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, + uuid CHAR(36) UNIQUE NOT NULL, + user_id BIGINT UNSIGNED NOT NULL, + provider VARCHAR(50) NOT NULL, + provider_payment_id VARCHAR(255) NULL, + amount DECIMAL(10,2) NOT NULL, + currency CHAR(3) NOT NULL DEFAULT 'EUR', + status ENUM('initiated','pending','succeeded','failed','cancelled','refunded') NOT NULL DEFAULT 'initiated', + idempotency_key VARCHAR(100) NOT NULL, + return_url VARCHAR(255) NULL, + raw_payload JSON NULL, + created_at TIMESTAMP NULL, + updated_at TIMESTAMP NULL, + UNIQUE KEY uniq_payment_idempotency (idempotency_key), + CONSTRAINT fk_payment_transactions_user FOREIGN KEY (user_id) REFERENCES users(id) +); +``` + +### Table `wallet_transactions` +```sql +CREATE TABLE wallet_transactions ( + id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, + uuid CHAR(36) UNIQUE NOT NULL, + wallet_id BIGINT UNSIGNED NOT NULL, + type ENUM('credit','debit','hold','release','refund','adjustment') NOT NULL, + amount DECIMAL(10,2) NOT NULL, + balance_before DECIMAL(10,2) NOT NULL, + balance_after DECIMAL(10,2) NOT NULL, + source_type VARCHAR(100) NOT NULL, + source_id BIGINT UNSIGNED NULL, + idempotency_key VARCHAR(100) NULL, + metadata JSON NULL, + created_at TIMESTAMP NULL, + CONSTRAINT fk_wallet_transactions_wallet FOREIGN KEY (wallet_id) REFERENCES wallets(id) +); +``` + +## 5.6 Réservations et lavages + +### Table `bookings` +```sql +CREATE TABLE bookings ( + id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, + uuid CHAR(36) UNIQUE NOT NULL, + user_id BIGINT UNSIGNED NOT NULL, + machine_id BIGINT UNSIGNED NOT NULL, + slot_start TIMESTAMP NOT NULL, + slot_end TIMESTAMP NOT NULL, + booking_fee DECIMAL(8,2) NOT NULL DEFAULT 0.00, + reserved_amount DECIMAL(8,2) NOT NULL DEFAULT 0.00, + penalty_amount DECIMAL(8,2) NOT NULL DEFAULT 0.00, + status ENUM('pending','confirmed','cancelled','expired','active','completed','no_show') NOT NULL DEFAULT 'pending', + cancelled_at TIMESTAMP NULL, + penalty_applied_at TIMESTAMP NULL, + created_at TIMESTAMP NULL, + updated_at TIMESTAMP NULL, + CONSTRAINT fk_bookings_user FOREIGN KEY (user_id) REFERENCES users(id), + CONSTRAINT fk_bookings_machine FOREIGN KEY (machine_id) REFERENCES machines(id) +); +``` + +### Table `washes` +```sql +CREATE TABLE washes ( + id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, + uuid CHAR(36) UNIQUE NOT NULL, + user_id BIGINT UNSIGNED NOT NULL, + machine_id BIGINT UNSIGNED NOT NULL, + booking_id BIGINT UNSIGNED NULL, + machine_command_id BIGINT UNSIGNED NULL, + trigger_method ENUM('qr_code','booking','supervisor','system') NOT NULL, + status ENUM('pending_start','running','completed','failed','cancelled') NOT NULL DEFAULT 'pending_start', + program VARCHAR(100) NULL, + started_at TIMESTAMP NULL, + ended_at TIMESTAMP NULL, + duration_minutes INT UNSIGNED NULL, + cost DECIMAL(8,2) NOT NULL, + created_at TIMESTAMP NULL, + updated_at TIMESTAMP NULL, + CONSTRAINT fk_washes_user FOREIGN KEY (user_id) REFERENCES users(id), + CONSTRAINT fk_washes_machine FOREIGN KEY (machine_id) REFERENCES machines(id), + CONSTRAINT fk_washes_booking FOREIGN KEY (booking_id) REFERENCES bookings(id), + CONSTRAINT fk_washes_command FOREIGN KEY (machine_command_id) REFERENCES machine_commands(id) +); +``` + +## 5.7 Notifications, audit et statistiques + +### Table `push_notifications` +```sql +CREATE TABLE push_notifications ( + id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, + user_id BIGINT UNSIGNED NULL, + type VARCHAR(100) NOT NULL, + title VARCHAR(255) NOT NULL, + body TEXT NOT NULL, + data JSON NULL, + status ENUM('pending','sent','failed') NOT NULL DEFAULT 'pending', + sent_at TIMESTAMP NULL, + created_at TIMESTAMP NULL, + updated_at TIMESTAMP NULL, + CONSTRAINT fk_push_notifications_user FOREIGN KEY (user_id) REFERENCES users(id) +); +``` + +### Table `audit_logs` +```sql +CREATE TABLE audit_logs ( + id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, + actor_type VARCHAR(50) NOT NULL, + actor_id BIGINT UNSIGNED NOT NULL, + organization_id BIGINT UNSIGNED NULL, + establishment_id BIGINT UNSIGNED NULL, + action VARCHAR(100) NOT NULL, + target_type VARCHAR(100) NOT NULL, + target_id BIGINT UNSIGNED NULL, + before_data JSON NULL, + after_data JSON NULL, + ip_address VARCHAR(45) NULL, + created_at TIMESTAMP NULL +); +``` + +### Table `daily_establishment_stats` +```sql +CREATE TABLE daily_establishment_stats ( + id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, + establishment_id BIGINT UNSIGNED NOT NULL, + stat_date DATE NOT NULL, + total_washes INT UNSIGNED NOT NULL DEFAULT 0, + total_revenue DECIMAL(10,2) NOT NULL DEFAULT 0.00, + bookings_count INT UNSIGNED NOT NULL DEFAULT 0, + no_show_count INT UNSIGNED NOT NULL DEFAULT 0, + top_up_amount DECIMAL(10,2) NOT NULL DEFAULT 0.00, + occupancy_rate DECIMAL(5,2) NOT NULL DEFAULT 0.00, + created_at TIMESTAMP NULL, + updated_at TIMESTAMP NULL, + UNIQUE KEY uniq_daily_establishment_stats (establishment_id, stat_date), + CONSTRAINT fk_daily_establishment_stats_establishment FOREIGN KEY (establishment_id) REFERENCES establishments(id) +); +``` + +--- + +## 6. Contrat d'intégration machine + +### 6.1 Principe +Le backend doit supporter deux scénarios : +- **pull** : notre système appelle une API partenaire pour envoyer une commande ou lire un statut, +- **push** : le partenaire pousse des événements vers notre système, +- **hybrid** : combinaison des deux, +- **simulated** : mode démonstration / sandbox. + +### 6.2 Abstraction applicative + +```php +interface MachineProviderInterface { + public function startCycle(Machine $machine, array $payload): MachineCommandResult; + public function stopCycle(Machine $machine, array $payload = []): MachineCommandResult; + public function refreshStatus(Machine $machine): MachineStatusSnapshot; + public function handleInboundEvent(array $payload): void; +} +``` + +### 6.3 Événements entrants attendus +- `heartbeat` +- `machine_online` +- `machine_offline` +- `cycle_started` +- `cycle_completed` +- `cycle_failed` +- `error_reported` +- `status_changed` + +### 6.4 Endpoints d'intégration proposés + +| Méthode | Endpoint | Usage | +|---------|----------|-------| +| POST | `/api/v1/integrations/machines/events` | Réception d'événements machine | +| POST | `/api/v1/integrations/machines/heartbeat` | Heartbeat passerelle / machine | +| POST | `/api/v1/integrations/machines/commands/{uuid}/ack` | Accusé de réception optionnel | + +### 6.5 Payload minimal d'événement +```json +{ + "provider": "client_gateway", + "event_type": "cycle_completed", + "occurred_at": "2026-06-27T10:15:00Z", + "machine_external_id": "MACH-001", + "correlation_id": "cmd_12345", + "data": { + "status": "available", + "duration_minutes": 42, + "error_code": null + } +} +``` + +### 6.6 Exigences minimales côté partenaire +- identifiant machine externe stable, +- horodatage fiable, +- type d'événement, +- état courant de la machine, +- identifiant de corrélation si une commande a été envoyée par notre système, +- stratégie d'authentification technique, +- documentation des erreurs métiers. + +### 6.7 Mode démonstration +Un provider simulé doit permettre : +- démarrage immédiat d'un cycle, +- passage en statut `running`, +- émission d'un faux événement `cycle_completed` après un délai configurable, +- retour en statut `available`. + +Ce mode permet une démo sans matériel réel. + +--- + +## 7. Endpoints API — V1 + +### 7.1 Auth utilisateur +| Méthode | Endpoint | Description | +|---------|----------|-------------| +| POST | `/api/v1/auth/register` | Inscription | +| POST | `/api/v1/auth/login` | Connexion utilisateur | +| POST | `/api/v1/auth/refresh` | Renouvellement token | +| POST | `/api/v1/auth/logout` | Déconnexion | +| GET | `/api/v1/auth/me` | Profil courant | + +### 7.2 Wallet +| Méthode | Endpoint | Description | +|---------|----------|-------------| +| GET | `/api/v1/wallet` | Solde | +| GET | `/api/v1/wallet/transactions` | Historique | +| POST | `/api/v1/wallet/top-up/initiate` | Démarrer un rechargement | +| POST | `/api/v1/wallet/top-up/confirm` | Confirmation de rechargement | +| POST | `/api/v1/wallet/top-up/webhook/{provider}` | Webhook PSP | + +### 7.3 Établissements & machines +| Méthode | Endpoint | Description | +|---------|----------|-------------| +| GET | `/api/v1/establishments` | Liste établissements | +| GET | `/api/v1/establishments/{uuid}` | Détail établissement | +| GET | `/api/v1/machines/{uuid}` | Détail machine | +| GET | `/api/v1/machines/{uuid}/availability` | Créneaux disponibles | +| GET | `/api/v1/machines/{uuid}/pricing` | Tarif actif | + +### 7.4 Réservations +| Méthode | Endpoint | Description | +|---------|----------|-------------| +| POST | `/api/v1/bookings` | Créer une réservation | +| GET | `/api/v1/bookings` | Mes réservations | +| GET | `/api/v1/bookings/{uuid}` | Détail réservation | +| PATCH | `/api/v1/bookings/{uuid}/cancel` | Annuler | +| PATCH | `/api/v1/bookings/{uuid}/move` | Déplacer | + +### 7.5 Lavages +| Méthode | Endpoint | Description | +|---------|----------|-------------| +| POST | `/api/v1/washes/start` | Démarrer un lavage | +| GET | `/api/v1/washes` | Historique | +| GET | `/api/v1/washes/{uuid}` | Détail | + +### 7.6 Superviseur +| Méthode | Endpoint | Description | +|---------|----------|-------------| +| POST | `/api/v1/supervisor/auth/login` | Connexion superviseur | +| POST | `/api/v1/supervisor/auth/logout` | Déconnexion | +| GET | `/api/v1/supervisor/dashboard` | Dashboard | +| GET | `/api/v1/supervisor/machines` | Parc machines | +| GET | `/api/v1/supervisor/bookings` | Réservations | +| GET | `/api/v1/supervisor/washes` | Lavages | +| GET | `/api/v1/supervisor/pricing` | Tarifs | +| POST | `/api/v1/supervisor/pricing` | Création tarif | +| PUT | `/api/v1/supervisor/pricing/{id}` | Modification tarif | +| GET | `/api/v1/supervisor/promotions` | Promotions | + +--- + +## 8. Règles métier critiques + +### 8.1 Wallet +- aucune écriture directe de solde hors `WalletService`, +- toute opération critique porte une clé d'idempotence, +- séparation stricte entre transaction de paiement externe et mouvement de wallet interne. + +### 8.2 Réservations +- un créneau machine ne peut jamais être réservé deux fois, +- création de réservation sous transaction avec verrou logique, +- pénalité et remboursement centralisés dans `BookingService`. + +### 8.3 Lavages +- un lavage ne peut démarrer que si la machine est éligible, +- toute commande machine doit être historisée, +- toute fin de cycle doit être confirmée par événement ou simulation contrôlée. + +### 8.4 Scoping exploitant +- toute requête superviseur doit être filtrée par `organization_id`, +- si `establishment_id` est renseigné sur le superviseur, la visibilité est limitée à cet établissement. + +--- + +## 9. Tâches planifiées + +| Fréquence | Job | Description | +|-----------|-----|-------------| +| Toutes les 5 min | `CheckNoShowBookings` | Détection des no-shows | +| Toutes les 5 min | `RefreshOfflineMachines` | Contrôle heartbeat / statuts | +| Toutes les heures | `SendScheduledNotifications` | Notifications en attente | +| Chaque nuit | `GenerateDailyStats` | Agrégats journaliers | +| Chaque nuit | `PurgeRevokedTokens` | Nettoyage technique | +| Chaque semaine | `AnonymizeInactiveUsers` | Politique RGPD | + +--- + +## 10. Sécurité + +### 10.1 Général +- HTTPS obligatoire, +- validation stricte des entrées, +- rate limiting sur auth, paiements et endpoints techniques, +- headers de sécurité côté web, +- UUID publics dans l'API. + +### 10.2 Paiements +- vérification systématique des webhooks, +- aucune confiance dans le retour client seul, +- conservation du payload brut du PSP. + +### 10.3 Intégrations machines +- API key dédiée ou signature HMAC, +- journalisation des appels, +- horodatage et contrôle anti-rejeu si possible. + +--- + +## 11. Observabilité + +- Sentry backend, +- logs structurés JSON, +- suivi des jobs Laravel Horizon, +- métriques minimales : + - taux d'échec commandes machines, + - taux d'échec paiements, + - machines offline, + - jobs en échec, + - no-shows. + +--- + +## 12. Tests + +| Type | Outil | Portée | +|------|-------|--------| +| Unitaires | Pest | Wallet, Booking, Pricing, Machine provider | +| Intégration | Pest + MySQL réel | Endpoints critiques | +| Contrat | Payloads partenaires | Intégration machine / paiement | +| Charge | k6 | Wallet, booking, start wash | + +### Cas critiques obligatoires +- double webhook paiement, +- double réservation même créneau, +- timeout commande machine, +- événement machine reçu en doublon, +- annulation et no-show simultanés, +- scoping superviseur inter-organisation. + +--- + +## 13. Livrables attendus + +- code source Laravel, +- migrations et seeders, +- documentation OpenAPI, +- `.env.example`, +- Docker pour dev, +- suite de tests critique, +- mode simulation machine, +- données de démonstration multi-laveries. diff --git a/documentation/CDC_BackOffice.md b/documentation/CDC_BackOffice.md new file mode 100644 index 0000000..afe5249 --- /dev/null +++ b/documentation/CDC_BackOffice.md @@ -0,0 +1,460 @@ +# 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) diff --git a/documentation/CDC_BackOffice_v2.md b/documentation/CDC_BackOffice_v2.md new file mode 100644 index 0000000..eadd378 --- /dev/null +++ b/documentation/CDC_BackOffice_v2.md @@ -0,0 +1,396 @@ +# Cahier des Charges — Back-Office Exploitant +## Projet : Laverie Connectée — Interface d'Administration +**Version :** 2.0 +**Stack principale :** Laravel 11 + Vue.js 3 + Inertia.js +**Positionnement :** V1 simple, cloisonnée par exploitant, démontrable rapidement + +--- + +## 1. Contexte & Objectifs + +### 1.1 Objectif +Le back-office permet à un exploitant de piloter ses laveries sans exposer les données des autres exploitants présents sur la plateforme. + +### 1.2 Objectifs V1 +- authentifier les exploitants, +- afficher les machines et leur état, +- afficher les indicateurs métier de base, +- consulter réservations et lavages, +- gérer les tarifs, +- consulter les alertes techniques simples, +- garantir un cloisonnement strict par exploitant. + +### 1.3 Principes +- une organisation ne voit que ses établissements, +- un manager peut être restreint à un établissement, +- la V1 privilégie la lisibilité et la fiabilité plutôt que la richesse fonctionnelle, +- le temps réel peut être simulé ou remplacé par du polling si nécessaire. + +--- + +## 2. Périmètre fonctionnel + +### 2.1 MVP V1 +- connexion superviseur, +- tableau de bord simple, +- liste des machines, +- détail machine, +- liste des réservations, +- liste des lavages, +- gestion simple des tarifs, +- consultation simple des promotions si activées en V1, +- affichage des alertes de base, +- audit minimal des actions sensibles. + +### 2.2 V2 prévue +- campagnes de notifications, +- exports avancés, +- analytics détaillées, +- gestion multi-utilisateurs plus riche, +- segmentation marketing, +- vue consolidée plus poussée pour groupes de laveries. + +### 2.3 Hors périmètre V1 +- CRM, +- marketing automation, +- édition de rapports complexes, +- configuration technique profonde des intégrations machines. + +--- + +## 3. Utilisateurs et droits + +| Rôle | Portée | Droits | +|------|--------|--------| +| `platform_admin` | plateforme complète | vision globale, administration complète | +| `owner` | organization complète | accès complet à ses laveries | +| `manager` | organization ou établissement | gestion opérationnelle | +| `viewer` | organization ou établissement | lecture seule | + +### 3.1 Règle de cloisonnement +Toute donnée visible dans le back-office doit être filtrée au minimum par `organization_id`. Si le superviseur est attaché à un établissement précis, la visibilité est encore plus restreinte. + +--- + +## 4. Stack Technique + +| Composant | Technologie | Version | Commentaire | +|-----------|-------------|---------|-------------| +| Backend | Laravel 11 | — | Backend partagé avec l'API | +| Rendu SPA | Inertia.js | 2.x | Intégration Laravel / Vue | +| UI | Vue.js | 3.x | Composition API | +| UI kit | PrimeVue | 4.x | Rapide pour dashboard / tables | +| Styles | Tailwind CSS | 3.x | Mise en forme rapide | +| State | Pinia | 2.x | Si besoin de stores front | +| Graphiques | Chart.js | 4.x | KPIs simples | +| Tests front | Vitest + Vue Test Utils | — | Composants critiques | +| Tests E2E | Playwright | — | Flux exploitant | + +### 4.1 Temps réel +Le temps réel n'est pas un prérequis absolu de la V1. Deux stratégies possibles : +- **V1 rapide** : polling 15 à 30 secondes, +- **V1 enrichie** : Laravel Echo / Soketi ou Pusher. + +Si le délai est serré, le polling est acceptable. + +--- + +## 5. Architecture recommandée + +``` +resources/js/ +├── Components/ +│ ├── Layout/ +│ ├── Dashboard/ +│ ├── Machines/ +│ ├── Pricing/ +│ ├── Bookings/ +│ ├── Washes/ +│ └── UI/ +├── Pages/ +│ ├── Auth/ +│ ├── Dashboard/ +│ ├── Machines/ +│ ├── Pricing/ +│ ├── Bookings/ +│ ├── Washes/ +│ └── Settings/ +└── app.js +``` + +### 5.1 Principe de navigation +- menu latéral simple, +- accès rapide au dashboard, +- pages data-centric, +- peu de modales complexes en V1. + +--- + +## 6. Écrans V1 + +## 6.1 Connexion + +### Fonctionnalités +- email, +- mot de passe, +- message d'erreur clair, +- redirection vers dashboard si session active. + +### Sécurité +- session cookie, +- CSRF, +- rate limiting. + +--- + +## 6.2 Tableau de bord + +### Objectif +Donner à l'exploitant une vue immédiate de l'état de ses laveries. + +### KPIs V1 +- chiffre d'affaires du jour, +- nombre de lavages du jour, +- réservations du jour, +- taux d'occupation courant, +- nombre de machines offline / en erreur. + +### Sections recommandées +- bandeau KPI, +- liste des alertes, +- aperçu du parc machines, +- graphique simple de CA ou occupation. + +### Données affichées +Toujours filtrées sur le périmètre du superviseur connecté. + +--- + +## 6.3 Liste des machines + +### Colonnes minimales +- nom, +- établissement, +- type, +- statut, +- dernier heartbeat, +- utilisateur courant anonymisé si pertinent, +- actions. + +### Filtres +- établissement, +- statut, +- type. + +### Actions V1 +- voir détail, +- basculer maintenance si autorisé, +- forcer disponibilité seulement si besoin réel et journalisé. + +--- + +## 6.4 Détail machine + +### Contenu +- informations générales, +- statut actuel, +- historique récent des cycles, +- historique récent des événements machines, +- tarification appliquée, +- alertes récentes, +- uptime simple si disponible. + +Cette page est très utile pour la démo car elle montre la profondeur du produit sans nécessiter trop d'écrans. + +--- + +## 6.5 Réservations + +### Vue liste +- utilisateur anonymisé, +- machine, +- établissement, +- créneau, +- statut, +- montant réservé, +- pénalité éventuelle. + +### Filtres +- période, +- établissement, +- machine, +- statut. + +--- + +## 6.6 Lavages + +### Vue liste +- utilisateur anonymisé, +- machine, +- établissement, +- heure de démarrage, +- heure de fin, +- coût, +- statut. + +### Intérêt +Permet à l'exploitant de relier l'activité terrain au chiffre d'affaires. + +--- + +## 6.7 Tarification + +### Vue liste +- établissement, +- machine ou type de machine, +- plage horaire, +- prix, +- libellé, +- actif / inactif. + +### Formulaire V1 +- machine ou type, +- jours applicables, +- heure début / fin, +- prix, +- libellé, +- tarif exclusif app si retenu. + +### Validations +- pas de plage inversée, +- pas de conflit de règles non géré, +- audit de toute modification. + +--- + +## 6.8 Promotions + +Si activé en V1, les promotions restent simples : +- portée établissement, +- type de machine, +- réduction fixe ou pourcentage, +- date début / fin. + +Si le timing est trop serré, cette page peut être préparée mais non activée en démonstration. + +--- + +## 6.9 Paramètres + +### Contenu minimal +- profil superviseur, +- établissement ou organisation associés, +- informations de session, +- éventuellement préférences simples. + +--- + +## 7. Cloisonnement des données + +### Règles obligatoires +- `platform_admin` : accès global, +- `owner` : toutes les laveries de son organization, +- `manager` : selon son scope, +- `viewer` : lecture seule. + +### Implémentation +- policies Laravel, +- query scopes, +- tests dédiés au cloisonnement. + +### Interdiction +Aucune page ne doit faire remonter des agrégats globaux non filtrés à un exploitant local. + +--- + +## 8. Audit + +### Actions à journaliser +- connexion superviseur, +- modification de tarif, +- création / modification de promotion, +- changement manuel de statut machine, +- toute action d'administration sensible. + +### Utilité +- sécurité, +- compréhension métier, +- support, +- preuve en cas de litige. + +--- + +## 9. Temps réel / polling + +### Option 1 - Polling V1 recommandé si délai serré +- refresh du dashboard toutes les 30 secondes, +- refresh détail machine toutes les 15 à 30 secondes. + +### Option 2 - Temps réel enrichi +- Echo, +- Soketi ou Pusher, +- mise à jour machine / alertes / KPIs. + +### Recommandation +Pour la démo de fin de mois, le polling propre est souvent suffisant. + +--- + +## 10. Sécurité + +### 10.1 Authentification +- session cookie sécurisée, +- CSRF, +- timeout de session, +- rate limiting login. + +### 10.2 Autorisation +- policies explicites par rôle, +- filtre systématique des établissements. + +### 10.3 Données utilisateur +- anonymisation des noms dans les écrans exploitants si non nécessaire, +- pas d'affichage d'email complet côté exploitation terrain. + +--- + +## 11. Dashboard de démo + +Le dashboard de démo doit montrer visuellement : +- plusieurs laveries sur la plateforme, +- un exploitant qui ne voit que les siennes, +- des machines dans plusieurs états, +- un lavage qui démarre puis se termine, +- un KPI qui évolue, +- une tarification modifiable. + +C'est le meilleur compromis entre crédibilité et temps de développement. + +--- + +## 12. Tests + +| Type | Outil | Portée | +|------|-------|--------| +| Controllers | Pest | Accès dashboard, pricing, machines | +| Components | Vitest | KPIs, listes, formulaires | +| E2E | Playwright | Connexion, dashboard, tarif, machine | + +### Cas critiques +1. un exploitant A ne voit pas les données de B, +2. un viewer ne peut pas modifier un tarif, +3. un manager voit les bons KPI, +4. une machine passe de disponible à en cours, +5. une modification de tarif est auditée. + +--- + +## 13. Livrables attendus + +- code source back-office, +- pages Inertia principales, +- dataset de démonstration multi-exploitants, +- audit minimal, +- tests critiques, +- guide court de démonstration. diff --git a/documentation/Cadrage_global_laverie_V1_V2.md b/documentation/Cadrage_global_laverie_V1_V2.md new file mode 100644 index 0000000..c95a12c --- /dev/null +++ b/documentation/Cadrage_global_laverie_V1_V2.md @@ -0,0 +1,191 @@ +# Cadrage global — Laverie Connectée + +## 1. Vision produit + +Le produit vise à centraliser la gestion de plusieurs laveries dans une même plateforme, avec deux populations principales : + +- **les utilisateurs finaux**, qui consultent les laveries, rechargent leur wallet, réservent un créneau et lancent des lavages ; +- **les exploitants**, qui pilotent uniquement leurs propres établissements, leurs machines et leurs indicateurs. + +La première version doit être **démontrable rapidement**, avec une base technique suffisamment propre pour permettre une V2 sans refonte majeure. + +--- + +## 2. Périmètre + +### MVP V1 + +- authentification utilisateur +- authentification exploitant +- multi-laveries +- cloisonnement des exploitants par organization +- consultation des établissements et machines +- wallet + rechargement +- réservation de créneau +- lancement d'un lavage +- historique simple des transactions, réservations et lavages +- dashboard exploitant simple +- tarification simple +- intégration machine simulable +- audit minimal +- statistiques journalières simples + +### V2 + +- fidélité +- parrainage +- abonnements +- promotions avancées +- campagnes marketing +- exports avancés +- analytics détaillées +- RGPD enrichi +- modes d'intégration machine plus complets + +### Hors périmètre V1 + +- chat +- avis +- FAQ dynamique +- IA / lecture photo étiquette +- moteur prédictif +- interfaçage matériel bas niveau + +--- + +## 3. Architecture métier cible + +### Entités principales + +- `organizations` +- `establishments` +- `supervisors` +- `users` +- `machines` +- `machine_integrations` +- `machine_commands` +- `machine_events` +- `wallets` +- `payment_transactions` +- `wallet_transactions` +- `bookings` +- `washes` +- `pricing_rules` +- `promotions` +- `push_notifications` +- `audit_logs` +- `daily_establishment_stats` + +### Cloisonnement + +- un exploitant ne voit que les données de son `organization_id` +- un manager peut être limité à un seul établissement +- un `platform_admin` peut avoir une vue globale + +--- + +## 4. Stratégie d'intégration machine + +Le système doit être pensé comme une **couche d'intégration** entre le métier laverie et un fournisseur technique externe. + +### Modes supportés + +- **push** : le partenaire envoie des événements à notre API +- **pull** : notre backend appelle son API +- **hybrid** : combinaison des deux +- **simulated** : mode démo + +### Principe recommandé + +Pour la V1 et la démo, implémenter d'abord un **provider simulé**. + +Cela permet de démontrer : +- le lancement d'un lavage, +- le passage machine en cours, +- la fin de cycle, +- les mises à jour du dashboard, +- les notifications, + +sans dépendre du matériel réel. + +--- + +## 5. Démo de fin de mois + +### Parcours utilisateur + +1. connexion +2. affichage des laveries +3. consultation des machines +4. rechargement wallet +5. réservation d'un créneau +6. lancement d'un lavage +7. cycle simulé +8. notification de fin +9. historique mis à jour + +### Parcours exploitant + +1. connexion exploitant +2. dashboard limité à ses laveries +3. visualisation d'une machine qui passe en `running` +4. évolution d'un KPI +5. modification d'un tarif + +--- + +## 6. Backlog priorisé + +### Sprint 1 — Fondations + +- modèle organizations / establishments / supervisors +- auth utilisateur +- auth exploitant +- seeders de démo +- policies de cloisonnement + +### Sprint 2 — Coeur métier + +- machines +- wallet +- rechargement +- réservation +- lavages +- provider machine simulé + +### Sprint 3 — Interfaces + +- écrans Flutter MVP +- back-office MVP +- dashboard simple +- historique simple + +### Sprint 4 — Démo et stabilisation + +- audit minimal +- notifications +- agrégats journaliers +- nettoyage UX +- dataset final de démonstration + +--- + +## 7. Risques à surveiller + +- dérive de périmètre côté client +- ambiguïté sur l'intégration machine réelle +- sous-estimation de la partie paiement +- dette technique si trop d'optimisation UI inutile en V1 + +--- + +## 8. Règle de pilotage recommandée + +Toute fonctionnalité V1 doit répondre à au moins un de ces critères : + +- utile à la démonstration, +- nécessaire au fonctionnement métier minimal, +- nécessaire à la sécurité / traçabilité, +- nécessaire pour éviter une refonte en V2. + +Si elle ne remplit aucun de ces critères, elle doit être repoussée. diff --git a/documentation/docker-compose.yml b/documentation/docker-compose.yml new file mode 100644 index 0000000..4d9015a --- /dev/null +++ b/documentation/docker-compose.yml @@ -0,0 +1,70 @@ +# Stack de développement Laverie Connectée +# Usage : docker compose up -d + +services: + mysql: + image: mysql:8.0 + container_name: laverie-mysql + restart: unless-stopped + ports: + - "${MYSQL_PORT:-3306}:3306" + environment: + MYSQL_ROOT_PASSWORD: "${MYSQL_ROOT_PASSWORD:-secret}" + MYSQL_DATABASE: laverie + MYSQL_USER: laverie + MYSQL_PASSWORD: "${MYSQL_PASSWORD:-laverie}" + volumes: + - laverie_mysql_data:/var/lib/mysql + healthcheck: + test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-p${MYSQL_ROOT_PASSWORD:-secret}"] + interval: 10s + timeout: 5s + retries: 5 + + redis: + image: redis:7-alpine + container_name: laverie-redis + restart: unless-stopped + ports: + - "${REDIS_PORT:-6379}:6379" + volumes: + - laverie_redis_data:/data + healthcheck: + test: ["CMD", "redis-cli", "ping"] + interval: 10s + timeout: 5s + retries: 5 + + # Service applicatif Laravel (optionnel — décommenter pour lancer l'API dans Docker) + # app: + # build: + # context: ./backend + # dockerfile: Dockerfile + # container_name: laverie-app + # restart: unless-stopped + # ports: + # - "${APP_PORT:-8000}:8000" + # environment: + # APP_ENV: local + # APP_DEBUG: "true" + # DB_CONNECTION: mysql + # DB_HOST: mysql + # DB_PORT: 3306 + # DB_DATABASE: laverie + # DB_USERNAME: laverie + # DB_PASSWORD: "${MYSQL_PASSWORD:-laverie}" + # REDIS_HOST: redis + # REDIS_PORT: 6379 + # LAVERIE_MACHINE_API_KEY: "${LAVERIE_MACHINE_API_KEY:-demo-machine-api-key}" + # volumes: + # - ./backend:/var/www/html + # depends_on: + # mysql: + # condition: service_healthy + # redis: + # condition: service_healthy + # command: php artisan serve --host=0.0.0.0 --port=8000 + +volumes: + laverie_mysql_data: + laverie_redis_data: