openapi: 3.1.0
info:
  title: Offers API Docs
  description: >-
    # OpenAPI & Swagger UI

    [OpenAPI
    specification](https://connect.jobrad.org/api-docs/offers-api/offers-api.yaml)


    [Swagger
    UI](https://connect.jobrad.org/v1/offers/docs/swagger-ui/index.html) -
    Interactive API documentation using Swagger UI.


    # Overview

    The Offers API is crafted for dealers and partners seeking to enhance their

    leasing operations for bicycles and accessories. By integrating this API,

    you can seamlessly align your workflows, minimize manual effort, reduce

    errors, and expedite the process through efficient status information

    updates. The API offers a comprehensive suite of functionalities:



    - **Submit New Lease Offers**: Simplify the creation and submission of

    offers for bicycles and accessories, enabling swift and effective leasing

    management.


    - **Track Offer Status**: Continuously monitor the status of submitted

    offers throughout the approval process to ensure transparency and informed

    decision-making.


    - **Query Existing Offers**: Utilize advanced filter options to efficiently

    access details of existing offers, streamlining data retrieval and

    management.


    - **Access Leasable Accessories Catalog**: Retrieve the catalog of

    accessories available for lease, enhancing the value and customization of

    bicycle offers.



    These capabilities are designed to optimize the leasing process and enhance

    business operations through seamless API integration. This overview provides

    foundational insights into constructing offers using key components, thus

    ensuring seamless integration into your leasing workflow. The following

    section of this document will provide a detailed description of each

    component and how to leverage them effectively.



    We are committed to continuously improving everything related to offers and

    refining the processes our partners utilize. Your feedback is invaluable to

    us, and we welcome you to get in touch if you're interested in partnering or

    have suggestions to make our offerings even better.



    ---



    ### Offer Overview



    In the Offers API, the main offer object comprises several key components

    integral to creating a complete and leasable offer. Here’s a brief summary

    of its main parts:



    - **Product (Bike)**:
      - **Bidex Code**: Mandatory identifier for the bicycle, crucial for standardized classification.
      - **Details**: Includes various descriptive attributes such as brand name, model name, year, color, size, drive manufacturer, battery capacity, and pricing information. These fields provide essential characteristics and are customizable text entries.
    ---


    - **Accessories**:
      - **Bidex Code**: Ensures identification and checks for leaseability.
      - **Accessory Details**: Includes attributes like brand name, model name, and pricing details.
    ---


    - **Additional Fields**:
      - **Shipping Cost**: Specifies the net cost of shipping the offer.
      - **Installation Cost**: Details the net cost associated with installation.
      - **Delivery Date**: Indicates expected date and time for delivery.
      - **Notes**: Contains any special instructions or additional information associated with the offer.
    ---


    This overview provides a foundational understanding of how to construct an

    offer using the essential components, ensuring seamless integration into the

    leasing workflow.



    # Offer Status and Processing


    ## Asynchronous Processing



    Offers submitted via this API are processed asynchronously, ensuring

    efficient background handling. Initially, upon submission, the service

    accepts the offer, returning an HTTP status code *202 Accepted*. It's

    crucial to note that this does not confirm successful processing; subsequent

    background processes may identify issues such as missing or invalid data.



    ## Key Actions



    - After submission, use the **GET /offers** endpoint to regularly check

    offer status, ensuring it progresses as expected.


    - Pay attention to any status changes which indicate requirements for

    further action.



    ## Status



    This section details potential statuses within the offer lifecycle and

    relevant developer actions:



    - **`pending`**: Awaiting acceptance by JobRad, indicating the initial

    submission state. [*Initial status*]



    - **`draft`**: The offer is accepted, yet to undergo further processing by

    JobRad.



    - **`order_journey_started`**: The offer has been integrated into an order,

    with the process now active. At this stage, **no modifications are

    allowed**.



    - **`employer_decision_pending`**: Awaiting employer approval, requiring

    developers to monitor and manage response setups.



    - **`jobrad_decision_pending`**: Awaiting JobRad's approval, similar to

    employer approval considerations.



    - **`done`**: The offer is complete, requiring no further actions. [*Final

    status*]



    - **`canceled`**: The offer is canceled; no subsequent processing occurs.

    [*Final status*]



    - **`rejected`**: The offer is deemed invalid and rejected. Ensure to check

    the `errors` field for rejection reasons. Correct the issues, then resubmit

    for processing.



    ##### Example Response



    The following is a typical response structure containing the "status"

    object. The "status" object provides detailed insight into the lifecycle and

    processing of offers and is returned in both the POST endpoint when

    submitting an offer and in all GET endpoints within the "data" object. This

    allows for tracking the current status of each offer.



    ```json


    {
      "status": {
        "current": {
            "code": "pending",
            "message": "The offer is currently processed by Jobrad.",
            "translations": {
              "de": "Das Angebot wird aktuell von JobRad bearbeitet."
            }
        },
        "nextSteps": [
            {
              "code": "draft",
              "message": "The offer is waiting for customer approval.",
              "translations": {
                  "de": "Das Angebot wartet auf Zustimmung durch den Arbeitgeber."
              }
            },
            {
              "code": "canceled",
              "message": "The offer is cancelled.",
              "translations": {
                  "de": "Das Angebot wurde storniert."
              }
            },
            {
              "code": "rejected",
              "message": "The offer has been rejected by Jobrad.",
              "translations": {
                  "de": "Das Angebot wurde von Jobrad abgelehnt."
              }
            }
        ],
        "errors": [
            {
              "code": "INTERNAL_SERVER_ERROR",
              "message": "An internal error occurred. Please try again later.",
              "translations": {
                  "de": "Es ist ein interner Fehler aufgetreten. Versuchen Sie es zu einem späteren Zeitpunkt erneut."
              }
            }
        ],
        "infos": [
            {
              "code": "EXTERNAL_ID_ALREADY_USED",
              "field": "externalOfferId",
              "message": "The external ID XYZ is already associated with other offers.",
              "translations": {
                  "de": "Die externalId XYZ ist bereits mit anderen Angeboten verknüpft."
              }
            }
        ]
      }
    }


    ```



    #### Explanation of the Status Object:



    - **`current`**: Represents the current status of the offer, providing

    immediate information on where the offer stands in the processing lifecycle.



    - **`nextSteps`**: Lists potential status transitions and actions that could

    be taken next. This helps in forecasting the workflow progression and

    preparing for upcoming stages.



    - **`errors`**: Indicates issues that require action. These errors are

    critical and signify problems that must be addressed to proceed with the

    offer processing.



    - **`infos`**: Comprises informational messages that highlight changes or

    updates, such as normalization of text. These should be presented to the

    user but generally do not necessitate immediate action.



    By understanding these elements, developers can efficiently manage offers,

    anticipate necessary actions, and ensure both accuracy and compliance in

    offer handling.



    ## Offer Status Diagram


    The diagram below illustrates the various statuses an offer can transition

    through during its lifecycle.



    <p>
      <img src="/api-docs/offers-api/img/offer-status-diagram.svg" alt="Offer Status Diagram" width="360"/>
    </p>



    ## External Offer ID


    The `externalOfferId` is a identifier assigned by the dealer for an offer.

    It needs to be unique for a dealer.


    Typically, it corresponds to the dealer's offer number.



    # Bidex Codes and Identification



    Bidex Codes are integral to identifying bicycles and accessories within this

    API, facilitating streamlined classification and data exchange. These

    standardized six-digit codes promote efficient communication between

    suppliers and retailers.



    ##### Structure of Bidex Codes:



    - **First two digits**: Indicate the product category (e.g., bicycles,

    accessories).


    - **Next two digits**: Represent the main product group within the category.


    - **Final two digits**: Define the sub-product group.



    ###### Example Breakdown:



    Consider the Bidex Code `103030`:



    - `10` – Product category: **Bicycles**


    - `30` – Main group: **City/Urban**


    - `30` – Sub group: **Touring**



    Thus, `103030` signifies a **City Touring Bike**.



    For comprehensive details, refer to the [Bidex

    website](https://www.bidex.bike).



    #### Internal Remapping of Bidex Codes for Leasable Accessories



    To streamline categorization, some Bidex Codes undergo internal remapping to

    a higher-level main group. For instance, codes such as `202120`, `202130`,

    `202140`, and `202150` are remapped to `202100`, as they represent **gear

    system upgrades** in JobRad's context.



    ##### Key Actions for Developers:



    - When submitting an offer with a code like `202120`, expect the offer

    retrieval process to return the remapped code `202100`.



    - Use the *GET /offers/accessory* endpoint to:
      - **Verify Leaseability**: Check whether an accessory's Bidex code is leasable, thus ensuring it can be included in a bike offer. If the code is present, the accessory is leasable; if not, it should not be part of the leasing offer to avoid rejection.
      - **Optimize Processes**: Integrate this endpoint early in your workflow to swiftly identify leaseable accessories, maintaining operational efficiency and reducing errors in offer submissions.

    This systematic approach to using Bidex Codes enhances data consistency and

    facilitates seamless integration into the leasing process.



    ---



    # Authentication



    All interactions with the Offers API require authentication using the OAuth

    2.0 protocol.



    To securely access protected endpoints, you must first obtain a valid access

    token from our authorization server. The process for retrieving and using

    these tokens is outlined in detail in our [OAuth Authentication

    Guide](/doku/docs/auth/oauth).



    Following these guidelines ensures that all requests are properly

    authenticated and authorized — providing secure, reliable access to the

    JobRad API.
  version: '1.0'
servers:
  - url: https://connect.jobrad.org
security:
  - oauth2: []
tags:
  - name: Offers
    description: Operations related to offers
paths:
  /v1/offers/{id}:
    get:
      tags:
        - Offers
      summary: Get an offer by ID
      description: >
        Retrieve a specific offer by its UUID.

        - The offer is returned in the status it was last processed. As a
        result, the offer might have changed since it was last created or
        modified..
      operationId: getOfferById
      parameters:
        - name: id
          in: path
          description: UUID of the offer to retrieve
          required: true
          schema:
            type: string
            format: uuid
            description: UUID of the offer to retrieve
            example: a0ab7953-ca09-4bc6-8fee-5b003fccbb6d
          example: a0ab7953-ca09-4bc6-8fee-5b003fccbb6d
      responses:
        '200':
          description: Offer retrieved successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OfferGetResponse'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                errors:
                  - code: UNAUTHORIZED
                    message: You are not authorized to access this resource.
                    translations:
                      de: >-
                        Sie haben keine Berechtigung für die angeforderte
                        Ressource
        '403':
          description: Permission denied
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                errors:
                  - code: ACCESS_DENIED
                    message: Access denied
                    translations:
                      de: >-
                        Sie haben keine Berechtigung für die angeforderte
                        Ressource
        '404':
          description: Offer not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                errors:
                  - code: OFFER_NOT_FOUND
                    message: 'Offer was not found: 3186f9c2-239b-4494-b68e-6e53202420b0'
                    translations:
                      de: >-
                        Angebot wurde nicht gefunden:
                        3186f9c2-239b-4494-b68e-6e53202420b0
        '500':
          description: Internal Server Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                errors:
                  - code: INTERNAL_SERVER_ERROR
                    message: An unexpected error occurred
                    translations:
                      de: Ein unerwarteter Fehler ist aufgetreten
    put:
      tags:
        - Offers
      summary: Update an existing offer
      description: |2-

                Update an existing offer by its UUID.
                - The processing of this operation is asynchronous. For more information on asynchronous processing, see the
                  *General Notes -> Asynchronous Processing* section above.
                - It is possible that updates cannot be applied if previous changes have not yet been processed. In this case,
                  the offer will be rejected with a 406 status code and error code *OFFER_IS_BEING_PROCESSED*.
            
      operationId: updateOffer
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/OfferUpdate'
        required: true
      responses:
        '202':
          description: Offer successfully updated.
          content:
            '*/*':
              schema:
                $ref: '#/components/schemas/OfferOperationResponse'
        '400':
          description: Validation error in input data
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                errors:
                  - code: MISSING_PARAMETER
                    message: 'Attribute must be specified: Offer.customerName'
                    field: Offer.customerName
                    translations:
                      de: >-
                        Das folgende Attribute muss angegeben werden:
                        Offer.customerName
                  - code: INVALID_PARAMETER
                    message: >-
                      Attribute must be a valid email address:
                      invalid-mail-example.com
                    field: Offer.customerEmail
                    translations:
                      de: >-
                        Das Feld muss eine gültige Email-Adresse sein:
                        invalid-mail-example.com
                  - code: INVALID_PARAMETER
                    message: >-
                      Attribute 'Accessory.priceGross' must be less than the
                      'Accessory.listPriceGross'
                    field: Accessory.priceGross
                    translations:
                      de: >-
                        Der Zubehör Brutto-Preis muss geringer als die UVP des
                        Zubehörs sein: 199.99 > 119.99
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                errors:
                  - code: UNAUTHORIZED
                    message: You are not authorized to access this resource.
                    translations:
                      de: >-
                        Sie haben keine Berechtigung für die angeforderte
                        Ressource
        '403':
          description: Permission denied
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                errors:
                  - code: ACCESS_DENIED
                    message: Access denied
                    translations:
                      de: >-
                        Sie haben keine Berechtigung für die angeforderte
                        Ressource
        '404':
          description: Offer not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                errors:
                  - code: OFFER_NOT_FOUND
                    message: 'Offer was not found: 3186f9c2-239b-4494-b68e-6e53202420b0'
                    translations:
                      de: >-
                        Angebot wurde nicht gefunden:
                        3186f9c2-239b-4494-b68e-6e53202420b0
        '406':
          description: Offer is already being processed. No changes allowed.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                errors:
                  - code: OFFER_IS_BEING_PROCESSED
                    message: >-
                      Offer is currently being processed:
                      3186f9c2-239b-4494-b68e-6e53202420b0
                    translations:
                      de: >-
                        Angebot wird bereits verarbeitet:
                        3186f9c2-239b-4494-b68e-6e53202420b0
        '500':
          description: Internal Server Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                errors:
                  - code: INTERNAL_SERVER_ERROR
                    message: An unexpected error occurred
                    translations:
                      de: Ein unerwarteter Fehler ist aufgetreten
    delete:
      tags:
        - Offers
      summary: Cancel offer by UUID
      description: Cancel a specific offer by its UUID
      operationId: deleteOfferByUuid
      parameters:
        - name: id
          in: path
          description: UUID of the offer to cancel
          required: true
          schema:
            type: string
            format: uuid
            description: UUID of the offer to cancel
            example: a0ab7953-ca09-4bc6-8fee-5b003fccbb6d
          example: a0ab7953-ca09-4bc6-8fee-5b003fccbb6d
        - name: contactFromEmployeeRequested
          in: query
          required: false
          schema:
            type: boolean
      responses:
        '204':
          description: Offer canceled successfully
          content: {}
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                errors:
                  - code: UNAUTHORIZED
                    message: You are not authorized to access this resource.
                    translations:
                      de: >-
                        Sie haben keine Berechtigung für die angeforderte
                        Ressource
        '403':
          description: Permission denied
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                errors:
                  - code: ACCESS_DENIED
                    message: Access denied
                    translations:
                      de: >-
                        Sie haben keine Berechtigung für die angeforderte
                        Ressource
        '404':
          description: Offer not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                errors:
                  - code: OFFER_NOT_FOUND
                    message: 'Offer was not found: 3186f9c2-239b-4494-b68e-6e53202420b0'
                    translations:
                      de: >-
                        Angebot wurde nicht gefunden:
                        3186f9c2-239b-4494-b68e-6e53202420b0
        '500':
          description: Internal Server Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                errors:
                  - code: INTERNAL_SERVER_ERROR
                    message: An unexpected error occurred
                    translations:
                      de: Ein unerwarteter Fehler ist aufgetreten
  /v1/offers:
    get:
      tags:
        - Offers
      summary: Get all offers
      description: >-
        Get all offers belonging to the current dealer that match the specified
        filter criteria
      operationId: listOffersV1
      parameters:
        - name: creationDateFrom
          in: query
          required: false
          schema:
            type: string
            format: date-time
        - name: creationDateTo
          in: query
          required: false
          schema:
            type: string
            format: date-time
        - name: statusChangedDateFrom
          in: query
          description: >-
            The starting datetime (ISO-8601 format) for filtering offers by the
            date their status was last changed.
          required: false
          schema:
            type: string
            format: date-time
          example: '2023-10-01T00:00:00.000Z'
        - name: statusChangedDateTo
          in: query
          description: >-
            The ending datetime (ISO-8601 format) for filtering offers by the
            date their status was last changed.
          required: false
          schema:
            type: string
            format: date-time
          example: '2023-10-01T00:00:00.000Z'
        - name: status
          in: query
          description: |2-

                    Multiple comma-separated values are allowed. The filter can be negated with `!`.
                
          required: false
          schema:
            type: string
          example: pending,!rejected
        - name: externalOfferId
          in: query
          description: >-
            Multiple comma separated values allow, the filter can be negated
            with !
          required: false
          schema:
            type: string
          example: EX01,!EX02
        - name: email
          in: query
          description: >-
            Multiple comma separated values allow, the filter can be negated
            with !
          required: false
          schema:
            type: string
          example: j.dow@gmail.com,!j.dow@jobrad.com
        - name: offerNumber
          in: query
          description: >-
            Multiple comma separated values allow, the filter can be negated
            with !
          required: false
          schema:
            type: string
          example: FHA-190,!FHA-200
        - name: page
          in: query
          required: false
          schema:
            type: integer
            format: int32
            default: 1
        - name: perPage
          in: query
          required: false
          schema:
            type: integer
            format: int32
            default: 10
        - name: sortBy
          in: query
          required: false
          schema:
            type: string
            default: createdAt
            enum:
              - createdAt
              - status
              - externalOfferId
              - customerEmail
        - name: order
          in: query
          required: false
          schema:
            type: string
            default: desc
            enum:
              - asc
              - desc
      responses:
        '200':
          description: List of offers
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OfferListResponse'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                errors:
                  - code: UNAUTHORIZED
                    message: You are not authorized to access this resource.
                    translations:
                      de: >-
                        Sie haben keine Berechtigung für die angeforderte
                        Ressource
        '403':
          description: Permission denied
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                errors:
                  - code: ACCESS_DENIED
                    message: Access denied
                    translations:
                      de: >-
                        Sie haben keine Berechtigung für die angeforderte
                        Ressource
        '500':
          description: Internal Server Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                errors:
                  - code: INTERNAL_SERVER_ERROR
                    message: An unexpected error occurred
                    translations:
                      de: Ein unerwarteter Fehler ist aufgetreten
    post:
      tags:
        - Offers
      summary: Create a new offer
      description: |2-

                Submit a new offer with product and accessory information.
                - The processing of this operation is asynchronous. For more information on asynchronous processing, see the
                  *General Notes -> Asynchronous Processing* section above.
                - The offer will be created in an initial status *pending*.
            
      operationId: createNewOffer
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/Offer'
        required: true
      responses:
        '202':
          description: Offer has been accepted and saved.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OfferOperationResponse'
        '400':
          description: Validation error in input data
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                errors:
                  - code: MISSING_PARAMETER
                    message: 'Attribute must be specified: Offer.customerName'
                    field: Offer.customerName
                    translations:
                      de: >-
                        Das folgende Attribute muss angegeben werden:
                        Offer.customerName
                  - code: INVALID_PARAMETER
                    message: >-
                      Attribute must be a valid email address:
                      invalid-mail-example.com
                    field: Offer.customerEmail
                    translations:
                      de: >-
                        Das Feld muss eine gültige Email-Adresse sein:
                        invalid-mail-example.com
                  - code: INVALID_PARAMETER
                    message: >-
                      Attribute 'Accessory.priceGross' must be less than the
                      'Accessory.listPriceGross'
                    field: Accessory.priceGross
                    translations:
                      de: >-
                        Der Zubehör Brutto-Preis muss geringer als die UVP des
                        Zubehörs sein: 199.99 > 119.99
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                errors:
                  - code: UNAUTHORIZED
                    message: You are not authorized to access this resource.
                    translations:
                      de: >-
                        Sie haben keine Berechtigung für die angeforderte
                        Ressource
        '403':
          description: Permission denied
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                errors:
                  - code: ACCESS_DENIED
                    message: Access denied
                    translations:
                      de: >-
                        Sie haben keine Berechtigung für die angeforderte
                        Ressource
        '406':
          description: Accessory category is not supported - invalid Bidex code
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                errors:
                  - code: UNSUPPORTED_BIDEX_CATEGORY
                    message: 'Bidex category not supported: 102099'
                    translations:
                      de: 'Bidex Kategorie wird nicht untertützt: 102099'
        '409':
          description: Offer already exists
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                errors:
                  - code: OFFER_ALREADY_EXISTS
                    message: 'Offer already exists: 3186f9c2-239b-4494-b68e-6e53202420b0'
                    translations:
                      de: >-
                        Angebot existiert bereits:
                        3186f9c2-239b-4494-b68e-6e53202420b0
        '500':
          description: Internal Server Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                errors:
                  - code: INTERNAL_SERVER_ERROR
                    message: An unexpected error occurred
                    translations:
                      de: Ein unerwarteter Fehler ist aufgetreten
  /v1/offers/{id}/history:
    get:
      tags:
        - Offers
      summary: Get the history of an offer
      description: >-
        The offer history endpoint is used to retrieve the history of a specific
        offer based on its UUID. From a business perspective, this means that
        all changes made to an offer, along with relevant metadata (e.g., who
        made the change and when), are returned. This allows the full lifecycle
        of an offer to be tracked and understood.
      operationId: getOfferHistory
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
        - name: page
          in: query
          required: false
          schema:
            type: integer
            format: int32
            default: 1
        - name: perPage
          in: query
          required: false
          schema:
            type: integer
            format: int32
            default: 10
      responses:
        '200':
          description: Offer history retrieved successfully
          content:
            '*/*':
              schema:
                $ref: '#/components/schemas/OfferHistoryResponse'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                errors:
                  - code: UNAUTHORIZED
                    message: You are not authorized to access this resource.
                    translations:
                      de: >-
                        Sie haben keine Berechtigung für die angeforderte
                        Ressource
        '403':
          description: Permission denied
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                errors:
                  - code: ACCESS_DENIED
                    message: Access denied
                    translations:
                      de: >-
                        Sie haben keine Berechtigung für die angeforderte
                        Ressource
        '404':
          description: Offer not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                errors:
                  - code: OFFER_NOT_FOUND
                    message: 'Offer was not found: 3186f9c2-239b-4494-b68e-6e53202420b0'
                    translations:
                      de: >-
                        Angebot wurde nicht gefunden:
                        3186f9c2-239b-4494-b68e-6e53202420b0
        '500':
          description: Internal Server Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                errors:
                  - code: INTERNAL_SERVER_ERROR
                    message: An unexpected error occurred
                    translations:
                      de: Ein unerwarteter Fehler ist aufgetreten
  /v1/offers/accessories:
    get:
      tags:
        - Offers Accessories
      summary: Get all accessory categories
      description: |2-

                Retrieve all leasable accessory entries with Bidex code and category path:
                - **Bidex Codes**: Used to identify bicycles and accessories. More information about Bidex codes can be found above
                  under the section *General Notes -> Bidex Codes*.
                - **JobradExtension**: Marks JobRad-specific Bidex categories that are not part of the standard.
            
      operationId: getAccessoryCategories
      responses:
        '200':
          description: List of accessory category entries
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AccessoryCategoryResponse'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                errors:
                  - code: UNAUTHORIZED
                    message: You are not authorized to access this resource.
                    translations:
                      de: >-
                        Sie haben keine Berechtigung für die angeforderte
                        Ressource
        '403':
          description: Permission denied
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                errors:
                  - code: ACCESS_DENIED
                    message: Access denied
                    translations:
                      de: >-
                        Sie haben keine Berechtigung für die angeforderte
                        Ressource
        '500':
          description: Internal Server Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                errors:
                  - code: INTERNAL_SERVER_ERROR
                    message: An unexpected error occurred
                    translations:
                      de: Ein unerwarteter Fehler ist aufgetreten
components:
  schemas:
    Accessory:
      type: object
      description: Represents an accessory for the bicycle
      properties:
        bidexCode:
          type: string
          description: BIDEX ID of the accessory category
          example: 301520
          pattern: ^[0-9]{6}$
        brandName:
          type: string
          description: Brand name of the accessory
          example: ABUS
        modelName:
          type: string
          description: Model name of the accessory
          example: Bordo Granit XPlus 6500
        priceGross:
          type: number
          format: double
          description: >-
            Selling gross price of the accessory. Must be less than or equal to
            `listPriceGross`
          example: 99.99
        listPriceGross:
          type: number
          format: double
          description: Manufacturer's suggested retail gross price of the accessory
          example: 119.99
      required:
        - bidexCode
        - brandName
        - listPriceGross
        - modelName
        - priceGross
    OfferUpdate:
      type: object
      description: Represents an update request for an existing dealer offer
      properties:
        product:
          $ref: '#/components/schemas/Product'
          description: Represents the product information
        accessory:
          type: array
          description: List of accessories for the bicycle
          items:
            $ref: '#/components/schemas/Accessory'
        withShipping:
          type: boolean
          description: Shipping is part of the offer
        shippingCostGross:
          type: number
          format: double
          description: Gross costs for shipping
          example: 49.99
        installationCostGross:
          type: number
          format: double
          description: Gross costs for installation
          example: 29.99
        deliveryDate:
          type: string
          format: date-time
          description: Expected delivery date
          example: '2030-12-01T00:00:00.000Z'
        notes:
          type: string
          description: Additional notes for the offer
          example: Please call before delivery
      required:
        - accessory
        - product
    Product:
      type: object
      description: Represents the product information
      properties:
        bidexCode:
          type: string
          description: BIDEX ID of the accessory category
          example: 123040
          pattern: ^[0-9]{6}$
        brandName:
          type: string
          description: Name of the bicycle brand
          example: Gudereit
        modelName:
          type: string
          description: Model name of the bicycle
          example: Model X
        modelYear:
          type: string
          description: >-
            Model year of the bicycle, must be the complete year representation
            (between 1900 and 2099)
          example: 2023
        color:
          type: string
          description: Color of the bicycle
          example: Black
        size:
          type: string
          description: Frame size of the bicycle
          example: L
        driveManufacturer:
          type: string
          description: Drive manufacture Name
          example: Bosch
        batteryCapacity:
          type: string
          description: Battery capacity for e-bikes [in kilowatt-hours]
          example: 500
          pattern: ^[0-9]*$
        priceGross:
          type: number
          format: double
          description: >-
            Actual selling gross price. Must be less than or equal to
            `listPriceGross`
          example: 2900.99
        listPriceGross:
          type: number
          format: double
          description: Manufacturer's suggested retail gross price (MSRP)
          example: 2999.99
      required:
        - bidexCode
        - brandName
        - listPriceGross
        - modelName
        - modelYear
        - priceGross
    Error:
      type: object
      properties:
        code:
          type: string
          description: Code of the error
          example: SOME_ERROR
        message:
          type: string
          description: Message of the error
          example: Some error message.
        field:
          type: string
          description: >-
            The field which produced the error, if the error is related to one
            field
          example: offer.customerEmail
        translations:
          type: object
          additionalProperties:
            type: string
          description: Translations of the error message
          example:
            de: Irgendeine Fehlermeldung
      required:
        - code
        - message
        - translations
    ErrorResponse:
      type: object
      properties:
        errors:
          type: array
          items:
            $ref: '#/components/schemas/Error'
      required:
        - errors
    InfoMessage:
      type: object
      properties:
        code:
          type: string
          description: Status code for infos
          enum:
            - info
          example: info
        message:
          type: string
          description: Short description of info
          example: Bike brand name 'CUBE' mapped to 'Cube'
        field:
          type: string
          description: >-
            The field which produced the info, if the info is related to one
            field
          example: product.brandName
        translations:
          type: object
          additionalProperties:
            type: string
          description: Translations of the info message
          example:
            de: Bike-Brandname 'CUBE' wurde auf 'Cube' umgemappt.
      required:
        - code
        - message
        - translations
    OfferOperationResponse:
      type: object
      description: The response on successful offer creation or update
      example:
        data:
          id: a0ab7953-ca09-4bc6-8fee-5b003fccbb6d
          status: pending
        meta:
          timestamp: '2024-12-04T14:05:56.222858Z'
      properties:
        data:
          $ref: '#/components/schemas/OfferOperationResponseData'
          description: Offer creation or update response data
          example:
            id: a0ab7953-ca09-4bc6-8fee-5b003fccbb6d
            status: pending
        meta:
          $ref: '#/components/schemas/ResponseMeta'
          description: Offer creation or update meta data
          example:
            timestamp: '2024-12-04T14:05:56.222858Z'
      required:
        - data
        - meta
    OfferOperationResponseData:
      type: object
      properties:
        id:
          type: string
          description: The UUID of the offer
          example: a0ab7953-ca09-4bc6-8fee-5b003fccbb6d
        status:
          $ref: '#/components/schemas/OfferStatusDetail'
          description: The status of the offer
      required:
        - id
        - status
    OfferStatusDetail:
      type: object
      properties:
        current:
          $ref: '#/components/schemas/StatusMessage'
          description: The current status of the offer
        nextSteps:
          type: array
          items:
            description: Possible next status steps given the current status
            example:
              code: draft
              message: The offer is accepted, but not yet processed by JobRad.
              translations:
                de: Der bzw. die Mitarbeitende hat jetzt Ihr Angebot erhalten.
        errors:
          type: array
          description: Errors that occurred during processing.
          example: []
          items:
            $ref: '#/components/schemas/ProcessingError'
        infos:
          type: array
          description: >-
            Warnings or hints for topics that occurred during processing, but do
            not require immediate action.
          items:
            $ref: '#/components/schemas/InfoMessage'
      required:
        - current
        - nextSteps
    ProcessingError:
      type: object
      properties:
        code:
          type: string
          enum:
            - INTERNAL_SERVER_ERROR
            - CHANGE_OUTDATED
            - VALIDATION_ERROR
            - UNKNOWN_ERROR
        message:
          type: string
      required:
        - code
        - message
    ResponseMeta:
      type: object
      properties:
        timestamp:
          type: string
          format: date-time
          description: timestamp of the response
          example: '2024-12-04T14:05:56.222858Z'
      required:
        - timestamp
    StatusMessage:
      type: object
      properties:
        code:
          type: string
          description: Status code
          enum:
            - pending
            - draft
            - order_journey_started
            - employer_decision_pending
            - jobrad_decision_pending
            - done
            - canceled
            - rejected
          example: pending
        message:
          type: string
          description: Short description of status
          example: The offer is awaiting acceptance by JobRad
        translations:
          type: object
          additionalProperties:
            type: string
          description: Translations of the status message
          example:
            de: JobRad prüft gerade die Angaben Ihres Angebots.
      required:
        - code
        - message
        - translations
    Offer:
      type: object
      description: Represents a dealer offer for a JobRad with all relevant information
      properties:
        externalOfferId:
          type: string
          description: External ID for the offer
          example: external-id-123
        customerName:
          type: string
          description: Full name of the customer
          example: Max Mustermann
        customerEmail:
          type: string
          description: Customer's email address for communication
          example: max.mustermann@example.com
          pattern: >-
            ^[a-zA-Z0-9_+&*-]+(?:\.[a-zA-Z0-9_+&*-]+)*@(?:[a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}$
        product:
          $ref: '#/components/schemas/Product'
          description: Represents the product information
        accessory:
          type: array
          description: List of accessories for the bicycle
          items:
            $ref: '#/components/schemas/Accessory'
        withShipping:
          type: boolean
          description: Shipping is part of the offer
        shippingCostGross:
          type: number
          format: double
          description: Gross costs for shipping
          example: 49.99
        installationCostGross:
          type: number
          format: double
          description: Gross costs for installation
          example: 29.99
        deliveryDate:
          type: string
          format: date-time
          description: Expected delivery date
          example: '2030-12-01T00:00:00.000Z'
        notes:
          type: string
          description: Optional notes for the offer
          example: Please call before delivery
      required:
        - accessory
        - customerEmail
        - customerName
        - externalOfferId
        - product
    ListResponseMeta:
      type: object
      properties:
        totalCount:
          type: integer
          format: int32
          description: Total count of entities in the system
          example: 1
        page:
          type: integer
          format: int32
          description: Current listed page
          example: 1
        perPage:
          type: integer
          format: int32
          description: Maximum number of entities in the response
          example: 10
        sortBy:
          type: string
          description: Sorted by this attribute
          example: createdAt
        order:
          type: string
          description: The sorting order desc/asc
          example: desc
      required:
        - order
        - page
        - perPage
        - sortBy
        - totalCount
    OfferLinks:
      type: object
      description: A block of links and references related to the offer
      properties:
        portalOffer:
          type: string
          description: Link to the offer detail page in the dealer portal
          example: https://fachhandel.integration.jobrad.org/supplier/offer/detail/123
        portalOfferPdf:
          type: string
          description: Link to the offer PDF download in the dealer portal
          example: https://fachhandel.integration.jobrad.org/supplier/offer/print/123
        portalOrder:
          type: string
          description: Link to the order detail page in the dealer portal
          example: https://fachhandel.integration.jobrad.org/supplier/order/105
      required:
        - portalOffer
        - portalOfferPdf
    OfferListResponse:
      type: object
      description: The response on successful offer listing
      properties:
        data:
          type: array
          description: List of offers
          items:
            $ref: '#/components/schemas/OfferResponse'
        meta:
          $ref: '#/components/schemas/ListResponseMeta'
          description: Meta data of the offers list
      required:
        - data
        - meta
    OfferResponse:
      type: object
      description: Represents a dealer offer for a JobRad with all relevant information
      properties:
        id:
          type: string
          format: uuid
          description: The UUID of this offer
          example: 978fe89a-da63-4221-a8be-bbb743ef07bc
        externalOfferId:
          type: string
          description: External ID for the offer
          example: external-id-123
        offerNumber:
          type: string
          description: FHA number for the offer
          example: FHA-000123
        orderNumber:
          type: string
          description: LAU number for the order that was created out of this offer
          example: LAU-000105
        customerName:
          type: string
          description: Full name of the customer
          example: Max Mustermann
        customerEmail:
          type: string
          description: Customer's email address for communication
          example: max.mustermann@example.com
          pattern: >-
            ^[a-zA-Z0-9_+&*-]+(?:\.[a-zA-Z0-9_+&*-]+)*@(?:[a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}$
        product:
          $ref: '#/components/schemas/Product'
          description: Represents the product information
        accessory:
          type: array
          description: List of accessories for the bicycle
          items:
            $ref: '#/components/schemas/Accessory'
        withShipping:
          type: boolean
          description: Shipping is part of the offer
        shippingCostGross:
          type: number
          format: double
          description: Gross costs for shipping
          example: 49.99
        installationCostGross:
          type: number
          format: double
          description: Gross costs for installation
          example: 29.99
        deliveryDate:
          type: string
          format: date-time
          description: Expected delivery date
          example: '2030-12-01T00:00:00.000Z'
        notes:
          type: string
          description: Optional notes for the offer
          example: Please call before delivery
        createdAt:
          type: string
          format: date-time
          description: The date the offer was created
          example: '2030-12-01T00:00:00.000Z'
        lastUpdateAt:
          type: string
          format: date-time
          description: The last time the entity was updated by any party.
          example: '2030-12-01T00:00:00.000Z'
        status:
          $ref: '#/components/schemas/OfferStatusDetail'
          description: The status of this offer.
        links:
          $ref: '#/components/schemas/OfferLinks'
          description: Links related to the offer
      required:
        - accessory
        - createdAt
        - customerEmail
        - customerName
        - externalOfferId
        - id
        - lastUpdateAt
        - product
        - status
    OfferGetResponse:
      type: object
      properties:
        data:
          $ref: '#/components/schemas/OfferResponse'
          description: the offer
        meta:
          $ref: '#/components/schemas/ResponseMeta'
          description: Meta data of the offer
      required:
        - data
        - meta
    Offer History entry:
      type: object
      description: >-
        Represents a snapshot of an offer at a certain point in time in the
        offer history
      properties:
        id:
          type: string
          format: uuid
          description: The UUID of this offer history entry
          example: 978fe89a-da63-4221-a8be-bbb743ef07bc
        offer:
          $ref: '#/components/schemas/Offer History entry'
          description: The actual offer of this history entry
          example: 978fe89a-da63-4221-a8be-bbb743ef07bc
        origin:
          type: string
          description: The origin that triggered the change of the offer
          enum:
            - dealer_api
            - dealer_portal
            - backend
            - unknown
          example: dealer_api
        createdAt:
          type: string
          format: date-time
          description: The date the offer history entry was created
          example: '2030-12-01T00:00:00.000Z'
        status:
          $ref: '#/components/schemas/OfferStatusDetail'
          description: The status of this offer.
        offerId:
          type: string
          format: uuid
          description: The UUID of the offer
          example: 978fe89a-da63-4221-a8be-bbb743ef07bc
      required:
        - createdAt
        - id
        - offer
        - origin
        - status
    OfferHistoryResponse:
      type: object
      description: Represents the history of an offer with all relevant information
      properties:
        data:
          type: array
          description: List of history entries for an offer
          items:
            $ref: '#/components/schemas/Offer History entry'
        meta:
          $ref: '#/components/schemas/ListResponseMeta'
          description: Meta data of the history list
      required:
        - data
        - meta
    AccessoryCategory:
      type: object
      description: Represents an leasable accessory category for the dealer
      example:
        bidexCode: '301010'
        jobradCategoryDe: Alle / Zubehör / Kindertransport / Kindersitz
        jobradExtension: false
      properties:
        bidexCode:
          type: string
          description: BIDEX ID of the accessory category
          example: 202110
          pattern: ^[0-9]{6}$
        jobradCategoryDe:
          type: string
          description: Name of the accessory with category path in german
          example: Alle / Zubehör / Kindertransport / Kindersitz
        jobradExtension:
          type: boolean
          description: Flag whether this accessory category is a jobrad extension
          example: false
      required:
        - bidexCode
        - jobradCategoryDe
        - jobradExtension
    AccessoryCategoryResponse:
      type: object
      description: Response with data and meta parts
      example:
        data:
          - bidexCode: '204070'
            jobradCategoryDe: Alle / Zubehör / Sonstiges / Laufradsatz-Upgrade
            jobradExtension: true
          - bidexCode: '301010'
            jobradCategoryDe: Alle / Zubehör / Kindertransport / Kindersitz
            jobradExtension: false
        meta:
          timestamp: '2024-12-04T14:05:56.222858Z'
      properties:
        data:
          type: array
          description: Response data
          items:
            $ref: '#/components/schemas/AccessoryCategory'
        meta:
          $ref: '#/components/schemas/ResponseMeta'
          description: Response meta data
      required:
        - data
        - meta
  securitySchemes:
    oauth2:
      type: oauth2
      description: Keycloak OAuth2 Auth
      flows:
        clientCredentials:
          authorizationUrl: https://id.jobrad.org/realms/external/protocol/openid-connect/auth
          tokenUrl: https://id.jobrad.org/realms/external/protocol/openid-connect/token
          scopes:
            dealer-api: Zugang zu Fachhaendler API
