travelTo(CarbonImmutable::parse('2026-09-30 12:00:00')); $this->f = receiptFixture('1500.000'); supplyGrant($this->f['issuer'], ['procurement', 'supply_prepare', 'supply_history_prepare']); supplyGrant($this->f['receiver'], ['warehouse', 'supply_review', 'supply_history_review', 'supply_reserve']); $this->actingAs($this->f['receiver']); app(ReceiptWorkflow::class)->confirm($this->f['receiver'], $this->f['order'], receiptPayload($this->f, '1200.000')); historyIssue($this, '2026-10-10', '100.000'); historyIssue($this, '2026-10-20', '200.000'); $this->travelTo(CarbonImmutable::parse('2026-11-01 12:00:00')); $this->actingAs($this->f['issuer']); }); function historyIssue($test, string $date, string $quantity): object { $test->travelTo(CarbonImmutable::parse($date.' 12:00:00')); $test->actingAs($test->f['receiver']); return app(StockWorkflow::class)->record($test->f['receiver'], ['kind' => 'issue', 'reference' => 'HIST-'.Str::random(10), 'idempotency_key' => (string) Str::uuid(), 'responsible_id' => $test->f['receiver']->id, 'warehouse_id' => $test->f['warehouse']->id, 'stock_item_id' => $test->f['stock']->id, 'quantity' => $quantity, 'reason' => 'Consumo fictício para testar o período histórico.']); } function historyData(array $f): array { return ['stock_item_id' => $f['stock']->id, 'warehouse_id' => $f['warehouse']->id, 'purpose' => 'internal', 'period_from' => '2026-10-01', 'period_until' => '2026-10-30', 'quality' => 'complete', 'pattern' => 'regular']; } function approvedHistory($test, array $extra = []): SupplyRecord { $test->actingAs($test->f['issuer'])->postJson('/compras/fornecimento/history', supplyInput([...historyData($test->f), ...$extra]))->assertRedirect(); $draft = app(SupplyPlanning::class)->latest('history')->sole(); $test->actingAs($test->f['receiver'])->postJson('/compras/fornecimento/history', supplyInput(['confirm_scope' => true], 'approve', $draft))->assertRedirect(); return app(SupplyPlanning::class)->latest('history')->sole(); } function historyPolicy($test, SupplyRecord $history, array $extra = []): SupplyRecord { $data = [...supplyPolicy($test->f), 'purpose' => 'internal', 'safety_milli' => 600000, 'minimum_stock_milli' => 600000, 'target_milli' => 900000, 'maximum_milli' => 1200000, 'forecast_method' => 'historical', 'history_key' => $history->record_key, 'history_min_days' => 30, 'history_max_age_days' => 90, 'daily_milli' => 999999, ...$extra]; $test->actingAs($test->f['issuer'])->postJson('/compras/fornecimento/policy', supplyInput($data))->assertRedirect(); $draft = app(SupplyPlanning::class)->latest('policy')->sole(); $test->actingAs($test->f['receiver'])->postJson('/compras/fornecimento/policy', supplyInput([], 'approve', $draft))->assertRedirect(); return app(SupplyPlanning::class)->latest('policy')->sole(); } test('history source search finds an older approved period before the recent choice limit', function () { $old = approvedHistory($this); $this->actingAs($this->f['issuer']); $draft = app(SupplyHistory::class)->save($this->f['issuer'], supplyInput([...historyData($this->f), 'period_from' => '2026-09-01', 'period_until' => '2026-09-30'])); $this->actingAs($this->f['receiver']); $recent = app(SupplyHistory::class)->save($this->f['receiver'], supplyInput(['confirm_scope' => true], 'approve', $draft)); for ($i = 0; $i < 99; $i++) { $copy = $recent->replicate(); $copy->record_key = (string) Str::uuid(); $copy->operation_key = (string) Str::uuid(); $copy->created_at = $recent->created_at; $copy->save(); } $query = '/compras/fornecimento?view=calendar&history_stock_item_id='.$this->f['stock']->id.'&history_warehouse_id='.$this->f['warehouse']->id.'&history_purpose=internal'; $before = [$old->fresh()->getAttributes(), DB::table('audit_logs')->count(), DB::table('stock_movements')->count()]; $this->actingAs($this->f['issuer'])->get('/compras/fornecimento?view=calendar')->assertInertia(fn (Assert $p) => $p->has('histories', 100)->where('histories', fn ($rows) => collect($rows)->where('key', $old->record_key)->isEmpty())); $this->get($query.'&history_period_from=2026-10-01&history_period_until=2026-10-30')->assertInertia(fn (Assert $p) => $p->has('histories', 1)->where('histories.0.key', $old->record_key)); $this->get($query.'&history_period_until=2026-09-30&selected_history_key='.$old->record_key)->assertInertia(fn (Assert $p) => $p->has('histories', 101)); $this->get(str_replace('history_purpose=internal', 'history_purpose=resale', $query).'&selected_history_key='.$old->record_key)->assertInertia(fn (Assert $p) => $p->has('histories', 0)); expect([$old->fresh()->getAttributes(), DB::table('audit_logs')->count(), DB::table('stock_movements')->count()])->toBe($before); $this->actingAs($this->f['receiver']); app(SupplyHistory::class)->save($this->f['receiver'], supplyInput([], 'suspend', $old)); $this->actingAs($this->f['issuer'])->get($query.'&history_period_from=2026-10-01&selected_history_key='.$old->record_key)->assertInertia(fn (Assert $p) => $p->has('histories', 0)); }); test('history source search validates scope dates and preparation rights', function () { $old = approvedHistory($this); $query = '/compras/fornecimento?view=calendar&history_stock_item_id='.$this->f['stock']->id.'&history_warehouse_id='.$this->f['warehouse']->id.'&history_purpose=internal'; $this->actingAs($this->f['issuer'])->getJson($query.'&history_period_from=2026-10-30&history_period_until=2026-10-01')->assertUnprocessable(); $this->getJson('/compras/fornecimento?view=calendar&history_period_from=2026-10-01')->assertUnprocessable(); $tenant = Tenant::query()->create(['name' => 'Histórico isolado']); $foreign = User::factory()->create(['tenant_id' => $tenant->id]); supplyGrant($foreign, ['supply_prepare']); $this->actingAs($foreign)->getJson($query.'&selected_history_key='.$old->record_key)->assertUnprocessable(); $this->actingAs($this->f['receiver'])->get($query)->assertInertia(fn (Assert $p) => $p->where('canPrepare', false)->has('histories', 0)); }); test('supply pagination preserves an approved historical source already selected outside the recent choices', function () { $history = approvedHistory($this); $policy = historyPolicy($this, $history); for ($i = 0; $i < 100; $i++) { $copy = $history->replicate(); $copy->record_key = (string) Str::uuid(); $copy->operation_key = (string) Str::uuid(); $copy->created_at = $history->created_at; $copy->save(); } $before = [$policy->fresh()->getAttributes(), $history->fresh()->getAttributes(), DB::table('audit_logs')->count()]; $this->actingAs($this->f['issuer'])->get('/compras/fornecimento?view=calendar&record_id='.$policy->id)->assertInertia(fn (Assert $page) => $page ->has('histories', 101)->where('histories', fn ($rows) => collect($rows)->where('key', $history->record_key)->count() === 1) ->where('records.0.data.history_key', $history->record_key)); expect([$policy->fresh()->getAttributes(), $history->fresh()->getAttributes(), DB::table('audit_logs')->count()])->toBe($before); $this->actingAs($this->f['receiver']); app(SupplyHistory::class)->save($this->f['receiver'], supplyInput([], 'suspend', $history)); $this->actingAs($this->f['issuer'])->get('/compras/fornecimento?view=calendar&record_id='.$policy->id)->assertInertia(fn (Assert $page) => $page ->has('histories', 100)->where('histories', fn ($rows) => collect($rows)->where('key', $history->record_key)->isEmpty()) ->where('records.0.data.history_key', $history->record_key)); expect($policy->fresh()->getAttributes())->toBe($before[0])->and($history->fresh()->getAttributes())->toBe($before[1]); }); test('historical mean includes all calendar days and policy reuses the approved period without accepting forged totals', function () { $history = approvedHistory($this, ['total_milli' => 999999, 'daily_mean_milli' => '999']); expect($history->snapshot['analysis']['total_milli'])->toBe(300000)->and($history->snapshot['analysis']['days'])->toBe(30) ->and($history->snapshot['analysis']['daily_mean_milli'])->toBe('10000.000000')->and($history->snapshot['analysis']['active_days'])->toBe(2); $policy = historyPolicy($this, $history); expect($policy->data['daily_milli'])->toBeNull()->and($policy->data['history_version'])->toBe($history->version); $effective = app(SupplyHistory::class)->resolve($policy); $projection = app(ReplenishmentProjection::class)->calculate($effective['data'], 900000, today()->toDateString()); expect($projection['method'])->toBe('historical_mean_calendar_days')->and($projection['daily_milli'])->toBe('10000.000000')->and($projection['series'][0]['demand_milli'])->toBe(10000); $this->actingAs($this->f['issuer'])->get('/compras/fornecimento?view=calendar')->assertInertia(fn (Assert $page) => $page->where('projections.'.$policy->record_key.'.forecast_history.available', true)); }); test('transfers adjustments counts and receptions affect stock but never inflate consumption', function () { $this->travelTo(CarbonImmutable::parse('2026-10-22 12:00:00')); $this->actingAs($this->f['receiver']); $other = Warehouse::query()->create(['tenant_id' => $this->f['tenant']->id, 'created_by' => $this->f['receiver']->id, 'code' => 'HIST-2', 'name' => 'Outro local do ensaio']); $base = ['responsible_id' => $this->f['receiver']->id, 'warehouse_id' => $this->f['warehouse']->id, 'stock_item_id' => $this->f['stock']->id, 'reason' => 'Operação não representa consumo na análise.']; app(StockWorkflow::class)->record($this->f['receiver'], [...$base, 'kind' => 'transfer', 'reference' => 'HIST-TRANS', 'idempotency_key' => (string) Str::uuid(), 'destination_warehouse_id' => $other->id, 'quantity' => '10.000']); $version = app(StockWorkflow::class)->balance($this->f['stock']->id, $this->f['warehouse']->id)['version']; $count = app(StockWorkflow::class)->record($this->f['receiver'], [...$base, 'kind' => 'count', 'reference' => 'HIST-COUNT', 'idempotency_key' => (string) Str::uuid(), 'quantity' => '905.000', 'ledger_version' => $version]); app(StockWorkflow::class)->record($this->f['receiver'], [...$base, 'kind' => 'adjustment', 'reference' => 'HIST-ADJUST', 'idempotency_key' => (string) Str::uuid(), 'quantity' => '905.000', 'count_id' => $count->id]); app(ReceiptWorkflow::class)->confirm($this->f['receiver'], $this->f['order'], receiptPayload($this->f, '50.000')); $this->travelTo(CarbonImmutable::parse('2026-11-01 12:00:00')); $analysis = app(SupplyHistory::class)->analyse($this->f['stock']->id, $this->f['warehouse']->id, '2026-10-01', '2026-10-30'); expect($analysis['total_milli'])->toBe(300000)->and($analysis['movement_types']['issue'])->toBe(2)->and($analysis['movement_types']['transfer_out'])->toBe(1) ->and($analysis['movement_types']['adjustment'])->toBe(1)->and($analysis['movement_types']['receipt_in'])->toBe(1); }); test('unknown history remains unavailable whereas reviewed zero consumption is a genuine zero and known needs remain', function () { $history = approvedHistory($this, ['period_from' => '2026-10-21']); $policy = historyPolicy($this, $history, ['history_min_days' => 10]); $effective = app(SupplyHistory::class)->resolve($policy); $zero = app(ReplenishmentProjection::class)->calculate($effective['data'], 900000, today()->toDateString()); $unknown = app(ReplenishmentProjection::class)->calculate([...supplyPolicy($this->f), 'daily_milli' => null], 900000, today()->toDateString()); expect($zero['available'])->toBeTrue()->and($zero['daily_milli'])->toBe('0.000000')->and($zero['quantity_milli'])->toBe(0)->and($unknown['available'])->toBeFalse(); $known = app(ReplenishmentProjection::class)->calculate($effective['data'], 900000, today()->toDateString(), [['date' => today()->addDay()->toDateString(), 'quantity' => 350000, 'additional' => true]]); expect($known['risk_date'])->toBe(today()->addDay()->toDateString())->and($known['quantity_milli'])->toBeGreaterThan(0); }); test('fractional milliunit average is projected cumulatively instead of becoming false zero', function () { historyIssue($this, '2026-10-28', '0.001'); $this->travelTo(CarbonImmutable::parse('2026-11-01 12:00:00')); $history = approvedHistory($this, ['period_from' => '2026-10-28']); $policy = historyPolicy($this, $history, ['history_min_days' => 3]); $data = app(SupplyHistory::class)->resolve($policy)['data']; $projection = app(ReplenishmentProjection::class)->calculate([...$data, 'horizon_days' => 3], 900000, today()->toDateString()); expect($projection['daily_milli'])->toBe('0.333333')->and(array_sum(array_column($projection['series'], 'demand_milli')))->toBe(1); }); test('independent confirmation is mandatory and late historical entries invalidate rather than overwrite an approved analysis', function () { $input = supplyInput(historyData($this->f)); $this->postJson('/compras/fornecimento/history', $input)->assertRedirect(); $this->postJson('/compras/fornecimento/history', $input)->assertRedirect(); $draft = app(SupplyPlanning::class)->latest('history')->sole(); supplyGrant($this->f['issuer'], ['supply_history_review']); $this->postJson('/compras/fornecimento/history', supplyInput(['confirm_scope' => true], 'approve', $draft))->assertUnprocessable(); $this->actingAs($this->f['receiver'])->postJson('/compras/fornecimento/history', supplyInput([], 'approve', $draft))->assertUnprocessable(); $this->postJson('/compras/fornecimento/history', supplyInput(['confirm_scope' => true], 'approve', $draft))->assertRedirect(); $approved = app(SupplyPlanning::class)->latest('history')->sole(); $policy = historyPolicy($this, $approved); historyIssue($this, '2026-10-25', '1.000'); $this->travelTo(CarbonImmutable::parse('2026-11-01 12:00:00')); $result = app(SupplyHistory::class)->resolve($policy); expect($result['history']['available'])->toBeFalse()->and($result['data']['daily_milli'])->toBeNull()->and($approved->fresh()->snapshot['analysis']['total_milli'])->toBe(300000); }); test('insufficient coverage and nonregular patterns cannot silently feed a simple average', function (string $quality, string $pattern) { $history = approvedHistory($this, ['quality' => $quality, 'pattern' => $pattern]); $data = [...supplyPolicy($this->f), 'purpose' => 'internal', 'forecast_method' => 'historical', 'history_key' => $history->record_key, 'history_min_days' => 30, 'history_max_age_days' => 90]; $this->actingAs($this->f['issuer'])->postJson('/compras/fornecimento/policy', supplyInput($data))->assertUnprocessable(); })->with([['unknown', 'regular'], ['incomplete', 'regular'], ['stockout', 'regular'], ['complete', 'seasonal'], ['complete', 'intermittent']]); test('minimum coverage purpose age and suspension remain explicit policy conditions', function () { $history = approvedHistory($this); $data = [...supplyPolicy($this->f), 'forecast_method' => 'historical', 'history_key' => $history->record_key, 'history_min_days' => 31, 'history_max_age_days' => 90]; $this->actingAs($this->f['issuer'])->postJson('/compras/fornecimento/policy', supplyInput([...$data, 'purpose' => 'internal']))->assertUnprocessable(); $this->postJson('/compras/fornecimento/policy', supplyInput([...$data, 'history_min_days' => 30]))->assertUnprocessable(); $policy = historyPolicy($this, $history); $this->travelTo(CarbonImmutable::parse('2027-02-01 12:00:00')); expect(app(SupplyHistory::class)->resolve($policy)['history']['available'])->toBeFalse(); $this->travelTo(CarbonImmutable::parse('2026-11-01 12:00:00')); $this->actingAs($this->f['receiver'])->postJson('/compras/fornecimento/history', supplyInput([], 'suspend', $history))->assertRedirect(); expect(app(SupplyHistory::class)->resolve($policy)['history']['available'])->toBeFalse(); }); test('historical dates must be closed and the source ledger never crosses companies or changes through analysis', function () { $before = StockMovement::query()->count(); $this->postJson('/compras/fornecimento/history', supplyInput([...historyData($this->f), 'period_until' => today()->toDateString()]))->assertUnprocessable(); $other = receiptFixture(); $this->actingAs($this->f['issuer'])->postJson('/compras/fornecimento/history', supplyInput([...historyData($this->f), 'stock_item_id' => $other['stock']->id]))->assertNotFound(); $this->get('/compras/fornecimento?view=history')->assertInertia(fn (Assert $page) => $page->component('supply/history')->where('canPrepare', true)); expect(StockMovement::query()->count())->toBe($before); }); test('suggestions freeze the historical period and a suspended source cannot be used to convert an older approval', function () { $history = approvedHistory($this); $policy = historyPolicy($this, $history); app(RequisitionCatalogue::class)->save($this->f['receiver'], $this->f['stock']->id, ['version' => 0, 'stock_basis' => app(RequisitionCatalogue::class)->basis($this->f['stock']), 'category' => 'Material', 'purchase_type' => 'consumable', 'published' => true, 'reason' => 'Reutilização do catálogo no percurso histórico.']); supplyGrant($this->f['issuer'], ['supply_suggest_prepare', 'supply_suggest_convert', 'requester']); supplyGrant($this->f['receiver'], ['supply_suggest_review']); $this->actingAs($this->f['issuer'])->postJson('/compras/fornecimento/suggestion', supplyInput(['policy_key' => $policy->record_key, 'unit_price' => '10.00']))->assertRedirect(); $suggestion = app(SupplyPlanning::class)->latest('suggestion')->sole(); expect($suggestion->snapshot['forecast_history']['version'])->toBe($history->version)->and($suggestion->snapshot['forecast_history']['analysis']['total_milli'])->toBe(300000); $this->actingAs($this->f['receiver'])->postJson('/compras/fornecimento/suggestion', supplyInput([], 'approve', $suggestion))->assertRedirect(); $approved = app(SupplyPlanning::class)->latest('suggestion')->sole(); $this->postJson('/compras/fornecimento/history', supplyInput([], 'suspend', $history))->assertRedirect(); $this->actingAs($this->f['issuer'])->postJson('/compras/fornecimento/suggestion', supplyInput(['cost_center_id' => $this->f['r']->cost_center_id], 'convert', $approved))->assertUnprocessable(); expect($approved->fresh()->snapshot['forecast_history']['version'])->toBe($history->version)->and($approved->fresh()->state)->toBe('approved'); }); test('replacement policy can reuse all approved parameters by reference while activation remains independent and unique', function () { $history = approvedHistory($this); $source = historyPolicy($this, $history); $this->actingAs($this->f['issuer'])->postJson('/compras/fornecimento/policy', supplyInput(['predecessor_key' => $source->record_key, 'predecessor_version' => $source->version]))->assertRedirect(); $copy = app(SupplyPlanning::class)->latest('policy')->where('record_key', '!=', $source->record_key)->sole(); expect($copy->data['target_milli'])->toBe($source->data['target_milli'])->and($copy->data['history_key'])->toBe($history->record_key)->and($copy->data['predecessor_version'])->toBe($source->version); $this->actingAs($this->f['receiver'])->postJson('/compras/fornecimento/policy', supplyInput([], 'approve', $copy))->assertUnprocessable(); $this->postJson('/compras/fornecimento/policy', supplyInput([], 'suspend', $source))->assertRedirect(); $this->postJson('/compras/fornecimento/policy', supplyInput([], 'approve', $copy))->assertRedirect(); expect(app(SupplyPlanning::class)->latest('policy')->where('state', 'approved')->count())->toBe(1)->and($source->fresh()->data['target_milli'])->toBe(900000); }); test('history task routes the prepared analysis to an independent reviewer without changing scope or ledger', function () { $draft = app(SupplyHistory::class)->save($this->f['issuer'], supplyInput(historyData($this->f))); supplyGrant($this->f['issuer'], ['supply_history_review']); $path = '/compras/fornecimento?view=history&record_id='.$draft->id; $this->get('/requisicoes/tarefas?module=supply')->assertInertia(fn (Assert $page) => $page->where('tasks.total', 0)); $this->get($path)->assertInertia(fn (Assert $page) => $page->where('records.0.canApprove', false)); $this->actingAs($this->f['receiver'])->get('/requisicoes/tarefas?module=supply')->assertInertia(fn (Assert $page) => $page->where('tasks.total', 1) ->where('tasks.data.0.href', $path.'#supply-record-'.$draft->id)->where('tasks.data.0.due_on', null)->missing('tasks.data.0.snapshot')); $before = [SupplyRecord::query()->count(), StockMovement::query()->count(), DB::table('audit_logs')->count()]; $this->get($path)->assertHeader('Cache-Control', 'max-age=0, no-store, private')->assertInertia(fn (Assert $page) => $page->has('records', 1) ->where('focusRecordId', $draft->id)->where('focusChanged', false)->where('records.0.canApprove', true)->where('records.0.canReturn', true)); expect([SupplyRecord::query()->count(), StockMovement::query()->count(), DB::table('audit_logs')->count()])->toBe($before); $this->postJson('/compras/fornecimento/history', supplyInput(['confirm_scope' => false], 'approve', $draft))->assertUnprocessable(); $approved = app(SupplyHistory::class)->save($this->f['receiver'], supplyInput(['confirm_scope' => true], 'approve', $draft)); $this->get('/requisicoes/tarefas?module=supply')->assertInertia(fn (Assert $page) => $page->where('tasks.total', 0)); $this->get($path)->assertInertia(fn (Assert $page) => $page->where('focusChanged', true)->where('records.0.id', $approved->id) ->where('records.0.canApprove', false)->where('records.0.canSuspend', true)); }); test('history task excludes every previous preparer and uses the specific historical competence', function () { $draft = app(SupplyHistory::class)->save($this->f['issuer'], supplyInput(historyData($this->f))); supplyGrant($this->f['receiver'], ['supply_history_prepare']); $this->actingAs($this->f['receiver']); $updated = app(SupplyHistory::class)->save($this->f['receiver'], supplyInput(historyData($this->f), 'save', $draft)); supplyGrant($this->f['issuer'], ['supply_history_review']); foreach ([$this->f['issuer'], $this->f['receiver']] as $user) { $this->actingAs($user)->get('/requisicoes/tarefas?module=supply')->assertInertia(fn (Assert $page) => $page->where('tasks.total', 0)); expect(app(SupplyHistory::class)->canReview($user, $updated))->toBeFalse(); } supplyGrant($this->f['otherReceiver'], ['supply_review']); $this->actingAs($this->f['otherReceiver'])->get('/requisicoes/tarefas?module=supply')->assertInertia(fn (Assert $page) => $page->where('tasks.total', 0)); $this->get('/compras/fornecimento?view=history')->assertForbidden(); supplyGrant($this->f['otherReceiver'], ['supply_history_review']); $this->get('/requisicoes/tarefas?module=supply')->assertInertia(fn (Assert $page) => $page->where('tasks.total', 1)->where('tasks.data.0.task', 'history_review')); expect(app(SupplyHistory::class)->canReview($this->f['otherReceiver'], $draft))->toBeFalse() ->and(app(SupplyHistory::class)->canReview($this->f['otherReceiver'], $updated))->toBeTrue(); }); test('history task keeps changed sources available for return and never approves stale analysis silently', function () { $draft = app(SupplyHistory::class)->save($this->f['issuer'], supplyInput(historyData($this->f))); $this->f['stock']->update(['name' => 'Nome alterado depois da análise']); $this->actingAs($this->f['receiver'])->get('/requisicoes/tarefas?module=supply')->assertInertia(fn (Assert $page) => $page->where('tasks.total', 1)); $this->postJson('/compras/fornecimento/history', supplyInput(['confirm_scope' => true], 'approve', $draft))->assertUnprocessable(); $returned = app(SupplyHistory::class)->save($this->f['receiver'], supplyInput([], 'return', $draft)); expect($returned->state)->toBe('returned')->and($returned->snapshot['item'])->toBe($draft->snapshot['item']); }); test('history task preserves old pending analyses beyond the display cap and all task pages', function () { $first = app(SupplyHistory::class)->save($this->f['issuer'], supplyInput(historyData($this->f))); for ($index = 1; $index < 105; $index++) { app(SupplyHistory::class)->save($this->f['issuer'], supplyInput(historyData($this->f))); } $this->actingAs($this->f['receiver'])->get('/requisicoes/tarefas?module=supply')->assertInertia(fn (Assert $page) => $page->where('tasks.total', 105)->has('tasks.data', 25)->where('tasks.data.0.id', 'supply:'.$first->id.':review')); $this->get('/requisicoes/tarefas?module=supply&page=5')->assertInertia(fn (Assert $page) => $page->where('tasks.total', 105)->has('tasks.data', 5)); $this->get('/compras/fornecimento?view=history')->assertInertia(fn (Assert $page) => $page->has('records', 25)->where('recordPagination.total', 105)); $this->get('/compras/fornecimento?view=history&record_id='.$first->id)->assertInertia(fn (Assert $page) => $page->has('records', 1)->where('records.0.id', $first->id)); }); test('history task preserves company isolation document type maintenance and grant expiry', function () { $draft = app(SupplyHistory::class)->save($this->f['issuer'], supplyInput(historyData($this->f))); $path = '/compras/fornecimento?view=history&record_id='.$draft->id; $tenant = Tenant::query()->create(['name' => 'Outra empresa de ensaio']); $foreign = User::factory()->create(['tenant_id' => $tenant->id]); supplyGrant($foreign, ['supply_history_review']); $this->actingAs($foreign)->get('/requisicoes/tarefas?module=supply')->assertInertia(fn (Assert $page) => $page->where('tasks.total', 0)); $this->getJson($path)->assertUnprocessable(); $this->actingAs($this->f['receiver'])->getJson('/compras/fornecimento?view=calendar&record_id='.$draft->id)->assertUnprocessable(); DB::table('organization_memberships')->where('user_id', $this->f['receiver']->id)->update(['maintenance' => true, 'expires_at' => now()->addHour()]); $this->get('/requisicoes/tarefas?module=supply')->assertInertia(fn (Assert $page) => $page->where('tasks.total', 0)); $this->get($path)->assertForbidden(); DB::table('organization_memberships')->where('user_id', $this->f['receiver']->id)->update(['expires_at' => now()->subMinute()]); $this->get('/requisicoes/tarefas?module=supply')->assertForbidden(); });