'Consultar processo', 'prepare' => 'Preparar e gerir candidaturas', 'review' => 'Conferir abertura e avaliar', 'approve' => 'Decidir a selecção', 'contract' => 'Preparar minuta contratual', 'manage' => 'Atribuir equipa deste processo', 'financial' => 'Consultar condições financeiras', 'interviews' => 'Consultar registos de entrevista e evidências', 'contract_view' => 'Consultar minuta contratual']; /** @return list */ public function scopes(User $actor, PeopleProcess $record): array { if ($actor->tenant_id !== $record->tenant_id || ! Schema::hasTable('people_process_access') || ! app(PeopleHiring::class)->allowed($actor, $record->kind)) { return []; } $row = DB::table('people_process_access')->where('tenant_id', $actor->tenant_id)->where('people_process_id', $record->id)->where('user_id', $actor->id) ->where(fn ($q) => $q->whereNull('expires_at')->orWhere('expires_at', '>', now()))->first(); return $row === null ? [] : json_decode($row->scopes, true, 16, JSON_THROW_ON_ERROR); } public function can(User $actor, PeopleProcess $record, string $scope): bool { return in_array($scope, $this->scopes($actor, $record), true); } /** @param Builder $query * @return Builder */ public function visible(Builder $query, User $actor): Builder { if (! Schema::hasTable('people_process_access')) { return $query->whereRaw('1 = 0'); } return $query->whereExists(fn ($q) => $q->selectRaw('1')->from('people_process_access as pa')->whereColumn('pa.people_process_id', 'people_processes.id') ->where('pa.tenant_id', $actor->tenant_id)->where('pa.user_id', $actor->id)->whereJsonContains('pa.scopes', 'view')->where(fn ($q) => $q->whereNull('pa.expires_at')->orWhere('pa.expires_at', '>', now()))); } public function manages(User $actor, PeopleProcess $record): bool { return $actor->tenant_id === $record->tenant_id && ($actor->hasFunctions(['admin']) || $this->can($actor, $record, 'manage')); } /** @param list $scopes */ public function insert(PeopleProcess $record, int $user, array $scopes, string $reason): void { DB::table('people_process_access')->insert(['tenant_id' => $record->tenant_id, 'people_process_id' => $record->id, 'user_id' => $user, 'scopes' => json_encode($scopes, JSON_THROW_ON_ERROR), 'reason' => $reason, 'created_at' => now(), 'updated_at' => now()]); } /** @param array $input */ public function assign(User $actor, int $id, array $input): void { DB::transaction(function () use ($actor, $id, $input) { app(ConflictGuard::class)->lockCompany($actor); $actor = app(CurrentActor::class)->lock($actor); abort_if(app(OrganizationAccess::class)->maintenanceOnly($actor), 403); $record = PeopleProcess::query()->lockForUpdate()->findOrFail($id); abort_unless($this->manages($actor, $record), 403); if (($input['scopes'] ?? null) === []) { $input['expires_at'] = null; } $data = Validator::make($input, ['version' => 'required|integer|min:1', 'user_id' => 'required|integer|min:1', 'scopes' => 'present|array', 'scopes.*' => ['string', 'distinct', Rule::in(array_keys(self::SCOPES))], 'expires_at' => 'nullable|date|after:now', 'reason' => 'required|string|min:10|max:4000'])->validate(); $target = User::query()->where('tenant_id', $actor->tenant_id)->where('id', $data['user_id'])->firstOrFail(); abort_unless($data['scopes'] === [] || app(PeopleHiring::class)->allowed($target, $record->kind), 422, 'Atribua primeiro um perfil compatível com este percurso na parametrização da empresa.'); foreach ($data['scopes'] as $scope) { $function = match ($scope) { 'review', 'approve' => 'review', 'prepare', 'contract' => 'prepare', default => null }; if ($function !== null && ! app(PeopleHiring::class)->allowed($target, $record->kind, $function)) { throw ValidationException::withMessages(['scopes' => 'O perfil desta pessoa não permite a intervenção seleccionada. Ajuste o perfil da empresa ou escolha outra intervenção.']); } } $state = DB::table('people_access_states')->where('people_process_id', $record->id)->lockForUpdate()->first(); if ((int) $data['version'] !== (int) $state->version) { throw ValidationException::withMessages(['version' => 'A equipa mudou. Actualize antes de guardar.']); } $before = DB::table('people_process_access')->where('people_process_id', $record->id)->where('user_id', $target->id)->first(); $scopes = $data['scopes'] === [] ? [] : array_values(array_unique(['view', ...$data['scopes']])); DB::table('people_process_access')->updateOrInsert(['people_process_id' => $record->id, 'user_id' => $target->id], ['tenant_id' => $record->tenant_id, 'scopes' => json_encode($scopes, JSON_THROW_ON_ERROR), 'expires_at' => isset($data['expires_at']) ? now()->parse($data['expires_at'])->utc()->format('Y-m-d H:i:s') : null, 'reason' => $data['reason'], 'created_at' => $before->created_at ?? now(), 'updated_at' => now()]); DB::table('people_access_states')->where('id', $state->id)->update(['version' => $state->version + 1]); app(RequisitionWorkflow::class)->audit($actor, 'people.access', null, $before === null ? null : (array) $before, ['id' => $record->id, 'user_id' => $target->id, 'scopes' => $scopes, 'expires_at' => $data['expires_at'] ?? null, 'reason' => $data['reason']]); }); } }