[FEAT] A4 Fase 4.2 — Competenze (skill/requisiti ruolo/gap) + alert→azione NCR+CAPA
Secondo modulo del modello relazionale A4 (docs/DESIGN_A4_RELATIONAL.md). Backend: - Migration 042 (docs/sql/042_competences.sql) + runner aggiornato (scripts/migrate-a4.php). 4 tabelle additive/idempotenti: skills (org-owned o globali), role_skills (competenze richieste dal ruolo, UNIQUE role+skill), user_skills (possedute, UNIQUE org+user+skill), skill_course_map. APPLICATA su prod (nis2-db v8.0.45, TLSv1.3). - CompetenceController (route 'competences'): catalog, skills CRUD, roleSkills, userSkills, skillCourses (map/unmap), gapGrid (richiesto−posseduto per ruolo con titolare), openAction. Multi-tenancy + anti-IDOR + livelli 1-5; competenze globali in sola lettura per gli utenti org. - alert→azione: openAction RIUSA il workflow NCR/CAPA reale (la tabella azioni è capa_actions figlia di non_conformities, NON 'corrective_actions' che non esiste): crea NCR (source='management_review', source_entity_type='competence_gap', source_entity_id=role_skills.id) + capa_actions figlia. Anti-duplicato sul gap. Zero ALTER alle tabelle esistenti. Ancoraggio PR.AT-01/02, GV.RR-04, art.24 D.Lgs. Frontend: - competenze.html (4 schede: Catalogo, Requisiti per ruolo, Competenze persone, Gap & azioni) + js/competenze.js. Bootstrap Italia V2. CTA "Assegna corso" (riuso /training/assign) e "Apri non conformità". - Voce sidebar "Competenze" (common.js + common-bi.js + BI_PAGES) + nav.competences i18n IT/EN. api.js: metodi comp* + assignTraining. Help/KB/PWA: - help.js: guida contestuale 'competences' (schede, calcolo gap, PR.AT-01/02, GV.RR-04, art.24 D.Lgs., disclaimer no-parere-legale) + mappa pagina. - sw.js: nome cache nis2-shell-v1.17.0 (allineamento PWA). - Design doc corretto (rettifica capa_actions vs corrective_actions) + avanzamento 4.1/4.2. Cache-buster: ?v=20260618 dei 5 JS condivisi su 32 HTML. version.json 1.16.0 -> 1.17.0. Smoke E2E su prod (fpm reale): catalogo, requisito, competenza, gap=2, openAction (NCR+CAPA), anti-dup 409, mappatura corso — tutti verdi; dati di test ripuliti. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
47199f1e4e
commit
5368b0a61c
@@ -1,7 +1,11 @@
|
||||
# DESIGN — A4: Modello relazionale (organigramma RACI ↔ skill / formazione / inventario / procedure / rischi + scadenziario)
|
||||
|
||||
> Stato: **DESIGN (pre-implementazione)**. Contesto: segnalazioni Simon Fattori, punti **2, 3, 4, 5, 6, 7** (accolti in A4). Data: 2026-06-15.
|
||||
> Stato: **IN CORSO**. Contesto: segnalazioni Simon Fattori, punti **2, 3, 4, 5, 6, 7** (accolti in A4). Data: 2026-06-15.
|
||||
> È l'epic più grande → da realizzare a **FASI**. Ancoraggio normativo verificato (Det. ACN 164179/2025 + D.Lgs. 138/2024). Vedi `reference-dlgs-articles`, `DESIGN_A2_ONBOARDING.md`.
|
||||
>
|
||||
> **AVANZAMENTO**: ✅ **4.1 Organigramma** (migration `041_org_roles.sql`, `OrgRoleController`, `organigramma.html`) — LIVE. ✅ **4.2 Competenze** (migration `042_competences.sql`: `skills`/`role_skills`/`user_skills`/`skill_course_map`; `CompetenceController`; `competenze.html`) — LIVE. ⏳ 4.3 RACI · 4.4 scadenziario · 4.5 stakeholder.
|
||||
>
|
||||
> 🔴 **RETTIFICA NOMI REALI (verificata sul codice)**: questo design parlava di una tabella **`corrective_actions`** che **NON ESISTE**. La tabella delle azioni reale è **`capa_actions`** (mig.004), **figlia obbligatoria** di **`non_conformities`** (NCR) via `ncr_id NOT NULL`, e **non ha** una colonna `source_type`. L'alert "gap → azione" (impl. in 4.2) quindi **NON** crea `corrective_actions`/`source_type`: crea una **`non_conformities`** ancorata al gap (`source='management_review'`, `source_entity_type='competence_gap'`, `source_entity_id=role_skills.id`) + una **`capa_actions`** figlia. Le menzioni di `corrective_actions`/`source_type` qui sotto vanno lette in questo senso.
|
||||
|
||||
## 0. Principio
|
||||
A4 trasforma i moduli oggi "a silos" in un **grafo relazionale** con al centro l'**organigramma** (ruoli/responsabilità) e la **matrice RACI** come hub che lega i ruoli agli oggetti (inventario, procedure, rischi). Obiettivo: compliance *vissuta e dimostrabile in audit* (chi è responsabile di cosa, con quali competenze, con quali scadenze).
|
||||
@@ -31,7 +35,7 @@ A4 trasforma i moduli oggi "a silos" in un **grafo relazionale** con al centro l
|
||||
- **`raci_assignments`** — **MATRICE RACI** (hub): `id, organization_id, role_id → org_roles, object_type ENUM('inventory','procedure','risk','supplier'), object_id, raci ENUM('R','A','C','I')`. → lega l'organigramma a inventario/procedure/rischi/fornitori (m2m via questa tabella).
|
||||
- **link m2m diretti** (richiesti dalle segnalazioni 4/5/7): `procedure_inventory(procedure_id, asset_id)`, `procedure_risk(procedure_id, risk_id)`, `inventory_risk(asset_id, risk_id)`, `risk_measure(risk_id, measure_code)` (le misure ACN dell'assessment).
|
||||
- **`review_schedule`** — **SCADENZIARIO centralizzato**: `id, organization_id, entity_type ENUM('role','skill','inventory','procedure','risk','supplier','measure'), entity_id, owner_role_id NULL, frequency_months, last_reviewed_at, next_review_date, status ENUM('ok','due','overdue'), notes`. Aggrega/sostituisce le scadenze sparse.
|
||||
- **`actions`** (oppure riuso `corrective_actions`): azione generata da **alert** (gap competenza, scadenza, rischio) → `source_type, source_id, assigned_role_id, due_date, status`. *(Riuso consigliato di `corrective_actions` + un campo `source_type` per non duplicare il workflow CAPA.)*
|
||||
- **azione da alert** → **riuso `non_conformities` + `capa_actions`** (mig.004): l'alert (gap competenza, scadenza, rischio) crea una NCR ancorata via `source_entity_type`/`source_entity_id` (es. `'competence_gap'` + `role_skills.id`) e una `capa_actions` figlia. *(NON esiste `corrective_actions`/`source_type`: si riusa l'intero workflow CAPA senza alterarne lo schema.)*
|
||||
|
||||
**Relazioni chiave** (tutte m2m dove indicato dalle segnalazioni):
|
||||
```
|
||||
@@ -41,11 +45,11 @@ skills ──< skill_course_map >── training_courses
|
||||
procedures ──< procedure_inventory >── inventory ──< inventory_risk >── risks
|
||||
procedures ──< procedure_risk >── risks ──< risk_measure >── misure ACN (assessment)
|
||||
(role|skill|inventory|procedure|risk|supplier) ──1:N── review_schedule
|
||||
gap competenze / scadenze / rischi ──trigger──> actions(corrective_actions)
|
||||
gap competenze / scadenze / rischi ──trigger──> non_conformities ──< capa_actions (workflow CAPA)
|
||||
```
|
||||
|
||||
## 3. Gap competenze (segnalazione 3)
|
||||
**Gap = `role_skills.required_level` (del ruolo che l'utente ricopre) − `user_skills.level`** per ciascuna skill richiesta. Vista `competence_gap` (calcolata): per ogni utente con `org_roles.holder_user_id`, confronto richiesto vs posseduto. **Alert** quando gap > 0 → apertura **azione** (`corrective_actions`, `source_type='competence_gap'`) + voce nello **`review_schedule`**. Ancoraggio: **PR.AT-01** (tutti), **PR.AT-02** (solo essenziali), **GV.RR-04** (cyber nelle pratiche HR).
|
||||
**Gap = `role_skills.required_level` (del ruolo che l'utente ricopre) − `user_skills.level`** per ciascuna skill richiesta. Vista `competence_gap` (calcolata): per ogni utente con `org_roles.holder_user_id`, confronto richiesto vs posseduto. **Alert** quando gap > 0 → apertura **azione** = NCR (`non_conformities`, `source_entity_type='competence_gap'`, `source_entity_id=role_skills.id`) + `capa_actions` figlia (+ in futuro voce nello **`review_schedule`**). Ancoraggio: **PR.AT-01** (tutti), **PR.AT-02** (solo essenziali), **GV.RR-04** (cyber nelle pratiche HR).
|
||||
|
||||
## 4. Ancoraggio normativo per entità (must-have)
|
||||
- **Organigramma/ruoli** → **GV.RR-02** (ruoli, responsabilità e poteri stabiliti/comunicati) + **Art. 23 D.Lgs. 138/2024** (responsabilità organi di amministrazione e direttivi) → **`is_governance_body`** obbligatorio come nodo distinto con potere di approvazione.
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
-- ============================================================================
|
||||
-- Migration 042 — A4 Fase 4.2: Competenze (skill / requisiti ruolo / competenze
|
||||
-- possedute / mappatura corsi) + base per gap competenze e alert->azione.
|
||||
-- ----------------------------------------------------------------------------
|
||||
-- 4 tabelle ADDITIVE. Idempotente (CREATE TABLE IF NOT EXISTS). Applicare con la
|
||||
-- STESSA connessione PDO dell'app (host MySQL servito da php-fpm), via runner.
|
||||
--
|
||||
-- Ancoraggio normativo (docs/DESIGN_A4_RELATIONAL.md sez.3/4): PR.AT-01 (tutti),
|
||||
-- PR.AT-02 (solo soggetti essenziali), GV.RR-04 (cyber nelle pratiche HR).
|
||||
--
|
||||
-- NOTA architetturale: l'alert "gap competenza -> azione" RIUSA il workflow
|
||||
-- NCR/CAPA gia esistente (tabelle non_conformities + capa_actions, mig.004).
|
||||
-- NON esiste alcuna tabella 'corrective_actions' (nome errato nel design): le
|
||||
-- azioni sono capa_actions, figlie obbligatorie di una non_conformity. Quindi
|
||||
-- questa migration NON crea/altera tabelle azioni: ancora il gap alla NCR via
|
||||
-- non_conformities.source_entity_type='competence_gap' + source_entity_id.
|
||||
-- ============================================================================
|
||||
|
||||
-- Catalogo competenze. organization_id NULL = competenza globale di sistema
|
||||
-- (visibile a tutte le org, come i corsi globali).
|
||||
CREATE TABLE IF NOT EXISTS skills (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
organization_id INT NULL,
|
||||
name VARCHAR(150) NOT NULL,
|
||||
area VARCHAR(100) NULL,
|
||||
description TEXT NULL,
|
||||
created_by INT NULL,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (organization_id) REFERENCES organizations(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (created_by) REFERENCES users(id) ON DELETE SET NULL,
|
||||
INDEX idx_skills_org (organization_id),
|
||||
INDEX idx_skills_area (area)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
-- Competenze RICHIESTE da un ruolo dell'organigramma (m2m ruolo<->skill).
|
||||
CREATE TABLE IF NOT EXISTS role_skills (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
organization_id INT NOT NULL,
|
||||
role_id INT NOT NULL,
|
||||
skill_id INT NOT NULL,
|
||||
required_level TINYINT NOT NULL DEFAULT 3,
|
||||
created_by INT NULL,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (organization_id) REFERENCES organizations(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (role_id) REFERENCES org_roles(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (skill_id) REFERENCES skills(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (created_by) REFERENCES users(id) ON DELETE SET NULL,
|
||||
UNIQUE KEY uk_role_skill (role_id, skill_id),
|
||||
INDEX idx_role_skills_org (organization_id),
|
||||
INDEX idx_role_skills_skill (skill_id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
-- Competenze POSSEDUTE da un utente (m2m utente<->skill). acquired_via_course_id
|
||||
-- collega eventualmente la competenza al corso che l'ha erogata (registro corsi).
|
||||
CREATE TABLE IF NOT EXISTS user_skills (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
organization_id INT NOT NULL,
|
||||
user_id INT NOT NULL,
|
||||
skill_id INT NOT NULL,
|
||||
level TINYINT NOT NULL DEFAULT 1,
|
||||
acquired_via_course_id INT NULL,
|
||||
evidence VARCHAR(500) NULL,
|
||||
assessed_at DATETIME NULL,
|
||||
created_by INT NULL,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (organization_id) REFERENCES organizations(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (skill_id) REFERENCES skills(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (acquired_via_course_id) REFERENCES training_courses(id) ON DELETE SET NULL,
|
||||
FOREIGN KEY (created_by) REFERENCES users(id) ON DELETE SET NULL,
|
||||
UNIQUE KEY uk_user_skill (organization_id, user_id, skill_id),
|
||||
INDEX idx_user_skills_user (user_id),
|
||||
INDEX idx_user_skills_skill (skill_id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
-- Mappatura competenza <-> corso che la eroga (m2m). Il corso puo essere globale
|
||||
-- (training_courses.organization_id NULL) o dell'org.
|
||||
CREATE TABLE IF NOT EXISTS skill_course_map (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
skill_id INT NOT NULL,
|
||||
training_course_id INT NOT NULL,
|
||||
created_by INT NULL,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (skill_id) REFERENCES skills(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (training_course_id) REFERENCES training_courses(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (created_by) REFERENCES users(id) ON DELETE SET NULL,
|
||||
UNIQUE KEY uk_skill_course (skill_id, training_course_id),
|
||||
INDEX idx_skill_course_course (training_course_id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
Reference in New Issue
Block a user