Produkte
Logistikprodukte mit Context-, Feasibility-, Pricing- und Scheduling-Bricks definieren. Mit DataPools, Versionierung, Verfügbarkeitseinschränkungen und Hilfsfunktionen.
Logistikprodukte mit Context-, Feasibility-, Pricing- und Scheduling-Bricks definieren. Mit DataPools, Versionierung, Verfügbarkeitseinschränkungen und Hilfsfunktionen.
War dieser Artikel hilfreich?
Keine Antwort gefunden?
Fragen Sie Luna oder schreiben Sie unserem Support-Team.
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.
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
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.
ProductBricks sind die Bausteine von Produkten in Orbit. Es gibt vier Arten von ProductBricks:
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.
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 |
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.
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 |
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 |
Scheduling
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.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.
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:
// 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 Transportbedingungenwarning: Bedingungen, die den Transport beeinflussen können, die Buchung aber nicht verhindernerror: kritische Probleme, die der Nutzer kennen sollteOrbit 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.
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.
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:
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). 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).
// Feasibility Brick
return {
isFeasible: false,
isRequestable: false
};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:
In dieser Anleitung erstellen wir ein einfaches Produkt, um die Grundbegriffe im Detail zu erklären. Unser Produkt enthält:
In weiteren Schritten zeigen wir, wie Sie das Produkt um diese Funktionen erweitern:
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.
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“.
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.
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:
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:
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.
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:
const [route] = await utils.route.calculateRoutes([routeRequest]);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:
return {
isFeasible: true,
result: {
route,
context: {},
},
};Die Context-Funktion hat ein festes Rückgabeformat mit zwei Arten von Informationen:
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.
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).
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:
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“.
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:
const distance = utils.route.getVehicleSectionDistanceSum(context.route!.routes[0].sections);
const isDistanceBelowLimit = distance <= 1000 * 1000;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:
const maxTotalWeight = utils.loads.maxWeightForStopCountAndLoads(
input.request.tour.stops.length,
input.request.tour.loads
);
const isWeightBelowLimit = maxTotalWeight <= 27 * 1000;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.
return {
isFeasible: isDistanceBelowLimit && isWeightBelowLimit
};Perfekt. Wir haben eine einfache Feasibility-Funktion gebaut, die zwei Bedingungen prüft.
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.
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“.
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.
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:

Klicken Sie auf „Speichern“, um den DataPool zu speichern.
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.
Ö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:
const [yourDataPool, otherDataPool] = await utils.dataPools.getDataPoolsByNames(
["YourDataPool", "OtherDataPool"]
);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.
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.
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:
const [pricePerKm, fuelFloater] = constants;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:
const vehicleDistance = utils.route.getVehicleSectionDistanceSum(
context.route!.routes[0].sections
);Jetzt haben wir alle Angaben für die Preisberechnung. Fügen Sie diese Codezeile ein:
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.
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.
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.
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!
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“.
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:
// 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:
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.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.Jetzt können wir unser Produkt anlegen. Los geht's:
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.
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.
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.
Fügen Sie in jedem der vier folgenden ProductBrick-Bereiche den passenden Brick hinzu, den wir vorher erstellt haben.
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“.
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.
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.
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.
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.
const doesFit = utils.loads.doesLoadPlanFit(loadplan);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.
const extents = utils.loads.getLoadPlanExtents(loadplan);
const ldm = extents.maxLoadingLength;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.
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:
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.
Laden Sie im PricingBrick den DataPool mit utils.dataPools.getDataPoolsByNames, genau wie in Schritt 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.
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.
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.
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.
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.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:
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.
Die folgenden Hilfsfunktionen stehen im Objekt utils bereit. Sie nutzen sie in ProductBricks für verschiedene Berechnungen und zur Bearbeitung von Daten.
getVehicleSectionDistanceSum(sections: RouterSections[]): numberFunktion, 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.
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.
getTotalRouteDuration(r?: HereRoute): numberFunktion, die die Gesamtdauer einer Route berechnet. Gibt die Gesamtdauer in Sekunden zurück oder 0, wenn keine Route übergeben wird.
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.
buildRouteRequest(stops: GeocodedStop[], options: { transportMode: string }): RouteRequestFunktion, die ein Routenanfrage-Objekt für eine Menge von Stopps und eine Transportart erzeugt.
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.
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.
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.
matrixToJson(data: string[][]): object[]Funktion, die eine Datenmatrix in ein Array von JSON-Objekten umwandelt.
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.
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.
kmhToMetersPerSecond(kmh: number): numberFunktion, die eine Geschwindigkeit von Kilometern pro Stunde in Meter pro Sekunde umrechnet.
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.
getLoadPlanExtents(loadPlan: StopCentricLoadPlan): LoadPlanExtentsFunktion, 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.
doesLoadPlanFit(loadPlan: LoadPlan3D): booleanFunktion, 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.
hasDangerousGoods(loads: BaseLoad[]): booleanFunktion, die prüft, ob eine der übergebenen Ladungen Gefahrgut enthält.
maxWeightForStopCountAndLoads(stopCount: number, loads: Load[]): number | undefinedFunktion, die für eine Anzahl von Stopps und Ladungen das höchste Ladungsgewicht an einem Stopp berechnet.
buildSchedule(params: BuildScheduleParams): ScheduleDie 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.
emptyStringToUndefined(value: string): string | undefinedFunktion, die leere Strings in undefined umwandelt.
zZod-Bibliothek zur Schema-Prüfung. Siehe Dokumentation hier.
DateTimeDateTime-Objekt für Datums- und Uhrzeitberechnungen. Siehe Dokumentation hier.
DurationDuration-Objekt für Berechnungen mit Zeitspannen. Siehe Dokumentation hier.
getNextBookableDate(from: number, disabledWeekdays: number[], direction: "forward" | "backward" = "forward", minDaysDistance = 1): DateTimeFunktion, die anhand der übergebenen Parameter das nächste buchbare Datum ermittelt.
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 } }): DateTimeFunktion, die anhand der übergebenen Parameter die nächste buchbare Uhrzeit ermittelt und dabei Öffnungszeiten und gesperrte Wochentage beachtet.
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.
\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.