# Commandes

> Créez des commandes clients, enregistrez la réalisation et les corrections, et ajoutez un suivi de livraison facultatif.

Les acheteurs passent des commandes dans leur portail commercial. Les fournisseurs gèrent les factures. Ils peuvent proposer en option [paiements clients hébergés par Stripe](/docs/guides/customer-payments) après avoir confirmé le total final. Les demandes de commande et le crédit commercial restent disponibles. Utilisez l'API pour récupérer les commandes dans votre ERP ou système de comptabilité et pour maintenir leur statut à jour.

## Statuts des commandes

| Statut         | Signification                                 |
| -------------- | --------------------------------------------- |
| `new`          | Passée                                        |
| `pending`      | En attente                                    |
| `acknowledged` | Consultée et en cours de préparation          |
| `processed`    | Prête pour l'expédition                       |
| `dispatched`   | Expédiée par le fournisseur                   |
| `completed`    | Fournisseur marqué comme terminé              |
| `cancelled`    | Annulée avec une raison visible par le client |

Le personnel peut sauter les étapes intermédiaires de préparation. Le fait de revenir en arrière ou de rouvrir une commande terminée ou annulée nécessite un `correctionReason` interne. L'historique précédent reste. Les corrections n'altèrent jamais l'état de paiement. Lisez `revision` et envoyez `expectedRevision` avec les mises à jour pour rejeter les modifications obsolètes.

## Créer une commande pour un client

**POST** `/api/v1/orders/preview`

Exige `orders.create` et l'accès au client. Envoyez `customerAccountId` et `lines` contenant `productId` et un entier `qty` (1 à 1 000 000). Choisissez chaque produit ordinaire une seule fois, au maximum 100 lignes. Une ligne peut inclure un `configuration` optionnel contenant les clés de mesure et de choix retournées ; différentes configurations d'un même produit restent séparées. Un `lineKey` optionnel donne à chaque ligne de prévisualisation une identité stable. Un `additionalDiscountBp` supplémentaire nécessite une autorisation de tarification et `discountReason`. Les lectures de commande configurées incluent un `configuration` instantané immuable avec les entrées, la spécification, la version, le fractionnement et les frais. La prévisualisation retourne `total`, `fingerprint`, les prix actuels du client et la devise. Les comptes en pause, les produits indisponibles et les quantités inférieures au minimum sont refusés. Un catalogue uniquement prix accepte toujours une commande entrée ici ; les acheteurs ne peuvent pas en envoyer un eux-mêmes tant que cela est désactivé.

**POST** `/api/v1/orders`

Envoyez le même client et les mêmes lignes, `expectedTotal` depuis la prévisualisation, `expectedBasket` défini sur son empreinte, et un UUID frais `requestKey`. Les `poNumber` facultatifs (60 caractères) et les `notes` visibles par le client (2 000 caractères) sont pris en charge. Réutilisez exactement la même demande et la même clé après une réponse incertaine : une tentative de réessai réussie retourne la commande originale au lieu d'en créer une autre. Les prix modifiés nécessitent une autre revue. Le résultat est `placed` avec `orderId` et `reference`, ou `prices_changed`, `empty` ou `unavailable`.

Le personnel agit sous son propre identifiant. Le panier du client reste intact. Les contacts par courriel du client peuvent recevoir une copie ; cela ne génère pas de paiement. Le représentant commercial a par défaut les droits de création ; les mises à jour de traitement et de livraison nécessitent la permission séparée `orders.process`.

## Livraison et suivi

**PATCH** `/api/v1/orders/{id}/delivery`

Requiert `orders.process`. Envoyez `deliveryMethod` : `unspecified`, `courier`, `own_delivery` ou `collection`. Les `courierName` facultatifs (100 caractères), `trackingReference` (120 caractères) et `trackingUrl` (1 000 caractères) sont visibles par le client. Les liens de suivi doivent utiliser HTTPS sans identifiants de connexion intégrés. Les détails facultatifs omis les effacent. Envoyez `expectedRevision` depuis la commande actuelle. Il y a un enregistrement de livraison par commande ; les intégrations de transporteurs et les envois fractionnés ne sont pas incluses. Les détails de transporteur vides n'impliquent jamais une livraison interne.

## Liste des commandes

**GET** `/api/v1/orders`

Commandes les plus récentes en premier. La liste couvre les **200 commandes les plus récentes** correspondant à vos filtres. Pour récupérer de nouvelles commandes, interrogez avec `status=new`.

