167 lines
14 KiB
PHP
167 lines
14 KiB
PHP
<?php
|
|
|
|
use App\Models\StockItem;
|
|
use App\Models\SupplyRecord;
|
|
use App\Models\Tenant;
|
|
use App\Models\User;
|
|
use App\Services\SupplyHistory;
|
|
use App\Services\SupplyPlanning;
|
|
use App\Services\SupplySuggestions;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Str;
|
|
use Inertia\Testing\AssertableInertia as Assert;
|
|
|
|
require_once __DIR__.'/../Support/SuggestionFixture.php';
|
|
|
|
beforeEach(function () {
|
|
$this->f = suggestionFixture();
|
|
supplyGrant($this->f['issuer'], ['supply_read', 'supply_history_prepare', 'import_prepare', 'import_read']);
|
|
$this->actingAs($this->f['issuer']);
|
|
});
|
|
|
|
test('agreement source search finds old approved sources and preserves selection without reviving suspension', function () {
|
|
$old = $this->f['agreement']->replicate();
|
|
$old->record_key = (string) Str::uuid();
|
|
$old->operation_key = (string) Str::uuid();
|
|
$old->created_at = now();
|
|
$old->data = [...$old->data, 'reference' => 'ACORDO-ANTIGO-PESQUISA'];
|
|
$old->save();
|
|
for ($i = 0; $i < 100; $i++) {
|
|
$copy = $old->replicate();
|
|
$copy->record_key = (string) Str::uuid();
|
|
$copy->operation_key = (string) Str::uuid();
|
|
$copy->data = [...$copy->data, 'reference' => 'RECENTE-'.$i];
|
|
$copy->created_at = now();
|
|
$copy->save();
|
|
}
|
|
$base = '/compras/fornecimento?view=calendar&agreement_stock_item_id='.$this->f['stock']->id.'&agreement_warehouse_id='.$this->f['warehouse']->id;
|
|
$before = [SupplyRecord::query()->count(), DB::table('audit_logs')->count(), DB::table('requisitions')->count(), DB::table('stock_movements')->count()];
|
|
$this->get('/compras/fornecimento?view=calendar')->assertInertia(fn (Assert $p) => $p->where('agreements', fn ($rows) => collect($rows)->where('record_key', $old->record_key)->isEmpty()));
|
|
$this->get($base.'&agreement_search=acordo-antigo')->assertInertia(fn (Assert $p) => $p
|
|
->where('agreements', fn ($rows) => collect($rows)->where('record_key', $old->record_key)->count() === 1));
|
|
$this->get($base.'&agreement_search=NO-MATCH&selected_agreement_key='.$old->record_key)->assertInertia(fn (Assert $p) => $p
|
|
->where('agreements', fn ($rows) => collect($rows)->where('record_key', $old->record_key)->count() === 1));
|
|
expect([SupplyRecord::query()->count(), DB::table('audit_logs')->count(), DB::table('requisitions')->count(), DB::table('stock_movements')->count()])->toBe($before);
|
|
$policy = app(SupplyPlanning::class)->save($this->f['issuer'], 'policy', supplyInput([...supplyPolicy($this->f), 'agreement_key' => $old->record_key]));
|
|
expect($policy->data['agreement_version'])->toBe($old->version)->and($policy->data['lead_days'])->toBe($old->data['lead_days']);
|
|
$this->actingAs($this->f['receiver']);
|
|
app(SupplyPlanning::class)->save($this->f['receiver'], 'agreement', supplyInput([], 'suspend', $old));
|
|
$this->actingAs($this->f['issuer'])->get($base.'&selected_agreement_key='.$old->record_key)->assertInertia(fn (Assert $p) => $p
|
|
->where('agreements', fn ($rows) => collect($rows)->where('record_key', $old->record_key)->isEmpty()));
|
|
expect($old->fresh()->state)->toBe('approved');
|
|
});
|
|
|
|
test('agreement source search validates company and scope and leaves readers without preparation choices', function () {
|
|
$base = '/compras/fornecimento?view=calendar';
|
|
$this->getJson($base.'&agreement_search=TEST')->assertUnprocessable();
|
|
$this->getJson($base.'&agreement_stock_item_id='.$this->f['stock']->id)->assertUnprocessable();
|
|
$scope = '&agreement_stock_item_id='.$this->f['stock']->id.'&agreement_warehouse_id='.$this->f['warehouse']->id;
|
|
$this->getJson($base.$scope.'&agreement_search='.str_repeat('A', 101))->assertUnprocessable();
|
|
$otherItem = StockItem::query()->create(['tenant_id' => $this->f['tenant']->id, 'created_by' => $this->f['issuer']->id, 'code' => 'OTHER-AGREEMENT', 'name' => 'Outro artigo do ensaio', 'unit' => 'cx']);
|
|
$this->get($base.'&agreement_stock_item_id='.$otherItem->id.'&agreement_warehouse_id='.$this->f['warehouse']->id.'&selected_agreement_key='.$this->f['agreement']->record_key)
|
|
->assertInertia(fn (Assert $p) => $p->has('agreements', 0));
|
|
$foreign = suggestionFixture();
|
|
$this->actingAs($this->f['issuer'])->getJson($base.$scope.'&selected_agreement_key='.$foreign['agreement']->record_key)->assertUnprocessable();
|
|
$this->get($base.$scope.'&agreement_search='.urlencode("' OR 1=1 --"))->assertInertia(fn (Assert $p) => $p->has('agreements', 1));
|
|
supplyGrant($this->f['otherReceiver'], ['supply_read']);
|
|
$this->actingAs($this->f['otherReceiver'])->get($base.$scope)->assertInertia(fn (Assert $p) => $p->where('canPrepare', false)->has('agreements', 0));
|
|
});
|
|
|
|
test('supply pagination retains old records beyond one hundred and preserves direct focus without writes', function (string $view) {
|
|
$first = match ($view) {
|
|
'agreements' => $this->f['agreement'],
|
|
'calendar' => $this->f['policy'],
|
|
'suggestions' => app(SupplySuggestions::class)->save($this->f['issuer'], supplyInput(['policy_key' => $this->f['policy']->record_key])),
|
|
'history' => app(SupplyHistory::class)->save($this->f['issuer'], supplyInput(['stock_item_id' => $this->f['stock']->id,
|
|
'warehouse_id' => $this->f['warehouse']->id, 'purpose' => 'internal', 'period_from' => today()->subDays(30)->toDateString(),
|
|
'period_until' => today()->subDay()->toDateString(), 'quality' => 'complete', 'pattern' => 'regular'])),
|
|
};
|
|
for ($i = 0; $i < 104; $i++) {
|
|
$copy = $first->replicate();
|
|
$copy->record_key = (string) Str::uuid();
|
|
$copy->operation_key = (string) Str::uuid();
|
|
$copy->state = 'draft';
|
|
$copy->version = 1;
|
|
$copy->created_at = $first->created_at;
|
|
$copy->save();
|
|
}
|
|
$before = [SupplyRecord::query()->count(), DB::table('audit_logs')->count(), DB::table('stock_movements')->count(), DB::table('requisitions')->count(), $first->fresh()->getAttributes()];
|
|
$this->get('/compras/fornecimento?view='.$view)->assertInertia(fn (Assert $page) => $page->where('recordPagination.total', 105)->has('records', 25));
|
|
$this->get('/compras/fornecimento?view='.$view.'&records_page=5')->assertHeader('Cache-Control', 'max-age=0, no-store, private')->assertInertia(fn (Assert $page) => $page
|
|
->where('recordPagination.current_page', 5)->has('records', 5)->where('records.4.id', $first->id)
|
|
->where('recordPagination.prev_page_url', fn ($url) => str_contains($url, 'view='.$view)));
|
|
$this->get('/compras/fornecimento?view='.$view.'&record_id='.$first->id.'&records_page=5')->assertInertia(fn (Assert $page) => $page->where('recordPagination', null)
|
|
->has('records', 1)->where('records.0.id', $first->id));
|
|
expect([SupplyRecord::query()->count(), DB::table('audit_logs')->count(), DB::table('stock_movements')->count(), DB::table('requisitions')->count(), $first->fresh()->getAttributes()])->toBe($before);
|
|
})->with(['agreements', 'calendar', 'suggestions', 'history']);
|
|
|
|
test('supply pagination keeps import milestones in one current record and preserves its historical source', function () {
|
|
$this->f = receiptFixture();
|
|
supplyGrant($this->f['issuer'], ['procurement', 'import_prepare']);
|
|
$this->actingAs($this->f['issuer']);
|
|
$service = app(SupplyPlanning::class);
|
|
$first = $service->save($this->f['issuer'], 'import', supplyInput(['purchase_order_id' => $this->f['order']->id, 'purpose' => 'internal',
|
|
'jurisdiction' => 'Jurisdição sintética', 'origin_country' => 'País A', 'dispatch_country' => 'País B', 'regime' => 'Regime fictício de teste']));
|
|
$before = $first->fresh()->getAttributes();
|
|
$current = $service->save($this->f['issuer'], 'import', supplyInput(['evidence' => 'Documento fictício de trânsito', 'event_date' => today()->toDateString()], 'transit', $first));
|
|
$this->get('/compras/fornecimento?view=imports')->assertInertia(fn (Assert $page) => $page->where('recordPagination.total', 1)->has('records', 1)->where('records.0.id', $current->id));
|
|
$this->get('/compras/fornecimento?view=imports&record_id='.$first->id)->assertInertia(fn (Assert $page) => $page->where('recordPagination', null)
|
|
->where('focusChanged', true)->where('records.0.id', $current->id)->has('history.'.$first->record_key, 2));
|
|
expect($first->fresh()->getAttributes())->toBe($before);
|
|
});
|
|
|
|
test('supply pagination enforces company scope and page validation across the views', function (string $view) {
|
|
$this->getJson('/compras/fornecimento?view='.$view.'&records_page=0')->assertUnprocessable();
|
|
$this->getJson('/compras/fornecimento?view='.$view.'&records_page=1000001')->assertUnprocessable();
|
|
$tenant = Tenant::query()->create(['name' => 'Empresa sintética isolada']);
|
|
$foreign = User::factory()->create(['tenant_id' => $tenant->id]);
|
|
supplyGrant($foreign, ['supply_read', 'import_read']);
|
|
$this->actingAs($foreign)->get('/compras/fornecimento?view='.$view)->assertInertia(fn (Assert $page) => $page->where('recordPagination.total', 0)->has('records', 0));
|
|
$this->getJson('/compras/fornecimento?view='.$view.'&record_id='.$this->f['agreement']->id)->assertUnprocessable();
|
|
})->with(['agreements', 'calendar', 'suggestions', 'history', 'imports']);
|
|
|
|
test('supply pagination preserves maintenance and membership expiry boundaries', function () {
|
|
DB::table('organization_memberships')->where('user_id', $this->f['issuer']->id)->update(['maintenance' => true, 'expires_at' => now()->addHour()]);
|
|
$this->get('/compras/fornecimento?view=agreements')->assertForbidden();
|
|
DB::table('organization_memberships')->where('user_id', $this->f['issuer']->id)->update(['expires_at' => now()->subMinute()]);
|
|
$this->get('/compras/fornecimento?view=calendar')->assertForbidden();
|
|
});
|
|
|
|
test('supply pagination preserves the approved policy of an older suggestion outside recent choices', function () {
|
|
$suggestion = app(SupplySuggestions::class)->save($this->f['issuer'], supplyInput(['policy_key' => $this->f['policy']->record_key]));
|
|
for ($i = 0; $i < 100; $i++) {
|
|
$item = StockItem::query()->create(['tenant_id' => $this->f['tenant']->id, 'created_by' => $this->f['issuer']->id,
|
|
'code' => 'POLICY-CHOICE-'.$i, 'name' => 'Artigo sintético de política '.$i, 'unit' => 'cx']);
|
|
$this->actingAs($this->f['issuer']);
|
|
$draft = app(SupplyPlanning::class)->save($this->f['issuer'], 'policy', supplyInput([...supplyPolicy($this->f), 'stock_item_id' => $item->id]));
|
|
$this->actingAs($this->f['receiver']);
|
|
app(SupplyPlanning::class)->save($this->f['receiver'], 'policy', supplyInput([], 'approve', $draft));
|
|
}
|
|
$before = [$suggestion->fresh()->getAttributes(), DB::table('audit_logs')->count(), DB::table('requisitions')->count()];
|
|
$this->actingAs($this->f['issuer'])->get('/compras/fornecimento?view=suggestions&record_id='.$suggestion->id)->assertInertia(fn (Assert $page) => $page
|
|
->has('policies', 101)->where('policies', fn ($rows) => collect($rows)->where('key', $this->f['policy']->record_key)->count() === 1)
|
|
->where('records.0.data.policy_key', $this->f['policy']->record_key));
|
|
expect([$suggestion->fresh()->getAttributes(), DB::table('audit_logs')->count(), DB::table('requisitions')->count()])->toBe($before);
|
|
$this->actingAs($this->f['receiver']);
|
|
app(SupplyPlanning::class)->save($this->f['receiver'], 'policy', supplyInput([], 'suspend', $this->f['policy']));
|
|
$this->actingAs($this->f['issuer'])->get('/compras/fornecimento?view=suggestions&record_id='.$suggestion->id)->assertInertia(fn (Assert $page) => $page
|
|
->has('policies', 100)->where('policies', fn ($rows) => collect($rows)->where('key', $this->f['policy']->record_key)->isEmpty())
|
|
->where('records.0.data.policy_key', $this->f['policy']->record_key));
|
|
expect($suggestion->fresh()->getAttributes())->toBe($before[0]);
|
|
});
|
|
|
|
test('supply pagination import source search preserves selection validates scope and never creates documents', function () {
|
|
$before = [SupplyRecord::query()->count(), DB::table('audit_logs')->count(), DB::table('requisitions')->count(), DB::table('stock_movements')->count()];
|
|
$number = mb_strtolower($this->f['order']->number);
|
|
$this->get('/compras/fornecimento?view=imports&order_search='.urlencode($number))->assertInertia(fn (Assert $page) => $page->has('orders', 1)->where('orders.0.id', $this->f['order']->id));
|
|
$this->get('/compras/fornecimento?view=imports&order_search=NO-MATCH-FICTICIO')->assertInertia(fn (Assert $page) => $page->has('orders', 0));
|
|
$this->get('/compras/fornecimento?view=imports&order_search=NO-MATCH-FICTICIO&selected_order_id='.$this->f['order']->id)->assertInertia(fn (Assert $page) => $page->has('orders', 1)->where('orders.0.id', $this->f['order']->id));
|
|
$this->getJson('/compras/fornecimento?view=imports&order_search='.str_repeat('A', 101))->assertUnprocessable();
|
|
$this->get('/compras/fornecimento?view=imports&order_search='.urlencode("' OR 1=1 --"))->assertInertia(fn (Assert $page) => $page->has('orders', 0));
|
|
expect([SupplyRecord::query()->count(), DB::table('audit_logs')->count(), DB::table('requisitions')->count(), DB::table('stock_movements')->count()])->toBe($before);
|
|
$foreign = receiptFixture();
|
|
$this->actingAs($this->f['issuer'])->getJson('/compras/fornecimento?view=imports&selected_order_id='.$foreign['order']->id)->assertUnprocessable();
|
|
supplyGrant($this->f['otherReceiver'], ['import_read']);
|
|
$this->actingAs($this->f['otherReceiver'])->get('/compras/fornecimento?view=imports&selected_order_id='.$this->f['order']->id)->assertInertia(fn (Assert $page) => $page->where('canPrepare', false)->has('orders', 0));
|
|
});
|