# Preise

> Was ein Kunde für eine SKU bei einer Menge zahlt und genau welches Regelsetz den Preis bestimmt.

TradeCatalog berechnet jeden Kundenpreis an einem Ort, auf die gleiche Weise für die App, den Handelsportale, MCP und die API. Sie berechnen Preise nie selbst.

Konfigurierte Produkte, gemessene Raten, Mengenschwellen, Netto-Zusätze und explizite Rundung verwenden auch diesen Preis-Service. Ein vollständiges Zeilenangebot mit `POST /products/{id}/quote` anbieten; ein fehlender Preis kann `configurationRequired` bedeuten, während eine gültige unpreiswerte Spezifikation „Preis auf Anfrage“ ist. Verwenden Sie den zurückgegebenen Zeilentotalbetrag. Siehe [Trade Catalog Setup](/docs/guides/trade-catalogues) für Vorrang, Netto-Behandlung und Veröffentlichung.

## Wie die Preise berechnet werden

Die spezifischste Regel gewinnt, und **Anpassungen stapeln sich nie**:

1. **Vereinbarter Preis.** Wenn der Kunde einen vereinbarten (festen) Preis für das Produkt hat, ist das der Preis.
2. Andernfalls der **Listenpreis mit einem Prozentsatz**, der erste, der gilt. Ein Prozentsatz ist ein Rabatt (abgezogen) oder eine Aufschlag (hinzugefügt):
   1. der Prozentsatz des Kunden für dieses **Produkt**
   2. der Prozentsatz des Kunden für die **Kategorie** des Produkts (die nächstgelegene Elternkategorie gewinnt)
   3. der Prozentsatz des Kunden für die **Marke** des Produkts
   4. der eigene Prozentsatz des Kunden auf die **gesamte Liste**
3. Andernfalls der **Listenpreis**.

Eine Kategorie-Rabatt von 10 % und eine Gesamtliste-Marge von 5 % ergeben 10 % Rabatt, nicht beides. Die Einheit ist niemals unter null und wird auf eine ganze kleine Einheit aufgerundet. Der Zeilentotalbetrag ist `unit × qty`.

Listenpreise und vereinbarte Preise werden nach Datum versioniert. Die API gibt immer den Preis zurück, der **derzeit gültig** ist. Zukünftige Preise aus einer geplanten Preisanpassung gelten ab ihrem Datum.

## Preislisten und Katalogzugriff

Standard ist der Hauptkatalog des Lieferanten. Andere Preislisten können Produkte mit Preisen und Produkte enthalten, die auf ein Angebot warten. Ein fehlender Preis auf einer zugewiesenen Liste fällt nicht auf Standard zurück.

Im Arbeitsbereich öffnen Sie **Preislisten** und wählen Sie eine benannte Liste. **Zugewiesene Kunden können sehen** hat zwei Auswahlmöglichkeiten:

- **Alle erlaubten Katalogprodukte** ist die Standardeinstellung. Die Liste legt die Preise fest; zugewiesene Käufer können auch andere erlaubte Produkte als Preis auf Anfrage durchsuchen.
- **Nur Produkte auf dieser Liste** beschränkt den Katalog, Suchen, Produktlinks, Downloads und neue Bestellungen jedes zugewiesenen Käufers auf diese Liste. Andere Produktansichts- und genehmigte Listenbeschränkungen gelten weiterhin. Kunden auf anderen Listen behalten ihren Zugriff.

Jede Liste zeigt ihre Kundenanzahl, preiswerte Produkte, unpreiswerte Produkte und Gesamtprodukte an. Die Gesamtzahlen beinhalten inaktive Produkte, sodass sie größer sein können, als ein Käufer derzeit sieht. Ein Preis von null gilt als preiswert. Ein zukünftiger Preis gilt als unpreiswert, bis sein Startdatum erreicht ist.

Um Produkte hinzuzufügen, wählen Sie sie auf **Produkte**, wählen Sie **Zur Preisliste hinzufügen**, und kopieren Sie Standardpreise mit einem optionalen Prozentsatz oder wählen Sie **Ohne Preise hinzufügen**. Bestehende Mitglieder behalten ihre Preise. Produkte ohne Standardpreis werden als Preis auf Anfrage hinzugefügt. Auf der Seite einer benannten Liste fügt das Speichern eines SKU mit einem leeren Preis es ohne Preis hinzu oder löscht seinen aktuellen Preis. **Entfernen** entfernt es von dieser Liste, während das Master-Produkt und frühere Bestellungen erhalten bleiben. Beheben Sie zuerst eine ausstehende Preisänderung für dieses Produkt.

Diese Steuerungselemente erfordern Pro und globale Preisbearbeitungsrechte. Bestehende Listen behalten ihren Katalogzugriff, bis ein Lieferant ihn ändert. API-Importe in eine benannte Liste enthalten gewöhnliche Produktzeilen ohne Preis als unpreiswerte Mitglieder; eine leere Zelle in einem Preis-Import lässt den Zugriff unverändert.

## Das Preisobjekt

Jedes Produkt hat die Form von `price`. `GET /prices` gibt dieselben Felder auf der obersten Ebene von `data` zurück.

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