| Nom        | Type   | Obligatoire | Description                            |
| ---------- | ------ | ----------- | -------------------------------------- |
| `status`   | chaîne | non         | L'un des statuts ci-dessus.            |
| `customer` | chaîne | non         | Identifiant client ou code de compte.  |
| `limit`    | entier | non         | 1 à 100. Valeur par défaut 100.        |
| `offset`   | entier | non         | Lignes à ignorer. Valeur par défaut 0. |

```bash
curl "https://tradecatalog.app/api/v1/orders?status=new" \
  -H "Authorization: Bearer $TRADECATALOG_API_KEY"
```

```js
const res = await fetch('https://tradecatalog.app/api/v1/orders?status=new', {
  headers: { Authorization: `Bearer ${process.env.TRADECATALOG_API_KEY}` },
})
const { data: orders } = await res.json()
```

```json
{
  "data": [
    {
      "id": "5b6c7d8e-9f0a-4b1c-8d2e-3f4a5b6c7d8e",
      "reference": "SO-000142",
      "status": "new",
      "total": 31460,
      "currency": "GBP",
      "poNumber": "PO-88231",
      "createdAt": "2026-09-27T07:48:12.000Z",
      "customer": "Oakfield Joinery"
    }
  ],
  "nextOffset": null
}
```

| Champ       | Type           | Description                                                                                      |
| ----------- | -------------- | ------------------------------------------------------------------------------------------------ |
| `id`        | chaîne         | Identifiant de la commande.                                                                      |
| `reference` | chaîne         | Numéro de commande vu par les acheteurs, comme `SO-000142`.                                      |
| `status`    | chaîne         | Voir [statuts](#order-statuses).                                                                 |
| `total`     | entier         | Montant total de la commande selon les prix (voir `taxLabel`), en unités mineures de `currency`. |
| `currency`  | chaîne         | Date à laquelle la commande a été passée.                                                        |
| `poNumber`  | chaîne ou null | Numéro de commande du client.                                                                    |
| `createdAt` | chaîne         | Date à laquelle la commande a été passée.                                                        |
| `customer`  | chaîne         | Nom du client. Obtenez le code de compte à partir de `GET /orders/{id}`.                         |

## Obtenir une commande

**GET** `/api/v1/orders/{id}`

La commande avec ses lignes et son historique des statuts.

| Nom  | Dans   | Type   | Requis | Description                 |
| ---- | ------ | ------ | ------ | --------------------------- |
| `id` | chemin | chaîne | oui    | Identifiant de la commande. |

```bash
curl https://tradecatalog.app/api/v1/orders/5b6c7d8e-9f0a-4b1c-8d2e-3f4a5b6c7d8e \
  -H "Authorization: Bearer $TRADECATALOG_API_KEY"
```

```js
const res = await fetch(`https://tradecatalog.app/api/v1/orders/${orderId}`, {
  headers: { Authorization: `Bearer ${process.env.TRADECATALOG_API_KEY}` },
})
const { data: order } = await res.json()
```

```json
{
  "data": {
    "id": "5b6c7d8e-9f0a-4b1c-8d2e-3f4a5b6c7d8e",
    "customerAccountId": "7d2e4f0a-1b3c-4d5e-8f90-a1b2c3d4e5f6",
    "placedBy": "Fq3L0cZ7tN1pW9aYx2Rk",
    "reference": "SO-000142",
    "poNumber": "PO-88231",
    "notes": "Deliver to the Hebden Road site, gate 2.",
    "status": "new",
    "cancelReason": null,
    "currency": "GBP",
    "total": 31460,
    "createdAt": "2026-09-27T07:48:12.000Z",
    "updatedAt": "2026-09-27T07:48:12.000Z",
    "customer": { "name": "Oakfield Joinery", "accountCode": "OAK01" },
    "lines": [
      {
        "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d",
        "orderId": "5b6c7d8e-9f0a-4b1c-8d2e-3f4a5b6c7d8e",
        "productId": "3f8a2c1e-9b4d-4e6f-a7c8-1d2e3f4a5b6c",
        "sku": "HX-8510",
        "name": "Hex bolt M10 x 50 zinc",
        "unitLabel": "Box of 100",
        "qty": 20,
        "unitPrice": 1573,
        "listPrice": 1850,
        "priceLabel": "Brand discount −15%",
        "lineTotal": 31460
      }
    ],
    "events": [
      {
        "status": "new",
        "note": null,
        "createdAt": "2026-09-27T07:48:12.000Z",
        "actor": "Sam Patel"
      }
    ]
  }
}
```

| Champ                                       | Description                                                                                                                                                                           |
| ------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `customerAccountId`                         | L'identifiant du client.                                                                                                                                                              |
| `placedBy`                                  | L'identifiant de la personne qui l'a saisie. `source` distingue les commandes des clients et celles du personnel.                                                                     |
| `notes`                                     | Les notes de livraison ou d'commande du client.                                                                                                                                       |
| `cancelReason`                              | La raison de l'annulation, si applicable.                                                                                                                                             |
| `lines`                                     | Ce qui a été commandé à quel prix. Une capture d'écran : les changements de prix ultérieurs n'affectent pas cette valeur. `productId` est `null` si le produit a depuis été supprimé. |
| `lines[].priceLabel`                        | La règle qui a fixé le prix, comme sur [Prix](/docs/api/prices#the-price-object).                                                                                                     |
| `lines[].priceLabel` est `Price on request` | Le produit n'a pas de prix : `unitPrice` et `lineTotal` sont `0` et la ligne n'est pas dans `total`. Le fournisseur confirme son prix.                                                |
| `events`                                    | Historique des statuts, du plus ancien au plus récent. `actor` est le nom de la personne, ou `null`.                                                                                  |

## Mettre à jour ou corriger une commande

**PATCH** `/api/v1/orders/{id}`

Modifie le statut d'une commande. Retourne la commande mise à jour sous la même forme que `GET /orders/{id}`.

- Les mouvements normaux vers l'avant suivent les étapes de traitement ci-dessus. Les autres mouvements nécessitent une raison de correction privée ; un statut actuel répété ne provoque aucun changement.
- L'annulation nécessite un `note`, qui est affiché au client comme raison.
- Les changements de statut peuvent envoyer un courriel aux contacts clients, y compris la note publique. Un courriel généré ne prouve pas la livraison.
- Le changement est enregistré dans l'historique de la commande contre la personne ayant effectué la clé.

| Nom      | Dans   | Type   | Requis | Description                                            |
| -------- | ------ | ------ | ------ | ------------------------------------------------------ |
| `id`     | chemin | chaîne | oui    | Identifiant de la commande.                            |
| `status` | corps  | chaîne | oui    | Nouveau statut.                                        |
| `note`   | corps  | chaîne | non    | Jusqu'à 1 000 caractères. Requis lors de l'annulation. |

```bash
curl -X PATCH https://tradecatalog.app/api/v1/orders/5b6c7d8e-9f0a-4b1c-8d2e-3f4a5b6c7d8e \
  -H "Authorization: Bearer $TRADECATALOG_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "status": "dispatched", "note": "DPD tracking 1554 2210 0931" }'
