openapi: 3.0.3
info:
  title: myREST Public API
  description: |
    Public API for integrating third-party services with the myREST restaurant management platform.

    ## Authentication
    All endpoints require an API key passed via the `X-API-Key` header.
    Each API key is bound to a single locale (restaurant) — cross-locale access is forbidden.

    ## Rate Limiting
    Default: 60 requests per 60 seconds (configurable per API key).
    Rate limit info is returned in response headers:
    - `X-RateLimit-Limit` — maximum requests per window
    - `X-RateLimit-Remaining` — remaining requests in current window
    - `X-RateLimit-Reset` — seconds until the window resets

    ## Scopes
    API keys can be restricted to specific scopes:
    - `menu:read` — read menu data
    - `reservations:read` — read reservations
    - `reservations:create` — create reservations
    - `locale:read` — read locale info
    - `menu:create` — create categories and items
    - `menu:update` — update categories and items
    - `menu:delete` — delete categories and items (soft delete)
  version: 1.1.0
  contact:
    name: myREST Support

servers:
  - url: https://{host}/api/v1
    variables:
      host:
        default: api.myrest.io

security:
  - ApiKeyAuth: []

tags:
  - name: Locale
    description: Restaurant information, departments, and tables
  - name: Menu
    description: Menu categories and items
  - name: Reservations
    description: Reservation management

