[FEAT] Visura auto-fill, adesione volontaria, modulo NCR/CAPA
1. Fix auto-fill visura: mapping corretto suggested_sector e employees_range, indicatori visivi verdi sui campi auto-compilati, fatturato sempre manuale 2. Adesione volontaria: colonna voluntary_compliance, checkbox in onboarding step 5 quando not_applicable, toggle in settings, reset su ri-classificazione 3. Modulo NCR/CAPA: NonConformityController con 10 endpoint API, tabelle non_conformities + capa_actions, generazione NCR dai gap assessment, predisposizione integrazione SistemiG.agile (webhook + sync) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,502 @@
|
||||
<?php
|
||||
/**
|
||||
* NIS2 Agile - Non-Conformity & CAPA Controller
|
||||
*
|
||||
* Gestione non conformità e azioni correttive/preventive.
|
||||
* Predisposizione integrazione SistemiG.agile.
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/BaseController.php';
|
||||
|
||||
class NonConformityController extends BaseController
|
||||
{
|
||||
// ═══════════════════════════════════════════════════════════════════════
|
||||
// NCR CRUD
|
||||
// ═══════════════════════════════════════════════════════════════════════
|
||||
|
||||
/**
|
||||
* POST /api/ncr/create
|
||||
*/
|
||||
public function create(): void
|
||||
{
|
||||
$this->requireOrgRole(['org_admin', 'compliance_manager', 'auditor']);
|
||||
$this->validateRequired(['title', 'source']);
|
||||
|
||||
$orgId = $this->getCurrentOrgId();
|
||||
$ncrCode = $this->generateCode('NCR');
|
||||
|
||||
$ncrId = Database::insert('non_conformities', [
|
||||
'organization_id' => $orgId,
|
||||
'ncr_code' => $ncrCode,
|
||||
'title' => trim($this->getParam('title')),
|
||||
'description' => $this->getParam('description'),
|
||||
'source' => $this->getParam('source'),
|
||||
'source_entity_type' => $this->getParam('source_entity_type'),
|
||||
'source_entity_id' => $this->getParam('source_entity_id'),
|
||||
'severity' => $this->getParam('severity', 'minor'),
|
||||
'category' => $this->getParam('category'),
|
||||
'nis2_article' => $this->getParam('nis2_article'),
|
||||
'identified_by' => $this->getCurrentUserId(),
|
||||
'assigned_to' => $this->getParam('assigned_to'),
|
||||
'target_close_date' => $this->getParam('target_close_date'),
|
||||
]);
|
||||
|
||||
$this->logAudit('ncr_created', 'non_conformity', $ncrId, [
|
||||
'ncr_code' => $ncrCode,
|
||||
'source' => $this->getParam('source'),
|
||||
]);
|
||||
|
||||
$this->jsonSuccess([
|
||||
'id' => $ncrId,
|
||||
'ncr_code' => $ncrCode,
|
||||
], 'Non conformita\' creata', 201);
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /api/ncr/list
|
||||
*/
|
||||
public function list(): void
|
||||
{
|
||||
$this->requireOrgAccess();
|
||||
$orgId = $this->getCurrentOrgId();
|
||||
|
||||
[$page, $perPage] = $this->getPagination();
|
||||
$offset = ($page - 1) * $perPage;
|
||||
|
||||
// Filters
|
||||
$where = 'n.organization_id = ?';
|
||||
$params = [$orgId];
|
||||
|
||||
$status = $this->getParam('status');
|
||||
if ($status) {
|
||||
$where .= ' AND n.status = ?';
|
||||
$params[] = $status;
|
||||
}
|
||||
|
||||
$severity = $this->getParam('severity');
|
||||
if ($severity) {
|
||||
$where .= ' AND n.severity = ?';
|
||||
$params[] = $severity;
|
||||
}
|
||||
|
||||
$source = $this->getParam('source');
|
||||
if ($source) {
|
||||
$where .= ' AND n.source = ?';
|
||||
$params[] = $source;
|
||||
}
|
||||
|
||||
$total = Database::count('non_conformities n', $where, $params);
|
||||
|
||||
$ncrs = Database::fetchAll(
|
||||
"SELECT n.*, u1.full_name as identified_by_name, u2.full_name as assigned_to_name,
|
||||
(SELECT COUNT(*) FROM capa_actions WHERE ncr_id = n.id) as capa_count
|
||||
FROM non_conformities n
|
||||
LEFT JOIN users u1 ON u1.id = n.identified_by
|
||||
LEFT JOIN users u2 ON u2.id = n.assigned_to
|
||||
WHERE {$where}
|
||||
ORDER BY n.created_at DESC
|
||||
LIMIT {$perPage} OFFSET {$offset}",
|
||||
$params
|
||||
);
|
||||
|
||||
$this->jsonPaginated($ncrs, $total, $page, $perPage);
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /api/ncr/{id}
|
||||
*/
|
||||
public function get(int $id): void
|
||||
{
|
||||
$this->requireOrgAccess();
|
||||
|
||||
$ncr = Database::fetchOne(
|
||||
'SELECT n.*, u1.full_name as identified_by_name, u2.full_name as assigned_to_name
|
||||
FROM non_conformities n
|
||||
LEFT JOIN users u1 ON u1.id = n.identified_by
|
||||
LEFT JOIN users u2 ON u2.id = n.assigned_to
|
||||
WHERE n.id = ? AND n.organization_id = ?',
|
||||
[$id, $this->getCurrentOrgId()]
|
||||
);
|
||||
|
||||
if (!$ncr) {
|
||||
$this->jsonError('Non conformita\' non trovata', 404, 'NCR_NOT_FOUND');
|
||||
}
|
||||
|
||||
// Load CAPA actions
|
||||
$ncr['capa_actions'] = Database::fetchAll(
|
||||
'SELECT ca.*, u1.full_name as responsible_name, u2.full_name as verified_by_name
|
||||
FROM capa_actions ca
|
||||
LEFT JOIN users u1 ON u1.id = ca.responsible_user_id
|
||||
LEFT JOIN users u2 ON u2.id = ca.verified_by
|
||||
WHERE ca.ncr_id = ?
|
||||
ORDER BY ca.created_at ASC',
|
||||
[$id]
|
||||
);
|
||||
|
||||
$this->jsonSuccess($ncr);
|
||||
}
|
||||
|
||||
/**
|
||||
* PUT /api/ncr/{id}
|
||||
*/
|
||||
public function update(int $id): void
|
||||
{
|
||||
$this->requireOrgRole(['org_admin', 'compliance_manager', 'auditor']);
|
||||
|
||||
$ncr = Database::fetchOne(
|
||||
'SELECT id FROM non_conformities WHERE id = ? AND organization_id = ?',
|
||||
[$id, $this->getCurrentOrgId()]
|
||||
);
|
||||
|
||||
if (!$ncr) {
|
||||
$this->jsonError('Non conformita\' non trovata', 404, 'NCR_NOT_FOUND');
|
||||
}
|
||||
|
||||
$allowedFields = [
|
||||
'title', 'description', 'severity', 'category', 'nis2_article',
|
||||
'status', 'assigned_to', 'target_close_date', 'actual_close_date',
|
||||
'root_cause_analysis', 'root_cause_method',
|
||||
];
|
||||
|
||||
$updates = [];
|
||||
foreach ($allowedFields as $field) {
|
||||
if ($this->hasParam($field)) {
|
||||
$updates[$field] = $this->getParam($field);
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($updates)) {
|
||||
$this->jsonError('Nessun campo da aggiornare', 400, 'NO_UPDATES');
|
||||
}
|
||||
|
||||
// Auto-set close date when status becomes closed
|
||||
if (isset($updates['status']) && $updates['status'] === 'closed' && !isset($updates['actual_close_date'])) {
|
||||
$updates['actual_close_date'] = date('Y-m-d');
|
||||
}
|
||||
|
||||
Database::update('non_conformities', $updates, 'id = ?', [$id]);
|
||||
|
||||
$this->logAudit('ncr_updated', 'non_conformity', $id, $updates);
|
||||
|
||||
$this->jsonSuccess($updates, 'Non conformita\' aggiornata');
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════
|
||||
// CAPA ACTIONS
|
||||
// ═══════════════════════════════════════════════════════════════════════
|
||||
|
||||
/**
|
||||
* POST /api/ncr/{id}/capa
|
||||
*/
|
||||
public function addCapa(int $ncrId): void
|
||||
{
|
||||
$this->requireOrgRole(['org_admin', 'compliance_manager', 'auditor']);
|
||||
$this->validateRequired(['title', 'action_type']);
|
||||
|
||||
$orgId = $this->getCurrentOrgId();
|
||||
|
||||
// Verify NCR belongs to org
|
||||
$ncr = Database::fetchOne(
|
||||
'SELECT id FROM non_conformities WHERE id = ? AND organization_id = ?',
|
||||
[$ncrId, $orgId]
|
||||
);
|
||||
|
||||
if (!$ncr) {
|
||||
$this->jsonError('Non conformita\' non trovata', 404, 'NCR_NOT_FOUND');
|
||||
}
|
||||
|
||||
$capaCode = $this->generateCode('CAPA');
|
||||
|
||||
$capaId = Database::insert('capa_actions', [
|
||||
'ncr_id' => $ncrId,
|
||||
'organization_id' => $orgId,
|
||||
'capa_code' => $capaCode,
|
||||
'action_type' => $this->getParam('action_type', 'corrective'),
|
||||
'title' => trim($this->getParam('title')),
|
||||
'description' => $this->getParam('description'),
|
||||
'responsible_user_id' => $this->getParam('responsible_user_id'),
|
||||
'due_date' => $this->getParam('due_date'),
|
||||
'notes' => $this->getParam('notes'),
|
||||
]);
|
||||
|
||||
// Update NCR status to action_planned if still open/investigating
|
||||
$ncrStatus = Database::fetchOne('SELECT status FROM non_conformities WHERE id = ?', [$ncrId]);
|
||||
if ($ncrStatus && in_array($ncrStatus['status'], ['open', 'investigating'])) {
|
||||
Database::update('non_conformities', ['status' => 'action_planned'], 'id = ?', [$ncrId]);
|
||||
}
|
||||
|
||||
$this->logAudit('capa_created', 'capa_action', $capaId, [
|
||||
'ncr_id' => $ncrId,
|
||||
'capa_code' => $capaCode,
|
||||
]);
|
||||
|
||||
$this->jsonSuccess([
|
||||
'id' => $capaId,
|
||||
'capa_code' => $capaCode,
|
||||
], 'Azione CAPA creata', 201);
|
||||
}
|
||||
|
||||
/**
|
||||
* PUT /api/ncr/capa/{subId}
|
||||
*/
|
||||
public function updateCapa(int $capaId): void
|
||||
{
|
||||
$this->requireOrgRole(['org_admin', 'compliance_manager', 'auditor']);
|
||||
|
||||
$capa = Database::fetchOne(
|
||||
'SELECT id, ncr_id FROM capa_actions WHERE id = ? AND organization_id = ?',
|
||||
[$capaId, $this->getCurrentOrgId()]
|
||||
);
|
||||
|
||||
if (!$capa) {
|
||||
$this->jsonError('Azione CAPA non trovata', 404, 'CAPA_NOT_FOUND');
|
||||
}
|
||||
|
||||
$allowedFields = [
|
||||
'title', 'description', 'action_type', 'status',
|
||||
'responsible_user_id', 'due_date', 'completion_date',
|
||||
'verification_date', 'verified_by', 'effectiveness_review',
|
||||
'is_effective', 'notes',
|
||||
];
|
||||
|
||||
$updates = [];
|
||||
foreach ($allowedFields as $field) {
|
||||
if ($this->hasParam($field)) {
|
||||
$updates[$field] = $this->getParam($field);
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($updates)) {
|
||||
$this->jsonError('Nessun campo da aggiornare', 400, 'NO_UPDATES');
|
||||
}
|
||||
|
||||
// Auto-set completion date
|
||||
if (isset($updates['status']) && $updates['status'] === 'completed' && !isset($updates['completion_date'])) {
|
||||
$updates['completion_date'] = date('Y-m-d');
|
||||
}
|
||||
|
||||
Database::update('capa_actions', $updates, 'id = ?', [$capaId]);
|
||||
|
||||
$this->logAudit('capa_updated', 'capa_action', $capaId, $updates);
|
||||
|
||||
$this->jsonSuccess($updates, 'Azione CAPA aggiornata');
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════
|
||||
// BULK CREATE FROM ASSESSMENT
|
||||
// ═══════════════════════════════════════════════════════════════════════
|
||||
|
||||
/**
|
||||
* POST /api/ncr/fromAssessment
|
||||
* Genera NCR dai gap (risposte not_implemented/partial) di un assessment
|
||||
*/
|
||||
public function fromAssessment(): void
|
||||
{
|
||||
$this->requireOrgRole(['org_admin', 'compliance_manager']);
|
||||
$this->validateRequired(['assessment_id']);
|
||||
|
||||
$orgId = $this->getCurrentOrgId();
|
||||
$assessmentId = (int) $this->getParam('assessment_id');
|
||||
|
||||
// Verify assessment belongs to org
|
||||
$assessment = Database::fetchOne(
|
||||
'SELECT id, title FROM assessments WHERE id = ? AND organization_id = ?',
|
||||
[$assessmentId, $orgId]
|
||||
);
|
||||
|
||||
if (!$assessment) {
|
||||
$this->jsonError('Assessment non trovato', 404, 'ASSESSMENT_NOT_FOUND');
|
||||
}
|
||||
|
||||
// Check for existing NCRs from this assessment to avoid duplicates
|
||||
$existingCount = Database::count(
|
||||
'non_conformities',
|
||||
'organization_id = ? AND source = ? AND source_entity_type = ? AND source_entity_id IN (SELECT id FROM assessment_responses WHERE assessment_id = ?)',
|
||||
[$orgId, 'assessment', 'assessment_response', $assessmentId]
|
||||
);
|
||||
|
||||
if ($existingCount > 0) {
|
||||
$this->jsonError(
|
||||
"Esistono gia' {$existingCount} non conformita' generate da questo assessment",
|
||||
409,
|
||||
'NCR_ALREADY_GENERATED'
|
||||
);
|
||||
}
|
||||
|
||||
// Find gaps
|
||||
$gaps = Database::fetchAll(
|
||||
'SELECT * FROM assessment_responses
|
||||
WHERE assessment_id = ?
|
||||
AND response_value IN ("not_implemented", "partial")
|
||||
ORDER BY category, question_code',
|
||||
[$assessmentId]
|
||||
);
|
||||
|
||||
if (empty($gaps)) {
|
||||
$this->jsonSuccess(['created' => [], 'total' => 0], 'Nessun gap trovato nell\'assessment');
|
||||
return;
|
||||
}
|
||||
|
||||
$created = [];
|
||||
$assessmentTitle = $assessment['title'];
|
||||
|
||||
foreach ($gaps as $gap) {
|
||||
$severity = $gap['response_value'] === 'not_implemented' ? 'major' : 'minor';
|
||||
$ncrCode = $this->generateCode('NCR');
|
||||
|
||||
$questionText = $gap['question_text'] ?? '';
|
||||
$title = mb_strlen($questionText) > 200
|
||||
? 'Gap: ' . mb_substr($questionText, 0, 197) . '...'
|
||||
: 'Gap: ' . $questionText;
|
||||
|
||||
$ncrId = Database::insert('non_conformities', [
|
||||
'organization_id' => $orgId,
|
||||
'ncr_code' => $ncrCode,
|
||||
'title' => $title,
|
||||
'description' => "Gap identificato nell'assessment \"{$assessmentTitle}\": {$questionText}",
|
||||
'source' => 'assessment',
|
||||
'source_entity_type' => 'assessment_response',
|
||||
'source_entity_id' => $gap['id'],
|
||||
'severity' => $severity,
|
||||
'category' => $gap['category'] ?? null,
|
||||
'nis2_article' => $gap['nis2_article'] ?? null,
|
||||
'identified_by' => $this->getCurrentUserId(),
|
||||
'status' => 'open',
|
||||
]);
|
||||
|
||||
$created[] = [
|
||||
'id' => $ncrId,
|
||||
'ncr_code' => $ncrCode,
|
||||
'severity' => $severity,
|
||||
'category' => $gap['category'] ?? null,
|
||||
];
|
||||
}
|
||||
|
||||
$this->logAudit('ncr_bulk_created', 'assessment', $assessmentId, [
|
||||
'count' => count($created),
|
||||
]);
|
||||
|
||||
$this->jsonSuccess([
|
||||
'created' => $created,
|
||||
'total' => count($created),
|
||||
], count($created) . ' non conformita\' generate dall\'assessment');
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════
|
||||
// STATISTICS
|
||||
// ═══════════════════════════════════════════════════════════════════════
|
||||
|
||||
/**
|
||||
* GET /api/ncr/stats
|
||||
*/
|
||||
public function stats(): void
|
||||
{
|
||||
$this->requireOrgAccess();
|
||||
$orgId = $this->getCurrentOrgId();
|
||||
|
||||
// NCR counts by status
|
||||
$byStatus = Database::fetchAll(
|
||||
'SELECT status, COUNT(*) as count FROM non_conformities WHERE organization_id = ? GROUP BY status',
|
||||
[$orgId]
|
||||
);
|
||||
|
||||
// NCR counts by severity
|
||||
$bySeverity = Database::fetchAll(
|
||||
'SELECT severity, COUNT(*) as count FROM non_conformities WHERE organization_id = ? GROUP BY severity',
|
||||
[$orgId]
|
||||
);
|
||||
|
||||
// CAPA counts by status
|
||||
$capaByStatus = Database::fetchAll(
|
||||
'SELECT status, COUNT(*) as count FROM capa_actions WHERE organization_id = ? GROUP BY status',
|
||||
[$orgId]
|
||||
);
|
||||
|
||||
// Overdue NCRs
|
||||
$overdueNcr = Database::count(
|
||||
'non_conformities',
|
||||
'organization_id = ? AND target_close_date < CURDATE() AND status NOT IN ("closed", "cancelled")',
|
||||
[$orgId]
|
||||
);
|
||||
|
||||
// Overdue CAPAs
|
||||
$overdueCapa = Database::count(
|
||||
'capa_actions',
|
||||
'organization_id = ? AND due_date < CURDATE() AND status NOT IN ("completed", "verified")',
|
||||
[$orgId]
|
||||
);
|
||||
|
||||
$totalNcr = Database::count('non_conformities', 'organization_id = ?', [$orgId]);
|
||||
$openNcr = Database::count('non_conformities', 'organization_id = ? AND status NOT IN ("closed", "cancelled")', [$orgId]);
|
||||
|
||||
$this->jsonSuccess([
|
||||
'total_ncr' => $totalNcr,
|
||||
'open_ncr' => $openNcr,
|
||||
'overdue_ncr' => $overdueNcr,
|
||||
'overdue_capa' => $overdueCapa,
|
||||
'ncr_by_status' => $byStatus,
|
||||
'ncr_by_severity' => $bySeverity,
|
||||
'capa_by_status' => $capaByStatus,
|
||||
]);
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════
|
||||
// EXTERNAL INTEGRATION (SistemiG.agile - Future)
|
||||
// ═══════════════════════════════════════════════════════════════════════
|
||||
|
||||
/**
|
||||
* POST /api/ncr/{id}/sync
|
||||
* Sincronizza NCR con sistema esterno (SistemiG.agile)
|
||||
*/
|
||||
public function syncExternal(int $id): void
|
||||
{
|
||||
$this->requireOrgRole(['org_admin', 'compliance_manager']);
|
||||
|
||||
$ncr = Database::fetchOne(
|
||||
'SELECT * FROM non_conformities WHERE id = ? AND organization_id = ?',
|
||||
[$id, $this->getCurrentOrgId()]
|
||||
);
|
||||
|
||||
if (!$ncr) {
|
||||
$this->jsonError('Non conformita\' non trovata', 404, 'NCR_NOT_FOUND');
|
||||
}
|
||||
|
||||
$targetUrl = defined('SISTEMIG_API_URL') ? SISTEMIG_API_URL : '';
|
||||
if (empty($targetUrl)) {
|
||||
$this->jsonError('Integrazione SistemiG non configurata', 501, 'INTEGRATION_NOT_CONFIGURED');
|
||||
}
|
||||
|
||||
// Placeholder: in future this will POST to SistemiG API
|
||||
$this->jsonSuccess([
|
||||
'ncr_id' => $id,
|
||||
'ncr_code' => $ncr['ncr_code'],
|
||||
'sync_status' => 'not_implemented',
|
||||
'message' => 'Integrazione SistemiG.agile in fase di sviluppo',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /api/ncr/webhook
|
||||
* Webhook per ricezione aggiornamenti da sistema esterno
|
||||
* Autenticato via API key (X-SistemiG-Key header), non JWT
|
||||
*/
|
||||
public function webhook(): void
|
||||
{
|
||||
// Authenticate via API key instead of JWT
|
||||
$apiKey = $_SERVER['HTTP_X_SISTEMIG_KEY'] ?? '';
|
||||
$expectedKey = defined('SISTEMIG_API_KEY') ? SISTEMIG_API_KEY : '';
|
||||
|
||||
if (empty($expectedKey) || $apiKey !== $expectedKey) {
|
||||
$this->jsonError('Chiave API non valida', 401, 'INVALID_API_KEY');
|
||||
}
|
||||
|
||||
// Placeholder: process incoming webhook payload
|
||||
$payload = $this->getJsonBody();
|
||||
|
||||
$this->jsonSuccess([
|
||||
'received' => true,
|
||||
'message' => 'Webhook ricevuto. Integrazione SistemiG.agile in fase di sviluppo.',
|
||||
'payload_keys' => array_keys($payload),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -170,8 +170,12 @@ class OnboardingController extends BaseController
|
||||
(float) ($orgData['annual_turnover_eur'] ?? 0)
|
||||
);
|
||||
|
||||
// Handle voluntary compliance
|
||||
$voluntaryCompliance = ($entityType === 'not_applicable' && (int) $this->getParam('voluntary_compliance', 0)) ? 1 : 0;
|
||||
|
||||
Database::update('organizations', [
|
||||
'entity_type' => $entityType,
|
||||
'voluntary_compliance' => $voluntaryCompliance,
|
||||
], 'id = ?', [$orgId]);
|
||||
|
||||
// Link user as org_admin
|
||||
@@ -207,10 +211,11 @@ class OnboardingController extends BaseController
|
||||
]);
|
||||
|
||||
$this->jsonSuccess([
|
||||
'organization_id' => $orgId,
|
||||
'name' => $orgData['name'],
|
||||
'entity_type' => $entityType,
|
||||
'classification' => $this->getClassificationDetails($entityType, $orgData['sector'], (int)($orgData['employee_count'] ?? 0), (float)($orgData['annual_turnover_eur'] ?? 0)),
|
||||
'organization_id' => $orgId,
|
||||
'name' => $orgData['name'],
|
||||
'entity_type' => $entityType,
|
||||
'voluntary_compliance' => $voluntaryCompliance,
|
||||
'classification' => $this->getClassificationDetails($entityType, $orgData['sector'], (int)($orgData['employee_count'] ?? 0), (float)($orgData['annual_turnover_eur'] ?? 0)),
|
||||
], 'Onboarding completato', 201);
|
||||
|
||||
} catch (Throwable $e) {
|
||||
|
||||
@@ -154,7 +154,7 @@ class OrganizationController extends BaseController
|
||||
$allowedFields = [
|
||||
'name', 'vat_number', 'fiscal_code', 'sector', 'employee_count',
|
||||
'annual_turnover_eur', 'country', 'city', 'address', 'website',
|
||||
'contact_email', 'contact_phone',
|
||||
'contact_email', 'contact_phone', 'voluntary_compliance',
|
||||
];
|
||||
|
||||
foreach ($allowedFields as $field) {
|
||||
@@ -170,11 +170,25 @@ class OrganizationController extends BaseController
|
||||
// Ri-classifica se cambiano settore, dipendenti o fatturato
|
||||
if (isset($updates['sector']) || isset($updates['employee_count']) || isset($updates['annual_turnover_eur'])) {
|
||||
$org = Database::fetchOne('SELECT * FROM organizations WHERE id = ?', [$id]);
|
||||
$updates['entity_type'] = $this->classifyNis2Entity(
|
||||
$newEntityType = $this->classifyNis2Entity(
|
||||
$updates['sector'] ?? $org['sector'],
|
||||
(int) ($updates['employee_count'] ?? $org['employee_count']),
|
||||
(float) ($updates['annual_turnover_eur'] ?? $org['annual_turnover_eur'])
|
||||
);
|
||||
$updates['entity_type'] = $newEntityType;
|
||||
// Reset voluntary compliance if no longer not_applicable
|
||||
if ($newEntityType !== 'not_applicable') {
|
||||
$updates['voluntary_compliance'] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure voluntary_compliance is only set when entity_type is not_applicable
|
||||
if (isset($updates['voluntary_compliance'])) {
|
||||
$currentOrg = $org ?? Database::fetchOne('SELECT entity_type FROM organizations WHERE id = ?', [$id]);
|
||||
$currentType = $updates['entity_type'] ?? $currentOrg['entity_type'];
|
||||
if ($currentType !== 'not_applicable') {
|
||||
$updates['voluntary_compliance'] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
Database::update('organizations', $updates, 'id = ?', [$id]);
|
||||
@@ -302,11 +316,12 @@ class OrganizationController extends BaseController
|
||||
$entityType = $this->classifyNis2Entity($sector, $employees, $turnover);
|
||||
|
||||
$this->jsonSuccess([
|
||||
'entity_type' => $entityType,
|
||||
'sector' => $sector,
|
||||
'employee_count' => $employees,
|
||||
'entity_type' => $entityType,
|
||||
'sector' => $sector,
|
||||
'employee_count' => $employees,
|
||||
'annual_turnover_eur' => $turnover,
|
||||
'explanation' => $this->getClassificationExplanation($entityType, $sector, $employees, $turnover),
|
||||
'allows_voluntary' => $entityType === 'not_applicable',
|
||||
'explanation' => $this->getClassificationExplanation($entityType, $sector, $employees, $turnover),
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user