openapi: "3.0.3"

info:
  title: "Leopardo RH API"
  version: "4.24.0"
  license:
    name: "MIT"
    url: "https://github.com/kitokoh/leopardo-hr/blob/main/LICENSE"
  description: |
    OpenAPI alignee sur la surface API reellement exposee par l'API.

    Cette specification sert de contrat pour les apps employee, manager,
    platform admin, kiosque, vitrine client et futurs integrateurs tiers.

    Regles generales :
    - base API stable sous /api/v1 ;
    - authentification Bearer Sanctum ;
    - listes paginees compatibles Laravel (data, links, meta) ;
    - erreurs JSON standardisees (error, message, errors) ;
    - isolation tenant stricte sur toutes les routes authentifiees.

externalDocs:
  description: "Guide integration partenaires et ecosysteme developpeur"
  url: "https://gestionemployerbackend.onrender.com/api-explorer"

servers:
  - url: "https://gestionemployerbackend.onrender.com/api/v1"
    description: "Staging/production Render actuel"
  - url: "https://api.leopardo-rh.com/api/v1"
    description: "Production future domaine dedie"
  - url: "http://localhost:8000/api/v1"
    description: "Local Docker"

security:
  - BearerAuth: []

tags:
  - name: "Health"
  - name: "Auth"
  - name: "Payrolls"
  - name: "HR Referentials"
  - name: "Notifications"
  - name: "Readiness"
  - name: "Dashboard"
  - name: "Schedules"
  - name: "Projects"
  - name: "Tasks"
  - name: "Evaluations"
  - name: "Platform"
  - name: "Employees"
  - name: "Attendance"
  - name: "Estimations"
  - name: "Me"
  - name: "Invitations"
  - name: "Biometrics"
  - name: "Privacy"
  - name: "Kiosks"
  - name: "Cameras"
  - name: "Vehicle Tracking"
  - name: "Fleet Management"
  - name: "Webhooks"
    description: "Evenements sortants documentes pour integrateurs; livraison effective selon configuration tenant."
  - name: "Growth"
    description: "Programme partenaires, commissions et parrainages."
  - name: "Onboarding"
    description: "Onboarding entreprise, checklist de configuration initiale et QR codes."
  - name: "Training"
    description: "Formations, sessions, inscriptions et parcours de developpement."
  - name: "Marketing"
    description: "Connexion du compte social (agregateur Ayrshare) et gestion des publications reseaux sociaux d'un tenant."
  - name: "Platform Admin"
    description: "Administration super-admin : gestion tenants, plans, metriques globales."

paths:
  /employees/{employee}/certificate-of-employment:
    get:
      tags: ["Payroll"]
      summary: "Certificat de travail PDF (fin de contrat, F-08)"
      parameters:
        - name: employee
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: "PDF (Content-Disposition: attachment)"
          content:
            application/pdf:
              schema:
                type: string
                format: binary
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          description: "Manager role required"
        "404":
          description: "Employe introuvable (isolation tenant)"

  /employees/{employee}/end-of-contract:
    get:
      tags: ["Payroll"]
      summary: "Solde de tout compte (fin de contrat, F-08)"
      description: "Calcul du solde de tout compte : prorata du dernier mois, indemnite de conges payes non pris, preavis, indemnite d'anciennete (1 mois/an, regime DZ documente). Reserve aux managers, isole par tenant."
      parameters:
        - name: employee
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: "Detail du solde de tout compte"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    properties:
                      employee_id: { type: integer }
                      end_date: { type: string }
                      years_of_service: { type: number }
                      monthly_base: { type: number }
                      breakdown:
                        type: object
                        properties:
                          prorated_pay: { type: number }
                          leave_indemnity: { type: number }
                          notice_pay: { type: number }
                          severance: { type: number }
                          total: { type: number }
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          description: "Manager role required"
        "404":
          description: "Employe introuvable (isolation tenant)"

  /demo-users:
    get:
      tags: ["Auth"]
      summary: "Comptes demo publics pour la documentation QA"
      description: "Contrat public de documentation QA (AGENTS.md v4.16.128) : personas demo employee/manager/super-admin. Throttle 10/min."
      responses:
        "200":
          description: "Personas de demonstration"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array

  /health:
    get:
      tags: ["Health"]
      summary: "Healthcheck live + ready"
      security: []
      responses:
        "200":
          description: "API disponible"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/HealthResponse"
        "503":
          description: "Base de donnees indisponible"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/HealthResponse"

  /health/live:
    get:
      tags: ["Health"]
      summary: "Liveness probe — processus API vivant (sans dépendances)"
      security: []
      responses:
        "200":
          description: "API vivante"
          content:
            application/json:
              schema:
                type: object
                properties:
                  status: { type: string, example: ok }

  /health/ready:
    get:
      tags: ["Health"]
      summary: "Readiness probe — base de données disponible"
      security: []
      responses:
        "200":
          description: "API prête"
          content:
            application/json:
              schema:
                type: object
                properties:
                  status: { type: string, example: ok }
                  checks:
                    type: object
                    additionalProperties: true

  # ─── EdgeSync — installation et telechargements (public) ─────────────

  /edge/health:
    get:
      tags: ["Health"]
      summary: "Healthcheck edge (public, machine-to-cloud)"
      description: "Sonde de sante de l'API pour les nœuds edge (throttle 300/min)."
      security: []
      responses:
        "200":
          description: "API edge disponible"
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                    example: ok

  /edge/readiness:
    get:
      tags: ["Health"]
      summary: "Readiness probe du nœud edge (schéma SQLite)"
      description: |
        Vérifie que le schéma SQLite local est provisionné (SELECT 1 sur
        sync_queue). Distinct de /edge/health (liveness sans DB) : ce endpoint
        garantit qu'un nœud frais est prêt à synchroniser avant de le déclarer
        opérationnel. (#4411)
      security: []
      responses:
        "200":
          description: "Nœud prêt — schéma SQLite provisionné"
          content:
            application/json:
              schema:
                type: object
                properties:
                  edge:
                    type: boolean
                    example: true
                  status:
                    type: string
                    example: ok
                  schema:
                    type: string
                    example: provisioned
                  time:
                    type: string
                    format: date-time
        "503":
          description: "Schéma SQLite manquant — nœud non prêt"
          content:
            application/json:
              schema:
                type: object
                properties:
                  edge:
                    type: boolean
                    example: true
                  status:
                    type: string
                    example: not_ready
                  reason:
                    type: string
                    example: edge_schema_missing
                  time:
                    type: string
                    format: date-time

  /edge/install.sh:
    get:
      tags: ["Health"]
      summary: "Script d'installation du nœud edge (public)"
      security: []
      responses:
        "200":
          description: "Script shell"
          content:
            text/plain:
              schema:
                type: string
        "404":
          description: "Script introuvable"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /edge/download/docker-compose.yml:
    get:
      tags: ["Health"]
      summary: "docker-compose.yml de reference pour le nœud edge (public)"
      security: []
      responses:
        "200":
          description: "Fichier docker-compose"
          content:
            text/plain:
              schema:
                type: string
        "404":
          description: "Fichier introuvable"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /edge/download/Caddyfile.edge:
    get:
      tags: ["Health"]
      summary: "Caddyfile.edge de reference pour le nœud edge (public)"
      description: "Fichier de configuration Caddy monté par docker-compose.yml du nœud edge (issue #3741)."
      security: []
      responses:
        "200":
          description: "Fichier Caddyfile.edge"
          content:
            text/plain:
              schema:
                type: string
        "404":
          description: "Fichier introuvable"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /edge/download/sha256.txt:
    get:
      tags: ["Health"]
      summary: "Sommes SHA-256 des artefacts edge (public)"
      description: "Manifeste des sommes SHA-256 (format texte) utilise par install.sh pour verifier l'integrite des telechargements (issues #3529/#3770)."
      security: []
      responses:
        "200":
          description: "Manifeste SHA-256"
          content:
            text/plain:
              schema:
                type: string
        "404":
          description: "Manifeste introuvable"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /edge/download/docker-compose.yml.sha256:
    get:
      tags: ["Health"]
      summary: "Somme SHA-256 de docker-compose.yml edge (public)"
      security: []
      responses:
        "200":
          description: "Somme SHA-256 du fichier docker-compose.yml"
          content:
            text/plain:
              schema:
                type: string
        "404":
          description: "Somme introuvable"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /edge/download/Caddyfile.edge.sha256:
    get:
      tags: ["Health"]
      summary: "Somme SHA-256 de Caddyfile.edge (public)"
      security: []
      responses:
        "200":
          description: "Somme SHA-256 du fichier Caddyfile.edge"
          content:
            text/plain:
              schema:
                type: string
        "404":
          description: "Somme introuvable"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /edge/download/env-example:
    get:
      tags: ["Health"]
      summary: "Exemple de fichier d'environnement pour le nœud edge (public)"
      security: []
      responses:
        "200":
          description: "Fichier .env.example"
          content:
            text/plain:
              schema:
                type: string

  /edge/license-public-key:
    get:
      tags: ["Health"]
      summary: "Cle publique de verification des licences edge (public)"
      security: []
      responses:
        "200":
          description: "Cle publique"
          content:
            text/plain:
              schema:
                type: string

  /edge/heartbeat:
    post:
      tags: ["Health"]
      summary: "Heartbeat machine-to-cloud d'un nœud edge (public)"
      security: []
      responses:
        "200":
          description: "Heartbeat enregistre"
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                    example: ok

  # ─── EdgeSync — gestion des nœuds (tenant authentifie) ───────────────

  /edge:
    get:
      tags: ["Edge"]
      summary: "Lister les nœuds edge du tenant"
      security:
        - BearerAuth: []
      responses:
        "200":
          description: "Liste des nœuds edge"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
        "401":
          $ref: "#/components/responses/Error401"
    post:
      tags: ["Edge"]
      summary: "Enregistrer un nœud edge"
      security:
        - BearerAuth: []
      responses:
        "201":
          description: "Nœud enregistre"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
        "401":
          $ref: "#/components/responses/Error401"
        "422":
          $ref: "#/components/responses/Validation422"

  /edge/{nodeId}:
    get:
      tags: ["Edge"]
      summary: "Detail d'un nœud edge du tenant"
      security:
        - BearerAuth: []
      parameters:
        - name: nodeId
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: "Nœud edge"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
        "401":
          $ref: "#/components/responses/Error401"
        "404":
          $ref: "#/components/responses/Error404"

  /edge/{nodeId}/sync:
    post:
      tags: ["Edge"]
      summary: "Declencher la synchronisation d'un nœud edge"
      security:
        - BearerAuth: []
      parameters:
        - name: nodeId
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: "Synchronisation declenchee"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
        "401":
          $ref: "#/components/responses/Error401"
        "404":
          $ref: "#/components/responses/Error404"

  /edge/{nodeId}/license:
    post:
      tags: ["Edge"]
      summary: "Emettre une licence pour un nœud edge"
      security:
        - BearerAuth: []
      parameters:
        - name: nodeId
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: "Licence emise"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
        "401":
          $ref: "#/components/responses/Error401"
        "404":
          $ref: "#/components/responses/Error404"

  # ─── EdgeSync — routes machine du nœud (X-Kiosk-Token / throttle) ────

  /edge-node/{nodeId}/push:
    post:
      tags: ["Edge"]
      summary: "Pousser les donnees du nœud edge vers le cloud"
      security: []
      parameters:
        - name: nodeId
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: "Donnees poussees"
          content:
            application/json:
              schema:
                type: object
        "422":
          $ref: "#/components/responses/Validation422"

  /edge-node/{nodeId}/pull:
    get:
      tags: ["Edge"]
      summary: "Recuperer le delta de synchronisation pour le nœud edge"
      security: []
      parameters:
        - name: nodeId
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: "Delta de synchronisation"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object

  /edge-node/{nodeId}/heartbeat:
    post:
      tags: ["Edge"]
      summary: "Heartbeat d'un nœud edge (machine-to-cloud)"
      security: []
      parameters:
        - name: nodeId
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: "Heartbeat enregistre"
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                    example: ok

  /edge-node/validate-license:
    post:
      tags: ["Edge"]
      summary: "Valider une licence edge (machine-to-cloud)"
      security: []
      responses:
        "200":
          description: "Licence validee"
          content:
            application/json:
              schema:
                type: object
                properties:
                  valid:
                    type: boolean
        "403":
          $ref: "#/components/responses/Error403"

  /trial/signup:
    post:
      tags: ["Billing"]
      summary: "Demande d'essai guidé / auto-service (onboarding)"
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [email, company, country]
              properties:
                email: { type: string, format: email }
                company: { type: string, maxLength: 120 }
                country: { type: string, minLength: 2, maxLength: 2, description: "Code ISO pays supporté (DZ, MA, TN, SN…)" }
                first_name: { type: string }
                last_name: { type: string }
                role: { type: string, enum: [founder, manager, hr, operations, other] }
                employees: { type: string, enum: ["1-10", "11-50", "51-200", "201-500", "500+"] }
                phone: { type: string }
                plan: { type: string }
                source: { type: string }
                referral_code: { type: string }
                requestedWorkflow: { type: string, enum: [guided_trial, self_service] }
      responses:
        "200":
          description: "Demande reçue (guided_trial : provisioning lancé)"
          content:
            application/json:
              schema:
                type: object
                properties:
                  success: { type: boolean, example: true }
                  message: { type: string }
                  data:
                    type: object
                    properties:
                      email: { type: string }
                      status: { type: string, example: provisioning_sandbox }
                      provisioning_token: { type: string }
        "409":
          description: "Email déjà enregistré"
        "422":
          description: "Validation échouée (pays non supporté…)"

  /trial/verify:
    post:
      tags: ["Billing"]
      summary: "Vérifie le code OTP et provisionne l'espace d'essai"
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [email, code]
              properties:
                email: { type: string, format: email }
                code: { type: string, minLength: 6, maxLength: 6 }
      responses:
        "201":
          description: "Espace d'essai créé (identifiants temporaires)"
          content:
            application/json:
              schema:
                type: object
                properties:
                  success: { type: boolean, example: true }
                  data:
                    type: object
                    properties:
                      company: { type: object }
                      manager:
                        type: object
                        properties:
                          email: { type: string }
                          temp_password: { type: string }
                      trial:
                        type: object
                        properties:
                          days: { type: integer, example: 14 }
                          ends_at: { type: string, format: date-time }
        "400":
          description: "Code invalide ou expiré"
        "422":
          description: "Pays invalide"

  /trial/status:
    get:
      tags: ["Billing"]
      summary: "Statut du provisioning d'un essai guidé (polling)"
      description: "Route publique pollee par la vitrine (throttle dedie 60/min). Verifie le token de provisioning de 64 caracteres."
      security: []
      parameters:
        - name: token
          in: query
          required: true
          schema: { type: string, minLength: 64, maxLength: 64 }
      responses:
        "200":
          description: "Statut du provisioning"
          content:
            application/json:
              schema:
                type: object
                properties:
                  success: { type: boolean, example: true }
                  data:
                    type: object
                    properties:
                      status: { type: string, enum: [pending, ready, failed] }
                      provisioned_at: { type: string, format: date-time, nullable: true }
        "404":
          description: "Token invalide"
          content:
            application/json:
              schema:
                type: object
                properties:
                  success: { type: boolean, example: false }
                  error: { type: string, example: "PROVISIONING_TOKEN_INVALID" }
                  message: { type: string }

  /user/register:
    post:
      tags: ["User"]
      summary: "Crée un compte utilisateur sans entreprise (parcours legacy mobile)"
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [email, password, first_name, last_name]
              properties:
                email: { type: string, format: email }
                password: { type: string, minLength: 8 }
                first_name: { type: string }
                last_name: { type: string }
      responses:
        "201":
          description: "Compte créé"
        "422":
          description: "Validation échouée"

  /user/login:
    post:
      tags: ["User"]
      summary: "Connexion utilisateur sans entreprise (parcours legacy mobile)"
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [email, password]
              properties:
                email: { type: string, format: email }
                password: { type: string }
      responses:
        "200":
          description: "Token d'accès"
        "401":
          description: "Identifiants invalides"

  /user/me:
    get:
      tags: ["User"]
      summary: "Profil de l'utilisateur connecté"
      security:
        - BearerAuth: []
      responses:
        "200":
          description: "Profil utilisateur"
        "401":
          description: "Non authentifié"
  /user/profile:
    patch:
      tags: ["User"]
      summary: "Met à jour le profil de l'utilisateur connecté"
      security:
        - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                first_name:
                  type: string
                last_name:
                  type: string
                phone:
                  type: string
                personal_email:
                  type: string
                recovery_email:
                  type: string
                personal_phone:
                  type: string
                preferred_language:
                  type: string
      responses:
        "200":
          description: "Profil mis à jour"
        "401":
          description: "Non authentifié"
        "422":
          $ref: "#/components/responses/Validation422"

  /user/change-password:
    post:
      tags: ["User"]
      summary: "Change le mot de passe de l'utilisateur connecté"
      security:
        - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [current_password, new_password]
              properties:
                current_password:
                  type: string
                new_password:
                  type: string
      responses:
        "200":
          description: "Mot de passe changé"
        "401":
          description: "Non authentifié"
        "422":
          $ref: "#/components/responses/Validation422"

  /user/logout:
    post:
      tags: ["User"]
      summary: "Déconnecte l'utilisateur (révoque le token)"
      security:
        - BearerAuth: []
      responses:
        "200":
          description: "Déconnecté"
        "401":
          description: "Non authentifié"

  /user/google-signin:
    post:
      tags: ["User"]
      summary: "Connexion ou création de compte via Google (compte ordinaire)"
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [id_token]
              properties:
                id_token:
                  type: string
      responses:
        "200":
          description: "Token de session"
        "422":
          $ref: "#/components/responses/Validation422"

  /user/company-requests:
    get:
      tags: ["User"]
      summary: "Liste les demandes de création d'entreprise de l'utilisateur"
      security:
        - BearerAuth: []
      responses:
        "200":
          description: "Demandes paginées"
        "401":
          description: "Non authentifié"
    post:
      tags: ["User"]
      summary: "Crée une demande de création d'entreprise"
      security:
        - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [company_name, email]
              properties:
                company_name:
                  type: string
                email:
                  type: string
                country:
                  type: string
                city:
                  type: string
      responses:
        "201":
          description: "Demande créée"
        "401":
          description: "Non authentifié"
        "422":
          $ref: "#/components/responses/Validation422"

  /user/company-requests/{id}:
    get:
      tags: ["User"]
      summary: "Détail d'une demande de création d'entreprise"
      security:
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
      responses:
        "200":
          description: "Demande"
        "401":
          description: "Non authentifié"
        "404":
          description: "Demande introuvable"

  /user/employee-links:
    get:
      tags: ["User"]
      summary: "Liste les liens employé de l'utilisateur ordinaire"
      security:
        - BearerAuth: []
      responses:
        "200":
          description: "Liens employé"
        "401":
          description: "Non authentifié"

  /hr/me:
    get:
      tags: ["HR App"]
      summary: "Profil de l'utilisateur RH connecté (manager_role rh/principal)"
      security:
        - BearerAuth: []
      responses:
        "200":
          description: "Profil RH"
        "401":
          description: "Non authentifié"
        "403":
          description: "Manager rh/principal requis"

  /hr/dashboard:
    get:
      tags: ["HR App"]
      summary: "Dashboard RH — indicateurs et actions en attente"
      security:
        - BearerAuth: []
      responses:
        "200":
          description: "Dashboard RH"
        "401":
          description: "Non authentifié"
        "403":
          description: "Manager rh/principal requis"

  /hr/team-overview:
    get:
      tags: ["HR App"]
      summary: "Vue d'ensemble de l'équipe (effectifs, répartition)"
      security:
        - BearerAuth: []
      responses:
        "200":
          description: "Vue d'ensemble"
        "401":
          description: "Non authentifié"
        "403":
          description: "Manager rh/principal requis"

  /hr/employees:
    get:
      tags: ["HR App"]
      summary: "Liste des employés (scope RH)"
      security:
        - BearerAuth: []
      responses:
        "200":
          description: "Employés paginés"
        "401":
          description: "Non authentifié"
        "403":
          description: "Manager rh/principal requis"
    post:
      tags: ["HR App"]
      summary: "Ajoute un employé (scope RH — sans changement de rôle manager)"
      security:
        - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/StoreEmployeeRequest"
      responses:
        "201":
          description: "Employé créé"
        "401":
          description: "Non authentifié"
        "403":
          description: "Manager rh/principal requis"
        "422":
          $ref: "#/components/responses/Validation422"

  /hr/employees/{employee}:
    get:
      tags: ["HR App"]
      summary: "Détail d'un employé (scope RH)"
      security:
        - BearerAuth: []
      parameters:
        - name: employee
          in: path
          required: true
          schema:
            type: integer
      responses:
        "200":
          description: "Employé"
        "401":
          description: "Non authentifié"
        "403":
          description: "Manager rh/principal requis"
        "404":
          description: "Employé introuvable"
    patch:
      tags: ["HR App"]
      summary: "Modifie un employé (scope RH — sans changement de rôle manager)"
      security:
        - BearerAuth: []
      parameters:
        - name: employee
          in: path
          required: true
          schema:
            type: integer
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/StoreEmployeeRequest"
      responses:
        "200":
          description: "Employé mis à jour"
        "401":
          description: "Non authentifié"
        "403":
          description: "Manager rh/principal requis"
        "404":
          description: "Employé introuvable"
        "422":
          $ref: "#/components/responses/Validation422"

  /employees/link-user:
    post:
      tags: ["User"]
      summary: "Manager — lie un utilisateur ordinaire à un employé du tenant"
      security:
        - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [email, employee_id]
              properties:
                email:
                  type: string
                employee_id:
                  type: integer
      responses:
        "200":
          description: "Utilisateur lié"
        "403":
          description: "Manager requis"
        "404":
          description: "Utilisateur ou employé introuvable"
        "409":
          description: "Déjà lié"
        "422":
          $ref: "#/components/responses/Validation422"


  /payrolls:
    get:
      tags: ["Payroll"]
      summary: "Liste des campagnes de paie (tenant)"
      security:
        - BearerAuth: []
      responses:
        "200":
          description: "Campagnes de paie paginées"
    post:
      tags: ["Payroll"]
      summary: "Crée une campagne de paie"
      security:
        - BearerAuth: []
      responses:
        "201":
          description: "Campagne créée"
        "422":
          description: "Validation échouée"

  /metrics:
    get:
      tags: ["Platform"]
      summary: "Métriques plateforme (super-admin)"
      security:
        - BearerAuth: []
      responses:
        "200":
          description: "Métriques"
        "401":
          description: "Non autorisé"


  /auth/google:
    get:
      tags: ["Auth"]
      summary: "Redirection vers le flux OAuth Google (state anti-CSRF en session, #2619)"
      responses:
        "302":
          description: "Redirection vers la page de consentement Google"
        "500":
          description: "Erreur de configuration OAuth"

  /auth/google/callback:
    get:
      tags: ["Auth"]
      summary: "Callback OAuth Google — échange le code contre une session"
      parameters:
        - name: code
          in: query
          schema: { type: string }
        - name: state
          in: query
          schema: { type: string }
      responses:
        "200":
          description: "Authentification réussie (session ou token)"
        "400":
          description: "State invalide ou code manquant"
        "401":
          description: "Email inconnu (EMPLOYEE_NOT_FOUND, plus d'auto-création tenantless #2998)"

  /webhooks/stripe:
    post:
      tags: ["Webhooks"]
      summary: "Webhook Stripe (événements de paiement/abonnement)"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              description: "Payload signé Stripe (stripe-signature)"
      responses:
        "200":
          description: "Événement traité (retry Stripe si erreur, #2668)"
        "400":
          description: "Signature invalide"
        "503":
          description: "Traitement indisponible — l'erreur doit être renvoyée pour retry Stripe"

  /webhooks/chargily:
    post:
      tags: ["Webhooks"]
      summary: "Webhook Chargily Pay (paiements CEMAC)"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
      responses:
        "200":
          description: "Événement traité"
        "400":
          description: "Payload invalide"

  /webhooks/email-bounce:
    post:
      tags: ["Webhooks"]
      summary: "Webhook de bounce email (SES/SendGrid) — fail-closed si secret absent"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
      responses:
        "200":
          description: "Bounce enregistré"
        "503":
          description: "Secret non configuré (fail-closed, #2616/#2667)"

  /public/careers/{companySlug}:
    get:
      tags: ["Careers"]
      summary: "Offres d'emploi publiques d'une entreprise"
      parameters:
        - name: companySlug
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: "Liste des offres publiques"
        "404":
          description: "Entreprise inconnue ou sans page carrières"

  /public/careers/{companySlug}/feed.xml:
    get:
      tags: ["Careers"]
      summary: "Flux XML des offres (agrégateurs)"
      parameters:
        - name: companySlug
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: "Flux XML"
          content:
            application/xml:
              schema:
                type: string

  /public/careers/{companySlug}/jobs/{jobPosting}:
    get:
      tags: ["Careers"]
      summary: "Détail d'une offre publique"
      parameters:
        - name: companySlug
          in: path
          required: true
          schema: { type: string }
        - name: jobPosting
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: "Détail de l'offre"
        "404":
          description: "Offre introuvable ou non publiée"

  /public/careers/{companySlug}/jobs/{jobPosting}/apply:
    post:
      tags: ["Careers"]
      summary: "Postuler à une offre publique (anti-doublon par email, #3860)"
      parameters:
        - name: companySlug
          in: path
          required: true
          schema: { type: string }
        - name: jobPosting
          in: path
          required: true
          schema: { type: integer }
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              required: [first_name, last_name, email]
              properties:
                first_name:
                  type: string
                  maxLength: 100
                last_name:
                  type: string
                  maxLength: 100
                email:
                  type: string
                  format: email
                  maxLength: 255
                phone:
                  type: string
                  maxLength: 20
                cover_letter:
                  type: string
                  maxLength: 5000
                resume_url:
                  type: string
                  format: uri
                  maxLength: 500
                resume:
                  type: string
                  format: binary
                  description: "CV (pdf/doc/docx, 5 Mo max)"
                source:
                  type: string
                  enum: [website, linkedin, referral, agency, other]
      responses:
        "201":
          description: "Candidature créée"
        "409":
          description: "ALREADY_APPLIED (déjà postulé)"
        "422":
          description: "Validation"

  /onboarding/invitation/{token}:
    get:
      tags: ["Onboarding"]
      summary: "Consulter une invitation d'onboarding (token public)"
      parameters:
        - name: token
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: "Invitation valide"
        "404":
          description: "INVITATION_NOT_FOUND"

  /onboarding/invitation/{token}/activate:
    post:
      tags: ["Onboarding"]
      summary: "Activer une invitation (définir le mot de passe)"
      parameters:
        - name: token
          in: path
          required: true
          schema: { type: string }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [password]
              properties:
                password:
                  type: string
                  format: password
                  minLength: 8
                password_confirmation:
                  type: string
      responses:
        "200":
          description: "Compte activé"
        "404":
          description: "INVITATION_NOT_FOUND"
        "422":
          description: "Validation (mot de passe trop court / non confirmé)"

  /onboarding/checklist:
    get:
      tags: ["Onboarding"]
      summary: "Checklist d'onboarding de l'employé authentifié"
      responses:
        "200":
          description: "Progression de l'onboarding"
        "401":
          $ref: "#/components/responses/Error401"

  /company-requests:
    get:
      tags: ["Billing"]
      summary: "Demandes de création d'entreprise de l'utilisateur"
      responses:
        "200":
          description: "Demandes"
        "401":
          $ref: "#/components/responses/Error401"
    post:
      tags: ["Billing"]
      summary: "Soumettre une demande de création d'entreprise"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [company_name]
              properties:
                company_name:
                  type: string
                  maxLength: 200
                sector:
                  type: string
                  maxLength: 100
                country:
                  type: string
                  maxLength: 100
                city:
                  type: string
                  maxLength: 100
      responses:
        "201":
          description: "Demande créée"
        "401":
          $ref: "#/components/responses/Error401"
        "422":
          description: "Validation"

  /auth/login:
    post:
      tags: ["Auth"]
      summary: "Connexion employee"
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/LoginRequest"
      responses:
        "200":
          description: "Connexion reussie"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/LoginResponse"
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          $ref: "#/components/responses/Error403"
        "422":
          $ref: "#/components/responses/Validation422"

  /auth/me:
    get:
      tags: ["Auth"]
      summary: "Profil authentifie"
      responses:
        "200":
          description: "Profil employee courant"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/CurrentEmployee"
        "401":
          $ref: "#/components/responses/Error401"

  /client-events:
    post:
      tags: ["Auth"]
      summary: "Persister un evenement UX client tenant-scope"
      description: "Endpoint authentifie pour les mesures produit non bloquantes du portail client. Les proprietes sont minimisee cote API."
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ClientEventRequest"
      responses:
        "201":
          description: "Evenement stocke"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ClientEventResponse"
        "401":
          $ref: "#/components/responses/Error401"
        "422":
          $ref: "#/components/responses/Validation422"

  /launch-readiness:
    get:
      tags: ["Readiness"]
      summary: "Lire le score de readiness lancement du tenant"
      description: "Endpoint manager principal/RH pour verifier les pre-requis go-live avant campagne marketing ou onboarding client."
      responses:
        "200":
          description: "Readiness lancement"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/LaunchReadinessResponse"
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          $ref: "#/components/responses/Error403"

  /dashboard/manager-digest:
    get:
      tags: ["Dashboard"]
      summary: "Lire les signaux manager du jour"
      description: "Contrat mobile manager tenant-scope pour les presences, retards, sessions ouvertes et actions RH en attente."
      responses:
        "200":
          description: "Digest manager du jour"
          content:
            application/json:
              schema:
                type: object
                required: [data]
                properties:
                  data:
                    type: object
                    required:
                      - date
                      - team_scope
                      - team_size
                      - present
                      - late
                      - open_sessions
                      - pending_actions
                    properties:
                      date:
                        type: string
                        format: date
                      team_scope:
                        type: string
                        enum: ["company", "managed_team"]
                      team_size:
                        type: integer
                        minimum: 0
                      present:
                        type: integer
                        minimum: 0
                      late:
                        type: integer
                        minimum: 0
                      open_sessions:
                        type: integer
                        minimum: 0
                      pending_actions:
                        type: integer
                        minimum: 0
                      pending_absences:
                        type: integer
                        minimum: 0
                      pending_salary_advances:
                        type: integer
                        minimum: 0
                      pending_corrections:
                        type: integer
                        minimum: 0
                      items:
                        type: array
                        items:
                          type: object
                          properties:
                            kind:
                              type: string
                            label:
                              type: string
                            count:
                              type: integer
                            severity:
                              type: string
                            route:
                              type: string
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          $ref: "#/components/responses/Error403"

  /payroll-runs/{payrollRun}/anomalies:
    get:
      tags: ["Payroll"]
      summary: "Rapport d'anomalies pre-cloture (F-20)"
      description: "Lecture seule : doublons, bulletins incoherents, variance de brut > 30 %, ecarts pointage -> paie (heures supplementaires pointees non integrees). Action humaine requise avant clouture."
      parameters:
        - name: payrollRun
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: "Liste d'anomalies (type, severite, message)"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    properties:
                      run_id: { type: integer }
                      total: { type: integer }
                      anomalies:
                        type: array
                        items:
                          type: object
                          properties:
                            type: { type: string }
                            severity: { type: string }
                            employee_id: { type: integer }
                            message: { type: string }
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          description: "Manager role required"
        "404":
          description: "Run introuvable (isolation tenant)"

  /payroll-runs/{payrollRun}/journal:
    get:
      tags: ["Payroll"]
      summary: "Journal de paie mensuel CSV (F-10)"
      description: "Une ligne par bulletin valide (matricule, nom, brut, cotisations, IRG, autres deductions, net, cout employeur) + ligne de totaux. Reserve aux managers principal/comptable."
      parameters:
        - name: payrollRun
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: "Fichier CSV (Content-Disposition: attachment)"
          content:
            text/csv:
              schema:
                type: string
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          description: "Manager role required"
        "404":
          description: "Run introuvable (isolation tenant)"

  /payroll-runs/{payrollRun}/lock:
    post:
      tags: ["Payroll"]
      summary: "Verrouiller la session de paie (cloture comptable, F-11)"
      parameters:
        - name: payrollRun
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: "Session verrouillee (locked_by/locked_at renseignes)"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/PayrollRun"
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          description: "Manager role required"
        "404":
          description: "Run introuvable (isolation tenant)"
        "422":
          description: "Le run doit etre valide (etape RH) avant verrouillage"

  /payroll-runs/{payrollRun}/unlock:
    post:
      tags: ["Payroll"]
      summary: "Deverrouiller une session cloturee (raison obligatoire, F-11)"
      parameters:
        - name: payrollRun
          in: path
          required: true
          schema: { type: integer }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [reason]
              properties:
                reason:
                  type: string
                  maxLength: 1000
                  description: "Raison du deverrouillage (audit trail)"
      responses:
        "200":
          description: "Session deverrouillee (retour a validated)"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/PayrollRun"
        "401":
          $ref: "#/components/responses/Error401"
        "422":
          description: "Raison manquante ou run non verrouille"

  /schedules:
    get:
      tags: ["Schedules"]
      summary: "Lister les horaires de l'entreprise"
      responses:
        "200":
          description: "Horaires tenant-scope"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/Schedule"
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          $ref: "#/components/responses/Error403"
    post:
      tags: ["Schedules"]
      summary: "Creer un horaire de travail"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ScheduleRequest"
      responses:
        "201":
          description: "Horaire cree"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/Schedule"
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          $ref: "#/components/responses/Error403"
        "422":
          $ref: "#/components/responses/Validation422"

  /schedules/{schedule}:
    parameters:
      - name: schedule
        in: path
        required: true
        schema:
          type: integer
    put:
      tags: ["Schedules"]
      summary: "Modifier un horaire"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ScheduleRequest"
      responses:
        "200":
          description: "Horaire modifie"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/Schedule"
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          $ref: "#/components/responses/Error403"
        "422":
          $ref: "#/components/responses/Validation422"
    delete:
      tags: ["Schedules"]
      summary: "Supprimer un horaire non defaut"
      responses:
        "200":
          description: "Horaire supprime"
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          $ref: "#/components/responses/Error403"
        "422":
          $ref: "#/components/responses/Validation422"

  /schedules/{schedule}/assign-employees:
    parameters:
      - name: schedule
        in: path
        required: true
        schema:
          type: integer
    post:
      tags: ["Schedules"]
      summary: "Affecter un horaire/regle entreprise a des employes"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: ["employee_ids"]
              properties:
                employee_ids:
                  type: array
                  minItems: 1
                  maxItems: 500
                  items:
                    type: integer
      responses:
        "200":
          description: "Horaire affecte aux employes du tenant courant"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    properties:
                      schedule:
                        $ref: "#/components/schemas/Schedule"
                      assigned_count:
                        type: integer
                      employee_ids:
                        type: array
                        items:
                          type: integer
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          $ref: "#/components/responses/Error403"
        "422":
          $ref: "#/components/responses/Validation422"


  /auth/forgot-password:
    post:
      tags: ["Auth"]
      summary: "Demander un lien de reinitialisation de mot de passe"
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [email]
              properties:
                email:
                  type: string
                  format: email
      responses:
        "200":
          description: "Lien de reinitialisation envoye si le compte existe"
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
        "422":
          $ref: "#/components/responses/Validation422"

  /auth/reset-password:
    post:
      tags: ["Auth"]
      summary: "Reinitialiser le mot de passe avec le jeton recu par email"
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [email, token, password, password_confirmation]
              properties:
                email:
                  type: string
                  format: email
                token:
                  type: string
                password:
                  type: string
                  format: password
                password_confirmation:
                  type: string
                  format: password
      responses:
        "200":
          description: "Mot de passe reinitialise"
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
        "422":
          $ref: "#/components/responses/Validation422"

  /auth/profile:
    patch:
      tags: ["Auth"]
      summary: "Mettre a jour le profil courant"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdateProfileRequest"
      responses:
        "200":
          description: "Profil mis a jour"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/CurrentEmployee"
        "401":
          $ref: "#/components/responses/Error401"
        "422":
          $ref: "#/components/responses/Validation422"

  /auth/change-password:
    post:
      tags: ["Auth"]
      summary: "Changer le mot de passe"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ChangePasswordRequest"
      responses:
        "200":
          description: "Mot de passe change"
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                    example: "ok"
        "401":
          $ref: "#/components/responses/Error401"
        "422":
          description: "Mot de passe courant incorrect ou validation"
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: "#/components/schemas/ErrorResponse"
                  - $ref: "#/components/schemas/ValidationErrorResponse"

  /auth/logout:
    post:
      tags: ["Auth"]
      summary: "Deconnexion employee"
      responses:
        "200":
          description: "Token courant supprime"
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                    example: "ok"

  /auth/biometric-enrollment:
    get:
      tags: ["Biometrics"]
      summary: "Derniere demande biometrie de l'utilisateur courant"
      responses:
        "200":
          description: "Derniere demande ou null"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/BiometricEnrollmentRequest"
    post:
      tags: ["Biometrics"]
      summary: "Soumettre une demande biometrie"
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              $ref: "#/components/schemas/BiometricEnrollmentSubmitRequest"
      responses:
        "201":
          description: "Demande enregistree"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/BiometricEnrollmentRequest"
        "422":
          $ref: "#/components/responses/Validation422"

  /me/career:
    get:
      tags: ["Me"]
      summary: "Lire le parcours professionnel durable de l'employe courant"
      description: "Retourne le rattachement courant et la timeline professionnelle visible par l'utilisateur mobile."
      responses:
        "200":
          description: "Parcours professionnel"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/EmployeeCareerResponse"
        "401":
          $ref: "#/components/responses/Error401"

  /privacy/export:
    get:
      tags: ["Privacy"]
      summary: "Exporter les donnees personnelles de l'employe authentifie"
      responses:
        "200":
          description: "Bundle personnel self-service"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PrivacyExportResponse"
        "401":
          $ref: "#/components/responses/Error401"

  /privacy/deletion-request:
    post:
      tags: ["Privacy"]
      summary: "Creer une demande de suppression non destructive"
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              properties:
                reason:
                  type: string
                  nullable: true
                  maxLength: 1000
      responses:
        "202":
          description: "Demande recue pour revue RH/juridique"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PrivacyRequestResponse"
        "401":
          $ref: "#/components/responses/Error401"
        "422":
          $ref: "#/components/responses/Validation422"

  /privacy/biometric-consent:
    patch:
      tags: ["Privacy"]
      summary: "Enregistrer ou retirer le consentement biometrique"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: ["consented"]
              properties:
                consented:
                  type: boolean
      responses:
        "200":
          description: "Etat biometrique courant"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/BiometricConsentResponse"
        "401":
          $ref: "#/components/responses/Error401"
        "422":
          $ref: "#/components/responses/Validation422"

  /platform/auth/login:
    post:
      tags: ["Platform"]
      summary: "Connexion super-admin"
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/PlatformLoginRequest"
      responses:
        "200":
          description: "Connexion super-admin reussie"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PlatformLoginResponse"
        "401":
          $ref: "#/components/responses/Error401"
        "422":
          $ref: "#/components/responses/Validation422"

  /platform/auth/me:
    get:
      tags: ["Platform"]
      summary: "Super-admin courant"
      security:
        - SuperAdminBearerAuth: []
      responses:
        "200":
          description: "Profil super-admin"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/SuperAdminUser"

  /platform/auth/logout:
    post:
      tags: ["Platform"]
      summary: "Deconnexion super-admin"
      security:
        - SuperAdminBearerAuth: []
      responses:
        "200":
          description: "Token supprime"
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                    example: "ok"

  /platform/auth/profile:
    patch:
      tags: ["Platform Admin"]
      summary: "Mettre à jour le profil du super-admin"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                name:
                  type: string
                  maxLength: 100
                email:
                  type: string
                  format: email
                  maxLength: 150
      responses:
        "200":
          description: "Profil mis à jour"
        "401":
          $ref: "#/components/responses/Error401"

  /platform/auth/change-password:
    post:
      tags: ["Platform Admin"]
      summary: "Changer le mot de passe du super-admin"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [current_password, new_password]
              properties:
                current_password:
                  type: string
                new_password:
                  type: string
                  format: password
                  minLength: 8
                new_password_confirmation:
                  type: string
      responses:
        "200":
          description: "Mot de passe changé"
        "401":
          $ref: "#/components/responses/Error401"
        "422":
          description: "Validation / mot de passe actuel incorrect"

  /platform/auth/2fa/setup:
    post:
      tags: ["Platform Admin"]
      summary: "Démarrer la configuration 2FA (secret TOTP)"
      responses:
        "200":
          description: "Secret TOTP (qrcode/secret) — à confirmer via enable"
        "409":
          description: "2FA déjà configurée"
        "401":
          $ref: "#/components/responses/Error401"

  /platform/auth/2fa/enable:
    post:
      tags: ["Platform Admin"]
      summary: "Activer la 2FA avec le code de confirmation"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [code]
              properties:
                code:
                  type: string
      responses:
        "200":
          description: "2FA activée"
        "401":
          $ref: "#/components/responses/Error401"
        "422":
          description: "Code invalide"

  /platform/auth/2fa/disable:
    post:
      tags: ["Platform Admin"]
      summary: "Désactiver la 2FA (mot de passe requis)"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [password]
              properties:
                password:
                  type: string
      responses:
        "200":
          description: "2FA désactivée"
        "401":
          $ref: "#/components/responses/Error401"
        "422":
          description: "Mot de passe incorrect"
  /platform/country-defaults:
    get:
      tags: ["Platform"]
      summary: "Lister les pays supportes et leurs defaults"
      description: "Source de verite pour les formulaires platform admin : pays, libelle, langue, devise et fuseau horaire."
      security:
        - SuperAdminBearerAuth: []
      responses:
        "200":
          description: "Pays supportes"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      type: object
                      required: ["country", "label", "language", "currency", "timezone"]
                      properties:
                        country:
                          type: string
                          example: "SN"
                        label:
                          type: string
                          example: "Senegal"
                        language:
                          type: string
                          example: "fr"
                        currency:
                          type: string
                          example: "XOF"
                        timezone:
                          type: string
                          example: "Africa/Dakar"

  /platform/companies:
    get:
      tags: ["Platform"]
      summary: "Lister les societes"
      security:
        - SuperAdminBearerAuth: []
      parameters:
        - name: status
          in: query
          schema:
            type: string
            enum: ["active", "trial", "suspended", "expired"]
        - name: search
          in: query
          schema:
            type: string
            maxLength: 100
        - name: per_page
          in: query
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 20
      responses:
        "200":
          description: "Liste des societes"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/PlatformCompany"
                  meta:
                    $ref: "#/components/schemas/PaginationMeta"
    post:
      tags: ["Platform"]
      summary: "Creer une societe shared"
      security:
        - SuperAdminBearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/PlatformCompanyCreateRequest"
      responses:
        "201":
          description: "Societe et manager crees"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PlatformCompanyCreateResponse"
        "422":
          $ref: "#/components/responses/Validation422"

  /platform/plans:
    get:
      tags: ["Platform"]
      summary: "Lister le catalogue des plans SaaS"
      security:
        - SuperAdminBearerAuth: []
      responses:
        "200":
          description: "Catalogue des plans actifs et inactifs"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PlatformPlansResponse"
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          $ref: "#/components/responses/Error403"

  /platform/companies/health:
    get:
      tags: ["Platform"]
      summary: "Vue portefeuille health des societes"
      security:
        - SuperAdminBearerAuth: []
      parameters:
        - name: limit
          in: query
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 50
      responses:
        "200":
          description: "Resume compact adoption, risque et MRR par societe"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PlatformCompanyHealthPortfolioResponse"
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          $ref: "#/components/responses/Error403"

  /platform/companies/{company}/health:
    get:
      tags: ["Platform"]
      summary: "Health detaille d'une societe"
      security:
        - SuperAdminBearerAuth: []
      parameters:
        - $ref: "#/components/parameters/CompanyUuid"
      responses:
        "200":
          description: "Adoption, abonnement, anomalies et prochaine action"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PlatformCompanyHealthResponse"
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          $ref: "#/components/responses/Error403"
        "404":
          $ref: "#/components/responses/Error404"

  /platform/companies/{company}/subscription:
    get:
      tags: ["Platform"]
      summary: "Lire l'abonnement plateforme d'une societe"
      security:
        - SuperAdminBearerAuth: []
      parameters:
        - $ref: "#/components/parameters/CompanyUuid"
      responses:
        "200":
          description: "Abonnement courant"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PlatformCompanySubscriptionResponse"
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          $ref: "#/components/responses/Error403"
        "404":
          $ref: "#/components/responses/Error404"
    patch:
      tags: ["Platform"]
      summary: "Mettre a jour l'abonnement plateforme d'une societe"
      security:
        - SuperAdminBearerAuth: []
      parameters:
        - $ref: "#/components/parameters/CompanyUuid"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/PlatformCompanySubscriptionUpdateRequest"
      responses:
        "200":
          description: "Abonnement mis a jour"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PlatformCompanySubscriptionResponse"
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          $ref: "#/components/responses/Error403"
        "404":
          $ref: "#/components/responses/Error404"
        "422":
          $ref: "#/components/responses/Validation422"

  /platform/companies/{company}/country:
    patch:
      tags: ["Platform"]
      summary: "Reformer/choisir le pays legal d'un tenant (invariant 9)"
      description: "Chemin de sortie super-admin pour les tenants legacy crees sans pays (#1867/#1952). Refuse (422) des que des donnees de paie existent (invariant 9)."
      security:
        - SuperAdminBearerAuth: []
      parameters:
        - $ref: "#/components/parameters/CompanyUuid"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [country]
              properties:
                country: { type: string, minLength: 2, maxLength: 2, description: "Code pays ISO 3166-1 alpha-2 (allowliste CountryDefaults)" }
      responses:
        "200":
          description: "Pays legal mis a jour (devise/fuseau/langue alignes)"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    properties:
                      company: { type: object }
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          $ref: "#/components/responses/Error403"
        "404":
          $ref: "#/components/responses/Error404"
        "422":
          $ref: "#/components/responses/Validation422"

  /platform/companies/{company}/features:
    get:
      tags: ["Platform"]
      summary: "Lire les feature flags d'une societe"
      security:
        - SuperAdminBearerAuth: []
      parameters:
        - $ref: "#/components/parameters/CompanyUuid"
      responses:
        "200":
          description: "Feature flags actifs et modules connus"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PlatformCompanyFeaturesResponse"
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          $ref: "#/components/responses/Error403"
        "404":
          $ref: "#/components/responses/Error404"
    patch:
      tags: ["Platform"]
      summary: "Mettre a jour les feature flags d'une societe"
      security:
        - SuperAdminBearerAuth: []
      parameters:
        - $ref: "#/components/parameters/CompanyUuid"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/PlatformCompanyFeaturesUpdateRequest"
      responses:
        "200":
          description: "Feature flags mis a jour"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PlatformCompanyFeaturesResponse"
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          $ref: "#/components/responses/Error403"
        "404":
          $ref: "#/components/responses/Error404"
        "422":
          $ref: "#/components/responses/Validation422"

  /platform/users:
    get:
      tags: ["Platform"]
      summary: "Lister les utilisateurs plateforme (super-admins)"
      description: "QA wave 2026-08-14 T004 (#2229). Pagine, filtrable par `search` (nom/email) et `status`."
      security:
        - SuperAdminBearerAuth: []
      parameters:
        - name: search
          in: query
          schema: { type: string, maxLength: 100 }
        - name: status
          in: query
          schema: { type: string, enum: [active, deactivated, suspended] }
        - name: per_page
          in: query
          schema: { type: integer, minimum: 1, maximum: 100, default: 20 }
      responses:
        "200":
          description: "Utilisateurs pagines"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/PlatformUser"
                  meta:
                    type: object
        "401":
          $ref: "#/components/responses/Error401"
    post:
      tags: ["Platform"]
      summary: "Creer un utilisateur plateforme"
      security:
        - SuperAdminBearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [name, email, password]
              properties:
                name: { type: string, maxLength: 100 }
                email: { type: string, format: email, maxLength: 150 }
                password: { type: string, minLength: 12 }
      responses:
        "201":
          description: "Utilisateur cree"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/PlatformUser"
        "401":
          $ref: "#/components/responses/Error401"
        "422":
          $ref: "#/components/responses/Validation422"

  /platform/users/{user}:
    get:
      tags: ["Platform"]
      summary: "Detail d'un utilisateur plateforme"
      security:
        - SuperAdminBearerAuth: []
      parameters:
        - name: user
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: "Utilisateur"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/PlatformUser"
        "401":
          $ref: "#/components/responses/Error401"
    patch:
      tags: ["Platform"]
      summary: "Mettre a jour un utilisateur plateforme"
      security:
        - SuperAdminBearerAuth: []
      parameters:
        - name: user
          in: path
          required: true
          schema: { type: integer }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                name: { type: string, maxLength: 100 }
                email: { type: string, format: email, maxLength: 150 }
                password: { type: string, minLength: 12 }
                status: { type: string, enum: [active, deactivated, suspended] }
      responses:
        "200":
          description: "Utilisateur mis a jour"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/PlatformUser"
        "401":
          $ref: "#/components/responses/Error401"
        "422":
          $ref: "#/components/responses/Validation422"
    delete:
      tags: ["Platform"]
      summary: "Desactiver un utilisateur plateforme (jamais de suppression physique)"
      security:
        - SuperAdminBearerAuth: []
      parameters:
        - name: user
          in: path
          required: true
          schema: { type: integer }
      responses:
        "204":
          description: "Compte desactive"
        "401":
          $ref: "#/components/responses/Error401"
        "422":
          $ref: "#/components/responses/Validation422"

  /platform/users/{user}/activate:
    post:
      tags: ["Platform"]
      summary: "Activer un utilisateur plateforme"
      security:
        - SuperAdminBearerAuth: []
      parameters:
        - name: user
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: "Utilisateur active"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/PlatformUser"
        "401":
          $ref: "#/components/responses/Error401"

  /platform/users/{user}/deactivate:
    post:
      tags: ["Platform"]
      summary: "Desactiver un utilisateur plateforme"
      security:
        - SuperAdminBearerAuth: []
      parameters:
        - name: user
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: "Utilisateur desactive"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/PlatformUser"
        "401":
          $ref: "#/components/responses/Error401"
        "422":
          $ref: "#/components/responses/Validation422"

  /platform/users/{user}/suspend:
    post:
      tags: ["Platform"]
      summary: "Suspendre un utilisateur plateforme"
      security:
        - SuperAdminBearerAuth: []
      parameters:
        - name: user
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: "Utilisateur suspendu"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/PlatformUser"
        "401":
          $ref: "#/components/responses/Error401"
        "422":
          $ref: "#/components/responses/Validation422"

  /platform/company-requests:
    get:
      tags: ["Platform"]
      summary: "Lister les demandes de creation de societe"
      security:
        - SuperAdminBearerAuth: []
      parameters:
        - name: status
          in: query
          schema:
            type: string
            enum: ["pending", "approved", "rejected"]
        - name: search
          in: query
          schema:
            type: string
            maxLength: 100
        - name: per_page
          in: query
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 20
      responses:
        "200":
          description: "Demandes paginees"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PlatformCompanyRequestsResponse"
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          $ref: "#/components/responses/Error403"

  /platform/company-requests/{id}:
    get:
      tags: ["Platform"]
      summary: "Lire une demande de creation de societe"
      security:
        - SuperAdminBearerAuth: []
      parameters:
        - $ref: "#/components/parameters/NumericId"
      responses:
        "200":
          description: "Detail de la demande"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PlatformCompanyRequestResponse"
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          $ref: "#/components/responses/Error403"
        "404":
          $ref: "#/components/responses/Error404"
    patch:
      tags: ["Platform"]
      summary: "Approuver ou rejeter une demande de creation de societe"
      security:
        - SuperAdminBearerAuth: []
      parameters:
        - $ref: "#/components/parameters/NumericId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/PlatformCompanyRequestReviewRequest"
      responses:
        "200":
          description: "Decision enregistree"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PlatformCompanyRequestReviewResponse"
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          $ref: "#/components/responses/Error403"
        "404":
          $ref: "#/components/responses/Error404"
        "422":
          $ref: "#/components/responses/Validation422"

  /platform/metrics/overview:
    get:
      tags: ["Platform"]
      summary: "Agregats business du cockpit super-admin"
      description: "Retourne des metriques globales non nominatives pour piloter MRR, ARR, encaissements, impayes, companies, subscriptions, billing et systeme."
      security:
        - SuperAdminBearerAuth: []
      responses:
        "200":
          description: "Metriques plateforme agregees"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PlatformMetricsOverviewResponse"
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          $ref: "#/components/responses/Error403"


  /admin/tax-slabs:
    get:
      tags: ["Platform Admin"]
      summary: "Lister les barèmes fiscaux nationaux (platform_admin) — issue #1814"
      operationId: listAdminTaxSlabs
      parameters:
        - name: country_code
          in: query
          required: false
          schema: { type: string }
      responses:
        "200":
          description: "Tranches nationales"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/TaxSlab"
        "401": { $ref: "#/components/responses/Error401" }
        "403": { $ref: "#/components/responses/Error403" }
    post:
      tags: ["Platform Admin"]
      summary: "Créer une tranche nationale (platform_admin) — issue #1814"
      operationId: storeAdminTaxSlab
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/TaxSlabInput"
      responses:
        "201":
          description: "Tranche créée"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/TaxSlab"
        "401": { $ref: "#/components/responses/Error401" }
        "403": { $ref: "#/components/responses/Error403" }
        "422": { $ref: "#/components/responses/Validation422" }
  /admin/tax-slabs/{taxSlab}:
    put:
      tags: ["Platform Admin"]
      summary: "Modifier une tranche nationale (platform_admin) — issue #1814"
      operationId: updateAdminTaxSlab
      parameters:
        - name: taxSlab
          in: path
          required: true
          schema: { type: integer }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/TaxSlabInput"
      responses:
        "200":
          description: "Tranche modifiée"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/TaxSlab"
        "401": { $ref: "#/components/responses/Error401" }
        "403": { $ref: "#/components/responses/Error403" }
        "404": { $ref: "#/components/responses/Error404" }
    delete:
      tags: ["Platform Admin"]
      summary: "Supprimer une tranche nationale (platform_admin) — issue #1814"
      operationId: destroyAdminTaxSlab
      parameters:
        - name: taxSlab
          in: path
          required: true
          schema: { type: integer }
      responses:
        "204": { description: "Supprimé" }
        "401": { $ref: "#/components/responses/Error401" }
        "403": { $ref: "#/components/responses/Error403" }
        "404": { $ref: "#/components/responses/Error404" }

  /admin/tax-slabs/reset-defaults:
    post:
      tags: ["Platform Admin"]
      summary: "Réinitialiser les tranches nationales d'un pays aux valeurs légales (platform_admin) — issue #1814"
      operationId: resetAdminTaxSlabs
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [country_code]
              properties:
                country_code: { type: string }
      responses:
        "200":
          description: "Barème réinitialisé"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    properties:
                      country_code: { type: string }
                      created: { type: integer }
        "401": { $ref: "#/components/responses/Error401" }
        "403": { $ref: "#/components/responses/Error403" }
        "422": { $ref: "#/components/responses/Validation422" }
  /admin/payroll/simulate:
    post:
      tags: ["Platform Admin"]
      summary: "Simuler l'impact d'un barème (platform_admin, dry-run) — issue #1814"
      operationId: simulatePayrollAdmin
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/PayrollSimulationInput"
      responses:
        "200":
          description: "Résultat de la simulation (non persistant)"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/PayrollSimulationResult"
        "401": { $ref: "#/components/responses/Error401" }
        "403": { $ref: "#/components/responses/Error403" }
        "422": { $ref: "#/components/responses/Validation422" }
  /admin/payroll/audit:
    get:
      tags: ["Platform Admin"]
      summary: "Lister les enregistrements d'audit des calculs de paie (platform_admin, cross-tenant) — issue #1874"
      description: "Vue plateforme de l'audit des calculs (runs de paie + simulations) : lecture seule, isolation par filtre company_id optionnel. RBAC : platform_admin (PayrollAuditPolicy)."
      operationId: listPayrollAuditsAdmin
      parameters:
        - name: company_id
          in: query
          required: false
          description: "Filtrer sur un tenant précis (UUID)"
          schema: { type: string, format: uuid }
        - name: status
          in: query
          required: false
          schema: { type: string, enum: [success, validation_error, rule_missing, provider_error, fallback_forbidden] }
        - name: country_code
          in: query
          required: false
          schema: { type: string }
        - name: per_page
          in: query
          required: false
          schema: { type: integer, default: 15 }
      responses:
        "200":
          description: "Liste paginée des enregistrements d'audit"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/PayrollCalculationAudit"
                  meta:
                    $ref: "#/components/schemas/PaginationMeta"
        "401": { $ref: "#/components/responses/Error401" }
        "403": { $ref: "#/components/responses/Error403" }
  /admin/payroll/audit/{correlationId}:
    get:
      tags: ["Platform Admin"]
      summary: "Détail d'un enregistrement d'audit de calcul de paie (platform_admin) — issue #1874"
      description: "Reproduction du contexte d'un calcul (pays, période, version de règles, paramètres agrégés, résultats agrégés, statut) à partir de son correlation_id. Cross-tenant pour le platform_admin."
      operationId: getPayrollAuditAdmin
      parameters:
        - name: correlationId
          in: path
          required: true
          schema: { type: string, format: uuid }
      responses:
        "200":
          description: "Enregistrement d'audit"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/PayrollCalculationAudit"
        "401": { $ref: "#/components/responses/Error401" }
        "403": { $ref: "#/components/responses/Error403" }
        "404": { $ref: "#/components/responses/Error404" }
  /admin/rate-validation/pending:
    get:
      tags: ["Platform Admin"]
      summary: "Lister les modifications de taux en attente de validation — issue #1813"
      operationId: listPendingRateValidation
      parameters:
        - name: table
          in: query
          required: false
          description: "Filtrer par table (défaut : all)"
          schema: { type: string, enum: [all, tax_slabs, social_contributions], default: all }
      responses:
        "200":
          description: "Barèmes + cotisations en pending_validation"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/RateValidationItem"
        "401": { $ref: "#/components/responses/Error401" }
        "403": { $ref: "#/components/responses/Error403" }
  /admin/rate-validation/{table}/{id}/approve:
    put:
      tags: ["Platform Admin"]
      summary: "Approuver une modification de taux (platform_admin) — issue #1813"
      operationId: approveRateValidation
      parameters:
        - name: table
          in: path
          required: true
          schema: { type: string, enum: [tax_slabs, social_contributions] }
        - name: id
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: "Ligne active, ancienne ligne superseded"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/RateValidationItem"
        "401": { $ref: "#/components/responses/Error401" }
        "403": { $ref: "#/components/responses/Error403" }
        "404": { $ref: "#/components/responses/Error404" }
        "422": { $ref: "#/components/responses/Validation422" }
  /admin/rate-validation/{table}/{id}/reject:
    put:
      tags: ["Platform Admin"]
      summary: "Rejeter une modification de taux avec motif (platform_admin) — issue #1813"
      operationId: rejectRateValidation
      parameters:
        - name: table
          in: path
          required: true
          schema: { type: string, enum: [tax_slabs, social_contributions] }
        - name: id
          in: path
          required: true
          schema: { type: integer }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [reason]
              properties:
                reason: { type: string }
      responses:
        "200":
          description: "Ligne retournée en draft avec motif"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/RateValidationItem"
        "401": { $ref: "#/components/responses/Error401" }
        "403": { $ref: "#/components/responses/Error403" }
        "404": { $ref: "#/components/responses/Error404" }
        "422": { $ref: "#/components/responses/Validation422" }
  /admin/dashboard/stats:
    get:
      tags: ["Platform"]
      summary: "Statistiques globales du cockpit super-admin"
      description: "Agregats plateforme pour le dashboard admin (contrat SPA front/admin-dashboard, issue #1764) : utilisateurs, companies, abonnements actifs, revenu mensuel, tickets support et sante systeme."
      security:
        - SuperAdminBearerAuth: []
      responses:
        "200":
          description: "Statistiques du cockpit"
          content:
            application/json:
              schema:
                type: object
                properties:
                  totalUsers:
                    type: integer
                  totalCompanies:
                    type: integer
                  activeSubscriptions:
                    type: integer
                  monthlyRevenue:
                    type: number
                  newUsersToday:
                    type: integer
                  newCompaniesToday:
                    type: integer
                  supportTickets:
                    type: integer
                  systemHealth:
                    type: string
                    enum: [good, warning, error]
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          $ref: "#/components/responses/Error403"
  /admin/dashboard/activities:
    get:
      tags: ["Platform"]
      summary: "Activites recentes plateforme (cockpit super-admin)"
      security:
        - SuperAdminBearerAuth: []
      responses:
        "200":
          description: "Liste des activites recentes"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      type: object
                      properties:
                        id: { type: string }
                        type: { type: string }
                        message: { type: string }
                        entity: { type: string }
                        created_at: { type: string, format: date-time }
        "401":
          $ref: "#/components/responses/Error401"

  /admin/dashboard/alerts:
    get:
      tags: ["Platform"]
      summary: "Alertes systeme du cockpit super-admin"
      security:
        - SuperAdminBearerAuth: []
      responses:
        "200":
          description: "Alertes calculees (hors alertes dismissées)"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      type: object
                      properties:
                        id: { type: string }
                        level: { type: string, enum: [critical, warning, info] }
                        message: { type: string }
                        created_at: { type: string, format: date-time }
        "401":
          $ref: "#/components/responses/Error401"

  /admin/dashboard/alerts/{alertKey}/dismiss:
    post:
      tags: ["Platform"]
      summary: "Masquer une alerte du cockpit (persiste le dismiss)"
      security:
        - SuperAdminBearerAuth: []
      parameters:
        - in: path
          name: alertKey
          required: true
          schema:
            type: string
            pattern: "[A-Za-z0-9_-]+"
      responses:
        "202":
          description: "Alerte dismissée"
          content:
            application/json:
              schema:
                type: object
                properties:
                  status: { type: string, example: dismissed }
        "401":
          $ref: "#/components/responses/Error401"

  /admin/edge-nodes:
    get:
      tags: ["Platform"]
      summary: "Lister les nœuds Edge de tous les tenants (super-admin)"
      security:
        - SuperAdminBearerAuth: []
      responses:
        "200":
          description: "Liste des nœuds Edge"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
        "401":
          $ref: "#/components/responses/Error401"

  /admin/edge-nodes/{nodeId}/sync:
    post:
      tags: ["Platform"]
      summary: "Forcer la synchronisation d'un nœud Edge (super-admin)"
      security:
        - SuperAdminBearerAuth: []
      parameters:
        - in: path
          name: nodeId
          required: true
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: "Synchronisation déclenchée"
        "404":
          $ref: "#/components/responses/Error404"

  /admin/edge-nodes/{nodeId}/revoke:
    post:
      tags: ["Platform"]
      summary: "Révoquer un nœud Edge et sa licence (super-admin)"
      security:
        - SuperAdminBearerAuth: []
      parameters:
        - in: path
          name: nodeId
          required: true
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: "Nœud révoqué"
        "404":
          $ref: "#/components/responses/Error404"

  /admin/ai/conversations:
    get:
      tags: ["Platform"]
      summary: "Conversations IA cross-tenant (super-admin)"
      security:
        - SuperAdminBearerAuth: []
      responses:
        "200":
          description: "Liste des conversations IA"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
        "401":
          $ref: "#/components/responses/Error401"

  /admin/ai/conversations/{conversation}/messages:
    get:
      tags: ["Platform"]
      summary: "Messages d'une conversation IA (super-admin)"
      security:
        - SuperAdminBearerAuth: []
      parameters:
        - in: path
          name: conversation
          required: true
          schema:
            type: integer
      responses:
        "200":
          description: "Messages de la conversation"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
        "404":
          $ref: "#/components/responses/Error404"

  /admin/ai/chat:
    post:
      tags: ["Platform"]
      summary: "Envoyer un message a l'assistant IA (super-admin)"
      description: "Reponse honnete cote plateforme (issue #2311) : l'IA conversationnelle est configuree PAR TENANT et la console super-admin est cross-tenant en lecture seule. L'envoi retourne une reponse structuree indiquant que l'assistant n'est pas disponible au niveau plateforme. Aucune ecriture cross-tenant."
      security:
        - SuperAdminBearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [message]
              properties:
                message:
                  type: string
                  maxLength: 4000
                conversation_id:
                  type: integer
                  nullable: true
      responses:
        "200":
          description: "Reponse de l'assistant (indisponible au niveau plateforme)"
          content:
            application/json:
              schema:
                type: object
                properties:
                  conversation_id:
                    type: integer
                    nullable: true
                  response:
                    type: string
        "404":
          $ref: "#/components/responses/Error404"
        "422":
          $ref: "#/components/responses/Validation422"

  /admin/fleet/alerts:
    get:
      tags: ["Platform"]
      summary: "Alertes flotte cross-tenant (super-admin)"
      security:
        - SuperAdminBearerAuth: []
      responses:
        "200":
          description: "Liste des alertes flotte"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
        "401":
          $ref: "#/components/responses/Error401"

  /admin/impersonations:
    get:
      tags: ["Platform"]
      summary: "Lister les sessions d'impersonation super-admin"
      security:
        - SuperAdminBearerAuth: []
      responses:
        "200":
          description: "Liste des sessions d'impersonation"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
        "401":
          $ref: "#/components/responses/Error401"
    post:
      tags: ["Platform"]
      summary: "Creer une session d'impersonation (login en tant que)"
      security:
        - SuperAdminBearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [employee_id, reason]
              properties:
                employee_id:
                  type: integer
                reason:
                  type: string
                expires_minutes:
                  type: integer
                  default: 30
      responses:
        "201":
          description: "Session creee"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
        "422":
          $ref: "#/components/responses/Validation422"

  /admin/impersonations/{session}:
    delete:
      tags: ["Platform"]
      summary: "Terminer une session d'impersonation"
      security:
        - SuperAdminBearerAuth: []
      parameters:
        - in: path
          name: session
          required: true
          schema:
            type: integer
      responses:
        "204":
          description: "Session terminee"
        "404":
          $ref: "#/components/responses/Error404"

  /admin/training/courses:
    get:
      tags: ["Platform"]

      summary: "Cours de formation cross-tenant (super-admin, avec société et nombre de sessions)"
      security:
        - SuperAdminBearerAuth: []
      parameters:
        - name: per_page
          in: query
          required: false
          schema:
            type: integer
            default: 20
      responses:
        "200":
          description: "Liste des cours"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
        "401":
          $ref: "#/components/responses/Error401"

  /admin/training/sessions:
    get:
      tags: ["Platform"]
      summary: "Sessions de formation cross-tenant (super-admin)"
      security:
        - SuperAdminBearerAuth: []
      responses:
        "200":
          description: "Liste des sessions"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
        "401":
          $ref: "#/components/responses/Error401"

  /admin/training/enrollments:
    get:
      tags: ["Platform"]
      summary: "Inscriptions aux formations cross-tenant (super-admin)"
      security:
        - SuperAdminBearerAuth: []
      responses:
        "200":
          description: "Liste des inscriptions"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
        "401":
          $ref: "#/components/responses/Error401"

  /admin/webhooks:
    get:
      tags: ["Platform"]
      summary: "Endpoints webhook cross-tenant (super-admin)"
      security:
        - SuperAdminBearerAuth: []
      responses:
        "200":
          description: "Liste des endpoints webhook"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
        "401":
          $ref: "#/components/responses/Error401"
    post:
      tags: ["Platform"]
      summary: "Creer un endpoint webhook"
      security:
        - SuperAdminBearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [url, events]
              properties:
                url:
                  type: string
                  format: uri
                events:
                  type: array
                  items:
                    type: string
                secret:
                  type: string
      responses:
        "201":
          description: "Endpoint cree"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
        "422":
          $ref: "#/components/responses/Validation422"

  /admin/webhooks/events:
    get:
      tags: ["Platform"]
      summary: "Evenements webhook disponibles"
      security:
        - SuperAdminBearerAuth: []
      responses:
        "200":
          description: "Liste des evenements"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      type: string
        "401":
          $ref: "#/components/responses/Error401"

  /admin/webhooks/{webhookEndpoint}:
    get:
      tags: ["Platform"]
      summary: "Detail d'un endpoint webhook"
      security:
        - SuperAdminBearerAuth: []
      parameters:
        - in: path
          name: webhookEndpoint
          required: true
          schema:
            type: integer
      responses:
        "200":
          description: "Endpoint"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
        "404":
          $ref: "#/components/responses/Error404"
    put:
      tags: ["Platform"]
      summary: "Mettre a jour un endpoint webhook"
      security:
        - SuperAdminBearerAuth: []
      parameters:
        - in: path
          name: webhookEndpoint
          required: true
          schema:
            type: integer
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                url:
                  type: string
                  format: uri
                events:
                  type: array
                  items:
                    type: string
                is_active:
                  type: boolean
      responses:
        "200":
          description: "Endpoint mis a jour"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
        "422":
          $ref: "#/components/responses/Validation422"
    patch:
      tags: ["Platform"]
      summary: "Mettre a jour partiellement un endpoint webhook"
      security:
        - SuperAdminBearerAuth: []
      parameters:
        - in: path
          name: webhookEndpoint
          required: true
          schema:
            type: integer
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                url:
                  type: string
                  format: uri
                events:
                  type: array
                  items:
                    type: string
                is_active:
                  type: boolean
      responses:
        "200":
          description: "Endpoint mis a jour"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
        "422":
          $ref: "#/components/responses/Validation422"
    delete:
      tags: ["Platform"]
      summary: "Supprimer un endpoint webhook"
      security:
        - SuperAdminBearerAuth: []
      parameters:
        - in: path
          name: webhookEndpoint
          required: true
          schema:
            type: integer
      responses:
        "204":
          description: "Endpoint supprime"
        "404":
          $ref: "#/components/responses/Error404"

  /admin/webhooks/{webhookEndpoint}/test:
    post:
      tags: ["Platform"]
      summary: "Dispatcher un evenement de test sur l'endpoint"
      security:
        - SuperAdminBearerAuth: []
      parameters:
        - in: path
          name: webhookEndpoint
          required: true
          schema:
            type: integer
      responses:
        "200":
          description: "Evenement de test dispatche"
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
        "404":
          $ref: "#/components/responses/Error404"

  /admin/webhooks/{webhookEndpoint}/dead-letters:
    get:
      tags: ["Platform"]
      summary: "Livraisons webhook en echec (dead letters)"
      security:
        - SuperAdminBearerAuth: []
      parameters:
        - in: path
          name: webhookEndpoint
          required: true
          schema:
            type: integer
      responses:
        "200":
          description: "Liste des livraisons en echec"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
        "404":
          $ref: "#/components/responses/Error404"

  /admin/webhooks/{webhookEndpoint}/dead-letters/{delivery}/replay:
    post:
      tags: ["Platform"]
      summary: "Rejouer une livraison webhook en echec"
      security:
        - SuperAdminBearerAuth: []
      parameters:
        - in: path
          name: webhookEndpoint
          required: true
          schema:
            type: integer
        - in: path
          name: delivery
          required: true
          schema:
            type: integer
      responses:
        "202":
          description: "Livraison re-queuee"
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
        "404":
          $ref: "#/components/responses/Error404"

  /admin/hr-reports:
    get:
      tags: ["Platform"]
      summary: "Rapports RH cross-tenant (super-admin)"
      description: "Types : headcount, turnover, absenteeism, payroll_summary, training_progress. Reponse columns/rows attendue par le SPA admin."
      security:
        - SuperAdminBearerAuth: []
      parameters:
        - in: query
          name: type
          required: true
          schema:
            type: string
            enum: [headcount, turnover, absenteeism, payroll_summary, training_progress]
        - in: query
          name: start_date
          required: true
          schema:
            type: string
            format: date
        - in: query
          name: end_date
          required: true
          schema:
            type: string
            format: date
      responses:
        "200":
          description: "Rapport généré"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    properties:
                      columns:
                        type: array
                        items: { type: string }
                      rows:
                        type: array
        "422":
          $ref: "#/components/responses/Validation422"

  /admin/users:
    get:
      tags: ["Platform Admin"]
      summary: "Lister les utilisateurs plateforme (super-admin) — issue #2269"
      description: "Liste paginée des utilisateurs public.users avec entreprise liée. Recherche nom/email, filtre statut, tri allowlisté."
      security:
        - SuperAdminBearerAuth: []
      parameters:
        - in: query
          name: page
          required: false
          schema: { type: integer, default: 1 }
        - in: query
          name: per_page
          required: false
          schema: { type: integer, default: 25, maximum: 100 }
        - in: query
          name: search
          required: false
          schema: { type: string }
        - in: query
          name: status
          required: false
          schema:
            type: string
            enum: [active, disabled, pending, suspended]
        - in: query
          name: sort_by
          required: false
          schema:
            type: string
            enum: [created_at, last_login_at, email, last_name]
        - in: query
          name: sort_dir
          required: false
          schema:
            type: string
            enum: [asc, desc]
      responses:
        "200":
          description: "Liste paginée d'utilisateurs plateforme"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/PlatformUser"
                  meta:
                    type: object
                    properties:
                      current_page: { type: integer }
                      last_page: { type: integer }
                      per_page: { type: integer }
                      total: { type: integer }
        "401": { $ref: "#/components/responses/Error401" }
        "403": { $ref: "#/components/responses/Error403" }
  /admin/users/{user}:
    get:
      tags: ["Platform Admin"]
      summary: "Détail d'un utilisateur plateforme (super-admin) — issue #2269"
      security:
        - SuperAdminBearerAuth: []
      parameters:
        - name: user
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: "Détail utilisateur avec rôles et entreprise liée"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/PlatformUser"
        "401": { $ref: "#/components/responses/Error401" }
        "403": { $ref: "#/components/responses/Error403" }
        "404": { $ref: "#/components/responses/Error404" }
    patch:
      tags: ["Platform Admin"]
      summary: "Activer/désactiver un utilisateur plateforme (super-admin) — issue #2269"
      description: "is_active=false bloque le login (status=disabled). 422 si l'utilisateur cible partage l'email du super-admin courant."
      security:
        - SuperAdminBearerAuth: []
      parameters:
        - name: user
          in: path
          required: true
          schema: { type: integer }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [is_active]
              properties:
                is_active: { type: boolean }
      responses:
        "200":
          description: "Utilisateur mis à jour"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/PlatformUser"
        "401": { $ref: "#/components/responses/Error401" }
        "403": { $ref: "#/components/responses/Error403" }
        "404": { $ref: "#/components/responses/Error404" }
        "422": { $ref: "#/components/responses/Validation422" }
  /admin/platform/marketing/oauth-config:
    get:
      tags: ["Platform"]
      summary: "Config OAuth marketing (super-admin)"
      security:
        - SuperAdminBearerAuth: []
      responses:
        "200":
          description: "Config par provider (client_secret jamais renvoyé)"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
        "401":
          $ref: "#/components/responses/Error401"
    put:
      tags: ["Platform"]
      summary: "Enregistrer la config OAuth d'un provider marketing"
      security:
        - SuperAdminBearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [provider, client_id, redirect_uri]
              properties:
                provider:
                  type: string
                  enum: [linkedin, facebook, twitter]
                client_id:
                  type: string
                redirect_uri:
                  type: string
                  format: uri
                client_secret:
                  type: string
      responses:
        "200":
          description: "Config enregistrée"
        "422":
          $ref: "#/components/responses/Validation422"
  /pay-slips:
    get:
      tags: ["Payrolls"]
      summary: "Lister les bulletins du tenant (manager)"
      description: |
        Liste paginee des bulletins sans lignes de detail (`pay_slip_lines`).
        Filtre `payroll_run_id` restreint au tenant courant (404 si le run n'appartient pas a l'entreprise).
      parameters:
        - name: payroll_run_id
          in: query
          required: false
          schema:
            type: integer
        - name: status
          in: query
          required: false
          schema:
            type: string
            enum: [calculated, validated, sent]
        - $ref: "#/components/parameters/PerPage"
        - $ref: "#/components/parameters/Page"
      responses:
        "200":
          description: "Liste paginee"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/PaySlip"
                  meta:
                    type: object
        "403":
          $ref: "#/components/responses/Error403"
        "404":
          $ref: "#/components/responses/Error404"
        "422":
          $ref: "#/components/responses/Validation422"

  /employees:
    get:
      tags: ["Employees"]
      summary: "Lister les employes"
      parameters:
        - $ref: "#/components/parameters/PerPage"
        - $ref: "#/components/parameters/Page"
      responses:
        "200":
          description: "Liste paginee simple"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/EmployeeIndexResponse"
        "403":
          $ref: "#/components/responses/Error403"
    post:
      tags: ["Employees"]
      summary: "Creer un employe"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/StoreEmployeeRequest"
      responses:
        "201":
          description: "Employe cree"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/EmployeeDetail"
        "403":
          $ref: "#/components/responses/Error403"
        "422":
          $ref: "#/components/responses/Validation422"

  /employees/{employee}:
    get:
      tags: ["Employees"]
      summary: "Voir un employe"
      parameters:
        - $ref: "#/components/parameters/EmployeeId"
      responses:
        "200":
          description: "Detail employe"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/EmployeeDetail"
        "404":
          $ref: "#/components/responses/Error404"
    put:
      tags: ["Employees"]
      summary: "Mettre a jour un employe"
      parameters:
        - $ref: "#/components/parameters/EmployeeId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdateEmployeeRequestFull"
      responses:
        "200":
          description: "Employe mis a jour"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/EmployeeDetail"
        "403":
          $ref: "#/components/responses/Error403"
        "404":
          $ref: "#/components/responses/Error404"
        "422":
          $ref: "#/components/responses/Validation422"
    patch:
      tags: ["Employees"]
      summary: "Patch employe"
      parameters:
        - $ref: "#/components/parameters/EmployeeId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdateEmployeeRequestFull"
      responses:
        "200":
          description: "Employe mis a jour"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/EmployeeDetail"
        "403":
          $ref: "#/components/responses/Error403"
        "404":
          $ref: "#/components/responses/Error404"
        "422":
          $ref: "#/components/responses/Validation422"

  /employees/{employee}/archive:
    post:
      tags: ["Employees"]
      summary: "Archiver un employe"
      parameters:
        - $ref: "#/components/parameters/EmployeeId"
      responses:
        "200":
          description: "Employe archive"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    properties:
                      id:
                        type: integer
                      status:
                        type: string
                        example: "archived"
        "403":
          $ref: "#/components/responses/Error403"
        "404":
          $ref: "#/components/responses/Error404"

  /me/qr-profile:
    get:
      tags: ["Employees"]
      summary: "Generer le QR profil employe"
      responses:
        "200":
          description: "QR profil employe signe"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    properties:
                      token:
                        type: string
                      type:
                        type: string
                        example: "employee_profile"
                      expires_at:
                        type: string
                        format: date-time
        "401":
          $ref: "#/components/responses/Error401"

  /company/qr-onboarding:
    get:
      tags: ["Employees"]
      summary: "Generer le QR onboarding entreprise"
      responses:
        "200":
          description: "QR entreprise signe"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    properties:
                      token:
                        type: string
                      type:
                        type: string
                        example: "company_onboarding"
                      expires_at:
                        type: string
                        format: date-time
        "403":
          $ref: "#/components/responses/Error403"

  /company/branding:
    get:
      tags: ["Company"]
      summary: "Lire l'identite visuelle de l'entreprise courante"
      security:
        - BearerAuth: []
      responses:
        "200":
          description: "Branding entreprise"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/CompanyBrandingResponse"
        "401":
          $ref: "#/components/responses/Error401"
    patch:
      tags: ["Company"]
      summary: "Mettre a jour l'identite visuelle entreprise"
      description: "Reserve aux managers principal/RH du tenant courant. Accepte JSON ou multipart pour l'upload logo."
      security:
        - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CompanyBrandingUpdateRequest"
          multipart/form-data:
            schema:
              allOf:
                - $ref: "#/components/schemas/CompanyBrandingUpdateRequest"
                - type: object
                  properties:
                    logo:
                      type: string
                      format: binary
      responses:
        "200":
          description: "Branding mis a jour"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/CompanyBrandingResponse"
                  message:
                    type: string
        "403":
          $ref: "#/components/responses/Error403"
        "422":
          $ref: "#/components/responses/Validation422"

  /company/qr-onboarding/scan-employee:
    post:
      tags: ["Employees"]
      summary: "Lire un QR employe pour pre-remplir l'ajout manager"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: ["qr_token"]
              properties:
                qr_token:
                  type: string
      responses:
        "200":
          description: "Profil pre-rempli"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    properties:
                      prefill:
                        type: object
        "403":
          $ref: "#/components/responses/Error403"
        "422":
          $ref: "#/components/responses/Validation422"

  /company/qr-onboarding/create-employee:
    post:
      tags: ["Employees"]
      summary: "Creer un employe depuis QR"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: ["qr_token", "email"]
              properties:
                qr_token:
                  type: string
                email:
                  type: string
                  format: email
                contract_start:
                  type: string
                  format: date
                schedule_id:
                  type: integer
                salary_type:
                  type: string
                  enum: ["fixed", "hourly", "daily"]
                salary_base:
                  type: number
                hourly_rate:
                  type: number
      responses:
        "201":
          description: "Employe cree depuis QR"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/EmployeeDetail"
        "403":
          $ref: "#/components/responses/Error403"
        "422":
          $ref: "#/components/responses/Validation422"

  /me/company-qr/scan:
    post:
      tags: ["Employees"]
      summary: "Soumettre une demande d'integration via QR entreprise"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: ["qr_token"]
              properties:
                qr_token:
                  type: string
                message:
                  type: string
                  maxLength: 500
      responses:
        "201":
          description: "Demande creee"
        "200":
          description: "Demande deja en attente"
        "422":
          $ref: "#/components/responses/Validation422"

  /employees/{employee}/daily-summary:
    get:
      tags: ["Estimations"]
      summary: "Resume journalier d'un employe"
      parameters:
        - $ref: "#/components/parameters/EmployeeId"
        - name: date
          in: query
          schema:
            type: string
            format: date
      responses:
        "200":
          description: "Resume journalier"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/DailySummary"

  /employees/{employee}/quick-estimate:
    get:
      tags: ["Estimations"]
      summary: "Estimation sur periode"
      parameters:
        - $ref: "#/components/parameters/EmployeeId"
        - name: from
          in: query
          required: true
          schema:
            type: string
            format: date
        - name: to
          in: query
          required: true
          schema:
            type: string
            format: date
      responses:
        "200":
          description: "Estimation"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/QuickEstimate"
        "422":
          $ref: "#/components/responses/Validation422"

  /employees/{employee}/receipt:
    get:
      tags: ["Estimations"]
      summary: "Telecharger le PDF d'estimation"
      parameters:
        - $ref: "#/components/parameters/EmployeeId"
        - name: from
          in: query
          required: true
          schema:
            type: string
            format: date
        - name: to
          in: query
          required: true
          schema:
            type: string
            format: date
      responses:
        "200":
          description: "PDF"
          content:
            application/pdf:
              schema:
                type: string
                format: binary
        "422":
          $ref: "#/components/responses/Validation422"

  /me/daily-summary:
    get:
      tags: ["Me"]
      summary: "Resume journalier utilisateur courant"
      parameters:
        - name: date
          in: query
          schema:
            type: string
            format: date
      responses:
        "200":
          description: "Resume journalier"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/DailySummary"

  /me/quick-estimate:
    get:
      tags: ["Me"]
      summary: "Estimation utilisateur courant"
      parameters:
        - name: from
          in: query
          schema:
            type: string
            format: date
        - name: to
          in: query
          schema:
            type: string
            format: date
      responses:
        "200":
          description: "Estimation"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/QuickEstimate"

  /me/monthly-summary:
    get:
      tags: ["Me"]
      summary: "Resume mensuel utilisateur courant"
      parameters:
        - name: year
          in: query
          schema:
            type: integer
        - name: month
          in: query
          schema:
            type: integer
      responses:
        "200":
          description: "Resume mensuel"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    allOf:
                      - $ref: "#/components/schemas/QuickEstimate"
                      - type: object
                        properties:
                          year:
                            type: integer
                          month:
                            type: integer

  /attendance/check-in:
    post:
      tags: ["Attendance"]
      summary: "Check-in"
      description: |
        Pointage d'arrivee. Si l'entreprise impose le mode `photo_required`
        (voir `AttendanceModeSettings.punch_photo_mode`), le champ multipart
        `punch_photo` est obligatoire, sinon la requete est rejetee avec
        l'erreur `PUNCH_PHOTO_REQUIRED`.
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/GpsPayload"
          multipart/form-data:
            schema:
              allOf:
                - $ref: "#/components/schemas/GpsPayload"
                - type: object
                  properties:
                    punch_photo:
                      type: string
                      format: binary
                      description: "Photo de pointage (obligatoire si l'entreprise impose punch_photo_mode=photo_required). Image, 5 Mo max."
      responses:
        "201":
          description: "Pointage cree"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/AttendanceLog"
        "422":
          description: "Erreur metier ou validation (dont PUNCH_PHOTO_REQUIRED)"
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: "#/components/schemas/ErrorResponse"
                  - $ref: "#/components/schemas/ValidationErrorResponse"

  /attendance/check-out:
    post:
      tags: ["Attendance"]
      summary: "Check-out"
      description: |
        Pointage de depart. Meme contrainte photo obligatoire que le check-in
        quand `punch_photo_mode=photo_required`.
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/GpsPayload"
          multipart/form-data:
            schema:
              allOf:
                - $ref: "#/components/schemas/GpsPayload"
                - type: object
                  properties:
                    punch_photo:
                      type: string
                      format: binary
                      description: "Photo de pointage (obligatoire si l'entreprise impose punch_photo_mode=photo_required). Image, 5 Mo max."
      responses:
        "200":
          description: "Pointage mis a jour"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/AttendanceLog"
        "422":
          description: "Erreur metier ou validation (dont PUNCH_PHOTO_REQUIRED)"
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: "#/components/schemas/ErrorResponse"
                  - $ref: "#/components/schemas/ValidationErrorResponse"

  /attendance/{attendanceLog}/punch-photo:
    get:
      tags: ["Attendance"]
      summary: "Telecharger la photo de pointage d'un log"
      parameters:
        - name: attendanceLog
          in: path
          required: true
          schema:
            type: integer
      responses:
        "200":
          description: "Fichier image de la photo de pointage"
          content:
            image/*:
              schema:
                type: string
                format: binary
        "404":
          description: "Log introuvable ou aucune photo enregistree"

  /attendance/corrections:
    get:
      tags: ["Attendance"]
      summary: "Lister les demandes de correction de pointage"
      parameters:
        - name: status
          in: query
          schema:
            type: string
            enum: ["pending", "approved", "rejected", "applied"]
        - $ref: "#/components/parameters/PerPage"
        - $ref: "#/components/parameters/Page"
      responses:
        "200":
          description: "File de corrections"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/AttendanceCorrectionRequest"
                  meta:
                    $ref: "#/components/schemas/PaginationMeta"
    post:
      tags: ["Attendance"]
      summary: "Demander une correction de pointage"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/AttendanceCorrectionRequestInput"
      responses:
        "201":
          description: "Demande de correction creee"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/AttendanceCorrectionRequest"
                  message:
                    type: string
        "422":
          $ref: "#/components/responses/Validation422"

  /attendance/corrections/{correction}/approve:
    post:
      tags: ["Attendance"]
      summary: "Appliquer une demande de correction de pointage"
      parameters:
        - name: correction
          in: path
          required: true
          schema:
            type: integer
      responses:
        "200":
          description: "Correction appliquee"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/AttendanceCorrectionRequest"
                  attendance_log:
                    $ref: "#/components/schemas/AttendanceLog"
                  message:
                    type: string
        "422":
          $ref: "#/components/responses/Validation422"
    put:
      tags: ["Deprecated"]
      summary: "Approuver une correction de pointage (déprécié — utiliser POST)"
      deprecated: true
      parameters:
        - name: correction
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: "Action effectuée (ancien verbe, déprécié #4930)"
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          description: "Non autorisé"
        "404":
          description: "Introuvable"

  /attendance/corrections/{correction}/reject:
    post:
      tags: ["Attendance"]
      summary: "Refuser une demande de correction de pointage"
      parameters:
        - name: correction
          in: path
          required: true
          schema:
            type: integer
      responses:
        "200":
          description: "Correction refusee"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/AttendanceCorrectionRequest"
                  message:
                    type: string
        "422":
          $ref: "#/components/responses/Validation422"
    put:
      tags: ["Deprecated"]
      summary: "Refuser une correction de pointage (déprécié — utiliser POST)"
      deprecated: true
      parameters:
        - name: correction
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: "Action effectuée (ancien verbe, déprécié #4930)"
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          description: "Non autorisé"
        "404":
          description: "Introuvable"

  /attendance/today:
    get:
      tags: ["Attendance"]
      summary: "Etat du jour"
      parameters:
        - name: employee_id
          in: query
          schema:
            type: integer
        - $ref: "#/components/parameters/PerPage"
        - $ref: "#/components/parameters/Page"
      responses:
        "200":
          description: "Vue single ou collection"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AttendanceTodayEnvelope"
        "422":
          $ref: "#/components/responses/Validation422"

  /attendance/{attendanceLog}:
    put:
      tags: ["Attendance"]
      summary: "Modifier directement un pointage"
      parameters:
        - name: attendanceLog
          in: path
          required: true
          schema:
            type: integer
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                check_in:
                  type: string
                  format: date-time
                  nullable: true
                check_out:
                  type: string
                  format: date-time
                  nullable: true
                notes:
                  type: string
                  maxLength: 500
      responses:
        "200":
          description: "Pointage modifie"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/AttendanceLog"
        "403":
          $ref: "#/components/responses/Error403"
        "422":
          $ref: "#/components/responses/Validation422"

  /attendance:
    get:
      tags: ["Attendance"]
      summary: "Historique de pointage"
      parameters:
        - name: employee_id
          in: query
          schema:
            type: integer
        - name: date_from
          in: query
          schema:
            type: string
            format: date
        - name: date_to
          in: query
          schema:
            type: string
            format: date
        - $ref: "#/components/parameters/PerPage"
        - $ref: "#/components/parameters/Page"
      responses:
        "200":
          description: "Historique"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AttendanceIndexResponse"
        "422":
          $ref: "#/components/responses/Validation422"

  /invitations:
    get:
      tags: ["Invitations"]
      summary: "Lister les invitations de l'entreprise"
      responses:
        "200":
          description: "Invitations"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/Invitation"
        "403":
          $ref: "#/components/responses/Error403"

  /invitations/{invitation}/resend:
    post:
      tags: ["Invitations"]
      summary: "Renvoyer une invitation"
      parameters:
        - $ref: "#/components/parameters/InvitationId"
      responses:
        "200":
          description: "Invitation renvoyee"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    properties:
                      id:
                        type: string
                        format: uuid
                      email:
                        type: string
                        format: email
                      resent_at:
                        type: string
                        format: date-time
        "403":
          $ref: "#/components/responses/Error403"
        "404":
          $ref: "#/components/responses/Error404"
        "410":
          description: "Invitation deja acceptee"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /biometric-enrollment-requests:
    get:
      tags: ["Biometrics"]
      summary: "Lister les demandes biometrie de la company"
      responses:
        "200":
          description: "Demandes"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/BiometricEnrollmentRequest"
        "403":
          $ref: "#/components/responses/Error403"

  /biometric-enrollment-requests/{id}/approve:
    post:
      tags: ["Biometrics"]
      summary: "Approuver une demande biometrie"
      parameters:
        - $ref: "#/components/parameters/GenericIntId"
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/BiometricDecisionRequest"
      responses:
        "200":
          description: "Demande approuvee"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/BiometricEnrollmentRequest"
        "403":
          $ref: "#/components/responses/Error403"

  /biometric-enrollment-requests/{id}/reject:
    post:
      tags: ["Biometrics"]
      summary: "Rejeter une demande biometrie"
      parameters:
        - $ref: "#/components/parameters/GenericIntId"
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/BiometricDecisionRequest"
      responses:
        "200":
          description: "Demande rejetee"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/BiometricEnrollmentRequest"
        "403":
          $ref: "#/components/responses/Error403"

  /kiosks:
    post:
      tags: ["Kiosks"]
      summary: "Enregistrer un kiosk"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/KioskRegisterRequest"
      responses:
        "201":
          description: "Kiosk cree"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/Kiosk"
        "403":
          $ref: "#/components/responses/Error403"

  /kiosks/{deviceCode}/roster:
    get:
      tags: ["Kiosks"]
      summary: "Roster biometrie d'un kiosk"
      security: []
      parameters:
        - $ref: "#/components/parameters/DeviceCode"
        - $ref: "#/components/parameters/KioskTokenHeader"
      responses:
        "200":
          description: "Roster"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/KioskRosterResponse"
        "401":
          $ref: "#/components/responses/Error401"

  /kiosks/{deviceCode}/punch:
    post:
      tags: ["Kiosks"]
      summary: "Pointage via kiosk"
      security: []
      parameters:
        - $ref: "#/components/parameters/DeviceCode"
        - $ref: "#/components/parameters/KioskTokenHeader"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/KioskPunchRequest"
      responses:
        "200":
          description: "Check-out via kiosk"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/KioskPunchResponse"
        "201":
          description: "Check-in via kiosk"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/KioskPunchResponse"
        "401":
          $ref: "#/components/responses/Error401"
        "422":
          $ref: "#/components/responses/Validation422"

  /kiosks/{deviceCode}/sync:
    post:
      tags: ["Kiosks"]
      summary: "Synchroniser des evenements offline"
      security: []
      parameters:
        - $ref: "#/components/parameters/DeviceCode"
        - $ref: "#/components/parameters/KioskTokenHeader"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/KioskSyncRequest"
      responses:
        "200":
          description: "Sync terminee"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/KioskSyncResponse"
        "401":
          $ref: "#/components/responses/Error401"
        "422":
          $ref: "#/components/responses/Validation422"

  /kiosks/{deviceCode}/employee-info:
    post:
      tags: ["Kiosks"]
      summary: "Infos employe pour ecran kiosk (device token)"
      security: []
      parameters:
        - $ref: "#/components/parameters/DeviceCode"
        - $ref: "#/components/parameters/KioskTokenHeader"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [employee_code]
              properties:
                employee_code:
                  type: string
      responses:
        "200":
          description: "Infos employe (nom, poste, horaire)"
        "401":
          $ref: "#/components/responses/Error401"
        "404":
          $ref: "#/components/responses/Error404"

  /kiosks/{deviceCode}/announcements:
    get:
      tags: ["Kiosks"]
      summary: "Annonces du kiosk (device token)"
      security: []
      parameters:
        - $ref: "#/components/parameters/DeviceCode"
        - $ref: "#/components/parameters/KioskTokenHeader"
      responses:
        "200":
          description: "Liste des annonces actives (data peut etre vide)"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      type: object
        "401":
          $ref: "#/components/responses/Error401"

  /kiosks/{deviceCode}/leave-balance:
    post:
      tags: ["Kiosks"]
      summary: "Solde conges employe pour kiosk (device token)"
      security: []
      parameters:
        - $ref: "#/components/parameters/DeviceCode"
        - $ref: "#/components/parameters/KioskTokenHeader"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [employee_code]
              properties:
                employee_code:
                  type: string
      responses:
        "200":
          description: "Soldes conges de l'employe"
        "401":
          $ref: "#/components/responses/Error401"
        "404":
          $ref: "#/components/responses/Error404"

  /kiosks/{deviceCode}/qr-punch:
    post:
      tags: ["Kiosks"]
      summary: "Pointage QR via kiosk (device token)"
      security: []
      parameters:
        - $ref: "#/components/parameters/DeviceCode"
        - $ref: "#/components/parameters/KioskTokenHeader"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [qr_token]
              properties:
                qr_token:
                  type: string
      responses:
        "200":
          description: "Pointage enregistre"
        "401":
          $ref: "#/components/responses/Error401"
        "422":
          $ref: "#/components/responses/Validation422"

  /cameras:
    get:
      tags: ["Cameras"]
      summary: "Lister les cameras"
      responses:
        "200":
          description: "Liste des cameras"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CameraIndexResponse"
        "403":
          $ref: "#/components/responses/Error403"
    post:
      tags: ["Cameras"]
      summary: "Creer une camera"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/StoreCameraRequest"
      responses:
        "201":
          description: "Camera creee"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/Camera"
        "403":
          description: "Module indisponible ou limite plan"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "422":
          $ref: "#/components/responses/Validation422"

  /cameras/test-rtsp:
    post:
      tags: ["Cameras"]
      summary: "Tester une URL RTSP"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/TestRtspRequest"
      responses:
        "200":
          description: "Connexion valide ou test saute"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TestRtspSuccessResponse"
        "408":
          description: "Timeout RTSP"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "422":
          description: "Erreur de validation ou connexion"
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: "#/components/schemas/ErrorResponse"
                  - $ref: "#/components/schemas/ValidationErrorResponse"
        "503":
          description: "Proxy video indisponible"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /cameras/{camera}:
    get:
      tags: ["Cameras"]
      summary: "Voir une camera"
      parameters:
        - $ref: "#/components/parameters/CameraId"
      responses:
        "200":
          description: "Camera"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/Camera"
        "404":
          $ref: "#/components/responses/Error404"
    put:
      tags: ["Cameras"]
      summary: "Mettre a jour une camera"
      parameters:
        - $ref: "#/components/parameters/CameraId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdateCameraRequest"
      responses:
        "200":
          description: "Camera mise a jour"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/Camera"
        "422":
          $ref: "#/components/responses/Validation422"
    patch:
      tags: ["Cameras"]
      summary: "Patch camera"
      parameters:
        - $ref: "#/components/parameters/CameraId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdateCameraRequest"
      responses:
        "200":
          description: "Camera mise a jour"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/Camera"
        "422":
          $ref: "#/components/responses/Validation422"
    delete:
      tags: ["Cameras"]
      summary: "Desactiver une camera"
      parameters:
        - $ref: "#/components/parameters/CameraId"
      responses:
        "200":
          description: "Camera desactivee"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    properties:
                      id:
                        type: integer
                      is_active:
                        type: boolean
                        example: false

  /cameras/{camera}/stream-token:
    get:
      tags: ["Cameras"]
      summary: "Regenerer un stream token client"
      parameters:
        - $ref: "#/components/parameters/CameraId"
      responses:
        "200":
          description: "Payload camera avec token"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/Camera"

  /cameras/{camera}/access-tokens:
    get:
      tags: ["Cameras"]
      summary: "Lister les tokens de partage"
      parameters:
        - $ref: "#/components/parameters/CameraId"
      responses:
        "200":
          description: "Tokens"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/CameraAccessToken"
    post:
      tags: ["Cameras"]
      summary: "Creer un token de partage"
      parameters:
        - $ref: "#/components/parameters/CameraId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/StoreCameraAccessTokenRequest"
      responses:
        "201":
          description: "Token cree"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/CameraAccessTokenWithSecret"
        "422":
          $ref: "#/components/responses/Validation422"

  /cameras/{camera}/access-tokens/{token}:
    delete:
      tags: ["Cameras"]
      summary: "Revoquer un token de partage"
      parameters:
        - $ref: "#/components/parameters/CameraId"
        - $ref: "#/components/parameters/TokenId"
      responses:
        "200":
          description: "Token revoque"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/CameraAccessToken"

  /cameras/{camera}/permissions:
    get:
      tags: ["Cameras"]
      summary: "Lister les permissions internes"
      parameters:
        - $ref: "#/components/parameters/CameraId"
      responses:
        "200":
          description: "Permissions"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/CameraPermission"
    post:
      tags: ["Cameras"]
      summary: "Creer ou remplacer une permission interne"
      parameters:
        - $ref: "#/components/parameters/CameraId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/StoreCameraPermissionRequest"
      responses:
        "201":
          description: "Permission enregistree"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/CameraPermission"
        "422":
          $ref: "#/components/responses/Validation422"

  /cameras/{camera}/permissions/{permission}:
    delete:
      tags: ["Cameras"]
      summary: "Supprimer une permission interne"
      parameters:
        - $ref: "#/components/parameters/CameraId"
        - $ref: "#/components/parameters/PermissionId"
      responses:
        "200":
          description: "Permission supprimee"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    properties:
                      id:
                        type: integer
                      deleted:
                        type: boolean
                        example: true

  /cameras/{camera}/access-logs:
    get:
      tags: ["Cameras"]
      summary: "Lister les logs d'acces camera"
      parameters:
        - $ref: "#/components/parameters/CameraId"
        - $ref: "#/components/parameters/PerPage"
      responses:
        "200":
          description: "Logs sur 30 jours glissants"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CameraAccessLogIndexResponse"

  /internal/camera-token/verify:
    get:
      tags: ["Cameras"]
      summary: "Verification interne MediaMTX"
      security: []
      parameters:
        - name: token
          in: query
          required: true
          schema:
            type: string
        - name: camera_id
          in: query
          required: true
          schema:
            type: integer
        - name: client_ip
          in: query
          schema:
            type: string
        - name: Authorization
          in: header
          required: false
          schema:
            type: string
            example: "Bearer mediamtx-secret"
      responses:
        "200":
          description: "Decision de verification"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/InternalCameraVerifyResponse"
        "401":
          description: "Appelant interne non autorise"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/InternalCameraVerifyResponse"

  /view/cam:
    get:
      tags: ["Cameras"]
      summary: "Viewer public par token"
      security: []
      parameters:
        - name: t
          in: query
          required: true
          schema:
            type: string
      responses:
        "200":
          description: "Payload public de lecture"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/PublicCameraView"
        "404":
          $ref: "#/components/responses/Error404"

  /projects:
    get:
      tags: ["Projects"]
      summary: "Lister les projets"
      parameters:
        - name: status
          in: query
          schema:
            type: string
            enum: ["active", "completed", "archived"]
        - $ref: "#/components/parameters/PerPage"
      responses:
        "200":
          description: "Liste paginee des projets"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ProjectIndexResponse"
        "401":
          $ref: "#/components/responses/Error401"
        "422":
          $ref: "#/components/responses/Validation422"
    post:
      tags: ["Projects"]
      summary: "Creer un projet"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/StoreProjectRequest"
      responses:
        "201":
          description: "Projet cree"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/Project"
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          $ref: "#/components/responses/Error403"
        "422":
          $ref: "#/components/responses/Validation422"

  /projects/{project}:
    get:
      tags: ["Projects"]
      summary: "Voir un projet"
      parameters:
        - $ref: "#/components/parameters/ProjectId"
      responses:
        "200":
          description: "Detail du projet"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/Project"
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          $ref: "#/components/responses/Error403"
        "404":
          $ref: "#/components/responses/Error404"
    put:
      tags: ["Projects"]
      summary: "Mettre a jour un projet"
      parameters:
        - $ref: "#/components/parameters/ProjectId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdateProjectRequest"
      responses:
        "200":
          description: "Projet mis a jour"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/Project"
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          $ref: "#/components/responses/Error403"
        "404":
          $ref: "#/components/responses/Error404"
        "422":
          $ref: "#/components/responses/Validation422"
    patch:
      tags: ["Projects"]
      summary: "Mettre a jour partiellement un projet"
      parameters:
        - $ref: "#/components/parameters/ProjectId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdateProjectRequest"
      responses:
        "200":
          description: "Projet mis a jour"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/Project"
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          $ref: "#/components/responses/Error403"
        "404":
          $ref: "#/components/responses/Error404"
        "422":
          $ref: "#/components/responses/Validation422"
    delete:
      tags: ["Projects"]
      summary: "Supprimer un projet"
      parameters:
        - $ref: "#/components/parameters/ProjectId"
      responses:
        "200":
          description: "Projet supprime"
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
                    example: "Project deleted successfully"
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          $ref: "#/components/responses/Error403"
        "404":
          $ref: "#/components/responses/Error404"

  /tasks:
    get:
      tags: ["Tasks"]
      summary: "Lister les taches"
      parameters:
        - name: project_id
          in: query
          schema:
            type: integer
            minimum: 1
        - name: status
          in: query
          schema:
            type: string
            enum: ["todo", "inprogress", "review", "done", "rejected", "cancelled"]
        - name: priority
          in: query
          schema:
            type: string
            enum: ["low", "normal", "high", "urgent"]
        - name: assigned_to
          in: query
          schema:
            type: integer
            minimum: 1
        - $ref: "#/components/parameters/PerPage"
      responses:
        "200":
          description: "Liste paginee des taches"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TaskIndexResponse"
        "401":
          $ref: "#/components/responses/Error401"
        "422":
          $ref: "#/components/responses/Validation422"
    post:
      tags: ["Tasks"]
      summary: "Creer une tache"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/StoreTaskRequest"
      responses:
        "201":
          description: "Tache creee"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/Task"
        "401":
          $ref: "#/components/responses/Error401"
        "422":
          $ref: "#/components/responses/Validation422"

  /tasks/today:
    get:
      tags: ["Tasks"]
      summary: "Lister les taches du jour"
      parameters:
        - name: assigned_to
          in: query
          schema:
            type: integer
            minimum: 1
      responses:
        "200":
          description: "Taches du jour"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/Task"
        "401":
          $ref: "#/components/responses/Error401"
        "422":
          $ref: "#/components/responses/Validation422"

  /tasks/{task}:
    get:
      tags: ["Tasks"]
      summary: "Voir une tache"
      parameters:
        - $ref: "#/components/parameters/TaskId"
      responses:
        "200":
          description: "Detail de la tache"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/Task"
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          $ref: "#/components/responses/Error403"
        "404":
          $ref: "#/components/responses/Error404"
    put:
      tags: ["Tasks"]
      summary: "Mettre a jour une tache"
      parameters:
        - $ref: "#/components/parameters/TaskId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdateTaskRequest"
      responses:
        "200":
          description: "Tache mise a jour"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/Task"
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          $ref: "#/components/responses/Error403"
        "404":
          $ref: "#/components/responses/Error404"
        "422":
          $ref: "#/components/responses/Validation422"
    patch:
      tags: ["Tasks"]
      summary: "Mettre a jour partiellement une tache"
      parameters:
        - $ref: "#/components/parameters/TaskId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdateTaskRequest"
      responses:
        "200":
          description: "Tache mise a jour"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/Task"
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          $ref: "#/components/responses/Error403"
        "404":
          $ref: "#/components/responses/Error404"
        "422":
          $ref: "#/components/responses/Validation422"
    delete:
      tags: ["Tasks"]
      summary: "Supprimer une tache"
      parameters:
        - $ref: "#/components/parameters/TaskId"
      responses:
        "200":
          description: "Tache supprimee"
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
                    example: "Task deleted successfully"
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          $ref: "#/components/responses/Error403"
        "404":
          $ref: "#/components/responses/Error404"

  /tasks/{task}/comments:
    post:
      tags: ["Tasks"]
      summary: "Ajouter un commentaire sur une tache"
      parameters:
        - $ref: "#/components/parameters/TaskId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/StoreTaskCommentRequest"
      responses:
        "201":
          description: "Commentaire cree"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/TaskComment"
        "401":
          $ref: "#/components/responses/Error401"
        "404":
          $ref: "#/components/responses/Error404"
        "422":
          $ref: "#/components/responses/Validation422"

  /evaluations:
    get:
      tags: ["Evaluations"]
      summary: "Lister les evaluations"
      parameters:
        - name: employee_id
          in: query
          schema:
            type: integer
            minimum: 1
        - name: evaluator_id
          in: query
          schema:
            type: integer
            minimum: 1
        - name: period
          in: query
          schema:
            type: string
            maxLength: 20
        - name: status
          in: query
          schema:
            type: string
            enum: ["draft", "submitted", "acknowledged"]
        - $ref: "#/components/parameters/PerPage"
      responses:
        "200":
          description: "Liste paginee des evaluations"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/EvaluationIndexResponse"
        "401":
          $ref: "#/components/responses/Error401"
        "422":
          $ref: "#/components/responses/Validation422"
    post:
      tags: ["Evaluations"]
      summary: "Creer une evaluation"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/StoreEvaluationRequest"
      responses:
        "201":
          description: "Evaluation creee"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/Evaluation"
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          $ref: "#/components/responses/Error403"
        "422":
          description: "Validation ou conflit metier"
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: "#/components/schemas/ErrorResponse"
                  - $ref: "#/components/schemas/ValidationErrorResponse"

  /evaluations/{evaluation}:
    get:
      tags: ["Evaluations"]
      summary: "Voir une evaluation"
      parameters:
        - $ref: "#/components/parameters/EvaluationId"
      responses:
        "200":
          description: "Detail de l'evaluation"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/Evaluation"
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          $ref: "#/components/responses/Error403"
        "404":
          $ref: "#/components/responses/Error404"
    put:
      tags: ["Evaluations"]
      summary: "Mettre a jour une evaluation"
      parameters:
        - $ref: "#/components/parameters/EvaluationId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdateEvaluationRequest"
      responses:
        "200":
          description: "Evaluation mise a jour"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/Evaluation"
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          $ref: "#/components/responses/Error403"
        "404":
          $ref: "#/components/responses/Error404"
        "422":
          description: "Validation ou regle metier"
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: "#/components/schemas/ErrorResponse"
                  - $ref: "#/components/schemas/ValidationErrorResponse"
    patch:
      tags: ["Evaluations"]
      summary: "Mettre a jour partiellement une evaluation"
      parameters:
        - $ref: "#/components/parameters/EvaluationId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdateEvaluationRequest"
      responses:
        "200":
          description: "Evaluation mise a jour"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/Evaluation"
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          $ref: "#/components/responses/Error403"
        "404":
          $ref: "#/components/responses/Error404"
        "422":
          description: "Validation ou regle metier"
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: "#/components/schemas/ErrorResponse"
                  - $ref: "#/components/schemas/ValidationErrorResponse"
    delete:
      tags: ["Evaluations"]
      summary: "Supprimer une evaluation"
      parameters:
        - $ref: "#/components/parameters/EvaluationId"
      responses:
        "200":
          description: "Evaluation supprimee"
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
                    example: "Evaluation deleted successfully"
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          $ref: "#/components/responses/Error403"
        "404":
          $ref: "#/components/responses/Error404"
        "422":
          description: "Seule une evaluation draft est supprimable"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /evaluations/{evaluation}/submit:
    put:
      tags: ["Evaluations"]
      summary: "Soumettre une evaluation"
      parameters:
        - $ref: "#/components/parameters/EvaluationId"
      responses:
        "200":
          description: "Evaluation soumise"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/Evaluation"
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          $ref: "#/components/responses/Error403"
        "404":
          $ref: "#/components/responses/Error404"
        "422":
          description: "Seule une evaluation draft peut etre soumise"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /evaluations/{evaluation}/acknowledge:
    put:
      tags: ["Evaluations"]
      summary: "Accuser reception d'une evaluation"
      parameters:
        - $ref: "#/components/parameters/EvaluationId"
      responses:
        "200":
          description: "Evaluation accusee de reception"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/Evaluation"
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          $ref: "#/components/responses/Error403"
        "404":
          $ref: "#/components/responses/Error404"
        "422":
          description: "Seule une evaluation submitted peut etre accusee de reception"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /notifications:
    get:
      tags: ["Notifications"]
      summary: "Lister les notifications de l'utilisateur courant"
      parameters:
        - name: unread
          in: query
          schema:
            type: boolean
        - name: type
          in: query
          schema:
            type: string
        - name: sort_dir
          in: query
          schema:
            type: string
            enum: [asc, desc]
        - $ref: "#/components/parameters/PerPage"
      responses:
        "200":
          description: "Liste paginee des notifications"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/NotificationIndexResponse"
        "401":
          $ref: "#/components/responses/Error401"
        "422":
          $ref: "#/components/responses/Validation422"

  /device-tokens:
    get:
      tags: ["Notifications"]
      summary: "Lister les tokens push actifs de l'utilisateur courant"
      responses:
        "200":
          description: "Tokens actifs"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      type: object
                      properties:
                        id:
                          type: integer
                        employee_id:
                          type: integer
                        platform:
                          type: string
                          enum: [ios, android, web]
                        device_name:
                          type: string
                          nullable: true
                        is_active:
                          type: boolean
                        last_used_at:
                          type: string
                          format: date-time
                          nullable: true
        "401":
          $ref: "#/components/responses/Error401"
    post:
      tags: ["Notifications"]
      summary: "Enregistrer ou rafraichir le token FCM du device courant"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [token, platform]
              properties:
                token:
                  type: string
                  maxLength: 512
                  example: "fcm-token-redacted"
                platform:
                  type: string
                  enum: [ios, android, web]
                device_name:
                  type: string
                  maxLength: 120
                  nullable: true
      responses:
        "201":
          description: "Token enregistre"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
        "401":
          $ref: "#/components/responses/Error401"
        "422":
          $ref: "#/components/responses/Validation422"
    delete:
      tags: ["Notifications"]
      summary: "Supprimer le token FCM du device courant au logout"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [token]
              properties:
                token:
                  type: string
                  maxLength: 512
      responses:
        "200":
          description: "Token supprime"
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
                    example: "Device token removed."
        "401":
          $ref: "#/components/responses/Error401"
        "422":
          $ref: "#/components/responses/Validation422"

  /push-notifications/send:
    post:
      tags: ["Notifications"]
      summary: "Envoyer une notification push de test a un employe"
      description: "Reserve aux managers. Sert de preuve de bout en bout : notification app, push FCM et audit communication."
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [employee_id, title, body]
              properties:
                employee_id:
                  type: integer
                title:
                  type: string
                  maxLength: 200
                body:
                  type: string
                  maxLength: 500
      responses:
        "200":
          description: "Resultat d'envoi"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    properties:
                      sent:
                        type: integer
                      results:
                        type: object
                      notification_id:
                        type: integer
                        nullable: true
                      employee_id:
                        type: integer
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          $ref: "#/components/responses/Error403"
        "422":
          $ref: "#/components/responses/Validation422"

  /notification-preferences:
    get:
      tags: ["Notifications"]
      summary: "Lire les preferences de notification de l'utilisateur courant"
      responses:
        "200":
          description: "Preferences de notification"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/NotificationPreference"
        "401":
          $ref: "#/components/responses/Error401"
    patch:
      tags: ["Notifications"]
      summary: "Mettre a jour les preferences de notification de l'utilisateur courant"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/NotificationPreferenceUpdateRequest"
      responses:
        "200":
          description: "Preferences de notification mises a jour"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/NotificationPreference"
        "401":
          $ref: "#/components/responses/Error401"
        "422":
          $ref: "#/components/responses/Validation422"

  /communication/analytics:
    get:
      tags: ["Notifications"]
      summary: "Analytics communication tenant"
      description: "Reserve aux managers principal/RH. Retourne volumes, echecs, canaux et templates sur la periode demandee."
      parameters:
        - name: days
          in: query
          schema:
            type: integer
            minimum: 1
            maximum: 90
            default: 30
      responses:
        "200":
          description: "Synthese communication"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CommunicationAnalyticsResponse"
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          $ref: "#/components/responses/Error403"
        "422":
          $ref: "#/components/responses/Validation422"

  /notifications/read-all:
    post:
      tags: ["Notifications"]
      summary: "Marquer toutes les notifications comme lues"
      description: "Verbe canonique POST (issue #2674) — ne pas utiliser PUT."
      responses:
        "200":
          description: "Notifications marquees comme lues"
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
                    example: "All notifications marked as read"
        "401":
          $ref: "#/components/responses/Error401"

  /departments/{department}/hierarchy:
    get:
      tags: ["HR"]
      summary: "Organigramme d'un departement (arbre department/teams/managers/employees, scope tenant)"
      parameters:
        - in: path
          name: department
          required: true
          schema:
            type: integer
      responses:
        "200":
          description: "Arbre hierarchique du departement"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      type: object
        "401":
          $ref: "#/components/responses/Error401"
        "404":
          $ref: "#/components/responses/Error404"

  /notifications/{notification}/read:
    patch:
      tags: ["Notifications"]
      summary: "Marquer une notification comme lue"
      description: "Verbe canonique PATCH (issue #2674) — ne pas utiliser PUT."
      parameters:
        - $ref: "#/components/parameters/NotificationId"
      responses:
        "200":
          description: "Notification mise a jour"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/Notification"
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          $ref: "#/components/responses/Error403"
        "404":
          $ref: "#/components/responses/Error404"

  /notifications/{notification}:
    delete:
      tags: ["Notifications"]
      summary: "Supprimer une notification"
      parameters:
        - $ref: "#/components/parameters/NotificationId"
      responses:
        "200":
          description: "Notification supprimee"
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
                    example: "Notification deleted successfully"
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          $ref: "#/components/responses/Error403"
        "404":
          $ref: "#/components/responses/Error404"

  # ─── Company announcements (PA2-COMM-004 / PA2-COMM-011) ─────────────

  /announcements:
    get:
      tags: ["Notifications"]
      summary: "Lister les annonces de l'entreprise"
      description: "Un employe ne voit que les annonces publiees qui lui sont adressees (audience entreprise, departement ou employe) ; les managers principal/rh moderent aussi les brouillons et annonces planifiees."
      security:
        - BearerAuth: []
      parameters:
        - name: status
          in: query
          required: false
          schema:
            type: string
            enum: [draft, scheduled, published, cancelled]
        - $ref: "#/components/parameters/PerPage"
      responses:
        "200":
          description: "Liste paginee des annonces"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
        "401":
          $ref: "#/components/responses/Error401"
    post:
      tags: ["Notifications"]
      summary: "Creer une annonce d'entreprise (manager)"
      security:
        - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [title, body, audience_type]
              properties:
                title:
                  type: string
                  maxLength: 200
                body:
                  type: string
                  maxLength: 5000
                priority:
                  type: string
                  enum: [low, normal, high, urgent]
                audience_type:
                  type: string
                  enum: [company, department, employee]
                audience_department_id:
                  type: integer
                  nullable: true
                audience_employee_id:
                  type: integer
                  nullable: true
                status:
                  type: string
                  enum: [draft, scheduled, published]
                scheduled_at:
                  type: string
                  format: date-time
                  nullable: true
                expires_at:
                  type: string
                  format: date-time
                  nullable: true
      responses:
        "201":
          description: "Annonce creee"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          $ref: "#/components/responses/Error403"
        "422":
          $ref: "#/components/responses/Validation422"

  /announcements/{announcement}:
    delete:
      tags: ["Notifications"]
      summary: "Supprimer une annonce d'entreprise"
      security:
        - BearerAuth: []
      parameters:
        - name: announcement
          in: path
          required: true
          schema:
            type: integer
      responses:
        "200":
          description: "Annonce supprimee"
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
        "401":
          $ref: "#/components/responses/Error401"
        "404":
          $ref: "#/components/responses/Error404"

  /announcements/{announcement}/publish:
    post:
      tags: ["Notifications"]
      summary: "Publier immediatement une annonce (moderation PA2-COMM-011)"
      security:
        - BearerAuth: []
      parameters:
        - name: announcement
          in: path
          required: true
          schema:
            type: integer
      responses:
        "200":
          description: "Annonce publiee"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
        "401":
          $ref: "#/components/responses/Error401"
        "404":
          $ref: "#/components/responses/Error404"

  /announcements/{announcement}/cancel:
    post:
      tags: ["Notifications"]
      summary: "Annuler une annonce programmee ou publiee (moderation PA2-COMM-011)"
      security:
        - BearerAuth: []
      parameters:
        - name: announcement
          in: path
          required: true
          schema:
            type: integer
      responses:
        "200":
          description: "Annonce annulee"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
        "401":
          $ref: "#/components/responses/Error401"
        "404":
          $ref: "#/components/responses/Error404"

  # ─── Conversations employe/manager (PA2-COMM-002) ─────────────────────

  /conversations:
    get:
      tags: ["Notifications"]
      summary: "Lister les conversations de l'employe courant"
      security:
        - BearerAuth: []
      parameters:
        - $ref: "#/components/parameters/PerPage"
      responses:
        "200":
          description: "Liste paginee des conversations"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
        "401":
          $ref: "#/components/responses/Error401"
    post:
      tags: ["Notifications"]
      summary: "Ouvrir une conversation (fil de discussion)"
      security:
        - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [title, body]
              properties:
                title:
                  type: string
                  maxLength: 200
                body:
                  type: string
                  maxLength: 2000
                subject_type:
                  type: string
                  nullable: true
                subject_id:
                  type: integer
                  nullable: true
                recipient_id:
                  type: integer
                  nullable: true
                attachment:
                  type: string
                  format: binary
                  nullable: true
      responses:
        "201":
          description: "Conversation creee"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
        "401":
          $ref: "#/components/responses/Error401"
        "422":
          $ref: "#/components/responses/Validation422"

  /conversations/{thread}:
    get:
      tags: ["Notifications"]
      summary: "Detail d'une conversation avec ses messages"
      security:
        - BearerAuth: []
      parameters:
        - name: thread
          in: path
          required: true
          schema:
            type: integer
      responses:
        "200":
          description: "Conversation et messages"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
        "401":
          $ref: "#/components/responses/Error401"
        "404":
          $ref: "#/components/responses/Error404"

  /conversations/{thread}/messages:
    post:
      tags: ["Notifications"]
      summary: "Repondre dans une conversation"
      security:
        - BearerAuth: []
      parameters:
        - name: thread
          in: path
          required: true
          schema:
            type: integer
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [body]
              properties:
                body:
                  type: string
                  maxLength: 2000
                attachment:
                  type: string
                  format: binary
                  nullable: true
      responses:
        "201":
          description: "Message ajoute"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
        "401":
          $ref: "#/components/responses/Error401"
        "404":
          $ref: "#/components/responses/Error404"
        "422":
          $ref: "#/components/responses/Validation422"

  /conversations/{thread}/messages/{message}/attachment:
    get:
      tags: ["Notifications"]
      summary: "Telecharger la piece jointe d'un message de conversation"
      security:
        - BearerAuth: []
      parameters:
        - name: thread
          in: path
          required: true
          schema:
            type: integer
        - name: message
          in: path
          required: true
          schema:
            type: integer
      responses:
        "200":
          description: "Fichier joint"
          content:
            application/octet-stream:
              schema:
                type: string
                format: binary
        "401":
          $ref: "#/components/responses/Error401"
        "404":
          $ref: "#/components/responses/Error404"

  # ─── Support tickets tenant (PA2-COMM-012) ────────────────────────────

  /support-tickets:
    get:
      tags: ["Platform"]
      summary: "Lister les tickets de support ouverts par le tenant"
      security:
        - BearerAuth: []
      parameters:
        - name: status
          in: query
          required: false
          schema:
            type: string
        - name: priority
          in: query
          required: false
          schema:
            type: string
        - $ref: "#/components/parameters/PerPage"
      responses:
        "200":
          description: "Liste paginee des tickets"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                  meta:
                    type: object
        "401":
          $ref: "#/components/responses/Error401"
    post:
      tags: ["Platform"]
      summary: "Ouvrir un ticket de support (employe/manager)"
      security:
        - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [subject, category, message]
              properties:
                subject:
                  type: string
                  maxLength: 200
                category:
                  type: string
                priority:
                  type: string
                  enum: [low, normal, high, urgent]
                message:
                  type: string
                  maxLength: 5000
      responses:
        "201":
          description: "Ticket cree"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
        "401":
          $ref: "#/components/responses/Error401"
        "422":
          $ref: "#/components/responses/Validation422"

  /support-tickets/{supportTicket}:
    get:
      tags: ["Platform"]
      summary: "Detail d'un ticket de support (tenant)"
      security:
        - BearerAuth: []
      parameters:
        - name: supportTicket
          in: path
          required: true
          schema:
            type: integer
      responses:
        "200":
          description: "Ticket avec messages"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
        "401":
          $ref: "#/components/responses/Error401"
        "404":
          $ref: "#/components/responses/Error404"

  /support-tickets/{supportTicket}/close:
    post:
      parameters:
      - in: path
        name: supportTicket
        required: true
        schema:
          type: integer
      responses:
        '200':
          content:
            application/json:
              schema:
                properties:
                  data:
                    type: object
                type: object
          description: Ticket clos
        '401':
          $ref: '#/components/responses/Error401'
        '403':
          $ref: '#/components/responses/Error403'
        '404':
          $ref: '#/components/responses/Error404'
        '422':
          $ref: '#/components/responses/Validation422'
      security:
      - BearerAuth: []
      summary: Cloturer un ticket de support (auteur ou manager tenant)
      tags:
      - Platform
  /support-tickets/{supportTicket}/reply:
    post:
      tags: ["Platform"]
      summary: "Repondre a un ticket de support (tenant)"
      security:
        - BearerAuth: []
      parameters:
        - name: supportTicket
          in: path
          required: true
          schema:
            type: integer
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [message]
              properties:
                message:
                  type: string
                  maxLength: 5000
      responses:
        "200":
          description: "Reponse ajoutee"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
        "401":
          $ref: "#/components/responses/Error401"
        "404":
          $ref: "#/components/responses/Error404"
        "422":
          $ref: "#/components/responses/Validation422"

  # ─── Vehicle Tracking ───────────────────────────────────────────────

  /vehicles:
    get:
      tags: ["Vehicle Tracking"]
      summary: "Liste des vehicules de l'entreprise"
      security:
        - BearerAuth: []
      responses:
        "200":
          description: "Liste paginee des vehicules"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/Vehicle"
        "401":
          $ref: "#/components/responses/Error401"
    post:
      tags: ["Vehicle Tracking"]
      summary: "Creer un vehicule"
      security:
        - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [plate_number]
              properties:
                plate_number:
                  type: string
                brand:
                  type: string
                  nullable: true
                model:
                  type: string
                  nullable: true
                year:
                  type: integer
                  nullable: true
                type:
                  type: string
                  enum: [car, van, truck, motorcycle, bus]
                  nullable: true
                fuel_type:
                  type: string
                  enum: [diesel, gasoline, electric, hybrid, lpg]
                  nullable: true
      responses:
        "201":
          description: "Vehicule cree"
        "422":
          $ref: "#/components/responses/Error422"

  /vehicles/{id}:
    get:
      tags: ["Vehicle Tracking"]
      summary: "Detail d'un vehicule"
      security:
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
      responses:
        "200":
          description: "Detail vehicule"
        "404":
          $ref: "#/components/responses/Error404"
    put:
      tags: ["Vehicle Tracking"]
      summary: "Modifier un vehicule"
      security:
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                plate_number:
                  type: string
                brand:
                  type: string
                  nullable: true
                model:
                  type: string
                  nullable: true
                status:
                  type: string
                  enum: [active, maintenance, decommissioned]
      responses:
        "200":
          description: "Vehicule mis a jour"
    delete:
      tags: ["Vehicle Tracking"]
      summary: "Supprimer un vehicule"
      security:
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
      responses:
        "200":
          description: "Vehicule supprime"

  /vehicles/{id}/position:
    get:
      tags: ["Vehicle Tracking"]
      summary: "Position actuelle du vehicule (via Traccar)"
      security:
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
      responses:
        "200":
          description: "Position GPS actuelle"
          content:
            application/json:
              schema:
                type: object
                properties:
                  latitude:
                    type: number
                    format: double
                  longitude:
                    type: number
                    format: double
                  speed:
                    type: number
                  updated_at:
                    type: string
                    format: date-time

  /vehicles/{id}/trips:
    get:
      tags: ["Vehicle Tracking"]
      summary: "Trajets d'un vehicule"
      security:
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
      responses:
        "200":
          description: "Liste des trajets"

  /vehicles/{id}/alerts:
    get:
      tags: ["Vehicle Tracking"]
      summary: "Alertes d'un vehicule"
      security:
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
      responses:
        "200":
          description: "Liste des alertes"

  /vehicles/{id}/maintenance:
    get:
      tags: ["Vehicle Tracking"]
      summary: "Historique maintenance d'un vehicule"
      security:
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
      responses:
        "200":
          description: "Liste des maintenances"

  /vehicles/{id}/assign:
    post:
      tags: ["Vehicle Tracking"]
      summary: "Assigner un vehicule a un employe"
      security:
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [employee_id]
              properties:
                employee_id:
                  type: integer
      responses:
        "200":
          description: "Vehicule assigne"

  /vehicles/{id}/unassign:
    post:
      tags: ["Vehicle Tracking"]
      summary: "Desassigner un vehicule"
      security:
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
      responses:
        "200":
          description: "Vehicule desassigne"

  /vehicles/{id}/assignments:
    get:
      tags: ["Vehicle Tracking"]
      summary: "Historique des affectations conducteur"
      security:
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
      responses:
        "200":
          description: "Affectations du vehicule"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      type: object
        "401":
          $ref: "#/components/responses/Error401"
        "404":
          $ref: "#/components/responses/Error404"


  /vehicle-trips:
    get:
      tags: ["Vehicle Tracking"]
      summary: "Liste de tous les trajets"
      security:
        - BearerAuth: []
      responses:
        "200":
          description: "Liste paginee des trajets"

  /vehicle-trips/{id}:
    get:
      tags: ["Vehicle Tracking"]
      summary: "Detail d'un trajet"
      security:
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
      responses:
        "200":
          description: "Detail trajet"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
        "401":
          $ref: "#/components/responses/Error401"
        "404":
          $ref: "#/components/responses/Error404"

  /vehicle-alerts:
    get:
      tags: ["Vehicle Tracking"]
      summary: "Liste de toutes les alertes vehicules"
      security:
        - BearerAuth: []
      responses:
        "200":
          description: "Liste paginee des alertes"

  /vehicle-alerts/{id}/acknowledge:
    post:
      tags: ["Vehicle Tracking"]
      summary: "Acquitter une alerte vehicule"
      security:
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
      responses:
        "200":
          description: "Alerte acquittee"

  /vehicle-maintenance:
    get:
      tags: ["Vehicle Tracking"]
      summary: "Liste des maintenances vehicules"
      security:
        - BearerAuth: []
      responses:
        "200":
          description: "Liste paginee"
    post:
      tags: ["Vehicle Tracking"]
      summary: "Planifier une maintenance"
      security:
        - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [vehicle_id, type, service_date]
              properties:
                vehicle_id:
                  type: integer
                type:
                  type: string
                  enum: [oil_change, tire, brake, battery, inspection, repair, other]
                description:
                  type: string
                  nullable: true
                service_date:
                  type: string
                  format: date
                next_service_date:
                  type: string
                  format: date
                  nullable: true
                cost:
                  type: number
                  nullable: true
                currency:
                  type: string
                  nullable: true
      responses:
        "201":
          description: "Maintenance planifiee"

  /vehicle-maintenance/{id}:
    put:
      tags: ["Vehicle Tracking"]
      summary: "Modifier une maintenance"
      security:
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                type:
                  type: string
                  enum: [oil_change, tire, brake, battery, inspection, repair, other]
                description:
                  type: string
                  nullable: true
                cost:
                  type: number
                  nullable: true
                currency:
                  type: string
                  nullable: true
                mileage_at_service:
                  type: integer
                  nullable: true
                service_date:
                  type: string
                  format: date
                next_service_date:
                  type: string
                  format: date
                  nullable: true
                next_service_mileage:
                  type: integer
                  nullable: true
                provider:
                  type: string
                  nullable: true
      responses:
        "200":
          description: "Maintenance mise a jour"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
        "401":
          $ref: "#/components/responses/Error401"
        "404":
          $ref: "#/components/responses/Error404"
        "422":
          $ref: "#/components/responses/Error422"
    delete:
      tags: ["Vehicle Tracking"]
      summary: "Supprimer une maintenance"
      security:
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
      responses:
        "200":
          description: "Maintenance supprimee"
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
        "401":
          $ref: "#/components/responses/Error401"
        "404":
          $ref: "#/components/responses/Error404"

  /tracking/sync-devices:
    post:
      tags: ["Vehicle Tracking"]
      summary: "Synchroniser les devices Traccar"
      security:
        - BearerAuth: []
      responses:
        "200":
          description: "Devices synchronises"

  /tracking/sync-positions:
    post:
      tags: ["Vehicle Tracking"]
      summary: "Synchroniser les positions Traccar"
      security:
        - BearerAuth: []
      responses:
        "200":
          description: "Positions synchronisees"

  /tracking/sync-trips:
    post:
      tags: ["Vehicle Tracking"]
      summary: "Synchroniser les trajets Traccar"
      security:
        - BearerAuth: []
      responses:
        "200":
          description: "Trajets synchronises"

  /fleet/overview:
    get:
      tags: ["Fleet Management"]
      summary: "Vue d'ensemble de la flotte"
      security:
        - BearerAuth: []
      responses:
        "200":
          description: "Statistiques flotte"

  /fleet/live-map:
    get:
      tags: ["Fleet Management"]
      summary: "Carte temps reel des vehicules"
      security:
        - BearerAuth: []
      responses:
        "200":
          description: "Positions live de tous les vehicules"

  /fleet/reports/fuel:
    get:
      tags: ["Fleet Management"]
      summary: "Rapport consommation carburant"
      security:
        - BearerAuth: []
      responses:
        "200":
          description: "Rapport carburant"

  /fleet/reports/mileage:
    get:
      tags: ["Fleet Management"]
      summary: "Rapport kilometrage"
      security:
        - BearerAuth: []
      responses:
        "200":
          description: "Rapport kilometrage"

  /fleet/reports/maintenance-due:
    get:
      tags: ["Fleet Management"]
      summary: "Vehicules necessitant une maintenance"
      security:
        - BearerAuth: []
      responses:
        "200":
          description: "Liste vehicules maintenance due"

  # ─── HR Reports (tenant, managers) ───────────────────────────────

  /reports/headcount:
    get:
      tags: ["HR Reports"]
      summary: "Effectif par département, type de contrat et genre"
      security:
        - BearerAuth: []
      responses:
        "200":
          description: "Payload {total, by_department[], by_contract_type[], by_gender[]}"

  /reports/turnover:
    get:
      tags: ["HR Reports"]
      summary: "Taux de rotation (turnover) sur N mois"
      security:
        - BearerAuth: []
      parameters:
        - in: query
          name: months
          required: false
          schema:
            type: integer
            default: 12
      responses:
        "200":
          description: "Taux de rotation mensuel et agrégé"

  /reports/absenteeism:
    get:
      tags: ["HR Reports"]
      summary: "Absentéisme du mois/année"
      security:
        - BearerAuth: []
      parameters:
        - in: query
          name: month
          required: false
          schema:
            type: integer
        - in: query
          name: year
          required: false
          schema:
            type: integer
      responses:
        "200":
          description: "Taux d'absentéisme et ventilation"

  /reports/payroll-summary:
    get:
      tags: ["HR Reports"]
      summary: "Synthèse paie du mois/année"
      security:
        - BearerAuth: []
      parameters:
        - in: query
          name: month
          required: false
          schema:
            type: integer
        - in: query
          name: year
          required: false
          schema:
            type: integer
      responses:
        "200":
          description: "Synthèse brute/net/cotisations"

  /reports/overtime:
    get:
      tags: ["HR Reports"]
      summary: "Heures supplémentaires du mois/année"
      security:
        - BearerAuth: []
      parameters:
        - in: query
          name: month
          required: false
          schema:
            type: integer
        - in: query
          name: year
          required: false
          schema:
            type: integer
      responses:
        "200":
          description: "Heures supplémentaires par employé"

  /reports/recruitment-pipeline:
    get:
      tags: ["HR Reports"]
      summary: "Pipeline recrutement (candidatures par statut)"
      security:
        - BearerAuth: []
      responses:
        "200":
          description: "Candidatures comptées par statut"

  /reports/training-completion:
    get:
      tags: ["HR Reports"]
      summary: "Taux de complétion des formations"
      security:
        - BearerAuth: []
      responses:
        "200":
          description: "Complétion par formation"

  /reports/loan-summary:
    get:
      tags: ["HR Reports"]
      summary: "Synthèse des avances/prêts employés"
      security:
        - BearerAuth: []
      responses:
        "200":
          description: "Avances et prêts par employé/statut"

  /reports/demographics:
    get:
      tags: ["HR Reports"]
      summary: "Ventilation démographique des employés"
      security:
        - BearerAuth: []
      responses:
        "200":
          description: "Répartition âge/genre/ancienneté/département"

  /reports/cost-analysis:
    get:
      tags: ["HR Reports"]
      summary: "Analyse des coûts (masse salariale par département)"
      security:
        - BearerAuth: []
      responses:
        "200":
          description: "Coûts salariaux ventilés"

  # ─── Departments (tenant, managers) ──────────────────────────────

  /departments:
    get:
      tags: ["Departments"]
      summary: "Lister les départements du tenant"
      security:
        - BearerAuth: []
      responses:
        "200":
          description: "Liste paginée des départements"
    post:
      tags: ["Departments"]
      summary: "Créer un département"
      security:
        - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [name]
              properties:
                name:
                  type: string
                description:
                  type: string
                parent_id:
                  type: integer
                  nullable: true
      responses:
        "201":
          description: "Département créé"

  /departments/{department}:
    get:
      tags: ["Departments"]
      summary: "Détail d'un département"
      security:
        - BearerAuth: []
      parameters:
        - in: path
          name: department
          required: true
          schema:
            type: integer
      responses:
        "200":
          description: "Département"
        "404":
          description: "Département introuvable ou hors tenant"
    put:
      tags: ["Departments"]
      summary: "Mettre à jour un département"
      security:
        - BearerAuth: []
      parameters:
        - in: path
          name: department
          required: true
          schema:
            type: integer
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                name:
                  type: string
                description:
                  type: string
                parent_id:
                  type: integer
                  nullable: true
      responses:
        "200":
          description: "Département mis à jour"
    patch:
      tags: ["Departments"]
      summary: "Mise à jour partielle d'un département"
      security:
        - BearerAuth: []
      parameters:
        - in: path
          name: department
          required: true
          schema:
            type: integer
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                name:
                  type: string
                description:
                  type: string
      responses:
        "200":
          description: "Département mis à jour"
    delete:
      tags: ["Departments"]
      summary: "Supprimer un département"
      security:
        - BearerAuth: []
      parameters:
        - in: path
          name: department
          required: true
          schema:
            type: integer
      responses:
        "204":
          description: "Département supprimé"

  /ai/chat:
    post:
      tags: ["AI"]
      summary: "Envoyer un message au chatbot IA"
      security:
        - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [message]
              properties:
                message:
                  type: string
                conversation_id:
                  type: integer
                  nullable: true
      responses:
        "200":
          description: "Reponse IA"
          content:
            application/json:
              schema:
                type: object
                properties:
                  reply:
                    type: string
                  conversation_id:
                    type: integer
                  tools_used:
                    type: array
                    items:
                      type: string

  /ai/chat/history:
    get:
      tags: ["AI"]
      summary: "Historique des conversations IA"
      security:
        - BearerAuth: []
      responses:
        "200":
          description: "Liste des conversations"

  /ai/chat/{conversationId}:
    delete:
      tags: ["AI"]
      summary: "Supprimer une conversation IA"
      security:
        - BearerAuth: []
      parameters:
        - name: conversationId
          in: path
          required: true
          schema:
            type: integer
      responses:
        "200":
          description: "Conversation supprimee"

  /ai/tools:
    get:
      tags: ["AI"]
      summary: "Liste des outils IA disponibles"
      security:
        - BearerAuth: []
      responses:
        "200":
          description: "Liste des tools enregistres dans le ToolRegistry"

  /ai/analytics/usage:
    get:
      tags: ["AI Analytics"]
      summary: "Metriques d'usage IA"
      security:
        - BearerAuth: []
      responses:
        "200":
          description: "Stats usage (nb conversations, messages, tokens)"

  /ai/analytics/costs:
    get:
      tags: ["AI Analytics"]
      summary: "Couts IA par periode"
      security:
        - BearerAuth: []
      responses:
        "200":
          description: "Ventilation couts par provider/modele"

  /ai/analytics/tools:
    get:
      tags: ["AI Analytics"]
      summary: "Usage des outils IA"
      security:
        - BearerAuth: []
      responses:
        "200":
          description: "Stats par outil (appels, succes, erreurs)"

  /ai/analytics/errors:
    get:
      tags: ["AI Analytics"]
      summary: "Erreurs IA recentes"
      security:
        - BearerAuth: []
      responses:
        "200":
          description: "Liste des erreurs IA avec details"



  # =============================================
  # ITERATION 33.1 — CORE RH
  # =============================================

  # --- Absences ---
  /absences:
    get:
      tags: ["Absences"]
      summary: "Lister les absences"
      parameters:
        - name: status
          in: query
          schema: { type: string, enum: [pending, approved, rejected, cancelled] }
        - name: employee_id
          in: query
          schema: { type: integer }
        - name: month
          in: query
          schema: { type: integer }
        - name: year
          in: query
          schema: { type: integer }
        - name: per_page
          in: query
          schema: { type: integer, default: 15 }
        - name: sort_by
          in: query
          schema: { type: string, default: created_at }
        - name: sort_dir
          in: query
          schema: { type: string, enum: [asc, desc], default: desc }
      responses:
        "200":
          description: "Liste paginee des absences"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/Absence"
                  meta:
                    $ref: "#/components/schemas/PaginationMeta"
        "401":
          $ref: "#/components/responses/Error401"
    post:
      tags: ["Absences"]
      summary: "Demander une absence"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/StoreAbsenceRequest"
      responses:
        "201":
          description: "Absence creee"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/Absence"
        "401":
          $ref: "#/components/responses/Error401"
        "422":
          $ref: "#/components/responses/Validation422"

  /absences/{absence}:
    put:
      parameters:
      - in: path
        name: absence
        required: true
        schema:
          type: integer
      requestBody:
        content:
          application/json:
            schema:
              properties:
                end_date:
                  format: date
                  type: string
                reason:
                  type: string
                start_date:
                  format: date
                  type: string
              type: object
        required: true
      responses:
        '200':
          content:
            application/json:
              schema:
                properties:
                  data:
                    $ref: '#/components/schemas/Absence'
                type: object
          description: Absence modifiee
        '401':
          $ref: '#/components/responses/Error401'
        '403':
          $ref: '#/components/responses/Error403'
        '404':
          $ref: '#/components/responses/Error404'
        '422':
          $ref: '#/components/responses/Validation422'
      summary: Modifier une demande d'absence en attente (dates/raison)
      tags:
      - Absences
    get:
      tags: ["Absences"]
      summary: "Voir une absence"
      parameters:
        - name: absence
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: "Detail absence"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/Absence"
        "401":
          $ref: "#/components/responses/Error401"
        "404":
          $ref: "#/components/responses/Error404"
    delete:
      tags: ["Absences"]
      summary: "Annuler une absence (employe proprietaire)"
      parameters:
        - name: absence
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: "Absence annulee"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/Absence"
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          $ref: "#/components/responses/Error403"

  /absences/{absence}/approve:
    post:
      tags: ["Absences"]
      summary: "Approuver une absence (manager)"
      parameters:
        - name: absence
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: "Absence approuvee"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/Absence"
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          $ref: "#/components/responses/Error403"
    put:
      tags: ["Deprecated"]
      summary: "Approuver une absence (déprécié — utiliser POST)"
      deprecated: true
      parameters:
        - name: absence
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: "Action effectuée (ancien verbe, déprécié #4930)"
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          description: "Non autorisé"
        "404":
          description: "Introuvable"

  /absences/{absence}/reject:
    post:
      tags: ["Absences"]
      summary: "Rejeter une absence (manager)"
      parameters:
        - name: absence
          in: path
          required: true
          schema: { type: integer }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [rejected_reason]
              properties:
                rejected_reason:
                  type: string
                  maxLength: 500
      responses:
        "200":
          description: "Absence rejetee"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/Absence"
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          $ref: "#/components/responses/Error403"

  # --- Salary Advances ---
    put:
      tags: ["Deprecated"]
      summary: "Refuser une absence (déprécié — utiliser POST)"
      deprecated: true
      parameters:
        - name: absence
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: "Action effectuée (ancien verbe, déprécié #4930)"
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          description: "Non autorisé"
        "404":
          description: "Introuvable"

  /salary-advances:
    get:
      tags: ["Salary Advances"]
      summary: "Lister les avances sur salaire"
      parameters:
        - name: status
          in: query
          schema: { type: string, enum: [pending, approved, rejected, cancelled] }
        - name: employee_id
          in: query
          schema: { type: integer }
        - name: per_page
          in: query
          schema: { type: integer, default: 15 }
      responses:
        "200":
          description: "Liste paginee"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/SalaryAdvance"
                  meta:
                    $ref: "#/components/schemas/PaginationMeta"
        "401":
          $ref: "#/components/responses/Error401"
    post:
      tags: ["Salary Advances"]
      summary: "Demander une avance sur salaire"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/StoreSalaryAdvanceRequest"
      responses:
        "201":
          description: "Avance creee"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/SalaryAdvance"
        "401":
          $ref: "#/components/responses/Error401"
        "422":
          $ref: "#/components/responses/Validation422"

  /salary-advances/{salaryAdvance}:
    get:
      tags: ["Salary Advances"]
      summary: "Voir une avance"
      parameters:
        - name: salaryAdvance
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: "Detail avance"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/SalaryAdvance"
        "401":
          $ref: "#/components/responses/Error401"
        "404":
          $ref: "#/components/responses/Error404"
    delete:
      tags: ["Salary Advances"]
      summary: "Annuler une avance (employe proprietaire)"
      parameters:
        - name: salaryAdvance
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: "Avance annulee"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/SalaryAdvance"
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          $ref: "#/components/responses/Error403"

  /salary-advances/{salaryAdvance}/approve:
    post:
      tags: ["Salary Advances"]
      summary: "Approuver une avance (manager)"
      parameters:
        - name: salaryAdvance
          in: path
          required: true
          schema: { type: integer }
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                decision_comment:
                  type: string
      responses:
        "200":
          description: "Avance approuvee"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/SalaryAdvance"
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          $ref: "#/components/responses/Error403"
    put:
      tags: ["Deprecated"]
      summary: "Approuver une avance sur salaire (déprécié — utiliser POST)"
      deprecated: true
      parameters:
        - name: salaryAdvance
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: "Action effectuée (ancien verbe, déprécié #4930)"
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          description: "Non autorisé"
        "404":
          description: "Introuvable"

  /salary-advances/{salaryAdvance}/reject:
    post:
      tags: ["Salary Advances"]
      summary: "Rejeter une avance (manager)"
      parameters:
        - name: salaryAdvance
          in: path
          required: true
          schema: { type: integer }
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                decision_comment:
                  type: string
      responses:
        "200":
          description: "Avance rejetee"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/SalaryAdvance"
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          $ref: "#/components/responses/Error403"
    put:
      tags: ["Deprecated"]
      summary: "Refuser une avance sur salaire (déprécié — utiliser POST)"
      deprecated: true
      parameters:
        - name: salaryAdvance
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: "Action effectuée (ancien verbe, déprécié #4930)"
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          description: "Non autorisé"
        "404":
          description: "Introuvable"

  /salary-advances/{salaryAdvance}/manager-approve:
    post:
      tags: ["Salary Advances"]
      summary: "Valider une avance avant paiement (manager/RH)"
      parameters:
        - name: salaryAdvance
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: "Avance validee par le manager"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/SalaryAdvance"
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          $ref: "#/components/responses/Error403"
    put:
      tags: ["Deprecated"]
      summary: "Validation manager d'une avance (déprécié — utiliser POST)"
      deprecated: true
      parameters:
        - name: salaryAdvance
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: "Action effectuée (ancien verbe, déprécié #4930)"
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          description: "Non autorisé"
        "404":
          description: "Introuvable"

  /salary-advances/{salaryAdvance}/mark-paid:
    put:
      tags: ["Salary Advances"]
      summary: "Declarer l'avance comme envoyee (manager/RH)"
      parameters:
        - name: salaryAdvance
          in: path
          required: true
          schema: { type: integer }
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                payment_reference:
                  type: string
                  nullable: true
                payment_note:
                  type: string
                  nullable: true
      responses:
        "200":
          description: "Paiement declare"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/SalaryAdvance"
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          $ref: "#/components/responses/Error403"

  /salary-advances/{salaryAdvance}/confirm-received:
    put:
      tags: ["Salary Advances"]
      summary: "Confirmer la reception de l'avance (employe)"
      parameters:
        - name: salaryAdvance
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: "Reception confirmee"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/SalaryAdvance"
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          $ref: "#/components/responses/Error403"

  /salary-advances/{salaryAdvance}/dispute:
    put:
      tags: ["Salary Advances"]
      summary: "Ouvrir une reclamation sur une avance dont le paiement a ete declare mais non recu (employe)"
      description: "PA2-PAY-015. Requiert que validation_status soit payment_declared."
      operationId: disputeSalaryAdvance
      parameters:
        - name: salaryAdvance
          in: path
          required: true
          schema: { type: integer }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [dispute_reason]
              properties:
                dispute_reason:
                  type: string
                  minLength: 5
                  maxLength: 1000
      responses:
        "200":
          description: "Reclamation ouverte"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/SalaryAdvance"
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          description: "Seul le proprietaire de l'avance peut la contester"
        "404":
          $ref: "#/components/responses/Error404"
        "422":
          description: "Le paiement doit avoir ete declare avant de pouvoir etre conteste"

  /salary-advances/{salaryAdvance}/resolve-dispute:
    put:
      tags: ["Salary Advances"]
      summary: "Resoudre une reclamation sur une avance (manager/RH)"
      description: "PA2-PAY-015. Requiert que validation_status soit disputed. resolution=confirmed repasse a employee_confirmed ; resolution=reopened repasse a payment_declared."
      operationId: resolveSalaryAdvanceDispute
      parameters:
        - name: salaryAdvance
          in: path
          required: true
          schema: { type: integer }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [resolution]
              properties:
                resolution:
                  type: string
                  enum: [confirmed, reopened]
                dispute_resolution_note:
                  type: string
                  nullable: true
                  maxLength: 1000
      responses:
        "200":
          description: "Reclamation resolue"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/SalaryAdvance"
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          $ref: "#/components/responses/Error403"
        "404":
          $ref: "#/components/responses/Error404"
        "422":
          description: "L'avance n'est pas actuellement en litige"

  /salary-advances/{salaryAdvance}/proof:
    get:
      tags: ["Salary Advances"]
      summary: "Telecharger le justificatif joint a une demande d'avance (employe proprietaire ou manager)"
      description: "PA2-MOB-006. Retourne le fichier binaire (image ou PDF) attache lors de la creation de la demande."
      operationId: downloadSalaryAdvanceProof
      parameters:
        - name: salaryAdvance
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: "Fichier binaire du justificatif"
          content:
            application/octet-stream:
              schema:
                type: string
                format: binary
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          $ref: "#/components/responses/Error403"
        "404":
          description: "Avance introuvable, ou aucun justificatif joint, ou fichier manquant sur le disque"

  # --- Leave Policies ---
  /leave-policies:
    get:
      tags: ["Leave Policies"]
      summary: "Lister les politiques de conges"
      parameters:
        - name: per_page
          in: query
          schema: { type: integer, default: 15 }
      responses:
        "200":
          description: "Liste paginee"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/LeavePolicy"
                  meta:
                    $ref: "#/components/schemas/PaginationMeta"
        "401":
          $ref: "#/components/responses/Error401"
    post:
      tags: ["Leave Policies"]
      summary: "Creer une politique de conges"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/StoreLeavePolicyRequest"
      responses:
        "201":
          description: "Politique creee"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/LeavePolicy"
        "401":
          $ref: "#/components/responses/Error401"
        "422":
          $ref: "#/components/responses/Validation422"

  /leave-policies/{leavePolicy}:
    get:
      tags: ["Leave Policies"]
      summary: "Voir une politique"
      parameters:
        - name: leavePolicy
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: "Detail politique"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/LeavePolicy"
        "401":
          $ref: "#/components/responses/Error401"
    put:
      tags: ["Leave Policies"]
      summary: "Modifier une politique"
      parameters:
        - name: leavePolicy
          in: path
          required: true
          schema: { type: integer }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/StoreLeavePolicyRequest"
      responses:
        "200":
          description: "Politique modifiee"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/LeavePolicy"
        "401":
          $ref: "#/components/responses/Error401"
        "422":
          $ref: "#/components/responses/Validation422"
    delete:
      tags: ["Leave Policies"]
      summary: "Supprimer une politique"
      parameters:
        - name: leavePolicy
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: "Politique supprimee"
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          $ref: "#/components/responses/Error403"

  /leave-balances:
    get:
      tags: ["Leave Policies"]
      summary: "Soldes de conges globaux (manager)"
      parameters:
        - name: employee_id
          in: query
          schema: { type: integer }
      responses:
        "200":
          description: "Liste des soldes"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/LeaveBalance"
        "401":
          $ref: "#/components/responses/Error401"

  /me/leave-balances:
    get:
      tags: ["Me"]
      summary: "Mes soldes de conges"
      responses:
        "200":
          description: "Soldes employe courant"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/LeaveBalance"
        "401":
          $ref: "#/components/responses/Error401"

  /leave-accruals:
    get:
      tags: ["Leave Policies"]
      summary: "Lister les accruals"
      responses:
        "200":
          description: "Liste des accruals"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/LeaveAccrual"
        "401":
          $ref: "#/components/responses/Error401"
    post:
      tags: ["Leave Policies"]
      summary: "Creer un accrual"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/StoreLeaveAccrualRequest"
      responses:
        "201":
          description: "Accrual cree"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/LeaveAccrual"
        "401":
          $ref: "#/components/responses/Error401"
        "422":
          $ref: "#/components/responses/Validation422"

  # --- Contracts ---
  /contracts:
    get:
      tags: ["Contracts"]
      summary: "Lister les contrats"
      parameters:
        - name: employee_id
          in: query
          schema: { type: integer }
        - name: status
          in: query
          schema: { type: string, enum: [draft, active, suspended, terminated, expired] }
        - name: per_page
          in: query
          schema: { type: integer, default: 15 }
      responses:
        "200":
          description: "Liste paginee"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/Contract"
                  meta:
                    $ref: "#/components/schemas/PaginationMeta"
        "401":
          $ref: "#/components/responses/Error401"
    post:
      tags: ["Contracts"]
      summary: "Creer un contrat"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/StoreContractRequest"
      responses:
        "201":
          description: "Contrat cree"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/Contract"
        "401":
          $ref: "#/components/responses/Error401"
        "422":
          $ref: "#/components/responses/Validation422"

  /contracts/expiring:
    get:
      tags: ["Contracts"]
      summary: "Contrats expirant bientot"
      parameters:
        - name: days
          in: query
          schema: { type: integer, default: 30 }
      responses:
        "200":
          description: "Liste des contrats expirants"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/Contract"
        "401":
          $ref: "#/components/responses/Error401"

  /contracts/{contract}:
    get:
      tags: ["Contracts"]
      summary: "Voir un contrat"
      parameters:
        - name: contract
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: "Detail contrat avec avenants"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/Contract"
        "401":
          $ref: "#/components/responses/Error401"
        "404":
          $ref: "#/components/responses/Error404"
    put:
      tags: ["Contracts"]
      summary: "Modifier un contrat"
      parameters:
        - name: contract
          in: path
          required: true
          schema: { type: integer }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdateContractRequest"
      responses:
        "200":
          description: "Contrat modifie"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/Contract"
        "401":
          $ref: "#/components/responses/Error401"
        "422":
          $ref: "#/components/responses/Validation422"

  /contracts/{contract}/activate:
    post:
      tags: ["Contracts"]
      summary: "Activer un contrat draft"
      parameters:
        - name: contract
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: "Contrat active"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/Contract"
        "401":
          $ref: "#/components/responses/Error401"
        "422":
          description: "Only draft contracts can be activated"

  /contracts/{contract}/suspend:
    post:
      tags: ["Contracts"]
      summary: "Suspendre un contrat actif"
      parameters:
        - name: contract
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: "Contrat suspendu"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/Contract"
        "401":
          $ref: "#/components/responses/Error401"
        "422":
          description: "Only active contracts can be suspended"

  /contracts/{contract}/terminate:
    post:
      tags: ["Contracts"]
      summary: "Resilier un contrat"
      parameters:
        - name: contract
          in: path
          required: true
          schema: { type: integer }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [termination_reason]
              properties:
                termination_reason:
                  type: string
                  maxLength: 500
      responses:
        "200":
          description: "Contrat resilie"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/Contract"
        "401":
          $ref: "#/components/responses/Error401"
        "422":
          description: "Contract must be active or suspended to terminate"

  /contracts/{contract}/renew:
    post:
      tags: ["Contracts"]
      summary: "Renouveler un contrat"
      parameters:
        - name: contract
          in: path
          required: true
          schema: { type: integer }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [start_date]
              properties:
                start_date:
                  type: string
                  format: date
                end_date:
                  type: string
                  format: date
                  nullable: true
                base_salary:
                  type: number
                  nullable: true
      responses:
        "201":
          description: "Nouveau contrat cree (ancien expire)"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/Contract"
        "401":
          $ref: "#/components/responses/Error401"
        "422":
          $ref: "#/components/responses/Validation422"

  /contracts/{contract}/amendments:
    get:
      tags: ["Contracts"]
      summary: "Lister les avenants"
      parameters:
        - name: contract
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: "Liste des avenants"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/ContractAmendment"
        "401":
          $ref: "#/components/responses/Error401"
    post:
      tags: ["Contracts"]
      summary: "Creer un avenant"
      parameters:
        - name: contract
          in: path
          required: true
          schema: { type: integer }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [amendment_type, changes, effective_date]
              properties:
                amendment_type:
                  type: string
                  enum: [salary_change, position_change, hours_change, renewal, other]
                changes:
                  type: object
                effective_date:
                  type: string
                  format: date
                reason:
                  type: string
                  nullable: true
      responses:
        "201":
          description: "Avenant cree"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/ContractAmendment"
        "401":
          $ref: "#/components/responses/Error401"
        "422":
          $ref: "#/components/responses/Validation422"

  /contracts/{contract}/generate-pdf:
    get:
      tags: ["Contracts"]
      summary: "Donnees PDF du contrat"
      parameters:
        - name: contract
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: "Donnees contrat pour generation PDF"
        "401":
          $ref: "#/components/responses/Error401"
        "404":
          $ref: "#/components/responses/Error404"

  /me/vehicles:
    get:
      tags: ["Vehicle Tracking"]
      summary: "Vehicules assignes a l'employe connecte (mobile)"
      description: "Liste des vehicules actifs assignes au conducteur connecte, avec la derniere position Traccar aplatie (shape alignee sur le modele mobile VehiclePosition)."
      security:
        - BearerAuth: []
      responses:
        "200":
          description: "Vehicules de l'employe"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      type: object
                      properties:
                        vehicle_id: { type: integer }
                        plate_number: { type: string }
                        brand: { type: string, nullable: true }
                        model: { type: string, nullable: true }
                        type: { type: string, nullable: true }
                        latitude: { type: number, format: double, nullable: true }
                        longitude: { type: number, format: double, nullable: true }
                        speed: { type: number, format: double, nullable: true }
                        updated_at: { type: string, format: date-time, nullable: true }
        "401":
          $ref: "#/components/responses/Error401"

  /contracts/{contract}/pdf:
    get:
      tags: ["Contracts"]
      summary: "Alias PDF du contrat (issue #2226)"
      description: "Alias retro-compatible de GET /contracts/{contract}/generate-pdf — meme generateur, meme shape."
      parameters:
        - name: contract
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: "Donnees contrat pour generation PDF"
        "401":
          $ref: "#/components/responses/Error401"
        "404":
          $ref: "#/components/responses/Error404"

  /me/contracts:
    get:
      tags: ["Me"]
      summary: "Mes contrats"
      responses:
        "200":
          description: "Liste de mes contrats"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/Contract"
        "401":
          $ref: "#/components/responses/Error401"

  # --- HR Referentials ---
  /positions:
    get:
      tags: ["Referentials"]
      summary: "Lister les postes"
      parameters:
        - name: per_page
          in: query
          schema: { type: integer, default: 15 }
      responses:
        "200":
          description: "Liste paginee des postes"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/Position"
        "401":
          $ref: "#/components/responses/Error401"
    post:
      tags: ["Referentials"]
      summary: "Creer un poste"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [name]
              properties:
                name:
                  type: string
                  maxLength: 100
                description:
                  type: string
                  nullable: true
                department_id:
                  type: integer
                  nullable: true
      responses:
        "201":
          description: "Poste cree"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/Position"
        "401":
          $ref: "#/components/responses/Error401"
        "422":
          $ref: "#/components/responses/Validation422"

  /positions/{position}:
    get:
      tags: ["Referentials"]
      summary: "Voir un poste"
      parameters:
        - name: position
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: "Detail poste"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/Position"
        "401":
          $ref: "#/components/responses/Error401"
    put:
      tags: ["Referentials"]
      summary: "Modifier un poste"
      parameters:
        - name: position
          in: path
          required: true
          schema: { type: integer }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                name:
                  type: string
                  maxLength: 100
                description:
                  type: string
                  nullable: true
                department_id:
                  type: integer
                  nullable: true
      responses:
        "200":
          description: "Poste modifie"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/Position"
        "401":
          $ref: "#/components/responses/Error401"
    delete:
      tags: ["Referentials"]
      summary: "Supprimer un poste"
      parameters:
        - name: position
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: "Poste supprime"
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          $ref: "#/components/responses/Error403"

  /sites:
    get:
      tags: ["Referentials"]
      summary: "Lister les sites"
      parameters:
        - name: per_page
          in: query
          schema: { type: integer, default: 15 }
      responses:
        "200":
          description: "Liste des sites"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/Site"
        "401":
          $ref: "#/components/responses/Error401"
    post:
      tags: ["Referentials"]
      summary: "Creer un site"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [name]
              properties:
                name:
                  type: string
                address:
                  type: string
                  nullable: true
                city:
                  type: string
                  nullable: true
                country:
                  type: string
                  nullable: true
                latitude:
                  type: number
                  nullable: true
                longitude:
                  type: number
                  nullable: true
                geofence_radius:
                  type: integer
                  nullable: true
      responses:
        "201":
          description: "Site cree"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/Site"
        "401":
          $ref: "#/components/responses/Error401"
        "422":
          $ref: "#/components/responses/Validation422"

  /sites/{site}:
    get:
      tags: ["Referentials"]
      summary: "Voir un site"
      parameters:
        - name: site
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: "Detail site"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/Site"
        "401":
          $ref: "#/components/responses/Error401"
    put:
      tags: ["Referentials"]
      summary: "Modifier un site"
      parameters:
        - name: site
          in: path
          required: true
          schema: { type: integer }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                name:
                  type: string
                address:
                  type: string
                  nullable: true
                city:
                  type: string
                  nullable: true
                country:
                  type: string
                  nullable: true
                latitude:
                  type: number
                  nullable: true
                longitude:
                  type: number
                  nullable: true
                geofence_radius:
                  type: integer
                  nullable: true
      responses:
        "200":
          description: "Site modifie"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/Site"
        "401":
          $ref: "#/components/responses/Error401"
    delete:
      tags: ["Referentials"]
      summary: "Supprimer un site"
      parameters:
        - name: site
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: "Site supprime"
        "401":
          $ref: "#/components/responses/Error401"

  /attendance/anomalies:
    get:
      tags: ["Attendance"]
      summary: "Anomalies de pointage"
      parameters:
        - name: month
          in: query
          schema: { type: integer }
        - name: year
          in: query
          schema: { type: integer }
      responses:
        "200":
          description: "Liste des anomalies"
        "401":
          $ref: "#/components/responses/Error401"

  /attendance/monthly-report:
    get:
      tags: ["Attendance"]
      summary: "Rapport mensuel de pointage"
      parameters:
        - name: month
          in: query
          schema: { type: integer }
        - name: year
          in: query
          schema: { type: integer }
        - name: employee_id
          in: query
          schema: { type: integer }
      responses:
        "200":
          description: "Rapport mensuel"
        "401":
          $ref: "#/components/responses/Error401"

  /supported-countries:
    get:
      tags: ["Payroll Engine"]
      summary: "Registre des pays supportes"
      description: "Source unique de verite (MULTI-PAYS #1867) : pour chaque pays du referentiel — code ISO, libelle, langue, devise, fuseau horaire, niveau de confiance des regles de paie et disponibilite (regles resolubles ou non)."
      operationId: listSupportedCountries
      responses:
        "200":
          description: "Registre des pays"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      type: object
                      properties:
                        country: { type: string }
                        label: { type: string }
                        language: { type: string }
                        currency: { type: string }
                        timezone: { type: string }
                        confidence: { type: string, enum: [production, pilot, placeholder, unknown] }
                        available: { type: boolean }
                        compliance:
                          type: object
                          description: "Bloc conformite par niveau de confiance (contrat #1872, issue #2127)."
                          properties:
                            level: { type: string, enum: [production, pilot, placeholder, unknown] }
                            warning: { type: string }
                            warning_localized: { type: string }
                            source:
                              type: string
                              nullable: true
                            verified_at:
                              type: string
                              nullable: true
        "401":
          $ref: "#/components/responses/Error401"

  /me/pay-slips:
    get:
      tags: ["Me"]
      summary: "Mes bulletins de paie"
      parameters:
        - name: per_page
          in: query
          schema: { type: integer, default: 15 }
      responses:
        "200":
          description: "Liste de mes bulletins"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/PaySlip"
        "401":
          $ref: "#/components/responses/Error401"

  /me/pay-slips/{paySlip}:
    get:
      tags: ["Me"]
      summary: "Voir un de mes bulletins de paie"
      operationId: showMyPaySlip
      parameters:
        - name: paySlip
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: "Detail du bulletin (doit appartenir a l'employe connecte)"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/PaySlip"
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          $ref: "#/components/responses/Error403"
        "404":
          $ref: "#/components/responses/Error404"

  /me/pay-slips/{paySlip}/pdf:
    get:
      tags: ["Me"]
      summary: "Telecharger un de mes bulletins de paie en PDF"
      operationId: downloadMyPaySlipPdf
      parameters:
        - name: paySlip
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: "Fichier PDF"
          content:
            application/pdf:
              schema:
                type: string
                format: binary
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          $ref: "#/components/responses/Error403"
        "404":
          $ref: "#/components/responses/Error404"

  /me/pay-slips/{paySlip}/document:
    get:
      tags: ["Me"]
      summary: "Telecharger mon bulletin archive dans le Cabinet (PDF)"
      description: "Issue #1817 : apres cloture du run de paie, le bulletin est archive automatiquement dans le Cabinet employe (CabinetDocument payslip, lecture seule). Retourne le PDF lui-meme : document archive si present, sinon repli sur le bulletin standard."
      operationId: archivedMyPaySlipDocument
      parameters:
        - name: paySlip
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: "Fichier PDF du bulletin (archive Cabinet ou standard)"
          content:
            application/pdf:
              schema:
                type: string
                format: binary
        "401":
          $ref: "#/components/responses/Error401"
        "404":
          $ref: "#/components/responses/Error404"

  /me/balance:
    get:
      tags: ["Me"]
      summary: "Mon solde de paie courant"
      description: "Retourne le montant du, les avances deduites, le deja paye et le reste a recevoir pour le cycle de paie courant."
      responses:
        "200":
          description: "Solde employe"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/PayrollBalance"
                  compliance:
                    type: object
                    description: "Bloc conformite paie (niveau de confiance + avertissement localise) — issue #2144. Vide si pays non resoluble."
                    properties:
                      level: { type: string, enum: [production, pilot, placeholder, unknown] }
                      warning: { type: string }
                      warning_key: { type: string }
                      source: { type: string }
                      verification_date: { type: string, format: date, nullable: true }
        "401":
          $ref: "#/components/responses/Error401"

  /me/payment-documents:
    get:
      tags: ["Me"]
      summary: "Mes documents de paiement"
      description: "Liste les recus, bordereaux et bulletins generes en arriere-plan pour l'utilisateur connecte."
      parameters:
        - in: query
          name: document_type
          schema:
            type: string
            enum: [payment_receipt, payment_slip, advance_receipt, payroll_summary]
        - in: query
          name: status
          schema:
            type: string
            enum: [pending, generating, available, failed]
      responses:
        "200":
          description: "Documents de paiement"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/PaymentDocument"
        "401":
          $ref: "#/components/responses/Error401"

  /me/payment-documents/{paymentDocument}/download:
    get:
      tags: ["Me"]
      summary: "Telecharger un document de paiement"
      parameters:
        - in: path
          name: paymentDocument
          required: true
          schema:
            type: integer
      responses:
        "200":
          description: "Fichier PDF"
          content:
            application/pdf:
              schema:
                type: string
                format: binary
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          $ref: "#/components/responses/Error403"
        "409":
          description: "Document pas encore disponible"

  /payment-confirmations/{paymentItem}/confirm:
    post:
      tags: ["Payroll"]
      summary: "Confirmer la reception d'un paiement"
      parameters:
        - in: path
          name: paymentItem
          required: true
          schema:
            type: integer
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              properties:
                device_signature:
                  type: string
                document_version:
                  type: string
                metadata:
                  type: object
      responses:
        "200":
          description: "Reception confirmee"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
        "401":
          $ref: "#/components/responses/Error401"
        "404":
          $ref: "#/components/responses/Error404"
        "422":
          $ref: "#/components/responses/Validation422"

  /payroll/cycles/preview:
    get:
      tags: ["Payroll"]
      summary: "Previsualiser un cycle de paie candidat"
      description: "PA2-PAY-003 - Permet a un manager de previsualiser la periode resultante et un total de paie estime pour une regle de cycle candidate (journalier/hebdomadaire/mensuel, jour de paie, debut de semaine) avant de l'enregistrer via PUT /payroll/cycle-settings. Lecture seule, ne persiste rien."
      parameters:
        - in: query
          name: pay_cycle
          required: false
          schema:
            type: string
            enum: [daily, weekly, monthly]
        - in: query
          name: pay_day
          required: false
          schema:
            type: integer
            minimum: 1
            maximum: 31
        - in: query
          name: week_start
          required: false
          schema:
            type: integer
            minimum: 1
            maximum: 7
      responses:
        "200":
          description: "Previsualisation du cycle candidat"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    properties:
                      settings:
                        type: object
                        properties:
                          pay_cycle: { type: string, enum: [daily, weekly, monthly] }
                          pay_day: { type: integer }
                          week_start: { type: integer }
                      period:
                        type: object
                        properties:
                          start: { type: string, format: date }
                          end: { type: string, format: date }
                          label: { type: string }
                      next_payment_date: { type: string, format: date }
                      currency: { type: string }
                      employee_count: { type: integer }
                      estimated_total_gross: { type: number, format: float }
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          $ref: "#/components/responses/Error403"
        "422":
          $ref: "#/components/responses/Error422"

  /payroll/mobile-summary:
    get:
      tags: ["Payroll"]
      summary: "Resume mobile des soldes paie equipe"
      description: "Retourne les soldes paie du perimetre manager autorise avec totaux agreges."
      responses:
        "200":
          description: "Resume manager mobile"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    properties:
                      items:
                        type: array
                        items:
                          $ref: "#/components/schemas/PayrollBalance"
                      totals:
                        type: object
                        properties:
                          gross_due: { type: number, format: float }
                          advances: { type: number, format: float }
                          paid: { type: number, format: float }
                          remaining: { type: number, format: float }
                          overtime_hours: { type: number, format: float }
                          overtime_pay: { type: number, format: float }
                      compliance:
                        type: object
                        description: "Bloc conformite paie (niveau de confiance + avertissement localise) — issue #2144. Vide si pays non resoluble."
                        properties:
                          level: { type: string, enum: [production, pilot, placeholder, unknown] }
                          warning: { type: string }
                          warning_key: { type: string }
                          source: { type: string }
                          verification_date: { type: string, nullable: true }
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          $ref: "#/components/responses/Error403"

  /payment-batches:
    get:
      tags: ["Payroll"]
      summary: "Lister les lots de paiement"
      responses:
        "200":
          description: "Lots de paiement"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/PaymentBatch"
        "403":
          $ref: "#/components/responses/Error403"
    post:
      tags: ["Payroll"]
      summary: "Creer un lot de paiement depuis un cycle paie"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [payroll_run_id]
              properties:
                payroll_run_id:
                  type: integer
                currency:
                  type: string
                metadata:
                  type: object
      responses:
        "201":
          description: "Lot cree"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/PaymentBatch"
        "422":
          $ref: "#/components/responses/Validation422"

  /payment-batches/{paymentBatch}:
    get:
      tags: ["Payroll"]
      summary: "Detail d'un lot de paiement"
      parameters:
        - in: path
          name: paymentBatch
          required: true
          schema:
            type: integer
      responses:
        "200":
          description: "Detail lot"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/PaymentBatch"
        "404":
          $ref: "#/components/responses/Error404"

  /payment-batches/{paymentBatch}/mark-paid:
    post:
      tags: ["Payroll"]
      summary: "Declarer un lot de paiement comme envoye"
      parameters:
        - in: path
          name: paymentBatch
          required: true
          schema:
            type: integer
      responses:
        "202":
          description: "Traitement asynchrone lance"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/PaymentBatch"
                  message:
                    type: string
        "422":
          $ref: "#/components/responses/Validation422"

  /payments/{payrollRun}/documents:
    get:
      tags: ["Payroll"]
      summary: "Documents de paiement d.un cycle paie"
      parameters:
        - in: path
          name: payrollRun
          required: true
          schema:
            type: integer
      responses:
        "200":
          description: "Documents du cycle"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/PaymentDocument"
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          $ref: "#/components/responses/Error403"

  /me/trainings:
    get:
      tags: ["Me"]
      summary: "Mes formations"
      responses:
        "200":
          description: "Liste de mes formations"
        "401":
          $ref: "#/components/responses/Error401"

  /me/training-enrollments:
    get:
      tags: ["Me"]
      summary: "Mes inscriptions formation (alias mobile employee)"
      description: "Alias de /me/trainings : liste des inscriptions de l'employe courant avec course_title, session_date, progress, status."
      responses:
        "200":
          description: "Liste de mes inscriptions formation"
        "401":
          $ref: "#/components/responses/Error401"

  /me/trainings/{session}/enroll:
    post:
      tags: ["Me"]
      summary: "Auto-inscription a une formation"
      parameters:
        - name: session
          in: path
          required: true
          schema: { type: integer }
      responses:
        "201":
          description: "Inscription confirmee"
        "401":
          $ref: "#/components/responses/Error401"

  /me/loans:
    get:
      tags: ["Me"]
      summary: "Mes prets"
      responses:
        "200":
          description: "Liste de mes prets"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/Loan"
        "401":
          $ref: "#/components/responses/Error401"

  /me/loans/{loan}/repayments:
    get:
      tags: ["Me"]
      summary: "Mes remboursements de pret"
      parameters:
        - name: loan
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: "Historique remboursements"
        "401":
          $ref: "#/components/responses/Error401"

  /employees/import:
    post:
      tags: ["Employees"]
      summary: "Importer des employes par CSV"
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              required: [file]
              properties:
                file:
                  type: string
                  format: binary
      responses:
        "200":
          description: "Resultat import"
        "401":
          $ref: "#/components/responses/Error401"
        "422":
          $ref: "#/components/responses/Validation422"

  /employees/import-template:
    get:
      tags: ["Employees"]
      summary: "Template CSV import"
      responses:
        "200":
          description: "Fichier CSV template"
        "401":
          $ref: "#/components/responses/Error401"

  # --- Auth extras ---
  /auth/register:
    post:
      tags: ["Auth"]
      summary: "Inscription employe"
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/EmployeeRegisterRequest"
      responses:
        "201":
          description: "Inscription reussie"
        "422":
          $ref: "#/components/responses/Validation422"

  /auth/google/token:
    post:
      tags: ["Auth"]
      summary: "Connexion Google OAuth"
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [token]
              properties:
                token:
                  type: string
      responses:
        "200":
          description: "Connexion reussie"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/LoginResponse"
        "401":
          $ref: "#/components/responses/Error401"

  /auth/language:
    patch:
      tags: ["Auth"]
      summary: "Changer la langue"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [language]
              properties:
                language:
                  type: string
                  enum: [fr, en, ar]
      responses:
        "200":
          description: "Langue modifiee"
        "401":
          $ref: "#/components/responses/Error401"

  /auth/refresh-token:
    post:
      tags: ["Auth"]
      summary: "Rafraichir le token"
      responses:
        "200":
          description: "Nouveau token"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/LoginResponse"
        "401":
          $ref: "#/components/responses/Error401"

  # =============================================
  # ITERATION 33.2 — PAYROLL + BILLING
  # =============================================

  # --- Payroll Runs ---
  /payroll/audit:
    get:
      tags: ["Payroll"]
      summary: "Lister les enregistrements d'audit des calculs de paie (manager principal/RH) — issue #1874"
      description: "Audit immuable des calculs (runs de paie + simulations) du tenant courant : lecture seule, isolation tenant stricte (la liste est scopée à la société de l'acteur). RBAC : manager principal/RH (PayrollAuditPolicy)."
      operationId: listPayrollAudits
      parameters:
        - name: status
          in: query
          required: false
          schema: { type: string, enum: [success, validation_error, rule_missing, provider_error, fallback_forbidden] }
        - name: country_code
          in: query
          required: false
          schema: { type: string }
        - name: per_page
          in: query
          required: false
          schema: { type: integer, default: 15 }
      responses:
        "200":
          description: "Liste paginée des enregistrements d'audit du tenant"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/PayrollCalculationAudit"
                  meta:
                    $ref: "#/components/schemas/PaginationMeta"
        "401": { $ref: "#/components/responses/Error401" }
        "403": { $ref: "#/components/responses/Error403" }
  /payroll/audit/{correlationId}:
    get:
      tags: ["Payroll"]
      summary: "Détail d'un enregistrement d'audit de calcul de paie (manager principal/RH) — issue #1874"
      description: "Reproduction du contexte d'un calcul (pays, période, version de règles, paramètres agrégés, résultats agrégés, statut) à partir de son correlation_id. 404 si l'enregistrement n'appartient pas au tenant courant."
      operationId: getPayrollAudit
      parameters:
        - name: correlationId
          in: path
          required: true
          schema: { type: string, format: uuid }
      responses:
        "200":
          description: "Enregistrement d'audit"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/PayrollCalculationAudit"
        "401": { $ref: "#/components/responses/Error401" }
        "403": { $ref: "#/components/responses/Error403" }
        "404": { $ref: "#/components/responses/Error404" }

  /payroll-runs:
    get:
      tags: ["Payroll"]
      summary: "Lister les sessions de paie"
      parameters:
        - name: status
          in: query
          schema: { type: string, enum: [draft, calculating, validated, cancelled] }
        - name: per_page
          in: query
          schema: { type: integer, default: 15 }
      responses:
        "200":
          description: "Liste paginee"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/PayrollRun"
                  meta:
                    $ref: "#/components/schemas/PaginationMeta"
        "401":
          $ref: "#/components/responses/Error401"
    post:
      tags: ["Payroll"]
      summary: "Creer une session de paie"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [period_month, period_year]
              properties:
                period_month:
                  type: integer
                  minimum: 1
                  maximum: 12
                period_year:
                  type: integer
                label:
                  type: string
                  nullable: true
      responses:
        "201":
          description: "Session creee"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/PayrollRun"
        "401":
          $ref: "#/components/responses/Error401"
        "422":
          $ref: "#/components/responses/Validation422"

  /payroll-runs/{payrollRun}:
    get:
      tags: ["Payroll"]
      summary: "Voir une session de paie"
      parameters:
        - name: payrollRun
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: "Detail session avec bulletins"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/PayrollRun"
        "401":
          $ref: "#/components/responses/Error401"
        "404":
          $ref: "#/components/responses/Error404"

  /payroll-runs/{payrollRun}/calculate:
    post:
      tags: ["Payroll"]
      summary: "Calculer la paie"
      parameters:
        - name: payrollRun
          in: path
          required: true
          schema: { type: integer }
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              properties:
                acknowledge_placeholder:
                  type: boolean
                  description: "Confirmation explicite requise pour les pays en regle placeholder (issue #2332/#1872)"
      responses:
        "200":
          description: "Calcul lance"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/PayrollRun"
        "401":
          $ref: "#/components/responses/Error401"
        "422":
          description: "Run non recalculable dans son statut, ou pays placeholder sans acknowledge_placeholder (issue #2332)"

  /payroll-runs/{payrollRun}/validate:
    post:
      tags: ["Payroll"]
      summary: "Valider la session de paie"
      parameters:
        - name: payrollRun
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: "Session validee"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/PayrollRun"
        "401":
          $ref: "#/components/responses/Error401"
        "422":
          description: "Only calculated runs can be validated"

  /payroll-runs/{payrollRun}/cancel:
    post:
      tags: ["Payroll"]
      summary: "Annuler une session de paie"
      parameters:
        - name: payrollRun
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: "Session annulee"
        "401":
          $ref: "#/components/responses/Error401"
        "422":
          description: "Validated runs cannot be cancelled"

  /payroll-runs/{payrollRun}/declarations/cnss-ci:
    get:
      tags: ["Payroll Engine"]
      summary: "Déclaration CNSS mensuelle Côte d'Ivoire — CSV (CEDEAO #1830)"
      description: "CSV DAS ivoirien : une ligne par bulletin validé (matricule CNSS, nom, prénom, brut, assiette plafonnée à 1 647 315 XOF, retraite salariale 3,2 %, retraite patronale 4,5 %, famille 5,75 %, AT 2 %, totaux) + TOTAUX. 422 si le run n'est pas CI. RBAC manager ; 404 cross-tenant."
      operationId: downloadCnssCiDeclaration
      parameters:
        - name: payrollRun
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: "Fichier CSV CNSS CI"
          content:
            text/csv:
              schema:
                type: string
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          $ref: "#/components/responses/Error403"
        "404":
          $ref: "#/components/responses/Error404"
        "422":
          $ref: "#/components/responses/Validation422"

  /payroll-runs/{payrollRun}/declarations/ipres-sn:
    get:
      tags: ["Payroll Engine"]
      summary: "Déclaration IPRES/CSS mensuelle Sénégal — CSV (CEDEAO #1830)"
      description: "CSV sénégalais : une ligne par bulletin validé (matricule IPRES, nom, prénom, catégorie general/cadre, brut, assiette T1 plafonnée 432 000 XOF, cotisations T1 5,6 %/8,4 %, assiette T2 cadres 2 160 000−432 000, cotisations T2 2,4 %/3,6 %, CSS famille 7 %) + TOTAUX. 422 si le run n'est pas SN. RBAC manager ; 404 cross-tenant."
      operationId: downloadIpresSnDeclaration
      parameters:
        - name: payrollRun
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: "Fichier CSV IPRES/CSS SN"
          content:
            text/csv:
              schema:
                type: string
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          $ref: "#/components/responses/Error403"
        "404":
          $ref: "#/components/responses/Error404"
        "422":
          $ref: "#/components/responses/Validation422"

  /payroll-runs/{payrollRun}/declarations/cnps-cm:
    get:
      tags: ["Payroll Engine"]
      summary: "Déclaration CNPS mensuelle Cameroun — format DAS CSV (CEMAC/CM #1823)"
      description: "Télécharge le CSV DAS du run : une ligne par bulletin validé (matricule CNPS, nom, prénom, brut, assiette plafonnée à 750 000 XAF, vieillesse salariale/patronale 4,2 %, famille patronale 7,0 %, AT patronale 2,0 % non plafonnée, total patronal) + ligne TOTAUX. Taux et plafond résolus depuis CemacPayrollRules (asOf period du run). RBAC : manager principal/comptable ; 404 cross-tenant ; 422 si le run n'est pas un run CM."
      operationId: downloadCnpsCmDeclaration
      parameters:
        - name: payrollRun
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: "Fichier CSV DAS"
          content:
            text/csv:
              schema:
                type: string
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          $ref: "#/components/responses/Error403"
        "404":
          $ref: "#/components/responses/Error404"
        "422":
          description: "Le run ne concerne pas le Cameroun (CNPS CM)"
          $ref: "#/components/responses/Validation422"

  /payroll-runs/{payrollRun}/declarations/cnss-ga:
    get:
      tags: ["Payroll Engine"]
      summary: "Declaration CNSS mensuelle Gabon — CSV (CEMAC/GA #2155)"
      description: "CSV du run : une ligne par bulletin valide (matricule CNSS, nom, prenom, brut, retraite salariale 2,5 % / patronale 5,0 %, famille patronale 8,0 %, AT patronale 3,0 % non plafonnee, plafond 3 000 000 XAF) + ligne TOTAUX. RBAC : manager principal/comptable ; 404 cross-tenant ; 422 si le run n'est pas un run GA."
      operationId: downloadCnssGaDeclaration
      parameters:
        - name: payrollRun
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: "Fichier CSV CNSS GA"
          content:
            text/csv:
              schema:
                type: string
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          $ref: "#/components/responses/Error403"
        "404":
          $ref: "#/components/responses/Error404"
        "422":
          $ref: "#/components/responses/Validation422"

  /payroll-runs/{payrollRun}/declarations/cnss-cg:
    get:
      tags: ["Payroll Engine"]
      summary: "Declaration CNSS mensuelle Congo — CSV (CEMAC/CG #2155)"
      description: "CSV du run : une ligne par bulletin valide (matricule CNSS, nom, prenom, brut, retraite salariale 4,0 % / patronale 8,0 %, famille patronale 10,0 %, AT patronale 3,0 % non plafonnee, plafond 2 500 000 XAF) + ligne TOTAUX. RBAC : manager principal/comptable ; 404 cross-tenant ; 422 si le run n'est pas un run CG."
      operationId: downloadCnssCgDeclaration
      parameters:
        - name: payrollRun
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: "Fichier CSV CNSS CG"
          content:
            text/csv:
              schema:
                type: string
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          $ref: "#/components/responses/Error403"
        "404":
          $ref: "#/components/responses/Error404"
        "422":
          $ref: "#/components/responses/Validation422"

  /payroll-runs/{payrollRun}/declarations/cnss-bf:
    get:
      tags: ["Payroll Engine"]
      summary: "Déclaration CNSS mensuelle Burkina Faso — CSV (CEDEAO #2158)"
      description: "CSV burkinabè : une ligne par bulletin validé (matricule CNSS BF, nom, prénom, brut, assiette retraite plafonnée 900 000 XOF, retraite salariale/patronale 5,5 %/6,5 %, famille patronale 7,0 %, AT patronale 3,5 % non plafonnée, totaux) + ligne TOTAUX. RBAC : manager principal/comptable ; 404 cross-tenant ; 422 si le run n'est pas BF."
      operationId: downloadCnssBfDeclaration
      parameters:
        - name: payrollRun
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: "Fichier CSV CNSS BF"
          content:
            text/csv:
              schema:
                type: string
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          $ref: "#/components/responses/Error403"
        "404":
          $ref: "#/components/responses/Error404"
        "422":
          $ref: "#/components/responses/Validation422"

  /payroll-runs/{payrollRun}/declarations/inps-ml:
    get:
      tags: ["Payroll Engine"]
      summary: "Déclaration INPS mensuelle Mali — CSV (CEDEAO #2158)"
      description: "CSV malien : une ligne par bulletin validé (matricule INPS ML, nom, prénom, brut, assiette retraite plafonnée 3 000 000 XOF, retraite salariale/patronale 3,6 %/7,4 %, famille patronale 4,0 % non plafonnée, AT patronale 2,0 % non plafonnée, totaux) + ligne TOTAUX. RBAC : manager principal/comptable ; 404 cross-tenant ; 422 si le run n'est pas ML."
      operationId: downloadInpsMlDeclaration
      parameters:
        - name: payrollRun
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: "Fichier CSV INPS ML"
          content:
            text/csv:
              schema:
                type: string
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          $ref: "#/components/responses/Error403"
        "404":
          $ref: "#/components/responses/Error404"
        "422":
          $ref: "#/components/responses/Validation422"

  /payroll-runs/{payrollRun}/regularize:
    post:
      tags: ["Payroll Engine"]
      summary: "Créer un run de régularisation (DZ-DEPTH #1818)"
      description: "Crée un run type=regularization lié à un run verrouillé (original_run_id), même période, statut draft — sans modifier l'original. Motif obligatoire, tracé dans l'audit payroll_run_regularization_created. Seul un run `locked` est régularisable (422 sinon)."
      operationId: regularizePayrollRun
      parameters:
        - name: payrollRun
          in: path
          required: true
          schema: { type: integer }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [reason]
              properties:
                reason:
                  type: string
                  minLength: 5
                  maxLength: 2000
      responses:
        "201":
          description: "Run de régularisation créé"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/PayrollRun"
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          $ref: "#/components/responses/Error403"
        "404":
          $ref: "#/components/responses/Error404"
        "422":
          description: "Le run ne concerne pas le Cameroun (CNPS CM)"
          $ref: "#/components/responses/Validation422"

  /payroll-runs/{payrollRun}/regularizations:
    get:
      tags: ["Payroll Engine"]
      summary: "Lister les régularisations d'un run (DZ-DEPTH #1818)"
      operationId: listPayrollRunRegularizations
      parameters:
        - name: payrollRun
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: "Runs de régularisation liés"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/PayrollRun"
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          $ref: "#/components/responses/Error403"
        "404":
          $ref: "#/components/responses/Error404"

  /payroll-runs/{payrollRun}/summary:
    get:
      tags: ["Payroll"]
      summary: "Resume de la session de paie"
      parameters:
        - name: payrollRun
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: "Totaux et statistiques"
        "401":
          $ref: "#/components/responses/Error401"

  /payroll-runs/{payrollRun}/bank-export:
    post:
      tags: ["Payroll"]
      summary: "Generer un export bancaire pour un run valide (alias chemin)"
      description: "Variante chemin du POST /bank-exports (body payroll_run_id + format) : genere une ligne BankExport pending traitee par job."
      security:
        - BearerAuth: []
      parameters:
        - name: payrollRun
          in: path
          required: true
          schema: { type: integer }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [format]
              properties:
                format:
                  type: string
                  enum: [sepa_xml, ccp_dz, virement_ma, csv_generic]
      responses:
        "202":
          description: "Export cree (generation asynchrone)"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/BankExport"
        "403":
          $ref: "#/components/responses/Error403"
        "404":
          $ref: "#/components/responses/Error404"
        "422":
          $ref: "#/components/responses/Validation422"

  /payroll-runs/{payrollRun}/pay-slips:
    get:
      tags: ["Payroll"]
      summary: "Lister les bulletins de paie d'une session"
      operationId: listPayrollRunPaySlips
      parameters:
        - name: payrollRun
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: "Liste des bulletins de la session"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/PaySlip"
        "401":
          $ref: "#/components/responses/Error401"
        "404":
          $ref: "#/components/responses/Error404"

  # --- Pay Slips ---
  /export/pay-slips:
    get:
      tags: ["Payroll"]
      summary: "Exporter les bulletins de paie de l'entreprise (JSON ou CSV encode en JSON) - manager uniquement"
      description: "Retourne au plus 10000 lignes de la table pay_slips filtrees par company_id. format=csv renvoie le contenu CSV encode dans un champ JSON (pas de flux binaire direct)."
      operationId: exportPaySlips
      parameters:
        - name: format
          in: query
          schema: { type: string, enum: [json, csv], default: json }
      responses:
        "200":
          description: "Export au format demande"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    properties:
                      format: { type: string, enum: [json, csv] }
                      records:
                        type: array
                        items: { type: object }
                        description: "Present seulement quand format=json"
                      content:
                        type: string
                        description: "Contenu CSV brut, present seulement quand format=csv ou xlsx"
                      filename: { type: string }
                      count: { type: integer }
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          description: "Reserve aux managers"

  /export/history:
    get:
      tags: ["HR"]
      summary: "Historique des exports du portail manager (issue #2199) - manager uniquement"
      description: "Source reelle : table append-only `export_history`, tenant-scopee. Pagine, filtre par type."
      operationId: exportHistory
      parameters:
        - name: type
          in: query
          required: false
          schema: { type: string, maxLength: 100 }
          description: "Filtre par type d'export (employees, attendance, pay_slips, absences, training, contracts, vehicles, payroll_journal, payroll_ledger, accounting_od)"
        - name: per_page
          in: query
          required: false
          schema: { type: integer, minimum: 1, maximum: 100, default: 20 }
      responses:
        "200":
          description: "Historique pagine des exports"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      type: object
                      properties:
                        id: { type: integer }
                        type: { type: string }
                        format: { type: string, nullable: true }
                        record_count: { type: integer, nullable: true }
                        filename: { type: string, nullable: true }
                        created_at: { type: string, format: date-time }
                  meta:
                    type: object
                    properties:
                      current_page: { type: integer }
                      last_page: { type: integer }
                      per_page: { type: integer }
                      total: { type: integer }
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          description: "Reserve aux managers"

  /pay-slips/{paySlip}:
    get:
      tags: ["Payroll"]
      summary: "Voir un bulletin de paie"
      parameters:
        - name: paySlip
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: "Detail bulletin"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/PaySlip"
        "401":
          $ref: "#/components/responses/Error401"

  /pay-slips/{paySlip}/pdf:
    get:
      tags: ["Payroll"]
      summary: "Telecharger bulletin PDF"
      parameters:
        - name: paySlip
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: "Fichier PDF"
          content:
            application/pdf:
              schema:
                type: string
                format: binary
        "401":
          $ref: "#/components/responses/Error401"

  # --- Payroll Engine : Structures salariales, composants, bareme fiscal, cotisations ---
  /salary-structures:
    get:
      tags: ["Payroll Engine"]
      summary: "Lister les structures salariales (manager)"
      operationId: listSalaryStructures
      parameters:
        - name: country_code
          in: query
          schema: { type: string }
        - name: active_only
          in: query
          schema: { type: boolean, default: false }
      responses:
        "200":
          description: "Liste des structures"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/SalaryStructure"
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          $ref: "#/components/responses/Error403"
    post:
      tags: ["Payroll Engine"]
      summary: "Creer une structure salariale (manager)"
      operationId: createSalaryStructure
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [name, base_salary, currency, country_code]
              properties:
                name: { type: string, maxLength: 150 }
                base_salary: { type: number, minimum: 0 }
                currency: { type: string, minLength: 3, maxLength: 3 }
                country_code:
                  type: string
                  enum: [DZ, MA, TN, FR, TR, SN, CM, CF, TD, CG, GA, GQ, CI, ML, BF, BJ, TG, NE, CA]
                frequency:
                  type: string
                  enum: [monthly, bi_weekly, weekly]
                  nullable: true
      responses:
        "201":
          description: "Structure creee"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/SalaryStructure"
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          $ref: "#/components/responses/Error403"
        "422":
          $ref: "#/components/responses/Validation422"

  /salary-structures/{salaryStructure}:
    get:
      tags: ["Payroll Engine"]
      summary: "Voir une structure salariale avec ses composants (manager)"
      operationId: showSalaryStructure
      parameters:
        - name: salaryStructure
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: "Detail de la structure, composants inclus"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/SalaryStructure"
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          $ref: "#/components/responses/Error403"
        "404":
          $ref: "#/components/responses/Error404"
    put:
      tags: ["Payroll Engine"]
      summary: "Modifier une structure salariale (manager)"
      operationId: updateSalaryStructure
      parameters:
        - name: salaryStructure
          in: path
          required: true
          schema: { type: integer }
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                name: { type: string, maxLength: 150 }
                base_salary: { type: number, minimum: 0 }
                currency: { type: string, minLength: 3, maxLength: 3 }
                country_code:
                  type: string
                  enum: [DZ, MA, TN, FR, TR, SN, CM, CF, TD, CG, GA, GQ, CI, ML, BF, BJ, TG, NE, CA]
                frequency:
                  type: string
                  enum: [monthly, bi_weekly, weekly]
                active: { type: boolean }
      responses:
        "200":
          description: "Structure mise a jour"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/SalaryStructure"
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          $ref: "#/components/responses/Error403"
        "404":
          $ref: "#/components/responses/Error404"
        "422":
          $ref: "#/components/responses/Validation422"
    delete:
      tags: ["Payroll Engine"]
      summary: "Supprimer une structure salariale (manager)"
      operationId: deleteSalaryStructure
      parameters:
        - name: salaryStructure
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: "Structure supprimee"
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          $ref: "#/components/responses/Error403"
        "404":
          $ref: "#/components/responses/Error404"

  /admin/public-holidays:
    get:
      tags: ["Platform Admin"]
      summary: "Lister les jours fériés d'un pays/année (super-admin : national + entreprise)"
      description: "Issue #1811 — CRUD jours fériés par pays. Super-admin : voit tout ; manager principal : nationaux + siens."
      operationId: listPublicHolidaysAdmin
      parameters:
        - name: country_code
          in: query
          required: false
          schema: { type: string, default: DZ }
        - name: year
          in: query
          required: false
          schema: { type: integer }
      responses:
        "200":
          description: "Liste des jours fériés"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/PublicHoliday"
        "401": { $ref: "#/components/responses/Error401" }
        "403": { $ref: "#/components/responses/Error403" }
    post:
      tags: ["Platform Admin"]
      summary: "Créer un jour férié national (super-admin)"
      operationId: storePublicHolidayAdmin
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/PublicHolidayInput"
      responses:
        "201":
          description: "Jour férié créé"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/PublicHoliday"
        "401": { $ref: "#/components/responses/Error401" }
        "403": { $ref: "#/components/responses/Error403" }
        "422": { $ref: "#/components/responses/Error422" }
  /admin/public-holidays/{publicHoliday}:
    put:
      tags: ["Platform Admin"]
      summary: "Modifier un jour férié (super-admin)"
      operationId: updatePublicHolidayAdmin
      parameters:
        - name: publicHoliday
          in: path
          required: true
          schema: { type: integer }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/PublicHolidayInput"
      responses:
        "200":
          description: "Jour férié modifié"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/PublicHoliday"
        "401": { $ref: "#/components/responses/Error401" }
        "403": { $ref: "#/components/responses/Error403" }
        "422": { $ref: "#/components/responses/Error422" }
    delete:
      tags: ["Platform Admin"]
      summary: "Supprimer un jour férié (super-admin)"
      operationId: destroyPublicHolidayAdmin
      parameters:
        - name: publicHoliday
          in: path
          required: true
          schema: { type: integer }
      responses:
        "204":
          description: "Supprimé"
        "401": { $ref: "#/components/responses/Error401" }
        "403": { $ref: "#/components/responses/Error403" }
  /admin/islamic-calendar:
    get:
      tags: ["Platform Admin"]
      summary: "Lister les fêtes islamiques d'une année (super-admin)"
      description: "Issue #1812 — Calendrier islamique dynamique : dates mobiles des fêtes islamiques (Aïd el-Fitr, Aïd el-Adha/Tabaski, Maouloud, Tamkharit, Nouvel an hégirien) saisies par année. Uniquement accessible au super-admin (guard super_admin_api)."
      operationId: listIslamicCalendar
      parameters:
        - name: year
          in: query
          required: false
          schema: { type: integer }
      responses:
        "200":
          description: "Fêtes islamiques de l'année + compteur de dates non confirmées"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/IslamicCalendarHoliday"
                  meta:
                    type: object
                    properties:
                      year: { type: integer }
                      unconfirmed_count: { type: integer }
                      unconfirmed:
                        type: array
                        items: { type: object }
        "401": { $ref: "#/components/responses/Error401" }
        "403": { $ref: "#/components/responses/Error403" }
  /admin/islamic-calendar/confirm-year/{year}:
    post:
      tags: ["Platform Admin"]
      summary: "Confirmer toutes les dates islamiques d'une année (super-admin)"
      operationId: confirmIslamicCalendarYear
      parameters:
        - name: year
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: "Année confirmée"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    properties:
                      year: { type: integer }
                      confirmed_count: { type: integer }
        "401": { $ref: "#/components/responses/Error401" }
        "403": { $ref: "#/components/responses/Error403" }
        "422": { $ref: "#/components/responses/Error422" }
  /admin/islamic-calendar/{holidayKey}/{year}:
    put:
      tags: ["Platform Admin"]
      summary: "Modifier/confirmer une fête islamique (super-admin)"
      operationId: updateIslamicCalendarHoliday
      parameters:
        - name: holidayKey
          in: path
          required: true
          schema: { type: string, enum: [eid_al_fitr, eid_al_adha, mawlid, tahmarit, muharram] }
        - name: year
          in: path
          required: true
          schema: { type: integer }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/IslamicCalendarHolidayInput"
      responses:
        "200":
          description: "Fête islamique mise à jour"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/IslamicCalendarHoliday"
        "401": { $ref: "#/components/responses/Error401" }
        "403": { $ref: "#/components/responses/Error403" }
        "422": { $ref: "#/components/responses/Error422" }
  /public-holidays:
    get:
      tags: ["Payroll Engine"]
      summary: "Lister les jours fériés du pays (manager principal : nationaux + entreprise)"
      operationId: listPublicHolidays
      parameters:
        - name: country_code
          in: query
          required: false
          schema: { type: string, default: DZ }
        - name: year
          in: query
          required: false
          schema: { type: integer }
      responses:
        "200":
          description: "Liste des jours fériés"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/PublicHoliday"
        "401": { $ref: "#/components/responses/Error401" }
        "403": { $ref: "#/components/responses/Error403" }
    post:
      tags: ["Payroll Engine"]
      summary: "Créer un jour férié d'entreprise (manager principal)"
      operationId: storePublicHoliday
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/PublicHolidayInput"
      responses:
        "201":
          description: "Jour férié créé"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/PublicHoliday"
        "401": { $ref: "#/components/responses/Error401" }
        "403": { $ref: "#/components/responses/Error403" }
        "422": { $ref: "#/components/responses/Error422" }
  /public-holidays/{publicHoliday}:
    put:
      tags: ["Payroll Engine"]
      summary: "Modifier un jour férié d'entreprise (manager principal)"
      operationId: updatePublicHoliday
      parameters:
        - name: publicHoliday
          in: path
          required: true
          schema: { type: integer }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/PublicHolidayInput"
      responses:
        "200":
          description: "Jour férié modifié"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/PublicHoliday"
        "401": { $ref: "#/components/responses/Error401" }
        "403": { $ref: "#/components/responses/Error403" }
        "422": { $ref: "#/components/responses/Error422" }
    delete:
      tags: ["Payroll Engine"]
      summary: "Supprimer un jour férié d'entreprise (manager principal)"
      operationId: destroyPublicHoliday
      parameters:
        - name: publicHoliday
          in: path
          required: true
          schema: { type: integer }
      responses:
        "204":
          description: "Supprimé"
        "401": { $ref: "#/components/responses/Error401" }
        "403": { $ref: "#/components/responses/Error403" }

  /salary-components:
    get:
      tags: ["Payroll Engine"]
      summary: "Lister les composants de salaire (manager)"
      operationId: listSalaryComponents
      parameters:
        - name: salary_structure_id
          in: query
          schema: { type: integer }
        - name: type
          in: query
          schema: { type: string, enum: [earning, deduction, employer_contribution] }
        - name: active_only
          in: query
          schema: { type: boolean, default: false }
      responses:
        "200":
          description: "Liste des composants, triee par order"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/SalaryComponent"
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          $ref: "#/components/responses/Error403"
    post:
      tags: ["Payroll Engine"]
      summary: "Creer un composant de salaire (manager)"
      operationId: createSalaryComponent
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [name, code, type, calculation_type]
              properties:
                salary_structure_id: { type: integer, nullable: true }
                name: { type: string, maxLength: 150 }
                code: { type: string, maxLength: 50 }
                type: { type: string, enum: [earning, deduction, employer_contribution] }
                calculation_type: { type: string, enum: [fixed, percentage_of_base, percentage_of_gross, formula] }
                amount: { type: number, minimum: 0, nullable: true }
                percentage: { type: number, minimum: 0, maximum: 100, nullable: true }
                formula: { type: string, maxLength: 500, nullable: true }
                is_taxable: { type: boolean, nullable: true }
                is_recurring: { type: boolean, nullable: true }
                order: { type: integer, minimum: 0, nullable: true }
      responses:
        "201":
          description: "Composant cree"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/SalaryComponent"
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          $ref: "#/components/responses/Error403"
        "422":
          $ref: "#/components/responses/Validation422"

  /salary-components/{salaryComponent}:
    get:
      tags: ["Payroll Engine"]
      summary: "Voir un composant de salaire (manager)"
      operationId: showSalaryComponent
      parameters:
        - name: salaryComponent
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: "Detail du composant"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/SalaryComponent"
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          $ref: "#/components/responses/Error403"
        "404":
          $ref: "#/components/responses/Error404"
    put:
      tags: ["Payroll Engine"]
      summary: "Modifier un composant de salaire (manager)"
      operationId: updateSalaryComponent
      parameters:
        - name: salaryComponent
          in: path
          required: true
          schema: { type: integer }
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                name: { type: string, maxLength: 150 }
                code: { type: string, maxLength: 50 }
                type: { type: string, enum: [earning, deduction, employer_contribution] }
                calculation_type: { type: string, enum: [fixed, percentage_of_base, percentage_of_gross, formula] }
                amount: { type: number, minimum: 0, nullable: true }
                percentage: { type: number, minimum: 0, maximum: 100, nullable: true }
                formula: { type: string, maxLength: 500, nullable: true }
                is_taxable: { type: boolean }
                is_recurring: { type: boolean }
                order: { type: integer, minimum: 0 }
                active: { type: boolean }
      responses:
        "200":
          description: "Composant mis a jour"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/SalaryComponent"
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          $ref: "#/components/responses/Error403"
        "404":
          $ref: "#/components/responses/Error404"
        "422":
          $ref: "#/components/responses/Validation422"
    delete:
      tags: ["Payroll Engine"]
      summary: "Supprimer un composant de salaire (manager)"
      operationId: deleteSalaryComponent
      parameters:
        - name: salaryComponent
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: "Composant supprime"
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          $ref: "#/components/responses/Error403"
        "404":
          $ref: "#/components/responses/Error404"

  /payroll/simulate:
    post:
      tags: ["Payroll Engine"]
      summary: "Simuler l'impact d'un barème (manager, dry-run) — issue #1814"
      operationId: simulatePayroll
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/PayrollSimulationInput"
      responses:
        "200":
          description: "Résultat de la simulation (non persistant)"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/PayrollSimulationResult"
        "401": { $ref: "#/components/responses/Error401" }
        "403": { $ref: "#/components/responses/Error403" }
        "422": { $ref: "#/components/responses/Validation422" }
  /tax-slabs:
    get:
      tags: ["Payroll Engine"]
      summary: "Lister le bareme fiscal (tranches d'impot) (manager)"
      operationId: listTaxSlabs
      parameters:
        - name: country_code
          in: query
          schema: { type: string }
      responses:
        "200":
          description: "Liste des tranches, triees par pays puis montant min"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/TaxSlab"
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          $ref: "#/components/responses/Error403"
    post:
      tags: ["Payroll Engine"]
      summary: "Creer une tranche d'impot (manager)"
      operationId: createTaxSlab
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [country_code, name, min_amount, rate, effective_from]
              properties:
                country_code:
                  type: string
                  enum: [DZ, MA, TN, FR, TR, SN, CM, CF, TD, CG, GA, GQ, CI, ML, BF, BJ, TG, NE, CA]
                name: { type: string, maxLength: 150 }
                min_amount: { type: number, minimum: 0 }
                max_amount: { type: number, minimum: 0, nullable: true }
                rate: { type: number, minimum: 0, maximum: 100 }
                fixed_deduction: { type: number, minimum: 0, nullable: true }
                effective_from: { type: string, format: date }
                effective_to: { type: string, format: date, nullable: true }
      responses:
        "201":
          description: "Tranche creee"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/TaxSlab"
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          $ref: "#/components/responses/Error403"
        "422":
          $ref: "#/components/responses/Validation422"

  /tax-slabs/{taxSlab}:
    put:
      tags: ["Payroll Engine"]
      summary: "Modifier une tranche d'impot (manager)"
      operationId: updateTaxSlab
      parameters:
        - name: taxSlab
          in: path
          required: true
          schema: { type: integer }
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                name: { type: string, maxLength: 150 }
                min_amount: { type: number, minimum: 0 }
                max_amount: { type: number, minimum: 0, nullable: true }
                rate: { type: number, minimum: 0, maximum: 100 }
                fixed_deduction: { type: number, minimum: 0 }
                effective_from: { type: string, format: date }
                effective_to: { type: string, format: date, nullable: true }
      responses:
        "200":
          description: "Tranche mise a jour"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/TaxSlab"
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          $ref: "#/components/responses/Error403"
        "404":
          $ref: "#/components/responses/Error404"
        "422":
          $ref: "#/components/responses/Validation422"
    delete:
      tags: ["Payroll Engine"]
      summary: "Supprimer une tranche d'impot (manager)"
      operationId: deleteTaxSlab
      parameters:
        - name: taxSlab
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: "Tranche supprimee"
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          $ref: "#/components/responses/Error403"
        "404":
          $ref: "#/components/responses/Error404"

  /tax-slabs/{taxSlab}/submit:
    put:
      tags: ["Payroll Engine"]
      summary: "Soumettre une tranche d'impot pour validation (manager) — issue #1813"
      operationId: submitTaxSlab
      parameters:
        - name: taxSlab
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: "Tranche soumise (pending_validation)"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/TaxSlab"
        "401": { $ref: "#/components/responses/Error401" }
        "403": { $ref: "#/components/responses/Error403" }
        "404": { $ref: "#/components/responses/Error404" }
        "422": { $ref: "#/components/responses/Validation422" }
  /tax-slabs/{taxSlab}/history:
    get:
      tags: ["Payroll Engine"]
      summary: "Historique immuable des modifications d'une tranche (manager) — issue #1813"
      operationId: taxSlabHistory
      parameters:
        - name: taxSlab
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: "Audit trail (tax_rate_change_log, append-only)"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/TaxRateChangeLog"
        "401": { $ref: "#/components/responses/Error401" }
        "403": { $ref: "#/components/responses/Error403" }
        "404": { $ref: "#/components/responses/Error404" }

  /social-contributions:
    get:
      tags: ["Payroll Engine"]
      summary: "Lister les regles de cotisations sociales (manager)"
      operationId: listSocialContributions
      responses:
        "200":
          description: "Liste des cotisations"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/SocialContribution"
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          $ref: "#/components/responses/Error403"
    post:
      tags: ["Payroll Engine"]
      summary: "Creer une regle de cotisation sociale (manager)"
      description: "PA2-ARCH-004 : l'unicite de 'code' est scopee par (company_id, effective_from), pas globale, pour permettre un nouveau taux date sans supprimer l'historique utilise par d'anciennes sessions de paie."
      operationId: createSocialContribution
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [country_code, name, code, type, rate, effective_from]
              properties:
                country_code:
                  type: string
                  enum: [DZ, MA, TN, FR, TR, SN, CM, CF, TD, CG, GA, GQ, CI, ML, BF, BJ, TG, NE, CA]
                name: { type: string, maxLength: 150 }
                code: { type: string, maxLength: 50 }
                type: { type: string, enum: [employee, employer] }
                rate: { type: number, minimum: 0, maximum: 100 }
                cap: { type: number, minimum: 0, nullable: true }
                effective_from: { type: string, format: date }
                effective_to: { type: string, format: date, nullable: true }
      responses:
        "201":
          description: "Cotisation creee"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/SocialContribution"
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          $ref: "#/components/responses/Error403"
        "422":
          $ref: "#/components/responses/Validation422"

  /social-contributions/{socialContribution}:
    put:
      tags: ["Payroll Engine"]
      summary: "Modifier une regle de cotisation sociale (manager)"
      operationId: updateSocialContribution
      parameters:
        - name: socialContribution
          in: path
          required: true
          schema: { type: integer }
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                name: { type: string, maxLength: 150 }
                type: { type: string, enum: [employee, employer] }
                rate: { type: number, minimum: 0, maximum: 100 }
                cap: { type: number, minimum: 0, nullable: true }
                effective_from: { type: string, format: date }
                effective_to: { type: string, format: date, nullable: true }
      responses:
        "200":
          description: "Cotisation mise a jour"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/SocialContribution"
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          $ref: "#/components/responses/Error403"
        "404":
          $ref: "#/components/responses/Error404"
        "422":
          $ref: "#/components/responses/Validation422"
    delete:
      tags: ["Payroll Engine"]
      summary: "Supprimer une regle de cotisation sociale (manager)"
      operationId: deleteSocialContribution
      parameters:
        - name: socialContribution
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: "Cotisation supprimee"
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          $ref: "#/components/responses/Error403"
        "404":
          $ref: "#/components/responses/Error404"

  /social-contributions/{socialContribution}/submit:
    put:
      tags: ["Payroll Engine"]
      summary: "Soumettre une regle de cotisation pour validation (manager) — issue #1813"
      operationId: submitSocialContribution
      parameters:
        - name: socialContribution
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: "Cotisation soumise (pending_validation)"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/SocialContribution"
        "401": { $ref: "#/components/responses/Error401" }
        "403": { $ref: "#/components/responses/Error403" }
        "404": { $ref: "#/components/responses/Error404" }
        "422": { $ref: "#/components/responses/Validation422" }
  /social-contributions/{socialContribution}/history:
    get:
      tags: ["Payroll Engine"]
      summary: "Historique immuable des modifications d'une cotisation (manager) — issue #1813"
      operationId: socialContributionHistory
      parameters:
        - name: socialContribution
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: "Audit trail (tax_rate_change_log, append-only)"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/TaxRateChangeLog"
        "401": { $ref: "#/components/responses/Error401" }
        "403": { $ref: "#/components/responses/Error403" }
        "404": { $ref: "#/components/responses/Error404" }

  /admin/social-contributions:
    get:
      tags: ["Platform Admin"]
      summary: "Lister les règles de cotisations sociales nationales (platform_admin) — issue #1815"
      operationId: listAdminSocialContributions
      parameters:
        - name: country_code
          in: query
          required: false
          schema: { type: string }
      responses:
        "200":
          description: "Règles de cotisations"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/SocialContribution"
        "401": { $ref: "#/components/responses/Error401" }
        "403": { $ref: "#/components/responses/Error403" }
    post:
      tags: ["Platform Admin"]
      summary: "Créer une règle de cotisation nationale (platform_admin) — issue #1815"
      operationId: storeAdminSocialContribution
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SocialContributionInput"
      responses:
        "201":
          description: "Règle créée"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/SocialContribution"
        "401": { $ref: "#/components/responses/Error401" }
        "403": { $ref: "#/components/responses/Error403" }
        "422": { $ref: "#/components/responses/Validation422" }
  /admin/social-contributions/{socialContribution}:
    put:
      tags: ["Platform Admin"]
      summary: "Modifier une règle de cotisation nationale (platform_admin) — issue #1815"
      operationId: updateAdminSocialContribution
      parameters:
        - name: socialContribution
          in: path
          required: true
          schema: { type: integer }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SocialContributionInput"
      responses:
        "200":
          description: "Règle modifiée"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/SocialContribution"
        "401": { $ref: "#/components/responses/Error401" }
        "403": { $ref: "#/components/responses/Error403" }
        "404": { $ref: "#/components/responses/Error404" }
    delete:
      tags: ["Platform Admin"]
      summary: "Supprimer une règle de cotisation nationale (platform_admin) — issue #1815"
      operationId: destroyAdminSocialContribution
      parameters:
        - name: socialContribution
          in: path
          required: true
          schema: { type: integer }
      responses:
        "204": { description: "Supprimé" }
        "401": { $ref: "#/components/responses/Error401" }
        "403": { $ref: "#/components/responses/Error403" }
        "404": { $ref: "#/components/responses/Error404" }

  /social-declarations/cnas-dz:
    post:
      tags: ["Payroll Engine"]
      summary: "Generer la declaration trimestrielle CNAS (Algerie) pour tous les employes actifs"
      description: "Agrege les bulletins valides (pay_slips.status=validated) des 3 mois du trimestre demande, par employe."
      operationId: generateCnasDzDeclaration
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [quarter, year]
              properties:
                quarter: { type: string, enum: [Q1, Q2, Q3, Q4] }
                year: { type: integer, minimum: 2020, maximum: 2099 }
      responses:
        "200":
          description: "Declaration CNAS generee (lignes par employe : salaire brut cumule, mois travailles)"
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          $ref: "#/components/responses/Error403"
        "422":
          $ref: "#/components/responses/Validation422"

  /social-declarations/cnss-ma:
    post:
      tags: ["Payroll Engine"]
      summary: "Generer la declaration trimestrielle CNSS (Maroc) pour tous les employes actifs"
      description: "Agrege les bulletins valides et les jours de presence (attendance_logs) des 3 mois du trimestre demande, par employe."
      operationId: generateCnssMaDeclaration
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [quarter, year]
              properties:
                quarter: { type: string, enum: [Q1, Q2, Q3, Q4] }
                year: { type: integer, minimum: 2020, maximum: 2099 }
      responses:
        "200":
          description: "Declaration CNSS generee (lignes par employe : salaire brut cumule, jours travailles)"
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          $ref: "#/components/responses/Error403"
        "422":
          $ref: "#/components/responses/Validation422"

  /social-declarations/dsn-fr:
    post:
      tags: ["Payroll Engine"]
      summary: "Generer la DSN (Declaration Sociale Nominative, France) pour un mois donne"
      description: "Agrege les bulletins valides du mois/annee demandes, par employe (identite, contrat, brut/net/net imposable, heures travaillees)."
      operationId: generateDsnFrDeclaration
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [month, year]
              properties:
                month: { type: integer, minimum: 1, maximum: 12 }
                year: { type: integer, minimum: 2020, maximum: 2099 }
      responses:
        "200":
          description: "DSN generee"
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          $ref: "#/components/responses/Error403"
        "422":
          $ref: "#/components/responses/Validation422"

  /cotisation-simulation:
    post:
      tags: ["Payroll Engine"]
      summary: "Simuler les cotisations sociales employe/employeur et l'impot sur le revenu pour un salaire brut donne, sans persister (manager)"
      description: "Delegue au moteur de paie (CountryRulesInterface via PayrollCalculator, issue #1782). Issue #1869 : la simulation et le bulletin passent par le meme noyau de calcul (computeNetBreakdown) — resultats identiques pour un meme brut ; reponse complete et explicable : cotisations + assiette + impot + taxe de minimum fiscal + net + cout employeur, avec pays, devise, identifiant des regles et politique d'arrondi (docs/payroll/CALCULATION_CONTRACT.md)."
      operationId: simulateCotisations
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [country_code, gross_salary]
              properties:
                country_code:
                  type: string
                  enum: [DZ, MA, FR, TN, TR, SN, CM, CF, TD, CG, GA, GQ, CI, ML, BF, BJ, TG, NE, CA]
                gross_salary: { type: number, minimum: 0 }
                acknowledge_placeholder: { type: boolean, description: "Confirmation explicite requise pour les pays en règle placeholder (issue #1872)" }
      responses:
        "200":
          description: "Simulation des cotisations et net estime (contrat complet issue #1869)"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    properties:
                      correlation_id:
                        type: string
                        format: uuid
                        description: "Issue #1874 — identifiant de corrélation de la requête (logs ↔ réponse ↔ audit)."
                      country_code: { type: string }
                      gross_salary: { type: number }
                      employee_contributions:
                        type: array
                        items:
                          type: object
                          properties:
                            name: { type: string }
                            code: { type: string }
                            rate: { type: number }
                            cap: { type: number, nullable: true }
                            amount: { type: number }
                      employer_contributions:
                        type: array
                        items:
                          type: object
                          properties:
                            name: { type: string }
                            code: { type: string }
                            rate: { type: number }
                            cap: { type: number, nullable: true }
                            amount: { type: number }
                      total_employee_deduction: { type: number }
                      total_employer_cost: { type: number }
                      taxable_gross: { type: number }
                      income_tax: { type: number }
                      bracket_tax: { type: number }
                      total_deductions: { type: number }
                      net_before_tax: { type: number }
                      net_salary: { type: number }
                      total_cost_employer: { type: number }
                      contract:
                        type: object
                        description: "Contrat de calcul complet et explicable (MULTI-PAYS #1869) — mêmes appels métier que le bulletin."
                        properties:
                          country_code: { type: string }
                          currency: { type: string }
                          rules_identifier: { type: string }
                          rules_period: { type: string, format: date }
                          slab_version: { type: string }
                          confidence_level: { type: string, enum: [production, pilot, placeholder] }
                          compliance:
                            type: object
                            description: "Avertissement de conformité structuré (MULTI-PAYS #1872) — niveau, message localisé (clé i18n), source légale, date de vérification experte."
                            properties:
                              level: { type: string, enum: [production, pilot, placeholder] }
                              warning: { type: string }
                              warning_key: { type: string }
                              source: { type: string }
                              verification_date: { type: string, format: date, nullable: true }
                          rounding_policy: { type: string }
                          gross: { type: number }
                          social_employee: { type: number }
                          tax_base: { type: number }
                          income_tax: { type: number }
                          bracket_tax: { type: number }
                          other_deductions: { type: number }
                          net_salary: { type: number }
                          social_employer: { type: number }
                          total_cost: { type: number }
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          $ref: "#/components/responses/Error403"
        "422":
          $ref: "#/components/responses/Validation422"

  # --- Bank Export ---
  /bank-exports:
    get:
      tags: ["Payroll"]
      summary: "Lister les exports bancaires du tenant (pagine)"
      parameters:
        - name: per_page
          in: query
          required: false
          schema: { type: integer, minimum: 1, maximum: 100, default: 20 }
        - name: status
          in: query
          required: false
          schema: { type: string, maxLength: 20 }
      responses:
        "200":
          description: "Liste paginee des exports bancaires"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/BankExport"
                  meta:
                    type: object
                    properties:
                      current_page: { type: integer }
                      last_page: { type: integer }
                      per_page: { type: integer }
                      total: { type: integer }
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          $ref: "#/components/responses/Error403"
    post:
      tags: ["Payroll"]
      summary: "Creer un export bancaire pour un run valide/paye (asynchrone)"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [payroll_run_id, format]
              properties:
                payroll_run_id:
                  type: integer
                format:
                  type: string
                  enum: [sepa_xml, ccp_dz, virement_ma, csv_generic]
      responses:
        "202":
          description: "Export cree (generation asynchrone)"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/BankExport"
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          $ref: "#/components/responses/Error403"
        "422":
          $ref: "#/components/responses/Validation422"

  /bank-exports/{bankExport}:
    get:
      tags: ["Payroll"]
      summary: "Détail d'un export bancaire"
      parameters:
        - name: bankExport
          in: path
          required: true
          schema:
            type: integer
      responses:
        "200":
          description: "Export bancaire"
        "401":
          $ref: "#/components/responses/Error401"
        "404":
          $ref: "#/components/responses/Error404"

  /bank-exports/{bankExport}/download:
    get:
      tags: ["Payroll"]
      summary: "Telecharger un export bancaire"
      parameters:
        - name: bankExport
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: "Fichier export"
        "401":
          $ref: "#/components/responses/Error401"

  # --- Billing ---
  /billing/subscription:
    get:
      tags: ["Billing"]
      summary: "Voir l'abonnement courant"
      responses:
        "200":
          description: "Detail abonnement"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/Subscription"
        "401":
          $ref: "#/components/responses/Error401"

  /billing/subscription/upgrade:
    post:
      tags: ["Billing"]
      summary: "Upgrader l'abonnement (manager)"
      operationId: upgradeSubscription
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [plan]
              properties:
                plan:
                  type: string
                  enum: [starter, business, enterprise]
                payment_method:
                  type: string
                  enum: [stripe, bank_transfer, manual]
                  nullable: true
      responses:
        "200":
          description: "Abonnement mis a jour (cree s'il n'existait pas encore)"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/Subscription"
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          $ref: "#/components/responses/Error403"
        "422":
          $ref: "#/components/responses/Validation422"

  /billing/subscription/cancel:
    post:
      tags: ["Billing"]
      summary: "Annuler l'abonnement (manager)"
      operationId: cancelSubscription
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                reason:
                  type: string
                  nullable: true
                  maxLength: 1000
      responses:
        "200":
          description: "Abonnement annule"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/Subscription"
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          $ref: "#/components/responses/Error403"
        "404":
          description: "Aucun abonnement trouve pour cette entreprise"

  /billing/subscription/renew:
    post:
      tags: ["Billing"]
      summary: "Renouveler l'abonnement (manager)"
      operationId: renewSubscription
      responses:
        "200":
          description: "Abonnement renouvele, nouvelle periode d'un mois"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/Subscription"
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          $ref: "#/components/responses/Error403"
        "404":
          description: "Aucun abonnement trouve pour cette entreprise"

  /billing/checkout:
    post:
      tags: ["Billing"]
      summary: "Creer une session Stripe Checkout pour souscrire/upgrader (manager)"
      operationId: createBillingCheckoutSession
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [plan, success_url, cancel_url]
              properties:
                plan:
                  type: string
                  enum: [starter, business, enterprise]
                success_url:
                  type: string
                  format: uri
                  maxLength: 500
                cancel_url:
                  type: string
                  format: uri
                  maxLength: 500
      responses:
        "200":
          description: "Session Stripe Checkout creee"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    properties:
                      checkout_url:
                        type: string
                        format: uri
                      session_id:
                        type: string
        "401":
          $ref: "#/components/responses/Error401"
        "422":
          $ref: "#/components/responses/Validation422"
        "500":
          description: "Echec de creation de la session Stripe"
        "503":
          description: "Stripe non configure sur cet environnement"

  /billing/portal:
    post:
      tags: ["Billing"]
      summary: "Creer une session Stripe Customer Portal pour gerer l'abonnement (manager)"
      operationId: createBillingPortalSession
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [return_url]
              properties:
                return_url:
                  type: string
                  format: uri
                  maxLength: 500
      responses:
        "200":
          description: "URL du portail client Stripe"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    properties:
                      portal_url:
                        type: string
                        format: uri
        "401":
          $ref: "#/components/responses/Error401"
        "404":
          description: "Aucun compte de paiement Stripe associe a cette entreprise"
        "422":
          $ref: "#/components/responses/Validation422"
        "500":
          description: "Echec d'acces au portail de facturation"

  /billing/invoices:
    get:
      tags: ["Billing"]
      summary: "Lister les factures"
      responses:
        "200":
          description: "Liste des factures"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/Invoice"
        "401":
          $ref: "#/components/responses/Error401"

  /billing/invoices/{id}:
    get:
      tags: ["Billing"]
      summary: "Voir une facture"
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: "Detail facture"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/Invoice"
        "401":
          $ref: "#/components/responses/Error401"

  /billing/invoices/{id}/pdf:
    get:
      tags: ["Billing"]
      summary: "Telecharger facture PDF"
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: "Fichier PDF"
          content:
            application/pdf:
              schema:
                type: string
                format: binary
        "401":
          $ref: "#/components/responses/Error401"

  # =============================================
  # ITERATION 33.3 — MODULES ETENDUS
  # =============================================

  # --- Recruitment ---
  /recruitment/jobs:
    get:
      tags: ["Recruitment"]
      summary: "Lister les offres d'emploi"
      parameters:
        - name: status
          in: query
          schema: { type: string, enum: [draft, published, closed] }
        - name: per_page
          in: query
          schema: { type: integer, default: 15 }
      responses:
        "200":
          description: "Liste paginee"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/JobPosting"
                  meta:
                    $ref: "#/components/schemas/PaginationMeta"
        "401":
          $ref: "#/components/responses/Error401"
    post:
      tags: ["Recruitment"]
      summary: "Creer une offre d'emploi"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/StoreJobPostingRequest"
      responses:
        "201":
          description: "Offre creee"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/JobPosting"
        "401":
          $ref: "#/components/responses/Error401"
        "422":
          $ref: "#/components/responses/Validation422"

  /recruitment/jobs/{jobPosting}:
    get:
      tags: ["Recruitment"]
      summary: "Voir une offre"
      parameters:
        - name: jobPosting
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: "Detail offre"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/JobPosting"
        "401":
          $ref: "#/components/responses/Error401"
    put:
      tags: ["Recruitment"]
      summary: "Modifier une offre"
      parameters:
        - name: jobPosting
          in: path
          required: true
          schema: { type: integer }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/StoreJobPostingRequest"
      responses:
        "200":
          description: "Offre modifiee"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/JobPosting"
        "401":
          $ref: "#/components/responses/Error401"

  /recruitment/jobs/{jobPosting}/publish:
    post:
      tags: ["Recruitment"]
      summary: "Publier une offre"
      parameters:
        - name: jobPosting
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: "Offre publiee"
        "401":
          $ref: "#/components/responses/Error401"

  /recruitment/jobs/{jobPosting}/close:
    post:
      tags: ["Recruitment"]
      summary: "Cloturer une offre"
      parameters:
        - name: jobPosting
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: "Offre cloturee"
        "401":
          $ref: "#/components/responses/Error401"

  /recruitment/jobs/{jobPosting}/applicants:
    get:
      tags: ["Recruitment"]
      summary: "Lister les candidats"
      parameters:
        - name: jobPosting
          in: path
          required: true
          schema: { type: integer }
        - name: stage
          in: query
          schema: { type: string }
        - name: per_page
          in: query
          schema: { type: integer, default: 15 }
      responses:
        "200":
          description: "Liste des candidats"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/Applicant"
        "401":
          $ref: "#/components/responses/Error401"
    post:
      tags: ["Recruitment"]
      summary: "Ajouter un candidat"
      parameters:
        - name: jobPosting
          in: path
          required: true
          schema: { type: integer }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/StoreApplicantRequest"
      responses:
        "201":
          description: "Candidat ajoute"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/Applicant"
        "401":
          $ref: "#/components/responses/Error401"
        "422":
          $ref: "#/components/responses/Validation422"

  /recruitment/applicants/{applicant}:
    put:
      tags: ["Recruitment"]
      summary: "Modifier un candidat"
      parameters:
        - name: applicant
          in: path
          required: true
          schema: { type: integer }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                stage:
                  type: string
                rating:
                  type: integer
                  minimum: 1
                  maximum: 5
                notes:
                  type: string
                  nullable: true
      responses:
        "200":
          description: "Candidat modifie"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/Applicant"
        "401":
          $ref: "#/components/responses/Error401"

  /recruitment/applicants/{applicant}/interviews:
    post:
      tags: ["Recruitment"]
      summary: "Planifier un entretien"
      parameters:
        - name: applicant
          in: path
          required: true
          schema: { type: integer }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [scheduled_at, type]
              properties:
                scheduled_at:
                  type: string
                  format: date-time
                type:
                  type: string
                  enum: [phone, video, onsite, technical]
                interviewer_ids:
                  type: array
                  items:
                    type: integer
                location:
                  type: string
                  nullable: true
                notes:
                  type: string
                  nullable: true
      responses:
        "201":
          description: "Entretien planifie"
        "401":
          $ref: "#/components/responses/Error401"
        "422":
          $ref: "#/components/responses/Validation422"

  /recruitment/interviews/{interview}:
    put:
      tags: ["Recruitment"]
      summary: "Modifier un entretien"
      parameters:
        - name: interview
          in: path
          required: true
          schema: { type: integer }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                status:
                  type: string
                  enum: [scheduled, completed, cancelled]
                feedback:
                  type: string
                  nullable: true
                rating:
                  type: integer
                  minimum: 1
                  maximum: 5
      responses:
        "200":
          description: "Entretien modifie"
        "401":
          $ref: "#/components/responses/Error401"

  # --- Training ---
  /training/courses:
    get:
      tags: ["Training"]
      summary: "Lister les formations"
      parameters:
        - name: per_page
          in: query
          schema: { type: integer, default: 15 }
      responses:
        "200":
          description: "Liste paginee"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/TrainingCourse"
        "401":
          $ref: "#/components/responses/Error401"
    post:
      tags: ["Training"]
      summary: "Creer une formation"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/StoreTrainingCourseRequest"
      responses:
        "201":
          description: "Formation creee"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/TrainingCourse"
        "401":
          $ref: "#/components/responses/Error401"
        "422":
          $ref: "#/components/responses/Validation422"

  /training/courses/{trainingCourse}:
    delete:
      parameters:
      - in: path
        name: trainingCourse
        required: true
        schema:
          type: integer
      responses:
        '200':
          description: Supprimer une formation
        '401':
          $ref: '#/components/responses/Error401'
        '403':
          $ref: '#/components/responses/Error403'
        '404':
          $ref: '#/components/responses/Error404'
      summary: Supprimer une formation
      tags:
      - Training
    get:
      tags: ["Training"]
      summary: "Voir une formation"
      parameters:
        - name: trainingCourse
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: "Detail formation"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/TrainingCourse"
        "401":
          $ref: "#/components/responses/Error401"
    put:
      tags: ["Training"]
      summary: "Modifier une formation"
      parameters:
        - name: trainingCourse
          in: path
          required: true
          schema: { type: integer }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/StoreTrainingCourseRequest"
      responses:
        "200":
          description: "Formation modifiee"
        "401":
          $ref: "#/components/responses/Error401"

  /training/courses/{trainingCourse}/sessions:
    get:
      tags: ["Training"]
      summary: "Lister les sessions d'une formation"
      parameters:
        - name: trainingCourse
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: "Liste des sessions"
        "401":
          $ref: "#/components/responses/Error401"
    post:
      tags: ["Training"]
      summary: "Creer une session de formation"
      parameters:
        - name: trainingCourse
          in: path
          required: true
          schema: { type: integer }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [start_date, end_date, max_participants]
              properties:
                start_date:
                  type: string
                  format: date
                end_date:
                  type: string
                  format: date
                max_participants:
                  type: integer
                location:
                  type: string
                  nullable: true
                trainer_name:
                  type: string
                  nullable: true
      responses:
        "201":
          description: "Session creee"
        "401":
          $ref: "#/components/responses/Error401"
        "422":
          $ref: "#/components/responses/Validation422"

  /training/sessions:
    get:
      tags: ["Training"]
      summary: "Toutes les sessions de formation du tenant"
      description: "Liste paginee des sessions (avec cours) — cockpit manager/admin. Ajoutee en vague QA 2026-08-14."
      security:
        - BearerAuth: []
      parameters:
        - name: per_page
          in: query
          required: false
          schema: { type: integer, default: 15 }
      responses:
        "200":
          description: "Sessions paginees"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/TrainingSession"
                  meta:
                    $ref: "#/components/schemas/PaginationMeta"
        "401":
          $ref: "#/components/responses/Error401"

  /training/enrollments:
    get:
      tags: ["Training"]
      summary: "Toutes les inscriptions formation du tenant"
      description: "Liste paginee des inscriptions (avec cours) — cockpit manager/admin. Ajoutee en vague QA 2026-08-14."
      security:
        - BearerAuth: []
      parameters:
        - name: per_page
          in: query
          required: false
          schema: { type: integer, default: 15 }
      responses:
        "200":
          description: "Inscriptions paginees"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/TrainingEnrollment"
                  meta:
                    $ref: "#/components/schemas/PaginationMeta"
        "401":
          $ref: "#/components/responses/Error401"

  /training/sessions/{trainingSession}:
    delete:
      parameters:
      - in: path
        name: trainingSession
        required: true
        schema:
          type: integer
      responses:
        '200':
          description: Supprimer une session
        '401':
          $ref: '#/components/responses/Error401'
        '403':
          $ref: '#/components/responses/Error403'
        '404':
          $ref: '#/components/responses/Error404'
      summary: Supprimer une session
      tags:
      - Training
    put:
      tags: ["Training"]
      summary: "Modifier une session"
      parameters:
        - name: trainingSession
          in: path
          required: true
          schema: { type: integer }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                start_date:
                  type: string
                  format: date
                end_date:
                  type: string
                  format: date
                status:
                  type: string
                  enum: [planned, in_progress, completed, cancelled]
      responses:
        "200":
          description: "Session modifiee"
        "401":
          $ref: "#/components/responses/Error401"

  /training/sessions/{trainingSession}/enroll:
    post:
      tags: ["Training"]
      summary: "Inscrire un employe"
      parameters:
        - name: trainingSession
          in: path
          required: true
          schema: { type: integer }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [employee_id]
              properties:
                employee_id:
                  type: integer
      responses:
        "201":
          description: "Inscription confirmee"
        "401":
          $ref: "#/components/responses/Error401"
        "422":
          $ref: "#/components/responses/Validation422"

  /training/enrollments/{trainingEnrollment}:
    delete:
      parameters:
      - in: path
        name: trainingEnrollment
        required: true
        schema:
          type: integer
      responses:
        '200':
          description: Supprimer une inscription formation
        '401':
          $ref: '#/components/responses/Error401'
        '403':
          $ref: '#/components/responses/Error403'
        '404':
          $ref: '#/components/responses/Error404'
      summary: Supprimer une inscription formation
      tags:
      - Training
    put:
      tags: ["Training"]
      summary: "Modifier une inscription"
      parameters:
        - name: trainingEnrollment
          in: path
          required: true
          schema: { type: integer }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                status:
                  type: string
                  enum: [enrolled, completed, cancelled]
                score:
                  type: number
                  nullable: true
                feedback:
                  type: string
                  nullable: true
      responses:
        "200":
          description: "Inscription modifiee"
        "401":
          $ref: "#/components/responses/Error401"

  # --- Loans ---
  /loans:
    get:
      tags: ["Loans"]
      summary: "Lister les prets"
      parameters:
        - name: status
          in: query
          schema: { type: string, enum: [pending, approved, disbursed, repaying, completed, rejected] }
        - name: employee_id
          in: query
          schema: { type: integer }
        - name: per_page
          in: query
          schema: { type: integer, default: 15 }
      responses:
        "200":
          description: "Liste paginee"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/Loan"
                  meta:
                    $ref: "#/components/schemas/PaginationMeta"
        "401":
          $ref: "#/components/responses/Error401"
    post:
      tags: ["Loans"]
      summary: "Demander un pret"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/StoreLoanRequest"
      responses:
        "201":
          description: "Pret cree"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/Loan"
        "401":
          $ref: "#/components/responses/Error401"
        "422":
          $ref: "#/components/responses/Validation422"

  /loans/{employeeLoan}:
    get:
      tags: ["Loans"]
      summary: "Voir un pret"
      parameters:
        - name: employeeLoan
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: "Detail pret"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/Loan"
        "401":
          $ref: "#/components/responses/Error401"

  /loans/{employeeLoan}/approve:
    post:
      tags: ["Loans"]
      summary: "Approuver un pret"
      parameters:
        - name: employeeLoan
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: "Pret approuve"
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          $ref: "#/components/responses/Error403"
    put:
      tags: ["Deprecated"]
      summary: "Approuver un prêt (déprécié — utiliser POST)"
      deprecated: true
      parameters:
        - name: employeeLoan
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: "Action effectuée (ancien verbe, déprécié #4930)"
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          description: "Non autorisé"
        "404":
          description: "Introuvable"

  /loans/{employeeLoan}/disburse:
    post:
      tags: ["Loans"]
      summary: "Decaisser un pret"
      parameters:
        - name: employeeLoan
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: "Pret decaisse"
        "401":
          $ref: "#/components/responses/Error401"

  # --- Expense Claims ---
    put:
      tags: ["Deprecated"]
      summary: "Décaisser un prêt (déprécié — utiliser POST)"
      deprecated: true
      parameters:
        - name: employeeLoan
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: "Action effectuée (ancien verbe, déprécié #4930)"
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          description: "Non autorisé"
        "404":
          description: "Introuvable"

  /expense-claims:
    get:
      tags: ["Expenses"]
      summary: "Lister les notes de frais"
      parameters:
        - name: status
          in: query
          schema: { type: string, enum: [draft, submitted, approved, rejected] }
        - name: per_page
          in: query
          schema: { type: integer, default: 15 }
      responses:
        "200":
          description: "Liste paginee"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/ExpenseClaim"
                  meta:
                    $ref: "#/components/schemas/PaginationMeta"
        "401":
          $ref: "#/components/responses/Error401"
    post:
      tags: ["Expenses"]
      summary: "Creer une note de frais"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/StoreExpenseClaimRequest"
      responses:
        "201":
          description: "Note creee"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/ExpenseClaim"
        "401":
          $ref: "#/components/responses/Error401"
        "422":
          $ref: "#/components/responses/Validation422"

  /expense-claims/{expenseClaim}:
    delete:
      parameters:
      - in: path
        name: expenseClaim
        required: true
        schema:
          type: integer
      responses:
        '200':
          description: Note de frais supprimee
        '401':
          $ref: '#/components/responses/Error401'
        '403':
          $ref: '#/components/responses/Error403'
        '404':
          $ref: '#/components/responses/Error404'
      summary: Supprimer une note de frais
      tags:
      - Expenses
    put:
      parameters:
      - in: path
        name: expenseClaim
        required: true
        schema:
          type: integer
      requestBody:
        content:
          application/json:
            schema:
              properties:
                amount:
                  format: number
                  type: number
                currency:
                  type: string
                description:
                  type: string
                expense_date:
                  format: date
                  type: string
                expense_type:
                  type: string
              type: object
        required: true
      responses:
        '200':
          content:
            application/json:
              schema:
                properties:
                  data:
                    $ref: '#/components/schemas/ExpenseClaim'
                type: object
          description: Note de frais modifiee
        '401':
          $ref: '#/components/responses/Error401'
        '403':
          $ref: '#/components/responses/Error403'
        '404':
          $ref: '#/components/responses/Error404'
        '422':
          $ref: '#/components/responses/Validation422'
      summary: Modifier une note de frais
      tags:
      - Expenses
    get:
      tags: ["Expenses"]
      summary: "Voir une note de frais"
      parameters:
        - name: expenseClaim
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: "Detail note"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/ExpenseClaim"
        "401":
          $ref: "#/components/responses/Error401"

  /expense-claims/{expenseClaim}/submit:
    put:
      tags: ["Expenses"]
      summary: "Soumettre une note de frais"
      parameters:
        - name: expenseClaim
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: "Note soumise"
        "401":
          $ref: "#/components/responses/Error401"

  /expense-claims/{expenseClaim}/approve:
    post:
      tags: ["Expenses"]
      summary: "Approuver une note de frais"
      parameters:
        - name: expenseClaim
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: "Note approuvee"
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          $ref: "#/components/responses/Error403"
    put:
      tags: ["Deprecated"]
      summary: "Approuver une note de frais (déprécié — utiliser POST)"
      deprecated: true
      parameters:
        - name: expenseClaim
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: "Action effectuée (ancien verbe, déprécié #4930)"
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          description: "Non autorisé"
        "404":
          description: "Introuvable"

  /expense-claims/{expenseClaim}/reject:
    post:
      tags: ["Expenses"]
      summary: "Rejeter une note de frais"
      parameters:
        - name: expenseClaim
          in: path
          required: true
          schema: { type: integer }
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                rejection_reason:
                  type: string
      responses:
        "200":
          description: "Note rejetee"
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          $ref: "#/components/responses/Error403"

  # =============================================
  # ITERATION 33.4 — ADMIN, INTEGRATIONS
  # =============================================

  # --- Approvals ---
    put:
      tags: ["Deprecated"]
      summary: "Refuser une note de frais (déprécié — utiliser POST)"
      deprecated: true
      parameters:
        - name: expenseClaim
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: "Action effectuée (ancien verbe, déprécié #4930)"
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          description: "Non autorisé"
        "404":
          description: "Introuvable"

  /approval-workflows:
    get:
      tags: ["Approvals"]
      summary: "Lister les workflows d'approbation"
      responses:
        "200":
          description: "Liste des workflows"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/ApprovalWorkflow"
        "401":
          $ref: "#/components/responses/Error401"
    post:
      tags: ["Approvals"]
      summary: "Creer un workflow"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/StoreApprovalWorkflowRequest"
      responses:
        "201":
          description: "Workflow cree"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/ApprovalWorkflow"
        "401":
          $ref: "#/components/responses/Error401"
        "422":
          $ref: "#/components/responses/Validation422"

  /approval-workflows/{approvalWorkflow}:
    put:
      tags: ["Approvals"]
      summary: "Modifier un workflow"
      parameters:
        - name: approvalWorkflow
          in: path
          required: true
          schema: { type: integer }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/StoreApprovalWorkflowRequest"
      responses:
        "200":
          description: "Workflow modifie"
        "401":
          $ref: "#/components/responses/Error401"
    delete:
      tags: ["Approvals"]
      summary: "Supprimer un workflow"
      parameters:
        - name: approvalWorkflow
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: "Workflow supprime"
        "401":
          $ref: "#/components/responses/Error401"

  /approvals/pending:
    get:
      tags: ["Approvals"]
      summary: "Approbations en attente"
      responses:
        "200":
          description: "Liste des demandes en attente"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/ApprovalRequest"
        "401":
          $ref: "#/components/responses/Error401"

  /approvals/{approvalRequest}/approve:
    post:
      tags: ["Approvals"]
      summary: "Approuver une demande"
      parameters:
        - name: approvalRequest
          in: path
          required: true
          schema: { type: integer }
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                comment:
                  type: string
                  nullable: true
      responses:
        "200":
          description: "Demande approuvee"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/ApprovalRequest"
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          $ref: "#/components/responses/Error403"

  /approvals/{approvalRequest}/reject:
    post:
      tags: ["Approvals"]
      summary: "Rejeter une demande"
      parameters:
        - name: approvalRequest
          in: path
          required: true
          schema: { type: integer }
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                comment:
                  type: string
      responses:
        "200":
          description: "Demande rejetee"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/ApprovalRequest"
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          $ref: "#/components/responses/Error403"

  /approvals/history:
    get:
      tags: ["Approvals"]
      summary: "Historique des approbations"
      parameters:
        - name: per_page
          in: query
          schema: { type: integer, default: 15 }
      responses:
        "200":
          description: "Historique pagine de l'approbateur (enveloppe data/meta)"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      type: object
                      properties:
                        id: { type: integer }
                        approval_request_id: { type: integer }
                        level: { type: integer }
                        approver_id: { type: integer }
                        decision: { type: string, enum: [approved, rejected] }
                        comment: { type: [string, "null"] }
                        decided_at: { type: [string, "null"], format: date-time }
                        request:
                          type: object
                          properties:
                            id: { type: integer }
                            status: { type: string }
                            approvable_type: { type: string }
                            approvable_id: { type: integer }
                            requester_id: { type: integer }
                            requester:
                              type: [object, "null"]
                              properties:
                                id: { type: integer }
                                first_name: { type: string }
                                last_name: { type: string }
                        created_at: { type: [string, "null"], format: date-time }
                  links:
                    type: object
                    properties:
                      first: { type: [string, "null"] }
                      last: { type: [string, "null"] }
                      prev: { type: [string, "null"] }
                      next: { type: [string, "null"] }
                  meta:
                    type: object
                    properties:
                      current_page: { type: integer }
                      from: { type: [integer, "null"] }
                      last_page: { type: integer }
                      path: { type: string }
                      per_page: { type: integer }
                      to: { type: [integer, "null"] }
                      total: { type: integer }
        "401":
          $ref: "#/components/responses/Error401"

  # --- Cabinet (Digital Filing) ---
  /cabinet/folders:
    get:
      tags: ["Cabinet"]
      summary: "Lister les dossiers"
      parameters:
        - name: parent_id
          in: query
          schema: { type: integer, nullable: true }
        - name: employee_id
          in: query
          schema: { type: integer }
      responses:
        "200":
          description: "Liste des dossiers"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/CabinetFolder"
        "401":
          $ref: "#/components/responses/Error401"
    post:
      tags: ["Cabinet"]
      summary: "Creer un dossier"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [name]
              properties:
                name:
                  type: string
                parent_id:
                  type: integer
                  nullable: true
                employee_id:
                  type: integer
                  nullable: true
      responses:
        "201":
          description: "Dossier cree"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/CabinetFolder"
        "401":
          $ref: "#/components/responses/Error401"
        "422":
          $ref: "#/components/responses/Validation422"

  /cabinet/folders/{cabinetFolder}:
    get:
      tags: ["Cabinet"]
      summary: "Voir un dossier"
      parameters:
        - name: cabinetFolder
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: "Detail dossier avec documents"
        "401":
          $ref: "#/components/responses/Error401"
    put:
      tags: ["Cabinet"]
      summary: "Modifier un dossier"
      parameters:
        - name: cabinetFolder
          in: path
          required: true
          schema: { type: integer }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                name:
                  type: string
      responses:
        "200":
          description: "Dossier modifie"
        "401":
          $ref: "#/components/responses/Error401"
    delete:
      tags: ["Cabinet"]
      summary: "Supprimer un dossier"
      parameters:
        - name: cabinetFolder
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: "Dossier supprime"
        "401":
          $ref: "#/components/responses/Error401"

  /cabinet/stats:
    get:
      tags: ["Cabinet"]
      summary: "Statistiques de stockage du placard de l'employe courant"
      security: [{ BearerAuth: [] }]
      responses:
        "200":
          description: "Statistiques de stockage (documents, taille utilisee...)"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
        "401":
          $ref: "#/components/responses/Error401"

  /cabinet/documents:
    get:
      tags: ["Cabinet"]
      summary: "Lister les documents"
      parameters:
        - name: folder_id
          in: query
          schema: { type: integer }
        - name: employee_id
          in: query
          schema: { type: integer }
        - name: type
          in: query
          schema: { type: string }
        - name: per_page
          in: query
          schema: { type: integer, default: 15 }
      responses:
        "200":
          description: "Liste des documents"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/CabinetDocument"
        "401":
          $ref: "#/components/responses/Error401"
    post:
      tags: ["Cabinet"]
      summary: "Uploader un document"
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              required: [file, name]
              properties:
                file:
                  type: string
                  format: binary
                name:
                  type: string
                folder_id:
                  type: integer
                  nullable: true
                employee_id:
                  type: integer
                  nullable: true
                document_type:
                  type: string
                  nullable: true
      responses:
        "201":
          description: "Document uploade"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/CabinetDocument"
        "401":
          $ref: "#/components/responses/Error401"
        "422":
          $ref: "#/components/responses/Validation422"

  /cabinet/documents/{cabinetDocument}:
    get:
      tags: ["Cabinet"]
      summary: "Voir un document"
      parameters:
        - name: cabinetDocument
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: "Detail document"
        "401":
          $ref: "#/components/responses/Error401"
    put:
      tags: ["Cabinet"]
      summary: "Modifier un document"
      parameters:
        - name: cabinetDocument
          in: path
          required: true
          schema: { type: integer }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                name:
                  type: string
                document_type:
                  type: string
      responses:
        "200":
          description: "Document modifie"
        "401":
          $ref: "#/components/responses/Error401"
    delete:
      tags: ["Cabinet"]
      summary: "Supprimer un document"
      parameters:
        - name: cabinetDocument
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: "Document supprime"
        "401":
          $ref: "#/components/responses/Error401"

  /cabinet/documents/{cabinetDocument}/download:
    get:
      tags: ["Cabinet"]
      summary: "Telecharger un document"
      parameters:
        - name: cabinetDocument
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: "Fichier"
          content:
            application/octet-stream:
              schema:
                type: string
                format: binary
        "401":
          $ref: "#/components/responses/Error401"

  /cabinet/documents/{cabinetDocument}/move:
    patch:
      tags: ["Cabinet"]
      summary: "Deplacer un document"
      parameters:
        - name: cabinetDocument
          in: path
          required: true
          schema: { type: integer }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [folder_id]
              properties:
                folder_id:
                  type: integer
      responses:
        "200":
          description: "Document deplace"
        "401":
          $ref: "#/components/responses/Error401"

  # --- Org Chart ---
  /org-chart:
    get:
      tags: ["Organization"]
      summary: "Organigramme complet"
      responses:
        "200":
          description: "Arbre hierarchique"
        "401":
          $ref: "#/components/responses/Error401"

  /org-chart/{employeeId}/subordinates:
    get:
      tags: ["Organization"]
      summary: "Subordonnes d'un employe"
      parameters:
        - name: employeeId
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: "Liste des subordonnes"
        "401":
          $ref: "#/components/responses/Error401"

  /org-chart/{employeeId}/manager-chain:
    get:
      tags: ["Organization"]
      summary: "Chaine hierarchique"
      parameters:
        - name: employeeId
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: "Liste managers vers le haut"
        "401":
          $ref: "#/components/responses/Error401"

  # --- Dashboard ---
  /dashboard/summary:
    get:
      tags: ["Dashboard"]
      summary: "Resume dashboard"
      responses:
        "200":
          description: "KPIs et compteurs"
        "401":
          $ref: "#/components/responses/Error401"

  /dashboard/recent-activity:
    get:
      tags: ["Dashboard"]
      summary: "Activite recente"
      responses:
        "200":
          description: "Dernieres activites"
        "401":
          $ref: "#/components/responses/Error401"

  /dashboard/kpi:
    get:
      tags: ["Dashboard"]
      summary: "KPIs detailles"
      responses:
        "200":
          description: "Indicateurs cles"
        "401":
          $ref: "#/components/responses/Error401"

  /api-tokens:
    get:
      tags: ["Tokens"]
      summary: "Lister les tokens API du manager (self-service)"
      responses:
        "200":
          description: "Tokens (id, name, abilities, last_used_at, created_at)"
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          description: "Non-manager"
    post:
      tags: ["Tokens"]
      summary: "Creer un token API (renvoye une seule fois en clair)"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [name]
              properties:
                name:
                  type: string
                  maxLength: 255
      responses:
        "201":
          description: "Token cree (plainTextToken inclus)"
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          description: "Non-manager"

  /api-tokens/{tokenId}:
    delete:
      tags: ["Tokens"]
      summary: "Revoquer un token API"
      parameters:
        - name: tokenId
          in: path
          required: true
          schema: { type: integer }
      responses:
        "204":
          description: "Token revoque"
        "401":
          $ref: "#/components/responses/Error401"

  /notifications/unread:
    get:
      tags: ["Notifications"]
      summary: "Notifications non lues de l'employe"
      responses:
        "200":
          description: "Notifications non lues"
        "401":
          $ref: "#/components/responses/Error401"

  /notifications/mark-all-read:
    post:
      tags: ["Notifications"]
      summary: "Marquer toutes les notifications comme lues"
      responses:
        "200":
          description: "Notifications marquees lues"
        "401":
          $ref: "#/components/responses/Error401"

  /dashboard/admin:
    get:
      tags: ["Dashboard"]
      summary: "Synthese dashboard admin (principal)"
      responses:
        "200":
          description: "Synthese"
        "401":
          $ref: "#/components/responses/Error401"

  /dashboard/rh:
    get:
      tags: ["Dashboard"]
      summary: "Synthese dashboard RH"
      responses:
        "200":
          description: "Synthese"
        "401":
          $ref: "#/components/responses/Error401"

  /dashboard/comptable:
    get:
      tags: ["Dashboard"]
      summary: "Synthese dashboard comptable"
      responses:
        "200":
          description: "Synthese"
        "401":
          $ref: "#/components/responses/Error401"

  /dashboard/marketing:
    get:
      tags: ["Dashboard"]
      summary: "Synthese dashboard marketing"
      responses:
        "200":
          description: "Synthese"
        "401":
          $ref: "#/components/responses/Error401"

  /company/team-roles:
    get:
      tags: ["Employees"]
      summary: "Roles de l'equipe (principal uniquement)"
      responses:
        "200":
          description: "Employes avec leurs roles"
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          description: "Non-principal"

  /employees/{employee}/assign-role:
    post:
      tags: ["Employees"]
      summary: "Assigner un role manager a un employe (principal)"
      parameters:
        - name: employee
          in: path
          required: true
          schema: { type: integer }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                manager_role:
                  type: string
                  nullable: true
                  enum: [rh, comptable, marketing, dept, principal]
      responses:
        "200":
          description: "Role assigne (audit trail dispatching)"
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          description: "Non-principal"
        "404":
          description: "Employe hors societe"

  # --- Exports ---
  /export/employees:
    get:
      tags: ["Exports"]
      summary: "Exporter les employes"
      parameters:
        - name: format
          in: query
          schema: { type: string, enum: [csv, xlsx, pdf], default: csv }
      responses:
        "200":
          description: "Donnees export"
        "401":
          $ref: "#/components/responses/Error401"

  /export/attendance:
    get:
      tags: ["Exports"]
      summary: "Exporter le pointage"
      parameters:
        - name: format
          in: query
          schema: { type: string, enum: [csv, xlsx, pdf], default: csv }
        - name: month
          in: query
          schema: { type: integer }
        - name: year
          in: query
          schema: { type: integer }
      responses:
        "200":
          description: "Donnees export"
        "401":
          $ref: "#/components/responses/Error401"


  /export/absences:
    get:
      tags: ["Exports"]
      summary: "Exporter les absences"
      parameters:
        - name: format
          in: query
          schema: { type: string, enum: [csv, xlsx, pdf], default: csv }
      responses:
        "200":
          description: "Donnees export"
        "401":
          $ref: "#/components/responses/Error401"

  /export/training:
    get:
      tags: ["Exports"]
      summary: "Exporter les formations"
      parameters:
        - name: format
          in: query
          schema: { type: string, enum: [csv, xlsx, pdf], default: csv }
      responses:
        "200":
          description: "Donnees export"
        "401":
          $ref: "#/components/responses/Error401"

  /export/contracts:
    get:
      tags: ["Exports"]
      summary: "Exporter les contrats"
      parameters:
        - name: format
          in: query
          schema: { type: string, enum: [csv, xlsx, pdf], default: csv }
      responses:
        "200":
          description: "Donnees export"
        "401":
          $ref: "#/components/responses/Error401"

  /export/vehicles:
    get:
      tags: ["Exports"]
      summary: "Exporter la flotte"
      parameters:
        - name: format
          in: query
          schema: { type: string, enum: [csv, xlsx, pdf], default: csv }
      responses:
        "200":
          description: "Donnees export"
        "401":
          $ref: "#/components/responses/Error401"

  /export/payroll-journal:
    get:
      tags: ["Exports"]
      summary: "Exporter le journal de paie"
      parameters:
        - name: format
          in: query
          schema: { type: string, enum: [csv, xlsx], default: csv }
      responses:
        "200":
          description: "Donnees export"
        "401":
          $ref: "#/components/responses/Error401"

  /export/payroll-ledger:
    get:
      tags: ["Exports"]
      summary: "Exporter le grand livre de paie"
      parameters:
        - name: format
          in: query
          schema: { type: string, enum: [csv, xlsx], default: csv }
      responses:
        "200":
          description: "Donnees export"
        "401":
          $ref: "#/components/responses/Error401"

  /export/accounting-od:
    get:
      tags: ["Exports"]
      summary: "Exporter l'ordre de virement (OD)"
      parameters:
        - name: format
          in: query
          schema: { type: string, enum: [csv, xlsx], default: csv }
      responses:
        "200":
          description: "Donnees export"
        "401":
          $ref: "#/components/responses/Error401"

  # --- Notification Preferences ---
  # --- Audit Log ---
  /audit-logs:
    get:
      tags: ["Admin"]
      summary: "Journal d'audit"
      parameters:
        - name: action
          in: query
          schema: { type: string }
        - name: user_id
          in: query
          schema: { type: integer }
        - name: from
          in: query
          schema: { type: string, format: date }
        - name: to
          in: query
          schema: { type: string, format: date }
        - name: per_page
          in: query
          schema: { type: integer, default: 50 }
      responses:
        "200":
          description: "Liste des evenements"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/AuditLog"
                  meta:
                    $ref: "#/components/schemas/PaginationMeta"
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          $ref: "#/components/responses/Error403"

  /audit-logs/export-csv:
    get:
      tags: ["Admin"]
      summary: "Exporter le journal d'audit en CSV"
      description: |
        Reserve au manager `principal`. Applique les memes filtres que `GET /audit-logs`
        (action, user_id, from, to) et retourne un flux CSV (`Content-Type: text/csv`).
      parameters:
        - name: action
          in: query
          schema: { type: string }
        - name: user_id
          in: query
          schema: { type: integer }
        - name: from
          in: query
          schema: { type: string, format: date }
        - name: to
          in: query
          schema: { type: string, format: date }
      responses:
        "200":
          description: "Flux CSV du journal d'audit"
          content:
            text/csv:
              schema: { type: string, format: binary }
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          $ref: "#/components/responses/Error403"

  /audit-logs/{auditLog}:
    get:
      tags: ["Admin"]
      summary: "Voir un evenement d'audit"
      description: "Reserve au manager `principal`. Journal en lecture seule (append-only), pas de update/delete."
      parameters:
        - name: auditLog
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: "Detail de l'evenement"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/AuditLog"
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          $ref: "#/components/responses/Error403"
        "404":
          $ref: "#/components/responses/Error404"

  # --- HR Predictions (AI) ---
  # Reserve aux managers avec role principal ou rh (App\Modules\HR\...\PredictionController).
  /predictions/turnover:
    get:
      tags: ["AI Analytics"]
      summary: "Predire le risque de turnover par employe"
      parameters:
        - name: months
          in: query
          description: "Horizon d'analyse historique en mois"
          schema: { type: integer, default: 6 }
      responses:
        "200":
          description: "Predictions de turnover"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { type: object }
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          $ref: "#/components/responses/Error403"

  /predictions/absenteeism:
    get:
      tags: ["AI Analytics"]
      summary: "Predire les risques d'absenteisme"
      parameters:
        - name: horizon
          in: query
          description: "Horizon de prediction en mois"
          schema: { type: integer, default: 3 }
      responses:
        "200":
          description: "Predictions d'absenteisme"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { type: object }
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          $ref: "#/components/responses/Error403"

  /predictions/notifications:
    get:
      tags: ["AI Analytics"]
      summary: "Notifications RH proactives generees par l'IA"
      responses:
        "200":
          description: "Liste de notifications proactives"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items: { type: object }
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          $ref: "#/components/responses/Error403"

  # --- SSO (SAML 2.0 / OpenID Connect) ---
  # Plan 15 item K2. providers/callbacks sont publics; status/configure/disable requierent role principal.
  /sso/providers:
    get:
      tags: ["Platform"]
      summary: "Lister les fournisseurs SSO supportes"
      security: []
      responses:
        "200":
          description: "Liste des fournisseurs (saml, oidc)"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items: { type: string }

  /sso/saml/{companyId}/callback:
    post:
      tags: ["Platform"]
      summary: "Callback SAML 2.0 (IdP -> SP)"
      description: "Endpoint public recevant l'assertion SAML de l'IdP. Stub — validation complete a implementer."
      security: []
      parameters:
        - name: companyId
          in: path
          required: true
          schema: { type: string }
      requestBody:
        required: true
        content:
          application/x-www-form-urlencoded:
            schema:
              type: object
              properties:
                SAMLResponse: { type: string }
      responses:
        "200":
          description: "Assertion recue"
        "400":
          description: "SAMLResponse manquant"
        "422":
          description: "Erreur de traitement de l'assertion"
        "501":
          description: "Validation des assertions SAML non implementee (audit #1694) — refus explicite"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /sso/oidc/{companyId}/authorize:
    get:
      tags: ["Platform"]
      summary: "Demarrer le flux OpenID Connect (SP -> IdP)"
      description: "Endpoint public : genere state+nonce et retourne l'URL d'autorisation de l'IdP a ouvrir par le navigateur (issue #2231)."
      security: []
      parameters:
        - name: companyId
          in: path
          required: true
          schema: { type: string, format: uuid }
      responses:
        "200":
          description: "URL d'autorisation"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    properties:
                      authorize_url: { type: string }
        "422":
          description: "OIDC non configure ou incomplet"
          content:
            application/json:
              schema:
                type: object
                properties:
                  error: { type: string }

  /sso/oidc/{companyId}/callback:
    get:
      tags: ["Platform"]
      summary: "Callback OpenID Connect (IdP -> SP)"
      description: "Endpoint public recevant le code/id_token OIDC : verifie le state, echange le code contre les tokens, valide l'id_token (signature JWKS + iss/aud/exp/nonce) et emet un token Sanctum pour l'employe identifie (issue #2231)."
      security: []
      parameters:
        - name: companyId
          in: path
          required: true
          schema: { type: string, format: uuid }
        - name: code
          in: query
          schema: { type: string }
        - name: state
          in: query
          schema: { type: string }
        - name: id_token
          in: query
          schema: { type: string }
      responses:
        "200":
          description: "Connexion reussie — token Sanctum + employe"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    properties:
                      token: { type: string }
                      token_type: { type: string }
                      token_expires_at: { type: string, format: date-time, nullable: true }
                      employee:
                        type: object
                        properties:
                          id: { type: integer }
                          first_name: { type: string }
                          last_name: { type: string }
                          email: { type: string }
                          role: { type: string }
                          manager_role: { type: string, nullable: true }
        "400":
          description: "Code ou id_token manquant"
        "422":
          description: "Erreur d'echange de token"
        "501":
          description: "Validation OIDC non implementee (audit #1694) — refus explicite"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /sso/status:
    get:
      tags: ["Platform"]
      summary: "Statut SSO de l'entreprise"
      description: "Reserve au manager `principal`."
      responses:
        "200":
          description: "Statut SSO"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    properties:
                      enabled: { type: boolean }
                      provider: { type: string, nullable: true }
                      validation_available:
                        type: boolean
                        description: "Validation des assertions SAML/OIDC disponible — toujours false tant que le moteur de validation n'est pas implemente (audit #1694)."
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          $ref: "#/components/responses/Error403"

  /sso/configure:
    post:
      tags: ["Platform"]
      summary: "Configurer le SSO de l'entreprise"
      description: "Reserve au manager `principal`."
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [provider]
              properties:
                provider: { type: string, enum: [saml, oidc] }
                entity_id: { type: string, format: uri, nullable: true }
                sso_url: { type: string, format: uri, nullable: true }
                slo_url: { type: string, format: uri, nullable: true }
                certificate: { type: string, nullable: true }
                client_id: { type: string, nullable: true }
                client_secret: { type: string, nullable: true }
                # OpenID Connect (issue #2231)
                issuer: { type: string, format: uri, nullable: true }
                authorize_url: { type: string, format: uri, nullable: true }
                token_url: { type: string, format: uri, nullable: true }
                jwks_uri: { type: string, format: uri, nullable: true }
                redirect_uri: { type: string, format: uri, nullable: true }
                scopes: { type: string, nullable: true }
      responses:
        "200":
          description: "SSO configure avec succes"
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          $ref: "#/components/responses/Error403"
        "422":
          $ref: "#/components/responses/Validation422"

  /sso/disable:
    delete:
      tags: ["Platform"]
      summary: "Desactiver le SSO de l'entreprise"
      description: "Reserve au manager `principal`."
      responses:
        "200":
          description: "SSO desactive"
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          $ref: "#/components/responses/Error403"

  # --- Webhooks ---
  # Manager-facing CRUD for outbound partner webhooks (App\Modules\Billing\Interfaces\Api\V1\WebhookController).
  # Auth: Sanctum Bearer, tenant-scoped by `company_id`. Only employees with the `principal` manager
  # role may list/create/view/delete a webhook or manage its dead-letters (`hasManagerRole('principal')`,
  # abort(403) otherwise). `update` (PUT) currently has no explicit role gate beyond authentication —
  # documented as-is to match `WebhookController::update()`; do not assume 403 is possible there.
  # Cross-tenant access on `show`/`update`/`destroy`/dead-letter routes returns 404 (not 403), since the
  # endpoint is fetched by numeric id without a tenant-scoped global scope on `WebhookEndpoint` — a 404
  # avoids leaking whether the id exists on another company's tenant.
  /webhooks:
    get:
      tags: ["Webhooks"]
      summary: "Lister les webhooks de l'entreprise"
      description: |
        Retourne tous les endpoints webhook du tenant courant (toutes companies confondues cote base,
        mais sans filtre explicite par `company_id` dans le controller actuel — voir
        `WebhookController::index()`). Necessite le role manager `principal`.
      security:
        - BearerAuth: []
      responses:
        "200":
          description: "Liste des webhooks"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/WebhookEndpoint"
              example:
                data:
                  - id: 12
                    url: "https://partner.example.com/hooks/leopardo"
                    events: ["employee.created", "absence.approved"]
                    active: true
                    failure_count: 0
                    last_triggered_at: "2026-07-20T09:15:00+00:00"
                    created_at: "2026-05-01T08:00:00+00:00"
                    updated_at: "2026-07-20T09:15:00+00:00"
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          description: "Manager sans role principal (abort(403) dans WebhookController::index())"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
              example:
                error: "FORBIDDEN"
                message: "Cette action n'est pas autorisee."
    post:
      tags: ["Webhooks"]
      summary: "Creer un webhook"
      description: |
        Cree un endpoint webhook pour l'entreprise de l'utilisateur authentifie. Le `secret` (HMAC de
        signature des livraisons) est genere cote serveur (`Str::random(40)`) et n'est jamais fourni en
        entree ; il n'est renvoye qu'une fois au moment de la creation (absent des reponses `GET`
        ulterieures). Voir `App\Rules\NotPrivateUrl` : les URLs privees/loopback/liens locaux sont
        rejetees (SSRF guard).
      security:
        - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/WebhookEndpointRequest"
            example:
              url: "https://partner.example.com/hooks/leopardo"
              events: ["employee.created", "employee.archived"]
      responses:
        "201":
          description: "Webhook cree"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/WebhookEndpoint"
              example:
                data:
                  id: 13
                  url: "https://partner.example.com/hooks/leopardo"
                  events: ["employee.created", "employee.archived"]
                  active: true
                  failure_count: 0
                  last_triggered_at: null
                  created_at: "2026-07-25T10:00:00+00:00"
                  updated_at: "2026-07-25T10:00:00+00:00"
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          description: "Requete rejetee par StoreWebhookEndpointRequest::authorize() (role manager principal requis)"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "422":
          description: "URL invalide/privee (App\\Rules\\NotPrivateUrl), events manquants/inconnus, ou liste vide"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ValidationErrorResponse"
              example:
                error: "VALIDATION_ERROR"
                message: "Les donnees fournies sont invalides."
                errors:
                  url: ["L'URL cible ne peut pas pointer vers une adresse privee ou locale."]
                  events: ["L'evenement selectionne est invalide."]

  /webhooks/events:
    get:
      tags: ["Webhooks"]
      summary: "Lister les types d'evenements webhook disponibles"
      description: |
        Catalogue statique des evenements pouvant etre souscrits via `events[]` sur `POST /webhooks` et
        `PUT /webhooks/{webhook}` (voir `WebhookController::AVAILABLE_EVENTS`). Aucune restriction de
        role au-dela de l'authentification.
      security:
        - BearerAuth: []
      responses:
        "200":
          description: "Liste des evenements disponibles"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      type: string
              example:
                data:
                  - "employee.created"
                  - "employee.updated"
                  - "employee.archived"
                  - "attendance.checked_in"
                  - "attendance.checked_out"
                  - "absence.requested"
                  - "absence.approved"
                  - "absence.rejected"
                  - "contract.created"
                  - "contract.activated"
                  - "contract.terminated"
                  - "payroll.validated"
                  - "loan.approved"
                  - "loan.disbursed"
                  - "expense.submitted"
                  - "expense.approved"
        "401":
          $ref: "#/components/responses/Error401"

  /webhooks/{webhook}:
    get:
      tags: ["Webhooks"]
      summary: "Voir un webhook et ses dernieres livraisons"
      description: |
        Retourne le webhook (incluant ses 20 dernieres livraisons, triees par `delivered_at` descendant).
        Necessite le role manager `principal`. Renvoie 404 (pas 403) si le webhook appartient a une autre
        entreprise, pour ne pas confirmer l'existence de l'id a un tenant non autorise.
      security:
        - BearerAuth: []
      parameters:
        - name: webhook
          in: path
          required: true
          description: "Identifiant numerique du webhook (route model binding sur `webhookEndpoint`)"
          schema: { type: integer }
      responses:
        "200":
          description: "Detail du webhook avec ses livraisons recentes"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    allOf:
                      - $ref: "#/components/schemas/WebhookEndpoint"
                      - type: object
                        properties:
                          deliveries:
                            type: array
                            items:
                              $ref: "#/components/schemas/WebhookDelivery"
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          description: "Manager sans role principal"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "404":
          description: "Webhook inexistant ou appartenant a une autre entreprise (isolation tenant)"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
              example:
                error: "RESOURCE_NOT_FOUND"
                message: "Ressource introuvable."
    put:
      tags: ["Webhooks"]
      summary: "Modifier un webhook"
      description: |
        Met a jour l'URL, les evenements souscrits et/ou l'etat actif du webhook. Passer `active: true`
        remet `failure_count` a zero (relance manuelle apres desactivation automatique par depassement de
        seuil d'echecs). Le champ `secret` n'est jamais modifiable via cet endpoint. Renvoie 404 si le
        webhook appartient a une autre entreprise.
      security:
        - BearerAuth: []
      parameters:
        - name: webhook
          in: path
          required: true
          schema: { type: integer }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/WebhookEndpointUpdateRequest"
            example:
              active: false
      responses:
        "200":
          description: "Webhook modifie"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/WebhookEndpoint"
        "401":
          $ref: "#/components/responses/Error401"
        "404":
          $ref: "#/components/responses/Error404"
        "422":
          $ref: "#/components/responses/Validation422"
    delete:
      tags: ["Webhooks"]
      summary: "Supprimer un webhook"
      description: "Suppression definitive. Necessite le role manager `principal`. Renvoie 404 si le webhook appartient a une autre entreprise."
      security:
        - BearerAuth: []
      parameters:
        - name: webhook
          in: path
          required: true
          schema: { type: integer }
      responses:
        "204":
          description: "Webhook supprime (reponse sans contenu)"
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          description: "Manager sans role principal"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "404":
          $ref: "#/components/responses/Error404"

  /webhooks/{webhook}/dead-letters:
    get:
      tags: ["Webhooks"]
      summary: "Lister les livraisons webhook dead-letter (retries epuises)"
      description: |
        Livraisons ayant epuise toutes les tentatives de retry (voir `DispatchWebhook::failed()` /
        `DispatchWebhook::$tries = 3`). Permet a un manager principal d'inspecter et de rejouer une
        livraison sans acces shell/DB. Necessite le role manager `principal`. PA2-API-006.
      security:
        - BearerAuth: []
      parameters:
        - name: webhook
          in: path
          required: true
          schema: { type: integer }
        - name: per_page
          in: query
          required: false
          schema: { type: integer, default: 20 }
      responses:
        "200":
          description: "Liste paginee des livraisons dead-letter"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/WebhookDelivery"
                  meta:
                    $ref: "#/components/schemas/PaginationMeta"
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          description: "Manager sans role principal"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "404":
          description: "Webhook inexistant ou appartenant a une autre entreprise"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /webhooks/{webhook}/dead-letters/{delivery}/replay:
    post:
      tags: ["Webhooks"]
      summary: "Rejouer une livraison webhook dead-letter"
      description: |
        Redispatch le meme evenement/payload sans re-executer l'action metier d'origine (pas de doublon
        fonctionnel cote plateforme). Reactive le webhook et remet `failure_count` a zero, pour eviter un
        redeclenchement immediat du seuil d'auto-desactivation. Necessite le role manager `principal`.
        Renvoie 404 si `delivery` n'appartient pas a `webhook` ou n'est pas dead-lettered
        (`dead_lettered_at` null).
      security:
        - BearerAuth: []
      parameters:
        - name: webhook
          in: path
          required: true
          schema: { type: integer }
        - name: delivery
          in: path
          required: true
          schema: { type: integer }
      responses:
        "202":
          description: "Livraison re-mise en file"
          content:
            application/json:
              schema:
                type: object
                properties:
                  message: { type: string }
              example:
                message: "Webhook delivery re-queued."
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          description: "Manager sans role principal"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "404":
          description: "Webhook/livraison introuvable, appartenant a une autre entreprise, ou livraison non dead-letter"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /webhooks/{webhook}/test:
    post:
      tags: ["Webhooks"]
      summary: "Tester un endpoint webhook"
      description: "Dispatch d'un evenement de test `webhook.test` vers l'endpoint. Necessite le role manager `principal`. Ajoute en vague QA 2026-08-14."
      security:
        - BearerAuth: []
      parameters:
        - name: webhook
          in: path
          required: true
          schema: { type: integer }
      responses:
        "202":
          description: "Evenement de test dispatché"
          content:
            application/json:
              schema:
                type: object
                properties:
                  message: { type: string }
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          description: "Manager sans role principal"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "404":
          description: "Webhook inexistant ou appartenant a une autre entreprise"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  # --- Features / i18n ---
  /features/manifest:
    get:
      tags: ["Admin"]
      summary: "Obtenir le manifeste des fonctionnalites disponibles pour l'utilisateur courant"
      operationId: getFeatureManifest
      parameters:
        - name: mobile_version
          in: query
          required: false
          schema: { type: string, default: "1.0.0" }
      responses:
        "200":
          description: "Manifeste filtre selon les permissions de l'utilisateur"
          content:
            application/json:
              schema:
                type: object
                properties:
                  success: { type: boolean }
                  data:
                    type: object
                    properties:
                      features: { type: array, items: { type: object } }
                      total_features: { type: integer }
                      user_id: { type: integer }
                      user_role: { type: string }
                  meta:
                    type: object
                    properties:
                      generated_for_user: { type: integer }
                      mobile_version: { type: string }
                      api_version: { type: string }
                      cache_ttl: { type: integer }
        "401":
          $ref: "#/components/responses/Error401"
        "500":
          description: "Echec de generation du manifeste"

  /features/compatible/{version}:
    get:
      tags: ["Admin"]
      summary: "Lister les fonctionnalites compatibles avec une version mobile donnee"
      operationId: getCompatibleFeatures
      parameters:
        - name: version
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: "Fonctionnalites compatibles, filtrees selon les permissions"
          content:
            application/json:
              schema:
                type: object
                properties:
                  success: { type: boolean }
                  data:
                    type: object
                    properties:
                      mobile_version: { type: string }
                      total_features: { type: integer }
                      features: { type: array, items: { type: object } }
        "401":
          $ref: "#/components/responses/Error401"
        "500":
          description: "Echec de recuperation des fonctionnalites"

  /features/admin/statistics:
    get:
      tags: ["Admin"]
      summary: "Statistiques du registre de fonctionnalites (admin)"
      operationId: getFeatureRegistryStatistics
      responses:
        "200":
          description: "Statistiques du registre"
          content:
            application/json:
              schema:
                type: object
                properties:
                  success: { type: boolean }
                  data: { type: object }
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          description: "Acces admin requis"

  /features/admin/synchronize:
    post:
      tags: ["Admin"]
      summary: "Synchroniser manuellement le registre de fonctionnalites (admin)"
      operationId: synchronizeFeatureRegistry
      responses:
        "200":
          description: "Synchronisation terminee"
          content:
            application/json:
              schema:
                type: object
                properties:
                  success: { type: boolean }
                  data: { type: object }
                  message: { type: string }
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          description: "Acces admin requis"

  /features/{key}:
    get:
      tags: ["Admin"]
      summary: "Voir le detail d'une fonctionnalite du registre"
      operationId: getFeatureByKey
      parameters:
        - name: key
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: "Detail de la fonctionnalite"
          content:
            application/json:
              schema:
                type: object
                properties:
                  success: { type: boolean }
                  data: { type: object }
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          description: "Permissions insuffisantes pour cette fonctionnalite"
        "404":
          description: "Fonctionnalite introuvable"

  /feature-flags/matrix:
    get:
      tags: ["Billing"]
      summary: "Voir la matrice complete feature x plan"
      operationId: getFeatureFlagMatrix
      responses:
        "200":
          description: "Matrice groupee par feature_key puis par plan"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    additionalProperties:
                      type: object
                      additionalProperties: { type: boolean }
        "401":
          $ref: "#/components/responses/Error401"
  /feature-flags/check/{featureKey}:
    get:
      tags: ["Billing"]
      summary: "Verifier si une fonctionnalite est activee pour le plan de l'entreprise courante"
      operationId: checkFeatureFlag
      parameters:
        - name: featureKey
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: "Statut de la fonctionnalite pour le plan courant"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    properties:
                      feature: { type: string }
                      plan: { type: string }
                      enabled: { type: boolean }
                      limit: { type: integer, nullable: true }
        "401":
          $ref: "#/components/responses/Error401"

  /i18n/catalog:
    get:
      tags: ["Admin"]
      summary: "Catalogue de traductions complet"
      responses:
        "200":
          description: "Catalogue de traductions (toutes locales)"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
        "401":
          $ref: "#/components/responses/Error401"

  /i18n/catalog/{locale}:
    get:
      tags: ["Admin"]
      summary: "Traductions pour une locale"
      parameters:
        - name: locale
          in: path
          required: true
          schema: { type: string, enum: [fr, en, ar] }
      responses:
        "200":
          description: "Fichier de traduction"

  # ── Growth / Partenaires ─────────────────────────────────────────────────────
  /growth/partner/apply:
    post:
      tags: ["Growth"]
      summary: "Candidature partenaire"
      security: [{ BearerAuth: [] }]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [name, email]
              properties:
                name: { type: string }
                email: { type: string, format: email }
                company: { type: string }
                referral_code: { type: string }
      responses:
        "201": { description: "Candidature soumise" }
        "422": { $ref: "#/components/responses/Error422" }
        "401": { $ref: "#/components/responses/Error401" }

  /growth/partner/stats:
    get:
      tags: ["Growth"]
      summary: "Statistiques partenaire connecté"
      security: [{ BearerAuth: [] }]
      responses:
        "200": { description: "Statistiques commissions et parrainages" }
        "401": { $ref: "#/components/responses/Error401" }

  /growth/partner/companies:
    get:
      tags: ["Growth"]
      summary: "Entreprises parrainées par le partenaire"
      security: [{ BearerAuth: [] }]
      responses:
        "200": { description: "Liste des entreprises parrainées" }

  /growth/partner/payout:
    post:
      tags: ["Growth"]
      summary: "Demande de versement commission"
      security: [{ BearerAuth: [] }]
      responses:
        "201": { description: "Demande créée" }
        "422": { $ref: "#/components/responses/Error422" }


  /platform/growth/partners:
    get:
      tags: ["Growth", "Platform Admin"]
      summary: "Lister les partenaires (admin)"
      security: [{ BearerAuth: [] }]
      responses:
        "200": { description: "Liste des partenaires" }
        "403": { $ref: "#/components/responses/Error403" }

  /platform/growth/partners/{partner}/rate:
    patch:
      tags: ["Growth", "Platform Admin"]
      summary: "Mettre à jour le taux de commission d'un partenaire"
      security: [{ BearerAuth: [] }]
      parameters:
        - name: partner
          in: path
          required: true
          schema: { type: integer }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [rate]
              properties:
                rate: { type: number, minimum: 0, maximum: 1 }
      responses:
        "200": { description: "Taux mis à jour" }
        "404": { $ref: "#/components/responses/Error404" }

  /platform/growth/payouts:
    get:
      tags: ["Growth", "Platform Admin"]
      summary: "Lister les demandes de versement"
      security: [{ BearerAuth: [] }]
      responses:
        "200": { description: "Liste des paiements partenaires" }

  # ── Marketing (Ayrshare / reseaux sociaux) ─────────────────────────────────────
  /ai/actions/{pendingActionId}/confirm:
    post:
      tags: ["AI"]
      summary: "Confirmer une action IA en attente"
      parameters:
        - name: pendingActionId
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: "Action exécutée"
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          description: "Feature IA non active (AIFeatureCheck)"

  /ai/actions/{pendingActionId}/reject:
    post:
      tags: ["AI"]
      summary: "Rejeter une action IA en attente"
      parameters:
        - name: pendingActionId
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: "Action rejetée"
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          description: "Feature IA non active"

  /ai/voice/transcribe:
    post:
      tags: ["AI"]
      summary: "Transcrire un audio (commande vocale)"
      responses:
        "200":
          description: "Transcription"
        "401":
          $ref: "#/components/responses/Error401"

  /ai/voice/synthesize:
    post:
      tags: ["AI"]
      summary: "Synthétiser un texte en audio"
      responses:
        "200":
          description: "Audio généré"
        "401":
          $ref: "#/components/responses/Error401"

  /ai/voice/command:
    post:
      tags: ["AI"]
      summary: "Exécuter une commande vocale"
      responses:
        "200":
          description: "Résultat de la commande"
        "401":
          $ref: "#/components/responses/Error401"

  /ai/agent/run:
    post:
      tags: ["AI"]
      summary: "Lancer l'agent IA sur une requête"
      responses:
        "200":
          description: "Réponse de l'agent"
        "401":
          $ref: "#/components/responses/Error401"

  /ai/agent/workflows:
    get:
      tags: ["AI"]
      summary: "Lister les workflows d'agent disponibles"
      responses:
        "200":
          description: "Workflows"
        "401":
          $ref: "#/components/responses/Error401"

  /ai/workflows/prepare-payroll:
    post:
      tags: ["AI"]
      summary: "Workflow IA de préparation de paie"
      responses:
        "200":
          description: "Préparation effectuée"
        "401":
          $ref: "#/components/responses/Error401"

  /ai/workflows/weekly-report:
    get:
      tags: ["AI"]
      summary: "Rapport hebdomadaire généré par l'IA"
      responses:
        "200":
          description: "Rapport"
        "401":
          $ref: "#/components/responses/Error401"

  /marketing/social-accounts:
    get:
      tags: ["Marketing"]
      summary: "Comptes sociaux connectés (alias pluriel de /social-account)"
      responses:
        "200":
          description: "Comptes sociaux"
        "401":
          $ref: "#/components/responses/Error401"

  /marketing/social-accounts/connect:
    post:
      tags: ["Marketing"]
      summary: "Connecter un compte social (alias pluriel)"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
      responses:
        "200":
          description: "Compte connecté"
        "401":
          $ref: "#/components/responses/Error401"

  /marketing/social-accounts/disconnect:
    post:
      tags: ["Marketing"]
      summary: "Déconnecter un compte social (alias pluriel)"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
      responses:
        "200":
          description: "Compte déconnecté"
        "401":
          $ref: "#/components/responses/Error401"

  /marketing/posts:
    get:
      tags: ["Marketing"]
      summary: "Lister les publications (alias court de /social-posts)"
      responses:
        "200":
          description: "Publications"
        "401":
          $ref: "#/components/responses/Error401"
    post:
      tags: ["Marketing"]
      summary: "Créer une publication (alias court de /social-posts)"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
      responses:
        "201":
          description: "Publication créée"
        "401":
          $ref: "#/components/responses/Error401"

  /marketing/posts/{socialPost}:
    get:
      tags: ["Marketing"]
      summary: "Détail d'une publication (alias court)"
      parameters:
        - name: socialPost
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: "Publication"
        "401":
          $ref: "#/components/responses/Error401"
    patch:
      tags: ["Marketing"]
      summary: "Mettre à jour une publication (alias court)"
      parameters:
        - name: socialPost
          in: path
          required: true
          schema: { type: integer }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
      responses:
        "200":
          description: "Publication mise à jour"
        "401":
          $ref: "#/components/responses/Error401"
    delete:
      tags: ["Marketing"]
      summary: "Supprimer une publication (alias court)"
      parameters:
        - name: socialPost
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: "Publication supprimée"
        "401":
          $ref: "#/components/responses/Error401"

  /marketing/posts/{socialPost}/publish:
    post:
      tags: ["Marketing"]
      summary: "Publier une publication planifiée (alias court)"
      parameters:
        - name: socialPost
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: "Publication envoyée"
        "401":
          $ref: "#/components/responses/Error401"

  /calendar/connections:
    get:
      tags: ["Calendar"]
      summary: "Lister les connexions calendrier de l'employe"
      responses:
        "200":
          description: "Connexions calendrier"
        "401":
          $ref: "#/components/responses/Error401"

  /calendar/connect:
    post:
      tags: ["Calendar"]
      summary: "Connecter un calendrier (google, outlook, caldav)"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [provider, access_token]
              properties:
                provider:
                  type: string
                  enum: [google, outlook, caldav]
                access_token:
                  type: string
                refresh_token:
                  type: string
                calendar_id:
                  type: string
                  maxLength: 255
                expires_in:
                  type: integer
                sync_leaves:
                  type: boolean
                sync_training:
                  type: boolean
      responses:
        "201":
          description: "Connexion creee"
        "401":
          $ref: "#/components/responses/Error401"

  /calendar/disconnect/{provider}:
    delete:
      tags: ["Calendar"]
      summary: "Deconnecter un calendrier"
      parameters:
        - name: provider
          in: path
          required: true
          schema:
            type: string
            enum: [google, outlook, caldav]
      responses:
        "200":
          description: "Calendrier deconnecte"
        "401":
          $ref: "#/components/responses/Error401"

  /calendar/sync:
    post:
      tags: ["Calendar"]
      summary: "Synchroniser conges et formations vers le calendrier"
      responses:
        "200":
          description: "Synchronisation effectuee"
        "401":
          $ref: "#/components/responses/Error401"

  /calendar/events:
    get:
      tags: ["Calendar"]
      summary: "Lister les evenements calendrier sur une periode"
      parameters:
        - name: from
          in: query
          required: true
          schema: { type: string, format: date }
        - name: to
          in: query
          required: true
          schema: { type: string, format: date }
      responses:
        "200":
          description: "Evenements"
        "401":
          $ref: "#/components/responses/Error401"

  /cabinet/shared/{token}:
    get:
      tags: ["Cabinet"]
      summary: "Acceder a un partage par jeton (lien public)"
      parameters:
        - name: token
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: "Contenu partage"
        "404":
          description: "Partage introuvable ou expire"

  /cabinet/shares:
    get:
      tags: ["Cabinet"]
      summary: "Lister les partages du cabinet"
      parameters:
        - name: per_page
          in: query
          schema: { type: integer, minimum: 1, maximum: 100 }
      responses:
        "200":
          description: "Partages (data + meta)"
        "401":
          $ref: "#/components/responses/Error401"
    post:
      tags: ["Cabinet"]
      summary: "Creer un partage de dossier ou document"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [shareable_type, shareable_id, shared_via]
              properties:
                shareable_type:
                  type: string
                  enum: [folder, document]
                shareable_id:
                  type: integer
                shared_via:
                  type: string
                  enum: [email, link]
                shared_with_email:
                  type: string
                  format: email
                  maxLength: 255
                expires_at:
                  type: string
                  format: date-time
      responses:
        "201":
          description: "Partage cree"
        "401":
          $ref: "#/components/responses/Error401"

  /cabinet/shares/{cabinetShare}:
    delete:
      tags: ["Cabinet"]
      summary: "Revoquer un partage"
      parameters:
        - name: cabinetShare
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: "Partage revoque"
        "401":
          $ref: "#/components/responses/Error401"

  /absences/{absence}/proof:
    get:
      tags: ["Absences"]
      summary: "Telecharger la piece jointe d'une demande d'absence"
      parameters:
        - name: absence
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: "Fichier"
          content:
            application/octet-stream:
              schema:
                type: string
                format: binary
        "401":
          $ref: "#/components/responses/Error401"
  /marketing/leads:
    post:
      tags: ["Marketing"]
      summary: "Persister un lead marketing (vitrine)"
      description: "Appele par captureMarketingLead() (vitrine) avec le secret partage MARKETING_LEAD_WEBHOOK_TOKEN. Fire-and-forget cote vitrine."
      security:
        - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [email]
              properties:
                email: { type: string, format: email }
                source: { type: string }
                company_name: { type: string }
                role: { type: string }
                company_size: { type: string }
                phone: { type: string, nullable: true }
      responses:
        "201":
          description: "Lead persiste"
        "422":
          $ref: "#/components/responses/Validation422"

  /marketing/social-account:
    get:
      tags: ["Marketing"]
      summary: "Compte social connecte du tenant"
      description: "Reserve aux managers principal/marketing (middleware api.manager:marketing,principal)."
      security: [{ BearerAuth: [] }]
      responses:
        "200": { description: "Compte social connecte" }
        "404": { description: "Aucun compte social connecte pour l'entreprise" }
        "401": { $ref: "#/components/responses/Error401" }
        "403": { $ref: "#/components/responses/Error403" }

  /marketing/social-account/connect:
    post:
      tags: ["Marketing"]
      summary: "Connecter un compte social (agregateur Ayrshare)"
      security: [{ BearerAuth: [] }]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [display_name]
              properties:
                display_name: { type: string, maxLength: 120 }
                provider: { type: string, enum: [ayrshare], default: ayrshare }
      responses:
        "201": { description: "Compte social connecte (cree ou reactive)" }
        "422": { $ref: "#/components/responses/Validation422" }
        "401": { $ref: "#/components/responses/Error401" }
        "403": { $ref: "#/components/responses/Error403" }

  /marketing/social-account/disconnect:
    post:
      tags: ["Marketing"]
      summary: "Deconnecter le compte social du tenant"
      security: [{ BearerAuth: [] }]
      responses:
        "200": { description: "Compte social marque comme revoque" }
        "404": { description: "Aucun compte social connecte pour l'entreprise" }
        "401": { $ref: "#/components/responses/Error401" }
        "403": { $ref: "#/components/responses/Error403" }

  /marketing/social-posts:
    get:
      tags: ["Marketing"]
      summary: "Lister les publications reseaux sociaux du tenant"
      security: [{ BearerAuth: [] }]
      parameters:
        - name: per_page
          in: query
          schema: { type: integer, minimum: 1, maximum: 100, default: 15 }
      responses:
        "200": { description: "Liste paginee des publications" }
        "401": { $ref: "#/components/responses/Error401" }
        "403": { $ref: "#/components/responses/Error403" }
    post:
      tags: ["Marketing"]
      summary: "Creer une publication (brouillon, ou planifiee/publiee si scheduled_at fourni)"
      security: [{ BearerAuth: [] }]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [content, target_platforms]
              properties:
                content: { type: string, maxLength: 5000 }
                target_platforms:
                  type: array
                  minItems: 1
                  items:
                    type: string
                    enum: [linkedin, facebook_page, facebook_group, twitter, instagram, youtube, tiktok, pinterest, reddit, telegram, gmb, bluesky, threads]
                media_paths:
                  type: array
                  items: { type: string, maxLength: 2000 }
                scheduled_at:
                  type: string
                  format: date-time
                  description: "Optionnel. Absent = brouillon (statut draft). Date future = planifie (statut scheduled), publie automatiquement par le job PublishScheduledSocialPost quand echu."
      responses:
        "201": { description: "Publication cree (draft ou scheduled)" }
        "422": { $ref: "#/components/responses/Validation422" }
        "401": { $ref: "#/components/responses/Error401" }
        "403": { $ref: "#/components/responses/Error403" }

  /marketing/social-posts/{socialPost}:
    get:
      tags: ["Marketing"]
      summary: "Detail d'une publication"
      security: [{ BearerAuth: [] }]
      parameters:
        - name: socialPost
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200": { description: "Detail de la publication" }
        "404": { $ref: "#/components/responses/Error404" }
        "401": { $ref: "#/components/responses/Error401" }
        "403": { $ref: "#/components/responses/Error403" }
    patch:
      tags: ["Marketing"]
      summary: "Modifier une publication (draft uniquement)"
      security: [{ BearerAuth: [] }]
      parameters:
        - name: socialPost
          in: path
          required: true
          schema: { type: integer }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                content: { type: string, maxLength: 5000 }
                target_platforms:
                  type: array
                  items:
                    type: string
                    enum: [linkedin, facebook_page, facebook_group, twitter, instagram, youtube, tiktok, pinterest, reddit, telegram, gmb, bluesky, threads]
                media_paths:
                  type: array
                  items: { type: string, maxLength: 2000 }
      responses:
        "200": { description: "Publication mise a jour" }
        "422": { $ref: "#/components/responses/Validation422" }
        "403": { description: "Publication deja publiee, non modifiable (SocialPostPolicy::update)" }
        "404": { $ref: "#/components/responses/Error404" }
        "401": { $ref: "#/components/responses/Error401" }
    delete:
      tags: ["Marketing"]
      summary: "Supprimer une publication (draft uniquement)"
      security: [{ BearerAuth: [] }]
      parameters:
        - name: socialPost
          in: path
          required: true
          schema: { type: integer }
      responses:
        "204": { description: "Publication supprimee" }
        "403": { description: "Publication deja publiee, non supprimable" }
        "404": { $ref: "#/components/responses/Error404" }
        "401": { $ref: "#/components/responses/Error401" }

  /marketing/social-posts/{socialPost}/publish:
    post:
      tags: ["Marketing"]
      summary: "Publier immediatement ou (re)planifier une publication"
      description: "scheduled_at absent = publication immediate via l'agregateur Ayrshare (SocialPublishingService::publishNow) ; scheduled_at futur = planification (statut scheduled), publie automatiquement par le job PublishScheduledSocialPost quand echu."
      security: [{ BearerAuth: [] }]
      parameters:
        - name: socialPost
          in: path
          required: true
          schema: { type: integer }
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                scheduled_at: { type: string, format: date-time }
      responses:
        "200": { description: "Publication publiee ou (re)planifiee" }
        "422": { $ref: "#/components/responses/Validation422" }
        "404": { $ref: "#/components/responses/Error404" }
        "401": { $ref: "#/components/responses/Error401" }
        "403": { $ref: "#/components/responses/Error403" }

  # ── Onboarding Setup ─────────────────────────────────────────────────────────
  /onboarding-setup/checklist:
    get:
      tags: ["Onboarding"]
      summary: "Récupérer la checklist d'onboarding"
      security: [{ BearerAuth: [] }]
      responses:
        "200":
          description: "Checklist avec statut de chaque étape"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      type: object
                      properties:
                        key: { type: string }
                        label: { type: string }
                        completed: { type: boolean }
                        skipped: { type: boolean }
        "401": { $ref: "#/components/responses/Error401" }

  /onboarding-setup/progress:
    get:
      tags: ["Onboarding"]
      summary: "Progression globale de l'onboarding"
      security: [{ BearerAuth: [] }]
      responses:
        "200":
          description: "Pourcentage de complétion"
          content:
            application/json:
              schema:
                type: object
                properties:
                  progress_percent: { type: integer }
                  completed_steps: { type: integer }
                  total_steps: { type: integer }

  /onboarding-setup/{stepKey}/complete:
    post:
      tags: ["Onboarding"]
      summary: "Marquer une étape comme complète"
      security: [{ BearerAuth: [] }]
      parameters:
        - name: stepKey
          in: path
          required: true
          schema: { type: string }
      responses:
        "200": { description: "Étape marquée complète" }
        "404": { description: "Étape inconnue" }
    patch:
      tags: ["Deprecated"]
      summary: "Marquer une étape complète (déprécié — utiliser POST)"
      deprecated: true
      parameters:
        - name: stepKey
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: "Action effectuée (ancien verbe, déprécié #4930)"
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          description: "Non autorisé"
        "404":
          description: "Introuvable"

  /onboarding-setup/{stepKey}/skip:
    post:
      tags: ["Onboarding"]
      summary: "Ignorer une étape d'onboarding"
      security: [{ BearerAuth: [] }]
      parameters:
        - name: stepKey
          in: path
          required: true
          schema: { type: string }
      responses:
        "200": { description: "Étape ignorée" }
        "404": { description: "Étape inconnue" }

  # ─────────────────────────────────────────────────────────────────────────
  # SmartAttendance — Pointage GPS automatique par géofencing
  # Ajouté dans v4.18.0
  # ─────────────────────────────────────────────────────────────────────────
    patch:
      tags: ["Deprecated"]
      summary: "Passer une étape (déprécié — utiliser POST)"
      deprecated: true
      parameters:
        - name: stepKey
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: "Action effectuée (ancien verbe, déprécié #4930)"
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          description: "Non autorisé"
        "404":
          description: "Introuvable"

  /smart-attendance/mode-settings:
    get:
      tags: ["SmartAttendance"]
      summary: "Parametres du mode de pointage de l'entreprise"
      responses:
        "200":
          description: "Configuration (forced_mode, punch_photo_mode, gps_enabled, latitude, longitude, radius_meters, allow_employee_override)"
        "401":
          $ref: "#/components/responses/Error401"
    put:
      tags: ["SmartAttendance"]
      summary: "Configurer le mode de pointage (principal)"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                forced_mode:
                  type: string
                  enum: [gps_auto, qr, manual, mixed]
                punch_photo_mode:
                  type: string
                  enum: [kiosk, photo_required]
                gps_enabled:
                  type: boolean
                latitude:
                  type: number
                  minimum: -90
                  maximum: 90
                longitude:
                  type: number
                  minimum: -180
                  maximum: 180
                radius_meters:
                  type: integer
                  minimum: 10
                  maximum: 5000
                allow_employee_override:
                  type: boolean
      responses:
        "200":
          description: "Configuration mise a jour"
        "401":
          $ref: "#/components/responses/Error401"

  /payrolls/{payroll}:
    get:
      tags: ["Payroll Engine"]
      summary: "Détail d'un run de paie"
      parameters:
        - name: payroll
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: "Run de paie"
        "401":
          $ref: "#/components/responses/Error401"
        "404":
          description: "Hors société"
    put:
      tags: ["Payroll Engine"]
      summary: "Mettre à jour un run de paie (manager)"
      parameters:
        - name: payroll
          in: path
          required: true
          schema: { type: integer }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
      responses:
        "200":
          description: "Run mis à jour"
        "401":
          $ref: "#/components/responses/Error401"
    patch:
      tags: ["Payroll Engine"]
      summary: "Mettre à jour partiellement un run de paie (manager)"
      parameters:
        - name: payroll
          in: path
          required: true
          schema: { type: integer }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
      responses:
        "200":
          description: "Run mis à jour"
        "401":
          $ref: "#/components/responses/Error401"
    delete:
      tags: ["Payroll Engine"]
      summary: "Supprimer un run de paie (manager)"
      parameters:
        - name: payroll
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: "Run supprimé"
        "401":
          $ref: "#/components/responses/Error401"

  /payrolls/{payroll}/validate:
    post:
      tags: ["Payroll Engine"]
      summary: "Valider un run de paie (manager, même société)"
      parameters:
        - name: payroll
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: "Run validé"
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          description: "Non-manager"
        "404":
          description: "Hors société"
    put:
      tags: ["Deprecated"]
      summary: "Valider un run de paie (déprécié — utiliser POST)"
      deprecated: true
      parameters:
        - name: payroll
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: "Action effectuée (ancien verbe, déprécié #4930)"
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          description: "Non autorisé"
        "404":
          description: "Introuvable"

  /positions/{position}:
    patch:
      tags: ["Payroll Engine"]
      summary: "Mettre à jour un poste (manager)"
      parameters:
        - name: position
          in: path
          required: true
          schema: { type: integer }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
      responses:
        "200":
          description: "Poste mis à jour"
        "401":
          $ref: "#/components/responses/Error401"

  /sites/{site}:
    patch:
      tags: ["Payroll Engine"]
      summary: "Mettre à jour un site (manager)"
      parameters:
        - name: site
          in: path
          required: true
          schema: { type: integer }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
      responses:
        "200":
          description: "Site mis à jour"
        "401":
          $ref: "#/components/responses/Error401"

  /schedules/{schedule}:
    get:
      tags: ["Payroll Engine"]
      summary: "Détail d'un planning"
      parameters:
        - name: schedule
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: "Planning"
        "401":
          $ref: "#/components/responses/Error401"
    patch:
      tags: ["Payroll Engine"]
      summary: "Mettre à jour un planning (manager — sécurité #2217)"
      parameters:
        - name: schedule
          in: path
          required: true
          schema: { type: integer }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
      responses:
        "200":
          description: "Planning mis à jour"
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          description: "Non-manager"

  /notifications/stream:
    get:
      tags: ["Notifications"]
      summary: "Flux SSE temps réel des notifications (employee/manager)"
      responses:
        "200":
          description: "Flux Server-Sent Events"
          content:
            text/event-stream:
              schema:
                type: string
        "401":
          $ref: "#/components/responses/Error401"

  /notifications/sse-token:
    post:
      tags: ["Notifications"]
      summary: "Émettre un token SSE temporaire pour le flux temps réel"
      responses:
        "200":
          description: "Token SSE"
        "401":
          $ref: "#/components/responses/Error401"

  /tasks/{task}/comments:
    get:
      tags: ["Tasks"]
      summary: "Commentaires d'une tâche (auteur + employés liés)"
      parameters:
        - name: task
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: "Commentaires"
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          description: "Non autorisé (hors périmètre)"

  /me/contract:
    get:
      tags: ["Me"]
      summary: "Contrat actif de l'employé connecté"
      responses:
        "200":
          description: "Contrat actif"
        "401":
          $ref: "#/components/responses/Error401"
        "404":
          description: "Aucun contrat actif"

  /recruitment/jobs/{id}:
    delete:
      tags: ["Recruitment"]
      summary: "Supprimer une offre d'emploi (manager)"
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: "Offre supprimée"
        "401":
          $ref: "#/components/responses/Error401"

  /recruitment/applicants/{id}:
    get:
      tags: ["Recruitment"]
      summary: "Détail d'une candidature (manager)"
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: "Candidature"
        "401":
          $ref: "#/components/responses/Error401"
    delete:
      tags: ["Recruitment"]
      summary: "Supprimer une candidature (manager)"
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: "Candidature supprimée"
        "401":
          $ref: "#/components/responses/Error401"

  /recruitment/applicants/{id}/status:
    patch:
      tags: ["Recruitment"]
      summary: "Changer le statut d'une candidature (principal/rh)"
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: integer }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [status]
              properties:
                status:
                  type: string
                  enum: [new, screening, interview, offer, hired, rejected, withdrawn]
      responses:
        "200":
          description: "Statut mis à jour"
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          description: "Rôle requis (principal/rh)"

  /recruitment/interviews/{id}/feedback:
    patch:
      tags: ["Recruitment"]
      summary: "Saisir le feedback d'un entretien (le clôture)"
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: integer }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [feedback]
              properties:
                feedback:
                  type: string
                  maxLength: 5000
                rating:
                  type: integer
                  minimum: 1
                  maximum: 5
      responses:
        "200":
          description: "Entretien clôturé (status=completed)"
        "401":
          $ref: "#/components/responses/Error401"

  /recruitment/interviews/{id}:
    delete:
      tags: ["Recruitment"]
      summary: "Supprimer un entretien (manager)"
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: "Entretien supprimé"
        "401":
          $ref: "#/components/responses/Error401"

  /smart-attendance/config:
    get:
      tags: ["SmartAttendance"]
      summary: "Lire la configuration de mode active pour l'employe connecte"
      description: "Resout le mode de pointage effectif (GPS/QR/manuel) et indique si une photo de pointage est obligatoire (punch_photo_mode=photo_required)."
      security:
        - BearerAuth: []
      responses:
        "200":
          description: "Configuration de mode active"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    properties:
                      mode: { type: string, enum: [manual, gps_auto, qr, mixed] }
                      can_override: { type: boolean }
                      gps_enabled: { type: boolean }
                      geofence:
                        type: object
                        nullable: true
                        properties:
                          latitude: { type: number, nullable: true }
                          longitude: { type: number, nullable: true }
                          radius_meters: { type: integer, nullable: true }
                      requires_consent: { type: boolean }
                      requires_punch_photo:
                        type: boolean
                        description: "true si l'entreprise impose une photo a chaque pointage mobile (arrivee/depart)."
        "401":
          $ref: "#/components/responses/Error401"

  /smart-attendance/preferences:
    put:
      tags: ["SmartAttendance"]
      summary: "Mettre à jour les préférences de pointage"
      security:
        - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [preferred_mode]
              properties:
                preferred_mode:
                  type: string
                  enum: [gps_auto, qr, manual]
                gps_consent_given:
                  type: boolean
                revoke_consent:
                  type: boolean
      responses:
        "200":
          description: "Préférences mises à jour"
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
                  data:
                    $ref: "#/components/schemas/EmployeeAttendancePreference"
        "403":
          $ref: "#/components/responses/Error403"
        "422":
          $ref: "#/components/responses/Validation422"

  /smart-attendance/geo-events:
    post:
      tags: ["SmartAttendance"]
      summary: "Envoyer un événement géographique (entrée/sortie de zone)"
      security:
        - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/GeoEventRequest"
      responses:
        "200":
          description: "Événement enregistré"
        "422":
          $ref: "#/components/responses/Validation422"

  /smart-attendance/sessions:
    get:
      tags: ["SmartAttendance"]
      summary: "Lister les sessions GPS"
      security:
        - BearerAuth: []
      parameters:
        - name: status
          in: query
          schema:
            type: string
            enum: [detected, pending_validation, approved, rejected, cancelled]
        - name: per_page
          in: query
          schema:
            type: integer
            default: 20
      responses:
        "200":
          description: "Liste paginée des sessions GPS"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/GeoAttendanceSession"

  /smart-attendance/sessions/{id}:
    get:
      tags: ["SmartAttendance"]
      summary: "Détail d'une session GPS"
      security:
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
      responses:
        "200":
          description: "Session GPS"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/GeoAttendanceSession"
        "404":
          $ref: "#/components/responses/Error404"

  /smart-attendance/sessions/{id}/approve:
    post:
      tags: ["SmartAttendance"]
      summary: "Approuver une session GPS (manager/RH)"
      security:
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
      responses:
        "200":
          description: "Session mise à jour"
        "401":
          $ref: "#/components/responses/Error401"
        "404":
          $ref: "#/components/responses/Error404"

  /smart-attendance/sessions/{id}/reject:
    post:
      tags: ["SmartAttendance"]
      summary: "Rejeter une session GPS (manager/RH)"
      security:
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
      responses:
        "200":
          description: "Session mise à jour"
        "401":
          $ref: "#/components/responses/Error401"
        "404":
          $ref: "#/components/responses/Error404"

  /smart-attendance/dashboard:
    get:
      tags: ["SmartAttendance"]
      summary: "Statistiques du jour — Smart Attendance (manager/RH)"
      security:
        - BearerAuth: []
      responses:
        "200":
          description: "Statistiques du jour"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SmartAttendanceDashboard"

  # --- ZKTeco (pointeuses biometriques) ---
  /zkteco/devices:
    get:
      tags: ["ZKTeco"]
      summary: "Lister les pointeuses ZKTeco de l'entreprise (manager)"
      operationId: listZktecoDevices
      responses:
        "200":
          description: "Liste des appareils"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/ZktecoDevice"
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          $ref: "#/components/responses/Error403"
    post:
      tags: ["ZKTeco"]
      summary: "Enregistrer une nouvelle pointeuse ZKTeco (manager)"
      operationId: registerZktecoDevice
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [serial_number, name]
              properties:
                serial_number: { type: string, maxLength: 100 }
                name: { type: string, maxLength: 120 }
                ip_address: { type: string, format: ipv4, nullable: true }
                port: { type: integer, minimum: 1, maximum: 65535, nullable: true }
                protocol: { type: string, enum: [tcp, udp, cloud_api], nullable: true }
                location_label: { type: string, maxLength: 120, nullable: true }
                model: { type: string, maxLength: 60, nullable: true }
                firmware_version: { type: string, maxLength: 60, nullable: true }
      responses:
        "201":
          description: "Appareil enregistre"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/ZktecoDevice"
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          $ref: "#/components/responses/Error403"
        "422":
          $ref: "#/components/responses/Validation422"

  /zkteco/devices/{id}:
    get:
      tags: ["ZKTeco"]
      summary: "Voir une pointeuse ZKTeco avec son historique de synchronisation (manager)"
      operationId: showZktecoDevice
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: "Detail de l'appareil + 10 dernieres synchronisations"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/ZktecoDevice"
                  sync_history:
                    type: array
                    items:
                      $ref: "#/components/schemas/ZktecoSyncLog"
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          $ref: "#/components/responses/Error403"
        "404":
          $ref: "#/components/responses/Error404"
    put:
      tags: ["ZKTeco"]
      summary: "Modifier une pointeuse ZKTeco (manager)"
      operationId: updateZktecoDevice
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: integer }
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                name: { type: string, maxLength: 120 }
                ip_address: { type: string, format: ipv4, nullable: true }
                port: { type: integer, minimum: 1, maximum: 65535, nullable: true }
                protocol: { type: string, enum: [tcp, udp, cloud_api], nullable: true }
                location_label: { type: string, maxLength: 120, nullable: true }
                status: { type: string, enum: [online, offline, maintenance], nullable: true }
      responses:
        "200":
          description: "Appareil mis a jour"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/ZktecoDevice"
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          $ref: "#/components/responses/Error403"
        "404":
          $ref: "#/components/responses/Error404"
        "422":
          $ref: "#/components/responses/Validation422"
    delete:
      tags: ["ZKTeco"]
      summary: "Supprimer une pointeuse ZKTeco (manager)"
      operationId: deleteZktecoDevice
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: integer }
      responses:
        "204":
          description: "Appareil supprime"
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          $ref: "#/components/responses/Error403"
        "404":
          $ref: "#/components/responses/Error404"

  /zkteco/devices/{id}/regenerate-token:
    post:
      tags: ["ZKTeco"]
      summary: "Regenerer le jeton d'une pointeuse (manager)"
      security:
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: "Appareil avec nouveau jeton (device_token a usage unique)"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/ZktecoDevice"
                  device_token:
                    type: string
                    description: "Nouveau jeton en clair — a transmettre au terminal"
        "403":
          $ref: "#/components/responses/Error403"
        "404":
          $ref: "#/components/responses/Error404"

  /zkteco/devices/{id}/sync-logs:
    get:
      tags: ["ZKTeco"]
      summary: "Lister l'historique de synchronisation d'une pointeuse (manager)"
      operationId: listZktecoSyncLogs
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: integer }
        - name: limit
          in: query
          schema: { type: integer, default: 20 }
      responses:
        "200":
          description: "Historique de synchronisation"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/ZktecoSyncLog"
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          $ref: "#/components/responses/Error403"
        "404":
          $ref: "#/components/responses/Error404"

  /zkteco/heartbeat/{serialNumber}:
    post:
      tags: ["ZKTeco"]
      summary: "Signal de vie envoye par la pointeuse elle-meme (pas d'authentification employe)"
      description: "Appele directement par le firmware de l'appareil, pas par l'app mobile/web. Marque l'appareil online et met a jour last_seen_at."
      operationId: zktecoHeartbeat
      parameters:
        - name: serialNumber
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: "Heartbeat enregistre"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    properties:
                      device_id: { type: integer }
                      status: { type: string, enum: [online] }
                      server_time: { type: string, format: date-time }
        "404":
          description: "Aucune pointeuse enregistree avec ce numero de serie"

  /zkteco/sync-attendance/{serialNumber}:
    post:
      tags: ["ZKTeco"]
      summary: "Pousser des pointages bruts depuis l'appareil vers le serveur (appele par l'appareil)"
      operationId: zktecoSyncAttendance
      parameters:
        - name: serialNumber
          in: path
          required: true
          schema: { type: string }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [records]
              properties:
                records:
                  type: array
                  items:
                    type: object
                    required: [user_id, timestamp]
                    properties:
                      user_id: { type: string }
                      timestamp: { type: string, format: date-time }
                      punch_type: { type: integer, minimum: 0, maximum: 5, nullable: true }
                      badge_number: { type: string, nullable: true }
      responses:
        "201":
          description: "Lot de pointages traite"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    properties:
                      sync_id: { type: integer }
                      records_processed: { type: integer }
                      errors: { type: integer }
                      status: { type: string }
        "404":
          description: "Aucune pointeuse enregistree avec ce numero de serie"
        "422":
          $ref: "#/components/responses/Validation422"

  /zkteco/devices/{serialNumber}/push-users:
    post:
      tags: ["ZKTeco"]
      summary: "Pousser la liste des employes vers la pointeuse (manager)"
      operationId: pushZktecoUsers
      parameters:
        - name: serialNumber
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: "Synchronisation des utilisateurs lancee"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    properties:
                      sync_id: { type: integer }
                      users_count: { type: integer }
                      status: { type: string }
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          $ref: "#/components/responses/Error403"
        "404":
          description: "Aucune pointeuse enregistree avec ce numero de serie"

  /smart-attendance/my-sessions:
    get:
      tags: ["SmartAttendance"]
      summary: "Sessions GPS de l'employé courant"
      security:
        - BearerAuth: []
      responses:
        "200":
          description: "Sessions de l'employé"
        "401":
          $ref: "#/components/responses/Error401"


  /smart-attendance/employees/{employeeId}/preference:
    get:
      tags: ["SmartAttendance"]
      summary: "Préférence mode géolocalisation d'un employé (manager/RH)"
      security:
        - BearerAuth: []
      parameters:
        - name: employeeId
          in: path
          required: true
          schema:
            type: integer
      responses:
        "200":
          description: "Préférence de l'employé"
        "401":
          $ref: "#/components/responses/Error401"

  /growth/partner/dashboard:
    get:
      tags: ["Growth"]
      summary: "Tableau de bord de l'espace partenaire"
      security:
        - BearerAuth: []
      responses:
        "200":
          description: "Données du tableau de bord partenaire"
        "401":
          $ref: "#/components/responses/Error401"

  /platform/growth/payouts/{payout}:
    patch:
      tags: ["Growth"]
      summary: "Mettre à jour le statut d'un payout partenaire (super-admin)"
      security:
        - BearerAuth: []
      parameters:
        - name: payout
          in: path
          required: true
          schema:
            type: integer
      responses:
        "200":
          description: "Payout mis à jour"
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          $ref: "#/components/responses/Error403"

  /platform/growth/history:
    get:
      tags: ["Growth"]
      summary: "Historique des opérations growth (super-admin)"
      security:
        - BearerAuth: []
      responses:
        "200":
          description: "Historique"
        "401":
          $ref: "#/components/responses/Error401"

  /platform/growth/partners/{partner}/application:
    patch:
      tags: ["Growth"]
      summary: "Mettre à jour le statut de candidature d'un partenaire (super-admin)"
      security:
        - BearerAuth: []
      parameters:
        - name: partner
          in: path
          required: true
          schema:
            type: integer
      responses:
        "200":
          description: "Candidature mise à jour"
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          $ref: "#/components/responses/Error403"

  /employees/{employee}/balance:
    get:
      tags: ["HR"]
      summary: "Solde de congés d'un employé (manager/RH)"
      security:
        - BearerAuth: []
      parameters:
        - name: employee
          in: path
          required: true
          schema:
            type: integer
      responses:
        "200":
          description: "Soldes de l'employé"
        "401":
          $ref: "#/components/responses/Error401"
        "404":
          $ref: "#/components/responses/Error404"

  /employees/{employee}/ledger:
    get:
      tags: ["HR"]
      summary: "Journal de paie d'un employé (manager/RH)"
      security:
        - BearerAuth: []
      parameters:
        - name: employee
          in: path
          required: true
          schema:
            type: integer
      responses:
        "200":
          description: "Journal de paie"
        "401":
          $ref: "#/components/responses/Error401"
        "404":
          $ref: "#/components/responses/Error404"

  /me/ledger:
    get:
      tags: ["HR"]
      summary: "Journal de paie de l'employé courant"
      security:
        - BearerAuth: []
      responses:
        "200":
          description: "Journal de paie"
        "401":
          $ref: "#/components/responses/Error401"

  /me/attendance-anomalies:
    get:
      tags: ["HR"]
      summary: "Anomalies de pointage de l'employé courant"
      security:
        - BearerAuth: []
      responses:
        "200":
          description: "Anomalies de pointage"
        "401":
          $ref: "#/components/responses/Error401"

  /attendance/regularity:
    get:
      tags: ["Attendance"]
      summary: "Régularité de présence (manager/RH)"
      security:
        - BearerAuth: []
      responses:
        "200":
          description: "Statistiques de régularité"
        "401":
          $ref: "#/components/responses/Error401"

  # ─── Platform announcements (PA2-COMM-005, super-admin) ──────────────

  /platform/announcements:
    get:
      tags: ["Platform Admin"]
      summary: "Lister les annonces plateforme (super-admin)"
      security:
        - SuperAdminBearerAuth: []
      parameters:
        - $ref: "#/components/parameters/PerPage"
      responses:
        "200":
          description: "Liste paginee des annonces plateforme"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
        "401":
          $ref: "#/components/responses/Error401"
    post:
      tags: ["Platform Admin"]
      summary: "Creer une annonce plateforme (maintenance, feature, incident, action requise)"
      security:
        - SuperAdminBearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [title, body]
              properties:
                title:
                  type: string
                  maxLength: 200
                body:
                  type: string
                  maxLength: 5000
                category:
                  type: string
                  nullable: true
                severity:
                  type: string
                  enum: [low, normal, high, urgent]
                  nullable: true
                audience_type:
                  type: string
                  enum: [all, companies]
                  nullable: true
                company_ids:
                  type: array
                  items:
                    type: string
                    format: uuid
                  nullable: true
                expires_at:
                  type: string
                  format: date-time
                  nullable: true
      responses:
        "201":
          description: "Annonce creee et diffusee"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
        "401":
          $ref: "#/components/responses/Error401"
        "422":
          $ref: "#/components/responses/Validation422"

  /platform/announcements/{announcement}:
    get:
      tags: ["Platform Admin"]
      summary: "Detail d'une annonce plateforme"
      security:
        - SuperAdminBearerAuth: []
      parameters:
        - name: announcement
          in: path
          required: true
          schema:
            type: integer
      responses:
        "200":
          description: "Annonce plateforme"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
        "401":
          $ref: "#/components/responses/Error401"
        "404":
          $ref: "#/components/responses/Error404"
    delete:
      tags: ["Platform Admin"]
      summary: "Supprimer une annonce plateforme"
      security:
        - SuperAdminBearerAuth: []
      parameters:
        - name: announcement
          in: path
          required: true
          schema:
            type: integer
      responses:
        "200":
          description: "Annonce supprimee"
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
        "401":
          $ref: "#/components/responses/Error401"
        "404":
          $ref: "#/components/responses/Error404"

  # ─── Impersonation super-admin (PA2-ADM-006) ─────────────────────────

  /platform/impersonations:
    get:
      tags: ["Platform Admin"]
      summary: "Lister les sessions d'impersonation (super-admin)"
      security:
        - SuperAdminBearerAuth: []
      parameters:
        - name: active_only
          in: query
          required: false
          schema:
            type: boolean
        - $ref: "#/components/parameters/PerPage"
      responses:
        "200":
          description: "Liste paginee des sessions d'impersonation"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                  meta:
                    type: object
        "401":
          $ref: "#/components/responses/Error401"
    post:
      tags: ["Platform Admin"]
      summary: "Creer une session d'impersonation (login en tant que)"
      description: "Motif obligatoire, duree limitee (30 min par defaut, 120 min max), session entierement auditee."
      security:
        - SuperAdminBearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [company_id, employee_id, reason]
              properties:
                company_id:
                  type: string
                  format: uuid
                employee_id:
                  type: integer
                reason:
                  type: string
                  minLength: 5
                  maxLength: 500
                ttl_minutes:
                  type: integer
                  minimum: 1
                  maximum: 120
                  default: 30
      responses:
        "201":
          description: "Session creee avec token d'impersonation"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                  token:
                    type: string
                  token_type:
                    type: string
                    example: "Bearer"
                  expires_at:
                    type: string
                    format: date-time
        "401":
          $ref: "#/components/responses/Error401"
        "422":
          $ref: "#/components/responses/Validation422"

  /platform/impersonations/{session}:
    delete:
      tags: ["Platform Admin"]
      summary: "Terminer une session d'impersonation"
      security:
        - SuperAdminBearerAuth: []
      parameters:
        - name: session
          in: path
          required: true
          schema:
            type: integer
      responses:
        "200":
          description: "Session terminee"
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                    example: ok
        "401":
          $ref: "#/components/responses/Error401"
        "404":
          $ref: "#/components/responses/Error404"

  # ─── Support center super-admin (PA2-COMM-012) ───────────────────────

  /platform/support-tickets:
    get:
      tags: ["Platform Admin"]
      summary: "Lister tous les tickets de support (super-admin, cross-tenant)"
      security:
        - SuperAdminBearerAuth: []
      parameters:
        - name: status
          in: query
          required: false
          schema:
            type: string
        - name: priority
          in: query
          required: false
          schema:
            type: string
        - name: company_id
          in: query
          required: false
          schema:
            type: string
            format: uuid
        - $ref: "#/components/parameters/PerPage"
      responses:
        "200":
          description: "Liste paginee des tickets avec compteurs par statut"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                  meta:
                    type: object
        "401":
          $ref: "#/components/responses/Error401"

  /platform/support-tickets/{supportTicket}:
    get:
      tags: ["Platform Admin"]
      summary: "Detail d'un ticket de support (super-admin)"
      security:
        - SuperAdminBearerAuth: []
      parameters:
        - name: supportTicket
          in: path
          required: true
          schema:
            type: integer
      responses:
        "200":
          description: "Ticket avec messages"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
        "401":
          $ref: "#/components/responses/Error401"
        "404":
          $ref: "#/components/responses/Error404"

  /platform/support-tickets/{supportTicket}/reply:
    post:
      tags: ["Platform Admin"]
      summary: "Repondre a un ticket (super-admin)"
      security:
        - SuperAdminBearerAuth: []
      parameters:
        - name: supportTicket
          in: path
          required: true
          schema:
            type: integer
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [message]
              properties:
                message:
                  type: string
                  maxLength: 5000
      responses:
        "200":
          description: "Reponse ajoutee"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
        "401":
          $ref: "#/components/responses/Error401"
        "404":
          $ref: "#/components/responses/Error404"
        "422":
          $ref: "#/components/responses/Validation422"

  /platform/support-tickets/{supportTicket}/triage:
    patch:
      tags: ["Platform Admin"]
      summary: "Trier un ticket (statut, priorite, assignation)"
      security:
        - SuperAdminBearerAuth: []
      parameters:
        - name: supportTicket
          in: path
          required: true
          schema:
            type: integer
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                status:
                  type: string
                  nullable: true
                priority:
                  type: string
                  nullable: true
                assign_to_me:
                  type: boolean
                  default: false
      responses:
        "200":
          description: "Ticket trie"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
        "401":
          $ref: "#/components/responses/Error401"
        "404":
          $ref: "#/components/responses/Error404"
        "422":
          $ref: "#/components/responses/Validation422"

  # ─── Observabilite super-admin (PA2-QA-006 / PA2-ADM-005) ────────────

  /platform/observability/queues:
    get:
      tags: ["Platform Admin"]
      summary: "Profondeur des queues Redis et jobs echoues (super-admin)"
      security:
        - SuperAdminBearerAuth: []
      responses:
        "200":
          description: "Snapshot des queues (profondeur, failed jobs, dernier run planifie)"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
        "401":
          $ref: "#/components/responses/Error401"

  /platform/observability/notifications:
    get:
      tags: ["Platform Admin"]
      summary: "Taux d'echec de notifications cross-tenant sur 24h (super-admin)"
      security:
        - SuperAdminBearerAuth: []
      responses:
        "200":
          description: "Snapshot observabilite notifications avec liens runbook"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
        "401":
          $ref: "#/components/responses/Error401"

  # ─── Pipeline CRM super-admin (PA2-ADM-004) ──────────────────────────

  /platform/crm/pipeline:
    get:
      tags: ["Platform Admin"]
      summary: "Pipeline CRM des demandes clients (leads, essais, actifs, rejetes)"
      security:
        - SuperAdminBearerAuth: []
      responses:
        "200":
          description: "Pipeline avec buckets leads/trials/active/rejected et resume de conversion"
          content:
            application/json:
              schema:
                type: object
                properties:
                  leads:
                    type: array
                  trials:
                    type: array
                  active:
                    type: array
                  rejected:
                    type: array
                  summary:
                    type: object
        "401":
          $ref: "#/components/responses/Error401"

  /platform/edge/nodes:
    get:
      tags: ["Edge"]
      summary: "Lister les nœuds edge de la plateforme (super-admin)"
      security:
        - BearerAuth: []
      responses:
        "200":
          description: "Liste des nœuds edge"
        "401":
          $ref: "#/components/responses/Error401"

  /platform/edge/nodes/{nodeId}/sync:
    post:
      tags: ["Edge"]
      summary: "Forcer la synchronisation d'un nœud edge (super-admin)"
      security:
        - BearerAuth: []
      parameters:
        - name: nodeId
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: "Synchronisation déclenchée"
        "401":
          $ref: "#/components/responses/Error401"

  /platform/edge/nodes/{nodeId}:
    delete:
      tags: ["Edge"]
      summary: "Révoquer un nœud edge (super-admin)"
      security:
        - BearerAuth: []
      parameters:
        - name: nodeId
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: "Nœud révoqué"
        "401":
          $ref: "#/components/responses/Error401"


  /planning/weekly-optimization:
    get:
      tags: ["Planning"]
      summary: "Optimisation hebdomadaire du planning (suggestions)"
      security:
        - BearerAuth: []
      parameters:
        - name: week_start
          in: query
          required: false
          schema: { type: string, format: date }
      responses:
        "200":
          description: "Suggestions d'optimisation"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { type: object }
        "401":
          $ref: "#/components/responses/Error401"

  /planning/shift-rebalancing:
    get:
      tags: ["Planning"]
      summary: "Suggestions de rééquilibrage des équipes"
      security:
        - BearerAuth: []
      responses:
        "200":
          description: "Suggestions de rééquilibrage"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { type: object }
        "401":
          $ref: "#/components/responses/Error401"

  /payroll-runs/{payrollRun}/export:
    get:
      tags: ["Payroll"]
      summary: "Export comptable du run (CSV/Excel streamé)"
      security:
        - BearerAuth: []
      parameters:
        - name: payrollRun
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: "Fichier d'export (stream)"
          content:
            text/csv: {}
        "401":
          $ref: "#/components/responses/Error401"
        "404":
          $ref: "#/components/responses/Error404"

  /payroll-runs/{payrollRun}/send-slips:
    post:
      tags: ["Payroll"]
      summary: "Envoyer les bulletins du run aux employés"
      security:
        - BearerAuth: []
      parameters:
        - name: payrollRun
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: "Bulletins envoyés"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { type: object }
        "401":
          $ref: "#/components/responses/Error401"
        "404":
          $ref: "#/components/responses/Error404"

  /payroll/cycles:
    get:
      tags: ["Payroll"]
      summary: "Historique des cycles de paie"
      security:
        - BearerAuth: []
      responses:
        "200":
          description: "Cycles de paie"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { type: array, items: { type: object } }
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          $ref: "#/components/responses/Error403"

  /payroll/cycles/current:
    get:
      tags: ["Payroll"]
      summary: "Cycle de paie courant"
      security:
        - BearerAuth: []
      responses:
        "200":
          description: "Cycle courant (ou 404 si aucun)"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { type: object }
        "401":
          $ref: "#/components/responses/Error401"
        "404":
          $ref: "#/components/responses/Error404"

  /payroll/cycle-settings:
    get:
      tags: ["Payroll"]
      summary: "Réglages du cycle de paie (société)"
      security:
        - BearerAuth: []
      responses:
        "200":
          description: "Réglages du cycle"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { type: object }
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          $ref: "#/components/responses/Error403"
    put:
      tags: ["Payroll"]
      summary: "Mettre à jour les réglages du cycle de paie"
      security:
        - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                closing_day: { type: integer, nullable: true }
                payment_day: { type: integer, nullable: true }
                period_label: { type: string, nullable: true }
      responses:
        "200":
          description: "Réglages mis à jour"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { type: object }
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          $ref: "#/components/responses/Error403"
        "422":
          $ref: "#/components/responses/Error422"

  /payroll-runs/{payrollRun}/bulk-pay:
    post:
      tags: ["Payroll"]
      summary: "Lancer le paiement groupé du run (virements)"
      security:
        - BearerAuth: []
      parameters:
        - name: payrollRun
          in: path
          required: true
          schema: { type: integer }
      responses:
        "202":
          description: "Paiement groupé accepté (asynchrone)"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { type: object }
        "401":
          $ref: "#/components/responses/Error401"
        "404":
          $ref: "#/components/responses/Error404"
        "422":
          $ref: "#/components/responses/Validation422"

  /payroll-runs/{payrollRun}/bulk-pay/status:
    get:
      tags: ["Payroll"]
      summary: "Statut du paiement groupé du run"
      security:
        - BearerAuth: []
      parameters:
        - name: payrollRun
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: "Statut du bulk-pay"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { type: object }
        "401":
          $ref: "#/components/responses/Error401"
        "404":
          $ref: "#/components/responses/Error404"

components:
  securitySchemes:
    BearerAuth:
      type: http
      scheme: bearer
      bearerFormat: Sanctum
    SuperAdminBearerAuth:
      type: http
      scheme: bearer
      bearerFormat: Sanctum

  parameters:
    ProjectId:
      name: project
      in: path
      required: true
      schema:
        type: integer
    NotificationId:
      name: notification
      in: path
      required: true
      schema:
        type: integer
    TaskId:
      name: task
      in: path
      required: true
      schema:
        type: integer
    EvaluationId:
      name: evaluation
      in: path
      required: true
      schema:
        type: integer
    EmployeeId:
      name: employee
      in: path
      required: true
      schema:
        type: integer
    CameraId:
      name: camera
      in: path
      required: true
      schema:
        type: integer
    TokenId:
      name: token
      in: path
      required: true
      schema:
        type: integer
    PermissionId:
      name: permission
      in: path
      required: true
      schema:
        type: integer
    InvitationId:
      name: invitation
      in: path
      required: true
      schema:
        type: string
        format: uuid
    GenericIntId:
      name: id
      in: path
      required: true
      schema:
        type: integer
    NumericId:
      name: id
      in: path
      required: true
      schema:
        type: integer
    CompanyUuid:
      name: company
      in: path
      required: true
      schema:
        type: string
        format: uuid
    DeviceCode:
      name: deviceCode
      in: path
      required: true
      schema:
        type: string
    PerPage:
      name: per_page
      in: query
      schema:
        type: integer
        minimum: 1
        maximum: 100
    Page:
      name: page
      in: query
      schema:
        type: integer
        minimum: 1
    KioskTokenHeader:
      name: X-Kiosk-Token
      in: header
      required: true
      schema:
        type: string

  headers:
    XRequestId:
      description: "Identifiant de correlation a fournir au support en cas d'incident."
      schema:
        type: string
    RetryAfter:
      description: "Delai conseille avant retry lorsqu'une limite de debit est atteinte."
      schema:
        type: integer

  responses:
    Error401:
      description: "Non authentifie"
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
    Error403:
      description: "Action interdite"
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
    Error404:
      description: "Ressource introuvable"
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
    Error429:
      description: "Limite de debit atteinte"
      headers:
        Retry-After:
          $ref: "#/components/headers/RetryAfter"
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
    Error422:
      description: "Erreur de validation"
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ValidationErrorResponse"
    Validation422:
      description: "Erreur de validation"
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ValidationErrorResponse"

  schemas:
    # ── SmartAttendance schemas (v4.18.0) ──
    AttendanceModeSettings:
      type: object
      properties:
        id: { type: integer }
        company_id: { type: string, format: uuid }
        forced_mode: { type: string, enum: [manual, gps, qr], nullable: true }
        punch_photo_mode: { type: string, enum: [kiosk, photo_required], nullable: true, description: "Mode de pointage mobile : kiosque (clic simple) ou photo obligatoire a chaque pointage." }
        gps_enabled: { type: boolean }
        latitude: { type: number, format: double, nullable: true }
        longitude: { type: number, format: double, nullable: true }
        radius_meters: { type: integer, default: 100 }
        allow_employee_override: { type: boolean }
        updated_at: { type: string, format: date-time }

    AttendanceModeSettingsRequest:
      type: object
      properties:
        gps_enabled: { type: boolean }
        forced_mode: { type: string, enum: [manual, gps, qr], nullable: true }
        punch_photo_mode: { type: string, enum: [kiosk, photo_required], nullable: true }
        latitude: { type: number, format: double }
        longitude: { type: number, format: double }
        radius_meters: { type: integer, minimum: 50, maximum: 5000 }
        allow_employee_override: { type: boolean }

    EmployeeAttendancePreference:
      type: object
      properties:
        id: { type: integer }
        employee_id: { type: integer }
        company_id: { type: string, format: uuid }
        preferred_mode: { type: string, enum: [gps_auto, qr, manual] }
        gps_consent_given: { type: boolean }
        gps_consent_at: { type: string, format: date-time, nullable: true }

    GeoEventRequest:
      type: object
      required: [event_type, latitude, longitude]
      properties:
        event_type:
          type: string
          enum: [zone_enter, zone_exit]
        latitude: { type: number, format: double }
        longitude: { type: number, format: double }
        accuracy: { type: integer, description: "Précision GPS en mètres" }

    GeoAttendanceSession:
      type: object
      properties:
        id: { type: integer }
        employee_id: { type: integer }
        employee_name: { type: string }
        company_id: { type: string, format: uuid }
        site_id: { type: integer, nullable: true }
        started_at: { type: string, format: date-time }
        ended_at: { type: string, format: date-time, nullable: true }
        duration_seconds: { type: integer, nullable: true }
        check_in_lat: { type: number, format: double }
        check_in_lng: { type: number, format: double }
        check_in_accuracy_meters: { type: integer, nullable: true }
        check_out_lat: { type: number, format: double, nullable: true }
        check_out_lng: { type: number, format: double, nullable: true }
        status:
          type: string
          enum: [detected, pending_validation, approved, rejected, cancelled]
        attendance_log_id: { type: integer, nullable: true }
        validated_by: { type: integer, nullable: true }
        validated_at: { type: string, format: date-time, nullable: true }
        validation_note: { type: string, nullable: true }

    SmartAttendanceDashboard:
      type: object
      properties:
        today:
          type: object
          properties:
            detected: { type: integer }
            pending_validation: { type: integer }
            approved: { type: integer }
            rejected: { type: integer }
            cancelled: { type: integer }
            total_employees_present: { type: integer }
        week:
          type: object
          properties:
            total_sessions: { type: integer }
            auto_approved_rate: { type: number, format: float }
    # ── End SmartAttendance schemas ──

    CompanyBranding:
      type: object
      properties:
        display_name:
          type: string
          example: "TechCorp Algerie"
        logo_url:
          type: string
          nullable: true
          example: "https://api.example.com/storage/company-branding/logo.png"
        logo_path:
          type: string
          nullable: true
        logo_disk:
          type: string
          nullable: true
          example: "public"
        primary_color:
          type: string
          pattern: "^#[0-9A-Fa-f]{6}$"
          example: "#10B981"
        accent_color:
          type: string
          pattern: "^#[0-9A-Fa-f]{6}$"
          example: "#2563EB"
        brand_mode:
          type: string
          enum: ["default", "light", "dark", "auto"]
    CompanyBrandingResponse:
      type: object
      properties:
        company_id:
          type: string
          format: uuid
        branding:
          $ref: "#/components/schemas/CompanyBranding"
    CompanyBrandingUpdateRequest:
      type: object
      properties:
        display_name:
          type: string
          minLength: 2
          maxLength: 120
        logo_url:
          type: string
          format: uri
          nullable: true
        primary_color:
          type: string
          pattern: "^#[0-9A-Fa-f]{6}$"
        accent_color:
          type: string
          pattern: "^#[0-9A-Fa-f]{6}$"
        brand_mode:
          type: string
          enum: ["default", "light", "dark", "auto"]
    Schedule:
      type: object
      properties:
        id:
          type: integer
        company_id:
          type: string
          format: uuid
        name:
          type: string
        start_time:
          type: string
          example: "08:00"
        end_time:
          type: string
          example: "17:00"
        break_minutes:
          type: integer
          minimum: 0
        work_days:
          type: array
          items:
            type: integer
            minimum: 1
            maximum: 7
        rest_days:
          type: array
          items:
            type: integer
            minimum: 1
            maximum: 7
        break_rules:
          type: array
          items:
            type: object
            properties:
              label:
                type: string
              start_time:
                type: string
                nullable: true
              end_time:
                type: string
                nullable: true
              minutes:
                type: integer
              is_paid:
                type: boolean
        leave_rules:
          type: array
          items:
            type: object
            properties:
              label:
                type: string
              type:
                type: string
                nullable: true
              days_per_year:
                type: number
                nullable: true
              policy_id:
                type: integer
                nullable: true
        assignment_notes:
          type: string
          nullable: true
        late_tolerance_minutes:
          type: integer
          minimum: 0
        overtime_threshold_daily:
          type: number
        overtime_threshold_weekly:
          type: number
        is_default:
          type: boolean
    ScheduleRequest:
      type: object
      required: ["name", "start_time", "end_time"]
      properties:
        name:
          type: string
          maxLength: 100
        start_time:
          type: string
          pattern: "^\\d{2}:\\d{2}$"
        end_time:
          type: string
          pattern: "^\\d{2}:\\d{2}$"
        break_minutes:
          type: integer
          minimum: 0
          maximum: 480
        work_days:
          type: array
          items:
            type: integer
            minimum: 1
            maximum: 7
        rest_days:
          type: array
          items:
            type: integer
            minimum: 1
            maximum: 7
        break_rules:
          type: array
          maxItems: 10
          items:
            type: object
            properties:
              label:
                type: string
                maxLength: 80
              start_time:
                type: string
                pattern: "^\\d{2}:\\d{2}$"
              end_time:
                type: string
                pattern: "^\\d{2}:\\d{2}$"
              minutes:
                type: integer
                minimum: 0
                maximum: 480
              is_paid:
                type: boolean
        leave_rules:
          type: array
          maxItems: 20
          items:
            type: object
            properties:
              label:
                type: string
                maxLength: 120
              type:
                type: string
                maxLength: 80
              days_per_year:
                type: number
                minimum: 0
                maximum: 365
              policy_id:
                type: integer
                minimum: 1
        assignment_notes:
          type: string
          maxLength: 1000
        late_tolerance_minutes:
          type: integer
          minimum: 0
          maximum: 120
        overtime_threshold_daily:
          type: number
          minimum: 0
        overtime_threshold_weekly:
          type: number
          minimum: 0
        is_default:
          type: boolean
    ClientEventRequest:
      type: object
      required: ["name"]
      properties:
        name:
          type: string
          enum: ["login_success", "dashboard_loaded", "feature_blocked", "demo_user_selected", "kiosk_status"]
        surface:
          type: string
          enum: ["web", "admin", "mobile", "kiosk"]
          default: "web"
        session_id:
          type: string
          nullable: true
          maxLength: 120
        duration_ms:
          type: integer
          nullable: true
          minimum: 0
          maximum: 600000
        occurred_at:
          type: string
          format: date-time
          nullable: true
        properties:
          type: object
          additionalProperties: true
          nullable: true
    ClientEventResponse:
      type: object
      properties:
        data:
          type: object
          properties:
            id:
              type: integer
            name:
              type: string
            stored:
              type: boolean
    LaunchReadinessResponse:
      type: object
      properties:
        data:
          type: object
          properties:
            company_id:
              type: string
              format: uuid
            score:
              type: integer
              minimum: 0
              maximum: 100
            go_live_ready:
              type: boolean
            required_blockers:
              type: array
              items:
                $ref: "#/components/schemas/LaunchReadinessBlocker"
            next_actions:
              type: array
              items:
                $ref: "#/components/schemas/LaunchReadinessAction"
            checks:
              type: array
              items:
                $ref: "#/components/schemas/LaunchReadinessCheck"
    LaunchReadinessCheck:
      type: object
      properties:
        key:
          type: string
          enum:
            - company_profile
            - manager_access
            - employee_base
            - communication_governance
            - payroll_base
            - attendance_entry
            - client_experience_tracking
        label:
          type: string
        required:
          type: boolean
        completed:
          type: boolean
        metrics:
          type: object
          additionalProperties: true
    LaunchReadinessBlocker:
      type: object
      properties:
        key:
          type: string
        label:
          type: string
        metrics:
          type: object
          additionalProperties: true
    LaunchReadinessAction:
      type: object
      properties:
        key:
          type: string
        label:
          type: string
        required:
          type: boolean
    CommunicationAnalyticsResponse:
      type: object
      properties:
        data:
          type: object
          properties:
            period:
              type: object
              properties:
                days:
                  type: integer
                since:
                  type: string
                  format: date-time
            totals:
              type: object
              properties:
                events:
                  type: integer
                sent_or_queued:
                  type: integer
                failed:
                  type: integer
                skipped:
                  type: integer
                failure_rate:
                  type: number
                  format: float
            by_channel:
              type: array
              items:
                $ref: "#/components/schemas/KeyCount"
            by_status:
              type: array
              items:
                $ref: "#/components/schemas/KeyCount"
            by_template:
              type: array
              items:
                $ref: "#/components/schemas/KeyCount"
            daily:
              type: array
              items:
                type: object
                properties:
                  date:
                    type: string
                    format: date
                  count:
                    type: integer
    KeyCount:
      type: object
      properties:
        key:
          type: string
        count:
          type: integer
    Vehicle:
      type: object
      properties:
        id:
          type: integer
        company_id:
          type: integer
        plate_number:
          type: string
        brand:
          type: string
          nullable: true
        model:
          type: string
          nullable: true
        year:
          type: integer
          nullable: true
        type:
          type: string
          enum: [car, van, truck, motorcycle, bus]
          nullable: true
        vin:
          type: string
          nullable: true
        fuel_type:
          type: string
          enum: [diesel, gasoline, electric, hybrid, lpg]
          nullable: true
        status:
          type: string
          enum: [active, maintenance, decommissioned]
        mileage:
          type: integer
          nullable: true
        insurance_expiry:
          type: string
          format: date
          nullable: true
        technical_control_expiry:
          type: string
          format: date
          nullable: true
        traccar_unique_id:
          type: string
          nullable: true
        traccar_device_id:
          type: integer
          nullable: true
        assigned_driver_id:
          type: integer
          nullable: true
        metadata:
          type: object
          nullable: true
          additionalProperties: true
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time

    ErrorResponse:
      type: object
      required: ["error", "message"]
      properties:
        error:
          type: string
          enum:
            - UNAUTHENTICATED
            - FORBIDDEN
            - RESOURCE_NOT_FOUND
            - COMPANY_NOT_FOUND
            - TWO_FA_REQUIRED
            - VALIDATION_ERROR
            - RATE_LIMITED
            - NOT_IMPLEMENTED
          example: "RESOURCE_NOT_FOUND"
        message:
          type: string
          example: "Ressource introuvable."
        localized_message:
          type: string
          description: "Message localisé (catalogue errors.*) — présent sur toute réponse d'erreur HTTP (#4689)"
          example: "Ressource introuvable."
        request_id:
          type: string
          nullable: true
          example: "req_01HYZ4VY9K7M1A2B3C4D5E6F7G"

    ValidationErrorResponse:
      type: object
      required: ["error", "message", "errors"]
      properties:
        error:
          type: string
          example: "VALIDATION_ERROR"
        message:
          type: string
          example: "The given data was invalid."
        localized_message:
          type: string
          description: "Message localisé (catalogue errors.*) — présent sur toute réponse d'erreur HTTP (#4689)"
          example: "Les données fournies sont invalides."
        errors:
          type: object
          additionalProperties:
            type: array
            items:
              type: string
        request_id:
          type: string
          nullable: true

    WebhookEndpoint:
      type: object
      description: "App\\Http\\Resources\\Api\\V1\\WebhookEndpointResource. Le champ `secret` HMAC n'est jamais expose ici, uniquement au moment de la creation."
      properties:
        id:
          type: integer
          example: 12
        url:
          type: string
          format: uri
          example: "https://partner.example.com/hooks/leopardo"
        events:
          type: array
          items:
            type: string
          example: ["employee.created", "absence.approved"]
        active:
          type: boolean
        failure_count:
          type: integer
          description: "Nombre d'echecs consecutifs; un seuil desactive automatiquement le webhook (active=false)."
        last_triggered_at:
          type: string
          format: date-time
          nullable: true
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time

    WebhookEndpointRequest:
      type: object
      required: ["url", "events"]
      description: "App\\Modules\\Billing\\Interfaces\\Api\\V1\\Requests\\StoreWebhookEndpointRequest."
      properties:
        url:
          type: string
          format: uri
          maxLength: 500
          description: "Rejetee si l'URL pointe vers une adresse privee/loopback (App\\Rules\\NotPrivateUrl, garde SSRF)."
        events:
          type: array
          minItems: 1
          items:
            type: string
            description: "Doit appartenir a la liste retournee par GET /webhooks/events."

    WebhookEndpointUpdateRequest:
      type: object
      description: "App\\Modules\\Billing\\Interfaces\\Api\\V1\\Requests\\UpdateWebhookEndpointRequest. Tous les champs sont optionnels (sometimes)."
      properties:
        url:
          type: string
          format: uri
          maxLength: 500
        events:
          type: array
          minItems: 1
          items:
            type: string
        active:
          type: boolean
          description: "Passer a true remet failure_count a 0."

    WebhookDelivery:
      type: object
      description: "App\\Http\\Resources\\Api\\V1\\WebhookDeliveryResource."
      properties:
        id:
          type: integer
        webhook_endpoint_id:
          type: integer
        event:
          type: string
          example: "employee.created"
        response_code:
          type: integer
          description: "0 si la livraison a echoue (timeout/connexion refusee) plutot qu'un vrai code HTTP."
        response_body:
          type: string
          nullable: true
        duration_ms:
          type: integer
        dead_lettered_at:
          type: string
          format: date-time
          nullable: true
          description: "Non-null une fois les 3 tentatives (DispatchWebhook::$tries) epuisees."
        delivered_at:
          type: string
          format: date-time
          nullable: true

    HealthCheck:
      type: object
      properties:
        ok:
          type: boolean
        latency_ms:
          type: integer
          nullable: true
        status:
          type: string
          nullable: true
        error:
          type: string
          nullable: true

    HealthResponse:
      type: object
      properties:
        status:
          type: string
          example: "ok"
        version:
          type: string
          nullable: true
        checks:
          type: object
          properties:
            database:
              $ref: "#/components/schemas/HealthCheck"
            redis:
              $ref: "#/components/schemas/HealthCheck"
            storage:
              $ref: "#/components/schemas/HealthCheck"
        timestamp:
          type: string
          format: date-time

    LoginRequest:
      type: object
      required: ["email", "password"]
      properties:
        email:
          type: string
          format: email
        password:
          type: string
        device_name:
          type: string
          maxLength: 100

    PlatformLoginRequest:
      type: object
      required: ["email", "password"]
      properties:
        email:
          type: string
          format: email
        password:
          type: string
        device_name:
          type: string

    CompanyMini:
      type: object
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
        language:
          type: string
          nullable: true
        timezone:
          type: string
          nullable: true
        currency:
          type: string
          nullable: true

    CurrentEmployee:
      type: object
      properties:
        id:
          type: integer
        matricule:
          type: string
          nullable: true
        company_id:
          type: string
          format: uuid
        first_name:
          type: string
        middle_name:
          type: string
          nullable: true
        last_name:
          type: string
        preferred_name:
          type: string
          nullable: true
        email:
          type: string
          format: email
        personal_email:
          type: string
          format: email
          nullable: true
        recovery_email:
          type: string
          format: email
          nullable: true
        phone:
          type: string
          nullable: true
        personal_phone:
          type: string
          nullable: true
        role:
          type: string
          enum: ["employee", "manager"]
        manager_role:
          type: string
          nullable: true
        status:
          type: string
        photo_path:
          type: string
          nullable: true
        biometric_face_enabled:
          type: boolean
        biometric_fingerprint_enabled:
          type: boolean
        address_line:
          type: string
          nullable: true
        postal_code:
          type: string
          nullable: true
        emergency_contact_name:
          type: string
          nullable: true
        emergency_contact_phone:
          type: string
          nullable: true
        extra_data:
          type: object
          additionalProperties: true
        capabilities:
          type: object
          additionalProperties:
            type: boolean
        features:
          type: object
          additionalProperties:
            type: boolean
        suggested_home_route:
          type: string
        company:
          $ref: "#/components/schemas/CompanyMini"

    PrivacyExportResponse:
      type: object
      properties:
        data:
          type: object
          properties:
            employee:
              type: object
              properties:
                id:
                  type: integer
                company_id:
                  type: string
                  format: uuid
                matricule:
                  type: string
                  nullable: true
                first_name:
                  type: string
                last_name:
                  type: string
                preferred_name:
                  type: string
                  nullable: true
                email:
                  type: string
                  format: email
                personal_email:
                  type: string
                  format: email
                  nullable: true
                phone:
                  type: string
                  nullable: true
                role:
                  type: string
                manager_role:
                  type: string
                  nullable: true
                status:
                  type: string
                preferred_language:
                  type: string
                  nullable: true
                biometric_face_enabled:
                  type: boolean
                biometric_fingerprint_enabled:
                  type: boolean
                biometric_consent_at:
                  type: string
                  format: date-time
                  nullable: true
                created_at:
                  type: string
                  format: date-time
                  nullable: true
                updated_at:
                  type: string
                  format: date-time
                  nullable: true
            activity_summary:
              type: object
              properties:
                attendance_logs_count:
                  type: integer
                absence_requests_count:
                  type: integer
                pay_slips_count:
                  type: integer
                expense_claims_count:
                  type: integer
            privacy:
              type: object
              properties:
                exported_at:
                  type: string
                  format: date-time
                scope:
                  type: string
                format_version:
                  type: string

    PrivacyRequestResponse:
      type: object
      properties:
        data:
          type: object
          properties:
            id:
              type: integer
            type:
              type: string
              example: "deletion"
            status:
              type: string
              example: "received"
            message:
              type: string

    BiometricConsentResponse:
      type: object
      properties:
        data:
          type: object
          properties:
            biometric_face_enabled:
              type: boolean
            biometric_fingerprint_enabled:
              type: boolean
            biometric_consent_at:
              type: string
              format: date-time
              nullable: true

    LoginResponse:
      type: object
      properties:
        data:
          $ref: "#/components/schemas/CurrentEmployee"
        token:
          type: string
        token_type:
          type: string
          example: "Bearer"
        token_expires_at:
          type: string
          format: date-time
          nullable: true

    UpdateProfileRequest:
      type: object
      properties:
        first_name:
          type: string
        last_name:
          type: string
        email:
          type: string
          format: email
        personal_email:
          type: string
          format: email
          nullable: true
        recovery_email:
          type: string
          format: email
          nullable: true
        personal_phone:
          type: string
          nullable: true

    EmployeeCareerResponse:
      type: object
      properties:
        available_for_new_company:
          type: boolean
        current_company_id:
          type: string
          format: uuid
          nullable: true
        current_company_name:
          type: string
          nullable: true
        timeline:
          type: array
          items:
            type: object
            properties:
              company_id:
                type: string
                format: uuid
                nullable: true
              company_name:
                type: string
                nullable: true
              start_date:
                type: string
                format: date
                nullable: true
              end_date:
                type: string
                format: date
                nullable: true
              job_title:
                type: string
                nullable: true
              contract_type:
                type: string
                nullable: true
              status:
                type: string
                nullable: true
              current:
                type: boolean

    ChangePasswordRequest:
      type: object
      required: ["current_password", "new_password", "new_password_confirmation"]
      properties:
        current_password:
          type: string
        new_password:
          type: string
          minLength: 8
        new_password_confirmation:
          type: string

    SuperAdminUser:
      type: object
      properties:
        id:
          type: integer
        name:
          type: string
        email:
          type: string
          format: email

    PlatformLoginResponse:
      type: object
      properties:
        data:
          $ref: "#/components/schemas/SuperAdminUser"
        token:
          type: string
        token_type:
          type: string
          example: "Bearer"

    PlatformCompany:
      type: object
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
        slug:
          type: string
          nullable: true
        sector:
          type: string
          nullable: true
        country:
          type: string
        city:
          type: string
        address:
          type: string
          nullable: true
        email:
          type: string
          format: email
        phone:
          type: string
          nullable: true
        plan_id:
          type: integer
        schema_name:
          type: string
          nullable: true
        tenancy_type:
          type: string
        status:
          type: string
        language:
          type: string
          nullable: true
        timezone:
          type: string
          nullable: true
        currency:
          type: string
          nullable: true
        features:
          type: object
          additionalProperties: true
        metadata:
          type: object
          additionalProperties: true
        created_at:
          type: string
          format: date-time
          nullable: true
        updated_at:
          type: string
          format: date-time
          nullable: true

    PlatformMetricsOverviewResponse:
      type: object
      properties:
        data:
          type: object
          properties:
            revenue:
              $ref: "#/components/schemas/PlatformMetricsRevenue"
            companies:
              $ref: "#/components/schemas/PlatformMetricsCompanies"
            subscriptions:
              $ref: "#/components/schemas/PlatformMetricsSubscriptions"
            billing:
              $ref: "#/components/schemas/PlatformMetricsBilling"
            system:
              $ref: "#/components/schemas/PlatformMetricsSystem"
            generated_at:
              type: string
              format: date-time

    PlatformMetricsRevenue:
      type: object
      properties:
        currency:
          type: string
          example: "DZD"
        mrr:
          type: number
          format: float
          example: 128
        arr:
          type: number
          format: float
          example: 1536
        collected_30d:
          type: number
          format: float
          example: 119
        overdue_total:
          type: number
          format: float
          example: 50

    PlatformMetricsCompanies:
      type: object
      properties:
        total:
          type: integer
          example: 12
        active:
          type: integer
          example: 8
        trial:
          type: integer
          example: 2
        suspended:
          type: integer
          example: 1
        expired:
          type: integer
          example: 1

    PlatformMetricsSubscriptions:
      type: object
      properties:
        total:
          type: integer
          example: 12
        active:
          type: integer
          example: 8
        trial:
          type: integer
          example: 2
        past_due:
          type: integer
          example: 1
        cancelled_30d:
          type: integer
          example: 1

    PlatformMetricsBilling:
      type: object
      properties:
        invoices_total:
          type: integer
          example: 24
        invoices_paid:
          type: integer
          example: 18
        invoices_overdue:
          type: integer
          example: 3
        payments_completed_30d:
          type: integer
          example: 7

    PlatformMetricsSystem:
      type: object
      properties:
        php_version:
          type: string
          example: "8.4.20"
        laravel_version:
          type: string
          example: "11.x"
        memory_usage_mb:
          type: integer
          example: 32
        cache_driver:
          type: string
          example: "redis"
        queue_driver:
          type: string
          example: "redis"
        db_driver:
          type: string
          example: "pgsql"

    PlatformPlan:
      type: object
      properties:
        id:
          type: integer
        name:
          type: string
        price_monthly:
          type: number
          format: float
        price_yearly:
          type: number
          format: float
        max_employees:
          type: integer
          nullable: true
        features:
          type: object
          additionalProperties: true
        trial_days:
          type: integer
        is_active:
          type: boolean

    PlatformPlansResponse:
      type: object
      properties:
        data:
          type: object
          properties:
            items:
              type: array
              items:
                $ref: "#/components/schemas/PlatformPlan"

    PlatformCompanySubscription:
      type: object
      properties:
        company_id:
          type: string
          format: uuid
        status:
          type: string
          enum: ["active", "trial", "suspended", "expired"]
        plan:
          type: object
          properties:
            id:
              type: integer
              nullable: true
            name:
              type: string
              nullable: true
            price_monthly:
              type: number
              format: float
              nullable: true
            price_yearly:
              type: number
              format: float
              nullable: true
            max_employees:
              type: integer
              nullable: true
        subscription_start:
          type: string
          format: date
          nullable: true
        subscription_end:
          type: string
          format: date
          nullable: true
        currency:
          type: string
          nullable: true
        notes:
          type: string
          nullable: true

    PlatformCompanySubscriptionResponse:
      type: object
      properties:
        data:
          $ref: "#/components/schemas/PlatformCompanySubscription"

    PlatformCompanySubscriptionUpdateRequest:
      type: object
      required: ["plan_id", "status"]
      properties:
        plan_id:
          type: integer
        status:
          type: string
          enum: ["active", "trial", "suspended", "expired"]
        subscription_start:
          type: string
          format: date
          nullable: true
        subscription_end:
          type: string
          format: date
          nullable: true
        notes:
          type: string
          nullable: true
          maxLength: 1000

    PlatformCompanyFeaturesResponse:
      type: object
      properties:
        data:
          type: object
          properties:
            company_id:
              type: string
              format: uuid
            features:
              type: object
              additionalProperties:
                type: boolean
            known_modules:
              type: array
              items:
                type: string

    PlatformCompanyFeaturesUpdateRequest:
      type: object
      required: ["features"]
      properties:
        features:
          type: object
          additionalProperties:
            type: boolean

    PlatformHealthCompany:
      type: object
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
        status:
          type: string
        country:
          type: string
        currency:
          type: string
          nullable: true
        timezone:
          type: string
          nullable: true

    PlatformCompanyHealthResponse:
      type: object
      properties:
        data:
          type: object
          properties:
            company:
              $ref: "#/components/schemas/PlatformHealthCompany"
            plan:
              type: object
              additionalProperties: true
            features:
              type: object
              additionalProperties: true
            period:
              type: object
              additionalProperties: true
            subscription:
              type: object
              additionalProperties: true
            adoption:
              type: object
              properties:
                health_score:
                  type: integer
                  minimum: 0
                  maximum: 100
                risk_level:
                  type: string
                  enum: ["low", "medium", "high"]
                employees:
                  type: object
                  additionalProperties: true
                attendance:
                  type: object
                  additionalProperties: true
                onboarding:
                  type: object
                  additionalProperties: true
                anomalies:
                  type: object
                  additionalProperties: true
            next_actions:
              type: array
              items:
                $ref: "#/components/schemas/PlatformNextAction"

    PlatformCompanyHealthPortfolioResponse:
      type: object
      properties:
        data:
          type: object
          properties:
            summary:
              type: object
              additionalProperties: true
            items:
              type: array
              items:
                type: object
                properties:
                  company:
                    $ref: "#/components/schemas/PlatformHealthCompany"
                  plan:
                    type: object
                    additionalProperties: true
                  subscription:
                    type: object
                    additionalProperties: true
                  health_score:
                    type: integer
                  risk_level:
                    type: string
                  employees_active:
                    type: integer
                  attendance_logs_30d:
                    type: integer
                  last_punch_at:
                    type: string
                    format: date-time
                    nullable: true
                  critical_anomalies_30d:
                    type: integer
                  next_action:
                    type: object
                    nullable: true
                    allOf:
                      - $ref: "#/components/schemas/PlatformNextAction"

    PlatformNextAction:
      type: object
      properties:
        key:
          type: string
        label:
          type: string
        priority:
          type: string
          enum: ["low", "medium", "high"]

    PlatformCompanyRequest:
      type: object
      properties:
        id:
          type: integer
        company_name:
          type: string
        sector:
          type: string
          nullable: true
        country:
          type: string
          nullable: true
        city:
          type: string
          nullable: true
        email:
          type: string
          format: email
          nullable: true
        phone:
          type: string
          nullable: true
        description:
          type: string
          nullable: true
        status:
          type: string
          enum: ["pending", "approved", "rejected"]
        admin_notes:
          type: string
          nullable: true
        user:
          nullable: true
          type: object
          properties:
            id:
              type: integer
            name:
              type: string
            email:
              type: string
              format: email
        created_at:
          type: string
          format: date-time
          nullable: true
        reviewed_at:
          type: string
          format: date-time
          nullable: true

    PlatformCompanyRequestsResponse:
      type: object
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/PlatformCompanyRequest"
        meta:
          $ref: "#/components/schemas/PaginationMeta"

    PlatformCompanyRequestResponse:
      type: object
      properties:
        data:
          $ref: "#/components/schemas/PlatformCompanyRequest"

    PlatformCompanyRequestReviewRequest:
      type: object
      required: ["status"]
      properties:
        status:
          type: string
          enum: ["approved", "rejected"]
        admin_notes:
          type: string
          nullable: true
          maxLength: 2000
        plan_id:
          type: integer
          nullable: true

    PlatformCompanyRequestReviewResponse:
      type: object
      properties:
        data:
          type: object
          properties:
            id:
              type: integer
            status:
              type: string
              enum: ["approved", "rejected"]
            approved_company_id:
              type: string
              format: uuid
              nullable: true
            admin_notes:
              type: string
              nullable: true
            reviewed_at:
              type: string
              format: date-time
              nullable: true

    PlatformCompanyCreateRequest:
      type: object
      required:
        - name
        - country
        - city
        - email
        - manager_first_name
        - manager_last_name
        - manager_email
      properties:
        name:
          type: string
        slug:
          type: string
        sector:
          type: string
        country:
          type: string
          minLength: 2
          maxLength: 2
          description: "ISO 3166-1 alpha-2. Defaults for language, currency and timezone are derived server-side when omitted."
        city:
          type: string
        address:
          type: string
        email:
          type: string
          format: email
        phone:
          type: string
        plan_id:
          type: integer
        language:
          type: string
        timezone:
          type: string
        currency:
          type: string
          description: "Optional. If omitted, derived from country, e.g. DZ/DZD, SN/XOF, CM/XAF, FR/EUR, TR/TRY."
        status:
          type: string
          enum: ["trial", "active"]
          default: "trial"
          description: "Initial platform status. Mobile platform admin can create a trial client or activate it immediately."
        notes:
          type: string
        manager_first_name:
          type: string
        manager_last_name:
          type: string
        manager_email:
          type: string
          format: email
        manager_phone:
          type: string

    PlatformCompanyCreateResponse:
      type: object
      properties:
        data:
          type: object
          properties:
            company:
              $ref: "#/components/schemas/PlatformCompany"
            manager:
              type: object
              properties:
                id:
                  type: integer
                email:
                  type: string
                  format: email
                role:
                  type: string
                manager_role:
                  type: string
                  nullable: true

    EmployeeIndexItem:
      type: object
      properties:
        id:
          type: integer
        first_name:
          type: string
        last_name:
          type: string
        email:
          type: string
          format: email
        role:
          type: string
        status:
          type: string
        hire_date:
          type: string
          format: date
          nullable: true
        salary_type:
          type: string
          enum: ["fixed", "hourly", "daily"]
          nullable: true
        salary_base:
          type: number
          format: float
          nullable: true
        schedule_id:
          type: integer
          nullable: true
        schedule:
          type: object
          nullable: true
          allOf:
            - $ref: "#/components/schemas/Schedule"
        hourly_rate:
          type: number
          format: float
          nullable: true
        currency:
          type: string
          nullable: true

    EmployeeDetail:
      type: object
      properties:
        id:
          type: integer
        first_name:
          type: string
        last_name:
          type: string
        email:
          type: string
          format: email
        role:
          type: string
        manager_role:
          type: string
          nullable: true
        status:
          type: string
        hire_date:
          type: string
          format: date
          nullable: true
        salary_type:
          type: string
          enum: ["fixed", "hourly", "daily"]
          nullable: true
        salary_base:
          type: number
          format: float
          nullable: true
        schedule_id:
          type: integer
          nullable: true
        schedule:
          type: object
          nullable: true
          allOf:
            - $ref: "#/components/schemas/Schedule"
        hourly_rate:
          type: number
          format: float
          nullable: true
        currency:
          type: string
          nullable: true
        phone:
          type: string
          nullable: true
        personal_email:
          type: string
          format: email
          nullable: true
        address_line:
          type: string
          nullable: true
        postal_code:
          type: string
          nullable: true
        emergency_contact_name:
          type: string
          nullable: true
        emergency_contact_phone:
          type: string
          nullable: true
        biometric_face_enabled:
          type: boolean
        biometric_fingerprint_enabled:
          type: boolean
        extra_data:
          type: object
          additionalProperties: true

    EmployeeIndexResponse:
      type: object
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/EmployeeIndexItem"
        meta:
          $ref: "#/components/schemas/PaginationMeta"

    Project:
      type: object
      properties:
        id:
          type: integer
        name:
          type: string
        description:
          type: string
          nullable: true
        start_date:
          type: string
          format: date
          nullable: true
        end_date:
          type: string
          format: date
          nullable: true
        members:
          type: array
          nullable: true
          items:
            type: integer
        status:
          type: string
          enum: ["active", "completed", "archived"]
        created_by:
          type: integer
        created_at:
          type: string
          format: date-time
          nullable: true

    ProjectIndexResponse:
      type: object
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/Project"
        meta:
          $ref: "#/components/schemas/PaginationMeta"

    StoreProjectRequest:
      type: object
      required: ["name"]
      properties:
        name:
          type: string
          maxLength: 150
        description:
          type: string
          nullable: true
        start_date:
          type: string
          format: date
          nullable: true
        end_date:
          type: string
          format: date
          nullable: true
        members:
          type: array
          nullable: true
          items:
            type: integer
            minimum: 1
        status:
          type: string
          nullable: true
          enum: ["active", "completed", "archived"]

    UpdateProjectRequest:
      type: object
      properties:
        name:
          type: string
          maxLength: 150
        description:
          type: string
          nullable: true
        start_date:
          type: string
          format: date
          nullable: true
        end_date:
          type: string
          format: date
          nullable: true
        members:
          type: array
          nullable: true
          items:
            type: integer
            minimum: 1
        status:
          type: string
          nullable: true
          enum: ["active", "completed", "archived"]

    TaskCommentAuthorMini:
      type: object
      properties:
        id:
          type: integer
        first_name:
          type: string
          nullable: true
        last_name:
          type: string
          nullable: true

    TaskComment:
      type: object
      properties:
        id:
          type: integer
        task_id:
          type: integer
        author_id:
          type: integer
        author:
          type: object
          allOf:
            - $ref: "#/components/schemas/TaskCommentAuthorMini"
          nullable: true
        content:
          type: string
        created_at:
          type: string
          format: date-time
          nullable: true

    Task:
      type: object
      properties:
        id:
          type: integer
        title:
          type: string
        description:
          type: string
          nullable: true
        created_by:
          type: integer
        assigned_to:
          type: array
          nullable: true
          items:
            type: integer
        project_id:
          type: integer
          nullable: true
        due_date:
          type: string
          format: date-time
          nullable: true
        priority:
          type: string
          enum: ["low", "normal", "high", "urgent"]
        estimated_minutes:
          type: integer
          nullable: true
        completed_minutes:
          type: integer
          nullable: true
        completed_at:
          type: string
          format: date-time
          nullable: true
        completion_note:
          type: string
          nullable: true
        performance_score:
          type: string
          nullable: true
        recurrence_rule:
          type: string
          nullable: true
        template_key:
          type: string
          nullable: true
        status:
          type: string
          enum: ["todo", "inprogress", "review", "done", "rejected", "cancelled"]
        category:
          type: string
          nullable: true
        visibility:
          type: string
          enum: ["private", "visible"]
        checklist:
          type: array
          nullable: true
          items: {}
        comments:
          type: array
          nullable: true
          items:
            $ref: "#/components/schemas/TaskComment"
        created_at:
          type: string
          format: date-time
          nullable: true
        updated_at:
          type: string
          format: date-time
          nullable: true

    TaskIndexResponse:
      type: object
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/Task"
        meta:
          $ref: "#/components/schemas/PaginationMeta"

    StoreTaskRequest:
      type: object
      required: ["title", "due_date"]
      properties:
        title:
          type: string
          maxLength: 200
        description:
          type: string
          nullable: true
        assigned_to:
          type: array
          nullable: true
          items:
            type: integer
            minimum: 1
        project_id:
          type: integer
          nullable: true
          minimum: 1
        due_date:
          type: string
          format: date
        priority:
          type: string
          nullable: true
          enum: ["low", "normal", "high", "urgent"]
        estimated_minutes:
          type: integer
          nullable: true
          minimum: 1
          maximum: 1440
        recurrence_rule:
          type: string
          nullable: true
          maxLength: 120
        template_key:
          type: string
          nullable: true
          maxLength: 100
        category:
          type: string
          nullable: true
          maxLength: 100
        visibility:
          type: string
          nullable: true
          enum: ["private", "visible"]
        checklist:
          type: array
          nullable: true
          items: {}

    UpdateTaskRequest:
      type: object
      properties:
        title:
          type: string
          maxLength: 200
        description:
          type: string
          nullable: true
        assigned_to:
          type: array
          items:
            type: integer
            minimum: 1
        project_id:
          type: integer
          nullable: true
          minimum: 1
        due_date:
          type: string
          format: date
        priority:
          type: string
          enum: ["low", "normal", "high", "urgent"]
        status:
          type: string
          enum: ["todo", "inprogress", "review", "done", "rejected", "cancelled"]
        estimated_minutes:
          type: integer
          nullable: true
          minimum: 1
          maximum: 1440
        completed_minutes:
          type: integer
          nullable: true
          minimum: 1
          maximum: 1440
        completion_note:
          type: string
          nullable: true
          maxLength: 1000
        recurrence_rule:
          type: string
          nullable: true
          maxLength: 120
        template_key:
          type: string
          nullable: true
          maxLength: 100
        category:
          type: string
          nullable: true
          maxLength: 100
        visibility:
          type: string
          enum: ["private", "visible"]
        checklist:
          type: array
          nullable: true
          items: {}

    StoreTaskCommentRequest:
      type: object
      required: ["content"]
      properties:
        content:
          type: string
          maxLength: 5000

    EvaluationEmployeeMini:
      type: object
      properties:
        id:
          type: integer
        first_name:
          type: string
          nullable: true
        last_name:
          type: string
          nullable: true
        email:
          type: string
          format: email
          nullable: true

    EvaluationEvaluatorMini:
      type: object
      properties:
        id:
          type: integer
        first_name:
          type: string
          nullable: true
        last_name:
          type: string
          nullable: true

    EvaluationCriterion:
      type: object
      required: ["label", "score"]
      properties:
        label:
          type: string
        score:
          type: number
          format: float

    Evaluation:
      type: object
      properties:
        id:
          type: integer
        employee_id:
          type: integer
        employee:
          type: object
          allOf:
            - $ref: "#/components/schemas/EvaluationEmployeeMini"
          nullable: true
        evaluator_id:
          type: integer
        evaluator:
          type: object
          allOf:
            - $ref: "#/components/schemas/EvaluationEvaluatorMini"
          nullable: true
        period:
          type: string
        score:
          type: number
          format: float
          nullable: true
        criteria:
          type: array
          nullable: true
          items:
            $ref: "#/components/schemas/EvaluationCriterion"
        strengths:
          type: string
          nullable: true
        improvements:
          type: string
          nullable: true
        overall_comment:
          type: string
          nullable: true
        status:
          type: string
          enum: ["draft", "submitted", "acknowledged"]
        acknowledged_at:
          type: string
          format: date-time
          nullable: true
        created_at:
          type: string
          format: date-time
          nullable: true
        updated_at:
          type: string
          format: date-time
          nullable: true

    EvaluationIndexResponse:
      type: object
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/Evaluation"
        meta:
          $ref: "#/components/schemas/PaginationMeta"

    StoreEvaluationRequest:
      type: object
      required: ["employee_id", "period"]
      properties:
        employee_id:
          type: integer
          minimum: 1
        period:
          type: string
          maxLength: 20
        score:
          type: number
          format: float
          nullable: true
          minimum: 0
          maximum: 5
        criteria:
          type: array
          nullable: true
          items:
            $ref: "#/components/schemas/EvaluationCriterion"
        strengths:
          type: string
          nullable: true
          maxLength: 2000
        improvements:
          type: string
          nullable: true
          maxLength: 2000
        overall_comment:
          type: string
          nullable: true
          maxLength: 2000

    UpdateEvaluationRequest:
      type: object
      properties:
        score:
          type: number
          format: float
          nullable: true
          minimum: 0
          maximum: 5
        criteria:
          type: array
          nullable: true
          items:
            $ref: "#/components/schemas/EvaluationCriterion"
        strengths:
          type: string
          nullable: true
          maxLength: 2000
        improvements:
          type: string
          nullable: true
          maxLength: 2000
        overall_comment:
          type: string
          nullable: true
          maxLength: 2000

    Notification:
      type: object
      properties:
        id:
          type: integer
        type:
          type: string
        title:
          type: string
        body:
          type: string
          nullable: true
        data:
          type: object
          nullable: true
          additionalProperties: true
        is_read:
          type: boolean
        read_at:
          type: string
          format: date-time
          nullable: true
        created_at:
          type: string
          format: date-time
          nullable: true

    NotificationIndexResponse:
      type: object
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/Notification"
        meta:
          type: object
          properties:
            current_page:
              type: integer
            last_page:
              type: integer
            per_page:
              type: integer
            total:
              type: integer
            unread_count:
              type: integer

    NotificationPreference:
      type: object
      properties:
        id:
          type: integer
        company_id:
          type: string
          format: uuid
        employee_id:
          type: integer
        app_enabled:
          type: boolean
        email_enabled:
          type: boolean
        push_enabled:
          type: boolean
        sms_enabled:
          type: boolean
        whatsapp_enabled:
          type: boolean
        whatsapp_consent_given:
          type: boolean
          description: PA2-COMM-008 - explicit opt-in required before WhatsApp Business messages are sent, distinct from whatsapp_enabled.
        whatsapp_consent_at:
          type: string
          format: date-time
          nullable: true
          description: Server-stamped timestamp of the last consent change; never accepted as client input.
        locale:
          type: string
          enum: [fr, ar, en, tr]
          nullable: true
        timezone:
          type: string
          nullable: true
        categories:
          type: object
          nullable: true
          additionalProperties:
            type: boolean
        quiet_hours:
          type: object
          nullable: true
          additionalProperties: true

    NotificationPreferenceUpdateRequest:
      type: object
      properties:
        app_enabled:
          type: boolean
        email_enabled:
          type: boolean
        push_enabled:
          type: boolean
        sms_enabled:
          type: boolean
        whatsapp_enabled:
          type: boolean
        whatsapp_consent_given:
          type: boolean
          description: PA2-COMM-008 - set true to give explicit WhatsApp opt-in consent, false to withdraw it. The server stamps/clears whatsapp_consent_at accordingly.
        locale:
          type: string
          enum: [fr, ar, en, tr]
          nullable: true
        timezone:
          type: string
          nullable: true
        categories:
          type: object
          additionalProperties:
            type: boolean
        quiet_hours:
          type: object
          properties:
            enabled:
              type: boolean
            start:
              type: string
              nullable: true
              example: "20:00"
            end:
              type: string
              nullable: true
              example: "07:00"

    PaginationMeta:
      type: object
      properties:
        current_page:
          type: integer
        last_page:
          type: integer
        per_page:
          type: integer
        total:
          type: integer

    StoreEmployeeRequest:
      type: object
      required: ["first_name", "last_name", "email"]
      properties:
        matricule:
          type: string
        first_name:
          type: string
        last_name:
          type: string
        email:
          type: string
          format: email
        password:
          type: string
          minLength: 8
        contract_start:
          type: string
          format: date
        schedule_id:
          type: integer
        salary_type:
          type: string
          enum: ["fixed", "hourly", "daily"]
        salary_base:
          type: number
          format: float
          minimum: 0
        hourly_rate:
          type: number
          format: float
          minimum: 0
        role:
          type: string
          enum: ["employee", "manager"]
        manager_role:
          type: string
          enum: ["principal", "rh", "dept", "comptable", "superviseur", "marketing"]
        phone:
          type: string
        personal_email:
          type: string
          format: email
        middle_name:
          type: string
        preferred_name:
          type: string
        date_of_birth:
          type: string
          format: date
        place_of_birth:
          type: string
        gender:
          type: string
          enum: ["M", "F"]
        nationality:
          type: string
        marital_status:
          type: string
        address_line:
          type: string
        postal_code:
          type: string
        emergency_contact_name:
          type: string
        emergency_contact_phone:
          type: string
        emergency_contact_relation:
          type: string
        biometric_face_enabled:
          type: boolean
        biometric_fingerprint_enabled:
          type: boolean
        biometric_face_reference_path:
          type: string
        biometric_fingerprint_reference_path:
          type: string
        photo_path:
          type: string
        zkteco_id:
          type: string
        extra_data:
          type: object
          additionalProperties: true
        send_invitation:
          type: boolean

    UpdateEmployeeRequestFull:
      type: object
      properties:
        matricule:
          type: string
        first_name:
          type: string
        last_name:
          type: string
        email:
          type: string
          format: email
        password:
          type: string
        contract_start:
          type: string
          format: date
        schedule_id:
          type: integer
          nullable: true
        salary_type:
          type: string
          enum: ["fixed", "hourly", "daily"]
        salary_base:
          type: number
          format: float
          minimum: 0
        hourly_rate:
          type: number
          format: float
          minimum: 0
        role:
          type: string
          enum: ["employee", "manager"]
        manager_role:
          type: string
          enum: ["principal", "rh", "dept", "comptable", "superviseur", "marketing"]
        status:
          type: string
          enum: ["active", "suspended"]
        phone:
          type: string
        personal_email:
          type: string
          format: email
        middle_name:
          type: string
        preferred_name:
          type: string
        date_of_birth:
          type: string
          format: date
        place_of_birth:
          type: string
        gender:
          type: string
          enum: ["M", "F"]
        nationality:
          type: string
        marital_status:
          type: string
        address_line:
          type: string
        postal_code:
          type: string
        emergency_contact_name:
          type: string
        emergency_contact_phone:
          type: string
        emergency_contact_relation:
          type: string
        biometric_face_enabled:
          type: boolean
        biometric_fingerprint_enabled:
          type: boolean
        biometric_face_reference_path:
          type: string
        biometric_fingerprint_reference_path:
          type: string
        photo_path:
          type: string
        zkteco_id:
          type: string
        extra_data:
          type: object
          additionalProperties: true

    DailySummary:
      type: object
      properties:
        employee_id:
          type: integer
        name:
          type: string
        date:
          type: string
          format: date
        check_in:
          type: string
          nullable: true
        check_out:
          type: string
          nullable: true
        sessions_count:
          type: integer
          description: "Nombre de sessions de pointage agregees sur la journee."
        hours_worked:
          type: number
        overtime_hours:
          type: number
        late_minutes:
          type: integer
        base_gain:
          type: number
        overtime_gain:
          type: number
        total_estimated:
          type: number
        currency:
          type: string
        status:
          type: string

    QuickEstimate:
      type: object
      properties:
        employee_id:
          type: integer
        name:
          type: string
        period:
          type: object
          properties:
            from:
              type: string
              format: date
            to:
              type: string
              format: date
            working_days:
              type: integer
            days_present:
              type: integer
            days_absent:
              type: integer
        totals:
          type: object
          properties:
            hours:
              type: number
            overtime_hours:
              type: number
            gross:
              type: number
            deductions:
              type: number
            net:
              type: number
        currency:
          type: string
        breakdown:
          type: array
          items:
            type: object
            properties:
              date:
                type: string
                format: date
              hours:
                type: number
              overtime_hours:
                type: number
              base_gain:
                type: number
              overtime_gain:
                type: number
              total:
                type: number
        disclaimer:
          type: string

    GpsPayload:
      type: object
      properties:
        gps_lat:
          type: number
        gps_lng:
          type: number
        gps_accuracy:
          type: number
          nullable: true
          description: "Approximate mobile GPS accuracy in meters."
        device_timezone:
          type: string
          description: "Timezone or UTC offset reported by the mobile device."
        work_type:
          type: string
          enum: ["normal", "overtime", "break", "resume", "mission", "travel", "training", "other"]
        punch_note:
          type: string
          nullable: true

    AttendanceLog:
      type: object
      properties:
        id:
          type: integer
        employee_id:
          type: integer
        date:
          type: string
          format: date
        check_in:
          type: string
          format: date-time
          nullable: true
        check_out:
          type: string
          format: date-time
          nullable: true
        check_in_local:
          type: string
          format: date-time
          nullable: true
        check_out_local:
          type: string
          format: date-time
          nullable: true
        timezone:
          type: string
        device_timezone:
          type: string
          nullable: true
        method:
          type: string
        work_type:
          type: string
        punch_note:
          type: string
          nullable: true
        punch_meta:
          type: object
          nullable: true
        punch_photo_url:
          type: string
          nullable: true
          description: "URL de telechargement de la photo de pointage, si une photo a ete fournie."
        source_device_code:
          type: string
          nullable: true
        hours_worked:
          type: number
          nullable: true
        overtime_hours:
          type: number
          nullable: true
        status:
          type: string
        late_minutes:
          type: integer
          nullable: true
        gps:
          type: object
          properties:
            lat:
              type: number
              nullable: true
            lng:
              type: number
              nullable: true
            accuracy_m:
              type: number
              nullable: true
        geofence:
          type: object
          nullable: true
          properties:
            configured:
              type: boolean
            inside:
              type: boolean
              nullable: true
            distance_meters:
              type: integer
              nullable: true
            radius_meters:
              type: integer
              nullable: true
            source:
              type: string
              nullable: true

    AttendanceCorrectionRequestInput:
      type: object
      required:
        - date
        - requested_check_in
        - reason
      properties:
        attendance_log_id:
          type: integer
          nullable: true
        date:
          type: string
          format: date
        requested_check_in:
          type: string
          format: date-time
        requested_check_out:
          type: string
          format: date-time
          nullable: true
        reason:
          type: string
          maxLength: 500

    AttendanceCorrectionRequest:
      type: object
      properties:
        id:
          type: integer
        company_id:
          type: string
          format: uuid
        employee_id:
          type: integer
        attendance_log_id:
          type: integer
          nullable: true
        employee:
          type: object
          nullable: true
          properties:
            id:
              type: integer
            name:
              type: string
            matricule:
              type: string
              nullable: true
        status:
          type: string
          enum: ["pending", "approved", "rejected", "applied"]
        date:
          type: string
          format: date
        requested_check_in:
          type: string
          format: date-time
        requested_check_out:
          type: string
          format: date-time
          nullable: true
        reason:
          type: string
        reviewed_by:
          type: integer
          nullable: true
        reviewed_at:
          type: string
          format: date-time
          nullable: true

    AttendanceTodayItem:
      type: object
      properties:
        employee_id:
          type: integer
        name:
          type: string
        checked_in:
          type: boolean
        check_in_time:
          type: string
          nullable: true
        check_out_time:
          type: string
          nullable: true
        hours_worked:
          oneOf:
            - type: string
            - type: number
        status:
          type: string

    AttendanceTodayEnvelope:
      type: object
      properties:
        data:
          oneOf:
            - type: object
              properties:
                mode:
                  type: string
                  example: "single"
                item:
                  $ref: "#/components/schemas/AttendanceTodayItem"
            - type: object
              properties:
                mode:
                  type: string
                  example: "collection"
                items:
                  type: array
                  items:
                    $ref: "#/components/schemas/AttendanceTodayItem"
                meta:
                  $ref: "#/components/schemas/PaginationMeta"

    AttendanceIndexResponse:
      type: object
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/AttendanceLog"
        meta:
          $ref: "#/components/schemas/PaginationMeta"

    Invitation:
      type: object
      properties:
        id:
          type: string
          format: uuid
        email:
          type: string
          format: email
        role:
          type: string
        manager_role:
          type: string
          nullable: true
        employee_id:
          type: integer
        expires_at:
          type: string
          format: date-time
          nullable: true
        accepted_at:
          type: string
          format: date-time
          nullable: true
        last_sent_at:
          type: string
          format: date-time
          nullable: true
        status:
          type: string
          enum: ["pending", "accepted", "expired"]

    BiometricEnrollmentRequest:
      type: object
      properties:
        id:
          type: integer
        employee_id:
          type: integer
        status:
          type: string
          enum: ["pending", "approved", "rejected"]
        requested_face_enabled:
          type: boolean
        requested_fingerprint_enabled:
          type: boolean
        requested_face_reference_path:
          type: string
          nullable: true
        requested_fingerprint_reference_path:
          type: string
          nullable: true
        requested_fingerprint_device_id:
          type: string
          nullable: true
        employee_note:
          type: string
          nullable: true
        manager_note:
          type: string
          nullable: true
        submitted_at:
          type: string
          format: date-time
          nullable: true
        approved_at:
          type: string
          format: date-time
          nullable: true
        rejected_at:
          type: string
          format: date-time
          nullable: true

    BiometricEnrollmentSubmitRequest:
      type: object
      properties:
        requested_face_enabled:
          type: boolean
        requested_fingerprint_enabled:
          type: boolean
        requested_fingerprint_reference_path:
          type: string
        requested_fingerprint_device_id:
          type: string
        employee_note:
          type: string
        face_image:
          type: string
          format: binary

    BiometricDecisionRequest:
      type: object
      properties:
        manager_note:
          type: string

    KioskRegisterRequest:
      type: object
      required: ["name"]
      properties:
        name:
          type: string
        location_label:
          type: string
        biometric_mode:
          type: string
          enum: ["fingerprint", "face", "mixed"]
        trusted_device_label:
          type: string

    Kiosk:
      type: object
      properties:
        id:
          type: integer
        name:
          type: string
        location_label:
          type: string
          nullable: true
        device_code:
          type: string
        status:
          type: string
        biometric_mode:
          type: string
        trusted_device_label:
          type: string
          nullable: true
        sync_token:
          type: string

    KioskRosterEmployee:
      type: object
      properties:
        employee_id:
          type: integer
        name:
          type: string
        email:
          type: string
          format: email
        matricule:
          type: string
          nullable: true
        zkteco_id:
          type: string
          nullable: true
        face_enabled:
          type: boolean
        fingerprint_enabled:
          type: boolean

    KioskRosterResponse:
      type: object
      properties:
        data:
          type: object
          properties:
            device_code:
              type: string
            company_id:
              type: string
              format: uuid
            company_name:
              type: string
            employees:
              type: array
              items:
                $ref: "#/components/schemas/KioskRosterEmployee"

    KioskPunchRequest:
      type: object
      required: ["identifier"]
      properties:
        identifier:
          type: string
        action:
          type: string
          enum: ["check_in", "check_out"]

    KioskPunchResponse:
      type: object
      properties:
        data:
          type: object
          properties:
            employee_id:
              type: integer
            date:
              type: string
              format: date
            check_in:
              type: string
              format: date-time
              nullable: true
            check_out:
              type: string
              format: date-time
              nullable: true
            method:
              type: string
            status:
              type: string

    KioskSyncEvent:
      type: object
      required: ["identifier"]
      properties:
        identifier:
          type: string
        action:
          type: string
          enum: ["check_in", "check_out"]
        occurred_at:
          type: string
          format: date-time
        external_event_id:
          type: string
        biometric_type:
          type: string
          enum: ["fingerprint", "face", "mixed"]

    KioskSyncRequest:
      type: object
      required: ["events"]
      properties:
        events:
          type: array
          items:
            $ref: "#/components/schemas/KioskSyncEvent"

    KioskSyncResponse:
      type: object
      properties:
        data:
          type: object
          properties:
            processed_count:
              type: integer
            processed_log_ids:
              type: array
              items:
                type: integer
            skipped_count:
              type: integer
              description: >-
                Nombre d'événements refusés (#3587) : le bridge kiosk les
                isole en dead-letter au lieu de les marquer synchronisés.
            skipped:
              type: array
              description: Détail des événements refusés (contrat #3587).
              items:
                type: object
                properties:
                  external_event_id:
                    type: string
                    nullable: true
                  identifier:
                    type: string
                  reason:
                    type: string
                    enum: ["IDENTIFIER_REQUIRED", "EMPLOYEE_NOT_FOUND", "BIOMETRIC_NOT_APPROVED", "NO_OPEN_SESSION"]
            last_sync_at:
              type: string
              format: date-time
              nullable: true

    StoreCameraRequest:
      type: object
      required: ["name", "rtsp_url"]
      properties:
        name:
          type: string
        rtsp_url:
          type: string
          example: "rtsp://camera.local/live"
        location:
          type: string
        sort_order:
          type: integer
        stream_path_override:
          type: string
        metadata:
          type: object
          additionalProperties: true

    UpdateCameraRequest:
      type: object
      properties:
        name:
          type: string
        rtsp_url:
          type: string
        location:
          type: string
        is_active:
          type: boolean
        sort_order:
          type: integer
        stream_path_override:
          type: string
        metadata:
          type: object
          additionalProperties: true

    Camera:
      type: object
      properties:
        id:
          type: integer
        name:
          type: string
        location:
          type: string
          nullable: true
        is_active:
          type: boolean
        sort_order:
          type: integer
        thumbnail_url:
          type: string
          nullable: true
        stream_url:
          type: string
        stream_token:
          type: string
        token_expires_at:
          type: string
          format: date-time
        created_at:
          type: string
          format: date-time
          nullable: true

    CameraIndexResponse:
      type: object
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/Camera"
        plan_limit:
          type: object
          properties:
            max_cameras:
              type: integer
              nullable: true
            current_count:
              type: integer

    TestRtspRequest:
      type: object
      required: ["rtsp_url"]
      properties:
        rtsp_url:
          type: string

    TestRtspSuccessResponse:
      type: object
      properties:
        data:
          type: object
          properties:
            ok:
              type: boolean
            duration_ms:
              type: integer
              nullable: true
            skipped:
              type: boolean
              nullable: true

    StoreCameraAccessTokenRequest:
      type: object
      required: ["expires_in_minutes"]
      properties:
        label:
          type: string
        granted_to_email:
          type: string
          format: email
        granted_to_name:
          type: string
        expires_in_minutes:
          type: integer
        permissions:
          type: object
          properties:
            view:
              type: boolean
            ptz:
              type: boolean
            audio:
              type: boolean
        ip_whitelist:
          type: array
          items:
            type: string

    CameraAccessToken:
      type: object
      properties:
        id:
          type: integer
        camera_id:
          type: integer
        label:
          type: string
          nullable: true
        granted_to_email:
          type: string
          format: email
          nullable: true
        granted_to_name:
          type: string
          nullable: true
        granted_by:
          type: integer
        permissions:
          type: object
          additionalProperties: true
        expires_at:
          type: string
          format: date-time
          nullable: true
        last_used_at:
          type: string
          format: date-time
          nullable: true
        use_count:
          type: integer
        is_revoked:
          type: boolean
        created_at:
          type: string
          format: date-time
          nullable: true

    CameraAccessTokenWithSecret:
      allOf:
        - $ref: "#/components/schemas/CameraAccessToken"
        - type: object
          properties:
            token:
              type: string
            share_url:
              type: string
              nullable: true

    StoreCameraPermissionRequest:
      type: object
      required: ["employee_id"]
      properties:
        employee_id:
          type: integer
        can_view:
          type: boolean
        can_share:
          type: boolean
        can_manage:
          type: boolean
        expires_at:
          type: string
          format: date-time

    CameraPermission:
      type: object
      properties:
        id:
          type: integer
        camera_id:
          type: integer
        employee_id:
          type: integer
        can_view:
          type: boolean
        can_share:
          type: boolean
        can_manage:
          type: boolean
        granted_by:
          type: integer
        granted_at:
          type: string
          format: date-time
          nullable: true
        expires_at:
          type: string
          format: date-time
          nullable: true

    CameraAccessLog:
      type: object
      properties:
        id:
          type: integer
        camera_id:
          type: integer
        employee_id:
          type: integer
          nullable: true
        access_token_id:
          type: integer
          nullable: true
        actor_type:
          type: string
        action:
          type: string
        reason:
          type: string
          nullable: true
        ip_address:
          type: string
          nullable: true
        metadata:
          type: object
          nullable: true
          additionalProperties: true
        created_at:
          type: string
          format: date-time
          nullable: true

    CameraAccessLogIndexResponse:
      type: object
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/CameraAccessLog"
        meta:
          type: object
          properties:
            current_page:
              type: integer
            per_page:
              type: integer
            total:
              type: integer
            window_days:
              type: integer

    InternalCameraVerifyResponse:
      type: object
      properties:
        allowed:
          type: boolean
        reason:
          type: string
          nullable: true
        company_id:
          type: string
          format: uuid
          nullable: true
        type:
          type: string
          nullable: true
        camera_id:
          type: integer
          nullable: true

    PublicCameraView:
      type: object
      properties:
        camera:
          type: object
          properties:
            id:
              type: integer
            name:
              type: string
            location:
              type: string
              nullable: true
        stream_url:
          type: string
        stream_token:
          type: string
        expires_at:
          type: string
          format: date-time
          nullable: true
        permissions:
          type: object
          additionalProperties: true
        label:
          type: string
          nullable: true
        granted_to_name:
          type: string
          nullable: true

    # =============================================
    # Plan 33 — New Schemas
    # =============================================

    Absence:
      type: object
      properties:
        id:
          type: integer
        company_id:
          type: integer
        employee_id:
          type: integer
        absence_type_id:
          type: integer
        start_date:
          type: string
          format: date
        end_date:
          type: string
          format: date
        days_count:
          type: number
        status:
          type: string
          enum: [pending, approved, rejected, cancelled]
        reason:
          type: string
          nullable: true
        approved_by:
          type: integer
          nullable: true
        rejected_reason:
          type: string
          nullable: true
        absence_type:
          type: object
          properties:
            id:
              type: integer
            name:
              type: string
            code:
              type: string
            deducts_leave:
              type: boolean
        employee:
          type: object
          properties:
            id:
              type: integer
            first_name:
              type: string
            last_name:
              type: string
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time

    StoreAbsenceRequest:
      type: object
      required: [absence_type_id, start_date, end_date]
      properties:
        absence_type_id:
          type: integer
        start_date:
          type: string
          format: date
        end_date:
          type: string
          format: date
        reason:
          type: string
          nullable: true

    SalaryAdvance:
      type: object
      properties:
        id:
          type: integer
        employee_id:
          type: integer
        company_id:
          type: string
        company_name:
          type: string
          nullable: true
        amount:
          type: number
        status:
          type: string
          enum: [pending, approved, active, rejected, cancelled, repaid]
        validation_status:
          type: string
          nullable: true
          enum: [pending, manager_approved, payment_declared, employee_confirmed, rejected]
        reason:
          type: string
          nullable: true
        decision_comment:
          type: string
          nullable: true
        approved_by:
          type: integer
          nullable: true
        manager_approved_at:
          type: string
          format: date-time
          nullable: true
        payment_declared_at:
          type: string
          format: date-time
          nullable: true
        payment_reference:
          type: string
          nullable: true
        payment_note:
          type: string
          nullable: true
        employee_confirmed_at:
          type: string
          format: date-time
          nullable: true
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time

    StoreSalaryAdvanceRequest:
      type: object
      required: [amount]
      properties:
        amount:
          type: number
          minimum: 0
        reason:
          type: string
          nullable: true

    LeavePolicy:
      type: object
      properties:
        id:
          type: integer
        company_id:
          type: integer
        name:
          type: string
        leave_type:
          type: string
        days_per_year:
          type: number
        carry_over_days:
          type: integer
        carry_over_expiry_months:
          type: integer
          nullable: true
        accrual_method:
          type: string
          enum: [annual, monthly, biweekly]
        requires_approval:
          type: boolean
        min_days_advance:
          type: integer
        max_consecutive_days:
          type: integer
          nullable: true
        applicable_from:
          type: string
          format: date
          nullable: true
        created_at:
          type: string
          format: date-time

    StoreLeavePolicyRequest:
      type: object
      required: [name, leave_type, days_per_year, accrual_method]
      properties:
        name:
          type: string
          maxLength: 100
        leave_type:
          type: string
        days_per_year:
          type: number
          minimum: 0
        carry_over_days:
          type: integer
          default: 0
        carry_over_expiry_months:
          type: integer
          nullable: true
        accrual_method:
          type: string
          enum: [annual, monthly, biweekly]
        requires_approval:
          type: boolean
          default: true
        min_days_advance:
          type: integer
          default: 0
        max_consecutive_days:
          type: integer
          nullable: true
        applicable_from:
          type: string
          format: date
          nullable: true

    LeaveBalance:
      type: object
      properties:
        id:
          type: integer
        employee_id:
          type: integer
        leave_policy_id:
          type: integer
        total_days:
          type: number
        used_days:
          type: number
        remaining_days:
          type: number
        carried_over:
          type: number
        year:
          type: integer
        leave_policy:
          $ref: "#/components/schemas/LeavePolicy"

    LeaveAccrual:
      type: object
      properties:
        id:
          type: integer
        leave_policy_id:
          type: integer
        employee_id:
          type: integer
        days:
          type: number
        accrual_date:
          type: string
          format: date
        created_at:
          type: string
          format: date-time

    StoreLeaveAccrualRequest:
      type: object
      required: [leave_policy_id, employee_id, days, accrual_date]
      properties:
        leave_policy_id:
          type: integer
        employee_id:
          type: integer
        days:
          type: number
        accrual_date:
          type: string
          format: date

    Contract:
      type: object
      properties:
        id:
          type: integer
        company_id:
          type: integer
        employee_id:
          type: integer
        contract_type:
          type: string
          enum: [cdi, cdd, stage, freelance, interim]
        reference:
          type: string
          nullable: true
        start_date:
          type: string
          format: date
        end_date:
          type: string
          format: date
          nullable: true
        job_title:
          type: string
          nullable: true
        department_id:
          type: integer
          nullable: true
        position_id:
          type: integer
          nullable: true
        base_salary:
          type: number
        currency:
          type: string
          nullable: true
        salary_frequency:
          type: string
          enum: [monthly, hourly, daily]
          nullable: true
        work_hours_per_week:
          type: number
          nullable: true
        probation_end_date:
          type: string
          format: date
          nullable: true
        benefits:
          type: object
          nullable: true
        clauses:
          type: object
          nullable: true
        status:
          type: string
          enum: [draft, active, suspended, terminated, expired]
        signed_at:
          type: string
          format: date-time
          nullable: true
        terminated_at:
          type: string
          format: date-time
          nullable: true
        termination_reason:
          type: string
          nullable: true
        created_by:
          type: integer
        employee:
          type: object
          properties:
            id:
              type: integer
            first_name:
              type: string
            last_name:
              type: string
        department:
          type: object
          properties:
            id:
              type: integer
            name:
              type: string
          nullable: true
        position:
          type: object
          properties:
            id:
              type: integer
            name:
              type: string
          nullable: true
        created_at:
          type: string
          format: date-time

    StoreContractRequest:
      type: object
      required: [employee_id, contract_type, start_date, base_salary]
      properties:
        employee_id:
          type: integer
        contract_type:
          type: string
          enum: [cdi, cdd, stage, freelance, interim]
        reference:
          type: string
          maxLength: 50
          nullable: true
        start_date:
          type: string
          format: date
        end_date:
          type: string
          format: date
          nullable: true
        job_title:
          type: string
          maxLength: 150
          nullable: true
        department_id:
          type: integer
          nullable: true
        position_id:
          type: integer
          nullable: true
        base_salary:
          type: number
          minimum: 0
        currency:
          type: string
          maxLength: 3
          nullable: true
        salary_frequency:
          type: string
          enum: [monthly, hourly, daily]
          nullable: true
        work_hours_per_week:
          type: number
          minimum: 0
          maximum: 168
          nullable: true
        probation_end_date:
          type: string
          format: date
          nullable: true
        benefits:
          type: object
          nullable: true
        clauses:
          type: object
          nullable: true

    UpdateContractRequest:
      type: object
      properties:
        contract_type:
          type: string
          enum: [cdi, cdd, stage, freelance, interim]
        reference:
          type: string
          maxLength: 50
          nullable: true
        end_date:
          type: string
          format: date
          nullable: true
        job_title:
          type: string
          maxLength: 150
          nullable: true
        department_id:
          type: integer
          nullable: true
        position_id:
          type: integer
          nullable: true
        base_salary:
          type: number
          minimum: 0
        currency:
          type: string
          maxLength: 3
          nullable: true
        salary_frequency:
          type: string
          enum: [monthly, hourly, daily]
          nullable: true
        work_hours_per_week:
          type: number
          nullable: true
        probation_end_date:
          type: string
          format: date
          nullable: true
        benefits:
          type: object
          nullable: true
        clauses:
          type: object
          nullable: true
        status:
          type: string
          enum: [draft, active, suspended, terminated]
        termination_reason:
          type: string
          nullable: true

    ContractAmendment:
      type: object
      properties:
        id:
          type: integer
        contract_id:
          type: integer
        company_id:
          type: integer
        amendment_type:
          type: string
          enum: [salary_change, position_change, hours_change, renewal, other]
        changes:
          type: object
        effective_date:
          type: string
          format: date
        reason:
          type: string
          nullable: true
        approved_by:
          type: integer
        created_at:
          type: string
          format: date-time

    Position:
      type: object
      properties:
        id:
          type: integer
        name:
          type: string
        description:
          type: string
          nullable: true
        department_id:
          type: integer
          nullable: true
        created_at:
          type: string
          format: date-time

    Site:
      type: object
      properties:
        id:
          type: integer
        name:
          type: string
        address:
          type: string
          nullable: true
        city:
          type: string
          nullable: true
        country:
          type: string
          nullable: true
        latitude:
          type: number
          nullable: true
        longitude:
          type: number
          nullable: true
        geofence_radius:
          type: integer
          nullable: true
        created_at:
          type: string
          format: date-time

    ScheduleSimple:
      type: object
      properties:
        id:
          type: integer
        name:
          type: string
        type:
          type: string
          enum: [fixed, rotating, flexible]
        slots:
          type: array
          items:
            type: object
        created_at:
          type: string
          format: date-time

    Compliance:
      type: object
      description: >-
        Issue #1872/#2116 — niveau de confiance des règles pays appliquées
        à un calcul ou un bulletin. `level` : production | pilot | placeholder
        | unknown. `warning_key` pointe le catalogue i18n
        `payroll.compliance_warning_{level}` ; `source` référence la fiche
        compliance pays ; `verification_date` (nullable) = date de la dernière
        validation experte (registre docs/payroll/VALIDATION_EXPERTE.md).
      properties:
        level:
          type: string
          enum: [production, pilot, placeholder, unknown]
        warning:
          type: string
        warning_key:
          type: string
          example: payroll.compliance_warning_pilot
        source:
          type: string
          example: docs/payroll/CI_COMPLIANCE.md
        verification_date:
          type: string
          format: date
          nullable: true

    PaySlip:
      type: object
      properties:
        id:
          type: integer
        employee_id:
          type: integer
        payroll_run_id:
          type: integer
        period_month:
          type: integer
        period_year:
          type: integer
        period_start:
          type: string
          format: date
        period_end:
          type: string
          format: date
        period:
          type: string
          description: Libellé court `YYYY-MM` du bulletin (champ calculé, issue #2116)
          example: "2026-05"
        employee_name:
          type: string
          nullable: true
          description: Nom complet calculé `prénom nom` (champ calculé, issue #2116)
        country_code:
          type: string
          nullable: true
          description: Pays du run de paie (source du bloc compliance)
          example: CI
        compliance:
          type: object
          allOf:
            - $ref: "#/components/schemas/Compliance"
          nullable: true
          description: >-
            Bloc conformité (issue #2116) — null quand le pays du run n'est
            pas supporté par le moteur (rétro-compatible : aucun affichage
            côté client)
        gross_salary:
          type: number
        net_salary:
          type: number
        currency:
          type: string
          example: XOF
        total_deductions:
          type: number
        total_contributions:
          type: number
        lines:
          type: array
          items:
            type: object
        created_at:
          type: string
          format: date-time

    PayrollBalance:
      type: object
      properties:
        employee_id:
          type: integer
        employee_name:
          type: string
        compliance:
          type: object
          nullable: true
          description: >-
            Issue #2143/#1872 — bloc de conformité paie du pays (niveau,
            avertissement localisé, source légale, date de vérification
            experte). Null quand le pays n'est pas supporté par le moteur
            (rétro-compatible : aucun affichage côté client).
          properties:
            level:
              type: string
              enum: [production, pilot, placeholder, unknown]
            warning:
              type: string
            warning_key:
              type: string
              example: payroll.compliance_warning_pilot
            source:
              type: string
              example: docs/payroll/DZ_COMPLIANCE.md
            verification_date:
              type: string
              format: date
              nullable: true
        period:
          type: object
          properties:
            start:
              type: string
              format: date
            end:
              type: string
              format: date
            label:
              type: string
            cycle:
              type: string
              enum: [daily, weekly, monthly]
        currency:
          type: string
          example: DZD
        gross_due:
          type: number
          format: float
        advances:
          type: number
          format: float
        paid:
          type: number
          format: float
        remaining:
          type: number
          format: float
        overtime_hours:
          type: number
          format: float
          description: "PA2-PAY-010 - heures supplementaires enregistrees pour le cycle courant (agregation attendance_logs.overtime_hours)."
        overtime_pay:
          type: number
          format: float
          description: "PA2-PAY-010 - estimation du paiement des heures supplementaires (taux horaire estime x1.5), a titre indicatif pour le dashboard manager."
        pay_slip:
          type: object
          properties:
            id:
              type: integer
              nullable: true
            status:
              type: string
              nullable: true
            payroll_run_id:
              type: integer
              nullable: true

    BankExport:
      type: object
      properties:
        id:
          type: integer
        company_id:
          type: string
          format: uuid
        payroll_run_id:
          type: integer
        format:
          type: string
          enum: [sepa_xml, ccp_dz, virement_ma, csv_generic]
        status:
          type: string
          enum: [pending, generating, generated, failed, sent, confirmed]
        file_path:
          type: string
          nullable: true
        error_message:
          type: string
          nullable: true
        total_amount:
          type: number
        transfer_count:
          type: integer
        generated_at:
          type: string
          format: date-time
          nullable: true
        created_at:
          type: string
          format: date-time

    PaymentDocument:
      type: object
      properties:
        id:
          type: integer
        company_id:
          type: string
          format: uuid
        employee_id:
          type: integer
          nullable: true
        payroll_run_id:
          type: integer
          nullable: true
        pay_slip_id:
          type: integer
          nullable: true
        salary_advance_id:
          type: integer
          nullable: true
        document_type:
          type: string
          enum: [payment_receipt, payment_slip, advance_receipt, payroll_summary]
        status:
          type: string
          enum: [pending, generating, available, failed]
        filename:
          type: string
          nullable: true
        mime_type:
          type: string
          example: application/pdf
        size_bytes:
          type: integer
          nullable: true
        is_downloadable:
          type: boolean
        metadata:
          type: object
          additionalProperties: true
        generated_at:
          type: string
          format: date-time
          nullable: true
        created_at:
          type: string
          format: date-time
          nullable: true

    PaymentBatch:
      type: object
      properties:
        id:
          type: integer
        company_id:
          type: string
          format: uuid
        payroll_run_id:
          type: integer
          nullable: true
        period_start:
          type: string
          format: date
          nullable: true
        period_end:
          type: string
          format: date
          nullable: true
        status:
          type: string
          enum: [draft, processing, paid, partially_confirmed, confirmed, failed]
        total_amount:
          type: number
        currency:
          type: string
        items_count:
          type: integer
        marked_paid_at:
          type: string
          format: date-time
          nullable: true
        confirmed_at:
          type: string
          format: date-time
          nullable: true
        items:
          type: array
          items:
            $ref: "#/components/schemas/PaymentItem"

    PaymentItem:
      type: object
      properties:
        id:
          type: integer
        employee_id:
          type: integer
        employee_name:
          type: string
          nullable: true
        pay_slip_id:
          type: integer
          nullable: true
        amount:
          type: number
        currency:
          type: string
        status:
          type: string
          enum: [pending, paid, confirmed]
        paid_at:
          type: string
          format: date-time
          nullable: true
        confirmed_at:
          type: string
          format: date-time
          nullable: true

    PayrollRun:
      type: object
      properties:
        id:
          type: integer
        company_id:
          type: integer
        country_code:
          type: string
          description: ISO-3166 country context used by the payroll rules.
        correlation_id:
          type: string
          format: uuid
          nullable: true
          description: "Issue #1874 — identifiant de corrélation du run (logs ↔ réponse ↔ audit)."
        rules_version:
          type: string
          nullable: true
          description: Stable fingerprint of the effective country rules used for calculation.
        rules_period:
          type: string
          format: date
          nullable: true
          description: Effective date used to resolve the country rules.
        rules_identifier:
          type: string
          nullable: true
          description: Country rules implementation identifier.
        period_month:
          type: integer
        period_year:
          type: integer
        label:
          type: string
          nullable: true
        status:
          type: string
          enum: [draft, calculating, validated, cancelled, locked, paid, error]
        type:
          type: string
          enum: [standard, regularization]
        original_run_id:
          type: integer
          nullable: true
        reason:
          type: string
          nullable: true
        total_gross:
          type: number
          nullable: true
        total_net:
          type: number
          nullable: true
        employee_count:
          type: integer
          nullable: true
        calculated_at:
          type: string
          format: date-time
          nullable: true
        validated_at:
          type: string
          format: date-time
          nullable: true
        created_at:
          type: string
          format: date-time

    PayrollCalculationAudit:
      type: object
      description: "Issue #1874 — enregistrement d'audit immuable d'un calcul de paie (run ou simulation) : contexte de règles (pays, période, version), paramètres d'entrée agrégés NON sensibles, résultats agrégés et statut de résolution — de quoi expliquer/reproduire/auditer un résultat sans exposer de données individuelles ni de secrets."
      properties:
        id: { type: integer }
        correlation_id: { type: string, format: uuid }
        company_id: { type: string, format: uuid, nullable: true, description: "NULL pour les calculs plateforme (simulation super-admin sur règles nationales)." }
        actor:
          type: object
          properties:
            type: { type: string, enum: [user, job] }
            id: { type: integer, nullable: true }
        country_code: { type: string }
        period_start: { type: string, format: date, nullable: true }
        period_end: { type: string, format: date, nullable: true }
        rules_version:
          type: string
          nullable: true
          description: "Empreinte stable des règles effectives (permet de reproduire le calcul à l'identique)."
        rules_identifier:
          type: string
          nullable: true
          description: "Implémentation des règles pays appliquée."
        input:
          type: object
          nullable: true
          description: "Paramètres agrégés non sensibles (ex. nombre d'employés, masse salariale brute) — jamais de salaires individuels ni de secrets."
          additionalProperties: true
        result:
          type: object
          nullable: true
          description: "Résultats agrégés (net total, coût employeur total…) — null en cas d'échec."
          additionalProperties: true
        status:
          type: string
          enum: [success, validation_error, rule_missing, provider_error, fallback_forbidden]
        error_message:
          type: string
          nullable: true
          description: "Classe d'erreur uniquement (jamais de message brut ni de secret)."
        created_at: { type: string, format: date-time }

    SalaryStructure:
      type: object
      properties:
        id:
          type: integer
        name:
          type: string
        description:
          type: string
          nullable: true
        base_salary:
          type: number
        currency:
          type: string
        is_active:
          type: boolean
        components:
          type: array
          items:
            $ref: "#/components/schemas/SalaryComponent"
          description: "Present seulement quand la relation components est chargee (GET /salary-structures/{id})"
        created_at:
          type: string
          format: date-time

    SalaryComponent:
      type: object
      properties:
        id:
          type: integer
        name:
          type: string
        code:
          type: string
        type:
          type: string
          enum: [earning, deduction, employer_contribution]
        amount:
          type: number
          nullable: true
        percentage:
          type: number
          nullable: true
        is_taxable:
          type: boolean
        is_active:
          type: boolean
        created_at:
          type: string
          format: date-time

    TaxRateChangeLog:
      type: object
      description: "Entrée de l'audit trail immuable (tax_rate_change_log) retournée par GET /tax-slabs/{id}/history et /social-contributions/{id}/history — aligné sur la réponse réelle des contrôleurs (issue #1926)."
      properties:
        id: { type: integer }
        action: { type: string, enum: [created, submitted, approved, rejected, superseded] }
        actor_id: { type: integer }
        actor_role: { type: string }
        previous_value: { type: object, nullable: true }
        new_value: { type: object }
        reason: { type: string, nullable: true }
        created_at: { type: string, format: date-time }
    RateValidationItem:
      type: object
      properties:
        id: { type: integer }
        table: { type: string, enum: [tax_slabs, social_contributions] }
        company_id: { type: string, nullable: true }
        country_code: { type: string }
        name: { type: string }
        rate: { type: number }
        status: { type: string, enum: [draft, pending_validation, active, superseded] }
        submitted_by: { type: integer, nullable: true }
        validated_by: { type: integer, nullable: true }
        validated_at: { type: string, nullable: true }
        rejection_reason: { type: string, nullable: true }
        effective_from: { type: string, format: date }
        effective_to: { type: string, nullable: true }
    PayrollSimulationInput:
      type: object
      required: [country_code, gross_salary]
      properties:
        country_code: { type: string }
        gross_salary: { type: number, minimum: 0 }
        acknowledge_placeholder: { type: boolean, description: "Confirmation explicite requise pour les pays en règle placeholder (issue #1872)" }
        slabs_override:
          type: array
          items:
            type: object
            required: [min, rate]
            properties:
              min: { type: number, minimum: 0 }
              max: { type: number, nullable: true }
              rate: { type: number, minimum: 0, maximum: 100 }
              fixed_deduction: { type: number, minimum: 0 }
    PayrollSimulationResult:
      type: object
      properties:
        correlation_id:
          type: string
          format: uuid
          description: "Issue #1874 — identifiant de corrélation de la requête (logs ↔ réponse ↔ audit)."
        gross: { type: number }
        country_code: { type: string }
        social_employee: { type: number }
        social_employer: { type: number }
        tax_base: { type: number }
        income_tax: { type: number }
        income_tax_by_slab:
          type: array
          items:
            type: object
            properties:
              min: { type: number }
              max: { type: number, nullable: true }
              rate: { type: number }
              taxable_amount: { type: number }
              tax: { type: number }
        net: { type: number }
        total_cost: { type: number }
    TrainingEnrollment:
      type: object
      description: "Inscription a une session de formation (TrainingEnrollmentResource) — liste globale GET /training/enrollments."
      properties:
        id: { type: integer }
        training_session_id: { type: integer }
        employee_id: { type: integer }
        status: { type: string }
        score: { type: number, nullable: true }
        feedback: { type: string, nullable: true }
        completed_at: { type: string, format: date-time, nullable: true }
        course_title: { type: string, nullable: true }
        session_date: { type: string, nullable: true }
        progress: { type: integer, nullable: true }
        employee: { type: object, nullable: true }
        created_at: { type: string, format: date-time, nullable: true }

    TrainingSession:
      type: object
      description: "Session de formation (TrainingSessionResource) — liste globale GET /training/sessions."
      properties:
        id: { type: integer }
        training_course_id: { type: integer }
        trainer_id: { type: integer, nullable: true }
        external_trainer: { type: string, nullable: true }
        start_date: { type: string, format: date, nullable: true }
        end_date: { type: string, format: date, nullable: true }
        location: { type: string, nullable: true }
        status: { type: string }
        notes: { type: string, nullable: true }
        trainer: { type: object, nullable: true }
        enrollments: { type: array, nullable: true, items: { $ref: "#/components/schemas/TrainingEnrollment" } }
        created_at: { type: string, format: date-time, nullable: true }

    TaxSlabInput:
      type: object
      required: [country_code, name, min_amount, rate, effective_from]
      properties:
        country_code: { type: string }
        name: { type: string }
        min_amount: { type: number, minimum: 0 }
        max_amount: { type: number, nullable: true }
        rate: { type: number, minimum: 0, maximum: 100 }
        fixed_deduction: { type: number, minimum: 0 }
        effective_from: { type: string, format: date }
        effective_to: { type: string, format: date, nullable: true }
    SocialContributionInput:
      type: object
      required: [country_code, name, code, type, rate, effective_from]
      properties:
        country_code: { type: string }
        name: { type: string }
        code: { type: string }
        type: { type: string, enum: [employee, employer] }
        rate: { type: number, minimum: 0, maximum: 100 }
        cap: { type: number, nullable: true, minimum: 0 }
        effective_from: { type: string, format: date }
        effective_to: { type: string, format: date, nullable: true }
    PlatformUser:
      type: object
      description: "Utilisateur plateforme (public.users) — contrat super-admin (issue #2269)"
      properties:
        id: { type: integer }
        first_name: { type: string, nullable: true }
        last_name: { type: string, nullable: true }
        name: { type: string, nullable: true }
        email: { type: string }
        phone: { type: string, nullable: true }
        status: { type: string, enum: [active, disabled, pending, suspended] }
        is_active: { type: boolean }
        preferred_language: { type: string }
        last_login_at: { type: string, format: date-time, nullable: true }
        failed_login_attempts: { type: integer }
        locked_until: { type: string, format: date-time, nullable: true }
        created_at: { type: string, format: date-time, nullable: true }
        updated_at: { type: string, format: date-time, nullable: true }
        company:
          type: object
          nullable: true
          properties:
            id: { type: string, format: uuid }
            name: { type: string, nullable: true }
            link_status: { type: string }
            employee_id: { type: integer }
        roles:
          type: array
          description: "Présent sur GET /admin/users/{id} uniquement"
          items:
            type: object
            properties:
              role: { type: string, nullable: true }
              manager_role: { type: string, nullable: true }
              company_id: { type: string, format: uuid, nullable: true }
              link_status: { type: string }
    TaxSlab:
      type: object
      properties:
        id:
          type: integer
        name:
          type: string
        country_code:
          type: string
        min_amount:
          type: number
        max_amount:
          type: number
          nullable: true
        rate:
          type: number
        effective_from:
          type: string
          format: date
        effective_to:
          type: string
          format: date
          nullable: true
        created_at:
          type: string
          format: date-time

    SocialContribution:
      type: object
      description: "ATTENTION (voir issue trackee dans ce depot) : SocialContributionResource expose employee_rate/employer_rate/is_active mais le modele SocialContribution n'a que rate (unique, selon type=employee|employer) et pas de colonne is_active ; ces trois champs sont donc toujours null en pratique tant que la Resource n'est pas corrigee pour refleter les vraies colonnes du modele (rate, type, cap)."
      properties:
        id:
          type: integer
        name:
          type: string
        code:
          type: string
        country_code:
          type: string
        employee_rate:
          type: number
          nullable: true
          description: "Toujours null actuellement, bug connu de SocialContributionResource"
        employer_rate:
          type: number
          nullable: true
          description: "Toujours null actuellement, bug connu de SocialContributionResource"
        cap:
          type: number
          nullable: true
        is_active:
          type: boolean
          nullable: true
          description: "Toujours null actuellement, bug connu de SocialContributionResource (pas de colonne is_active sur le modele)"
        created_at:
          type: string
          format: date-time

    ZktecoDevice:
      type: object
      description: "Modele Eloquent expose directement (pas de Resource) par ZktecoController."
      properties:
        id: { type: integer }
        company_id: { type: string }
        serial_number: { type: string }
        name: { type: string }
        ip_address: { type: string, nullable: true }
        port: { type: integer, nullable: true }
        protocol: { type: string, enum: [tcp, udp, cloud_api], nullable: true }
        location_label: { type: string, nullable: true }
        status: { type: string, enum: [online, offline, maintenance] }
        model: { type: string, nullable: true }
        firmware_version: { type: string, nullable: true }
        employee_capacity: { type: integer, nullable: true }
        fingerprint_capacity: { type: integer, nullable: true }
        face_capacity: { type: integer, nullable: true }
        capabilities: { type: object, nullable: true }
        last_heartbeat_at: { type: string, format: date-time, nullable: true }
        last_sync_at: { type: string, format: date-time, nullable: true }
        created_at: { type: string, format: date-time }
        updated_at: { type: string, format: date-time }

    ZktecoSyncLog:
      type: object
      description: "Modele Eloquent expose directement (pas de Resource) par ZktecoController."
      properties:
        id: { type: integer }
        zkteco_device_id: { type: integer }
        direction: { type: string, description: "push ou pull" }
        sync_type: { type: string, description: "attendance ou users" }
        records_count: { type: integer }
        errors_count: { type: integer }
        status: { type: string }
        error_message: { type: string, nullable: true }
        started_at: { type: string, format: date-time, nullable: true }
        completed_at: { type: string, format: date-time, nullable: true }

    Subscription:
      type: object
      properties:
        id:
          type: integer
        company_id:
          type: integer
        plan:
          type: string
          enum: [free, starter, pro, enterprise]
        billing_cycle:
          type: string
          enum: [monthly, yearly]
        status:
          type: string
          enum: [active, cancelled, past_due, trialing]
        current_period_start:
          type: string
          format: date
        current_period_end:
          type: string
          format: date
        employee_limit:
          type: integer
          nullable: true

    Invoice:
      type: object
      properties:
        id:
          type: integer
        company_id:
          type: integer
        amount:
          type: number
        currency:
          type: string
        status:
          type: string
          enum: [draft, sent, paid, overdue, cancelled]
        issued_at:
          type: string
          format: date
        due_at:
          type: string
          format: date
        paid_at:
          type: string
          format: date
          nullable: true

    Loan:
      type: object
      properties:
        id:
          type: integer
        employee_id:
          type: integer
        company_id:
          type: integer
        amount:
          type: number
        monthly_deduction:
          type: number
        total_repaid:
          type: number
        remaining:
          type: number
        status:
          type: string
          enum: [pending, approved, disbursed, repaying, completed, rejected]
        reason:
          type: string
          nullable: true
        approved_by:
          type: integer
          nullable: true
        disbursed_at:
          type: string
          format: date-time
          nullable: true
        created_at:
          type: string
          format: date-time

    StoreLoanRequest:
      type: object
      required: [amount, monthly_deduction]
      properties:
        amount:
          type: number
          minimum: 0
        monthly_deduction:
          type: number
          minimum: 0
        reason:
          type: string
          nullable: true

    ExpenseClaim:
      type: object
      properties:
        id:
          type: integer
        employee_id:
          type: integer
        company_id:
          type: integer
        title:
          type: string
        total_amount:
          type: number
        currency:
          type: string
        status:
          type: string
          enum: [draft, submitted, approved, rejected]
        items:
          type: array
          items:
            type: object
        submitted_at:
          type: string
          format: date-time
          nullable: true
        approved_by:
          type: integer
          nullable: true
        created_at:
          type: string
          format: date-time

    StoreExpenseClaimRequest:
      type: object
      required: [title, items]
      properties:
        title:
          type: string
          maxLength: 200
        currency:
          type: string
          maxLength: 3
          default: EUR
        items:
          type: array
          items:
            type: object
            required: [description, amount, date]
            properties:
              description:
                type: string
              amount:
                type: number
                minimum: 0
              date:
                type: string
                format: date
              category:
                type: string
                nullable: true

    JobPosting:
      type: object
      properties:
        id:
          type: integer
        company_id:
          type: integer
        title:
          type: string
        description:
          type: string
        department_id:
          type: integer
          nullable: true
        position_id:
          type: integer
          nullable: true
        location:
          type: string
          nullable: true
        contract_type:
          type: string
          nullable: true
        salary_min:
          type: number
          nullable: true
        salary_max:
          type: number
          nullable: true
        status:
          type: string
          enum: [draft, published, closed]
        published_at:
          type: string
          format: date-time
          nullable: true
        closes_at:
          type: string
          format: date
          nullable: true
        applicant_count:
          type: integer
        created_at:
          type: string
          format: date-time

    StoreJobPostingRequest:
      type: object
      required: [title, description]
      properties:
        title:
          type: string
          maxLength: 200
        description:
          type: string
        department_id:
          type: integer
          nullable: true
        position_id:
          type: integer
          nullable: true
        location:
          type: string
          nullable: true
        contract_type:
          type: string
          nullable: true
        salary_min:
          type: number
          nullable: true
        salary_max:
          type: number
          nullable: true
        closes_at:
          type: string
          format: date
          nullable: true
        requirements:
          type: array
          items:
            type: string

    Applicant:
      type: object
      properties:
        id:
          type: integer
        job_posting_id:
          type: integer
        first_name:
          type: string
        last_name:
          type: string
        email:
          type: string
        phone:
          type: string
          nullable: true
        stage:
          type: string
        rating:
          type: integer
          nullable: true
        notes:
          type: string
          nullable: true
        cv_url:
          type: string
          nullable: true
        created_at:
          type: string
          format: date-time

    StoreApplicantRequest:
      type: object
      required: [first_name, last_name, email]
      properties:
        first_name:
          type: string
          maxLength: 100
        last_name:
          type: string
          maxLength: 100
        email:
          type: string
          format: email
        phone:
          type: string
          nullable: true
        notes:
          type: string
          nullable: true
        cv_url:
          type: string
          nullable: true

    TrainingCourse:
      type: object
      properties:
        id:
          type: integer
        company_id:
          type: integer
        title:
          type: string
        description:
          type: string
          nullable: true
        category:
          type: string
          nullable: true
        duration_hours:
          type: number
          nullable: true
        provider:
          type: string
          nullable: true
        is_mandatory:
          type: boolean
        created_at:
          type: string
          format: date-time


    StoreTrainingCourseRequest:
      type: object
      required: [title]
      properties:
        title:
          type: string
          maxLength: 200
        description:
          type: string
          nullable: true
        category:
          type: string
          nullable: true
        duration_hours:
          type: number
          nullable: true
        provider:
          type: string
          nullable: true
        is_mandatory:
          type: boolean
          default: false

    ApprovalWorkflow:
      type: object
      properties:
        id:
          type: integer
        company_id:
          type: integer
        name:
          type: string
        entity_type:
          type: string
        steps:
          type: array
          items:
            type: object
        is_active:
          type: boolean
        created_at:
          type: string
          format: date-time

    StoreApprovalWorkflowRequest:
      type: object
      required: [name, entity_type, steps]
      properties:
        name:
          type: string
          maxLength: 100
        entity_type:
          type: string
        steps:
          type: array
          items:
            type: object
            required: [approver_type]
            properties:
              approver_type:
                type: string
                enum: [manager, role, specific_user]
              approver_id:
                type: integer
                nullable: true
              order:
                type: integer
        is_active:
          type: boolean
          default: true

    ApprovalRequest:
      type: object
      properties:
        id:
          type: integer
        company_id:
          type: integer
        entity_type:
          type: string
        entity_id:
          type: integer
        requester_id:
          type: integer
        status:
          type: string
          enum: [pending, approved, rejected]
        current_step:
          type: integer
        steps:
          type: array
          items:
            type: object
        created_at:
          type: string
          format: date-time

    CabinetFolder:
      type: object
      properties:
        id:
          type: integer
        company_id:
          type: integer
        name:
          type: string
        parent_id:
          type: integer
          nullable: true
        employee_id:
          type: integer
          nullable: true
        documents_count:
          type: integer
        created_at:
          type: string
          format: date-time

    CabinetDocument:
      type: object
      properties:
        id:
          type: integer
        company_id:
          type: integer
        folder_id:
          type: integer
          nullable: true
        employee_id:
          type: integer
          nullable: true
        name:
          type: string
        document_type:
          type: string
          nullable: true
        file_path:
          type: string
        file_size:
          type: integer
        mime_type:
          type: string
        created_at:
          type: string
          format: date-time

    AuditLog:
      type: object
      properties:
        id:
          type: integer
        company_id:
          type: integer
        user_id:
          type: integer
          nullable: true
        action:
          type: string
        entity_type:
          type: string
        entity_id:
          type: integer
        old_values:
          type: object
          nullable: true
        new_values:
          type: object
          nullable: true
        ip_address:
          type: string
          nullable: true
        user_agent:
          type: string
          nullable: true
        created_at:
          type: string
          format: date-time

    EmployeeRegisterRequest:
      type: object
      required: [first_name, last_name, email, password, company_code]
      properties:
        first_name:
          type: string
          maxLength: 100
        last_name:
          type: string
          maxLength: 100
        email:
          type: string
          format: email
        password:
          type: string
          minLength: 8
        company_code:
          type: string

    PublicHoliday:
      type: object
      properties:
        id: { type: integer }
        company_id: { type: string, nullable: true }
        country_code: { type: string }
        name: { type: string }
        date: { type: string, format: date }
        year: { type: integer }
        is_recurring: { type: boolean }
        month_day: { type: string, nullable: true }
        holiday_type: { type: string, enum: [fixed, islamic, christian, custom] }
    PublicHolidayInput:
      type: object
      required: [country_code, name, date, year, holiday_type]
      properties:
        country_code: { type: string }
        name: { type: string }
        date: { type: string, format: date }
        year: { type: integer }
        is_recurring: { type: boolean, default: false }
        month_day: { type: string, nullable: true }
        holiday_type: { type: string, enum: [fixed, islamic, christian, custom] }

    IslamicCalendarHoliday:
      type: object
      properties:
        id: { type: integer }
        holiday_key: { type: string, enum: [eid_al_fitr, eid_al_adha, mawlid, tahmarit, muharram] }
        name: { type: string }
        year: { type: integer }
        gregorian_date: { type: string, format: date }
        duration_days: { type: integer }
        source: { type: string, enum: [manual, api, computed] }
        confirmed: { type: boolean }
        confirmed_by: { type: integer, nullable: true }
        countries:
          type: array
          items: { type: string }
    IslamicCalendarHolidayInput:
      type: object
      required: [gregorian_date, duration_days]
      properties:
        gregorian_date: { type: string, format: date }
        duration_days: { type: integer, minimum: 1, maximum: 5 }
        confirmed: { type: boolean, default: true }
        source: { type: string, enum: [manual, api, computed], default: manual }
