[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
@@ -235,6 +235,25 @@ class NIS2API {
|
||||
orgRoleUpdate(id, data) { return this._acn(this.put(`/org-roles/${id}`, data)); }
|
||||
orgRoleDelete(id) { return this._acn(this.del(`/org-roles/${id}`)); }
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// Competenze (A4 Fase 4.2) — skill / requisiti ruolo / competenze utente /
|
||||
// mappatura corsi / gap competenze / apertura azione (NCR+CAPA). _acn.
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
compCatalog() { return this._acn(this.get('/competences/catalog')); }
|
||||
compCreateSkill(data) { return this._acn(this.post('/competences/skills', data || {})); }
|
||||
compUpdateSkill(id, data) { return this._acn(this.put(`/competences/skills/${id}`, data)); }
|
||||
compDeleteSkill(id) { return this._acn(this.del(`/competences/skills/${id}`)); }
|
||||
compRoleSkills(roleId) { return this._acn(this.get(`/competences/role-skills/${roleId}`)); }
|
||||
compSetRoleSkill(data) { return this._acn(this.post('/competences/role-skills', data)); }
|
||||
compRemoveRoleSkill(id) { return this._acn(this.del(`/competences/role-skills/${id}`)); }
|
||||
compUserSkills(userId) { return this._acn(this.get(`/competences/user-skills/${userId}`)); }
|
||||
compSetUserSkill(data) { return this._acn(this.post('/competences/user-skills', data)); }
|
||||
compRemoveUserSkill(id) { return this._acn(this.del(`/competences/user-skills/${id}`)); }
|
||||
compMapCourse(data) { return this._acn(this.post('/competences/skill-courses', data)); }
|
||||
compUnmapCourse(id) { return this._acn(this.del(`/competences/skill-courses/${id}`)); }
|
||||
compGapGrid() { return this._acn(this.get('/competences/gap-grid')); }
|
||||
compOpenAction(roleSkillId) { return this._acn(this.post('/competences/open-action', { role_skill_id: roleSkillId })); }
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// Dashboard
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
@@ -308,6 +327,7 @@ class NIS2API {
|
||||
listCourses() { return this.get('/training/courses'); }
|
||||
getMyTraining() { return this.get('/training/assignments'); }
|
||||
getTrainingCompliance() { return this.get('/training/compliance-status'); }
|
||||
assignTraining(courseId, userIds, dueDate) { return this.post('/training/assign', { course_id: courseId, user_ids: userIds, due_date: dueDate || null }); }
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// Assets
|
||||
|
||||
Reference in New Issue
Block a user