---
title: "Produkte"
description: "Logistikprodukte mit Context-, Feasibility-, Pricing- und Scheduling-Bricks definieren. Mit DataPools, Versionierung, Verfügbarkeitseinschränkungen und Hilfsfunktionen."
url: "https://support.pr-4.orbit.do/de/advanced-features/products"
locale: "de"
lastReviewed: "2026-10-09"
---

# Produkte

Logistikprodukte mit Context-, Feasibility-, Pricing- und Scheduling-Bricks definieren. Mit DataPools, Versionierung, Verfügbarkeitseinschränkungen und Hilfsfunktionen.

## Überblick

Mit Produkten in Orbit definieren Sie die Logistikprodukte, die Sie Ihren Kunden, Partnern und Lieferanten anbieten. Paketdienste bieten verschiedene klar definierte Versandprodukte für Pakete an (Economy, Express, Kurier, Premium usw.), abhängig von Größe, Gewicht, Preis oder Laufzeit. Ähnlich verbinden Produkte in Orbit **Zeitberechnung**, **Routing**, **Machbarkeitsprüfung**, **kaufmännische Bedingungen** und **Preise aus Einzelpositionen**. Orbit-Produkte gelten für alle Transport- und Zustellprozesse und sind nicht auf bestimmte Fälle wie Pakete, letzte Meile oder FTL/LTL beschränkt.

