# Scan Inventaire — Scanner de plaques (version PHP / Symfony)

Application web de scan de plaques d'immatriculation et de codes VIN à partir de photos.
Migration de l'app Next.js d'origine (`../platevision-scanner`) vers **Symfony 7.4**.

## Stack

- **Symfony 7.4** (PHP 8.2+) — Twig, AssetMapper, Stimulus (Symfony UX)
- **Tailwind CSS v4** via `symfonycasts/tailwind-bundle`
- **Doctrine ORM** — MySQL en production, SQLite en développement local
- **Google Gemini** (`gemini-2.5-flash-lite`) — OCR via `symfony/http-client`
- **Mailjet** (`symfony/mailjet-mailer`) — emails OTP + rapports
- **OpenSpout** — génération Excel (.xlsx) sans dépendance `ext-gd`
- Authentification : **OTP par email** (code à 6 chiffres) + sessions Symfony,
  réservée aux domaines `@emilfrey.fr` et `@autosphere.fr`

## Différences avec la version Next.js

- Conversion **HEIC** et compression d'image faites **100 % côté navigateur**
  (heic2any + canvas) — l'hébergement mutualisé n'a ni `gd` ni `imagick`.
- Base **MySQL** (au lieu de PostgreSQL) ; pas de reprise de l'ancien historique.
- Les concessions sont stockées **par utilisateur en base** (au lieu du LocalStorage).

## Démarrage en local

Prérequis : PHP 8.2+ (extensions `pdo_sqlite` ou `pdo_mysql`, `curl`, `intl`, `mbstring`, `zip`), Composer.

```bash
composer install

# Secrets locaux (déjà présents dans .env.local) :
#   DATABASE_URL  → SQLite par défaut (aucun serveur requis)
#   MAILER_DSN    → Mailjet (mettre null://null pour ne rien envoyer en test)
#   GEMINI_API_KEY → clé Google Gemini

# Base de données (SQLite)
php bin/console doctrine:database:create   # crée var/data.db
php bin/console doctrine:schema:create

# Compiler le CSS Tailwind (télécharge le binaire au 1er appel)
php -d memory_limit=1G bin/console tailwind:build

# Lancer (sans Symfony CLI)
php -S 127.0.0.1:8000 -t public public/index.php
```

Ouvrir http://127.0.0.1:8000 — vous serez redirigé vers `/auth/login`.

> En dev, pour voir le CSS se recompiler à la volée : `php bin/console tailwind:build --watch` dans un terminal séparé.

## Architecture

```
src/
├── Controller/        AuthController, OcrController, HistoryController,
│                      ReportController, ExportController, ConcessionsController, PageController
├── Entity/            User, OtpCode, ScanSession
├── Repository/        …
├── Ocr/               OcrParser, VinMakeResolver, GeminiOcrClient (+ OcrParseResult)
├── Service/           OtpManager, EmailDomainPolicy, ImageValidator,
│                      ConcessionsProvider, CsvReportBuilder, XlsxReportBuilder, ReportMailer
├── Security/          OtpAuthenticator, AppAuthenticationEntryPoint
templates/             base, app_base, auth/login, page/*, partials/*, email/*
assets/controllers/    scan, login, concession_picker, history, navbar, inactivity (Stimulus)
```

### Endpoints API

| Méthode | URL | Rôle |
|---|---|---|
| POST | `/api/auth/send-code` | Envoie un code OTP (rate-limité, domaines restreints) |
| POST | `/api/auth/verify` | Vérifie le code et ouvre la session (OtpAuthenticator) |
| GET | `/api/auth/me` | Email de l'utilisateur connecté |
| GET | `/api/auth/logout` | Déconnexion (firewall) |
| POST | `/api/ocr` | OCR d'une image (Gemini) |
| GET/POST/DELETE | `/api/history` | Historique des sessions (par utilisateur) |
| POST | `/api/send-report` | Envoi du rapport Excel par email |
| POST | `/api/export/{csv,xlsx}` | Téléchargement CSV / Excel |
| GET/POST | `/api/concessions` | Concessions de l'utilisateur |
| GET | `/param` | Menu **Paramètres** (admin) |
| POST | `/param/concessions/{import,reset}` | Import Excel/CSV de la liste de concessions (admin) |

### Administration

Le menu **Paramètres** (`/param`) est réservé aux emails listés dans `ADMIN_EMAILS`
(défaut : `eric.masse@emilfrey.fr`) — accordés `ROLE_ADMIN` via `AdminVoter`, sans stockage du rôle en base.
Il permet de **remplacer la liste des concessions sélectionnables** par import d'un fichier
`.xlsx`/`.csv` (une concession par ligne, première colonne). La liste importée est stockée en base
(table `concessions`) ; si elle est vide, l'app utilise la liste par défaut intégrée (423 entrées).

## Déploiement OVH mutualisé

Voir **[DEPLOYMENT-OVH.md](DEPLOYMENT-OVH.md)**.
