openapi: 3.1.0

# ============================================================================
# S&O B2B-Partner-API v1 — der Vertrag (Phase 0, docs/plan-b2b-api.md)
#
# Diese Datei ist die einzige Quelle der Wahrheit fuer die Schnittstelle:
# Die Entwicklerdoku wird hieraus erzeugt, die Implementierung (Gateway und
# Hub-Modul b2b_connect) wird gegen sie gebaut, nicht umgekehrt.
#
# Die API ist bewusst eigenstaendig (kein gemeinsamer Code oder Betrieb mit
# anderen Partneranbindungen des Hauses) und folgt durchgehend offenen
# Standards — das ist die Zukunftssicherung:
#   - Feldnamen englisch (camelCase), Beschreibungen deutsch.
#   - Mengen und Betraege sind STRINGS mit Punkt als Dezimaltrenner —
#     unterwegs entsteht so keine Gleitkommarundung.
#   - Zeitstempel ISO-8601 (RFC 3339) MIT Zeitzone; Datumsangaben YYYY-MM-DD.
#   - Fehler als RFC 9457 Problem Details (application/problem+json) mit
#     stabilem Maschinencode `code`.
#   - Unbekannte Felder in Anfragen werden abgewiesen, nicht verschluckt.
#
# Versionierungszusage (steht so auch in der Partner-Doku): /v1 im Pfad;
# additive Aenderungen (neue optionale Felder, neue Endpunkte) ohne
# Versionswechsel; brechende Aenderungen nur als /v2 mit mindestens 12
# Monaten Parallelbetrieb und angekuendigter Abkuendigung.
#
# Aenderungsvermerk:
#   17.08.2026 — Funde des Doku-Fremdtests eingearbeitet (alles additiv):
#   abschliessendes Fehlercode-Register, discontinued-Tombstones im
#   Katalog-Delta, exactAvailability in /v1/ping, Sandbox-Praefix SBX-
#   dokumentiert, echte Umlaute in den Beschreibungstexten.
# ============================================================================

