# ADR-008 : Prix d'achat réel et coût de valorisation des écarts

**Statut :** Accepté
**Date :** 2026-10-12
**Contexte :** Lignes de commande d'achat (`OrderItemPricer`), prix catalogue `product_details.purchase_price`, valorisation des mouvements (ADR-007)

---

## Contexte

La note produit demande un rapport d'écarts **valorisé en FCFA** : « sur le mois, il manque 180 000 FCFA, dont les deux tiers sur deux références ». Ce chiffre n'est juste que si le coût unitaire l'est.

Aujourd'hui, le coût réel n'existe nulle part :

- `OrderItemPricer::price()` (`app/Services/OrderItemPricer.php:81-83`) impose `product_details.purchase_price`, le prix catalogue saisi à la main sur la fiche produit, comme prix de **toute** ligne d'achat. Le prix réellement facturé par le fournisseur ne peut pas être saisi. S'il augmente, le stock reste valorisé à l'ancien prix tant que quelqu'un ne pense pas à modifier la fiche.
- Cette règle vient d'un correctif de sécurité : l'audit WES du 2026-07-20, « critique #1 », a constaté que prix, taxe et remise étaient repris tels quels du client. Le risque visé était la **vente**, où un client modifié pouvait fabriquer n'importe quel montant facturé. Côté achat, le prix est une donnée que l'acheteur saisit d'après la facture du fournisseur : il n'y a pas d'autre source.
- `order_items.cost_price` fige le prix catalogue au moment de la vente et alimente `ProfitCalculationService`. Il n'existe ni coût moyen pondéré (CMUP) ni FIFO.

## Décision

### 1. Prix saisissable côté achat uniquement

Pour `purchases` et `purchase-returns` seulement, `OrderItemPricer::price()` accepte un `unit_price` envoyé par le client **si l'utilisateur a la permission `purchases_price_edit`** (ADR-006). Sinon, le comportement actuel s'applique : le prix catalogue fait foi.

Bornes vérifiées par le Form Request **et** par le pricer :

- le prix doit être strictement positif ;
- deux décimales au plus ;
- `≤ 1 000 000 000` (même plafond que les montants existants).

Un écart de plus de 50 % par rapport au prix catalogue est accepté, puisque les prix changent vraiment. Il est en revanche journalisé (`Log::notice('purchase_price_deviation', …)`), pour qu'une saisie aberrante reste visible.

**Le côté vente ne change pas** : `sales`, `sales-returns` et la caisse gardent le prix serveur. La protection de l'audit 2026-07-20 reste entière sur son périmètre d'origine.

### 2. Le dernier prix réel devient le prix catalogue

Quand une ligne d'achat est enregistrée avec le statut `received`, `product_details.purchase_price` de l'entrepôt prend la valeur du `unit_price` saisi, dans la même convention de taxe (`purchase_tax_type`) que la fiche produit. La mise à jour a lieu dans la transaction de `OrderProcessingService`.

Condition : la commande doit être **la réception la plus récente** (par `order_date`) de ce produit dans l'entrepôt. Rééditer ou saisir après coup un vieil achat ne ramène donc pas le catalogue à un ancien prix.

Ce n'est pas le cas pour une commande seulement « commandée » : le prix ne doit changer qu'à la réception. Avec les conditionnements (lot 1), le prix saisi par casier est ramené à l'unité de stock (prix ÷ facteur).

### 3. Coût figé sur chaque mouvement

Tout mouvement créé par les nouveaux modules enregistre un `unit_cost` (ADR-007) : écart d'inventaire, perte, vente importée, consommation de recette. Ce coût unitaire **hors taxe** vaut le `purchase_price` courant ramené hors taxe par `ProductPricingCalculator::calculate()`. On utilise le hors taxe parce que la taxe n'est pas une valeur de stock.

Le rapport d'écarts valorise avec ce coût figé, jamais avec le prix du jour où l'on consulte le rapport. Un rapport de mars donne donc le même total en juin.

### 4. Méthode de valorisation v1 : dernier prix d'achat

Le CMUP est différé. Pour le pilote boissons, des références aux prix stables et achetées souvent, le dernier prix d'achat est assez précis et immédiatement compréhensible par une gérante.

## Alternatives Considérées

### CMUP dès la v1
Différé. Recalculer le coût moyen à chaque réception est simple, mais il faudrait aussi le recalculer quand un achat est modifié ou supprimé. Tant que les documents restent modifiables hors période verrouillée (ADR-007), le CMUP dériverait sans être reconstructible. À réintroduire une fois le verrouillage en place et éprouvé, ou quand le coût matière par plat (lot 8) l'exigera.

### FIFO
Rejeté. Il faut suivre des lots d'entrée et leur consommation, ce qui suppose le registre `stock_movements` écarté par l'ADR-007. La précision supplémentaire ne change rien à la décision de la gérante (quelles références fuient).

### Garder le prix catalogue saisi à la main
Rejeté. L'écart serait valorisé à un prix que personne ne met à jour. C'est la « précision qui n'existe pas » contre laquelle la note met en garde.

### Prix saisissable par tous les utilisateurs côté achat
Rejeté. Saisir le prix d'achat, c'est influencer la valorisation du stock et des écarts, donc la mesure des pertes elle-même. Cela doit rester un droit explicite, accordé par défaut à l'`admin` seul.

## Conséquences

- La valorisation devient exacte au dernier prix réel, et elle est stable dans le temps grâce au coût figé.
- `ProfitCalculationService`, qui utilise `cost_price` figé à la vente, profite automatiquement d'un prix catalogue tenu à jour, sans modification.
- **Décision amendée** : celle de l'audit 2026-07-20, « critique #1 », côté achat seulement. Le commentaire d'en-tête de `OrderItemPricer` doit le mentionner et renvoyer à cet ADR.
- Tests à ajouter :
  - un prix envoyé par le client est ignoré sans permission ;
  - il est accepté avec la permission, dans les bornes ;
  - il est rejeté hors bornes ;
  - il est **toujours ignoré côté vente**, même avec la permission ;
  - la mise à jour du prix catalogue n'a lieu qu'au statut `received` ;
  - `unit_cost` est bien hors taxe pour les deux conventions de taxe.

## Quand Réviser

- Quand le coût matière par plat (lot 8) sera développé, ou si les prix d'achat deviennent très volatils : passer au CMUP.
- Si le contrôle de réception (lot 7) introduit un prix facturé distinct du prix commandé : préciser lequel alimente la valorisation.
