# Prijzen

> Wat een klant betaalt voor een SKU bij een hoeveelheid, en precies welk regelset die prijs is.

TradeCatalog berekent elke klantprijs op één plek, op dezelfde manier voor de app, de handelssite, MCP en de API. Je hoeft nooit zelf prijzen te berekenen.

Geconfigureerde producten, gemeten tarieven, hoeveelheid drempels, net extra's en expliciete afronding gebruiken ook deze prijsdienst. Geef een volledige lijn aan met `POST /products/{id}/quote`; een ontbrekende prijs kan betekenen `configurationRequired`, terwijl een geldige ongeprijsde specificatie Price on request is. Gebruik de teruggekregen lijntotaal. Zie [trade catalogue setup](/docs/guides/trade-catalogues) voor voorrang, net behandeling en publicatie.

## Hoe prijzen worden berekend

De meest specifieke regel wint, en **aanpassingen staan nooit op elkaar**:

1. **Overeengekomen prijs.** Als de klant een overeengekomen (vaste) prijs voor het product heeft, is dat de prijs.
2. Anders de **lijstprijs met een percentage**, het eerste dat van toepassing is. Een percentage is een korting (afgetrokken) of een toevoeging (toegevoegd):
   1. het percentage van de klant voor dat **product**
   2. het percentage van de klant voor de **categorie** van het product (de dichtstbijzijnde ouder wint)
   3. het percentage van de klant voor de **merk** van het product
   4. het eigen percentage van de klant op de **gehele lijst**
3. Anders de **lijstprijs**.

Een categorie korting van 10% en een totale lijst markup van 5% geven 10% korting, niet beide. De eenheid is nooit onder nul, en wordt half omhoog afgerond naar een geheel minderheidseenheid. De lijntotaal is `unit × qty`.

Lijstprijzen en overeengekomen prijzen zijn versiebeheerd per datum. De API geeft altijd de prijs die **nu geldt**. Toekomstige prijzen van een geplande prijsverandering gelden vanaf hun datum.

## Prijslijsten en catalogus toegang

Standard is de leveranciersmastercatalogus. Andere prijslijsten kunnen producten bevatten met prijzen en producten die op een offerte wachten. Een ontbrekende prijs op een toegewezen lijst valt niet terug naar Standard.

In de werkruimte, open **Prijslijsten** en kies een genoemde lijst. **Toegewezen klanten kunnen zien** heeft twee keuzes:

- **Alle toegestane catalogusproducten** is de standaardinstelling. De lijst stelt prijzen vast; toegewezen kopers kunnen ook andere toegestane producten bekijken met 'Prijs op verzoek'.
- **Alleen producten op deze lijst** beperkt elke toegewezen koper's catalogus, zoekopdrachten, productkoppelingen, downloads en nieuwe bestellingen tot die lijst's producten. Andere productzichtbaarheid en goedgekeurde lijstbeperkingen blijven van toepassing. Klanten op andere lijsten behouden hun bestaande toegang.

Elke lijst toont het aantal klanten, geprijsde producten, ongeprijsde producten en het totaal aantal producten. Totaal omvatten inactieve producten, dus ze kunnen groter zijn dan wat een koper momenteel ziet. Een nulprijs telt als geprijsd. Een toekomstige prijs telt als ongeprijsd tot aan de startdatum.

Om producten toe te voegen, selecteer ze op **Producten**, kies **Toevoegen aan prijslijst**, en kopieer standaardprijzen met een optionele percentage of kies **Toevoegen zonder prijzen**. Bestaande leden behouden hun prijzen. Producten zonder standaardprijs worden toegevoegd als 'Prijs op verzoek'. Op de pagina van een genaamde lijst, opslaan van een SKU met een lege prijs voegt het toe zonder prijs of verwijdert de huidige prijs. **Verwijderen** verwijdert het van die lijst terwijl het hoofdproduct en vorige bestellingen behouden blijven. Los eventuele uitstaande prijswijziging voor dat product eerst op.

Deze controle vereist Pro en globale prijsbewerkingsrechten. Bestaande lijsten behouden hun catalogus toegang tot het moment dat een leverancier deze verandert. API-importen in een genaamde lijst bevatten gewone productrijen zonder prijs als ongeprijsde leden; een leeg veld in een prijs-alleen import verandert de toegang niet.

## Het prijsobject

Elk product heeft de vorm van `price`. `GET /prices` retourneert dezelfde velden op het bovenste niveau van `data`.

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

