# Bestellingen

> Maak klantbestellingen aan, registreer uitvoering en correcties en voeg optionele leveringsvolgnummer toe.

Kopers plaatsen bestellingen in hun handelsportaal. Leveranciers regelen de facturering. Ze kunnen [Stripe-gestuurde klantbetalingen](/docs/guides/customer-payments) optie aanbieden na het bevestigen van het eindbedrag. Bestellingsverzoeken en handelskrediet blijven beschikbaar. Gebruik de API om bestellingen in te halen in uw ERP- of boekhoudsysteem en om hun status actueel te houden.

## Bestelstatussen

| Status         | Betekenis                               |
| -------------- | --------------------------------------- |
| `new`          | Geplaatst                               |
| `pending`      | In afwachting                           |
| `acknowledged` | Gezien en in behandeling                |
| `processed`    | Klaar voor verzending                   |
| `dispatched`   | Verzonden door de leverancier           |
| `completed`    | Leverancier heeft voltooid              |
| `cancelled`    | Geannuleerd met een klantgerichte reden |

Medewerkers mogen tussentijdse verwerkingstappen overslaan. Teruggaan of opnieuw openen van een voltooide of geannuleerde bestelling vereist een interne `correctionReason`. Vorige geschiedenis blijft behouden. Correcties veranderen nooit de betalingsstatus. Lees `revision` en stuur `expectedRevision` met updates om verouderde bewerkingen te weigeren.

## Maak een bestelling voor een klant

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

Vereist `orders.create` en toegang tot de klant. Stuur `customerAccountId` en `lines` met `productId` en geheel getal `qty` (1 tot 1.000.000). Kies elk gewoon product maximaal één keer, maximaal 100 regels. Een regel kan optioneel `configuration` bevatten met de geretourneerde meting en keuzesleutels; verschillende configuraties van één product blijven apart. Optioneel `lineKey` geeft elke voorbeeldregel een stabiele oproepgeveridentiteit. Een extra `additionalDiscountBp` heeft prijsbevoegdheid en `discountReason` nodig. Geconfigureerde bestellingen bevatten een onveranderlijke `configuration`-snapshot met invoer, specificatie, versie, afronding en kosten. De voorbeeldweergave retourneert `total`, `fingerprint`, huidige klantprijzen en valuta. Gepauzeerde rekeningen, niet beschikbare producten en hoeveelheden onder het minimum worden afgewezen. Een prijs alleen catalogus accepteert nog steeds een bestelling die hier is ingevoerd; kopers kunnen er geen zelf sturen terwijl dat uitgeschakeld is.

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

Stuur dezelfde klant en regels, `expectedTotal` van de voorbeeldweergave, `expectedBasket` ingesteld op zijn vingerafdruk, en een nieuw UUID `requestKey`. Optioneel `poNumber` (60 tekens) en klantzichtbare `notes` (2000 tekens) worden ondersteund. Hergebruik precies dezelfde aanvraag en sleutel na een onzeker antwoord: een geslaagde heropstart retourneert de oorspronkelijke bestelling in plaats van een nieuwe te maken. Veranderde prijzen vereisen een andere beoordeling. Het resultaat is `placed` met `orderId` en `reference`, of `prices_changed`, `empty` of `unavailable`.

Medewerkers handelen onder hun eigen identiteit. De klantens mandje blijft onaangetast. Klantemailcontacten kunnen een kopie ontvangen; dit veroorzaakt geen betaling. Verkoopmedewerker heeft standaard creatiebevoegdheid; verwerking en leveringsupdates vereisen de aparte `orders.process`-bevoegdheid.

## Levering en tracking

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

Vereist `orders.process`. Stuur `deliveryMethod`: `unspecified`, `courier`, `own_delivery` of `collection`. Optioneel `courierName` (100 tekens), `trackingReference` (120 tekens) en `trackingUrl` (1000 tekens) zijn zichtbaar voor de klant. Trackingslinks moeten HTTPS gebruiken zonder ingebedde inloggegevens. Opgelaten optionele details wissen ze. Stuur `expectedRevision` van de huidige bestelling. Er is één leveringsrecord per bestelling; draagverzendingen en gesplitste verzendingen zijn niet opgenomen. Lege leveranciersgegevens betekenen nooit eigen levering.

## Lijst met bestellingen

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

Bestellingen nieuwste eerst. De lijst omvat de **200 meest recente** bestellingen die aan uw filters voldoen. Om nieuwe bestellingen op te halen, poll met `status=new`.

