service = new SparePartInventoryService($reservationService, $shortageService); } // ==================== getCriticalShortages ==================== public function test_get_critical_shortages_returns_spare_parts_with_open_shortages(): void { // Arrange $sparePart1 = SparePart::factory()->create(); $sparePart2 = SparePart::factory()->create(); $reclamation = Reclamation::factory()->create(); Shortage::factory() ->open() ->withQuantities(5, 0) ->forSparePart($sparePart1) ->forReclamation($reclamation) ->create(); Shortage::factory() ->closed() ->forSparePart($sparePart2) ->forReclamation($reclamation) ->create(); // Act $criticalShortages = $this->service->getCriticalShortages(); // Assert - returns SparePart objects, not Shortages $this->assertCount(1, $criticalShortages); $this->assertEquals($sparePart1->id, $criticalShortages->first()->id); } public function test_get_critical_shortages_returns_empty_when_no_shortages(): void { // Act $criticalShortages = $this->service->getCriticalShortages(); // Assert $this->assertTrue($criticalShortages->isEmpty()); } // ==================== getBelowMinStock ==================== public function test_get_below_min_stock_returns_parts_below_minimum(): void { // Arrange $sparePart1 = SparePart::factory()->create(['min_stock' => 10]); $sparePart2 = SparePart::factory()->create(['min_stock' => 5]); // Create stock for sparePart1 - 3 items (below min 10) SparePartOrder::factory() ->inStock() ->withQuantity(3) ->forSparePart($sparePart1) ->create(); // Create stock for sparePart2 - 10 items (above min 5) SparePartOrder::factory() ->inStock() ->withQuantity(10) ->forSparePart($sparePart2) ->create(); // Act $belowMin = $this->service->getBelowMinStock(); // Assert $this->assertTrue($belowMin->contains('id', $sparePart1->id)); $this->assertFalse($belowMin->contains('id', $sparePart2->id)); } public function test_get_below_min_stock_considers_reservations(): void { // Arrange $sparePart = SparePart::factory()->create(['min_stock' => 5]); $reclamation = Reclamation::factory()->create(); $order = SparePartOrder::factory() ->inStock() ->withQuantity(10) ->forSparePart($sparePart) ->create(); // Reserve 7 items - leaves only 3 free (below min 5) Reservation::factory() ->active() ->withQuantity(7) ->fromOrder($order) ->forReclamation($reclamation) ->create(); // Act $belowMin = $this->service->getBelowMinStock(); // Assert $this->assertTrue($belowMin->contains('id', $sparePart->id)); } // ==================== calculateOrderQuantity ==================== public function test_calculate_order_quantity_returns_missing_from_shortages(): void { // Arrange $sparePart = SparePart::factory()->create(); $reclamation1 = Reclamation::factory()->create(); $reclamation2 = Reclamation::factory()->create(); // Create two shortages without documents Shortage::factory() ->open() ->withQuantities(5, 2) // missing 3 ->forSparePart($sparePart) ->forReclamation($reclamation1) ->withDocuments(false) ->create(); Shortage::factory() ->open() ->withQuantities(4, 1) // missing 3 ->forSparePart($sparePart) ->forReclamation($reclamation2) ->withDocuments(false) ->create(); // Act $orderQty = $this->service->calculateOrderQuantity($sparePart, false); // Assert $this->assertEquals(6, $orderQty); // 3 + 3 } public function test_calculate_order_quantity_respects_with_documents_flag(): void { // Arrange $sparePart = SparePart::factory()->create(); $reclamation = Reclamation::factory()->create(); Shortage::factory() ->open() ->withQuantities(5, 0) // missing 5 WITHOUT docs ->forSparePart($sparePart) ->forReclamation($reclamation) ->withDocuments(false) ->create(); Shortage::factory() ->open() ->withQuantities(3, 0) // missing 3 WITH docs ->forSparePart($sparePart) ->forReclamation($reclamation) ->withDocuments(true) ->create(); // Act $orderQtyWithDocs = $this->service->calculateOrderQuantity($sparePart, true); $orderQtyWithoutDocs = $this->service->calculateOrderQuantity($sparePart, false); // Assert $this->assertEquals(3, $orderQtyWithDocs); $this->assertEquals(5, $orderQtyWithoutDocs); } // ==================== calculateMinStockOrderQuantity ==================== public function test_calculate_min_stock_order_quantity_returns_difference_to_min(): void { // Arrange $sparePart = SparePart::factory()->create(['min_stock' => 15]); SparePartOrder::factory() ->inStock() ->withQuantity(10) ->forSparePart($sparePart) ->create(); // Act $orderQty = $this->service->calculateMinStockOrderQuantity($sparePart); // Assert $this->assertEquals(5, $orderQty); // 15 - 10 = 5 } public function test_calculate_min_stock_order_quantity_returns_zero_when_above_min(): void { // Arrange $sparePart = SparePart::factory()->create(['min_stock' => 5]); SparePartOrder::factory() ->inStock() ->withQuantity(10) ->forSparePart($sparePart) ->create(); // Act $orderQty = $this->service->calculateMinStockOrderQuantity($sparePart); // Assert $this->assertEquals(0, $orderQty); } public function test_calculate_min_stock_order_quantity_considers_reservations(): void { // Arrange $sparePart = SparePart::factory()->create(['min_stock' => 10]); $reclamation = Reclamation::factory()->create(); $order = SparePartOrder::factory() ->inStock() ->withQuantity(15) ->forSparePart($sparePart) ->create(); // Reserve 8 items - leaves 7 free Reservation::factory() ->active() ->withQuantity(8) ->fromOrder($order) ->forReclamation($reclamation) ->create(); // Act $orderQty = $this->service->calculateMinStockOrderQuantity($sparePart); // Assert $this->assertEquals(3, $orderQty); // 10 - 7 = 3 } // ==================== getStockInfo ==================== public function test_get_stock_info_returns_complete_information(): void { // Arrange $sparePart = SparePart::factory()->create(['min_stock' => 10]); $reclamation = Reclamation::factory()->create(); $orderWithDocs = SparePartOrder::factory() ->inStock() ->withDocuments(true) ->withQuantity(20) ->forSparePart($sparePart) ->create(); $orderWithoutDocs = SparePartOrder::factory() ->inStock() ->withDocuments(false) ->withQuantity(15) ->forSparePart($sparePart) ->create(); // Reserve some items Reservation::factory() ->active() ->withQuantity(5) ->withDocuments(true) ->fromOrder($orderWithDocs) ->forReclamation($reclamation) ->create(); // Act $info = $this->service->getStockInfo($sparePart->fresh()); // Assert $this->assertEquals($sparePart->id, $info['spare_part_id']); $this->assertEquals($sparePart->article, $info['article']); // Physical: 20 with docs + 15 without = 35 total $this->assertEquals(15, $info['physical_without_docs']); $this->assertEquals(20, $info['physical_with_docs']); $this->assertEquals(35, $info['physical_total']); // Reserved: 5 with docs $this->assertEquals(0, $info['reserved_without_docs']); $this->assertEquals(5, $info['reserved_with_docs']); $this->assertEquals(5, $info['reserved_total']); // Free: 35 - 5 = 30 $this->assertEquals(15, $info['free_without_docs']); $this->assertEquals(15, $info['free_with_docs']); // 20 - 5 $this->assertEquals(30, $info['free_total']); $this->assertEquals(10, $info['min_stock']); $this->assertFalse($info['below_min_stock']); // 30 > 10 } // ==================== getInventorySummary ==================== public function test_get_inventory_summary_returns_aggregated_data(): void { // Arrange $sparePart1 = SparePart::factory()->create(['min_stock' => 100]); // will be below min $sparePart2 = SparePart::factory()->create(['min_stock' => 5]); $reclamation = Reclamation::factory()->create(); SparePartOrder::factory() ->inStock() ->withQuantity(10) ->forSparePart($sparePart1) ->create(); $order2 = SparePartOrder::factory() ->inStock() ->withQuantity(20) ->forSparePart($sparePart2) ->create(); // Create shortage for sparePart1 Shortage::factory() ->open() ->withQuantities(5, 0) ->forSparePart($sparePart1) ->forReclamation($reclamation) ->create(); // Create reservation for sparePart2 Reservation::factory() ->active() ->withQuantity(3) ->fromOrder($order2) ->forReclamation($reclamation) ->create(); // Act $summary = $this->service->getInventorySummary(); // Assert $this->assertEquals(2, $summary['total_spare_parts']); $this->assertEquals(1, $summary['critical_shortages_count']); $this->assertEquals(1, $summary['below_min_stock_count']); $this->assertEquals(30, $summary['total_physical_stock']); // 10 + 20 $this->assertEquals(3, $summary['total_reserved']); $this->assertEquals(27, $summary['total_free']); // 30 - 3 } // ==================== getReservationsForReclamation ==================== public function test_get_reservations_for_reclamation_returns_active_reservations(): void { // Arrange $sparePart = SparePart::factory()->create(); $reclamation = Reclamation::factory()->create(); $order = SparePartOrder::factory() ->inStock() ->forSparePart($sparePart) ->create(); Reservation::factory() ->active() ->withQuantity(5) ->fromOrder($order) ->forReclamation($reclamation) ->create(); Reservation::factory() ->cancelled() ->fromOrder($order) ->forReclamation($reclamation) ->create(); // Act $reservations = $this->service->getReservationsForReclamation($reclamation->id); // Assert $this->assertCount(1, $reservations); $this->assertEquals(5, $reservations->first()->reserved_qty); } // ==================== getShortagesForReclamation ==================== public function test_get_shortages_for_reclamation_returns_all_shortages(): void { // Arrange $sparePart1 = SparePart::factory()->create(); $sparePart2 = SparePart::factory()->create(); $reclamation = Reclamation::factory()->create(); $otherReclamation = Reclamation::factory()->create(); Shortage::factory() ->open() ->forSparePart($sparePart1) ->forReclamation($reclamation) ->create(); Shortage::factory() ->closed() ->forSparePart($sparePart2) ->forReclamation($reclamation) ->create(); Shortage::factory() ->open() ->forSparePart($sparePart1) ->forReclamation($otherReclamation) ->create(); // Act $shortages = $this->service->getShortagesForReclamation($reclamation->id); // Assert $this->assertCount(2, $shortages); } // ==================== Deprecated methods ==================== public function test_reserve_for_reclamation_delegates_to_reservation_service(): void { // Arrange $sparePart = SparePart::factory()->create(['article' => 'TEST-001']); $reclamation = Reclamation::factory()->create(); SparePartOrder::factory() ->inStock() ->withDocuments(false) ->withQuantity(10) ->forSparePart($sparePart) ->create(); // Act $result = $this->service->reserveForReclamation( 'TEST-001', 5, false, $reclamation->id ); // Assert $this->assertEquals(5, $result->reserved); $this->assertEquals(0, $result->missing); } public function test_reserve_for_reclamation_returns_zero_for_unknown_article(): void { // Arrange $reclamation = Reclamation::factory()->create(); // Act $result = $this->service->reserveForReclamation( 'UNKNOWN-ARTICLE', 5, false, $reclamation->id ); // Assert $this->assertEquals(0, $result->reserved); $this->assertEquals(5, $result->missing); } }