commit21
This commit is contained in:
@@ -2,6 +2,8 @@
|
||||
|
||||
use App\Models\StockMovement;
|
||||
use App\Models\SupplyRecord;
|
||||
use App\Models\Tenant;
|
||||
use App\Models\User;
|
||||
use App\Models\Warehouse;
|
||||
use App\Services\ReceiptWorkflow;
|
||||
use App\Services\ReplenishmentProjection;
|
||||
@@ -10,6 +12,7 @@ use App\Services\StockWorkflow;
|
||||
use App\Services\SupplyHistory;
|
||||
use App\Services\SupplyPlanning;
|
||||
use Carbon\CarbonImmutable;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Str;
|
||||
use Inertia\Testing\AssertableInertia as Assert;
|
||||
|
||||
@@ -63,6 +66,66 @@ function historyPolicy($test, SupplyRecord $history, array $extra = []): SupplyR
|
||||
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)
|
||||
@@ -185,3 +248,76 @@ test('replacement policy can reuse all approved parameters by reference while ac
|
||||
$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();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user