info:
  title: S&O B2B-Partner-API
  version: 1.0.0-entwurf
  summary: Katalog, Preise, Verfügbarkeit und Bestellungen für B2B-Kunden und Softwarepartner.
  description: |
    Programmatischer Zugang für B2B-Kunden der S&O Handelsgesellschaft mbH
    und deren Software (Branchen-ERP, Planungsprogramme, Agenten).

    ## Authentifizierung und Berechtigungen
    Jede Anfrage trägt einen API-Schlüssel im Header `X-API-Key`. Ein
    Schlüssel ist genau einem Geschäftspartner (Kundennummer) zugeordnet;
    Preise, Verfügbarkeitssicht und Bestellrechte hängen daran. Je
    Schlüssel sind Berechtigungen freigeschaltet:

    | Berechtigung | erlaubt |
    |---|---|
    | `catalog:read` | Artikelstamm lesen |
    | `availability:read` | Verfügbarkeit lesen |
    | `prices:read` | kundenindividuelle Preise lesen |
    | `orders:read` | eigene Bestellungen und Ereignisse lesen |
    | `orders:write` | Bestellungen aufgeben (separate kaufmännische Freigabe) |

    Fehlt die Berechtigung, antwortet die API mit `403 PERMISSION_MISSING`.

    ## Fehler
    Fehler kommen als **RFC 9457 Problem Details** (`application/problem+json`)
    mit den Zusatzfeldern `code` (stabiler Maschinencode) und `field`
    (betroffenes Feld). Auf `code` dürfen Sie programmieren; `title` und
    `detail` sind für Menschen und können sich ändern.

    Das Register der Codes ist abschließend — jede Fehlerantwort dieser API
    trägt genau einen davon; neue Codes kommen nur additiv und angekündigt:

    | `code` | HTTP | Bedeutung | Wiederholen sinnvoll? |
    |---|---|---|---|
    | `UNAUTHORIZED` | 401 | Schlüssel fehlt, ist unbekannt oder gesperrt. | Nein — erst den Schlüssel klären. |
    | `PERMISSION_MISSING` | 403 | Der Schlüssel trägt die nötige Berechtigung nicht. | Nein — Freigabe anfragen. |
    | `NOT_FOUND` | 404 | Ressource oder Pfad existiert nicht. | Nein. |
    | `METHOD_NOT_ALLOWED` | 405 | HTTP-Methode passt nicht zu diesem Pfad. | Nein — Anfrage korrigieren. |
    | `VALIDATION` | 422 | Anfrage formal oder fachlich nicht verarbeitbar; `field` nennt den betroffenen Parameter. | Nein — Anfrage korrigieren. |
    | `RATE_LIMITED` | 429 | Kontingent des Schlüssels erschöpft. | Ja — nach `Retry-After` Sekunden. |
    | `INTERNAL` | 500 | Unerwarteter Fehler auf unserer Seite. | Ja — unverändert, mit Abstand. |
    | `NOT_IMPLEMENTED` | 501 | Endpunkt steht im Vertrag, ist aber noch nicht in Betrieb (Phase 2). | Nein — bis zur Ankündigung. |

    ## Idempotenz
    `POST /v1/orders` ist über `clientOrderId` idempotent: Dieselbe
    clientOrderId liefert bei Wiederholung dieselbe Antwort (HTTP 200 statt
    201) und legt NIE eine zweite Bestellung an. Netzfehler behandeln Sie
    deshalb immer mit unveränderter Wiederholung.

    ## Preise
    Preise sind NETTO in der angegebenen Währung und die Auskunft der in SAP
    hinterlegten Kondition. Verbindlich wird der Preis mit der
    Auftragsbestätigung (`confirmed`-Ereignis) — weicht er ab, steht dort
    der bestätigte Wert, wie bei einer klassischen AB. Artikel ohne
    hinterlegten Preis kommen mit `price: null` und `source: noPrice` —
    niemals als 0,00.

    ## Mengeneinheiten
    Alle Mengen dieser API sind LAGERSTÜCK. Artikel mit abweichender
    Verkaufseinheit tragen `salesUnit` und `unitsPerSalesUnit` — die
    Umrechnung ist Sache des Aufrufers und passiert nie stillschweigend.

    ## Ratenbegrenzung
    Je Schlüssel gilt ein Kontingent (Standard 60 Anfragen/Minute).
    Darüber antwortet die API mit `429` und `Retry-After`.
  contact:
    name: S&O Handelsgesellschaft mbH — IT
    email: it@so-handel.de

servers:
  - url: https://b2b-api-test.so-cloud.de
    description: Test/Sandbox (Schlüssel-Präfix sohd_b2b_test_)
  - url: https://b2b-api.so-cloud.de
    description: Produktion (Schlüssel-Präfix sohd_b2b_)

security:
  - ApiKey: []

tags:
  - name: System
  - name: Katalog
  - name: Preise und Verfügbarkeit
  - name: Bestellungen
  - name: Ereignisse

