openapi: 3.0.3
info:
  title: Daxium Air API
  version: v1.3
  description: |
    ## API REST Daxium Air

    Documentation officielle de l'API **Daxium Air**, version **v1.3**.

    Tous les appels doivent être chiffrés avec **TLS 1.2** ou supérieur, et chaque requête
    doit inclure l'en-tête de version :

    ```
    X-Accept-Version: v1.3
    ```

    ### Authentification

    L'API utilise **OAuth 2.0**. Chaque requête (sauf les requêtes de jeton) doit
    inclure un jeton Bearer :

    ```
    Authorization: Bearer <access_token>
    ```

    | Jeton | Durée de validité |
    |---|---|
    | `access_token` | 1 heure |
    | `refresh_token` | 42 jours |

    ### Filtrage des champs

    Tous les endpoints retournant des objets permettent de limiter les champs retournés :

    ```
    GET /{app_short}/any_route?fields=id,created_at
    ```

    ### Format des erreurs

    Toutes les erreurs retournent un corps JSON standardisé :

    ```json
    {
      "code": "DAERR-API1000",
      "message": "Invalid payload format",
      "url": "https://doc-dev.daxium-air.com/errors.en.html#DAERR-API1000",
      "api_version": "v1.3",
      "infos": [],
      "server_time": "1760616000"
    }
    ```

servers:
  - url: https://api.daxium-air.com
    description: Production

# ──────────────────────────────────── TAGS ───────────────────────────────────
tags:
  - name: auth
    x-page-title: Autorisation
    x-page-description: Obtenir et rafraîchir les jetons OAuth2
    x-page-icon: lock

  - name: apps
    x-page-title: Vertical métier
    x-page-description: Lister et récupérer les instances (vertical métier)
    x-page-icon: layer-group

  - name: files
    x-page-title: Fichiers
    x-page-description: Envoyer et télécharger des fichiers au niveau de l'instance
    x-page-icon: file

  - name: structures
    x-page-title: Formulaires
    x-page-description: Récupérer les définitions de formulaires et leurs versions
    x-page-icon: table-list

  - name: structure-relations
    x-parent: structures
    x-page-title: Relations de formulaires
    x-page-description: Récupérer les relations parent-enfant entre formulaires
    x-page-icon: sitemap

  - name: submissions
    x-page-title: Fiches
    x-page-description: Créer, lire, mettre à jour, supprimer et rechercher des fiches
    x-page-icon: database

  - name: submission-relations
    x-parent: submissions
    x-page-title: Relations de fiches
    x-page-description: Récupérer les relations parent-enfant entre fiches
    x-page-icon: sitemap

  - name: submission-comments
    x-parent: submissions
    x-page-title: Commentaires
    x-page-description: Gérer les commentaires et réponses sur les fiches
    x-page-icon: comment

  - name: customapps
    x-page-title: Applications personnalisées
    x-page-description: Récupérer les configurations des applications mobiles personnalisées
    x-page-icon: mobile

  - name: lists
    x-page-title: Listes
    x-page-description: Gérer les données de listes hiérarchiques
    x-page-icon: list

  - name: list-access
    x-parent: lists
    x-page-title: Autorisations d'accès aux listes
    x-page-description: Restreindre la visibilité des éléments de liste par utilisateur ou groupe
    x-page-icon: shield

  - name: tasks
    x-page-title: Tâches
    x-page-description: Récupérer les tâches liées aux fiches
    x-page-icon: check-square

  - name: tasks-write
    x-parent: tasks
    x-page-title: Création de tâches
    x-page-description: Créer des tâches simples ou récurrentes
    x-page-icon: calendar-plus

  - name: publipostages
    x-page-title: Publipostages
    x-page-description: Générer des documents (éditique) à partir de modèles et de fiches
    x-page-icon: envelopes-bulk

  - name: subscriptions
    x-page-title: Abonnements
    x-page-description: Lister les abonnements et licences d'un compte
    x-page-icon: ticket

  - name: groups
    x-page-title: Groupes
    x-page-description: Gérer les groupes d'un vertical métier
    x-page-icon: user-group

  - name: users
    x-page-title: Utilisateurs
    x-page-description: Gérer les utilisateurs, les licences et les accès
    x-page-icon: users

  - name: views
    x-page-title: Vues
    x-page-description: Récupérer les vues de fiches configurées
    x-page-icon: table-columns

