diff --git a/application/controllers/InviteController.php b/application/controllers/InviteController.php index 83854c6..80869b4 100644 --- a/application/controllers/InviteController.php +++ b/application/controllers/InviteController.php @@ -69,6 +69,9 @@ class InviteController extends BaseController $restrictVat = preg_replace('/[^0-9]/', '', $body['restrict_vat'] ?? ''); $restrictEmail = filter_var($body['restrict_email'] ?? '', FILTER_VALIDATE_EMAIL) ?: null; $notes = trim($body['notes'] ?? '') ?: null; + $maxUsersPerOrg = isset($body['max_users_per_org']) ? max(1, min(9999, (int)$body['max_users_per_org'])) : null; + $priceEur = isset($body['price_eur']) ? round((float)$body['price_eur'], 2) : null; + $resellerName = substr(trim($body['reseller_name'] ?? ''), 0, 128) ?: null; $issuedBy = $this->getCurrentUserId(); $expiresAt = date('Y-m-d H:i:s', strtotime("+{$expiresDays} days")); @@ -81,37 +84,41 @@ class InviteController extends BaseController Database::execute( 'INSERT INTO invites (token_prefix, token_hash, plan, duration_months, label, notes, - max_uses, expires_at, channel, issued_to, issued_by, + max_uses, max_users_per_org, price_eur, reseller_name, + expires_at, channel, issued_to, issued_by, restrict_vat, restrict_email) - VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)', + VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)', [ $prefix, $tokenHash, $plan, $durationMonths, - $label, $notes, $maxUses, $expiresAt, - $channel, $issuedTo, $issuedBy, + $label, $notes, $maxUses, $maxUsersPerOrg, $priceEur, $resellerName, + $expiresAt, $channel, $issuedTo, $issuedBy, $restrictVat ?: null, $restrictEmail, ] ); $inviteId = (int) Database::lastInsertId(); $created[] = [ - 'id' => $inviteId, - 'token' => $rawToken, // SOLO qui viene restituito in chiaro - 'token_prefix' => $prefix, - 'plan' => $plan, - 'duration_months' => $durationMonths, - 'max_uses' => $maxUses, - 'expires_at' => $expiresAt, - 'channel' => $channel, - 'issued_to' => $issuedTo, - 'label' => $label, - 'invite_url' => APP_URL . '/onboarding.html?invite=' . urlencode($rawToken), - 'provision_hint' => 'POST /api/services/provision con invite_token: "' . $rawToken . '"', + 'id' => $inviteId, + 'token' => $rawToken, // SOLO qui viene restituito in chiaro + 'token_prefix' => $prefix, + 'plan' => $plan, + 'duration_months' => $durationMonths, + 'max_uses' => $maxUses, + 'max_users_per_org' => $maxUsersPerOrg, + 'price_eur' => $priceEur, + 'expires_at' => $expiresAt, + 'channel' => $channel, + 'issued_to' => $issuedTo, + 'label' => $label, + 'invite_url' => APP_URL . '/onboarding.html?invite=' . urlencode($rawToken), + 'provision_hint' => 'POST /api/services/provision con invite_token: "' . $rawToken . '"', ]; // Audit $this->logAudit('invite.created', 'invite', $inviteId, [ 'plan' => $plan, 'channel' => $channel, 'expires_at' => $expiresAt, 'max_uses' => $maxUses, + 'max_users_per_org' => $maxUsersPerOrg, ]); } @@ -263,14 +270,15 @@ class InviteController extends BaseController $inv = $result['invite']; $this->jsonSuccess([ - 'valid' => true, - 'plan' => $inv['plan'], - 'duration_months' => (int) $inv['duration_months'], - 'expires_at' => $inv['expires_at'], - 'remaining_uses' => (int)$inv['max_uses'] - (int)$inv['used_count'], - 'channel' => $inv['channel'], - 'label' => $inv['label'], - 'plan_features' => self::planFeatures($inv['plan']), + 'valid' => true, + 'plan' => $inv['plan'], + 'duration_months' => (int) $inv['duration_months'], + 'expires_at' => $inv['expires_at'], + 'remaining_uses' => (int)$inv['max_uses'] - (int)$inv['used_count'], + 'max_users_per_org' => $inv['max_users_per_org'] !== null ? (int)$inv['max_users_per_org'] : null, + 'channel' => $inv['channel'], + 'label' => $inv['label'], + 'plan_features' => self::planFeatures($inv['plan']), ]); } diff --git a/application/controllers/ServicesController.php b/application/controllers/ServicesController.php index 3fe9d2f..cc07d35 100644 --- a/application/controllers/ServicesController.php +++ b/application/controllers/ServicesController.php @@ -564,8 +564,8 @@ class ServicesController extends BaseController legal_address, pec, phone, annual_turnover_eur, employees, sector, nis2_entity_type, status, provisioned_by, provisioned_at, license_plan, license_expires_at, - lg231_company_id, lg231_order_id) - VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,\'active\',?,NOW(),?,?,?,?)', + license_max_users, lg231_company_id, lg231_order_id) + VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,\'active\',?,NOW(),?,?,?,?,?)', [ $ragioneSociale, $company['forma_giuridica'] ?? null, @@ -585,6 +585,7 @@ class ServicesController extends BaseController isset($license['duration_months']) ? date('Y-m-d', strtotime('+' . (int)$license['duration_months'] . ' months')) : date('Y-m-d', strtotime('+12 months')), + $resolvedInvite ? ($resolvedInvite['max_users_per_org'] !== null ? (int)$resolvedInvite['max_users_per_org'] : null) : null, $caller['company_id'] ?? null, $license['lg231_order_id'] ?? null, ] diff --git a/docs/sql/013_license_ext.sql b/docs/sql/013_license_ext.sql new file mode 100644 index 0000000..2a1a4dd --- /dev/null +++ b/docs/sql/013_license_ext.sql @@ -0,0 +1,38 @@ +-- ============================================================ +-- NIS2 Agile — Migration 013: Licenze Estese (Marketing) +-- Aggiunge campi commerciali agli inviti e alle organizzazioni +-- per controllo numero aziende, utenti, prezzi e reseller. +-- ============================================================ + +USE nis2_agile_db; + +-- ── invites: campi commerciali aggiuntivi ────────────────────────────────── + +-- Numero massimo di utenti per organizzazione (NULL = illimitato) +ALTER TABLE invites + ADD COLUMN IF NOT EXISTS max_users_per_org SMALLINT UNSIGNED NULL DEFAULT NULL AFTER max_uses; + +-- Prezzo di listino (solo riferimento, non enforced da NIS2) +ALTER TABLE invites + ADD COLUMN IF NOT EXISTS price_eur DECIMAL(8,2) NULL DEFAULT NULL AFTER max_users_per_org; + +-- Nome reseller / partner che ha acquistato la licenza +ALTER TABLE invites + ADD COLUMN IF NOT EXISTS reseller_name VARCHAR(128) NULL DEFAULT NULL AFTER price_eur; + +-- ── organizations: limite utenti dalla licenza ───────────────────────────── + +-- Numero massimo utenti per org (valorizzato al provisioning) +ALTER TABLE organizations + ADD COLUMN IF NOT EXISTS license_max_users SMALLINT UNSIGNED NULL DEFAULT NULL AFTER license_expires_at; + +-- ── Verifica ────────────────────────────────────────────────────────────── +SELECT + COLUMN_NAME, COLUMN_TYPE, IS_NULLABLE +FROM INFORMATION_SCHEMA.COLUMNS +WHERE TABLE_SCHEMA = 'nis2_agile_db' + AND TABLE_NAME = 'invites' + AND COLUMN_NAME IN ('max_users_per_org','price_eur','reseller_name') +ORDER BY ORDINAL_POSITION; + +SELECT 'Migration 013 license_ext completata.' AS stato; diff --git a/public/licenseExt.html b/public/licenseExt.html new file mode 100644 index 0000000..5c4e336 --- /dev/null +++ b/public/licenseExt.html @@ -0,0 +1,889 @@ + + + + + +NIS2 Agile — Gestione Licenze + + + + + + +
+
+ +

Gestione Licenze NIS2

+

Accesso riservato al team marketing e amministratori

+
+ + +
+
+ + +
+ + +
+
+ + +
+ + + + +
+ + +
+ + +
+
Licenze totali
+
In attesa di uso
+
Usate / Attive
+
Scadute
+
Aziende provisionate
+
Utenti coinvolti
+
+ + +
+
+

Ultime licenze generate

+ +
+
+ + + + + + +
LabelPianoCanaleUsoScadeStatoAzioni
+
+
+
+ + + + + + + +
+
+ + + + +
+ + + + +