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>
436 lines
27 KiB
JavaScript
436 lines
27 KiB
JavaScript
/**
|
|
* NIS2 Agile - API Client
|
|
*
|
|
* Client JavaScript per comunicare con il backend REST API.
|
|
*/
|
|
|
|
class NIS2API {
|
|
constructor(baseUrl = '/api') {
|
|
this.baseUrl = baseUrl;
|
|
this.token = localStorage.getItem('nis2_access_token');
|
|
this.refreshToken = localStorage.getItem('nis2_refresh_token');
|
|
this.orgId = localStorage.getItem('nis2_org_id');
|
|
}
|
|
|
|
// ═══════════════════════════════════════════════════════════════════
|
|
// HTTP Methods
|
|
// ═══════════════════════════════════════════════════════════════════
|
|
|
|
async request(method, endpoint, data = null, options = {}) {
|
|
const url = `${this.baseUrl}${endpoint}`;
|
|
const headers = {
|
|
'Content-Type': 'application/json',
|
|
};
|
|
|
|
if (this.token) {
|
|
headers['Authorization'] = `Bearer ${this.token}`;
|
|
}
|
|
|
|
if (this.orgId) {
|
|
headers['X-Organization-Id'] = this.orgId;
|
|
}
|
|
|
|
const config = { method, headers };
|
|
|
|
if (data && (method === 'POST' || method === 'PUT')) {
|
|
config.body = JSON.stringify(data);
|
|
}
|
|
|
|
try {
|
|
const response = await fetch(url, config);
|
|
const json = await response.json();
|
|
|
|
// Token expired - try refresh
|
|
if (response.status === 401 && this.refreshToken && !options._isRetry) {
|
|
const refreshed = await this.doRefreshToken();
|
|
if (refreshed) {
|
|
return this.request(method, endpoint, data, { ...options, _isRetry: true });
|
|
}
|
|
}
|
|
|
|
if (!json.success && !options.silent) {
|
|
console.error(`[API] ${method} ${endpoint}:`, json.message);
|
|
}
|
|
|
|
return json;
|
|
} catch (error) {
|
|
console.error(`[API] Network error: ${method} ${endpoint}`, error);
|
|
const msg = typeof I18n !== 'undefined' ? I18n.t('msg.error_connection') : 'Errore di connessione al server';
|
|
return { success: false, message: msg };
|
|
}
|
|
}
|
|
|
|
get(endpoint) { return this.request('GET', endpoint); }
|
|
post(endpoint, data) { return this.request('POST', endpoint, data); }
|
|
put(endpoint, data) { return this.request('PUT', endpoint, data); }
|
|
del(endpoint) { return this.request('DELETE', endpoint); }
|
|
|
|
// ═══════════════════════════════════════════════════════════════════
|
|
// Auth
|
|
// ═══════════════════════════════════════════════════════════════════
|
|
|
|
async login(email, password) {
|
|
const result = await this.post('/auth/login', { email, password });
|
|
if (result.success) {
|
|
this.setTokens(result.data.access_token, result.data.refresh_token);
|
|
this.setUserRole(result.data.user.role);
|
|
if (result.data.organizations && result.data.organizations.length > 0) {
|
|
const primary = result.data.organizations.find(o => o.is_primary) || result.data.organizations[0];
|
|
this.setOrganization(primary.organization_id);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
async register(email, password, fullName, roleOrType = 'azienda') {
|
|
// Supporta sia i nuovi role NIS2 diretti (compliance_manager, org_admin, etc.)
|
|
// che il vecchio user_type (azienda, consultant) per retrocompatibilità
|
|
const result = await this.post('/auth/register', {
|
|
email, password, full_name: fullName,
|
|
role: roleOrType,
|
|
user_type: roleOrType, // backward compat
|
|
});
|
|
if (result.success) {
|
|
this.setTokens(result.data.access_token, result.data.refresh_token);
|
|
localStorage.setItem('nis2_user_role', result.data.user.role);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
async doRefreshToken() {
|
|
try {
|
|
const result = await this.post('/auth/refresh', { refresh_token: this.refreshToken });
|
|
if (result.success) {
|
|
this.setTokens(result.data.access_token, result.data.refresh_token);
|
|
return true;
|
|
}
|
|
} catch (e) { /* ignore */ }
|
|
this.logout();
|
|
return false;
|
|
}
|
|
|
|
logout() {
|
|
this.post('/auth/logout', {}).catch(() => {});
|
|
this.clearTokens();
|
|
window.location.href = '/login.html';
|
|
}
|
|
|
|
getMe() { return this.get('/auth/me'); }
|
|
|
|
setTokens(access, refresh) {
|
|
this.token = access;
|
|
this.refreshToken = refresh;
|
|
localStorage.setItem('nis2_access_token', access);
|
|
localStorage.setItem('nis2_refresh_token', refresh);
|
|
}
|
|
|
|
clearTokens() {
|
|
this.token = null;
|
|
this.refreshToken = null;
|
|
this.orgId = null;
|
|
localStorage.removeItem('nis2_access_token');
|
|
localStorage.removeItem('nis2_refresh_token');
|
|
localStorage.removeItem('nis2_org_id');
|
|
localStorage.removeItem('nis2_user_role');
|
|
}
|
|
|
|
// Salva ruolo utente al login
|
|
setUserRole(role) {
|
|
localStorage.setItem('nis2_user_role', role);
|
|
}
|
|
|
|
getUserRole() {
|
|
return localStorage.getItem('nis2_user_role');
|
|
}
|
|
|
|
isConsultant() {
|
|
return this.getUserRole() === 'consultant';
|
|
}
|
|
|
|
setOrganization(orgId) {
|
|
this.orgId = orgId;
|
|
localStorage.setItem('nis2_org_id', orgId);
|
|
}
|
|
|
|
isAuthenticated() {
|
|
return !!this.token;
|
|
}
|
|
|
|
// ═══════════════════════════════════════════════════════════════════
|
|
// Organizations
|
|
// ═══════════════════════════════════════════════════════════════════
|
|
|
|
createOrganization(data) { return this.post('/organizations/create', data); }
|
|
getCurrentOrg() { return this.get('/organizations/current'); }
|
|
listOrganizations() { return this.get('/organizations/list'); }
|
|
updateOrganization(id, data) { return this.put(`/organizations/${id}`, data); }
|
|
classifyEntity(data) { return this.post('/organizations/classify', data); }
|
|
|
|
// ═══════════════════════════════════════════════════════════════════
|
|
// Assessments
|
|
// ═══════════════════════════════════════════════════════════════════
|
|
|
|
listAssessments() { return this.get('/assessments/list'); }
|
|
createAssessment(data) { return this.post('/assessments/create', data); }
|
|
getAssessment(id) { return this.get(`/assessments/${id}`); }
|
|
getAssessmentQuestions(id) { return this.get(`/assessments/${id}/questions`); }
|
|
saveAssessmentResponse(id, data) { return this.post(`/assessments/${id}/respond`, data); }
|
|
completeAssessment(id) { return this.post(`/assessments/${id}/complete`, {}); }
|
|
getAssessmentReport(id) { return this.get(`/assessments/${id}/report`); }
|
|
aiAnalyzeAssessment(id) { return this.post(`/assessments/${id}/ai-analyze`, {}); }
|
|
|
|
// ═══════════════════════════════════════════════════════════════════
|
|
// Gap Analysis ACN (Determinazione 164179/2025 - misure/requisiti)
|
|
// I metodi acn* ritornano direttamente il payload `data` e lanciano
|
|
// un errore {message, error_code} se success=false (per il try/catch UI).
|
|
// ═══════════════════════════════════════════════════════════════════
|
|
async _acn(promise) {
|
|
const r = await promise;
|
|
if (!r || !r.success) {
|
|
const err = new Error((r && r.message) || 'Errore');
|
|
err.error_code = r && (r.error_code || (r.data && r.data.error_code));
|
|
err.data = r && r.data;
|
|
throw err;
|
|
}
|
|
return r.data;
|
|
}
|
|
acnCatalog() { return this._acn(this.get('/acn-gap/catalog')); }
|
|
acnList() { return this._acn(this.get('/acn-gap/list')); }
|
|
acnCreate(data) { return this._acn(this.post('/acn-gap/create', data || {})); }
|
|
acnGet(id) { return this._acn(this.get(`/acn-gap/${id}`)); }
|
|
acnRequirements(id) { return this._acn(this.get(`/acn-gap/${id}/requirements`)); }
|
|
acnRespond(id, data) { return this._acn(this.post(`/acn-gap/${id}/respond`, data)); }
|
|
acnComplete(id) { return this._acn(this.post(`/acn-gap/${id}/complete`, {})); }
|
|
acnReport(id) { return this._acn(this.get(`/acn-gap/${id}/report`)); }
|
|
acnAiAnalyze(id) { return this._acn(this.post(`/acn-gap/${id}/aiAnalyze`, {})); }
|
|
|
|
// ═══════════════════════════════════════════════════════════════════
|
|
// Modello Organizzativo SGSI (ISO 27001/27017/27018) — vedi IsmsModelController
|
|
// Stesso contratto degli acn*: ritornano `data`, lanciano su success=false.
|
|
// ═══════════════════════════════════════════════════════════════════
|
|
ismsGetModel() { return this._acn(this.get('/isms/model')); }
|
|
ismsSaveModel(data) { return this._acn(this.post('/isms/model', data || {})); }
|
|
ismsAnnexControls() { return this._acn(this.get('/isms/annex-controls')); }
|
|
ismsGetSoa() { return this._acn(this.get('/isms/soa')); }
|
|
ismsDeriveSoa() { return this._acn(this.post('/isms/soa/derive', {})); }
|
|
ismsUpdateSoa(data) { return this._acn(this.put('/isms/soa', data)); }
|
|
ismsRoles() { return this._acn(this.get('/isms/roles')); }
|
|
ismsSaveRole(data) { return this._acn(this.post('/isms/roles', data)); }
|
|
ismsDeleteRole(id) { return this._acn(this.del(`/isms/roles/${id}`)); }
|
|
ismsDocuments() { return this._acn(this.get('/isms/documents')); }
|
|
ismsCreateDocument(data) { return this._acn(this.post('/isms/documents', data)); }
|
|
ismsAiGenerateDocument(data) { return this._acn(this.post('/isms/documents/ai-generate', data)); }
|
|
ismsUpdateDocument(id, data) { return this._acn(this.put(`/isms/documents/${id}`, data)); }
|
|
ismsReadiness() { return this._acn(this.get('/isms/readiness')); }
|
|
ismsExport() { return this._acn(this.get('/isms/export')); }
|
|
|
|
// ═══════════════════════════════════════════════════════════════════
|
|
// Organigramma (A4 Fase 4.1) — ruoli/gerarchia + nodo governance (Art.23).
|
|
// Stesso contratto degli acn*/isms*: ritornano `data`, lanciano su success=false.
|
|
// ═══════════════════════════════════════════════════════════════════
|
|
orgRolesList() { return this._acn(this.get('/org-roles/list')); }
|
|
orgRolesAssignableUsers() { return this._acn(this.get('/org-roles/assignable-users')); }
|
|
orgRoleGet(id) { return this._acn(this.get(`/org-roles/${id}`)); }
|
|
orgRoleCreate(data) { return this._acn(this.post('/org-roles/create', data || {})); }
|
|
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
|
|
// ═══════════════════════════════════════════════════════════════════
|
|
|
|
getDashboardOverview() { return this.get('/dashboard/overview'); }
|
|
getComplianceScore() { return this.get('/dashboard/compliance-score'); }
|
|
getUpcomingDeadlines() { return this.get('/dashboard/upcoming-deadlines'); }
|
|
getRecentActivity() { return this.get('/dashboard/recent-activity'); }
|
|
getRiskHeatmap() { return this.get('/dashboard/risk-heatmap'); }
|
|
|
|
// ═══════════════════════════════════════════════════════════════════
|
|
// Risks
|
|
// ═══════════════════════════════════════════════════════════════════
|
|
|
|
listRisks(params = {}) { return this.get('/risks/list?' + new URLSearchParams(params)); }
|
|
createRisk(data) { return this.post('/risks/create', data); }
|
|
getRisk(id) { return this.get(`/risks/${id}`); }
|
|
updateRisk(id, data) { return this.put(`/risks/${id}`, data); }
|
|
deleteRisk(id) { return this.del(`/risks/${id}`); }
|
|
getRiskMatrix() { return this.get('/risks/matrix'); }
|
|
aiSuggestRisks() { return this.post('/risks/ai-suggest', {}); }
|
|
// FAIR quantitativo + KRI (P2)
|
|
computeFair(id, data) { return this.post(`/risks/${id}/fair`, data); }
|
|
getFairRegister() { return this.get('/risks/fairRegister'); }
|
|
listKri() { return this.get('/risks/kri'); }
|
|
createKri(data) { return this.post('/risks/kri', data); }
|
|
updateKri(id, data) { return this.put(`/risks/kri/${id}`, data); }
|
|
|
|
// ═══════════════════════════════════════════════════════════════════
|
|
// Incidents
|
|
// ═══════════════════════════════════════════════════════════════════
|
|
|
|
listIncidents(params = {}) { return this.get('/incidents/list?' + new URLSearchParams(params)); }
|
|
createIncident(data) { return this.post('/incidents/create', data); }
|
|
getIncident(id) { return this.get(`/incidents/${id}`); }
|
|
updateIncident(id, data) { return this.put(`/incidents/${id}`, data); }
|
|
sendEarlyWarning(id) { return this.post(`/incidents/${id}/early-warning`, {}); }
|
|
sendNotification(id) { return this.post(`/incidents/${id}/notification`, {}); }
|
|
sendFinalReport(id) { return this.post(`/incidents/${id}/final-report`, {}); }
|
|
aiClassifyIncident(id) { return this.post(`/incidents/${id}/aiClassify`, {}); }
|
|
getIncidentMetrics(id) { return this.get(`/incidents/${id}/metrics`); }
|
|
getIncidentPir(id) { return this.get(`/incidents/${id}/pir`); }
|
|
saveIncidentPir(id, data) { return this.post(`/incidents/${id}/pir`, data); }
|
|
|
|
// ═══════════════════════════════════════════════════════════════════
|
|
// Policies
|
|
// ═══════════════════════════════════════════════════════════════════
|
|
|
|
listPolicies(params = {}) { return this.get('/policies/list?' + new URLSearchParams(params)); }
|
|
createPolicy(data) { return this.post('/policies/create', data); }
|
|
getPolicy(id) { return this.get(`/policies/${id}`); }
|
|
updatePolicy(id, data) { return this.put(`/policies/${id}`, data); }
|
|
approvePolicy(id) { return this.post(`/policies/${id}/approve`, {}); }
|
|
aiGeneratePolicy(category) { return this.post('/policies/ai-generate', { category }); }
|
|
getPolicyTemplates() { return this.get('/policies/templates'); }
|
|
|
|
// ═══════════════════════════════════════════════════════════════════
|
|
// Supply Chain
|
|
// ═══════════════════════════════════════════════════════════════════
|
|
|
|
listSuppliers() { return this.get('/supply-chain/list'); }
|
|
createSupplier(data) { return this.post('/supply-chain/create', data); }
|
|
getSupplier(id) { return this.get(`/supply-chain/${id}`); }
|
|
updateSupplier(id, data) { return this.put(`/supply-chain/${id}`, data); }
|
|
assessSupplier(id, data) { return this.post(`/supply-chain/${id}/assess`, data); }
|
|
|
|
// ═══════════════════════════════════════════════════════════════════
|
|
// Training
|
|
// ═══════════════════════════════════════════════════════════════════
|
|
|
|
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
|
|
// ═══════════════════════════════════════════════════════════════════
|
|
|
|
listAssets(params = {}) { return this.get('/assets/list?' + new URLSearchParams(params)); }
|
|
createAsset(data) { return this.post('/assets/create', data); }
|
|
getAsset(id) { return this.get(`/assets/${id}`); }
|
|
updateAsset(id, data) { return this.put(`/assets/${id}`, data); }
|
|
deleteAsset(id) { return this.del(`/assets/${id}`); }
|
|
// NIS2 relevance scoring (GV.OC-04)
|
|
getScoringGrid() { return this.get('/assets/scoringGrid'); }
|
|
scoreAsset(id, criteria) { return this.post(`/assets/${id}/score`, { criteria }); }
|
|
listRelevantSystems() { return this.get('/assets/relevantSystems'); }
|
|
importAssets(data) { return this.post('/assets/import', data); } // P2 import CMDB/CSV
|
|
getControlsMonitoring() { return this.get('/audit/controlsMonitoring'); }
|
|
getAcnRequirements() { return this.get('/audit/acnRequirements'); } // requisiti ACN per org
|
|
updateAcnRequirement(id, status, note) { return this.put(`/audit/acnRequirements/${id}`, { status, evidence_note: note }); }
|
|
sendSupplierQuestionnaire(id, email) { return this.post(`/supply-chain/${id}/send-questionnaire`, email ? { email } : {}); } // self-assessment fornitore (link esterno)
|
|
getSupplierQuestionnaireStatus(id) { return this.get(`/supply-chain/${id}/questionnaire-status`); } // P1 continuous control monitoring (JWT)
|
|
|
|
// Modulo questionari configurabile (Fase 1): categorie, template, domande, import
|
|
getSupplierCategories() { return this.get('/supply-chain/categories'); }
|
|
createSupplierCategory(data) { return this.post('/supply-chain/categories', data); }
|
|
updateSupplierCategory(id, data) { return this.put(`/supply-chain/categories/${id}`, data); }
|
|
deleteSupplierCategory(id) { return this.del(`/supply-chain/categories/${id}`); }
|
|
getQuestionnaireTemplates() { return this.get('/supply-chain/templates'); }
|
|
getQuestionnaireTemplate(id) { return this.get(`/supply-chain/templates/${id}`); }
|
|
createQuestionnaireTemplate(data) { return this.post('/supply-chain/templates', data); }
|
|
updateQuestionnaireTemplate(id, data) { return this.put(`/supply-chain/templates/${id}`, data); }
|
|
addTemplateQuestion(templateId, data) { return this.post(`/supply-chain/${templateId}/questions`, data); }
|
|
updateTemplateQuestion(id, data) { return this.put(`/supply-chain/questions/${id}`, data); }
|
|
deleteTemplateQuestion(id) { return this.del(`/supply-chain/questions/${id}`); }
|
|
importSuppliers(suppliers) { return this.post('/supply-chain/import', { suppliers }); }
|
|
// Campagne questionario (Fase 2)
|
|
getQuestionnaireCampaigns() { return this.get('/supply-chain/campaigns'); }
|
|
createQuestionnaireCampaign(supplierId, data) { return this.post(`/supply-chain/${supplierId}/campaigns`, data); }
|
|
// Campagne questionario (Fase 2)
|
|
getQuestionnaireCampaigns() { return this.get('/supply-chain/campaigns'); }
|
|
createQuestionnaireCampaign(supplierId, data) { return this.post(`/supply-chain/${supplierId}/campaigns`, data); }
|
|
|
|
// ═══════════════════════════════════════════════════════════════════
|
|
// Audit
|
|
// ═══════════════════════════════════════════════════════════════════
|
|
|
|
listControls() { return this.get('/audit/controls'); }
|
|
updateControl(id, data) { return this.put(`/audit/controls/${id}`, data); }
|
|
generateComplianceReport() { return this.get('/audit/report'); }
|
|
getAuditLogs(params = {}) { return this.get('/audit/logs?' + new URLSearchParams(params)); }
|
|
getIsoMapping() { return this.get('/audit/iso27001-mapping'); }
|
|
getExecutiveReportUrl() { return this.baseUrl + '/audit/executive-report'; }
|
|
getExportUrl(type) { return this.baseUrl + '/audit/export?type=' + type; }
|
|
|
|
// ═══════════════════════════════════════════════════════════════════
|
|
// Onboarding
|
|
// ═══════════════════════════════════════════════════════════════════
|
|
|
|
async uploadVisura(file) {
|
|
const formData = new FormData();
|
|
formData.append('visura', file);
|
|
const headers = { 'Authorization': 'Bearer ' + this.token };
|
|
if (this.orgId) headers['X-Organization-Id'] = this.orgId;
|
|
try {
|
|
const response = await fetch(this.baseUrl + '/onboarding/upload-visura', {
|
|
method: 'POST',
|
|
headers,
|
|
body: formData,
|
|
});
|
|
return response.json();
|
|
} catch (error) {
|
|
const msg = typeof I18n !== 'undefined' ? I18n.t('msg.error_connection') : 'Errore di connessione al server';
|
|
return { success: false, message: msg };
|
|
}
|
|
}
|
|
|
|
fetchCompany(vatNumber) { return this.post('/onboarding/fetch-company', { vat_number: vatNumber }); }
|
|
completeOnboarding(data) { return this.post('/onboarding/complete', data); }
|
|
|
|
// ═══════════════════════════════════════════════════════════════════
|
|
// Non-Conformity & CAPA
|
|
// ═══════════════════════════════════════════════════════════════════
|
|
|
|
listNCRs(params = {}) { return this.get('/ncr/list?' + new URLSearchParams(params)); }
|
|
createNCR(data) { return this.post('/ncr/create', data); }
|
|
getNCR(id) { return this.get(`/ncr/${id}`); }
|
|
updateNCR(id, data) { return this.put(`/ncr/${id}`, data); }
|
|
addCapa(ncrId, data) { return this.post(`/ncr/${ncrId}/capa`, data); }
|
|
updateCapa(capaId, data) { return this.put(`/ncr/capa/${capaId}`, data); }
|
|
createNCRsFromAssessment(assessmentId) { return this.post('/ncr/from-assessment', { assessment_id: assessmentId }); }
|
|
getNCRStats() { return this.get('/ncr/stats'); }
|
|
|
|
// ═══════════════════════════════════════════════════════════════════
|
|
// Feedback & Segnalazioni
|
|
// ═══════════════════════════════════════════════════════════════════
|
|
|
|
submitFeedback(data) { return this.post('/feedback/submit', data); }
|
|
getMyFeedback() { return this.get('/feedback/mine'); }
|
|
listFeedback(params = {}) { return this.get('/feedback/list?' + new URLSearchParams(params)); }
|
|
getFeedback(id) { return this.get(`/feedback/${id}`); }
|
|
updateFeedback(id, data) { return this.put(`/feedback/${id}`, data); }
|
|
resolveFeedback(id, password) { return this.post(`/feedback/${id}/resolve`, { password }); }
|
|
}
|
|
|
|
// Singleton globale
|
|
const api = new NIS2API();
|