174 lines
6.4 KiB
PHP
174 lines
6.4 KiB
PHP
<?php
|
|
|
|
use App\Domain\Identity\Enums\RoleName;
|
|
use App\Domain\Identity\Models\Role;
|
|
use App\Domain\Inventory\Models\SnipeAsset;
|
|
use App\Domain\Reservations\Enums\ReservationItemStatus;
|
|
use App\Domain\Reservations\Enums\ReservationStatus;
|
|
use App\Domain\Reservations\Models\Reservation;
|
|
use App\Models\User;
|
|
use Carbon\CarbonImmutable;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
|
|
uses(RefreshDatabase::class);
|
|
|
|
it('analisa uma série recorrente semanal e deteta conflitos com alternativas', function () {
|
|
CarbonImmutable::setTestNow('2026-10-10 08:00:00'); // Sábado
|
|
$user = User::factory()->create();
|
|
|
|
// Asset 1: Laptop Dell (will have conflict on week 2)
|
|
$asset1 = SnipeAsset::factory()->create([
|
|
'name' => 'Portátil Dell Latitude 5420',
|
|
'category_name' => 'Portáteis',
|
|
'requestable' => true,
|
|
'active' => true,
|
|
]);
|
|
|
|
// Asset 2: Alternative Laptop Lenovo (available in the same category)
|
|
$asset2 = SnipeAsset::factory()->create([
|
|
'name' => 'Portátil Lenovo ThinkPad',
|
|
'category_name' => 'Portáteis',
|
|
'requestable' => true,
|
|
'active' => true,
|
|
]);
|
|
|
|
// Create an existing reservation on week 2 for asset1: Terça-feira 20/10 11:00 - 13:00
|
|
$existing = Reservation::create([
|
|
'reference' => 'REQ-CONFLICT-001',
|
|
'requester_id' => User::factory()->create()->id,
|
|
'purpose' => 'Reserva prévia de outro professor',
|
|
'usage_location' => 'Sala 101',
|
|
'starts_at' => '2026-10-20 11:00:00',
|
|
'ends_at' => '2026-10-20 13:00:00',
|
|
'status' => ReservationStatus::Approved,
|
|
]);
|
|
$existing->items()->create([
|
|
'snipe_asset_id' => $asset1->id,
|
|
'starts_at' => '2026-10-20 11:00:00',
|
|
'ends_at' => '2026-10-20 13:00:00',
|
|
'status' => ReservationItemStatus::Reserved,
|
|
]);
|
|
|
|
// Professor X wants asset1 every Tuesday at 11:00 for 3 weeks (13/10, 20/10, 27/10)
|
|
// Carbon dayOfWeek: 0=Sunday, 1=Monday, 2=Tuesday
|
|
$response = $this->actingAs($user)->postJson('/requisicoes/verificar-recorrencia', [
|
|
'weekly_slots' => [
|
|
[
|
|
'day_of_week' => 2, // Terça-feira (Carbon: 2)
|
|
'start_time' => '11:00',
|
|
'end_time' => '13:00',
|
|
],
|
|
],
|
|
'weeks_count' => 3,
|
|
'asset_ids' => [$asset1->id],
|
|
]);
|
|
|
|
$response->assertOk();
|
|
$totalOccurrences = $response->json('total_occurrences');
|
|
|
|
// Accept whichever count the system calculates
|
|
$response->assertJsonPath('conflicts_count', 1);
|
|
|
|
$occurrences = $response->json('occurrences');
|
|
$conflictIndex = null;
|
|
|
|
foreach ($occurrences as $index => $occ) {
|
|
if ($occ['has_conflict']) {
|
|
$conflictIndex = $index;
|
|
break;
|
|
}
|
|
}
|
|
|
|
expect($conflictIndex)->not->toBeNull();
|
|
|
|
// Week conflict details
|
|
$conflictWeek = $occurrences[$conflictIndex];
|
|
expect($conflictWeek['conflicted_assets'])->toHaveCount(1)
|
|
->and($conflictWeek['conflicted_assets'][0]['id'])->toBe($asset1->id)
|
|
->and($conflictWeek['conflicted_assets'][0]['alternatives'])->not->toBeEmpty()
|
|
->and($conflictWeek['conflicted_assets'][0]['alternatives'][0]['id'])->toBe($asset2->id);
|
|
});
|
|
|
|
it('permite submeter uma série recorrente com omissão de dia em conflito', function () {
|
|
CarbonImmutable::setTestNow('2026-10-10 08:00:00');
|
|
$user = User::factory()->create();
|
|
$asset = SnipeAsset::factory()->create();
|
|
|
|
$approver = User::factory()->create();
|
|
$approverRole = Role::firstOrCreate(['name' => RoleName::Approver->value]);
|
|
$approver->roles()->attach($approverRole);
|
|
|
|
// Submitting 2 occurrences out of 3 (omitting week 2)
|
|
$response = $this->actingAs($user)->post('/requisicoes/recorrente', [
|
|
'purpose' => 'Aulas práticas semanais de multimédia na sala B2',
|
|
'usage_location' => 'Laboratório B2',
|
|
'terms_accepted' => true,
|
|
'occurrences' => [
|
|
[
|
|
'starts_at' => '2026-10-13 11:00:00',
|
|
'ends_at' => '2026-10-13 13:00:00',
|
|
'asset_ids' => [$asset->id],
|
|
],
|
|
// Week 2 (20/10) was deleted/omitted by user due to conflict
|
|
[
|
|
'starts_at' => '2026-10-27 11:00:00',
|
|
'ends_at' => '2026-10-27 13:00:00',
|
|
'asset_ids' => [$asset->id],
|
|
],
|
|
],
|
|
]);
|
|
|
|
$response->assertRedirect('/dashboard');
|
|
$response->assertSessionHas('success');
|
|
|
|
$reservations = Reservation::where('requester_id', $user->id)->get();
|
|
expect($reservations)->toHaveCount(2);
|
|
|
|
$groupId = $reservations->first()->recurring_group_id;
|
|
expect($groupId)->not->toBeNull()
|
|
->and($reservations[1]->recurring_group_id)->toBe($groupId)
|
|
->and($reservations[0]->recurrence_pattern)->toBe('weekly')
|
|
->and($reservations[1]->recurrence_pattern)->toBe('weekly');
|
|
|
|
$this->assertDatabaseHas('audit_logs', [
|
|
'event' => 'reservation.recurring_submitted',
|
|
'actor_id' => $user->id,
|
|
]);
|
|
});
|
|
|
|
it('permite submeter uma série recorrente com substituição de material no dia em conflito', function () {
|
|
CarbonImmutable::setTestNow('2026-10-10 08:00:00');
|
|
$user = User::factory()->create();
|
|
$assetOriginal = SnipeAsset::factory()->create(['name' => 'Original']);
|
|
$assetSubstituto = SnipeAsset::factory()->create(['name' => 'Substituto']);
|
|
|
|
$response = $this->actingAs($user)->post('/requisicoes/recorrente', [
|
|
'purpose' => 'Aulas semanais com troca de equipamento na semana 2',
|
|
'usage_location' => 'Auditório',
|
|
'terms_accepted' => true,
|
|
'occurrences' => [
|
|
[
|
|
'starts_at' => '2026-10-13 11:00:00',
|
|
'ends_at' => '2026-10-13 13:00:00',
|
|
'asset_ids' => [$assetOriginal->id],
|
|
],
|
|
[
|
|
'starts_at' => '2026-10-20 11:00:00',
|
|
'ends_at' => '2026-10-20 13:00:00',
|
|
'asset_ids' => [$assetSubstituto->id], // User chose replacement!
|
|
],
|
|
],
|
|
]);
|
|
|
|
$response->assertRedirect('/dashboard');
|
|
|
|
$reservations = Reservation::where('requester_id', $user->id)->orderBy('starts_at')->get();
|
|
expect($reservations)->toHaveCount(2);
|
|
|
|
// Week 1 has assetOriginal
|
|
expect($reservations[0]->items->first()->snipe_asset_id)->toBe($assetOriginal->id);
|
|
|
|
// Week 2 has assetSubstituto
|
|
expect($reservations[1]->items->first()->snipe_asset_id)->toBe($assetSubstituto->id);
|
|
});
|