paths:
  /locale/{locale}:
    get:
      tags: [Locale]
      summary: Get locale info
      description: Returns public information about the restaurant.
      operationId: getLocale
      parameters:
        - $ref: '#/components/parameters/locale'
      responses:
        '200':
          description: Locale details
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/SuccessResponse'
                  - type: object
                    properties:
                      data:
                        $ref: '#/components/schemas/Locale'
        '403':
          $ref: '#/components/responses/Forbidden'

  /locale/{locale}/departments:
    get:
      tags: [Locale]
      summary: List departments
      description: Returns reservation departments for this locale. Use department IDs when creating reservations.
      operationId: getLocaleDepartments
      parameters:
        - $ref: '#/components/parameters/locale'
      responses:
        '200':
          description: List of departments
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/SuccessResponse'
                  - type: object
                    properties:
                      data:
                        type: array
                        items:
                          $ref: '#/components/schemas/Department'
        '403':
          $ref: '#/components/responses/Forbidden'

  /locale/{locale}/tables:
    get:
      tags: [Locale]
      summary: List tables
      description: Returns all tables for this locale.
      operationId: getLocaleTables
      parameters:
        - $ref: '#/components/parameters/locale'
      responses:
        '200':
          description: List of tables
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/SuccessResponse'
                  - type: object
                    properties:
                      data:
                        type: array
                        items:
                          $ref: '#/components/schemas/Table'
        '403':
          $ref: '#/components/responses/Forbidden'

  /menu/{locale}:
    get:
      tags: [Menu]
      summary: Get full menu
      description: Returns the complete menu structure with categories, subcategories, and items.
      operationId: getMenu
      parameters:
        - $ref: '#/components/parameters/locale'
        - $ref: '#/components/parameters/lang'
      responses:
        '200':
          description: Full menu tree
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/SuccessResponse'
                  - type: object
                    properties:
                      data:
                        type: array
                        items:
                          $ref: '#/components/schemas/MenuCategory'
        '403':
          $ref: '#/components/responses/Forbidden'
    post:
      tags: [Menu]
      summary: Create menu item
      description: Creates a new menu item in the specified category. Supports base64 image upload and multi-language translations.
      operationId: createMenuItem
      parameters:
        - $ref: '#/components/parameters/locale'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateMenuItemRequest'
      responses:
        '200':
          description: Item created
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/SuccessResponse'
                  - type: object
                    properties:
                      data:
                        $ref: '#/components/schemas/MenuItemDetail'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          description: Category not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

  /menu/{locale}/categories:
    get:
      tags: [Menu]
      summary: List categories
      description: Returns a flat list of all menu categories.
      operationId: getMenuCategories
      parameters:
        - $ref: '#/components/parameters/locale'
        - $ref: '#/components/parameters/lang'
      responses:
        '200':
          description: List of categories
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/SuccessResponse'
                  - type: object
                    properties:
                      data:
                        type: array
                        items:
                          $ref: '#/components/schemas/CategorySummary'
        '403':
          $ref: '#/components/responses/Forbidden'
    post:
      tags: [Menu]
      summary: Create category
      description: Creates a new menu category. Supports subcategories via parent_category and multi-language translations.
      operationId: createCategory
      parameters:
        - $ref: '#/components/parameters/locale'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateCategoryRequest'
      responses:
        '200':
          description: Category created
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/SuccessResponse'
                  - type: object
                    properties:
                      data:
                        $ref: '#/components/schemas/CategoryDetail'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          description: Parent category not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

  /menu/{locale}/categories/{id}:
    put:
      tags: [Menu]
      summary: Update category
      description: Updates an existing category. Only provided fields are changed (partial update).
      operationId: updateCategory
      parameters:
        - $ref: '#/components/parameters/locale'
        - $ref: '#/components/parameters/id'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdateCategoryRequest'
      responses:
        '200':
          description: Category updated
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/SuccessResponse'
                  - type: object
                    properties:
                      data:
                        $ref: '#/components/schemas/CategoryDetail'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          description: Category not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
    delete:
      tags: [Menu]
      summary: Delete category
      description: Soft-deletes a category. The category is marked as deleted but not removed from the database.
      operationId: deleteCategory
      parameters:
        - $ref: '#/components/parameters/locale'
        - $ref: '#/components/parameters/id'
      responses:
        '200':
          description: Category deleted
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SuccessResponse'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          description: Category not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

  /menu/{locale}/categories/{id}/toggle:
    patch:
      tags: [Menu]
      summary: Toggle category availability
      description: Flips the `is_active` flag on a category (1→0 or 0→1). Requires `menu:update` scope.
      operationId: toggleCategory
      parameters:
        - $ref: '#/components/parameters/locale'
        - $ref: '#/components/parameters/id'
      responses:
        '200':
          description: Category toggled
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/SuccessResponse'
                  - type: object
                    properties:
                      data:
                        $ref: '#/components/schemas/ToggleResponse'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          description: Category not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

  /menu/{locale}/options/{id}/toggle:
    patch:
      tags: [Menu]
      summary: Toggle option group availability
      description: Flips the `is_active` flag on an option group (1→0 or 0→1). Requires `menu:update` scope.
      operationId: toggleOptionGroup
      parameters:
        - $ref: '#/components/parameters/locale'
        - $ref: '#/components/parameters/id'
      responses:
        '200':
          description: Option group toggled
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/SuccessResponse'
                  - type: object
                    properties:
                      data:
                        $ref: '#/components/schemas/ToggleResponse'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          description: Option group not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

  /menu/{locale}/{id}/toggle:
    patch:
      tags: [Menu]
      summary: Toggle menu item availability
      description: Flips the `is_active` flag on a menu item (1→0 or 0→1). Requires `menu:update` scope.
      operationId: toggleMenuItem
      parameters:
        - $ref: '#/components/parameters/locale'
        - $ref: '#/components/parameters/id'
      responses:
        '200':
          description: Item toggled
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/SuccessResponse'
                  - type: object
                    properties:
                      data:
                        $ref: '#/components/schemas/ToggleResponse'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          description: Menu item not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

  /menu/{locale}/options:
    get:
      tags: [Menu]
      summary: List option groups
      description: Returns all option/addon groups for this locale with their items.
      operationId: listOptionGroups
      parameters:
        - $ref: '#/components/parameters/locale'
      responses:
        '200':
          description: List of option groups
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/SuccessResponse'
                  - type: object
                    properties:
                      data:
                        type: array
                        items:
                          $ref: '#/components/schemas/OptionGroup'
        '403':
          $ref: '#/components/responses/Forbidden'
    post:
      tags: [Menu]
      summary: Create option group
      description: Creates a new option/addon group with optional inline items and translations.
      operationId: createOptionGroup
      parameters:
        - $ref: '#/components/parameters/locale'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateOptionGroupRequest'
      responses:
        '200':
          description: Option group created
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/SuccessResponse'
                  - type: object
                    properties:
                      data:
                        $ref: '#/components/schemas/OptionGroup'
        '403':
          $ref: '#/components/responses/Forbidden'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

  /menu/{locale}/options/{id}:
    put:
      tags: [Menu]
      summary: Update option group
      description: Updates an existing option group. Partial update — only provided fields are changed. Can add new items via the items array.
      operationId: updateOptionGroup
      parameters:
        - $ref: '#/components/parameters/locale'
        - $ref: '#/components/parameters/id'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdateOptionGroupRequest'
      responses:
        '200':
          description: Option group updated
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/SuccessResponse'
                  - type: object
                    properties:
                      data:
                        $ref: '#/components/schemas/OptionGroup'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          description: Option group not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
    delete:
      tags: [Menu]
      summary: Delete option group
      description: Soft-deletes an option group.
      operationId: deleteOptionGroup
      parameters:
        - $ref: '#/components/parameters/locale'
        - $ref: '#/components/parameters/id'
      responses:
        '200':
          description: Option group deleted
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SuccessResponse'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          description: Option group not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

  /menu/{locale}/{id}/options:
    get:
      tags: [Menu]
      summary: Get item options
      description: Returns option groups linked to a specific menu item. Supports language via ?lang= parameter.
      operationId: getItemOptions
      parameters:
        - $ref: '#/components/parameters/locale'
        - $ref: '#/components/parameters/id'
        - $ref: '#/components/parameters/lang'
      responses:
        '200':
          description: Linked option groups with items
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/SuccessResponse'
                  - type: object
                    properties:
                      data:
                        type: array
                        items:
                          $ref: '#/components/schemas/LinkedOptionGroup'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          description: Menu item not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
    put:
      tags: [Menu]
      summary: Set item options
      description: Replaces all option group links for a menu item. Pass an empty array to unlink all options.
      operationId: setItemOptions
      parameters:
        - $ref: '#/components/parameters/locale'
        - $ref: '#/components/parameters/id'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [option_group_ids]
              properties:
                option_group_ids:
                  type: array
                  description: Array of option group IDs to link. Empty array unlinks all.
                  items:
                    type: integer
      responses:
        '200':
          description: Options updated
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/SuccessResponse'
                  - type: object
                    properties:
                      data:
                        type: array
                        items:
                          $ref: '#/components/schemas/LinkedOptionGroup'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          description: Menu item or option group not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

  /menu/{locale}/{id}:
    get:
      tags: [Menu]
      summary: Get menu item
      description: Returns a single menu item with translations and options.
      operationId: getMenuItem
      parameters:
        - $ref: '#/components/parameters/locale'
        - $ref: '#/components/parameters/id'
        - $ref: '#/components/parameters/lang'
      responses:
        '200':
          description: Menu item details
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/SuccessResponse'
                  - type: object
                    properties:
                      data:
                        $ref: '#/components/schemas/MenuItem'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          description: Item not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                success: false
                error:
                  code: ITEM_NOT_FOUND
                  message: Menu item not found.
    put:
      tags: [Menu]
      summary: Update menu item
      description: Updates an existing menu item. Only provided fields are changed (partial update). Supports image replacement via base64.
      operationId: updateMenuItem
      parameters:
        - $ref: '#/components/parameters/locale'
        - $ref: '#/components/parameters/id'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdateMenuItemRequest'
      responses:
        '200':
          description: Item updated
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/SuccessResponse'
                  - type: object
                    properties:
                      data:
                        $ref: '#/components/schemas/MenuItemDetail'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          description: Item not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
    delete:
      tags: [Menu]
      summary: Delete menu item
      description: Soft-deletes a menu item. The item is marked as deleted but not removed from the database.
      operationId: deleteMenuItem
      parameters:
        - $ref: '#/components/parameters/locale'
        - $ref: '#/components/parameters/id'
      responses:
        '200':
          description: Item deleted
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SuccessResponse'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          description: Item not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

  /reservation/{locale}:
    get:
      tags: [Reservations]
      summary: List reservations
      description: Returns paginated list of reservations with optional filters.
      operationId: listReservations
      parameters:
        - $ref: '#/components/parameters/locale'
        - name: department_id
          in: query
          schema:
            type: integer
          description: Filter by department
        - name: date_from
          in: query
          schema:
            type: string
            format: date
          description: Start date (YYYY-MM-DD)
        - name: date_to
          in: query
          schema:
            type: string
            format: date
          description: End date (YYYY-MM-DD)
        - name: status
          in: query
          schema:
            type: integer
            enum: [1, 2, 3, 4, 5]
          description: "1=CONFIRMED, 2=WAITING_FOR_CONFIRMATION, 3=DECLINED, 4=WAITING_FOR_PAYMENT, 5=PAYMENT_FAILED"
        - name: page
          in: query
          schema:
            type: integer
            default: 1
        - name: limit
          in: query
          schema:
            type: integer
            default: 20
            maximum: 100
      responses:
        '200':
          description: List of reservations
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/SuccessResponse'
                  - type: object
                    properties:
                      data:
                        type: array
                        items:
                          $ref: '#/components/schemas/Reservation'
                      meta:
                        $ref: '#/components/schemas/PaginationMeta'
        '403':
          $ref: '#/components/responses/Forbidden'
    post:
      tags: [Reservations]
      summary: Create reservation
      description: Creates a new reservation. The department must belong to the locale.
      operationId: createReservation
      parameters:
        - $ref: '#/components/parameters/locale'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateReservationRequest'
      responses:
        '200':
          description: Reservation created
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/SuccessResponse'
                  - type: object
                    properties:
                      data:
                        $ref: '#/components/schemas/Reservation'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          description: Department not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

  /reservation/{locale}/{id}:
    get:
      tags: [Reservations]
      summary: Get reservation
      description: Returns a single reservation by ID.
      operationId: getReservation
      parameters:
        - $ref: '#/components/parameters/locale'
        - $ref: '#/components/parameters/id'
      responses:
        '200':
          description: Reservation details
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/SuccessResponse'
                  - type: object
                    properties:
                      data:
                        $ref: '#/components/schemas/Reservation'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          description: Reservation not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