| Veld         | Type                   | Beschrijving                                                                                                                                 |
| ------------ | ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
| `unit`       | geheel getal of null   | Prijs voor één eenheid, in mindere eenheden. `null` als het product geen prijs heeft.                                                        |
| `line`       | geheel getal of null   | `unit × qty`.                                                                                                                                |
| `list`       | geheel getal of null   | De huidige lijsprijs.                                                                                                                        |
| `discountBp` | geheel getal           | De aangepaste waarde, in basispunten. `0` voor overeengekomen en lijsprijzen.                                                                |
| `adjustment` | `discount` of `markup` | Welke richting `discountBp` de lijsprijs verandert. `discount` wanneer er geen percentage is.                                                |
| `source`     | string                 | Welke regelset de prijs bepaalt: zie hieronder.                                                                                              |
| `label`      | string                 | Woorden die kopers naast de prijs zien.                                                                                                      |
| `remark`     | string of null         | De overeengekomen prijs opmerking wanneer die klant deze mag zien. Anders `null`. De interne notitie voor medewerkers wordt nooit opgenomen. |

| `source`   | Betekenis                            | Voorbeeld `label`         |
| ---------- | ------------------------------------ | ------------------------- |
| `fixed`    | De overeengekomen prijs van de klant | Overeengekomen prijs      |
| `product`  | Percentage op één product            | 10% korting op uw lijst   |
| `category` | Percentage op een categorie          | 12,5% korting op uw lijst |
| `brand`    | Percentage op een merk               | 15% boven uw lijst        |
| `account`  | Percentage op de hele lijst          | 10% boven uw lijst        |
| `list`     | Lijsprijs, geen aanpassing           | (leeg)                    |
| `unpriced` | Het product heeft nog geen prijs     | (leeg)                    |

## Controleer een klantprijs

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

Wat een klant betaalt voor één SKU in een hoeveelheid, en waarom. Gebruik het om te beantwoorden:

| Naam       | Type         | Verplicht | Beschrijving                  |
| ---------- | ------------ | --------- | ----------------------------- |
| `customer` | string       | ja        | Klant ID of accountcode.      |
| `sku`      | string       | ja        | De SKU, hoofdlettergevoelig.  |
| `qty`      | geheel getal | nee       | 1 tot 1.000.000. Standaard 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"
}
```

| Status | Code               | Wanneer                                                 |
| ------ | ------------------ | ------------------------------------------------------- |
| 400    | `validation_error` | `customer` of `sku` ontbrekend, of `qty` buiten bereik. |
| 404    | `not_found`        | Geen dergelijke klant, of geen product met die SKU.     |

Deze eindpunt controleert alleen de prijs. Het controleert niet of de klant het product kan zien. Gebruik hiervoor [`GET /products/{id}?customer=…`](/docs/api/products#get-one-product).

## Prijzen voor veel producten

Gebruik `GET /products?customer=OAK01` om de prijs van een gehele pagina producten voor één klant te bepalen. Elke rij's `price` is die klant's prijs, en klant-specifieke producten voor andere klanten worden uitgelaten. Zie [Producten](/docs/api/products#list-or-search-products).

## Prijzen wijzigen

- **Lijsprijzen** veranderen door prijsveranderingen, zodat klanten op de hoogte komen. Stuur nieuwe prijzen met [`PUT /products`](/docs/api/products#create-or-update-products); ze gaan in een concept dat u in de app publiceert.
- **Kostenopslagen, kortingen en overeengekomen prijzen** worden per klant in de app ingesteld. Lees ze met [`GET /customers/{id}`](/docs/api/customers#get-one-customer).
- **Overeengekomen prijzen** voor veel klanten worden ingesteld met [`PUT /agreed-prices`](/docs/api/prices#set-agreed-prices).
- Een klant's **gehele lijst percentage** kan worden ingesteld met [`PUT /customers`](/docs/api/customers#create-or-update-customers) (`discountBp`, en optioneel `adjustment`).

## Overeengekomen prijzen instellen

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

Maak of vervang overeengekomen prijzen voor bestaande klanten en producten. Stuur maximaal 2.000 rijen. Prijzen zijn gehele minoreenheden. Laat `commit` onwaar zijn om te previewen: niets wordt opgeslagen en elke onbekende account of SKU wordt als rijfout teruggestuurd. Zet `commit` op waar om op te slaan. Slaan sluit de overeengekomen prijs nu af en voegt de nieuwe toe vanaf dit moment, in batches van 100. Alleen voor Pro.

| Naam          | Type    | Vereist | Beschrijving                                      |
| ------------- | ------- | ------- | ------------------------------------------------- |
| `commit`      | boolean | nee     | `true` opslaan. Standaard `false` preview alleen. |
| `prices`      | array   | ja      | 1 tot 2.000 prijzen.                              |
| `accountCode` | string  | ja      | Klantaccountcode, hoofdletteronafhankelijk.       |
| `sku`         | string  | ja      | Product SKU, hoofdletteronafhankelijk.            |
| `price`       | integer | ja      | Overeengekomen prijs in minoreenheden.            |
| `note`        | string  | nee     | Maximaal 200 tekens, zichtbaar voor medewerkers.  |

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

| Status | Code               | Wanneer                                                      |
| ------ | ------------------ | ------------------------------------------------------------ |
| 400    | `validation_error` | Een prijs ontbreekt, negatief of boven de limiet.            |
| 403    | `read_only`        | De werkruimte is niet op Pro, of de sleutel is alleen-lezen. |
