diff --git a/Writerside/topics/Bookings_OpenAPIv1/API_Reference.topic b/Writerside/topics/Bookings_OpenAPIv1/API_Reference.topic new file mode 100644 index 00000000..7919fadf --- /dev/null +++ b/Writerside/topics/Bookings_OpenAPIv1/API_Reference.topic @@ -0,0 +1,9 @@ + + + + +

Bookings API OpenAPI 1.0.0 (separate from XLVask)

+
diff --git a/Writerside/topics/Bookings_OpenAPIv1/Admin_Bookings_CompleteWithoutWashCertificate_POST.topic b/Writerside/topics/Bookings_OpenAPIv1/Admin_Bookings_CompleteWithoutWashCertificate_POST.topic new file mode 100644 index 00000000..e08fa11e --- /dev/null +++ b/Writerside/topics/Bookings_OpenAPIv1/Admin_Bookings_CompleteWithoutWashCertificate_POST.topic @@ -0,0 +1,8 @@ + + + + + + diff --git a/Writerside/topics/Bookings_OpenAPIv1/Admin_Bookings_Delete_POST.topic b/Writerside/topics/Bookings_OpenAPIv1/Admin_Bookings_Delete_POST.topic new file mode 100644 index 00000000..0d9a6544 --- /dev/null +++ b/Writerside/topics/Bookings_OpenAPIv1/Admin_Bookings_Delete_POST.topic @@ -0,0 +1,8 @@ + + + + + + diff --git a/Writerside/topics/Bookings_OpenAPIv1/Admin_Bookings_Department_Count_GET.topic b/Writerside/topics/Bookings_OpenAPIv1/Admin_Bookings_Department_Count_GET.topic new file mode 100644 index 00000000..1ab0258c --- /dev/null +++ b/Writerside/topics/Bookings_OpenAPIv1/Admin_Bookings_Department_Count_GET.topic @@ -0,0 +1,8 @@ + + + + + + diff --git a/Writerside/topics/Bookings_OpenAPIv1/Admin_Bookings_Sync_POST.topic b/Writerside/topics/Bookings_OpenAPIv1/Admin_Bookings_Sync_POST.topic new file mode 100644 index 00000000..0dfb8798 --- /dev/null +++ b/Writerside/topics/Bookings_OpenAPIv1/Admin_Bookings_Sync_POST.topic @@ -0,0 +1,8 @@ + + + + + + diff --git a/Writerside/topics/Bookings_OpenAPIv1/Bookings_Download_PDF_GET.topic b/Writerside/topics/Bookings_OpenAPIv1/Bookings_Download_PDF_GET.topic new file mode 100644 index 00000000..125243cf --- /dev/null +++ b/Writerside/topics/Bookings_OpenAPIv1/Bookings_Download_PDF_GET.topic @@ -0,0 +1,8 @@ + + + + + + diff --git a/Writerside/topics/Bookings_OpenAPIv1/Bookings_GET.topic b/Writerside/topics/Bookings_OpenAPIv1/Bookings_GET.topic new file mode 100644 index 00000000..efa2d4b4 --- /dev/null +++ b/Writerside/topics/Bookings_OpenAPIv1/Bookings_GET.topic @@ -0,0 +1,8 @@ + + + + + + diff --git a/Writerside/topics/Bookings_OpenAPIv1/Bookings_PUT.topic b/Writerside/topics/Bookings_OpenAPIv1/Bookings_PUT.topic new file mode 100644 index 00000000..509928a7 --- /dev/null +++ b/Writerside/topics/Bookings_OpenAPIv1/Bookings_PUT.topic @@ -0,0 +1,8 @@ + + + + + + diff --git a/Writerside/topics/Bookings_OpenAPIv1/Department_Timebookings_Entries_Public_GET.topic b/Writerside/topics/Bookings_OpenAPIv1/Department_Timebookings_Entries_Public_GET.topic new file mode 100644 index 00000000..310961e4 --- /dev/null +++ b/Writerside/topics/Bookings_OpenAPIv1/Department_Timebookings_Entries_Public_GET.topic @@ -0,0 +1,8 @@ + + + + + + diff --git a/Writerside/topics/Bookings_OpenAPIv1/Department_Timebookings_Entries_Public_POST.topic b/Writerside/topics/Bookings_OpenAPIv1/Department_Timebookings_Entries_Public_POST.topic new file mode 100644 index 00000000..8f67eecf --- /dev/null +++ b/Writerside/topics/Bookings_OpenAPIv1/Department_Timebookings_Entries_Public_POST.topic @@ -0,0 +1,8 @@ + + + + + + diff --git a/Writerside/topics/Bookings_OpenAPIv1/Department_Timebookings_OpeningHours_Public_GET.topic b/Writerside/topics/Bookings_OpenAPIv1/Department_Timebookings_OpeningHours_Public_GET.topic new file mode 100644 index 00000000..1b311aff --- /dev/null +++ b/Writerside/topics/Bookings_OpenAPIv1/Department_Timebookings_OpeningHours_Public_GET.topic @@ -0,0 +1,8 @@ + + + + + + diff --git a/Writerside/topics/Bookings_OpenAPIv1/Department_Timebookings_Types_Public_GET.topic b/Writerside/topics/Bookings_OpenAPIv1/Department_Timebookings_Types_Public_GET.topic new file mode 100644 index 00000000..117f9f00 --- /dev/null +++ b/Writerside/topics/Bookings_OpenAPIv1/Department_Timebookings_Types_Public_GET.topic @@ -0,0 +1,8 @@ + + + + + + diff --git a/Writerside/topics/Bookings_OpenAPIv1/Superuser_Bookings_Sync_All_POST.topic b/Writerside/topics/Bookings_OpenAPIv1/Superuser_Bookings_Sync_All_POST.topic new file mode 100644 index 00000000..48335301 --- /dev/null +++ b/Writerside/topics/Bookings_OpenAPIv1/Superuser_Bookings_Sync_All_POST.topic @@ -0,0 +1,8 @@ + + + + + + diff --git a/Writerside/topics/Bookings_OpenAPIv1/User_Bookings_Delete_POST.topic b/Writerside/topics/Bookings_OpenAPIv1/User_Bookings_Delete_POST.topic new file mode 100644 index 00000000..372d4f69 --- /dev/null +++ b/Writerside/topics/Bookings_OpenAPIv1/User_Bookings_Delete_POST.topic @@ -0,0 +1,8 @@ + + + + + + diff --git a/Writerside/topics/Bookings_OpenAPIv1/User_Bookings_GET.topic b/Writerside/topics/Bookings_OpenAPIv1/User_Bookings_GET.topic new file mode 100644 index 00000000..07748d78 --- /dev/null +++ b/Writerside/topics/Bookings_OpenAPIv1/User_Bookings_GET.topic @@ -0,0 +1,8 @@ + + + + + + diff --git a/Writerside/topics/Bookings_OpenAPIv1/User_Bookings_Washcertificate_Download_POST.topic b/Writerside/topics/Bookings_OpenAPIv1/User_Bookings_Washcertificate_Download_POST.topic new file mode 100644 index 00000000..ab568ba4 --- /dev/null +++ b/Writerside/topics/Bookings_OpenAPIv1/User_Bookings_Washcertificate_Download_POST.topic @@ -0,0 +1,8 @@ + + + + + + diff --git a/services/nginx/app/modules/bookings/Bookings_OpenAPIv1.json b/services/nginx/app/modules/bookings/Bookings_OpenAPIv1.json new file mode 100644 index 00000000..97bdc529 --- /dev/null +++ b/services/nginx/app/modules/bookings/Bookings_OpenAPIv1.json @@ -0,0 +1,496 @@ +{ + "openapi": "3.0.3", + "info": { + "title": "Bookings API", + "version": "1.0.0", + "description": "OpenAPI specification for booking-related endpoints. This spec is standalone and not related to XLVask." + }, + "servers": [ + { "url": "https://api.truckwash.dk:4433" } + ], + "tags": [ + { "name": "Bookings" }, + { "name": "User Bookings" }, + { "name": "Admin Bookings" }, + { "name": "Public Time Bookings" } + ], + "paths": { + "/bookings": { + "get": { + "tags": ["Bookings"], + "summary": "List bookings or fetch a single booking by id", + "description": "Requires permission `list_bookings`. If query parameter `id` is set, returns a single booking; otherwise returns a paginated list. If the caller has permission `issue_wash_certificates`, the meta will include `wash_certificate_token`.", + "parameters": [ + { "$ref": "#/components/parameters/id" }, + { "$ref": "#/components/parameters/page" }, + { "$ref": "#/components/parameters/limit" }, + { "$ref": "#/components/parameters/search" }, + { "$ref": "#/components/parameters/filters" }, + { "$ref": "#/components/parameters/order" } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/EnvelopeBookingsOrBooking" } + } + } + }, + "400": { "description": "Invalid request or session" }, + "403": { "description": "Forbidden" }, + "404": { "description": "Booking not found" } + }, + "security": [{ "BearerAuth": [] }] + }, + "put": { + "tags": ["Bookings"], + "summary": "Update a booking (reference number)", + "description": "Requires permission `list_own_bookings`. Updates only the `reference_number` for a booking the user has access to.", + "parameters": [ + { "$ref": "#/components/parameters/id" }, + { + "name": "reference_number", + "in": "query", + "required": false, + "schema": { "type": "string", "maxLength": 255 } + } + ], + "responses": { + "200": { + "description": "Updated booking", + "content": { "application/json": { "schema": { "$ref": "#/components/schemas/EnvelopeBooking" } } } + }, + "400": { "description": "Invalid request or session" }, + "403": { "description": "Forbidden" }, + "404": { "description": "Booking not found" } + }, + "security": [{ "BearerAuth": [] }] + } + }, + + "/user/bookings": { + "get": { + "tags": ["User Bookings"], + "summary": "List bookings for the authenticated user", + "description": "Requires permission `list_own_bookings`. Returns a paginated list of the user's own bookings.", + "parameters": [ + { "$ref": "#/components/parameters/page" }, + { "$ref": "#/components/parameters/limit" }, + { "$ref": "#/components/parameters/search" }, + { "$ref": "#/components/parameters/filters" }, + { "$ref": "#/components/parameters/order" } + ], + "responses": { + "200": { "description": "Success", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/EnvelopeBookingsList" } } } }, + "400": { "description": "Invalid request or session" } + }, + "security": [{ "BearerAuth": [] }] + } + }, + + "/admin/bookings/sync": { + "post": { + "tags": ["Admin Bookings"], + "summary": "Sync or upsert a booking from external system", + "description": "Requires permission `sync_bookings` unless `auth_key` equals the configured value. Accepts all booking fields as query/body form parameters and upserts the booking.", + "requestBody": { + "required": false, + "content": { + "application/x-www-form-urlencoded": { "schema": { "$ref": "#/components/schemas/SyncBookingRequest" } }, + "application/json": { "schema": { "$ref": "#/components/schemas/SyncBookingRequest" } } + } + }, + "responses": { + "200": { "description": "Success", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/EnvelopeMessage" } } } } + } + } + }, + + "/admin/bookings/department/count": { + "get": { + "tags": ["Admin Bookings"], + "summary": "Get unfulfilled bookings count for a department (today)", + "description": "Requires permission `list_department_bookings_count`. Returns an integer count. Response meta may include `cached: true` when the value was served from cache.", + "parameters": [ + { "$ref": "#/components/parameters/department_id" } + ], + "responses": { + "200": { + "description": "Count", + "content": { "application/json": { "schema": { "$ref": "#/components/schemas/EnvelopeInteger" } } } + }, + "400": { "description": "Invalid request or session" }, + "404": { "description": "Department not found" } + }, + "security": [{ "BearerAuth": [] }] + } + }, + + "/user/bookings/washcertificate/download": { + "post": { + "tags": ["User Bookings"], + "summary": "Get download link for a booking's wash certificate", + "description": "Requires permission `download_own_wash_certificate`. Returns a presigned download link if certificate exists and user has access.", + "parameters": [ { "$ref": "#/components/parameters/id" } ], + "responses": { + "200": { "description": "Link", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/EnvelopeDownloadLink" } } } }, + "400": { "description": "Not allowed or invalid status" }, + "404": { "description": "Booking or certificate not found" } + }, + "security": [{ "BearerAuth": [] }] + } + }, + + "/bookings/download_pdf": { + "get": { + "tags": ["User Bookings"], + "summary": "Get download link for a booking's wash certificate PDF", + "description": "Requires permission `download_own_wash_certificate`. Checks both legacy and current storage buckets.", + "parameters": [ { "$ref": "#/components/parameters/id" } ], + "responses": { + "200": { "description": "Link", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/EnvelopeDownloadLink" } } } }, + "400": { "description": "Not allowed or invalid session" }, + "404": { "description": "Booking or certificate not found" }, + "500": { "description": "Storage inconsistency" } + }, + "security": [{ "BearerAuth": [] }] + } + }, + + "/admin/bookings/delete": { + "post": { + "tags": ["Admin Bookings"], + "summary": "Delete a booking", + "description": "Requires permission `delete_booking`. Deletes the booking if the user has access.", + "parameters": [ { "$ref": "#/components/parameters/id" } ], + "responses": { + "200": { "description": "Deleted", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/EnvelopeMessage" } } } }, + "400": { "description": "Not allowed or invalid session" } + }, + "security": [{ "BearerAuth": [] }] + } + }, + + "/superuser/bookings/sync/all": { + "post": { + "tags": ["Admin Bookings"], + "summary": "Sync all bookings from external system", + "description": "Requires permission `sync_all_bookings`.", + "responses": { + "200": { "description": "Triggered", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/EnvelopeMessage" } } } }, + "400": { "description": "Invalid session" } + }, + "security": [{ "BearerAuth": [] }] + } + }, + + "/admin/bookings/completeWashWithoutWashCertificate": { + "post": { + "tags": ["Admin Bookings"], + "summary": "Complete wash without generating a certificate", + "description": "Requires permission `complete_wash_without_wash_certificate`.", + "parameters": [ { "$ref": "#/components/parameters/id" } ], + "responses": { + "200": { "description": "Completed", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/EnvelopeMessage" } } } }, + "400": { "description": "Not allowed or invalid session" } + }, + "security": [{ "BearerAuth": [] }] + } + }, + + "/user/bookings/delete": { + "post": { + "tags": ["User Bookings"], + "summary": "Delete own booking", + "description": "Requires permission `delete_own_booking`. Deletes the user's own booking if accessible.", + "parameters": [ { "$ref": "#/components/parameters/id" } ], + "responses": { + "200": { "description": "Deleted", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/EnvelopeMessage" } } } }, + "400": { "description": "Not allowed or invalid session" } + }, + "security": [{ "BearerAuth": [] }] + } + }, + + "/department/timebookings/opening-hours/public": { + "get": { + "tags": ["Public Time Bookings"], + "summary": "Get public opening hours for a department", + "parameters": [ { "$ref": "#/components/parameters/id" } ], + "responses": { + "200": { "description": "Opening hours", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/EnvelopeOpeningHours" } } } }, + "400": { "description": "Missing or invalid id" }, + "404": { "description": "Department or opening hours not found, or module disabled" } + } + } + }, + + "/department/timebookings/types/public": { + "get": { + "tags": ["Public Time Bookings"], + "summary": "List public booking types for a department", + "parameters": [ { "$ref": "#/components/parameters/id" } ], + "responses": { + "200": { "description": "Types", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/EnvelopeBookingTypesList" } } } }, + "400": { "description": "Missing or invalid id" }, + "404": { "description": "Department or types not found, or module disabled" } + } + } + }, + + "/department/timebookings/entries/public": { + "get": { + "tags": ["Public Time Bookings"], + "summary": "List public booking entries for a department", + "parameters": [ { "$ref": "#/components/parameters/id" }, { "$ref": "#/components/parameters/page" }, { "$ref": "#/components/parameters/limit" }, { "$ref": "#/components/parameters/search" }, { "$ref": "#/components/parameters/filters" }, { "$ref": "#/components/parameters/order" } ], + "responses": { + "200": { "description": "Entries", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/EnvelopeTimeBookingEntriesList" } } } }, + "400": { "description": "Missing or invalid id" }, + "404": { "description": "Department not found or module disabled" } + } + }, + "post": { + "tags": ["Public Time Bookings"], + "summary": "Create a public booking entry", + "description": "Creates a time booking entry for a department and type at a specific start time. The duration is derived from the type.", + "requestBody": { + "required": true, + "content": { + "application/x-www-form-urlencoded": { "schema": { "$ref": "#/components/schemas/PublicEntryCreate" } }, + "application/json": { "schema": { "$ref": "#/components/schemas/PublicEntryCreate" } } + } + }, + "responses": { + "200": { "description": "Created", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/EnvelopeTimeBookingEntry" } } } }, + "400": { "description": "Validation error" }, + "404": { "description": "Department/type not found or module disabled" } + } + } + } + }, + "components": { + "securitySchemes": { + "BearerAuth": { + "type": "http", + "scheme": "bearer", + "bearerFormat": "JWT" + } + }, + "parameters": { + "id": { "name": "id", "in": "query", "required": false, "schema": { "type": "integer", "format": "int32" } }, + "department_id": { "name": "department_id", "in": "query", "required": true, "schema": { "type": "integer", "format": "int32" } }, + "page": { "name": "page", "in": "query", "required": false, "schema": { "type": "integer", "minimum": 1 } }, + "limit": { "name": "limit", "in": "query", "required": false, "schema": { "type": "integer", "minimum": 1 } }, + "search": { "name": "search", "in": "query", "required": false, "schema": { "type": "string" } }, + "filters": { "name": "filters", "in": "query", "required": false, "schema": { "type": "string" }, "description": "Comma-separated filters: key:value pairs (supports multiple keys)." }, + "order": { "name": "order", "in": "query", "required": false, "schema": { "type": "string" }, "description": "Sort order, e.g. `id:ASC` or `created_at:DESC`." } + }, + "schemas": { + "Envelope": { + "type": "object", + "properties": { + "success": { "type": "boolean" }, + "data": {}, + "meta": { "type": "object", "additionalProperties": true }, + "includes": { "type": "object", "additionalProperties": true } + }, + "required": ["success", "data", "meta", "includes"] + }, + "EnvelopeMessage": { + "allOf": [ + { "$ref": "#/components/schemas/Envelope" }, + { "type": "object", "properties": { "data": { "type": "object", "properties": { "message": { "type": "string" } }, "required": ["message"] } } } + ] + }, + "EnvelopeInteger": { + "allOf": [ + { "$ref": "#/components/schemas/Envelope" }, + { "type": "object", "properties": { "data": { "type": "integer" } } } + ] + }, + "EnvelopeDownloadLink": { + "allOf": [ + { "$ref": "#/components/schemas/Envelope" }, + { "type": "object", "properties": { "data": { "$ref": "#/components/schemas/DownloadLink" } } } + ] + }, + "EnvelopeBookingsList": { + "allOf": [ + { "$ref": "#/components/schemas/Envelope" }, + { "type": "object", "properties": { "data": { "type": "array", "items": { "$ref": "#/components/schemas/Booking" } } } } + ] + }, + "EnvelopeBooking": { + "allOf": [ + { "$ref": "#/components/schemas/Envelope" }, + { "type": "object", "properties": { "data": { "$ref": "#/components/schemas/Booking" } } } + ] + }, + "EnvelopeBookingsOrBooking": { + "allOf": [ + { "$ref": "#/components/schemas/Envelope" }, + { "type": "object", "properties": { "data": { "oneOf": [ { "$ref": "#/components/schemas/Booking" }, { "type": "array", "items": { "$ref": "#/components/schemas/Booking" } } ] } } } + ] + }, + "EnvelopeOpeningHours": { + "allOf": [ + { "$ref": "#/components/schemas/Envelope" }, + { "type": "object", "properties": { "data": { "$ref": "#/components/schemas/OpeningHours" } } } + ] + }, + "EnvelopeBookingTypesList": { + "allOf": [ + { "$ref": "#/components/schemas/Envelope" }, + { "type": "object", "properties": { "data": { "type": "array", "items": { "$ref": "#/components/schemas/BookingType" } } } } + ] + }, + "EnvelopeTimeBookingEntriesList": { + "allOf": [ + { "$ref": "#/components/schemas/Envelope" }, + { "type": "object", "properties": { "data": { "type": "array", "items": { "$ref": "#/components/schemas/TimeBookingPublicEntry" } } } } + ] + }, + "EnvelopeTimeBookingEntry": { + "allOf": [ + { "$ref": "#/components/schemas/Envelope" }, + { "type": "object", "properties": { "data": { "$ref": "#/components/schemas/TimeBookingEntry" } } } + ] + }, + + "Booking": { + "type": "object", + "properties": { + "id": { "type": "integer" }, + "customer_number": { "type": "integer" }, + "wash_type": { "type": "string" }, + "contact_email": { "type": "string" }, + "reference_number": { "type": "string", "nullable": true }, + "regNrTraekker": { "type": "string", "nullable": true }, + "regNrTrailer": { "type": "string", "nullable": true }, + "washCertificateEmail": { "type": "string", "nullable": true }, + "date": { "type": "string" }, + "department": { "type": "integer" }, + "pickup_bool": { "type": "boolean" }, + "notes": { "type": "string", "nullable": true }, + "washCertificateStatus": { "type": "string", "enum": ["pending", "completed", "cancelled"], "nullable": true }, + "washCertificateUrl": { "type": "string", "nullable": true }, + "wash_certificate_pdf": { "type": "string", "nullable": true }, + "status": { "type": "string", "enum": ["pending", "completed", "cancelled"] }, + "data": { "type": "object", "nullable": true, "additionalProperties": true }, + "parsed_services": { + "type": "object", + "nullable": true, + "properties": { + "string": { "type": "string" }, + "array": { "type": "array", "items": { "type": "integer" } } + } + } + }, + "required": ["id", "customer_number", "wash_type", "date", "department", "pickup_bool", "status"] + }, + + "DownloadLink": { + "type": "object", + "properties": { "link": { "type": "string", "format": "uri" } }, + "required": ["link"] + }, + + "OpeningHours": { + "type": "object", + "properties": { + "monday_start": { "type": ["string", "null"] }, + "monday_end": { "type": ["string", "null"] }, + "tuesday_start": { "type": ["string", "null"] }, + "tuesday_end": { "type": ["string", "null"] }, + "wednesday_start": { "type": ["string", "null"] }, + "wednesday_end": { "type": ["string", "null"] }, + "thursday_start": { "type": ["string", "null"] }, + "thursday_end": { "type": ["string", "null"] }, + "friday_start": { "type": ["string", "null"] }, + "friday_end": { "type": ["string", "null"] }, + "saturday_start": { "type": ["string", "null"] }, + "saturday_end": { "type": ["string", "null"] }, + "sunday_start": { "type": ["string", "null"] }, + "sunday_end": { "type": ["string", "null"] } + } + }, + + "BookingType": { + "type": "object", + "properties": { + "id": { "type": "integer" }, + "department": { "type": "integer" }, + "product": { "type": "integer" }, + "name": { "type": "string" }, + "description": { "type": "string" }, + "duration": { "type": "integer", "description": "Duration in minutes" } + }, + "required": ["id", "department", "product", "name", "description", "duration"] + }, + + "TimeBookingPublicEntry": { + "type": "object", + "properties": { + "start": { "type": "string" }, + "duration": { "type": "integer" } + }, + "required": ["start", "duration"] + }, + + "TimeBookingEntry": { + "type": "object", + "properties": { + "id": { "type": "integer" }, + "department": { "type": "integer" }, + "type": { "type": "integer" }, + "start": { "type": "string" }, + "end": { "type": "string" }, + "note": { "type": ["string", "null"] }, + "reg": { "type": ["string", "null"] }, + "phone": { "type": "integer" }, + "phone_country_code": { "type": "integer" } + }, + "required": ["id", "department", "type", "start", "end", "phone", "phone_country_code"] + }, + + "PublicEntryCreate": { + "type": "object", + "properties": { + "department": { "type": "integer" }, + "type": { "type": "integer" }, + "start": { "type": "string", "example": "2025-05-01 10:00:00" }, + "phone_country_code": { "type": "integer", "nullable": true }, + "phone": { "type": "integer", "nullable": true }, + "note": { "type": "string", "nullable": true }, + "reg": { "type": "string", "nullable": true }, + "addons": { "type": "array", "nullable": true, "items": { "type": "integer" } } + }, + "required": ["department", "type", "start"] + }, + + "SyncBookingRequest": { + "type": "object", + "properties": { + "auth_key": { "type": "string", "description": "Special key which allows syncing without the `sync_bookings` permission when valid" }, + "id": { "type": "integer" }, + "customer_number": { "type": "integer" }, + "wash_type": { "type": "string" }, + "contact_email": { "type": "string" }, + "reference_number": { "type": "string" }, + "regNrTraekker": { "type": "string" }, + "regNrTrailer": { "type": "string" }, + "washCertificateEmail": { "type": "string" }, + "date": { "type": "string" }, + "department": { "type": "integer" }, + "pickup_bool": { "type": "integer" }, + "notes": { "type": "string" }, + "washCertificateStatus": { "type": "string" }, + "washCertificateUrl": { "type": "string" }, + "status": { "type": "string" } + }, + "required": ["id", "customer_number", "wash_type", "contact_email", "reference_number", "regNrTraekker", "regNrTrailer", "washCertificateEmail", "date", "department", "pickup_bool", "notes", "washCertificateStatus", "washCertificateUrl", "status"] + } + } + } +}