SparePartOrderControllerTest.php 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542
  1. <?php
  2. namespace Tests\Feature;
  3. use App\Models\Reservation;
  4. use App\Models\Role;
  5. use App\Models\SparePart;
  6. use App\Models\SparePartOrder;
  7. use App\Models\User;
  8. use Illuminate\Foundation\Testing\RefreshDatabase;
  9. use Tests\TestCase;
  10. class SparePartOrderControllerTest extends TestCase
  11. {
  12. use RefreshDatabase;
  13. protected $seed = true;
  14. private User $adminUser;
  15. private User $managerUser;
  16. protected function setUp(): void
  17. {
  18. parent::setUp();
  19. $this->adminUser = User::factory()->create(['role' => Role::ADMIN]);
  20. $this->managerUser = User::factory()->create(['role' => Role::MANAGER]);
  21. }
  22. // ==================== Authentication ====================
  23. public function test_guest_cannot_access_spare_part_orders_index(): void
  24. {
  25. $response = $this->get(route('spare_part_orders.index'));
  26. $response->assertRedirect(route('login'));
  27. }
  28. public function test_guest_cannot_access_spare_part_order_create(): void
  29. {
  30. $response = $this->get(route('spare_part_orders.create'));
  31. $response->assertRedirect(route('login'));
  32. }
  33. public function test_guest_cannot_store_spare_part_order(): void
  34. {
  35. $response = $this->post(route('spare_part_orders.store'), []);
  36. $response->assertRedirect(route('login'));
  37. }
  38. // ==================== Index ====================
  39. public function test_admin_can_access_spare_part_orders_index(): void
  40. {
  41. $response = $this->actingAs($this->adminUser)
  42. ->get(route('spare_part_orders.index'));
  43. $response->assertStatus(200);
  44. $response->assertViewIs('spare_parts.index');
  45. }
  46. public function test_manager_can_access_spare_part_orders_index(): void
  47. {
  48. $response = $this->actingAs($this->managerUser)
  49. ->get(route('spare_part_orders.index'));
  50. $response->assertStatus(200);
  51. $response->assertViewIs('spare_parts.index');
  52. }
  53. public function test_index_passes_only_ordered_order_numbers_to_view(): void
  54. {
  55. SparePartOrder::factory()->ordered()->create([
  56. 'order_number' => 'SP-BULK-001',
  57. ]);
  58. SparePartOrder::factory()->ordered()->create([
  59. 'order_number' => 'SP-BULK-001',
  60. ]);
  61. SparePartOrder::factory()->ordered()->create([
  62. 'order_number' => 'SP-BULK-002',
  63. ]);
  64. SparePartOrder::factory()->inStock()->create([
  65. 'order_number' => 'SP-IN-STOCK-001',
  66. ]);
  67. SparePartOrder::factory()->ordered()->create([
  68. 'order_number' => '',
  69. ]);
  70. SparePartOrder::factory()->ordered()->create([
  71. 'order_number' => null,
  72. ]);
  73. $response = $this->actingAs($this->adminUser)
  74. ->get(route('spare_part_orders.index'));
  75. $response->assertStatus(200);
  76. $response->assertViewHas('order_numbers', function ($orderNumbers) {
  77. return $orderNumbers->values()->all() === ['SP-BULK-001', 'SP-BULK-002'];
  78. });
  79. }
  80. // ==================== Show ====================
  81. public function test_admin_can_view_spare_part_order(): void
  82. {
  83. $sparePartOrder = SparePartOrder::factory()->create();
  84. $response = $this->actingAs($this->adminUser)
  85. ->get(route('spare_part_orders.show', $sparePartOrder));
  86. $response->assertStatus(200);
  87. $response->assertViewIs('spare_part_orders.edit');
  88. }
  89. public function test_manager_can_view_spare_part_order(): void
  90. {
  91. $sparePartOrder = SparePartOrder::factory()->create();
  92. $response = $this->actingAs($this->managerUser)
  93. ->get(route('spare_part_orders.show', $sparePartOrder));
  94. $response->assertStatus(200);
  95. $response->assertViewIs('spare_part_orders.edit');
  96. }
  97. public function test_guest_cannot_view_spare_part_order(): void
  98. {
  99. $sparePartOrder = SparePartOrder::factory()->create();
  100. $response = $this->get(route('spare_part_orders.show', $sparePartOrder));
  101. $response->assertRedirect(route('login'));
  102. }
  103. // ==================== Create ====================
  104. public function test_admin_can_access_create_form(): void
  105. {
  106. $response = $this->actingAs($this->adminUser)
  107. ->get(route('spare_part_orders.create'));
  108. $response->assertStatus(200);
  109. $response->assertViewIs('spare_part_orders.edit');
  110. }
  111. public function test_manager_can_access_create_form(): void
  112. {
  113. $response = $this->actingAs($this->managerUser)
  114. ->get(route('spare_part_orders.create'));
  115. $response->assertStatus(200);
  116. $response->assertViewIs('spare_part_orders.edit');
  117. }
  118. // ==================== Store ====================
  119. public function test_admin_can_create_spare_part_order(): void
  120. {
  121. $sparePart = SparePart::factory()->create();
  122. $response = $this->actingAs($this->adminUser)
  123. ->post(route('spare_part_orders.store'), [
  124. 'spare_part_id' => $sparePart->id,
  125. 'status' => SparePartOrder::STATUS_ORDERED,
  126. 'ordered_quantity' => 10,
  127. 'source_text' => 'Test Supplier',
  128. ]);
  129. $response->assertRedirect();
  130. $response->assertSessionHas('success');
  131. $this->assertDatabaseHas('spare_part_orders', [
  132. 'spare_part_id' => $sparePart->id,
  133. 'status' => SparePartOrder::STATUS_ORDERED,
  134. 'ordered_quantity' => 10,
  135. ]);
  136. }
  137. public function test_manager_can_create_spare_part_order(): void
  138. {
  139. $sparePart = SparePart::factory()->create();
  140. $response = $this->actingAs($this->managerUser)
  141. ->post(route('spare_part_orders.store'), [
  142. 'spare_part_id' => $sparePart->id,
  143. 'status' => SparePartOrder::STATUS_ORDERED,
  144. 'ordered_quantity' => 5,
  145. ]);
  146. $response->assertRedirect();
  147. $response->assertSessionHas('success');
  148. }
  149. public function test_store_validates_required_spare_part_id(): void
  150. {
  151. $response = $this->actingAs($this->adminUser)
  152. ->post(route('spare_part_orders.store'), [
  153. 'status' => SparePartOrder::STATUS_ORDERED,
  154. 'ordered_quantity' => 10,
  155. ]);
  156. $response->assertSessionHasErrors('spare_part_id');
  157. }
  158. public function test_store_validates_required_ordered_quantity(): void
  159. {
  160. $sparePart = SparePart::factory()->create();
  161. $response = $this->actingAs($this->adminUser)
  162. ->post(route('spare_part_orders.store'), [
  163. 'spare_part_id' => $sparePart->id,
  164. 'status' => SparePartOrder::STATUS_ORDERED,
  165. ]);
  166. $response->assertSessionHasErrors('ordered_quantity');
  167. }
  168. // ==================== Update ====================
  169. public function test_admin_can_update_spare_part_order(): void
  170. {
  171. $sparePartOrder = SparePartOrder::factory()->ordered()->create();
  172. $response = $this->actingAs($this->adminUser)
  173. ->put(route('spare_part_orders.update', $sparePartOrder), [
  174. 'spare_part_id' => $sparePartOrder->spare_part_id,
  175. 'status' => SparePartOrder::STATUS_ORDERED,
  176. 'ordered_quantity' => 20,
  177. 'order_number' => 'ORD-2024-001',
  178. ]);
  179. $response->assertRedirect();
  180. $response->assertSessionHas('success');
  181. $this->assertDatabaseHas('spare_part_orders', [
  182. 'id' => $sparePartOrder->id,
  183. 'ordered_quantity' => 20,
  184. 'order_number' => 'ORD-2024-001',
  185. ]);
  186. }
  187. public function test_manager_can_update_spare_part_order(): void
  188. {
  189. $sparePartOrder = SparePartOrder::factory()->ordered()->create();
  190. $response = $this->actingAs($this->managerUser)
  191. ->put(route('spare_part_orders.update', $sparePartOrder), [
  192. 'spare_part_id' => $sparePartOrder->spare_part_id,
  193. 'status' => SparePartOrder::STATUS_ORDERED,
  194. 'ordered_quantity' => 15,
  195. ]);
  196. $response->assertRedirect();
  197. $response->assertSessionHas('success');
  198. }
  199. public function test_guest_cannot_update_spare_part_order(): void
  200. {
  201. $sparePartOrder = SparePartOrder::factory()->create();
  202. $response = $this->put(route('spare_part_orders.update', $sparePartOrder), []);
  203. $response->assertRedirect(route('login'));
  204. }
  205. // ==================== Destroy ====================
  206. public function test_admin_can_delete_spare_part_order_without_active_reservations(): void
  207. {
  208. $sparePartOrder = SparePartOrder::factory()->create();
  209. $response = $this->actingAs($this->adminUser)
  210. ->delete(route('spare_part_orders.destroy', $sparePartOrder));
  211. $response->assertRedirect(route('spare_part_orders.index'));
  212. $response->assertSessionHas('success');
  213. $this->assertSoftDeleted('spare_part_orders', ['id' => $sparePartOrder->id]);
  214. }
  215. public function test_admin_cannot_delete_spare_part_order_with_active_reservations(): void
  216. {
  217. $sparePartOrder = SparePartOrder::factory()->inStock()->create();
  218. Reservation::factory()->create([
  219. 'spare_part_order_id' => $sparePartOrder->id,
  220. 'spare_part_id' => $sparePartOrder->spare_part_id,
  221. 'status' => Reservation::STATUS_ACTIVE,
  222. ]);
  223. $response = $this->actingAs($this->adminUser)
  224. ->delete(route('spare_part_orders.destroy', $sparePartOrder));
  225. $response->assertRedirect(route('spare_part_orders.show', $sparePartOrder));
  226. $response->assertSessionHas('error');
  227. $this->assertDatabaseHas('spare_part_orders', ['id' => $sparePartOrder->id, 'deleted_at' => null]);
  228. }
  229. public function test_manager_cannot_delete_spare_part_order(): void
  230. {
  231. $sparePartOrder = SparePartOrder::factory()->create();
  232. $response = $this->actingAs($this->managerUser)
  233. ->delete(route('spare_part_orders.destroy', $sparePartOrder));
  234. $response->assertStatus(403);
  235. }
  236. public function test_guest_cannot_delete_spare_part_order(): void
  237. {
  238. $sparePartOrder = SparePartOrder::factory()->create();
  239. $response = $this->delete(route('spare_part_orders.destroy', $sparePartOrder));
  240. $response->assertRedirect(route('login'));
  241. }
  242. // ==================== SetInStock ====================
  243. public function test_admin_can_set_order_in_stock(): void
  244. {
  245. $sparePartOrder = SparePartOrder::factory()->ordered()->create();
  246. $response = $this->actingAs($this->adminUser)
  247. ->post(route('spare_part_orders.set_in_stock', $sparePartOrder));
  248. $response->assertRedirect(route('spare_part_orders.show', $sparePartOrder));
  249. $response->assertSessionHas('success');
  250. $this->assertDatabaseHas('spare_part_orders', [
  251. 'id' => $sparePartOrder->id,
  252. 'status' => SparePartOrder::STATUS_IN_STOCK,
  253. ]);
  254. }
  255. public function test_manager_can_set_order_in_stock(): void
  256. {
  257. $sparePartOrder = SparePartOrder::factory()->ordered()->create();
  258. $response = $this->actingAs($this->managerUser)
  259. ->post(route('spare_part_orders.set_in_stock', $sparePartOrder));
  260. $response->assertRedirect(route('spare_part_orders.show', $sparePartOrder));
  261. $response->assertSessionHas('success');
  262. }
  263. public function test_guest_cannot_set_order_in_stock(): void
  264. {
  265. $sparePartOrder = SparePartOrder::factory()->create();
  266. $response = $this->post(route('spare_part_orders.set_in_stock', $sparePartOrder));
  267. $response->assertRedirect(route('login'));
  268. }
  269. public function test_admin_can_set_whole_order_in_stock_by_order_number(): void
  270. {
  271. $first = SparePartOrder::factory()->ordered()->create([
  272. 'order_number' => 'SP-BULK-100',
  273. ]);
  274. $second = SparePartOrder::factory()->ordered()->create([
  275. 'order_number' => 'SP-BULK-100',
  276. ]);
  277. $sameNumberInStock = SparePartOrder::factory()->inStock()->create([
  278. 'order_number' => 'SP-BULK-100',
  279. ]);
  280. $other = SparePartOrder::factory()->ordered()->create([
  281. 'order_number' => 'SP-BULK-200',
  282. ]);
  283. $response = $this->actingAs($this->adminUser)
  284. ->post(route('spare_part_orders.set_order_in_stock'), [
  285. 'bulk_order_number' => 'SP-BULK-100',
  286. ]);
  287. $response->assertRedirect(route('spare_part_orders.index'));
  288. $response->assertSessionHas('success');
  289. $this->assertDatabaseHas('spare_part_orders', [
  290. 'id' => $first->id,
  291. 'status' => SparePartOrder::STATUS_IN_STOCK,
  292. ]);
  293. $this->assertDatabaseHas('spare_part_orders', [
  294. 'id' => $second->id,
  295. 'status' => SparePartOrder::STATUS_IN_STOCK,
  296. ]);
  297. $this->assertDatabaseHas('spare_part_orders', [
  298. 'id' => $sameNumberInStock->id,
  299. 'status' => SparePartOrder::STATUS_IN_STOCK,
  300. ]);
  301. $this->assertDatabaseHas('spare_part_orders', [
  302. 'id' => $other->id,
  303. 'status' => SparePartOrder::STATUS_ORDERED,
  304. ]);
  305. }
  306. public function test_bulk_set_order_in_stock_validates_selected_number_has_ordered_rows(): void
  307. {
  308. SparePartOrder::factory()->inStock()->create([
  309. 'order_number' => 'SP-NOT-ORDERED',
  310. ]);
  311. $response = $this->from(route('spare_part_orders.index'))
  312. ->actingAs($this->adminUser)
  313. ->post(route('spare_part_orders.set_order_in_stock'), [
  314. 'bulk_order_number' => 'SP-NOT-ORDERED',
  315. ]);
  316. $response->assertRedirect(route('spare_part_orders.index'));
  317. $response->assertSessionHasErrors('bulk_order_number');
  318. }
  319. // ==================== Ship ====================
  320. public function test_admin_can_ship_spare_part_order(): void
  321. {
  322. $sparePartOrder = SparePartOrder::factory()->inStock()->withQuantity(20)->create();
  323. $response = $this->actingAs($this->adminUser)
  324. ->post(route('spare_part_orders.ship', $sparePartOrder), [
  325. 'quantity' => 5,
  326. 'note' => 'Тестовая отгрузка',
  327. ]);
  328. $response->assertRedirect(route('spare_part_orders.show', $sparePartOrder));
  329. $response->assertSessionHas('success');
  330. $this->assertDatabaseHas('spare_part_orders', [
  331. 'id' => $sparePartOrder->id,
  332. 'available_qty' => 15,
  333. ]);
  334. }
  335. public function test_ship_fails_if_quantity_exceeds_available(): void
  336. {
  337. $sparePartOrder = SparePartOrder::factory()->inStock()->withQuantity(3)->create();
  338. $response = $this->actingAs($this->adminUser)
  339. ->post(route('spare_part_orders.ship', $sparePartOrder), [
  340. 'quantity' => 10,
  341. 'note' => 'Попытка отгрузить больше чем есть',
  342. ]);
  343. $response->assertRedirect(route('spare_part_orders.show', $sparePartOrder));
  344. $response->assertSessionHas('error');
  345. $this->assertDatabaseHas('spare_part_orders', [
  346. 'id' => $sparePartOrder->id,
  347. 'available_qty' => 3,
  348. ]);
  349. }
  350. public function test_ship_validates_required_quantity(): void
  351. {
  352. $sparePartOrder = SparePartOrder::factory()->inStock()->create();
  353. $response = $this->actingAs($this->adminUser)
  354. ->post(route('spare_part_orders.ship', $sparePartOrder), [
  355. 'note' => 'Без количества',
  356. ]);
  357. $response->assertSessionHasErrors('quantity');
  358. }
  359. public function test_manager_can_ship_spare_part_order(): void
  360. {
  361. $sparePartOrder = SparePartOrder::factory()->inStock()->withQuantity(10)->create();
  362. $response = $this->actingAs($this->managerUser)
  363. ->post(route('spare_part_orders.ship', $sparePartOrder), [
  364. 'quantity' => 2,
  365. 'note' => 'Отгрузка менеджером',
  366. ]);
  367. $response->assertRedirect(route('spare_part_orders.show', $sparePartOrder));
  368. $response->assertSessionHas('success');
  369. }
  370. public function test_guest_cannot_ship_spare_part_order(): void
  371. {
  372. $sparePartOrder = SparePartOrder::factory()->create();
  373. $response = $this->post(route('spare_part_orders.ship', $sparePartOrder), [
  374. 'quantity' => 1,
  375. 'note' => 'Test',
  376. ]);
  377. $response->assertRedirect(route('login'));
  378. }
  379. // ==================== Correct (Inventory) ====================
  380. public function test_admin_can_correct_inventory(): void
  381. {
  382. $sparePartOrder = SparePartOrder::factory()->inStock()->withQuantity(10)->create();
  383. $response = $this->actingAs($this->adminUser)
  384. ->post(route('spare_part_orders.correct', $sparePartOrder), [
  385. 'new_quantity' => 8,
  386. 'reason' => 'Инвентаризация — обнаружена недостача',
  387. ]);
  388. $response->assertRedirect(route('spare_part_orders.show', $sparePartOrder));
  389. $response->assertSessionHas('success');
  390. $this->assertDatabaseHas('spare_part_orders', [
  391. 'id' => $sparePartOrder->id,
  392. 'available_qty' => 8,
  393. ]);
  394. }
  395. public function test_correct_fails_if_quantity_unchanged(): void
  396. {
  397. $sparePartOrder = SparePartOrder::factory()->inStock()->withQuantity(10)->create();
  398. $response = $this->actingAs($this->adminUser)
  399. ->post(route('spare_part_orders.correct', $sparePartOrder), [
  400. 'new_quantity' => 10,
  401. 'reason' => 'Без изменений',
  402. ]);
  403. $response->assertRedirect(route('spare_part_orders.show', $sparePartOrder));
  404. $response->assertSessionHas('error');
  405. }
  406. public function test_manager_cannot_correct_inventory(): void
  407. {
  408. $sparePartOrder = SparePartOrder::factory()->inStock()->create();
  409. $response = $this->actingAs($this->managerUser)
  410. ->post(route('spare_part_orders.correct', $sparePartOrder), [
  411. 'new_quantity' => 5,
  412. 'reason' => 'Test',
  413. ]);
  414. $response->assertStatus(403);
  415. }
  416. public function test_guest_cannot_correct_inventory(): void
  417. {
  418. $sparePartOrder = SparePartOrder::factory()->create();
  419. $response = $this->post(route('spare_part_orders.correct', $sparePartOrder), [
  420. 'new_quantity' => 5,
  421. 'reason' => 'Test',
  422. ]);
  423. $response->assertRedirect(route('login'));
  424. }
  425. }