171 lines
7.6 KiB
PHP
171 lines
7.6 KiB
PHP
<?php
|
|
|
|
namespace App\Services;
|
|
|
|
use App\Models\User;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Facades\Route;
|
|
use Illuminate\Support\Facades\Schema;
|
|
|
|
class WorkContext
|
|
{
|
|
public const OPTIONS = [
|
|
'procurement.internal' => ['area' => 'procurement', 'areaLabel' => 'Compras', 'label' => 'Compras internas', 'operational' => true],
|
|
'procurement.sales' => ['area' => 'procurement', 'areaLabel' => 'Compras', 'label' => 'Venda para terceiros', 'operational' => false],
|
|
'people.internal' => ['area' => 'people', 'areaLabel' => 'Pessoas e contratações', 'label' => 'Contratações internas', 'operational' => true],
|
|
'people.third_party' => ['area' => 'people', 'areaLabel' => 'Pessoas e contratações', 'label' => 'Contratações para terceiros', 'operational' => false],
|
|
];
|
|
|
|
/** @return list<string> */
|
|
public function purchased(User $user): array
|
|
{
|
|
if (! in_array(config('work_context.composition'), ['sgc', 'sgf'], true)) {
|
|
return [];
|
|
}
|
|
$keys = config('work_context.entitlements.'.$user->tenant_id, $this->evaluation($user) ? array_keys(self::OPTIONS) : []);
|
|
if (config('work_context.composition') === 'sgf' && is_array($keys)) {
|
|
$keys = array_diff($keys, ['people.third_party']);
|
|
}
|
|
|
|
return is_array($keys) ? array_values(array_intersect(array_keys(self::OPTIONS), $keys)) : [];
|
|
}
|
|
|
|
/** @return array<string,mixed>|null */
|
|
public function policy(User $user): ?array
|
|
{
|
|
if ($user->tenant_id === null || ! Schema::hasColumn('tenants', 'work_context_policy')) {
|
|
return null;
|
|
}
|
|
|
|
$value = DB::table('tenants')->where('id', $user->tenant_id)->value('work_context_policy');
|
|
|
|
return $value === null ? ($this->evaluation($user) ? ['revision' => 0, 'enabled' => array_keys(self::OPTIONS), 'evaluation_only' => true] : null) : json_decode($value, true, 32, JSON_THROW_ON_ERROR);
|
|
}
|
|
|
|
private function evaluation(User $user): bool
|
|
{
|
|
if (! app()->bound('request') || ! EvaluationNavigation::local(request()) || request()->getPort() !== 8005
|
|
|| DB::connection()->getDriverName() !== 'pgsql') {
|
|
return false;
|
|
}
|
|
|
|
return app(EvaluationAccess::class)->source(request())->tenant_id === $user->tenant_id;
|
|
}
|
|
|
|
public function enforced(User $user): bool
|
|
{
|
|
// Existing installations require explicit, audited activation.
|
|
// New company delivery initialises an empty policy, never implicit grants.
|
|
return $this->policy($user) !== null;
|
|
}
|
|
|
|
/** @return list<string> */
|
|
public function available(User $user): array
|
|
{
|
|
if (app(OrganizationAccess::class)->active($user->tenant_id)) {
|
|
return $this->configuredAvailable($user);
|
|
}
|
|
$granted = [];
|
|
if (app()->environment(['testing', 'payable-testing'])) {
|
|
// Development roles remain usable locally; never become customer grants.
|
|
foreach (array_keys(self::OPTIONS) as $key) {
|
|
if ($this->functional(app(OrganizationAccess::class)->permissions($user), $key)) {
|
|
$granted[] = $key;
|
|
}
|
|
}
|
|
}
|
|
|
|
return array_values(array_intersect($this->purchased($user), $this->policy($user)['enabled'] ?? [], array_unique($granted)));
|
|
}
|
|
|
|
/** Same configured intersection used after activation, without enabling it.
|
|
* @return list<string>
|
|
*/
|
|
public function configuredAvailable(User $user): array
|
|
{
|
|
if (! Schema::hasTable('organization_memberships') || ! Schema::hasColumn('organization_profiles', 'work_contexts')) {
|
|
return [];
|
|
}
|
|
$granted = [];
|
|
$profiles = DB::table('organization_memberships as m')->join('organization_profiles as p', 'p.id', '=', 'm.profile_id')
|
|
->where('m.tenant_id', $user->tenant_id)->where('p.tenant_id', $user->tenant_id)->where('m.user_id', $user->id)
|
|
->where(fn ($q) => $q->whereNull('m.expires_at')->orWhere('m.expires_at', '>', now()))->get(['p.work_contexts', 'p.permissions']);
|
|
foreach ($profiles as $profile) {
|
|
$permissions = json_decode($profile->permissions, true, 32, JSON_THROW_ON_ERROR);
|
|
foreach (json_decode($profile->work_contexts ?? '[]', true, 32, JSON_THROW_ON_ERROR) as $key) {
|
|
if ($this->functional($permissions, $key)) {
|
|
$granted[] = $key;
|
|
}
|
|
}
|
|
}
|
|
|
|
return array_values(array_intersect($this->purchased($user), $this->policy($user)['enabled'] ?? [], array_unique($granted)));
|
|
}
|
|
|
|
/** @param list<string> $permissions */
|
|
private function functional(array $permissions, string $key): bool
|
|
{
|
|
if (! isset(self::OPTIONS[$key])) {
|
|
return false;
|
|
}
|
|
$people = array_values(array_filter(array_keys(OrganizationAccess::PERMISSIONS), fn ($p) => str_starts_with($p, 'people_')));
|
|
$wanted = self::OPTIONS[$key]['area'] === 'people' ? ['admin', ...$people] : array_diff(array_keys(OrganizationAccess::PERMISSIONS), $people);
|
|
|
|
return array_intersect($permissions, $wanted) !== [];
|
|
}
|
|
|
|
public function active(Request $request): ?string
|
|
{
|
|
$user = $request->user();
|
|
$state = $request->session()->get('work_context');
|
|
if (! $user instanceof User || ! is_array($state) || ($state['user_id'] ?? null) !== $user->id || ($state['tenant_id'] ?? null) !== $user->tenant_id) {
|
|
return null;
|
|
}
|
|
$key = $state['key'] ?? null;
|
|
|
|
return is_string($key) && in_array($key, $this->available($user), true) ? $key : null;
|
|
}
|
|
|
|
/** @return array<string,mixed> */
|
|
public function shared(Request $request): array
|
|
{
|
|
$user = $request->user();
|
|
$enabled = $user instanceof User && $this->enforced($user);
|
|
|
|
$active = $enabled ? $this->active($request) : null;
|
|
$permissions = $active !== null ? app(OrganizationAccess::class)->permissions($user, $active) : [];
|
|
if ($active === 'procurement.internal' && ! app(OrganizationAccess::class)->active($user->tenant_id) && app(OrganizationAccess::class)->canRequest($user)) {
|
|
$permissions[] = 'requester';
|
|
}
|
|
$paths = [];
|
|
$sharedPaths = [];
|
|
foreach (Route::getRoutes()->getRoutes() as $route) {
|
|
$path = '/'.$route->uri();
|
|
if (! str_contains($path, '{') && in_array('GET', $route->methods(), true)) {
|
|
$area = self::area($path);
|
|
if ($area === null) {
|
|
$sharedPaths[] = $path;
|
|
}
|
|
if ($area === null || ($active !== null && self::OPTIONS[$active]['operational'] && self::OPTIONS[$active]['area'] === $area)) {
|
|
$paths[] = $path;
|
|
}
|
|
}
|
|
}
|
|
|
|
return ['enforced' => $enabled, 'active' => $active, 'paths' => $paths, 'sharedPaths' => $sharedPaths, 'permissions' => $permissions,
|
|
'options' => self::OPTIONS, 'available' => $enabled ? $this->available($user) : [],
|
|
'purchased' => $user instanceof User ? $this->purchased($user) : []];
|
|
}
|
|
|
|
public static function area(string $path): ?string
|
|
{
|
|
$path = '/'.ltrim(explode('?', $path)[0], '/');
|
|
if ($path === '/dashboard' || str_starts_with($path, '/administracao/') || str_starts_with($path, '/empresa') || str_starts_with($path, '/settings') || str_starts_with($path, '/contexto')) {
|
|
return null;
|
|
}
|
|
|
|
return $path === '/pessoas' || str_starts_with($path, '/pessoas/') ? 'people' : 'procurement';
|
|
}
|
|
}
|