|
@@ -0,0 +1,521 @@
|
|
|
|
|
+<?php
|
|
|
|
|
+
|
|
|
|
|
+namespace Tests\Unit\Services;
|
|
|
|
|
+
|
|
|
|
|
+use App\Models\InventoryMovement;
|
|
|
|
|
+use App\Models\Reclamation;
|
|
|
|
|
+use App\Models\Reservation;
|
|
|
|
|
+use App\Models\Shortage;
|
|
|
|
|
+use App\Models\SparePart;
|
|
|
|
|
+use App\Models\SparePartOrder;
|
|
|
|
|
+use App\Services\ReservationResult;
|
|
|
|
|
+use App\Services\ShortageService;
|
|
|
|
|
+use App\Services\SparePartReservationService;
|
|
|
|
|
+use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
|
|
|
+use Tests\TestCase;
|
|
|
|
|
+
|
|
|
|
|
+class SparePartReservationServiceTest extends TestCase
|
|
|
|
|
+{
|
|
|
|
|
+ use RefreshDatabase;
|
|
|
|
|
+
|
|
|
|
|
+ private SparePartReservationService $service;
|
|
|
|
|
+ private ShortageService $shortageService;
|
|
|
|
|
+
|
|
|
|
|
+ protected function setUp(): void
|
|
|
|
|
+ {
|
|
|
|
|
+ parent::setUp();
|
|
|
|
|
+
|
|
|
|
|
+ $this->shortageService = new ShortageService();
|
|
|
|
|
+ $this->service = new SparePartReservationService($this->shortageService);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public function test_reserve_with_sufficient_stock_creates_reservation(): void
|
|
|
|
|
+ {
|
|
|
|
|
+ // Arrange
|
|
|
|
|
+ $sparePart = SparePart::factory()->create();
|
|
|
|
|
+ $reclamation = Reclamation::factory()->create();
|
|
|
|
|
+ $order = SparePartOrder::factory()
|
|
|
|
|
+ ->inStock()
|
|
|
|
|
+ ->withDocuments(false)
|
|
|
|
|
+ ->withQuantity(10)
|
|
|
|
|
+ ->forSparePart($sparePart)
|
|
|
|
|
+ ->create();
|
|
|
|
|
+
|
|
|
|
|
+ // Act
|
|
|
|
|
+ $result = $this->service->reserve(
|
|
|
|
|
+ sparePartId: $sparePart->id,
|
|
|
|
|
+ quantity: 5,
|
|
|
|
|
+ withDocuments: false,
|
|
|
|
|
+ reclamationId: $reclamation->id
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ // Assert
|
|
|
|
|
+ $this->assertInstanceOf(ReservationResult::class, $result);
|
|
|
|
|
+ $this->assertEquals(5, $result->reserved);
|
|
|
|
|
+ $this->assertEquals(0, $result->missing);
|
|
|
|
|
+ $this->assertTrue($result->isFullyReserved());
|
|
|
|
|
+ $this->assertFalse($result->hasShortage());
|
|
|
|
|
+ $this->assertCount(1, $result->reservations);
|
|
|
|
|
+
|
|
|
|
|
+ // Check reservation was created in DB
|
|
|
|
|
+ $this->assertDatabaseHas('reservations', [
|
|
|
|
|
+ 'spare_part_id' => $sparePart->id,
|
|
|
|
|
+ 'spare_part_order_id' => $order->id,
|
|
|
|
|
+ 'reclamation_id' => $reclamation->id,
|
|
|
|
|
+ 'reserved_qty' => 5,
|
|
|
|
|
+ 'with_documents' => false,
|
|
|
|
|
+ 'status' => Reservation::STATUS_ACTIVE,
|
|
|
|
|
+ ]);
|
|
|
|
|
+
|
|
|
|
|
+ // Check inventory movement was created
|
|
|
|
|
+ $this->assertDatabaseHas('inventory_movements', [
|
|
|
|
|
+ 'spare_part_id' => $sparePart->id,
|
|
|
|
|
+ 'spare_part_order_id' => $order->id,
|
|
|
|
|
+ 'qty' => 5,
|
|
|
|
|
+ 'movement_type' => InventoryMovement::TYPE_RESERVE,
|
|
|
|
|
+ 'source_type' => InventoryMovement::SOURCE_RECLAMATION,
|
|
|
|
|
+ 'source_id' => $reclamation->id,
|
|
|
|
|
+ ]);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public function test_reserve_with_partial_stock_creates_shortage(): void
|
|
|
|
|
+ {
|
|
|
|
|
+ // Arrange
|
|
|
|
|
+ $sparePart = SparePart::factory()->create();
|
|
|
|
|
+ $reclamation = Reclamation::factory()->create();
|
|
|
|
|
+ SparePartOrder::factory()
|
|
|
|
|
+ ->inStock()
|
|
|
|
|
+ ->withDocuments(false)
|
|
|
|
|
+ ->withQuantity(3)
|
|
|
|
|
+ ->forSparePart($sparePart)
|
|
|
|
|
+ ->create();
|
|
|
|
|
+
|
|
|
|
|
+ // Act
|
|
|
|
|
+ $result = $this->service->reserve(
|
|
|
|
|
+ sparePartId: $sparePart->id,
|
|
|
|
|
+ quantity: 5,
|
|
|
|
|
+ withDocuments: false,
|
|
|
|
|
+ reclamationId: $reclamation->id
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ // Assert
|
|
|
|
|
+ $this->assertEquals(3, $result->reserved);
|
|
|
|
|
+ $this->assertEquals(2, $result->missing);
|
|
|
|
|
+ $this->assertFalse($result->isFullyReserved());
|
|
|
|
|
+ $this->assertTrue($result->hasShortage());
|
|
|
|
|
+
|
|
|
|
|
+ // Check shortage was created
|
|
|
|
|
+ $this->assertDatabaseHas('shortages', [
|
|
|
|
|
+ 'spare_part_id' => $sparePart->id,
|
|
|
|
|
+ 'reclamation_id' => $reclamation->id,
|
|
|
|
|
+ 'with_documents' => false,
|
|
|
|
|
+ 'required_qty' => 5,
|
|
|
|
|
+ 'reserved_qty' => 3,
|
|
|
|
|
+ 'missing_qty' => 2,
|
|
|
|
|
+ 'status' => Shortage::STATUS_OPEN,
|
|
|
|
|
+ ]);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public function test_reserve_with_no_stock_creates_full_shortage(): void
|
|
|
|
|
+ {
|
|
|
|
|
+ // Arrange
|
|
|
|
|
+ $sparePart = SparePart::factory()->create();
|
|
|
|
|
+ $reclamation = Reclamation::factory()->create();
|
|
|
|
|
+ // No orders created = no stock
|
|
|
|
|
+
|
|
|
|
|
+ // Act
|
|
|
|
|
+ $result = $this->service->reserve(
|
|
|
|
|
+ sparePartId: $sparePart->id,
|
|
|
|
|
+ quantity: 5,
|
|
|
|
|
+ withDocuments: false,
|
|
|
|
|
+ reclamationId: $reclamation->id
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ // Assert
|
|
|
|
|
+ $this->assertEquals(0, $result->reserved);
|
|
|
|
|
+ $this->assertEquals(5, $result->missing);
|
|
|
|
|
+ $this->assertFalse($result->isFullyReserved());
|
|
|
|
|
+ $this->assertTrue($result->hasShortage());
|
|
|
|
|
+ $this->assertEmpty($result->reservations);
|
|
|
|
|
+
|
|
|
|
|
+ // Check full shortage was created
|
|
|
|
|
+ $this->assertDatabaseHas('shortages', [
|
|
|
|
|
+ 'spare_part_id' => $sparePart->id,
|
|
|
|
|
+ 'reclamation_id' => $reclamation->id,
|
|
|
|
|
+ 'required_qty' => 5,
|
|
|
|
|
+ 'reserved_qty' => 0,
|
|
|
|
|
+ 'missing_qty' => 5,
|
|
|
|
|
+ 'status' => Shortage::STATUS_OPEN,
|
|
|
|
|
+ ]);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public function test_reserve_respects_fifo_order_for_multiple_batches(): void
|
|
|
|
|
+ {
|
|
|
|
|
+ // Arrange
|
|
|
|
|
+ $sparePart = SparePart::factory()->create();
|
|
|
|
|
+ $reclamation = Reclamation::factory()->create();
|
|
|
|
|
+
|
|
|
|
|
+ // Create first batch (older)
|
|
|
|
|
+ $olderOrder = SparePartOrder::factory()
|
|
|
|
|
+ ->inStock()
|
|
|
|
|
+ ->withDocuments(false)
|
|
|
|
|
+ ->withQuantity(3)
|
|
|
|
|
+ ->forSparePart($sparePart)
|
|
|
|
|
+ ->create(['created_at' => now()->subDays(2)]);
|
|
|
|
|
+
|
|
|
|
|
+ // Create second batch (newer)
|
|
|
|
|
+ $newerOrder = SparePartOrder::factory()
|
|
|
|
|
+ ->inStock()
|
|
|
|
|
+ ->withDocuments(false)
|
|
|
|
|
+ ->withQuantity(5)
|
|
|
|
|
+ ->forSparePart($sparePart)
|
|
|
|
|
+ ->create(['created_at' => now()->subDay()]);
|
|
|
|
|
+
|
|
|
|
|
+ // Act - reserve 5 items (should use 3 from older + 2 from newer)
|
|
|
|
|
+ $result = $this->service->reserve(
|
|
|
|
|
+ sparePartId: $sparePart->id,
|
|
|
|
|
+ quantity: 5,
|
|
|
|
|
+ withDocuments: false,
|
|
|
|
|
+ reclamationId: $reclamation->id
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ // Assert
|
|
|
|
|
+ $this->assertEquals(5, $result->reserved);
|
|
|
|
|
+ $this->assertCount(2, $result->reservations);
|
|
|
|
|
+
|
|
|
|
|
+ // Check FIFO: first batch fully used
|
|
|
|
|
+ $this->assertDatabaseHas('reservations', [
|
|
|
|
|
+ 'spare_part_order_id' => $olderOrder->id,
|
|
|
|
|
+ 'reserved_qty' => 3,
|
|
|
|
|
+ ]);
|
|
|
|
|
+
|
|
|
|
|
+ // Check FIFO: second batch partially used
|
|
|
|
|
+ $this->assertDatabaseHas('reservations', [
|
|
|
|
|
+ 'spare_part_order_id' => $newerOrder->id,
|
|
|
|
|
+ 'reserved_qty' => 2,
|
|
|
|
|
+ ]);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public function test_reserve_respects_with_documents_flag(): void
|
|
|
|
|
+ {
|
|
|
|
|
+ // Arrange
|
|
|
|
|
+ $sparePart = SparePart::factory()->create();
|
|
|
|
|
+ $reclamation = Reclamation::factory()->create();
|
|
|
|
|
+
|
|
|
|
|
+ // Create batch WITHOUT documents
|
|
|
|
|
+ SparePartOrder::factory()
|
|
|
|
|
+ ->inStock()
|
|
|
|
|
+ ->withDocuments(false)
|
|
|
|
|
+ ->withQuantity(10)
|
|
|
|
|
+ ->forSparePart($sparePart)
|
|
|
|
|
+ ->create();
|
|
|
|
|
+
|
|
|
|
|
+ // Create batch WITH documents
|
|
|
|
|
+ $orderWithDocs = SparePartOrder::factory()
|
|
|
|
|
+ ->inStock()
|
|
|
|
|
+ ->withDocuments(true)
|
|
|
|
|
+ ->withQuantity(10)
|
|
|
|
|
+ ->forSparePart($sparePart)
|
|
|
|
|
+ ->create();
|
|
|
|
|
+
|
|
|
|
|
+ // Act - reserve WITH documents
|
|
|
|
|
+ $result = $this->service->reserve(
|
|
|
|
|
+ sparePartId: $sparePart->id,
|
|
|
|
|
+ quantity: 5,
|
|
|
|
|
+ withDocuments: true,
|
|
|
|
|
+ reclamationId: $reclamation->id
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ // Assert - should only use batch with documents
|
|
|
|
|
+ $this->assertEquals(5, $result->reserved);
|
|
|
|
|
+ $this->assertCount(1, $result->reservations);
|
|
|
|
|
+
|
|
|
|
|
+ $this->assertDatabaseHas('reservations', [
|
|
|
|
|
+ 'spare_part_order_id' => $orderWithDocs->id,
|
|
|
|
|
+ 'with_documents' => true,
|
|
|
|
|
+ ]);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public function test_reserve_considers_existing_reservations(): void
|
|
|
|
|
+ {
|
|
|
|
|
+ // Arrange
|
|
|
|
|
+ $sparePart = SparePart::factory()->create();
|
|
|
|
|
+ $reclamation1 = Reclamation::factory()->create();
|
|
|
|
|
+ $reclamation2 = Reclamation::factory()->create();
|
|
|
|
|
+
|
|
|
|
|
+ $order = SparePartOrder::factory()
|
|
|
|
|
+ ->inStock()
|
|
|
|
|
+ ->withDocuments(false)
|
|
|
|
|
+ ->withQuantity(10)
|
|
|
|
|
+ ->forSparePart($sparePart)
|
|
|
|
|
+ ->create();
|
|
|
|
|
+
|
|
|
|
|
+ // Create existing active reservation for 7 items
|
|
|
|
|
+ Reservation::factory()
|
|
|
|
|
+ ->active()
|
|
|
|
|
+ ->withQuantity(7)
|
|
|
|
|
+ ->fromOrder($order)
|
|
|
|
|
+ ->forReclamation($reclamation1)
|
|
|
|
|
+ ->create();
|
|
|
|
|
+
|
|
|
|
|
+ // Act - try to reserve 5 more (only 3 available)
|
|
|
|
|
+ $result = $this->service->reserve(
|
|
|
|
|
+ sparePartId: $sparePart->id,
|
|
|
|
|
+ quantity: 5,
|
|
|
|
|
+ withDocuments: false,
|
|
|
|
|
+ reclamationId: $reclamation2->id
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ // Assert
|
|
|
|
|
+ $this->assertEquals(3, $result->reserved);
|
|
|
|
|
+ $this->assertEquals(2, $result->missing);
|
|
|
|
|
+ $this->assertTrue($result->hasShortage());
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public function test_cancel_for_reclamation_cancels_all_reservations(): void
|
|
|
|
|
+ {
|
|
|
|
|
+ // Arrange
|
|
|
|
|
+ $sparePart = SparePart::factory()->create();
|
|
|
|
|
+ $reclamation = Reclamation::factory()->create();
|
|
|
|
|
+ $order = SparePartOrder::factory()
|
|
|
|
|
+ ->inStock()
|
|
|
|
|
+ ->withDocuments(false)
|
|
|
|
|
+ ->withQuantity(10)
|
|
|
|
|
+ ->forSparePart($sparePart)
|
|
|
|
|
+ ->create();
|
|
|
|
|
+
|
|
|
|
|
+ // Create reservations
|
|
|
|
|
+ Reservation::factory()
|
|
|
|
|
+ ->active()
|
|
|
|
|
+ ->withQuantity(3)
|
|
|
|
|
+ ->fromOrder($order)
|
|
|
|
|
+ ->forReclamation($reclamation)
|
|
|
|
|
+ ->create();
|
|
|
|
|
+
|
|
|
|
|
+ Reservation::factory()
|
|
|
|
|
+ ->active()
|
|
|
|
|
+ ->withQuantity(2)
|
|
|
|
|
+ ->fromOrder($order)
|
|
|
|
|
+ ->forReclamation($reclamation)
|
|
|
|
|
+ ->create();
|
|
|
|
|
+
|
|
|
|
|
+ // Create shortage
|
|
|
|
|
+ Shortage::factory()
|
|
|
|
|
+ ->open()
|
|
|
|
|
+ ->withQuantities(10, 5)
|
|
|
|
|
+ ->forSparePart($sparePart)
|
|
|
|
|
+ ->forReclamation($reclamation)
|
|
|
|
|
+ ->withDocuments(false)
|
|
|
|
|
+ ->create();
|
|
|
|
|
+
|
|
|
|
|
+ // Act
|
|
|
|
|
+ $cancelled = $this->service->cancelForReclamation($reclamation->id);
|
|
|
|
|
+
|
|
|
|
|
+ // Assert
|
|
|
|
|
+ $this->assertEquals(5, $cancelled);
|
|
|
|
|
+
|
|
|
|
|
+ // Check all reservations are cancelled
|
|
|
|
|
+ $this->assertDatabaseMissing('reservations', [
|
|
|
|
|
+ 'reclamation_id' => $reclamation->id,
|
|
|
|
|
+ 'status' => Reservation::STATUS_ACTIVE,
|
|
|
|
|
+ ]);
|
|
|
|
|
+
|
|
|
|
|
+ $this->assertEquals(2, Reservation::where('reclamation_id', $reclamation->id)
|
|
|
|
|
+ ->where('status', Reservation::STATUS_CANCELLED)->count());
|
|
|
|
|
+
|
|
|
|
|
+ // Check shortage is closed
|
|
|
|
|
+ $this->assertDatabaseHas('shortages', [
|
|
|
|
|
+ 'reclamation_id' => $reclamation->id,
|
|
|
|
|
+ 'status' => Shortage::STATUS_CLOSED,
|
|
|
|
|
+ ]);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public function test_cancel_for_reclamation_filters_by_spare_part(): void
|
|
|
|
|
+ {
|
|
|
|
|
+ // Arrange
|
|
|
|
|
+ $sparePart1 = SparePart::factory()->create();
|
|
|
|
|
+ $sparePart2 = SparePart::factory()->create();
|
|
|
|
|
+ $reclamation = Reclamation::factory()->create();
|
|
|
|
|
+
|
|
|
|
|
+ $order1 = SparePartOrder::factory()
|
|
|
|
|
+ ->inStock()
|
|
|
|
|
+ ->forSparePart($sparePart1)
|
|
|
|
|
+ ->create();
|
|
|
|
|
+
|
|
|
|
|
+ $order2 = SparePartOrder::factory()
|
|
|
|
|
+ ->inStock()
|
|
|
|
|
+ ->forSparePart($sparePart2)
|
|
|
|
|
+ ->create();
|
|
|
|
|
+
|
|
|
|
|
+ $reservation1 = Reservation::factory()
|
|
|
|
|
+ ->active()
|
|
|
|
|
+ ->withQuantity(3)
|
|
|
|
|
+ ->fromOrder($order1)
|
|
|
|
|
+ ->forReclamation($reclamation)
|
|
|
|
|
+ ->create();
|
|
|
|
|
+
|
|
|
|
|
+ $reservation2 = Reservation::factory()
|
|
|
|
|
+ ->active()
|
|
|
|
|
+ ->withQuantity(5)
|
|
|
|
|
+ ->fromOrder($order2)
|
|
|
|
|
+ ->forReclamation($reclamation)
|
|
|
|
|
+ ->create();
|
|
|
|
|
+
|
|
|
|
|
+ // Act - cancel only for sparePart1
|
|
|
|
|
+ $cancelled = $this->service->cancelForReclamation(
|
|
|
|
|
+ $reclamation->id,
|
|
|
|
|
+ sparePartId: $sparePart1->id
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ // Assert
|
|
|
|
|
+ $this->assertEquals(3, $cancelled);
|
|
|
|
|
+
|
|
|
|
|
+ $this->assertDatabaseHas('reservations', [
|
|
|
|
|
+ 'id' => $reservation1->id,
|
|
|
|
|
+ 'status' => Reservation::STATUS_CANCELLED,
|
|
|
|
|
+ ]);
|
|
|
|
|
+
|
|
|
|
|
+ $this->assertDatabaseHas('reservations', [
|
|
|
|
|
+ 'id' => $reservation2->id,
|
|
|
|
|
+ 'status' => Reservation::STATUS_ACTIVE,
|
|
|
|
|
+ ]);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public function test_get_reservations_for_reclamation_returns_active_only(): void
|
|
|
|
|
+ {
|
|
|
|
|
+ // Arrange
|
|
|
|
|
+ $sparePart = SparePart::factory()->create();
|
|
|
|
|
+ $reclamation = Reclamation::factory()->create();
|
|
|
|
|
+ $order = SparePartOrder::factory()
|
|
|
|
|
+ ->inStock()
|
|
|
|
|
+ ->forSparePart($sparePart)
|
|
|
|
|
+ ->create();
|
|
|
|
|
+
|
|
|
|
|
+ Reservation::factory()
|
|
|
|
|
+ ->active()
|
|
|
|
|
+ ->fromOrder($order)
|
|
|
|
|
+ ->forReclamation($reclamation)
|
|
|
|
|
+ ->create();
|
|
|
|
|
+
|
|
|
|
|
+ Reservation::factory()
|
|
|
|
|
+ ->cancelled()
|
|
|
|
|
+ ->fromOrder($order)
|
|
|
|
|
+ ->forReclamation($reclamation)
|
|
|
|
|
+ ->create();
|
|
|
|
|
+
|
|
|
|
|
+ Reservation::factory()
|
|
|
|
|
+ ->issued()
|
|
|
|
|
+ ->fromOrder($order)
|
|
|
|
|
+ ->forReclamation($reclamation)
|
|
|
|
|
+ ->create();
|
|
|
|
|
+
|
|
|
|
|
+ // Act
|
|
|
|
|
+ $reservations = $this->service->getReservationsForReclamation($reclamation->id);
|
|
|
|
|
+
|
|
|
|
|
+ // Assert
|
|
|
|
|
+ $this->assertCount(1, $reservations);
|
|
|
|
|
+ $this->assertEquals(Reservation::STATUS_ACTIVE, $reservations->first()->status);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public function test_adjust_reservation_increases_quantity(): void
|
|
|
|
|
+ {
|
|
|
|
|
+ // Arrange
|
|
|
|
|
+ $sparePart = SparePart::factory()->create();
|
|
|
|
|
+ $reclamation = Reclamation::factory()->create();
|
|
|
|
|
+ $order = SparePartOrder::factory()
|
|
|
|
|
+ ->inStock()
|
|
|
|
|
+ ->withDocuments(false)
|
|
|
|
|
+ ->withQuantity(20)
|
|
|
|
|
+ ->forSparePart($sparePart)
|
|
|
|
|
+ ->create();
|
|
|
|
|
+
|
|
|
|
|
+ // Initial reservation of 5
|
|
|
|
|
+ Reservation::factory()
|
|
|
|
|
+ ->active()
|
|
|
|
|
+ ->withQuantity(5)
|
|
|
|
|
+ ->withDocuments(false)
|
|
|
|
|
+ ->fromOrder($order)
|
|
|
|
|
+ ->forReclamation($reclamation)
|
|
|
|
|
+ ->create();
|
|
|
|
|
+
|
|
|
|
|
+ // Act - increase to 10
|
|
|
|
|
+ $result = $this->service->adjustReservation(
|
|
|
|
|
+ reclamationId: $reclamation->id,
|
|
|
|
|
+ sparePartId: $sparePart->id,
|
|
|
|
|
+ withDocuments: false,
|
|
|
|
|
+ newQuantity: 10
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ // Assert - should have reserved additional 5
|
|
|
|
|
+ $this->assertEquals(5, $result->reserved);
|
|
|
|
|
+ $this->assertEquals(0, $result->missing);
|
|
|
|
|
+
|
|
|
|
|
+ $totalReserved = Reservation::where('reclamation_id', $reclamation->id)
|
|
|
|
|
+ ->where('spare_part_id', $sparePart->id)
|
|
|
|
|
+ ->where('status', Reservation::STATUS_ACTIVE)
|
|
|
|
|
+ ->sum('reserved_qty');
|
|
|
|
|
+
|
|
|
|
|
+ $this->assertEquals(10, $totalReserved);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public function test_adjust_reservation_decreases_quantity(): void
|
|
|
|
|
+ {
|
|
|
|
|
+ // Arrange
|
|
|
|
|
+ $sparePart = SparePart::factory()->create();
|
|
|
|
|
+ $reclamation = Reclamation::factory()->create();
|
|
|
|
|
+ $order = SparePartOrder::factory()
|
|
|
|
|
+ ->inStock()
|
|
|
|
|
+ ->withDocuments(false)
|
|
|
|
|
+ ->withQuantity(20)
|
|
|
|
|
+ ->forSparePart($sparePart)
|
|
|
|
|
+ ->create();
|
|
|
|
|
+
|
|
|
|
|
+ // Initial reservation of 10
|
|
|
|
|
+ Reservation::factory()
|
|
|
|
|
+ ->active()
|
|
|
|
|
+ ->withQuantity(10)
|
|
|
|
|
+ ->withDocuments(false)
|
|
|
|
|
+ ->fromOrder($order)
|
|
|
|
|
+ ->forReclamation($reclamation)
|
|
|
|
|
+ ->create();
|
|
|
|
|
+
|
|
|
|
|
+ // Act - decrease to 3
|
|
|
|
|
+ $result = $this->service->adjustReservation(
|
|
|
|
|
+ reclamationId: $reclamation->id,
|
|
|
|
|
+ sparePartId: $sparePart->id,
|
|
|
|
|
+ withDocuments: false,
|
|
|
|
|
+ newQuantity: 3
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ // Assert
|
|
|
|
|
+ $this->assertEquals(3, $result->reserved);
|
|
|
|
|
+ $this->assertEquals(0, $result->missing);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public function test_cancel_reservation_does_not_cancel_non_active(): void
|
|
|
|
|
+ {
|
|
|
|
|
+ // Arrange
|
|
|
|
|
+ $sparePart = SparePart::factory()->create();
|
|
|
|
|
+ $reclamation = Reclamation::factory()->create();
|
|
|
|
|
+ $order = SparePartOrder::factory()
|
|
|
|
|
+ ->inStock()
|
|
|
|
|
+ ->forSparePart($sparePart)
|
|
|
|
|
+ ->create();
|
|
|
|
|
+
|
|
|
|
|
+ $reservation = Reservation::factory()
|
|
|
|
|
+ ->issued()
|
|
|
|
|
+ ->fromOrder($order)
|
|
|
|
|
+ ->forReclamation($reclamation)
|
|
|
|
|
+ ->create();
|
|
|
|
|
+
|
|
|
|
|
+ // Act
|
|
|
|
|
+ $result = $this->service->cancelReservation($reservation);
|
|
|
|
|
+
|
|
|
|
|
+ // Assert
|
|
|
|
|
+ $this->assertFalse($result);
|
|
|
|
|
+ $this->assertDatabaseHas('reservations', [
|
|
|
|
|
+ 'id' => $reservation->id,
|
|
|
|
|
+ 'status' => Reservation::STATUS_ISSUED,
|
|
|
|
|
+ ]);
|
|
|
|
|
+ }
|
|
|
|
|
+}
|