Files
compras/tests/Feature/SupplierImprovementReportTest.php
T
nala 63d20b10fc
tests / ci (push) Canceled after 0s
commit de armazens
2026-11-04 09:40:26 +02:00

201 lines
15 KiB
PHP

<?php
use App\Enums\PurchasingRole;
use App\Models\AuditLog;
use App\Models\Department;
use App\Models\Supplier;
use App\Models\User;
use App\Services\AdministrativeGeography;
use Illuminate\Support\Facades\DB;
use Inertia\Testing\AssertableInertia as Assert;
require_once __DIR__.'/../Support/ReceiptFixture.php';
require_once __DIR__.'/../Support/SupplierImprovementFixture.php';
beforeEach(function () {
$this->f = receiptFixture();
$this->actingAs($this->f['issuer']);
$this->evaluation = reportEvaluation($this->f);
$this->base = '/relatorios/medidas-fornecedores';
$this->travelTo(now()->setDate(2026, 10, 30)->startOfDay());
});
function supplierReportReference(bool $otherProvince = false): array
{
$record = collect(app(AdministrativeGeography::class)->catalogue()['records'])->first(fn ($record) => $record['selectable'] && $record['level'] === 4 && ($otherProvince ? $record['codes'][0] !== '01' : $record['codes'][0] === '01'));
return ['country' => 'MZ', 'catalogue' => config('geography.source_sha256'), 'record_id' => $record['id']];
}
test('supplier territory report shares directory classification and preserves approved evaluation snapshots', function () {
$this->withoutVite();
config(['geography.enabled' => true]);
$supplier = Supplier::query()->findOrFail($this->evaluation->supplier_id);
$supplier->update(['geography_reference' => supplierReportReference()]);
$measure = reportMeasure($this->evaluation, $this->f['issuer']);
$before = [];
foreach (['suppliers', 'supplier_evaluations', 'supplier_improvements', 'audit_logs'] as $table) {
$before[$table] = DB::table($table)->orderBy('id')->get()->toArray();
}
$this->get($this->base.'?location=2')->assertInertia(fn (Assert $page) => $page->where('records.total', 1)->where('records.data.0.id', $measure->id)->where('records.data.0.supplier', 'Fornecedor preservado QA')->where('counts.open', 1)->where('overdue', 1)->where('geographyFilter.path.0.name', 'Niassa'));
$this->get('/fornecedores?location=2')->assertInertia(fn (Assert $page) => $page->has('suppliers.data', 1)->where('suppliers.data.0.id', $supplier->id));
expect($this->get($this->base.'.csv?location=2')->assertOk()->streamedContent())->toContain('Niassa', 'localizacao_actual_da_ficha', 'Fornecedor preservado QA');
foreach ($before as $table => $rows) {
expect(DB::table($table)->orderBy('id')->get()->toArray())->toEqual($rows);
}
});
test('supplier territory report uses current registration without rewriting the evaluation when geography changes', function () {
$this->withoutVite();
config(['geography.enabled' => true]);
$supplier = Supplier::query()->findOrFail($this->evaluation->supplier_id);
$supplier->update(['geography_reference' => supplierReportReference()]);
reportMeasure($this->evaluation, $this->f['issuer']);
$original = $this->evaluation->fresh()->getAttributes();
$this->get($this->base.'?location=2')->assertInertia(fn (Assert $page) => $page->where('records.total', 1));
$supplier->update(['geography_reference' => supplierReportReference(true)]);
$this->get($this->base.'?location=2')->assertInertia(fn (Assert $page) => $page->where('records.total', 0));
$this->get($this->base)->assertInertia(fn (Assert $page) => $page->where('records.total', 1));
expect($this->evaluation->refresh()->getAttributes())->toBe($original);
});
test('supplier territory report keeps historical revisions suspended suppliers and complete CSV across pages', function () {
$this->withoutVite();
config(['geography.enabled' => true]);
Supplier::query()->findOrFail($this->evaluation->supplier_id)->update(['geography_reference' => supplierReportReference(), 'status' => 'suspended']);
$revision = reportEvaluation($this->f, null, 2, $this->evaluation->id);
foreach (range(1, 26) as $index) {
reportMeasure($index === 26 ? $revision : $this->evaluation, $this->f['issuer'], ['title' => 'Território '.$index, 'status' => $index === 1 ? 'closed' : 'open']);
}
$this->get($this->base.'?location=2')->assertInertia(fn (Assert $page) => $page->where('records.total', 26)->has('records.data', 25)->where('counts.open', 25)->where('counts.closed', 1)->where('overdue', 25));
$response = $this->get($this->base.'?location=2&page=2')->assertInertia(fn (Assert $page) => $page->has('records.data', 1)->where('records.data.0.revision', 2));
expect($response->inertiaProps('records.prev_page_url'))->toContain('location=2');
$csv = $this->get($this->base.'.csv?location=2&page=2')->assertOk()->streamedContent();
$rows = array_map(fn ($line) => str_getcsv($line, ';', '"', ''), explode("\n", trim($csv)));
expect($rows)->toHaveCount(29)->and($rows[2])->toHaveCount(17)->and($rows[3][4])->toBe('Território 1')->and($rows[28][4])->toBe('Território 26');
});
test('supplier territory report does not infer location from address or obsolete and foreign country references', function () {
$this->withoutVite();
config(['geography.enabled' => true]);
$supplier = Supplier::query()->findOrFail($this->evaluation->supplier_id);
$supplier->update(['address' => 'Niassa / CIDADE DE LICHINGA / Urbana nº 1']);
reportMeasure($this->evaluation, $this->f['issuer']);
foreach ([null, [...supplierReportReference(), 'catalogue' => str_repeat('0', 64)], [...supplierReportReference(), 'country' => 'ZA']] as $reference) {
$supplier->update(['geography_reference' => $reference]);
$this->get($this->base.'?location=2')->assertInertia(fn (Assert $page) => $page->where('records.total', 0));
$this->get('/fornecedores?location=2')->assertInertia(fn (Assert $page) => $page->has('suppliers.data', 0));
}
$this->get($this->base)->assertInertia(fn (Assert $page) => $page->where('records.total', 1));
});
test('supplier territory report combines native filters and retains department and tenant boundaries', function () {
$this->withoutVite();
config(['geography.enabled' => true]);
Supplier::query()->findOrFail($this->evaluation->supplier_id)->update(['geography_reference' => supplierReportReference()]);
reportMeasure($this->evaluation, $this->f['issuer'], ['title' => 'Visível territorial', 'status' => 'returned']);
$department = Department::query()->create(['tenant_id' => $this->f['tenant']->id, 'name' => 'Outra área']);
reportMeasure(reportEvaluation($this->f, $department->id), $this->f['issuer'], ['title' => 'OCULTA-AREA']);
$foreign = receiptFixture();
Supplier::query()->findOrFail($foreign['order']->supplier_id)->update(['geography_reference' => supplierReportReference()]);
reportMeasure(reportEvaluation($foreign), $foreign['issuer'], ['title' => 'OCULTA-EMPRESA']);
$head = User::factory()->create(['tenant_id' => $this->f['tenant']->id, 'department_id' => $this->f['department']->id, 'purchasing_role' => PurchasingRole::DepartmentHead]);
$this->actingAs($head);
$query = '?location=2&status=returned&supplier_id='.$this->evaluation->supplier_id.'&responsible_id='.$this->f['issuer']->id.'&search=territorial&overdue=1';
$this->get($this->base.$query)->assertInertia(fn (Assert $page) => $page->where('records.total', 1)->where('counts.returned', 1));
$csv = $this->get($this->base.'.csv'.$query)->assertOk()->streamedContent();
expect($csv)->toContain('Visível territorial')->not->toContain('OCULTA-AREA', 'OCULTA-EMPRESA');
});
test('supplier territory report validates territory before export and preserves legacy reports when disabled', function () {
$this->withoutVite();
config(['geography.enabled' => true]);
reportMeasure($this->evaluation, $this->f['issuer']);
$this->getJson($this->base.'?location=999999')->assertUnprocessable();
$this->getJson($this->base.'.csv?location=999999')->assertUnprocessable();
$this->getJson($this->base.'?location[]=2')->assertUnprocessable();
config(['geography.enabled' => false]);
$this->getJson($this->base.'?location=2')->assertServiceUnavailable();
$this->getJson($this->base.'.csv?location=2')->assertServiceUnavailable();
$this->get($this->base)->assertInertia(fn (Assert $page) => $page->where('geographyEnabled', false)->where('records.total', 1));
});
test('supplier territory report revalidates a revoked role before reading geographic results', function () {
config(['geography.enabled' => true]);
$this->f['issuer']->forceFill(['purchasing_role' => PurchasingRole::Requester])->save();
$this->getJson($this->base.'?location=2')->assertForbidden();
$this->getJson($this->base.'.csv?location=2')->assertForbidden();
});
test('report paginates all authorized revisions with counts over the entire filtered set', function () {
$next = reportEvaluation($this->f, null, 2, $this->evaluation->id);
foreach (range(1, 27) as $i) {
reportMeasure($i === 27 ? $next : $this->evaluation, $this->f['issuer'], ['title' => 'Medida '.$i, 'status' => $i === 1 ? 'closed' : 'open']);
}
$this->get($this->base)->assertInertia(fn (Assert $p) => $p->component('reports/supplier-improvements')->where('records.total', 27)->has('records.data', 25)->where('counts.open', 26)->where('counts.closed', 1)->where('overdue', 26)->has('suppliers', 1)->has('responsibles', 1)->missing('records.data.0.operation_key')->missing('records.data.0.creation_hash'));
$this->get($this->base.'?page=2')->assertInertia(fn (Assert $p) => $p->has('records.data', 2)->where('records.data.1.revision', 2)->where('records.total', 27));
});
test('deadline boundaries exclude closed and today from overdue and include the full end date', function () {
reportMeasure($this->evaluation, $this->f['issuer'], ['title' => 'Prazo ontem']);
reportMeasure($this->evaluation, $this->f['issuer'], ['title' => 'Prazo hoje', 'due_on' => '2026-10-30', 'status' => 'review']);
reportMeasure($this->evaluation, $this->f['issuer'], ['title' => 'Encerrada', 'due_on' => '2026-10-01', 'status' => 'closed', 'reviewed_at' => '2026-10-20 10:30:00']);
$this->get($this->base.'?overdue=1')->assertInertia(fn (Assert $p) => $p->where('records.total', 1)->where('overdue', 1)->where('records.data.0.title', 'Prazo ontem')->where('records.data.0.overdue', true));
$this->get($this->base.'?from=2026-10-30&to=2026-10-30')->assertInertia(fn (Assert $p) => $p->where('records.total', 1)->where('records.data.0.title', 'Prazo hoje')->where('records.data.0.overdue', false));
$this->get($this->base.'?status=closed&overdue=1')->assertInertia(fn (Assert $p) => $p->where('records.total', 0)->where('overdue', 0));
expect($this->get($this->base.'.csv?status=closed')->assertOk()->streamedContent())->toContain('2026-10-20T10:30:00+00:00');
});
test('supplier assignee state and title filters are combined and invalid ranges refused', function () {
reportMeasure($this->evaluation, $this->f['issuer'], ['title' => 'Referência ESPECIAL', 'status' => 'returned']);
$url = $this->base.'?supplier_id='.$this->evaluation->supplier_id.'&responsible_id='.$this->f['issuer']->id.'&status=returned&search='.urlencode('referência');
$this->get($url)->assertInertia(fn (Assert $p) => $p->where('records.total', 1)->where('counts.returned', 1));
$this->get($url.'&from=2026-11-01')->assertInertia(fn (Assert $p) => $p->where('records.total', 0));
$this->getJson($this->base.'?from=2026-11-01&to=2026-10-01')->assertUnprocessable()->assertJsonValidationErrors('to');
$this->getJson($this->base.'?status=forged')->assertUnprocessable();
$this->getJson($this->base.'.csv?overdue=0')->assertUnprocessable();
});
test('department and company restrictions apply to records counts choices and CSV', function () {
reportMeasure($this->evaluation, $this->f['issuer'], ['title' => 'Visível QA']);
$department = Department::query()->create(['tenant_id' => $this->f['tenant']->id, 'name' => 'Outro departamento']);
$otherEvaluation = reportEvaluation($this->f, $department->id);
$otherResponsible = User::factory()->create(['tenant_id' => $this->f['tenant']->id, 'department_id' => $department->id, 'purchasing_role' => PurchasingRole::Finance, 'name' => 'Responsável não exposto']);
reportMeasure($otherEvaluation, $otherResponsible, ['title' => 'Oculta pelo departamento']);
$foreign = receiptFixture();
reportMeasure(reportEvaluation($foreign), $foreign['issuer'], ['title' => 'Oculta pela empresa']);
$head = User::factory()->create(['tenant_id' => $this->f['tenant']->id, 'department_id' => $this->f['r']->department_id, 'purchasing_role' => PurchasingRole::DepartmentHead]);
$this->actingAs($head)->get($this->base)->assertInertia(fn (Assert $p) => $p->where('records.total', 1)->has('responsibles', 1)->where('responsibles.0.id', $this->f['issuer']->id));
$csv = $this->get($this->base.'.csv')->assertOk()->streamedContent();
expect($csv)->toContain('Visível QA')->not->toContain('Oculta', 'Responsável não exposto');
$this->get($this->base.'?responsible_id='.$otherResponsible->id)->assertInertia(fn (Assert $p) => $p->where('records.total', 0));
});
test('guest forbidden roles and a stale actor role cannot access either representation', function () {
foreach ([$this->base, $this->base.'.csv'] as $url) {
$this->actingAs($this->f['receiver'])->get($url)->assertForbidden();
}
$this->actingAs($this->f['issuer']);
DB::table('users')->where('id', $this->f['issuer']->id)->update(['purchasing_role' => 'requester']);
$this->get($this->base)->assertForbidden();
$this->get($this->base.'.csv')->assertForbidden();
auth()->logout();
$this->get($this->base)->assertRedirect('/login');
});
test('CSV exports beyond the current page in batches and protects formula-like text without writes', function () {
foreach (range(1, 207) as $i) {
reportMeasure($this->evaluation, $this->f['issuer'], ['title' => $i === 1 ? '=HYPERLINK("bad")' : 'Linha '.$i, 'result' => $i === 207 ? '+resultado final' : null, 'evidence' => $i === 207 ? '@referencia' : null]);
}
$supplier = Supplier::query()->findOrFail($this->evaluation->supplier_id)->getAttributes();
$evaluation = $this->evaluation->refresh()->getAttributes();
$audits = AuditLog::query()->count();
$response = $this->get($this->base.'.csv?page=2&status=open')->assertOk();
$csv = $response->streamedContent();
$rows = array_map(fn ($line) => str_getcsv($line, ';', '"', ''), explode("\n", trim($csv)));
expect($rows)->toHaveCount(210)->and($rows[3][4])->toBe('\'=HYPERLINK("bad")')->and($rows[209][4])->toBe('Linha 207')->and($rows[209][11])->toBe('\'+resultado final')->and($rows[209][12])->toBe('\'@referencia');
expect($rows[209][16])->toEndWith('/fornecedores/avaliacoes/'.$this->evaluation->id.'/relatorio#medida-207');
expect(Supplier::query()->findOrFail($this->evaluation->supplier_id)->getAttributes())->toBe($supplier)->and($this->evaluation->refresh()->getAttributes())->toBe($evaluation)->and(AuditLog::query()->count())->toBe($audits);
});