# Spec — Commandes et réception

> Statut : `done`

## Intention

Fermer la boucle du stock. Jusqu'ici il n'avait que des sorties — les ventes — et des corrections. Les entrées se tapaient à la main, sans qu'aucune trace ne dise pourquoi.

Et donner aux élèves le geste qui manque : analyser le cadencier, décider une quantité, puis pointer ce qui arrive. C'est le métier qu'on enseigne, fait comme dans un vrai magasin.

Une commande n'exprime qu'un **besoin de marchandise**. Elle ne porte aucun fournisseur et n'est envoyée à personne — voir [ADR-0010](../decisions/0010-commande-sans-fournisseur.md).

## Qui fait quoi

Même partage que l'inventaire, et pour la même raison : celui qui prépare n'est pas celui qui engage.

| Étape | Qui | Où |
|---|---|---|
| Préparer la commande | Vendeur | Front |
| Valider ou refuser | Admin | Back-office |
| Pointer la réception | Vendeur | Front |

La réception ne repasse **pas** par l'admin. Une entrée de marchandise est un fait — les cartons sont là ou ils n'y sont pas — là où un ajustement d'inventaire est une correction, qui elle se discute. C'est ce qui justifie de trancher différemment.

## Contraintes

- Scopé à l'association, comme toute donnée métier.
- **Aucune écriture directe sur `Product.stock`.** La réception passe par `StockService`, qui reste le seul écrivain du stock.
- Une réception **partielle** est le cas normal, pas une exception.
- Une commande déjà tranchée ne se rejoue pas : la repasser doublerait les entrées.

## Comportement attendu

### Préparer — côté vendeur

1. L'écran liste **tous** les produits, avec pour chacun : ce qu'il en reste, ce qu'il se vend par semaine, et sa **couverture**. Ce sont les colonnes du cadencier, ramenées à ce qui sert à décider — mais sans son filtre : le cadencier écarte ce qui n'a rien fait et va bien, parce que c'est un tableau d'analyse. Ici on prépare une commande, et il faut pouvoir commander une nouveauté, un article de saison, ou simplement le premier réassort d'une association qui démarre.
2. Une **quantité est proposée** pour chaque produit : de quoi revenir à une couverture cible, arrondie à l'unité supérieure. C'est une proposition, jamais une imposition — le vendeur écrase le chiffre comme il veut, et un produit laissé à zéro n'entre pas dans la commande.
3. La proposition ne s'affiche que pour les produits qui se vendent. Proposer une quantité pour un produit qui ne part pas serait un conseil de commander pour rien.
4. À l'envoi, la commande part **à valider**.

### Valider — côté admin

5. L'admin voit les commandes en attente, avec le détail ligne par ligne : quantité demandée, stock au moment de la demande, couverture.
6. Il **valide** — la commande passe à l'état *passée* — ou il **refuse**.
7. Un refus ne touche à rien et garde la commande dans l'historique. On ne supprime pas une demande : le fait qu'elle ait été formulée est une information.

### Réceptionner — côté vendeur

8. Sur une commande *passée*, le vendeur saisit **ce qui est arrivé**, ligne par ligne. La quantité commandée est rappelée mais n'est pas pré-remplie : la recopier sans regarder est exactement l'erreur qu'on veut éviter.
9. Une ligne à zéro veut dire « rien n'est arrivé pour ce produit ». Une quantité reçue peut dépasser la quantité commandée — ça arrive, et le refuser ferait mentir le stock.
10. À la validation, pour **chaque ligne dont la quantité reçue est supérieure à zéro**, un `StockMovement` de type `ENTRY` est créé, avec le motif `Commande n° X du JJ/MM/AAAA`.
11. L'ensemble tient dans une transaction : soit toute la réception est enregistrée, soit rien.
12. La commande passe à l'état *réceptionnée*. Une réception partielle la ferme quand même : rouvrir une commande pour un reliquat supposerait un fournisseur qui doit encore quelque chose, et il n'y en a pas.

### Consultation

13. L'historique des commandes est consultable : date, auteur, validateur, statut, nombre de lignes, quantités demandées et reçues.
14. Depuis une commande réceptionnée, on retrouve les mouvements d'entrée qu'elle a générés.

### Cas limites

- **Une commande vide** ne part pas : rien à valider.
- **Une commande refusée** ne peut plus être réceptionnée.
- **Un produit supprimé** entre la demande et la réception : hors périmètre, les produits ne se suppriment pas dans le back-office.

## Critères de « fait »

- [x] Entité `Commande` (association, auteur, date, statut, validateur, date de validation, réceptionnaire, date de réception) et `CommandeLine` (produit, quantité demandée, stock au moment de la demande, quantité reçue).
- [x] Enum `CommandeStatut` (à valider, passée, réceptionnée, refusée).
- [x] Écran de préparation côté vendeur listant tous les produits, avec couverture et quantité proposée.
- [x] La proposition ne s'affiche que pour les produits qui se vendent.
- [x] Écran de validation côté admin : détail, valider ou refuser.
- [x] Écran de pointage de réception côté vendeur, quantité reçue non pré-remplie.
- [x] Réception partielle admise, réception supérieure au commandé admise.
- [x] La réception génère un `ENTRY` par ligne reçue, via `StockService`, en transaction.
- [x] Une commande déjà tranchée ne se rejoue pas.
- [x] Historique consultable.
- [x] Tout est scopé à l'association.

## Notes

La **couverture cible** — combien de semaines de stock on veut avoir après réception — est un réglage de l'association, comme les seuils d'alerte. Trois semaines par défaut : assez pour ne pas commander tous les jours, assez peu pour ne pas immobiliser de l'argent dans des cartons de chips.

Ce que cette spec n'introduit pas, et qui n'est pas un oubli : pas de fournisseur, pas d'envoi, pas de facture, pas de délai. Les raisons sont dans l'[ADR-0010](../decisions/0010-commande-sans-fournisseur.md).
