85 lines
5.9 KiB
PHP
85 lines
5.9 KiB
PHP
<?php
|
|
|
|
use App\Enums\PurchasingRole;
|
|
use App\Models\BudgetConsumption;
|
|
use App\Models\Invoice;
|
|
use App\Models\User;
|
|
use App\Services\ConflictGuard;
|
|
use App\Services\InvoiceWorkflow;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Inertia\Testing\AssertableInertia as Assert;
|
|
|
|
require_once __DIR__.'/../Support/InvoiceFixture.php';
|
|
require_once __DIR__.'/../Support/SupplyFixture.php';
|
|
|
|
beforeEach(function () {
|
|
$this->f = invoiceFixture();
|
|
$this->actingAs($this->f['author']);
|
|
});
|
|
|
|
test('compact invoice registration reuses order facts and preserves independent totals and validation', function () {
|
|
$data = [...$this->f['data'], 'number' => 'FT-COMPACT-2', 'subtotal' => '40.00', 'total' => '40.00',
|
|
'items' => [['purchase_order_item_id' => $this->f['line']->id, 'quantity' => '0.400']]];
|
|
$this->postJson('/ordens/'.$this->f['order']->id.'/facturas', $data)->assertRedirect();
|
|
$invoice = Invoice::query()->where('number', 'FT-COMPACT-2')->sole();
|
|
expect($invoice->status)->toBe('pending')->and($invoice->items()->sole()->snapshot['unit'])->toBe('cx')
|
|
->and((int) $invoice->items()->sole()->snapshot['unit_price_minor'])->toBe(10000)->and(BudgetConsumption::query()->count())->toBe(0);
|
|
$this->actingAs($this->f['validator']);
|
|
app(InvoiceWorkflow::class)->validate($this->f['validator'], $this->f['invoice']);
|
|
app(InvoiceWorkflow::class)->validate($this->f['validator'], $invoice);
|
|
expect((int) BudgetConsumption::query()->sum('amount_minor'))->toBe(10000);
|
|
});
|
|
|
|
test('compact registration still refuses forged supplied facts quantity and documentary totals', function () {
|
|
$base = [...$this->f['data'], 'number' => 'FORGED', 'items' => [['purchase_order_item_id' => $this->f['line']->id, 'quantity' => '0.600']]];
|
|
foreach ([['unit' => 'kg'], ['unit_price' => '99.00'], ['tax_rate' => '16'], ['quantity' => '2.000']] as $extra) {
|
|
$this->postJson('/ordens/'.$this->f['order']->id.'/facturas', [...$base, 'items' => [[...$base['items'][0], ...$extra]]])->assertUnprocessable();
|
|
}
|
|
$this->postJson('/ordens/'.$this->f['order']->id.'/facturas', [...$base, 'total' => '59.99'])->assertUnprocessable();
|
|
expect(Invoice::query()->count())->toBe(1)->and(BudgetConsumption::query()->count())->toBe(0);
|
|
});
|
|
|
|
test('finance task belongs to an independent finance user and reaches the exact invoice', function () {
|
|
$invoice = $this->f['invoice'];
|
|
$this->get('/requisicoes/tarefas?module=finance')->assertInertia(fn (Assert $page) => $page->where('tasks.total', 0));
|
|
$this->actingAs($this->f['validator'])->get('/requisicoes/tarefas?module=finance')->assertInertia(fn (Assert $page) => $page->where('tasks.total', 1)
|
|
->where('tasks.data.0.href', '/facturas?invoice_id='.$invoice->id.'#invoice-'.$invoice->id));
|
|
$this->get('/facturas?invoice_id='.$invoice->id)->assertHeader('Cache-Control', 'max-age=0, no-store, private')->assertInertia(fn (Assert $page) => $page->where('focusInvoiceId', $invoice->id)->where('invoices.total', 1)->where('invoices.data.0.canValidate', true));
|
|
$this->post('/facturas/'.$invoice->id.'/validar')->assertRedirect();
|
|
$this->get('/requisicoes/tarefas?module=finance')->assertInertia(fn (Assert $page) => $page->where('tasks.total', 0));
|
|
$this->actingAs($this->f['author'])->get('/facturas?purchase_order_id='.$this->f['order']->id)->assertInertia(fn (Assert $page) => $page->where('focusOrderId', $this->f['order']->id));
|
|
});
|
|
|
|
test('finance tasks respect declared conflicts current permissions and maintenance without changing facts', function () {
|
|
$actor = $this->f['validator'];
|
|
$this->actingAs($actor);
|
|
$before = [DB::table('invoices')->get()->toArray(), DB::table('budget_consumptions')->count(), DB::table('audit_logs')->count()];
|
|
$this->get('/requisicoes/tarefas?module=finance')->assertOk();
|
|
expect([DB::table('invoices')->get()->toArray(), DB::table('budget_consumptions')->count(), DB::table('audit_logs')->count()])->toEqual($before);
|
|
$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('/facturas')->assertInertia(fn (Assert $page) => $page->where('invoices.data.0.canValidate', false));
|
|
});
|
|
|
|
test('non finance users cannot inherit invoice validation tasks or cross company invoice focus', function () {
|
|
$admin = User::factory()->create(['tenant_id' => $this->f['tenant']->id, 'purchasing_role' => PurchasingRole::Admin]);
|
|
$this->actingAs($admin)->get('/requisicoes/tarefas?module=finance')->assertInertia(fn (Assert $page) => $page->where('tasks.total', 0));
|
|
$this->get('/facturas?invoice_id='.$this->f['invoice']->id)->assertInertia(fn (Assert $page) => $page->where('invoices.data.0.canValidate', false));
|
|
$foreign = receiptFixture();
|
|
$foreignFinance = User::factory()->create(['tenant_id' => $foreign['tenant']->id, 'purchasing_role' => PurchasingRole::Finance]);
|
|
$this->actingAs($foreignFinance)->getJson('/facturas?invoice_id='.$this->f['invoice']->id)->assertUnprocessable();
|
|
});
|
|
|
|
test('maintenance finance can consult but never inherits invoice execution', 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('/facturas')->assertInertia(fn (Assert $page) => $page->where('canManage', false)->where('invoices.data.0.canValidate', false));
|
|
$this->post('/facturas/'.$this->f['invoice']->id.'/validar')->assertForbidden();
|
|
expect($this->f['invoice']->fresh()->status)->toBe('pending')->and(BudgetConsumption::query()->count())->toBe(0);
|
|
});
|