SparePartIssueServiceTest.php 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727
  1. <?php
  2. namespace Tests\Unit\Services;
  3. use App\Models\InventoryMovement;
  4. use App\Models\Reclamation;
  5. use App\Models\Reservation;
  6. use App\Models\SparePart;
  7. use App\Models\SparePartOrder;
  8. use App\Models\User;
  9. use App\Services\IssueResult;
  10. use App\Services\SparePartIssueService;
  11. use Illuminate\Foundation\Testing\RefreshDatabase;
  12. use Tests\TestCase;
  13. class SparePartIssueServiceTest extends TestCase
  14. {
  15. use RefreshDatabase;
  16. protected $seed = true;
  17. private SparePartIssueService $service;
  18. protected function setUp(): void
  19. {
  20. parent::setUp();
  21. $this->service = new SparePartIssueService();
  22. }
  23. // ==================== issueReservation ====================
  24. public function test_issue_reservation_decreases_available_qty(): void
  25. {
  26. // Arrange
  27. $user = User::factory()->create();
  28. $this->actingAs($user);
  29. $sparePart = SparePart::factory()->create();
  30. $reclamation = Reclamation::factory()->create();
  31. $order = SparePartOrder::factory()
  32. ->inStock()
  33. ->withDocuments(false)
  34. ->withQuantity(10)
  35. ->forSparePart($sparePart)
  36. ->create();
  37. $reservation = Reservation::factory()
  38. ->active()
  39. ->withQuantity(3)
  40. ->withDocuments(false)
  41. ->fromOrder($order)
  42. ->forReclamation($reclamation)
  43. ->create();
  44. // Act
  45. $result = $this->service->issueReservation($reservation);
  46. // Assert
  47. $this->assertInstanceOf(IssueResult::class, $result);
  48. $this->assertEquals(3, $result->issued);
  49. $order->refresh();
  50. $this->assertEquals(7, $order->available_qty);
  51. }
  52. public function test_issue_reservation_marks_reservation_as_issued(): void
  53. {
  54. // Arrange
  55. $user = User::factory()->create();
  56. $this->actingAs($user);
  57. $sparePart = SparePart::factory()->create();
  58. $reclamation = Reclamation::factory()->create();
  59. $order = SparePartOrder::factory()
  60. ->inStock()
  61. ->withQuantity(10)
  62. ->forSparePart($sparePart)
  63. ->create();
  64. $reservation = Reservation::factory()
  65. ->active()
  66. ->withQuantity(5)
  67. ->fromOrder($order)
  68. ->forReclamation($reclamation)
  69. ->create();
  70. // Act
  71. $this->service->issueReservation($reservation);
  72. // Assert
  73. $reservation->refresh();
  74. $this->assertEquals(Reservation::STATUS_ISSUED, $reservation->status);
  75. $this->assertTrue($reservation->isIssued());
  76. }
  77. public function test_issue_reservation_creates_inventory_movement(): void
  78. {
  79. // Arrange
  80. $user = User::factory()->create();
  81. $this->actingAs($user);
  82. $sparePart = SparePart::factory()->create();
  83. $reclamation = Reclamation::factory()->create();
  84. $order = SparePartOrder::factory()
  85. ->inStock()
  86. ->withDocuments(true)
  87. ->withQuantity(10)
  88. ->forSparePart($sparePart)
  89. ->create();
  90. $reservation = Reservation::factory()
  91. ->active()
  92. ->withQuantity(4)
  93. ->withDocuments(true)
  94. ->fromOrder($order)
  95. ->forReclamation($reclamation)
  96. ->create();
  97. // Act
  98. $result = $this->service->issueReservation($reservation, 'Тестовое примечание');
  99. // Assert
  100. $this->assertDatabaseHas('inventory_movements', [
  101. 'spare_part_id' => $sparePart->id,
  102. 'spare_part_order_id' => $order->id,
  103. 'qty' => 4,
  104. 'movement_type' => InventoryMovement::TYPE_ISSUE,
  105. 'source_type' => InventoryMovement::SOURCE_RECLAMATION,
  106. 'source_id' => $reclamation->id,
  107. 'with_documents' => true,
  108. 'user_id' => $user->id,
  109. ]);
  110. $this->assertNotNull($result->movement);
  111. $this->assertEquals(InventoryMovement::TYPE_ISSUE, $result->movement->movement_type);
  112. }
  113. public function test_issue_reservation_changes_order_status_to_shipped_when_empty(): void
  114. {
  115. // Arrange
  116. $user = User::factory()->create();
  117. $this->actingAs($user);
  118. $sparePart = SparePart::factory()->create();
  119. $reclamation = Reclamation::factory()->create();
  120. $order = SparePartOrder::factory()
  121. ->inStock()
  122. ->withQuantity(5)
  123. ->forSparePart($sparePart)
  124. ->create();
  125. $reservation = Reservation::factory()
  126. ->active()
  127. ->withQuantity(5)
  128. ->fromOrder($order)
  129. ->forReclamation($reclamation)
  130. ->create();
  131. // Act
  132. $this->service->issueReservation($reservation);
  133. // Assert
  134. $order->refresh();
  135. $this->assertEquals(0, $order->available_qty);
  136. $this->assertEquals(SparePartOrder::STATUS_SHIPPED, $order->status);
  137. }
  138. public function test_issue_reservation_throws_exception_for_non_active_reservation(): void
  139. {
  140. // Arrange
  141. $sparePart = SparePart::factory()->create();
  142. $reclamation = Reclamation::factory()->create();
  143. $order = SparePartOrder::factory()
  144. ->inStock()
  145. ->forSparePart($sparePart)
  146. ->create();
  147. $reservation = Reservation::factory()
  148. ->cancelled()
  149. ->fromOrder($order)
  150. ->forReclamation($reclamation)
  151. ->create();
  152. // Assert
  153. $this->expectException(\InvalidArgumentException::class);
  154. $this->expectExceptionMessage('Резерв не активен');
  155. // Act
  156. $this->service->issueReservation($reservation);
  157. }
  158. public function test_issue_reservation_throws_exception_when_insufficient_stock(): void
  159. {
  160. // Arrange
  161. $user = User::factory()->create();
  162. $this->actingAs($user);
  163. $sparePart = SparePart::factory()->create();
  164. $reclamation = Reclamation::factory()->create();
  165. $order = SparePartOrder::factory()
  166. ->inStock()
  167. ->withQuantity(2)
  168. ->forSparePart($sparePart)
  169. ->create();
  170. // Create reservation with more qty than available
  171. $reservation = Reservation::factory()
  172. ->active()
  173. ->withQuantity(5)
  174. ->fromOrder($order)
  175. ->forReclamation($reclamation)
  176. ->create();
  177. // Manually reduce available_qty to simulate inconsistency
  178. $order->update(['available_qty' => 2]);
  179. // Assert
  180. $this->expectException(\RuntimeException::class);
  181. $this->expectExceptionMessage('Недостаточно товара в партии');
  182. // Act
  183. $this->service->issueReservation($reservation);
  184. }
  185. // ==================== issueForReclamation ====================
  186. public function test_issue_for_reclamation_issues_all_active_reservations(): void
  187. {
  188. // Arrange
  189. $user = User::factory()->create();
  190. $this->actingAs($user);
  191. $sparePart = SparePart::factory()->create();
  192. $reclamation = Reclamation::factory()->create();
  193. $order = SparePartOrder::factory()
  194. ->inStock()
  195. ->withQuantity(20)
  196. ->forSparePart($sparePart)
  197. ->create();
  198. Reservation::factory()
  199. ->active()
  200. ->withQuantity(3)
  201. ->fromOrder($order)
  202. ->forReclamation($reclamation)
  203. ->create();
  204. Reservation::factory()
  205. ->active()
  206. ->withQuantity(5)
  207. ->fromOrder($order)
  208. ->forReclamation($reclamation)
  209. ->create();
  210. // Act
  211. $results = $this->service->issueForReclamation($reclamation->id);
  212. // Assert
  213. $this->assertCount(2, $results);
  214. $this->assertEquals(3 + 5, array_sum(array_map(fn($r) => $r->issued, $results)));
  215. $order->refresh();
  216. $this->assertEquals(20 - 8, $order->available_qty);
  217. }
  218. public function test_issue_for_reclamation_filters_by_spare_part(): void
  219. {
  220. // Arrange
  221. $user = User::factory()->create();
  222. $this->actingAs($user);
  223. $sparePart1 = SparePart::factory()->create();
  224. $sparePart2 = SparePart::factory()->create();
  225. $reclamation = Reclamation::factory()->create();
  226. $order1 = SparePartOrder::factory()
  227. ->inStock()
  228. ->withQuantity(10)
  229. ->forSparePart($sparePart1)
  230. ->create();
  231. $order2 = SparePartOrder::factory()
  232. ->inStock()
  233. ->withQuantity(10)
  234. ->forSparePart($sparePart2)
  235. ->create();
  236. Reservation::factory()
  237. ->active()
  238. ->withQuantity(3)
  239. ->fromOrder($order1)
  240. ->forReclamation($reclamation)
  241. ->create();
  242. Reservation::factory()
  243. ->active()
  244. ->withQuantity(5)
  245. ->fromOrder($order2)
  246. ->forReclamation($reclamation)
  247. ->create();
  248. // Act - issue only for sparePart1
  249. $results = $this->service->issueForReclamation(
  250. $reclamation->id,
  251. sparePartId: $sparePart1->id
  252. );
  253. // Assert
  254. $this->assertCount(1, $results);
  255. $this->assertEquals(3, $results[0]->issued);
  256. // Check sparePart2 reservation is still active
  257. $this->assertDatabaseHas('reservations', [
  258. 'spare_part_id' => $sparePart2->id,
  259. 'reclamation_id' => $reclamation->id,
  260. 'status' => Reservation::STATUS_ACTIVE,
  261. ]);
  262. }
  263. public function test_issue_for_reclamation_filters_by_with_documents(): void
  264. {
  265. // Arrange
  266. $user = User::factory()->create();
  267. $this->actingAs($user);
  268. $sparePart = SparePart::factory()->create();
  269. $reclamation = Reclamation::factory()->create();
  270. $orderWithDocs = SparePartOrder::factory()
  271. ->inStock()
  272. ->withDocuments(true)
  273. ->withQuantity(10)
  274. ->forSparePart($sparePart)
  275. ->create();
  276. $orderWithoutDocs = SparePartOrder::factory()
  277. ->inStock()
  278. ->withDocuments(false)
  279. ->withQuantity(10)
  280. ->forSparePart($sparePart)
  281. ->create();
  282. Reservation::factory()
  283. ->active()
  284. ->withQuantity(3)
  285. ->withDocuments(true)
  286. ->fromOrder($orderWithDocs)
  287. ->forReclamation($reclamation)
  288. ->create();
  289. Reservation::factory()
  290. ->active()
  291. ->withQuantity(5)
  292. ->withDocuments(false)
  293. ->fromOrder($orderWithoutDocs)
  294. ->forReclamation($reclamation)
  295. ->create();
  296. // Act - issue only with documents
  297. $results = $this->service->issueForReclamation(
  298. $reclamation->id,
  299. withDocuments: true
  300. );
  301. // Assert
  302. $this->assertCount(1, $results);
  303. $this->assertEquals(3, $results[0]->issued);
  304. }
  305. public function test_issue_reservation_from_selected_order_cancels_old_reservation_and_issues_from_new_order(): void
  306. {
  307. $user = User::factory()->create();
  308. $this->actingAs($user);
  309. $sparePart = SparePart::factory()->create();
  310. $reclamation = Reclamation::factory()->create();
  311. $oldOrder = SparePartOrder::factory()->inStock()->withDocuments(false)->withQuantity(10)->forSparePart($sparePart)->create();
  312. $newOrder = SparePartOrder::factory()->inStock()->withDocuments(false)->withQuantity(8)->forSparePart($sparePart)->create();
  313. \App\Models\ReclamationSparePart::query()->create([
  314. 'reclamation_id' => $reclamation->id,
  315. 'spare_part_id' => $sparePart->id,
  316. 'quantity' => 5,
  317. 'with_documents' => false,
  318. 'status' => 'reserved',
  319. 'reserved_qty' => 5,
  320. 'issued_qty' => 0,
  321. ]);
  322. $reservation = Reservation::factory()
  323. ->active()
  324. ->withQuantity(5)
  325. ->withDocuments(false)
  326. ->fromOrder($oldOrder)
  327. ->forReclamation($reclamation)
  328. ->create();
  329. $result = $this->service->issueReservationFromSelectedOrder($reservation, $newOrder->id);
  330. $this->assertEquals(5, $result->issued);
  331. $this->assertDatabaseHas('reservations', [
  332. 'id' => $reservation->id,
  333. 'status' => Reservation::STATUS_CANCELLED,
  334. ]);
  335. $this->assertDatabaseHas('reservations', [
  336. 'spare_part_order_id' => $newOrder->id,
  337. 'reclamation_id' => $reclamation->id,
  338. 'reserved_qty' => 5,
  339. 'status' => Reservation::STATUS_ISSUED,
  340. ]);
  341. $this->assertDatabaseHas('spare_part_orders', [
  342. 'id' => $newOrder->id,
  343. 'available_qty' => 3,
  344. ]);
  345. }
  346. public function test_issue_reservation_from_selected_order_splits_row_when_selected_order_has_partial_quantity(): void
  347. {
  348. $user = User::factory()->create();
  349. $this->actingAs($user);
  350. $sparePart = SparePart::factory()->create();
  351. $reclamation = Reclamation::factory()->create();
  352. $oldOrder = SparePartOrder::factory()->inStock()->withDocuments(true)->withQuantity(10)->forSparePart($sparePart)->create();
  353. $newOrder = SparePartOrder::factory()->inStock()->withDocuments(true)->withQuantity(2)->forSparePart($sparePart)->create([
  354. 'available_qty' => 2,
  355. ]);
  356. \App\Models\ReclamationSparePart::query()->create([
  357. 'reclamation_id' => $reclamation->id,
  358. 'spare_part_id' => $sparePart->id,
  359. 'quantity' => 5,
  360. 'with_documents' => true,
  361. 'status' => 'reserved',
  362. 'reserved_qty' => 5,
  363. 'issued_qty' => 0,
  364. ]);
  365. $reservation = Reservation::factory()
  366. ->active()
  367. ->withQuantity(5)
  368. ->withDocuments(true)
  369. ->fromOrder($oldOrder)
  370. ->forReclamation($reclamation)
  371. ->create();
  372. $result = $this->service->issueReservationFromSelectedOrder($reservation, $newOrder->id);
  373. $this->assertEquals(2, $result->issued);
  374. $this->assertDatabaseHas('reclamation_spare_part', [
  375. 'reclamation_id' => $reclamation->id,
  376. 'spare_part_id' => $sparePart->id,
  377. 'quantity' => 2,
  378. 'with_documents' => true,
  379. 'status' => 'issued',
  380. 'issued_qty' => 2,
  381. ]);
  382. $this->assertDatabaseHas('reclamation_spare_part', [
  383. 'reclamation_id' => $reclamation->id,
  384. 'spare_part_id' => $sparePart->id,
  385. 'quantity' => 3,
  386. 'with_documents' => true,
  387. 'status' => 'pending',
  388. 'reserved_qty' => 0,
  389. 'issued_qty' => 0,
  390. ]);
  391. $this->assertDatabaseHas('shortages', [
  392. 'reclamation_id' => $reclamation->id,
  393. 'spare_part_id' => $sparePart->id,
  394. 'missing_qty' => 3,
  395. 'with_documents' => true,
  396. 'status' => \App\Models\Shortage::STATUS_OPEN,
  397. ]);
  398. }
  399. // ==================== directIssue ====================
  400. public function test_direct_issue_decreases_available_qty(): void
  401. {
  402. // Arrange
  403. $user = User::factory()->create();
  404. $this->actingAs($user);
  405. $sparePart = SparePart::factory()->create();
  406. $order = SparePartOrder::factory()
  407. ->inStock()
  408. ->withQuantity(10)
  409. ->forSparePart($sparePart)
  410. ->create();
  411. // Act
  412. $result = $this->service->directIssue($order, 4, 'Ручное списание');
  413. // Assert
  414. $this->assertEquals(4, $result->issued);
  415. $this->assertNull($result->reservation);
  416. $order->refresh();
  417. $this->assertEquals(6, $order->available_qty);
  418. }
  419. public function test_direct_issue_creates_manual_movement(): void
  420. {
  421. // Arrange
  422. $user = User::factory()->create();
  423. $this->actingAs($user);
  424. $sparePart = SparePart::factory()->create();
  425. $order = SparePartOrder::factory()
  426. ->inStock()
  427. ->withDocuments(true)
  428. ->withQuantity(10)
  429. ->forSparePart($sparePart)
  430. ->create();
  431. // Act
  432. $result = $this->service->directIssue($order, 3, 'Тестовое списание');
  433. // Assert
  434. $this->assertDatabaseHas('inventory_movements', [
  435. 'spare_part_id' => $sparePart->id,
  436. 'spare_part_order_id' => $order->id,
  437. 'qty' => 3,
  438. 'movement_type' => InventoryMovement::TYPE_ISSUE,
  439. 'source_type' => InventoryMovement::SOURCE_MANUAL,
  440. 'source_id' => null,
  441. 'with_documents' => true,
  442. 'note' => 'Тестовое списание',
  443. ]);
  444. }
  445. public function test_direct_issue_throws_exception_when_insufficient_stock(): void
  446. {
  447. // Arrange
  448. $sparePart = SparePart::factory()->create();
  449. $order = SparePartOrder::factory()
  450. ->inStock()
  451. ->withQuantity(5)
  452. ->forSparePart($sparePart)
  453. ->create();
  454. // Assert
  455. $this->expectException(\InvalidArgumentException::class);
  456. $this->expectExceptionMessage('Недостаточно товара в партии');
  457. // Act
  458. $this->service->directIssue($order, 10, 'Попытка списать больше');
  459. }
  460. public function test_direct_issue_changes_status_to_shipped_when_empty(): void
  461. {
  462. // Arrange
  463. $user = User::factory()->create();
  464. $this->actingAs($user);
  465. $sparePart = SparePart::factory()->create();
  466. $order = SparePartOrder::factory()
  467. ->inStock()
  468. ->withQuantity(5)
  469. ->forSparePart($sparePart)
  470. ->create();
  471. // Act
  472. $this->service->directIssue($order, 5, 'Полное списание');
  473. // Assert
  474. $order->refresh();
  475. $this->assertEquals(0, $order->available_qty);
  476. $this->assertEquals(SparePartOrder::STATUS_SHIPPED, $order->status);
  477. }
  478. // ==================== correctInventory ====================
  479. public function test_correct_inventory_increases_quantity(): void
  480. {
  481. // Arrange
  482. $user = User::factory()->create();
  483. $this->actingAs($user);
  484. $sparePart = SparePart::factory()->create();
  485. $order = SparePartOrder::factory()
  486. ->inStock()
  487. ->withQuantity(10)
  488. ->forSparePart($sparePart)
  489. ->create();
  490. // Act
  491. $movement = $this->service->correctInventory($order, 15, 'Найдено при инвентаризации');
  492. // Assert
  493. $order->refresh();
  494. $this->assertEquals(15, $order->available_qty);
  495. $this->assertEquals(InventoryMovement::TYPE_CORRECTION_PLUS, $movement->movement_type);
  496. $this->assertEquals(5, $movement->qty);
  497. }
  498. public function test_correct_inventory_decreases_quantity(): void
  499. {
  500. // Arrange
  501. $user = User::factory()->create();
  502. $this->actingAs($user);
  503. $sparePart = SparePart::factory()->create();
  504. $order = SparePartOrder::factory()
  505. ->inStock()
  506. ->withQuantity(10)
  507. ->forSparePart($sparePart)
  508. ->create();
  509. // Act
  510. $movement = $this->service->correctInventory($order, 7, 'Недостача');
  511. // Assert
  512. $order->refresh();
  513. $this->assertEquals(7, $order->available_qty);
  514. $this->assertEquals(InventoryMovement::TYPE_CORRECTION_MINUS, $movement->movement_type);
  515. $this->assertEquals(3, $movement->qty);
  516. }
  517. public function test_correct_inventory_creates_movement_with_inventory_source(): void
  518. {
  519. // Arrange
  520. $user = User::factory()->create();
  521. $this->actingAs($user);
  522. $sparePart = SparePart::factory()->create();
  523. $order = SparePartOrder::factory()
  524. ->inStock()
  525. ->withDocuments(false)
  526. ->withQuantity(10)
  527. ->forSparePart($sparePart)
  528. ->create();
  529. // Act
  530. $this->service->correctInventory($order, 12, 'Коррекция');
  531. // Assert
  532. $this->assertDatabaseHas('inventory_movements', [
  533. 'spare_part_id' => $sparePart->id,
  534. 'spare_part_order_id' => $order->id,
  535. 'qty' => 2,
  536. 'movement_type' => InventoryMovement::TYPE_CORRECTION_PLUS,
  537. 'source_type' => InventoryMovement::SOURCE_INVENTORY,
  538. 'with_documents' => false,
  539. 'note' => 'Коррекция',
  540. ]);
  541. }
  542. public function test_correct_inventory_changes_status_to_shipped_when_zero(): void
  543. {
  544. // Arrange
  545. $user = User::factory()->create();
  546. $this->actingAs($user);
  547. $sparePart = SparePart::factory()->create();
  548. $order = SparePartOrder::factory()
  549. ->inStock()
  550. ->withQuantity(5)
  551. ->forSparePart($sparePart)
  552. ->create();
  553. // Act
  554. $this->service->correctInventory($order, 0, 'Списано всё');
  555. // Assert
  556. $order->refresh();
  557. $this->assertEquals(0, $order->available_qty);
  558. $this->assertEquals(SparePartOrder::STATUS_SHIPPED, $order->status);
  559. }
  560. public function test_correct_inventory_restores_status_to_in_stock_from_shipped(): void
  561. {
  562. // Arrange
  563. $user = User::factory()->create();
  564. $this->actingAs($user);
  565. $sparePart = SparePart::factory()->create();
  566. $order = SparePartOrder::factory()
  567. ->shipped()
  568. ->forSparePart($sparePart)
  569. ->create();
  570. // Act
  571. $this->service->correctInventory($order, 5, 'Найдено на складе');
  572. // Assert
  573. $order->refresh();
  574. $this->assertEquals(5, $order->available_qty);
  575. $this->assertEquals(SparePartOrder::STATUS_IN_STOCK, $order->status);
  576. }
  577. public function test_correct_inventory_throws_exception_for_negative_quantity(): void
  578. {
  579. // Arrange
  580. $sparePart = SparePart::factory()->create();
  581. $order = SparePartOrder::factory()
  582. ->inStock()
  583. ->withQuantity(5)
  584. ->forSparePart($sparePart)
  585. ->create();
  586. // Assert
  587. $this->expectException(\InvalidArgumentException::class);
  588. $this->expectExceptionMessage('Остаток не может быть отрицательным');
  589. // Act
  590. $this->service->correctInventory($order, -1, 'Отрицательный остаток');
  591. }
  592. public function test_correct_inventory_throws_exception_when_quantity_unchanged(): void
  593. {
  594. // Arrange
  595. $sparePart = SparePart::factory()->create();
  596. $order = SparePartOrder::factory()
  597. ->inStock()
  598. ->withQuantity(10)
  599. ->forSparePart($sparePart)
  600. ->create();
  601. // Assert
  602. $this->expectException(\InvalidArgumentException::class);
  603. $this->expectExceptionMessage('Количество не изменилось');
  604. // Act
  605. $this->service->correctInventory($order, 10, 'Без изменений');
  606. }
  607. }