# Prix

> Ce que le client paie pour un SKU à une quantité donnée, et exactement quelle règle appliquer à ce prix.

TradeCatalog calcule chaque prix client en un seul endroit, de la même manière pour l'application, le portail de commerce, MCP et l'API. Vous ne calculez jamais les prix vous-même.

Les produits configurés, les taux mesurés, les seuils de quantité, les extras nets et le fractionnement explicite utilisent également ce service de tarification. Citez une ligne complète avec `POST /products/{id}/quote` ; une absence de prix peut signifier `configurationRequired`, tandis qu'une spécification non tarifée valide est « Prix sur demande ». Utilisez le total de la ligne renvoyé. Voir [configuration du catalogue de commerce](/docs/guides/trade-catalogues) pour la priorité, le traitement net et la publication.

## Comment sont calculés les prix

La règle la plus spécifique l'emporte, et **les ajustements ne s'accumulent jamais** :

1. **Prix convenu.** Si le client a un prix (fixe) convenu pour le produit, c'est ce prix.
2. Sinon, le **prix de liste avec un pourcentage**, le premier qui s'applique. Un pourcentage est une remise (déduite) ou une marge (ajoutée) :
   1. le pourcentage du client pour ce **produit**
   2. le pourcentage du client pour la **catégorie** du produit (le plus proche parent gagne)
   3. le pourcentage du client pour la **marque** du produit
   4. le pourcentage du client sur la **liste entière**
3. Sinon, le **prix de liste**.

Une remise de 10 % sur une catégorie et une surcharge de 5 % sur la liste entière donnent 10 % de réduction, pas les deux. L'unité n'est jamais inférieure à zéro, et elle est arrondie à la demi-unité inférieure. Le total de la ligne est `unit × qty`.

Les prix de liste et les prix convenus sont versionnés par date. L'API fournit toujours le prix en vigueur **maintenant**. Les prix futurs d'un changement de prix programmé s'appliquent à partir de leur date.

## Listes de prix et accès au catalogue

Standard est le catalogue principal du fournisseur. Les autres listes de prix peuvent contenir des produits avec des prix et des produits en attente d'une offre. Une absence de prix sur une liste assignée ne revient pas à Standard.

Dans l'espace de travail, ouvrez **Listes de prix** et choisissez une liste nommée. **Les clients assignés peuvent voir** a deux choix :

- **Tous les produits du catalogue autorisés** est la valeur par défaut. La liste définit les prix ; les acheteurs assignés peuvent également parcourir d'autres produits autorisés en tant que Prix sur demande.
- **Seuls les produits de cette liste** limitent le catalogue, les recherches, les liens de produits, les téléchargements et les nouvelles commandes de chaque acheteur assigné à ces produits. La visibilité des autres produits et les restrictions des listes approuvées s'appliquent toujours. Les clients sur d'autres listes conservent leur accès existant.

Chaque liste affiche son nombre de clients, ses produits tarifés, ses produits non tarifés et son nombre total de produits. Les totaux incluent les produits inactifs, donc ils peuvent dépasser ce que l'acheteur voit actuellement. Un prix à zéro est considéré comme tarifé. Un prix futur est considéré comme non tarifé jusqu'à sa date de début.

Pour ajouter des produits, sélectionnez-les sur **Produits**, choisissez **Ajouter à la liste de prix**, puis copiez les prix Standard avec un pourcentage facultatif ou choisissez **Ajouter sans prix**. Les membres existants conservent leurs prix. Les produits sans prix Standard sont ajoutés en tant que Prix sur demande. Sur la page d'une liste nommée, enregistrer un SKU avec un prix vide l'ajoute sans prix ou efface son prix actuel. **Supprimer** le retire de cette liste tout en conservant le produit principal et les commandes passées. Résolvez d'abord tout changement de prix en attente pour ce produit.