paths:

  /v1/ping:
    get:
      tags: [System]
      operationId: ping
      summary: Erreichbarkeit und Schlüssel prüfen
      responses:
        "200":
          description: Dienst erreichbar, Schlüssel gültig.
          content:
            application/json:
              schema:
                type: object
                required: [status, environment, permissions, exactAvailability]
                properties:
                  status:
                    const: ok
                  environment:
                    type: string
                    enum: [test, production]
                  permissions:
                    type: array
                    items: { $ref: "#/components/schemas/Permission" }
                  exactAvailability:
                    type: boolean
                    description: |
                      true = dieser Schlüssel bekommt bei /v1/availability
                      die exakte freie Menge (`free`); false = nur die
                      Klasse (available/partial/unavailable).
        "401": { $ref: "#/components/responses/Unauthorized" }

  /v1/catalog/items:
    get:
      tags: [Katalog]
      operationId: listItems
      summary: Artikelstamm, delta-fähig
      description: |
        Seitenweise Liste des freigegebenen Sortiments. Mit `changedSince`
        kommen nur Artikel, deren Stammdaten sich seither geändert haben —
        der empfohlene Weg für die tägliche Synchronisation. Delta-Antworten
        melden auch Abgänge: Ein aus dem Sortiment gefallener Artikel kommt
        als Tombstone mit `discontinued: true` — nur so veraltet ein
        gespiegelter Katalog nicht schleichend. Die Seiten werden über
        `nextCursor` verkettet; ein fehlender `nextCursor` beendet die Liste.
      parameters:
        - name: changedSince
          in: query
          schema: { type: string, format: date-time }
          description: |
            Nur Artikel mit Änderung nach diesem Zeitpunkt. ISO 8601 MIT
            Zeitzone (z. B. 2026-08-17T12:00:00Z); ohne Zeitzone antwortet
            die API mit 422 VALIDATION.
        - name: cursor
          in: query
          schema: { type: string }
          description: Fortsetzungsmarke aus der vorigen Antwort.
        - name: limit
          in: query
          schema: { type: integer, minimum: 1, maximum: 500, default: 100 }
      responses:
        "200":
          description: Eine Seite Artikel.
          content:
            application/json:
              schema:
                type: object
                required: [items]
                properties:
                  items:
                    type: array
                    items: { $ref: "#/components/schemas/Item" }
                  nextCursor:
                    type: string
                    description: Fehlt auf der letzten Seite.
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }

  /v1/catalog/items/{id}:
    get:
      tags: [Katalog]
      operationId: getItem
      summary: Einzelartikel
      description: "`id` ist die S&O-Artikelnummer oder eine GTIN."
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string, maxLength: 14 }
      responses:
        "200":
          description: Der Artikel.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Item" }
        "404": { $ref: "#/components/responses/NotFound" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }

  /v1/prices:
    get:
      tags: [Preise und Verfügbarkeit]
      operationId: getPrices
      summary: Kundenindividuelle Preise
      description: |
        Preise des anfragenden Kunden (aus der Schlüssel-Bindung) für bis
        zu 100 Artikel. `quantity` aktiviert Mengenstaffeln, falls für den
        Kunden hinterlegt.
      parameters:
        - $ref: "#/components/parameters/ItemList"
        - name: quantity
          in: query
          schema: { $ref: "#/components/schemas/DecimalString" }
          description: Angefragte Menge in Lagerstück (Standard "1").
      responses:
        "200":
          description: "Preis je angefragtem Artikel — auch für unbekannte (dann source noPrice)."
          content:
            application/json:
              schema:
                type: object
                required: [customerNumber, prices]
                properties:
                  customerNumber: { type: string }
                  prices:
                    type: array
                    items: { $ref: "#/components/schemas/Price" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }

  /v1/availability:
    get:
      tags: [Preise und Verfügbarkeit]
      operationId: getAvailability
      summary: Verfügbarkeit
      description: |
        Freie Menge je Artikel aus dem Versandlager, dazu eine Klasse und —
        wenn vorhanden — der nächste bestätigte Wareneingang.
      parameters:
        - $ref: "#/components/parameters/ItemList"
        - name: quantity
          in: query
          schema: { $ref: "#/components/schemas/DecimalString" }
          description: Angefragte Menge; bestimmt nur die Klasse.
      responses:
        "200":
          description: Verfügbarkeit je Artikel.
          content:
            application/json:
              schema:
                type: object
                required: [availability]
                properties:
                  availability:
                    type: array
                    items: { $ref: "#/components/schemas/Availability" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }

  /v1/orders:
    post:
      tags: [Bestellungen]
      operationId: createOrder
      summary: Bestellung aufgeben (idempotent)
      description: |
        Nimmt die Bestellung an und antwortet mit `201` und Status
        `received`. Die fachliche Bestätigung mit den von SAP gerechneten
        Preisen kommt als `confirmed`-Ereignis (siehe /v1/events).

        Artikel werden je Position über GENAU EINES der Felder `itemCode`,
        `gtin` oder `manufacturerNo` identifiziert. Ist die Angabe mehrdeutig
        oder unbekannt, wird die GESAMTE Bestellung mit `422` abgewiesen —
        es wird nie geraten und nie eine Teilmenge angelegt.
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/OrderRequest" }
            example:
              clientOrderId: "SW-2026-004711"
              commission: "BV Mueller / Kueche OG"
              lines:
                - gtin: "4046567891234"
                  quantity: "10"
                  commission: "BV Mueller / Kueche OG"
                  lineReference: "SW-4711-P3"
                - itemCode: "108054"
                  quantity: "2"
      responses:
        "201":
          description: Bestellung angenommen.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Order" }
        "200":
          description: Wiederholung einer bekannten `clientOrderId` — unveränderte ursprüngliche Antwort.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Order" }
        "422":
          description: Fachlich nicht annehmbar (unbekannter/mehrdeutiger Artikel, Limit überschritten).
          content:
            application/problem+json:
              schema: { $ref: "#/components/schemas/Error" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }

  /v1/orders/{orderId}:
    get:
      tags: [Bestellungen]
      operationId: getOrder
      summary: Bestellstatus
      parameters:
        - name: orderId
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Aktueller Stand der Bestellung.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Order" }
        "404": { $ref: "#/components/responses/NotFound" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }

  /v1/events:
    get:
      tags: [Ereignisse]
      operationId: listEvents
      summary: Offene Ereignisse abrufen
      description: |
        Liefert die ältesten noch nicht bestätigten Ereignisse des Kunden
        (Bestätigung, Versand mit Sendungsnummern, Rechnung, Ablehnung).
        Abgerufene Ereignisse bleiben offen, bis sie per
        `POST /v1/events/ack` quittiert sind — verlorene Antworten kosten
        also nichts. Empfohlener Takt: alle 5-15 Minuten.
      parameters:
        - name: limit
          in: query
          schema: { type: integer, minimum: 1, maximum: 200, default: 50 }
      responses:
        "200":
          description: Ereignisse, älteste zuerst.
          content:
            application/json:
              schema:
                type: object
                required: [events]
                properties:
                  events:
                    type: array
                    items: { $ref: "#/components/schemas/Event" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }

  /v1/events/ack:
    post:
      tags: [Ereignisse]
      operationId: ackEvents
      summary: Ereignisse quittieren
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [eventIds]
              properties:
                eventIds:
                  type: array
                  minItems: 1
                  maxItems: 200
                  items: { type: string }
      responses:
        "200":
          description: Quittiert (unbekannte Ids werden ignoriert und gemeldet).
          content:
            application/json:
              schema:
                type: object
                required: [acknowledged]
                properties:
                  acknowledged: { type: integer }
                  unknownIds:
                    type: array
                    items: { type: string }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }

