requireOrgAccess(); $courses = Database::fetchAll( 'SELECT * FROM training_courses WHERE (organization_id = ? OR organization_id IS NULL) AND is_active = 1 ORDER BY is_mandatory DESC, title', [$this->getCurrentOrgId()] ); $this->jsonSuccess($courses); } public function createCourse(): void { $this->requireOrgRole(['org_admin', 'compliance_manager']); $this->validateRequired(['title']); $courseId = Database::insert('training_courses', [ 'organization_id' => $this->getCurrentOrgId(), 'title' => trim($this->getParam('title')), 'description' => $this->getParam('description'), 'target_role' => $this->getParam('target_role', 'all'), 'nis2_article' => $this->getParam('nis2_article'), 'is_mandatory' => $this->getParam('is_mandatory', 0), 'duration_minutes' => $this->getParam('duration_minutes'), 'content' => $this->getParam('content') ? json_encode($this->getParam('content')) : null, 'quiz' => $this->getParam('quiz') ? json_encode($this->getParam('quiz')) : null, 'passing_score' => $this->getParam('passing_score', 70), ]); $this->logAudit('course_created', 'training_course', $courseId); $this->jsonSuccess(['id' => $courseId], 'Corso creato', 201); } public function myAssignments(): void { $this->requireAuth(); $assignments = Database::fetchAll( 'SELECT ta.*, tc.title, tc.description, tc.duration_minutes, tc.is_mandatory FROM training_assignments ta JOIN training_courses tc ON tc.id = ta.course_id WHERE ta.user_id = ? ORDER BY ta.status, ta.due_date', [$this->getCurrentUserId()] ); $this->jsonSuccess($assignments); } public function assignCourse(): void { $this->requireOrgRole(['org_admin', 'compliance_manager']); $this->validateRequired(['course_id', 'user_ids']); $courseId = (int) $this->getParam('course_id'); $userIds = $this->getParam('user_ids'); $dueDate = $this->getParam('due_date'); $assigned = 0; foreach ($userIds as $userId) { $existing = Database::fetchOne( 'SELECT id FROM training_assignments WHERE course_id = ? AND user_id = ?', [$courseId, $userId] ); if ($existing) continue; Database::insert('training_assignments', [ 'course_id' => $courseId, 'user_id' => (int) $userId, 'organization_id' => $this->getCurrentOrgId(), 'due_date' => $dueDate, ]); $assigned++; } $this->logAudit('training_assigned', 'training_course', $courseId, ['assigned_count' => $assigned]); $this->jsonSuccess(['assigned' => $assigned], "{$assigned} assegnazioni create"); } public function updateAssignment(int $id): void { $this->requireAuth(); $updates = []; foreach (['status', 'quiz_score'] as $field) { if ($this->hasParam($field)) { $updates[$field] = $this->getParam($field); } } if (isset($updates['status']) && $updates['status'] === 'in_progress' && !isset($updates['started_at'])) { $updates['started_at'] = date('Y-m-d H:i:s'); } if (isset($updates['status']) && $updates['status'] === 'completed') { $updates['completed_at'] = date('Y-m-d H:i:s'); } if (!empty($updates)) { Database::update('training_assignments', $updates, 'id = ? AND user_id = ?', [$id, $this->getCurrentUserId()]); } $this->jsonSuccess($updates, 'Assegnazione aggiornata'); } public function complianceStatus(): void { $this->requireOrgAccess(); $members = Database::fetchAll( 'SELECT u.id, u.full_name, u.email, uo.role FROM user_organizations uo JOIN users u ON u.id = uo.user_id WHERE uo.organization_id = ? AND u.is_active = 1', [$this->getCurrentOrgId()] ); foreach ($members as &$member) { $member['assignments'] = Database::fetchAll( 'SELECT ta.status, ta.completed_at, ta.quiz_score, tc.title, tc.is_mandatory FROM training_assignments ta JOIN training_courses tc ON tc.id = ta.course_id WHERE ta.user_id = ? AND ta.organization_id = ?', [$member['id'], $this->getCurrentOrgId()] ); $mandatory = array_filter($member['assignments'], fn($a) => $a['is_mandatory']); $completedMandatory = array_filter($mandatory, fn($a) => $a['status'] === 'completed'); $member['mandatory_compliance'] = count($mandatory) > 0 ? round(count($completedMandatory) / count($mandatory) * 100) : 100; } $this->jsonSuccess($members); } }