openapi: 3.0.3
info:
  title: Sistema Tessera Sanitaria API
  description: |
    REST API v1 per l'integrazione con il Sistema Tessera Sanitaria (STS).
    Permette l'invio, la lettura e la gestione dei Documenti di Spesa,
    nonché la gestione dell'anagrafica degli Erogatori.

    ## Autenticazione
    Ogni chiamata può essere autenticata tramite **Basic Authentication** o **Bearer Token**.

    - **Basic Auth**: `Authorization: Basic <base64(username:password)>`
    - **Bearer Token**: `Authorization: Bearer <token>` — il token si ottiene chiamando `POST /authentication`

    A ogni chiamata con Basic Auth, gli header di risposta restituiscono anche:
    - `X-auth-token`: il token Bearer
    - `X-auth-expires`: la scadenza del token

    ## Paginazione
    Le chiamate `GET` su liste supportano i parametri:
    - `per_page`: numero di risultati per pagina (default 100, max 1000)
    - `page`: numero di pagina (default 1)

    La presenza di una pagina successiva è segnalata dall'header `Link: <url> rel="next"`.

    ## Ambienti
    - **Test**: `https://sistema-ts-api.it/api/v1/test`
    - **Produzione**: `https://sistema-ts-api.it/api/v1/prod`
  version: "1.0.0"
  contact:
    name: ITALA Tecnologia Informatica S.r.l.
    url: https://www.sistema-ts-api.it/contatti/
  license:
    name: Proprietaria
    url: https://www.sistema-ts-api.it

servers:
  - url: https://sistema-ts-api.it/api/v1/prod
    description: Ambiente di Produzione
  - url: https://sistema-ts-api.it/api/v1/test
    description: Ambiente di Test

security:
  - basicAuth: []
  - bearerAuth: []

tags:
  - name: Autenticazione
    description: Ottenimento del token Bearer
  - name: Documenti di Spesa
    description: Invio, lettura, aggiornamento e cancellazione dei documenti di spesa
  - name: Erogatori
    description: Gestione dell'anagrafica dei soggetti erogatori