| Naam       | Type    | Verplicht | Beschrijving                        |
| ---------- | ------- | --------- | ----------------------------------- |
| `status`   | string  | niet      | Een van de statussen hierboven.     |
| `customer` | string  | niet      | Klant id of accountcode.            |
| `limit`    | integer | niet      | 1 tot 100. Standaard 100.           |
| `offset`   | integer | niet      | Rijen om te overslaan. Standaard 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
}
```

| Veld        | Type           | Beschrijving                                                                              |
| ----------- | -------------- | ----------------------------------------------------------------------------------------- |
| `id`        | string         | Bestelingsid.                                                                             |
| `reference` | string         | Bestelnummer dat kopers zien, zoals `SO-000142`.                                          |
| `status`    | string         | Zie [statussen](#order-statuses).                                                         |
| `total`     | integer        | Totale bestelling zoals aangeprijsd (zie `taxLabel`), in mindere eenheden van `currency`. |
| `currency`  | string         | Vastgesteld toen de bestelling werd geplaatst.                                            |
| `poNumber`  | string of null | Het aankoopordernummer van de koper.                                                      |
| `createdAt` | string         | Wanneer het is geplaatst.                                                                 |
| `customer`  | string         | Naam van de klant. Haal de accountcode op uit `GET /orders/{id}`.                         |

## Een bestelling ophalen

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

De bestelling met zijn lijnen en statusgeschiedenis.

| Naam | In  | Type   | Verplicht | Beschrijving  |
| ---- | --- | ------ | --------- | ------------- |
| `id` | pad | string | ja        | Bestelingsid. |

```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"
      }
    ]
  }
}
```

| Veld                                       | Beschrijving                                                                                                                                                   |
| ------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `customerAccountId`                        | Het id van de klant.                                                                                                                                           |
| `placedBy`                                 | Id van de persoon die het heeft ingevoerd. `source` onderscheidt koper- en medewerkersbestellingen.                                                            |
| `notes`                                    | De leverings- of bestelnotities van de koper.                                                                                                                  |
| `cancelReason`                             | Waarom het is geannuleerd, indien van toepassing.                                                                                                              |
| `lines`                                    | Wat is besteld tegen welke prijs. Een snapshot: latere prijsveranderingen veranderen dit nooit. `productId` is `null` als het product sindsdien is verwijderd. |
| `lines[].priceLabel`                       | De regel die de prijs heeft ingesteld, zoals op [Prijzen](/docs/api/prices#the-price-object).                                                                  |
| `lines[].priceLabel` is `Price on request` | Het product heeft geen prijs: `unitPrice` en `lineTotal` zijn `0` en de regel is niet in `total`. De leverancier bevestigt de prijs.                           |
| `events`                                   | Statusgeschiedenis, oudste eerst. `actor` is de naam van de persoon, of `null`.                                                                                |

## Een bestelling bijwerken of corrigeren

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

Wijzigt de status van een bestelling. Geeft de bijgewerkte bestelling terug in dezelfde vorm als `GET /orders/{id}`.

- Normale voortgang volgt de uitvoeringsstappen hierboven. Andere bewegingen vereisen een privé correctieredenering; een herhaalde huidige status heeft geen verandering.
- Annuleren vereist een `note`, wat aan de koper wordt getoond als reden.
- Statuswijzigingen kunnen e-mails sturen naar klantcontacten, inclusief de openbare opmerking. Een gegenereerde e-mail bewijst geen levering.
- De wijziging wordt vastgelegd in de geschiedenis van de bestelling tegen de persoon die de sleutel heeft gemaakt.

| Naam     | In   | Type   | Verplicht | Beschrijving                                     |
| -------- | ---- | ------ | --------- | ------------------------------------------------ |
| `id`     | pad  | string | ja        | Bestelnummer.                                    |
| `status` | body | string | ja        | De nieuwe status.                                |
| `note`   | body | string | nee       | Maximaal 1.000 tekens. Verplicht bij annulering. |

```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()
```

| Status | Code                 | Wanneer                                                                                                                                                |
| ------ | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
| 400    | `invalid_request`    | De beweging is niet toegestaan, bijvoorbeeld “Een bestelling die is ontvangen kan niet als voltooid worden gemarkeerd”, of annuleren zonder opmerking. |
| 403    | `insufficient_scope` | De sleutel is alleen-lezen.                                                                                                                            |
| 404    | `not_found`          | Er is geen dergelijke bestelling in dit werkruimte.                                                                                                    |

Herproberen van een `PATCH` die al is geslaagd retourneert `400 invalid_request`, omdat de bestelling verder is gegaan. Controleer de status van de bestelling voordat u dit als een fout beschouwt.

## Niet beschikbaar

De API kan geen bestellingen plaatsen voor een koper, of wijzigen in de lijnen, hoeveelheden of prijzen van een bestelling. Kopers plaatsen bestellingen in hun handelsportaal; wijzigingen in een bestelling worden overeengekomen met de klant en worden in uw eigen systeem gefactureerd.

Bestellingslijsten bevatten ook `paymentStatus`: null voor gewone rekeningvoorwaarden, of de optionele Stripe betalingsstatus. Uitvoeringsstatus is apart en bewijst nooit betaling. De pagina met bestelgegevens is de plek om het eindige bevestigde totaal te bekijken en betaling te goedkeuren. De API kan geen betaling aanmaken of goedkeuren.

`internalNote` (2.000 tekens) en `correctionReason` (1.000 tekens) zijn staff-only geschiedenisvelden. `note` blijft openbaar, inclusief oude opmerkingen. Stuur `expectedRevision` om een tegelijkertijd bewerking met een ongeldige aanvraag te weigeren. Staff API-lezen omvatten privé-geschiedenis; koper tools en portal-lezen uitsluiten staff-identiteiten en privé-velden. Annuleren vereist nog steeds een openbare reden en terugtrekking of volledige terugbetaling van een openstaande betaling.
