Receber documentos privados e locais de disponibilidade nas candidaturas
tests / ci (push) Canceled after 0s
tests / ci (push) Canceled after 0s
This commit is contained in:
@@ -6,6 +6,8 @@ use App\Models\CandidateAccount;
|
||||
use App\Models\PeopleProcess;
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
@@ -32,7 +34,7 @@ class CandidateSubmissions
|
||||
abort_unless($form !== null && $form['definition']['available'] && app(CandidateProfiles::class)->portal($actor->tenant_id) !== null, 422, 'Configure o portal e prepare um processo com formulário disponível.');
|
||||
foreach ($form['definition']['documents'] as $document) {
|
||||
if ($document['required']) {
|
||||
throw ValidationException::withMessages(['active' => 'Este formulário exige anexos. A recepção electrónica de anexos ainda está em desenvolvimento; não publique sem esse percurso.']);
|
||||
abort_unless(Schema::hasTable('candidate_submission_documents'), 503, 'Instalação da recepção de documentos pendente.');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -62,36 +64,63 @@ class CandidateSubmissions
|
||||
/** @param array<string,mixed> $input */
|
||||
public function submit(CandidateAccount $actor, int $processId, array $input): int
|
||||
{
|
||||
return DB::transaction(function () use ($actor, $processId, $input) {
|
||||
DB::table('tenants')->where('id', $actor->tenant_id)->lockForUpdate()->firstOrFail();
|
||||
$sessionVersion = $actor->session_version;
|
||||
$actor = CandidateAccount::query()->where('id', $actor->id)->lockForUpdate()->firstOrFail();
|
||||
abort_unless($actor->session_version === $sessionVersion, 401);
|
||||
abort_unless($actor->email_verified_at !== null, 403, 'Confirme o email antes de submeter.');
|
||||
$data = Validator::make($input, ['operation_key' => 'required|uuid', 'profile_version' => 'required|integer|min:1', 'publication_version' => 'required|integer|min:1', 'answers' => 'present|array', 'documents' => 'sometimes|array|max:0'])->validate();
|
||||
$hash = hash('sha256', json_encode([$processId, $data], JSON_THROW_ON_ERROR));
|
||||
$previous = DB::table('candidate_submissions')->where('candidate_account_id', $actor->id)->where('operation_key', $data['operation_key'])->first();
|
||||
if ($previous !== null) {
|
||||
abort_unless($previous->people_process_id === $processId && $previous->input_hash === $hash, 409, 'Esta operação já foi utilizada com outros dados.');
|
||||
$stored = [];
|
||||
try {
|
||||
return DB::transaction(function () use ($actor, $processId, $input, &$stored) {
|
||||
DB::table('tenants')->where('id', $actor->tenant_id)->lockForUpdate()->firstOrFail();
|
||||
$sessionVersion = $actor->session_version;
|
||||
$actor = CandidateAccount::query()->where('id', $actor->id)->lockForUpdate()->firstOrFail();
|
||||
abort_unless($actor->session_version === $sessionVersion, 401);
|
||||
abort_unless($actor->email_verified_at !== null, 403, 'Confirme o email antes de submeter.');
|
||||
$data = Validator::make($input, ['operation_key' => 'required|uuid', 'profile_version' => 'required|integer|min:1', 'publication_version' => 'required|integer|min:1', 'answers' => 'present|array', 'documents' => 'sometimes|array|max:20'])->validate();
|
||||
$documents = app(CandidateDocuments::class);
|
||||
$fingerprint = $documents->fingerprint($data['documents'] ?? []);
|
||||
$hashData = $data;
|
||||
if (array_key_exists('documents', $hashData)) {
|
||||
$hashData['documents'] = $fingerprint;
|
||||
}
|
||||
$hash = hash('sha256', json_encode([$processId, $hashData], JSON_THROW_ON_ERROR));
|
||||
$previous = DB::table('candidate_submissions')->where('candidate_account_id', $actor->id)->where('operation_key', $data['operation_key'])->first();
|
||||
if ($previous !== null) {
|
||||
abort_unless($previous->people_process_id === $processId && $previous->input_hash === $hash, 409, 'Esta operação já foi utilizada com outros dados.');
|
||||
|
||||
return $previous->id;
|
||||
}
|
||||
$opportunity = $this->opportunity($actor->tenant_id, $processId);
|
||||
$profile = app(CandidateProfiles::class)->profile($actor);
|
||||
if ((int) $data['profile_version'] !== $profile['version'] || (int) $data['publication_version'] !== $opportunity['publication_version']) {
|
||||
throw ValidationException::withMessages(['version' => 'O perfil ou a publicação mudou. Actualize e confira antes de submeter.']);
|
||||
}
|
||||
abort_if(DB::table('candidate_submissions')->where('people_process_id', $processId)->where('candidate_account_id', $actor->id)->exists(), 409, 'Já apresentou candidatura a esta oportunidade.');
|
||||
foreach ($opportunity['form']['definition']['documents'] as $document) {
|
||||
abort_if($document['required'], 422, 'Faltam anexos obrigatórios.');
|
||||
}
|
||||
$answers = app(SubmissionAnswers::class)->validate($opportunity['form']['definition'], $data['answers']);
|
||||
$id = DB::table('candidate_submissions')->insertGetId(['tenant_id' => $actor->tenant_id, 'candidate_account_id' => $actor->id, 'people_process_id' => $processId,
|
||||
'operation_key' => $data['operation_key'], 'reference' => 'CAND-'.strtoupper((string) Str::uuid()), 'input_hash' => $hash,
|
||||
'snapshot' => json_encode(['opportunity' => $opportunity, 'profile' => $profile, 'email' => $actor->email, 'answers' => $answers, 'documents' => [], 'status' => 'received'], JSON_THROW_ON_ERROR), 'created_at' => now()]);
|
||||
app(CandidateProfiles::class)->event($actor, 'submission.received', ['submission_id' => $id, 'process_id' => $processId, 'profile_version' => $profile['version']]);
|
||||
return $previous->id;
|
||||
}
|
||||
$opportunity = $this->opportunity($actor->tenant_id, $processId);
|
||||
$profile = app(CandidateProfiles::class)->profile($actor);
|
||||
if ((int) $data['profile_version'] !== $profile['version'] || (int) $data['publication_version'] !== $opportunity['publication_version']) {
|
||||
throw ValidationException::withMessages(['version' => 'O perfil ou a publicação mudou. Actualize e confira antes de submeter.']);
|
||||
}
|
||||
abort_if(DB::table('candidate_submissions')->where('people_process_id', $processId)->where('candidate_account_id', $actor->id)->exists(), 409, 'Já apresentou candidatura a esta oportunidade.');
|
||||
$documents->validate($opportunity['form']['definition'], $data['documents'] ?? []);
|
||||
$answers = app(SubmissionAnswers::class)->validate($opportunity['form']['definition'], $data['answers']);
|
||||
$metadata = [];
|
||||
if ($fingerprint !== []) {
|
||||
abort_unless(Schema::hasTable('candidate_submission_documents'), 503, 'Instalação da recepção de documentos pendente.');
|
||||
foreach ($fingerprint as $key => $details) {
|
||||
$path = $data['documents'][$key]->store($actor->tenant_id.'/'.$actor->id, 'candidate_documents');
|
||||
abort_unless(is_string($path), 500);
|
||||
$stored[$key] = $path;
|
||||
$metadata[$key] = [...$details, 'key' => $key, 'verification' => 'format_checked'];
|
||||
}
|
||||
}
|
||||
$id = DB::table('candidate_submissions')->insertGetId(['tenant_id' => $actor->tenant_id, 'candidate_account_id' => $actor->id, 'people_process_id' => $processId,
|
||||
'operation_key' => $data['operation_key'], 'reference' => 'CAND-'.strtoupper((string) Str::uuid()), 'input_hash' => $hash,
|
||||
'snapshot' => json_encode(['opportunity' => $opportunity, 'profile' => $profile, 'email' => $actor->email, 'answers' => $answers, 'documents' => $metadata, 'status' => 'received'], JSON_THROW_ON_ERROR), 'created_at' => now()]);
|
||||
foreach ($stored as $key => $path) {
|
||||
DB::table('candidate_submission_documents')->insert(['tenant_id' => $actor->tenant_id, 'people_process_id' => $processId, 'candidate_submission_id' => $id,
|
||||
'document_key' => $key, 'path' => $path, 'original_name' => $fingerprint[$key]['name'], 'mime_type' => $fingerprint[$key]['mime'],
|
||||
'size' => $fingerprint[$key]['size'], 'sha256' => $fingerprint[$key]['sha256'], 'created_at' => now()]);
|
||||
}
|
||||
app(CandidateProfiles::class)->event($actor, 'submission.received', ['submission_id' => $id, 'process_id' => $processId, 'profile_version' => $profile['version']]);
|
||||
|
||||
return $id;
|
||||
});
|
||||
return $id;
|
||||
});
|
||||
} catch (\Throwable $error) {
|
||||
foreach ($stored as $path) {
|
||||
Storage::disk('candidate_documents')->delete($path);
|
||||
}
|
||||
throw $error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user