backend phase 8

This commit is contained in:
hamid
2026-07-06 02:48:56 +03:30
parent 99ebf5d881
commit 2cfc082a04
55 changed files with 7480 additions and 7 deletions
+148
View File
@@ -0,0 +1,148 @@
# Contract — Booking requests (backend phase b8)
> The **pre-payment intent** layer of the engagement lifecycle: a customer requests a nurse; the nurse
> accepts/rejects before a config-driven response deadline; on accept a config-driven **30-minute payment
> window** opens. **No money and no `bookings` row exist here** — accept only opens the window (conversion is
> b9/b10). Assumes [`../conventions/api-conventions.md`](../conventions/api-conventions.md) +
> [`../conventions/money-and-types.md`](../conventions/money-and-types.md). Machine schema:
> [`../openapi/swagger.v1.json`](../openapi/README.md) (refreshed for b8).
**Status:** live as of backend-phase-b8 · **Frontend consumer:** frontend-phase-f7-b8
> **Routing note.** Routes are **action-style** (`[controller]/[action]`, snake_cased). All responses use the
> standard `{ succeeded, statusCode, data }` envelope; `data` shapes are below. Request bodies are camelCase.
## Key semantics (read first)
- **Two-table split, no money on a request.** A `booking_requests` row never carries a price/total and never
creates a booking. Accept moves it to `accepted_awaiting_payment` and opens the payment window; b9 later
consumes that and creates the `bookings` row (+ money), setting the request to `converted`.
- **Two-stage clinical disclosure (stage 1).** The nurse sees **only** the unencrypted, limited
`customer_notes` — never a full clinical/care instruction (those are b9's encrypted, post-confirmation
`booking_care_instructions`). The **nurse view of a request masks the full address** (address line / postal
code / recipient) and shows only a coarse city/district location; the **customer/admin view** returns the
full address.
- **Tenancy invariant (enforced at create).** The `patient` and `customer_address` must belong to the caller's
customer; the `variant` must belong to the requested `nurse_id`. A mismatch is a clean `404` — never a leak.
- **Same-gender match is first-class.** `required_caregiver_gender` (`male`/`female`/`any`) is matched against
the nurse's gender at request time. `male`/`female` must equal the nurse's gender; `any` matches either. A
mismatch is a `400`. It is required on create and never silently defaulted.
- **Deadlines are frozen from config.** `nurse_response_deadline_at` = create-time `now +
nurse_response_deadline_hours` (default 24h); `payment_deadline_at` = accept-time `now +
booking_payment_deadline_minutes` (**30**). Both are absolute UTC timestamps stored on the row — a later
config change never moves an existing request's deadlines.
- **Forward-only status machine.** Illegal transitions return `409`. Terminal states
(`converted`/`rejected_by_nurse`/`expired_no_response`/`payment_deadline_expired`/`cancelled_by_customer`)
have no outgoing edges. An accept after the response deadline, or on a non-pending request, is `409`.
- **Auto-expiry.** A background sweep (also an admin manual trigger) moves `pending_nurse_response →
expired_no_response` past the response deadline and `accepted_awaiting_payment → payment_deadline_expired`
past the payment window, and notifies the customer.
- **Timestamps** are UTC ISO-8601. **Ids** are `BIGINT`. **There is no money field anywhere in this domain.**
## Enums used
- `booking_request_status`: `pending_nurse_response` | `accepted_awaiting_payment` | `converted` |
`rejected_by_nurse` | `expired_no_response` | `payment_deadline_expired` | `cancelled_by_customer`.
- `required_caregiver_gender`: `male` | `female` | `any`.
## Endpoints
### `POST api/v1/booking_requests/create`
- **Purpose:** a customer requests a nurse for a patient/variant/address/date.
- **Auth:** authenticated **customer (owner)** · **Rate-limited:** no · **Idempotency key:** no
- **Request body:**
```json
{
"nurseId": 42,
"variantId": 8,
"patientId": 5,
"customerAddressId": 6,
"requestedDate": "2026-08-01",
"requestedTimeStart": "09:00:00",
"requestedTimeEnd": "13:00:00",
"requiredCaregiverGender": "female",
"customerNotes": "Careful with the IV line."
}
```
- **Success `200` payload (`data`):** a `BookingRequestDto` (customer view — full address), status
`pending_nurse_response`, `nurseResponseDeadlineAt` set, `paymentDeadlineAt` null.
- **Failure cases:** `400` validation (missing/invalid gender, `requestedTimeEnd ≤ requestedTimeStart`,
past date, notes > 1000, inactive variant, nurse not verified/accepting, **same-gender mismatch**);
`401` unauthenticated; `403` not a customer / no customer profile; `404` patient/address not owned or
variant not the nurse's or nurse absent.
- **Side effects:** in-app `booking_request_received` notification to the nurse (`data_json`:
`booking_request_id`, `patient_display_name`, `requested_date`).
### `POST api/v1/booking_requests/accept/{id}`
- **Purpose:** the assigned nurse accepts a pending request, opening the 30-minute payment window.
- **Auth:** authenticated **nurse (assigned)** · path param `id` (BIGINT).
- **Request body:** none.
- **Success `200` payload (`data`):** `BookingRequestDto` (nurse view — masked address), status
`accepted_awaiting_payment`, `paymentDeadlineAt = now + booking_payment_deadline_minutes` (30 min).
- **Failure cases:** `401`; `403` not a nurse; `404` not the caller's request; `409` not pending / already
past the response deadline.
- **Side effects:** in-app `booking_request_accepted` notification to the customer (`data_json`:
`booking_request_id`, `payment_deadline_at`). **No `bookings` row and no money are created.**
### `POST api/v1/booking_requests/reject/{id}`
- **Purpose:** the assigned nurse declines a pending request with a reason.
- **Auth:** authenticated **nurse (assigned)** · path param `id`.
- **Request body:** `{ "reason": "Fully booked that week." }` (required, ≤ 500 chars).
- **Success `200` payload (`data`):** `BookingRequestDto`, status `rejected_by_nurse`, `nurseRejectionReason` set.
- **Failure cases:** `400` empty/too-long reason; `401`; `403`; `404`; `409` not pending.
- **Side effects:** in-app `booking_request_rejected` notification to the customer.
### `POST api/v1/booking_requests/cancel/{id}`
- **Purpose:** the customer withdraws a request that is still `pending_nurse_response` or
`accepted_awaiting_payment` (before paying).
- **Auth:** authenticated **customer (owner)** · path param `id`.
- **Request body:** none.
- **Success `200` payload (`data`):** `BookingRequestDto` (customer view), status `cancelled_by_customer`.
- **Failure cases:** `401`; `403`; `404` not owned; `409` request is terminal (converted/rejected/expired).
### `GET api/v1/booking_requests/list`
- **Purpose:** the role-scoped inbox (paginated).
- **Auth:** authenticated (customer **or** nurse).
- **Query params:** `status` (optional enum filter); `role` (`customer`|`nurse`, optional — disambiguates a
user who holds both roles; inferred from the caller's profile when omitted); `page`/`pageSize` (default
1 / 50, max 100).
- **Success `200` payload (`data`):** `PagedResult<BookingRequestListItemDto>` (`items`, `total`, `page`,
`pageSize`). Actionable rows sort first. The **customer** inbox sets `counterpartyName` = nurse name +
`nurseRating`; the **nurse** inbox sets `counterpartyName` = patient name + `customerNotes` (stage-1 only).
- **Failure cases:** `400` holds both roles and no `role` given; `401`. A caller with neither profile gets an
empty page.
### `GET api/v1/booking_requests/get/{id}`
- **Purpose:** a single request.
- **Auth:** its **customer** (full address), its **nurse** (masked address), or an **admin** (full).
- **Success `200` payload (`data`):** `BookingRequestDto`.
- **Failure cases:** `401`; `404` absent **or** caller is neither party nor admin (existence not leaked).
### `POST api/v1/admin_booking_requests/expire`
- **Purpose:** admin/test manual trigger of the expiry sweep (the same command the recurring job runs).
- **Auth:** **admin** (`DynamicPermission`) · **Rate-limited:** no.
- **Request body:** none.
- **Success `200` payload (`data`):** `{ "expiredNoResponse": 0, "paymentDeadlineExpired": 0 }` — counts moved
this run (idempotent; re-running on a drained set returns zeros).
- **Failure cases:** `401`; `403` non-admin.
## Shared shapes
- `BookingRequestDto` (full single-request view):
`id` (long), `status` (enum), `nurseId` (long), `nurseName` (string), `nurseRating` (decimal),
`nurseTotalReviews` (int), `patientId` (long), `patientName` (string), `variantId` (long),
`variantLabel` (string), `variantPriceUnit` (string), `customerAddressId` (long), `addressTitle` (string),
`cityId` (long), `cityNameFa`/`cityNameEn` (string), `districtId` (long?, null = whole city),
`districtNameFa`/`districtNameEn` (string?), `addressLine`/`postalCode`/`recipientName`/`recipientPhone`
(string?, **null in the nurse view** — masked), `requiredCaregiverGender` (enum?), `requestedDate` (date),
`requestedTimeStart`/`requestedTimeEnd` (time), `customerNotes` (string?, stage-1 plaintext),
`nurseResponseDeadlineAt` (UTC datetime), `paymentDeadlineAt` (UTC datetime?, null until accept),
`nurseRejectionReason` (string?), `createdAt` (UTC datetime).
- `BookingRequestListItemDto` (inbox row):
`id`, `status`, `counterpartyName` (nurse name for customer view / patient name for nurse view),
`nurseRating` (decimal?, customer view only), `requiredCaregiverGender` (enum?), `requestedDate`,
`requestedTimeStart`, `requestedTimeEnd`, `nurseResponseDeadlineAt`, `paymentDeadlineAt`,
`customerNotes` (string?, **nurse view only**).
- `ExpireBookingRequestsResult`: `expiredNoResponse` (int), `paymentDeadlineExpired` (int).
## Changelog
- b8 — initial contract (create/accept/reject/cancel + role-scoped list + single get + admin expire).
+906 -2
View File
@@ -7,10 +7,75 @@
},
"servers": [
{
"url": "http://localhost"
"url": "https://localhost:5002"
}
],
"paths": {
"/api/v1/admin_booking_requests/expire": {
"post": {
"tags": [
"AdminBookingRequests"
],
"operationId": "AdminBookingRequests_Expire",
"responses": {
"400": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiResultOfDictionaryOfStringAndListOfString"
}
}
}
},
"401": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiResult"
}
}
}
},
"403": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiResult"
}
}
}
},
"500": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiResult"
}
}
}
},
"200": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiResultOfExpireBookingRequestsResult"
}
}
}
}
},
"security": [
{
"Bearer": []
}
]
}
},
"/api/v1/admin_catalog/create_category": {
"post": {
"tags": [
@@ -2484,6 +2549,510 @@
]
}
},
"/api/v1/booking_requests/create": {
"post": {
"tags": [
"BookingRequests"
],
"summary": "Creates a BookingRequest",
"operationId": "BookingRequests_Create",
"requestBody": {
"x-name": "command",
"description": "A BookingRequest representation",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateBookingRequestCommand"
}
}
},
"required": true,
"x-position": 1
},
"responses": {
"400": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiResultOfDictionaryOfStringAndListOfString"
}
}
}
},
"401": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiResult"
}
}
}
},
"403": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiResult"
}
}
}
},
"500": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiResult"
}
}
}
},
"200": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiResultOfBookingRequestDto"
}
}
}
}
},
"security": [
{
"Bearer": []
}
]
}
},
"/api/v1/booking_requests/cancel/{id}": {
"post": {
"tags": [
"BookingRequests"
],
"operationId": "BookingRequests_Cancel",
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": {
"type": "integer",
"format": "int64"
},
"x-position": 1
}
],
"responses": {
"400": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiResultOfDictionaryOfStringAndListOfString"
}
}
}
},
"401": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiResult"
}
}
}
},
"403": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiResult"
}
}
}
},
"500": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiResult"
}
}
}
},
"200": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiResultOfBookingRequestDto"
}
}
}
}
},
"security": [
{
"Bearer": []
}
]
}
},
"/api/v1/booking_requests/accept/{id}": {
"post": {
"tags": [
"BookingRequests"
],
"operationId": "BookingRequests_Accept",
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": {
"type": "integer",
"format": "int64"
},
"x-position": 1
}
],
"responses": {
"400": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiResultOfDictionaryOfStringAndListOfString"
}
}
}
},
"401": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiResult"
}
}
}
},
"403": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiResult"
}
}
}
},
"500": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiResult"
}
}
}
},
"200": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiResultOfBookingRequestDto"
}
}
}
}
},
"security": [
{
"Bearer": []
}
]
}
},
"/api/v1/booking_requests/reject/{id}": {
"post": {
"tags": [
"BookingRequests"
],
"operationId": "BookingRequests_Reject",
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": {
"type": "integer",
"format": "int64"
},
"x-position": 1
}
],
"requestBody": {
"x-name": "command",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/RejectBookingRequestCommand"
}
}
},
"required": true,
"x-position": 2
},
"responses": {
"400": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiResultOfDictionaryOfStringAndListOfString"
}
}
}
},
"401": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiResult"
}
}
}
},
"403": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiResult"
}
}
}
},
"500": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiResult"
}
}
}
},
"200": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiResultOfBookingRequestDto"
}
}
}
}
},
"security": [
{
"Bearer": []
}
]
}
},
"/api/v1/booking_requests/list": {
"get": {
"tags": [
"BookingRequests"
],
"operationId": "BookingRequests_List",
"parameters": [
{
"name": "Status",
"in": "query",
"schema": {
"type": "string",
"nullable": true
},
"x-position": 1
},
{
"name": "Role",
"in": "query",
"schema": {
"type": "string",
"nullable": true
},
"x-position": 2
},
{
"name": "Page",
"in": "query",
"schema": {
"type": "integer",
"format": "int32"
},
"x-position": 3
},
{
"name": "PageSize",
"in": "query",
"schema": {
"type": "integer",
"format": "int32"
},
"x-position": 4
}
],
"responses": {
"400": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiResultOfDictionaryOfStringAndListOfString"
}
}
}
},
"401": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiResult"
}
}
}
},
"403": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiResult"
}
}
}
},
"500": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiResult"
}
}
}
},
"200": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiResultOfPagedResultOfBookingRequestListItemDto"
}
}
}
}
},
"security": [
{
"Bearer": []
}
]
}
},
"/api/v1/booking_requests/get/{id}": {
"get": {
"tags": [
"BookingRequests"
],
"summary": "Retrieves a BookingRequest by unique id",
"operationId": "BookingRequests_Get",
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"description": "A unique id for the BookingRequest",
"schema": {
"type": "integer",
"format": "int64"
},
"x-position": 1
}
],
"responses": {
"400": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiResultOfDictionaryOfStringAndListOfString"
}
}
}
},
"401": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiResult"
}
}
}
},
"403": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiResult"
}
}
}
},
"500": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiResult"
}
}
}
},
"200": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiResultOfBookingRequestDto"
}
}
}
}
},
"security": [
{
"Bearer": []
}
]
}
},
"/api/v1/catalog/categories": {
"get": {
"tags": [
@@ -3786,7 +4355,7 @@
"tags": [
"Me"
],
"description": "Role claims live inside the access token \u2014 after selecting a role the client should\n refresh its tokens to pick the new role up.",
"description": "Role claims live inside the access token after selecting a role the client should\n refresh its tokens to pick the new role up.",
"operationId": "Me_SelectRole",
"requestBody": {
"x-name": "command",
@@ -7200,6 +7769,41 @@
500
]
},
"ApiResultOfExpireBookingRequestsResult": {
"allOf": [
{
"$ref": "#/components/schemas/ApiResult"
},
{
"type": "object",
"additionalProperties": false,
"properties": {
"data": {
"nullable": true,
"oneOf": [
{
"$ref": "#/components/schemas/ExpireBookingRequestsResult"
}
]
}
}
}
]
},
"ExpireBookingRequestsResult": {
"type": "object",
"additionalProperties": false,
"properties": {
"expiredNoResponse": {
"type": "integer",
"format": "int32"
},
"paymentDeadlineExpired": {
"type": "integer",
"format": "int32"
}
}
},
"ApiResultOfServiceCategoryDto": {
"allOf": [
{
@@ -8578,6 +9182,306 @@
}
}
},
"ApiResultOfBookingRequestDto": {
"allOf": [
{
"$ref": "#/components/schemas/ApiResult"
},
{
"type": "object",
"additionalProperties": false,
"properties": {
"data": {
"nullable": true,
"oneOf": [
{
"$ref": "#/components/schemas/BookingRequestDto"
}
]
}
}
}
]
},
"BookingRequestDto": {
"type": "object",
"additionalProperties": false,
"properties": {
"id": {
"type": "integer",
"format": "int64"
},
"status": {
"type": "string"
},
"nurseId": {
"type": "integer",
"format": "int64"
},
"nurseName": {
"type": "string"
},
"nurseRating": {
"type": "number",
"format": "decimal"
},
"nurseTotalReviews": {
"type": "integer",
"format": "int32"
},
"patientId": {
"type": "integer",
"format": "int64"
},
"patientName": {
"type": "string"
},
"variantId": {
"type": "integer",
"format": "int64"
},
"variantLabel": {
"type": "string"
},
"variantPriceUnit": {
"type": "string"
},
"customerAddressId": {
"type": "integer",
"format": "int64"
},
"addressTitle": {
"type": "string"
},
"cityId": {
"type": "integer",
"format": "int64"
},
"cityNameFa": {
"type": "string"
},
"cityNameEn": {
"type": "string"
},
"districtId": {
"type": "integer",
"format": "int64",
"nullable": true
},
"districtNameFa": {
"type": "string",
"nullable": true
},
"districtNameEn": {
"type": "string",
"nullable": true
},
"addressLine": {
"type": "string",
"nullable": true
},
"postalCode": {
"type": "string",
"nullable": true
},
"recipientName": {
"type": "string",
"nullable": true
},
"recipientPhone": {
"type": "string",
"nullable": true
},
"requiredCaregiverGender": {
"type": "string",
"nullable": true
},
"requestedDate": {
"type": "string",
"format": "date"
},
"requestedTimeStart": {
"type": "string",
"format": "time"
},
"requestedTimeEnd": {
"type": "string",
"format": "time"
},
"customerNotes": {
"type": "string",
"nullable": true
},
"nurseResponseDeadlineAt": {
"type": "string",
"format": "date-time"
},
"paymentDeadlineAt": {
"type": "string",
"format": "date-time",
"nullable": true
},
"nurseRejectionReason": {
"type": "string",
"nullable": true
},
"createdAt": {
"type": "string",
"format": "date-time"
}
}
},
"CreateBookingRequestCommand": {
"type": "object",
"additionalProperties": false,
"properties": {
"nurseId": {
"type": "integer",
"format": "int64"
},
"variantId": {
"type": "integer",
"format": "int64"
},
"patientId": {
"type": "integer",
"format": "int64"
},
"customerAddressId": {
"type": "integer",
"format": "int64"
},
"requestedDate": {
"type": "string",
"format": "date"
},
"requestedTimeStart": {
"type": "string",
"format": "time"
},
"requestedTimeEnd": {
"type": "string",
"format": "time"
},
"requiredCaregiverGender": {
"type": "string"
},
"customerNotes": {
"type": "string",
"nullable": true
}
}
},
"RejectBookingRequestCommand": {
"type": "object",
"additionalProperties": false,
"properties": {
"reason": {
"type": "string",
"nullable": true
},
"id": {
"type": "integer",
"format": "int64"
}
}
},
"ApiResultOfPagedResultOfBookingRequestListItemDto": {
"allOf": [
{
"$ref": "#/components/schemas/ApiResult"
},
{
"type": "object",
"additionalProperties": false,
"properties": {
"data": {
"nullable": true,
"oneOf": [
{
"$ref": "#/components/schemas/PagedResultOfBookingRequestListItemDto"
}
]
}
}
}
]
},
"PagedResultOfBookingRequestListItemDto": {
"type": "object",
"additionalProperties": false,
"properties": {
"items": {
"type": "array",
"nullable": true,
"items": {
"$ref": "#/components/schemas/BookingRequestListItemDto"
}
},
"total": {
"type": "integer",
"format": "int32"
},
"page": {
"type": "integer",
"format": "int32"
},
"pageSize": {
"type": "integer",
"format": "int32"
}
}
},
"BookingRequestListItemDto": {
"type": "object",
"additionalProperties": false,
"properties": {
"id": {
"type": "integer",
"format": "int64"
},
"status": {
"type": "string"
},
"counterpartyName": {
"type": "string"
},
"nurseRating": {
"type": "number",
"format": "decimal",
"nullable": true
},
"requiredCaregiverGender": {
"type": "string",
"nullable": true
},
"requestedDate": {
"type": "string",
"format": "date"
},
"requestedTimeStart": {
"type": "string",
"format": "time"
},
"requestedTimeEnd": {
"type": "string",
"format": "time"
},
"nurseResponseDeadlineAt": {
"type": "string",
"format": "date-time"
},
"paymentDeadlineAt": {
"type": "string",
"format": "date-time",
"nullable": true
},
"customerNotes": {
"type": "string",
"nullable": true
}
}
},
"ApiResultOfPagedResultOfServiceCategoryDto": {
"allOf": [
{