Files
compras/tests/Feature/PaymentIntegrationTest.php
2026-11-02 20:27:11 +02:00

88 lines
6.6 KiB
PHP

<?php
use App\Enums\PurchasingRole;
use App\Models\BudgetConsumption;
use App\Models\User;
use App\Services\ConflictGuard;
use App\Services\ExternalPaymentWorkflow;
use App\Services\SupplierBankWorkflow;
use Illuminate\Support\Facades\DB;
use Inertia\Testing\AssertableInertia as Assert;
require_once __DIR__.'/../Support/PaymentFixture.php';
require_once __DIR__.'/../Support/SupplyFixture.php';
beforeEach(function () {
$this->f = paymentFixture();
$this->actingAs($this->f['author']);
$this->payment = app(ExternalPaymentWorkflow::class)->register($this->f['author'], $this->f['invoice'], $this->f['payment_data']);
});
test('payment task routes the independent reviewer to the exact record without exposing evidence in the task', function () {
$this->get('/requisicoes/tarefas?module=finance')->assertInertia(fn (Assert $page) => $page->where('tasks.total', 0));
$this->actingAs($this->f['validator']);
$before = [DB::table('external_payments')->get()->toArray(), DB::table('audit_logs')->count(), BudgetConsumption::query()->sum('amount_minor')];
$this->get('/requisicoes/tarefas?module=finance')->assertInertia(fn (Assert $page) => $page->where('tasks.total', 1)
->where('tasks.data.0.href', '/pagamentos?payment_id='.$this->payment->id.'#payment-'.$this->payment->id)
->missing('tasks.data.0.external_reference')->missing('tasks.data.0.evidence_reference')->missing('tasks.data.0.bank_snapshot'));
expect([DB::table('external_payments')->get()->toArray(), DB::table('audit_logs')->count(), BudgetConsumption::query()->sum('amount_minor')])->toEqual($before);
$this->get('/pagamentos?payment_id='.$this->payment->id)->assertHeader('Cache-Control', 'max-age=0, no-store, private')
->assertInertia(fn (Assert $page) => $page->where('focusPaymentId', $this->payment->id)->where('payments.total', 1)->where('payments.data.0.canReview', true));
$this->post('/pagamentos/'.$this->payment->id.'/conferir', ['decision' => 'confirmed', 'reason' => 'Conferência independente sintética', 'independently_verified' => true])->assertRedirect();
$this->get('/requisicoes/tarefas?module=finance')->assertInertia(fn (Assert $page) => $page->where('tasks.total', 0));
expect(app(ExternalPaymentWorkflow::class)->outstanding($this->f['invoice']))->toBe(2000)
->and(BudgetConsumption::query()->sum('amount_minor'))->toEqual($before[2]);
});
test('payment focus stays scoped to its invoice and process documents link to its exact record', function () {
$this->get('/pagamentos?invoice_id='.$this->f['invoice']->id)->assertInertia(fn (Assert $page) => $page->where('focusInvoiceId', $this->f['invoice']->id)->has('invoices', 1)->where('payments.total', 1));
$this->get('/requisicoes/'.$this->f['r']->id.'/processo')->assertInertia(fn (Assert $page) => $page
->where('stages.7.href', '/facturas?purchase_order_id='.$this->f['order']->id)
->where('documents', fn ($documents) => collect($documents)->contains('href', '/pagamentos?payment_id='.$this->payment->id.'#payment-'.$this->payment->id)));
});
test('payment task and review form respect conflicts and revoked finance authority', function () {
$this->actingAs($this->f['validator']);
$guard = Mockery::mock(ConflictGuard::class)->makePartial();
$guard->shouldReceive('processBlocked')->andReturn(true);
app()->instance(ConflictGuard::class, $guard);
$this->get('/requisicoes/tarefas?module=finance')->assertInertia(fn (Assert $page) => $page->where('tasks.total', 0));
$this->get('/pagamentos')->assertInertia(fn (Assert $page) => $page->where('payments.data.0.canReview', false));
app()->forgetInstance(ConflictGuard::class);
$this->f['validator']->forceFill(['purchasing_role' => PurchasingRole::Admin])->save();
$this->actingAs($this->f['validator']->fresh());
$this->get('/pagamentos')->assertInertia(fn (Assert $page) => $page->where('payments.data.0.canReview', false)->where('payments.data.0.external_reference', null));
$this->post('/pagamentos/'.$this->payment->id.'/conferir', ['decision' => 'confirmed', 'reason' => 'Teste', 'independently_verified' => true])->assertForbidden();
});
test('bank revision does not hide a pending rejection but confirmation still rechecks the bank', function () {
$this->actingAs($this->f['issuer']);
app(SupplierBankWorkflow::class)->propose($this->f['issuer'], $this->f['supplier'], [...$this->f['bank_data'], 'previous_id' => $this->f['bank']->id, 'account_identifier' => 'TEST-NEW-VERSION']);
$this->actingAs($this->f['validator']);
$this->get('/requisicoes/tarefas?module=finance')->assertInertia(fn (Assert $page) => $page->where('tasks.total', 1));
$data = ['decision' => 'confirmed', 'reason' => 'Conferência sintética', 'independently_verified' => true];
$this->postJson('/pagamentos/'.$this->payment->id.'/conferir', $data)->assertUnprocessable();
expect($this->payment->fresh()->status)->toBe('pending');
$this->post('/pagamentos/'.$this->payment->id.'/conferir', [...$data, 'decision' => 'rejected'])->assertRedirect();
$this->get('/requisicoes/tarefas?module=finance')->assertInertia(fn (Assert $page) => $page->where('tasks.total', 0));
});
test('payment and invoice focus cannot cross companies', function () {
$other = invoiceFixture();
$foreign = User::factory()->create(['tenant_id' => $other['tenant']->id, 'purchasing_role' => PurchasingRole::Finance]);
$this->actingAs($foreign)->getJson('/pagamentos?payment_id='.$this->payment->id)->assertUnprocessable();
$this->getJson('/pagamentos?invoice_id='.$this->f['invoice']->id)->assertUnprocessable();
$this->get('/requisicoes/tarefas?module=finance')->assertInertia(fn (Assert $page) => $page->where('tasks.total', 1)->where('tasks.data.0.id', 'invoice:'.$other['invoice']->id));
});
test('maintenance finance can consult payment without receiving evidence or decision authority', function () {
$actor = $this->f['validator'];
supplyGrant($actor, ['finance']);
DB::table('organization_memberships')->where('user_id', $actor->id)->update(['maintenance' => true, 'expires_at' => now()->addHour()]);
$this->actingAs($actor);
$this->get('/requisicoes/tarefas?module=finance')->assertInertia(fn (Assert $page) => $page->where('tasks.total', 0));
$this->get('/pagamentos')->assertInertia(fn (Assert $page) => $page->where('canManage', false)->where('payments.data.0.canReview', false)->where('payments.data.0.evidence_reference', null));
$this->post('/pagamentos/'.$this->payment->id.'/conferir', ['decision' => 'rejected', 'reason' => 'Teste', 'independently_verified' => true])->assertForbidden();
expect($this->payment->fresh()->status)->toBe('pending');
});