HEX
Server: nginx/1.27.0
System: Linux ns31201788 5.4.0-216-generic #236-Ubuntu SMP Fri Apr 11 19:53:21 UTC 2025 x86_64
User: www-data (33)
PHP: 8.1.31
Disabled: NONE
Upload Files
File: /var/www/rspp/rspp_schema.sql
-- =====================================================================
--  GESTIONALE RSPP / CONSULENTI SICUREZZA  —  SCHEMA FONDAZIONALE
--  Stack: MariaDB 10.6+ / InnoDB / utf8mb4
--  Modello: SaaS multi-tenant (tenant scoping applicativo, NON RLS nativa)
--  Normativa formazione: Accordo Stato-Regioni 17/04/2025 (in vigore 24/05/2025)
--
--  NOTE MULTI-TENANT:
--   - Ogni tabella di dominio porta tenant_id.
--   - L'isolamento NON e' garantito dal DB (MariaDB non ha RLS): va imposto
--     nel layer dati applicativo, che deve iniettare sempre WHERE tenant_id=?.
--   - Centralizzare TUTTO l'accesso dati in un repository non bypassabile.
-- =====================================================================

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ---------------------------------------------------------------------
-- 1. TENANT, UTENTI, ABBONAMENTI
-- ---------------------------------------------------------------------