> **Note:** **Für wen dieser Artikel ist.** Dieser Artikel ist die technische Referenz zum Erstellen von Produkten. Er setzt voraus, dass Sie ProductBrick-Logik in TypeScript in Orbit MissionControl schreiben. Wenn Sie nur verstehen möchten, was Produkte sind und wie die Preisbildung funktioniert, in einfacher Sprache und ohne Code, lesen Sie stattdessen den Überblick [Produkte und Preise verstehen](https://support.pr-4.orbit.do/de/advanced-features/understanding-products-pricing).

Produkte sind ein starkes und flexibles Werkzeug. Damit bilden Sie Ihr logistisches und kaufmännisches Angebot genau in der Orbit-Plattform ab, beim Preis *und* beim Servicelevel. Produkte dienen dem **Verkauf (für Shipper)** und dem **Einkauf (für Carrier)** von Transporten.

Jedes *Produkt* hat vier zentrale Logikbausteine und dazu einige weitere Einstellungen: **Context**, **Feasibility**, **Pricing** und **Scheduling**. Diese Logikbausteine heißen **ProductBricks**.

Die Logik der ProductBricks schreiben Sie in der Programmiersprache TypeScript. So bleibt sie sehr flexibel und trotzdem gut wartbar. Orbit hat einen eingebauten Code-Editor mit vielen Funktionen, zum Beispiel Code-Vervollständigung und Syntaxhervorhebung. Der Code-Editor von Orbit ist dafür gemacht, Entwicklern beim Schreiben und Pflegen von ProductBrick-Code zu helfen.

Code eignet sich gut, um Logik abzubilden. Die meiste Produktlogik braucht aber irgendwann auch strukturierte Daten. Das kann eine Zonentabelle mit Preisen sein, eine Liste unterstützter Länder oder einfach ein paar Faktoren für die Preisberechnung (z. B. für entfernungsbasierte Preise). Orbit **DataPools** speichern strukturierte Daten, und Sie greifen aus der Logik der ProductBricks einfach darauf zu.

In diesem Artikel lernen Sie Orbit-Produkte und ihre Bausteine im Detail kennen. Dazu gibt es Beispiele und Code-Schnipsel für den Einstieg.

> **Tip:** Um Produkte zu erstellen und zu verwalten, brauchen Sie Grundkenntnisse im Programmieren, genauer in TypeScript oder JavaScript. Für den Einstieg in TypeScript empfehlen wir diesen Online-Kurs: [https://www.codecademy.com/learn/learn-typescript](https://www.codecademy.com/learn/learn-typescript)

> **Note:** Wenn Sie Hilfe beim Definieren von Orbit-Produkten brauchen, wenden Sie sich gern an Ihren Ansprechpartner bei Orbit. Er unterstützt Sie und gibt Ihnen Hinweise. Ob Sie Fragen zu Produkteinstellungen haben, Funktionen einbinden möchten oder ein Problem lösen müssen: Ihr Ansprechpartner bei Orbit hilft Ihnen.

## Grundbegriffe und Funktionen

### ProductBricks

ProductBricks sind die Bausteine von Produkten in Orbit. Es gibt vier Arten von ProductBricks:

1. **Context**

   * Der Context-Brick läuft vor allen anderen Bricks eines Produkts. Er erzeugt ein Context-Objekt, auf das alle anderen Bricks über den Parameter `context` zugreifen.

   * Allgemein sollte der Context-Brick Logik enthalten, die mehrere andere Bricks teilen oder nutzen. Typisch sind: eine Route berechnen, die passende Fahrzeugklasse finden usw.

   * Ein Produkt muss genau einen Context-Brick enthalten.

   * Context-Bricks können optional ein Array `messages` zurückgeben. Es enthält Hinweise mit Schweregrad (`info`, `warning`, `error`). Diese Meldungen sehen Nutzer während der Buchung und in den Details der Order. Details finden Sie unter [Produktmeldungen](https://support.pr-4.orbit.do/de/advanced-features/product-messages).

   * **Rückgabetyp (**`ContextFunctionResult`**):**

     | Field           | Type                                 | Description                                                                                                 |
     | --------------- | ------------------------------------ | ----------------------------------------------------------------------------------------------------------- |
     | `isFeasible`    | `boolean`                            | Ob das Produkt für die Transportanfrage machbar ist                                                         |
     | `result`        | `{ route?, context }`                | Context-Objekt, das an alle anderen Bricks geht. Routendaten gehören in `route`, eigene Daten in `context`. |
     | `errors`        | `Record<string, unknown>` (optional) | Fehlerdetails, wenn das Produkt nicht machbar ist                                                           |
     | `isRequestable` | `boolean` (optional)                 | Bei `true` erscheint das Produkt als anfragbar, auch wenn es nicht machbar ist                              |
     | `messages`      | `BrickMessage[]` (optional)          | Hinweise mit Schweregrad (`info`, `warning`, `error`) und lokalisiertem Text                                |
2. **Feasibility**

   * Feasibility-Bricks entscheiden, ob mit dem Produkt unter den gegebenen Bedingungen ein Angebot für die Transportanfrage entstehen kann.

   * Machbarkeitsprüfungen unterscheiden sich je nach Anwendungsfall stark. Beispiele: maximale Maße oder maximales Gewicht der Ladung (Load) prüfen, unterstützte Start- oder Zielländer prüfen usw.

   * Ein Feasibility-Brick erzeugt einen booleschen Wert (true/false), der die Machbarkeit anzeigt. Dazu kommen optional Fehlermeldungen, die dem Nutzer erklären, warum das Produkt nicht machbar ist, und optional `messages` mit Schweregrad. Siehe [Produktmeldungen](https://support.pr-4.orbit.do/de/advanced-features/product-messages).

   * Ein Produkt kann mehrere Feasibility-Bricks enthalten. Ihre Machbarkeitswerte werden mit logischem UND verknüpft, ihre Fehlermeldungen zu einer Liste zusammengeführt.

   * **Rückgabetyp (**`FeasibilityFunctionResult`**):**

     | Field           | Type                                 | Description                                                                                                                  |
     | --------------- | ------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------- |
     | `isFeasible`    | `boolean`                            | Ob das Produkt die Transportanfrage erfüllen kann                                                                            |
     | `errors`        | `Record<string, unknown>` (optional) | Fehlerdetails, die erklären, warum das Produkt nicht machbar ist                                                             |
     | `isRequestable` | `boolean` (optional)                 | Bei `true` erscheint das Produkt als anfragbar, auch wenn es nicht machbar ist. Bei mehreren Bricks gilt der strengste Wert. |
     | `messages`      | `BrickMessage[]` (optional)          | Hinweise mit Schweregrad und lokalisiertem Text                                                                              |
3. **Pricing**

   * Pricing-Bricks berechnen den Preis eines Produkts für einen bestimmten Transport.

   * Oft beruht die Preislogik auf Entfernung und/oder Dauer der berechneten Route oder auf einer Zonentabelle in einem DataPool. Pricing-Bricks erlauben aber auch Preislogik mit ganz anderem Aufbau.

   * Ein Pricing-Brick erzeugt eine Liste von Einzelpositionen. Einzelpositionen ähneln den Buchungspositionen auf einer Rechnung. Jede Einzelposition besteht aus Anzahl, Name, Geldbetrag und Steuersatz.

   * Ein Produkt kann mehrere Pricing-Bricks enthalten. Die erzeugten Einzelpositionen werden zu einer Liste zusammengeführt. Die Summe aller Einzelpositionen aus allen Pricing-Bricks eines Produkts ist der Preis des Produkts für einen bestimmten Transport.

   * **Rückgabetyp (**`LineItem[]`**):**

     | Field           | Type                                 | Description                                                                   |
     | --------------- | ------------------------------------ | ----------------------------------------------------------------------------- |
     | `type`          | `"base" \| "item" \| "discount"`     | Kategorie der Einzelposition                                                  |
     | `subtype`       | `string`                             | Weitere Einordnung (z. B. `"fuel_surcharge"`, `"base_rate"`)                  |
     | `name`          | `string`                             | Anzeigename der Einzelposition                                                |
     | `description`   | `string` (optional)                  | Zusätzlicher Beschreibungstext                                                |
     | `count`         | `number`                             | Menge (z. B. Anzahl Kilometer, Paletten)                                      |
     | `amount`        | `number`                             | Geldbetrag pro Einheit                                                        |
     | `amountType`    | `"fixed" \| "percentage"` (optional) | Ob `amount` ein fester Wert oder ein Prozentsatz ist. Standard ist `"fixed"`. |
     | `vatPercentage` | `number \| null`                     | Mehrwertsteuersatz in Prozent (z. B. `19`) oder `null`, wenn keiner gilt      |
     | `voucher`       | `string` (optional)                  | Zugehöriger Gutscheincode, falls vorhanden                                    |
4. **Scheduling**

   * Der Scheduling-Brick legt den verfügbaren Zeitplan für einen Transport fest, indem er einen Terminierungs-Assistenten konfiguriert, der mehrere Stopps (Stop) abdeckt. Jeder Stopp bekommt eigene Terminoptionen, Datumsgrenzen und Fahrtregeln.
   * Scheduling-Bricks rufen `utils.scheduling.buildSchedule()` auf und geben das erzeugte `Schedule`-Objekt direkt zurück. Die Funktion nimmt eine Konfiguration pro Stopp (Optionen, Betriebszeiten, gesperrte Wochentage, frühestes/spätestes Datum) sowie die Auswahl des Nutzers und optionale Empfehlungen entgegen.
   * Jeder Stopp unterstützt drei Arten von Optionen: **feste Uhrzeit** (der Nutzer wählt eine genaue Uhrzeit), **festes Zeitfenster** (ein vom Operator festgelegtes Fenster wie 08:00–12:00) und **flexibles Zeitfenster** (der Nutzer wählt ein Fenster innerhalb der vom Operator gesetzten Grenzen).
   * Aufeinanderfolgende Stopps können **Fahrtbedingungen** festlegen. Sie erzwingen eine Mindestfahrzeit zwischen den Stopps, gemessen ab dem Beginn oder dem Ende des gewählten Fensters am vorherigen Stopp.
   * Ein Produkt muss genau einen Scheduling-Brick enthalten, denn Scheduling-Bricks lassen sich nicht kombinieren.
   * *Hinweis: Der Scheduling-Brick wird ignoriert, wenn Sie einen Transport über den TransportComposer in Orbit MissionControl oder über die Orbit API buchen.*

Jeden ProductBrick definieren Sie mit TypeScript-Code im eingebauten Code-Editor von Orbit. Der Editor hilft mit Syntaxhervorhebung und Code-Vervollständigung, sauberen und funktionierenden Code zu schreiben.

Der Code zur Berechnung von Produkten läuft in einer abgeschotteten Ausführungsumgebung. Das bietet ein hohes Maß an Sicherheit.

### Brick Messages

Context- und Feasibility-Bricks können neben ihren normalen Rückgabewerten optional ein Array `messages` zurückgeben. Jede Meldung hat einen **Schweregrad** und einen **lokalisierten Text**:

```ts
// Inside a context or feasibility brick
return {
  isFeasible: true,
  result: { route, context: {} },
  messages: [
    {
      level: "info",
      text: { en: "Route includes toll roads", de: "Route enthält Mautstraßen" },
    },
    {
      level: "warning",
      text: { en: "Approaching weight limit", de: "Gewichtslimit fast erreicht" },
    },
  ],
};
```

**Schweregrade:**

* `info`: allgemeine Informationen zum Angebot oder zu den Transportbedingungen
* `warning`: Bedingungen, die den Transport beeinflussen können, die Buchung aber nicht verhindern
* `error`: kritische Probleme, die der Nutzer kennen sollte

Orbit sammelt die Meldungen aller Context- und Feasibility-Bricks eines Produkts, entfernt Duplikate und sortiert sie nach Schweregrad (zuerst Fehler, dann Warnungen, dann Infos). Sie erscheinen als farbige Hinweise in Orbit MissionControl und Orbit Hub: während der Buchung auf den Produktkarten und nach dem Buchen in den Details der Order.

Die Eigenschaft `text` nutzt das Format `Localization` und unterstützt mehrere Sprachen. Die Meldung erscheint in der Sprache, die der Nutzer gewählt hat.

Meldungen dienen nur der Information und verhindern keine Buchung. Ob ein Produkt buchbar ist, steuern Sie mit dem Rückgabewert `isFeasible`.

Wie Nutzer die Meldungen sehen, beschreibt der Artikel [Produktmeldungen](https://support.pr-4.orbit.do/de/advanced-features/product-messages).

### DataPools

**DataPools** sind Sammlungen strukturierter Daten in Tabellenform, auf die mehrere ProductBricks zugreifen können. Sie eignen sich gut für Daten wie eine Liste von Fahrzeugklassen oder eine zonenbasierte Preismatrix. Weil diese Daten an einer Stelle liegen, bleiben sie über verschiedene Produkte hinweg einheitlich, und die Pflege der Preisdaten wird einfacher.

Ein ProductBrick liest einen DataPool über seinen Namen mit `utils.dataPools.getDataPoolsByNames` (siehe die Hilfsfunktionen unten); der Code-Editor schlägt die Namen beim Tippen vor. Das Verknüpfen eines DataPools unten im Formular des ProductBricks ist veraltet: Bestehende Verknüpfungen funktionieren weiter, lassen sich in Orbit MissionControl aber nicht mehr bearbeiten.

Damit Sie verschiedene Datentypen und Formate einfach aus DataPools lesen können, **bietet Orbit mehrere Hilfsfunktionen**. Mehr dazu und Beispiele finden Sie weiter unten.

### Produkte

Um Produkte geht es hier. Ein Produkt ist das, was einem Kunden (Shipper) oder einem Dienstleister (Carrier) angeboten wird. Ein Produkt verbindet mehrere ProductBricks mit einigen Metadaten und kaufmännischen Bedingungen.

Ein Produkt kann an einen Kunden verkauft werden oder dazu dienen, eine Leistung bei einem Dienstleister (z. B. einem Carrier) einzukaufen. Dasselbe Orbit-Produkt kann auch für Verkauf *und* Einkauf dienen. Für diese Fälle legen Sie ein Produkt in einer von drei Kategorien an: **Einkauf**, **Verkauf** oder **Verkauf & Einkauf**. Die Kategorie steuert, an welchen Stellen und für welche Nutzergruppen Orbit das Produkt anzeigt:

* Verkaufsprodukte berechnen im Orbit TransportComposer einen Preis, wenn Sie eine Order anlegen. Sie können Kunden auch in Orbit Hub angeboten werden.
* Einkaufsprodukte berechnen im TransportComposer die Kosten, wenn Sie eine Tour anlegen. Sie kommen außerdem überall dort zum Einsatz, wo eine Tour entsteht oder stark geändert wird (z. B. beim Routing eines Shipments oder beim Optimieren einer Tour).

### **Verfügbarkeitszustände von Produkten**

Neben Machbarkeit und Preis können Produkte **anfragbar** oder **nicht verfügbar** sein.

Daraus ergeben sich drei mögliche Zustände:

* **Angebot (type: "offer")**

  Machbar, Preis berechnet → direkt buchbar

* **Anfrage (type: "request")**

  Nicht machbar oder kein Preis, aber isRequestable: true → Anfrage möglich

* **Nicht verfügbar (type: "unavailable")**

  Nicht machbar oder kein Preis und isRequestable: false → wird als nicht verfügbar angezeigt

Jedes dieser drei Ergebnisse ist gültig und korrekt erfasst. Es ist kein Fehler, den Sie beheben müssen. Eine **Anfrage** oder **Nicht verfügbar** ist eine echte Aussage über einen bestimmten Transport: Dieser Transport lässt sich nicht automatisch bepreisen, oder er ist unter den gegebenen Bedingungen nicht durchführbar. Solche Ergebnisse tragen meist eine Meldung mit dem Grund, zum Beispiel eine Ladung, die das Gewichtslimit überschreitet, oder eine Route außerhalb des unterstützten Gebiets. Der Nutzer sieht diese Meldung (siehe [Produktmeldungen](https://support.pr-4.orbit.do/de/advanced-features/product-messages)). Geben Sie diesen Grund weiter, statt einen fehlenden Preis als Störung zu behandeln. Dasselbe Produkt liefert für einen anderen Transport, der seine Bedingungen erfüllt, ein buchbares **Angebot**.

`isRequestable` können Sie am Produkt festlegen, oder FeasibilityBricks setzen es dynamisch. Wenn mehrere FeasibilityBricks isRequestable zurückgeben, gilt der strengste Wert (false gewinnt).

```ts
// Feasibility Brick
return {
  isFeasible: false,
  isRequestable: false
};
```

### Versionierung

* Produkte, ProductBricks und DataPools sind **nach Zeit versioniert**. Alte Versionen bleiben verfügbar, und eine Preisänderung lässt sich vor dem Tag vorbereiten, an dem sie gilt.
* Jede Änderung an einem Produkt, ProductBrick oder DataPool erzeugt eine neue Version mit einem Gültigkeitsdatum: dem Tag, ab dem diese Version gilt. Wird ein Produkt angefragt, nimmt Orbit das Startdatum des Transports und nutzt die Version, die an diesem Tag gilt. Der Start des Transports entscheidet also, welche Version ihn bepreist, nicht der Zeitpunkt der Anfrage.
* Bei Orders und Touren, die über ein Produkt bepreist sind, zeigen die Detailseiten in Orbit MissionControl den Produktnamen zusammen mit der verwendeten Version an.

### Verfügbarkeitseinschränkungen

* Ob, wo und für wen Produkte verfügbar sind, steuern Sie mit Verfügbarkeitseinschränkungen. Das sind Abfragen, die Werte von Carriern (bei Einkaufsprodukten) oder Shippern (bei Verkaufsprodukten) und deren Nutzern und Teams prüfen.

* Ohne Einschränkungen ist das Produkt für alle Shipper und Carrier verfügbar.

* Mit Einschränkungen ist ein Produkt nur verfügbar, wenn alle Bedingungen erfüllt sind. Wichtig: Diese Einschränkungen gelten nur, wenn der jeweilige Shipper oder Carrier beim Berechnen des Produkts bekannt ist. Das ist zum Beispiel der Fall, wenn Sie im TransportComposer einen Shipper auswählen. Ist kein Shipper oder Carrier ausgewählt, bleibt das Produkt trotz der Einschränkungen verfügbar.

* Für Produkte in Hub und im TransportComposer gibt es eine weitere Filterebene:

  * Ein Produkt erreicht Orbit Hub über die TransportShape eines Hub-Flows: Nehmen Sie es in die **Verkauf-Products** der Shape auf und weisen Sie die Shape dem Flow unter **Hub Einstellungen → Flow-Konfiguration** in Orbit MissionControl zu. Verfügbarkeitseinschränkungen gelten zusätzlich. Anders als in MissionControl zeigt Hub nicht verfügbare oder deaktivierte Produkte nicht an. Ein Shipper, der die Verfügbarkeitsfilter eines Produkts nicht erfüllt, sieht dieses Produkt in Hub nicht.
  * Damit ein Produkt im TransportComposer nutzbar ist, müssen Sie es in der zugehörigen [**TransportShape**](https://support.pr-4.orbit.do/de/advanced-features/transport-shape) ausdrücklich aktivieren\*\*.\*\* Nach der Aktivierung gelten weiterhin die Verfügbarkeitsregeln, falls welche eingerichtet sind.

## Schritt für Schritt

In dieser Anleitung erstellen wir ein einfaches Produkt, um die Grundbegriffe im Detail zu erklären. Unser Produkt enthält:

* einen ContextBrick mit Routing-Funktion, der Entfernung und Dauer einer Route berechnet
* einen PricingBrick, der eine Einzelposition für den Grundpreis erzeugt
* einen DataPool mit Variablen für die Preisberechnung
* einen einfachen FeasibilityBrick, der Grenzen für die Ladung prüft
* einen SchedulingBrick, der die Dauer der Route berücksichtigt

In weiteren Schritten zeigen wir, wie Sie das Produkt um diese Funktionen erweitern:

* zonenbasierte Preise mit einer Zonentabelle aus dem DataPool
* dynamisch auf Properties von Shipper oder Carrier reagieren

### 1. Eine Route im ContextBrick berechnen

Das Produkt in dieser Anleitung berechnet Preise und Laufzeiten auf Basis der Route, die für den angefragten Transport berechnet wird. Das erledigen wir im ContextBrick des Produkts.

1. Legen Sie im Tab „ProductBricks“ im Einstellungsbereich von MissionControl einen neuen ContextBrick an. Klicken Sie dazu im Bereich ContextBrick auf „ProductBrick erstellen“. Geben Sie einen Namen und eine Beschreibung für Ihren ProductBrick ein. Einen DataPool müssen Sie vorerst nicht verknüpfen. Klicken Sie auf „Anlegen“.
2. Suchen Sie den neuen ContextBrick in der Liste und klicken Sie auf „Mehr anzeigen“. Klicken Sie auf „Code-Editor öffnen“, um die Logik zu bearbeiten. Entsperren Sie den Code-Editor mit dem Schloss-Symbol unten links im Editorfenster. Jetzt können wir die Logik des ProductBricks schreiben, indem wir seinen Code bearbeiten. Die erste und die letzte Zeile des Codes lassen sich nicht bearbeiten, denn sie legen das Format der Brick-Funktion fest. Unser eigener Code kommt in den Rumpf dieser Funktion.
3. Um eine Route zu berechnen, brauchen wir zuerst eine Routenanfrage. Sie enthält Start- und Endpunkte und einige Einstellungen, die das System zur Routenberechnung braucht. Die Anfrage ist nur ein Datenobjekt in einem bestimmten Format, und wir könnten sie im Code selbst aufbauen. Einfacher geht es mit der Hilfsfunktion `buildRouteRequest`. Fügen Sie diesen Code in Ihren Brick ein:

   ```ts
   const routeRequest = utils.route.buildRouteRequest(
   	input.request.tour?.stops.map((s) => s.geocoded) ?? [],
     { transportMode: "truck" }
   );
   ```

   Dieser Code übergibt die Koordinaten aller Stopps aus der eingehenden Transportanfrage als erstes Argument an die Funktion buildRouteRequest. Das zweite Argument ist das Options-Objekt. Es setzt die Transportart auf Lkw, damit der Routing-Dienst die Straßenbeschränkungen für diese Fahrzeugklasse beachtet. Sie können dem Objekt weitere Optionen hinzufügen.

   > **Tip:** `utils.route.buildRouteRequest` ist eine Hilfsfunktion, die eine einfache Routenanfrage für einfache Fälle erzeugt.
   >
   > Für anspruchsvollere Fälle empfehlen wir, die Anfrage selbst aufzubauen und an `utils.route.calculateRoutes` zu übergeben. So haben Sie alle Routing-Parameter selbst in der Hand.
   >
   > Das Objekt, das Sie an `utils.route.calculateRoutes` übergeben, ist vollständig typisiert. Die Typen zeigen also gültige Eingabewerte und liefern hilfreiche Hinweise im Editor und Prüfungen beim Kompilieren. Zum Beispiel:
   >
   > ```ts
   > const origin = input.request.tour?.stops?.[0]?.geocoded.position?.lat + "," + input.request.tour?.stops?.[0]?.geocoded.position?.lng;
   > const destination = input.request.tour?.stops?.[1]?.geocoded.position?.lat + "," + input.request.tour?.stops?.[1]?.geocoded.position?.lng;
   >
   > const [route] = await utils.route.calculateRoutes([
   >     {
   >         origin,
   >         destination,
   >         transportMode: "truck",
   >         vehicle: {
   >             height: 280,
   >             length: 1300,
   >             width: 240,
   >             grossWeight: 40000,
   >         },
   >         tolls: {
   >             emissionType: "euro6",
   >         },
   >     },
   > ]);
   > ```
   >
   > Das Objekt für die Routenanfrage bietet alle Parameter der HERE Routing API als Objekt an. Die vollständige Liste der Felder und gültigen Werte finden Sie in der Dokumentation von HERE: [HERE Routing API documentation.](https://www.here.com/docs/bundle/routing-api-v8-api-reference/page/index.html)
4. Die Routenanfrage steht. Jetzt schicken wir sie an unseren Routing-Dienst, um die Route zu erhalten. Fügen Sie unter dem vorigen Code-Block diesen Code ein:

   ```tsx
   const [route] = await utils.route.calculateRoutes([routeRequest]);
   ```
5. Wir haben vom Routing-Dienst ein `route`-Objekt erhalten. Es enthält Angaben wie Entfernung, Dauer usw. Diese Angaben lesen und nutzen wir in den nächsten Schritten. Vorerst geben wir die `route` aus dem Context-Brick zurück, damit alle anderen Bricks (z. B. der PricingBrick) auf die Routendaten zugreifen können. Fügen Sie diesen Code direkt vor den schließenden Klammern der Funktion ein:

   ```tsx
   return {
       isFeasible: true,
       result: {
         route,
         context: {},
       },
     };
   ```

   Die Context-Funktion hat ein festes Rückgabeformat mit zwei Arten von Informationen:
6. Das Flag `isFeasible` zeigt an, ob der angefragte Transport mit diesem Produkt machbar ist. Weil unser Context-Brick im Moment keine Machbarkeit prüft, geben wir fest den Wert `true` zurück.
7. Das `result` des Context-Bricks, das alle anderen Bricks erhalten. Es kann im vorgesehenen Schlüssel `route` eine Route enthalten und im Schlüssel `context` weitere Daten (im Moment leer).

### 2. Einfache Machbarkeit

Der Context unseres Produkts mit den Routendaten steht. Jetzt bauen wir eine einfache Machbarkeitsprüfung. Unser Produkt soll machbar sein, wenn zwei Bedingungen erfüllt sind: 1. Die gesamte Transportstrecke liegt unter 1000 km, und 2. das Gesamtgewicht aller Ladungen liegt unter 27 t. Meist sind Machbarkeitsprüfungen natürlich umfangreicher. Für den Anfang bleiben wir bei diesen einfachen Prüfungen. Setzen wir sie um:

1. Legen Sie im Tab „ProductBricks“ im Einstellungsbereich von MissionControl einen neuen FeasibilityBrick an. Klicken Sie dazu im Bereich FeasibilityBrick auf „ProductBrick erstellen“. Geben Sie einen Namen und eine Beschreibung für Ihren Brick ein. Einen DataPool müssen Sie vorerst nicht verknüpfen. Klicken Sie auf „Anlegen“.
2. Zuerst stellen wir sicher, dass die Transportstrecke unter 1000 km liegt. Die erzeugte Route erreichen wir über den Parameter context, der an unsere Funktion übergeben wird. Routenobjekte sind komplex und enthalten viele verschachtelte Daten. Deshalb bietet Orbit Hilfsfunktionen, die die Arbeit mit Routen erleichtern. Eine davon ist `getVehicleSectionDistanceSum`. Sie nimmt ein Routenobjekt entgegen und gibt die gesamte Strecke zurück, die das Fahrzeug fahren muss (ohne Abschnitte per Fähre usw.). Mit dieser Funktion ermitteln wir die gesamte Routenlänge und prüfen, ob sie unter 1000 km liegt. Beachten Sie, dass Orbit Entfernungen meist in Metern angibt. Das müssen wir beim Vergleich berücksichtigen. Fügen Sie der Feasibility-Funktion diesen Block hinzu:

   ```tsx
   const distance = utils.route.getVehicleSectionDistanceSum(context.route!.routes[0].sections);
   const isDistanceBelowLimit = distance <= 1000 * 1000;
   ```
3. Die erste Bedingung ist berechnet. Weiter mit der zweiten: Das Gesamtgewicht der Ladung muss unter 27 t liegen. Ein Transport kann mehrere Stopps mit Be- und Entladungen haben. Deshalb ist es nicht leicht, das maximale Ladungsgewicht eines Transports zu bestimmen. Auch hier bietet Orbit eine Hilfsfunktion. Sie heißt maxWeightForStopCountAndLoads, nimmt die Anzahl der Stopps eines Transports und die transportierten Ladungen entgegen und gibt das maximale Gewicht zurück. Damit berechnen wir das Maximum und prüfen, ob es unter unserer Grenze von 27 t liegt. Orbit gibt Gewichte in kg an. Deshalb rechnen wir Tonnen vor dem Vergleich in kg um. Fügen Sie diesen Code im Feasibility-Brick direkt unter dem vorigen Code ein:

   ```tsx
   const maxTotalWeight = utils.loads.maxWeightForStopCountAndLoads(
       input.request.tour.stops.length,
       input.request.tour.loads
     );
   const isWeightBelowLimit = maxTotalWeight <= 27 * 1000;
   ```
4. Sehr gut! Wir haben beide Bedingungen berechnet und in den Variablen `isDistanceBelowLimit` und `isWeightBelowLimit` gespeichert. Jetzt soll unser Produkt machbar sein, wenn beide Bedingungen erfüllt sind. Dazu verknüpfen wir die beiden Bedingungen mit dem logischen UND-Operator `&&` und geben den Wert im Schlüssel `isFeasible` unserer Funktion zurück.

```tsx
return {
	isFeasible: isDistanceBelowLimit && isWeightBelowLimit
};
```

Perfekt. Wir haben eine einfache Feasibility-Funktion gebaut, die zwei Bedingungen prüft.

### 3. Parametrisierte Preise

Wir haben einen Context mit Routendaten erstellt und die Machbarkeit unseres Produkts geregelt. Jetzt kommen wir zur Kernaufgabe der Produktlogik: der Preisberechnung. Wir legen einen DataPool mit den Konstanten für die Preisberechnung an. Dann lesen wir diese Daten in unserer Logik und berechnen damit den Preis. Los geht's.

1. Legen Sie im Tab „DataPools“ im Einstellungsbereich von MissionControl einen neuen DataPool an. Klicken Sie dazu auf „DataPool erstellen“. Geben Sie einen Namen und eine Beschreibung für Ihren DataPool ein. Klicken Sie auf „Anlegen“.
2. Um den DataPool mit den nötigen Daten zu füllen, klicken Sie oben rechts im neuen DataPool auf „anzeigen & bearbeiten“. Ein Fenster mit den Daten des DataPools öffnet sich. Entsperren Sie den DataPool mit dem Schloss-Symbol unten links, um die Bearbeitung freizugeben.
3. Für unser einfaches Preismodell brauchen wir nur zwei Konstanten: den Preis pro Kilometer und den aktuellen Kraftstoffzuschlag (Fuel Floater). In DataPools lassen sich auch viel komplexere Daten ablegen, etwa Listen und Matrizen (z. B. Zonentabellen oder Preislisten nach Klassen). Das zeigen wir in einem anderen Beispiel. Tragen Sie jetzt die beiden Konstanten neben ihren Namen in den DataPool ein. Ihr DataPool sollte so aussehen:

   ![Screenshot 2024-07-16 um 15.27.22](https://support.pr-4.orbit.do/images/products/screenshot-2024-07-16-at-15-27-22.png)
4. Klicken Sie auf „Speichern“, um den DataPool zu speichern.
5. Jetzt legen wir den PricingBrick an, in dem wir den eben erstellten DataPool nutzen. Legen Sie im Tab „ProductBricks“ im Einstellungsbereich von MissionControl einen neuen PricingBrick an. Klicken Sie dazu im Bereich PricingBrick auf „ProductBrick erstellen“. Geben Sie einen Namen und eine Beschreibung für Ihren Brick ein.
6. Öffnen Sie den Code-Editor für den neuen Brick und beginnen Sie mit der Bearbeitung. DataPools rufen Sie mit der Funktion `utils.dataPools.getDataPoolsByNames` ab.\
   Auf die Daten in Ihrem DataPool greifen Sie so zu:

   ```tsx
   const [yourDataPool, otherDataPool] = await utils.dataPools.getDataPoolsByNames(
   	["YourDataPool", "OtherDataPool"]
   );
   ```
7. Damit Sie leichter auf Daten in einem DataPool zugreifen, bietet Orbit zwei Hilfsfunktionen: `getSheetColumns` und `getSheetRows`. Sie lesen bestimmte Spalten (oder Zeilen) aus einem Tabellenblatt in eine typisierte Matrix. Wir möchten Spalte „B“ als Liste (1D-Matrix) von Gleitkommazahlen auslesen. Weil wir eine Spalte auslesen, nutzen wir `getSheetColumns`. Fügen Sie diesen Code-Block in den Rumpf des PricingBricks ein.

   ```ts
   const constants = utils.dataPools.getSheetColumns(
       yourDataPool["Sheet 1"],
       { startRow: 0, endRow: 1, parser: parseFloat },
       1
   )[0];
   ```

   Das erste Argument von `getSheetColumns` ist das Tabellenblatt, aus dem wir lesen. Jeder DataPool enthält mehrere Tabellenblätter, ähnlich wie eine Tabellenkalkulation. Das erste Blatt heißt standardmäßig „Sheet 1“. Sie können den Namen unten links im DataPool-Editor ändern, wo Sie auch zwischen den Blättern wechseln. Das zweite Argument ist ein Objekt mit Optionen. Weil wir nur zwei Konstanten haben, brauchen wir nur die ersten beiden Zeilen. Dazu setzen wir Start- und Endindex auf 0 und 1. Weil unsere Konstanten Gleitkommazahlen sind, übergeben wir den Standard-Parser `parseFloat` als `parser`. Das letzte Argument ist der Spaltenindex. Beim Befüllen des DataPools haben wir die Namen in die erste Spalte und die Werte in die zweite geschrieben. Deshalb lesen wir hier die zweite Spalte (Index 1) aus. `getSheetColumns` kann mehrere Spalten auslesen und gibt deshalb eine Liste von Spalten zurück (ein Array von Arrays). Wir haben nur eine Spalte ausgelesen und brauchen nur deren Daten. Um nur auf die erste Spalte zuzugreifen, hängen wir an den Funktionsaufruf den Zugriff auf den ersten Index `[0]` an.
8. Als Nächstes holen wir die beiden Konstanten aus der Spalte, die wir im vorigen Schritt erhalten haben. Wir wissen, dass sie das erste und zweite Element der Spalte sind. Das erledigt diese Codezeile:

   ```tsx
   const [pricePerKm, fuelFloater] = constants;
   ```
9. Für den Endpreis brauchen wir die gesamte Strecke, die für diesen Transport gefahren werden muss. Wir ermitteln sie wie vorhin in der Feasibility-Funktion mit der Hilfsfunktion `getVehicleSectionDistanceSum`:

   ```tsx
   const vehicleDistance = utils.route.getVehicleSectionDistanceSum(
       context.route!.routes[0].sections
    );
   ```
10. Jetzt haben wir alle Angaben für die Preisberechnung. Fügen Sie diese Codezeile ein:

    ```tsx
    const price = Math.round(pricePerKm * vehicleDistance / 1000 * (1 + fuelFloater / 100));
    ```

    Das wirkt zuerst kompliziert, ist aber einfach: Für den Endpreis (in Cent) multiplizieren wir den Preis pro km mit den gefahrenen Kilometern (Entfernung in Metern geteilt durch 1000). Das Ergebnis multiplizieren wir mit einem Faktor für den Kraftstoffzuschlag. Diesen Faktor erhalten wir, indem wir den Prozentwert in eine Dezimalzahl umrechnen (geteilt durch 100) und 1 addieren. Das Ergebnis ist in Cent. Deshalb runden wir auf eine ganze Zahl, denn Bruchteile von Cent gibt es nicht. Das ist die gesamte Logik der Preisberechnung, und unser Pricing-Brick ist fast fertig.
11. Jetzt müssen wir nur noch das Ergebnis aus dem Brick zurückgeben. Geldbeträge stehen in Orbit als Einzelpositionen. Ein Pricing-Brick gibt immer eine Liste von Einzelpositionen zurück. Hier enthält die Liste nur einen Eintrag: die Grundposition mit dem berechneten Preis. Fügen Sie diesen Code vor den schließenden Klammern der Brick-Funktion ein. Die Werte `name`, `vatPercentage` und `subtype` können Sie bei Bedarf anpassen.

```tsx
return [
    {
      amount: price,
      vatPercentage: 19,
      name: "Grundpreis",
      subtype: "base",
      type: "base",
      count: 1,
    },
];
```

Sehr gut! Wir haben eine parametrisierte Preisfunktion gebaut. Wenn wir den Kraftstoffzuschlag oder den Preis pro km ändern möchten, müssen wir die Logik nicht mehr anfassen. Wir ändern einfach die Werte im DataPool, und der Brick nutzt sie. Zusammen mit der zeitbasierten Versionierung können wir Änderungen, etwa beim Kraftstoffzuschlag, im Voraus planen und müssen Werte nicht genau dann ändern, wenn sie in Kraft treten.

### 4. Einfache Terminierung

Den größten Teil der Logik für unser Produkt haben wir umgesetzt. Es fehlt nur noch der SchedulingBrick. Er sagt dem System, wie Beladung, Zustellung und alle Zwischenstopps terminiert werden können. Scheduling-Bricks erzeugen mit `utils.scheduling.buildSchedule()` ein anzeigefertiges `Schedule`-Objekt. Los geht's!

1. Legen Sie im Tab „ProductBricks“ im Einstellungsbereich von Orbit MissionControl einen SchedulingBrick an. Klicken Sie dazu im Bereich SchedulingBrick auf „ProductBrick erstellen“. Geben Sie einen Namen und eine Beschreibung ein. Klicken Sie auf „Anlegen“.
2. Die Funktion `buildSchedule()` nimmt eine Liste von Stopp-Konfigurationen entgegen und gibt ein `Schedule` zurück, das der Buchungsassistent direkt anzeigt. Jeder Stopp legt seine eigenen Terminoptionen, Betriebszeiten und gesperrten Wochentage fest. Für aufeinanderfolgende Stopps können Sie Fahrtbedingungen hinzufügen, die eine Mindestfahrzeit zwischen den Stopps erzwingen. Fügen Sie diesen Code in den Rumpf des SchedulingBricks ein:

```ts
// Calculate the route duration from the context brick
const routeDuration = utils.route.getTotalRouteDuration(context.route?.routes[0]);

// Build date strings for recommendations
const today = new Date();
const yyyy = today.getFullYear();
const mm = String(today.getMonth() + 1).padStart(2, '0');
const dd = String(today.getDate()).padStart(2, '0');
const todayStr = yyyy + '-' + mm + '-' + dd;

const stops = input.request.tour?.stops ?? [];
const lastStopIndex = stops.length - 1;

const schedule = utils.scheduling.buildSchedule({
  timezone: "Europe/Berlin",
  stops: stops.map((_, i) => ({
    options: [
      {
        id: "fix",
        label: { de: "Fixtermin", en: "Fixed Time" },
        granularity: "time",
        timeInterval: { hour: 0, minute: 15, second: 0, millisecond: 0 },
      },
      {
        id: "window",
        label: { de: "Zeitfenster", en: "Time Window" },
        granularity: "timewindow-fixed",
        timewindowFixed: {
          from: { hour: 8, minute: 0, second: 0, millisecond: 0 },
          to: { hour: 20, minute: 0, second: 0, millisecond: 0 },
        },
      },
    ],
    operatingHours: input.product.operatingHours,
    disabledWeekdays: input.product.disabledWeekdays,
    // For stops after the first, add a transit constraint
    ...(i > 0
      ? {
          transit: {
            durationSeconds: Math.round(routeDuration),
            anchor: "start",
          },
        }
      : {}),
  })),
  selections: input.request.scheduleSelections ?? [],
  recommendations: stops.length === 2
    ? [
        {
          id: "standard",
          scope: "global",
          label: { de: "Standard", en: "Standard" },
          description: {
            de: "Abholung heute, Lieferung morgen",
            en: "Pickup today, delivery tomorrow",
          },
          presets: [
            {
              stopIndex: 0,
              optionId: "window",
              date: todayStr,
              windowFrom: "08:00",
              windowTo: "20:00",
            },
          ],
        },
      ]
    : [],
});

return schedule;
```

Was dieser Code tut:

* **Konfiguration pro Stopp:** Jeder Stopp der Tour bekommt eigene Terminoptionen. Hier bieten wir an jedem Stopp zwei Optionen an: eine feste Uhrzeit (im 15-Minuten-Takt) und ein Zeitfenster (08:00–20:00). Betriebszeiten und gesperrte Wochentage des Produkts gehen an jeden Stopp weiter.
* **Fahrtbedingung:** Für jeden Stopp nach dem ersten fügen wir ein `transit`-Objekt hinzu. Das Feld `durationSeconds` ist die Routendauer aus unserem Context-Brick. `anchor: "start"` bedeutet, dass die Fahrzeit ab dem Beginn des gewählten Fensters am vorherigen Stopp zählt. So bietet das System keine Zustellzeiten an, die praktisch nicht erreichbar sind.
* **Empfehlungen:** Für Touren mit zwei Stopps legen wir eine Empfehlung „Standard“ fest, die Beladung heute und Zustellung morgen vorausfüllt. Mit Empfehlungen wählen Kunden bequem eine Vorgabe mit einem Klick.
* **Auswahl:** `input.request.scheduleSelections` enthält, was der Kunde schon ausgewählt hat (z. B. hat er ein Beladedatum gewählt und wählt jetzt die Zustellung). `buildSchedule()` filtert damit die verfügbaren Daten und Zeitfenster der folgenden Stopps.

### 5. Alles zusammenführen

Jetzt können wir unser Produkt anlegen. Los geht's:

1. Legen Sie im Tab „Products“ im Einstellungsbereich von MissionControl ein neues Produkt für Verkauf und Einkauf an. Klicken Sie dazu im Bereich Verkauf & Einkauf auf „Product erstellen“. Dieses Produkt dient Verkauf und Einkauf, zeigt im Moment aber für beide dieselben Preise an. In der Praxis müssten Sie das anpassen, entweder über die Preislogik oder mit getrennten Produkten für Verkauf und Einkauf.
2. Geben Sie dem Produkt einen Namen und eine Beschreibung. Diese Felder lassen sich lokalisieren, damit Operator und Kunden die Texte in ihrer gewählten Sprache sehen.
3. Die Felder für Tooltip und Info sehen Kunden in Orbit Hub. Wir überspringen diese Felder vorerst. Wenn Sie das Produkt in Orbit Hub nutzen möchten, sollten Sie sie aber unbedingt ausfüllen. Auch die Verfügbarkeitseinschränkungen überspringen wir vorerst, denn sie sind eine Funktion für Fortgeschrittene.
4. Fügen Sie in jedem der vier folgenden ProductBrick-Bereiche den passenden Brick hinzu, den wir vorher erstellt haben.
5. Im Bereich Metadaten legen wir die Betriebszeit und die gesperrten Wochentage fest. Wir arbeiten jeden Tag von 7:00 bis 18:00, außer Samstag und Sonntag. Klicken Sie auf „Anlegen“.
6. Herzlichen Glückwunsch! Sie haben Ihr erstes Orbit-Produkt angelegt. Sie können es jetzt an mehreren Stellen in Orbit nutzen, z. B. um im ShipmentRouter den Preis einer Tour zu berechnen. Für Hub und den TransportComposer brauchen Sie zusätzliche Einstellungen in der Shop-Konfiguration oder in der zugehörigen [TransportShape](https://support.pr-4.orbit.do/de/advanced-features/transport-shape).

### 6. Mit LoadPlans arbeiten

Wer mit Ladungen und Fahrzeugen oder Fahrzeugklassen arbeitet, fragt oft, ob eine Menge von Ladungen unter bestimmten Platzvorgaben in ein Fahrzeug oder eine Fahrzeugklasse passt und wie viel Platz die Ladungen in jeder Richtung belegen.

Ladungen im dreidimensionalen Raum zu packen ist ein schwieriges Optimierungsproblem. Orbit löst es mit Algorithmen auf dem neuesten Stand. Im ProductBrick-Code steht diese Funktion als LoadPlan bereit (genauer: LoadPlan3D).

Im folgenden Beispiel berechnen wir einen Ladeplan. Damit prüfen wir, ob die Ladungen in einen bestimmten Container passen, und ermitteln, wie viel Platz sie in jeder Richtung des Containers belegen.

\1. Für den Ladeplan nutzen wir die Hilfsfunktion aus den Ladungs-Hilfsfunktionen. Die Funktion zur Berechnung des Ladeplans nimmt eine Liste von Ladungen und eine Liste von Containern entgegen. Sie verteilt immer alle Ladungen auf jeden Container. Sie gibt für jeden übergebenen Container einen Ladeplan zurück. Die Reihenfolge der Ladepläne entspricht der Reihenfolge der Container: Der Ladeplan an Index 0 gehört zum Container an Index 0.

```ts
const [loadplan] = await utils.loads.calculateLoadPlan3DsForContainers(
  input.request.tour!.loads,
  [
    {
      containerHeight: 270,
      containerLength: 720,
      containerWidth: 245,
      weightCapacity: 10000,
      accessDirection: ["Back"],
    },
  ]
);
```

Das zurückgegebene Ladeplan-Objekt enthält genaue dreidimensionale Koordinaten für jede Ladung. Damit ließe sich eine ausführliche Darstellung bauen, aber im ProductBrick-Code ist das meist nicht das Ziel. Stattdessen lesen wir aus dem Ladeplan ein paar einfache Kennzahlen.

1. Der Ladeplan ist berechnet. Jetzt prüfen wir, ob alle Ladungen in den Container passen oder ob einige wegen Platzmangel außerhalb liegen. Dafür nutzen wir eine Hilfsfunktion.

   ```ts
   const doesFit = utils.loads.doesLoadPlanFit(loadplan);
   ```
2. Passen alle Ladungen in den Container, berechnen wir, wie viel Platz sie in der Länge belegen („Lademeter“). Dieser Wert dient oft zur Berechnung der Transportkosten: Die belegten Meter werden mit einem Preisfaktor multipliziert. Wir nutzen wieder eine Hilfsfunktion und lesen dann eine Eigenschaft des Objekts.

   ```ts
   const extents = utils.loads.getLoadPlanExtents(loadplan);

   const ldm = extents.maxLoadingLength;
   ```
3. Das war's. Wir haben geprüft, ob die Ladungen in einen bestimmten Container passen, und den belegten Platz ermittelt. Diese Angaben können wir in unserer Preisberechnung nutzen.

### 7. Zonenbasierte Preise

Die Entfernung ist nicht immer die richtige Grundlage für einen Preis. Viele Tarife rechnen stattdessen nach **Zonen**: Start und Ziel liegen jeweils in einem benannten Gebiet (zum Beispiel einem Land, einer Region oder einer Gruppe von Postleitzahlen). Den Preis liest man aus einer Matrix, die jede Startzone jeder Zielzone zuordnet. Dafür eignet sich ein DataPool gut.

Der Ansatz baut direkt auf dem PricingBrick aus Schritt 3 auf. Nur die Quelle des Preises ändert sich:

1. Legen Sie einen DataPool mit Ihrer Zonenmatrix an. Ein üblicher Aufbau nutzt ein Tabellenblatt, in dem die erste Spalte und die erste Zeile die Zonennamen enthalten. Jede übrige Zelle enthält den Preis für dieses Paar aus Start und Ziel. Ein zweites Tabellenblatt kann Postleitzahlen oder Länder den Zonennamen zuordnen. So kann der Brick die Adresse eines Stopps einer Zone zuordnen.
2. Laden Sie im PricingBrick den DataPool mit `utils.dataPools.getDataPoolsByNames`, genau wie in Schritt 3.
3. Bestimmen Sie Start- und Zielzone. Jeder Stopp der Anfrage trägt seine Adresse. Sie können also `input.request.tour?.stops[0].address.zipCode` (oder `country`) für den Start lesen und den entsprechenden Wert am letzten Stopp für das Ziel. Schlagen Sie dann beide im Tabellenblatt mit der Zuordnung von Postleitzahl zu Zone nach.
4. Lesen Sie die passende Zelle aus der Matrix. Mit `utils.dataPools.getSheetRows` oder `getSheetColumns` lesen Sie die passende Zeile und Spalte aus, oder Sie wandeln das Tabellenblatt mit `matrixToJson` in Objekte um, auf die Sie per Index zugreifen. Der Wert, an dem sich Startzeile und Zielspalte treffen, ist Ihr Zonenpreis.
5. Geben Sie diesen Wert als Einzelposition zurück, genau wie in Schritt 3.

Weil der ganze Tarif im DataPool liegt, ändern Sie Preise oder fügen Zonen hinzu, indem Sie Daten bearbeiten, ganz ohne Code-Änderung. Mit der zeitbasierten Versionierung planen Sie außerdem eine neue Preistabelle für ein späteres Datum.

### 8. Preise nach Properties des Shippers anpassen

Preise hängen oft davon ab, *wer* bucht, nicht nur davon, was transportiert wird. Ein bestimmter Shipper hat vielleicht einen ausgehandelten Tarif, einen vertraglichen Rabatt oder einen Zuschlag, der an seine Einrichtung gebunden ist. Ist für den Transport ein Shipper ausgewählt, erhält der Brick diesen Shipper samt aller dafür eingerichteten benutzerdefinierten Felder (Properties). So kann die Preislogik darauf reagieren.

* Der ausgewählte Shipper steht in `input.request.shipper`, seine benutzerdefinierten Felder in `input.request.shipper.properties`. Die benutzerdefinierten Felder des Transports selbst stehen in `input.request.shipmentProperties`, `orderProperties` und `tourProperties`.
* Ein Shipper ist erst vorhanden, wenn einer ausgewählt ist, zum Beispiel nachdem Sie im Orbit TransportComposer einen Shipper gewählt haben. Prüfen Sie immer den Fall, dass `input.request.shipper` `undefined` ist, und nutzen Sie dann Ihren Standardpreis.

Ein typisches Muster liest einen Wert, der zum Shipper gehört, und wendet ihn als Faktor oder als zusätzliche Einzelposition auf den Grundpreis aus Schritt 3 an:

```ts
const shipper = input.request.shipper;
if (shipper) {
  // Property ids and names are tenant-specific configuration, so keep the
  // "which shipper gets which terms" mapping in a DataPool rather than
  // hard-coding ids or names in the brick.
  const [terms] = await utils.dataPools.getDataPoolsByNames(["ShipperTerms"]);
  // ...look up this shipper's discount or surcharge in the DataPool
  //    and adjust the returned line items accordingly.
}
```

Legen Sie die Werte für einzelne Shipper in einem DataPool ab, statt im Brick nach fest eingetragenen Shipper-IDs oder Property-Namen zu verzweigen. So bleibt die Logik für alle Shipper wiederverwendbar, und Sie ändern kaufmännische Bedingungen, indem Sie Daten bearbeiten. Dieselbe Eingabe enthält bei Einkaufsprodukten den ausgewählten Carrier in `input.request.carrier`. Mit genau derselben Technik passen Sie also die Kosten auf der Einkaufsseite an.

## Hilfsfunktionen

Die folgenden Hilfsfunktionen stehen im Objekt `utils` bereit. Sie nutzen sie in ProductBricks für verschiedene Berechnungen und zur Bearbeitung von Daten.

### Route

```tsx
getVehicleSectionDistanceSum(sections: RouterSections[]): number
```

Funktion, die die Strecke berechnet, die ein Fahrzeug auf einer Route fahren muss. Abschnitte, die z. B. per Fähre zurückgelegt werden, zählen nicht mit. Das ist der übliche Weg, die Entfernung einer Route zu berechnen. Gibt die gesamte Entfernung in Metern zurück.

```tsx
getTransitSections(r?: HereRoute): HereRouterComponents["schemas"]["TransitSection"][]
```

Funktion, die die Transit-Abschnitte einer Route ausliest. Gibt ein Array von Transit-Abschnitten zurück oder ein leeres Array, wenn keine Route übergeben wird.

```tsx
getTotalRouteDuration(r?: HereRoute): number
```

Funktion, die die Gesamtdauer einer Route berechnet. Gibt die Gesamtdauer in Sekunden zurück oder 0, wenn keine Route übergeben wird.

```tsx
groupRouteSectionsForWaypoints(sections: HereRouterComponents["schemas"]["RouterSection"][], originalWaypoints: Location[]): HereRouterComponents["schemas"]["RouterSection"][][]
```

Funktion, die Routenabschnitte nach Wegpunkten gruppiert. Gibt ein Array von Abschnittsgruppen zurück. Jede Gruppe enthält die Abschnitte, die nötig sind, um einen Wegpunkt zu erreichen.

```tsx
buildRouteRequest(stops: GeocodedStop[], options: { transportMode: string }): RouteRequest
```

Funktion, die ein Routenanfrage-Objekt für eine Menge von Stopps und eine Transportart erzeugt.

### DataPools

```
getDataPoolsByNames(names: string[]): Promise<(Record<string, string[][]> | null)[]>
```

Funktion, die einen oder mehrere DataPools asynchron über ihren Namen abruft. Sie ist der wichtigste Weg, aus dem Code eines ProductBricks auf DataPools zuzugreifen.

> **Note:** Wenn Sie noch die inzwischen veraltete Verknüpfung von DataPools nutzen, stellen Sie auf diese neue Funktion um. Verknüpfte DataPools funktionieren auf absehbare Zeit weiter, aber DataPool-Verknüpfungen lassen sich in der ProductBricks-Oberfläche in MissionControl nicht mehr bearbeiten oder ansehen.

```tsx
getSheetColumns<T = string>(sheet: string[][], options: Object, ...columnIndices: Array<number | number[]>): T[][]
```

Funktion, die bestimmte Spalten aus einem Tabellenblatt in eine typisierte Matrix ausliest und optional umwandelt.

```tsx
getSheetRows<T = string>(sheet: string[][], options: Object, ...rowIndices: Array<number | number[]>): T[][]
```

Funktion, die bestimmte Zeilen aus einem Tabellenblatt in eine typisierte Matrix ausliest und optional umwandelt.

```tsx
matrixToJson(data: string[][]): object[]
```

Funktion, die eine Datenmatrix in ein Array von JSON-Objekten umwandelt.

```tsx
parseObjectsFromSheetRows<T>(schema: ZodType<T>, sheet: string[][], options: Object, rowIndices: number[]): T[]
```

Funktion, die Objekte aus Zeilen eines Tabellenblatts mit einem Zod-Schema einliest und prüft.

```ts
getDataPoolDimensions(sheet: string[][]): { rows: number; columns: number }
```

Funktion, die die tatsächliche Größe eines DataPools bestimmt, indem sie die letzte Zeile und Spalte mit einem Wert sucht. Funktioniert auch zuverlässig, wenn der DataPool viele leere Zellen hat.

### Umrechnungen

```tsx
kmhToMetersPerSecond(kmh: number): number
```

Funktion, die eine Geschwindigkeit von Kilometern pro Stunde in Meter pro Sekunde umrechnet.

### Ladungen

```tsx
calculateLoadPlan3DsForContainers(loads: Load[], containers: Container[]) => Promise<LoadPlan3D[]>;
```

Funktion, die für jeden übergebenen Container einen Ladeplan mit allen Ladungen berechnet. Die Reihenfolge der Ladepläne im zurückgegebenen Array entspricht der Reihenfolge der übergebenen Container. Aus Leistungsgründen sollten Sie alle benötigten Ladepläne in einer Anfrage berechnen, statt diese Funktion mehrmals nacheinander aufzurufen.

> **Warning:** Die alte Funktion `calculateLoadPlan` berechnet keinen 3D-Ladeplan und ist offiziell veraltet. Wenn Sie `calculateLoadPlan` noch nutzen, stellen Sie auf `calculateLoadPlan3DsForContainers` um. Die alte Funktion wird in einer der nächsten Versionen entfernt.

```ts
getLoadPlanExtents(loadPlan: StopCentricLoadPlan): LoadPlanExtents
```

Funktion, die den größten Platz bestimmt, den die Ladungen in einem Ladeplan belegen. Gibt ein Objekt mit der größten Ausdehnung der Ladungen entlang jeder Achse zurück.

```ts
doesLoadPlanFit(loadPlan: LoadPlan3D): boolean
```

Funktion, die prüft, ob laut Ladeplan alle Ladungen in ihren Container passen. Gibt die Funktion false zurück, liegt mindestens eine Ladung außerhalb des Containers.

```ts
hasDangerousGoods(loads: BaseLoad[]): boolean
```

Funktion, die prüft, ob eine der übergebenen Ladungen Gefahrgut enthält.

```ts
maxWeightForStopCountAndLoads(stopCount: number, loads: Load[]): number | undefined
```

Funktion, die für eine Anzahl von Stopps und Ladungen das höchste Ladungsgewicht an einem Stopp berechnet.

### Terminierung

```ts
buildSchedule(params: BuildScheduleParams): Schedule
```

Die wichtigste Funktion für die Terminierung. Sie nimmt eine deklarative Konfiguration entgegen und gibt ein anzeigefertiges `Schedule`-Objekt zurück, das der Buchungsassistent direkt anzeigt. Scheduling-Bricks rufen diese Funktion auf und geben ihr Ergebnis zurück.

**Parameter (**`BuildScheduleParams`**):**

| Field             | Type                                | Description                                                                                  |
| ----------------- | ----------------------------------- | -------------------------------------------------------------------------------------------- |
| `timezone`        | `string`                            | IANA-Zeitzonenkennung (z. B. `"Europe/Berlin"`)                                              |
| `stops`           | `BuildScheduleStopParams[]`         | Geordnete Liste der Konfigurationen pro Stopp (mindestens eine)                              |
| `selections`      | `ScheduleSelection[]`               | Aktuelle Auswahl des Nutzers, meist direkt aus `input.request.scheduleSelections` übernommen |
| `recommendations` | `RecommendationSchema[]` (optional) | Globale Vorgaben für einen Klick, oben im Assistenten angezeigt                              |

**Felder pro Stopp (**`BuildScheduleStopParams`**):**

| Field              | Type                      | Description                                                                                                                                 |
| ------------------ | ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
| `options`          | `ScheduleOption[]`        | Verfügbare Terminoptionen (mindestens eine). Unterschieden nach `granularity`: `"time"`, `"timewindow-fixed"` oder `"timewindow-flexible"`. |
| `operatingHours`   | `{ from, to }` (optional) | Grenze für die Betriebszeit; jede Seite ist `{ hour, minute, second, millisecond }`                                                         |
| `disabledWeekdays` | `number[]` (optional)     | Zu sperrende Wochentage (0=Sonntag, 1=Montag, …, 6=Samstag)                                                                                 |
| `disabledDates`    | `string[]` (optional)     | Zu sperrende Daten als ISO-Datum (z. B. `"2026-12-25"`)                                                                                     |
| `earliestDate`     | `number` (optional)       | Epochensekunden: frühestes wählbares Datum für diesen Stopp                                                                                 |
| `latestDate`       | `number` (optional)       | Epochensekunden: spätestes wählbares Datum für diesen Stopp                                                                                 |
| `transit`          | `StopTransit` (optional)  | Fahrtbedingung ab dem vorherigen Stopp. Gilt nur für Stopps nach dem ersten.                                                                |
| `visible`          | `boolean` (optional)      | Ob der Stopp im Assistenten erscheint. Standard ist `true`.                                                                                 |

**Fahrtbedingung (**`StopTransit`**):**

| Field             | Type               | Description                                                                         |
| ----------------- | ------------------ | ----------------------------------------------------------------------------------- |
| `durationSeconds` | `number`           | Mindestfahrzeit zwischen den Stopps in Sekunden                                     |
| `anchor`          | `"start" \| "end"` | `"start"`: Fahrzeit ab dem Beginn des vorherigen Fensters. `"end"`: ab dessen Ende. |

**Arten von Optionen:**

* `"time"`: Der Nutzer wählt eine genaue Uhrzeit in einem einstellbaren Takt (z. B. alle 15 Minuten). Erfordert `timeInterval`.
* `"timewindow-fixed"`: Ein festes Zeitfenster, das sich nicht ändern lässt (z. B. 08:00–12:00). Erfordert `timewindowFixed: { from, to }`. Optional blendet `sameDayCutOffTime` die Option ab einer bestimmten Uhrzeit am selben Tag aus.
* `"timewindow-flexible"`: Der Nutzer wählt ein Fenster innerhalb der vom Operator gesetzten Grenzen (minimale/maximale Dauer, frühester Beginn, spätestes Ende). Erfordert `timewindowFlexible` mit Feldern für Dauer und Grenzen.

**Empfehlungen (**`RecommendationSchema`**):**

Empfehlungen sind Vorgaben für einen Klick, die die Terminauswahl für einen oder mehrere Stopps vorausfüllen. Sie erscheinen oben im Terminierungs-Assistenten (globale Ebene) oder innerhalb eines bestimmten Stopps (Stopp-Ebene). Jede Empfehlung enthält eine oder mehrere Vorgaben, die jeweils für einen bestimmten Stopp gelten.

| Field           | Type                      | Description                                                                                                        |
| --------------- | ------------------------- | ------------------------------------------------------------------------------------------------------------------ |
| `id`            | `string`                  | Eindeutige Kennung der Empfehlung                                                                                  |
| `scope`         | `"global" \| "stop"`      | `"global"`-Empfehlungen erscheinen auf der Ebene der Konfiguration, `"stop"`-Empfehlungen auf der Ebene des Stopps |
| `label`         | `Localization` (optional) | Anzeigename für den Kunden, z. B. `{ de: "Standard", en: "Standard" }`                                             |
| `description`   | `Localization` (optional) | Kurze Beschreibung, z. B. `{ de: "Abholung heute, Lieferung morgen", en: "Pickup today, delivery tomorrow" }`      |
| `surchargeHint` | `Localization` (optional) | Optionaler Hinweis, dass ein Zuschlag anfällt, z. B. `{ de: "Expresszuschlag", en: "Express surcharge" }`          |
| `presets`       | `RecommendationPreset[]`  | Eine oder mehrere Vorgaben für Stopps (mindestens eine nötig)                                                      |

**Vorgaben für Empfehlungen (**`RecommendationPreset`**):**

| Field        | Type                | Description                                                             |
| ------------ | ------------------- | ----------------------------------------------------------------------- |
| `stopIndex`  | `number`            | Index des Stopps (ab 0), für den diese Vorgabe gilt                     |
| `optionId`   | `string`            | Die `id` der Option, die am betreffenden Stopp vorausgewählt wird       |
| `date`       | `string`            | ISO-Datum, z. B. `"2026-03-21"`                                         |
| `windowFrom` | `string` (optional) | Fensterbeginn im Format HH:mm für Zeitfenster-Optionen, z. B. `"08:00"` |
| `windowTo`   | `string` (optional) | Fensterende im Format HH:mm für Zeitfenster-Optionen, z. B. `"20:00"`   |

> **Note:** Vorgaben für Empfehlungen können keine Optionen mit der Granularität `"timewindow-flexible"` nutzen. Flexible Fenster brauchen eine Eingabe des Nutzers und lassen sich nicht im Voraus festlegen.

### Werkzeuge

```tsx
emptyStringToUndefined(value: string): string | undefined
```

Funktion, die leere Strings in undefined umwandelt.

```tsx
z
```

Zod-Bibliothek zur Schema-Prüfung. Siehe Dokumentation [hier](https://zod.dev/).

### DateTime

```tsx
DateTime
```

DateTime-Objekt für Datums- und Uhrzeitberechnungen. Siehe Dokumentation [hier](https://moment.github.io/luxon/api-docs/index.html#datetime).

```tsx
Duration
```

Duration-Objekt für Berechnungen mit Zeitspannen. Siehe Dokumentation [hier](https://moment.github.io/luxon/api-docs/index.html#duration).

```tsx
getNextBookableDate(from: number, disabledWeekdays: number[], direction: "forward" | "backward" = "forward", minDaysDistance = 1): DateTime
```

Funktion, die anhand der übergebenen Parameter das nächste buchbare Datum ermittelt.

```tsx
getNextBookableTime(from: number, disabledWeekdays: number[] = [], openingHours: { from: TimeObjectUnits; to: TimeObjectUnits } = { from: { hour: 0, minute: 0, second: 0, millisecond: 0 }, to: { hour: 0, minute: 0, second: 0, millisecond: 0 } }): DateTime
```

Funktion, die anhand der übergebenen Parameter die nächste buchbare Uhrzeit ermittelt und dabei Öffnungszeiten und gesperrte Wochentage beachtet.

## **FAQs und Fehlerbehebung**

**Q: Woher weiß ich, ob mein Code funktioniert?**

Wir empfehlen, den Code des Basisprodukts als Ausgangspunkt zu nehmen und jeden ProductBrick an Ihre Bedürfnisse anzupassen. Die eingebaute Syntaxhervorhebung und die Fehleranzeige des Code-Editors helfen Ihnen in den meisten Fällen. Wenn Sie weitere Hilfe brauchen, wenden Sie sich gern an unseren Kundensupport.

**Q: Mein Produkt erscheint nicht an einer Stelle, an der ich es erwarte (z. B. in Hub, im ShipmentRouter usw.). Wie finde ich den Grund?**

\1. Prüfen Sie die Produktkategorie (Verkauf, Einkauf). 2. Prüfen Sie die Verfügbarkeitseinschränkungen. 3. Wenn Sie Orbit Hub nutzen, prüfen Sie, ob das Produkt ein Verkauf-Product der TransportShape ist, die der Hub-Flow nutzt. Wenn Sie den TransportComposer nutzen, prüfen Sie die TransportShape. 4. Prüfen Sie die Gültigkeit von Produkt und Transportdatum. Liegt das Transportdatum vor dem Datum der ersten Produktversion, erscheint das Produkt nicht.
