101 lines
6.5 KiB
PHP
101 lines
6.5 KiB
PHP
<?php
|
|
|
|
namespace App\Services;
|
|
|
|
use App\Models\CandidateAccount;
|
|
use App\Models\User;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Facades\Schema;
|
|
use Illuminate\Support\Facades\Validator;
|
|
use Illuminate\Validation\ValidationException;
|
|
use stdClass;
|
|
|
|
class CandidateProfiles
|
|
{
|
|
public function portal(int $tenantId): ?stdClass
|
|
{
|
|
return Schema::hasTable('candidate_portal_versions') ? DB::table('candidate_portal_versions')->where('tenant_id', $tenantId)->orderByDesc('version')->first() : null;
|
|
}
|
|
|
|
/** @param array<string,mixed> $input */
|
|
public function configure(User $actor, array $input): void
|
|
{
|
|
DB::transaction(function () use ($actor, $input) {
|
|
app(ConflictGuard::class)->lockCompany($actor);
|
|
$actor = app(CurrentActor::class)->lock($actor);
|
|
abort_unless(app(SubmissionForms::class)->canManage($actor), 403);
|
|
abort_unless(Schema::hasTable('candidate_portal_versions'), 503, 'Instalação da área do candidato pendente.');
|
|
$data = Validator::make($input, ['version' => 'required|integer|min:0', 'registration_open' => 'required|boolean',
|
|
'notice' => 'required|string|min:30|max:15000', 'reason' => 'required|string|min:10|max:4000'])->validate();
|
|
$previous = $this->portal($actor->tenant_id);
|
|
if ((int) $data['version'] !== (int) ($previous->version ?? 0)) {
|
|
throw ValidationException::withMessages(['version' => 'A configuração mudou. Actualize antes de guardar.']);
|
|
}
|
|
$id = DB::table('candidate_portal_versions')->insertGetId(['tenant_id' => $actor->tenant_id, 'created_by' => $actor->id,
|
|
'version' => (int) $data['version'] + 1, 'registration_open' => $data['registration_open'], 'notice' => $data['notice'], 'reason' => $data['reason'], 'created_at' => now()]);
|
|
app(RequisitionWorkflow::class)->audit($actor, 'candidate_portal.configured', null, $previous === null ? null : (array) $previous,
|
|
['id' => $id, ...$data, 'scope' => 'candidate_profile_registration_only', 'opportunity_publication_required' => true]);
|
|
});
|
|
}
|
|
|
|
/** @param array<string,mixed> $input */
|
|
public function register(int $tenantId, array $input): CandidateAccount
|
|
{
|
|
return DB::transaction(function () use ($tenantId, $input) {
|
|
// Same tenant mutex as company configuration; never lock or authenticate as a staff user.
|
|
DB::table('tenants')->where('id', $tenantId)->lockForUpdate()->firstOrFail();
|
|
$portal = $this->portal($tenantId);
|
|
abort_unless($portal !== null && $portal->registration_open, 403, 'Novos registos estão encerrados.');
|
|
$data = Validator::make($input, ['email' => 'required|string|email|max:254', 'password' => 'required|string|min:12|max:128|confirmed', 'notice_acknowledged' => 'accepted', 'portal_version' => 'required|integer|min:1'])->validate();
|
|
if ((int) $data['portal_version'] !== $portal->version) {
|
|
throw ValidationException::withMessages(['portal_version' => 'O aviso mudou. Actualize e confira antes de criar a conta.']);
|
|
}
|
|
$email = mb_strtolower(trim($data['email']));
|
|
if (CandidateAccount::query()->where('tenant_id', $tenantId)->where('email', $email)->exists()) {
|
|
throw ValidationException::withMessages(['email' => 'Não foi possível criar a conta. Se já se registou, utilize a entrada de candidatos.']);
|
|
}
|
|
$account = CandidateAccount::query()->create(['tenant_id' => $tenantId, 'email' => $email, 'password' => $data['password'], 'portal_version_id' => $portal->id]);
|
|
$this->event($account, 'account.created', ['portal_version_id' => $portal->id, 'notice_acknowledged' => true, 'email_verified' => false]);
|
|
|
|
return $account;
|
|
});
|
|
}
|
|
|
|
/** @return array<string,mixed> */
|
|
public function profile(CandidateAccount $account): array
|
|
{
|
|
$row = DB::table('candidate_profile_versions')->where('tenant_id', $account->tenant_id)->where('candidate_account_id', $account->id)->orderByDesc('version')->first();
|
|
|
|
return ['version' => $row->version ?? 0, 'data' => $row === null ? ['name' => '', 'phone' => '', 'summary' => '', 'availability' => ['province' => '', 'district' => '', 'locality' => '']] : json_decode($row->data, true, 16, JSON_THROW_ON_ERROR)];
|
|
}
|
|
|
|
/** @param array<string,mixed> $input */
|
|
public function save(CandidateAccount $account, array $input): void
|
|
{
|
|
DB::transaction(function () use ($account, $input) {
|
|
$sessionVersion = $account->session_version;
|
|
$account = CandidateAccount::query()->where('tenant_id', $account->tenant_id)->where('id', $account->id)->lockForUpdate()->firstOrFail();
|
|
abort_unless($account->session_version === $sessionVersion, 401);
|
|
$data = Validator::make($input, ['version' => 'required|integer|min:0', 'name' => 'required|string|min:3|max:200', 'phone' => 'nullable|string|max:50',
|
|
'summary' => 'required|string|min:10|max:5000', 'availability' => 'required|array:province,district,locality',
|
|
'availability.province' => 'required|string|max:150', 'availability.district' => 'required|string|max:150', 'availability.locality' => 'required|string|max:150',
|
|
'reason' => 'required|string|min:10|max:4000'])->validate();
|
|
$previous = $this->profile($account);
|
|
if ((int) $data['version'] !== $previous['version']) {
|
|
throw ValidationException::withMessages(['version' => 'O perfil mudou. Actualize e confira antes de guardar.']);
|
|
}
|
|
$id = DB::table('candidate_profile_versions')->insertGetId(['tenant_id' => $account->tenant_id, 'candidate_account_id' => $account->id,
|
|
'version' => $previous['version'] + 1, 'data' => json_encode(array_intersect_key($data, array_flip(['name', 'phone', 'summary', 'availability'])), JSON_THROW_ON_ERROR),
|
|
'reason' => $data['reason'], 'created_at' => now()]);
|
|
$this->event($account, 'profile.version_created', ['profile_version_id' => $id, 'version' => $previous['version'] + 1]);
|
|
});
|
|
}
|
|
|
|
/** @param array<string,mixed> $metadata */
|
|
public function event(CandidateAccount $account, string $action, array $metadata): void
|
|
{
|
|
DB::table('candidate_events')->insert(['tenant_id' => $account->tenant_id, 'candidate_account_id' => $account->id, 'action' => $action,
|
|
'metadata' => json_encode($metadata, JSON_THROW_ON_ERROR), 'created_at' => now()]);
|
|
}
|
|
}
|