[LICENSE] Gestione licenze marketing + campi commerciali estesi
- licenseExt.html: dashboard marketing per generare/gestire licenze Login JWT super_admin, stats strip (totali/usate/orgs/utenti), form genera con label/piano/durata/max-aziende/max-utenti/prezzo/reseller, lista paginata con filtri stato+canale, dettaglio modale, revoca/rigenera, export CSV e copia token/URL - Migration 013: invites +max_users_per_org, +price_eur, +reseller_name organizations +license_max_users (da provisioning) - InviteController::create() gestisce nuovi campi, validate() espone max_users_per_org - ServicesController::provision() salva license_max_users nell'org Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
612befd66d
commit
cb0988da27
@@ -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']),
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user