# ADR-013 : Fiches techniques et consommation des ingrédients à la vente

**Statut :** Accepté
**Date :** 2026-10-15
**Contexte :** Lot 6 de la feuille de route restauration, réservé au mode restauration (ADR-006), sur le socle des mouvements typés et immuables (ADR-007) et des quantités `decimal(14,3)` (ADR-007 §5)

---

## Contexte

Un restaurant vend des plats, pas des ingrédients. Quand il vend un poulet braisé, il sort du stock 350 g de poulet, 50 g d'oignon et 2 cl d'huile, mais GestionStock ne le sait pas : le poulet reste en stock jusqu'à l'inventaire, où il apparaît comme une perte inexpliquée. Pour la cuisine, l'écart d'inventaire est inutilisable tant que les ventes ne font pas sortir les ingrédients.

La note produit demande des fiches techniques limitées aux 5 à 10 plats phares, avec un rendement (la part utilisable d'un ingrédient après parage ou cuisson).

## Décision

### 1. Un plat composé n'a pas de stock propre

`products.stock_tracking` vaut `stocked` (par défaut, tous les produits existants) ou `recipe`. Un produit `recipe` :
- se vend en caisse et en vente, **sans contrôle ni plafond de stock** : son stock affiché est nul et masqué ;
- ne s'achète pas (422), ne se compte pas à l'inventaire, ne se déclare pas en perte (on déclare ses ingrédients) ;
- a un stock courant forcé à zéro par le recalcul.

Un produit qui a du stock ne peut pas devenir un plat composé (il faut d'abord le solder). Un plat déjà vendu ne peut pas redevenir un produit stocké : ses ventes passées feraient apparaître un stock négatif.

### 2. Fiche technique

Table `recipe_items` : plat, ingrédient (un produit stocké, jamais un autre plat), **quantité nette par portion** dans l'unité de stock de l'ingrédient, **rendement** en % (100 par défaut).

Quantité sortie du stock par portion = quantité nette × 100 / rendement. Exemple : 210 g de poisson net avec un rendement de 60 % font sortir 350 g de poisson.

La fiche affiche le **coût matière par portion** (somme des quantités brutes × coût unitaire de l'ingrédient, ADR-008 §3) et sa part du prix de vente. C'est le premier indicateur que la gérante peut suivre.

### 3. Consommation à la vente : synchronisation par commande

Dans la transaction du traitement d'une vente (création, modification, caisse) et à sa suppression :
1. **attendu** = pour chaque ingrédient, Σ (quantité vendue du plat × quantité brute par portion), selon les fiches en vigueur ;
2. **déjà sorti** = somme des consommations déjà enregistrées pour cette vente ;
3. chaque différence non nulle crée une ligne `recipe_consumptions` (vente, ingrédient, quantité signée) et un mouvement typé `recipe_consumption` (ADR-007) dont elle est la source, daté de la vente. Une sortie s'il faut sortir plus, une rentrée s'il faut moins.

Cette mécanique est **idempotente** : rejouer le traitement d'une vente inchangée n'écrit rien. Elle respecte l'immutabilité de l'ADR-007, car on ne modifie jamais un mouvement, on écrit la différence. Elle reste traçable : chaque sortie d'ingrédient remonte à sa vente.

`recipe_consumptions.order_id` n'a pas de clé étrangère : la trace survit à la suppression de la vente, dont la suppression a écrit la rentrée correspondante.

### 4. Ce qui ne change pas

- Le coût des ventes et les montants des factures ne bougent pas.
- Un **retour de vente** d'un plat ne remet pas les ingrédients en stock : un plat servi ne redevient pas du poulet cru.
- Modifier une fiche ne réécrit pas les ventes passées. Seule une vente modifiée après coup est recalculée, avec la fiche en vigueur.

### 5. Accès

| Action | Permission | Garde |
|---|---|---|
| Voir les fiches techniques | `products_view` | `check.feature:restaurant` |
| Créer, modifier, retirer une fiche | `products_edit` | `check.feature:restaurant` |

La consommation à la vente ne dépend pas du mode : un plat qui a une fiche consomme ses ingrédients même si le mode est désactivé plus tard. Sinon, désactiver le mode ferait silencieusement dériver le stock.

## Alternatives Considérées

### Calculer la consommation au recalcul du stock (sans mouvement)
Rejeté. Modifier une fiche réécrirait toutes les ventes passées et le stock théorique des inventaires validés, ce que l'ADR-007 interdit.

### Un mouvement par ligne de vente et par ingrédient, réécrit à chaque modification
Rejeté. Les mouvements typés sont immuables, et la contrainte unique `(source_type, source_id, reason)` n'admet qu'un mouvement par source. La différence par commande suffit et reste idempotente.

### Sous-recettes (un plat composé d'une sauce elle-même composée)
Différé. Les 5 à 10 plats du pilote n'en ont pas besoin. Une sauce préparée en lot peut être un produit stocké, produit par un ajustement.

## Conséquences

- Nouvelle colonne `products.stock_tracking` et tables `recipe_items` et `recipe_consumptions`. Tous les produits existants restent `stocked`.
- La caisse, la recherche de ventes, les achats, l'inventaire et les pertes tiennent compte du type de produit.
- L'écart d'inventaire des ingrédients devient lisible : la part vendue est sortie, ce qui reste est la vraie perte.
- Nouvel écran « Fiches techniques » dans la gestion des produits, visible si le mode restauration est actif.

## Quand Réviser

- Si les cuisines préparent en lots (sauces, fonds) : produits semi-finis avec ordres de production.
- Si les fiches changent souvent en cours de journée : figer la fiche sur la ligne de vente au moment de la vente.