Ces contrôles nécessitent le plan Pro et les droits d'édition globale des prix. Les listes existantes conservent leur accès au catalogue jusqu'à ce qu'un fournisseur le modifie. Les imports API dans une liste nommée incluent des lignes de produits ordinaires sans prix comme membres non tarifés ; une cellule vide dans un import uniquement prix laisse l'accès inchangé.

## L'objet prix

Chaque produit a cette forme pour son `price`. `GET /prices` retourne les mêmes champs au niveau supérieur de `data`.

```json
{
  "unit": 1573,
  "line": 15730,
  "list": 1850,
  "discountBp": 1500,
  "adjustment": "discount",
  "source": "brand",
  "label": "15% off your list",
  "remark": null
}
```

| Champ        | Type                   | Description                                                                                                                                 |
| ------------ | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
| `unit`       | entier ou null         | Prix pour une unité, en unités mineures. `null` si le produit n'a pas de prix.                                                              |
| `line`       | entier ou null         | `unit × qty`.                                                                                                                               |
| `list`       | entier ou null         | Le prix de liste actuel.                                                                                                                    |
| `discountBp` | entier                 | L'ajustement appliqué, en points de base. `0` pour les prix convenus et de liste.                                                           |
| `adjustment` | `discount` ou `markup` | Le sens dans lequel `discountBp` modifie le prix de liste. `discount` lorsqu'il n'y a pas de pourcentage.                                   |
| `source`     | chaîne                 | Le jeu de règles qui a défini le prix : voir ci-dessous.                                                                                    |
| `label`      | chaîne                 | La formulation que les acheteurs voient à côté du prix.                                                                                     |
| `remark`     | chaîne ou null         | La note de prix convenu lorsque ce client est autorisé à la voir. Sinon `null`. La note interne réservée au personnel n'est jamais incluse. |

| `source`   | Signification                             | Exemple `label`                     |
| ---------- | ----------------------------------------- | ----------------------------------- |
| `fixed`    | Le prix convenu du client                 | Prix convenu                        |
| `product`  | Pourcentage sur un produit                | 10 % de réduction sur votre liste   |
| `category` | Pourcentage sur une catégorie             | 12,5 % de réduction sur votre liste |
| `brand`    | Pourcentage sur une marque                | 15 % de hausse sur votre liste      |
| `account`  | Pourcentage sur la liste entière          | 10 % de hausse sur votre liste      |
| `list`     | Prix de liste, sans ajustement            | (vide)                              |
| `unpriced` | Le produit n'a pas de prix pour l'instant | (vide)                              |

## Vérifier le prix d'un client

**GET** `/api/v1/prices`

Ce que le client paie pour un SKU à une quantité, et pourquoi. Utilisez-le pour répondre à la question « Oakfield paie combien pour 10 boîtes de HX-8510 ? ».

| Nom        | Type   | Obligatoire | Description                              |
| ---------- | ------ | ----------- | ---------------------------------------- |
| `customer` | chaîne | oui         | Identifiant du client ou code de compte. |
| `sku`      | chaîne | oui         | Le SKU, en ignorant la casse.            |
| `qty`      | entier | non         | 1 à 1 000 000. Valeur par défaut 1.      |

```bash
curl "https://tradecatalog.app/api/v1/prices?customer=OAK01&sku=HX-8510&qty=10" \
  -H "Authorization: Bearer $TRADECATALOG_API_KEY"
```

