commit21
This commit is contained in:
@@ -3,21 +3,43 @@
|
||||
use App\Enums\PurchasingRole;
|
||||
use App\Models\AuditLog;
|
||||
use App\Models\Department;
|
||||
use App\Models\Quotation;
|
||||
use App\Models\Requisition;
|
||||
use App\Models\User;
|
||||
use App\Services\ExternalPaymentWorkflow;
|
||||
use Carbon\CarbonImmutable;
|
||||
use Illuminate\Routing\Exceptions\StreamedResponseException;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Inertia\Testing\AssertableInertia as Assert;
|
||||
use Symfony\Component\HttpKernel\Exception\HttpException;
|
||||
|
||||
require_once __DIR__.'/../Support/ReceiptFixture.php';
|
||||
require_once __DIR__.'/../Support/PaymentFixture.php';
|
||||
|
||||
beforeEach(function () {
|
||||
$this->f = receiptFixture();
|
||||
if (DB::getDriverName() === 'pgsql') {
|
||||
// Reports deliberately require their own read snapshot, outside the test wrapper.
|
||||
// RefreshDatabase detects the completed wrapper and remigrates the disposable DB.
|
||||
if (! preg_match('/^compras_receipts_test_[a-f0-9]{12}$/D', DB::connection()->getDatabaseName()) || DB::transactionLevel() !== 1) {
|
||||
throw new LogicException('Relatórios PostgreSQL apenas na base descartável e transacção de teste esperadas.');
|
||||
}
|
||||
DB::commit();
|
||||
}
|
||||
$this->actingAs($this->f['issuer']);
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
if (DB::getDriverName() === 'pgsql') {
|
||||
if (! app()->environment('testing') || ! preg_match('/^compras_receipts_test_[a-f0-9]{12}$/D', DB::connection()->getDatabaseName()) || DB::transactionLevel() !== 0) {
|
||||
throw new LogicException('Limpeza apenas do schema da base descartável fora de transacções.');
|
||||
}
|
||||
// Committed fixtures require schema cleanup, including functions migrate:fresh leaves behind.
|
||||
DB::statement('DROP SCHEMA public CASCADE');
|
||||
DB::statement('CREATE SCHEMA public');
|
||||
}
|
||||
});
|
||||
|
||||
function reportCopy(Requisition $original, string $number, array $attributes = []): Requisition
|
||||
{
|
||||
$copy = $original->replicate();
|
||||
@@ -26,6 +48,110 @@ function reportCopy(Requisition $original, string $number, array $attributes = [
|
||||
return $copy;
|
||||
}
|
||||
|
||||
test('needed attention report filters all pages and CSV by native status and needed date without writes', function () {
|
||||
$this->freezeTime();
|
||||
foreach (range(1, 27) as $index) {
|
||||
reportCopy($this->f['r'], 'ATTENTION-'.$index, ['status' => $index === 1 ? 'draft' : 'pending', 'needed_by' => today()->subDay()->toDateString()]);
|
||||
}
|
||||
foreach (['approved', 'rejected', 'cancelled'] as $status) {
|
||||
reportCopy($this->f['r'], 'CLOSED-'.$status, ['status' => $status, 'needed_by' => today()->subDay()->toDateString()]);
|
||||
}
|
||||
reportCopy($this->f['r'], 'NEEDED-TODAY', ['status' => 'pending', 'needed_by' => today()->toDateString()]);
|
||||
reportCopy($this->f['r'], 'NEEDED-FUTURE', ['status' => 'draft', 'needed_by' => today()->addDay()->toDateString()]);
|
||||
$before = [];
|
||||
foreach (['requisitions', 'approval_steps', 'budget_entries', 'stock_movements', 'audit_logs'] as $table) {
|
||||
$before[$table] = DB::table($table)->orderBy('id')->get()->toArray();
|
||||
}
|
||||
$this->get('/relatorios?attention=unapproved_needed')->assertInertia(fn (Assert $p) => $p->where('records.total', 27)->has('records.data', 25)
|
||||
->where('insights.unapproved_past_needed_date', 27)->where('counts.pending', 26)->where('counts.draft', 1)
|
||||
->where('records.data.0.needed_by', today()->subDay()->toDateString())
|
||||
->where('records.next_page_url', fn ($url) => str_contains($url, 'attention=unapproved_needed')));
|
||||
$this->get('/relatorios?attention=unapproved_needed&page=2')->assertInertia(fn (Assert $p) => $p->has('records.data', 2));
|
||||
$csv = $this->get('/relatorios/compras.csv?attention=unapproved_needed&page=2')->assertOk()->streamedContent();
|
||||
expect($csv)->toContain('ATTENTION-1')->toContain('ATTENTION-27')->toContain('Data de necessidade')->not->toContain('CLOSED-')->not->toContain('NEEDED-TODAY')->not->toContain('NEEDED-FUTURE');
|
||||
$this->get('/relatorios?attention=unapproved_needed&status=approved')->assertInertia(fn (Assert $p) => $p->where('records.total', 0));
|
||||
$this->getJson('/relatorios?attention=unknown')->assertUnprocessable();
|
||||
$this->getJson('/relatorios/compras.csv?attention=unknown')->assertUnprocessable();
|
||||
foreach ($before as $table => $rows) {
|
||||
expect(DB::table($table)->orderBy('id')->get()->toArray())->toEqual($rows);
|
||||
}
|
||||
$this->travel(1)->days();
|
||||
$this->get('/relatorios?attention=unapproved_needed')->assertInertia(fn (Assert $p) => $p->where('records.total', 28));
|
||||
});
|
||||
|
||||
test('needed attention report rechecks stream access and preserves department boundaries', function () {
|
||||
$past = reportCopy($this->f['r'], 'OWN-ATTENTION', ['status' => 'pending', 'needed_by' => today()->subDay()->toDateString()]);
|
||||
$head = User::factory()->create(['tenant_id' => $past->tenant_id, 'department_id' => $past->department_id, 'purchasing_role' => PurchasingRole::DepartmentHead]);
|
||||
$otherDepartment = Department::query()->create(['tenant_id' => $past->tenant_id, 'code' => 'ATT-OTHER', 'name' => 'Outro departamento']);
|
||||
reportCopy($past, 'PRIVATE-OTHER-DEPARTMENT', ['department_id' => $otherDepartment->id]);
|
||||
$this->actingAs($head)->get('/relatorios?attention=unapproved_needed')->assertInertia(fn (Assert $p) => $p->where('records.total', 1));
|
||||
expect($this->get('/relatorios/compras.csv?attention=unapproved_needed')->assertOk()->streamedContent())->not->toContain('PRIVATE-OTHER-DEPARTMENT');
|
||||
$response = $this->get('/relatorios/compras.csv?attention=unapproved_needed')->assertOk();
|
||||
$head->forceFill(['purchasing_role' => PurchasingRole::Requester])->save();
|
||||
$bufferLevel = ob_get_level();
|
||||
try {
|
||||
$response->streamedContent();
|
||||
test()->fail('Revoked report access must fail before streaming');
|
||||
} catch (StreamedResponseException $error) {
|
||||
expect($error->getInnerException())->toBeInstanceOf(HttpException::class);
|
||||
expect($error->getInnerException()->getStatusCode())->toBe(403);
|
||||
} finally {
|
||||
while (ob_get_level() > $bufferLevel) {
|
||||
ob_end_clean();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
test('approved attention reports reuse registered quote and visible order facts across every page and CSV', function () {
|
||||
foreach (range(1, 27) as $index) {
|
||||
reportCopy($this->f['r'], 'EMPTY-APPROVED-'.$index);
|
||||
}
|
||||
$withQuote = reportCopy($this->f['r'], 'REGISTERED-NO-ORDER');
|
||||
$quote = Quotation::query()->where('requisition_id', $this->f['r']->id)->firstOrFail()->replicate();
|
||||
$quote->requisition_id = $withQuote->id;
|
||||
$quote->reference = 'REGISTERED-EXPIRED';
|
||||
$quote->valid_until = today()->subDay();
|
||||
$quote->save();
|
||||
reportCopy($this->f['r'], 'PENDING-NO-QUOTE', ['status' => 'pending']);
|
||||
$before = [];
|
||||
foreach (['requisitions', 'quotations', 'purchase_orders', 'budget_entries', 'stock_movements', 'audit_logs'] as $table) {
|
||||
$before[$table] = DB::table($table)->orderBy('id')->get()->toArray();
|
||||
}
|
||||
$this->get('/relatorios')->assertInertia(fn (Assert $p) => $p->where('insights.approved.without_registered_quotes', 27)->where('insights.approved.without_visible_order', 28));
|
||||
foreach (['approved_without_quotes' => 27, 'approved_without_order' => 28] as $attention => $total) {
|
||||
$base = '/relatorios?attention='.$attention;
|
||||
$this->get($base)->assertInertia(fn (Assert $p) => $p->where('records.total', $total)->has('records.data', 25)->where('counts.approved', $total)
|
||||
->where('records.next_page_url', fn ($url) => str_contains($url, 'attention='.$attention)));
|
||||
$this->get($base.'&page=2')->assertInertia(fn (Assert $p) => $p->has('records.data', $total - 25));
|
||||
$csv = $this->get('/relatorios/compras.csv?attention='.$attention.'&page=2')->assertOk()->streamedContent();
|
||||
expect($csv)->toContain('EMPTY-APPROVED-1')->toContain('EMPTY-APPROVED-27')->not->toContain('PENDING-NO-QUOTE');
|
||||
if ($attention === 'approved_without_quotes') {
|
||||
expect($csv)->not->toContain('REGISTERED-NO-ORDER');
|
||||
} else {
|
||||
expect($csv)->toContain('REGISTERED-NO-ORDER');
|
||||
}
|
||||
$this->get($base.'&status=pending')->assertInertia(fn (Assert $p) => $p->where('records.total', 0));
|
||||
}
|
||||
foreach ($before as $table => $rows) {
|
||||
expect(DB::table($table)->orderBy('id')->get()->toArray())->toEqual($rows);
|
||||
}
|
||||
});
|
||||
|
||||
test('approved attention reports keep department and company scopes while finding registered facts', function () {
|
||||
$own = reportCopy($this->f['r'], 'OWN-EMPTY-APPROVED');
|
||||
$department = Department::query()->create(['tenant_id' => $own->tenant_id, 'name' => 'Departamento reservado']);
|
||||
reportCopy($own, 'PRIVATE-OTHER-DEPT', ['department_id' => $department->id]);
|
||||
$head = User::factory()->create(['tenant_id' => $own->tenant_id, 'department_id' => $own->department_id, 'purchasing_role' => PurchasingRole::DepartmentHead]);
|
||||
$foreign = receiptFixture();
|
||||
reportCopy($foreign['r'], 'PRIVATE-OTHER-COMPANY');
|
||||
$this->actingAs($head);
|
||||
foreach (['approved_without_quotes', 'approved_without_order'] as $attention) {
|
||||
$this->get('/relatorios?attention='.$attention)->assertInertia(fn (Assert $p) => $p->where('records.total', 1)->where('records.data.0.id', $own->id));
|
||||
$csv = $this->get('/relatorios/compras.csv?attention='.$attention)->assertOk()->streamedContent();
|
||||
expect($csv)->toContain('OWN-EMPTY-APPROVED')->not->toContain('PRIVATE-OTHER-DEPT')->not->toContain('PRIVATE-OTHER-COMPANY');
|
||||
}
|
||||
});
|
||||
|
||||
test('report entry connects visible requests to the existing process and report without business writes', function () {
|
||||
$audits = AuditLog::query()->count();
|
||||
$this->get('/relatorios')->assertOk()->assertHeader('Cache-Control', 'max-age=0, no-store, private')->assertInertia(fn (Assert $page) => $page
|
||||
|
||||
Reference in New Issue
Block a user