# Spec — Annulation d'une vente et écran des corrections

> Statut : `done`

## Intention

Donner un recours à l'erreur de saisie — deux fois le même article, le mauvais produit, une quantité tapée à côté — sans qu'une ligne du journal devienne discutable.

Voir [ADR-0012](../decisions/0012-annulation-de-vente.md) pour le raisonnement. Cette spec décrit ce que ça donne à l'écran.

## Contraintes

- **On n'efface ni ne modifie une vente.** On l'annule : elle reste, marquée, avec qui, quand, pourquoi.
- **Pas de correction partielle**, et **une annulation ne s'annule pas**. On resaisit.
- **La clôture d'une session close ne bouge jamais** : ni son attendu, ni son écart, ni son motif.
- Motif **obligatoire**.
- Tout passe par `StockService` : le stock rendu est un mouvement, jamais une écriture directe.

## Le modèle

Trois colonnes sur `Sale`, pas d'entité nouvelle :

| Colonne | |
|---|---|
| `annuleeLe` | nulle tant que la vente est valide — c'est elle qui fait foi |
| `annuleePar` | l'auteur de l'annulation, jamais celui de la vente |
| `motifAnnulation` | obligatoire, 255 caractères |

Un type de mouvement de stock **`ANNULATION`** s'ajoute à l'enum. Réutiliser `ENTRY` mélangerait une marchandise qui arrive et une vente qui n'a pas eu lieu : dans le cadencier comme dans le journal, ce ne sont pas les mêmes faits.

Le motif du mouvement porte la référence — « Annulation de la vente n° 42 — double saisie ».

## Comportement

### Annuler

Depuis la vente, un bouton **« Annuler cette vente »**, puis un écran de confirmation qui montre **ce qui va se passer**, dans les termes du cas :

- le détail de la vente — produits, quantités, montant, moyen de paiement ;
- ce qui revient en stock, ligne par ligne ;
- **si la session est ouverte** : « L'attendu de la caisse baissera de X €. » ;
- **si la session est close** : « La clôture du JJ/MM ne sera pas modifiée. Son écart reste celui qui a été constaté. Le stock revient à la date d'aujourd'hui. » ;
- le champ **motif**, obligatoire, avec des propositions courtes — double saisie, mauvais produit, erreur de quantité, client parti sans payer — et la saisie libre.

C'est le même dispositif que le récapitulatif de clôture : on montre avant, pas après.

### Qui

| | Portée |
|---|---|
| Vendeur | **sa** vente, dans **sa** session ouverte |
| Admin | toute vente de son association, session close comprise |

### Ce que ça déclenche

Dans une transaction :

1. un mouvement `ANNULATION` par ligne de vente, du montant de la quantité vendue, via `StockService` ;
2. l'horodatage, l'auteur et le motif posés sur la vente ;
3. **si la session est ouverte**, l'attendu se recalcule de lui-même — il est déjà une somme des ventes non annulées.

Rien d'autre. Aucune écriture sur une session close.

## Ce qui change ailleurs

- **Liste des ventes** : la vente annulée reste, barrée, avec une pastille « annulée ». Elle n'est pas filtrée par défaut — la faire disparaître serait la réécrire.
- **Journal des mouvements** : le mouvement `ANNULATION` porte le numéro de la vente et le motif.
- **Statistiques, cadencier, 20/80, ruptures, front élèves** : la vente annulée **et** son mouvement d'annulation sont exclus, **dans les deux sens**. Retirer la vente *et* compter l'annulation en négatif soustrairait deux fois.
- **Écran « Corrections »**, nouveau, sur le front : toutes les annulations de l'association, la plus récente en tête — vente, montant, qui a vendu, qui a annulé, quand, pourquoi. **Accessible à tous les membres**, vendeurs compris. C'est ce qui distingue une correction transparente d'un privilège d'administrateur.

## Critères de « fait »

- [x] Trois colonnes sur `Sale`, type `ANNULATION` dans l'enum, migration.
- [x] Annulation transactionnelle : mouvements de stock **et** marquage, ou rien.
- [x] Motif obligatoire, refus explicite s'il manque.
- [x] Écran de confirmation qui annonce l'effet réel, différent selon que la session est ouverte ou close.
- [x] Un vendeur ne peut annuler que sa propre vente, dans sa session ouverte — vérifié par un test.
- [x] **Une session close ne bouge pas** : attendu, écart et motif identiques avant et après — vérifié par un test.
- [x] Une vente annulée ne peut pas l'être deux fois.
- [x] Vente annulée et mouvement d'annulation exclus des statistiques, du cadencier et du front élèves — vérifié par un test qui compare le chiffre avant et après.
- [x] Écran « Corrections » accessible à un vendeur.
- [x] Aide contextuelle sur l'écran de confirmation : pourquoi la clôture ne bouge pas.

## Hors périmètre

- **Annuler une clôture.** Elle est irréversible par conception, et l'ADR-0008 en dépend.
- **Modifier une vente.** Voir l'ADR-0012 : annuler et resaisir répond au même besoin sans créer de version concurrente.
- **Annuler une annulation.** On resaisit.
- **Rapprocher l'écart d'espèces d'une session close** après une annulation tardive. L'outil ne peut pas le faire tout seul ; le motif l'explique, le rapprochement se fait à la main.
- Un **droit d'annulation limité dans le temps** (« pas au-delà de sept jours »). Utile le jour où l'usage montrera des annulations tardives suspectes ; inutile à inventer avant.