CREATE TABLE tenants (
  id              BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  ragione_sociale VARCHAR(255)    NOT NULL,
  partita_iva     VARCHAR(16)     NULL,
  codice_fiscale  VARCHAR(16)     NULL,
  email           VARCHAR(255)    NOT NULL,
  piano           ENUM('free','pro','enterprise') NOT NULL DEFAULT 'free',
  stato           ENUM('attivo','sospeso','trial','cessato') NOT NULL DEFAULT 'trial',
  regione         VARCHAR(64)     NULL,  -- per periodicita' aggiornamenti regione-dipendenti
  trial_scade_il  DATE            NULL,
  created_at      DATETIME        NOT NULL DEFAULT CURRENT_TIMESTAMP,
  updated_at      DATETIME        NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (id),
  UNIQUE KEY uq_tenant_email (email)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE users (
  id            BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  tenant_id     BIGINT UNSIGNED NOT NULL,
  nome          VARCHAR(120)    NOT NULL,
  email         VARCHAR(255)    NOT NULL,
  password_hash VARCHAR(255)    NOT NULL,  -- password_hash() PHP, ARGON2ID
  ruolo         ENUM('owner','consulente','collaboratore','readonly') NOT NULL DEFAULT 'consulente',
  attivo        TINYINT(1)      NOT NULL DEFAULT 1,
  ultimo_login  DATETIME        NULL,
  created_at    DATETIME        NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (id),
  UNIQUE KEY uq_user_email (email),
  KEY idx_users_tenant (tenant_id),
  CONSTRAINT fk_users_tenant FOREIGN KEY (tenant_id) REFERENCES tenants(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- Token OAuth per integrazioni cloud (Google Drive/Calendar/Gmail, Microsoft Graph/OneDrive)
CREATE TABLE integrazioni (
  id            BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  tenant_id     BIGINT UNSIGNED NOT NULL,
  provider      ENUM('google','microsoft') NOT NULL,
  account_email VARCHAR(255)    NULL,
  access_token  TEXT            NULL,    -- cifrare a riposo (libsodium)
  refresh_token TEXT            NULL,    -- cifrare a riposo
  scope         TEXT            NULL,
  expires_at    DATETIME        NULL,
  created_at    DATETIME        NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (id),
  KEY idx_integr_tenant (tenant_id),
  CONSTRAINT fk_integr_tenant FOREIGN KEY (tenant_id) REFERENCES tenants(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- ---------------------------------------------------------------------
-- 2. CLIENTI (aziende seguite dal consulente)
-- ---------------------------------------------------------------------

CREATE TABLE clienti (
  id              BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  tenant_id       BIGINT UNSIGNED NOT NULL,
  ragione_sociale VARCHAR(255)    NOT NULL,
  partita_iva     VARCHAR(16)     NULL,
  codice_fiscale  VARCHAR(16)     NULL,
  codice_ateco    VARCHAR(16)     NULL,   -- determina classe di rischio
  classe_rischio  ENUM('basso','medio','alto') NULL,
  indirizzo       VARCHAR(255)    NULL,
  comune          VARCHAR(120)    NULL,
  provincia       CHAR(2)         NULL,
  cap             VARCHAR(5)      NULL,
  pec             VARCHAR(255)    NULL,
  note            TEXT            NULL,
  created_at      DATETIME        NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (id),
  KEY idx_clienti_tenant (tenant_id),
  CONSTRAINT fk_clienti_tenant FOREIGN KEY (tenant_id) REFERENCES tenants(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- Figure della sicurezza ex D.Lgs 81/08 (DdL, RSPP, MC, RLS, Preposto, Addetto emergenza...)
CREATE TABLE figure_sicurezza (
  id          BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  tenant_id   BIGINT UNSIGNED NOT NULL,
  cliente_id  BIGINT UNSIGNED NOT NULL,
  tipo        ENUM('datore_lavoro','rspp','aspp','medico_competente','rls',
                   'preposto','dirigente','addetto_antincendio','addetto_primo_soccorso',
                   'coordinatore') NOT NULL,
  nominativo  VARCHAR(160)    NOT NULL,
  email       VARCHAR(255)    NULL,
  telefono    VARCHAR(40)     NULL,
  data_nomina DATE            NULL,
  esterno     TINYINT(1)      NOT NULL DEFAULT 0, -- es. RSPP/MC esterno
  attivo      TINYINT(1)      NOT NULL DEFAULT 1,
  PRIMARY KEY (id),
  KEY idx_figure_tenant (tenant_id),
  KEY idx_figure_cliente (cliente_id),
  CONSTRAINT fk_figure_tenant  FOREIGN KEY (tenant_id)  REFERENCES tenants(id)  ON DELETE CASCADE,
  CONSTRAINT fk_figure_cliente FOREIGN KEY (cliente_id) REFERENCES clienti(id)  ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- ---------------------------------------------------------------------
-- 3. APPALTI (N per cliente) e DIPENDENTI
-- ---------------------------------------------------------------------

CREATE TABLE appalti (
  id           BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  tenant_id    BIGINT UNSIGNED NOT NULL,
  cliente_id   BIGINT UNSIGNED NOT NULL,
  descrizione  VARCHAR(255)    NOT NULL,
  committente  VARCHAR(255)    NULL,
  sede_operativa VARCHAR(255)  NULL,
  data_inizio  DATE            NULL,
  data_fine    DATE            NULL,
  stato        ENUM('attivo','sospeso','chiuso') NOT NULL DEFAULT 'attivo',
  PRIMARY KEY (id),
  KEY idx_appalti_tenant (tenant_id),
  KEY idx_appalti_cliente (cliente_id),
  CONSTRAINT fk_appalti_tenant  FOREIGN KEY (tenant_id)  REFERENCES tenants(id) ON DELETE CASCADE,
  CONSTRAINT fk_appalti_cliente FOREIGN KEY (cliente_id) REFERENCES clienti(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE dipendenti (
  id            BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  tenant_id     BIGINT UNSIGNED NOT NULL,
  appalto_id    BIGINT UNSIGNED NOT NULL,
  nome          VARCHAR(120)    NOT NULL,
  cognome       VARCHAR(120)    NOT NULL,
  codice_fiscale VARCHAR(16)    NULL,
  mansione      VARCHAR(160)    NULL,
  data_assunzione DATE          NULL,
  data_cessazione DATE          NULL,
  -- regola Accordo 2025: niente piu' "60 giorni", formazione PRIMA della mansione
  idoneo_alla_mansione TINYINT(1) NOT NULL DEFAULT 0, -- flag calcolato: ha formazione+idoneita'?
  attivo        TINYINT(1)      NOT NULL DEFAULT 1,
  PRIMARY KEY (id),
  KEY idx_dip_tenant (tenant_id),
  KEY idx_dip_appalto (appalto_id),
  CONSTRAINT fk_dip_tenant  FOREIGN KEY (tenant_id)  REFERENCES tenants(id) ON DELETE CASCADE,
  CONSTRAINT fk_dip_appalto FOREIGN KEY (appalto_id) REFERENCES appalti(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- ---------------------------------------------------------------------
-- 4. DOCUMENTI + DUAL STORAGE
--    Il DB e' la source of truth dei metadati; i provider sono blob storage.
-- ---------------------------------------------------------------------

CREATE TABLE documenti (
  id            BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  tenant_id     BIGINT UNSIGNED NOT NULL,
  -- polimorfico: un documento appartiene a cliente | appalto | dipendente | figura
  owner_type    ENUM('cliente','appalto','dipendente','figura') NOT NULL,
  owner_id      BIGINT UNSIGNED NOT NULL,
  categoria     VARCHAR(80)     NOT NULL,  -- es. 'DVR','contratto','idoneita_mc','attestato','DPI','DiCo'
  titolo        VARCHAR(255)    NOT NULL,
  data_emissione DATE           NULL,
  data_scadenza DATE            NULL,      -- NULL = senza scadenza
  stato         ENUM('valido','in_scadenza','scaduto','archiviato') NOT NULL DEFAULT 'valido',
  created_at    DATETIME        NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (id),
  KEY idx_doc_tenant (tenant_id),
  KEY idx_doc_owner (owner_type, owner_id),
  KEY idx_doc_scadenza (tenant_id, data_scadenza),
  CONSTRAINT fk_doc_tenant FOREIGN KEY (tenant_id) REFERENCES tenants(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE documento_storage_refs (
  id           BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  documento_id BIGINT UNSIGNED NOT NULL,
  provider     ENUM('google_drive','onedrive','locale') NOT NULL,
  file_id      VARCHAR(255)    NULL,   -- id del file sul provider
  path         VARCHAR(1024)   NULL,
  url          VARCHAR(1024)   NULL,
  checksum     VARCHAR(64)     NULL,   -- sha256, per rilevare divergenze tra provider
  sincronizzato_il DATETIME    NULL,
  PRIMARY KEY (id),
  KEY idx_storage_doc (documento_id),
  CONSTRAINT fk_storage_doc FOREIGN KEY (documento_id) REFERENCES documenti(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- ---------------------------------------------------------------------
-- 5. FORMAZIONE — Catalogo corsi (Accordo Stato-Regioni 2025) + istanze
-- ---------------------------------------------------------------------

CREATE TABLE corso_tipi (
  id                 BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  tenant_id          BIGINT UNSIGNED NULL,   -- NULL = catalogo di sistema condiviso
  codice             VARCHAR(40)     NOT NULL,
  descrizione        VARCHAR(255)    NOT NULL,
  durata_ore         SMALLINT        NULL,
  -- periodicita' aggiornamento in mesi (NULL = senza aggiornamento periodico)
  aggiornamento_mesi SMALLINT        NULL,
  durata_aggiornamento_ore SMALLINT  NULL,
  riferimento_norm   VARCHAR(120)    NULL,
  PRIMARY KEY (id),
  KEY idx_corsotipi_tenant (tenant_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- Seed catalogo di sistema (Accordo Stato-Regioni 17/04/2025).
-- NB: l'aggiornamento del PREPOSTO varia per regione (2 o 5 anni): reso configurabile per tenant.
INSERT INTO corso_tipi
  (tenant_id, codice, descrizione, durata_ore, aggiornamento_mesi, durata_aggiornamento_ore, riferimento_norm) VALUES
  (NULL,'DDL_BASE',          'Datore di lavoro - formazione base',        16, 60, 6, 'ASR 2025'),
  (NULL,'DDL_CANTIERI',      'Datore di lavoro - modulo cantieri',         6, 60, 6, 'ASR 2025'),
  (NULL,'DIRIGENTE',         'Dirigenti',                                 12, 60, 6, 'ASR 2025'),
  (NULL,'DIRIGENTE_CANTIERI','Dirigenti - modulo cantieri',                6, 60, 6, 'ASR 2025'),
  (NULL,'PREPOSTO',          'Preposti (in presenza)',                    12, 60, 6, 'ASR 2025'),
  (NULL,'LAV_GENERALE',      'Lavoratori - formazione generale',           4, NULL, NULL, 'ASR 2025'),
  (NULL,'LAV_SPEC_BASSO',    'Lavoratori - specifica rischio basso',       4, 60, 6, 'ASR 2025'),
  (NULL,'LAV_SPEC_MEDIO',    'Lavoratori - specifica rischio medio',       8, 60, 6, 'ASR 2025'),
  (NULL,'LAV_SPEC_ALTO',     'Lavoratori - specifica rischio alto',       12, 60, 6, 'ASR 2025'),
  (NULL,'RLS',               'Rappresentante dei lavoratori (RLS)',       32, 12, 8, 'D.Lgs 81/08 art.37'),
  (NULL,'DLSPP_BASSO',       'Datore di lavoro-RSPP rischio basso',       16, 60, 8, 'ASR 2025'),
  (NULL,'DLSPP_MEDIO',       'Datore di lavoro-RSPP rischio medio',       32, 60, 8, 'ASR 2025'),
  (NULL,'DLSPP_ALTO',        'Datore di lavoro-RSPP rischio alto',        48, 60, 8, 'ASR 2025'),
  (NULL,'PRIMO_SOCCORSO_A',  'Addetto primo soccorso gruppo A',           16, 36, 6, 'DM 388/2003'),
  (NULL,'PRIMO_SOCCORSO_BC', 'Addetto primo soccorso gruppo B/C',         12, 36, 4, 'DM 388/2003'),
  (NULL,'ANTINCENDIO_1',     'Addetto antincendio livello 1 (basso)',      4, 60, 2, 'DM 02/09/2021'),
  (NULL,'ANTINCENDIO_2',     'Addetto antincendio livello 2 (medio)',      8, 60, 5, 'DM 02/09/2021'),
  (NULL,'ANTINCENDIO_3',     'Addetto antincendio livello 3 (alto)',      16, 60, 8, 'DM 02/09/2021');

-- Istanze formative: chi ha fatto cosa, quando, con scadenza calcolata
CREATE TABLE formazioni (
  id            BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  tenant_id     BIGINT UNSIGNED NOT NULL,
  corso_tipo_id BIGINT UNSIGNED NOT NULL,
  -- soggetto: un dipendente OPPURE una figura della sicurezza
  soggetto_type ENUM('dipendente','figura') NOT NULL,
  soggetto_id   BIGINT UNSIGNED NOT NULL,
  data_corso    DATE            NOT NULL,
  -- data_scadenza = data_corso + aggiornamento_mesi (calcolata in app o trigger)
  data_scadenza DATE            NULL,
  ente_formatore VARCHAR(200)   NULL,
  attestato_doc_id BIGINT UNSIGNED NULL,
  stato         ENUM('valido','in_scadenza','scaduto') NOT NULL DEFAULT 'valido',
  PRIMARY KEY (id),
  KEY idx_form_tenant (tenant_id),
  KEY idx_form_soggetto (soggetto_type, soggetto_id),
  KEY idx_form_scadenza (tenant_id, data_scadenza),
  CONSTRAINT fk_form_tenant FOREIGN KEY (tenant_id)     REFERENCES tenants(id)    ON DELETE CASCADE,
  CONSTRAINT fk_form_corso  FOREIGN KEY (corso_tipo_id) REFERENCES corso_tipi(id),
  CONSTRAINT fk_form_doc    FOREIGN KEY (attestato_doc_id) REFERENCES documenti(id) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- ---------------------------------------------------------------------
-- 6. MODULISTICA 81/08 compilata da mobile (nomine, schede DPI, verbali, checklist)
-- ---------------------------------------------------------------------

CREATE TABLE moduli_compilati (
  id            BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  tenant_id     BIGINT UNSIGNED NOT NULL,
  appalto_id    BIGINT UNSIGNED NULL,
  cliente_id    BIGINT UNSIGNED NULL,
  tipo          ENUM('nomina','scheda_dpi','verbale_riunione','checklist','sopralluogo') NOT NULL,
  dati          JSON            NOT NULL,   -- payload del form (MariaDB: LONGTEXT + json_valid)
  compilato_da  BIGINT UNSIGNED NULL,       -- users.id
  -- firma (riuso pattern OTP-SMS gia' usato su Airpro/Skebby)
  firmato       TINYINT(1)      NOT NULL DEFAULT 0,
  firma_metodo  ENUM('otp_sms','disegno','nessuna') NULL,
  firmato_il    DATETIME        NULL,
  pdf_doc_id    BIGINT UNSIGNED NULL,
  created_at    DATETIME        NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (id),
  KEY idx_mod_tenant (tenant_id),
  CONSTRAINT fk_mod_tenant FOREIGN KEY (tenant_id) REFERENCES tenants(id) ON DELETE CASCADE,
  CONSTRAINT fk_mod_pdf    FOREIGN KEY (pdf_doc_id) REFERENCES documenti(id) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- ---------------------------------------------------------------------
-- 7. SCADENZARIO unificato + sync Google Calendar
--    Vista materializzata "logica": le scadenze provengono da documenti,
--    formazioni e idoneita'. Questa tabella le aggrega per alert/calendar.
-- ---------------------------------------------------------------------

CREATE TABLE scadenze (
  id            BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  tenant_id     BIGINT UNSIGNED NOT NULL,
  origine_type  ENUM('documento','formazione','idoneita','attivita') NOT NULL,
  origine_id    BIGINT UNSIGNED NOT NULL,
  titolo        VARCHAR(255)    NOT NULL,
  data_scadenza DATE            NOT NULL,
  giorni_preavviso SMALLINT     NOT NULL DEFAULT 30,
  stato         ENUM('aperta','notificata','gestita','chiusa') NOT NULL DEFAULT 'aperta',
  gcal_event_id VARCHAR(255)    NULL,  -- per sync bidirezionale Google Calendar
  PRIMARY KEY (id),
  KEY idx_scad_tenant (tenant_id),
  KEY idx_scad_data (tenant_id, data_scadenza),
  CONSTRAINT fk_scad_tenant FOREIGN KEY (tenant_id) REFERENCES tenants(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- ---------------------------------------------------------------------
-- 8. AUDIT LOG immutabile (per ispezioni / contenzioso)
-- ---------------------------------------------------------------------

CREATE TABLE audit_log (
  id          BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  tenant_id   BIGINT UNSIGNED NOT NULL,
  user_id     BIGINT UNSIGNED NULL,
  azione      VARCHAR(80)     NOT NULL,   -- create|update|delete|download|firma|login
  entita      VARCHAR(80)     NOT NULL,
  entita_id   BIGINT UNSIGNED NULL,
  dettaglio   JSON            NULL,
  ip          VARCHAR(45)     NULL,
  created_at  DATETIME        NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (id),
  KEY idx_audit_tenant (tenant_id, created_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

SET FOREIGN_KEY_CHECKS = 1;