3722 lines
146 KiB
JSON
3722 lines
146 KiB
JSON
{
|
|
"openapi": "3.1.0",
|
|
"info": {
|
|
"title": "Holzleitner Backend API",
|
|
"description": "Backend f\u00fcr die Holzleitner-Lieferservice-App \u2014 Tour, Beladung, Ausf\u00fchrung.",
|
|
"contact": {
|
|
"name": "Holzleitner GmbH"
|
|
},
|
|
"license": {
|
|
"name": "Proprietary",
|
|
"identifier": "Proprietary"
|
|
},
|
|
"version": "0.1.0"
|
|
},
|
|
"paths": {
|
|
"/accounts/{personalnummer}": {
|
|
"get": {
|
|
"tags": [
|
|
"accounts"
|
|
],
|
|
"summary": "Liest den Account zu einer Personalnummer.",
|
|
"operationId": "get_account",
|
|
"parameters": [
|
|
{
|
|
"name": "personalnummer",
|
|
"in": "path",
|
|
"description": "Personalnummer des Accounts",
|
|
"required": true,
|
|
"schema": {
|
|
"type": "integer",
|
|
"format": "int64"
|
|
}
|
|
}
|
|
],
|
|
"responses": {
|
|
"200": {
|
|
"description": "Account gefunden",
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"$ref": "#/components/schemas/Account"
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"401": {
|
|
"description": "Authentifizierung fehlgeschlagen"
|
|
},
|
|
"404": {
|
|
"description": "Kein Account zu dieser Personalnummer"
|
|
}
|
|
},
|
|
"security": [
|
|
{
|
|
"bearer_auth": []
|
|
}
|
|
]
|
|
}
|
|
},
|
|
"/admin/delivered-belegnummern": {
|
|
"get": {
|
|
"tags": [
|
|
"admin"
|
|
],
|
|
"summary": "Liefert die Belegnummern ausgelieferter (abgeschlossener) Lieferungen,\n**deren Liefermail noch nicht versendet wurde** (`mail_sent_at IS NULL`).\n\u201eAusgeliefert\" = es existiert ein Abschluss. Mit `day` (DD-MM-YYYY) nur\nAbschl\u00fcsse dieses Berliner Kalendertages; **ohne `day` alle offenen** (\u00fcber\nalle Tage) \u2014 so bleiben Belege \u00fcber Mitternacht nicht h\u00e4ngen.",
|
|
"operationId": "delivered_belegnummern",
|
|
"parameters": [
|
|
{
|
|
"name": "day",
|
|
"in": "query",
|
|
"description": "Tag DD-MM-YYYY; ohne Angabe ALLE offenen Belege",
|
|
"required": false,
|
|
"schema": {
|
|
"type": "string"
|
|
}
|
|
}
|
|
],
|
|
"responses": {
|
|
"200": {
|
|
"description": "Offene (nicht versendete) Belegnummern",
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"$ref": "#/components/schemas/DeliveredBelegnummernResponse"
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"400": {
|
|
"description": "Ung\u00fcltiger Tag"
|
|
},
|
|
"401": {
|
|
"description": "Admin-API-Key fehlt/ung\u00fcltig"
|
|
}
|
|
},
|
|
"security": [
|
|
{
|
|
"admin_api_key": []
|
|
}
|
|
]
|
|
}
|
|
},
|
|
"/admin/import-erp": {
|
|
"post": {
|
|
"tags": [
|
|
"admin"
|
|
],
|
|
"summary": "St\u00f6\u00dft den ERP-Import f\u00fcr ein Datum an und liefert die Zusammenfassung.",
|
|
"operationId": "import_erp",
|
|
"parameters": [
|
|
{
|
|
"name": "date",
|
|
"in": "query",
|
|
"description": "Ziel-Tourdatum YYYY-MM-DD (Default: heute)",
|
|
"required": false,
|
|
"schema": {
|
|
"type": "string"
|
|
}
|
|
}
|
|
],
|
|
"responses": {
|
|
"200": {
|
|
"description": "Import durchgef\u00fchrt",
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"$ref": "#/components/schemas/ImportSummary"
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"400": {
|
|
"description": "Ung\u00fcltiges Datum"
|
|
},
|
|
"401": {
|
|
"description": "Admin-API-Key fehlt/ung\u00fcltig"
|
|
},
|
|
"502": {
|
|
"description": "ERP nicht erreichbar / Lesefehler"
|
|
}
|
|
},
|
|
"security": [
|
|
{
|
|
"admin_api_key": []
|
|
}
|
|
]
|
|
}
|
|
},
|
|
"/admin/mark-mail-sent": {
|
|
"post": {
|
|
"tags": [
|
|
"admin"
|
|
],
|
|
"summary": "Markiert die Liefermails der angegebenen Belegnummern als **versendet**\n(`mail_sent_at = now()`, nur wo noch offen). Vom Mailclient aufzurufen,\nNACHDEM ERPframe die Mails erfolgreich verschickt hat \u2014 danach erscheinen\ndie Belege nicht mehr in `GET /admin/delivered-belegnummern`.",
|
|
"operationId": "mark_mail_sent",
|
|
"requestBody": {
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"$ref": "#/components/schemas/MarkMailSentRequest"
|
|
}
|
|
}
|
|
},
|
|
"required": true
|
|
},
|
|
"responses": {
|
|
"200": {
|
|
"description": "Markierung durchgef\u00fchrt",
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"$ref": "#/components/schemas/MarkMailSentResponse"
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"401": {
|
|
"description": "Admin-API-Key fehlt/ung\u00fcltig"
|
|
}
|
|
},
|
|
"security": [
|
|
{
|
|
"admin_api_key": []
|
|
}
|
|
]
|
|
}
|
|
},
|
|
"/admin/push-completion": {
|
|
"post": {
|
|
"tags": [
|
|
"admin"
|
|
],
|
|
"summary": "St\u00f6\u00dft das ERP-R\u00fcckschreiben eines bereits lokal abgeschlossenen\nLieferabschlusses erneut an (idempotenter Retry, falls der automatische\nPush beim Abschluss fehlschlug).",
|
|
"operationId": "push_completion",
|
|
"parameters": [
|
|
{
|
|
"name": "delivery_id",
|
|
"in": "query",
|
|
"description": "UUID der abgeschlossenen Lieferung",
|
|
"required": true,
|
|
"schema": {
|
|
"type": "string"
|
|
}
|
|
}
|
|
],
|
|
"responses": {
|
|
"204": {
|
|
"description": "R\u00fcckschreiben erfolgreich"
|
|
},
|
|
"400": {
|
|
"description": "Ung\u00fcltige delivery_id"
|
|
},
|
|
"401": {
|
|
"description": "Admin-API-Key fehlt/ung\u00fcltig"
|
|
},
|
|
"404": {
|
|
"description": "Lieferung nicht gefunden / nicht abgeschlossen"
|
|
},
|
|
"502": {
|
|
"description": "ERP nicht erreichbar / Schreibfehler"
|
|
}
|
|
},
|
|
"security": [
|
|
{
|
|
"admin_api_key": []
|
|
}
|
|
]
|
|
}
|
|
},
|
|
"/attachments/{id}": {
|
|
"get": {
|
|
"tags": [
|
|
"attachments"
|
|
],
|
|
"summary": "Liefert ein gerendertes Vorschaubild des Attachments (Bytes), geladen\naus DOCUframe. Aufl\u00f6sung/Format \u00fcber Query-Parameter steuerbar\n(`?w=&h=&q=&ext=&page=`).",
|
|
"operationId": "get_attachment",
|
|
"parameters": [
|
|
{
|
|
"name": "id",
|
|
"in": "path",
|
|
"description": "Attachment-Id (unsere UUID)",
|
|
"required": true,
|
|
"schema": {
|
|
"type": "string",
|
|
"format": "uuid"
|
|
}
|
|
},
|
|
{
|
|
"name": "w",
|
|
"in": "query",
|
|
"description": "Breite in Pixeln (Default 1024)",
|
|
"required": false,
|
|
"schema": {
|
|
"type": "integer",
|
|
"format": "int32",
|
|
"minimum": 0
|
|
}
|
|
},
|
|
{
|
|
"name": "h",
|
|
"in": "query",
|
|
"description": "H\u00f6he in Pixeln (Default 1024)",
|
|
"required": false,
|
|
"schema": {
|
|
"type": "integer",
|
|
"format": "int32",
|
|
"minimum": 0
|
|
}
|
|
},
|
|
{
|
|
"name": "q",
|
|
"in": "query",
|
|
"description": "Qualit\u00e4t 0\u2013100 (Default 85)",
|
|
"required": false,
|
|
"schema": {
|
|
"type": "integer",
|
|
"format": "int32",
|
|
"minimum": 0
|
|
}
|
|
},
|
|
{
|
|
"name": "ext",
|
|
"in": "query",
|
|
"description": "png|jpeg|jpg|webp|tiff (Default jpeg)",
|
|
"required": false,
|
|
"schema": {
|
|
"type": "string"
|
|
}
|
|
},
|
|
{
|
|
"name": "page",
|
|
"in": "query",
|
|
"description": "Seitennummer (Default 1)",
|
|
"required": false,
|
|
"schema": {
|
|
"type": "string"
|
|
}
|
|
}
|
|
],
|
|
"responses": {
|
|
"200": {
|
|
"description": "Vorschaubild (Bytes)",
|
|
"content": {
|
|
"image/jpeg": {}
|
|
}
|
|
},
|
|
"401": {
|
|
"description": "Authentifizierung fehlgeschlagen"
|
|
},
|
|
"404": {
|
|
"description": "Attachment nicht gefunden"
|
|
}
|
|
},
|
|
"security": [
|
|
{
|
|
"bearer_auth": []
|
|
}
|
|
]
|
|
}
|
|
},
|
|
"/deliveries/{delivery_id}/assigned-car": {
|
|
"put": {
|
|
"tags": [
|
|
"deliveries"
|
|
],
|
|
"summary": "Setzt das `assigned_car_id` einer Lieferung. `carId: null` l\u00f6st\ndie Zuordnung wieder. Der Use Case stellt sicher, dass das Fahrzeug\nzum angemeldeten Account geh\u00f6rt.",
|
|
"operationId": "assign_car",
|
|
"parameters": [
|
|
{
|
|
"name": "delivery_id",
|
|
"in": "path",
|
|
"required": true,
|
|
"schema": {
|
|
"type": "string",
|
|
"format": "uuid"
|
|
}
|
|
}
|
|
],
|
|
"requestBody": {
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"$ref": "#/components/schemas/AssignCarRequest"
|
|
}
|
|
}
|
|
},
|
|
"required": true
|
|
},
|
|
"responses": {
|
|
"200": {
|
|
"description": "Fahrzeug zugewiesen / entfernt",
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"$ref": "#/components/schemas/DeliveryResponse"
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"400": {
|
|
"description": "Fahrzeug geh\u00f6rt nicht zum Account"
|
|
},
|
|
"401": {
|
|
"description": "Authentifizierung fehlgeschlagen"
|
|
},
|
|
"404": {
|
|
"description": "Lieferung nicht gefunden"
|
|
}
|
|
},
|
|
"security": [
|
|
{
|
|
"bearer_auth": []
|
|
}
|
|
]
|
|
}
|
|
},
|
|
"/deliveries/{delivery_id}/cancel": {
|
|
"post": {
|
|
"tags": [
|
|
"deliveries"
|
|
],
|
|
"summary": "Setzt die Lieferung auf `canceled` \u2014 endg\u00fcltig. Erlaubt aus\n`active` und `held`.",
|
|
"operationId": "cancel",
|
|
"parameters": [
|
|
{
|
|
"name": "delivery_id",
|
|
"in": "path",
|
|
"required": true,
|
|
"schema": {
|
|
"type": "string",
|
|
"format": "uuid"
|
|
}
|
|
}
|
|
],
|
|
"requestBody": {
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"$ref": "#/components/schemas/CancelDeliveryRequest"
|
|
}
|
|
}
|
|
},
|
|
"required": true
|
|
},
|
|
"responses": {
|
|
"200": {
|
|
"description": "Lieferung storniert",
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"$ref": "#/components/schemas/DeliveryResponse"
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"400": {
|
|
"description": "Invalider Status\u00fcbergang oder leerer Reason"
|
|
},
|
|
"401": {
|
|
"description": "Authentifizierung fehlgeschlagen"
|
|
},
|
|
"404": {
|
|
"description": "Lieferung nicht gefunden"
|
|
}
|
|
},
|
|
"security": [
|
|
{
|
|
"bearer_auth": []
|
|
}
|
|
]
|
|
}
|
|
},
|
|
"/deliveries/{delivery_id}/complete": {
|
|
"post": {
|
|
"tags": [
|
|
"deliveries"
|
|
],
|
|
"summary": "Schlie\u00dft die Lieferung ab \u2014 `state = completed`. Nur aus `active`.",
|
|
"description": "`multipart/form-data` mit drei Feldern:\n * `customer_signature` \u2014 PNG der Kunden-Unterschrift (Pflicht)\n * `driver_signature` \u2014 PNG der Fahrer-Unterschrift (Pflicht)\n * `acknowledgements` \u2014 JSON (`CompleteDeliveryAcknowledgements`):\n `receiptConfirmed` (Pflicht true), `notesAcknowledged`,\n `acknowledgedNoteIds`, `authorCarId`.\n\nAtomar: Signaturen werden lokal gespeichert, die Abschluss-Zeile\ngeschrieben und der Status auf `completed` gesetzt \u2014 alles oder nichts.\nGates: Lieferung aktiv, alle scanbaren Positionen fertig, Notizen\nbest\u00e4tigt (falls vorhanden).",
|
|
"operationId": "complete",
|
|
"parameters": [
|
|
{
|
|
"name": "delivery_id",
|
|
"in": "path",
|
|
"required": true,
|
|
"schema": {
|
|
"type": "string",
|
|
"format": "uuid"
|
|
}
|
|
}
|
|
],
|
|
"requestBody": {
|
|
"description": "Felder `customer_signature`, `driver_signature` (PNG) + `acknowledgements` (JSON)",
|
|
"content": {
|
|
"multipart/form-data": {}
|
|
}
|
|
},
|
|
"responses": {
|
|
"200": {
|
|
"description": "Lieferung abgeschlossen",
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"$ref": "#/components/schemas/DeliveryResponse"
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"400": {
|
|
"description": "Invalider Status\u00fcbergang / fehlende Signatur / offene Scans / Notizen unbest\u00e4tigt"
|
|
},
|
|
"401": {
|
|
"description": "Authentifizierung fehlgeschlagen"
|
|
},
|
|
"404": {
|
|
"description": "Lieferung nicht gefunden"
|
|
}
|
|
},
|
|
"security": [
|
|
{
|
|
"bearer_auth": []
|
|
}
|
|
]
|
|
}
|
|
},
|
|
"/deliveries/{delivery_id}/credit": {
|
|
"post": {
|
|
"tags": [
|
|
"deliveries"
|
|
],
|
|
"summary": "Wendet ein Betrags-Gutschrift-Ereignis an (`set`/`remove`). Append-only,\nidempotent \u00fcber `clientEventId`. Nur bei aktiver Lieferung; bei `set` sind\nBetrag (0 < x \u2264 150 \u20ac, 10-\u20ac-Schritte) und Grund Pflicht. Antwort: der\naktuelle Gutschrift-Stand (`null`, wenn entfernt).",
|
|
"operationId": "apply_credit",
|
|
"parameters": [
|
|
{
|
|
"name": "delivery_id",
|
|
"in": "path",
|
|
"required": true,
|
|
"schema": {
|
|
"type": "string",
|
|
"format": "uuid"
|
|
}
|
|
}
|
|
],
|
|
"requestBody": {
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"$ref": "#/components/schemas/DeliveryCreditEventRequest"
|
|
}
|
|
}
|
|
},
|
|
"required": true
|
|
},
|
|
"responses": {
|
|
"200": {
|
|
"description": "Gutschrift gesetzt/entfernt",
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"$ref": "#/components/schemas/DeliveryCreditResponse"
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"400": {
|
|
"description": "Ung\u00fcltiger Betrag/Grund oder Lieferung nicht aktiv"
|
|
},
|
|
"401": {
|
|
"description": "Authentifizierung fehlgeschlagen"
|
|
},
|
|
"404": {
|
|
"description": "Lieferung nicht gefunden"
|
|
}
|
|
},
|
|
"security": [
|
|
{
|
|
"bearer_auth": []
|
|
}
|
|
]
|
|
}
|
|
},
|
|
"/deliveries/{delivery_id}/hold": {
|
|
"post": {
|
|
"tags": [
|
|
"deliveries"
|
|
],
|
|
"summary": "Setzt die Lieferung auf `held`. Nur aus `active` zul\u00e4ssig.",
|
|
"operationId": "hold",
|
|
"parameters": [
|
|
{
|
|
"name": "delivery_id",
|
|
"in": "path",
|
|
"required": true,
|
|
"schema": {
|
|
"type": "string",
|
|
"format": "uuid"
|
|
}
|
|
}
|
|
],
|
|
"requestBody": {
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"$ref": "#/components/schemas/HoldDeliveryRequest"
|
|
}
|
|
}
|
|
},
|
|
"required": true
|
|
},
|
|
"responses": {
|
|
"200": {
|
|
"description": "Lieferung geholdet",
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"$ref": "#/components/schemas/DeliveryResponse"
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"400": {
|
|
"description": "Invalider Status\u00fcbergang oder leerer Reason"
|
|
},
|
|
"401": {
|
|
"description": "Authentifizierung fehlgeschlagen"
|
|
},
|
|
"404": {
|
|
"description": "Lieferung nicht gefunden"
|
|
}
|
|
},
|
|
"security": [
|
|
{
|
|
"bearer_auth": []
|
|
}
|
|
]
|
|
}
|
|
},
|
|
"/deliveries/{delivery_id}/notes": {
|
|
"post": {
|
|
"tags": [
|
|
"deliveries"
|
|
],
|
|
"summary": "Legt eine neue Notiz an einer Lieferung an. Mindestens eines von\n`text` und `imageAttachment` muss inhaltlich gef\u00fcllt sein\n(Leerstrings werden serverseitig getrimmt und als leer behandelt).",
|
|
"operationId": "create_note",
|
|
"parameters": [
|
|
{
|
|
"name": "delivery_id",
|
|
"in": "path",
|
|
"required": true,
|
|
"schema": {
|
|
"type": "string",
|
|
"format": "uuid"
|
|
}
|
|
}
|
|
],
|
|
"requestBody": {
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"$ref": "#/components/schemas/CreateDeliveryNoteRequest"
|
|
}
|
|
}
|
|
},
|
|
"required": true
|
|
},
|
|
"responses": {
|
|
"200": {
|
|
"description": "Notiz angelegt",
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"$ref": "#/components/schemas/DeliveryNoteResponse"
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"400": {
|
|
"description": "Notiz ohne Inhalt"
|
|
},
|
|
"401": {
|
|
"description": "Authentifizierung fehlgeschlagen"
|
|
},
|
|
"404": {
|
|
"description": "Lieferung nicht gefunden"
|
|
}
|
|
},
|
|
"security": [
|
|
{
|
|
"bearer_auth": []
|
|
}
|
|
]
|
|
}
|
|
},
|
|
"/deliveries/{delivery_id}/notes/image": {
|
|
"post": {
|
|
"tags": [
|
|
"deliveries"
|
|
],
|
|
"summary": "L\u00e4dt ein Bild zu einer Lieferung hoch (multipart/form-data, Feld `file`)\nund legt daf\u00fcr eine Bild-Notiz an. Das Bild geht in den\nDOCUframe-Dokumentenspeicher; gespeichert wird die zur\u00fcckgelieferte\nReferenz (`~ObjectID`) als `image_attachment` der Notiz.",
|
|
"operationId": "upload_note_image",
|
|
"parameters": [
|
|
{
|
|
"name": "delivery_id",
|
|
"in": "path",
|
|
"required": true,
|
|
"schema": {
|
|
"type": "string",
|
|
"format": "uuid"
|
|
}
|
|
}
|
|
],
|
|
"requestBody": {
|
|
"description": "Formularfeld `file` mit den Bilddaten",
|
|
"content": {
|
|
"multipart/form-data": {}
|
|
}
|
|
},
|
|
"responses": {
|
|
"200": {
|
|
"description": "Bild hochgeladen, Notiz angelegt",
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"$ref": "#/components/schemas/DeliveryNoteResponse"
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"400": {
|
|
"description": "Kein/leeres Datei-Feld"
|
|
},
|
|
"401": {
|
|
"description": "Authentifizierung fehlgeschlagen"
|
|
},
|
|
"404": {
|
|
"description": "Lieferung nicht gefunden"
|
|
},
|
|
"500": {
|
|
"description": "Upload zu DOCUframe fehlgeschlagen"
|
|
}
|
|
},
|
|
"security": [
|
|
{
|
|
"bearer_auth": []
|
|
}
|
|
]
|
|
}
|
|
},
|
|
"/deliveries/{delivery_id}/notes/{note_id}": {
|
|
"delete": {
|
|
"tags": [
|
|
"deliveries"
|
|
],
|
|
"summary": "L\u00f6scht eine Notiz. Antwortet mit `204 No Content`.",
|
|
"operationId": "delete_note",
|
|
"parameters": [
|
|
{
|
|
"name": "delivery_id",
|
|
"in": "path",
|
|
"required": true,
|
|
"schema": {
|
|
"type": "string",
|
|
"format": "uuid"
|
|
}
|
|
},
|
|
{
|
|
"name": "note_id",
|
|
"in": "path",
|
|
"required": true,
|
|
"schema": {
|
|
"type": "string",
|
|
"format": "uuid"
|
|
}
|
|
}
|
|
],
|
|
"responses": {
|
|
"204": {
|
|
"description": "Notiz gel\u00f6scht"
|
|
},
|
|
"401": {
|
|
"description": "Authentifizierung fehlgeschlagen"
|
|
},
|
|
"404": {
|
|
"description": "Notiz nicht gefunden"
|
|
}
|
|
},
|
|
"security": [
|
|
{
|
|
"bearer_auth": []
|
|
}
|
|
]
|
|
},
|
|
"patch": {
|
|
"tags": [
|
|
"deliveries"
|
|
],
|
|
"summary": "\u00c4ndert Text/Bild einer Notiz. Innerhalb des (geteilten) Accounts darf\njeder Fahrer Notizen pflegen \u2014 kein Autor-Check. `delivery_id` ist Teil\ndes Pfads (REST-Konsistenz), die Notiz wird \u00fcber `note_id` adressiert.",
|
|
"operationId": "update_note",
|
|
"parameters": [
|
|
{
|
|
"name": "delivery_id",
|
|
"in": "path",
|
|
"required": true,
|
|
"schema": {
|
|
"type": "string",
|
|
"format": "uuid"
|
|
}
|
|
},
|
|
{
|
|
"name": "note_id",
|
|
"in": "path",
|
|
"required": true,
|
|
"schema": {
|
|
"type": "string",
|
|
"format": "uuid"
|
|
}
|
|
}
|
|
],
|
|
"requestBody": {
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"$ref": "#/components/schemas/UpdateDeliveryNoteRequest"
|
|
}
|
|
}
|
|
},
|
|
"required": true
|
|
},
|
|
"responses": {
|
|
"200": {
|
|
"description": "Notiz aktualisiert",
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"$ref": "#/components/schemas/DeliveryNoteResponse"
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"400": {
|
|
"description": "Notiz ohne Inhalt"
|
|
},
|
|
"401": {
|
|
"description": "Authentifizierung fehlgeschlagen"
|
|
},
|
|
"404": {
|
|
"description": "Notiz nicht gefunden"
|
|
}
|
|
},
|
|
"security": [
|
|
{
|
|
"bearer_auth": []
|
|
}
|
|
]
|
|
}
|
|
},
|
|
"/deliveries/{delivery_id}/resume": {
|
|
"post": {
|
|
"tags": [
|
|
"deliveries"
|
|
],
|
|
"summary": "Setzt die Lieferung zur\u00fcck auf `active`. Nur aus `held` zul\u00e4ssig.",
|
|
"operationId": "resume",
|
|
"parameters": [
|
|
{
|
|
"name": "delivery_id",
|
|
"in": "path",
|
|
"required": true,
|
|
"schema": {
|
|
"type": "string",
|
|
"format": "uuid"
|
|
}
|
|
}
|
|
],
|
|
"responses": {
|
|
"200": {
|
|
"description": "Lieferung wieder aktiv",
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"$ref": "#/components/schemas/DeliveryResponse"
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"400": {
|
|
"description": "Invalider Status\u00fcbergang"
|
|
},
|
|
"401": {
|
|
"description": "Authentifizierung fehlgeschlagen"
|
|
},
|
|
"404": {
|
|
"description": "Lieferung nicht gefunden"
|
|
}
|
|
},
|
|
"security": [
|
|
{
|
|
"bearer_auth": []
|
|
}
|
|
]
|
|
}
|
|
},
|
|
"/deliveries/{delivery_id}/services/{service_id}": {
|
|
"put": {
|
|
"tags": [
|
|
"deliveries"
|
|
],
|
|
"summary": "Setzt (Upsert) den Wert eines Service f\u00fcr eine Lieferung. Genau das zum\nService-Typ passende Feld (`boolValue`/`numericValue`) muss gesetzt sein;\nnumerische Werte werden gegen min/max gepr\u00fcft. Nur bei aktiver Lieferung.",
|
|
"operationId": "set_service",
|
|
"parameters": [
|
|
{
|
|
"name": "delivery_id",
|
|
"in": "path",
|
|
"required": true,
|
|
"schema": {
|
|
"type": "string",
|
|
"format": "uuid"
|
|
}
|
|
},
|
|
{
|
|
"name": "service_id",
|
|
"in": "path",
|
|
"required": true,
|
|
"schema": {
|
|
"type": "string",
|
|
"format": "uuid"
|
|
}
|
|
}
|
|
],
|
|
"requestBody": {
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"$ref": "#/components/schemas/SetDeliveryServiceRequest"
|
|
}
|
|
}
|
|
},
|
|
"required": true
|
|
},
|
|
"responses": {
|
|
"200": {
|
|
"description": "Wert gesetzt",
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"$ref": "#/components/schemas/DeliveryServiceResponse"
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"400": {
|
|
"description": "Wert passt nicht zum Service-Typ / au\u00dferhalb min-max / Lieferung nicht aktiv"
|
|
},
|
|
"401": {
|
|
"description": "Authentifizierung fehlgeschlagen"
|
|
},
|
|
"404": {
|
|
"description": "Service oder Lieferung nicht gefunden"
|
|
}
|
|
},
|
|
"security": [
|
|
{
|
|
"bearer_auth": []
|
|
}
|
|
]
|
|
},
|
|
"delete": {
|
|
"tags": [
|
|
"deliveries"
|
|
],
|
|
"summary": "Entfernt den Service-Wert einer Lieferung (Service \u201enicht gesetzt\").\nNur bei aktiver Lieferung. Antwort `204`.",
|
|
"operationId": "delete_service_value",
|
|
"parameters": [
|
|
{
|
|
"name": "delivery_id",
|
|
"in": "path",
|
|
"required": true,
|
|
"schema": {
|
|
"type": "string",
|
|
"format": "uuid"
|
|
}
|
|
},
|
|
{
|
|
"name": "service_id",
|
|
"in": "path",
|
|
"required": true,
|
|
"schema": {
|
|
"type": "string",
|
|
"format": "uuid"
|
|
}
|
|
}
|
|
],
|
|
"responses": {
|
|
"204": {
|
|
"description": "Wert entfernt"
|
|
},
|
|
"400": {
|
|
"description": "Lieferung nicht aktiv"
|
|
},
|
|
"401": {
|
|
"description": "Authentifizierung fehlgeschlagen"
|
|
},
|
|
"404": {
|
|
"description": "Lieferung nicht gefunden"
|
|
}
|
|
},
|
|
"security": [
|
|
{
|
|
"bearer_auth": []
|
|
}
|
|
]
|
|
}
|
|
},
|
|
"/health": {
|
|
"get": {
|
|
"tags": [
|
|
"health"
|
|
],
|
|
"summary": "Health-Endpoint f\u00fcr Load-Balancer und Container-Probes. Bewusst\nkein Auth \u2014 eine `200 ok`-Antwort darf nicht von der Auth abh\u00e4ngen.",
|
|
"operationId": "health",
|
|
"responses": {
|
|
"200": {
|
|
"description": "Service ist erreichbar",
|
|
"content": {
|
|
"text/plain": {
|
|
"schema": {
|
|
"type": "string"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"security": []
|
|
}
|
|
},
|
|
"/me/cars": {
|
|
"get": {
|
|
"tags": [
|
|
"cars"
|
|
],
|
|
"summary": "Listet die Fahrzeuge des angemeldeten Fahrers.",
|
|
"operationId": "list_my_cars",
|
|
"parameters": [
|
|
{
|
|
"name": "includeInactive",
|
|
"in": "query",
|
|
"description": "Wenn true, werden inaktive Fahrzeuge mitgeliefert (default: false)",
|
|
"required": false,
|
|
"schema": {
|
|
"type": "boolean"
|
|
}
|
|
}
|
|
],
|
|
"responses": {
|
|
"200": {
|
|
"description": "Fahrzeuge des Fahrers",
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"$ref": "#/components/schemas/CarsList"
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"401": {
|
|
"description": "Authentifizierung fehlgeschlagen"
|
|
}
|
|
},
|
|
"security": [
|
|
{
|
|
"bearer_auth": []
|
|
}
|
|
]
|
|
},
|
|
"post": {
|
|
"tags": [
|
|
"cars"
|
|
],
|
|
"summary": "Legt ein neues Fahrzeug f\u00fcr den angemeldeten Fahrer an.",
|
|
"operationId": "create_my_car",
|
|
"requestBody": {
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"$ref": "#/components/schemas/CreateCarRequest"
|
|
}
|
|
}
|
|
},
|
|
"required": true
|
|
},
|
|
"responses": {
|
|
"200": {
|
|
"description": "Fahrzeug angelegt",
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"$ref": "#/components/schemas/CarResponse"
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"400": {
|
|
"description": "Validierungsfehler (z. B. doppeltes Kennzeichen)"
|
|
},
|
|
"401": {
|
|
"description": "Authentifizierung fehlgeschlagen"
|
|
}
|
|
},
|
|
"security": [
|
|
{
|
|
"bearer_auth": []
|
|
}
|
|
]
|
|
}
|
|
},
|
|
"/me/cars/{car_id}": {
|
|
"patch": {
|
|
"tags": [
|
|
"cars"
|
|
],
|
|
"summary": "Aktualisiert ein Fahrzeug (Kennzeichen \u00e4ndern / deaktivieren).",
|
|
"operationId": "update_my_car",
|
|
"parameters": [
|
|
{
|
|
"name": "car_id",
|
|
"in": "path",
|
|
"required": true,
|
|
"schema": {
|
|
"type": "string",
|
|
"format": "uuid"
|
|
}
|
|
}
|
|
],
|
|
"requestBody": {
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"$ref": "#/components/schemas/UpdateCarRequest"
|
|
}
|
|
}
|
|
},
|
|
"required": true
|
|
},
|
|
"responses": {
|
|
"200": {
|
|
"description": "Fahrzeug aktualisiert",
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"$ref": "#/components/schemas/CarResponse"
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"400": {
|
|
"description": "Validierungsfehler"
|
|
},
|
|
"401": {
|
|
"description": "Authentifizierung fehlgeschlagen"
|
|
},
|
|
"404": {
|
|
"description": "Fahrzeug nicht gefunden oder geh\u00f6rt nicht zu diesem Account"
|
|
}
|
|
},
|
|
"security": [
|
|
{
|
|
"bearer_auth": []
|
|
}
|
|
]
|
|
}
|
|
},
|
|
"/me/tours/today": {
|
|
"get": {
|
|
"tags": [
|
|
"tours"
|
|
],
|
|
"summary": "Listet heutige Touren des angemeldeten Fahrers (Filter aus dem JWT).",
|
|
"operationId": "list_my_tours_today",
|
|
"responses": {
|
|
"200": {
|
|
"description": "Liste der heutigen Touren",
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"$ref": "#/components/schemas/TourSummaryList"
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"401": {
|
|
"description": "Authentifizierung fehlgeschlagen"
|
|
}
|
|
},
|
|
"security": [
|
|
{
|
|
"bearer_auth": []
|
|
}
|
|
]
|
|
}
|
|
},
|
|
"/payment-methods": {
|
|
"get": {
|
|
"tags": [
|
|
"payment-methods"
|
|
],
|
|
"summary": "Listet die Zahlungsmethoden.",
|
|
"operationId": "list_payment_methods",
|
|
"parameters": [
|
|
{
|
|
"name": "includeInactive",
|
|
"in": "query",
|
|
"description": "Wenn true, werden inaktive Methoden mitgeliefert (default: false)",
|
|
"required": false,
|
|
"schema": {
|
|
"type": "boolean"
|
|
}
|
|
}
|
|
],
|
|
"responses": {
|
|
"200": {
|
|
"description": "Zahlungsmethoden",
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"$ref": "#/components/schemas/PaymentMethodsList"
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"401": {
|
|
"description": "Authentifizierung fehlgeschlagen"
|
|
}
|
|
},
|
|
"security": [
|
|
{
|
|
"bearer_auth": []
|
|
}
|
|
]
|
|
},
|
|
"post": {
|
|
"tags": [
|
|
"payment-methods"
|
|
],
|
|
"summary": "Legt eine neue Zahlungsmethode an.",
|
|
"operationId": "create_payment_method",
|
|
"requestBody": {
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"$ref": "#/components/schemas/CreatePaymentMethodRequest"
|
|
}
|
|
}
|
|
},
|
|
"required": true
|
|
},
|
|
"responses": {
|
|
"200": {
|
|
"description": "",
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"$ref": "#/components/schemas/PaymentMethodResponse"
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"400": {
|
|
"description": "Validierungsfehler (z. B. doppelter code)"
|
|
},
|
|
"401": {
|
|
"description": "Authentifizierung fehlgeschlagen"
|
|
}
|
|
},
|
|
"security": [
|
|
{
|
|
"bearer_auth": []
|
|
}
|
|
]
|
|
}
|
|
},
|
|
"/payment-methods/{id}": {
|
|
"delete": {
|
|
"tags": [
|
|
"payment-methods"
|
|
],
|
|
"summary": "Hartes L\u00f6schen. `409 Conflict`, wenn die Methode von einer Lieferung\nreferenziert wird \u2014 der Admin soll dann den `active = false`-Pfad\nnutzen.",
|
|
"operationId": "delete_payment_method",
|
|
"parameters": [
|
|
{
|
|
"name": "id",
|
|
"in": "path",
|
|
"description": "Zahlungsmethoden-Id",
|
|
"required": true,
|
|
"schema": {
|
|
"type": "string",
|
|
"format": "uuid"
|
|
}
|
|
}
|
|
],
|
|
"responses": {
|
|
"204": {
|
|
"description": "Methode gel\u00f6scht"
|
|
},
|
|
"401": {
|
|
"description": "Authentifizierung fehlgeschlagen"
|
|
},
|
|
"404": {
|
|
"description": "Methode nicht gefunden"
|
|
},
|
|
"409": {
|
|
"description": "Methode ist noch von Lieferungen referenziert"
|
|
}
|
|
},
|
|
"security": [
|
|
{
|
|
"bearer_auth": []
|
|
}
|
|
]
|
|
},
|
|
"patch": {
|
|
"tags": [
|
|
"payment-methods"
|
|
],
|
|
"summary": "Patcht Anzeige-Name und/oder Aktiv-Flag.",
|
|
"operationId": "update_payment_method",
|
|
"parameters": [
|
|
{
|
|
"name": "id",
|
|
"in": "path",
|
|
"description": "Zahlungsmethoden-Id",
|
|
"required": true,
|
|
"schema": {
|
|
"type": "string",
|
|
"format": "uuid"
|
|
}
|
|
}
|
|
],
|
|
"requestBody": {
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"$ref": "#/components/schemas/UpdatePaymentMethodRequest"
|
|
}
|
|
}
|
|
},
|
|
"required": true
|
|
},
|
|
"responses": {
|
|
"200": {
|
|
"description": "",
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"$ref": "#/components/schemas/PaymentMethodResponse"
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"401": {
|
|
"description": "Authentifizierung fehlgeschlagen"
|
|
},
|
|
"404": {
|
|
"description": "Methode nicht gefunden"
|
|
}
|
|
},
|
|
"security": [
|
|
{
|
|
"bearer_auth": []
|
|
}
|
|
]
|
|
}
|
|
},
|
|
"/scans": {
|
|
"post": {
|
|
"tags": [
|
|
"scans"
|
|
],
|
|
"summary": "Wendet eine Liste von Scan-Events idempotent an.",
|
|
"description": "Pro Event ein eigenes Resultat. Status `applied` schreibt einen\nfrischen Audit-Eintrag, `duplicate` liefert den aktuellen Stand am\nServer, `rejected` enth\u00e4lt die Begr\u00fcndung. Reihenfolge der `results`\nentspricht der Reihenfolge der `scans` im Request.",
|
|
"operationId": "apply_scans",
|
|
"requestBody": {
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"$ref": "#/components/schemas/ApplyScansRequest"
|
|
}
|
|
}
|
|
},
|
|
"required": true
|
|
},
|
|
"responses": {
|
|
"200": {
|
|
"description": "Bulk-Ergebnis pro Event",
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"$ref": "#/components/schemas/ApplyScansResponse"
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"401": {
|
|
"description": "Authentifizierung fehlgeschlagen"
|
|
}
|
|
},
|
|
"security": [
|
|
{
|
|
"bearer_auth": []
|
|
}
|
|
]
|
|
}
|
|
},
|
|
"/services": {
|
|
"get": {
|
|
"tags": [
|
|
"services"
|
|
],
|
|
"summary": "Listet die Services (sortiert nach `sortOrder`).",
|
|
"operationId": "list_services",
|
|
"parameters": [
|
|
{
|
|
"name": "includeInactive",
|
|
"in": "query",
|
|
"description": "Wenn true, werden inaktive Services mitgeliefert (default: false)",
|
|
"required": false,
|
|
"schema": {
|
|
"type": "boolean"
|
|
}
|
|
}
|
|
],
|
|
"responses": {
|
|
"200": {
|
|
"description": "Services",
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"$ref": "#/components/schemas/ServicesList"
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"401": {
|
|
"description": "Authentifizierung fehlgeschlagen"
|
|
}
|
|
},
|
|
"security": [
|
|
{
|
|
"bearer_auth": []
|
|
}
|
|
]
|
|
},
|
|
"post": {
|
|
"tags": [
|
|
"services"
|
|
],
|
|
"summary": "Legt einen neuen Service an.",
|
|
"operationId": "create_service",
|
|
"requestBody": {
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"$ref": "#/components/schemas/CreateServiceRequest"
|
|
}
|
|
}
|
|
},
|
|
"required": true
|
|
},
|
|
"responses": {
|
|
"200": {
|
|
"description": "",
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"$ref": "#/components/schemas/ServiceResponse"
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"400": {
|
|
"description": "Validierungsfehler (z. B. kind/min/max inkonsistent)"
|
|
},
|
|
"401": {
|
|
"description": "Authentifizierung fehlgeschlagen"
|
|
},
|
|
"409": {
|
|
"description": "key existiert bereits"
|
|
}
|
|
},
|
|
"security": [
|
|
{
|
|
"bearer_auth": []
|
|
}
|
|
]
|
|
}
|
|
},
|
|
"/services/{id}": {
|
|
"delete": {
|
|
"tags": [
|
|
"services"
|
|
],
|
|
"summary": "Hartes L\u00f6schen. `409 Conflict`, wenn der Service noch von einer Lieferung\nreferenziert wird \u2014 dann stattdessen deaktivieren.",
|
|
"operationId": "delete_service",
|
|
"parameters": [
|
|
{
|
|
"name": "id",
|
|
"in": "path",
|
|
"description": "Service-Id",
|
|
"required": true,
|
|
"schema": {
|
|
"type": "string",
|
|
"format": "uuid"
|
|
}
|
|
}
|
|
],
|
|
"responses": {
|
|
"204": {
|
|
"description": "Service gel\u00f6scht"
|
|
},
|
|
"401": {
|
|
"description": "Authentifizierung fehlgeschlagen"
|
|
},
|
|
"404": {
|
|
"description": "Service nicht gefunden"
|
|
},
|
|
"409": {
|
|
"description": "Service ist noch referenziert"
|
|
}
|
|
},
|
|
"security": [
|
|
{
|
|
"bearer_auth": []
|
|
}
|
|
]
|
|
},
|
|
"patch": {
|
|
"tags": [
|
|
"services"
|
|
],
|
|
"summary": "Patcht Name/Grenzen/Aktiv-Flag/Sortierung. `kind` ist nicht \u00e4nderbar.",
|
|
"operationId": "update_service",
|
|
"parameters": [
|
|
{
|
|
"name": "id",
|
|
"in": "path",
|
|
"description": "Service-Id",
|
|
"required": true,
|
|
"schema": {
|
|
"type": "string",
|
|
"format": "uuid"
|
|
}
|
|
}
|
|
],
|
|
"requestBody": {
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"$ref": "#/components/schemas/UpdateServiceRequest"
|
|
}
|
|
}
|
|
},
|
|
"required": true
|
|
},
|
|
"responses": {
|
|
"200": {
|
|
"description": "",
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"$ref": "#/components/schemas/ServiceResponse"
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"401": {
|
|
"description": "Authentifizierung fehlgeschlagen"
|
|
},
|
|
"404": {
|
|
"description": "Service nicht gefunden"
|
|
}
|
|
},
|
|
"security": [
|
|
{
|
|
"bearer_auth": []
|
|
}
|
|
]
|
|
}
|
|
},
|
|
"/sync/tour": {
|
|
"post": {
|
|
"tags": [
|
|
"sync"
|
|
],
|
|
"summary": "Sync-Endpoint f\u00fcr das ERP: legt eine Tagestour samt Lieferungen und\nPositionen idempotent an. Identit\u00e4t pro Tour\n`(driver_personalnummer, tour_date)`, pro Lieferung\n`(belegart_id, belegnummer)`.",
|
|
"operationId": "sync_tour",
|
|
"requestBody": {
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"$ref": "#/components/schemas/SyncTourRequest"
|
|
}
|
|
}
|
|
},
|
|
"required": true
|
|
},
|
|
"responses": {
|
|
"200": {
|
|
"description": "Tour gespeichert",
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"$ref": "#/components/schemas/SyncTourResponse"
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"400": {
|
|
"description": "Validierungsfehler im Sync-Payload"
|
|
},
|
|
"401": {
|
|
"description": "Authentifizierung fehlgeschlagen"
|
|
}
|
|
},
|
|
"security": [
|
|
{
|
|
"bearer_auth": []
|
|
}
|
|
]
|
|
}
|
|
},
|
|
"/tours/{tour_id}": {
|
|
"get": {
|
|
"tags": [
|
|
"tours"
|
|
],
|
|
"summary": "L\u00e4dt eine Tour mit allen Lieferungen, Positionen und referenzierten\nStammdaten \u2014 die App nutzt das als einzigen gro\u00dfen Read.",
|
|
"operationId": "get_tour",
|
|
"parameters": [
|
|
{
|
|
"name": "tour_id",
|
|
"in": "path",
|
|
"description": "Eindeutige Tour-Id (UUID)",
|
|
"required": true,
|
|
"schema": {
|
|
"type": "string",
|
|
"format": "uuid"
|
|
}
|
|
}
|
|
],
|
|
"responses": {
|
|
"200": {
|
|
"description": "Tour-Aggregat gefunden",
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"$ref": "#/components/schemas/TourDetails"
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"401": {
|
|
"description": "Authentifizierung fehlgeschlagen"
|
|
},
|
|
"404": {
|
|
"description": "Keine Tour mit dieser Id"
|
|
}
|
|
},
|
|
"security": [
|
|
{
|
|
"bearer_auth": []
|
|
}
|
|
]
|
|
}
|
|
},
|
|
"/tours/{tour_id}/delivery-order": {
|
|
"put": {
|
|
"tags": [
|
|
"tours"
|
|
],
|
|
"summary": "Schreibt die Sortier-Reihenfolge aller Lieferungen einer Tour neu.\nDer Client schickt die **vollst\u00e4ndige** neue Reihenfolge; fehlende\noder fremde Lieferungs-Ids werden mit `400 validation` abgelehnt.",
|
|
"operationId": "set_delivery_order",
|
|
"parameters": [
|
|
{
|
|
"name": "tour_id",
|
|
"in": "path",
|
|
"required": true,
|
|
"schema": {
|
|
"type": "string",
|
|
"format": "uuid"
|
|
}
|
|
}
|
|
],
|
|
"requestBody": {
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"$ref": "#/components/schemas/SetDeliveryOrderRequest"
|
|
}
|
|
}
|
|
},
|
|
"required": true
|
|
},
|
|
"responses": {
|
|
"200": {
|
|
"description": "Neue Reihenfolge gespeichert",
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"$ref": "#/components/schemas/SetDeliveryOrderResponse"
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"400": {
|
|
"description": "Mengen-Mismatch oder Duplikate"
|
|
},
|
|
"401": {
|
|
"description": "Authentifizierung fehlgeschlagen"
|
|
},
|
|
"404": {
|
|
"description": "Tour nicht gefunden"
|
|
}
|
|
},
|
|
"security": [
|
|
{
|
|
"bearer_auth": []
|
|
}
|
|
]
|
|
}
|
|
}
|
|
},
|
|
"components": {
|
|
"schemas": {
|
|
"Account": {
|
|
"type": "object",
|
|
"description": "Account eines Liefer-Unternehmens oder Einzel-Lieferfahrers.\n\nDie Personalnummer ist sowohl Prim\u00e4rschl\u00fcssel als auch Login-ID. Sie\nstammt aus dem ERP-Stamm \u2014 entweder ein Unternehmen (juristische\nPerson, eigener Personalnummern-Kreis) oder eine nat\u00fcrliche Person.\n\nMehrere physische Fahrer k\u00f6nnen denselben Account benutzen; das Modell\nunterscheidet sie nicht, sondern loggt die Aktivit\u00e4t auf [`crate::domain::Car`]-\nEbene (siehe Audit-Log).",
|
|
"required": [
|
|
"personalnummer",
|
|
"name",
|
|
"active"
|
|
],
|
|
"properties": {
|
|
"active": {
|
|
"type": "boolean"
|
|
},
|
|
"name": {
|
|
"type": "string"
|
|
},
|
|
"personalnummer": {
|
|
"type": "integer",
|
|
"format": "int64"
|
|
}
|
|
}
|
|
},
|
|
"Address": {
|
|
"type": "object",
|
|
"description": "Postanschrift \u2014 wird sowohl als aktuelle Kundenanschrift in [`Customer`]\nals auch als unver\u00e4nderlicher Snapshot in [`crate::domain::Delivery`]\nverwendet (`delivery_address_snapshot`).\n\nBewusst als Value Object modelliert: gleiche Adresse = gleicher Wert.\nStrikte Equality erleichtert Sync-Diffs zwischen ERP und Backend.\n\n[`Customer`]: crate::domain::Customer",
|
|
"required": [
|
|
"street",
|
|
"houseNumber",
|
|
"postalCode",
|
|
"city",
|
|
"country"
|
|
],
|
|
"properties": {
|
|
"city": {
|
|
"type": "string"
|
|
},
|
|
"country": {
|
|
"type": "string"
|
|
},
|
|
"houseNumber": {
|
|
"type": "string"
|
|
},
|
|
"postalCode": {
|
|
"type": "string"
|
|
},
|
|
"street": {
|
|
"type": "string"
|
|
}
|
|
}
|
|
},
|
|
"ApplyScansRequest": {
|
|
"type": "object",
|
|
"required": [
|
|
"scans"
|
|
],
|
|
"properties": {
|
|
"scans": {
|
|
"type": "array",
|
|
"items": {
|
|
"$ref": "#/components/schemas/ScanEvent"
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"ApplyScansResponse": {
|
|
"type": "object",
|
|
"required": [
|
|
"results"
|
|
],
|
|
"properties": {
|
|
"results": {
|
|
"type": "array",
|
|
"items": {
|
|
"$ref": "#/components/schemas/ScanResult"
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"Article": {
|
|
"type": "object",
|
|
"description": "Artikel. ERP-Mirror; die `article_number` ist die business-stabile\nArtikelnummer aus dem ERP-Stamm und dient gleichzeitig als Br\u00fccke.\n\n`scannable = false` markiert nicht-physische Positionen wie\nDienstleistungen, Versandpauschalen o.\u00e4. \u2014 sie tauchen zwar als\n`DeliveryItem` auf, blockieren aber den Beladen-Fortschritt nicht.",
|
|
"required": [
|
|
"id",
|
|
"articleNumber",
|
|
"name",
|
|
"scannable"
|
|
],
|
|
"properties": {
|
|
"articleNumber": {
|
|
"type": "string"
|
|
},
|
|
"defaultWarehouseId": {
|
|
"type": [
|
|
"string",
|
|
"null"
|
|
],
|
|
"format": "uuid"
|
|
},
|
|
"id": {
|
|
"type": "string",
|
|
"format": "uuid"
|
|
},
|
|
"name": {
|
|
"type": "string"
|
|
},
|
|
"scannable": {
|
|
"type": "boolean"
|
|
}
|
|
}
|
|
},
|
|
"AssignCarRequest": {
|
|
"type": "object",
|
|
"description": "Setzt das `assigned_car_id` einer Lieferung. `None` (`carId: null`)\nentfernt die Zuordnung.",
|
|
"properties": {
|
|
"carId": {
|
|
"type": [
|
|
"string",
|
|
"null"
|
|
],
|
|
"format": "uuid"
|
|
}
|
|
}
|
|
},
|
|
"AuditAction": {
|
|
"type": "string",
|
|
"description": "Aktion-Typen im Scan-Audit-Log.\n\n* `Scan` / `Unscan` ver\u00e4ndern die `scanned_quantity` (+1 / -1).\n* `Hold` / `Unhold` \u00e4ndern nur den Status, keine Menge.\n* `Remove` markiert die Position als entfernt (Status `Removed`,\n z. B. weil der Kunde sie nicht annimmt).\n* `Unremove` hebt ein `Remove` wieder auf \u2014 die Position landet\n zur\u00fcck in `InProgress` (oder `Done`, falls die `scanned_quantity`\n schon `required_quantity` erreicht hatte). Der urspr\u00fcngliche\n `Remove`-Audit-Eintrag bleibt unangetastet; das `Unremove` erzeugt\n einen eigenen Audit-Eintrag \u2014 die Historie bleibt vollst\u00e4ndig.",
|
|
"enum": [
|
|
"scan",
|
|
"unscan",
|
|
"hold",
|
|
"unhold",
|
|
"remove",
|
|
"unremove"
|
|
]
|
|
},
|
|
"CancelDeliveryRequest": {
|
|
"type": "object",
|
|
"required": [
|
|
"reason"
|
|
],
|
|
"properties": {
|
|
"reason": {
|
|
"type": "string"
|
|
}
|
|
}
|
|
},
|
|
"Car": {
|
|
"type": "object",
|
|
"description": "Fahrzeug eines [`crate::domain::Account`]. Wird in der App selbst\ngepflegt \u2014 kein ERP-Spiegel. Eindeutig per UUID.\n\nIm Audit-Log ist der `Car` der \u201eAkteur\": die Personalnummer-Ebene\n(Account) ist gr\u00f6ber und unterscheidet nicht zwischen mehreren\ngleichzeitig aktiven Fahrern desselben Subunternehmens.",
|
|
"required": [
|
|
"id",
|
|
"accountId",
|
|
"plate",
|
|
"active"
|
|
],
|
|
"properties": {
|
|
"accountId": {
|
|
"type": "integer",
|
|
"format": "int64",
|
|
"description": "Verweis auf [`crate::domain::Account::personalnummer`]."
|
|
},
|
|
"active": {
|
|
"type": "boolean"
|
|
},
|
|
"id": {
|
|
"type": "string",
|
|
"format": "uuid"
|
|
},
|
|
"plate": {
|
|
"type": "string"
|
|
}
|
|
}
|
|
},
|
|
"CarResponse": {
|
|
"type": "object",
|
|
"required": [
|
|
"car"
|
|
],
|
|
"properties": {
|
|
"car": {
|
|
"$ref": "#/components/schemas/Car"
|
|
}
|
|
}
|
|
},
|
|
"CarsList": {
|
|
"type": "object",
|
|
"required": [
|
|
"cars"
|
|
],
|
|
"properties": {
|
|
"cars": {
|
|
"type": "array",
|
|
"items": {
|
|
"$ref": "#/components/schemas/Car"
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"CompleteDeliveryAcknowledgements": {
|
|
"type": "object",
|
|
"description": "Dokumentierte Best\u00e4tigungen des Kunden zum Abschlusszeitpunkt.",
|
|
"required": [
|
|
"receiptConfirmed"
|
|
],
|
|
"properties": {
|
|
"acknowledgedNoteIds": {
|
|
"type": "array",
|
|
"items": {
|
|
"type": "string",
|
|
"format": "uuid"
|
|
},
|
|
"description": "Notiz-IDs, die zum Abschlusszeitpunkt sichtbar waren und mit-best\u00e4tigt\nwurden (Audit-Robustheit)."
|
|
},
|
|
"authorCarId": {
|
|
"type": [
|
|
"string",
|
|
"null"
|
|
],
|
|
"format": "uuid",
|
|
"description": "Fahrzeug des Akteurs (Audit-Spur). Muss zum Account geh\u00f6ren."
|
|
},
|
|
"notesAcknowledged": {
|
|
"type": "boolean",
|
|
"description": "\u201eAnmerkungen zur Lieferung zur Kenntnis genommen.\" \u2014 Pflicht nur, wenn\nNotizen existieren (das pr\u00fcft der Server)."
|
|
},
|
|
"paymentCollected": {
|
|
"type": "boolean",
|
|
"description": "Inkasso-Best\u00e4tigung des Fahrers: \u201eder offene Betrag wurde erhalten\n(bar) bzw. \u00fcber das EC-Ger\u00e4t abgerechnet.\" Pflicht nur, wenn beim\nAbschluss ein offener Betrag > 0 besteht UND die Methode ein Vor-Ort-\nInkasso ist (Bar/EC) \u2014 das pr\u00fcft der Server. Der kassierte Betrag wird\nserver-seitig autoritativ berechnet (nicht vom Client \u00fcbernommen)."
|
|
},
|
|
"paymentMethodId": {
|
|
"type": [
|
|
"string",
|
|
"null"
|
|
],
|
|
"format": "uuid",
|
|
"description": "Optionale Zahlungsmethode, die der Fahrer beim Abschluss gew\u00e4hlt hat.\n`None` = die am Beleg hinterlegte Methode bleibt. Falls gesetzt, muss\nsie existieren **und** aktiv sein (vom Server gepr\u00fcft)."
|
|
},
|
|
"receiptConfirmed": {
|
|
"type": "boolean",
|
|
"description": "\u201eWare im ordnungsgem\u00e4\u00dfen Zustand erhalten / Aufbau korrekt.\" \u2014 Pflicht."
|
|
}
|
|
}
|
|
},
|
|
"ContactChannel": {
|
|
"type": "object",
|
|
"description": "Ein einzelner Kontaktkanal (Telefonnummer / Mobil / E-Mail / Web).\nMehrere pro [`ContactSource`] m\u00f6glich, die `position` h\u00e4lt die\n1-basierte ERP-Reihenfolge (`Telefon` \u2192 1, `Telefon2` \u2192 2 usw.) fest,\ndamit der \u201eprim\u00e4re\" Kanal je Art stabil identifizierbar bleibt.",
|
|
"required": [
|
|
"id",
|
|
"sourceId",
|
|
"kind",
|
|
"position",
|
|
"value"
|
|
],
|
|
"properties": {
|
|
"id": {
|
|
"type": "string",
|
|
"format": "uuid"
|
|
},
|
|
"kind": {
|
|
"$ref": "#/components/schemas/ContactKind"
|
|
},
|
|
"position": {
|
|
"type": "integer",
|
|
"format": "int32"
|
|
},
|
|
"sourceId": {
|
|
"type": "string",
|
|
"format": "uuid"
|
|
},
|
|
"value": {
|
|
"type": "string"
|
|
}
|
|
}
|
|
},
|
|
"ContactKind": {
|
|
"type": "string",
|
|
"description": "Art eines Kommunikationskanals. `fax` bewusst nicht mitgef\u00fchrt \u2014 in der\nApp nicht verwendet.",
|
|
"enum": [
|
|
"phone",
|
|
"mobile",
|
|
"email",
|
|
"web"
|
|
]
|
|
},
|
|
"ContactRole": {
|
|
"type": "string",
|
|
"description": "Rolle, mit der ein Kontakt-Datensatz an einer Lieferung h\u00e4ngt. Spiegelt\ndie f\u00fcnf Adress-FKs von `Belegkopf` (bzw. den Umweg \u00fcber den Kunden):\n`header` = Belegadresse, `delivery` = Lieferadresse, `billing` =\nRechnungsadresse, `contact_person` = Ansprechpartner, `customer_master`\n= Stammadresse des Kunden \u00fcber `Kunden.AdressId`.",
|
|
"enum": [
|
|
"header",
|
|
"delivery",
|
|
"billing",
|
|
"contact_person",
|
|
"customer_master"
|
|
]
|
|
},
|
|
"ContactSource": {
|
|
"type": "object",
|
|
"description": "Snapshot eines ERP-Adress-Datensatzes, der zum Zeitpunkt des Tour-Syncs\nan einer Lieferung hing \u2014 Namensblock ohne Anschrift, weil die Adresse\nihrerseits schon im Lieferungs-Snapshot steckt (`snap_*`-Spalten). Die\neigentlichen Telefonnummern, E-Mails etc. liegen in den\nzugeh\u00f6rigen [`ContactChannel`]s.",
|
|
"required": [
|
|
"id",
|
|
"deliveryId",
|
|
"role"
|
|
],
|
|
"properties": {
|
|
"abteilung": {
|
|
"type": [
|
|
"string",
|
|
"null"
|
|
]
|
|
},
|
|
"anrede": {
|
|
"type": [
|
|
"string",
|
|
"null"
|
|
]
|
|
},
|
|
"deliveryId": {
|
|
"type": "string",
|
|
"format": "uuid"
|
|
},
|
|
"funktion": {
|
|
"type": [
|
|
"string",
|
|
"null"
|
|
]
|
|
},
|
|
"id": {
|
|
"type": "string",
|
|
"format": "uuid"
|
|
},
|
|
"name1": {
|
|
"type": [
|
|
"string",
|
|
"null"
|
|
]
|
|
},
|
|
"name2": {
|
|
"type": [
|
|
"string",
|
|
"null"
|
|
]
|
|
},
|
|
"name3": {
|
|
"type": [
|
|
"string",
|
|
"null"
|
|
]
|
|
},
|
|
"role": {
|
|
"$ref": "#/components/schemas/ContactRole"
|
|
},
|
|
"titel": {
|
|
"type": [
|
|
"string",
|
|
"null"
|
|
]
|
|
}
|
|
}
|
|
},
|
|
"CreateCarRequest": {
|
|
"type": "object",
|
|
"required": [
|
|
"plate"
|
|
],
|
|
"properties": {
|
|
"plate": {
|
|
"type": "string"
|
|
}
|
|
}
|
|
},
|
|
"CreateDeliveryNoteRequest": {
|
|
"type": "object",
|
|
"properties": {
|
|
"authorCarId": {
|
|
"type": [
|
|
"string",
|
|
"null"
|
|
],
|
|
"format": "uuid",
|
|
"description": "Fahrzeug, das die Notiz erzeugt hat. Muss zum angemeldeten\nAccount geh\u00f6ren. `None` ist erlaubt."
|
|
},
|
|
"creditDeliveryItemId": {
|
|
"type": [
|
|
"string",
|
|
"null"
|
|
],
|
|
"format": "uuid",
|
|
"description": "Optionaler Gutschrift-Bezug: die Belegzeile, f\u00fcr die diese Notiz als\nGutschrift-Grund angelegt wird. Erm\u00f6glicht das gezielte L\u00f6schen beim\nUnremove. `None` f\u00fcr normale Notizen."
|
|
},
|
|
"imageAttachment": {
|
|
"type": [
|
|
"string",
|
|
"null"
|
|
],
|
|
"description": "Object-Storage-Key oder URL eines vorab hochgeladenen Bildes."
|
|
},
|
|
"isAmountCreditNote": {
|
|
"type": "boolean",
|
|
"description": "`true` markiert die Notiz als Grund einer Betrags-Gutschrift\n(Lieferungs-Ebene). Default `false`."
|
|
},
|
|
"text": {
|
|
"type": [
|
|
"string",
|
|
"null"
|
|
]
|
|
}
|
|
}
|
|
},
|
|
"CreatePaymentMethodRequest": {
|
|
"type": "object",
|
|
"required": [
|
|
"code",
|
|
"name"
|
|
],
|
|
"properties": {
|
|
"code": {
|
|
"type": "string",
|
|
"description": "Eindeutiger Programm-Identifier (z. B. `\"paypal\"`, `\"klarna\"`)."
|
|
},
|
|
"name": {
|
|
"type": "string",
|
|
"description": "Anzeige-Name in der UI."
|
|
}
|
|
}
|
|
},
|
|
"CreateServiceRequest": {
|
|
"type": "object",
|
|
"required": [
|
|
"key",
|
|
"name",
|
|
"kind"
|
|
],
|
|
"properties": {
|
|
"key": {
|
|
"type": "string",
|
|
"description": "Eindeutiger Programm-Identifier (z. B. `\"podium_setup\"`)."
|
|
},
|
|
"kind": {
|
|
"$ref": "#/components/schemas/ServiceKind"
|
|
},
|
|
"maxValue": {
|
|
"type": [
|
|
"integer",
|
|
"null"
|
|
],
|
|
"format": "int32"
|
|
},
|
|
"minValue": {
|
|
"type": [
|
|
"integer",
|
|
"null"
|
|
],
|
|
"format": "int32",
|
|
"description": "Nur bei `Numeric` sinnvoll."
|
|
},
|
|
"name": {
|
|
"type": "string"
|
|
},
|
|
"sortOrder": {
|
|
"type": [
|
|
"integer",
|
|
"null"
|
|
],
|
|
"format": "int32"
|
|
}
|
|
}
|
|
},
|
|
"CreditAction": {
|
|
"type": "string",
|
|
"description": "Art des Gutschrift-Ereignisses.",
|
|
"enum": [
|
|
"set",
|
|
"remove"
|
|
]
|
|
},
|
|
"Customer": {
|
|
"type": "object",
|
|
"description": "Kunde. ERP-Mirror: die Stammdaten geh\u00f6ren dem ERP, wir spiegeln sie\nf\u00fcr die App. Die `erp_customer_id` ist die Br\u00fccke zur\u00fcck (in der\nRegel die `Kunde.row_id` aus ERPframe).\n\nDie `Customer.address` ist die *aktuelle* Anschrift. F\u00fcr historische\nStabilit\u00e4t f\u00fchrt [`crate::domain::Delivery`] zus\u00e4tzlich einen\n`delivery_address_snapshot` \u2014 Adress-\u00c4nderungen wirken nicht\nr\u00fcckwirkend auf bereits zugestellte oder geplante Lieferungen.",
|
|
"required": [
|
|
"id",
|
|
"erpCustomerId",
|
|
"name",
|
|
"address"
|
|
],
|
|
"properties": {
|
|
"address": {
|
|
"$ref": "#/components/schemas/Address"
|
|
},
|
|
"erpCustomerId": {
|
|
"type": "integer",
|
|
"format": "int64"
|
|
},
|
|
"id": {
|
|
"type": "string",
|
|
"format": "uuid"
|
|
},
|
|
"name": {
|
|
"type": "string"
|
|
}
|
|
}
|
|
},
|
|
"CustomerContact": {
|
|
"type": "object",
|
|
"description": "Ansprechpartner eines Kunden. Ein Kunde kann mehrere Kontaktpersonen\nhaben (z. B. Empfang vor Ort + Gesch\u00e4ftsf\u00fchrung). Eine Lieferung w\u00e4hlt\n0..N davon als aktive Kontakte aus (siehe\n`Delivery::contact_person_ids`).",
|
|
"required": [
|
|
"id",
|
|
"customerId",
|
|
"name"
|
|
],
|
|
"properties": {
|
|
"customerId": {
|
|
"type": "string",
|
|
"format": "uuid"
|
|
},
|
|
"email": {
|
|
"type": [
|
|
"string",
|
|
"null"
|
|
]
|
|
},
|
|
"id": {
|
|
"type": "string",
|
|
"format": "uuid"
|
|
},
|
|
"name": {
|
|
"type": "string"
|
|
},
|
|
"phone": {
|
|
"type": [
|
|
"string",
|
|
"null"
|
|
]
|
|
}
|
|
}
|
|
},
|
|
"DeliveredBelegnummernResponse": {
|
|
"type": "object",
|
|
"required": [
|
|
"day",
|
|
"count",
|
|
"belegnummern"
|
|
],
|
|
"properties": {
|
|
"belegnummern": {
|
|
"type": "array",
|
|
"items": {
|
|
"type": "string"
|
|
},
|
|
"description": "Belegnummern aller **ausgelieferten** (abgeschlossenen) Lieferungen,\nderen Liefermail noch **nicht versendet** wurde, aufsteigend nach\nAbschluss-Zeitpunkt."
|
|
},
|
|
"count": {
|
|
"type": "integer",
|
|
"description": "Anzahl der offenen (noch nicht versendeten) Belege.",
|
|
"minimum": 0
|
|
},
|
|
"day": {
|
|
"type": "string",
|
|
"description": "Tag, nach dem gefiltert wurde (ISO `YYYY-MM-DD`), oder `\"all\"` wenn kein\n`day` angegeben war."
|
|
}
|
|
}
|
|
},
|
|
"Delivery": {
|
|
"type": "object",
|
|
"description": "Eine einzelne Lieferung an einen Kunden. Aggregat-Wurzel f\u00fcr die\nLiefer-Items, Notizen und das ggf. zugeordnete Fahrzeug.",
|
|
"required": [
|
|
"id",
|
|
"tourId",
|
|
"erpBelegartId",
|
|
"erpBelegnummer",
|
|
"customerId",
|
|
"deliveryAddressSnapshot",
|
|
"contactPersonIds",
|
|
"state",
|
|
"prepaidAmount",
|
|
"paymentMethodId"
|
|
],
|
|
"properties": {
|
|
"assignedCarId": {
|
|
"type": [
|
|
"string",
|
|
"null"
|
|
],
|
|
"format": "uuid",
|
|
"description": "Fahrzeug-Zuordnung, gesetzt in der Ausw\u00e4hlen-Phase.\nBei Ein-Auto-Teams beim Sync automatisch gef\u00fcllt."
|
|
},
|
|
"contactPersonIds": {
|
|
"type": "array",
|
|
"items": {
|
|
"type": "string",
|
|
"format": "uuid"
|
|
},
|
|
"description": "Ausgew\u00e4hlte Ansprechpartner f\u00fcr genau diese Lieferung (Auswahl\naus `Customer.contacts`). Kann leer sein."
|
|
},
|
|
"customerId": {
|
|
"type": "string",
|
|
"format": "uuid"
|
|
},
|
|
"deliveryAddressSnapshot": {
|
|
"$ref": "#/components/schemas/Address",
|
|
"description": "Eingefrorene Liefer-Adresse zum Zeitpunkt des Tour-Syncs.\nSch\u00fctzt vor r\u00fcckwirkenden Kunden-Adress\u00e4nderungen."
|
|
},
|
|
"desiredTime": {
|
|
"type": [
|
|
"string",
|
|
"null"
|
|
],
|
|
"description": "Wunsch-Lieferzeit als Freitext (z. B. \"vormittags\", \"ab 14:00\")."
|
|
},
|
|
"erpBelegartId": {
|
|
"type": "integer",
|
|
"format": "int64",
|
|
"description": "ERP-Beleg-Bezug: business-stabiles Paar `(Belegart, Belegnummer)`.\n\u00dcberlebt den Belegkopf-Archiv\u00fcbergang."
|
|
},
|
|
"erpBelegnummer": {
|
|
"type": "string"
|
|
},
|
|
"id": {
|
|
"type": "string",
|
|
"format": "uuid"
|
|
},
|
|
"paymentMethodId": {
|
|
"type": "string",
|
|
"format": "uuid",
|
|
"description": "F\u00fcr den Restbetrag gew\u00e4hlte Zahlungsart \u2014 FK auf `payment_methods`.\nVom Kunden bei Bestellung festgelegt, der Fahrer \u00fcbernimmt nur\ndie Abwicklung. Aktiv-Flag und Anzeige-Name werden \u00fcber die\nStammdaten-Tabelle aufgel\u00f6st, nicht hier embeddet."
|
|
},
|
|
"prepaidAmount": {
|
|
"type": "number",
|
|
"format": "double",
|
|
"description": "Bei Bestellung schon bezahlter Betrag in EUR. `0.0` wenn der\nKunde alles bei Lieferung zahlt. Wird vom ERP-Sync gef\u00fcllt."
|
|
},
|
|
"specialAgreements": {
|
|
"type": [
|
|
"string",
|
|
"null"
|
|
],
|
|
"description": "Sondervereinbarungen (z. B. \u201eT\u00fcrklingel defekt, hintenrum klopfen\")."
|
|
},
|
|
"state": {
|
|
"$ref": "#/components/schemas/DeliveryState"
|
|
},
|
|
"stateReason": {
|
|
"type": [
|
|
"string",
|
|
"null"
|
|
],
|
|
"description": "Begr\u00fcndung bei `state == Held` oder `state == Canceled`. Beim\nResume / Complete wieder `None`."
|
|
},
|
|
"tourId": {
|
|
"type": "string",
|
|
"format": "uuid"
|
|
}
|
|
}
|
|
},
|
|
"DeliveryCredit": {
|
|
"type": "object",
|
|
"description": "Aktuelle Betrags-Gutschrift einer Lieferung (Geld-Nachlass, unabh\u00e4ngig von\nSt\u00fcckzahl). Abgeleitet aus dem j\u00fcngsten Ereignis im append-only\n`delivery_credit_audit`; existiert nur, solange der letzte Stand `set`\n(und nicht `remove`) ist. `delivery_id` macht den Eintrag \u2014 wie eine\nNotiz \u2014 clientseitig per Lieferung join-bar.",
|
|
"required": [
|
|
"deliveryId",
|
|
"amountCents",
|
|
"reason"
|
|
],
|
|
"properties": {
|
|
"amountCents": {
|
|
"type": "integer",
|
|
"format": "int64",
|
|
"description": "Gutschrift-Betrag in Cent (> 0, \u2264 15000)."
|
|
},
|
|
"deliveryId": {
|
|
"type": "string",
|
|
"format": "uuid"
|
|
},
|
|
"reason": {
|
|
"type": "string"
|
|
}
|
|
}
|
|
},
|
|
"DeliveryCreditEventRequest": {
|
|
"type": "object",
|
|
"required": [
|
|
"clientEventId",
|
|
"action"
|
|
],
|
|
"properties": {
|
|
"action": {
|
|
"$ref": "#/components/schemas/CreditAction"
|
|
},
|
|
"amountCents": {
|
|
"type": [
|
|
"integer",
|
|
"null"
|
|
],
|
|
"format": "int64",
|
|
"description": "Bei `Set` Pflicht: Betrag in Cent (> 0, \u2264 15000, Vielfaches von 1000)."
|
|
},
|
|
"authorCarId": {
|
|
"type": [
|
|
"string",
|
|
"null"
|
|
],
|
|
"format": "uuid",
|
|
"description": "Fahrzeug des Akteurs (Audit-Spur). Muss zum Account geh\u00f6ren."
|
|
},
|
|
"clientEventId": {
|
|
"type": "string",
|
|
"format": "uuid",
|
|
"description": "Idempotenz-Schl\u00fcssel \u2014 pro erzeugtem Ereignis genau einmal vergeben.\nEin Retry mit derselben Id wendet nichts erneut an."
|
|
},
|
|
"reason": {
|
|
"type": [
|
|
"string",
|
|
"null"
|
|
],
|
|
"description": "Bei `Set` Pflicht: Begr\u00fcndung."
|
|
}
|
|
}
|
|
},
|
|
"DeliveryCreditResponse": {
|
|
"type": "object",
|
|
"properties": {
|
|
"credit": {
|
|
"oneOf": [
|
|
{
|
|
"type": "null"
|
|
},
|
|
{
|
|
"$ref": "#/components/schemas/DeliveryCredit",
|
|
"description": "Aktueller Stand nach dem Ereignis \u2014 `None`, wenn (zuletzt) entfernt."
|
|
}
|
|
]
|
|
}
|
|
}
|
|
},
|
|
"DeliveryItem": {
|
|
"type": "object",
|
|
"description": "Einzelposition einer Lieferung. Vereint regul\u00e4re Belegzeilen und\nSt\u00fccklisten-Komponenten zu einer flachen Liste \u2014 die St\u00fccklisten-\nHierarchie ist ein ERP-Konstrukt und wird beim Sync aufgel\u00f6st.\n\n\u00dcber die Felder `belegzeilen_nr` und `komponenten_artikel_nr` bleibt\ndie ERP-Herkunft aufl\u00f6sbar.",
|
|
"required": [
|
|
"id",
|
|
"deliveryId",
|
|
"articleId",
|
|
"requiredQuantity",
|
|
"warehouseId",
|
|
"unitPrice",
|
|
"belegzeilenNr",
|
|
"scanState"
|
|
],
|
|
"properties": {
|
|
"articleId": {
|
|
"type": "string",
|
|
"format": "uuid"
|
|
},
|
|
"belegzeilenNr": {
|
|
"type": "integer",
|
|
"format": "int32",
|
|
"description": "ERP-Belegzeilen-Nr (Position innerhalb des Belegs)."
|
|
},
|
|
"deliveryId": {
|
|
"type": "string",
|
|
"format": "uuid"
|
|
},
|
|
"id": {
|
|
"type": "string",
|
|
"format": "uuid"
|
|
},
|
|
"komponentenArtikelNr": {
|
|
"type": [
|
|
"string",
|
|
"null"
|
|
],
|
|
"description": "Bei Items aus einer St\u00fcckliste: Artikelnummer der Komponente.\nBei regul\u00e4ren Belegzeilen: `None`."
|
|
},
|
|
"parentArtikelNr": {
|
|
"type": [
|
|
"string",
|
|
"null"
|
|
],
|
|
"description": "Artikelnummer des Oberartikels, zu dem diese Komponente geh\u00f6rt.\n`None` bei Oberartikeln/regul\u00e4ren Zeilen \u2014 die App r\u00fcckt Komponenten\ndar\u00fcber unter ihrem Oberartikel ein."
|
|
},
|
|
"requiredQuantity": {
|
|
"type": "integer",
|
|
"format": "int32"
|
|
},
|
|
"scanState": {
|
|
"$ref": "#/components/schemas/ScanState"
|
|
},
|
|
"unitPrice": {
|
|
"type": "number",
|
|
"format": "double",
|
|
"description": "St\u00fcckpreis (brutto, EUR) aus dem ERP-Sync. Der Warenwert einer\nLieferung = \u03a3 `unit_price` \u00d7 ausgelieferte Menge."
|
|
},
|
|
"warehouseId": {
|
|
"type": "string",
|
|
"format": "uuid"
|
|
}
|
|
}
|
|
},
|
|
"DeliveryNote": {
|
|
"type": "object",
|
|
"description": "Notiz an einer Lieferung \u2014 frei eingegeben durch den Fahrer.\n\nMindestens eines von `text` oder `image_attachment` muss gesetzt\nsein. Die Constraint sitzt sowohl im DB-Schema (CHECK) als auch\nin der Application-Schicht.",
|
|
"required": [
|
|
"id",
|
|
"deliveryId",
|
|
"authorPersonalnummer",
|
|
"isAmountCreditNote",
|
|
"createdAt"
|
|
],
|
|
"properties": {
|
|
"authorCarId": {
|
|
"type": [
|
|
"string",
|
|
"null"
|
|
],
|
|
"format": "uuid",
|
|
"description": "Fahrzeug, falls bekannt \u2014 nullable bis das Backend Cars verwaltet."
|
|
},
|
|
"authorPersonalnummer": {
|
|
"type": "integer",
|
|
"format": "int64",
|
|
"description": "Personalnummer des Akteurs (aus dem JWT). Pflicht."
|
|
},
|
|
"createdAt": {
|
|
"type": "string",
|
|
"format": "date-time"
|
|
},
|
|
"creditDeliveryItemId": {
|
|
"type": [
|
|
"string",
|
|
"null"
|
|
],
|
|
"format": "uuid",
|
|
"description": "Wenn die Notiz als Gutschrift-Grund zu einer Belegzeile angelegt\nwurde: deren `DeliveryItem`-Id. Erlaubt dem Client, die Notiz beim\nZur\u00fccknehmen der Gutschrift (Unremove) gezielt wieder zu l\u00f6schen.\n`None` bei normalen Text-/Foto-Notizen."
|
|
},
|
|
"deliveryId": {
|
|
"type": "string",
|
|
"format": "uuid"
|
|
},
|
|
"id": {
|
|
"type": "string",
|
|
"format": "uuid"
|
|
},
|
|
"imageAttachment": {
|
|
"type": [
|
|
"string",
|
|
"null"
|
|
],
|
|
"description": "Referenz auf einen Bild-Anhang (z. B. Object-Storage-Key/URL)."
|
|
},
|
|
"imageAttachmentDeleted": {
|
|
"type": "boolean",
|
|
"description": "`true`, wenn die lokale Bilddatei nach erfolgreichem Report-Upload\ngel\u00f6scht wurde (das Bild steckt nun im Lieferbericht in DOCUframe).\nRead-only; die App zeigt dann statt der Vorschau einen Hinweis.\nBei Text-Notizen / vorhandenem Bild: `false`."
|
|
},
|
|
"isAmountCreditNote": {
|
|
"type": "boolean",
|
|
"description": "`true`, wenn die Notiz den Grund einer **Betrags-Gutschrift**\n(Geld-Nachlass, Lieferungs-Ebene) dokumentiert. Erlaubt dem Client,\nsie beim Entfernen der Gutschrift gezielt zu l\u00f6schen."
|
|
},
|
|
"text": {
|
|
"type": [
|
|
"string",
|
|
"null"
|
|
]
|
|
}
|
|
}
|
|
},
|
|
"DeliveryNoteResponse": {
|
|
"type": "object",
|
|
"required": [
|
|
"note"
|
|
],
|
|
"properties": {
|
|
"note": {
|
|
"$ref": "#/components/schemas/DeliveryNote"
|
|
}
|
|
}
|
|
},
|
|
"DeliveryOrderEntry": {
|
|
"type": "object",
|
|
"required": [
|
|
"deliveryId",
|
|
"sortOrder"
|
|
],
|
|
"properties": {
|
|
"deliveryId": {
|
|
"type": "string",
|
|
"format": "uuid"
|
|
},
|
|
"sortOrder": {
|
|
"type": "integer",
|
|
"format": "int32"
|
|
}
|
|
}
|
|
},
|
|
"DeliveryResponse": {
|
|
"type": "object",
|
|
"required": [
|
|
"delivery"
|
|
],
|
|
"properties": {
|
|
"delivery": {
|
|
"$ref": "#/components/schemas/Delivery"
|
|
}
|
|
}
|
|
},
|
|
"DeliveryServiceResponse": {
|
|
"type": "object",
|
|
"required": [
|
|
"value"
|
|
],
|
|
"properties": {
|
|
"value": {
|
|
"$ref": "#/components/schemas/DeliveryServiceValue"
|
|
}
|
|
}
|
|
},
|
|
"DeliveryServiceValue": {
|
|
"type": "object",
|
|
"description": "Pro-Lieferung gew\u00e4hlter Wert eines Service. Genau einer der beiden\nWert-Slots ist je nach `ServiceKind` gesetzt; per `service_id`/`delivery_id`\nclientseitig join-bar (wie Notizen/Gutschriften).",
|
|
"required": [
|
|
"deliveryId",
|
|
"serviceId"
|
|
],
|
|
"properties": {
|
|
"boolValue": {
|
|
"type": [
|
|
"boolean",
|
|
"null"
|
|
]
|
|
},
|
|
"deliveryId": {
|
|
"type": "string",
|
|
"format": "uuid"
|
|
},
|
|
"numericValue": {
|
|
"type": [
|
|
"integer",
|
|
"null"
|
|
],
|
|
"format": "int32"
|
|
},
|
|
"serviceId": {
|
|
"type": "string",
|
|
"format": "uuid"
|
|
}
|
|
}
|
|
},
|
|
"DeliveryState": {
|
|
"type": "string",
|
|
"description": "Lebenszyklus einer Lieferung.\n\n`Held` ist f\u00fcr \u201eheute nicht zustellbar, aber nicht endg\u00fcltig abgesagt\"\nreserviert; `Canceled` ist endg\u00fcltig. `Completed` setzt der\nAbschluss-Flow am Ende der Auslieferung.",
|
|
"enum": [
|
|
"active",
|
|
"held",
|
|
"canceled",
|
|
"completed"
|
|
]
|
|
},
|
|
"DeliveryWithItems": {
|
|
"allOf": [
|
|
{
|
|
"$ref": "#/components/schemas/Delivery"
|
|
},
|
|
{
|
|
"type": "object",
|
|
"required": [
|
|
"sortOrder",
|
|
"items"
|
|
],
|
|
"properties": {
|
|
"items": {
|
|
"type": "array",
|
|
"items": {
|
|
"$ref": "#/components/schemas/DeliveryItem"
|
|
}
|
|
},
|
|
"sortOrder": {
|
|
"type": "integer",
|
|
"format": "int32",
|
|
"description": "Sortier-Reihenfolge innerhalb der Tour (1-basiert)."
|
|
}
|
|
}
|
|
}
|
|
]
|
|
},
|
|
"HoldDeliveryRequest": {
|
|
"type": "object",
|
|
"required": [
|
|
"reason"
|
|
],
|
|
"properties": {
|
|
"reason": {
|
|
"type": "string"
|
|
}
|
|
}
|
|
},
|
|
"ImportSummary": {
|
|
"type": "object",
|
|
"description": "Ergebnis eines Import-Laufs \u2014 pro Fahrer-Tour Erfolg/Fehler getrennt,\ndamit ein einzelner kaputter Beleg nicht den ganzen Tag blockiert.",
|
|
"required": [
|
|
"date",
|
|
"toursTotal",
|
|
"toursOk",
|
|
"toursFailed",
|
|
"errors"
|
|
],
|
|
"properties": {
|
|
"date": {
|
|
"type": "string",
|
|
"format": "date"
|
|
},
|
|
"driversProvisioned": {
|
|
"type": "integer",
|
|
"description": "Anzahl der **neu** im Identity-Provider (Keycloak) angelegten\nFahrer-Konten in diesem Lauf (0, wenn Provisionierung deaktiviert ist\noder alle Konten bereits existierten).",
|
|
"minimum": 0
|
|
},
|
|
"errors": {
|
|
"type": "array",
|
|
"items": {
|
|
"type": "string"
|
|
},
|
|
"description": "Fehlertexte je fehlgeschlagener Fahrer-Tour (z. B. unbekannter Fahrer\n\u2192 FK auf `accounts`, oder Validierungsfehler)."
|
|
},
|
|
"provisioningErrors": {
|
|
"type": "array",
|
|
"items": {
|
|
"type": "string"
|
|
},
|
|
"description": "Fehlertexte der Konto-Provisionierung (Keycloak). Best-effort: ein\nFehler hier blockiert den Touren-Import **nicht**."
|
|
},
|
|
"toursFailed": {
|
|
"type": "integer",
|
|
"minimum": 0
|
|
},
|
|
"toursOk": {
|
|
"type": "integer",
|
|
"minimum": 0
|
|
},
|
|
"toursTotal": {
|
|
"type": "integer",
|
|
"minimum": 0
|
|
}
|
|
}
|
|
},
|
|
"MarkMailSentRequest": {
|
|
"type": "object",
|
|
"required": [
|
|
"belegnummern"
|
|
],
|
|
"properties": {
|
|
"belegnummern": {
|
|
"type": "array",
|
|
"items": {
|
|
"type": "string"
|
|
},
|
|
"description": "Belegnummern, deren Liefermail erfolgreich versendet wurde und die als\nversendet markiert werden sollen."
|
|
}
|
|
}
|
|
},
|
|
"MarkMailSentResponse": {
|
|
"type": "object",
|
|
"required": [
|
|
"marked"
|
|
],
|
|
"properties": {
|
|
"marked": {
|
|
"type": "integer",
|
|
"format": "int64",
|
|
"description": "Anzahl frisch markierter (vorher offener) Belege. Bereits markierte\nz\u00e4hlen nicht mit (idempotent).",
|
|
"minimum": 0
|
|
}
|
|
}
|
|
},
|
|
"PaymentMethod": {
|
|
"type": "object",
|
|
"description": "Zahlungs-Stammdatensatz.\n\nBewusst eine Tabelle und kein Enum: neue Anbieter (PayPal, Klarna, \u2026)\nkommen \u00fcber den `POST /payment-methods`-Endpoint hinzu. Domain-Code\nkann trotzdem fachliche Sonderf\u00e4lle \u00fcber den stabilen `code` (z. B.\n`\"invoice\"` braucht Bonit\u00e4tspr\u00fcfung) referenzieren \u2014 die UUID dient\nnur als FK in `deliveries`.\n\n`active = false` ist Soft-Delete: die Methode bleibt referenzierbar\nf\u00fcr historische Lieferungen, taucht aber in der UI-Auswahl nicht\nmehr auf. Echtes L\u00f6schen ist nur m\u00f6glich, wenn keine Lieferung sie\nreferenziert \u2014 Datenbank-Constraint regelt das via\n`ON DELETE RESTRICT`.",
|
|
"required": [
|
|
"id",
|
|
"code",
|
|
"name",
|
|
"active",
|
|
"createdAt"
|
|
],
|
|
"properties": {
|
|
"active": {
|
|
"type": "boolean"
|
|
},
|
|
"code": {
|
|
"type": "string",
|
|
"description": "Stabiler Programm-Identifier \u2014 z. B. `\"cash\"`, `\"ec_card\"`.\nEindeutig pro Eintrag. Wird vom Aufrufer beim Anlegen gesetzt."
|
|
},
|
|
"createdAt": {
|
|
"type": "string",
|
|
"format": "date-time"
|
|
},
|
|
"id": {
|
|
"type": "string",
|
|
"format": "uuid"
|
|
},
|
|
"name": {
|
|
"type": "string",
|
|
"description": "Display-Name in der UI \u2014 frei via PATCH \u00e4nderbar."
|
|
}
|
|
}
|
|
},
|
|
"PaymentMethodResponse": {
|
|
"type": "object",
|
|
"required": [
|
|
"method"
|
|
],
|
|
"properties": {
|
|
"method": {
|
|
"$ref": "#/components/schemas/PaymentMethod"
|
|
}
|
|
}
|
|
},
|
|
"PaymentMethodsList": {
|
|
"type": "object",
|
|
"required": [
|
|
"methods"
|
|
],
|
|
"properties": {
|
|
"methods": {
|
|
"type": "array",
|
|
"items": {
|
|
"$ref": "#/components/schemas/PaymentMethod"
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"ScanEvent": {
|
|
"type": "object",
|
|
"required": [
|
|
"clientScanId",
|
|
"deliveryItemId",
|
|
"action",
|
|
"clientScannedAt"
|
|
],
|
|
"properties": {
|
|
"action": {
|
|
"$ref": "#/components/schemas/AuditAction"
|
|
},
|
|
"actorCarId": {
|
|
"type": [
|
|
"string",
|
|
"null"
|
|
],
|
|
"format": "uuid",
|
|
"description": "Fahrzeug, in dem der Scan gemacht wurde. Muss zum\nangemeldeten Account geh\u00f6ren. `None` ist erlaubt, schw\u00e4cht\naber den Audit-Trail."
|
|
},
|
|
"clientScanId": {
|
|
"type": "string",
|
|
"format": "uuid"
|
|
},
|
|
"clientScannedAt": {
|
|
"type": "string",
|
|
"format": "date-time"
|
|
},
|
|
"deliveryItemId": {
|
|
"type": "string",
|
|
"format": "uuid"
|
|
},
|
|
"manual": {
|
|
"type": "boolean",
|
|
"description": "`true`, wenn der Fahrer die Position **manuell** als geladen best\u00e4tigt\nhat (Fallback ohne Barcode-Scan). Wird nur im Audit (`scan_audit.manual`)\nfestgehalten; an der Mengen-/Status-Logik \u00e4ndert es nichts. Default\n`false` (regul\u00e4rer Barcode-Scan)."
|
|
},
|
|
"quantity": {
|
|
"type": [
|
|
"integer",
|
|
"null"
|
|
],
|
|
"format": "int32",
|
|
"description": "Menge f\u00fcr `Remove` / `Unremove` (Mengen-Gutschrift): wie viele St\u00fcck\nder Belegzeile gutgeschrieben bzw. wieder hergestellt werden.\n`None` = ganze Restmenge (abw\u00e4rtskompatibel zum bisherigen\n\u201eganze Zeile entfernen\"). Bei `Scan`/`Unscan`/`Hold`/`Unhold`\nignoriert. Muss, wenn gesetzt, `> 0` sein."
|
|
},
|
|
"reason": {
|
|
"type": [
|
|
"string",
|
|
"null"
|
|
],
|
|
"description": "Pflicht bei `Hold` und `Remove`. Sonst ignoriert."
|
|
}
|
|
}
|
|
},
|
|
"ScanResult": {
|
|
"type": "object",
|
|
"required": [
|
|
"clientScanId",
|
|
"status"
|
|
],
|
|
"properties": {
|
|
"clientScanId": {
|
|
"type": "string",
|
|
"format": "uuid"
|
|
},
|
|
"deliveryItemId": {
|
|
"type": [
|
|
"string",
|
|
"null"
|
|
],
|
|
"format": "uuid",
|
|
"description": "Aktueller `scan_state` der Position nach der Verarbeitung \u2014\ngenau dann gesetzt, wenn der Server den Stand kennen konnte\n(`Applied` oder `Duplicate`). Erlaubt der App, die UI ohne\nRe-Fetch zu aktualisieren."
|
|
},
|
|
"newScanState": {
|
|
"oneOf": [
|
|
{
|
|
"type": "null"
|
|
},
|
|
{
|
|
"$ref": "#/components/schemas/ScanState"
|
|
}
|
|
]
|
|
},
|
|
"reason": {
|
|
"type": [
|
|
"string",
|
|
"null"
|
|
],
|
|
"description": "Bei `Rejected`: Begr\u00fcndung. Bei `Applied`/`Duplicate`: `None`."
|
|
},
|
|
"status": {
|
|
"$ref": "#/components/schemas/ScanResultStatus"
|
|
}
|
|
}
|
|
},
|
|
"ScanResultStatus": {
|
|
"type": "string",
|
|
"enum": [
|
|
"applied",
|
|
"duplicate",
|
|
"rejected"
|
|
]
|
|
},
|
|
"ScanState": {
|
|
"type": "object",
|
|
"description": "Eingebetteter Scan-Zustand pro [`DeliveryItem`]. Wird durch\n`ScanAuditEntry`-Events fortgeschrieben \u2014 das Audit-Log ist die\nWahrheit \u00fcber das WIE und WANN, dieses Embedded-VO ist die schnelle\nWahrheit \u00fcber das WIEVIEL.",
|
|
"required": [
|
|
"scannedQuantity",
|
|
"creditedQuantity",
|
|
"status",
|
|
"lastUpdatedAt"
|
|
],
|
|
"properties": {
|
|
"creditedQuantity": {
|
|
"type": "integer",
|
|
"format": "int32",
|
|
"description": "Als Gutschrift entfernte Menge (0..=required_quantity). Eigene\nDimension neben `scanned_quantity`: \u201ewie viele St\u00fcck dieser Zeile hat\nder Kunde nicht angenommen\". `status == Removed` entspricht\n`credited_quantity == required_quantity` (ganze Zeile gutgeschrieben)."
|
|
},
|
|
"heldReason": {
|
|
"type": [
|
|
"string",
|
|
"null"
|
|
],
|
|
"description": "Grund bei `status == Held` oder `status == Removed`."
|
|
},
|
|
"lastUpdatedAt": {
|
|
"type": "string",
|
|
"format": "date-time"
|
|
},
|
|
"scannedQuantity": {
|
|
"type": "integer",
|
|
"format": "int32"
|
|
},
|
|
"status": {
|
|
"$ref": "#/components/schemas/ScanStatus"
|
|
}
|
|
}
|
|
},
|
|
"ScanStatus": {
|
|
"type": "string",
|
|
"description": "Status einer einzelnen Scan-Position innerhalb eines Items.",
|
|
"enum": [
|
|
"in_progress",
|
|
"done",
|
|
"held",
|
|
"removed"
|
|
]
|
|
},
|
|
"Service": {
|
|
"type": "object",
|
|
"description": "Service-Stammdatensatz \u2014 admin-konfigurierbar (Muster wie `PaymentMethod`).\n\n`key` ist der stabile Programm-Identifier (eindeutig), `name` der\nAnzeige-Name. `min_value`/`max_value` sind nur f\u00fcr `Numeric` relevant.\n`active = false` ist Soft-Delete (bleibt f\u00fcr historische Lieferungen\nreferenzierbar, f\u00e4llt aus dem Default-Listing).",
|
|
"required": [
|
|
"id",
|
|
"key",
|
|
"name",
|
|
"kind",
|
|
"active",
|
|
"sortOrder"
|
|
],
|
|
"properties": {
|
|
"active": {
|
|
"type": "boolean"
|
|
},
|
|
"id": {
|
|
"type": "string",
|
|
"format": "uuid"
|
|
},
|
|
"key": {
|
|
"type": "string"
|
|
},
|
|
"kind": {
|
|
"$ref": "#/components/schemas/ServiceKind"
|
|
},
|
|
"maxValue": {
|
|
"type": [
|
|
"integer",
|
|
"null"
|
|
],
|
|
"format": "int32"
|
|
},
|
|
"minValue": {
|
|
"type": [
|
|
"integer",
|
|
"null"
|
|
],
|
|
"format": "int32"
|
|
},
|
|
"name": {
|
|
"type": "string"
|
|
},
|
|
"sortOrder": {
|
|
"type": "integer",
|
|
"format": "int32"
|
|
}
|
|
}
|
|
},
|
|
"ServiceKind": {
|
|
"type": "string",
|
|
"description": "Eingabetyp eines Service (fr\u00fcher \u201eLieferoption\"). `Boolean` rendert als\nCheckbox, `Numeric` als Zahlenfeld mit optionalen Grenzen.",
|
|
"enum": [
|
|
"boolean",
|
|
"numeric"
|
|
]
|
|
},
|
|
"ServiceResponse": {
|
|
"type": "object",
|
|
"required": [
|
|
"service"
|
|
],
|
|
"properties": {
|
|
"service": {
|
|
"$ref": "#/components/schemas/Service"
|
|
}
|
|
}
|
|
},
|
|
"ServicesList": {
|
|
"type": "object",
|
|
"required": [
|
|
"services"
|
|
],
|
|
"properties": {
|
|
"services": {
|
|
"type": "array",
|
|
"items": {
|
|
"$ref": "#/components/schemas/Service"
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"SetDeliveryOrderRequest": {
|
|
"type": "object",
|
|
"required": [
|
|
"deliveryIds"
|
|
],
|
|
"properties": {
|
|
"deliveryIds": {
|
|
"type": "array",
|
|
"items": {
|
|
"type": "string",
|
|
"format": "uuid"
|
|
},
|
|
"description": "Reihenfolge: Position im Array (0-basiert) wird zu `sort_order`\n(1-basiert) gemappt."
|
|
}
|
|
}
|
|
},
|
|
"SetDeliveryOrderResponse": {
|
|
"type": "object",
|
|
"required": [
|
|
"tourId",
|
|
"order"
|
|
],
|
|
"properties": {
|
|
"order": {
|
|
"type": "array",
|
|
"items": {
|
|
"$ref": "#/components/schemas/DeliveryOrderEntry"
|
|
}
|
|
},
|
|
"tourId": {
|
|
"type": "string",
|
|
"format": "uuid"
|
|
}
|
|
}
|
|
},
|
|
"SetDeliveryServiceRequest": {
|
|
"type": "object",
|
|
"description": "Setzt den Wert eines Service f\u00fcr eine Lieferung (Upsert). Es muss genau\ndas zum `ServiceKind` passende Feld gesetzt sein (Use Case pr\u00fcft das).",
|
|
"properties": {
|
|
"authorCarId": {
|
|
"type": [
|
|
"string",
|
|
"null"
|
|
],
|
|
"format": "uuid"
|
|
},
|
|
"boolValue": {
|
|
"type": [
|
|
"boolean",
|
|
"null"
|
|
]
|
|
},
|
|
"numericValue": {
|
|
"type": [
|
|
"integer",
|
|
"null"
|
|
],
|
|
"format": "int32"
|
|
}
|
|
}
|
|
},
|
|
"SyncContactChannel": {
|
|
"type": "object",
|
|
"required": [
|
|
"kind",
|
|
"position",
|
|
"value"
|
|
],
|
|
"properties": {
|
|
"kind": {
|
|
"$ref": "#/components/schemas/ContactKind"
|
|
},
|
|
"position": {
|
|
"type": "integer",
|
|
"format": "int32",
|
|
"description": "1-basiert: spiegelt ERP-Spaltenposition (Telefon \u2192 1, Telefon2 \u2192 2, \u2026)."
|
|
},
|
|
"value": {
|
|
"type": "string"
|
|
}
|
|
}
|
|
},
|
|
"SyncContactSource": {
|
|
"type": "object",
|
|
"description": "Eine Adress-Rolle eines Belegs mit Namensblock und allen ausgef\u00fcllten\nTelefon-/Mobil-/E-Mail-/Web-Eintr\u00e4gen.",
|
|
"required": [
|
|
"role"
|
|
],
|
|
"properties": {
|
|
"abteilung": {
|
|
"type": [
|
|
"string",
|
|
"null"
|
|
]
|
|
},
|
|
"anrede": {
|
|
"type": [
|
|
"string",
|
|
"null"
|
|
]
|
|
},
|
|
"channels": {
|
|
"type": "array",
|
|
"items": {
|
|
"$ref": "#/components/schemas/SyncContactChannel"
|
|
}
|
|
},
|
|
"funktion": {
|
|
"type": [
|
|
"string",
|
|
"null"
|
|
]
|
|
},
|
|
"name1": {
|
|
"type": [
|
|
"string",
|
|
"null"
|
|
]
|
|
},
|
|
"name2": {
|
|
"type": [
|
|
"string",
|
|
"null"
|
|
]
|
|
},
|
|
"name3": {
|
|
"type": [
|
|
"string",
|
|
"null"
|
|
]
|
|
},
|
|
"role": {
|
|
"$ref": "#/components/schemas/ContactRole"
|
|
},
|
|
"titel": {
|
|
"type": [
|
|
"string",
|
|
"null"
|
|
]
|
|
}
|
|
}
|
|
},
|
|
"SyncDelivery": {
|
|
"type": "object",
|
|
"required": [
|
|
"belegartId",
|
|
"belegnummer",
|
|
"erpCustomerId",
|
|
"customerName",
|
|
"customerAddress",
|
|
"deliveryAddress",
|
|
"sortOrder",
|
|
"items"
|
|
],
|
|
"properties": {
|
|
"belegartCode": {
|
|
"type": [
|
|
"string",
|
|
"null"
|
|
],
|
|
"description": "Belegart-Kurzcode (z. B. \u201eVL5\"), aus `Belegarten.Belegart` (getrimmt)."
|
|
},
|
|
"belegartId": {
|
|
"type": "integer",
|
|
"format": "int64"
|
|
},
|
|
"belegartName": {
|
|
"type": [
|
|
"string",
|
|
"null"
|
|
],
|
|
"description": "Belegart-Klartext (z. B. \u201eLieferschein EH\"), aus `Belegarten.Bezeichnung`."
|
|
},
|
|
"belegnummer": {
|
|
"type": "string"
|
|
},
|
|
"contactSources": {
|
|
"type": "array",
|
|
"items": {
|
|
"$ref": "#/components/schemas/SyncContactSource"
|
|
},
|
|
"description": "Alle vom ERP an diesem Beleg h\u00e4ngenden Kontakt-Adressen (Beleg-/\nLiefer-/Rechnungsadresse, Ansprechpartner, Kundenstamm). Leere\nQuellen (kein einziger ausgef\u00fcllter Kanal *und* kein Name) l\u00e4sst\nder Sync weg."
|
|
},
|
|
"customerAddress": {
|
|
"$ref": "#/components/schemas/Address"
|
|
},
|
|
"customerName": {
|
|
"type": "string"
|
|
},
|
|
"deliveryAddress": {
|
|
"$ref": "#/components/schemas/Address",
|
|
"description": "Snapshot der Lieferadresse (kann von der Stammadresse abweichen)."
|
|
},
|
|
"desiredTime": {
|
|
"type": [
|
|
"string",
|
|
"null"
|
|
]
|
|
},
|
|
"erpCustomerId": {
|
|
"type": "integer",
|
|
"format": "int64"
|
|
},
|
|
"items": {
|
|
"type": "array",
|
|
"items": {
|
|
"$ref": "#/components/schemas/SyncDeliveryItem"
|
|
}
|
|
},
|
|
"paymentMethodCode": {
|
|
"type": [
|
|
"string",
|
|
"null"
|
|
],
|
|
"description": "F\u00fcr den Restbetrag gew\u00e4hlte Zahlungsart \u2014 Referenz per\n`code` (z. B. `\"cash\"`, `\"invoice\"`). Das ERP kennt seine\nStandard-Codes, der Sync-Code resolvet sie zur UUID. Wenn\n`None`, f\u00e4llt der Backend-Code auf `\"cash\"` zur\u00fcck."
|
|
},
|
|
"prepaidAmount": {
|
|
"type": "number",
|
|
"format": "double",
|
|
"description": "Bei Bestellung schon bezahlter Betrag in EUR. Default `0.0`,\nwenn der Kunde alles bei Lieferung zahlt. Der ERP-Sync liefert\nden Wert mit."
|
|
},
|
|
"sortOrder": {
|
|
"type": "integer",
|
|
"format": "int32",
|
|
"description": "1-basiert, definiert die initiale Reihenfolge in der App."
|
|
},
|
|
"specialAgreements": {
|
|
"type": [
|
|
"string",
|
|
"null"
|
|
]
|
|
}
|
|
}
|
|
},
|
|
"SyncDeliveryItem": {
|
|
"type": "object",
|
|
"required": [
|
|
"belegzeilenNr",
|
|
"articleNumber",
|
|
"articleName",
|
|
"articleScannable",
|
|
"warehouseCode",
|
|
"warehouseName",
|
|
"requiredQuantity"
|
|
],
|
|
"properties": {
|
|
"articleDefaultWarehouseCode": {
|
|
"type": [
|
|
"string",
|
|
"null"
|
|
],
|
|
"description": "Default-Lager-Code f\u00fcr den Artikel (Anlage neuer Artikel)."
|
|
},
|
|
"articleName": {
|
|
"type": "string"
|
|
},
|
|
"articleNumber": {
|
|
"type": "string"
|
|
},
|
|
"articleScannable": {
|
|
"type": "boolean"
|
|
},
|
|
"belegzeilenNr": {
|
|
"type": "integer",
|
|
"format": "int32"
|
|
},
|
|
"komponentenArtikelNr": {
|
|
"type": [
|
|
"string",
|
|
"null"
|
|
],
|
|
"description": "Komponenten-Artikelnummer bei aufgel\u00f6sten St\u00fccklisten, sonst leer.\nTr\u00e4gt die **eigene** Nummer der Komponente (eindeutig je Belegzeile)."
|
|
},
|
|
"parentArtikelNr": {
|
|
"type": [
|
|
"string",
|
|
"null"
|
|
],
|
|
"description": "Artikelnummer des **Oberartikels**, zu dem diese Komponente geh\u00f6rt.\n`None` bei Oberartikeln/regul\u00e4ren Zeilen. Erlaubt der App, Komponenten\nunter ihrem Oberartikel einzur\u00fccken."
|
|
},
|
|
"requiredQuantity": {
|
|
"type": "integer",
|
|
"format": "int32"
|
|
},
|
|
"unitPrice": {
|
|
"type": "number",
|
|
"format": "double",
|
|
"description": "St\u00fcckpreis (brutto, EUR). Default `0.0`. Liefert der ERP-Sync mit;\ndie App rechnet daraus den Warenwert."
|
|
},
|
|
"warehouseCode": {
|
|
"type": "string"
|
|
},
|
|
"warehouseName": {
|
|
"type": "string"
|
|
}
|
|
}
|
|
},
|
|
"SyncTourRequest": {
|
|
"type": "object",
|
|
"required": [
|
|
"driverPersonalnummer",
|
|
"tourDate",
|
|
"deliveries"
|
|
],
|
|
"properties": {
|
|
"deliveries": {
|
|
"type": "array",
|
|
"items": {
|
|
"$ref": "#/components/schemas/SyncDelivery"
|
|
}
|
|
},
|
|
"driverPersonalnummer": {
|
|
"type": "integer",
|
|
"format": "int64"
|
|
},
|
|
"tourDate": {
|
|
"type": "string",
|
|
"format": "date"
|
|
}
|
|
}
|
|
},
|
|
"SyncTourResponse": {
|
|
"type": "object",
|
|
"description": "Antwort-H\u00fclle f\u00fcr `POST /sync/tour`.",
|
|
"required": [
|
|
"tourId"
|
|
],
|
|
"properties": {
|
|
"tourId": {
|
|
"type": "string",
|
|
"format": "uuid"
|
|
}
|
|
}
|
|
},
|
|
"Tour": {
|
|
"type": "object",
|
|
"description": "Tour eines Tages, pro [`crate::domain::Account`]. Aggregat-Wurzel\nf\u00fcr die Lieferungen dieses Tages \u2014 die einzelnen [`crate::domain::Delivery`]\nreferenzieren ihre Tour per FK.\n\nDer Sync vom ERP l\u00e4uft in der Regel einmal am Vortag und f\u00fcllt eine\nneue Tour-Zeile inklusive Delivery- und DeliveryItem-Strukturen.",
|
|
"required": [
|
|
"id",
|
|
"accountId",
|
|
"date",
|
|
"syncedAt"
|
|
],
|
|
"properties": {
|
|
"accountId": {
|
|
"type": "integer",
|
|
"format": "int64"
|
|
},
|
|
"date": {
|
|
"type": "string",
|
|
"format": "date"
|
|
},
|
|
"id": {
|
|
"type": "string",
|
|
"format": "uuid"
|
|
},
|
|
"syncedAt": {
|
|
"type": "string",
|
|
"format": "date-time",
|
|
"description": "Zeitpunkt des letzten ERP-Sync \u2014 f\u00fcr Drift-Erkennung."
|
|
}
|
|
}
|
|
},
|
|
"TourDetails": {
|
|
"type": "object",
|
|
"required": [
|
|
"tour",
|
|
"deliveries",
|
|
"customers",
|
|
"customerContacts",
|
|
"articles",
|
|
"warehouses",
|
|
"notes",
|
|
"credits",
|
|
"services",
|
|
"deliveryServices",
|
|
"contactSources",
|
|
"contactChannels"
|
|
],
|
|
"properties": {
|
|
"articles": {
|
|
"type": "array",
|
|
"items": {
|
|
"$ref": "#/components/schemas/Article"
|
|
}
|
|
},
|
|
"contactChannels": {
|
|
"type": "array",
|
|
"items": {
|
|
"$ref": "#/components/schemas/ContactChannel"
|
|
},
|
|
"description": "Die zu `contact_sources` geh\u00f6renden Einzel-Kan\u00e4le (Telefon, Mobil,\nE-Mail, Web). Join per `source_id`."
|
|
},
|
|
"contactSources": {
|
|
"type": "array",
|
|
"items": {
|
|
"$ref": "#/components/schemas/ContactSource"
|
|
},
|
|
"description": "Alle vom ERP gespiegelten Kontaktquellen aller Lieferungen dieser\nTour. Die App joint clientseitig per `delivery_id` und gruppiert\nnach `role` (Lieferadresse / Rechnungsadresse / Ansprechpartner /\nKundenstamm / Belegadresse)."
|
|
},
|
|
"credits": {
|
|
"type": "array",
|
|
"items": {
|
|
"$ref": "#/components/schemas/DeliveryCredit"
|
|
},
|
|
"description": "Aktuelle Betrags-Gutschriften (j\u00fcngster Stand pro Lieferung), nur f\u00fcr\nLieferungen, deren letztes Ereignis `set` war. Join per `delivery_id`."
|
|
},
|
|
"customerContacts": {
|
|
"type": "array",
|
|
"items": {
|
|
"$ref": "#/components/schemas/CustomerContact"
|
|
}
|
|
},
|
|
"customers": {
|
|
"type": "array",
|
|
"items": {
|
|
"$ref": "#/components/schemas/Customer"
|
|
}
|
|
},
|
|
"deliveries": {
|
|
"type": "array",
|
|
"items": {
|
|
"$ref": "#/components/schemas/DeliveryWithItems"
|
|
}
|
|
},
|
|
"deliveryServices": {
|
|
"type": "array",
|
|
"items": {
|
|
"$ref": "#/components/schemas/DeliveryServiceValue"
|
|
},
|
|
"description": "Pro-Lieferung gesetzte Service-Werte. Join per `delivery_id` +\n`service_id`."
|
|
},
|
|
"notes": {
|
|
"type": "array",
|
|
"items": {
|
|
"$ref": "#/components/schemas/DeliveryNote"
|
|
},
|
|
"description": "Alle Notizen aller Lieferungen dieser Tour, in einer Liste.\nDie App joint clientseitig per `delivery_id`. Reihenfolge:\npro Lieferung aufsteigend nach `created_at`."
|
|
},
|
|
"services": {
|
|
"type": "array",
|
|
"items": {
|
|
"$ref": "#/components/schemas/Service"
|
|
},
|
|
"description": "Aktive Service-Definitionen (Stammdaten) \u2014 die App rendert daraus\nPhase 4. Bewusst hier mitgeliefert, damit die Detailseite alles aus\ndem Tour-Aggregat hat."
|
|
},
|
|
"tour": {
|
|
"$ref": "#/components/schemas/Tour"
|
|
},
|
|
"warehouses": {
|
|
"type": "array",
|
|
"items": {
|
|
"$ref": "#/components/schemas/Warehouse"
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"TourSummary": {
|
|
"type": "object",
|
|
"required": [
|
|
"tourId",
|
|
"tourDate",
|
|
"deliveryCount"
|
|
],
|
|
"properties": {
|
|
"deliveryCount": {
|
|
"type": "integer",
|
|
"format": "int64"
|
|
},
|
|
"tourDate": {
|
|
"type": "string",
|
|
"format": "date"
|
|
},
|
|
"tourId": {
|
|
"type": "string",
|
|
"format": "uuid"
|
|
}
|
|
}
|
|
},
|
|
"TourSummaryList": {
|
|
"type": "object",
|
|
"description": "Antwort-H\u00fclle f\u00fcr `GET /me/tours/today`. Eigenes Struct, weil\nutoipa f\u00fcr `Vec<T>` als Top-Level-Response keinen sauberen\nSchemanamen vergibt \u2014 und ein Wrapper macht die Erweiterbarkeit\n(z. B. Paginierung in Zukunft) zur Nicht-Breaking-Change.",
|
|
"required": [
|
|
"tours"
|
|
],
|
|
"properties": {
|
|
"tours": {
|
|
"type": "array",
|
|
"items": {
|
|
"$ref": "#/components/schemas/TourSummary"
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"UpdateCarRequest": {
|
|
"type": "object",
|
|
"properties": {
|
|
"active": {
|
|
"type": [
|
|
"boolean",
|
|
"null"
|
|
],
|
|
"description": "Wenn gesetzt: aktiv/inaktiv. Inaktive Fahrzeuge tauchen in\n`GET /me/cars?activeOnly=true` (default) nicht auf."
|
|
},
|
|
"plate": {
|
|
"type": [
|
|
"string",
|
|
"null"
|
|
],
|
|
"description": "Wenn gesetzt: neues Kennzeichen."
|
|
}
|
|
}
|
|
},
|
|
"UpdateDeliveryNoteRequest": {
|
|
"type": "object",
|
|
"description": "Request f\u00fcr `PATCH /deliveries/{id}/notes/{note_id}`. Wie beim Create\nmuss mindestens eines von `text` / `image_attachment` inhaltlich gef\u00fcllt\nsein \u2014 gepr\u00fcft im Use Case.",
|
|
"properties": {
|
|
"imageAttachment": {
|
|
"type": [
|
|
"string",
|
|
"null"
|
|
],
|
|
"description": "Object-Storage-Key oder URL eines vorab hochgeladenen Bildes."
|
|
},
|
|
"text": {
|
|
"type": [
|
|
"string",
|
|
"null"
|
|
]
|
|
}
|
|
}
|
|
},
|
|
"UpdatePaymentMethodRequest": {
|
|
"type": "object",
|
|
"properties": {
|
|
"active": {
|
|
"type": [
|
|
"boolean",
|
|
"null"
|
|
],
|
|
"description": "Wenn gesetzt: aktiv/inaktiv. Inaktive Methoden bleiben f\u00fcr\nhistorische Lieferungen referenzierbar, tauchen aber im\nDefault-Listing nicht auf."
|
|
},
|
|
"name": {
|
|
"type": [
|
|
"string",
|
|
"null"
|
|
],
|
|
"description": "Wenn gesetzt: neuer Anzeige-Name."
|
|
}
|
|
}
|
|
},
|
|
"UpdateServiceRequest": {
|
|
"type": "object",
|
|
"description": "Teil-Update eines Service. `kind` ist bewusst **nicht** \u00e4nderbar \u2014 ein\nWechsel boolean\u2194numeric w\u00fcrde bestehende Pro-Lieferung-Werte ung\u00fcltig\nmachen (dann lieber deaktivieren + neu anlegen).",
|
|
"properties": {
|
|
"active": {
|
|
"type": [
|
|
"boolean",
|
|
"null"
|
|
]
|
|
},
|
|
"maxValue": {
|
|
"type": [
|
|
"integer",
|
|
"null"
|
|
],
|
|
"format": "int32"
|
|
},
|
|
"minValue": {
|
|
"type": [
|
|
"integer",
|
|
"null"
|
|
],
|
|
"format": "int32"
|
|
},
|
|
"name": {
|
|
"type": [
|
|
"string",
|
|
"null"
|
|
]
|
|
},
|
|
"sortOrder": {
|
|
"type": [
|
|
"integer",
|
|
"null"
|
|
],
|
|
"format": "int32"
|
|
}
|
|
}
|
|
},
|
|
"Warehouse": {
|
|
"type": "object",
|
|
"description": "Lager. ERP-Mirror; `code` ist die ERP-Lager-Nr (z. B. `\"0\"` f\u00fcr das\nStandardlager). Das `is_standard`-Flag ist der schnelle Filter f\u00fcr\ndie Beladen-Logik (\u201enur Standardlager-Artikel z\u00e4hlen f\u00fcr Fertig\").",
|
|
"required": [
|
|
"id",
|
|
"code",
|
|
"name",
|
|
"isStandard"
|
|
],
|
|
"properties": {
|
|
"code": {
|
|
"type": "string"
|
|
},
|
|
"id": {
|
|
"type": "string",
|
|
"format": "uuid"
|
|
},
|
|
"isStandard": {
|
|
"type": "boolean"
|
|
},
|
|
"name": {
|
|
"type": "string"
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"securitySchemes": {
|
|
"admin_api_key": {
|
|
"type": "apiKey",
|
|
"in": "header",
|
|
"name": "X-Admin-Api-Key"
|
|
},
|
|
"bearer_auth": {
|
|
"type": "http",
|
|
"scheme": "bearer",
|
|
"bearerFormat": "JWT"
|
|
}
|
|
}
|
|
},
|
|
"security": [
|
|
{
|
|
"bearer_auth": []
|
|
}
|
|
],
|
|
"tags": [
|
|
{
|
|
"name": "health",
|
|
"description": "Health- und Status-Endpoints"
|
|
},
|
|
{
|
|
"name": "accounts",
|
|
"description": "Account-Stammdaten"
|
|
},
|
|
{
|
|
"name": "tours",
|
|
"description": "Touren der Fahrer"
|
|
},
|
|
{
|
|
"name": "sync",
|
|
"description": "ERP-Sync-Endpunkte"
|
|
},
|
|
{
|
|
"name": "scans",
|
|
"description": "Scan-Events (Beladung & Auslieferung)"
|
|
},
|
|
{
|
|
"name": "deliveries",
|
|
"description": "Delivery-Lifecycle (hold / resume / cancel / complete)"
|
|
},
|
|
{
|
|
"name": "cars",
|
|
"description": "Fahrzeug-Stammdaten pro Fahrer"
|
|
},
|
|
{
|
|
"name": "payment-methods",
|
|
"description": "Zahlungsmethoden \u2014 globale Stammdaten"
|
|
},
|
|
{
|
|
"name": "services",
|
|
"description": "Services / Lieferoptionen \u2014 globale Stammdaten"
|
|
},
|
|
{
|
|
"name": "admin",
|
|
"description": "Betriebs-/Admin-Endpunkte (Maschinen-Zugang via X-Admin-Api-Key, kein JWT)"
|
|
}
|
|
]
|
|
}
|