| Feld         | Typ                      | Beschreibung                                                                                                                                       |
| ------------ | ------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------- |
| `unit`       | Ganzzahl oder null       | Preis für eine Einheit, in kleineren Einheiten. `null`, wenn das Produkt keinen Preis hat.                                                         |
| `line`       | Ganzzahl oder null       | `unit × qty`.                                                                                                                                      |
| `list`       | Ganzzahl oder null       | Der aktuelle Listenpreis.                                                                                                                          |
| `discountBp` | Ganzzahl                 | Die angewandte Anpassung, in Basispunkten. `0` für vereinbarte und Listenpreise.                                                                   |
| `adjustment` | `discount` oder `markup` | In welche Richtung `discountBp` den Listenpreis verändert. `discount`, wenn kein Prozentsatz vorhanden ist.                                        |
| `source`     | Zeichenkette             | Welches Regelset den Preis festgelegt hat: siehe unten.                                                                                            |
| `label`      | Zeichenkette             | Wording, das Käufer neben dem Preis sehen.                                                                                                         |
| `remark`     | Zeichenkette oder null   | Der vereinbarte Preis-Notiz, wenn dieser Kunde sie sehen darf. Andernfalls `null`. Die internen Notizen für das Personal werden niemals enthalten. |

| `source`   | Bedeutung                          | Beispiel `label`             |
| ---------- | ---------------------------------- | ---------------------------- |
| `fixed`    | Der vereinbarte Preis des Kunden   | Vereinbarter Preis           |
| `product`  | Prozentsatz auf einem Produkt      | 10 % Rabatt auf Ihre Liste   |
| `category` | Prozentsatz auf einer Kategorie    | 12,5 % Rabatt auf Ihre Liste |
| `brand`    | Prozentsatz auf einer Marke        | 15 % über Ihre Liste         |
| `account`  | Prozentsatz auf der gesamten Liste | 10 % über Ihre Liste         |
| `list`     | Listenpreis, keine Anpassung       | (leer)                       |
| `unpriced` | Das Produkt hat noch keinen Preis  | (leer)                       |

## Kundenpreis prüfen

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

Was ein Kunde für ein SKU bei einer Menge zahlt und warum. Nutzen Sie es, um zu beantworten: „Was zahlt Oakfield für 10 Kartons von HX-8510?“.

| Name       | Typ          | Erforderlich | Beschreibung                                   |
| ---------- | ------------ | ------------ | ---------------------------------------------- |
| `customer` | Zeichenkette | ja           | Kunden-ID oder Kontocode.                      |
| `sku`      | Zeichenkette | ja           | Die SKU, Groß-/Kleinschreibung wird ignoriert. |
| `qty`      | Ganzzahl     | nein         | 1 bis 1.000.000. Standard 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               | Wann                                                                 |
| ------ | ------------------ | -------------------------------------------------------------------- |
| 400    | `validation_error` | `customer` oder `sku` fehlen, oder `qty` ist außerhalb des Bereichs. |
| 404    | `not_found`        | Kein solcher Kunde, oder kein Produkt mit dieser SKU.                |

Dieser Endpunkt überprüft nur den Preis. Er überprüft nicht, ob der Kunde das Produkt sehen kann. Verwenden Sie hierzu [`GET /products/{id}?customer=…`](/docs/api/products#get-one-product).

## Preise für viele Produkte

Um eine ganze Produktseite für einen Kunden zu bewerten, verwenden Sie `GET /products?customer=OAK01`. Der `price` jeder Zeile ist der Preis dieses Kunden, und kundenspezifische Produkte für andere Kunden werden ausgelassen. Siehe [Produkte](/docs/api/products#list-or-search-products).

## Preise ändern

- **Listenpreise** ändern sich durch Preisanpassungen, sodass Kunden benachrichtigt werden. Senden Sie neue Preise mit [`PUT /products`](/docs/api/products#create-or-update-products); sie werden in einen Entwurf aufgenommen, den Sie in der App veröffentlichen.
- **Zuschläge, Rabatte und vereinbarte Preise** werden pro Kunde in der App festgelegt. Lesen Sie sie mit [`GET /customers/{id}`](/docs/api/customers#get-one-customer).
- **Vereinbarte Preise** für viele Kunden werden mit [`PUT /agreed-prices`](/docs/api/prices#set-agreed-prices) festgelegt.
- Der **Gesamtprozentsatz** eines Kunden kann mit [`PUT /customers`](/docs/api/customers#create-or-update-customers) (`discountBp`, und optional `adjustment`) festgelegt werden.

## Vereinbarte Preise festlegen

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

Erstellen oder ersetzen Sie vereinbarte Preise für bestehende Kunden und Produkte. Senden Sie bis zu 2.000 Zeilen. Preise sind Ganzzahlen in Kleinwährungseinheiten. Lassen Sie `commit` auf false, um eine Vorschau zu erhalten: Nichts wird gespeichert, und jede unbekannte Kontonummer oder SKU wird als Zeilenfehler zurückgegeben. Setzen Sie `commit` auf true, um zu speichern. Das Speichern schließt den aktuellen vereinbarten Preis und fügt den neuen ab diesem Moment ein, in Batches von 100. Nur Pro.

| Name          | Typ     | Erforderlich | Beschreibung                                              |
| ------------- | ------- | ------------ | --------------------------------------------------------- |
| `commit`      | boolean | nein         | `true` speichert. Standardmäßig `false` nur zur Vorschau. |
| `prices`      | array   | ja           | 1 bis 2.000 Preise.                                       |
| `accountCode` | string  | ja           | Kundenkontonummer, Groß-/Kleinschreibung wird ignoriert.  |
| `sku`         | string  | ja           | Produkt-SKU, Groß-/Kleinschreibung wird ignoriert.        |
| `price`       | integer | ja           | Vereinbarter Preis in Kleinwährungseinheiten.             |
| `note`        | string  | nein         | Bis zu 200 Zeichen, für Mitarbeiter sichtbar.             |

```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               | Wann                                                                      |
| ------ | ------------------ | ------------------------------------------------------------------------- |
| 400    | `validation_error` | Ein Preis fehlt, ist negativ oder über dem Limit.                         |
| 403    | `read_only`        | Der Workspace ist nicht auf Pro, oder der Schlüssel ist schreibgeschützt. |
