openapi: 3.0.3
info:
  title: GEBAN GEMODO Catalogo Modelli API
  version: 0.3.0
  description: >
    API per catalogo modelli, contratto dati e validazione payload verso GEBAN.
    Versione 0.2.0: aggiunge validazione tipologia GEBAN/SOL (codice_tipologia),
    campi con lingua condizionata (bando_inglese) - vedi
    docs/decision-register.yaml, DEC-001-TIPOLOGIE-SOL e DEC-001-LINGUA-IT-EN.
    Versione 0.3.0: le API operative richiedono JWT Bearer Keycloak con audience
    gemodo-backend e ruoli DOCUMENTI_VIEWER/DOCUMENTI_GENERATORE.
servers:
  - url: /api/v1
security:
  - KeycloakBearer: []
  - KeycloakSSO: []
paths:
  /catalogo/tipi-documento:
    get:
      summary: Elenco tipi documento disponibili
      description: Richiede ruolo DOCUMENTI_VIEWER o DOCUMENTI_GENERATORE.
      operationId: listTipiDocumento
      responses:
        "200":
          description: Tipi documento disponibili
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TipoDocumentoListResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
  /catalogo/tipi-documento/{codiceTipoDocumento}/profili:
    get:
      summary: Elenco profili per tipo documento
      description: Richiede ruolo DOCUMENTI_VIEWER o DOCUMENTI_GENERATORE.
      operationId: listProfiliDocumento
      parameters:
        - $ref: "#/components/parameters/CodiceTipoDocumento"
      responses:
        "200":
          description: Profili disponibili
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ProfiloDocumentoListResponse"
        "404":
          $ref: "#/components/responses/NotFound"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
  /catalogo/tipi-documento/{codiceTipoDocumento}/classificazione:
    get:
      summary: Albero classificazione per tipo documento
      description: >
        Restituisce l'albero configurato per il tipo documento: tipologie/procedure
        GEBAN/SOL disponibili e profili associati. Il mapping tecnico SOL resta interno
        e non viene esposto nella risposta.
      operationId: getClassificazioneDocumento
      parameters:
        - $ref: "#/components/parameters/CodiceTipoDocumento"
      responses:
        "200":
          description: Classificazione ad albero per il tipo documento
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ClassificazioneCatalogoResponse"
        "404":
          $ref: "#/components/responses/NotFound"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
  /catalogo/modelli:
    get:
      summary: Ricerca modelli pubblicati
      description: Richiede ruolo DOCUMENTI_VIEWER o DOCUMENTI_GENERATORE.
      operationId: searchModelli
      parameters:
        - name: tipo_documento
          in: query
          required: true
          schema:
            type: string
        - name: profilo
          in: query
          required: false
          description: Profilo professionale selezionato sotto la tipologia/procedura bando.
          schema:
            type: string
        - name: codice_tipologia
          in: query
          required: false
          description: >
            Nodo di primo livello dell'albero GEBAN/SOL selezionato da
            /catalogo/tipi-documento/{codiceTipoDocumento}/classificazione. Se presente,
            deve corrispondere a una tipologia/procedura bando configurata (perimetro
            iniziale: CP, TD, TI, IR, MOB); un valore non riconosciuto produce
            errore TIPOLOGIA_SOL_NON_VALIDA, non un elenco vuoto. Il codice SOL tecnico
            associato resta metadato interno.
          schema:
            type: string
        - name: modalita
          in: query
          required: false
          description: OPERATIVA restituisce modelli usabili; STORICO restituisce versioni archiviate o storiche.
          schema:
            type: string
            enum: [OPERATIVA, STORICO]
            default: OPERATIVA
        - name: data_riferimento
          in: query
          required: false
          schema:
            type: string
            format: date
        - name: pubblicato_da
          in: query
          required: false
          schema:
            type: string
            format: date
        - name: pubblicato_a
          in: query
          required: false
          schema:
            type: string
            format: date
      responses:
        "200":
          description: Modelli pubblicati filtrati
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ModelloSearchResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
  /catalogo/modelli/{modelloVersioneId}/campi-richiesti:
    get:
      summary: Contratto dati per versione modello pubblicata
      description: Richiede ruolo DOCUMENTI_VIEWER o DOCUMENTI_GENERATORE.
      operationId: getCampiRichiesti
      parameters:
        - $ref: "#/components/parameters/ModelloVersioneId"
      responses:
        "200":
          description: Contratto dati della versione modello
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CampiRichiestiResponse"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
  /documenti/valida:
    post:
      summary: Valida payload contro contratto dati
      description: Richiede ruolo DOCUMENTI_GENERATORE.
      operationId: validaPayload
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ValidazioneRequest"
      responses:
        "200":
          description: Esito validazione
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ValidazioneResponse"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
  /documenti/genera:
    post:
      summary: Simula richiesta generazione documento
      description: >
        Endpoint temporaneo per collaudo GEBAN: valida token, versione modello e payload.
        Se i dati sono corretti restituisce un esito OK e un testo placeholder dove in
        seguito verra' restituito il link per scaricare il PDF generato. Non produce
        ancora un file PDF reale.
      operationId: generaDocumentoPlaceholder
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ValidazioneRequest"
      responses:
        "200":
          description: Esito simulazione generazione
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/GenerazioneDocumentoResponse"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
components:
  securitySchemes:
    KeycloakBearer:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: >
        JWT Keycloak emesso dal realm CNR, con audience gemodo-backend e ruoli client in
        resource_access.gemodo-backend.roles. Le chiamate GEBAN usano client tecnico
        geban-backend.
    KeycloakSSO:
      type: oauth2
      description: >
        Login SSO CNR per test interattivo Swagger con client pubblico gemodo-frontend
        e Authorization Code + PKCE. Il token deve avere audience gemodo-backend e ruoli
        client in resource_access.gemodo-backend.roles.
      flows:
        authorizationCode:
          authorizationUrl: https://sso.test.si.cnr.it/auth/realms/cnr/protocol/openid-connect/auth
          tokenUrl: https://sso.test.si.cnr.it/auth/realms/cnr/protocol/openid-connect/token
          scopes:
            openid: Identita' OpenID Connect
            profile: Profilo utente CNR
            email: Email utente CNR
  parameters:
    CodiceTipoDocumento:
      name: codiceTipoDocumento
      in: path
      required: true
      schema:
        type: string
    ModelloVersioneId:
      name: modelloVersioneId
      in: path
      required: true
      schema:
        type: integer
        format: int64
  responses:
    BadRequest:
      description: Richiesta non valida
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
    Unauthorized:
      description: Token mancante, scaduto o non valido
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
    Forbidden:
      description: Client o ruolo non autorizzato per l'operazione richiesta
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
    NotFound:
      description: Risorsa non trovata
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
    Conflict:
      description: Risorsa non utilizzabile nello stato corrente
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
  schemas:
    TipoDocumentoListResponse:
      type: object
      required: [items]
      properties:
        items:
          type: array
          items:
            $ref: "#/components/schemas/TipoDocumento"
    TipoDocumento:
      type: object
      required: [codice, descrizione]
      properties:
        codice:
          type: string
        descrizione:
          type: string
    ProfiloDocumentoListResponse:
      type: object
      required: [tipo_documento, profili]
      properties:
        tipo_documento:
          type: string
        profili:
          type: array
          items:
            $ref: "#/components/schemas/ProfiloDocumento"
    ClassificazioneCatalogoResponse:
      type: object
      required: [tipo_documento, tipologie]
      properties:
        tipo_documento:
          type: string
        tipologie:
          type: array
          items:
            $ref: "#/components/schemas/TipologiaCatalogo"
    TipologiaCatalogo:
      type: object
      required: [codice, descrizione, profili]
      properties:
        codice:
          type: string
          example: TD
        descrizione:
          type: string
          example: Tempo determinato
        profili:
          type: array
          items:
            $ref: "#/components/schemas/ProfiloDocumento"
    ProfiloDocumento:
      type: object
      required: [codice, descrizione]
      properties:
        codice:
          type: string
        descrizione:
          type: string
    ModelloSearchResponse:
      type: object
      required: [tipo_documento, modelli]
      properties:
        tipo_documento:
          type: string
        profilo:
          type: string
          nullable: true
        codice_tipologia:
          type: string
          nullable: true
        modalita:
          type: string
          enum: [OPERATIVA, STORICO]
        modelli:
          type: array
          items:
            $ref: "#/components/schemas/ModelloCatalogo"
    ModelloCatalogo:
      type: object
      required: [modello_id, modello_versione_id, codice, descrizione, variante, versione, stato]
      properties:
        modello_id:
          type: integer
          format: int64
        modello_versione_id:
          type: integer
          format: int64
        codice:
          type: string
        descrizione:
          type: string
        variante:
          type: string
        versione:
          type: integer
        stato:
          type: string
          enum: [PUBBLICATO]
        data_inizio_validita:
          type: string
          format: date
          nullable: true
        data_fine_validita:
          type: string
          format: date
          nullable: true
        pubblicato_at:
          type: string
          format: date-time
          nullable: true
    CampiRichiestiResponse:
      type: object
      required: [modello_versione_id, campi, schema]
      properties:
        modello_versione_id:
          type: integer
          format: int64
        tipo_documento:
          type: string
        profilo:
          type: string
        campi:
          type: array
          items:
            $ref: "#/components/schemas/CampoRichiesto"
        schema:
          type: object
          additionalProperties: true
    CampoRichiesto:
      type: object
      required: [codice, etichetta, tipo, lingua, obbligatorio, ordine]
      properties:
        codice:
          type: string
        etichetta:
          type: string
        tipo:
          type: string
          enum: [string, number, date, boolean, array, object]
        lingua:
          type: string
          enum: [IT, EN]
          default: IT
          description: >
            Quando EN, il campo e' obbligatorio in validazione solo se il payload
            ha bando_inglese=true; altrimenti resta facoltativo indipendentemente da
            'obbligatorio' (DEC-001-LINGUA-IT-EN).
        obbligatorio:
          type: boolean
        ordine:
          type: integer
        descrizione:
          type: string
          nullable: true
        validazione:
          type: object
          additionalProperties: true
          nullable: true
    ValidazioneRequest:
      type: object
      required: [sistema_richiedente, external_context_id, modello_versione_id, dati]
      additionalProperties: false
      properties:
        sistema_richiedente:
          type: string
          example: GEBAN
        external_context_id:
          type: string
        modello_versione_id:
          type: integer
          format: int64
        data_riferimento:
          type: string
          format: date
          nullable: true
        bando_inglese:
          type: boolean
          default: false
          description: >
            Quando true, i campi del contratto dati con lingua=EN e
            obbligatorio=true diventano obbligatori in validazione
            (DEC-001-LINGUA-IT-EN).
        dati:
          type: object
          additionalProperties: true
    ValidazioneResponse:
      type: object
      required: [valido, errori]
      properties:
        valido:
          type: boolean
        errori:
          type: array
          items:
            $ref: "#/components/schemas/ErroreValidazione"
    GenerazioneDocumentoResponse:
      type: object
      required: [stato, messaggio, modello_versione_id, external_context_id, validazione]
      properties:
        stato:
          type: string
          enum: [GENERAZIONE_SIMULATA, DATI_NON_VALIDI]
        messaggio:
          type: string
        modello_versione_id:
          type: integer
          format: int64
        external_context_id:
          type: string
        download_placeholder:
          type: string
          nullable: true
          example: Qui sara' disponibile il link per scaricare il PDF generato.
        validazione:
          $ref: "#/components/schemas/ValidazioneResponse"
    ErroreValidazione:
      type: object
      required: [codice, messaggio]
      properties:
        campo:
          type: string
          nullable: true
        codice:
          type: string
        messaggio:
          type: string
    ErrorResponse:
      type: object
      required: [codice, messaggio]
      properties:
        codice:
          type: string
        messaggio:
          type: string