components:
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: X-API-Key
      description: API key generated in the admin panel. Each key is bound to one locale.

  parameters:
    locale:
      name: locale
      in: path
      required: true
      schema:
        type: integer
      description: Locale (restaurant) ID. Must match the locale bound to your API key.
    id:
      name: id
      in: path
      required: true
      schema:
        type: integer
      description: Resource ID
    lang:
      name: lang
      in: query
      schema:
        type: string
      description: Language code for translations (e.g. en, pl, de)

  responses:
    Forbidden:
      description: API key not authorized for this locale or missing required scope
      content:
        application/json:
          schema:
            type: object
            properties:
              name:
                type: string
                example: Forbidden
              message:
                type: string
                example: API key is not authorized for this locale.
              status:
                type: integer
                example: 403

  schemas:
    SuccessResponse:
      type: object
      properties:
        success:
          type: boolean
          example: true

    ErrorResponse:
      type: object
      properties:
        success:
          type: boolean
          example: false
        error:
          type: object
          properties:
            code:
              type: string
            message:
              type: string

    PaginationMeta:
      type: object
      properties:
        meta:
          type: object
          properties:
            pagination:
              type: object
              properties:
                total:
                  type: integer
                page:
                  type: integer
                per_page:
                  type: integer

    Locale:
      type: object
      properties:
        id:
          type: integer
        name:
          type: string
        logo:
          type: string
          nullable: true
        photo:
          type: string
          nullable: true
        phone:
          type: string
          nullable: true
        site_url:
          type: string
          nullable: true
        opens_at:
          type: integer
          description: Opening hour (24h format)
        closes_at:
          type: integer
          description: Closing hour (24h format)
        languages:
          type: array
          items:
            type: object
            properties:
              code:
                type: string
              name:
                type: string
              default:
                type: boolean
        color_main:
          type: string
          nullable: true
        color_secondary:
          type: string
          nullable: true
        social:
          type: object
          properties:
            facebook:
              type: string
              nullable: true
            tripadvisor:
              type: string
              nullable: true
            google:
              type: string
              nullable: true
            yelp:
              type: string
              nullable: true
            zomato:
              type: string
              nullable: true
        wifi_password:
          type: string
          nullable: true
        max_table_capacity:
          type: integer
          nullable: true
        menu_welcome_text:
          type: string
          nullable: true
        privacy_url:
          type: string
          nullable: true

    Department:
      type: object
      properties:
        id:
          type: integer
        name:
          type: string

    Table:
      type: object
      properties:
        id:
          type: integer
        name:
          type: string
          nullable: true

    MenuCategory:
      type: object
      properties:
        category_id:
          type: integer
        name:
          type: string
        description:
          type: string
          nullable: true
        order_position:
          type: integer
        items:
          type: array
          items:
            $ref: '#/components/schemas/MenuItem'
        subcategories:
          type: array
          items:
            $ref: '#/components/schemas/MenuCategory'

    CategorySummary:
      type: object
      properties:
        category_id:
          type: integer
        name:
          type: string
        description:
          type: string
          nullable: true
        parent_category:
          type: integer
          nullable: true
        order_position:
          type: integer

    MenuItem:
      type: object
      properties:
        id:
          type: integer
        category_id:
          type: integer
        name:
          type: string
        is_vegetarian:
          type: boolean
        is_low_calorie:
          type: boolean
        is_spicy:
          type: boolean
        is_gluten_free:
          type: boolean
        price:
          type: number
          format: float
        image:
          type: string
          nullable: true
        weight:
          type: string
          nullable: true
        volume:
          type: string
          nullable: true
        alcohol:
          type: boolean
        description:
          type: string
          nullable: true
        available_for_order:
          type: boolean
        is_adult:
          type: boolean
        options:
          type: array
          items:
            type: object

    Reservation:
      type: object
      properties:
        id:
          type: integer
        code:
          type: string
        department_id:
          type: integer
          nullable: true
        name:
          type: string
        persons:
          type: integer
          nullable: true
        date:
          type: string
          format: date
        time:
          type: string
          example: "18:00"
        reservation_till:
          type: string
          format: date-time
          nullable: true
        duration_hours:
          type: integer
          nullable: true
        duration_minutes:
          type: integer
          nullable: true
        email:
          type: string
        phone:
          type: string
        message:
          type: string
        comment:
          type: string
        status:
          type: integer
          description: "1=CONFIRMED, 2=WAITING_FOR_CONFIRMATION, 3=DECLINED, 4=WAITING_FOR_PAYMENT, 5=PAYMENT_FAILED"
        status_label:
          type: string
          enum: [CONFIRMED, WAITING_FOR_CONFIRMATION, DECLINED, WAITING_FOR_PAYMENT, PAYMENT_FAILED, UNKNOWN]
        source:
          type: string
          nullable: true
        arrival_status:
          type: string
          nullable: true
        table_id:
          type: integer
          nullable: true
        preferred_hall_id:
          type: integer
          nullable: true
        created_at:
          type: string
          format: date-time
          nullable: true

    CreateReservationRequest:
      type: object
      required: [department_id, date, time, persons]
      properties:
        department_id:
          type: integer
          description: Department ID (use /locale/{id}/departments to get available IDs)
        date:
          type: string
          format: date
          description: Reservation date (YYYY-MM-DD)
        time:
          type: string
          description: Reservation time (HH:MM)
          example: "18:00"
        persons:
          type: integer
          minimum: 1
        name:
          type: string
        email:
          type: string
          format: email
        phone:
          type: string
        message:
          type: string
        language:
          type: string
          default: en
        comment:
          type: string
        duration_hours:
          type: integer
        duration_minutes:
          type: integer
        preferred_hall_id:
          type: integer

    Translation:
      type: object
      properties:
        lang:
          type: string
          description: Language code (e.g. en, pl, de)
        name:
          type: string
        description:
          type: string

    CategoryDetail:
      type: object
      properties:
        id:
          type: integer
        name:
          type: string
        description:
          type: string
          nullable: true
        parent_category:
          type: integer
          nullable: true
        order_position:
          type: integer
        is_active:
          type: integer
        translations:
          type: array
          items:
            $ref: '#/components/schemas/Translation'

    CreateCategoryRequest:
      type: object
      required: [name]
      properties:
        name:
          type: string
        description:
          type: string
        parent_category:
          type: integer
          nullable: true
          description: Parent category ID for subcategories
        order_position:
          type: integer
          default: 0
        is_active:
          type: integer
          default: 1
          enum: [0, 1]
        translations:
          type: array
          description: Multi-language translations
          items:
            $ref: '#/components/schemas/Translation'

    UpdateCategoryRequest:
      type: object
      properties:
        name:
          type: string
        description:
          type: string
        parent_category:
          type: integer
          nullable: true
        order_position:
          type: integer
        is_active:
          type: integer
          enum: [0, 1]
        translations:
          type: array
          items:
            $ref: '#/components/schemas/Translation'

    MenuItemDetail:
      type: object
      properties:
        id:
          type: integer
        category_id:
          type: integer
        name:
          type: string
        description:
          type: string
          nullable: true
        price:
          type: number
          format: float
        vat:
          type: number
          format: float
        image:
          type: string
          nullable: true
        weight:
          type: integer
          nullable: true
        volume:
          type: integer
          nullable: true
        alcohol:
          type: number
          nullable: true
        is_vegetarian:
          type: integer
          enum: [0, 1]
        is_low_calorie:
          type: integer
          enum: [0, 1]
        is_gluten_free:
          type: integer
          enum: [0, 1]
        is_spicy:
          type: integer
          enum: [0, 1]
        is_adult:
          type: integer
          enum: [0, 1]
        available_for_order:
          type: integer
          enum: [0, 1]
        is_active:
          type: integer
          enum: [0, 1]
        order_position:
          type: integer
        translations:
          type: array
          items:
            $ref: '#/components/schemas/Translation'

    CreateMenuItemRequest:
      type: object
      required: [name, category_id]
      properties:
        name:
          type: string
        category_id:
          type: integer
          description: Category to place the item in
        description:
          type: string
        price:
          type: number
          format: float
          default: 0
        vat:
          type: number
          format: float
          default: 0
          description: VAT percentage (e.g. 8 for 8%)
        weight:
          type: integer
          nullable: true
        volume:
          type: integer
          nullable: true
        alcohol:
          type: number
          nullable: true
        is_vegetarian:
          type: integer
          default: 0
          enum: [0, 1]
        is_low_calorie:
          type: integer
          default: 0
          enum: [0, 1]
        is_gluten_free:
          type: integer
          default: 0
          enum: [0, 1]
        is_spicy:
          type: integer
          default: 0
          enum: [0, 1]
        is_adult:
          type: integer
          default: 0
          enum: [0, 1]
        available_for_order:
          type: integer
          default: 1
          enum: [0, 1]
        is_active:
          type: integer
          default: 1
          enum: [0, 1]
        order_position:
          type: integer
          default: 0
        image_base64:
          type: string
          description: Base64-encoded JPEG image
        translations:
          type: array
          items:
            $ref: '#/components/schemas/Translation'

    UpdateMenuItemRequest:
      type: object
      properties:
        name:
          type: string
        category_id:
          type: integer
        description:
          type: string
        price:
          type: number
          format: float
        vat:
          type: number
          format: float
        weight:
          type: integer
        volume:
          type: integer
        alcohol:
          type: number
        is_vegetarian:
          type: integer
          enum: [0, 1]
        is_low_calorie:
          type: integer
          enum: [0, 1]
        is_gluten_free:
          type: integer
          enum: [0, 1]
        is_spicy:
          type: integer
          enum: [0, 1]
        is_adult:
          type: integer
          enum: [0, 1]
        available_for_order:
          type: integer
          enum: [0, 1]
        is_active:
          type: integer
          enum: [0, 1]
        order_position:
          type: integer
        image_base64:
          type: string
          description: Base64-encoded JPEG image (replaces existing image)
        remove_image:
          type: boolean
          description: Set to true to remove the current image
        translations:
          type: array
          items:
            $ref: '#/components/schemas/Translation'

    OptionGroupItem:
      type: object
      properties:
        id:
          type: integer
        name:
          type: string
        price:
          type: number
          format: float
        is_active:
          type: integer
          enum: [0, 1]
        order_position:
          type: integer
        image:
          type: string
          nullable: true
        translations:
          type: array
          items:
            $ref: '#/components/schemas/Translation'

    OptionGroup:
      type: object
      properties:
        id:
          type: integer
        name:
          type: string
        description:
          type: string
          nullable: true
        is_required:
          type: integer
          enum: [0, 1]
          description: Whether selecting an option is required
        is_multiple:
          type: integer
          enum: [0, 1]
          description: Whether multiple options can be selected
        is_multiselect:
          type: integer
          enum: [0, 1]
        is_active:
          type: integer
          enum: [0, 1]
        order_position:
          type: integer
        items:
          type: array
          items:
            $ref: '#/components/schemas/OptionGroupItem'
        translations:
          type: array
          items:
            $ref: '#/components/schemas/Translation'

    LinkedOptionGroup:
      type: object
      description: Option group as linked to a menu item (translated names)
      properties:
        id:
          type: integer
        menu_item_id:
          type: integer
        name:
          type: string
          description: Translated name based on ?lang= parameter
        is_required:
          type: integer
          enum: [0, 1]
        is_multiple:
          type: integer
          enum: [0, 1]
        is_multiselect:
          type: integer
          enum: [0, 1]
        pos_id:
          type: string
          nullable: true
        options:
          type: array
          items:
            type: object
            properties:
              id:
                type: integer
              name:
                type: string
              price:
                type: number
                format: float
              is_active:
                type: integer
                enum: [0, 1]
              image:
                type: string
                nullable: true

    OptionGroupItemInput:
      type: object
      required: [name]
      properties:
        id:
          type: integer
          description: Existing item ID to update. Omit to create new.
        name:
          type: string
        price:
          type: number
          format: float
          default: 0
        is_active:
          type: integer
          default: 1
          enum: [0, 1]
        order_position:
          type: integer
          default: 0
        translations:
          type: array
          items:
            $ref: '#/components/schemas/Translation'

    CreateOptionGroupRequest:
      type: object
      required: [name]
      properties:
        name:
          type: string
        description:
          type: string
        is_required:
          type: integer
          default: 0
          enum: [0, 1]
        is_multiple:
          type: integer
          default: 0
          enum: [0, 1]
        is_multiselect:
          type: integer
          default: 0
          enum: [0, 1]
        is_active:
          type: integer
          default: 1
          enum: [0, 1]
        order_position:
          type: integer
          default: 0
        items:
          type: array
          description: Option values to create inside this group
          items:
            $ref: '#/components/schemas/OptionGroupItemInput'
        translations:
          type: array
          items:
            $ref: '#/components/schemas/Translation'

    ToggleResponse:
      type: object
      properties:
        id:
          type: integer
        name:
          type: string
        is_active:
          type: integer
          enum: [0, 1]
          description: New state after toggle

    UpdateOptionGroupRequest:
      type: object
      properties:
        name:
          type: string
        description:
          type: string
        is_required:
          type: integer
          enum: [0, 1]
        is_multiple:
          type: integer
          enum: [0, 1]
        is_multiselect:
          type: integer
          enum: [0, 1]
        is_active:
          type: integer
          enum: [0, 1]
        order_position:
          type: integer
        items:
          type: array
          description: Add or update option values
          items:
            $ref: '#/components/schemas/OptionGroupItemInput'
        translations:
          type: array
          items:
            $ref: '#/components/schemas/Translation'