components:

  securitySchemes:
    ApiKey:
      type: apiKey
      in: header
      name: X-API-Key
      description: Schlüssel je Partner-System; Präfix sohd_b2b_test_ bzw. sohd_b2b_.

  parameters:
    ItemList:
      name: items
      in: query
      required: true
      description: Kommagetrennte Artikelnummern oder GTINs, max. 100.
      schema:
        type: string
        maxLength: 1600
      example: "108054,4046567891234,135417"

  responses:
    Unauthorized:
      description: Schlüssel fehlt, unbekannt oder gesperrt.
      content:
        application/problem+json:
          schema: { $ref: "#/components/schemas/Error" }
    Forbidden:
      description: Berechtigung fehlt (PERMISSION_MISSING).
      content:
        application/problem+json:
          schema: { $ref: "#/components/schemas/Error" }
    NotFound:
      description: Unbekannte Ressource.
      content:
        application/problem+json:
          schema: { $ref: "#/components/schemas/Error" }

  schemas:

    DecimalString:
      type: string
      pattern: '^\d+(\.\d{1,4})?$'
      description: Menge/Betrag als String, Punkt als Dezimaltrenner.

    Permission:
      type: string
      enum: [catalog:read, availability:read, prices:read, orders:read, orders:write]

    Error:
      type: object
      description: RFC 9457 Problem Details, erweitert um `code` und `field`.
      required: [title, status, code]
      properties:
        type:
          type: string
          format: uri
          default: "about:blank"
          description: URI der Fehlerklasse (später je Code eine Doku-Seite).
        title:
          type: string
          description: Kurzbeschreibung der Fehlerklasse, deutsch.
        status:
          type: integer
          description: HTTP-Statuscode, redundant zur Antwort.
        detail:
          type: string
          description: Menschlich lesbare Einzelheit zu genau diesem Vorfall.
        code:
          type: string
          description: Stabiler Maschinencode aus dem abschließenden Register im Abschnitt "Fehler" der API-Beschreibung — hierauf programmieren.
        field:
          type: string
          description: Betroffenes Feld, falls zuordenbar (z. B. "lines.2.gtin").

    Item:
      type: object
      required: [itemCode, name, changedAt]
      properties:
        itemCode:
          type: string
          description: |
            S&O-Artikelnummer — der stabile Schlüssel. Produktivdaten sind
            6-stellig; die Sandbox-Artikel tragen das Präfix SBX- und sind
            10 Zeichen lang.
        gtin:
          type: string
          description: GTIN/EAN, falls gepflegt.
        manufacturerNo:
          type: string
          description: Herstellernummer, falls gepflegt.
        name:
          type: string
          description: Bezeichnung deutsch.
        nameEn:
          type: string
          description: Bezeichnung englisch, falls gepflegt.
        salesUnit:
          type: string
          description: Verkaufseinheit (z. B. "Set", "Paar"); fehlt bei Stück.
        unitsPerSalesUnit:
          $ref: "#/components/schemas/DecimalString"
          description: Lagerstück je Verkaufseinheit (fehlt = 1).
        orderMultiple:
          $ref: "#/components/schemas/DecimalString"
          description: Bestellvielfaches in Lagerstück, falls vorgegeben.
        weightKg:
          $ref: "#/components/schemas/DecimalString"
        media:
          type: array
          description: Bild-/Dokument-URLs (öffentlich abrufbar).
          items:
            type: object
            required: [url, kind]
            properties:
              url: { type: string, format: uri }
              kind: { type: string, enum: [image, datasheet, drawing] }
        attributes:
          type: object
          description: Strukturierte Merkmale (Schlüssel -> Wert); wächst mit der PIM-Pflege, ETIM-Klassifikation folgt in Phase 4.
          additionalProperties: { type: string }
        successorItemCode:
          type: string
          description: Nachfolgeartikel, falls dieser Artikel ausläuft.
        discontinued:
          type: boolean
          description: |
            true = der Artikel ist aus dem Sortiment gefallen. Erscheint in
            Delta-Antworten (changedSince) als Tombstone — dann sind nur
            itemCode, name und changedAt gefüllt — und beim Einzelabruf
            eines abgekündigten Artikels. Fehlt das Feld, ist der Artikel
            aktiv.
        changedAt:
          type: string
          format: date-time
          description: Letzte Stammdatenänderung — Anker für changedSince.

    Price:
      type: object
      required: [itemCode, source]
      properties:
        itemCode: { type: string }
        price:
          oneOf:
            - $ref: "#/components/schemas/DecimalString"
            - type: "null"
          description: Nettopreis je Lagerstück; null = kein Preis hinterlegt.
        currency:
          type: string
          description: ISO-4217, z. B. EUR. Fehlt bei price null.
        source:
          type: string
          enum: [priceList, specialPrice, quantityScale, noPrice]
          description: Ebene, aus der der Preis stammt.
        fromQuantity:
          $ref: "#/components/schemas/DecimalString"
          description: Nur bei quantityScale — ab dieser Menge gilt der Preis.
        notes:
          type: array
          items: { type: string }
          description: Hinweise (z. B. Fremdwährung) — anzeigen, nicht verwerfen.

    Availability:
      type: object
      required: [itemCode, class]
      properties:
        itemCode: { type: string }
        class:
          type: string
          enum: [available, partial, unavailable]
          description: Bewertung gegen die angefragte Menge.
        free:
          $ref: "#/components/schemas/DecimalString"
          description: Frei verfügbare Menge in Lagerstück (Bestand minus bereits zugesagt). Ob die exakte Zahl oder nur die Klasse geliefert wird, entscheidet die Freigabe des Schlüssels — sichtbar als exactAvailability in /v1/ping.
        salesUnit: { type: string }
        unitsPerSalesUnit: { $ref: "#/components/schemas/DecimalString" }
        nextInbound:
          type: object
          description: Nächster bestätigter Wareneingang, falls vorhanden.
          required: [date, quantity]
          properties:
            date: { type: string, format: date }
            quantity: { $ref: "#/components/schemas/DecimalString" }
            overdue:
              type: boolean
              description: true, wenn der bestätigte Termin bereits überschritten ist.

    OrderLineRequest:
      type: object
      description: Genau EINES von itemCode/gtin/manufacturerNo angeben.
      required: [quantity]
      properties:
        itemCode: { type: string }
        gtin: { type: string }
        manufacturerNo: { type: string }
        quantity: { $ref: "#/components/schemas/DecimalString" }
        commission:
          type: string
          maxLength: 100
          description: Kommission/Bauvorhaben dieser Position; erscheint auf Belegen und Paketen.
        lineReference:
          type: string
          maxLength: 64
          description: Positionsreferenz des bestellenden Systems; kommt in allen Ereignissen zurück.

    OrderRequest:
      type: object
      required: [clientOrderId, lines]
      properties:
        clientOrderId:
          type: string
          minLength: 1
          maxLength: 64
          description: Idempotenzschlüssel des Bestellers — einmalig je Bestellung.
        commission:
          type: string
          maxLength: 100
          description: Kommission für die gesamte Bestellung.
        requestedDeliveryDate:
          type: string
          format: date
          description: Wunschtermin; unverbindlich bis zur Bestätigung.
        note:
          type: string
          maxLength: 500
        lines:
          type: array
          minItems: 1
          maxItems: 200
          items: { $ref: "#/components/schemas/OrderLineRequest" }

    OrderLine:
      type: object
      required: [lineNumber, itemCode, quantity]
      properties:
        lineNumber: { type: integer, minimum: 1 }
        itemCode:
          type: string
          description: Aufgelöster S&O-Artikel — auch wenn per GTIN bestellt wurde.
        quantity: { $ref: "#/components/schemas/DecimalString" }
        confirmedQuantity:
          $ref: "#/components/schemas/DecimalString"
          description: Ab Status confirmed.
        confirmedPrice:
          $ref: "#/components/schemas/DecimalString"
          description: Von SAP gerechneter Nettopreis je Lagerstück, ab Status confirmed. Er ist der verbindliche Preis.
        currency: { type: string }
        commission: { type: string }
        lineReference: { type: string }

    Order:
      type: object
      required: [orderId, clientOrderId, status, createdAt, lines]
      properties:
        orderId:
          type: string
          description: Kennung dieser API (stabil, auch für /v1/orders/{orderId}).
        clientOrderId: { type: string }
        status:
          type: string
          enum: [received, confirmed, shipped, invoiced, rejected]
          description: |
            received -> confirmed -> shipped -> invoiced; rejected ist
            endgültig. Teillieferungen melden shipped-Ereignisse je Sendung;
            der Status wechselt mit der ERSTEN Sendung.
        sapDocNum:
          type: string
          description: SAP-Auftragsnummer, ab Status confirmed.
        createdAt: { type: string, format: date-time }
        commission: { type: string }
        lines:
          type: array
          items: { $ref: "#/components/schemas/OrderLine" }

    Event:
      type: object
      required: [eventId, type, orderId, occurredAt]
      properties:
        eventId:
          type: string
          description: Eindeutig; Grundlage der Quittung.
        type:
          type: string
          enum: [confirmed, shipped, invoiced, rejected]
        orderId: { type: string }
        clientOrderId: { type: string }
        occurredAt: { type: string, format: date-time }
        order:
          $ref: "#/components/schemas/Order"
          description: Stand der Bestellung zum Ereigniszeitpunkt (bei confirmed mit den verbindlichen Preisen).
        shipment:
          type: object
          description: Nur bei type shipped.
          properties:
            carrier: { type: string }
            trackingNumbers:
              type: array
              items: { type: string }
            lines:
              type: array
              items:
                type: object
                required: [lineNumber, quantity]
                properties:
                  lineNumber: { type: integer }
                  quantity: { $ref: "#/components/schemas/DecimalString" }
        invoice:
          type: object
          description: Nur bei type invoiced.
          properties:
            invoiceNumber: { type: string }
            totalNet: { $ref: "#/components/schemas/DecimalString" }
            currency: { type: string }
        rejectionReason:
          type: string
          description: Nur bei type rejected.
