openapi: 3.1.0
x-document-control:
  stand: "09.08.2026 15:33"
  branch: main
  status: historisch
  replaced_by: ./openapi-uc00.yaml
  change_history:
    - version: "1.1.1-doc"
      date_time: "09.08.2026 15:33"
      change: "Als historischen API-Block gekennzeichnet; verbindlicher UC00-Vertrag in openapi-uc00.yaml"
      author: Codex

info:
  title: "DiveLogix360 API – Block 4: Onboarding-Wizard (UC00)"
  version: 1.1.0
  contact:
    name: DiveLogix360 Support
    url: https://divelogix360.ch
  description: |
    > **Historisch:** Dieser Block bleibt als Arbeitsstand erhalten. Fuer UC00 ist
    > `openapi-uc00.yaml` verbindlich.

    ## Block 4: Onboarding-Wizard

    Dieser Block beschreibt alle API-Endpoints für den Onboarding-Wizard
    eines neu angelegten Tenants (UC00, Screens OB-01 bis OB-06).

    **Zugriff:** Tenant-Admin des eigenen Tenants.

    ---

    ## Wizard-Schritte (OB-01 bis OB-06)

    | stepKey            | Screen | Inhalt                                 | Pflicht |
    |--------------------|--------|----------------------------------------|---------|
    | `school_profile`   | OB-01  | Schul-Profil (Name, Adresse, Logo)     | Ja      |
    | `contact_details`  | OB-02  | Kontaktdaten (Telefon, Web, E-Mail)    | Ja      |
    | `certifications`   | OB-03  | Verbände & Zertifizierungen            | Nein    |
    | `operating_hours`  | OB-04  | Öffnungszeiten                         | Nein    |
    | `first_user`       | OB-05  | Ersten Mitarbeiter einladen            | Nein    |
    | `confirmation`     | OB-06  | Zusammenfassung & Abschluss            | Ja      |

    **Reihenfolge:** Strikt sequenziell. Schritt N wird erst freigeschaltet
    wenn Schritt N-1 den Status `completed` hat. Dies wird serverseitig erzwungen
    (`step_not_unlocked`) — unabhängig von der UI.

    **Pflichtschritte:** `school_profile`, `contact_details` und `confirmation`
    müssen zwingend abgeschlossen sein bevor `POST /onboarding/complete` möglich ist.

    **Draft-Speicherung:** Jeder Schritt kann als `draft` gespeichert werden
    (Daten zwischenspeichern ohne Schritt abzuschliessen). Beim nächsten
    Öffnen des Wizards werden Draft-Daten wiederhergestellt.

    **Telefon OB-02:** Mindestens `contact_phone_fixed` oder `contact_phone_mobile`
    muss bei `action: complete` angegeben werden. Bei `action: draft` sind beide
    Felder optional.

    ---

    ## Wizard-State-Machine

    ```
    not_started → in_progress → completed
                      ↑
                   (draft)
    ```

    - `not_started` : Schritt noch nicht begonnen
    - `in_progress` : Schritt wurde begonnen (mind. 1x Draft gespeichert)
    - `completed`   : Schritt abgeschlossen, nächster Schritt freigeschaltet

    Ein abgeschlossener Schritt (`completed`) kann jederzeit wieder
    geöffnet und bearbeitet werden — der Status bleibt `completed`,
    nur die Daten werden aktualisiert.

    ---

    ## Onboarding-Gesamt-Status

    ```
    pending → in_progress → completed
    ```

    - `pending`     : Kein Schritt begonnen (frisch angelegter Tenant)
    - `in_progress` : Mind. 1 Schritt begonnen
    - `completed`   : POST /onboarding/complete erfolgreich aufgerufen

    ---

    ## Änderungshistorie
    - v1.1.0: `StepData_ContactDetails` — `phone` aufgeteilt in
      `contact_phone_fixed` + `contact_phone_mobile` (at_least_one_required)

servers:
  - url: https://api.divelogix360.ch/v1
    description: Produktion (Hetzner Cloud, Go-Live)
  - url: https://api-dev.divelogix360.ch/v1
    description: Entwicklung / Staging

tags:
  - name: Onboarding
    description: Onboarding-Wizard State & Fortschritt (Tenant-Admin)

# ============================================================
# PATHS
# ============================================================