# ──────────────────────────────────── PATHS ──────────────────────────────────
paths:

  # ── AUTORISATION ───────────────────────────────────────────────────────────
  /oauth/v2/token:
    post:
      summary: Obtenir ou rafraîchir les jetons
      description: |
        Utilisez `grant_type: password` pour obtenir des jetons pour la première fois, ou
        `grant_type: refresh_token` pour les renouveler sans ressaisir les identifiants.
        Chaque rafraîchissement réinitialise l'expiration du jeton d'accès et génère un nouveau jeton de rafraîchissement.
      tags: [auth]
      operationId: getOrRefreshToken
      requestBody:
        required: true
        content:
          application/x-www-form-urlencoded:
            schema:
              oneOf:
                - $ref: "#/components/schemas/PasswordGrantRequest"
                - $ref: "#/components/schemas/RefreshTokenGrantRequest"
      responses:
        "200":
          description: Paire de jetons générée avec succès
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TokenResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
      x-codeSamples:
        - lang: cURL
          label: Authentification par mot de passe
          source: |
            curl -X POST https://api.daxium-air.com/oauth/v2/token \
              -d "client_id=VOTRE_CLIENT_ID" \
              -d "client_secret=VOTRE_SECRET" \
              -d "grant_type=password" \
              -d "username=utilisateur@exemple.com" \
              -d "password=motdepasse"
        - lang: cURL
          label: Rafraîchissement du jeton
          source: |
            curl -X POST https://api.daxium-air.com/oauth/v2/token \
              -d "client_id=VOTRE_CLIENT_ID" \
              -d "client_secret=VOTRE_SECRET" \
              -d "grant_type=refresh_token" \
              -d "refresh_token=VOTRE_REFRESH_TOKEN"

  # ── VERTICAL MÉTIER ────────────────────────────────────────────────────────
  /apps:
    get:
      summary: Lister toutes les instances
      description: Retourne toutes les instances accessibles par l'utilisateur authentifié.
      tags: [apps]
      operationId: listApps
      parameters:
        - $ref: "#/components/parameters/XAcceptVersion"
      responses:
        "200":
          description: Tableau des instances
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/App"
      x-codeSamples:
        - lang: cURL
          label: CLI
          source: |
            curl -H "Authorization: Bearer $TOKEN" \
                 -H "X-Accept-Version: v1.3" \
                 https://api.daxium-air.com/apps

  /{app_short}:
    get:
      summary: Récupérer une instance précise
      tags: [apps]
      operationId: getApp
      parameters:
        - $ref: "#/components/parameters/AppShort"
        - $ref: "#/components/parameters/XAcceptVersion"
      responses:
        "200":
          description: Détails de l'instance
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/App"

  # ── FICHIERS ────────────────────────────────────────────────────────────────
  /{app_short}/files/upload:
    post:
      summary: Envoyer un fichier
      description: |
        Envoie un fichier au niveau de l'instance. Une fois lié à une fiche, le fichier
        ne peut plus être réutilisé. Envoyez le binaire brut dans le corps de la requête
        avec le bon en-tête `Content-Type`.
      tags: [files]
      operationId: uploadFile
      parameters:
        - $ref: "#/components/parameters/AppShort"
      requestBody:
        required: true
        content:
          application/octet-stream:
            schema:
              type: string
              format: binary
      responses:
        "200":
          description: Fichier envoyé avec succès
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FileUploadResponse"

  /{app_short}/files/download_errors/{worker_unique_id}:
    get:
      summary: Télécharger un fichier d'erreurs
      description: Retourne le fichier d'erreurs généré par un worker en arrière-plan (ex. job d'import).
      tags: [files]
      operationId: downloadErrorFile
      parameters:
        - $ref: "#/components/parameters/AppShort"
        - name: worker_unique_id
          in: path
          required: true
          description: Identifiant unique du worker (UUID)
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: Contenu binaire du fichier
          content:
            application/octet-stream:
              schema:
                type: string
                format: binary

  # ── FORMULAIRES ─────────────────────────────────────────────────────────────
  /{app_short}/structures:
    get:
      summary: Lister tous les formulaires
      description: Retourne tous les formulaires disponibles avec pagination.
      tags: [structures]
      operationId: listStructures
      parameters:
        - $ref: "#/components/parameters/AppShort"
        - name: per_page
          in: query
          description: Nombre de formulaires par page (défaut 10)
          schema:
            type: integer
            default: 10
        - name: page
          in: query
          description: Numéro de page (base 0)
          schema:
            type: integer
            default: 0
        - name: updated_since
          in: query
          description: Ne retourner que les formulaires mis à jour depuis ce timestamp UNIX
          schema:
            type: integer
            format: int64
      responses:
        "200":
          description: Liste paginée de formulaires
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/StructureListResponse"

  /{app_short}/structures/{id}:
    get:
      summary: Récupérer un formulaire
      description: Retourne le formulaire à la version indiquée, ou la dernière version si omise.
      tags: [structures]
      operationId: getStructure
      parameters:
        - $ref: "#/components/parameters/AppShort"
        - name: id
          in: path
          required: true
          description: Identifiant du formulaire
          schema:
            type: integer
        - name: version
          in: query
          description: Numéro de version du formulaire
          schema:
            type: integer
      responses:
        "200":
          description: Objet formulaire
          content:
            application/json:
              schema:
                type: object
                properties:
                  structure:
                    $ref: "#/components/schemas/Structure"
                  server_time:
                    type: integer
                    format: int64

  /{app_short}/structures/{id}/file/{file_id}:
    get:
      summary: Récupérer un fichier lié à un formulaire
      description: Retourne le fichier lié au formulaire (actuellement uniquement les images des champs Logo).
      tags: [structures]
      operationId: getStructureFile
      parameters:
        - $ref: "#/components/parameters/AppShort"
        - name: id
          in: path
          required: true
          description: Identifiant du formulaire
          schema:
            type: integer
        - name: file_id
          in: path
          required: true
          description: Identifiant du fichier (UUID)
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: Contenu de l'image
          content:
            image/jpeg:
              schema:
                type: string
                format: binary

  # ── RELATIONS DE FORMULAIRES ────────────────────────────────────────────────
  /{app_short}/structure_relations/{structure_id}/parents:
    get:
      summary: Récupérer les formulaires parents d'un formulaire
      description: Retourne les identifiants des formulaires parents du formulaire enfant indiqué.
      tags: [structure-relations]
      operationId: getStructureParents
      parameters:
        - $ref: "#/components/parameters/AppShort"
        - name: structure_id
          in: path
          required: true
          description: Identifiant du formulaire enfant
          schema:
            type: string
        - name: per_page
          in: query
          description: Nombre d'identifiants par page
          schema:
            type: integer
        - name: page
          in: query
          description: Numéro de la page demandée
          schema:
            type: integer
        - name: updated_since
          in: query
          description: Filtre les résultats à partir de cette date de mise à jour (timestamp)
          schema:
            type: integer
            format: int64
      responses:
        "200":
          description: Liste paginée des identifiants de formulaires parents
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/StructureRelationPagedResponse"

  # ── RELATIONS DE FICHES ─────────────────────────────────────────────────────
  /{app_short}/submission_relations/{submission_uuid}/parents:
    get:
      summary: Récupérer les fiches parentes d'une fiche
      description: Retourne les identifiants des fiches parentes de la fiche enfant indiquée.
      tags: [submission-relations]
      operationId: getSubmissionParents
      parameters:
        - $ref: "#/components/parameters/AppShort"
        - name: submission_uuid
          in: path
          required: true
          description: Identifiant de la fiche enfant (UUID)
          schema:
            type: string
            format: uuid
        - name: updated_since
          in: query
          description: Filtre les résultats à partir de cette date de mise à jour (timestamp)
          schema:
            type: integer
            format: int64
      responses:
        "200":
          description: Liste paginée des identifiants de fiches parentes
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SubmissionRelationPagedResponse"

  # ── FICHES ──────────────────────────────────────────────────────────────────
  /{app_short}/submissions:
    get:
      summary: Lister les fiches d'un formulaire
      description: |
        Retourne les fiches d'un formulaire avec pagination.
        Définissez `reference=true` pour retourner toutes les données de référence
        (omettez alors `structure_id`).
      tags: [submissions]
      operationId: listSubmissions
      parameters:
        - $ref: "#/components/parameters/AppShort"
        - name: structure_id
          in: query
          description: Identifiant du formulaire (obligatoire sauf si `reference=true`)
          schema:
            type: integer
        - name: per_page
          in: query
          description: Nombre de fiches par page
          schema:
            type: integer
        - name: page
          in: query
          description: Numéro de la page demandée
          schema:
            type: integer
        - name: updated_since
          in: query
          description: Filtre les fiches mises à jour depuis ce timestamp UNIX
          schema:
            type: integer
            format: int64
        - name: items_expanded
          in: query
          description: Retourner les libellés des listes au lieu des identifiants
          schema:
            type: boolean
        - name: reference
          in: query
          description: "Définir à `true` pour retourner toutes les données de référence (listes de référence partagées par tous les formulaires, utilisées pour pré-remplir des champs). Omettez `structure_id` dans ce cas."
          schema:
            type: boolean
      responses:
        "200":
          description: Fiches paginées
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SubmissionListResponse"
      x-codeSamples:
        - lang: cURL
          label: Par formulaire
          source: |
            curl -H "Authorization: Bearer $TOKEN" \
                 -H "X-Accept-Version: v1.3" \
                 "https://api.daxium-air.com/daxium-test/submissions?structure_id=1"

    post:
      summary: Créer une fiche
      description: |
        Crée une nouvelle fiche. Pour les champs de type `image`, `signature` ou `file`,
        envoyez d'abord le fichier via l'endpoint dédié et utilisez l'UUID retourné ici.
      tags: [submissions]
      operationId: createSubmission
      parameters:
        - $ref: "#/components/parameters/AppShort"
        - name: partial
          in: query
          description: Si `true`, ignore les permissions de champs (requis / modifiable)
          schema:
            type: boolean
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SubmissionInput"
      responses:
        "200":
          description: Fiche créée
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Submission"
      x-codeSamples:
        - lang: cURL
          label: CLI
          source: |
            curl -X POST \
                 -H "Authorization: Bearer $TOKEN" \
                 -H "X-Accept-Version: v1.3" \
                 -H "Content-Type: application/json" \
                 -d '{"structure_id":1,"structure_version":3,"items":{"string4":"Bonjour"}}' \
                 https://api.daxium-air.com/daxium-test/submissions

  /{app_short}/submissions/byids:
    post:
      summary: Récupérer des fiches par identifiants (jusqu'à 500)
      description: |
        Retourne les fiches correspondant aux UUIDs fournis. Maximum 500 identifiants par requête.
        Chaque identifiant peut optionnellement porter un filtre `updated_since`.
      tags: [submissions]
      operationId: getSubmissionsByIds
      parameters:
        - $ref: "#/components/parameters/AppShort"
        - name: items_expanded
          in: query
          description: Retourner les libellés des listes au lieu des identifiants
          schema:
            type: boolean
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [submission_ids]
              properties:
                submission_ids:
                  type: array
                  maxItems: 500
                  items:
                    type: object
                    required: [id]
                    properties:
                      id:
                        type: string
                        format: uuid
                      updated_since:
                        type: integer
                        format: int64
                        description: Ignorer cette fiche si non modifiée depuis ce timestamp
      responses:
        "200":
          description: Fiches correspondantes
          content:
            application/json:
              schema:
                type: object
                properties:
                  submissions:
                    type: array
                    items:
                      $ref: "#/components/schemas/Submission"
        "400":
          description: Trop d'identifiants (max 500)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /{app_short}/submissions/search:
    post:
      summary: Rechercher des fiches
      description: |
        Retourne les fiches correspondant aux critères de recherche par champ.
        Chaque champ ne peut apparaître qu'une seule fois. La casse des opérateurs est significative.

        | Opérateur | Types de champs compatibles |
        |---|---|
        | `equal` / `notequal` | texte, nombre, booléen, date, email, téléphone |
        | `isNull` / `isNotNull` | tous |
        | `less` / `greater` / `lesseq` / `greatereq` | nombre |
        | `contains` / `notContains` / `startWith` / `endWith` | texte, email, téléphone |
        | `in` | liste, utilisateur, texte |
        | `range` | nombre, date |
      tags: [submissions]
      operationId: searchSubmissions
      parameters:
        - $ref: "#/components/parameters/AppShort"
        - name: structure_id
          in: query
          required: true
          description: Identifiant du formulaire
          schema:
            type: integer
        - name: per_page
          in: query
          description: Nombre de fiches par page
          schema:
            type: integer
        - name: page
          in: query
          description: Numéro de la page demandée
          schema:
            type: integer
        - name: last_updated_user_id
          in: query
          description: "Filtre les fiches dont la dernière modification a été effectuée par l'utilisateur ayant cet identifiant. Critère optionnel passé directement dans l'URL."
          schema:
            type: integer
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SearchCriteria"
      responses:
        "200":
          description: Fiches correspondantes
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SubmissionListResponse"

  /{app_short}/submissions/multiple:
    post:
      summary: Créer plusieurs fiches (asynchrone)
      description: Crée jusqu'à 100 fiches de manière asynchrone. Interrogez le `callbackUrl` pour suivre l'avancement.
      tags: [submissions]
      operationId: createMultipleSubmissions
      parameters:
        - $ref: "#/components/parameters/AppShort"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: array
              maxItems: 100
              items:
                $ref: "#/components/schemas/SubmissionInput"
      responses:
        "200":
          description: Job asynchrone accepté
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CallbackUrlResponse"
        "400":
          description: Corps vide ou plus de 100 fiches
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

    put:
      summary: Mettre à jour plusieurs fiches (asynchrone)
      description: Met à jour jusqu'à 100 fiches de manière asynchrone. Interrogez le `callbackUrl` pour suivre l'avancement.
      tags: [submissions]
      operationId: updateMultipleSubmissions
      parameters:
        - $ref: "#/components/parameters/AppShort"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: array
              maxItems: 100
              items:
                $ref: "#/components/schemas/SubmissionInput"
      responses:
        "200":
          description: Job asynchrone accepté
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CallbackUrlResponse"
        "400":
          description: Corps vide ou plus de 100 fiches
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /{app_short}/submissions/multiple/callback/{callback_id}:
    get:
      summary: Consulter le statut d'un job asynchrone de fiches
      description: Retourne le statut courant et les résultats par fiche d'un job de création ou de mise à jour multiple.
      tags: [submissions]
      operationId: getMultipleSubmissionsCallback
      parameters:
        - $ref: "#/components/parameters/AppShort"
        - name: callback_id
          in: path
          required: true
          description: Identifiant de callback retourné par l'endpoint asynchrone
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: Statut et détails du job
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SubmissionCallbackResponse"

  /{app_short}/submissions/upload:
    post:
      summary: Envoyer un fichier pour une fiche
      description: |
        Envoie un fichier à lier à un champ de fiche (image, signature ou fichier).
        Une fois lié, le fichier ne peut plus être réutilisé dans une autre fiche.
        Utilisez l'`uuid` retourné dans le champ correspondant.
      tags: [submissions]
      operationId: uploadSubmissionFile
      parameters:
        - $ref: "#/components/parameters/AppShort"
      requestBody:
        required: true
        content:
          application/octet-stream:
            schema:
              type: string
              format: binary
      responses:
        "200":
          description: Fichier envoyé — utilisez l'`uuid` retourné dans le champ de la fiche
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FileUploadResponse"

  /{app_short}/submissions/upload/{submissionId}/{fileId}:
    post:
      summary: Envoyer un fichier avec son identifiant et celui de la fiche
      description: Alternative à l'envoi simple — précisez directement l'UUID de la fiche et l'UUID du fichier.
      tags: [submissions]
      operationId: uploadSubmissionFileWithIds
      parameters:
        - $ref: "#/components/parameters/AppShort"
        - name: submissionId
          in: path
          required: true
          description: Identifiant de la fiche (UUID)
          schema:
            type: string
            format: uuid
        - name: fileId
          in: path
          required: true
          description: Identifiant du fichier (UUID)
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          application/octet-stream:
            schema:
              type: string
              format: binary
      responses:
        "200":
          description: Fichier envoyé
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FileUploadResponse"

  /{app_short}/submissions/comments:
    post:
      summary: Récupérer les commentaires de plusieurs fiches
      description: Retourne les commentaires pour jusqu'à **1000** identifiants de fiches en une seule requête.
      tags: [submission-comments]
      operationId: getCommentsBySubmissionIds
      parameters:
        - $ref: "#/components/parameters/AppShort"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [submission_ids]
              properties:
                submission_ids:
                  type: array
                  maxItems: 1000
                  description: Liste des UUIDs de fiches dont récupérer les commentaires
                  items:
                    type: string
                    format: uuid
      responses:
        "200":
          description: Commentaires groupés par UUID de fiche
          content:
            application/json:
              schema:
                type: object
                additionalProperties:
                  type: array
                  items:
                    $ref: "#/components/schemas/Comment"
        "400":
          description: Trop d'identifiants de fiches — max 1000 (DAERR-COM1010)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /{app_short}/submissions/{id}:
    get:
      summary: Récupérer une fiche
      tags: [submissions]
      operationId: getSubmission
      parameters:
        - $ref: "#/components/parameters/AppShort"
        - name: id
          in: path
          required: true
          description: Identifiant de la fiche (UUID)
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: Objet fiche
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Submission"

    put:
      summary: Mettre à jour une fiche
      description: |
        Remplace les données d'une fiche. Pour les formulaires avec workflow, incluez
        `trigger_id` pour faire avancer l'état. Les fiches dans un état final ne peuvent
        plus être modifiées.
      tags: [submissions]
      operationId: updateSubmission
      parameters:
        - $ref: "#/components/parameters/AppShort"
        - name: id
          in: path
          required: true
          description: Identifiant de la fiche (UUID)
          schema:
            type: string
            format: uuid
        - name: partial
          in: query
          description: Si `true`, ignore les permissions de champs (requis / modifiable)
          schema:
            type: boolean
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SubmissionInput"
      responses:
        "200":
          description: Fiche mise à jour
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Submission"

    patch:
      summary: Mettre à jour une fiche partiellement
      description: Met à jour uniquement les `items` fournis. Tous les autres champs restent inchangés.
      tags: [submissions]
      operationId: patchSubmission
      parameters:
        - $ref: "#/components/parameters/AppShort"
        - name: id
          in: path
          required: true
          description: Identifiant de la fiche (UUID)
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SubmissionInput"
      responses:
        "200":
          description: Fiche complète mise à jour retournée
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Submission"

    delete:
      summary: Supprimer une fiche
      tags: [submissions]
      operationId: deleteSubmission
      parameters:
        - $ref: "#/components/parameters/AppShort"
        - name: id
          in: path
          required: true
          description: Identifiant de la fiche (UUID)
          schema:
            type: string
            format: uuid
      responses:
        "204":
          description: Fiche supprimée

  /{app_short}/submissions/{id}/file/{file_id}:
    get:
      summary: Récupérer un fichier lié à une fiche
      tags: [submissions]
      operationId: getSubmissionFile
      parameters:
        - $ref: "#/components/parameters/AppShort"
        - name: id
          in: path
          required: true
          description: Identifiant de la fiche (UUID)
          schema:
            type: string
            format: uuid
        - name: file_id
          in: path
          required: true
          description: Identifiant du fichier (UUID)
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: Contenu binaire du fichier
          content:
            application/octet-stream:
              schema:
                type: string
                format: binary

  # ── COMMENTAIRES DE FICHES ──────────────────────────────────────────────────
  /{app_short}/submissions/{id}/comments/count:
    get:
      summary: Compter les commentaires d'une fiche
      description: Retourne le nombre total de commentaires liés à une fiche.
      tags: [submission-comments]
      operationId: countSubmissionComments
      parameters:
        - $ref: "#/components/parameters/AppShort"
        - name: id
          in: path
          required: true
          description: Identifiant de la fiche (UUID)
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: Nombre total de commentaires
          content:
            application/json:
              schema:
                type: object
                properties:
                  count:
                    type: integer
                    description: Nombre total de commentaires liés à la fiche

  /{app_short}/submissions/{id}/comments:
    get:
      summary: Lister les commentaires d'une fiche
      description: Retourne tous les commentaires (et leurs réponses) de la fiche indiquée.
      tags: [submission-comments]
      operationId: listSubmissionComments
      parameters:
        - $ref: "#/components/parameters/AppShort"
        - name: id
          in: path
          required: true
          description: Identifiant de la fiche (UUID)
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: Liste des commentaires (réponses incluses)
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/Comment"
        "403":
          description: Accès à la fiche interdit (DAERR-COM1000)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "404":
          description: Fiche introuvable (DAERR-COM1006)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

    post:
      summary: Créer un commentaire sur une fiche
      description: |
        Ajoute un nouveau commentaire. Texte brut uniquement — pas de HTML ni de balises script.
        Maximum **1000 caractères**. Accepte un UUID optionnel fourni par le client.
      tags: [submission-comments]
      operationId: createSubmissionComment
      parameters:
        - $ref: "#/components/parameters/AppShort"
        - name: id
          in: path
          required: true
          description: Identifiant de la fiche (UUID)
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CommentInput"
      responses:
        "200":
          description: Commentaire créé
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Comment"
        "400":
          description: |
            Erreur de validation — message manquant (DAERR-COM1002), trop long
            (DAERR-COM1003), date invalide (DAERR-COM1004), caractères invalides
            (DAERR-COM1005), UUID invalide (DAERR-COM1009), ou limite de commentaires
            atteinte (DAERR-COM1012)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "403":
          description: Accès interdit (DAERR-COM1000)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /{app_short}/submissions/{id}/comments/{comment_id}:
    put:
      summary: Mettre à jour un commentaire
      description: |
        Modifie le texte d'un commentaire. Un utilisateur ne peut modifier
        que ses propres commentaires (DAERR-COM1008).
      tags: [submission-comments]
      operationId: updateSubmissionComment
      parameters:
        - $ref: "#/components/parameters/AppShort"
        - name: id
          in: path
          required: true
          description: Identifiant de la fiche (UUID)
          schema:
            type: string
            format: uuid
        - name: comment_id
          in: path
          required: true
          description: Identifiant du commentaire (UUID)
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CommentInput"
      responses:
        "200":
          description: Commentaire mis à jour
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Comment"
        "403":
          description: Modification non autorisée (DAERR-COM1008)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "404":
          description: Commentaire introuvable (DAERR-COM1001)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

    delete:
      summary: Supprimer un commentaire
      description: |
        Supprime un commentaire. Un utilisateur ne peut supprimer
        que ses propres commentaires (DAERR-COM1007).
      tags: [submission-comments]
      operationId: deleteSubmissionComment
      parameters:
        - $ref: "#/components/parameters/AppShort"
        - name: id
          in: path
          required: true
          description: Identifiant de la fiche (UUID)
          schema:
            type: string
            format: uuid
        - name: comment_id
          in: path
          required: true
          description: Identifiant du commentaire (UUID)
          schema:
            type: string
            format: uuid
      responses:
        "204":
          description: Commentaire supprimé
        "403":
          description: Suppression non autorisée (DAERR-COM1007)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "404":
          description: Commentaire introuvable (DAERR-COM1001)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /{app_short}/submissions/{id}/comments/{comment_id}/replies:
    post:
      summary: Répondre à un commentaire
      description: |
        Ajoute une réponse à un commentaire de premier niveau (profondeur maximale : 2).
        Répondre à une réponse n'est pas autorisé (DAERR-COM1011).
      tags: [submission-comments]
      operationId: replyToSubmissionComment
      parameters:
        - $ref: "#/components/parameters/AppShort"
        - name: id
          in: path
          required: true
          description: Identifiant de la fiche (UUID)
          schema:
            type: string
            format: uuid
        - name: comment_id
          in: path
          required: true
          description: Identifiant du commentaire parent (UUID)
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CommentInput"
      responses:
        "200":
          description: Réponse créée
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Comment"
        "403":
          description: Impossible de répondre à un commentaire de second niveau (DAERR-COM1011)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  # ── APPLICATIONS PERSONNALISÉES ─────────────────────────────────────────────
  /{app_short}/customapps:
    get:
      summary: Lister les applications personnalisées
      description: |
        Retourne toutes les applications personnalisées au format court.
        Si aucune n'est configurée, retourne l'application par défaut au format complet.
      tags: [customapps]
      operationId: listCustomApps
      parameters:
        - $ref: "#/components/parameters/AppShort"
      responses:
        "200":
          description: Liste des applications personnalisées
          content:
            application/json:
              schema:
                type: object
                properties:
                  apps:
                    type: array
                    items:
                      $ref: "#/components/schemas/CustomAppSummary"
                  total_count:
                    type: integer
                  total_pages:
                    type: integer
                  server_time:
                    type: integer
                    format: int64

  /{app_short}/customapps/{app_id}:
    get:
      summary: Récupérer une application personnalisée
      description: Retourne une application personnalisée par son identifiant.
      tags: [customapps]
      operationId: getCustomApp
      parameters:
        - $ref: "#/components/parameters/AppShort"
        - name: app_id
          in: path
          required: true
          description: Identifiant de l'application personnalisée
          schema:
            type: integer
      responses:
        "200":
          description: Détails de l'application personnalisée
          content:
            application/json:
              schema:
                type: object
                properties:
                  app:
                    $ref: "#/components/schemas/CustomApp"
                  total_count:
                    type: integer
                  total_pages:
                    type: integer
                  server_time:
                    type: integer
                    format: int64
                  required:
                    type: object
                    properties:
                      files_uuid:
                        type: array
                        items:
                          type: string
                          format: uuid

  /{app_short}/customapps/{app_id}/file/{file_uuid}:
    get:
      summary: Récupérer un fichier d'une application personnalisée
      tags: [customapps]
      operationId: getCustomAppFile
      parameters:
        - $ref: "#/components/parameters/AppShort"
        - name: app_id
          in: path
          required: true
          description: Identifiant de l'application personnalisée
          schema:
            type: integer
        - name: file_uuid
          in: path
          required: true
          description: Identifiant du fichier (UUID)
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: Contenu binaire du fichier
          content:
            application/octet-stream:
              schema:
                type: string
                format: binary

  # ── LISTES ──────────────────────────────────────────────────────────────────
  /{app_short}/lists:
    get:
      summary: Lister toutes les listes (identifiants racine uniquement)
      description: Retourne tous les identifiants racine des listes de l'instance, sans leurs éléments enfants.
      tags: [lists]
      operationId: listLists
      parameters:
        - $ref: "#/components/parameters/AppShort"
        - name: per_page
          in: query
          description: Nombre de listes par page (défaut 10, maximum 1000)
          schema:
            type: integer
            default: 10
            maximum: 1000
        - name: page
          in: query
          description: Numéro de page (base 0)
          schema:
            type: integer
            default: 0
        - name: updated_since
          in: query
          description: Ne retourner que les listes modifiées après cette date (y compris supprimées)
          schema:
            type: integer
            format: int64
      responses:
        "200":
          description: Entrées racine paginées
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ListPagedResponse"

    post:
      summary: Créer une liste
      description: Crée un nœud racine avec des enfants hiérarchiques optionnels via `items`.
      tags: [lists]
      operationId: createList
      parameters:
        - $ref: "#/components/parameters/AppShort"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ListCreateRequest"
      responses:
        "201":
          description: Nœud racine créé
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ListItem"

  /{app_short}/lists/import:
    post:
      summary: Importer une liste depuis un CSV (asynchrone)
      description: |
        Importe ou synchronise les données d'une liste depuis un fichier CSV préalablement envoyé.
        Interrogez le `callbackUrl` retourné pour suivre l'avancement.
      tags: [lists]
      operationId: importList
      parameters:
        - $ref: "#/components/parameters/AppShort"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [list_id, file_id]
              properties:
                list_id:
                  type: integer
                  description: Identifiant de la liste cible
                file_id:
                  type: string
                  format: uuid
                  description: UUID du fichier (via l'endpoint d'envoi de fichier)
                separator:
                  type: string
                  default: ";"
                  description: Séparateur de colonnes du CSV
                mode:
                  type: string
                  enum: [APPEND, DIFF]
                  description: Mode d'import
                encoding:
                  type: string
                  enum: [UTF-8, WINDOWS-1252, WINDOWS-1256]
                  default: UTF-8
                  description: Encodage du fichier
      responses:
        "200":
          description: Job asynchrone accepté
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CallbackUrlResponse"
        "400":
          description: Erreur de validation (file_id manquant, encodage incorrect, fichier introuvable, etc.)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /{app_short}/lists/updated:
    post:
      summary: Récupérer les identifiants de listes et d'autorisations modifiés
      description: Retourne les identifiants des listes et autorisations d'accès nécessitant une synchronisation.
      tags: [lists]
      operationId: getUpdatedLists
      parameters:
        - $ref: "#/components/parameters/AppShort"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                list_states:
                  type: array
                  items:
                    type: object
                    properties:
                      list_id:
                        type: integer
                        description: Identifiant de la liste
                      list_updated_since:
                        type: integer
                        format: int64
                        description: Détecter les modifications de liste depuis ce timestamp
                      access_set_updated_since:
                        type: integer
                        format: int64
                        description: Détecter les modifications d'autorisation depuis ce timestamp
      responses:
        "200":
          description: Identifiants nécessitant une mise à jour
          content:
            application/json:
              schema:
                type: object
                properties:
                  lists_to_update:
                    type: array
                    items:
                      type: integer
                  lists_with_access_set:
                    type: array
                    items:
                      type: integer
                  access_set_to_update:
                    type: array
                    items:
                      type: integer

  /{app_short}/lists/callback/{callback_id}:
    get:
      summary: Consulter le statut d'un job asynchrone de liste
      description: Retourne le statut courant d'un job d'import de liste.
      tags: [lists]
      operationId: getListCallback
      parameters:
        - $ref: "#/components/parameters/AppShort"
        - name: callback_id
          in: path
          required: true
          description: Identifiant du callback (UUID)
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: Statut du job
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ListCallbackResponse"

  /{app_short}/lists/{list_id}:
    get:
      summary: Récupérer une liste avec tous ses éléments
      description: Retourne la liste et tous ses éléments enfants avec pagination.
      tags: [lists]
      operationId: getList
      parameters:
        - $ref: "#/components/parameters/AppShort"
        - name: list_id
          in: path
          required: true
          description: Identifiant du nœud racine
          schema:
            type: integer
        - name: per_page
          in: query
          description: Nombre d'éléments par page (maximum 1000)
          schema:
            type: integer
            maximum: 1000
        - name: page
          in: query
          description: Numéro de la page demandée
          schema:
            type: integer
        - name: updated_since
          in: query
          description: Ne retourner que les éléments modifiés après cette date (y compris supprimés)
          schema:
            type: integer
            format: int64
      responses:
        "200":
          description: Éléments de liste paginés
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ListItemPagedResponse"

    delete:
      summary: Supprimer une liste complète
      description: Supprime définitivement le nœud racine et tous ses enfants. La cible doit être un nœud racine.
      tags: [lists]
      operationId: deleteList
      parameters:
        - $ref: "#/components/parameters/AppShort"
        - name: list_id
          in: path
          required: true
          description: Identifiant du nœud racine
          schema:
            type: integer
      responses:
        "204":
          description: Liste supprimée

  /{app_short}/lists/{root_id}:
    patch:
      summary: Modifier le nom d'éléments de liste
      description: |
        Met à jour le `name` d'un ou plusieurs éléments identifiés par leur `id`.
        Envoyez un tableau d'objets `{id, name}`.
      tags: [lists]
      operationId: editListItems
      parameters:
        - $ref: "#/components/parameters/AppShort"
        - name: root_id
          in: path
          required: true
          description: Identifiant du nœud racine
          schema:
            type: integer
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: array
              items:
                type: object
                required: [id, name]
                properties:
                  id:
                    type: integer
                    description: Identifiant de l'élément à modifier
                  name:
                    type: string
                    description: Nouveau nom affiché
      responses:
        "200":
          description: Éléments mis à jour
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ListItemPagedResponse"

  /{app_short}/lists/{list_id}/selectables:
    get:
      summary: Récupérer les identifiants sélectionnables d'une liste
      description: Retourne tous les identifiants d'éléments que l'utilisateur authentifié peut sélectionner, selon les autorisations d'accès.
      tags: [lists]
      operationId: getListSelectables
      parameters:
        - $ref: "#/components/parameters/AppShort"
        - name: list_id
          in: path
          required: true
          description: Identifiant de la liste
          schema:
            type: integer
      responses:
        "200":
          description: Identifiants sélectionnables
          content:
            application/json:
              schema:
                type: object
                properties:
                  selectable_ids:
                    type: array
                    items:
                      type: integer
                  server_time:
                    type: integer
                    format: int64

  /{app_short}/lists/{root_id}/search:
    post:
      summary: Rechercher des éléments de liste par identifiant externe
      tags: [lists]
      operationId: searchListItems
      parameters:
        - $ref: "#/components/parameters/AppShort"
        - name: root_id
          in: path
          required: true
          description: Identifiant du nœud racine
          schema:
            type: integer
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [external_id]
              properties:
                external_id:
                  type: string
                  description: Identifiant externe recherché
      responses:
        "200":
          description: Éléments correspondants
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/ListItem"

  /{app_short}/lists/{root_id}/child/{parent_id}:
    get:
      summary: Récupérer les enfants directs d'un nœud
      description: Retourne les enfants de premier niveau du nœud parent indiqué.
      tags: [lists]
      operationId: getListChildren
      parameters:
        - $ref: "#/components/parameters/AppShort"
        - name: root_id
          in: path
          required: true
          description: Identifiant du nœud racine
          schema:
            type: integer
        - name: parent_id
          in: path
          required: true
          description: Identifiant du nœud parent
          schema:
            type: integer
      responses:
        "200":
          description: Éléments enfants directs
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ListItemPagedResponse"

  /{app_short}/lists/{root_id}/{list_id}:
    put:
      summary: Mettre à jour un élément de liste
      description: Met à jour les propriétés d'un élément de liste.
      tags: [lists]
      operationId: updateListItem
      parameters:
        - $ref: "#/components/parameters/AppShort"
        - name: root_id
          in: path
          required: true
          description: Identifiant du nœud racine
          schema:
            type: integer
        - name: list_id
          in: path
          required: true
          description: Identifiant de l'élément de liste
          schema:
            type: integer
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ListItemUpdateRequest"
      responses:
        "200":
          description: Élément mis à jour
          content:
            application/json:
              schema:
                type: array
                items: {}

    patch:
      summary: Ajouter des enfants en masse à un nœud
      description: Ajoute plusieurs éléments enfants à un nœud parent en une seule requête.
      tags: [lists]
      operationId: addListChildren
      parameters:
        - $ref: "#/components/parameters/AppShort"
        - name: root_id
          in: path
          required: true
          description: Identifiant du nœud racine
          schema:
            type: integer
        - name: list_id
          in: path
          required: true
          description: Identifiant du nœud parent
          schema:
            type: integer
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: array
              items:
                $ref: "#/components/schemas/ListItemUpdateRequest"
      responses:
        "200":
          description: Enfants ajoutés
          content:
            application/json:
              schema:
                type: array
                items: {}

    delete:
      summary: Supprimer un élément de liste
      description: Supprime l'élément et tous ses enfants de manière récursive.
      tags: [lists]
      operationId: deleteListItem
      parameters:
        - $ref: "#/components/parameters/AppShort"
        - name: root_id
          in: path
          required: true
          description: Identifiant du nœud racine
          schema:
            type: integer
        - name: list_id
          in: path
          required: true
          description: Identifiant de l'élément de liste
          schema:
            type: integer
      responses:
        "204":
          description: Élément supprimé

  /{app_short}/lists/{root_id}/{list_id}/image:
    get:
      summary: Récupérer l'image d'un élément de liste
      description: Retourne l'image de l'élément — uniquement si `has_image` est `true`.
      tags: [lists]
      operationId: getListItemImage
      parameters:
        - $ref: "#/components/parameters/AppShort"
        - name: root_id
          in: path
          required: true
          description: Identifiant du nœud racine
          schema:
            type: integer
        - name: list_id
          in: path
          required: true
          description: Identifiant de l'élément de liste
          schema:
            type: integer
      responses:
        "200":
          description: Contenu de l'image
          content:
            image/jpeg:
              schema:
                type: string
                format: binary

  /{app_short}/lists/{root_id}/{node_to_move_id}/{direction}/{node_position_id}:
    put:
      summary: Changer la position d'un nœud
      description: |
        Déplace un nœud par rapport à un nœud de référence.

        | `direction` | Effet |
        |---|---|
        | `child` | Devient le premier enfant du nœud de référence |
        | `before` | Positionné juste avant le nœud de référence (même parent) |
        | `after` | Positionné juste après le nœud de référence (même parent) |
      tags: [lists]
      operationId: moveListNode
      parameters:
        - $ref: "#/components/parameters/AppShort"
        - name: root_id
          in: path
          required: true
          description: Identifiant racine de la liste à modifier
          schema:
            type: integer
        - name: node_to_move_id
          in: path
          required: true
          description: Identifiant du nœud à déplacer
          schema:
            type: integer
        - name: direction
          in: path
          required: true
          description: Direction de placement par rapport au nœud de référence
          schema:
            type: string
            enum: [child, before, after]
        - name: node_position_id
          in: path
          required: true
          description: Identifiant du nœud de référence
          schema:
            type: integer
      responses:
        "200":
          description: Nœud repositionné

  # ── AUTORISATIONS D'ACCÈS AUX LISTES ────────────────────────────────────────
  /{app_short}/list/accessset:
    get:
      summary: Lister toutes les autorisations d'accès
      description: Retourne toutes les autorisations d'accès définies dans l'instance.
      tags: [list-access]
      operationId: listAccessSets
      parameters:
        - $ref: "#/components/parameters/AppShort"
      responses:
        "200":
          description: Toutes les autorisations d'accès
          content:
            application/json:
              schema:
                type: object
                properties:
                  listaccesssets:
                    type: array
                    items:
                      $ref: "#/components/schemas/ListAccessSetSummary"
                  total_count:
                    type: integer
                  total_pages:
                    type: integer
                  server_time:
                    type: integer
                    format: int64

  /{app_short}/list/{root_id}/accessset:
    post:
      summary: Créer une autorisation d'accès
      description: Crée une nouvelle autorisation d'accès restreignant la visibilité d'éléments de liste.
      tags: [list-access]
      operationId: createAccessSet
      parameters:
        - $ref: "#/components/parameters/AppShort"
        - name: root_id
          in: path
          required: true
          description: Identifiant du nœud racine de la liste
          schema:
            type: integer
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [name]
              properties:
                name:
                  type: string
                  description: Nom de l'autorisation d'accès
                groups:
                  type: array
                  items:
                    type: integer
                  description: Identifiants des groupes à inclure
                users:
                  type: array
                  items:
                    type: integer
                  description: Identifiants des utilisateurs à inclure
                lists:
                  type: array
                  items:
                    type: integer
                  description: Identifiants des éléments de liste à restreindre
      responses:
        "200":
          description: Autorisation d'accès créée
          content:
            application/json:
              schema:
                type: object
                properties:
                  id:
                    type: string
                    format: uuid

  /{app_short}/list/{list_id}/accessset/user/{user_id}:
    get:
      summary: Récupérer les autorisations d'accès pour une liste et un utilisateur
      description: Retourne les identifiants de toutes les autorisations définies pour une liste et un utilisateur donnés.
      tags: [list-access]
      operationId: getAccessSetsForUser
      parameters:
        - $ref: "#/components/parameters/AppShort"
        - name: list_id
          in: path
          required: true
          description: Identifiant de la liste
          schema:
            type: integer
        - name: user_id
          in: path
          required: true
          description: Identifiant de l'utilisateur
          schema:
            type: integer
      responses:
        "200":
          description: UUIDs des autorisations d'accès pour cet utilisateur et cette liste
          content:
            application/json:
              schema:
                type: array
                items:
                  type: string
                  format: uuid

  /{app_short}/list/{list_id}/accessset/{list_access_set_id}:
    get:
      summary: Récupérer les détails d'une autorisation d'accès
      description: Retourne les détails complets d'une autorisation d'accès.
      tags: [list-access]
      operationId: getAccessSet
      parameters:
        - $ref: "#/components/parameters/AppShort"
        - name: list_id
          in: path
          required: true
          description: Identifiant de la liste
          schema:
            type: integer
        - name: list_access_set_id
          in: path
          required: true
          description: Identifiant de l'autorisation d'accès (UUID)
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: Détails complets de l'autorisation d'accès
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ListAccessSetDetail"

    delete:
      summary: Supprimer une autorisation d'accès
      tags: [list-access]
      operationId: deleteAccessSet
      parameters:
        - $ref: "#/components/parameters/AppShort"
        - name: list_id
          in: path
          required: true
          description: Identifiant de la liste
          schema:
            type: integer
        - name: list_access_set_id
          in: path
          required: true
          description: Identifiant de l'autorisation d'accès (UUID)
          schema:
            type: string
            format: uuid
      responses:
        "204":
          description: Autorisation d'accès supprimée

  /{app_short}/list/{list_id}/accessset/{list_access_set_id}/generate:
    post:
      summary: Générer les dépendances d'une autorisation d'accès
      description: |
        Recalcule immédiatement toutes les dépendances internes de l'autorisation d'accès.
        S'exécute automatiquement toutes les 5 minutes ; à appeler manuellement après des modifications en lot.
      tags: [list-access]
      operationId: generateAccessSetDependencies
      parameters:
        - $ref: "#/components/parameters/AppShort"
        - name: list_id
          in: path
          required: true
          description: Identifiant de la liste
          schema:
            type: integer
        - name: list_access_set_id
          in: path
          required: true
          description: Identifiant de l'autorisation d'accès (UUID)
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: Dépendances régénérées

  /{app_short}/list/{list_id}/accessset/{list_access_set_id}/{entity}:
    get:
      summary: Récupérer les entités d'une autorisation d'accès
      description: |
        Retourne les utilisateurs, groupes ou éléments de liste appartenant à l'autorisation.
        Utilisez `users`, `groups` ou `lists` comme valeur du paramètre `entity`.
      tags: [list-access]
      operationId: getAccessSetEntity
      parameters:
        - $ref: "#/components/parameters/AppShort"
        - name: list_id
          in: path
          required: true
          description: Identifiant de la liste
          schema:
            type: integer
        - name: list_access_set_id
          in: path
          required: true
          description: Identifiant de l'autorisation d'accès (UUID)
          schema:
            type: string
            format: uuid
        - name: entity
          in: path
          required: true
          description: Type d'entité à retourner
          schema:
            type: string
            enum: [users, groups, lists]
      responses:
        "200":
          description: Tableau du type d'entité demandé
          content:
            application/json:
              schema:
                type: array
                items: {}

  /{app_short}/list/{list_id}/accessset/{list_access_set_id}/{entity}/{entity_id}:
    put:
      summary: Ajouter une entité à une autorisation d'accès
      description: Ajoute un utilisateur, groupe ou élément de liste à l'autorisation d'accès.
      tags: [list-access]
      operationId: addAccessSetEntity
      parameters:
        - $ref: "#/components/parameters/AppShort"
        - name: list_id
          in: path
          required: true
          description: Identifiant de la liste
          schema:
            type: integer
        - name: list_access_set_id
          in: path
          required: true
          description: Identifiant de l'autorisation d'accès (UUID)
          schema:
            type: string
            format: uuid
        - name: entity
          in: path
          required: true
          description: Type d'entité
          schema:
            type: string
            enum: [users, groups, lists]
        - name: entity_id
          in: path
          required: true
          description: Identifiant de l'utilisateur, groupe ou élément à ajouter
          schema:
            type: integer
      responses:
        "200":
          description: Entité ajoutée

    delete:
      summary: Retirer une entité d'une autorisation d'accès
      description: Retire un utilisateur, groupe ou élément de liste de l'autorisation d'accès.
      tags: [list-access]
      operationId: deleteAccessSetEntity
      parameters:
        - $ref: "#/components/parameters/AppShort"
        - name: list_id
          in: path
          required: true
          description: Identifiant de la liste
          schema:
            type: integer
        - name: list_access_set_id
          in: path
          required: true
          description: Identifiant de l'autorisation d'accès (UUID)
          schema:
            type: string
            format: uuid
        - name: entity
          in: path
          required: true
          description: Type d'entité
          schema:
            type: string
            enum: [users, groups, lists]
        - name: entity_id
          in: path
          required: true
          description: Identifiant de l'utilisateur, groupe ou élément à retirer
          schema:
            type: integer
      responses:
        "204":
          description: Entité retirée

  # ── TÂCHES ──────────────────────────────────────────────────────────────────
  /{app_short}/tasks:
    get:
      summary: Lister les tâches
      description: |
        Retourne les tâches filtrées par utilisateur, formulaire et/ou plage de dates.
        Tous les paramètres de date sont des timestamps UNIX.
        Utilisez `me` pour retourner uniquement les tâches de l'utilisateur authentifié.
      tags: [tasks]
      operationId: listTasks
      parameters:
        - $ref: "#/components/parameters/AppShort"
        - name: structure_id
          in: query
          description: Filtrer par identifiant de formulaire
          schema:
            type: string
        - name: greater_start_date
          in: query
          description: Date de début supérieure ou égale à ce timestamp
          schema:
            type: integer
            format: int64
        - name: lower_start_date
          in: query
          description: Date de début inférieure ou égale à ce timestamp
          schema:
            type: integer
            format: int64
        - name: greater_due_date
          in: query
          description: Date d'échéance supérieure ou égale à ce timestamp
          schema:
            type: integer
            format: int64
        - name: lower_due_date
          in: query
          description: Date d'échéance inférieure ou égale à ce timestamp
          schema:
            type: integer
            format: int64
        - name: user_id
          in: query
          description: Filtrer par identifiant d'utilisateur
          schema:
            type: integer
        - name: me
          in: query
          description: Si présent, retourner uniquement les tâches de l'utilisateur authentifié
          schema:
            type: boolean
        - name: page
          in: query
          description: Numéro de la page demandée
          schema:
            type: integer
        - name: per_page
          in: query
          description: Nombre de tâches par page
          schema:
            type: integer
      responses:
        "200":
          description: Liste paginée de tâches
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TaskListResponse"
      x-codeSamples:
        - lang: cURL
          label: Mes tâches
          source: |
            curl -H "Authorization: Bearer $TOKEN" \
                 -H "X-Accept-Version: v1.3" \
                 "https://api.daxium-air.com/daxium-test/tasks?me=true"
        - lang: cURL
          label: Par plage de dates
          source: |
            curl -H "Authorization: Bearer $TOKEN" \
                 -H "X-Accept-Version: v1.3" \
                 "https://api.daxium-air.com/daxium-test/tasks?greater_start_date=1700000000&lower_due_date=1702000000"

    post:
      summary: Créer une tâche (simple ou récurrente)
      description: |
        Crée une tâche liée à un modèle de fiche (`submission_model_id`) ou à une liste de
        fiches (`submissions`). Pour une tâche **récurrente**, ajoutez l'objet `recurrency` ;
        dans ce cas `due_date` n'est pas obligatoire.
      tags: [tasks-write]
      operationId: createTask
      parameters:
        - $ref: "#/components/parameters/AppShort"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/TaskInput"
      responses:
        "200":
          description: Tâche créée
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Task"

  # ── PUBLIPOSTAGES ───────────────────────────────────────────────────────────
  /{app_short}/report_templates:
    get:
      summary: Lister les modèles de publipostage
      description: |
        Retourne les modèles de publipostage disponibles pour un formulaire (`structure`)
        ou pour une fiche (`submission`). Fournissez l'un **ou** l'autre paramètre.
      tags: [publipostages]
      operationId: listReportTemplates
      parameters:
        - $ref: "#/components/parameters/AppShort"
        - name: structure
          in: query
          description: Identifiant du formulaire dont récupérer les modèles
          schema:
            type: integer
        - name: submission
          in: query
          description: Identifiant (UUID) de la fiche dont récupérer les modèles
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: Modèles de publipostage disponibles
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ReportTemplateListResponse"

  /{app_short}/reports:
    get:
      summary: Lister les publipostages d'une fiche
      description: Retourne tous les publipostages générés pour une fiche donnée.
      tags: [publipostages]
      operationId: listReportsBySubmission
      parameters:
        - $ref: "#/components/parameters/AppShort"
        - name: submission
          in: query
          required: true
          description: Identifiant (UUID) de la fiche
          schema:
            type: string
            format: uuid
        - name: per_page
          in: query
          description: Nombre de publipostages par page (défaut 10, maximum 1000)
          schema:
            type: integer
        - name: page
          in: query
          description: Numéro de la page demandée
          schema:
            type: integer
        - name: updated_since
          in: query
          description: Filtre les publipostages mis à jour après cette date (timestamp)
          schema:
            type: integer
            format: int64
      responses:
        "200":
          description: Liste paginée des publipostages
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ReportListResponse"

    post:
      summary: Lancer un publipostage
      description: |
        Démarre la génération d'un publipostage pour une fiche à partir d'un modèle.
        L'URL `api_callback` est appelée (méthode PUT) une fois la génération terminée ;
        `{publipostage_id}` y est remplacé par l'identifiant du publipostage créé.
      tags: [publipostages]
      operationId: startReport
      parameters:
        - $ref: "#/components/parameters/AppShort"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ReportInput"
      responses:
        "200":
          description: Publipostage créé
          content:
            application/json:
              schema:
                type: object
                properties:
                  report_id:
                    type: integer
                    description: Identifiant du publipostage créé

  /{app_short}/reports/{report_id}:
    get:
      summary: Récupérer les informations d'un publipostage
      description: Retourne le statut et les fichiers générés d'un publipostage par son identifiant.
      tags: [publipostages]
      operationId: getReport
      parameters:
        - $ref: "#/components/parameters/AppShort"
        - name: report_id
          in: path
          required: true
          description: Identifiant du publipostage
          schema:
            type: integer
      responses:
        "200":
          description: Informations du publipostage
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Report"

  /{app_short}/reports/{report_id}/results/{file_uuid}:
    get:
      summary: Télécharger le fichier généré d'un publipostage
      description: Retourne le fichier (binaire) généré par un publipostage à partir de son UUID.
      tags: [publipostages]
      operationId: getReportResultFile
      parameters:
        - $ref: "#/components/parameters/AppShort"
        - name: report_id
          in: path
          required: true
          description: Identifiant du publipostage
          schema:
            type: integer
        - name: file_uuid
          in: path
          required: true
          description: UUID du fichier généré
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: Contenu binaire du fichier généré
          content:
            application/octet-stream:
              schema:
                type: string
                format: binary

  # ── ABONNEMENTS ─────────────────────────────────────────────────────────────
  /subscriptions:
    get:
      summary: Lister les abonnements d'un compte
      description: |
        Retourne tous les abonnements liés au compte. Un utilisateur, pour se connecter,
        doit disposer d'une licence web et/ou mobile rattachée à un abonnement.
        Cet endpoint n'est pas préfixé par `{app_short}`.
      tags: [subscriptions]
      operationId: listSubscriptions
      responses:
        "200":
          description: Liste des abonnements disponibles
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SubscriptionListResponse"

  # ── GROUPES ──────────────────────────────────────────────────────────────────
  /{app_short}/groups:
    get:
      summary: Lister les groupes d'un vertical métier
      description: Retourne tous les groupes du vertical métier. Nécessite des droits administrateur.
      tags: [groups]
      operationId: listGroups
      parameters:
        - $ref: "#/components/parameters/AppShort"
      responses:
        "200":
          description: Liste des groupes
          content:
            application/json:
              schema:
                type: object
                properties:
                  groups:
                    type: array
                    items:
                      $ref: "#/components/schemas/Group"
                  total_count:
                    type: integer
                  total_pages:
                    type: integer
                  server_time:
                    type: integer
                    format: int64

  /{app_short}/group:
    post:
      summary: Créer un groupe
      tags: [groups]
      operationId: createGroup
      parameters:
        - $ref: "#/components/parameters/AppShort"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/GroupInput"
      responses:
        "200":
          description: Groupe créé
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Group"

  /{app_short}/group/{group_id}:
    put:
      summary: Mettre à jour un groupe
      tags: [groups]
      operationId: updateGroup
      parameters:
        - $ref: "#/components/parameters/AppShort"
        - name: group_id
          in: path
          required: true
          description: Identifiant du groupe
          schema:
            type: integer
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/GroupInput"
      responses:
        "200":
          description: Groupe mis à jour
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Group"

  # ── UTILISATEURS ─────────────────────────────────────────────────────────────
  /users:
    get:
      summary: Lister tous les utilisateurs
      description: Retourne tous les utilisateurs du compte. Cet endpoint n'est pas préfixé par `{app_short}`.
      tags: [users]
      operationId: listAllUsers
      responses:
        "200":
          description: Liste des utilisateurs
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/UserListResponse"

  /users/me:
    get:
      summary: Récupérer l'utilisateur courant
      description: Retourne l'utilisateur authentifié ainsi que le compte (société) associé.
      tags: [users]
      operationId: getCurrentUser
      responses:
        "200":
          description: Utilisateur courant et société
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CurrentUserResponse"

  /users/pushkey:
    post:
      summary: Envoyer une clé push
      description: Enregistre une clé de notification push pour un appareil de l'utilisateur.
      tags: [users]
      operationId: sendPushKey
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/PushKeyInput"
      responses:
        "200":
          description: Clé push enregistrée
          content:
            application/json:
              schema:
                type: object

  /{app_short}/users:
    get:
      summary: Lister les utilisateurs d'un vertical métier (ou rechercher par e-mail)
      description: |
        Retourne les utilisateurs du vertical métier. Si le paramètre `username`
        est fourni, retourne l'utilisateur unique correspondant à cet e-mail.
      tags: [users]
      operationId: listAppUsers
      parameters:
        - $ref: "#/components/parameters/AppShort"
        - name: username
          in: query
          description: Adresse e-mail pour récupérer un utilisateur précis
          schema:
            type: string
            format: email
      responses:
        "200":
          description: Liste d'utilisateurs, ou utilisateur unique si `username` est fourni
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: "#/components/schemas/UserListResponse"
                  - $ref: "#/components/schemas/User"

    post:
      summary: Créer un utilisateur
      description: |
        Crée un utilisateur. Fournissez `plainPassword` pour une authentification Daxium,
        ou `ssoProvider` pour une authentification via le fournisseur SSO de la société.
      tags: [users]
      operationId: createUser
      parameters:
        - $ref: "#/components/parameters/AppShort"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UserInput"
      responses:
        "200":
          description: Utilisateur créé
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/User"
        "400":
          description: Données invalides (mot de passe, ssoProvider, groupes ou abonnement invalides)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "409":
          description: L'adresse e-mail (username) existe déjà
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /{app_short}/users/multiple:
    post:
      summary: Créer plusieurs utilisateurs (asynchrone)
      description: Crée jusqu'à 100 utilisateurs de manière asynchrone. Interrogez le `callbackUrl` retourné.
      tags: [users]
      operationId: createMultipleUsers
      parameters:
        - $ref: "#/components/parameters/AppShort"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: array
              maxItems: 100
              items:
                $ref: "#/components/schemas/UserInput"
      responses:
        "200":
          description: Job asynchrone accepté
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CallbackUrlResponse"
        "400":
          description: Corps vide ou plus de 100 utilisateurs
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

    put:
      summary: Mettre à jour plusieurs utilisateurs (asynchrone)
      description: Met à jour jusqu'à 100 utilisateurs de manière asynchrone. Interrogez le `callbackUrl` retourné.
      tags: [users]
      operationId: updateMultipleUsers
      parameters:
        - $ref: "#/components/parameters/AppShort"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: array
              maxItems: 100
              items:
                $ref: "#/components/schemas/UserInput"
      responses:
        "200":
          description: Job asynchrone accepté
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CallbackUrlResponse"
        "400":
          description: Corps vide ou plus de 100 utilisateurs
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /{app_short}/users/multiple/callback/{callback_id}:
    get:
      summary: Consulter le statut d'un job asynchrone d'utilisateurs
      tags: [users]
      operationId: getMultipleUsersCallback
      parameters:
        - $ref: "#/components/parameters/AppShort"
        - name: callback_id
          in: path
          required: true
          description: Identifiant de callback (UUID)
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: Statut et résultats par utilisateur
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/UsersCallbackResponse"

  /{app_short}/users/{user_id}:
    get:
      summary: Récupérer un utilisateur par son identifiant
      tags: [users]
      operationId: getUser
      parameters:
        - $ref: "#/components/parameters/AppShort"
        - name: user_id
          in: path
          required: true
          description: Identifiant de l'utilisateur
          schema:
            type: integer
      responses:
        "200":
          description: Objet utilisateur
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/User"

    patch:
      summary: Mettre à jour un utilisateur
      tags: [users]
      operationId: updateUser
      parameters:
        - $ref: "#/components/parameters/AppShort"
        - name: user_id
          in: path
          required: true
          description: Identifiant de l'utilisateur
          schema:
            type: integer
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UserInput"
      responses:
        "200":
          description: Utilisateur mis à jour
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/User"

  # ── VUES ─────────────────────────────────────────────────────────────────────
  /{app_short}/submissions_views/{view_id}:
    get:
      summary: Récupérer une vue
      description: |
        Retourne une vue de fiches configurée : mode d'affichage, champs ordonnés,
        et règles de filtrage (sur les items et sur les métadonnées).
      tags: [views]
      operationId: getView
      parameters:
        - $ref: "#/components/parameters/AppShort"
        - name: view_id
          in: path
          required: true
          description: Identifiant de la vue (UUID)
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: Objet vue
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/View"

# ────────────────────────────────── COMPONENTS ───────────────────────────────
components:

  parameters:
    AppShort:
      name: app_short
      in: path
      required: true
      description: Nom court de l'instance (ex. `daxium-test`)
      schema:
        type: string
    XAcceptVersion:
      name: X-Accept-Version
      in: header
      required: true
      description: Version de l'API à cibler
      schema:
        type: string
        default: v1.3
        enum: [v1.3]

  securitySchemes:
    BearerAuth:
      type: http
      scheme: bearer

  responses:
    Unauthorized:
      description: Jeton invalide ou expiré
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"

  schemas:

    # ── Erreur ────────────────────────────────────────────────────────────────
    ErrorResponse:
      type: object
      properties:
        code:
          type: string
          example: DAERR-API1000
          description: Code d'erreur unique préfixé par `DAERR-`
        message:
          type: string
          description: Description lisible de l'erreur
        url:
          type: string
          format: uri
          description: Lien vers la documentation détaillée de l'erreur
        api_version:
          type: string
          example: v1.3
        infos:
          type: array
          items: {}
          description: Informations contextuelles supplémentaires (peut être vide)
        server_time:
          type: string
          description: Timestamp Unix sous forme de chaîne

    # ── Authentification ───────────────────────────────────────────────────────
    PasswordGrantRequest:
      type: object
      required: [client_id, client_secret, grant_type, username, password]
      properties:
        client_id:
          type: string
          description: Identifiant d'application OAuth2
        client_secret:
          type: string
          description: Clé secrète OAuth2
        grant_type:
          type: string
          enum: [password]
        username:
          type: string
          format: email
          description: Adresse e-mail de l'utilisateur
        password:
          type: string
          format: password
          description: Mot de passe de l'utilisateur

    RefreshTokenGrantRequest:
      type: object
      required: [client_id, client_secret, grant_type, refresh_token]
      properties:
        client_id:
          type: string
          description: Identifiant d'application OAuth2
        client_secret:
          type: string
          description: Clé secrète OAuth2
        grant_type:
          type: string
          enum: [refresh_token]
        refresh_token:
          type: string
          description: Jeton de rafraîchissement

    TokenResponse:
      type: object
      properties:
        access_token:
          type: string
          description: "À utiliser dans l'en-tête `Authorization: Bearer`"
        refresh_token:
          type: string
          description: À conserver pour le rafraîchissement des jetons
        expires_in:
          type: integer
          description: Durée de validité du jeton d'accès en secondes (3600)
        token_type:
          type: string
          example: bearer
        scope:
          type: string

    # ── Instances ──────────────────────────────────────────────────────────────
    App:
      type: object
      properties:
        name:
          type: string
          description: Nom visible pour l'utilisateur
        short:
          type: string
          description: Nom court à utiliser comme préfixe `{app_short}` dans les appels suivants
        description:
          type: string
          description: Description renseignée par l'administrateur
        settings:
          type: object
          additionalProperties: true
          description: Paramètres supplémentaires de l'instance
        android_map:
          type: boolean
          description: Indique si les cartes sont activées sur Android
        is_model:
          type: boolean
          description: Vrai si l'instance est un modèle de vertical métier

    # ── Fichiers ───────────────────────────────────────────────────────────────
    FileUploadResponse:
      type: object
      properties:
        uuid:
          type: string
          format: uuid
          description: Référencez cet UUID dans les champs de fiche de type image/signature/fichier
        mime-type:
          type: string
          description: Type MIME du fichier
        size:
          type: integer
          description: Taille du fichier en octets

    # ── Formulaires ────────────────────────────────────────────────────────────
    Structure:
      type: object
      properties:
        id:
          type: integer
          description: Identifiant du formulaire
        version:
          type: integer
          description: Numéro de version
        name:
          type: string
          description: Nom affiché
        created_at:
          type: integer
          format: int64
          description: Date de création (timestamp UNIX)
        updated_at:
          type: integer
          format: int64
          description: Date de mise à jour (timestamp UNIX)
        settings:
          type: object
          additionalProperties: true
          description: Paramètres du formulaire
        conditions:
          type: array
          items:
            $ref: "#/components/schemas/Condition"
          description: Liste des conditions
        fields:
          type: array
          items:
            $ref: "#/components/schemas/Field"
          description: Liste des champs
        layout:
          type: object
          description: Pages et positions des champs
          properties:
            pages:
              type: array
              items: {}
            fields:
              type: array
              items: {}
        workflows:
          type: array
          items:
            $ref: "#/components/schemas/Workflow"
          description: Workflows du formulaire (si présents)
        triggers:
          type: array
          items:
            $ref: "#/components/schemas/Trigger"
          description: Personnalisation des transitions du workflow

    StructureListResponse:
      type: object
      properties:
        structures:
          type: array
          items:
            $ref: "#/components/schemas/Structure"
          description: Liste des formulaires
        total_count:
          type: integer
          description: Nombre total de formulaires
        total_pages:
          type: integer
          description: Nombre total de pages
        next_page:
          type: integer
          description: Numéro de la page suivante
        server_time:
          type: integer
          format: int64
          description: Date courante du serveur (timestamp UNIX)
        workflows:
          type: array
          items:
            $ref: "#/components/schemas/Workflow"
          description: Workflows utilisés dans ces formulaires
        required:
          type: object
          description: Formulaires et listes liés aux résultats
          properties:
            linked_list_ids:
              type: array
              items:
                type: integer
            structure_ids:
              type: array
              items:
                type: integer

    Field:
      type: object
      description: Définition d'un champ de formulaire
      properties:
        name:
          type: string
          description: Nom système du champ — utilisé comme clé dans les `items` d'une fiche
        label:
          type: string
          description: Nom affiché
        position:
          type: integer
          description: Position dans le formulaire
        type:
          type: string
          description: >
            Type de champ : text, number, boolean, date, duration, phone, email,
            location, image, signature, file, list, relation, user, label…
        enable:
          type: boolean
          description: Champ activé ou non
        required:
          type: boolean
          description: Champ requis ou non
        prefill:
          type: boolean
          description: Peut être pré-rempli sur le mobile
        readonly:
          type: boolean
          description: Non modifiable sur le mobile
        searchable:
          type: boolean
          description: Peut être utilisé dans la recherche

    Condition:
      type: object
      description: Condition de visibilité d'un champ
      properties:
        uuid:
          type: string
          format: uuid
          description: Identifiant de la condition
        name:
          type: string
          description: Nom de la condition
        cdt:
          type: string
          description: Condition au format JavaScript
        fields:
          type: array
          items:
            type: string
          description: Champs utilisés dans la condition
        json_format:
          type: array
          description: Condition au format JSON
          items:
            type: object
            properties:
              field:
                type: string
              value:
                type: string
              operator:
                type: string

    Workflow:
      type: object
      description: Workflow d'un formulaire
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
          description: Nom du workflow
        created_at:
          type: integer
          format: int64
          description: Date de création (timestamp UNIX)
        updated_at:
          type: integer
          format: int64
          description: Date de mise à jour (timestamp UNIX)
        start_state:
          type: string
          format: uuid
          description: État initial du workflow
        states:
          type: array
          items:
            $ref: "#/components/schemas/WorkflowState"
          description: Liste des états
        transitions:
          type: array
          items:
            $ref: "#/components/schemas/WorkflowTransition"
          description: Liste des transitions

    WorkflowState:
      type: object
      description: État d'un workflow
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
          description: Nom affiché
        color:
          type: string
          description: Couleur hexadécimale (ex. `#FF9800`)
        end:
          type: boolean
          description: Si vrai, les fiches dans cet état ne peuvent plus être modifiées

    WorkflowTransition:
      type: object
      description: Transition entre deux états
      properties:
        id:
          type: string
          format: uuid
        from:
          type: string
          format: uuid
          description: État source
        to:
          type: string
          format: uuid
          description: État cible
        label:
          type: string
          description: Libellé de la transition

    Trigger:
      type: object
      description: Personnalise l'apparence d'une transition de workflow sous forme de bouton
      properties:
        id:
          type: string
          format: uuid
        type:
          type: string
        transition_id:
          type: string
          format: uuid
          description: Identifiant de la transition associée
        color:
          type: string
          description: Couleur du bouton
        label:
          type: string
          description: Libellé du bouton

    # ── Relations ──────────────────────────────────────────────────────────────
    StructureRelationPagedResponse:
      type: object
      properties:
        structure_relations:
          type: array
          items:
            type: object
            properties:
              id:
                type: string
                format: uuid
          description: Identifiants des formulaires parents
        total_count:
          type: integer
        total_pages:
          type: integer
        next_page:
          type: integer
        server_time:
          type: integer
          format: int64

    SubmissionRelationPagedResponse:
      type: object
      properties:
        submission_relations:
          type: array
          items:
            type: object
            properties:
              id:
                type: string
                format: uuid
          description: Identifiants des fiches parentes
        total_count:
          type: integer
        total_pages:
          type: integer
        next_page:
          type: integer
        server_time:
          type: integer
          format: int64

    # ── Fiches ─────────────────────────────────────────────────────────────────
    Submission:
      type: object
      properties:
        id:
          type: string
          format: uuid
          description: Identifiant de la fiche
        structure_id:
          type: integer
          description: Identifiant du formulaire
        structure_version:
          type: integer
          description: Version du formulaire
        created_at:
          type: integer
          format: int64
          description: Date de création (timestamp UNIX)
        updated_at:
          type: integer
          format: int64
          description: Date de mise à jour (timestamp UNIX)
        data_created_at:
          type: integer
          format: int64
          description: Date de création des données sur le serveur
        data_updated_at:
          type: integer
          format: int64
          description: Date de dernière mise à jour des données sur le serveur
        user_id:
          type: integer
          description: Propriétaire de la fiche
        channels:
          type: array
          items:
            type: string
          description: Conditions remplies par la fiche
        items:
          type: object
          additionalProperties: true
          description: |
            Dictionnaire clé/valeur dont la clé est le nom système du champ.

            **Types simples :** texte/email → chaîne | nombre → flottant | booléen → bool |
            date → timestamp UNIX | durée → ms | téléphone → RFC 3966

            **location :** `{lat, lng, address}`

            **image/signature/fichier :** `[{id, name, mimeType, extension, size, comment}]`

            **liste :** `[id, …]`

            **relation :** `{submissions: [{id, count?}, …]}`

            **utilisateur :** `[{id}, …]`
        submission_number:
          type: integer
          description: Numéro séquentiel global
        number_in_structure:
          type: integer
          description: Numéro séquentiel au sein du formulaire
        latitude:
          type: number
          format: double
          description: Latitude de la fiche
        longitude:
          type: number
          format: double
          description: Longitude de la fiche
        last_updated_user_id:
          type: integer
          description: Identifiant du dernier utilisateur ayant modifié la fiche
        current_state:
          type: string
          format: uuid
          nullable: true
          description: UUID de l'état de workflow courant (null si pas de workflow)
        assigned_user:
          $ref: "#/components/schemas/UserRef"
        subscribers:
          type: array
          items: {}
          description: Utilisateurs abonnés à la fiche
        settings:
          type: array
          items: {}

    SubmissionInput:
      type: object
      required: [structure_id, structure_version, items]
      properties:
        id:
          type: string
          format: uuid
          description: Généré automatiquement par le serveur si omis
        structure_id:
          type: integer
          description: Identifiant du formulaire
        structure_version:
          type: integer
          description: Version du formulaire
        created_at:
          type: integer
          format: int64
          description: "Date de création (défaut : maintenant)"
        updated_at:
          type: integer
          format: int64
          description: "Date de dernière modification (défaut : maintenant)"
        trigger_id:
          type: string
          format: uuid
          description: Transition de workflow à appliquer (PUT/PATCH uniquement)
        substitute_user_id:
          type: integer
          description: Optionnel. Définit un autre utilisateur comme créateur/propriétaire. L'utilisateur authentifié est toujours tracé dans l'historique.
        items:
          type: object
          additionalProperties: true
          description: Dictionnaire avec pour clé le nom système du champ

    SubmissionListResponse:
      type: object
      properties:
        submissions:
          type: array
          items:
            $ref: "#/components/schemas/Submission"
          description: Liste des fiches
        total_count:
          type: integer
          description: Nombre total de fiches
        total_pages:
          type: integer
          description: Nombre total de pages
        server_time:
          type: integer
          format: int64
          description: Date courante du serveur (timestamp UNIX)

    SearchCriteria:
      type: object
      additionalProperties:
        type: object
        required: [operator]
        properties:
          value:
            description: Valeur recherchée (à omettre pour isNull / isNotNull)
          operator:
            type: string
            enum:
              - equal
              - notequal
              - isNull
              - isNotNull
              - less
              - greater
              - lesseq
              - greatereq
              - contains
              - notContains
              - startWith
              - endWith
              - in
              - range
      description: Objet dont chaque clé est un nom système de champ valide. Respectez la casse des opérateurs.

    CallbackUrlResponse:
      type: object
      properties:
        callbackUrl:
          type: string
          description: URL à interroger pour suivre l'avancement du job asynchrone

    SubmissionCallbackResponse:
      type: object
      properties:
        callbackDetails:
          type: object
          properties:
            status:
              type: string
              enum:
                - Planned
                - Running
                - Finished
                - Finished with errors
                - Failed
                - In timeout
              description: Statut courant du job
            nbRecordsTodo:
              type: integer
              description: Nombre de fiches à traiter
            nbRecordsDone:
              type: integer
              description: Nombre de fiches déjà traitées
            nbSuccess:
              type: integer
              description: Nombre de fiches traitées sans erreur
            nbErrors:
              type: integer
              description: Nombre de fiches en erreur
            success:
              type: array
              items:
                type: string
                format: uuid
              description: UUIDs des fiches traitées avec succès
            errors:
              type: array
              items:
                type: object
                additionalProperties:
                  type: array
                  items:
                    type: string
              description: Erreurs par fiche

    UserRef:
      type: object
      description: Référence à un utilisateur
      properties:
        id:
          type: integer
        email:
          type: string
          format: email
        first_name:
          type: string
          description: Prénom
        last_name:
          type: string
          description: Nom
        groups:
          type: array
          items: {}
          description: Groupes de l'utilisateur
        system_groups:
          type: array
          items: {}
          description: Groupes système de l'utilisateur

    # ── Commentaires ───────────────────────────────────────────────────────────
    Comment:
      type: object
      description: Commentaire sur une fiche
      properties:
        id:
          type: string
          format: uuid
          description: Identifiant du commentaire (UUID v4)
        message:
          type: string
          maxLength: 1000
          description: Texte brut uniquement — pas de HTML ni de balises script
        client_created_at:
          type: integer
          format: int64
          description: Date de création côté client (timestamp UNIX)
        client_updated_at:
          type: integer
          format: int64
          description: Date de mise à jour côté client (timestamp UNIX)
        user_id:
          type: integer
          description: Identifiant de l'auteur
        submission_id:
          type: string
          format: uuid
          description: Identifiant de la fiche associée
        replies:
          type: array
          description: "Réponses de second niveau (profondeur maximale : 2)"
          items:
            $ref: "#/components/schemas/Comment"

    CommentInput:
      type: object
      required: [message]
      description: Corps d'une création ou mise à jour de commentaire
      properties:
        id:
          type: string
          format: uuid
          description: UUID v4 optionnel fourni par le client
        message:
          type: string
          maxLength: 1000
          description: Texte brut uniquement — pas de HTML, balises script ou caractères dangereux
        client_created_at:
          type: integer
          format: int64
          description: Timestamp UNIX côté client
        client_updated_at:
          type: integer
          format: int64
          description: Timestamp UNIX côté client

    # ── Applications personnalisées ────────────────────────────────────────────
    CustomAppSummary:
      type: object
      description: Résumé d'une application personnalisée
      properties:
        id:
          type: integer
          description: Identifiant
        name:
          type: string
          description: Nom
        created_at:
          type: integer
          format: int64
          description: Date de création (timestamp UNIX)
        updated_at:
          type: integer
          format: int64
          description: Date de modification (timestamp UNIX)

    CustomApp:
      allOf:
        - $ref: "#/components/schemas/CustomAppSummary"
        - type: object
          properties:
            banner:
              type: object
              additionalProperties: true
              description: Bannière de l'application
            splash:
              type: object
              description: Écran de démarrage
              properties:
                active:
                  type: boolean
                  description: Afficher l'écran de démarrage au lancement
                image:
                  type: string
                  description: UUID du fichier image
                color:
                  type: string
                  description: Couleur de fond (hexadécimal)
                aspect:
                  type: string
                  enum: [fit, fill]
                  description: "`fit` s'adapte à l'espace disponible, `fill` le remplit"
                created_at:
                  type: integer
                  format: int64
                updated_at:
                  type: integer
                  format: int64
            menus:
              type: array
              description: Éléments du menu
              items:
                type: object
                properties:
                  index:
                    type: integer
                    description: Position dans le menu
                  target:
                    type: string
                    description: Action déclenchée lors de l'interaction
                  label:
                    type: object
                    additionalProperties:
                      type: string
                    description: Libellés localisés (clé = code locale, ex. `fr`, `en`)
                  icon:
                    type: object
                    properties:
                      type:
                        type: string
                      value:
                        type: string
                  created_at:
                    type: integer
                    format: int64
                  updated_at:
                    type: integer
                    format: int64
            screens:
              type: array
              description: Écrans de l'application
              items:
                type: object
                properties:
                  name:
                    type: string
                    description: Nom identifiant l'écran
                  created_at:
                    type: integer
                    format: int64
                  updated_at:
                    type: integer
                    format: int64
                  pages:
                    type: array
                    description: Pages composant l'écran
                    items:
                      type: object
                      properties:
                        system_name:
                          type: string
                          description: Nom système de la page
                        title:
                          type: object
                          additionalProperties:
                            type: string
                          description: Titre localisé
                        side_menu:
                          type: boolean
                          description: Afficher le menu latéral sur cette page
                        widgets:
                          type: array
                          description: Boutons affichés sur la page
                          items:
                            type: object
                            properties:
                              type:
                                type: string
                                description: Type de bouton
                              index:
                                type: integer
                                description: Position d'affichage
                              label:
                                type: object
                                additionalProperties:
                                  type: string
                                description: Libellé localisé
                              color:
                                type: string
                                description: Couleur de fond (hexadécimal)
                              target:
                                type: string
                                description: Action déclenchée lors de l'interaction
                              icon:
                                type: object
                                additionalProperties: true
                                description: Icône du bouton
                              background:
                                type: string
                                description: UUID de l'image de fond (type `full`)
                        layout:
                          type: array
                          description: Disposition des widgets
                          items:
                            type: object
                            properties:
                              x:
                                type: integer
                                description: Coordonnée X
                              y:
                                type: integer
                                description: Coordonnée Y
                              width:
                                type: integer
                                description: Largeur relative
                              height:
                                type: integer
                                description: Hauteur relative
            settings:
              type: array
              items: {}
              description: Paramètres de l'application
            groups:
              type: array
              items: {}
              description: Groupes ayant accès à l'application
            widget_spacing:
              type: integer
              enum: [0, 1]
              description: "`0` = mode plat, `1` = mode standard"
            button_type:
              type: string
              enum: [standard, mix, full]
              description: "`standard` = icône Font Awesome, `mix` = icône image, `full` = image de fond"
            label_overlay:
              type: boolean
              description: Afficher le bandeau sous les libellés des boutons
            grant_all:
              type: boolean
              description: Accessible à tous les utilisateurs ou non

    # ── Listes ─────────────────────────────────────────────────────────────────
    ListItem:
      type: object
      description: Élément de liste
      properties:
        id:
          type: integer
          description: Identifiant de l'élément
        root_id:
          type: integer
          description: Identifiant du nœud racine
        parent_id:
          type: integer
          description: Identifiant du parent
        created_at:
          type: integer
          format: int64
          description: Date de création (timestamp UNIX)
        updated_at:
          type: integer
          format: int64
          description: Date de mise à jour (timestamp UNIX)
        deleted_at:
          type: integer
          format: int64
          nullable: true
          description: Date de suppression (timestamp UNIX)
        name:
          type: string
          description: Nom affiché
        has_image:
          type: boolean
          description: Indique si l'élément possède une image
        level:
          type: integer
          description: Niveau de profondeur — 0 est le nœud racine
        position:
          type: integer
          description: Position à son niveau de profondeur
        external_id:
          type: string
          description: Identifiant externe
        url:
          type: string
          description: URL associée à l'élément
        functionnal_status_color:
          type: string
          description: Couleur de l'élément (hexadécimal)

    ListPagedResponse:
      type: object
      properties:
        lists:
          type: array
          items:
            type: object
            properties:
              root_id:
                type: integer
                description: Identifiant racine
              name:
                type: string
                description: Nom de la liste
              created_at:
                type: integer
                format: int64
              list_last_updated:
                type: integer
                format: int64
                description: Date de la dernière modification d'un élément de la liste
        total_count:
          type: integer
        total_pages:
          type: integer
        next_page:
          type: integer
        server_time:
          type: integer
          format: int64

    ListItemPagedResponse:
      type: object
      properties:
        lists:
          type: array
          items:
            $ref: "#/components/schemas/ListItem"
        total_count:
          type: integer
        total_pages:
          type: integer
        next_page:
          type: integer
        server_time:
          type: integer
          format: int64

    ListCreateRequest:
      type: object
      required: [name]
      description: Corps de création d'une liste
      properties:
        name:
          type: string
          description: Nom de la liste (obligatoire)
        external_id:
          type: string
          description: Identifiant externe
        items:
          type: array
          description: Éléments enfants imbriqués (récursif)
          items:
            $ref: "#/components/schemas/ListCreateRequest"

    ListItemUpdateRequest:
      type: object
      description: Corps de mise à jour d'un élément de liste
      properties:
        name:
          type: string
          description: Nom de l'élément
        latitude:
          type: number
          format: float
          description: Latitude
        longitude:
          type: number
          format: float
          description: Longitude
        external_id:
          type: string
          description: Identifiant externe
        functionnal_status_color:
          type: string
          description: Couleur de l'élément (hexadécimal)
        url:
          type: string
          description: URL de l'élément

    ListCallbackResponse:
      type: object
      properties:
        callbackDetails:
          type: object
          properties:
            status:
              type: string
              enum:
                - Planned
                - Running
                - Finished
                - Finished with errors
                - Failed
              description: Statut courant du job
            nbRecordsTodo:
              type: integer
              description: Nombre d'éléments à traiter
            nbRecordsDone:
              type: integer
              description: Nombre d'éléments déjà traités
            nbSuccess:
              type: integer
              description: Nombre d'éléments traités avec succès
            nbErrors:
              type: integer
              description: Nombre d'éléments en erreur
            success:
              type: array
              items: {}
              description: Tableau vide
            errors:
              type: array
              items:
                type: string
              description: Messages d'erreur

    # ── Autorisations d'accès aux listes ──────────────────────────────────────
    ListAccessSetSummary:
      type: object
      description: Résumé d'une autorisation d'accès
      properties:
        id:
          type: string
          format: uuid
          description: Identifiant de l'autorisation d'accès
        name:
          type: string
          description: Nom de l'autorisation d'accès
        users:
          type: array
          items:
            type: string
          description: Identifiants des utilisateurs
        groups:
          type: array
          items: {}
          description: Groupes inclus
        root_id:
          type: integer
          description: Identifiant du nœud racine de la liste
        lists:
          type: array
          items:
            type: integer
          description: Identifiants des éléments de liste restreints

    ListAccessSetDetail:
      type: object
      description: Détails complets d'une autorisation d'accès
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
        users:
          type: array
          items:
            $ref: "#/components/schemas/UserRef"
          description: Utilisateurs inclus
        groups:
          type: array
          items: {}
          description: Groupes inclus
        root_id:
          type: object
          description: Nœud racine associé
          properties:
            id:
              type: integer
            name:
              type: string
            level:
              type: integer
            position:
              type: integer
            has_image:
              type: boolean
        lists:
          type: array
          items:
            $ref: "#/components/schemas/ListItem"
          description: Éléments de liste restreints

    # ── Tâches ─────────────────────────────────────────────────────────────────
    Task:
      type: object
      description: Tâche liée à une fiche
      properties:
        id:
          type: string
          format: uuid
          description: Identifiant de la tâche
        created_at:
          type: integer
          format: int64
          description: Date de création (timestamp UNIX)
        updated_at:
          type: integer
          format: int64
          description: Date de mise à jour (timestamp UNIX)
        start_date:
          type: integer
          format: int64
          description: Date de début prévue (timestamp UNIX)
        due_date:
          type: integer
          format: int64
          description: Date d'échéance (timestamp UNIX)
        duration:
          type: integer
          description: Durée prévue en secondes
        delay_before:
          type: integer
          description: Délai d'affichage indicatif en secondes avant le début de la tâche
        delay_after:
          type: integer
          description: Délai d'affichage indicatif en secondes après la fin de la tâche
        user_id:
          type: integer
          description: Identifiant de l'utilisateur assigné
        submission:
          $ref: "#/components/schemas/Submission"
          description: Fiche associée à la tâche

    TaskListResponse:
      type: object
      properties:
        tasks:
          type: array
          items:
            $ref: "#/components/schemas/Task"
          description: Liste des tâches
        total_count:
          type: integer
          description: Nombre total de tâches
        total_pages:
          type: integer
          description: Nombre total de pages
        server_time:
          type: integer
          format: int64
          description: Date courante du serveur (timestamp UNIX)

    # ── Tâches (création) ──────────────────────────────────────────────────────
    TaskInput:
      type: object
      required: [start_date]
      description: |
        Corps de création d'une tâche. Fournissez soit `submission_model_id`, soit `submissions`.
        Ajoutez `recurrency` pour une tâche récurrente (dans ce cas `due_date` est optionnel).
      properties:
        start_date:
          type: integer
          format: int64
          description: Date de début de la tâche (timestamp UNIX)
        due_date:
          type: integer
          format: int64
          description: Date d'échéance (timestamp UNIX). Optionnel pour une tâche récurrente.
        submission_model_id:
          type: string
          format: uuid
          description: Identifiant d'un modèle de fiche
        submissions:
          type: array
          description: Liste d'identifiants de fiches
          items:
            type: string
            format: uuid
        user_id:
          type: integer
          nullable: true
          description: Identifiant de l'utilisateur assigné
        duration:
          type: integer
          description: Durée de la tâche en secondes
        delay_before:
          type: integer
          description: Délai d'affichage indicatif en secondes avant le début de la tâche
        delay_after:
          type: integer
          description: Délai d'affichage indicatif en secondes après l'échéance de la tâche
        recurrency:
          $ref: "#/components/schemas/Recurrency"

    Recurrency:
      type: object
      description: Définition de la récurrence d'une tâche
      properties:
        type:
          type: string
          enum: [daily, weekly]
          description: Type de récurrence
        interval:
          type: integer
          description: Intervalle entre chaque occurrence (minimum 1)
        days:
          type: object
          description: |
            Jours de la semaine (3 premières lettres en anglais : mon, tue, wed, thu, fri, sat, sun).
            Pour la récurrence hebdomadaire, tous les jours ne sont pas requis.
          additionalProperties:
            type: object
            properties:
              start:
                type: string
                description: Heure de début (format HH:MM)
              end:
                type: integer
                description: Durée de la tâche en secondes
        end:
          type: object
          description: Condition de fin de la récurrence
          properties:
            type:
              type: string
              enum: [occurrence, date]
              description: "`occurrence` = nombre d'occurrences, `date` = date de fin"
            value:
              type: integer
              description: Nombre d'occurrences ou date de fin (timestamp) selon `type`

    # ── Publipostages ───────────────────────────────────────────────────────────
    ReportTemplate:
      type: object
      description: Modèle de publipostage
      properties:
        id:
          type: integer
          description: Identifiant du modèle
        name:
          type: string
          description: Nom du modèle
        listingMode:
          type: boolean
          description: Indique si le modèle est en mode listing
        templateEngine:
          type: string
          description: Moteur de génération du modèle (ex. docxopenxml, xlsxaspose)

    ReportTemplateListResponse:
      type: object
      properties:
        templates:
          type: array
          items:
            $ref: "#/components/schemas/ReportTemplate"
        total_count:
          type: integer
        total_pages:
          type: integer
        server_time:
          type: integer
          format: int64

    Report:
      type: object
      description: Publipostage généré
      properties:
        id:
          type: integer
          description: Identifiant du publipostage
        status:
          type: string
          description: Statut du publipostage (ex. finished, error)
        original_document:
          type: object
          description: Fichier généré au format d'origine
          properties:
            file_uuid:
              type: string
              description: UUID du fichier généré
            file_name:
              type: string
              description: Nom du fichier généré
        pdf_document:
          type: object
          description: Fichier généré au format PDF
          properties:
            file_uuid:
              type: string
            file_name:
              type: string
        errorMessage:
          type: string
          description: Message d'erreur, le cas échéant

    ReportListResponse:
      type: object
      properties:
        reports:
          type: array
          items:
            $ref: "#/components/schemas/Report"
        total_count:
          type: integer
        next_page:
          type: integer
        server_time:
          type: integer
          format: int64

    ReportInput:
      type: object
      required: [template_id, submission_id, api_callback]
      description: Corps de lancement d'un publipostage
      properties:
        template_id:
          type: integer
          description: Identifiant du modèle (obligatoire)
        submission_id:
          type: string
          format: uuid
          description: Identifiant de la fiche (obligatoire)
        api_callback:
          type: string
          description: "URL appelée (PUT) à la fin de la génération ; `{publipostage_id}` y est substitué (obligatoire)"
        header_name:
          type: string
          description: Nom d'en-tête HTTP à transmettre lors du callback (optionnel)
        header_value:
          type: string
          description: Valeur d'en-tête HTTP à transmettre lors du callback (optionnel)
        join_format:
          type: string
          enum: [original, pdf, "original,pdf"]
          description: "Format de sortie : `original`, `pdf`, ou `original,pdf` (deux rapports générés)"

    # ── Abonnements ──────────────────────────────────────────────────────────────
    Subscription:
      type: object
      description: Abonnement rattaché à un compte
      properties:
        start:
          type: integer
          format: int64
          description: Date de début (timestamp UNIX)
        end:
          type: integer
          format: int64
          description: Date de fin (timestamp UNIX)
        users_max:
          type: object
          description: Nombre maximum d'utilisateurs web et mobile
          properties:
            web:
              type: integer
            mobile:
              type: integer
        id:
          type: string
          format: uuid
          description: Identifiant de l'abonnement
        name:
          type: string
          description: Nom affiché
        sequence:
          type: integer
          description: Numéro séquentiel
        active:
          type: boolean
          description: Statut de l'abonnement
        used_licenses:
          type: object
          description: Nombre de licences déjà utilisées
          properties:
            web:
              type: integer
            mobile:
              type: integer

    SubscriptionListResponse:
      type: object
      properties:
        subscriptions:
          type: array
          items:
            $ref: "#/components/schemas/Subscription"
        total_count:
          type: integer
        total_pages:
          type: integer
        server_time:
          type: integer
          format: int64

    # ── Groupes ──────────────────────────────────────────────────────────────────
    Group:
      type: object
      description: Groupe d'un vertical métier
      properties:
        is_system:
          type: boolean
          description: Indique s'il s'agit d'un groupe système
        id:
          type: integer
          description: Identifiant du groupe
        name:
          type: string
          description: Nom affiché
        description:
          type: string
          description: Description renseignée par l'administrateur
        user_ids:
          type: array
          description: Identifiants des utilisateurs du groupe
          items:
            type: integer
        structure_ids:
          type: array
          description: Identifiants des formulaires liés au groupe
          items:
            type: integer

    GroupInput:
      type: object
      required: [name, description]
      description: Corps de création ou de mise à jour d'un groupe
      properties:
        name:
          type: string
          description: Nom du groupe
        description:
          type: string
          description: Description du groupe
        user_ids:
          type: array
          description: Identifiants des utilisateurs du groupe
          items:
            type: integer
        structure_ids:
          type: array
          description: Identifiants des formulaires du groupe
          items:
            type: integer

    # ── Utilisateurs ───────────────────────────────────────────────────────────
    User:
      type: object
      description: Utilisateur (objet retourné par les API GET)
      properties:
        id:
          type: integer
          description: Identifiant unique généré par le système
        username:
          type: string
          format: email
          description: Adresse e-mail (identifiant de connexion), unique
        active:
          type: boolean
          description: Statut (actif / inactif)
        first_name:
          type: string
          description: Prénom
        last_name:
          type: string
          description: Nom
        subscription_id:
          type: string
          description: UUID de l'abonnement rattaché
        system_groups:
          type: array
          description: "Groupes système : 1 = administrateur, 2 = accès mobile, 3 = accès web"
          items:
            type: integer
        groups:
          type: array
          description: Identifiants des groupes de l'utilisateur
          items:
            type: integer
        settings:
          type: object
          description: Paramètres de l'utilisateur (ex. langue/locale)
          additionalProperties: true

    UserInput:
      type: object
      required: [username, system_groups]
      description: |
        Corps de création ou de mise à jour d'un utilisateur. Fournissez `plainPassword`
        pour une authentification Daxium, ou `ssoProvider` pour une authentification SSO.
      properties:
        username:
          type: string
          format: email
          description: Adresse e-mail (identifiant de connexion), unique
        active:
          type: boolean
          description: Statut (actif / inactif)
        firstName:
          type: string
          description: Prénom
        lastName:
          type: string
          description: Nom
        locale:
          type: string
          enum: [en, fr]
          description: Langue de l'utilisateur
        subscription_id:
          type: string
          description: UUID de l'abonnement (voir l'API `/subscriptions`)
        system_groups:
          type: array
          description: "Groupes système : 1 = administrateur, 2 = accès mobile, 3 = accès web"
          items:
            type: integer
        groups:
          type: array
          description: Identifiants des groupes (voir l'API `/{app_short}/groups`)
          items:
            type: integer
        plainPassword:
          type: object
          description: |
            Mot de passe. Les clés `first` et `second` sont obligatoires et doivent être identiques.
            Règles : au moins 1 majuscule, 1 minuscule, 1 chiffre, 8 caractères au total.
          properties:
            first:
              type: string
              format: password
            second:
              type: string
              format: password
        force_login_password:
          type: integer
          enum: [0, 1]
          description: "[Sociétés SSO uniquement] 0 pour désactiver, 1 pour forcer l'authentification login/mot de passe Daxium"
        ssoProvider:
          type: integer
          nullable: true
          description: "[Sociétés SSO uniquement] Identifiant du fournisseur SSO à utiliser (fourni par Daxium)"

    UserListResponse:
      type: object
      properties:
        users:
          type: array
          items:
            $ref: "#/components/schemas/User"
        total_count:
          type: integer
        total_pages:
          type: integer
        server_time:
          type: integer
          format: int64

    Company:
      type: object
      description: Compte société de l'utilisateur courant
      properties:
        created_at:
          type: integer
          format: int64
          description: Date de création (timestamp UNIX)
        updated_at:
          type: integer
          format: int64
          description: Date de dernière mise à jour (timestamp UNIX)
        id:
          type: integer
          description: Identifiant
        title:
          type: string
          description: Nom affiché

    CurrentUserResponse:
      type: object
      properties:
        user:
          $ref: "#/components/schemas/User"
        company:
          $ref: "#/components/schemas/Company"

    PushKeyInput:
      type: object
      description: Clé de notification push d'un appareil
      properties:
        id:
          type: string
          description: Identifiant unique de l'appareil
        key:
          type: string
          description: Clé push
        os:
          type: string
          description: Système d'exploitation de l'appareil (android ou iOS)
        os_version:
          type: string
          description: Version de l'OS
        app_version:
          type: string
          description: Version de Daxium-Air
        name:
          type: string
          description: Nom de l'appareil

    UsersCallbackResponse:
      type: object
      properties:
        callbackDetails:
          type: object
          properties:
            status:
              type: string
              enum:
                - Planned
                - Running
                - Finished
                - Finished with errors
                - Failed
                - In timeout
              description: Statut courant du job
            nbRecordsTodo:
              type: integer
              description: Nombre d'utilisateurs à traiter
            nbRecordsDone:
              type: integer
              description: Nombre d'utilisateurs déjà traités
            nbSuccess:
              type: integer
              description: Nombre d'utilisateurs traités avec succès
            nbErrors:
              type: integer
              description: Nombre d'utilisateurs en erreur
            success:
              type: array
              description: "Utilisateurs créés/mis à jour avec succès. Format : {\"<username>\": {\"id\": <user_id>}}"
              items:
                type: object
                additionalProperties: true
            errors:
              type: array
              description: "Erreurs par utilisateur. Format : {\"<username>\": {\"<champ>\": [\"<erreur>\"]}}"
              items:
                type: object
                additionalProperties: true

    # ── Vues ─────────────────────────────────────────────────────────────────────
    View:
      type: object
      description: Vue de fiches configurée
      properties:
        id:
          type: string
          format: uuid
          description: Identifiant unique
        name:
          type: string
          description: Nom localisé de la vue
        icon:
          type: object
          description: Type et valeur de l'icône
          properties:
            type:
              type: string
            value:
              type: string
        view_mode:
          type: string
          description: Mode d'affichage de la vue (ex. table)
        view_options:
          type: object
          description: Options de la vue
          properties:
            ordered_fields:
              type: array
              description: Liste ordonnée des champs à afficher (applicable si view_mode = table)
              items:
                type: string
        assigned_to_user:
          type: boolean
          nullable: true
          description: Indique si la vue restreint les fiches à celles assignées à l'utilisateur courant
        filters:
          type: array
          description: Règles de filtrage de la vue
          items:
            type: object
            properties:
              structure_id:
                type: integer
                description: Identifiant du formulaire des fiches à afficher
              item_filters:
                type: array
                description: Règles de filtrage sur les champs
                items:
                  type: object
                  properties:
                    name:
                      type: string
                      description: Nom système du champ
                    type:
                      type: string
                      description: Type Daxium-Air du champ
                    operator:
                      type: string
                      description: Opérateur à appliquer
                    values:
                      type: array
                      description: Valeurs à confronter à la valeur du champ via l'opérateur
                      items: {}
              metadata_filters:
                type: array
                description: Règles de filtrage sur les métadonnées
                items:
                  type: object
                  properties:
                    name:
                      type: string
                      description: Nom système de la métadonnée
                    operator:
                      type: string
                      description: Opérateur à appliquer
                    values:
                      type: array
                      items: {}

# ─────────────────────────── SÉCURITÉ GLOBALE ────────────────────────────────
security:
  - BearerAuth: []
