InventoryMovementTest.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350
  1. <?php
  2. namespace Tests\Unit\Models;
  3. use App\Models\InventoryMovement;
  4. use App\Models\Reclamation;
  5. use App\Models\Shortage;
  6. use App\Models\SparePart;
  7. use App\Models\SparePartOrder;
  8. use App\Models\User;
  9. use Illuminate\Foundation\Testing\RefreshDatabase;
  10. use Tests\TestCase;
  11. class InventoryMovementTest extends TestCase
  12. {
  13. use RefreshDatabase;
  14. protected $seed = true;
  15. // ==================== Type Constants ====================
  16. public function test_type_constants_are_defined(): void
  17. {
  18. $this->assertEquals('receipt', InventoryMovement::TYPE_RECEIPT);
  19. $this->assertEquals('reserve', InventoryMovement::TYPE_RESERVE);
  20. $this->assertEquals('issue', InventoryMovement::TYPE_ISSUE);
  21. $this->assertEquals('reserve_cancel', InventoryMovement::TYPE_RESERVE_CANCEL);
  22. $this->assertEquals('correction_plus', InventoryMovement::TYPE_CORRECTION_PLUS);
  23. $this->assertEquals('correction_minus', InventoryMovement::TYPE_CORRECTION_MINUS);
  24. }
  25. public function test_type_names_are_defined(): void
  26. {
  27. $this->assertEquals('Поступление', InventoryMovement::TYPE_NAMES[InventoryMovement::TYPE_RECEIPT]);
  28. $this->assertEquals('Резервирование', InventoryMovement::TYPE_NAMES[InventoryMovement::TYPE_RESERVE]);
  29. $this->assertEquals('Списание', InventoryMovement::TYPE_NAMES[InventoryMovement::TYPE_ISSUE]);
  30. $this->assertEquals('Отмена резерва', InventoryMovement::TYPE_NAMES[InventoryMovement::TYPE_RESERVE_CANCEL]);
  31. $this->assertEquals('Коррекция (+)', InventoryMovement::TYPE_NAMES[InventoryMovement::TYPE_CORRECTION_PLUS]);
  32. $this->assertEquals('Коррекция (-)', InventoryMovement::TYPE_NAMES[InventoryMovement::TYPE_CORRECTION_MINUS]);
  33. }
  34. // ==================== Source Type Constants ====================
  35. public function test_source_type_constants_are_defined(): void
  36. {
  37. $this->assertEquals('order', InventoryMovement::SOURCE_ORDER);
  38. $this->assertEquals('reclamation', InventoryMovement::SOURCE_RECLAMATION);
  39. $this->assertEquals('manual', InventoryMovement::SOURCE_MANUAL);
  40. $this->assertEquals('shortage_fulfillment', InventoryMovement::SOURCE_SHORTAGE_FULFILLMENT);
  41. $this->assertEquals('inventory', InventoryMovement::SOURCE_INVENTORY);
  42. }
  43. // ==================== Accessors ====================
  44. public function test_type_name_attribute_returns_readable_name(): void
  45. {
  46. // Arrange
  47. $movement = InventoryMovement::factory()->receipt()->create();
  48. // Assert
  49. $this->assertEquals('Поступление', $movement->type_name);
  50. }
  51. public function test_type_name_attribute_returns_raw_for_unknown_type(): void
  52. {
  53. // Arrange
  54. $movement = new InventoryMovement(['movement_type' => 'unknown']);
  55. // Assert
  56. $this->assertEquals('unknown', $movement->type_name);
  57. }
  58. public function test_source_attribute_returns_reclamation(): void
  59. {
  60. // Arrange
  61. $reclamation = Reclamation::factory()->create();
  62. $movement = InventoryMovement::factory()
  63. ->forReclamation($reclamation->id)
  64. ->create();
  65. // Act
  66. $source = $movement->source;
  67. // Assert
  68. $this->assertInstanceOf(Reclamation::class, $source);
  69. $this->assertEquals($reclamation->id, $source->id);
  70. }
  71. public function test_source_attribute_returns_null_for_manual(): void
  72. {
  73. // Arrange
  74. $movement = InventoryMovement::factory()->create([
  75. 'source_type' => InventoryMovement::SOURCE_MANUAL,
  76. 'source_id' => null,
  77. ]);
  78. // Assert
  79. $this->assertNull($movement->source);
  80. }
  81. public function test_source_attribute_returns_null_when_no_source_id(): void
  82. {
  83. // Arrange
  84. $movement = InventoryMovement::factory()->create([
  85. 'source_type' => InventoryMovement::SOURCE_RECLAMATION,
  86. 'source_id' => null,
  87. ]);
  88. // Assert
  89. $this->assertNull($movement->source);
  90. }
  91. // ==================== Relationships ====================
  92. public function test_movement_belongs_to_spare_part_order(): void
  93. {
  94. // Arrange
  95. $order = SparePartOrder::factory()->create();
  96. $movement = InventoryMovement::factory()
  97. ->fromOrder($order)
  98. ->create();
  99. // Assert
  100. $this->assertInstanceOf(SparePartOrder::class, $movement->sparePartOrder);
  101. $this->assertEquals($order->id, $movement->sparePartOrder->id);
  102. }
  103. public function test_movement_belongs_to_spare_part(): void
  104. {
  105. // Arrange
  106. $sparePart = SparePart::factory()->create();
  107. $movement = InventoryMovement::factory()->create([
  108. 'spare_part_id' => $sparePart->id,
  109. ]);
  110. // Assert
  111. $this->assertInstanceOf(SparePart::class, $movement->sparePart);
  112. $this->assertEquals($sparePart->id, $movement->sparePart->id);
  113. }
  114. public function test_movement_belongs_to_user(): void
  115. {
  116. // Arrange
  117. $user = User::factory()->create();
  118. $movement = InventoryMovement::factory()->create([
  119. 'user_id' => $user->id,
  120. ]);
  121. // Assert
  122. $this->assertInstanceOf(User::class, $movement->user);
  123. $this->assertEquals($user->id, $movement->user->id);
  124. }
  125. // ==================== Scopes ====================
  126. public function test_scope_of_type_filters_by_movement_type(): void
  127. {
  128. // Arrange
  129. InventoryMovement::factory()->receipt()->count(2)->create();
  130. InventoryMovement::factory()->issue()->create();
  131. InventoryMovement::factory()->reserve()->create();
  132. // Act
  133. $receipts = InventoryMovement::ofType(InventoryMovement::TYPE_RECEIPT)->get();
  134. $issues = InventoryMovement::ofType(InventoryMovement::TYPE_ISSUE)->get();
  135. // Assert
  136. $this->assertCount(2, $receipts);
  137. $this->assertCount(1, $issues);
  138. }
  139. public function test_scope_for_spare_part_filters_by_spare_part_id(): void
  140. {
  141. // Arrange
  142. $sparePart1 = SparePart::factory()->create();
  143. $sparePart2 = SparePart::factory()->create();
  144. InventoryMovement::factory()->count(3)->create([
  145. 'spare_part_id' => $sparePart1->id,
  146. ]);
  147. InventoryMovement::factory()->create([
  148. 'spare_part_id' => $sparePart2->id,
  149. ]);
  150. // Act
  151. $movements = InventoryMovement::forSparePart($sparePart1->id)->get();
  152. // Assert
  153. $this->assertCount(3, $movements);
  154. }
  155. public function test_scope_with_documents_filters_by_with_documents(): void
  156. {
  157. // Arrange
  158. InventoryMovement::factory()->count(2)->create([
  159. 'with_documents' => true,
  160. ]);
  161. InventoryMovement::factory()->create([
  162. 'with_documents' => false,
  163. ]);
  164. // Act
  165. $withDocs = InventoryMovement::withDocuments(true)->get();
  166. $withoutDocs = InventoryMovement::withDocuments(false)->get();
  167. // Assert
  168. $this->assertCount(2, $withDocs);
  169. $this->assertCount(1, $withoutDocs);
  170. }
  171. public function test_scope_increasing_returns_correct_types(): void
  172. {
  173. // Arrange
  174. InventoryMovement::factory()->receipt()->create();
  175. InventoryMovement::factory()->reserveCancel()->create();
  176. InventoryMovement::factory()->create([
  177. 'movement_type' => InventoryMovement::TYPE_CORRECTION_PLUS,
  178. ]);
  179. InventoryMovement::factory()->issue()->create();
  180. InventoryMovement::factory()->reserve()->create();
  181. // Act
  182. $increasing = InventoryMovement::increasing()->get();
  183. // Assert
  184. $this->assertCount(3, $increasing);
  185. $types = $increasing->pluck('movement_type')->toArray();
  186. $this->assertContains(InventoryMovement::TYPE_RECEIPT, $types);
  187. $this->assertContains(InventoryMovement::TYPE_RESERVE_CANCEL, $types);
  188. $this->assertContains(InventoryMovement::TYPE_CORRECTION_PLUS, $types);
  189. }
  190. public function test_scope_decreasing_returns_correct_types(): void
  191. {
  192. // Arrange
  193. InventoryMovement::factory()->receipt()->create();
  194. InventoryMovement::factory()->reserve()->create();
  195. InventoryMovement::factory()->issue()->create();
  196. InventoryMovement::factory()->create([
  197. 'movement_type' => InventoryMovement::TYPE_CORRECTION_MINUS,
  198. ]);
  199. // Act
  200. $decreasing = InventoryMovement::decreasing()->get();
  201. // Assert
  202. $this->assertCount(3, $decreasing);
  203. $types = $decreasing->pluck('movement_type')->toArray();
  204. $this->assertContains(InventoryMovement::TYPE_RESERVE, $types);
  205. $this->assertContains(InventoryMovement::TYPE_ISSUE, $types);
  206. $this->assertContains(InventoryMovement::TYPE_CORRECTION_MINUS, $types);
  207. }
  208. // ==================== Methods ====================
  209. public function test_is_correction_returns_true_for_correction_types(): void
  210. {
  211. // Arrange
  212. $correctionPlus = new InventoryMovement([
  213. 'movement_type' => InventoryMovement::TYPE_CORRECTION_PLUS,
  214. ]);
  215. $correctionMinus = new InventoryMovement([
  216. 'movement_type' => InventoryMovement::TYPE_CORRECTION_MINUS,
  217. ]);
  218. $receipt = new InventoryMovement([
  219. 'movement_type' => InventoryMovement::TYPE_RECEIPT,
  220. ]);
  221. // Assert
  222. $this->assertTrue($correctionPlus->isCorrection());
  223. $this->assertTrue($correctionMinus->isCorrection());
  224. $this->assertFalse($receipt->isCorrection());
  225. }
  226. // ==================== Casts ====================
  227. public function test_qty_is_cast_to_integer(): void
  228. {
  229. // Arrange
  230. $movement = InventoryMovement::factory()->create(['qty' => 10]);
  231. // Assert
  232. $this->assertIsInt($movement->qty);
  233. }
  234. public function test_with_documents_is_cast_to_boolean(): void
  235. {
  236. // Arrange
  237. $movement = InventoryMovement::factory()->create(['with_documents' => true]);
  238. // Assert
  239. $this->assertIsBool($movement->with_documents);
  240. }
  241. public function test_source_id_is_cast_to_integer(): void
  242. {
  243. // Arrange
  244. $reclamation = Reclamation::factory()->create();
  245. $movement = InventoryMovement::factory()
  246. ->forReclamation($reclamation->id)
  247. ->create();
  248. // Assert
  249. $this->assertIsInt($movement->source_id);
  250. }
  251. // ==================== Factory ====================
  252. public function test_factory_creates_valid_movement(): void
  253. {
  254. // Act
  255. $movement = InventoryMovement::factory()->create();
  256. // Assert
  257. $this->assertDatabaseHas('inventory_movements', [
  258. 'id' => $movement->id,
  259. ]);
  260. $this->assertNotNull($movement->spare_part_id);
  261. $this->assertNotNull($movement->spare_part_order_id);
  262. $this->assertNotNull($movement->qty);
  263. $this->assertNotNull($movement->movement_type);
  264. }
  265. public function test_factory_receipt_state(): void
  266. {
  267. // Act
  268. $movement = InventoryMovement::factory()->receipt()->create();
  269. // Assert
  270. $this->assertEquals(InventoryMovement::TYPE_RECEIPT, $movement->movement_type);
  271. }
  272. public function test_factory_reserve_state(): void
  273. {
  274. // Act
  275. $movement = InventoryMovement::factory()->reserve()->create();
  276. // Assert
  277. $this->assertEquals(InventoryMovement::TYPE_RESERVE, $movement->movement_type);
  278. }
  279. public function test_factory_issue_state(): void
  280. {
  281. // Act
  282. $movement = InventoryMovement::factory()->issue()->create();
  283. // Assert
  284. $this->assertEquals(InventoryMovement::TYPE_ISSUE, $movement->movement_type);
  285. }
  286. }