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)); });