paths:

  /onboarding/status:
    get:
      tags: [Onboarding]
      summary: Onboarding-Status abrufen
      description: |
        Gibt den vollständigen aktuellen State des Onboarding-Wizards zurück:
        Gesamt-Status, Fortschritt aller Schritte und gespeicherte Draft-Daten.

        **Verwendung:**
        - Beim Öffnen des Wizards (welcher Schritt ist aktiv?)
        - Nach einem Page-Reload (Daten wiederherstellen)
        - Für den Fortschrittsbalken in der UI

        **Tenant-Isolation:** Serverseitig via RLS erzwungen.
      operationId: getOnboardingStatus
      security:
        - BearerAuth: []
      responses:
        '200':
          description: Aktueller Onboarding-State
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OnboardingStatusResponse'
              examples:
                fresh_tenant:
                  summary: Frischer Tenant (noch nichts begonnen)
                  value:
                    tenant_id: "b7e2d4f1-1234-5678-abcd-ef0123456789"
                    status: "pending"
                    completed_at: null
                    progress:
                      total_steps: 6
                      completed_steps: 0
                      required_steps_completed: false
                    steps:
                      - step_key: "school_profile"
                        step_number: 1
                        status: "not_started"
                        is_required: true
                        is_unlocked: true
                        completed_at: null
                        data: null
                      - step_key: "contact_details"
                        step_number: 2
                        status: "not_started"
                        is_required: true
                        is_unlocked: false
                        completed_at: null
                        data: null
                      - step_key: "certifications"
                        step_number: 3
                        status: "not_started"
                        is_required: false
                        is_unlocked: false
                        completed_at: null
                        data: null
                      - step_key: "operating_hours"
                        step_number: 4
                        status: "not_started"
                        is_required: false
                        is_unlocked: false
                        completed_at: null
                        data: null
                      - step_key: "first_user"
                        step_number: 5
                        status: "not_started"
                        is_required: false
                        is_unlocked: false
                        completed_at: null
                        data: null
                      - step_key: "confirmation"
                        step_number: 6
                        status: "not_started"
                        is_required: true
                        is_unlocked: false
                        completed_at: null
                        data: null
                in_progress:
                  summary: Teilweise abgeschlossen
                  value:
                    tenant_id: "b7e2d4f1-1234-5678-abcd-ef0123456789"
                    status: "in_progress"
                    completed_at: null
                    progress:
                      total_steps: 6
                      completed_steps: 2
                      required_steps_completed: false
                    steps:
                      - step_key: "school_profile"
                        step_number: 1
                        status: "completed"
                        is_required: true
                        is_unlocked: true
                        completed_at: "2024-01-15T11:00:00Z"
                        data:
                          name: "Tauchschule Muster AG"
                          street: "Musterstrasse 12"
                          city: "Zürich"
                          postal_code: "8001"
                          country: "CH"
                          logo_url: null
                      - step_key: "contact_details"
                        step_number: 2
                        status: "completed"
                        is_required: true
                        is_unlocked: true
                        completed_at: "2024-01-15T11:05:00Z"
                        data:
                          contact_phone_fixed: "+41 44 123 45 67"
                          contact_phone_mobile: null
                          website: "https://tauchschule-muster.ch"
                          public_email: "info@tauchschule-muster.ch"
                      - step_key: "certifications"
                        step_number: 3
                        status: "not_started"
                        is_required: false
                        is_unlocked: true
                        completed_at: null
                        data: null
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /onboarding/steps/{stepKey}:
    put:
      tags: [Onboarding]
      summary: Onboarding-Schritt speichern
      description: |
        Speichert die Daten eines Onboarding-Schritts als Draft oder
        schliesst den Schritt ab (complete).

        **Draft vs. Complete:**
        - `action: "draft"` — Zwischenspeichern, keine Pflichtfeld-Validierung
        - `action: "complete"` — Volle Validierung, nächster Schritt wird freigeschaltet

        **Reihenfolge (serverseitig erzwungen):**
        Schritt kann nur gespeichert werden wenn `is_unlocked: true`.
        Andernfalls: `step_not_unlocked` (403).
      operationId: saveOnboardingStep
      security:
        - BearerAuth: []
      parameters:
        - name: stepKey
          in: path
          required: true
          schema:
            type: string
            enum:
              - school_profile
              - contact_details
              - certifications
              - operating_hours
              - first_user
              - confirmation
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SaveOnboardingStepRequest'
            examples:
              school_profile_draft:
                summary: OB-01 Schul-Profil als Draft speichern
                value:
                  action: "draft"
                  step_data:
                    name: "Tauchschule Muster AG"
                    street: "Musterstrasse 12"
                    city: "Zürich"
                    postal_code: "8001"
                    country: "CH"
                    logo_url: null
              school_profile_complete:
                summary: OB-01 Schul-Profil abschliessen
                value:
                  action: "complete"
                  step_data:
                    name: "Tauchschule Muster AG"
                    street: "Musterstrasse 12"
                    city: "Zürich"
                    postal_code: "8001"
                    country: "CH"
                    logo_url: "https://api.divelogix360.ch/v1/uploads/logos/a3f9b2c1.png"
              contact_details_complete:
                summary: OB-02 Kontaktdaten abschliessen (nur Festnetz)
                value:
                  action: "complete"
                  step_data:
                    contact_phone_fixed: "+41 44 123 45 67"
                    contact_phone_mobile: null
                    website: "https://tauchschule-muster.ch"
                    public_email: "info@tauchschule-muster.ch"
              contact_details_mobile_only:
                summary: OB-02 Kontaktdaten abschliessen (nur Mobil)
                value:
                  action: "complete"
                  step_data:
                    contact_phone_fixed: null
                    contact_phone_mobile: "+41 79 123 45 67"
                    website: null
                    public_email: null
              contact_details_draft:
                summary: OB-02 Kontaktdaten als Draft (beide Felder leer erlaubt)
                value:
                  action: "draft"
                  step_data:
                    contact_phone_fixed: null
                    contact_phone_mobile: null
                    website: null
                    public_email: null
              certifications_complete:
                summary: OB-03 Verbände abschliessen (optional)
                value:
                  action: "complete"
                  step_data:
                    associations:
                      - "PADI"
                      - "SSI"
                    certification_notes: "PADI 5-Star Dive Center seit 2018"
              operating_hours_complete:
                summary: OB-04 Öffnungszeiten abschliessen (optional)
                value:
                  action: "complete"
                  step_data:
                    hours:
                      monday:    { open: "09:00", close: "17:00", closed: false }
                      tuesday:   { open: "09:00", close: "17:00", closed: false }
                      wednesday: { open: "09:00", close: "17:00", closed: false }
                      thursday:  { open: "09:00", close: "17:00", closed: false }
                      friday:    { open: "09:00", close: "17:00", closed: false }
                      saturday:  { open: "10:00", close: "15:00", closed: false }
                      sunday:    { open: null,    close: null,    closed: true  }
              first_user_complete:
                summary: OB-05 Ersten Mitarbeiter einladen (optional)
                value:
                  action: "complete"
                  step_data:
                    invite_email: "mitarbeiter@tauchschule-muster.ch"
                    invite_first_name: "Anna"
                    invite_last_name: "Beispiel"
                    invite_role: "mitarbeiter"
              first_user_skip:
                summary: OB-05 Überspringen
                value:
                  action: "complete"
                  step_data:
                    skipped: true
              confirmation_complete:
                summary: OB-06 Zusammenfassung bestätigen
                value:
                  action: "complete"
                  step_data:
                    confirmed: true
                    terms_accepted: true
                    terms_accepted_at: "2024-01-15T11:30:00Z"
      responses:
        '200':
          description: Schritt erfolgreich gespeichert
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SaveOnboardingStepResponse'
              examples:
                draft_saved:
                  summary: Draft gespeichert
                  value:
                    step_key: "school_profile"
                    step_number: 1
                    action: "draft"
                    status: "in_progress"
                    next_step_unlocked: false
                    onboarding_status: "in_progress"
                step_completed:
                  summary: Schritt abgeschlossen
                  value:
                    step_key: "school_profile"
                    step_number: 1
                    action: "complete"
                    status: "completed"
                    next_step_unlocked: true
                    next_step_key: "contact_details"
                    onboarding_status: "in_progress"
        '400':
          description: Validierungsfehler (bei action=complete)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationErrorResponse'
              examples:
                missing_name:
                  summary: Pflichtfeld fehlt
                  value:
                    error: "validation_error"
                    message: "Pflichtfelder fehlen."
                    status: 400
                    details:
                      - field: "step_data.name"
                        code: "required"
                        message: "Name der Tauchschule ist erforderlich."
                phone_required:
                  summary: Mindestens eine Telefonnummer erforderlich
                  value:
                    error: "validation_error"
                    message: "Mindestens eine Telefonnummer ist erforderlich."
                    status: 400
                    details:
                      - field: "step_data.contact_phone"
                        code: "at_least_one_required"
                        message: "Bitte geben Sie mindestens eine Telefonnummer an (Festnetz oder Mobil)."
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          description: Schritt nicht freigeschaltet oder falscher Tenant
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                step_not_unlocked:
                  value:
                    error: "step_not_unlocked"
                    message: "Dieser Schritt ist noch nicht freigeschaltet. Bitte schliessen Sie zuerst den vorherigen Schritt ab."
                    status: 403
        '404':
          description: Ungültiger stepKey
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                error: "step_not_found"
                message: "Onboarding-Schritt 'unknown_step' existiert nicht."
                status: 404
        '500':
          $ref: '#/components/responses/InternalServerError'

  /onboarding/complete:
    post:
      tags: [Onboarding]
      summary: Onboarding abschliessen
      description: |
        Schliesst das Onboarding des Tenants ab.

        **Voraussetzungen (serverseitig geprüft):**
        - Schritt `school_profile` → Status `completed`
        - Schritt `contact_details` → Status `completed`
        - Schritt `confirmation` → Status `completed` (inkl. `terms_accepted: true`)

        **Auswirkungen:**
        - Onboarding-Status → `completed`
        - Tenant erhält vollen Zugriff auf alle aktivierten Module
        - Willkommens-Notification wird ausgelöst
        - Wizard danach schreibgeschützt

        **Idempotenz:** Bei bereits abgeschlossenem Onboarding → `409`.
      operationId: completeOnboarding
      security:
        - BearerAuth: []
      responses:
        '200':
          description: Onboarding erfolgreich abgeschlossen
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OnboardingCompleteResponse'
              example:
                tenant_id: "b7e2d4f1-1234-5678-abcd-ef0123456789"
                status: "completed"
                completed_at: "2024-01-15T11:35:00Z"
                message: "Onboarding abgeschlossen. Willkommen bei DiveLogix360!"
        '400':
          description: Pflichtschritte nicht abgeschlossen
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OnboardingCompleteErrorResponse'
              example:
                error: "required_steps_incomplete"
                message: "Nicht alle Pflichtschritte sind abgeschlossen."
                status: 400
                missing_steps:
                  - step_key: "confirmation"
                    step_number: 6
                    reason: "Schritt muss abgeschlossen sein (confirmed: true, terms_accepted: true)."
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '409':
          description: Onboarding bereits abgeschlossen
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                error: "onboarding_already_completed"
                message: "Das Onboarding wurde bereits abgeschlossen."
                status: 409
        '500':
          $ref: '#/components/responses/InternalServerError'

