Files
compras/app/Services/WorkContext.php
T
2026-11-02 20:27:11 +02:00

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';
}
}