```

```js
const res = await fetch(`https://tradecatalog.app/api/v1/orders/${orderId}`, {
  method: 'PATCH',
  headers: {
    Authorization: `Bearer ${process.env.TRADECATALOG_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ status: 'acknowledged' }),
})
const { data: order } = await res.json()
```

| Statut | Code                 | Quand                                                                                                                                   |
| ------ | -------------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
| 400    | `invalid_request`    | Le mouvement n'est pas autorisé, par exemple « Une commande reçue ne peut pas être marquée comme terminée », ou l'annulation sans note. |
| 403    | `insufficient_scope` | La clé est en lecture seule.                                                                                                            |
| 404    | `not_found`          | Aucune commande de ce type dans cet espace de travail.                                                                                  |

La tentative de `PATCH` qui a déjà réussi retourne `400 invalid_request`, car la commande a évolué. Vérifiez le statut de la commande avant de la considérer comme une erreur.

## Non disponible

L'API ne peut pas passer de commandes pour un acheteur, ni modifier les lignes, les quantités ou les prix d'une commande. Les acheteurs passent leurs commandes dans leur portail de commerce ; les modifications d'une commande sont convenues avec le client et facturées dans votre propre système.

Les listes de commandes incluent également `paymentStatus` : null pour les conditions habituelles de compte, ou le statut de paiement Stripe facultatif. Le statut de livraison est séparé et ne prouve jamais le paiement. La page détaillée de la commande est l'endroit où il faut revoir le total final confirmé et approuver le paiement. L'API ne peut pas créer ou approuver un paiement.

`internalNote` (2 000 caractères) et `correctionReason` (1 000 caractères) sont des champs d'historique réservés au personnel. `note` reste public, y compris les anciennes notes. Envoyez `expectedRevision` pour rejeter une édition concurrente avec une demande non valide. Les lectures de l'API pour le personnel incluent l'historique privé ; les lectures des outils d'acheteur et du portail excluent les identités du personnel et les champs privés. L'annulation nécessite toujours une raison publique et le retrait ou le remboursement intégral d'un paiement en attente.