# ============================================================
# COMPONENTS
# ============================================================

components:

  securitySchemes:
    BearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: JWT Access Token (RS256). Rolle `tenant_admin` erforderlich.

  schemas:

    SaveOnboardingStepRequest:
      type: object
      required: [action, step_data]
      properties:
        action:
          type: string
          enum: [draft, complete]
          description: |
            `draft` — Zwischenspeichern (keine Pflichtfeld-Validierung)
            `complete` — Abschliessen (volle Validierung)
        step_data:
          type: object
          additionalProperties: true

    # ----------------------------------------------------------
    # Step Data Schemas (pro Schritt)
    # ----------------------------------------------------------

    StepData_SchoolProfile:
      type: object
      description: OB-01 – Pflichtfelder bei action=complete
      required: [name]
      properties:
        name:
          type: string
          minLength: 2
          maxLength: 100
        street:
          type: string
          maxLength: 100
          nullable: true
        city:
          type: string
          maxLength: 100
          nullable: true
        postal_code:
          type: string
          maxLength: 20
          nullable: true
        country:
          type: string
          enum: [CH, DE, AT]
        logo_url:
          type: string
          format: uri
          nullable: true

    StepData_ContactDetails:
      type: object
      description: |
        OB-02 – Kontaktdaten.

        **Telefon-Pflicht (bei action=complete):**
        Mindestens `contact_phone_fixed` oder `contact_phone_mobile` muss
        angegeben werden. Beide Felder sind optional, aber nicht beide
        gleichzeitig leer. Fehlercode: `at_least_one_required`.

        Bei `action=draft` sind beide Felder optional (kein Fehler).
      properties:
        contact_phone_fixed:
          type: string
          nullable: true
          description: Festnetznummer (international, z.B. +41 44 123 45 67)
        contact_phone_mobile:
          type: string
          nullable: true
          description: Mobilnummer (international, z.B. +41 79 123 45 67)
        website:
          type: string
          format: uri
          nullable: true
          description: Website-URL (mit https://)
        public_email:
          type: string
          format: email
          nullable: true
          description: Öffentliche Kontakt-E-Mail

    StepData_Certifications:
      type: object
      description: OB-03 – Optional
      properties:
        associations:
          type: array
          items:
            type: string
          example: ["PADI", "SSI"]
        certification_notes:
          type: string
          nullable: true
          maxLength: 500

    StepData_OperatingHours:
      type: object
      description: OB-04 – Optional
      properties:
        hours:
          type: object
          properties:
            monday:
              $ref: '#/components/schemas/DayHours'
            tuesday:
              $ref: '#/components/schemas/DayHours'
            wednesday:
              $ref: '#/components/schemas/DayHours'
            thursday:
              $ref: '#/components/schemas/DayHours'
            friday:
              $ref: '#/components/schemas/DayHours'
            saturday:
              $ref: '#/components/schemas/DayHours'
            sunday:
              $ref: '#/components/schemas/DayHours'

    DayHours:
      type: object
      properties:
        open:
          type: string
          nullable: true
          pattern: '^([0-1][0-9]|2[0-3]):[0-5][0-9]$'
          example: "09:00"
        close:
          type: string
          nullable: true
          pattern: '^([0-1][0-9]|2[0-3]):[0-5][0-9]$'
          example: "17:00"
        closed:
          type: boolean

    StepData_FirstUser:
      type: object
      description: OB-05 – Optional. skipped=true überspringt die Einladung.
      properties:
        skipped:
          type: boolean
        invite_email:
          type: string
          format: email
          nullable: true
        invite_first_name:
          type: string
          nullable: true
        invite_last_name:
          type: string
          nullable: true
        invite_role:
          type: string
          enum: [tenant_admin, mitarbeiter]
          nullable: true

    StepData_Confirmation:
      type: object
      description: OB-06 – confirmed und terms_accepted müssen true sein
      required: [confirmed, terms_accepted, terms_accepted_at]
      properties:
        confirmed:
          type: boolean
        terms_accepted:
          type: boolean
        terms_accepted_at:
          type: string
          format: date-time

    # ----------------------------------------------------------
    # Response Schemas
    # ----------------------------------------------------------

    OnboardingStatusResponse:
      type: object
      properties:
        tenant_id:
          type: string
          format: uuid
        status:
          type: string
          enum: [pending, in_progress, completed]
        completed_at:
          type: string
          format: date-time
          nullable: true
        progress:
          type: object
          properties:
            total_steps:
              type: integer
              example: 6
            completed_steps:
              type: integer
            required_steps_completed:
              type: boolean
        steps:
          type: array
          items:
            $ref: '#/components/schemas/OnboardingStepState'

    OnboardingStepState:
      type: object
      properties:
        step_key:
          type: string
          enum: [school_profile, contact_details, certifications, operating_hours, first_user, confirmation]
        step_number:
          type: integer
        status:
          type: string
          enum: [not_started, in_progress, completed]
        is_required:
          type: boolean
        is_unlocked:
          type: boolean
        completed_at:
          type: string
          format: date-time
          nullable: true
        data:
          type: object
          nullable: true
          additionalProperties: true

    SaveOnboardingStepResponse:
      type: object
      properties:
        step_key:
          type: string
        step_number:
          type: integer
        action:
          type: string
          enum: [draft, complete]
        status:
          type: string
          enum: [in_progress, completed]
        next_step_unlocked:
          type: boolean
        next_step_key:
          type: string
          nullable: true
        onboarding_status:
          type: string
          enum: [pending, in_progress, completed]

    OnboardingCompleteResponse:
      type: object
      properties:
        tenant_id:
          type: string
          format: uuid
        status:
          type: string
          enum: [completed]
        completed_at:
          type: string
          format: date-time
        message:
          type: string

    OnboardingCompleteErrorResponse:
      allOf:
        - $ref: '#/components/schemas/ErrorResponse'
        - type: object
          properties:
            missing_steps:
              type: array
              items:
                type: object
                properties:
                  step_key:
                    type: string
                  step_number:
                    type: integer
                  reason:
                    type: string

    ErrorResponse:
      type: object
      required: [error, message, status]
      properties:
        error:
          type: string
        message:
          type: string
        status:
          type: integer

    ValidationErrorResponse:
      allOf:
        - $ref: '#/components/schemas/ErrorResponse'
        - type: object
          properties:
            details:
              type: array
              items:
                type: object
                properties:
                  field:
                    type: string
                  code:
                    type: string
                  message:
                    type: string

  responses:

    Unauthorized:
      description: Nicht authentifiziert
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            error: "unauthorized"
            message: "Authentifizierung erforderlich."
            status: 401

    Forbidden:
      description: Keine Berechtigung
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            error: "forbidden"
            message: "Zugriff verweigert."
            status: 403

    InternalServerError:
      description: Interner Serverfehler
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            error: "internal_server_error"
            message: "Ein unerwarteter Fehler ist aufgetreten."
            status: 500
