This commit is contained in:
2026-11-02 08:08:05 +02:00
parent 13a6cb9922
commit 504b01b244
246 changed files with 24499 additions and 185 deletions
+144
View File
@@ -523,3 +523,147 @@ test('the person concluding technical clarification cannot approve the hiring se
hiringCommand($this, $this->approver, $record, 'confirm', ['outcome' => 'approved'])->assertForbidden();
$this->get('/pessoas')->assertInertia(fn (Assert $page) => $page->where('workdesk.counts.approvals', 0));
});
test('people reports reuse only visible processes and export filtered facts without private fields', function () {
$record = hiringCreate($this);
$this->actingAs($this->approver)->postJson('/pessoas', [...$this->body, 'operation_key' => (string) Str::uuid(), 'title' => 'PROCESSO RESERVADO', 'kind' => 'staff'])->assertRedirect();
$this->actingAs($this->preparer)->get('/pessoas/relatorios')->assertOk()->assertInertia(fn (Assert $page) => $page
->component('people/reports')->where('processes.total', 1)->has('summary', 1)
->where('processes.data.0.id', $record->id)->missing('processes.data.0.rules')->missing('processes.data.0.contract'));
$csv = $this->get('/pessoas/relatorios.csv')->assertOk();
expect($csv->streamedContent())->toContain('PES-'.$record->id)->not->toContain('PROCESSO RESERVADO');
expect($csv->headers->get('Cache-Control'))->toContain('no-store');
$this->get('/pessoas/relatorios?kind=staff')->assertInertia(fn (Assert $page) => $page->where('processes.total', 0)->has('summary', 0));
$this->get('/pessoas/relatorios?status=selected')->assertInertia(fn (Assert $page) => $page->where('processes.total', 0));
$this->get('/pessoas/relatorios?search=PES-'.$record->id)->assertInertia(fn (Assert $page) => $page->where('processes.total', 1));
DB::table('people_process_access')->where('people_process_id', $record->id)->where('user_id', $this->preparer->id)->update(['expires_at' => now()->subMinute()]);
$this->get('/pessoas/relatorios')->assertInertia(fn (Assert $page) => $page->where('processes.total', 0));
expect($this->get('/pessoas/relatorios.csv')->streamedContent())->not->toContain('PES-'.$record->id);
});
test('people report dates and csv protect scope and spreadsheet cells', function () {
$this->body['title'] = '=FORMULA DE ENSAIO';
$record = hiringCreate($this);
$csv = $this->get('/pessoas/relatorios.csv')->assertOk()->streamedContent();
expect($csv)->toContain("'=FORMULA DE ENSAIO");
$this->get('/pessoas/relatorios?from='.today()->addDay()->toDateString())->assertInertia(fn (Assert $page) => $page->where('processes.total', 0));
$this->getJson('/pessoas/relatorios?from=2026-11-02&to=2026-11-01')->assertUnprocessable();
$foreign = User::factory()->create(['tenant_id' => Tenant::query()->create(['name' => 'Empresa distinta'])->id, 'purchasing_role' => PurchasingRole::Admin]);
$this->actingAs($foreign)->get('/pessoas/relatorios')->assertInertia(fn (Assert $page) => $page->where('processes.total', 0));
expect($this->get('/pessoas/relatorios.csv')->streamedContent())->not->toContain('PES-'.$record->id);
});
test('application report reuses eligible assessments and preserves approved selection counts without private content', function () {
$record = hiringAssessment($this);
$application = DB::table('people_applications')->sole();
$url = '/pessoas/relatorios?view=applications';
$this->actingAs($this->preparer)->get($url)->assertInertia(fn (Assert $page) => $page
->where('applicationMetrics.'.$record->id.'.registered', 1)
->where('applicationMetrics.'.$record->id.'.evaluation_pending', 1)
->where('applicationMetrics.'.$record->id.'.evaluation_unstarted', 1));
hiringCommand($this, $this->reviewer, $record, 'evaluate', ['application_id' => $application->id, 'answers' => [
['score' => '8', 'evidence' => 'Evidência fictícia de competências.'], ['score' => '7', 'evidence' => 'Evidência fictícia de experiência.'],
]])->assertRedirect();
$this->actingAs($this->preparer)->get($url)->assertInertia(fn (Assert $page) => $page
->where('applicationMetrics.'.$record->id.'.evaluation_complete', 1)
->where('applicationMetrics.'.$record->id.'.evaluation_pending', 0)
->missing('applicationMetrics.'.$record->id.'.reviews')->missing('applicationMetrics.'.$record->id.'.name'));
hiringCommand($this, $this->preparer, $record, 'select', ['application_id' => $application->id])->assertRedirect();
hiringCommand($this, $this->approver, $record, 'confirm', ['outcome' => 'approved'])->assertRedirect();
DB::table('people_process_access')->where('people_process_id', $record->id)->where('user_id', $this->reviewer->id)->update(['expires_at' => now()->subMinute()]);
$this->actingAs($this->preparer)->get($url)->assertInertia(fn (Assert $page) => $page
->where('applicationMetrics.'.$record->id.'.basis', 'selection_snapshot')
->where('applicationMetrics.'.$record->id.'.evaluation_complete', 1));
$this->actingAs($this->approver)->get($url)->assertInertia(fn (Assert $page) => $page
->where('applicationMetrics.'.$record->id.'.portal_received', null));
expect($this->get('/pessoas/relatorios.csv?view=applications')->streamedContent())->toContain('Sem acesso')->toContain('Selecção preservada')->not->toContain($application->name);
});
test('selection report follows return approval and draft while restricting contract metadata', function (string $kind) {
$record = hiringAssessment($this, $kind);
$application = DB::table('people_applications')->sole();
$url = '/pessoas/relatorios?view=selections';
$prefix = 'selectionMetrics.'.$record->id.'.';
$this->actingAs($this->preparer)->get($url)->assertInertia(fn (Assert $page) => $page
->where($prefix.'selection_state', 'Sem proposta de selecção')->where($prefix.'contract_reference', null));
hiringCommand($this, $this->reviewer, $record, 'evaluate', ['application_id' => $application->id, 'answers' => [
['score' => '8', 'evidence' => 'Evidência fictícia de competências.'], ['score' => '7', 'evidence' => 'Evidência fictícia de experiência.'],
]])->assertRedirect();
hiringCommand($this, $this->preparer, $record, 'select', ['application_id' => $application->id])->assertRedirect();
$this->get($url)->assertInertia(fn (Assert $page) => $page->where($prefix.'selection_state', 'Proposta por conferir'));
hiringCommand($this, $this->approver, $record, 'confirm', ['outcome' => 'returned'])->assertRedirect();
$this->get($url)->assertInertia(fn (Assert $page) => $page->where($prefix.'selection_state', 'Proposta devolvida'));
hiringCommand($this, $this->preparer, $record, 'select', ['application_id' => $application->id])->assertRedirect();
hiringCommand($this, $this->approver, $record, 'confirm', ['outcome' => 'approved'])->assertRedirect();
$this->get($url)->assertInertia(fn (Assert $page) => $page->where($prefix.'selection_state', 'Selecção aprovada')->where($prefix.'pending', 'Preparar minuta contratual'));
hiringCommand($this, $this->preparer, $record, 'contract', ['reference' => '=MINUTA-RESERVADA', 'starts_on' => today()->toDateString(), 'terms' => 'TERMOS-CONTRATUAIS-RESERVADOS-NAO-EXPORTAR'])->assertRedirect();
$this->get($url)->assertInertia(fn (Assert $page) => $page->where($prefix.'contract_reference', '=MINUTA-RESERVADA')->missing($prefix.'terms'));
$csv = $this->get('/pessoas/relatorios.csv?view=selections')->streamedContent();
expect($csv)->toContain("'=MINUTA-RESERVADA")->not->toContain('TERMOS-CONTRATUAIS-RESERVADOS');
$this->actingAs($this->approver)->get($url)->assertInertia(fn (Assert $page) => $page
->where($prefix.'contract_reference', 'Sem acesso')->where($prefix.'contract_starts_on', 'Sem acesso'));
expect($this->get('/pessoas/relatorios.csv?view=selections')->streamedContent())->not->toContain('MINUTA-RESERVADA');
DB::table('people_process_access')->where('people_process_id', $record->id)->where('user_id', $this->approver->id)->update(['expires_at' => now()->subMinute()]);
$this->get($url)->assertInertia(fn (Assert $page) => $page->where('selectionMetrics', [])->where('processes.total', 0));
})->with(['service', 'staff']);
test('deadline report distinguishes process alerts from personal tasks and respects expired access', function () {
$record = hiringCreate($this);
$this->travel(2)->days();
$url = '/pessoas/relatorios?view=deadlines';
$this->actingAs($this->preparer)->get($url)->assertInertia(fn (Assert $p) => $p
->where('deadlineReport.tasks', 0)->where('deadlineReport.alerts', 1)->where('deadlineReport.overdue_items', 1)
->where('deadlineReport.items.0.days_overdue', 2)
->where('deadlineReport.items.0.responsibility', 'Alerta do processo; sem atribuição pessoal'));
expect($this->get('/pessoas/relatorios.csv?view=deadlines')->streamedContent())->toContain('PES-'.$record->id)->toContain('Dias de atraso');
DB::table('people_process_access')->where('people_process_id', $record->id)->where('user_id', $this->preparer->id)->update(['expires_at' => now()->subMinute()]);
$this->get($url)->assertInertia(fn (Assert $p) => $p->has('deadlineReport.items', 0)->where('deadlineReport.overdue_items', 0));
expect($this->get('/pessoas/relatorios.csv?view=deadlines')->streamedContent())->not->toContain('PES-'.$record->id);
});
test('deadline report reuses only the current actors work and never invents evaluation deadlines', function () {
$record = hiringAssessment($this);
$url = '/pessoas/relatorios?view=deadlines';
$this->actingAs($this->reviewer)->get($url)->assertInertia(fn (Assert $p) => $p
->where('deadlineReport.tasks', 1)->where('deadlineReport.without_deadline', 1)->where('deadlineReport.overdue_items', 0)
->where('deadlineReport.items.0.due_on', null)->where('deadlineReport.items.0.days_overdue', null)
->where('deadlineReport.items.0.responsibility', 'Minha intervenção autorizada')
->missing('deadlineReport.items.0.answers')->missing('deadlineReport.items.0.score_minor'));
expect($this->get('/pessoas/relatorios.csv?view=deadlines')->streamedContent())->toContain('Sem prazo definido')->toContain('Não determinável');
$this->actingAs($this->preparer)->get($url)->assertInertia(fn (Assert $p) => $p->where('deadlineReport.tasks', 0));
});
test('deadline windows distinguish future today and overdue calendar dates', function () {
$this->body['closes_on'] = today()->addDays(2)->toDateString();
hiringCreate($this);
$this->actingAs($this->reviewer);
$base = '/pessoas/relatorios?view=deadlines&deadline_window=';
$this->get($base.'future')->assertInertia(fn (Assert $p) => $p->where('deadlineReport.total_items', 1)->where('deadlineReport.approvals', 1));
$this->get($base.'today')->assertInertia(fn (Assert $p) => $p->where('deadlineReport.total_items', 0));
$this->travel(2)->days();
$this->get($base.'today')->assertInertia(fn (Assert $p) => $p->where('deadlineReport.total_items', 1)->where('deadlineReport.items.0.days_overdue', 0));
$this->get($base.'overdue')->assertInertia(fn (Assert $p) => $p->where('deadlineReport.total_items', 0));
$this->travel(1)->days();
$this->get($base.'overdue')->assertInertia(fn (Assert $p) => $p->where('deadlineReport.total_items', 1)->where('deadlineReport.items.0.days_overdue', 1));
$this->get($base.'undated')->assertInertia(fn (Assert $p) => $p->where('deadlineReport.total_items', 0));
$this->getJson($base.'invalid')->assertUnprocessable();
$this->getJson('/pessoas/relatorios?view=deadlines&deadline_page=0')->assertUnprocessable();
});
test('deadline totals cover all matching occurrences while screen pagination and csv keep the same scope', function () {
$record = hiringCreate($this);
hiringCommand($this, $this->reviewer, $record, 'open')->assertRedirect();
foreach (range(1, 30) as $number) {
hiringCommand($this, $this->preparer, $record, 'apply', ['operation_key' => (string) Str::uuid(), 'name' => 'Pessoa fictícia '.$number,
'reference' => 'CAND-'.$number, 'summary' => 'Experiência fictícia pertinente ao processo.', 'received_on' => today()->toDateString()])->assertRedirect();
}
hiringCommand($this, $this->preparer, $record, 'close')->assertRedirect();
$this->actingAs($this->reviewer);
$url = '/pessoas/relatorios?view=deadlines&deadline_window=undated';
$this->get($url)->assertInertia(fn (Assert $p) => $p->where('deadlineReport.tasks', 30)->where('deadlineReport.total_items', 30)
->where('deadlineReport.matching_processes', 1)->has('deadlineReport.items', 25)->where('deadlineReport.pagination.last_page', 2));
$this->get($url.'&deadline_page=2')->assertInertia(fn (Assert $p) => $p->where('deadlineReport.tasks', 30)->has('deadlineReport.items', 5));
$csv = $this->get('/pessoas/relatorios.csv?view=deadlines&deadline_window=undated&deadline_page=2')->streamedContent();
expect(substr_count($csv, 'Avaliar candidatura #'))->toBe(30);
$this->get('/pessoas/relatorios?view=deadlines&deadline_window=overdue')->assertInertia(fn (Assert $p) => $p->where('deadlineReport.total_items', 0));
});