# Spec — Vente (Front de caisse)

> Statut : `done`

## Intention

Interface mobile-first rapide pour que les élèves vendeurs saisissent les ventes au stand. Grille de produits, panier, choix du moyen de paiement, validation.

## Contraintes

- Mobile-first (téléphones des élèves + tablette du stand).
- Rapide à taper : minimum de clics pour une vente.
- Le stock est décrémenté à la validation.
- **Une vente n'est jamais refusée pour un motif de stock** (voir [ADR-0007](../decisions/0007-vente-jamais-bloquee-par-stock.md)).
- Le prix est figé au moment de la vente (snapshot dans `SaleLine`).
- L'appli ne gère pas l'encaissement, elle enregistre seulement le moyen de paiement.

## Comportement attendu

1. Le vendeur voit la grille des produits de son asso (nom + prix + stock).
2. Il tape sur un produit pour l'ajouter au panier (quantité par défaut = 1, incrémentable).
3. Il peut retirer un produit ou modifier la quantité.
4. Le total du panier est affiché en temps réel.
5. Il choisit le moyen de paiement : **Espèces** ou **SumUp**.
6. Il valide → la vente est enregistrée :
   - `Sale` créée avec session, vendeur, moyen de paiement, total.
   - `SaleLine` par produit avec quantité et prix unitaire snapshot.
   - `StockMovement` de type SALE pour chaque ligne.
   - Stock courant décrémenté, **même en dessous de zéro**.
7. Le panier est vidé, prêt pour la vente suivante.

### Stock à zéro ou négatif

Le stock affiché est une estimation, pas un inventaire. Si l'élève a le produit en main, la vente a eu lieu : la caisse l'enregistre sans discuter.

- Un produit à stock 0 ou négatif reste **ajoutable au panier**.
- Il est **signalé visuellement** sur sa carte — « 0 en stock », puis « −2 » — pour que le vendeur voie que le suivi décroche.
- Ce signalement informe, il n'empêche pas et ne demande aucune confirmation. Le vendeur n'a aucune décision à prendre pendant qu'un client attend.

La correction se fait ailleurs : les alertes signalent le décrochage au prof (`alertes.md`), l'inventaire le répare (`inventaire.md`).

### Autres cas limites
- Session fermée : pas de vente possible, message d'erreur.
- Panier vide : bouton de validation désactivé.

## Critères de « fait »

- [x] Grille produits responsive (mobile-first).
- [x] Panier avec ajout/retrait/modification quantité.
- [x] Total en temps réel (Stimulus).
- [x] Choix moyen de paiement (CASH / SUMUP).
- [x] Validation crée Sale + SaleLines + StockMovements.
- [x] Stock décrémenté à la validation.
- [x] Prix snapshot dans SaleLine.
- [x] Vente impossible sans session ouverte.
- [x] Taper sur la carte produit l'ajoute au panier, sans viser un bouton `+`.
- [x] Un produit à stock 0 ou négatif reste vendable.
- [x] Stock à 0 ou négatif signalé visuellement sur la carte produit.
- [x] Retrait de l'`InsufficientStockException` du chemin de vente.

## Notes

Pas de rendu de monnaie calculé. L'élève se débrouille avec la monnaie physique.

Le critère « produit à stock 0 grisé » a été retiré : il contredisait l'ADR-0007. La transaction et le verrou en écriture sur les produits sont conservés — ils garantissent que deux ventes simultanées ne perdent pas un décrément, indépendamment de toute règle de refus.
