adminUser = User::factory()->create(['role' => Role::ADMIN]); $this->managerUser = User::factory()->create(['role' => Role::MANAGER]); } // ==================== forReclamation (JSON API) ==================== public function test_guest_cannot_get_reservations_for_reclamation(): void { $reclamation = Reclamation::factory()->create(); $response = $this->get(route('spare_part_reservations.for_reclamation', $reclamation->id)); $response->assertRedirect(route('login')); } public function test_admin_can_get_reservations_for_reclamation(): void { $sparePart = SparePart::factory()->create(['article' => 'SP-RESRV-001']); $order = SparePartOrder::factory()->inStock()->withQuantity(10)->forSparePart($sparePart)->create(); $reclamation = Reclamation::factory()->create(); Reservation::factory()->create([ 'spare_part_id' => $sparePart->id, 'spare_part_order_id' => $order->id, 'reclamation_id' => $reclamation->id, 'reserved_qty' => 3, 'status' => Reservation::STATUS_ACTIVE, ]); $response = $this->actingAs($this->adminUser) ->getJson(route('spare_part_reservations.for_reclamation', $reclamation->id)); $response->assertStatus(200); $response->assertJsonStructure([ 'reservations' => [['id', 'spare_part_id', 'article', 'reserved_qty', 'status']], ]); $response->assertJsonFragment(['spare_part_id' => $sparePart->id]); } public function test_manager_can_get_reservations_for_reclamation(): void { $reclamation = Reclamation::factory()->create(); $response = $this->actingAs($this->managerUser) ->getJson(route('spare_part_reservations.for_reclamation', $reclamation->id)); $response->assertStatus(200); $response->assertJsonStructure(['reservations']); } public function test_returns_only_active_reservations_for_reclamation(): void { $sparePart = SparePart::factory()->create(); $order = SparePartOrder::factory()->inStock()->withQuantity(20)->forSparePart($sparePart)->create(); $reclamation = Reclamation::factory()->create(); // Активный резерв Reservation::factory()->create([ 'spare_part_id' => $sparePart->id, 'spare_part_order_id' => $order->id, 'reclamation_id' => $reclamation->id, 'reserved_qty' => 5, 'status' => Reservation::STATUS_ACTIVE, ]); // Отменённый резерв — не должен попасть Reservation::factory()->create([ 'spare_part_id' => $sparePart->id, 'spare_part_order_id' => $order->id, 'reclamation_id' => $reclamation->id, 'reserved_qty' => 2, 'status' => Reservation::STATUS_CANCELLED, ]); $response = $this->actingAs($this->adminUser) ->getJson(route('spare_part_reservations.for_reclamation', $reclamation->id)); $response->assertStatus(200); $this->assertCount(1, $response->json('reservations')); } // ==================== shortagesForReclamation (JSON API) ==================== public function test_guest_cannot_get_shortages_for_reclamation(): void { $reclamation = Reclamation::factory()->create(); $response = $this->get(route('spare_part_reservations.shortages_for_reclamation', $reclamation->id)); $response->assertRedirect(route('login')); } public function test_admin_can_get_shortages_for_reclamation(): void { $sparePart = SparePart::factory()->create(['article' => 'SP-SHORTAGE-001']); $reclamation = Reclamation::factory()->create(); Shortage::factory()->create([ 'spare_part_id' => $sparePart->id, 'reclamation_id' => $reclamation->id, 'required_qty' => 10, 'reserved_qty' => 3, 'missing_qty' => 7, 'status' => Shortage::STATUS_OPEN, ]); $response = $this->actingAs($this->adminUser) ->getJson(route('spare_part_reservations.shortages_for_reclamation', $reclamation->id)); $response->assertStatus(200); $response->assertJsonStructure([ 'shortages' => [['id', 'spare_part_id', 'article', 'required_qty', 'missing_qty', 'status']], ]); $response->assertJsonFragment(['spare_part_id' => $sparePart->id]); } public function test_manager_can_get_shortages_for_reclamation(): void { $reclamation = Reclamation::factory()->create(); $response = $this->actingAs($this->managerUser) ->getJson(route('spare_part_reservations.shortages_for_reclamation', $reclamation->id)); $response->assertStatus(200); $response->assertJsonStructure(['shortages']); } // ==================== cancel ==================== public function test_guest_cannot_cancel_reservation(): void { $reservation = Reservation::factory()->active()->create(); $response = $this->post(route('spare_part_reservations.cancel', $reservation)); $response->assertRedirect(route('login')); } public function test_admin_can_cancel_active_reservation(): void { $sparePart = SparePart::factory()->create(); $order = SparePartOrder::factory()->inStock()->withQuantity(10)->forSparePart($sparePart)->create(); $reclamation = Reclamation::factory()->create(); $reservation = Reservation::factory()->create([ 'spare_part_id' => $sparePart->id, 'spare_part_order_id' => $order->id, 'reclamation_id' => $reclamation->id, 'reserved_qty' => 5, 'status' => Reservation::STATUS_ACTIVE, ]); $response = $this->actingAs($this->adminUser) ->post(route('spare_part_reservations.cancel', $reservation), [ 'reason' => 'Тестовая отмена', ]); $response->assertRedirect(); $response->assertSessionHas('success'); $this->assertDatabaseHas('reservations', [ 'id' => $reservation->id, 'status' => Reservation::STATUS_CANCELLED, ]); } public function test_manager_can_cancel_active_reservation(): void { $sparePart = SparePart::factory()->create(); $order = SparePartOrder::factory()->inStock()->withQuantity(10)->forSparePart($sparePart)->create(); $reclamation = Reclamation::factory()->create(); $reservation = Reservation::factory()->create([ 'spare_part_id' => $sparePart->id, 'spare_part_order_id' => $order->id, 'reclamation_id' => $reclamation->id, 'reserved_qty' => 3, 'status' => Reservation::STATUS_ACTIVE, ]); $response = $this->actingAs($this->managerUser) ->post(route('spare_part_reservations.cancel', $reservation)); $response->assertRedirect(); $response->assertSessionHas('success'); } public function test_cannot_cancel_inactive_reservation(): void { $reservation = Reservation::factory()->cancelled()->create(); $response = $this->actingAs($this->adminUser) ->post(route('spare_part_reservations.cancel', $reservation)); $response->assertRedirect(); $response->assertSessionHas('error'); // Статус не изменился $this->assertDatabaseHas('reservations', [ 'id' => $reservation->id, 'status' => Reservation::STATUS_CANCELLED, ]); } public function test_cannot_cancel_issued_reservation(): void { $reservation = Reservation::factory()->issued()->create(); $response = $this->actingAs($this->adminUser) ->post(route('spare_part_reservations.cancel', $reservation)); $response->assertRedirect(); $response->assertSessionHas('error'); $this->assertDatabaseHas('reservations', [ 'id' => $reservation->id, 'status' => Reservation::STATUS_ISSUED, ]); } // ==================== issue ==================== public function test_guest_cannot_issue_reservation(): void { $reservation = Reservation::factory()->active()->create(); $response = $this->post(route('spare_part_reservations.issue', $reservation)); $response->assertRedirect(route('login')); } public function test_admin_can_issue_active_reservation(): void { $sparePart = SparePart::factory()->create(); $order = SparePartOrder::factory()->inStock()->forSparePart($sparePart)->create([ 'ordered_quantity' => 20, 'available_qty' => 20, ]); $reclamation = Reclamation::factory()->create(); $reservation = Reservation::factory()->create([ 'spare_part_id' => $sparePart->id, 'spare_part_order_id' => $order->id, 'reclamation_id' => $reclamation->id, 'reserved_qty' => 5, 'status' => Reservation::STATUS_ACTIVE, ]); $response = $this->actingAs($this->adminUser) ->post(route('spare_part_reservations.issue', $reservation), [ 'note' => 'Тестовое списание', ]); $response->assertRedirect(); $response->assertSessionHas('success'); $this->assertDatabaseHas('reservations', [ 'id' => $reservation->id, 'status' => Reservation::STATUS_ISSUED, ]); $this->assertDatabaseHas('spare_part_orders', [ 'id' => $order->id, 'available_qty' => 15, ]); } public function test_manager_can_issue_active_reservation(): void { $sparePart = SparePart::factory()->create(); $order = SparePartOrder::factory()->inStock()->forSparePart($sparePart)->create([ 'ordered_quantity' => 10, 'available_qty' => 10, ]); $reclamation = Reclamation::factory()->create(); $reservation = Reservation::factory()->create([ 'spare_part_id' => $sparePart->id, 'spare_part_order_id' => $order->id, 'reclamation_id' => $reclamation->id, 'reserved_qty' => 3, 'status' => Reservation::STATUS_ACTIVE, ]); $response = $this->actingAs($this->managerUser) ->post(route('spare_part_reservations.issue', $reservation)); $response->assertRedirect(); $response->assertSessionHas('success'); } public function test_cannot_issue_inactive_reservation(): void { $reservation = Reservation::factory()->cancelled()->create(); $response = $this->actingAs($this->adminUser) ->post(route('spare_part_reservations.issue', $reservation)); $response->assertRedirect(); $response->assertSessionHas('error'); } // ==================== issueAllForReclamation ==================== public function test_guest_cannot_issue_all_for_reclamation(): void { $reclamation = Reclamation::factory()->create(); $response = $this->post(route('spare_part_reservations.issue_all', $reclamation->id)); $response->assertRedirect(route('login')); } public function test_admin_can_issue_all_for_reclamation(): void { $sparePart = SparePart::factory()->create(); $order = SparePartOrder::factory()->inStock()->forSparePart($sparePart)->create([ 'ordered_quantity' => 50, 'available_qty' => 50, ]); $reclamation = Reclamation::factory()->create(); // Два активных резерва для рекламации Reservation::factory()->create([ 'spare_part_id' => $sparePart->id, 'spare_part_order_id' => $order->id, 'reclamation_id' => $reclamation->id, 'reserved_qty' => 5, 'status' => Reservation::STATUS_ACTIVE, ]); Reservation::factory()->create([ 'spare_part_id' => $sparePart->id, 'spare_part_order_id' => $order->id, 'reclamation_id' => $reclamation->id, 'reserved_qty' => 3, 'status' => Reservation::STATUS_ACTIVE, ]); $response = $this->actingAs($this->adminUser) ->post(route('spare_part_reservations.issue_all', $reclamation->id)); $response->assertRedirect(); $response->assertSessionHas('success'); // Все резервы рекламации должны быть списаны $this->assertDatabaseMissing('reservations', [ 'reclamation_id' => $reclamation->id, 'status' => Reservation::STATUS_ACTIVE, ]); } public function test_manager_can_issue_all_for_reclamation(): void { $reclamation = Reclamation::factory()->create(); $response = $this->actingAs($this->managerUser) ->post(route('spare_part_reservations.issue_all', $reclamation->id)); $response->assertRedirect(); $response->assertSessionHas('success'); } // ==================== cancelAllForReclamation ==================== public function test_guest_cannot_cancel_all_for_reclamation(): void { $reclamation = Reclamation::factory()->create(); $response = $this->post(route('spare_part_reservations.cancel_all', $reclamation->id)); $response->assertRedirect(route('login')); } public function test_admin_can_cancel_all_for_reclamation(): void { $sparePart = SparePart::factory()->create(); $order = SparePartOrder::factory()->inStock()->withQuantity(30)->forSparePart($sparePart)->create(); $reclamation = Reclamation::factory()->create(); Reservation::factory()->create([ 'spare_part_id' => $sparePart->id, 'spare_part_order_id' => $order->id, 'reclamation_id' => $reclamation->id, 'reserved_qty' => 4, 'status' => Reservation::STATUS_ACTIVE, ]); Reservation::factory()->create([ 'spare_part_id' => $sparePart->id, 'spare_part_order_id' => $order->id, 'reclamation_id' => $reclamation->id, 'reserved_qty' => 6, 'status' => Reservation::STATUS_ACTIVE, ]); $response = $this->actingAs($this->adminUser) ->post(route('spare_part_reservations.cancel_all', $reclamation->id), [ 'reason' => 'Тестовая массовая отмена', ]); $response->assertRedirect(); $response->assertSessionHas('success'); // Все активные резервы рекламации отменены $this->assertDatabaseMissing('reservations', [ 'reclamation_id' => $reclamation->id, 'status' => Reservation::STATUS_ACTIVE, ]); } public function test_manager_can_cancel_all_for_reclamation(): void { $reclamation = Reclamation::factory()->create(); $response = $this->actingAs($this->managerUser) ->post(route('spare_part_reservations.cancel_all', $reclamation->id)); $response->assertRedirect(); $response->assertSessionHas('success'); } public function test_cancel_all_includes_success_message_with_quantity(): void { $sparePart = SparePart::factory()->create(); $order = SparePartOrder::factory()->inStock()->withQuantity(20)->forSparePart($sparePart)->create(); $reclamation = Reclamation::factory()->create(); Reservation::factory()->create([ 'spare_part_id' => $sparePart->id, 'spare_part_order_id' => $order->id, 'reclamation_id' => $reclamation->id, 'reserved_qty' => 7, 'status' => Reservation::STATUS_ACTIVE, ]); $response = $this->actingAs($this->adminUser) ->post(route('spare_part_reservations.cancel_all', $reclamation->id)); $response->assertSessionHas('success'); $this->assertStringContainsString('7', session('success')); } }