Ajout documentation

This commit is contained in:
bastien
2026-07-04 22:47:55 +02:00
parent d95bfa6c58
commit 293f49adfa
6 changed files with 2627 additions and 0 deletions
+843
View File
@@ -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.