assertEquals('receipt', InventoryMovement::TYPE_RECEIPT); $this->assertEquals('reserve', InventoryMovement::TYPE_RESERVE); $this->assertEquals('issue', InventoryMovement::TYPE_ISSUE); $this->assertEquals('reserve_cancel', InventoryMovement::TYPE_RESERVE_CANCEL); $this->assertEquals('correction_plus', InventoryMovement::TYPE_CORRECTION_PLUS); $this->assertEquals('correction_minus', InventoryMovement::TYPE_CORRECTION_MINUS); } public function test_type_names_are_defined(): void { $this->assertEquals('Поступление', InventoryMovement::TYPE_NAMES[InventoryMovement::TYPE_RECEIPT]); $this->assertEquals('Резервирование', InventoryMovement::TYPE_NAMES[InventoryMovement::TYPE_RESERVE]); $this->assertEquals('Списание', InventoryMovement::TYPE_NAMES[InventoryMovement::TYPE_ISSUE]); $this->assertEquals('Отмена резерва', InventoryMovement::TYPE_NAMES[InventoryMovement::TYPE_RESERVE_CANCEL]); $this->assertEquals('Коррекция (+)', InventoryMovement::TYPE_NAMES[InventoryMovement::TYPE_CORRECTION_PLUS]); $this->assertEquals('Коррекция (-)', InventoryMovement::TYPE_NAMES[InventoryMovement::TYPE_CORRECTION_MINUS]); } // ==================== Source Type Constants ==================== public function test_source_type_constants_are_defined(): void { $this->assertEquals('order', InventoryMovement::SOURCE_ORDER); $this->assertEquals('reclamation', InventoryMovement::SOURCE_RECLAMATION); $this->assertEquals('manual', InventoryMovement::SOURCE_MANUAL); $this->assertEquals('shortage_fulfillment', InventoryMovement::SOURCE_SHORTAGE_FULFILLMENT); $this->assertEquals('inventory', InventoryMovement::SOURCE_INVENTORY); } // ==================== Accessors ==================== public function test_type_name_attribute_returns_readable_name(): void { // Arrange $movement = InventoryMovement::factory()->receipt()->create(); // Assert $this->assertEquals('Поступление', $movement->type_name); } public function test_type_name_attribute_returns_raw_for_unknown_type(): void { // Arrange $movement = new InventoryMovement(['movement_type' => 'unknown']); // Assert $this->assertEquals('unknown', $movement->type_name); } public function test_source_attribute_returns_reclamation(): void { // Arrange $reclamation = Reclamation::factory()->create(); $movement = InventoryMovement::factory() ->forReclamation($reclamation->id) ->create(); // Act $source = $movement->source; // Assert $this->assertInstanceOf(Reclamation::class, $source); $this->assertEquals($reclamation->id, $source->id); } public function test_source_attribute_returns_null_for_manual(): void { // Arrange $movement = InventoryMovement::factory()->create([ 'source_type' => InventoryMovement::SOURCE_MANUAL, 'source_id' => null, ]); // Assert $this->assertNull($movement->source); } public function test_source_attribute_returns_null_when_no_source_id(): void { // Arrange $movement = InventoryMovement::factory()->create([ 'source_type' => InventoryMovement::SOURCE_RECLAMATION, 'source_id' => null, ]); // Assert $this->assertNull($movement->source); } // ==================== Relationships ==================== public function test_movement_belongs_to_spare_part_order(): void { // Arrange $order = SparePartOrder::factory()->create(); $movement = InventoryMovement::factory() ->fromOrder($order) ->create(); // Assert $this->assertInstanceOf(SparePartOrder::class, $movement->sparePartOrder); $this->assertEquals($order->id, $movement->sparePartOrder->id); } public function test_movement_belongs_to_spare_part(): void { // Arrange $sparePart = SparePart::factory()->create(); $movement = InventoryMovement::factory()->create([ 'spare_part_id' => $sparePart->id, ]); // Assert $this->assertInstanceOf(SparePart::class, $movement->sparePart); $this->assertEquals($sparePart->id, $movement->sparePart->id); } public function test_movement_belongs_to_user(): void { // Arrange $user = User::factory()->create(); $movement = InventoryMovement::factory()->create([ 'user_id' => $user->id, ]); // Assert $this->assertInstanceOf(User::class, $movement->user); $this->assertEquals($user->id, $movement->user->id); } // ==================== Scopes ==================== public function test_scope_of_type_filters_by_movement_type(): void { // Arrange InventoryMovement::factory()->receipt()->count(2)->create(); InventoryMovement::factory()->issue()->create(); InventoryMovement::factory()->reserve()->create(); // Act $receipts = InventoryMovement::ofType(InventoryMovement::TYPE_RECEIPT)->get(); $issues = InventoryMovement::ofType(InventoryMovement::TYPE_ISSUE)->get(); // Assert $this->assertCount(2, $receipts); $this->assertCount(1, $issues); } public function test_scope_for_spare_part_filters_by_spare_part_id(): void { // Arrange $sparePart1 = SparePart::factory()->create(); $sparePart2 = SparePart::factory()->create(); InventoryMovement::factory()->count(3)->create([ 'spare_part_id' => $sparePart1->id, ]); InventoryMovement::factory()->create([ 'spare_part_id' => $sparePart2->id, ]); // Act $movements = InventoryMovement::forSparePart($sparePart1->id)->get(); // Assert $this->assertCount(3, $movements); } public function test_scope_with_documents_filters_by_with_documents(): void { // Arrange InventoryMovement::factory()->count(2)->create([ 'with_documents' => true, ]); InventoryMovement::factory()->create([ 'with_documents' => false, ]); // Act $withDocs = InventoryMovement::withDocuments(true)->get(); $withoutDocs = InventoryMovement::withDocuments(false)->get(); // Assert $this->assertCount(2, $withDocs); $this->assertCount(1, $withoutDocs); } public function test_scope_increasing_returns_correct_types(): void { // Arrange InventoryMovement::factory()->receipt()->create(); InventoryMovement::factory()->reserveCancel()->create(); InventoryMovement::factory()->create([ 'movement_type' => InventoryMovement::TYPE_CORRECTION_PLUS, ]); InventoryMovement::factory()->issue()->create(); InventoryMovement::factory()->reserve()->create(); // Act $increasing = InventoryMovement::increasing()->get(); // Assert $this->assertCount(3, $increasing); $types = $increasing->pluck('movement_type')->toArray(); $this->assertContains(InventoryMovement::TYPE_RECEIPT, $types); $this->assertContains(InventoryMovement::TYPE_RESERVE_CANCEL, $types); $this->assertContains(InventoryMovement::TYPE_CORRECTION_PLUS, $types); } public function test_scope_decreasing_returns_correct_types(): void { // Arrange InventoryMovement::factory()->receipt()->create(); InventoryMovement::factory()->reserve()->create(); InventoryMovement::factory()->issue()->create(); InventoryMovement::factory()->create([ 'movement_type' => InventoryMovement::TYPE_CORRECTION_MINUS, ]); // Act $decreasing = InventoryMovement::decreasing()->get(); // Assert $this->assertCount(3, $decreasing); $types = $decreasing->pluck('movement_type')->toArray(); $this->assertContains(InventoryMovement::TYPE_RESERVE, $types); $this->assertContains(InventoryMovement::TYPE_ISSUE, $types); $this->assertContains(InventoryMovement::TYPE_CORRECTION_MINUS, $types); } // ==================== Methods ==================== public function test_is_correction_returns_true_for_correction_types(): void { // Arrange $correctionPlus = new InventoryMovement([ 'movement_type' => InventoryMovement::TYPE_CORRECTION_PLUS, ]); $correctionMinus = new InventoryMovement([ 'movement_type' => InventoryMovement::TYPE_CORRECTION_MINUS, ]); $receipt = new InventoryMovement([ 'movement_type' => InventoryMovement::TYPE_RECEIPT, ]); // Assert $this->assertTrue($correctionPlus->isCorrection()); $this->assertTrue($correctionMinus->isCorrection()); $this->assertFalse($receipt->isCorrection()); } // ==================== Casts ==================== public function test_qty_is_cast_to_integer(): void { // Arrange $movement = InventoryMovement::factory()->create(['qty' => 10]); // Assert $this->assertIsInt($movement->qty); } public function test_with_documents_is_cast_to_boolean(): void { // Arrange $movement = InventoryMovement::factory()->create(['with_documents' => true]); // Assert $this->assertIsBool($movement->with_documents); } public function test_source_id_is_cast_to_integer(): void { // Arrange $reclamation = Reclamation::factory()->create(); $movement = InventoryMovement::factory() ->forReclamation($reclamation->id) ->create(); // Assert $this->assertIsInt($movement->source_id); } // ==================== Factory ==================== public function test_factory_creates_valid_movement(): void { // Act $movement = InventoryMovement::factory()->create(); // Assert $this->assertDatabaseHas('inventory_movements', [ 'id' => $movement->id, ]); $this->assertNotNull($movement->spare_part_id); $this->assertNotNull($movement->spare_part_order_id); $this->assertNotNull($movement->qty); $this->assertNotNull($movement->movement_type); } public function test_factory_receipt_state(): void { // Act $movement = InventoryMovement::factory()->receipt()->create(); // Assert $this->assertEquals(InventoryMovement::TYPE_RECEIPT, $movement->movement_type); } public function test_factory_reserve_state(): void { // Act $movement = InventoryMovement::factory()->reserve()->create(); // Assert $this->assertEquals(InventoryMovement::TYPE_RESERVE, $movement->movement_type); } public function test_factory_issue_state(): void { // Act $movement = InventoryMovement::factory()->issue()->create(); // Assert $this->assertEquals(InventoryMovement::TYPE_ISSUE, $movement->movement_type); } }