SparePartOrderTest.php 9.3 KB

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