256 lines
14 KiB
PHP
256 lines
14 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Controllers;
|
|
|
|
use App\Models\CandidateAccount;
|
|
use App\Models\PeopleProcess;
|
|
use App\Models\Tenant;
|
|
use App\Models\User;
|
|
use App\Services\CandidateAccess;
|
|
use App\Services\CandidateProfiles;
|
|
use App\Services\CandidateSubmissions;
|
|
use App\Services\CandidateTriage;
|
|
use App\Services\ConflictGuard;
|
|
use App\Services\CurrentActor;
|
|
use App\Services\OrganizationConfiguration;
|
|
use App\Services\RequisitionWorkflow;
|
|
use App\Services\SubmissionForms;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Support\Facades\Auth;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Facades\Hash;
|
|
use Illuminate\Support\Facades\Schema;
|
|
use Illuminate\Validation\ValidationException;
|
|
use Inertia\Inertia;
|
|
use Symfony\Component\HttpFoundation\Response;
|
|
|
|
class CandidatePortalController extends Controller
|
|
{
|
|
public function settings(Request $request, CandidateProfiles $profiles, OrganizationConfiguration $configuration): Response
|
|
{
|
|
$actor = $configuration->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);
|
|
|
|
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]]);
|
|
}
|
|
|
|
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', []);
|
|
|
|
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' => Schema::hasTable('candidate_opportunity_publications') ? DB::table('candidate_opportunity_publications as p')->join('people_processes as r', 'r.id', '=', 'p.people_process_id')
|
|
->where('p.tenant_id', $company)->where('r.tenant_id', $company)->where('p.active', true)->where('r.status', 'open')->where('r.closes_on', '>=', today()->toDateString())
|
|
->whereNotExists(fn ($q) => $q->selectRaw('1')->from('candidate_opportunity_publications as newer')->whereColumn('newer.people_process_id', 'p.people_process_id')->whereColumn('newer.version', '>', 'p.version'))
|
|
->orderBy('r.closes_on')->limit(20)->get(['r.id', 'r.title', 'r.closes_on']) : [],
|
|
'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' => ['reference' => $row->reference, 'received_at' => $row->created_at, 'snapshot' => json_decode($row->snapshot, true, 32, JSON_THROW_ON_ERROR)]]);
|
|
}
|
|
|
|
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<string,mixed> $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;
|
|
}
|
|
}
|