TenantAtlas/apps/platform/app/Services/Entitlements/WorkspaceEntitlementResolver.php
ahmido e222845a36
Some checks failed
Main Confidence / confidence (push) Failing after 53s
247: plans entitlements billing readiness (#287)
Automated commit and PR created by Copilot per user request.

Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de>
Reviewed-on: #287
2026-04-27 17:35:04 +00:00

327 lines
13 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Services\Entitlements;
use App\Models\Tenant;
use App\Models\Workspace;
use App\Models\WorkspaceSetting;
use App\Services\Settings\SettingsResolver;
use Carbon\CarbonInterface;
final class WorkspaceEntitlementResolver
{
public const SETTING_DOMAIN = 'entitlements';
public const SETTING_PLAN_PROFILE = 'plan_profile';
public const SETTING_MANAGED_TENANT_LIMIT_OVERRIDE_VALUE = 'managed_tenant_limit_override_value';
public const SETTING_MANAGED_TENANT_LIMIT_OVERRIDE_REASON = 'managed_tenant_limit_override_reason';
public const SETTING_REVIEW_PACK_GENERATION_OVERRIDE_VALUE = 'review_pack_generation_override_value';
public const SETTING_REVIEW_PACK_GENERATION_OVERRIDE_REASON = 'review_pack_generation_override_reason';
public const KEY_MANAGED_TENANT_ACTIVATION_LIMIT = 'managed_tenant_activation_limit';
public const KEY_REVIEW_PACK_GENERATION_ENABLED = 'review_pack_generation_enabled';
public function __construct(
private SettingsResolver $settingsResolver,
private WorkspacePlanProfileCatalog $planProfileCatalog,
) {}
/**
* @return array{
* plan_profile: array{id: string, label: string, description: string, managed_tenant_limit_default: int, review_pack_generation_default: bool, is_default: bool},
* decisions: array<string, array{
* workspace_id: int,
* plan_profile_id: string,
* plan_profile_label: string,
* plan_profile_description: string,
* key: string,
* effective_value: int|bool,
* source: 'plan_profile_default'|'workspace_override',
* rationale: string|null,
* current_usage: int|null,
* remaining_capacity: int|null,
* is_blocked: bool,
* block_reason: string|null,
* last_changed_at: CarbonInterface|null,
* last_changed_by: string|null
* }>
* }
*/
public function summary(Workspace $workspace): array
{
$planProfile = $this->resolvePlanProfile($workspace);
return [
'plan_profile' => $planProfile,
'decisions' => [
self::KEY_MANAGED_TENANT_ACTIVATION_LIMIT => $this->resolve($workspace, self::KEY_MANAGED_TENANT_ACTIVATION_LIMIT, $planProfile),
self::KEY_REVIEW_PACK_GENERATION_ENABLED => $this->resolve($workspace, self::KEY_REVIEW_PACK_GENERATION_ENABLED, $planProfile),
],
];
}
/**
* @return array{id: string, label: string, description: string, managed_tenant_limit_default: int, review_pack_generation_default: bool, is_default: bool}
*/
public function resolvePlanProfile(Workspace $workspace): array
{
$planProfileId = $this->settingsResolver->resolveValue(
workspace: $workspace,
domain: self::SETTING_DOMAIN,
key: self::SETTING_PLAN_PROFILE,
);
return $this->planProfileCatalog->resolve(is_string($planProfileId) ? $planProfileId : null);
}
/**
* @param array{id: string, label: string, description: string, managed_tenant_limit_default: int, review_pack_generation_default: bool, is_default: bool}|null $planProfile
* @return array{
* workspace_id: int,
* plan_profile_id: string,
* plan_profile_label: string,
* plan_profile_description: string,
* key: string,
* effective_value: int|bool,
* source: 'plan_profile_default'|'workspace_override',
* rationale: string|null,
* current_usage: int|null,
* remaining_capacity: int|null,
* is_blocked: bool,
* block_reason: string|null,
* last_changed_at: CarbonInterface|null,
* last_changed_by: string|null
* }
*/
public function resolve(Workspace $workspace, string $key, ?array $planProfile = null): array
{
$planProfile ??= $this->resolvePlanProfile($workspace);
$lastChanged = $this->lastChangedMetadata($workspace);
return match ($key) {
self::KEY_MANAGED_TENANT_ACTIVATION_LIMIT => $this->resolveManagedTenantActivationLimitDecision($workspace, $planProfile, $lastChanged),
self::KEY_REVIEW_PACK_GENERATION_ENABLED => $this->resolveReviewPackGenerationDecision($workspace, $planProfile, $lastChanged),
default => throw new \InvalidArgumentException(sprintf('Unknown workspace entitlement key: %s', $key)),
};
}
/**
* @param array{id: string, label: string, description: string, managed_tenant_limit_default: int, review_pack_generation_default: bool, is_default: bool} $planProfile
* @param array{last_changed_at: CarbonInterface|null, last_changed_by: string|null} $lastChanged
* @return array{
* workspace_id: int,
* plan_profile_id: string,
* plan_profile_label: string,
* plan_profile_description: string,
* key: string,
* effective_value: int,
* source: 'plan_profile_default'|'workspace_override',
* rationale: string|null,
* current_usage: int,
* remaining_capacity: int,
* is_blocked: bool,
* block_reason: string|null,
* last_changed_at: CarbonInterface|null,
* last_changed_by: string|null
* }
*/
private function resolveManagedTenantActivationLimitDecision(Workspace $workspace, array $planProfile, array $lastChanged): array
{
$overrideValue = $this->settingsResolver->resolveDetailed(
workspace: $workspace,
domain: self::SETTING_DOMAIN,
key: self::SETTING_MANAGED_TENANT_LIMIT_OVERRIDE_VALUE,
);
$overrideReason = $this->settingsResolver->resolveValue(
workspace: $workspace,
domain: self::SETTING_DOMAIN,
key: self::SETTING_MANAGED_TENANT_LIMIT_OVERRIDE_REASON,
);
$effectiveValue = is_int($overrideValue['value'])
? $overrideValue['value']
: (int) $planProfile['managed_tenant_limit_default'];
$source = $overrideValue['source'] === 'workspace_override'
? 'workspace_override'
: 'plan_profile_default';
$currentUsage = Tenant::activeQuery()
->where('workspace_id', (int) $workspace->getKey())
->count();
$remainingCapacity = $effectiveValue - $currentUsage;
$isBlocked = $currentUsage >= $effectiveValue;
$rationale = $source === 'workspace_override'
? (is_string($overrideReason) && $overrideReason !== '' ? $overrideReason : null)
: (string) $planProfile['description'];
return [
'workspace_id' => (int) $workspace->getKey(),
'plan_profile_id' => (string) $planProfile['id'],
'plan_profile_label' => (string) $planProfile['label'],
'plan_profile_description' => (string) $planProfile['description'],
'key' => self::KEY_MANAGED_TENANT_ACTIVATION_LIMIT,
'effective_value' => $effectiveValue,
'source' => $source,
'rationale' => $rationale,
'current_usage' => $currentUsage,
'remaining_capacity' => $remainingCapacity,
'is_blocked' => $isBlocked,
'block_reason' => $isBlocked
? $this->managedTenantLimitBlockReason($currentUsage, $effectiveValue, $source, $planProfile, $rationale)
: null,
'last_changed_at' => $lastChanged['last_changed_at'],
'last_changed_by' => $lastChanged['last_changed_by'],
];
}
/**
* @param array{id: string, label: string, description: string, managed_tenant_limit_default: int, review_pack_generation_default: bool, is_default: bool} $planProfile
* @param array{last_changed_at: CarbonInterface|null, last_changed_by: string|null} $lastChanged
* @return array{
* workspace_id: int,
* plan_profile_id: string,
* plan_profile_label: string,
* plan_profile_description: string,
* key: string,
* effective_value: bool,
* source: 'plan_profile_default'|'workspace_override',
* rationale: string|null,
* current_usage: null,
* remaining_capacity: null,
* is_blocked: bool,
* block_reason: string|null,
* last_changed_at: CarbonInterface|null,
* last_changed_by: string|null
* }
*/
private function resolveReviewPackGenerationDecision(Workspace $workspace, array $planProfile, array $lastChanged): array
{
$overrideValue = $this->settingsResolver->resolveDetailed(
workspace: $workspace,
domain: self::SETTING_DOMAIN,
key: self::SETTING_REVIEW_PACK_GENERATION_OVERRIDE_VALUE,
);
$overrideReason = $this->settingsResolver->resolveValue(
workspace: $workspace,
domain: self::SETTING_DOMAIN,
key: self::SETTING_REVIEW_PACK_GENERATION_OVERRIDE_REASON,
);
$effectiveValue = is_bool($overrideValue['value'])
? $overrideValue['value']
: (bool) $planProfile['review_pack_generation_default'];
$source = $overrideValue['source'] === 'workspace_override'
? 'workspace_override'
: 'plan_profile_default';
$rationale = $source === 'workspace_override'
? (is_string($overrideReason) && $overrideReason !== '' ? $overrideReason : null)
: (string) $planProfile['description'];
return [
'workspace_id' => (int) $workspace->getKey(),
'plan_profile_id' => (string) $planProfile['id'],
'plan_profile_label' => (string) $planProfile['label'],
'plan_profile_description' => (string) $planProfile['description'],
'key' => self::KEY_REVIEW_PACK_GENERATION_ENABLED,
'effective_value' => $effectiveValue,
'source' => $source,
'rationale' => $rationale,
'current_usage' => null,
'remaining_capacity' => null,
'is_blocked' => ! $effectiveValue,
'block_reason' => $effectiveValue
? null
: $this->reviewPackGenerationBlockReason($source, $planProfile, $rationale),
'last_changed_at' => $lastChanged['last_changed_at'],
'last_changed_by' => $lastChanged['last_changed_by'],
];
}
/**
* @return array{last_changed_at: CarbonInterface|null, last_changed_by: string|null}
*/
private function lastChangedMetadata(Workspace $workspace): array
{
$record = WorkspaceSetting::query()
->where('workspace_id', (int) $workspace->getKey())
->where('domain', self::SETTING_DOMAIN)
->whereIn('key', [
self::SETTING_PLAN_PROFILE,
self::SETTING_MANAGED_TENANT_LIMIT_OVERRIDE_VALUE,
self::SETTING_MANAGED_TENANT_LIMIT_OVERRIDE_REASON,
self::SETTING_REVIEW_PACK_GENERATION_OVERRIDE_VALUE,
self::SETTING_REVIEW_PACK_GENERATION_OVERRIDE_REASON,
])
->whereNotNull('updated_by_user_id')
->with('updatedByUser:id,name')
->latest('updated_at')
->latest('id')
->first();
if (! $record instanceof WorkspaceSetting) {
return [
'last_changed_at' => null,
'last_changed_by' => null,
];
}
return [
'last_changed_at' => $record->updated_at,
'last_changed_by' => $record->updatedByUser?->name,
];
}
/**
* @param array{id: string, label: string, description: string, managed_tenant_limit_default: int, review_pack_generation_default: bool, is_default: bool} $planProfile
*/
private function managedTenantLimitBlockReason(int $currentUsage, int $effectiveValue, string $source, array $planProfile, ?string $rationale): string
{
$prefix = $source === 'workspace_override'
? 'This workspace override currently allows'
: sprintf('The %s plan profile currently allows', $planProfile['label']);
$message = sprintf(
'%s %d active managed tenant%s, and this workspace already has %d active managed tenant%s.',
$prefix,
$effectiveValue,
$effectiveValue === 1 ? '' : 's',
$currentUsage,
$currentUsage === 1 ? '' : 's',
);
if ($source === 'workspace_override' && $rationale !== null) {
$message .= ' Reason: '.$rationale;
}
return $message;
}
/**
* @param array{id: string, label: string, description: string, managed_tenant_limit_default: int, review_pack_generation_default: bool, is_default: bool} $planProfile
*/
private function reviewPackGenerationBlockReason(string $source, array $planProfile, ?string $rationale): string
{
$message = $source === 'workspace_override'
? 'Review pack generation is disabled by workspace override.'
: sprintf('Review pack generation is disabled by the %s plan profile.', $planProfile['label']);
if ($source === 'workspace_override' && $rationale !== null) {
$message .= ' Reason: '.$rationale;
}
return $message;
}
}