administrator($this->staff($request)); $portal = $profiles->portal($actor->tenant_id); return $this->page($request, 'company/candidate-portal', ['portal' => $portal === null ? ['version' => 0, 'registration_open' => false, 'notice' => ''] : (array) $portal, 'canManage' => app(SubmissionForms::class)->canManage($actor), 'portalUrl' => '/candidaturas/'.$actor->tenant_id, 'emailDeliveryAvailable' => app(CandidateAccess::class)->deliveryAvailable()]); } public function configure(Request $request, CandidateProfiles $profiles): Response { $profiles->configure($this->staff($request), $request->all()); return back(); } public function entry(Request $request, int $company, CandidateProfiles $profiles): Response { $portal = $profiles->portal($company); abort_unless($portal !== null, 404); $tenant = Tenant::query()->findOrFail($company); $filter = $request->validate(['opportunities_page' => 'sometimes|integer|min:1']); return $this->page($request, 'candidate/entry', ['company' => ['id' => $company, 'name' => $tenant->name], 'portal' => ['version' => $portal->version, 'registration_open' => (bool) $portal->registration_open, 'notice' => $portal->notice], 'opportunities' => app(CandidateSubmissions::class)->publicOpportunities($company, (int) ($filter['opportunities_page'] ?? 1))]); } public function register(Request $request, int $company, CandidateProfiles $profiles): Response { $account = $profiles->register($company, $request->all()); Auth::guard('candidate')->login($account, false); $request->session()->put('candidate.session_version', $account->session_version); $request->session()->regenerate(); Inertia::clearHistory(); return redirect('/candidaturas/'.$company.'/perfil'); } public function login(Request $request, int $company, CandidateProfiles $profiles): Response { abort_unless($profiles->portal($company) !== null, 404); $data = $request->validate(['email' => 'required|string|max:254', 'password' => 'required|string|max:128']); $account = CandidateAccount::query()->where('tenant_id', $company)->where('email', mb_strtolower(trim($data['email'])))->first(); $dummy = Hash::make('candidate-unavailable-account'); $valid = Hash::check($data['password'], $account->password ?? $dummy); if ($account === null || ! $valid) { throw ValidationException::withMessages(['email' => 'Não foi possível entrar com os dados indicados.']); } $profiles->event($account, 'account.login', []); Auth::guard('candidate')->login($account, false); $request->session()->put('candidate.session_version', $account->session_version); $request->session()->regenerate(); Inertia::clearHistory(); return redirect('/candidaturas/'.$company.'/perfil'); } public function show(Request $request, int $company, CandidateProfiles $profiles): Response { $account = $this->account($request, $company); if ($account === null) { return redirect('/candidaturas/'.$company); } $profiles->event($account, 'profile.consulted', []); $filter = $request->validate(['opportunities_page' => 'sometimes|integer|min:1']); $opportunities = app(CandidateSubmissions::class)->publicOpportunities($company, (int) ($filter['opportunities_page'] ?? 1))->withPath('/candidaturas/'.$company.'/perfil'); return $this->page($request, 'candidate/profile', ['companyId' => $company, 'email' => $account->email, 'profile' => $profiles->profile($account), 'emailVerified' => $account->email_verified_at !== null, 'accessStatus' => $request->session()->get('candidate_access_status'), 'emailDeliveryAvailable' => app(CandidateAccess::class)->deliveryAvailable(), 'opportunities' => $opportunities->items(), 'opportunityPagination' => ['prev' => $opportunities->previousPageUrl(), 'next' => $opportunities->nextPageUrl()], 'submissions' => Schema::hasTable('candidate_submissions') ? DB::table('candidate_submissions')->where('tenant_id', $company)->where('candidate_account_id', $account->id)->latest('id')->limit(20)->get(['id', 'reference', 'created_at']) : []]); } public function save(Request $request, int $company, CandidateProfiles $profiles): Response { $account = $this->account($request, $company); abort_unless($account !== null, 401); $profiles->save($account, $request->all()); return back(); } public function logout(Request $request, int $company, CandidateProfiles $profiles): Response { $account = $this->account($request, $company); abort_unless($account !== null, 401); $profiles->event($account, 'account.logout', []); Auth::guard('candidate')->logout(); $request->session()->forget('candidate.session_version'); $request->session()->regenerate(); $request->session()->regenerateToken(); Inertia::clearHistory(); return redirect('/candidaturas/'.$company); } private function account(Request $request, int $company): ?CandidateAccount { $account = Auth::guard('candidate')->user(); if ($account instanceof CandidateAccount) { $account = CandidateAccount::query()->where('id', $account->id)->where('tenant_id', $company)->first(); } return $account instanceof CandidateAccount && $account->tenant_id === $company && $request->session()->get('candidate.session_version') === $account->session_version ? $account : null; } public function accessPage(Request $request, int $company, string $purpose, CandidateProfiles $profiles): Response { abort_unless($profiles->portal($company) !== null, 404); return $this->page($request, 'candidate/access', ['companyId' => $company, 'purpose' => $purpose, 'accessStatus' => $request->session()->get('candidate_access_status'), 'emailDeliveryAvailable' => app(CandidateAccess::class)->deliveryAvailable()]); } public function requestAccess(Request $request, int $company, CandidateAccess $access, CandidateProfiles $profiles): Response { abort_unless($profiles->portal($company) !== null, 404); $data = $request->validate(['email' => 'required|string|email|max:254']); $access->request($company, $data['email'], 'reset'); return back()->with('candidate_access_status', 'requested'); } public function requestVerification(Request $request, int $company, CandidateAccess $access): Response { $account = $this->account($request, $company); abort_unless($account !== null, 401); $access->request($company, $account->email, 'verify'); return back()->with('candidate_access_status', 'requested'); } public function confirmAccess(Request $request, int $company, string $purpose, CandidateAccess $access): Response { $access->consume($company, $purpose === 'confirmar-email' ? 'verify' : 'reset', $request->all()); Inertia::clearHistory(); return redirect('/candidaturas/'.$company.'/'.$purpose)->with('candidate_access_status', 'completed'); } private function staff(Request $request): User { $actor = $request->user('web'); abort_unless($actor instanceof User, 403); return $actor; } public function opportunity(Request $request, int $company, int $process, CandidateSubmissions $submissions): Response { $account = $this->account($request, $company); abort_unless($account !== null, 401); return $this->page($request, 'candidate/opportunity', ['companyId' => $company, 'opportunity' => $submissions->opportunity($company, $process), 'profile' => app(CandidateProfiles::class)->profile($account), 'email' => $account->email, 'emailVerified' => $account->email_verified_at !== null]); } public function submit(Request $request, int $company, int $process, CandidateSubmissions $submissions): Response { $account = $this->account($request, $company); abort_unless($account !== null, 401); $id = $submissions->submit($account, $process, $request->all()); return redirect('/candidaturas/'.$company.'/submissoes/'.$id); } public function receipt(Request $request, int $company, int $submission): Response { $account = $this->account($request, $company); abort_unless($account !== null, 401); $row = DB::table('candidate_submissions')->where('tenant_id', $company)->where('candidate_account_id', $account->id)->where('id', $submission)->first(); abort_unless($row !== null, 404); app(CandidateProfiles::class)->event($account, 'submission.consulted', ['submission_id' => $row->id]); return $this->page($request, 'candidate/receipt', ['companyId' => $company, 'decision' => app(CandidateTriage::class)->publicDecision($company, $submission), 'receipt' => ['id' => $row->id, 'reference' => $row->reference, 'received_at' => $row->created_at, 'snapshot' => json_decode($row->snapshot, true, 32, JSON_THROW_ON_ERROR)]]); } public function candidateDocument(Request $request, int $company, int $submission, string $document): Response { $account = $this->account($request, $company); abort_unless($account !== null, 401); $row = DB::table('candidate_submissions')->where('tenant_id', $company)->where('candidate_account_id', $account->id)->where('id', $submission)->first(); abort_unless($row !== null, 404); $file = $this->submissionDocument($company, $row->people_process_id, $submission, $document); app(CandidateProfiles::class)->event($account, 'submission.document_downloaded', ['submission_id' => $submission, 'document_key' => $document]); return app(CandidateDocuments::class)->download((array) $file); } public function staffDocument(Request $request, int $process, int $submission, string $document): Response { return DB::transaction(function () use ($request, $process, $submission, $document) { $actor = $this->staff($request); app(ConflictGuard::class)->lockCompany($actor); $actor = app(CurrentActor::class)->lock($actor); $record = PeopleProcess::query()->where('tenant_id', $actor->tenant_id)->where('id', $process)->firstOrFail(); abort_unless(app(CandidateTriage::class)->canRead($actor, $record), 403); $file = $this->submissionDocument($actor->tenant_id, $process, $submission, $document); app(RequisitionWorkflow::class)->audit($actor, 'candidate_submission.document_downloaded', null, null, ['process_id' => $process, 'submission_id' => $submission, 'document_key' => $document]); return app(CandidateDocuments::class)->download((array) $file); }); } private function submissionDocument(int $company, int $process, int $submission, string $key): \stdClass { abort_unless(Schema::hasTable('candidate_submission_documents'), 404); $row = DB::table('candidate_submission_documents')->where('tenant_id', $company)->where('people_process_id', $process)->where('candidate_submission_id', $submission)->where('document_key', $key)->first(); abort_unless($row !== null, 404); return $row; } public function inbox(Request $request, int $process, CandidateSubmissions $submissions): Response { return DB::transaction(function () use ($request, $process, $submissions) { $actor = $this->staff($request); app(ConflictGuard::class)->lockCompany($actor); $actor = app(CurrentActor::class)->lock($actor); $record = PeopleProcess::query()->where('tenant_id', $actor->tenant_id)->where('id', $process)->firstOrFail(); $triage = app(CandidateTriage::class); abort_unless($triage->canRead($actor, $record), 403); $filter = $request->validate(['submission' => 'nullable|integer|min:1']); $rows = DB::table('candidate_submissions')->where('tenant_id', $actor->tenant_id)->where('people_process_id', $process)->orderBy('id')->paginate(20); if (isset($filter['submission'])) { $rows = DB::table('candidate_submissions')->where('tenant_id', $actor->tenant_id)->where('people_process_id', $process)->where('id', $filter['submission'])->paginate(20)->withQueryString(); } $decisions = Schema::hasTable('candidate_triage') ? DB::table('candidate_triage')->where('tenant_id', $actor->tenant_id)->where('people_process_id', $process)->whereIn('candidate_submission_id', $rows->pluck('id'))->get(['candidate_submission_id', 'outcome', 'reason', 'public_message', 'created_at', 'people_application_id'])->keyBy('candidate_submission_id') : collect(); $rows->through(fn ($row) => ['id' => $row->id, 'reference' => $row->reference, 'received_at' => $row->created_at, 'snapshot' => json_decode($row->snapshot, true, 32, JSON_THROW_ON_ERROR), 'decision' => $decisions->get($row->id)]); app(RequisitionWorkflow::class)->audit($actor, 'candidate_submissions.consulted', null, null, ['process_id' => $process]); return $this->page($request, 'people/submissions', ['process' => ['id' => $record->id, 'title' => $record->title, 'version' => $record->version], 'publication' => $submissions->publication($record), 'submissions' => $rows, 'canPrepare' => $triage->canPrepare($actor, $record), 'canTriage' => Schema::hasTable('candidate_triage') && $triage->canPrepare($actor, $record) && in_array($record->status, ['open', 'assessment'], true), 'publicUrl' => '/candidaturas/'.$actor->tenant_id.'/oportunidades/'.$record->id]); }); } public function publish(Request $request, int $process, CandidateSubmissions $submissions): Response { $submissions->publish($this->staff($request), $process, $request->all()); return back(); } public function triage(Request $request, int $process, int $submission, CandidateTriage $triage): Response { $triage->decide($this->staff($request), $process, $submission, $request->all()); return back(); } /** @param array $props */ private function page(Request $request, string $component, array $props): Response { Inertia::encryptHistory(); $response = Inertia::render($component, $props)->toResponse($request); $response->headers->set('Cache-Control', 'private, no-store, max-age=0'); $response->headers->set('X-Robots-Tag', 'noindex, nofollow'); $response->headers->set('Referrer-Policy', 'no-referrer'); return $response; } }