844 lines
28 KiB
Markdown
844 lines
28 KiB
Markdown
# 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.
|