```js
const params = new URLSearchParams({
  customer: 'OAK01',
  sku: 'HX-8510',
  qty: '10',
})
const res = await fetch(`https://tradecatalog.app/api/v1/prices?${params}`, {
  headers: { Authorization: `Bearer ${process.env.TRADECATALOG_API_KEY}` },
})
const { data: price, currency } = await res.json()
```

```json
{
  "data": {
    "customer": {
      "id": "7d2e4f0a-1b3c-4d5e-8f90-a1b2c3d4e5f6",
      "accountCode": "OAK01",
      "name": "Oakfield Joinery"
    },
    "sku": "HX-8510",
    "name": "Hex bolt M10 x 50 zinc",
    "qty": 10,
    "unit": 1573,
    "line": 15730,
    "list": 1850,
    "discountBp": 1500,
    "source": "brand",
    "label": "15% off your list",
    "remark": null
  },
  "currency": "GBP"
}
```

| Statut | Code               | Quand                                                         |
| ------ | ------------------ | ------------------------------------------------------------- |
| 400    | `validation_error` | `customer` ou `sku` manquant, ou `qty` en dehors de la plage. |
| 404    | `not_found`        | Aucun client correspondant, ou aucun produit avec ce SKU.     |

Cet endpoint vérifie uniquement le prix. Il ne vérifie pas si le client peut voir le produit. Pour cela, utilisez [`GET /products/{id}?customer=…`](/docs/api/products#get-one-product).

## Prix pour plusieurs produits

Pour tarifer une page entière de produits pour un client, utilisez `GET /products?customer=OAK01`. Chaque ligne a son `price` qui est le prix de ce client, et les produits réservés aux autres clients sont ignorés. Voir [Produits](/docs/api/products#list-or-search-products).

## Modification des prix

- **Listes de prix** changent avec les modifications de prix, donc les clients sont informés. Envoyez de nouveaux prix avec [`PUT /products`](/docs/api/products#create-or-update-products) ; ils sont ajoutés à un brouillon que vous publiez dans l'application.
- **Marge, remises et prix convenus** sont définis par client dans l'application. Consultez-les avec [`GET /customers/{id}`](/docs/api/customers#get-one-customer).
- **Prix convenus** pour plusieurs clients sont définis avec [`PUT /agreed-prices`](/docs/api/prices#set-agreed-prices).
- Le **pourcentage de la liste complète** d'un client peut être défini avec [`PUT /customers`](/docs/api/customers#create-or-update-customers) (`discountBp`, et `adjustment` facultatif).

## Définir les prix convenus

**PUT** `/api/v1/agreed-prices`

Créez ou remplacez les prix convenus pour les clients et produits existants. Envoyez jusqu'à 2 000 lignes. Les prix sont en unités mineures entières. Laissez `commit` à false pour un aperçu : rien n'est enregistré, et chaque compte ou SKU inconnu revient comme erreur de ligne. Mettez `commit` à true pour enregistrer. L'enregistrement ferme le prix convenu actuel et insère le nouveau à partir de maintenant, par lots de 100. Pro uniquement.

| Nom           | Type    | Obligatoire | Description                                                            |
| ------------- | ------- | ----------- | ---------------------------------------------------------------------- |
| `commit`      | boolean | non         | `true` enregistre. La valeur par défaut `false` ne fait qu'apercevoir. |
| `prices`      | tableau | oui         | 1 à 2 000 prix.                                                        |
| `accountCode` | chaîne  | oui         | Code de compte client, indépendamment de la casse.                     |
| `sku`         | chaîne  | oui         | SKU du produit, indépendamment de la casse.                            |
| `price`       | entier  | oui         | Prix convenu en unités mineures.                                       |
| `note`        | chaîne  | non         | Jusqu'à 200 caractères, affiché au personnel.                          |

```bash
curl -X PUT https://tradecatalog.app/api/v1/agreed-prices \
  -H "Authorization: Bearer $TRADECATALOG_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"commit":false,"prices":[{"accountCode":"OAK01","sku":"HX-8510","price":1250,"note":"Contract"}]}'
```

| Statut | Code               | Quand                                                                 |
| ------ | ------------------ | --------------------------------------------------------------------- |
| 400    | `validation_error` | Un prix manque, est négatif ou dépasse la limite.                     |
| 403    | `read_only`        | L'espace de travail n'est pas en Pro, ou la clé est en lecture seule. |
