This commit is contained in:
2026-11-03 11:51:44 +02:00
parent badaada043
commit e753f7a0a4
309 changed files with 14537 additions and 529 deletions
+81
View File
@@ -1,6 +1,8 @@
<?php
use App\Enums\PurchasingRole;
use App\Http\Controllers\SupplierController;
use App\Http\Requests\SaveSupplierRequest;
use App\Models\AuditLog;
use App\Models\Department;
use App\Models\Supplier;
@@ -8,9 +10,14 @@ use App\Models\SupplierDocument;
use App\Models\Tenant;
use App\Models\User;
use App\Services\RequisitionWorkflow;
use App\Services\SupplierParticipation;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Storage;
use Inertia\Testing\AssertableInertia as Assert;
use Symfony\Component\HttpKernel\Exception\HttpException;
require_once __DIR__.'/../Support/SupplyFixture.php';
function maintenanceSupplierData(): array
{
@@ -145,3 +152,77 @@ test('file is cleaned up and database rolled back if audit cannot be written', f
expect(SupplierDocument::query()->count())->toBe(0)->and(Storage::disk('supplier_documents')->allFiles())->toBe([]);
expect($this->supplier->refresh()->status)->toBe('active')->and($this->supplier->version)->toBe(1);
});
test('supplier task connects independent review current forms and versioned edits without writing on reads', function () {
$this->supplier->update(['status' => 'pending']);
supplyGrant($this->buyer, ['procurement', 'finance']);
supplyGrant($this->finance, ['finance']);
$this->get('/requisicoes/tarefas?module=purchases')->assertInertia(fn (Assert $page) => $page->where('tasks.total', 0));
$this->get($this->url)->assertInertia(fn (Assert $page) => $page->where('canReview', false));
$this->actingAs($this->finance)->get('/requisicoes/tarefas?module=purchases')->assertInertia(fn (Assert $page) => $page->where('tasks.total', 1)
->where('tasks.data.0.task', 'supplier_review')->where('tasks.data.0.href', $this->url.'#supplier-review')
->missing('tasks.data.0.email')->missing('tasks.data.0.beneficiaries')->where('tasks.data.0.due_on', null));
$before = [Supplier::query()->count(), AuditLog::query()->count()];
$this->get($this->url)->assertHeader('Cache-Control', 'max-age=0, no-store, private')->assertInertia(fn (Assert $page) => $page->where('canReview', true)->where('supplier.version', 1));
expect([Supplier::query()->count(), AuditLog::query()->count()])->toBe($before);
$this->post($this->url.'/validar', ['version' => 1, 'status' => 'active', 'comment' => 'Conferência independente da ficha sintética.'])->assertRedirect();
$this->get('/requisicoes/tarefas?module=purchases')->assertInertia(fn (Assert $page) => $page->where('tasks.total', 0));
$this->actingAs($this->buyer)->put($this->url, [...maintenanceSupplierData(), 'name' => 'Ficha revista para novo uso', 'version' => 2])->assertRedirect();
$this->actingAs($this->finance)->get('/requisicoes/tarefas?module=purchases')->assertInertia(fn (Assert $page) => $page->where('tasks.total', 1)
->where('tasks.data.0.id', 'supplier:'.$this->supplier->id.':review:3'));
$this->postJson($this->url.'/validar', ['version' => 1, 'status' => 'active', 'comment' => 'Tentativa antiga que não deve sobrescrever a ficha.'])->assertUnprocessable();
});
test('supplier task excludes the last editor and preserves activation checks for blocked or expired sources', function () {
$this->supplier->update(['status' => 'pending', 'last_changed_by' => $this->finance->id]);
$this->actingAs($this->finance)->get('/requisicoes/tarefas?module=purchases')->assertInertia(fn (Assert $page) => $page->where('tasks.total', 0));
$this->post($this->url.'/validar', ['version' => 1, 'status' => 'active', 'comment' => 'Não pode conferir a alteração que efectuou.'])->assertForbidden();
$this->supplier->update(['last_changed_by' => $this->buyer->id, 'blocked' => true]);
$this->get('/requisicoes/tarefas?module=purchases')->assertInertia(fn (Assert $page) => $page->where('tasks.total', 1));
$this->postJson($this->url.'/validar', ['version' => 1, 'status' => 'active', 'comment' => 'O impedimento não pode ser ignorado na activação.'])->assertUnprocessable();
$this->supplier->update(['blocked' => false, 'next_review' => today()->subDay()]);
$this->postJson($this->url.'/validar', ['version' => 1, 'status' => 'active', 'comment' => 'A vigência deve ser renovada na ficha de origem.'])->assertUnprocessable();
$this->post($this->url.'/validar', ['version' => 1, 'status' => 'suspended', 'comment' => 'Suspensão sintética sem forçar a activação vencida.'])->assertRedirect();
$this->get('/requisicoes/tarefas?module=purchases')->assertInertia(fn (Assert $page) => $page->where('tasks.total', 0));
});
test('supplier task retains older pending records across all task pages and direct source links', function () {
$this->supplier->update(['status' => 'pending']);
for ($index = 1; $index < 105; $index++) {
Supplier::query()->create([...maintenanceSupplierData(), 'tenant_id' => $this->tenant->id, 'created_by' => $this->buyer->id, 'last_changed_by' => $this->buyer->id,
'name' => 'Ficha sintética '.$index, 'nuit' => str_pad((string) $index, 9, '0', STR_PAD_LEFT), 'status' => 'pending']);
}
$this->actingAs($this->finance)->get('/requisicoes/tarefas?module=purchases')->assertInertia(fn (Assert $page) => $page->where('tasks.total', 105)->has('tasks.data', 25)
->where('tasks.data.0.href', $this->url.'#supplier-review'));
$this->get('/requisicoes/tarefas?module=purchases&page=5')->assertInertia(fn (Assert $page) => $page->where('tasks.total', 105)->has('tasks.data', 5));
$this->get($this->url)->assertInertia(fn (Assert $page) => $page->where('canReview', true));
});
test('supplier task preserves tenant maintenance expiry and fresh actor authority on native commands', function () {
$this->supplier->update(['status' => 'pending']);
$foreignTenant = Tenant::query()->create(['name' => 'Outra empresa do ensaio']);
$foreign = User::factory()->create(['tenant_id' => $foreignTenant->id, 'purchasing_role' => PurchasingRole::Finance]);
$this->actingAs($foreign)->get('/requisicoes/tarefas?module=purchases')->assertInertia(fn (Assert $page) => $page->where('tasks.total', 0));
$this->get($this->url)->assertNotFound();
supplyGrant($this->finance, ['finance']);
supplyGrant($this->buyer, ['procurement']);
DB::table('organization_memberships')->where('user_id', $this->finance->id)->update(['maintenance' => true, 'expires_at' => now()->addHour()]);
$this->actingAs($this->finance)->get('/requisicoes/tarefas?module=purchases')->assertInertia(fn (Assert $page) => $page->where('tasks.total', 0));
$this->get($this->url)->assertInertia(fn (Assert $page) => $page->where('canReview', false));
$this->post($this->url.'/validar', ['version' => 1, 'status' => 'active', 'comment' => 'Manutenção não deve decidir no percurso de negócio.'])->assertForbidden();
DB::table('organization_memberships')->where('user_id', $this->buyer->id)->update(['maintenance' => true, 'expires_at' => now()->addHour()]);
$this->actingAs($this->buyer)->put($this->url, [...maintenanceSupplierData(), 'version' => 1])->assertForbidden();
DB::table('organization_memberships')->where('user_id', $this->finance->id)->update(['expires_at' => now()->subMinute()]);
$this->actingAs($this->finance)->get('/requisicoes/tarefas?module=purchases')->assertForbidden();
expect(app(SupplierParticipation::class)->canReview($this->finance, $this->supplier))->toBeFalse();
});
test('supplier task preparation cannot borrow a cached role revoked after form validation', function () {
$input = SaveSupplierRequest::create('/fornecedores', 'POST', [...maintenanceSupplierData(), 'nuit' => '987654321']);
$input->setContainer($this->app)->setRedirector($this->app['redirect']);
$input->setUserResolver(fn () => $this->buyer);
$input->validateResolved();
DB::table('users')->where('id', $this->buyer->id)->update(['purchasing_role' => PurchasingRole::Requester->value]);
$before = [Supplier::query()->count(), AuditLog::query()->count()];
expect(fn () => app(SupplierController::class)->store($input, app(RequisitionWorkflow::class)))
->toThrow(HttpException::class);
expect([Supplier::query()->count(), AuditLog::query()->count()])->toBe($before);
});