SparePartOrderTest.php 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296
  1. <?php
  2. namespace Tests\Unit\Models;
  3. use App\Models\Reservation;
  4. use App\Models\SparePart;
  5. use App\Models\SparePartOrder;
  6. use Illuminate\Foundation\Testing\RefreshDatabase;
  7. use Tests\TestCase;
  8. class SparePartOrderTest extends TestCase
  9. {
  10. use RefreshDatabase;
  11. public function test_status_names_constant_exists(): void
  12. {
  13. $this->assertArrayHasKey(SparePartOrder::STATUS_ORDERED, SparePartOrder::STATUS_NAMES);
  14. $this->assertArrayHasKey(SparePartOrder::STATUS_IN_STOCK, SparePartOrder::STATUS_NAMES);
  15. $this->assertArrayHasKey(SparePartOrder::STATUS_SHIPPED, SparePartOrder::STATUS_NAMES);
  16. }
  17. public function test_status_name_attribute_returns_correct_name(): void
  18. {
  19. $order = SparePartOrder::factory()
  20. ->inStock()
  21. ->create();
  22. $this->assertEquals('На складе', $order->status_name);
  23. }
  24. public function test_available_qty_defaults_to_ordered_quantity_on_create(): void
  25. {
  26. $sparePart = SparePart::factory()->create();
  27. $order = SparePartOrder::create([
  28. 'spare_part_id' => $sparePart->id,
  29. 'ordered_quantity' => 15,
  30. 'status' => SparePartOrder::STATUS_IN_STOCK,
  31. 'with_documents' => false,
  32. ]);
  33. $this->assertEquals(15, $order->available_qty);
  34. }
  35. public function test_year_defaults_to_current_year_on_create(): void
  36. {
  37. $sparePart = SparePart::factory()->create();
  38. $order = SparePartOrder::create([
  39. 'spare_part_id' => $sparePart->id,
  40. 'ordered_quantity' => 10,
  41. 'status' => SparePartOrder::STATUS_IN_STOCK,
  42. 'with_documents' => false,
  43. ]);
  44. $this->assertEquals((int) date('Y'), $order->year);
  45. }
  46. public function test_reserved_qty_calculates_active_reservations(): void
  47. {
  48. $order = SparePartOrder::factory()
  49. ->inStock()
  50. ->withQuantity(20)
  51. ->create();
  52. Reservation::factory()
  53. ->active()
  54. ->withQuantity(5)
  55. ->fromOrder($order)
  56. ->create();
  57. Reservation::factory()
  58. ->active()
  59. ->withQuantity(3)
  60. ->fromOrder($order)
  61. ->create();
  62. // Cancelled should not count
  63. Reservation::factory()
  64. ->cancelled()
  65. ->withQuantity(10)
  66. ->fromOrder($order)
  67. ->create();
  68. $this->assertEquals(8, $order->reserved_qty);
  69. }
  70. public function test_free_qty_calculates_available_minus_reserved(): void
  71. {
  72. $order = SparePartOrder::factory()
  73. ->inStock()
  74. ->withQuantity(15)
  75. ->create();
  76. Reservation::factory()
  77. ->active()
  78. ->withQuantity(6)
  79. ->fromOrder($order)
  80. ->create();
  81. // available: 15, reserved: 6, free: 9
  82. $this->assertEquals(9, $order->free_qty);
  83. }
  84. public function test_free_qty_never_goes_negative(): void
  85. {
  86. $order = SparePartOrder::factory()
  87. ->inStock()
  88. ->withQuantity(5)
  89. ->create();
  90. // Create reservation larger than available (edge case)
  91. Reservation::factory()
  92. ->active()
  93. ->withQuantity(10)
  94. ->fromOrder($order)
  95. ->create();
  96. $this->assertEquals(0, $order->free_qty);
  97. }
  98. public function test_can_reserve_returns_true_when_sufficient_qty(): void
  99. {
  100. $order = SparePartOrder::factory()
  101. ->inStock()
  102. ->withQuantity(10)
  103. ->create();
  104. $this->assertTrue($order->canReserve(5));
  105. $this->assertTrue($order->canReserve(10));
  106. }
  107. public function test_can_reserve_returns_false_when_insufficient_qty(): void
  108. {
  109. $order = SparePartOrder::factory()
  110. ->inStock()
  111. ->withQuantity(10)
  112. ->create();
  113. Reservation::factory()
  114. ->active()
  115. ->withQuantity(8)
  116. ->fromOrder($order)
  117. ->create();
  118. // Only 2 free, trying to reserve 5
  119. $this->assertFalse($order->canReserve(5));
  120. }
  121. public function test_is_in_stock_method(): void
  122. {
  123. $orderInStock = SparePartOrder::factory()->inStock()->create();
  124. $orderOrdered = SparePartOrder::factory()->ordered()->create();
  125. $this->assertTrue($orderInStock->isInStock());
  126. $this->assertFalse($orderOrdered->isInStock());
  127. }
  128. public function test_is_ordered_method(): void
  129. {
  130. $orderOrdered = SparePartOrder::factory()->ordered()->create();
  131. $orderInStock = SparePartOrder::factory()->inStock()->create();
  132. $this->assertTrue($orderOrdered->isOrdered());
  133. $this->assertFalse($orderInStock->isOrdered());
  134. }
  135. public function test_is_shipped_method(): void
  136. {
  137. $orderShipped = SparePartOrder::factory()->shipped()->create();
  138. $orderInStock = SparePartOrder::factory()->inStock()->create();
  139. $this->assertTrue($orderShipped->isShipped());
  140. $this->assertFalse($orderInStock->isShipped());
  141. }
  142. public function test_scope_in_stock(): void
  143. {
  144. SparePartOrder::factory()->inStock()->create();
  145. SparePartOrder::factory()->inStock()->create();
  146. SparePartOrder::factory()->ordered()->create();
  147. SparePartOrder::factory()->shipped()->create();
  148. $inStockOrders = SparePartOrder::inStock()->get();
  149. $this->assertCount(2, $inStockOrders);
  150. }
  151. public function test_scope_with_available(): void
  152. {
  153. SparePartOrder::factory()->inStock()->withQuantity(10)->create();
  154. SparePartOrder::factory()->shipped()->create(); // available_qty = 0
  155. $withAvailable = SparePartOrder::withAvailable()->get();
  156. $this->assertCount(1, $withAvailable);
  157. }
  158. public function test_scope_with_documents(): void
  159. {
  160. SparePartOrder::factory()->withDocuments(true)->create();
  161. SparePartOrder::factory()->withDocuments(true)->create();
  162. SparePartOrder::factory()->withDocuments(false)->create();
  163. $withDocs = SparePartOrder::withDocuments(true)->get();
  164. $withoutDocs = SparePartOrder::withDocuments(false)->get();
  165. $this->assertCount(2, $withDocs);
  166. $this->assertCount(1, $withoutDocs);
  167. }
  168. public function test_scope_for_spare_part(): void
  169. {
  170. $sparePart1 = SparePart::factory()->create();
  171. $sparePart2 = SparePart::factory()->create();
  172. SparePartOrder::factory()->forSparePart($sparePart1)->create();
  173. SparePartOrder::factory()->forSparePart($sparePart1)->create();
  174. SparePartOrder::factory()->forSparePart($sparePart2)->create();
  175. $ordersForPart1 = SparePartOrder::forSparePart($sparePart1->id)->get();
  176. $this->assertCount(2, $ordersForPart1);
  177. }
  178. public function test_scope_available_for_reservation(): void
  179. {
  180. $sparePart = SparePart::factory()->create();
  181. $availableOrder = SparePartOrder::factory()
  182. ->inStock()
  183. ->withDocuments(false)
  184. ->withQuantity(10)
  185. ->forSparePart($sparePart)
  186. ->create(['created_at' => now()->subDay()]);
  187. // Not matching conditions
  188. SparePartOrder::factory()
  189. ->ordered() // wrong status
  190. ->withDocuments(false)
  191. ->forSparePart($sparePart)
  192. ->create();
  193. SparePartOrder::factory()
  194. ->inStock()
  195. ->withDocuments(true) // wrong docs type
  196. ->forSparePart($sparePart)
  197. ->create();
  198. SparePartOrder::factory()
  199. ->shipped() // no available qty
  200. ->withDocuments(false)
  201. ->forSparePart($sparePart)
  202. ->create();
  203. $available = SparePartOrder::availableForReservation($sparePart->id, false)->get();
  204. $this->assertCount(1, $available);
  205. $this->assertEquals($availableOrder->id, $available->first()->id);
  206. }
  207. public function test_casts_are_correct(): void
  208. {
  209. $order = SparePartOrder::factory()
  210. ->withDocuments(true)
  211. ->withQuantity(10)
  212. ->create();
  213. $order->refresh();
  214. $this->assertIsBool($order->with_documents);
  215. $this->assertIsInt($order->ordered_quantity);
  216. $this->assertIsInt($order->available_qty);
  217. $this->assertIsInt($order->year);
  218. }
  219. public function test_remaining_quantity_backward_compatibility(): void
  220. {
  221. $order = SparePartOrder::factory()
  222. ->withQuantity(15)
  223. ->create();
  224. // Deprecated attribute should still work
  225. $this->assertEquals($order->available_qty, $order->remaining_quantity);
  226. }
  227. public function test_relations_exist(): void
  228. {
  229. $order = SparePartOrder::factory()->create();
  230. $this->assertInstanceOf(\Illuminate\Database\Eloquent\Relations\BelongsTo::class, $order->sparePart());
  231. $this->assertInstanceOf(\Illuminate\Database\Eloquent\Relations\BelongsTo::class, $order->user());
  232. $this->assertInstanceOf(\Illuminate\Database\Eloquent\Relations\HasMany::class, $order->reservations());
  233. $this->assertInstanceOf(\Illuminate\Database\Eloquent\Relations\HasMany::class, $order->movements());
  234. }
  235. }