| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271 |
- <?php
- namespace Tests\Unit\Observers;
- use App\Models\Reclamation;
- use App\Models\Reservation;
- use App\Models\Shortage;
- use App\Models\SparePart;
- use App\Models\SparePartOrder;
- use Illuminate\Foundation\Testing\RefreshDatabase;
- use Tests\TestCase;
- class SparePartOrderObserverTest extends TestCase
- {
- use RefreshDatabase;
- protected $seed = true;
- public function test_creating_order_with_in_stock_status_covers_shortages(): void
- {
- // Arrange
- $sparePart = SparePart::factory()->create();
- $reclamation = Reclamation::factory()->create();
- // Create open shortage
- $shortage = Shortage::factory()
- ->open()
- ->withQuantities(5, 0) // required=5, reserved=0, missing=5
- ->forSparePart($sparePart)
- ->forReclamation($reclamation)
- ->withDocuments(false)
- ->create();
- // Act - create order with in_stock status (observer should trigger)
- $order = SparePartOrder::factory()
- ->inStock()
- ->withDocuments(false)
- ->withQuantity(10)
- ->forSparePart($sparePart)
- ->create();
- // Assert
- $shortage->refresh();
- // Shortage should be covered (status=closed when fully covered)
- $this->assertEquals(5, $shortage->reserved_qty);
- $this->assertEquals(0, $shortage->missing_qty);
- $this->assertEquals(Shortage::STATUS_CLOSED, $shortage->status);
- // Reservation should be created
- $this->assertDatabaseHas('reservations', [
- 'spare_part_id' => $sparePart->id,
- 'spare_part_order_id' => $order->id,
- 'reclamation_id' => $reclamation->id,
- 'reserved_qty' => 5,
- 'status' => Reservation::STATUS_ACTIVE,
- ]);
- }
- public function test_creating_order_with_ordered_status_does_not_trigger_shortage_coverage(): void
- {
- // Arrange
- $sparePart = SparePart::factory()->create();
- $reclamation = Reclamation::factory()->create();
- Shortage::factory()
- ->open()
- ->withQuantities(5, 0)
- ->forSparePart($sparePart)
- ->forReclamation($reclamation)
- ->withDocuments(false)
- ->create();
- // Act - create order with ordered status
- SparePartOrder::factory()
- ->ordered()
- ->withDocuments(false)
- ->withQuantity(10)
- ->forSparePart($sparePart)
- ->create();
- // Assert - shortage should remain open
- $this->assertDatabaseHas('shortages', [
- 'spare_part_id' => $sparePart->id,
- 'reclamation_id' => $reclamation->id,
- 'status' => Shortage::STATUS_OPEN,
- 'missing_qty' => 5,
- ]);
- }
- public function test_updating_order_status_to_in_stock_covers_shortages(): void
- {
- // Arrange
- $sparePart = SparePart::factory()->create();
- $reclamation = Reclamation::factory()->create();
- Shortage::factory()
- ->open()
- ->withQuantities(3, 0)
- ->forSparePart($sparePart)
- ->forReclamation($reclamation)
- ->withDocuments(true)
- ->create();
- // Create order with ordered status (not in_stock)
- $order = SparePartOrder::factory()
- ->ordered()
- ->withDocuments(true)
- ->withQuantity(10)
- ->forSparePart($sparePart)
- ->create();
- // Act - update status to in_stock
- $order->update(['status' => SparePartOrder::STATUS_IN_STOCK]);
- // Assert
- $this->assertDatabaseHas('shortages', [
- 'spare_part_id' => $sparePart->id,
- 'reclamation_id' => $reclamation->id,
- 'status' => Shortage::STATUS_CLOSED,
- 'reserved_qty' => 3,
- 'missing_qty' => 0,
- ]);
- }
- public function test_updating_other_fields_does_not_trigger_shortage_coverage(): void
- {
- // Arrange
- $sparePart = SparePart::factory()->create();
- $reclamation = Reclamation::factory()->create();
- Shortage::factory()
- ->open()
- ->withQuantities(5, 0)
- ->forSparePart($sparePart)
- ->forReclamation($reclamation)
- ->withDocuments(false)
- ->create();
- $order = SparePartOrder::factory()
- ->inStock()
- ->withDocuments(false)
- ->withQuantity(10)
- ->forSparePart($sparePart)
- ->create();
- // Clear the shortage that was covered on creation
- Shortage::query()->update([
- 'status' => Shortage::STATUS_OPEN,
- 'reserved_qty' => 0,
- 'missing_qty' => 5,
- ]);
- Reservation::query()->delete();
- // Act - update note (not status)
- $order->update(['note' => 'Updated note']);
- // Assert - shortage should remain open (status didn't change)
- $this->assertDatabaseHas('shortages', [
- 'spare_part_id' => $sparePart->id,
- 'status' => Shortage::STATUS_OPEN,
- 'missing_qty' => 5,
- ]);
- }
- public function test_observer_respects_with_documents_flag(): void
- {
- // Arrange
- $sparePart = SparePart::factory()->create();
- $reclamation = Reclamation::factory()->create();
- // Create shortage requiring documents
- Shortage::factory()
- ->open()
- ->withQuantities(5, 0)
- ->forSparePart($sparePart)
- ->forReclamation($reclamation)
- ->withDocuments(true)
- ->create();
- // Act - create order WITHOUT documents
- SparePartOrder::factory()
- ->inStock()
- ->withDocuments(false) // Different from shortage
- ->withQuantity(10)
- ->forSparePart($sparePart)
- ->create();
- // Assert - shortage should remain open (documents mismatch)
- $this->assertDatabaseHas('shortages', [
- 'spare_part_id' => $sparePart->id,
- 'with_documents' => true,
- 'status' => Shortage::STATUS_OPEN,
- 'missing_qty' => 5,
- ]);
- }
- public function test_observer_covers_multiple_shortages_fifo(): void
- {
- // Arrange
- $sparePart = SparePart::factory()->create();
- $reclamation1 = Reclamation::factory()->create();
- $reclamation2 = Reclamation::factory()->create();
- // Create older shortage
- $shortage1 = Shortage::factory()
- ->open()
- ->withQuantities(3, 0)
- ->forSparePart($sparePart)
- ->forReclamation($reclamation1)
- ->withDocuments(false)
- ->create(['created_at' => now()->subDays(2)]);
- // Create newer shortage
- $shortage2 = Shortage::factory()
- ->open()
- ->withQuantities(4, 0)
- ->forSparePart($sparePart)
- ->forReclamation($reclamation2)
- ->withDocuments(false)
- ->create(['created_at' => now()->subDay()]);
- // Act - create order with 5 items (should cover first fully, second partially)
- SparePartOrder::factory()
- ->inStock()
- ->withDocuments(false)
- ->withQuantity(5)
- ->forSparePart($sparePart)
- ->create();
- // Assert
- $shortage1->refresh();
- $shortage2->refresh();
- // First shortage fully covered (closed)
- $this->assertEquals(Shortage::STATUS_CLOSED, $shortage1->status);
- $this->assertEquals(3, $shortage1->reserved_qty);
- // Second shortage partially covered
- $this->assertEquals(Shortage::STATUS_OPEN, $shortage2->status);
- $this->assertEquals(2, $shortage2->reserved_qty);
- $this->assertEquals(2, $shortage2->missing_qty);
- }
- public function test_observer_does_not_cover_already_covered_shortages(): void
- {
- // Arrange
- $sparePart = SparePart::factory()->create();
- $reclamation = Reclamation::factory()->create();
- // Create already covered shortage
- Shortage::factory()
- ->covered()
- ->withQuantities(5, 5) // fully covered
- ->forSparePart($sparePart)
- ->forReclamation($reclamation)
- ->withDocuments(false)
- ->create();
- // Act
- $order = SparePartOrder::factory()
- ->inStock()
- ->withDocuments(false)
- ->withQuantity(10)
- ->forSparePart($sparePart)
- ->create();
- // Assert - no new reservations for already covered shortage
- $this->assertEquals(0, Reservation::where('spare_part_order_id', $order->id)->count());
- }
- }
|