create([ 'purchase_price' => 150.50, // Will be stored as 15050 kopeks 'customer_price' => 200.00, 'expertise_price' => 180.25, ]); $sparePart->refresh(); $this->assertEquals(150.50, $sparePart->purchase_price); $this->assertEquals(200.00, $sparePart->customer_price); $this->assertEquals(180.25, $sparePart->expertise_price); } public function test_price_txt_accessors_format_prices(): void { $sparePart = SparePart::factory()->create([ 'purchase_price' => 1500.50, ]); $sparePart->refresh(); $this->assertStringContainsString('₽', $sparePart->purchase_price_txt); $this->assertStringContainsString('1', $sparePart->purchase_price_txt); $this->assertStringContainsString('500', $sparePart->purchase_price_txt); } public function test_price_txt_returns_dash_for_null_prices(): void { $sparePart = SparePart::factory()->create([ 'purchase_price' => null, ]); $sparePart->refresh(); $this->assertEquals('-', $sparePart->purchase_price_txt); } public function test_physical_stock_without_docs_calculates_correctly(): void { $sparePart = SparePart::factory()->create(); // Create orders WITHOUT documents SparePartOrder::factory() ->inStock() ->withDocuments(false) ->withQuantity(10) ->forSparePart($sparePart) ->create(); SparePartOrder::factory() ->inStock() ->withDocuments(false) ->withQuantity(5) ->forSparePart($sparePart) ->create(); // Create order WITH documents (should not count) SparePartOrder::factory() ->inStock() ->withDocuments(true) ->withQuantity(20) ->forSparePart($sparePart) ->create(); // Create shipped order (should not count) SparePartOrder::factory() ->shipped() ->withDocuments(false) ->forSparePart($sparePart) ->create(); $this->assertEquals(15, $sparePart->physical_stock_without_docs); } public function test_physical_stock_with_docs_calculates_correctly(): void { $sparePart = SparePart::factory()->create(); SparePartOrder::factory() ->inStock() ->withDocuments(true) ->withQuantity(7) ->forSparePart($sparePart) ->create(); SparePartOrder::factory() ->inStock() ->withDocuments(false) ->withQuantity(10) ->forSparePart($sparePart) ->create(); $this->assertEquals(7, $sparePart->physical_stock_with_docs); } public function test_reserved_without_docs_calculates_correctly(): void { $sparePart = SparePart::factory()->create(); $order = SparePartOrder::factory() ->inStock() ->withDocuments(false) ->forSparePart($sparePart) ->create(); // Active reservations WITHOUT documents Reservation::factory() ->active() ->withQuantity(3) ->withDocuments(false) ->fromOrder($order) ->forSparePart($sparePart) ->create(); Reservation::factory() ->active() ->withQuantity(2) ->withDocuments(false) ->fromOrder($order) ->forSparePart($sparePart) ->create(); // Cancelled reservation (should not count) Reservation::factory() ->cancelled() ->withQuantity(10) ->withDocuments(false) ->fromOrder($order) ->forSparePart($sparePart) ->create(); // Reservation WITH documents (should not count) // Need to create separate order with documents for this $orderWithDocs = SparePartOrder::factory() ->inStock() ->withDocuments(true) ->forSparePart($sparePart) ->create(); Reservation::factory() ->active() ->withQuantity(5) ->fromOrder($orderWithDocs) ->forSparePart($sparePart) ->create(); $this->assertEquals(5, $sparePart->reserved_without_docs); } public function test_free_stock_without_docs_calculates_correctly(): void { $sparePart = SparePart::factory()->create(); $order = SparePartOrder::factory() ->inStock() ->withDocuments(false) ->withQuantity(10) ->forSparePart($sparePart) ->create(); Reservation::factory() ->active() ->withQuantity(3) ->withDocuments(false) ->fromOrder($order) ->forSparePart($sparePart) ->create(); // Physical: 10, Reserved: 3, Free: 7 $this->assertEquals(7, $sparePart->free_stock_without_docs); } public function test_total_physical_stock_sums_both_types(): void { $sparePart = SparePart::factory()->create(); SparePartOrder::factory() ->inStock() ->withDocuments(false) ->withQuantity(10) ->forSparePart($sparePart) ->create(); SparePartOrder::factory() ->inStock() ->withDocuments(true) ->withQuantity(5) ->forSparePart($sparePart) ->create(); $this->assertEquals(15, $sparePart->total_physical_stock); } public function test_total_free_stock_calculates_correctly(): void { $sparePart = SparePart::factory()->create(); $orderNoDocs = SparePartOrder::factory() ->inStock() ->withDocuments(false) ->withQuantity(10) ->forSparePart($sparePart) ->create(); $orderWithDocs = SparePartOrder::factory() ->inStock() ->withDocuments(true) ->withQuantity(8) ->forSparePart($sparePart) ->create(); Reservation::factory() ->active() ->withQuantity(3) ->withDocuments(false) ->fromOrder($orderNoDocs) ->forSparePart($sparePart) ->create(); Reservation::factory() ->active() ->withQuantity(2) ->withDocuments(true) ->fromOrder($orderWithDocs) ->forSparePart($sparePart) ->create(); // Total physical: 18, Total reserved: 5, Total free: 13 $this->assertEquals(13, $sparePart->total_free_stock); } public function test_quantity_backward_compatibility_attributes(): void { $sparePart = SparePart::factory()->create(); $order = SparePartOrder::factory() ->inStock() ->withDocuments(false) ->withQuantity(10) ->forSparePart($sparePart) ->create(); Reservation::factory() ->active() ->withQuantity(3) ->withDocuments(false) ->fromOrder($order) ->forSparePart($sparePart) ->create(); // Old attributes should return free stock $this->assertEquals($sparePart->free_stock_without_docs, $sparePart->quantity_without_docs); $this->assertEquals($sparePart->free_stock_with_docs, $sparePart->quantity_with_docs); $this->assertEquals($sparePart->total_free_stock, $sparePart->total_quantity); } public function test_has_open_shortages_returns_true_when_shortages_exist(): void { $sparePart = SparePart::factory()->create(); Shortage::factory() ->open() ->forSparePart($sparePart) ->create(); $this->assertTrue($sparePart->hasOpenShortages()); } public function test_has_open_shortages_returns_false_when_no_shortages(): void { $sparePart = SparePart::factory()->create(); $this->assertFalse($sparePart->hasOpenShortages()); } public function test_has_open_shortages_ignores_closed_shortages(): void { $sparePart = SparePart::factory()->create(); Shortage::factory() ->closed() ->forSparePart($sparePart) ->create(); $this->assertFalse($sparePart->hasOpenShortages()); } public function test_open_shortages_qty_calculates_correctly(): void { $sparePart = SparePart::factory()->create(); Shortage::factory() ->open() ->withQuantities(10, 3) // missing 7 ->forSparePart($sparePart) ->create(); Shortage::factory() ->open() ->withQuantities(5, 2) // missing 3 ->forSparePart($sparePart) ->create(); Shortage::factory() ->withQuantities(20, 20) // fully reserved = closed ->forSparePart($sparePart) ->create(); $this->assertEquals(10, $sparePart->open_shortages_qty); } public function test_is_below_min_stock_returns_true_when_below(): void { $sparePart = SparePart::factory() ->withMinStock(10) ->create(); SparePartOrder::factory() ->inStock() ->withDocuments(false) ->withQuantity(5) ->forSparePart($sparePart) ->create(); $this->assertTrue($sparePart->isBelowMinStock()); } public function test_is_below_min_stock_returns_false_when_at_or_above(): void { $sparePart = SparePart::factory() ->withMinStock(10) ->create(); SparePartOrder::factory() ->inStock() ->withDocuments(false) ->withQuantity(15) ->forSparePart($sparePart) ->create(); $this->assertFalse($sparePart->isBelowMinStock()); } public function test_get_free_stock_helper_method(): void { $sparePart = SparePart::factory()->create(); SparePartOrder::factory() ->inStock() ->withDocuments(false) ->withQuantity(10) ->forSparePart($sparePart) ->create(); SparePartOrder::factory() ->inStock() ->withDocuments(true) ->withQuantity(5) ->forSparePart($sparePart) ->create(); $this->assertEquals(10, $sparePart->getFreeStock(false)); $this->assertEquals(5, $sparePart->getFreeStock(true)); } public function test_pricing_codes_list_attribute(): void { $sparePart = SparePart::factory()->create(); // Assuming pricing codes relationship works $this->assertIsString($sparePart->pricing_codes_list); } public function test_relations_exist(): void { $sparePart = SparePart::factory()->create(); $this->assertInstanceOf(\Illuminate\Database\Eloquent\Relations\HasMany::class, $sparePart->orders()); $this->assertInstanceOf(\Illuminate\Database\Eloquent\Relations\HasMany::class, $sparePart->reservations()); $this->assertInstanceOf(\Illuminate\Database\Eloquent\Relations\HasMany::class, $sparePart->shortages()); $this->assertInstanceOf(\Illuminate\Database\Eloquent\Relations\HasMany::class, $sparePart->movements()); } }