paths:

  # ─────────────────────────────────────────────
  # AUTENTICAZIONE
  # ─────────────────────────────────────────────
  /authentication:
    post:
      tags:
        - Autenticazione
      summary: Ottieni token Bearer
      description: |
        Autenticandosi con Basic Auth, restituisce un token Bearer da usare
        nelle chiamate successive. In alternativa il token è incluso
        negli header di risposta di qualsiasi altra chiamata autenticata con Basic Auth.
      security:
        - basicAuth: []
      responses:
        "200":
          description: Token generato con successo
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AuthToken"
              example:
                token: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
                expires: "2026-12-31 23:59:59"
        "401":
          $ref: "#/components/responses/Unauthorized"

  # ─────────────────────────────────────────────
  # DOCUMENTI DI SPESA
  # ─────────────────────────────────────────────
  /documenti-spesa:
    get:
      tags:
        - Documenti di Spesa
      summary: Elenco documenti di spesa inviati
      description: |
        Restituisce la lista dei documenti di spesa. Supporta filtri opzionali
        tramite query string e paginazione.
      parameters:
        - $ref: "#/components/parameters/PerPage"
        - $ref: "#/components/parameters/Page"
        - name: date_from
          in: query
          description: "Data di invio iniziale (formato: `yyyy-mm-dd` o `yyyy-mm-dd hh:mm:ss`)"
          schema:
            type: string
            example: "2026-01-01"
        - name: date_to
          in: query
          description: "Data di invio finale (formato: `yyyy-mm-dd` o `yyyy-mm-dd hh:mm:ss`)"
          schema:
            type: string
            example: "2026-12-31"
        - name: id_from
          in: query
          description: ID iniziale (filtro per intervallo)
          schema:
            type: integer
        - name: id_to
          in: query
          description: ID finale (filtro per intervallo)
          schema:
            type: integer
        - name: sts_protocollo_from
          in: query
          description: Protocollo STS iniziale
          schema:
            type: integer
        - name: sts_protocollo_to
          in: query
          description: Protocollo STS finale
          schema:
            type: integer
        - name: sts_protocollo_id
          in: query
          description: Identificativo protocollo STS esatto
          schema:
            type: integer
        - name: partita_iva
          in: query
          description: Filtra per partita IVA del soggetto erogatore
          schema:
            type: string
            example: "00000000000"
        - name: numero_documento
          in: query
          description: "Numero documento esatto (es: `123`)"
          schema:
            type: string
            example: "123"
        - name: anno_documento
          in: query
          description: "Anno del documento (es: `2026`)"
          schema:
            type: string
            example: "2026"
        - name: tipo_documento
          in: query
          description: "Tipo documento: `F` = Fattura, `D` = Documento commerciale"
          schema:
            type: string
            enum: [F, D]
      responses:
        "200":
          description: Lista documenti di spesa
          headers:
            Link:
              description: "Header di paginazione: `<url> rel=\"next\"` se esiste una pagina successiva"
              schema:
                type: string
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/DocumentoSpesaRisposta"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"

    post:
      tags:
        - Documenti di Spesa
      summary: Invia un documento di spesa (o cancellazione)
      description: |
        Invia un nuovo documento di spesa al Sistema Tessera Sanitaria.

        Il parametro opzionale `operazione` controlla il tipo di operazione:
        - `INS` (default): inserimento di un nuovo documento
        - `CAN`: cancellazione/annullamento/storno di un documento già trasmesso

        Per la cancellazione sono necessari solo: `partitaIvaErogatore`,
        `tipoDocumento`, `numeroDocumento`, `dataDocumento` (e facoltativamente `dispositivo`).
        Non devono essere inviati i dati del cittadino né le voci di spesa.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/DocumentoSpesaRichiesta"
            examples:
              inserimento:
                summary: Inserimento documento
                value:
                  partitaIvaErogatore: "00000000000"
                  tipoDocumento: "F"
                  numeroDocumento: "2"
                  dataDocumento: "2026-01-30"
                  codiceFiscaleCittadino: "AAABBB00A00H501A"
                  vociSpesa:
                    - tipoSpesa: "SP"
                      importo: 100.00
                      naturaIVA: "N2.2"
              cancellazione:
                summary: Cancellazione documento
                value:
                  operazione: "CAN"
                  partitaIvaErogatore: "00000000000"
                  tipoDocumento: "F"
                  numeroDocumento: "2"
                  dataDocumento: "2026-01-30"
                  dispositivo: "1"
      responses:
        "200":
          description: Documento elaborato correttamente
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DocumentoSpesaRisposta"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"

  /documenti-spesa/{id}:
    parameters:
      - $ref: "#/components/parameters/ResourceId"

    get:
      tags:
        - Documenti di Spesa
      summary: Leggi un singolo documento di spesa
      description: Restituisce i dati aggiornati di un singolo documento di spesa tramite il suo ID.
      responses:
        "200":
          description: Documento trovato
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DocumentoSpesaRisposta"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"

    put:
      tags:
        - Documenti di Spesa
      summary: Re-invia / aggiorna un documento di spesa
      description: |
        Aggiorna i dati di un documento in stato `ERRO` e prenota un nuovo
        tentativo di trasmissione verso il STS. Il corpo della richiesta ha
        la stessa struttura dell'endpoint di inserimento.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/DocumentoSpesaRichiesta"
            example:
              partitaIvaErogatore: "00000000000"
              tipoDocumento: "F"
              numeroDocumento: "2"
              dataDocumento: "2026-01-30"
              codiceFiscaleCittadino: "AAABBB00A00H501A"
              vociSpesa:
                - tipoSpesa: "SP"
                  importo: 100.00
                  naturaIVA: "N2.2"
      responses:
        "200":
          description: Documento aggiornato e re-inviato
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DocumentoSpesaRisposta"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"

  # ─────────────────────────────────────────────
  # EROGATORI
  # ─────────────────────────────────────────────
  /erogatori:
    get:
      tags:
        - Erogatori
      summary: Elenco erogatori
      description: Restituisce la lista degli erogatori censiti in anagrafica.
      parameters:
        - $ref: "#/components/parameters/PerPage"
        - $ref: "#/components/parameters/Page"
      responses:
        "200":
          description: Lista erogatori
          headers:
            Link:
              description: "Header di paginazione: `<url> rel=\"next\"` se esiste una pagina successiva"
              schema:
                type: string
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/ErogatoreRisposta"
        "401":
          $ref: "#/components/responses/Unauthorized"

    post:
      tags:
        - Erogatori
      summary: Crea un nuovo erogatore
      description: |
        Aggiunge un nuovo soggetto erogatore all'anagrafica.

        **Campi obbligatori**: `partitaIva`, `codiceFiscale`

        **Campi necessari per la trasmissione documenti**: `usernameSts`, `passwordSts`, `pincodeSts`
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ErogatoreRichiesta"
            example:
              denominazione: "Erogatore di esempio"
              partitaIva: "00000000000"
              codiceFiscale: "AAABBB00A00H501A"
              usernameSts: "123456"
              passwordSts: "789012"
              pincodeSts: "111111"
              descrizione: "Note facoltative"
      responses:
        "200":
          description: Erogatore creato
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErogatoreRisposta"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"

  /erogatori/{id}:
    parameters:
      - $ref: "#/components/parameters/ResourceId"

    get:
      tags:
        - Erogatori
      summary: Leggi un singolo erogatore
      description: Restituisce i dati di un singolo erogatore tramite il suo ID.
      responses:
        "200":
          description: Erogatore trovato
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErogatoreRisposta"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"

    put:
      tags:
        - Erogatori
      summary: Aggiorna un erogatore
      description: |
        Aggiorna parzialmente i dati di un erogatore. È sufficiente inviare
        solo i campi da modificare (es. solo `pincodeSts` alla scadenza).
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ErogatoreAggiornamento"
            example:
              pincodeSts: "222222"
      responses:
        "200":
          description: Erogatore aggiornato
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErogatoreRisposta"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"

    delete:
      tags:
        - Erogatori
      summary: Elimina un erogatore
      description: Rimuove un erogatore dall'anagrafica.
      responses:
        "200":
          description: Erogatore eliminato
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErogatoreRisposta"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"

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

  # ── Security Schemes ────────────────────────────────────────────────────────
  securitySchemes:
    basicAuth:
      type: http
      scheme: basic
      description: |
        Credenziali di accesso a sistema-ts-api.it codificate in Base64.
        Formato header: `Authorization: Basic <base64(username:password)>`
    bearerAuth:
      type: http
      scheme: bearer
      description: |
        Token ottenuto tramite `POST /authentication`.
        Formato header: `Authorization: Bearer <token>`

  # ── Parameters ──────────────────────────────────────────────────────────────
  parameters:
    ResourceId:
      name: id
      in: path
      required: true
      description: Identificativo numerico univoco della risorsa
      schema:
        type: integer
        example: 1

    PerPage:
      name: per_page
      in: query
      description: Numero massimo di risultati per pagina (default 100, max 1000)
      schema:
        type: integer
        minimum: 1
        maximum: 1000
        default: 100
        example: 100

    Page:
      name: page
      in: query
      description: Numero di pagina (default 1)
      schema:
        type: integer
        minimum: 1
        default: 1
        example: 1

  # ── Responses ───────────────────────────────────────────────────────────────
  responses:
    BadRequest:
      description: Richiesta non valida
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Errore"
          example:
            error: "Parametro obbligatorio mancante"

    Unauthorized:
      description: Credenziali mancanti o non valide
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Errore"
          example:
            error: "Autenticazione richiesta"

    NotFound:
      description: Risorsa non trovata
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Errore"
          example:
            error: "Risorsa non trovata"

  # ── Schemas ─────────────────────────────────────────────────────────────────
  schemas:

    # ── Errore generico ──────────────────────────────────────────────────────
    Errore:
      type: object
      properties:
        error:
          type: string
          description: Messaggio descrittivo dell'errore
      required:
        - error

    # ── Auth token ───────────────────────────────────────────────────────────
    AuthToken:
      type: object
      properties:
        token:
          type: string
          description: Token Bearer da usare nelle chiamate successive
          example: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
        expires:
          type: string
          description: "Data/ora di scadenza del token (formato: yyyy-mm-dd hh:mm:ss)"
          example: "2026-12-31 23:59:59"
      required:
        - token
        - expires

    # ── Voce di spesa (richiesta) ─────────────────────────────────────────────
    VoceSpesaRichiesta:
      type: object
      description: |
        Voce di spesa. È obbligatorio specificare **uno solo** tra `aliquotaIVA`
        e `naturaIVA`; non possono essere presenti entrambi.
      properties:
        tipoSpesa:
          type: string
          description: |
            Codice tipo di spesa a due lettere:
            - `TK` – Ticket
            - `FC` – Farmaco
            - `FV` – Farmaco Veterinario
            - `AD` – Acquisto/affitto dispositivo medico
            - `AS` – Spese sanitarie
            - `SR` – Prestazioni sanitarie specialistiche
            - `CT` – Cure termali
            - `PI` – Protesica e integrativa
            - `IC` – Chirurgia/medicina estetica
            - `SP` – Prestazioni sanitarie
            - `SV` – Spese veterinarie
            - `AA` – Altre spese
          enum: [TK, FC, FV, AD, AS, SR, CT, PI, IC, SP, SV, AA]
          example: "SP"
        importo:
          type: number
          format: float
          description: Importo della voce di spesa
          example: 100.00
        aliquotaIVA:
          type: number
          format: float
          nullable: true
          description: |
            Aliquota IVA applicata (in %). Non può essere specificata insieme a `naturaIVA`.
            Valori tipici: `10` (strutture sanitarie – ricovero), `22` (attività non sanitarie).
          example: 22
        naturaIVA:
          type: string
          nullable: true
          description: |
            Codice natura IVA. Non può essere specificato insieme a `aliquotaIVA`.
            Valori tipici:
            - `N4` – Prestazione sanitaria alla persona esente IVA
            - `N2.2` – Soggetto erogatore con regime forfettario
          example: "N2.2"
      required:
        - tipoSpesa
        - importo

    # ── Voce di spesa (risposta) ──────────────────────────────────────────────
    VoceSpesaRisposta:
      type: object
      properties:
        tipoSpesa:
          type: string
          enum: [TK, FC, FV, AD, AS, SR, CT, PI, IC, SP, SV, AA]
          example: "SP"
        importo:
          type: string
          description: Importo (restituito come stringa decimale)
          example: "100.00"
        aliquotaIVA:
          type: number
          nullable: true
          example: null
        naturaIVA:
          type: string
          nullable: true
          example: "N2.2"

    # ── Documento di spesa (richiesta) ────────────────────────────────────────
    DocumentoSpesaRichiesta:
      type: object
      properties:
        operazione:
          type: string
          description: |
            Tipo di operazione:
            - `INS` (default) – Inserimento
            - `CAN` – Cancellazione/annullamento/storno
          enum: [INS, CAN]
          default: INS
          example: "INS"
        partitaIvaErogatore:
          type: string
          description: Partita IVA del soggetto erogatore (già censito in anagrafica)
          example: "00000000000"
        tipoDocumento:
          type: string
          description: "`F` = Fattura, `D` = Documento commerciale"
          enum: [F, D]
          example: "F"
        numeroDocumento:
          type: string
          description: Numero del documento
          example: "2"
        dataDocumento:
          type: string
          format: date
          description: "Data del documento (formato: yyyy-mm-dd)"
          example: "2026-01-30"
        codiceFiscaleCittadino:
          type: string
          description: Codice fiscale del cittadino. Non richiesto per operazione `CAN`.
          example: "AAABBB00A00H501A"
        dispositivo:
          type: string
          description: Codice dispositivo (facoltativo)
          example: "1"
        dataPagamento:
          type: string
          format: date
          description: "Data del pagamento (formato: yyyy-mm-dd)"
          example: "2026-01-30"
        pagamentoTracciato:
          type: boolean
          description: Indica se il pagamento è tracciato
          example: true
        flagOpposizione:
          type: boolean
          description: Flag di opposizione alla trasmissione al STS
          example: false
        vociSpesa:
          type: array
          description: Voci di spesa. Non richieste per operazione `CAN`.
          items:
            $ref: "#/components/schemas/VoceSpesaRichiesta"
      required:
        - partitaIvaErogatore
        - tipoDocumento
        - numeroDocumento
        - dataDocumento

    # ── Documento di spesa (risposta) ─────────────────────────────────────────
    DocumentoSpesaRisposta:
      type: object
      properties:
        id:
          type: integer
          description: Identificativo univoco numerico assegnato dal sistema
          example: 1
        partitaIvaErogatore:
          type: string
          example: "00000000000"
        tipoDocumento:
          type: string
          enum: [F, D]
          example: "F"
        numeroDocumento:
          type: string
          example: "2"
        dataDocumento:
          type: string
          format: date
          example: "2026-01-13"
        dispositivo:
          type: string
          nullable: true
          example: "1"
        dataPagamento:
          type: string
          format: date
          nullable: true
          example: "2026-01-13"
        pagamentoTracciato:
          type: boolean
          example: true
        flagOpposizione:
          type: boolean
          example: false
        vociSpesa:
          type: array
          items:
            $ref: "#/components/schemas/VoceSpesaRisposta"
        statoSts:
          type: string
          description: |
            Stato trasmissione al STS:
            - `INVI` – Inviato
            - `PREN` – Prenotato (preso in carico, non ancora trasmesso)
            - `ERRO` – Errore (vedere `messaggioSts`)
          enum: [INVI, PREN, ERRO]
          example: "INVI"
        protocolloSts:
          type: integer
          nullable: true
          description: Numero di protocollo rilasciato dal STS (null in caso di errore)
          example: 1234567890
        messaggioSts:
          type: string
          description: Eventuale messaggio di errore restituito dal STS
          example: ""
        origine:
          type: string
          description: Origine del documento
          example: "A"
        dataInserimento:
          type: string
          description: "Data/ora di inserimento (formato: yyyy-mm-dd hh:mm:ss)"
          example: "2026-01-13 12:00:00"
      description: |
        Nota: il campo `codiceFiscaleCittadino` non viene restituito perché
        viene codificato sul server.

    # ── Erogatore (richiesta creazione) ──────────────────────────────────────
    ErogatoreRichiesta:
      type: object
      properties:
        denominazione:
          type: string
          description: Ragione sociale o nome dell'erogatore
          example: "Erogatore di esempio"
        partitaIva:
          type: string
          description: Partita IVA (obbligatoria)
          example: "00000000000"
        codiceFiscale:
          type: string
          description: Codice fiscale (obbligatorio)
          example: "AAABBB00A00H501A"
        usernameSts:
          type: string
          description: Username STS (necessario per la trasmissione documenti)
          example: "123456"
        passwordSts:
          type: string
          description: Password STS (necessaria per la trasmissione documenti)
          example: "789012"
        pincodeSts:
          type: string
          description: Pincode STS (necessario per la trasmissione documenti; codificato sul server)
          example: "111111"
        descrizione:
          type: string
          description: Note descrittive facoltative
          example: ""
      required:
        - partitaIva
        - codiceFiscale

    # ── Erogatore (aggiornamento parziale) ────────────────────────────────────
    ErogatoreAggiornamento:
      type: object
      description: |
        Tutti i campi sono opzionali. Inviare solo i campi da aggiornare
        (es. solo `pincodeSts` alla scadenza).
      properties:
        denominazione:
          type: string
          example: "Nuovo nome"
        partitaIva:
          type: string
          example: "00000000000"
        codiceFiscale:
          type: string
          example: "AAABBB00A00H501A"
        usernameSts:
          type: string
          example: "123456"
        passwordSts:
          type: string
          example: "789012"
        pincodeSts:
          type: string
          example: "222222"
        descrizione:
          type: string
          example: "Note aggiornate"

    # ── Erogatore (risposta) ──────────────────────────────────────────────────
    ErogatoreRisposta:
      type: object
      properties:
        id:
          type: string
          description: Identificativo univoco dell'erogatore
          example: "1"
        denominazione:
          type: string
          example: "Erogatore test"
        partitaIva:
          type: string
          example: "00000000000"
        usernameSts:
          type: string
          example: "123456"
        passwordSts:
          type: string
          example: "789012"
        descrizione:
          type: string
          example: ""
        dataInserimento:
          type: string
          nullable: true
          description: "Data/ora di inserimento (formato: yyyy-mm-dd hh:mm:ss)"
          example: null
      description: |
        Nota: i campi `codiceFiscale` e `pincodeSts` vengono codificati sul server
        e non sono inclusi nella risposta.
