@@ -123,7 +123,7 @@ it('grava buffers e prazo de aprovação definidos pela política', function ()
|
||||
'reservation.turnaround_minutes' => 30,
|
||||
'reservation.approval_deadline_hours' => 12,
|
||||
]), $administrator);
|
||||
$startsAt = CarbonImmutable::parse('2026-09-20 10:00:00');
|
||||
$startsAt = CarbonImmutable::now()->addDays(5)->setTime(10, 0, 0);
|
||||
$endsAt = $startsAt->addHours(2);
|
||||
|
||||
$reservation = app(ReservationService::class)->createPending(User::factory()->create(), [
|
||||
|
||||
@@ -6,6 +6,7 @@ use App\Domain\Reservations\Enums\ReservationStatus;
|
||||
use App\Domain\Reservations\Models\Reservation;
|
||||
use App\Domain\Reservations\Services\AvailabilityService;
|
||||
use App\Models\User;
|
||||
use Carbon\CarbonImmutable;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Inertia\Testing\AssertableInertia as Assert;
|
||||
|
||||
@@ -100,6 +101,7 @@ it('produz uma consulta de equipamentos livres para um período', function () {
|
||||
});
|
||||
|
||||
it('filtra o catálogo pelo período solicitado', function () {
|
||||
CarbonImmutable::setTestNow('2026-09-01 08:00:00');
|
||||
$user = User::factory()->create();
|
||||
$free = SnipeAsset::factory()->create();
|
||||
$busy = SnipeAsset::factory()->create();
|
||||
|
||||
173
tests/Feature/RecurringReservationTest.php
Normal file
173
tests/Feature/RecurringReservationTest.php
Normal file
@@ -0,0 +1,173 @@
|
||||
<?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);
|
||||
});
|
||||
Reference in New Issue
Block a user